]> Softwares of Agnibho - medscript.git/blob - window.py
Bugfix: Windows uninstall package permission error
[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 if(config["enable_form"] and custom is not None):
376 self.input_custom.setData(custom)
377 self.label_prescriber.setText(self.prescriber.name)
378 except Exception as e:
379 QMessageBox.warning(self,"Failed to load", "Failed to load the data into the application.")
380 logging.exception(e)
381
382 def load_interface_from_instance(self):
383 if(self.current_file.has_template()):
384 if(self.input_template.findText("<unchanged>")==-1):
385 self.input_template.addItem("<unchanged>")
386 self.input_template.setCurrentText("<unchanged>")
387 else:
388 self.input_template.removeItem(self.input_template.findText("<unchanged>"))
389 self.load_interface(
390 file=self.prescription.file,
391 date=self.prescription.date,
392 id=self.prescription.id,
393 pid=self.prescription.pid,
394 name=self.prescription.name,
395 dob=self.prescription.dob,
396 age=self.prescription.age,
397 sex=self.prescription.sex,
398 address=self.prescription.address,
399 contact=self.prescription.contact,
400 extra=self.prescription.extra,
401 mode=self.prescription.mode,
402 daw=self.prescription.daw,
403 diagnosis=self.prescription.diagnosis,
404 note=self.prescription.note,
405 report=self.prescription.report,
406 advice=self.prescription.advice,
407 investigation=self.prescription.investigation,
408 medication=self.prescription.medication,
409 additional=self.prescription.additional,
410 certificate=self.prescription.certificate,
411 custom=self.prescription.custom
412 )
413
414 def update_instance(self):
415 try:
416 self.prescription.set_data(
417 date=self.input_date.dateTime().toString("yyyy-MM-dd hh:mm:ss"),
418 id=self.input_id.text(),
419 pid=self.input_pid.text(),
420 name=self.input_name.text(),
421 dob=self.input_dob.text(),
422 age=self.input_age.text(),
423 sex=self.input_sex.currentText(),
424 address=self.input_address.text(),
425 contact=self.input_contact.text(),
426 extra=self.input_extra.toPlainText(),
427 mode=self.input_mode.currentText(),
428 daw=self.input_daw.isChecked(),
429 diagnosis=self.input_diagnosis.text(),
430 note=self.input_note.toPlainText(),
431 report=self.input_report.toPlainText(),
432 advice=self.input_advice.toPlainText(),
433 investigation=self.input_investigation.toPlainText(),
434 medication=self.input_medication.toPlainText(),
435 additional=self.input_additional.toPlainText(),
436 certificate=self.input_certificate.toPlainText(),
437 custom=self.input_custom.getData()
438 )
439 except Exception as e:
440 QMessageBox.critical(self,"Failed", "Critical failure happned. Please check console for more info.")
441 logging.error(e)
442
443 def new_doc(self):
444 self.current_file.reset()
445 self.prescription.set_data()
446 self.input_attachment.clear()
447 self.load_interface()
448 self.update_instance()
449 self.plugin.new(self.prescription)
450 self.load_interface_from_instance()
451 if(config["age_default"]):
452 self.btnAge.click()
453 self.update_instance()
454 self.save_state=md5(self.prescription.get_json().encode()).hexdigest()
455
456 def change_prescriber(self, file):
457 self.prescription.reload_prescriber(file)
458 self.prescriber.read_from(file)
459 self.refresh()
460
461 def edit_prescriber(self, file=None):
462 self.editPrescriber.load(file)
463 self.editPrescriber.exec()
464
465 def refresh(self):
466 self.update_instance()
467 self.plugin.refresh(self.prescription)
468 self.load_interface_from_instance()
469
470 def add_attachment(self):
471 try:
472 new=QFileDialog.getOpenFileName(self, "Open File", config["document_directory"], "PDF (*.pdf);; Images (*.jpg, *.jpeg, *.png, *.gif);; All Files (*)")[0]
473 if new:
474 self.input_attachment.addItem(new)
475 except Exception as e:
476 QMessageBox.warning(self,"Attach failed", "Failed to attach file.")
477 logging.exception(e)
478
479 def remove_attachment(self):
480 index=self.input_attachment.currentRow()
481 if(index>=0):
482 self.current_file.delete_attachment(self.input_attachment.item(index).text())
483 self.input_attachment.takeItem(index)
484 else:
485 QMessageBox.warning(self, "Select item", "Please select an attachment to remove.")
486
487 def save_attachment(self):
488 try:
489 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])
490 except Exception as e:
491 logging.exception(e)
492
493 def load_attachment(self, attachments):
494 for attach in attachments:
495 self.input_attachment.addItem(attach)
496
497 def toggleDobAge(self, active):
498 if active=="age":
499 self.input_dob.setDate(QDate(0,0,0))
500 self.input_dob.setDisplayFormat("yy")
501 self.input_dob.setEnabled(False)
502 self.input_age.setEnabled(True)
503 elif active=="dob":
504 self.input_dob.setDisplayFormat("MMMM dd, yyyy")
505 self.input_dob.setEnabled(True)
506 self.input_age.setText("")
507 self.input_age.setEnabled(False)
508
509 def load_presets(self):
510 self.preset_note=Preset("note")
511 self.preset_report=Preset("report")
512 self.preset_advice=Preset("advice")
513 self.preset_investigation=Preset("investigation")
514 self.preset_medication=Preset("medication", text_as_key=True)
515 self.preset_additional=Preset("additional")
516 self.preset_certificate=Preset("certificate")
517
518 def reload_presets(self):
519 self.load_presets()
520 self.input_note_preset.addItems(self.preset_note.data.keys())
521 self.input_report_preset.addItems(self.preset_note.data.keys())
522 self.input_advice_preset.addItems(self.preset_note.data.keys())
523 self.input_investigation_preset.addItems(self.preset_note.data.keys())
524 self.input_medication_preset.addItems(self.preset_note.data.keys())
525 self.input_additional_preset.addItems(self.preset_note.data.keys())
526 self.input_certificate_preset.addItems(self.preset_note.data.keys())
527
528 def confirm_close(self):
529 self.update_instance()
530 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?"))
531 return flag
532
533 def closeEvent(self, event):
534 if(self.confirm_close()):
535 event.accept()
536 else:
537 event.ignore()
538
539 def __init__(self, *args, **kwargs):
540 super().__init__(*args, **kwargs)
541
542 self.setWindowTitle("MedScript")
543 self.setGeometry(100, 100, 600, 400)
544 self.setWindowIcon(QIcon(os.path.join(config["resource"], "icon_medscript.ico")))
545
546 icon_index=QIcon(os.path.join(config["resource"], "icon_index.svg"))
547 icon_open=QIcon(os.path.join(config["resource"], "icon_open.svg"))
548 icon_save=QIcon(os.path.join(config["resource"], "icon_save.svg"))
549 icon_render=QIcon(os.path.join(config["resource"], "icon_render.svg"))
550 icon_refresh=QIcon(os.path.join(config["resource"], "icon_refresh.svg"))
551 icon_view=QIcon(os.path.join(config["resource"], "icon_view.svg"))
552
553 self.load_presets()
554
555 action_new=QAction("New File", self)
556 action_new.setShortcut("Ctrl+N")
557 action_new.triggered.connect(self.cmd_new)
558 action_open=QAction("Open File", self)
559 action_open2=QAction(icon_open, "Open", self)
560 action_open.setShortcut("Ctrl+O")
561 action_open.triggered.connect(self.cmd_open)
562 action_open2.triggered.connect(self.cmd_open)
563 action_save=QAction("Save File", self)
564 action_save2=QAction(icon_save, "Save", self)
565 action_save.setShortcut("Ctrl+S")
566 action_save.triggered.connect(self.cmd_save)
567 action_save2.triggered.connect(self.cmd_save)
568 action_save_as=QAction("Save As", self)
569 action_save_as.setShortcut("Ctrl+Shift+S")
570 action_save_as.triggered.connect(self.cmd_save_as)
571 action_refresh=QAction("Refresh Interface", self)
572 action_refresh.setShortcut("F5")
573 action_refresh2=QAction(icon_refresh, "Refresh", self)
574 action_refresh.triggered.connect(self.cmd_refresh)
575 action_refresh2.triggered.connect(self.cmd_refresh)
576 action_quit=QAction("Quit MedScript", self)
577 action_quit.setShortcut("Ctrl+Q")
578 action_quit.triggered.connect(self.cmd_quit)
579 action_render=QAction("Render Prescription", self)
580 action_render.setShortcut("Ctrl+R")
581 action_render2=QAction(icon_render, "Render", self)
582 action_render.triggered.connect(self.cmd_render)
583 action_render2.triggered.connect(self.cmd_render)
584 action_unrender=QAction("Quick Display", self)
585 action_unrender.setShortcut("Ctrl+D")
586 action_unrender2=QAction(icon_view, "Display", self)
587 action_unrender.triggered.connect(self.cmd_unrender)
588 action_unrender2.triggered.connect(self.cmd_unrender)
589 action_sign=QAction("Sign Prescription", self)
590 action_sign.triggered.connect(self.cmd_sign)
591 action_unsign=QAction("Delete Signature", self)
592 action_unsign.triggered.connect(self.cmd_unsign)
593 action_verify=QAction("Verify Signature", self)
594 action_verify.triggered.connect(self.cmd_verify)
595 action_configuration=QAction("Edit Configuration", self)
596 action_configuration.triggered.connect(self.cmd_configuration)
597 action_preset=QAction("Edit Presets", self)
598 action_preset.triggered.connect(self.cmd_preset)
599 action_prescriber=QAction("Select Prescriber", self)
600 action_prescriber.triggered.connect(self.cmd_prescriber)
601 action_installer=QAction("Package Installer", self)
602 action_installer.triggered.connect(self.cmd_installer)
603 action_index=QAction("Show Index", self)
604 action_index.triggered.connect(self.cmd_index)
605 action_index.setShortcut("Ctrl+I")
606 action_index2=QAction(icon_index, "Index", self)
607 action_index2.triggered.connect(self.cmd_index)
608 action_update=QAction("Check Update", self)
609 action_update.triggered.connect(self.cmd_update)
610 action_about=QAction("About MedScript", self)
611 action_about.triggered.connect(self.cmd_about)
612 action_help=QAction("Show Help", self)
613 action_help.setShortcut("F1")
614 action_help.triggered.connect(self.cmd_help)
615
616 menubar=self.menuBar()
617 menu_file=menubar.addMenu("File")
618 menu_file.addAction(action_index)
619 menu_file.addAction(action_new)
620 menu_file.addAction(action_open)
621 menu_file.addAction(action_save)
622 menu_file.addAction(action_save_as)
623 menu_file.addAction(action_quit)
624 menu_prepare=menubar.addMenu("Process")
625 menu_prepare.addAction(action_unrender)
626 menu_prepare.addAction(action_render)
627 menu_prepare.addAction(action_refresh)
628 if(config["smime"]):
629 menu_prepare.addAction(action_sign)
630 menu_prepare.addAction(action_unsign)
631 menu_prepare.addAction(action_verify)
632 menu_settings=menubar.addMenu("Settings")
633 menu_settings.addAction(action_configuration)
634 menu_settings.addAction(action_preset)
635 menu_settings.addAction(action_prescriber)
636 menu_settings.addAction(action_installer)
637
638 if(config["enable_plugin"]):
639 action_plugin=[]
640 try:
641 for i in self.plugin.commands():
642 action_plugin.append(QAction(i[1], self))
643 action_plugin[-1].triggered.connect(self.update_instance)
644 action_plugin[-1].triggered.connect(partial(self.plugin.run, i[0], self.prescription))
645 action_plugin[-1].triggered.connect(self.load_interface_from_instance)
646 except Exception as e:
647 logging.exception(e)
648 menu_plugin=menubar.addMenu("Plugin")
649 for i in action_plugin:
650 menu_plugin.addAction(i)
651
652 menu_help=menubar.addMenu("Help")
653 menu_help.addAction(action_update)
654 menu_help.addAction(action_about)
655 menu_help.addAction(action_help)
656
657 toolbar=QToolBar("Main Toolbar", floatable=False, movable=False)
658 toolbar.setIconSize(QSize(16, 16))
659 toolbar.addAction(action_index2)
660 toolbar.addAction(action_open2)
661 toolbar.addAction(action_save2)
662 toolbar.addAction(action_refresh2)
663 toolbar.addAction(action_unrender2)
664 toolbar.addAction(action_render2)
665 toolbar.addSeparator()
666 label_template=QLabel("Template:")
667 toolbar.addWidget(label_template)
668 self.input_template=QComboBox(self)
669 self.input_template.setMinimumWidth(200)
670 templates=os.listdir(config["template_directory"])
671 try:
672 templates.remove(os.path.basename(config["template"]))
673 templates.insert(0, os.path.basename(config["template"]))
674 except Exception as e:
675 logging.exception(e)
676 self.input_template.addItems(templates)
677 toolbar.addWidget(self.input_template)
678 spacer=QWidget(self)
679 spacer.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
680 toolbar.addWidget(spacer)
681 self.label_prescriber=QLabel(self)
682 toolbar.addWidget(self.label_prescriber)
683 self.addToolBar(toolbar)
684
685 tab_info=QWidget(self)
686 layout_info=QFormLayout(tab_info)
687 layout_info2=QHBoxLayout()
688 self.input_date=QDateTimeEdit(self)
689 self.input_date.setDisplayFormat("MMMM dd, yyyy hh:mm a")
690 self.input_date.setCalendarPopup(True)
691 self.input_date.setCalendarWidget(QCalendarWidget())
692 layout_info.addRow("Date", self.input_date)
693 self.input_id=QLineEdit(self)
694 layout_info.addRow("Prescription ID", self.input_id)
695 self.input_pid=QLineEdit(self)
696 layout_info.addRow("Patient ID", self.input_pid)
697 self.input_name=QLineEdit(self)
698 layout_info.addRow("Name", self.input_name)
699
700 self.input_dob=QDateEdit(self)
701 self.input_dob.setCalendarPopup(True)
702 self.input_dob.setCalendarWidget(QCalendarWidget())
703 self.input_dob.setEnabled(False)
704 layout_dobAge=QHBoxLayout()
705 dobAge=QButtonGroup()
706 self.btnDob=QRadioButton("Date of Birth")
707 self.btnAge=QRadioButton("Age")
708 dobAge.addButton(self.btnDob)
709 dobAge.addButton(self.btnAge)
710 layout_dobAge.addWidget(self.btnDob)
711 layout_dobAge.addWidget(self.btnAge)
712 self.btnDob.clicked.connect(lambda: self.toggleDobAge("dob"))
713 self.btnAge.clicked.connect(lambda: self.toggleDobAge("age"))
714 layout_info.addRow("", layout_dobAge)
715 layout_info.addRow("Date of Birth", self.input_dob)
716 self.input_age=QLineEdit(self)
717 layout_info.addRow("Age", self.input_age)
718 self.input_sex=QComboBox(self)
719
720 self.input_sex.addItems(["Male", "Female", "Other"])
721 self.input_sex.setEditable(True)
722 layout_info.addRow("Sex", self.input_sex)
723 self.input_address=QLineEdit(self)
724 layout_info.addRow("Address", self.input_address)
725 self.input_contact=QLineEdit(self)
726 layout_info.addRow("Contact", self.input_contact)
727 self.input_diagnosis=QLineEdit(self)
728 layout_info.addRow("Diagnosis", self.input_diagnosis)
729 self.input_extra=QTextEdit(self)
730 input_extra_preset_btn=QPushButton("Insert")
731 layout_info.addRow("Extra", self.input_extra)
732 self.input_mode=QComboBox(self)
733 self.input_mode.addItems(["In-Person", "Tele-Consultation", "Other"])
734 self.input_mode.setEditable(True)
735 layout_info.addRow("Mode", self.input_mode)
736 self.input_daw=QCheckBox("Dispense as written", self)
737 layout_info.addRow("DAW", self.input_daw)
738
739 tab_note=QWidget(self)
740 layout_note=QVBoxLayout(tab_note)
741 layout_note2=QHBoxLayout()
742 label_note=QLabel("Clinical Notes")
743 label_note.setProperty("class", "info_head")
744 self.input_note_preset=QComboBox(self)
745 self.input_note_preset.addItems(self.preset_note.data.keys())
746 self.input_note_preset.setCurrentIndex(-1)
747 self.input_note_preset.setEditable(True)
748 self.input_note_preset.completer().setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
749 self.input_note_preset.completer().setFilterMode(Qt.MatchFlag.MatchContains)
750 self.input_note_preset.setPlaceholderText("Select a preset")
751 input_note_preset_btn=QPushButton("Insert")
752 input_note_preset_btn.clicked.connect(self.insert_preset_note)
753 layout_note2.addWidget(self.input_note_preset, 5)
754 layout_note2.addWidget(input_note_preset_btn, 1)
755 self.input_note=QTextEdit(self)
756 layout_note.addWidget(label_note)
757 layout_note.addLayout(layout_note2)
758 layout_note.addWidget(self.input_note)
759
760 tab_report=QWidget(self)
761 layout_report=QVBoxLayout(tab_report)
762 layout_report2=QHBoxLayout()
763 label_report=QLabel("Available Reports")
764 label_report.setProperty("class", "info_head")
765 self.input_report_preset=QComboBox(self)
766 self.input_report_preset.addItems(self.preset_report.data.keys())
767 self.input_report_preset.setCurrentIndex(-1)
768 self.input_report_preset.setEditable(True)
769 self.input_report_preset.completer().setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
770 self.input_report_preset.completer().setFilterMode(Qt.MatchFlag.MatchContains)
771 self.input_report_preset.setPlaceholderText("Select a preset")
772 input_report_preset_btn=QPushButton("Insert")
773 input_report_preset_btn.clicked.connect(self.insert_preset_report)
774 layout_report2.addWidget(self.input_report_preset, 5)
775 layout_report2.addWidget(input_report_preset_btn, 1)
776 self.input_report=QTextEdit(self)
777 layout_report.addWidget(label_report)
778 layout_report.addLayout(layout_report2)
779 layout_report.addWidget(self.input_report)
780
781 tab_advice=QWidget(self)
782 layout_advice=QVBoxLayout(tab_advice)
783 layout_advice2=QHBoxLayout()
784 label_advice=QLabel("Advice")
785 label_advice.setProperty("class", "info_head")
786 self.input_advice_preset=QComboBox(self)
787 self.input_advice_preset.addItems(self.preset_advice.data.keys())
788 self.input_advice_preset.setCurrentIndex(-1)
789 self.input_advice_preset.setEditable(True)
790 self.input_advice_preset.completer().setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
791 self.input_advice_preset.completer().setFilterMode(Qt.MatchFlag.MatchContains)
792 self.input_advice_preset.setPlaceholderText("Select a preset")
793 input_advice_preset_btn=QPushButton("Insert")
794 input_advice_preset_btn.clicked.connect(self.insert_preset_advice)
795 layout_advice2.addWidget(self.input_advice_preset, 5)
796 layout_advice2.addWidget(input_advice_preset_btn, 1)
797 self.input_advice=QTextEdit(self)
798 layout_advice.addWidget(label_advice)
799 layout_advice.addLayout(layout_advice2)
800 layout_advice.addWidget(self.input_advice)
801
802 tab_investigation=QWidget(self)
803 layout_investigation=QVBoxLayout(tab_investigation)
804 layout_investigation2=QHBoxLayout()
805 label_investigation=QLabel("Recommended Investigations")
806 label_investigation.setProperty("class", "info_head")
807 self.input_investigation_preset=QComboBox(self)
808 self.input_investigation_preset.addItems(self.preset_investigation.data.keys())
809 self.input_investigation_preset.setCurrentIndex(-1)
810 self.input_investigation_preset.setEditable(True)
811 self.input_investigation_preset.completer().setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
812 self.input_investigation_preset.completer().setFilterMode(Qt.MatchFlag.MatchContains)
813 self.input_investigation_preset.setPlaceholderText("Select a preset")
814 input_investigation_preset_btn=QPushButton("Insert")
815 input_investigation_preset_btn.clicked.connect(self.insert_preset_investigation)
816 layout_investigation2.addWidget(self.input_investigation_preset, 5)
817 layout_investigation2.addWidget(input_investigation_preset_btn, 1)
818 self.input_investigation=QTextEdit(self)
819 layout_investigation.addWidget(label_investigation)
820 layout_investigation.addLayout(layout_investigation2)
821 layout_investigation.addWidget(self.input_investigation)
822
823 tab_medication=QWidget(self)
824 layout_medication=QVBoxLayout(tab_medication)
825 layout_medication2=QHBoxLayout()
826 label_medication=QLabel("Medication Advice")
827 label_medication.setProperty("class", "info_head")
828 self.input_medication_preset=QComboBox(self)
829 self.input_medication_preset.addItems(self.preset_medication.data.keys())
830 self.input_medication_preset.setCurrentIndex(-1)
831 self.input_medication_preset.setEditable(True)
832 self.input_medication_preset.completer().setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
833 self.input_medication_preset.completer().setFilterMode(Qt.MatchFlag.MatchContains)
834 self.input_medication_preset.setPlaceholderText("Select a preset")
835 input_medication_preset_btn=QPushButton("Insert")
836 input_medication_preset_btn.clicked.connect(self.insert_preset_medication)
837 layout_medication2.addWidget(self.input_medication_preset, 5)
838 layout_medication2.addWidget(input_medication_preset_btn, 1)
839 self.input_medication=QTextEdit(self)
840 layout_medication.addWidget(label_medication)
841 layout_medication.addLayout(layout_medication2)
842 layout_medication.addWidget(self.input_medication)
843
844 tab_additional=QWidget(self)
845 layout_additional=QVBoxLayout(tab_additional)
846 layout_additional2=QHBoxLayout()
847 label_additional=QLabel("Additional Advice")
848 label_additional.setProperty("class", "info_head")
849 self.input_additional_preset=QComboBox(self)
850 self.input_additional_preset.addItems(self.preset_additional.data.keys())
851 self.input_additional_preset.setCurrentIndex(-1)
852 self.input_additional_preset.setEditable(True)
853 self.input_additional_preset.completer().setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
854 self.input_additional_preset.completer().setFilterMode(Qt.MatchFlag.MatchContains)
855 self.input_additional_preset.setPlaceholderText("Select a preset")
856 input_additional_preset_btn=QPushButton("Insert")
857 input_additional_preset_btn.clicked.connect(self.insert_preset_additional)
858 layout_additional2.addWidget(self.input_additional_preset, 5)
859 layout_additional2.addWidget(input_additional_preset_btn, 1)
860 self.input_additional=QTextEdit(self)
861 layout_additional.addWidget(label_additional)
862 layout_additional.addLayout(layout_additional2)
863 layout_additional.addWidget(self.input_additional)
864
865 tab_certificate=QWidget(self)
866 layout_certificate=QVBoxLayout(tab_certificate)
867 layout_certificate2=QHBoxLayout()
868 label_certificate=QLabel("Certificate")
869 label_certificate.setProperty("class", "info_head")
870 self.input_certificate_preset=QComboBox(self)
871 self.input_certificate_preset.addItems(self.preset_certificate.data.keys())
872 self.input_certificate_preset.setCurrentIndex(-1)
873 self.input_certificate_preset.setEditable(True)
874 self.input_certificate_preset.completer().setCompletionMode(QCompleter.CompletionMode.PopupCompletion)
875 self.input_certificate_preset.completer().setFilterMode(Qt.MatchFlag.MatchContains)
876 self.input_certificate_preset.setPlaceholderText("Select a preset")
877 input_certificate_preset_btn=QPushButton("Insert")
878 input_certificate_preset_btn.clicked.connect(self.insert_preset_certificate)
879 layout_certificate2.addWidget(self.input_certificate_preset, 5)
880 layout_certificate2.addWidget(input_certificate_preset_btn, 1)
881 self.input_certificate=QTextEdit(self)
882 layout_certificate.addWidget(label_certificate)
883 layout_certificate.addLayout(layout_certificate2)
884 layout_certificate.addWidget(self.input_certificate)
885
886 tab_attachment=QWidget(self)
887 layout_attachment=QVBoxLayout(tab_attachment)
888 layout_attachment2=QHBoxLayout()
889 label_attachment=QLabel("Attached files")
890 label_attachment.setProperty("class", "info_head")
891 self.input_attachment=QListWidget(self)
892 button_add=QPushButton("Add")
893 button_add.clicked.connect(self.add_attachment)
894 button_remove=QPushButton("Remove")
895 button_remove.clicked.connect(self.remove_attachment)
896 button_save=QPushButton("Save")
897 button_save.clicked.connect(self.save_attachment)
898 layout_attachment.addWidget(label_attachment)
899 layout_attachment.addLayout(layout_attachment2)
900 layout_attachment.addWidget(self.input_attachment)
901 layout_attachment2.addWidget(button_add)
902 layout_attachment2.addWidget(button_remove)
903 layout_attachment2.addWidget(button_save)
904
905 tab_custom=QWidget(self)
906 layout_custom=QVBoxLayout(tab_custom)
907 self.input_custom=CustomForm()
908 layout_custom.addWidget(self.input_custom)
909
910 tab=QTabWidget(self)
911 tab.addTab(tab_info, "Patient")
912 tab.addTab(tab_note, "Clinical")
913 tab.addTab(tab_report, "Report")
914 tab.addTab(tab_advice, "Advice")
915 tab.addTab(tab_investigation, "Investigation")
916 tab.addTab(tab_medication, "Medication")
917 tab.addTab(tab_additional, "Additional")
918 tab.addTab(tab_certificate, "Certificate")
919 if(config["enable_form"]):
920 tab.addTab(tab_custom, "Custom")
921 else:
922 tab_custom.hide()
923 tab.addTab(tab_attachment, "Attachment")
924
925 self.setCentralWidget(tab)
926
927 self.statusbar=QStatusBar()
928 self.setStatusBar(self.statusbar)
929
930 self.renderbox=RenderBox()
931 self.unrenderbox=UnrenderBox()
932 self.signal_view.connect(self.renderbox.update)
933 self.editConfiguration=EditConfiguration()
934 self.editPrescriber=EditPrescriber()
935 self.editPrescriber.signal_save.connect(self.change_prescriber)
936 self.selectPrescriber=SelectPrescriber()
937 self.selectPrescriber.signal_edit.connect(self.edit_prescriber)
938 self.selectPrescriber.signal_select.connect(self.change_prescriber)
939 self.viewbox=ViewBox()
940 self.index=Index()
941 self.editPreset=EditPreset()
942 self.editPreset.presetEdited.connect(self.reload_presets)
943 self.installer=Installer()
944 self.index.signal_open.connect(self.cmd_open)
945 self.index.signal_copy.connect(self.cmd_copy)
946 self.plugin.update.connect(lambda: self.load_interface_from_instance())
947 self.signal_update.connect(self.show_update)
948
949 self.new_doc()
950 if(config["filename"]):
951 self.cmd_open(config["filename"])
952
953 if(len(self.prescription.prescriber.name.strip())<1):
954 self.edit_prescriber()
955
956 if(config["check_update"]):
957 threading.Thread(target=self.cmd_update, args=[True]).start()
958
959
960 self.setWindowIcon(QIcon(os.path.join(config["resource"], "icon_medscript.ico")))
961 self.showMaximized()