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/>.
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
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
31 class MainWindow(QMainWindow
):
33 signal_view
=pyqtSignal(str)
34 signal_update
=pyqtSignal(str)
36 current_file
=FileHandler()
37 prescription
=Prescription()
40 save_state
=md5("".encode()).hexdigest()
44 if(self
.confirm_close()):
47 def cmd_open(self
, file=None):
48 if(self
.confirm_close()):
50 self
.current_file
.reset()
52 self
.current_file
.set_file(file)
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()
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.")
67 def cmd_copy(self
, data
):
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()
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>"):
81 template
=self
.input_template
.currentText()
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?")):
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"))
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.")
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)
114 def cmd_refresh(self
):
115 self
.update_instance()
116 self
.plugin
.refresh(self
.prescription
)
117 self
.load_interface_from_instance()
121 if(self
.confirm_close()):
124 def cmd_render(self
):
126 if(self
.save_state
==md5(self
.prescription
.get_json().encode()).hexdigest()):
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
:
133 QMessageBox
.information(self
, "Save first", "Please save the file before rendering.")
136 QMessageBox
.information(self
, "Save first", "Please save the file before rendering.")
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)
145 self
.current_file
.sign()
146 #self.current_file.sign(password)
148 except FileNotFoundError
as e
:
150 QMessageBox
.information(self
, "Save first", "Please save the file before signing.")
151 except TypeError as e
:
153 QMessageBox
.information(self
, "Configure", "Please add valid key and certificate to the config file.")
154 except EVPError
as e
:
156 QMessageBox
.information(self
, "Check password", "Failed to load key. Please check if password is correct.")
157 except BIOError
as e
:
159 QMessageBox
.information(self
, "Not found", "Certifcate and/or key not found.")
160 except SMIME_Error
as e
:
162 QMessageBox
.information(self
, "Failed to load", "Failed to sign. Please check if certificate and key match.")
163 except Exception as e
:
165 QMessageBox
.information(self
, "Failed", "Failed to sign.")
166 except Exception as e
:
169 QMessageBox
.information(self
, "Save first", "Please save the file before signing.")
171 def cmd_unsign(self
):
172 self
.current_file
.delete_sign()
176 def cmd_verify(self
):
178 result
=self
.current_file
.verify()
180 QMessageBox
.critical(self
, "Verification failed", "Signature is invalid.")
182 QMessageBox
.warning(self
, "No Siganture", "No signature was found.")
185 QMessageBox
.information(self
, "Valid signature", "Valid signature found with the following information:\n"+result
)
186 except FileNotFoundError
as e
:
188 QMessageBox
.warning(self
, "No Siganture", "No signature was found.")
189 except Exception as e
:
191 QMessageBox
.warning(self
, "Failed", "Failed to verify.")
193 def cmd_tabular(self
):
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
:
200 QMessageBox
.critical(self
, "Export failed", "Failed to export the data.")
206 def cmd_configuration(self
):
207 self
.edit_configuration
.exec()
209 def cmd_prescriber(self
):
210 self
.edit_prescriber
.exec()
212 def cmd_prescriber_reload(self
, file=None):
213 self
.prescription
.reload_prescriber(file=None)
216 def cmd_switch(self
):
218 self
.prescription
.reload_prescriber(QFileDialog
.getOpenFileName(self
, "Open File", config
["prescriber_directory"], "JSON (*.json);; All Files (*)")[0])
220 except FileNotFoundError
as e
:
223 def cmd_preset(self
):
224 self
.edit_preset
.show()
227 year
=datetime
.datetime
.now().year
229 copy
="2023"+"-"+str(year
)
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
)
240 self
.viewbox
.md(os
.path
.join(real_dir
, "README"))
243 def cmd_update(self
, silent
=False):
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.")
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.")
257 def show_update(self
, message
):
258 QMessageBox
.information(self
, "Check update", message
)
260 def insert_preset_note(self
):
262 self
.input_note
.insertPlainText(self
.preset_note
.data
[self
.input_note_preset
.currentText()])
264 self
.input_note
.insertPlainText(self
.input_note_preset
.currentText())
266 self
.input_note_preset
.setCurrentIndex(-1)
267 if config
["preset_newline"]:
268 self
.input_note
.insertPlainText("\n")
270 def insert_preset_report(self
):
272 self
.input_report
.insertPlainText(self
.preset_report
.data
[self
.input_report_preset
.currentText()])
274 self
.input_report
.insertPlainText(self
.input_report_preset
.currentText())
276 self
.input_report_preset
.setCurrentIndex(-1)
277 if config
["preset_newline"]:
278 self
.input_report
.insertPlainText("\n")
280 def insert_preset_advice(self
):
282 self
.input_advice
.insertPlainText(self
.preset_advice
.data
[self
.input_advice_preset
.currentText()])
284 self
.input_advice
.insertPlainText(self
.input_advice_preset
.currentText())
286 self
.input_advice_preset
.setCurrentIndex(-1)
287 if config
["preset_newline"]:
288 self
.input_advice
.insertPlainText("\n")
290 def insert_preset_investigation(self
):
292 self
.input_investigation
.insertPlainText(self
.preset_investigation
.data
[self
.input_investigation_preset
.currentText()])
294 self
.input_investigation
.insertPlainText(self
.input_investigation_preset
.currentText())
296 self
.input_investigation_preset
.setCurrentIndex(-1)
297 if config
["preset_newline"]:
298 self
.input_investigation
.insertPlainText("\n")
300 def insert_preset_medication(self
):
302 self
.input_medication
.insertPlainText(self
.preset_medication
.data
[self
.input_medication_preset
.currentText()])
304 self
.input_medication
.insertPlainText(self
.input_medication_preset
.currentText())
306 self
.input_medication_preset
.setCurrentIndex(-1)
307 if config
["preset_newline"]:
308 self
.input_medication
.insertPlainText("\n")
310 def insert_preset_additional(self
):
312 self
.input_additional
.insertPlainText(self
.preset_additional
.data
[self
.input_additional_preset
.currentText()])
314 self
.input_additional
.insertPlainText(self
.input_additional_preset
.currentText())
316 self
.input_additional_preset
.setCurrentIndex(-1)
317 if config
["preset_newline"]:
318 self
.input_additional
.insertPlainText("\n")
320 def insert_preset_certificate(self
):
322 self
.input_certificate
.insertPlainText(self
.preset_certificate
.data
[self
.input_certificate_preset
.currentText()])
324 self
.input_certificate
.insertPlainText(self
.input_certificate_preset
.currentText())
326 self
.input_certificate_preset
.setCurrentIndex(-1)
327 if config
["preset_newline"]:
328 self
.input_certificate
.insertPlainText("\n")
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
=""):
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
)
336 d
=QDateTime
.currentDateTime()
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
))
344 self
.input_date
.setDateTime(d
)
345 self
.input_id
.setText(id)
346 self
.input_name
.setText(name
)
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
:
353 self
.input_age
.setText(age
)
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.")
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>")
383 self
.input_template
.removeItem(self
.input_template
.findText("<unchanged>"))
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
406 def update_instance(self
):
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()
429 except Exception as e
:
430 QMessageBox
.critical(self
,"Failed", "Critical failure happned. Please check console for more info.")
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()
444 self
.update_instance()
445 self
.load_interface_from_instance()
447 def add_attachment(self
):
449 new
=QFileDialog
.getOpenFileName(self
, "Open File", config
["document_directory"], "PDF (*.pdf);; Images (*.jpg, *.jpeg, *.png, *.gif);; All Files (*)")[0]
451 self
.input_attachment
.addItem(new
)
452 except Exception as e
:
453 QMessageBox
.warning(self
,"Attach failed", "Failed to attach file.")
456 def remove_attachment(self
):
457 index
=self
.input_attachment
.currentRow()
459 self
.current_file
.delete_attachment(self
.input_attachment
.item(index
).text())
460 self
.input_attachment
.takeItem(index
)
462 QMessageBox
.warning(self
, "Select item", "Please select an attachment to remove.")
464 def save_attachment(self
):
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
:
470 def load_attachment(self
, attachments
):
471 for attach
in attachments
:
472 self
.input_attachment
.addItem(attach
)
474 def toggleDobAge(self
, active
):
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)
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)
487 def confirm_close(self
):
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?"))
492 def closeEvent(self
, event
):
493 if(self
.confirm_close()):
498 def __init__(self
, *args
, **kwargs
):
499 super().__init
__(*args
, **kwargs
)
501 self
.setWindowTitle("MedScript")
502 self
.setGeometry(100, 100, 600, 400)
503 self
.setWindowIcon(QIcon(os
.path
.join(config
["resource"], "icon_medscript.ico")))
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"))
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"))
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
)
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
)
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
)
596 if(config
["enable_plugin"]):
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
:
606 menu_plugin
=menubar
.addMenu("Plugin")
607 for i
in action_plugin
:
608 menu_plugin
.addAction(i
)
610 menu_help
=menubar
.addMenu("Help")
611 menu_help
.addAction(action_update
)
612 menu_help
.addAction(action_about
)
613 menu_help
.addAction(action_help
)
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"])
628 templates
.remove(os
.path
.basename(config
["template"]))
629 templates
.insert(0, os
.path
.basename(config
["template"]))
630 except Exception as e
:
632 self
.input_template
.addItems(templates
)
633 toolbar
.addWidget(self
.input_template
)
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
)
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
)
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
)
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
)
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
)
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
)
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
)
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
)
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
)
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
)
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
)
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
)
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")
870 self
.setCentralWidget(tab
)
872 self
.statusbar
=QStatusBar()
873 self
.setStatusBar(self
.statusbar
)
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()
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
)
888 if(config
["filename"]):
889 self
.cmd_open(config
["filename"])
891 if(len(self
.prescription
.prescriber
.name
.strip())<1):
892 self
.cmd_prescriber()
894 if(config
["check_update"]):
895 threading
.Thread(target
=self
.cmd_update
, args
=[True]).start()
897 self
.setWindowIcon(QIcon(os
.path
.join(config
["resource"], "icon_medscript.ico")))