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