]> Softwares of Agnibho - medscript.git/blob - window.py
28186ad56ee4d432a04ae38c9c5860a7a2674852
[medscript.git] / window.py
1 # MedScript
2 # Copyright (C) 2023 Dr. Agnibho Mondal
3 # This file is part of MedScript.
4 # MedScript is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
5 # MedScript is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
6 # You should have received a copy of the GNU General Public License along with MedScript. If not, see <https://www.gnu.org/licenses/>.
7
8 import logging, os, sys, datetime, dateutil.parser, shutil, json, threading
9 from PyQt6.QtCore import Qt, QDateTime, QDate, QSize, pyqtSignal
10 from PyQt6.QtWidgets import QWidget, QMainWindow, QMessageBox, QLabel, QPushButton, QLineEdit, QTextEdit, QDateTimeEdit, QDateEdit, QCalendarWidget, QListWidget, QComboBox, QCheckBox, QRadioButton, QButtonGroup, QVBoxLayout, QHBoxLayout, QFormLayout, QToolBar, QTabWidget, QStatusBar, QFileDialog, QInputDialog, QCompleter, QSizePolicy
11 from PyQt6.QtGui import QAction, QIcon
12 from pathlib import Path
13 from hashlib import md5
14 from urllib import request
15 from packaging import version
16 from functools import partial
17
18 from config import config, info, real_dir
19 from prescription import Prescription, Prescriber
20 from renderer import Renderer
21 from filehandler import FileHandler
22 from renderbox import RenderBox, UnrenderBox
23 from setting import EditConfiguration, EditPrescriber, SelectPrescriber
24 from editpreset import EditPreset
25 from viewbox import ViewBox
26 from preset import Preset
27 from tabular import Tabular
28 from index import Index
29 from customform import CustomForm
30 from plugin import Plugin
31 from installer import Installer
32
33 class MainWindow(QMainWindow):
34
35 signal_view=pyqtSignal(str)
36 signal_update=pyqtSignal(str)
37
38 current_file=FileHandler()
39 prescription=Prescription()
40 prescriber=Prescriber()
41 renderer=Renderer()
42 plugin=Plugin()
43 save_state=md5("".encode()).hexdigest()
44 unchanged_state=False
45
46 def cmd_new(self):
47 if(self.confirm_close()):
48 self.new_doc()
49
50 def cmd_open(self, file=None):
51 if(self.confirm_close()):
52 try:
53 self.current_file.reset()
54 if(file):
55 self.current_file.set_file(file)
56 else:
57 self.current_file.set_file(QFileDialog.getOpenFileName(self, "Open File", config["document_directory"], "Prescriptions (*.mpaz);; All Files (*)")[0])
58 self.current_file.open()
59 self.prescription.read_from(os.path.join(self.current_file.directory.name,"prescription.json"))
60 self.plugin.open(self.prescription)
61 self.load_interface_from_instance()
62
63 self.save_state=md5(self.prescription.get_json().encode()).hexdigest()
64 self.load_attachment(self.current_file.list())
65 self.unchanged_state=True
66 except Exception as e:
67 QMessageBox.warning(self,"Open failed", "Failed to open file.")
68 logging.warning(e)
69
70 def cmd_copy(self, data):
71 self.cmd_new()
72 self.prescription.set_data_from_json(data)
73 self.prescription.id=""
74 self.prescription.date=None
75 self.load_interface_from_instance()
76 self.refresh()
77
78 def cmd_save(self, save_as=False):
79 self.update_instance()
80 self.plugin.save(self.prescription)
81 if(self.input_template.currentText()!="<unchanged>"):
82 change_template=True
83 template=self.input_template.currentText()
84 else:
85 change_template=False
86 self.load_interface_from_instance()
87 suggest=self.prescription.id if(self.prescription.id) else self.prescription.name
88 suggest=os.path.abspath(os.path.join(config["document_directory"], suggest)+".mpaz")
89 if(save_as or not self.unchanged_state or QMessageBox.StandardButton.Yes==QMessageBox.question(self,"Confirm change", "Modify the original file?")):
90 try:
91 if not os.path.exists(self.current_file.file):
92 filename=QFileDialog.getSaveFileName(self, "Save File", suggest, "Prescriptions (*.mpaz);; All Files (*)")[0]
93 if(not filename.endswith(".mpaz")):
94 filename=filename+".mpaz"
95 self.current_file.set_file(filename)
96 for i in range(self.input_attachment.count()):
97 self.current_file.copy(self.input_attachment.item(i).text())
98 self.prescription.write_to(os.path.join(self.current_file.directory.name, "prescription.json"))
99 if change_template:
100 config["template"]=os.path.join(config["template_directory"], template)
101 self.current_file.save(change_template=change_template)
102 self.unchanged_state=False
103 self.load_interface_from_instance()
104 self.save_state=md5(self.prescription.get_json().encode()).hexdigest()
105 except Exception as e:
106 QMessageBox.warning(self,"Save failed", "Failed to save file.")
107 logging.warning(e)
108
109 def cmd_save_as(self):
110 suggest=self.prescription.id if(self.prescription.id) else self.prescription.name
111 suggest=os.path.abspath(os.path.join(config["document_directory"], suggest)+".mpaz")
112 self.current_file.set_file(QFileDialog.getSaveFileName(self, "Save File", suggest, "Prescriptions (*.mpaz);; All Files (*)")[0])
113 Path(self.current_file.file).touch()
114 self.cmd_save(save_as=True)
115
116 def cmd_refresh(self):
117 self.refresh()
118
119 def cmd_quit(self):
120 if(self.confirm_close()):
121 sys.exit()
122
123 def cmd_unrender(self):
124 self.unrenderbox.show(self.prescription).exec()
125
126 def cmd_render(self):
127 self.refresh()
128 if(self.save_state==md5(self.prescription.get_json().encode()).hexdigest()):
129 try:
130 target=self.renderer.render(self.current_file.directory.name)
131 self.signal_view.emit(target)
132 self.renderbox.showMaximized()
133 except FileNotFoundError as e:
134 logging.warning(e)
135 QMessageBox.information(self, "Save first", "Please save the file before rendering.")
136
137 else:
138 QMessageBox.information(self, "Save first", "Please save the file before rendering.")
139
140 def cmd_sign(self):
141 self.refresh()
142 if(self.save_state==md5(self.prescription.get_json().encode()).hexdigest()):
143 ok=True #password, ok=QInputDialog.getText(self, "Enter password", "Private key password", QLineEdit.EchoMode.Password)
144 if(ok):
145 try:
146 try:
147 self.current_file.sign()
148 #self.current_file.sign(password)
149 self.cmd_save()
150 except FileNotFoundError as e:
151 logging.warning(e)
152 QMessageBox.information(self, "Save first", "Please save the file before signing.")
153 except TypeError as e:
154 logging.warning(e)
155 QMessageBox.information(self, "Configure", "Please add valid key and certificate to the config file.")
156 except EVPError as e:
157 logging.warning(e)
158 QMessageBox.information(self, "Check password", "Failed to load key. Please check if password is correct.")
159 except BIOError as e:
160 logging.warning(e)
161 QMessageBox.information(self, "Not found", "Certifcate and/or key not found.")
162 except SMIME_Error as e:
163 logging.warning(e)
164 QMessageBox.information(self, "Failed to load", "Failed to sign. Please check if certificate and key match.")
165 except Exception as e:
166 logging.warning(e)
167 QMessageBox.information(self, "Failed", "Failed to sign.")
168 except Exception as e:
169 logging.warning(e)
170 else:
171 QMessageBox.information(self, "Save first", "Please save the file before signing.")
172
173 def cmd_unsign(self):
174 self.current_file.delete_sign()
175 self.cmd_save()
176 self.refresh()
177
178 def cmd_verify(self):
179 try:
180 result=self.current_file.verify()
181 if result is False:
182 QMessageBox.critical(self, "Verification failed", "Signature is invalid.")
183 elif result is None:
184 QMessageBox.warning(self, "No Siganture", "No signature was found.")
185 else:
186 logging.info(result)
187 QMessageBox.information(self, "Valid signature", "Valid signature found with the following information:\n"+result)
188 except FileNotFoundError as e:
189 logging.warning(e)
190 QMessageBox.warning(self, "No Siganture", "No signature was found.")
191 except Exception as e:
192 logging.warning(e)
193 QMessageBox.warning(self, "Failed", "Failed to verify.")
194
195 def cmd_tabular(self):
196 try:
197 filename=QFileDialog.getSaveFileName(self, "Export CSV File", os.path.join(config["data_directory"], "data.csv"), "CSV (*.csv);; All Files (*)")[0]
198 Tabular.export(filename)
199 QMessageBox.information(self, "Data Exported", "Data exported to."+filename)
200 except Exception as e:
201 logging.warning(e)
202 QMessageBox.critical(self, "Export failed", "Failed to export the data.")
203
204 def cmd_index(self):
205 self.index.refresh()
206 self.index.show()
207
208 def cmd_configuration(self):
209 self.edit_configuration.exec()
210
211 def cmd_prescriber(self, file=None):
212 self.edit_prescriber.load(file)
213 self.edit_prescriber.exec()
214
215 def cmd_switch(self):
216 try:
217 self.select_prescriber.load()
218 self.select_prescriber.exec()
219 except FileNotFoundError as e:
220 logging.warning(e)
221
222 def cmd_preset(self):
223 self.edit_preset.show()
224
225 def cmd_installer(self):
226 self.installer.show()
227
228 def cmd_about(self):
229 year=datetime.datetime.now().year
230 if(year>2023):
231 copy="2023"+"-"+str(year)
232 else:
233 copy="2023"
234 txt="<h1>MedScript</h1>"
235 txt=txt+"<p>Version "+info["version"]+"</p>"
236 txt=txt+"<p>The Prescription Writing Software</p>"
237 txt=txt+"<p><a href='"+info["url"]+"'>Website</a></p>"
238 txt=txt+"<p>Copyright © "+copy+" Dr. Agnibho Mondal</p>"
239 QMessageBox.about(self, "MedScript", txt)
240
241 def cmd_help(self):
242 self.viewbox.md(os.path.join(real_dir, "README"))
243 self.viewbox.show()
244
245 def cmd_update(self, silent=False):
246 try:
247 logging.info("Current version "+info["version"])
248 with request.urlopen(info["url"]+"/info.json") as response:
249 latest=json.loads(response.read().decode())
250 logging.info("Latest version "+latest["version"])
251 if(version.parse(info["version"]) < version.parse(latest["version"])):
252 self.signal_update.emit("New version <strong>"+latest["version"]+"</strong> available.<br>Visit <a href='"+latest["url"]+"'>"+latest["url"]+"</a> to get the latest version.")
253 elif(not silent):
254 self.signal_update.emit("No update available. You are using version "+info["version"]+".")
255 except Exception as e:
256 self.signal_update.emit("Failed to check available update.")
257 logging.warning(e)
258
259 def show_update(self, message):
260 QMessageBox.information(self, "Check update", message)
261
262 def insert_preset_note(self):
263 try:
264 self.input_note.insertPlainText(self.preset_note.data[self.input_note_preset.currentText()])
265 except KeyError:
266 self.input_note.insertPlainText(self.input_note_preset.currentText())
267 finally:
268 self.input_note_preset.setCurrentIndex(-1)
269 if config["preset_newline"]:
270 self.input_note.insertPlainText("\n")
271
272 def insert_preset_report(self):
273 try:
274 self.input_report.insertPlainText(self.preset_report.data[self.input_report_preset.currentText()])
275 except KeyError:
276 self.input_report.insertPlainText(self.input_report_preset.currentText())
277 finally:
278 self.input_report_preset.setCurrentIndex(-1)
279 if config["preset_newline"]:
280 self.input_report.insertPlainText("\n")
281
282 def insert_preset_advice(self):
283 try:
284 self.input_advice.insertPlainText(self.preset_advice.data[self.input_advice_preset.currentText()])
285 except KeyError:
286 self.input_advice.insertPlainText(self.input_advice_preset.currentText())
287 finally:
288 self.input_advice_preset.setCurrentIndex(-1)
289 if config["preset_newline"]:
290 self.input_advice.insertPlainText("\n")
291
292 def insert_preset_investigation(self):
293 try:
294 self.input_investigation.insertPlainText(self.preset_investigation.data[self.input_investigation_preset.currentText()])
295 except KeyError:
296 self.input_investigation.insertPlainText(self.input_investigation_preset.currentText())
297 finally:
298 self.input_investigation_preset.setCurrentIndex(-1)
299 if config["preset_newline"]:
300 self.input_investigation.insertPlainText("\n")
301
302 def insert_preset_medication(self):
303 try:
304 self.input_medication.insertPlainText(self.preset_medication.data[self.input_medication_preset.currentText()])
305 except KeyError:
306 self.input_medication.insertPlainText(self.input_medication_preset.currentText())
307 finally:
308 self.input_medication_preset.setCurrentIndex(-1)
309 if config["preset_newline"]:
310 self.input_medication.insertPlainText("\n")
311
312 def insert_preset_additional(self):
313 try:
314 self.input_additional.insertPlainText(self.preset_additional.data[self.input_additional_preset.currentText()])
315 except KeyError:
316 self.input_additional.insertPlainText(self.input_additional_preset.currentText())
317 finally:
318 self.input_additional_preset.setCurrentIndex(-1)
319 if config["preset_newline"]:
320 self.input_additional.insertPlainText("\n")
321
322 def insert_preset_certificate(self):
323 try:
324 self.input_certificate.insertPlainText(self.preset_certificate.data[self.input_certificate_preset.currentText()])
325 except KeyError:
326 self.input_certificate.insertPlainText(self.input_certificate_preset.currentText())
327 finally:
328 self.input_certificate_preset.setCurrentIndex(-1)
329 if config["preset_newline"]:
330 self.input_certificate.insertPlainText("\n")
331
332 def load_interface(self, file="", date=None, id="", pid="", name="", dob="", age="", sex="", address="", contact="", extra="", mode="", daw="", diagnosis="", note="", report="", advice="", investigation="", medication="", additional="", certificate="", custom=None):
333 try:
334 file_msg=self.current_file.file if self.current_file.file else "New file"
335 sign_msg="(signed)" if config["smime"] and self.current_file.is_signed() else ""
336 self.statusbar.showMessage(file_msg+" "+sign_msg)
337 if date is None:
338 d=QDateTime.currentDateTime()
339 else:
340 try:
341 pdate=dateutil.parser.parse(date)
342 d=QDateTime.fromString(pdate.strftime("%Y-%m-%d %H:%M:%S"), "yyyy-MM-dd hh:mm:ss")
343 except Exception as e:
344 QMessageBox.warning(self,"Failed to load", str(e))
345 logging.warning(e)
346 self.input_date.setDateTime(d)
347 self.input_id.setText(id)
348 self.input_pid.setText(pid)
349 self.input_name.setText(name)
350 try:
351 pdate=dateutil.parser.parse(dob)
352 d=QDate.fromString(pdate.strftime("%Y-%m-%d"), "yyyy-MM-dd")
353 self.input_dob.setDate(d)
354 except Exception as e:
355 pass
356 self.input_age.setText(age)
357 if(age):
358 self.btnAge.click()
359 else:
360 self.btnDob.click()
361 self.input_sex.setCurrentText(sex)
362 self.input_address.setText(address)
363 self.input_contact.setText(contact)
364 self.input_extra.setText(extra)
365 self.input_mode.setCurrentText(mode)
366 self.input_daw.setChecked(bool(daw))
367 self.input_diagnosis.setText(diagnosis)
368 self.input_note.setText(note)
369 self.input_report.setText(report)
370 self.input_advice.setText(advice)
371 self.input_investigation.setText(investigation)
372 self.input_medication.setText(medication)
373 self.input_additional.setText(additional)
374 self.input_certificate.setText(certificate)
375 self.input_custom.setData(custom)
376 self.label_prescriber.setText(self.prescriber.name)
377 except Exception as e:
378 QMessageBox.warning(self,"Failed to load", "Failed to load the data into the application.")
379 logging.warning(e)
380
381 def load_interface_from_instance(self):
382 if(self.current_file.has_template()):
383 if(self.input_template.findText("<unchanged>")==-1):
384 self.input_template.addItem("<unchanged>")
385 self.input_template.setCurrentText("<unchanged>")
386 else:
387 self.input_template.removeItem(self.input_template.findText("<unchanged>"))
388 self.load_interface(
389 file=self.prescription.file,
390 date=self.prescription.date,
391 id=self.prescription.id,
392 pid=self.prescription.pid,
393 name=self.prescription.name,
394 dob=self.prescription.dob,
395 age=self.prescription.age,
396 sex=self.prescription.sex,
397 address=self.prescription.address,
398 contact=self.prescription.contact,
399 extra=self.prescription.extra,
400 mode=self.prescription.mode,
401 daw=self.prescription.daw,
402 diagnosis=self.prescription.diagnosis,
403 note=self.prescription.note,
404 report=self.prescription.report,
405 advice=self.prescription.advice,
406 investigation=self.prescription.investigation,
407 medication=self.prescription.medication,
408 additional=self.prescription.additional,
409 certificate=self.prescription.certificate,
410 custom=self.prescription.custom
411 )
412
413 def update_instance(self):
414 try:
415 self.prescription.set_data(
416 date=self.input_date.dateTime().toString("yyyy-MM-dd hh:mm:ss"),
417 id=self.input_id.text(),
418 pid=self.input_pid.text(),
419 name=self.input_name.text(),
420 dob=self.input_dob.text(),
421 age=self.input_age.text(),
422 sex=self.input_sex.currentText(),
423 address=self.input_address.text(),
424 contact=self.input_contact.text(),
425 extra=self.input_extra.toPlainText(),
426 mode=self.input_mode.currentText(),
427 daw=self.input_daw.isChecked(),
428 diagnosis=self.input_diagnosis.text(),
429 note=self.input_note.toPlainText(),
430 report=self.input_report.toPlainText(),
431 advice=self.input_advice.toPlainText(),
432 investigation=self.input_investigation.toPlainText(),
433 medication=self.input_medication.toPlainText(),
434 additional=self.input_additional.toPlainText(),
435 certificate=self.input_certificate.toPlainText(),
436 custom=self.input_custom.getData()
437 )
438 except Exception as e:
439 QMessageBox.critical(self,"Failed", "Critical failure happned. Please check console for more info.")
440 logging.error(e)
441
442 def new_doc(self):
443 self.current_file.reset()
444 self.prescription.set_data()
445 self.input_attachment.clear()
446 self.load_interface()
447 self.update_instance()
448 self.plugin.new(self.prescription)
449 self.load_interface_from_instance()
450 if(config["age_default"]):
451 self.btnAge.click()
452 self.update_instance()
453 self.save_state=md5(self.prescription.get_json().encode()).hexdigest()
454
455 def change_prescriber(self, file):
456 self.prescription.reload_prescriber(file)
457 self.prescriber.read_from(file)
458 self.refresh()
459
460 def refresh(self):
461 self.update_instance()
462 self.plugin.refresh(self.prescription)
463 self.load_interface_from_instance()
464
465 def add_attachment(self):
466 try:
467 new=QFileDialog.getOpenFileName(self, "Open File", config["document_directory"], "PDF (*.pdf);; Images (*.jpg, *.jpeg, *.png, *.gif);; All Files (*)")[0]
468 if new:
469 self.input_attachment.addItem(new)
470 except Exception as e:
471 QMessageBox.warning(self,"Attach failed", "Failed to attach file.")
472 logging.warning(e)
473
474 def remove_attachment(self):
475 index=self.input_attachment.currentRow()
476 if(index>=0):
477 self.current_file.delete_attachment(self.input_attachment.item(index).text())
478 self.input_attachment.takeItem(index)
479 else:
480 QMessageBox.warning(self, "Select item", "Please select an attachment to remove.")
481
482 def save_attachment(self):
483 try:
484 shutil.copyfile(self.input_attachment.currentItem().text(), QFileDialog.getSaveFileName(self, "Save Attachment", os.path.join(config["document_directory"], os.path.basename(self.input_attachment.currentItem().text())))[0])
485 except Exception as e:
486 logging.warning(e)
487
488 def load_attachment(self, attachments):
489 for attach in attachments:
490 self.input_attachment.addItem(attach)
491
492 def toggleDobAge(self, active):
493 if active=="age":
494 self.input_dob.setDate(QDate(0,0,0))
495 self.input_dob.setDisplayFormat("yy")
496 self.input_dob.setEnabled(False)
497 self.input_age.setEnabled(True)
498 elif active=="dob":
499 self.input_dob.setDisplayFormat("MMMM dd, yyyy")
500 self.input_dob.setEnabled(True)
501 self.input_age.setText("")
502 self.input_age.setEnabled(False)
503
504 def confirm_close(self):
505 self.refresh()
506 flag=(self.save_state==md5(self.prescription.get_json().encode()).hexdigest() or QMessageBox.StandardButton.Yes==QMessageBox.question(self,"Confirm action", "Unsaved changes may be lost. Continue?"))
507 return flag
508
509 def closeEvent(self, event):
510 if(self.confirm_close()):
511 event.accept()
512 else:
513 event.ignore()
514
515 def __init__(self, *args, **kwargs):
516 super().__init__(*args, **kwargs)
517
518 self.setWindowTitle("MedScript")
519 self.setGeometry(100, 100, 600, 400)
520 self.setWindowIcon(QIcon(os.path.join(config["resource"], "icon_medscript.ico")))
521
522 icon_open=QIcon(os.path.join(config["resource"], "icon_open.svg"))
523 icon_save=QIcon(os.path.join(config["resource"], "icon_save.svg"))
524 icon_render=QIcon(os.path.join(config["resource"], "icon_render.svg"))
525 icon_refresh=QIcon(os.path.join(config["resource"], "icon_refresh.svg"))
526 icon_view=QIcon(os.path.join(config["resource"], "icon_view.svg"))
527
528 self.preset_note=Preset("note")
529 self.preset_report=Preset("report")
530 self.preset_advice=Preset("advice")
531 self.preset_investigation=Preset("investigation")
532 self.preset_medication=Preset("medication", text_as_key=True)
533 self.preset_additional=Preset("additional")
534 self.preset_certificate=Preset("certificate")
535
536 action_new=QAction("New File", self)
537 action_new.setShortcut("Ctrl+N")
538 action_new.triggered.connect(self.cmd_new)
539 action_open=QAction("Open File", self)
540 action_open2=QAction(icon_open, "Open", self)
541 action_open.setShortcut("Ctrl+O")
542 action_open.triggered.connect(self.cmd_open)
543 action_open2.triggered.connect(self.cmd_open)
544 action_save=QAction("Save File", self)
545 action_save2=QAction(icon_save, "Save", self)
546 action_save.setShortcut("Ctrl+S")
547 action_save.triggered.connect(self.cmd_save)
548 action_save2.triggered.connect(self.cmd_save)
549 action_save_as=QAction("Save As", self)
550 action_save_as.setShortcut("Ctrl+Shift+S")
551 action_save_as.triggered.connect(self.cmd_save_as)
552 action_refresh=QAction("Refresh Interface", self)
553 action_refresh.setShortcut("F5")
554 action_refresh2=QAction(icon_refresh, "Refresh", self)
555 action_refresh.triggered.connect(self.cmd_refresh)
556 action_refresh2.triggered.connect(self.cmd_refresh)
557 action_quit=QAction("Quit MedScript", self)
558 action_quit.setShortcut("Ctrl+Q")
559 action_quit.triggered.connect(self.cmd_quit)
560 action_render=QAction("Render Prescription", self)
561 action_render.setShortcut("Ctrl+R")
562 action_render2=QAction(icon_render, "Render", self)
563 action_render.triggered.connect(self.cmd_render)
564 action_render2.triggered.connect(self.cmd_render)
565 action_unrender=QAction("Plain Display", self)
566 action_unrender.setShortcut("Ctrl+D")
567 action_unrender2=QAction(icon_view, "View", self)
568 action_unrender.triggered.connect(self.cmd_unrender)
569 action_unrender2.triggered.connect(self.cmd_unrender)
570 action_sign=QAction("Sign Prescription", self)
571 action_sign.triggered.connect(self.cmd_sign)
572 action_unsign=QAction("Delete Signature", self)
573 action_unsign.triggered.connect(self.cmd_unsign)
574 action_verify=QAction("Verify Signature", self)
575 action_verify.triggered.connect(self.cmd_verify)
576 action_configuration=QAction("Edit Configuration", self)
577 action_configuration.triggered.connect(self.cmd_configuration)
578 action_prescriber=QAction("Current Prescriber", self)
579 action_prescriber.triggered.connect(self.cmd_prescriber)
580 action_switch=QAction("Select Prescriber", self)
581 action_switch.triggered.connect(self.cmd_switch)
582 action_preset=QAction("Edit Presets", self)
583 action_preset.triggered.connect(self.cmd_preset)
584 action_installer=QAction("Package Installer", self)
585 action_installer.triggered.connect(self.cmd_installer)
586 action_tabular=QAction("Export Spreadsheet", self)
587 action_tabular.triggered.connect(self.cmd_tabular)
588 action_index=QAction("Show Index", self)
589 action_index.triggered.connect(self.cmd_index)
590 action_index.setShortcut("Ctrl+I")
591 action_update=QAction("Check Update", self)
592 action_update.triggered.connect(self.cmd_update)
593 action_about=QAction("About MedScript", self)
594 action_about.triggered.connect(self.cmd_about)
595 action_help=QAction("Show Help", self)
596 action_help.setShortcut("F1")
597 action_help.triggered.connect(self.cmd_help)
598
599 menubar=self.menuBar()
600 menu_file=menubar.addMenu("File")
601 menu_file.addAction(action_new)
602 menu_file.addAction(action_open)
603 menu_file.addAction(action_save)
604 menu_file.addAction(action_save_as)
605 menu_file.addAction(action_quit)
606 menu_prepare=menubar.addMenu("Prepare")
607 menu_prepare.addAction(action_unrender)
608 menu_prepare.addAction(action_render)
609 menu_prepare.addAction(action_refresh)
610 if(config["smime"]):
611 menu_prepare.addAction(action_sign)
612 menu_prepare.addAction(action_unsign)
613 menu_prepare.addAction(action_verify)
614 menu_settings=menubar.addMenu("Settings")
615 menu_settings.addAction(action_configuration)
616 menu_settings.addAction(action_prescriber)
617 menu_settings.addAction(action_switch)
618 menu_settings.addAction(action_preset)
619 menu_settings.addAction(action_installer)
620 menu_data=menubar.addMenu("Data")
621 menu_data.addAction(action_index)
622 menu_data.addAction(action_tabular)
623
624 if(config["enable_plugin"]):
625 action_plugin=[]
626 try:
627 for i in self.plugin.commands():
628 action_plugin.append(QAction(i[1], self))
629 action_plugin[-1].triggered.connect(self.update_instance)
630 action_plugin[-1].triggered.connect(partial(self.plugin.run, i[0], self.prescription))
631 action_plugin[-1].triggered.connect(self.load_interface_from_instance)
632 except Exception as e:
633 logging.warning(e)
634 menu_plugin=menubar.addMenu("Plugin")
635 for i in action_plugin:
636 menu_plugin.addAction(i)
637
638 menu_help=menubar.addMenu("Help")
639 menu_help.addAction(action_update)
640 menu_help.addAction(action_about)
641 menu_help.addAction(action_help)
642
643 toolbar=QToolBar("Main Toolbar", floatable=False, movable=False)
644 toolbar.setIconSize(QSize(16, 16))
645 toolbar.addAction(action_open2)
646 toolbar.addAction(action_save2)
647 toolbar.addAction(action_refresh2)
648 toolbar.addAction(action_unrender2)
649 toolbar.addAction(action_render2)
650 toolbar.addSeparator()
651 label_template=QLabel("Template:")
652 toolbar.addWidget(label_template)
653 self.input_template=QComboBox(self)
654 self.input_template.setMinimumWidth(200)
655 templates=os.listdir(config["template_directory"])
656 try:
657 templates.remove(os.path.basename(config["template"]))
658 templates.insert(0, os.path.basename(config["template"]))
659 except Exception as e:
660 logging.warning(e)
661 self.input_template.addItems(templates)
662 toolbar.addWidget(self.input_template)
663 spacer=QWidget(self)
664 spacer.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
665 toolbar.addWidget(spacer)
666 self.label_prescriber=QLabel(self)
667 toolbar.addWidget(self.label_prescriber)
668 self.addToolBar(toolbar)
669
670 tab_info=QWidget(self)
671 layout_info=QFormLayout(tab_info)
672 layout_info2=QHBoxLayout()
673 self.input_date=QDateTimeEdit(self)
674 self.input_date.setDisplayFormat("MMMM dd, yyyy hh:mm a")
675 self.input_date.setCalendarPopup(True)
676 self.input_date.setCalendarWidget(QCalendarWidget())
677 layout_info.addRow("Date", self.input_date)
678 self.input_id=QLineEdit(self)
679 layout_info.addRow("Prescription ID", self.input_id)
680 self.input_pid=QLineEdit(self)
681 layout_info.addRow("Patient ID", self.input_pid)
682 self.input_name=QLineEdit(self)
683 layout_info.addRow("Name", self.input_name)
684
685 self.input_dob=QDateEdit(self)
686 self.input_dob.setCalendarPopup(True)
687 self.input_dob.setCalendarWidget(QCalendarWidget())
688 self.input_dob.setEnabled(False)
689 layout_dobAge=QHBoxLayout()
690 dobAge=QButtonGroup()
691 self.btnDob=QRadioButton("Date of Birth")
692 self.btnAge=QRadioButton("Age")
693 dobAge.addButton(self.btnDob)
694 dobAge.addButton(self.btnAge)
695 layout_dobAge.addWidget(self.btnDob)
696 layout_dobAge.addWidget(self.btnAge)
697 self.btnDob.clicked.connect(lambda: self.toggleDobAge("dob"))
698 self.btnAge.clicked.connect(lambda: self.toggleDobAge("age"))
699 layout_info.addRow("", layout_dobAge)
700 layout_info.addRow("Date of Birth", self.input_dob)
701 self.input_age=QLineEdit(self)
702 layout_info.addRow("Age", self.input_age)
703 self.input_sex=QComboBox(self)
704
705 self.input_sex.addItems(["Male", "Female", "Other"])
706 self.input_sex.setEditable(True)
707 layout_info.addRow("Sex", self.input_sex)
708 self.input_address=QLineEdit(self)
709 layout_info.addRow("Address", self.input_address)
710 self.input_contact=QLineEdit(self)
711 layout_info.addRow("Contact", self.input_contact)
712 self.input_diagnosis=QLineEdit(self)
713 layout_info.addRow("Diagnosis", self.input_diagnosis)
714 self.input_extra=QTextEdit(self)
715 input_extra_preset_btn=QPushButton("Insert")
716 layout_info.addRow("Extra", self.input_extra)
717 self.input_mode=QComboBox(self)
718 self.input_mode.addItems(["In-Person", "Tele-Consultation", "Other"])
719 self.input_mode.setEditable(True)
720 layout_info.addRow("Mode", self.input_mode)
721 self.input_daw=QCheckBox("Dispense as written", self)
722 layout_info.addRow("DAW", self.input_daw)
723
724 tab_note=QWidget(self)
725 layout_note=QVBoxLayout(tab_note)
726 layout_note2=QHBoxLayout()
727 label_note=QLabel("Clinical Notes")
728 label_note.setProperty("class", "info_head")
729 self.input_note_preset=QComboBox(self)
730 self.input_note_preset.addItems(self.preset_note.data.keys())
731 self.input_note_preset.setCurrentIndex(-1)
732 self.input_note_preset.setEditable(True)
733 self.input_note_preset.completer().setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
734 self.input_note_preset.completer().setFilterMode(Qt.MatchFlag.MatchContains)
735 self.input_note_preset.setPlaceholderText("Select a preset")
736 input_note_preset_btn=QPushButton("Insert")
737 input_note_preset_btn.clicked.connect(self.insert_preset_note)
738 layout_note2.addWidget(self.input_note_preset, 5)
739 layout_note2.addWidget(input_note_preset_btn, 1)
740 self.input_note=QTextEdit(self)
741 layout_note.addWidget(label_note)
742 layout_note.addLayout(layout_note2)
743 layout_note.addWidget(self.input_note)
744
745 tab_report=QWidget(self)
746 layout_report=QVBoxLayout(tab_report)
747 layout_report2=QHBoxLayout()
748 label_report=QLabel("Available Reports")
749 label_report.setProperty("class", "info_head")
750 self.input_report_preset=QComboBox(self)
751 self.input_report_preset.addItems(self.preset_report.data.keys())
752 self.input_report_preset.setCurrentIndex(-1)
753 self.input_report_preset.setEditable(True)
754 self.input_report_preset.completer().setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
755 self.input_report_preset.completer().setFilterMode(Qt.MatchFlag.MatchContains)
756 self.input_report_preset.setPlaceholderText("Select a preset")
757 input_report_preset_btn=QPushButton("Insert")
758 input_report_preset_btn.clicked.connect(self.insert_preset_report)
759 layout_report2.addWidget(self.input_report_preset, 5)
760 layout_report2.addWidget(input_report_preset_btn, 1)
761 self.input_report=QTextEdit(self)
762 layout_report.addWidget(label_report)
763 layout_report.addLayout(layout_report2)
764 layout_report.addWidget(self.input_report)
765
766 tab_advice=QWidget(self)
767 layout_advice=QVBoxLayout(tab_advice)
768 layout_advice2=QHBoxLayout()
769 label_advice=QLabel("Advice")
770 label_advice.setProperty("class", "info_head")
771 self.input_advice_preset=QComboBox(self)
772 self.input_advice_preset.addItems(self.preset_advice.data.keys())
773 self.input_advice_preset.setCurrentIndex(-1)
774 self.input_advice_preset.setEditable(True)
775 self.input_advice_preset.completer().setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
776 self.input_advice_preset.completer().setFilterMode(Qt.MatchFlag.MatchContains)
777 self.input_advice_preset.setPlaceholderText("Select a preset")
778 input_advice_preset_btn=QPushButton("Insert")
779 input_advice_preset_btn.clicked.connect(self.insert_preset_advice)
780 layout_advice2.addWidget(self.input_advice_preset, 5)
781 layout_advice2.addWidget(input_advice_preset_btn, 1)
782 self.input_advice=QTextEdit(self)
783 layout_advice.addWidget(label_advice)
784 layout_advice.addLayout(layout_advice2)
785 layout_advice.addWidget(self.input_advice)
786
787 tab_investigation=QWidget(self)
788 layout_investigation=QVBoxLayout(tab_investigation)
789 layout_investigation2=QHBoxLayout()
790 label_investigation=QLabel("Recommended Investigations")
791 label_investigation.setProperty("class", "info_head")
792 self.input_investigation_preset=QComboBox(self)
793 self.input_investigation_preset.addItems(self.preset_investigation.data.keys())
794 self.input_investigation_preset.setCurrentIndex(-1)
795 self.input_investigation_preset.setEditable(True)
796 self.input_investigation_preset.completer().setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
797 self.input_investigation_preset.completer().setFilterMode(Qt.MatchFlag.MatchContains)
798 self.input_investigation_preset.setPlaceholderText("Select a preset")
799 input_investigation_preset_btn=QPushButton("Insert")
800 input_investigation_preset_btn.clicked.connect(self.insert_preset_investigation)
801 layout_investigation2.addWidget(self.input_investigation_preset, 5)
802 layout_investigation2.addWidget(input_investigation_preset_btn, 1)
803 self.input_investigation=QTextEdit(self)
804 layout_investigation.addWidget(label_investigation)
805 layout_investigation.addLayout(layout_investigation2)
806 layout_investigation.addWidget(self.input_investigation)
807
808 tab_medication=QWidget(self)
809 layout_medication=QVBoxLayout(tab_medication)
810 layout_medication2=QHBoxLayout()
811 label_medication=QLabel("Medication Advice")
812 label_medication.setProperty("class", "info_head")
813 self.input_medication_preset=QComboBox(self)
814 self.input_medication_preset.addItems(self.preset_medication.data.keys())
815 self.input_medication_preset.setCurrentIndex(-1)
816 self.input_medication_preset.setEditable(True)
817 self.input_medication_preset.completer().setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
818 self.input_medication_preset.completer().setFilterMode(Qt.MatchFlag.MatchContains)
819 self.input_medication_preset.setPlaceholderText("Select a preset")
820 input_medication_preset_btn=QPushButton("Insert")
821 input_medication_preset_btn.clicked.connect(self.insert_preset_medication)
822 layout_medication2.addWidget(self.input_medication_preset, 5)
823 layout_medication2.addWidget(input_medication_preset_btn, 1)
824 self.input_medication=QTextEdit(self)
825 layout_medication.addWidget(label_medication)
826 layout_medication.addLayout(layout_medication2)
827 layout_medication.addWidget(self.input_medication)
828
829 tab_additional=QWidget(self)
830 layout_additional=QVBoxLayout(tab_additional)
831 layout_additional2=QHBoxLayout()
832 label_additional=QLabel("Additional Advice")
833 label_additional.setProperty("class", "info_head")
834 self.input_additional_preset=QComboBox(self)
835 self.input_additional_preset.addItems(self.preset_additional.data.keys())
836 self.input_additional_preset.setCurrentIndex(-1)
837 self.input_additional_preset.setEditable(True)
838 self.input_additional_preset.completer().setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
839 self.input_additional_preset.completer().setFilterMode(Qt.MatchFlag.MatchContains)
840 self.input_additional_preset.setPlaceholderText("Select a preset")
841 input_additional_preset_btn=QPushButton("Insert")
842 input_additional_preset_btn.clicked.connect(self.insert_preset_additional)
843 layout_additional2.addWidget(self.input_additional_preset, 5)
844 layout_additional2.addWidget(input_additional_preset_btn, 1)
845 self.input_additional=QTextEdit(self)
846 layout_additional.addWidget(label_additional)
847 layout_additional.addLayout(layout_additional2)
848 layout_additional.addWidget(self.input_additional)
849
850 tab_certificate=QWidget(self)
851 layout_certificate=QVBoxLayout(tab_certificate)
852 layout_certificate2=QHBoxLayout()
853 label_certificate=QLabel("Certificate")
854 label_certificate.setProperty("class", "info_head")
855 self.input_certificate_preset=QComboBox(self)
856 self.input_certificate_preset.addItems(self.preset_certificate.data.keys())
857 self.input_certificate_preset.setCurrentIndex(-1)
858 self.input_certificate_preset.setEditable(True)
859 self.input_certificate_preset.completer().setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
860 self.input_certificate_preset.completer().setFilterMode(Qt.MatchFlag.MatchContains)
861 self.input_certificate_preset.setPlaceholderText("Select a preset")
862 input_certificate_preset_btn=QPushButton("Insert")
863 input_certificate_preset_btn.clicked.connect(self.insert_preset_certificate)
864 layout_certificate2.addWidget(self.input_certificate_preset, 5)
865 layout_certificate2.addWidget(input_certificate_preset_btn, 1)
866 self.input_certificate=QTextEdit(self)
867 layout_certificate.addWidget(label_certificate)
868 layout_certificate.addLayout(layout_certificate2)
869 layout_certificate.addWidget(self.input_certificate)
870
871 tab_attachment=QWidget(self)
872 layout_attachment=QVBoxLayout(tab_attachment)
873 layout_attachment2=QHBoxLayout()
874 label_attachment=QLabel("Attached files")
875 label_attachment.setProperty("class", "info_head")
876 self.input_attachment=QListWidget(self)
877 button_add=QPushButton("Add")
878 button_add.clicked.connect(self.add_attachment)
879 button_remove=QPushButton("Remove")
880 button_remove.clicked.connect(self.remove_attachment)
881 button_save=QPushButton("Save")
882 button_save.clicked.connect(self.save_attachment)
883 layout_attachment.addWidget(label_attachment)
884 layout_attachment.addLayout(layout_attachment2)
885 layout_attachment.addWidget(self.input_attachment)
886 layout_attachment2.addWidget(button_add)
887 layout_attachment2.addWidget(button_remove)
888 layout_attachment2.addWidget(button_save)
889
890 tab_custom=QWidget(self)
891 layout_custom=QVBoxLayout(tab_custom)
892 self.input_custom=CustomForm()
893 layout_custom.addWidget(self.input_custom)
894
895 tab=QTabWidget(self)
896 tab.addTab(tab_info, "Patient")
897 tab.addTab(tab_note, "Clinical")
898 tab.addTab(tab_report, "Report")
899 tab.addTab(tab_advice, "Advice")
900 tab.addTab(tab_investigation, "Investigation")
901 tab.addTab(tab_medication, "Medication")
902 tab.addTab(tab_additional, "Additional")
903 tab.addTab(tab_certificate, "Certificate")
904 if(config["enable_form"]):
905 tab.addTab(tab_custom, "Custom")
906 else:
907 tab_custom.hide()
908 tab.addTab(tab_attachment, "Attachment")
909
910 self.setCentralWidget(tab)
911
912 self.statusbar=QStatusBar()
913 self.setStatusBar(self.statusbar)
914
915 self.renderbox=RenderBox()
916 self.unrenderbox=UnrenderBox()
917 self.signal_view.connect(self.renderbox.update)
918 self.edit_configuration=EditConfiguration()
919 self.edit_prescriber=EditPrescriber()
920 self.edit_prescriber.signal_save.connect(self.change_prescriber)
921 self.select_prescriber=SelectPrescriber()
922 self.select_prescriber.signal_edit.connect(self.cmd_prescriber)
923 self.select_prescriber.signal_select.connect(self.change_prescriber)
924 self.viewbox=ViewBox()
925 self.index=Index()
926 self.edit_preset=EditPreset()
927 self.installer=Installer()
928 self.index.signal_open.connect(self.cmd_open)
929 self.index.signal_copy.connect(self.cmd_copy)
930 self.signal_update.connect(self.show_update)
931
932 self.new_doc()
933 if(config["filename"]):
934 self.cmd_open(config["filename"])
935
936 if(len(self.prescription.prescriber.name.strip())<1):
937 self.cmd_prescriber()
938
939 if(config["check_update"]):
940 threading.Thread(target=self.cmd_update, args=[True]).start()
941
942 self.setWindowIcon(QIcon(os.path.join(config["resource"], "icon_medscript.ico")))
943 self.showMaximized()