]> Softwares of Agnibho - medscript.git/blob - window.py
Plugin enabled by default
[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, copy, 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 index import Index
28 from customform import CustomForm
29 from plugin import Plugin
30 from installer import Installer
31
32 class MainWindow(QMainWindow):
33
34 signal_view=pyqtSignal(str)
35 signal_update=pyqtSignal(str)
36
37 current_file=FileHandler()
38 prescription=Prescription()
39 prescriber=Prescriber()
40 renderer=Renderer()
41 plugin=Plugin()
42 save_state=md5("".encode()).hexdigest()
43 unchanged_state=False
44
45 def cmd_new(self):
46 if(self.confirm_close()):
47 self.new_doc()
48
49 def cmd_open(self, file=None):
50 if(self.confirm_close()):
51 try:
52 self.current_file.reset()
53 if(file):
54 self.current_file.set_file(file)
55 else:
56 self.current_file.set_file(QFileDialog.getOpenFileName(self, "Open File", config["document_directory"], "Prescriptions (*.mpaz);; All Files (*)")[0])
57 self.current_file.open()
58 self.prescription.read_from(os.path.join(self.current_file.directory.name,"prescription.json"))
59 self.plugin.open(self.prescription)
60 self.load_interface_from_instance()
61 self.update_instance()
62 self.save_state=md5(self.prescription.get_json().encode()).hexdigest()
63 self.load_attachment(self.current_file.list())
64 self.unchanged_state=True
65 except FileNotFoundError as e:
66 logging.warning(e)
67 except Exception as e:
68 QMessageBox.warning(self,"Open failed", "Failed to open file.")
69 logging.exception(e)
70
71 def cmd_copy(self, data):
72 self.cmd_new()
73 self.prescription.set_data_from_json(data)
74 self.prescription.id=""
75 self.prescription.date=None
76 self.load_interface_from_instance()
77 self.refresh()
78
79 def cmd_save(self, save_as=False):
80 self.update_instance()
81 self.plugin.save(self.prescription)
82 if(self.input_template.currentText()!="<unchanged>"):
83 change_template=True
84 template=self.input_template.currentText()
85 else:
86 change_template=False
87 self.load_interface_from_instance()
88 suggest=self.prescription.id if(self.prescription.id) else self.prescription.name
89 suggest=os.path.abspath(os.path.join(config["document_directory"], suggest)+".mpaz")
90 if(save_as or not self.unchanged_state or QMessageBox.StandardButton.Yes==QMessageBox.question(self,"Confirm change", "Modify the original file?")):
91 try:
92 if not os.path.exists(self.current_file.file):
93 filename=QFileDialog.getSaveFileName(self, "Save File", suggest, "Prescriptions (*.mpaz);; All Files (*)")[0]
94 if(len(filename)<=0):
95 return
96 if(not filename.endswith(".mpaz")):
97 filename=filename+".mpaz"
98 self.current_file.set_file(filename)
99 for i in range(self.input_attachment.count()):
100 self.current_file.copy(self.input_attachment.item(i).text())
101 if(self.prescription.prescriber.get_json()!=self.prescriber.get_json()):
102 if(QMessageBox.StandardButton.Yes==QMessageBox.question(self,"Change Prescriber", "Original Prescriber: "+self.prescription.prescriber.name+"\nCurrent Prescriber: "+self.prescriber.name+"\nReplace original with current?")):
103 self.prescription.prescriber=copy.deepcopy(self.prescriber)
104 self.prescription.write_to(os.path.join(self.current_file.directory.name, "prescription.json"))
105 if change_template:
106 config["template"]=os.path.join(config["template_directory"], template)
107 self.current_file.save(change_template=change_template)
108 self.unchanged_state=False
109 self.load_interface_from_instance()
110 self.save_state=md5(self.prescription.get_json().encode()).hexdigest()
111 except Exception as e:
112 QMessageBox.warning(self,"Save failed", "Failed to save file.")
113 logging.exception(e)
114
115 def cmd_save_as(self):
116 suggest=self.prescription.id if(self.prescription.id) else self.prescription.name
117 suggest=os.path.abspath(os.path.join(config["document_directory"], suggest)+".mpaz")
118 filename=QFileDialog.getSaveFileName(self, "Save File", suggest, "Prescriptions (*.mpaz);; All Files (*)")[0]
119 if(len(filename)>0):
120 if not filename.endswith(".mpaz"):
121 filename=filename+".mpaz"
122 self.current_file.set_file(filename)
123 Path(self.current_file.file).touch()
124 self.cmd_save(save_as=True)
125
126 def cmd_refresh(self):
127 self.refresh()
128
129 def cmd_quit(self):
130 if(self.confirm_close()):
131 sys.exit()
132
133 def cmd_unrender(self):
134 self.update_instance()
135 self.unrenderbox.show(self.prescription).exec()
136
137 def cmd_render(self):
138 self.update_instance()
139 if(self.save_state==md5(self.prescription.get_json().encode()).hexdigest()):
140 try:
141 target=self.renderer.render(self.current_file.directory.name)
142 if target is not None:
143 self.signal_view.emit(target)
144 self.renderbox.showMaximized()
145 else:
146 QMessageBox.critical(self, "Render failed", "Presciption rendering failed. Please check if prescription file or template is corrupted.")
147 logging.error("Prescription rendering failed.")
148 except FileNotFoundError as e:
149 logging.warning(e)
150 QMessageBox.information(self, "Save first", "Please save the file before rendering.")
151 else:
152 QMessageBox.information(self, "Save first", "Please save the file before rendering.")
153
154 def cmd_sign(self):
155 self.update_instance()
156 if(self.save_state==md5(self.prescription.get_json().encode()).hexdigest()):
157 ok=True #password, ok=QInputDialog.getText(self, "Enter password", "Private key password", QLineEdit.EchoMode.Password)
158 if(ok):
159 try:
160 try:
161 self.current_file.sign()
162 #self.current_file.sign(password)
163 self.cmd_save()
164 except FileNotFoundError as e:
165 logging.warning(e)
166 QMessageBox.information(self, "Save first", "Please save the file before signing.")
167 except TypeError as e:
168 logging.warning(e)
169 QMessageBox.information(self, "Configure", "Please add valid key and certificate to the config file.")
170 except EVPError as e:
171 logging.warning(e)
172 QMessageBox.information(self, "Check password", "Failed to load key. Please check if password is correct.")
173 except BIOError as e:
174 logging.warning(e)
175 QMessageBox.information(self, "Not found", "Certifcate and/or key not found.")
176 except SMIME_Error as e:
177 logging.warning(e)
178 QMessageBox.information(self, "Failed to load", "Failed to sign. Please check if certificate and key match.")
179 except Exception as e:
180 logging.exception(e)
181 QMessageBox.information(self, "Failed", "Failed to sign.")
182 except Exception as e:
183 logging.exception(e)
184 else:
185 QMessageBox.information(self, "Save first", "Please save the file before signing.")
186
187 def cmd_unsign(self):
188 self.current_file.delete_sign()
189 self.cmd_save()
190 self.refresh()
191
192 def cmd_verify(self):
193 try:
194 result=self.current_file.verify()
195 if result is False:
196 QMessageBox.critical(self, "Verification failed", "Signature is invalid.")
197 elif result is None:
198 QMessageBox.warning(self, "No Siganture", "No signature was found.")
199 else:
200 logging.info(result)
201 QMessageBox.information(self, "Valid signature", "Valid signature found with the following information:\n"+result)
202 except FileNotFoundError as e:
203 logging.warning(e)
204 QMessageBox.warning(self, "No Siganture", "No signature was found.")
205 except Exception as e:
206 logging.exception(e)
207 QMessageBox.warning(self, "Failed", "Failed to verify.")
208
209 def cmd_index(self):
210 self.index.show()
211
212 def cmd_configuration(self):
213 self.editConfiguration.exec()
214
215 def cmd_prescriber(self):
216 try:
217 self.selectPrescriber.load()
218 self.selectPrescriber.exec()
219 except FileNotFoundError as e:
220 logging.warning(e)
221
222 def cmd_preset(self):
223 self.editPreset.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.exception(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.exception(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 edit_prescriber(self, file=None):
461 self.editPrescriber.load(file)
462 self.editPrescriber.exec()
463
464 def refresh(self):
465 self.update_instance()
466 self.plugin.refresh(self.prescription)
467 self.load_interface_from_instance()
468
469 def add_attachment(self):
470 try:
471 new=QFileDialog.getOpenFileName(self, "Open File", config["document_directory"], "PDF (*.pdf);; Images (*.jpg, *.jpeg, *.png, *.gif);; All Files (*)")[0]
472 if new:
473 self.input_attachment.addItem(new)
474 except Exception as e:
475 QMessageBox.warning(self,"Attach failed", "Failed to attach file.")
476 logging.exception(e)
477
478 def remove_attachment(self):
479 index=self.input_attachment.currentRow()
480 if(index>=0):
481 self.current_file.delete_attachment(self.input_attachment.item(index).text())
482 self.input_attachment.takeItem(index)
483 else:
484 QMessageBox.warning(self, "Select item", "Please select an attachment to remove.")
485
486 def save_attachment(self):
487 try:
488 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])
489 except Exception as e:
490 logging.exception(e)
491
492 def load_attachment(self, attachments):
493 for attach in attachments:
494 self.input_attachment.addItem(attach)
495
496 def toggleDobAge(self, active):
497 if active=="age":
498 self.input_dob.setDate(QDate(0,0,0))
499 self.input_dob.setDisplayFormat("yy")
500 self.input_dob.setEnabled(False)
501 self.input_age.setEnabled(True)
502 elif active=="dob":
503 self.input_dob.setDisplayFormat("MMMM dd, yyyy")
504 self.input_dob.setEnabled(True)
505 self.input_age.setText("")
506 self.input_age.setEnabled(False)
507
508 def load_presets(self):
509 self.preset_note=Preset("note")
510 self.preset_report=Preset("report")
511 self.preset_advice=Preset("advice")
512 self.preset_investigation=Preset("investigation")
513 self.preset_medication=Preset("medication", text_as_key=True)
514 self.preset_additional=Preset("additional")
515 self.preset_certificate=Preset("certificate")
516
517 def reload_presets(self):
518 self.load_presets()
519 self.input_note_preset.addItems(self.preset_note.data.keys())
520 self.input_report_preset.addItems(self.preset_note.data.keys())
521 self.input_advice_preset.addItems(self.preset_note.data.keys())
522 self.input_investigation_preset.addItems(self.preset_note.data.keys())
523 self.input_medication_preset.addItems(self.preset_note.data.keys())
524 self.input_additional_preset.addItems(self.preset_note.data.keys())
525 self.input_certificate_preset.addItems(self.preset_note.data.keys())
526
527 def confirm_close(self):
528 self.update_instance()
529 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?"))
530 return flag
531
532 def closeEvent(self, event):
533 if(self.confirm_close()):
534 event.accept()
535 else:
536 event.ignore()
537
538 def __init__(self, *args, **kwargs):
539 super().__init__(*args, **kwargs)
540
541 self.setWindowTitle("MedScript")
542 self.setGeometry(100, 100, 600, 400)
543 self.setWindowIcon(QIcon(os.path.join(config["resource"], "icon_medscript.ico")))
544
545 icon_index=QIcon(os.path.join(config["resource"], "icon_index.svg"))
546 icon_open=QIcon(os.path.join(config["resource"], "icon_open.svg"))
547 icon_save=QIcon(os.path.join(config["resource"], "icon_save.svg"))
548 icon_render=QIcon(os.path.join(config["resource"], "icon_render.svg"))
549 icon_refresh=QIcon(os.path.join(config["resource"], "icon_refresh.svg"))
550 icon_view=QIcon(os.path.join(config["resource"], "icon_view.svg"))
551
552 self.load_presets()
553
554 action_new=QAction("New File", self)
555 action_new.setShortcut("Ctrl+N")
556 action_new.triggered.connect(self.cmd_new)
557 action_open=QAction("Open File", self)
558 action_open2=QAction(icon_open, "Open", self)
559 action_open.setShortcut("Ctrl+O")
560 action_open.triggered.connect(self.cmd_open)
561 action_open2.triggered.connect(self.cmd_open)
562 action_save=QAction("Save File", self)
563 action_save2=QAction(icon_save, "Save", self)
564 action_save.setShortcut("Ctrl+S")
565 action_save.triggered.connect(self.cmd_save)
566 action_save2.triggered.connect(self.cmd_save)
567 action_save_as=QAction("Save As", self)
568 action_save_as.setShortcut("Ctrl+Shift+S")
569 action_save_as.triggered.connect(self.cmd_save_as)
570 action_refresh=QAction("Refresh Interface", self)
571 action_refresh.setShortcut("F5")
572 action_refresh2=QAction(icon_refresh, "Refresh", self)
573 action_refresh.triggered.connect(self.cmd_refresh)
574 action_refresh2.triggered.connect(self.cmd_refresh)
575 action_quit=QAction("Quit MedScript", self)
576 action_quit.setShortcut("Ctrl+Q")
577 action_quit.triggered.connect(self.cmd_quit)
578 action_render=QAction("Render Prescription", self)
579 action_render.setShortcut("Ctrl+R")
580 action_render2=QAction(icon_render, "Render", self)
581 action_render.triggered.connect(self.cmd_render)
582 action_render2.triggered.connect(self.cmd_render)
583 action_unrender=QAction("Quick Display", self)
584 action_unrender.setShortcut("Ctrl+D")
585 action_unrender2=QAction(icon_view, "Display", self)
586 action_unrender.triggered.connect(self.cmd_unrender)
587 action_unrender2.triggered.connect(self.cmd_unrender)
588 action_sign=QAction("Sign Prescription", self)
589 action_sign.triggered.connect(self.cmd_sign)
590 action_unsign=QAction("Delete Signature", self)
591 action_unsign.triggered.connect(self.cmd_unsign)
592 action_verify=QAction("Verify Signature", self)
593 action_verify.triggered.connect(self.cmd_verify)
594 action_configuration=QAction("Edit Configuration", self)
595 action_configuration.triggered.connect(self.cmd_configuration)
596 action_preset=QAction("Edit Presets", self)
597 action_preset.triggered.connect(self.cmd_preset)
598 action_prescriber=QAction("Select Prescriber", self)
599 action_prescriber.triggered.connect(self.cmd_prescriber)
600 action_installer=QAction("Package Installer", self)
601 action_installer.triggered.connect(self.cmd_installer)
602 action_index=QAction("Show Index", self)
603 action_index.triggered.connect(self.cmd_index)
604 action_index.setShortcut("Ctrl+I")
605 action_index2=QAction(icon_index, "Index", self)
606 action_index2.triggered.connect(self.cmd_index)
607 action_update=QAction("Check Update", self)
608 action_update.triggered.connect(self.cmd_update)
609 action_about=QAction("About MedScript", self)
610 action_about.triggered.connect(self.cmd_about)
611 action_help=QAction("Show Help", self)
612 action_help.setShortcut("F1")
613 action_help.triggered.connect(self.cmd_help)
614
615 menubar=self.menuBar()
616 menu_file=menubar.addMenu("File")
617 menu_file.addAction(action_index)
618 menu_file.addAction(action_new)
619 menu_file.addAction(action_open)
620 menu_file.addAction(action_save)
621 menu_file.addAction(action_save_as)
622 menu_file.addAction(action_quit)
623 menu_prepare=menubar.addMenu("Process")
624 menu_prepare.addAction(action_unrender)
625 menu_prepare.addAction(action_render)
626 menu_prepare.addAction(action_refresh)
627 if(config["smime"]):
628 menu_prepare.addAction(action_sign)
629 menu_prepare.addAction(action_unsign)
630 menu_prepare.addAction(action_verify)
631 menu_settings=menubar.addMenu("Settings")
632 menu_settings.addAction(action_configuration)
633 menu_settings.addAction(action_preset)
634 menu_settings.addAction(action_prescriber)
635 menu_settings.addAction(action_installer)
636
637 if(config["enable_plugin"]):
638 action_plugin=[]
639 try:
640 for i in self.plugin.commands():
641 action_plugin.append(QAction(i[1], self))
642 action_plugin[-1].triggered.connect(self.update_instance)
643 action_plugin[-1].triggered.connect(partial(self.plugin.run, i[0], self.prescription))
644 action_plugin[-1].triggered.connect(self.load_interface_from_instance)
645 except Exception as e:
646 logging.exception(e)
647 menu_plugin=menubar.addMenu("Plugin")
648 for i in action_plugin:
649 menu_plugin.addAction(i)
650
651 menu_help=menubar.addMenu("Help")
652 menu_help.addAction(action_update)
653 menu_help.addAction(action_about)
654 menu_help.addAction(action_help)
655
656 toolbar=QToolBar("Main Toolbar", floatable=False, movable=False)
657 toolbar.setIconSize(QSize(16, 16))
658 toolbar.addAction(action_index2)
659 toolbar.addAction(action_open2)
660 toolbar.addAction(action_save2)
661 toolbar.addAction(action_refresh2)
662 toolbar.addAction(action_unrender2)
663 toolbar.addAction(action_render2)
664 toolbar.addSeparator()
665 label_template=QLabel("Template:")
666 toolbar.addWidget(label_template)
667 self.input_template=QComboBox(self)
668 self.input_template.setMinimumWidth(200)
669 templates=os.listdir(config["template_directory"])
670 try:
671 templates.remove(os.path.basename(config["template"]))
672 templates.insert(0, os.path.basename(config["template"]))
673 except Exception as e:
674 logging.exception(e)
675 self.input_template.addItems(templates)
676 toolbar.addWidget(self.input_template)
677 spacer=QWidget(self)
678 spacer.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
679 toolbar.addWidget(spacer)
680 self.label_prescriber=QLabel(self)
681 toolbar.addWidget(self.label_prescriber)
682 self.addToolBar(toolbar)
683
684 tab_info=QWidget(self)
685 layout_info=QFormLayout(tab_info)
686 layout_info2=QHBoxLayout()
687 self.input_date=QDateTimeEdit(self)
688 self.input_date.setDisplayFormat("MMMM dd, yyyy hh:mm a")
689 self.input_date.setCalendarPopup(True)
690 self.input_date.setCalendarWidget(QCalendarWidget())
691 layout_info.addRow("Date", self.input_date)
692 self.input_id=QLineEdit(self)
693 layout_info.addRow("Prescription ID", self.input_id)
694 self.input_pid=QLineEdit(self)
695 layout_info.addRow("Patient ID", self.input_pid)
696 self.input_name=QLineEdit(self)
697 layout_info.addRow("Name", self.input_name)
698
699 self.input_dob=QDateEdit(self)
700 self.input_dob.setCalendarPopup(True)
701 self.input_dob.setCalendarWidget(QCalendarWidget())
702 self.input_dob.setEnabled(False)
703 layout_dobAge=QHBoxLayout()
704 dobAge=QButtonGroup()
705 self.btnDob=QRadioButton("Date of Birth")
706 self.btnAge=QRadioButton("Age")
707 dobAge.addButton(self.btnDob)
708 dobAge.addButton(self.btnAge)
709 layout_dobAge.addWidget(self.btnDob)
710 layout_dobAge.addWidget(self.btnAge)
711 self.btnDob.clicked.connect(lambda: self.toggleDobAge("dob"))
712 self.btnAge.clicked.connect(lambda: self.toggleDobAge("age"))
713 layout_info.addRow("", layout_dobAge)
714 layout_info.addRow("Date of Birth", self.input_dob)
715 self.input_age=QLineEdit(self)
716 layout_info.addRow("Age", self.input_age)
717 self.input_sex=QComboBox(self)
718
719 self.input_sex.addItems(["Male", "Female", "Other"])
720 self.input_sex.setEditable(True)
721 layout_info.addRow("Sex", self.input_sex)
722 self.input_address=QLineEdit(self)
723 layout_info.addRow("Address", self.input_address)
724 self.input_contact=QLineEdit(self)
725 layout_info.addRow("Contact", self.input_contact)
726 self.input_diagnosis=QLineEdit(self)
727 layout_info.addRow("Diagnosis", self.input_diagnosis)
728 self.input_extra=QTextEdit(self)
729 input_extra_preset_btn=QPushButton("Insert")
730 layout_info.addRow("Extra", self.input_extra)
731 self.input_mode=QComboBox(self)
732 self.input_mode.addItems(["In-Person", "Tele-Consultation", "Other"])
733 self.input_mode.setEditable(True)
734 layout_info.addRow("Mode", self.input_mode)
735 self.input_daw=QCheckBox("Dispense as written", self)
736 layout_info.addRow("DAW", self.input_daw)
737
738 tab_note=QWidget(self)
739 layout_note=QVBoxLayout(tab_note)
740 layout_note2=QHBoxLayout()
741 label_note=QLabel("Clinical Notes")
742 label_note.setProperty("class", "info_head")
743 self.input_note_preset=QComboBox(self)
744 self.input_note_preset.addItems(self.preset_note.data.keys())
745 self.input_note_preset.setCurrentIndex(-1)
746 self.input_note_preset.setEditable(True)
747 self.input_note_preset.completer().setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
748 self.input_note_preset.completer().setFilterMode(Qt.MatchFlag.MatchContains)
749 self.input_note_preset.setPlaceholderText("Select a preset")
750 input_note_preset_btn=QPushButton("Insert")
751 input_note_preset_btn.clicked.connect(self.insert_preset_note)
752 layout_note2.addWidget(self.input_note_preset, 5)
753 layout_note2.addWidget(input_note_preset_btn, 1)
754 self.input_note=QTextEdit(self)
755 layout_note.addWidget(label_note)
756 layout_note.addLayout(layout_note2)
757 layout_note.addWidget(self.input_note)
758
759 tab_report=QWidget(self)
760 layout_report=QVBoxLayout(tab_report)
761 layout_report2=QHBoxLayout()
762 label_report=QLabel("Available Reports")
763 label_report.setProperty("class", "info_head")
764 self.input_report_preset=QComboBox(self)
765 self.input_report_preset.addItems(self.preset_report.data.keys())
766 self.input_report_preset.setCurrentIndex(-1)
767 self.input_report_preset.setEditable(True)
768 self.input_report_preset.completer().setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
769 self.input_report_preset.completer().setFilterMode(Qt.MatchFlag.MatchContains)
770 self.input_report_preset.setPlaceholderText("Select a preset")
771 input_report_preset_btn=QPushButton("Insert")
772 input_report_preset_btn.clicked.connect(self.insert_preset_report)
773 layout_report2.addWidget(self.input_report_preset, 5)
774 layout_report2.addWidget(input_report_preset_btn, 1)
775 self.input_report=QTextEdit(self)
776 layout_report.addWidget(label_report)
777 layout_report.addLayout(layout_report2)
778 layout_report.addWidget(self.input_report)
779
780 tab_advice=QWidget(self)
781 layout_advice=QVBoxLayout(tab_advice)
782 layout_advice2=QHBoxLayout()
783 label_advice=QLabel("Advice")
784 label_advice.setProperty("class", "info_head")
785 self.input_advice_preset=QComboBox(self)
786 self.input_advice_preset.addItems(self.preset_advice.data.keys())
787 self.input_advice_preset.setCurrentIndex(-1)
788 self.input_advice_preset.setEditable(True)
789 self.input_advice_preset.completer().setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
790 self.input_advice_preset.completer().setFilterMode(Qt.MatchFlag.MatchContains)
791 self.input_advice_preset.setPlaceholderText("Select a preset")
792 input_advice_preset_btn=QPushButton("Insert")
793 input_advice_preset_btn.clicked.connect(self.insert_preset_advice)
794 layout_advice2.addWidget(self.input_advice_preset, 5)
795 layout_advice2.addWidget(input_advice_preset_btn, 1)
796 self.input_advice=QTextEdit(self)
797 layout_advice.addWidget(label_advice)
798 layout_advice.addLayout(layout_advice2)
799 layout_advice.addWidget(self.input_advice)
800
801 tab_investigation=QWidget(self)
802 layout_investigation=QVBoxLayout(tab_investigation)
803 layout_investigation2=QHBoxLayout()
804 label_investigation=QLabel("Recommended Investigations")
805 label_investigation.setProperty("class", "info_head")
806 self.input_investigation_preset=QComboBox(self)
807 self.input_investigation_preset.addItems(self.preset_investigation.data.keys())
808 self.input_investigation_preset.setCurrentIndex(-1)
809 self.input_investigation_preset.setEditable(True)
810 self.input_investigation_preset.completer().setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
811 self.input_investigation_preset.completer().setFilterMode(Qt.MatchFlag.MatchContains)
812 self.input_investigation_preset.setPlaceholderText("Select a preset")
813 input_investigation_preset_btn=QPushButton("Insert")
814 input_investigation_preset_btn.clicked.connect(self.insert_preset_investigation)
815 layout_investigation2.addWidget(self.input_investigation_preset, 5)
816 layout_investigation2.addWidget(input_investigation_preset_btn, 1)
817 self.input_investigation=QTextEdit(self)
818 layout_investigation.addWidget(label_investigation)
819 layout_investigation.addLayout(layout_investigation2)
820 layout_investigation.addWidget(self.input_investigation)
821
822 tab_medication=QWidget(self)
823 layout_medication=QVBoxLayout(tab_medication)
824 layout_medication2=QHBoxLayout()
825 label_medication=QLabel("Medication Advice")
826 label_medication.setProperty("class", "info_head")
827 self.input_medication_preset=QComboBox(self)
828 self.input_medication_preset.addItems(self.preset_medication.data.keys())
829 self.input_medication_preset.setCurrentIndex(-1)
830 self.input_medication_preset.setEditable(True)
831 self.input_medication_preset.completer().setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
832 self.input_medication_preset.completer().setFilterMode(Qt.MatchFlag.MatchContains)
833 self.input_medication_preset.setPlaceholderText("Select a preset")
834 input_medication_preset_btn=QPushButton("Insert")
835 input_medication_preset_btn.clicked.connect(self.insert_preset_medication)
836 layout_medication2.addWidget(self.input_medication_preset, 5)
837 layout_medication2.addWidget(input_medication_preset_btn, 1)
838 self.input_medication=QTextEdit(self)
839 layout_medication.addWidget(label_medication)
840 layout_medication.addLayout(layout_medication2)
841 layout_medication.addWidget(self.input_medication)
842
843 tab_additional=QWidget(self)
844 layout_additional=QVBoxLayout(tab_additional)
845 layout_additional2=QHBoxLayout()
846 label_additional=QLabel("Additional Advice")
847 label_additional.setProperty("class", "info_head")
848 self.input_additional_preset=QComboBox(self)
849 self.input_additional_preset.addItems(self.preset_additional.data.keys())
850 self.input_additional_preset.setCurrentIndex(-1)
851 self.input_additional_preset.setEditable(True)
852 self.input_additional_preset.completer().setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
853 self.input_additional_preset.completer().setFilterMode(Qt.MatchFlag.MatchContains)
854 self.input_additional_preset.setPlaceholderText("Select a preset")
855 input_additional_preset_btn=QPushButton("Insert")
856 input_additional_preset_btn.clicked.connect(self.insert_preset_additional)
857 layout_additional2.addWidget(self.input_additional_preset, 5)
858 layout_additional2.addWidget(input_additional_preset_btn, 1)
859 self.input_additional=QTextEdit(self)
860 layout_additional.addWidget(label_additional)
861 layout_additional.addLayout(layout_additional2)
862 layout_additional.addWidget(self.input_additional)
863
864 tab_certificate=QWidget(self)
865 layout_certificate=QVBoxLayout(tab_certificate)
866 layout_certificate2=QHBoxLayout()
867 label_certificate=QLabel("Certificate")
868 label_certificate.setProperty("class", "info_head")
869 self.input_certificate_preset=QComboBox(self)
870 self.input_certificate_preset.addItems(self.preset_certificate.data.keys())
871 self.input_certificate_preset.setCurrentIndex(-1)
872 self.input_certificate_preset.setEditable(True)
873 self.input_certificate_preset.completer().setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
874 self.input_certificate_preset.completer().setFilterMode(Qt.MatchFlag.MatchContains)
875 self.input_certificate_preset.setPlaceholderText("Select a preset")
876 input_certificate_preset_btn=QPushButton("Insert")
877 input_certificate_preset_btn.clicked.connect(self.insert_preset_certificate)
878 layout_certificate2.addWidget(self.input_certificate_preset, 5)
879 layout_certificate2.addWidget(input_certificate_preset_btn, 1)
880 self.input_certificate=QTextEdit(self)
881 layout_certificate.addWidget(label_certificate)
882 layout_certificate.addLayout(layout_certificate2)
883 layout_certificate.addWidget(self.input_certificate)
884
885 tab_attachment=QWidget(self)
886 layout_attachment=QVBoxLayout(tab_attachment)
887 layout_attachment2=QHBoxLayout()
888 label_attachment=QLabel("Attached files")
889 label_attachment.setProperty("class", "info_head")
890 self.input_attachment=QListWidget(self)
891 button_add=QPushButton("Add")
892 button_add.clicked.connect(self.add_attachment)
893 button_remove=QPushButton("Remove")
894 button_remove.clicked.connect(self.remove_attachment)
895 button_save=QPushButton("Save")
896 button_save.clicked.connect(self.save_attachment)
897 layout_attachment.addWidget(label_attachment)
898 layout_attachment.addLayout(layout_attachment2)
899 layout_attachment.addWidget(self.input_attachment)
900 layout_attachment2.addWidget(button_add)
901 layout_attachment2.addWidget(button_remove)
902 layout_attachment2.addWidget(button_save)
903
904 tab_custom=QWidget(self)
905 layout_custom=QVBoxLayout(tab_custom)
906 self.input_custom=CustomForm()
907 layout_custom.addWidget(self.input_custom)
908
909 tab=QTabWidget(self)
910 tab.addTab(tab_info, "Patient")
911 tab.addTab(tab_note, "Clinical")
912 tab.addTab(tab_report, "Report")
913 tab.addTab(tab_advice, "Advice")
914 tab.addTab(tab_investigation, "Investigation")
915 tab.addTab(tab_medication, "Medication")
916 tab.addTab(tab_additional, "Additional")
917 tab.addTab(tab_certificate, "Certificate")
918 if(config["enable_form"]):
919 tab.addTab(tab_custom, "Custom")
920 else:
921 tab_custom.hide()
922 tab.addTab(tab_attachment, "Attachment")
923
924 self.setCentralWidget(tab)
925
926 self.statusbar=QStatusBar()
927 self.setStatusBar(self.statusbar)
928
929 self.renderbox=RenderBox()
930 self.unrenderbox=UnrenderBox()
931 self.signal_view.connect(self.renderbox.update)
932 self.editConfiguration=EditConfiguration()
933 self.editPrescriber=EditPrescriber()
934 self.editPrescriber.signal_save.connect(self.change_prescriber)
935 self.selectPrescriber=SelectPrescriber()
936 self.selectPrescriber.signal_edit.connect(self.edit_prescriber)
937 self.selectPrescriber.signal_select.connect(self.change_prescriber)
938 self.viewbox=ViewBox()
939 self.index=Index()
940 self.editPreset=EditPreset()
941 self.editPreset.presetEdited.connect(self.reload_presets)
942 self.installer=Installer()
943 self.index.signal_open.connect(self.cmd_open)
944 self.index.signal_copy.connect(self.cmd_copy)
945 self.signal_update.connect(self.show_update)
946
947 self.new_doc()
948 if(config["filename"]):
949 self.cmd_open(config["filename"])
950
951 if(len(self.prescription.prescriber.name.strip())<1):
952 self.edit_prescriber()
953
954 if(config["check_update"]):
955 threading.Thread(target=self.cmd_update, args=[True]).start()
956
957
958 self.setWindowIcon(QIcon(os.path.join(config["resource"], "icon_medscript.ico")))
959 self.showMaximized()