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