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