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
, copy
, threading
9 from PyQt6
.QtCore
import Qt
, QDateTime
, QDate
, QSize
, pyqtSignal
10 from PyQt6
.QtWidgets
import QWidget
, QMainWindow
, QMessageBox
, QLabel
, QPushButton
, QLineEdit
, QTextEdit
, QDateTimeEdit
, QDateEdit
, QCalendarWidget
, QListWidget
, QComboBox
, QCheckBox
, QRadioButton
, QButtonGroup
, QVBoxLayout
, QHBoxLayout
, QFormLayout
, QToolBar
, QTabWidget
, QStatusBar
, QFileDialog
, QInputDialog
, QCompleter
, QSizePolicy
11 from PyQt6
.QtGui
import QAction
, QIcon
12 from pathlib
import Path
13 from hashlib
import md5
14 from urllib
import request
15 from packaging
import version
16 from functools
import partial
18 from config
import config
, info
, real_dir
19 from prescription
import Prescription
, Prescriber
20 from renderer
import Renderer
21 from filehandler
import FileHandler
22 from renderbox
import RenderBox
, UnrenderBox
23 from setting
import EditConfiguration
, EditPrescriber
, SelectPrescriber
24 from editpreset
import EditPreset
25 from viewbox
import ViewBox
26 from preset
import Preset
27 from index
import Index
28 from customform
import CustomForm
29 from plugin
import Plugin
30 from installer
import Installer
32 class MainWindow(QMainWindow
):
34 signal_view
=pyqtSignal(str)
35 signal_update
=pyqtSignal(str)
37 current_file
=FileHandler()
38 prescription
=Prescription()
39 prescriber
=Prescriber()
42 save_state
=md5("".encode()).hexdigest()
46 if(self
.confirm_close()):
49 def cmd_open(self
, file=None):
50 if(self
.confirm_close()):
52 self
.current_file
.reset()
54 self
.current_file
.set_file(file)
56 self
.current_file
.set_file(QFileDialog
.getOpenFileName(self
, "Open File", config
["document_directory"], "Prescriptions (*.mpaz);; All Files (*)")[0])
57 self
.current_file
.open()
58 self
.prescription
.read_from(os
.path
.join(self
.current_file
.directory
.name
,"prescription.json"))
59 self
.plugin
.open(self
.prescription
)
60 self
.load_interface_from_instance()
61 self
.update_instance()
62 self
.save_state
=md5(self
.prescription
.get_json().encode()).hexdigest()
63 self
.load_attachment(self
.current_file
.list())
64 self
.unchanged_state
=True
65 except FileNotFoundError
as e
:
67 except Exception as e
:
68 QMessageBox
.warning(self
,"Open failed", "Failed to open file.")
71 def cmd_copy(self
, data
):
73 self
.prescription
.set_data_from_json(data
)
74 self
.prescription
.id=""
75 self
.prescription
.date
=None
76 self
.load_interface_from_instance()
79 def cmd_save(self
, save_as
=False):
80 self
.update_instance()
81 self
.plugin
.save(self
.prescription
)
82 if(self
.input_template
.currentText()!="<unchanged>"):
84 template
=self
.input_template
.currentText()
87 self
.load_interface_from_instance()
88 suggest
=self
.prescription
.id if(self
.prescription
.id) else self
.prescription
.name
89 suggest
=os
.path
.abspath(os
.path
.join(config
["document_directory"], suggest
)+".mpaz")
90 if(save_as
or not self
.unchanged_state
or QMessageBox
.StandardButton
.Yes
==QMessageBox
.question(self
,"Confirm change", "Modify the original file?")):
92 if not os
.path
.exists(self
.current_file
.file):
93 filename
=QFileDialog
.getSaveFileName(self
, "Save File", suggest
, "Prescriptions (*.mpaz);; All Files (*)")[0]
96 if(not filename
.endswith(".mpaz")):
97 filename
=filename
+".mpaz"
98 self
.current_file
.set_file(filename
)
99 for i
in range(self
.input_attachment
.count()):
100 self
.current_file
.copy(self
.input_attachment
.item(i
).text())
101 if(self
.prescription
.prescriber
.get_json()!=self
.prescriber
.get_json()):
102 if(QMessageBox
.StandardButton
.Yes
==QMessageBox
.question(self
,"Change Prescriber", "Original Prescriber: "+self
.prescription
.prescriber
.name
+"\nCurrent Prescriber: "+self
.prescriber
.name
+"\nReplace original with current?")):
103 self
.prescription
.prescriber
=copy
.deepcopy(self
.prescriber
)
104 self
.prescription
.write_to(os
.path
.join(self
.current_file
.directory
.name
, "prescription.json"))
106 config
["template"]=os
.path
.join(config
["template_directory"], template
)
107 self
.current_file
.save(change_template
=change_template
)
108 self
.unchanged_state
=False
109 self
.load_interface_from_instance()
110 self
.save_state
=md5(self
.prescription
.get_json().encode()).hexdigest()
111 except Exception as e
:
112 QMessageBox
.warning(self
,"Save failed", "Failed to save file.")
115 def cmd_save_as(self
):
116 suggest
=self
.prescription
.id if(self
.prescription
.id) else self
.prescription
.name
117 suggest
=os
.path
.abspath(os
.path
.join(config
["document_directory"], suggest
)+".mpaz")
118 filename
=QFileDialog
.getSaveFileName(self
, "Save File", suggest
, "Prescriptions (*.mpaz);; All Files (*)")[0]
120 if not filename
.endswith(".mpaz"):
121 filename
=filename
+".mpaz"
122 self
.current_file
.set_file(filename
)
123 Path(self
.current_file
.file).touch()
124 self
.cmd_save(save_as
=True)
126 def cmd_refresh(self
):
130 if(self
.confirm_close()):
133 def cmd_unrender(self
):
134 self
.update_instance()
135 self
.unrenderbox
.show(self
.prescription
).exec()
137 def cmd_render(self
):
138 self
.update_instance()
139 if(self
.save_state
==md5(self
.prescription
.get_json().encode()).hexdigest()):
141 target
=self
.renderer
.render(self
.current_file
.directory
.name
)
142 if target
is not None:
143 self
.signal_view
.emit(target
)
144 self
.renderbox
.showMaximized()
146 QMessageBox
.critical(self
, "Render failed", "Presciption rendering failed. Please check if prescription file or template is corrupted.")
147 logging
.error("Prescription rendering failed.")
148 except FileNotFoundError
as e
:
150 QMessageBox
.information(self
, "Save first", "Please save the file before rendering.")
152 QMessageBox
.information(self
, "Save first", "Please save the file before rendering.")
155 self
.update_instance()
156 if(self
.save_state
==md5(self
.prescription
.get_json().encode()).hexdigest()):
157 ok
=True #password, ok=QInputDialog.getText(self, "Enter password", "Private key password", QLineEdit.EchoMode.Password)
161 self
.current_file
.sign()
162 #self.current_file.sign(password)
164 except FileNotFoundError
as e
:
166 QMessageBox
.information(self
, "Save first", "Please save the file before signing.")
167 except TypeError as e
:
169 QMessageBox
.information(self
, "Configure", "Please add valid key and certificate to the config file.")
170 except EVPError
as e
:
172 QMessageBox
.information(self
, "Check password", "Failed to load key. Please check if password is correct.")
173 except BIOError
as e
:
175 QMessageBox
.information(self
, "Not found", "Certifcate and/or key not found.")
176 except SMIME_Error
as e
:
178 QMessageBox
.information(self
, "Failed to load", "Failed to sign. Please check if certificate and key match.")
179 except Exception as e
:
181 QMessageBox
.information(self
, "Failed", "Failed to sign.")
182 except Exception as e
:
185 QMessageBox
.information(self
, "Save first", "Please save the file before signing.")
187 def cmd_unsign(self
):
188 self
.current_file
.delete_sign()
192 def cmd_verify(self
):
194 result
=self
.current_file
.verify()
196 QMessageBox
.critical(self
, "Verification failed", "Signature is invalid.")
198 QMessageBox
.warning(self
, "No Siganture", "No signature was found.")
201 QMessageBox
.information(self
, "Valid signature", "Valid signature found with the following information:\n"+result
)
202 except FileNotFoundError
as e
:
204 QMessageBox
.warning(self
, "No Siganture", "No signature was found.")
205 except Exception as e
:
207 QMessageBox
.warning(self
, "Failed", "Failed to verify.")
212 def cmd_configuration(self
):
213 self
.editConfiguration
.exec()
215 def cmd_prescriber(self
):
217 self
.selectPrescriber
.load()
218 self
.selectPrescriber
.exec()
219 except FileNotFoundError
as e
:
222 def cmd_preset(self
):
223 self
.editPreset
.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 if(config
["enable_form"] and custom
is not None):
376 self
.input_custom
.setData(custom
)
377 self
.label_prescriber
.setText(self
.prescriber
.name
)
378 except Exception as e
:
379 QMessageBox
.warning(self
,"Failed to load", "Failed to load the data into the application.")
382 def load_interface_from_instance(self
):
383 if(self
.current_file
.has_template()):
384 if(self
.input_template
.findText("<unchanged>")==-1):
385 self
.input_template
.addItem("<unchanged>")
386 self
.input_template
.setCurrentText("<unchanged>")
388 self
.input_template
.removeItem(self
.input_template
.findText("<unchanged>"))
390 file=self
.prescription
.file,
391 date
=self
.prescription
.date
,
392 id=self
.prescription
.id,
393 pid
=self
.prescription
.pid
,
394 name
=self
.prescription
.name
,
395 dob
=self
.prescription
.dob
,
396 age
=self
.prescription
.age
,
397 sex
=self
.prescription
.sex
,
398 address
=self
.prescription
.address
,
399 contact
=self
.prescription
.contact
,
400 extra
=self
.prescription
.extra
,
401 mode
=self
.prescription
.mode
,
402 daw
=self
.prescription
.daw
,
403 diagnosis
=self
.prescription
.diagnosis
,
404 note
=self
.prescription
.note
,
405 report
=self
.prescription
.report
,
406 advice
=self
.prescription
.advice
,
407 investigation
=self
.prescription
.investigation
,
408 medication
=self
.prescription
.medication
,
409 additional
=self
.prescription
.additional
,
410 certificate
=self
.prescription
.certificate
,
411 custom
=self
.prescription
.custom
414 def update_instance(self
):
416 self
.prescription
.set_data(
417 date
=self
.input_date
.dateTime().toString("yyyy-MM-dd hh:mm:ss"),
418 id=self
.input_id
.text(),
419 pid
=self
.input_pid
.text(),
420 name
=self
.input_name
.text(),
421 dob
=self
.input_dob
.text(),
422 age
=self
.input_age
.text(),
423 sex
=self
.input_sex
.currentText(),
424 address
=self
.input_address
.text(),
425 contact
=self
.input_contact
.text(),
426 extra
=self
.input_extra
.toPlainText(),
427 mode
=self
.input_mode
.currentText(),
428 daw
=self
.input_daw
.isChecked(),
429 diagnosis
=self
.input_diagnosis
.text(),
430 note
=self
.input_note
.toPlainText(),
431 report
=self
.input_report
.toPlainText(),
432 advice
=self
.input_advice
.toPlainText(),
433 investigation
=self
.input_investigation
.toPlainText(),
434 medication
=self
.input_medication
.toPlainText(),
435 additional
=self
.input_additional
.toPlainText(),
436 certificate
=self
.input_certificate
.toPlainText(),
437 custom
=self
.input_custom
.getData()
439 except Exception as e
:
440 QMessageBox
.critical(self
,"Failed", "Critical failure happned. Please check console for more info.")
444 self
.current_file
.reset()
445 self
.prescription
.set_data()
446 self
.input_attachment
.clear()
447 self
.load_interface()
448 self
.update_instance()
449 self
.plugin
.new(self
.prescription
)
450 self
.load_interface_from_instance()
451 if(config
["age_default"]):
453 self
.update_instance()
454 self
.save_state
=md5(self
.prescription
.get_json().encode()).hexdigest()
456 def change_prescriber(self
, file):
457 self
.prescription
.reload_prescriber(file)
458 self
.prescriber
.read_from(file)
461 def edit_prescriber(self
, file=None):
462 self
.editPrescriber
.load(file)
463 self
.editPrescriber
.exec()
466 self
.update_instance()
467 self
.plugin
.refresh(self
.prescription
)
468 self
.load_interface_from_instance()
470 def add_attachment(self
):
472 new
=QFileDialog
.getOpenFileName(self
, "Open File", config
["document_directory"], "PDF (*.pdf);; Images (*.jpg, *.jpeg, *.png, *.gif);; All Files (*)")[0]
474 self
.input_attachment
.addItem(new
)
475 except Exception as e
:
476 QMessageBox
.warning(self
,"Attach failed", "Failed to attach file.")
479 def remove_attachment(self
):
480 index
=self
.input_attachment
.currentRow()
482 self
.current_file
.delete_attachment(self
.input_attachment
.item(index
).text())
483 self
.input_attachment
.takeItem(index
)
485 QMessageBox
.warning(self
, "Select item", "Please select an attachment to remove.")
487 def save_attachment(self
):
489 shutil
.copyfile(self
.input_attachment
.currentItem().text(), QFileDialog
.getSaveFileName(self
, "Save Attachment", os
.path
.join(config
["document_directory"], os
.path
.basename(self
.input_attachment
.currentItem().text())))[0])
490 except Exception as e
:
493 def load_attachment(self
, attachments
):
494 for attach
in attachments
:
495 self
.input_attachment
.addItem(attach
)
497 def toggleDobAge(self
, active
):
499 self
.input_dob
.setDate(QDate(0,0,0))
500 self
.input_dob
.setDisplayFormat("yy")
501 self
.input_dob
.setEnabled(False)
502 self
.input_age
.setEnabled(True)
504 self
.input_dob
.setDisplayFormat("MMMM dd, yyyy")
505 self
.input_dob
.setEnabled(True)
506 self
.input_age
.setText("")
507 self
.input_age
.setEnabled(False)
509 def load_presets(self
):
510 self
.preset_note
=Preset("note")
511 self
.preset_report
=Preset("report")
512 self
.preset_advice
=Preset("advice")
513 self
.preset_investigation
=Preset("investigation")
514 self
.preset_medication
=Preset("medication", text_as_key
=True)
515 self
.preset_additional
=Preset("additional")
516 self
.preset_certificate
=Preset("certificate")
518 def reload_presets(self
):
520 self
.input_note_preset
.addItems(self
.preset_note
.data
.keys())
521 self
.input_report_preset
.addItems(self
.preset_note
.data
.keys())
522 self
.input_advice_preset
.addItems(self
.preset_note
.data
.keys())
523 self
.input_investigation_preset
.addItems(self
.preset_note
.data
.keys())
524 self
.input_medication_preset
.addItems(self
.preset_note
.data
.keys())
525 self
.input_additional_preset
.addItems(self
.preset_note
.data
.keys())
526 self
.input_certificate_preset
.addItems(self
.preset_note
.data
.keys())
528 def confirm_close(self
):
529 self
.update_instance()
530 flag
=(self
.save_state
==md5(self
.prescription
.get_json().encode()).hexdigest() or QMessageBox
.StandardButton
.Yes
==QMessageBox
.question(self
,"Confirm action", "Unsaved changes may be lost. Continue?"))
533 def closeEvent(self
, event
):
534 if(self
.confirm_close()):
539 def __init__(self
, *args
, **kwargs
):
540 super().__init
__(*args
, **kwargs
)
542 self
.setWindowTitle("MedScript")
543 self
.setGeometry(100, 100, 600, 400)
544 self
.setWindowIcon(QIcon(os
.path
.join(config
["resource"], "icon_medscript.ico")))
546 icon_index
=QIcon(os
.path
.join(config
["resource"], "icon_index.svg"))
547 icon_open
=QIcon(os
.path
.join(config
["resource"], "icon_open.svg"))
548 icon_save
=QIcon(os
.path
.join(config
["resource"], "icon_save.svg"))
549 icon_render
=QIcon(os
.path
.join(config
["resource"], "icon_render.svg"))
550 icon_refresh
=QIcon(os
.path
.join(config
["resource"], "icon_refresh.svg"))
551 icon_view
=QIcon(os
.path
.join(config
["resource"], "icon_view.svg"))
555 action_new
=QAction("New File", self
)
556 action_new
.setShortcut("Ctrl+N")
557 action_new
.triggered
.connect(self
.cmd_new
)
558 action_open
=QAction("Open File", self
)
559 action_open2
=QAction(icon_open
, "Open", self
)
560 action_open
.setShortcut("Ctrl+O")
561 action_open
.triggered
.connect(self
.cmd_open
)
562 action_open2
.triggered
.connect(self
.cmd_open
)
563 action_save
=QAction("Save File", self
)
564 action_save2
=QAction(icon_save
, "Save", self
)
565 action_save
.setShortcut("Ctrl+S")
566 action_save
.triggered
.connect(self
.cmd_save
)
567 action_save2
.triggered
.connect(self
.cmd_save
)
568 action_save_as
=QAction("Save As", self
)
569 action_save_as
.setShortcut("Ctrl+Shift+S")
570 action_save_as
.triggered
.connect(self
.cmd_save_as
)
571 action_refresh
=QAction("Refresh Interface", self
)
572 action_refresh
.setShortcut("F5")
573 action_refresh2
=QAction(icon_refresh
, "Refresh", self
)
574 action_refresh
.triggered
.connect(self
.cmd_refresh
)
575 action_refresh2
.triggered
.connect(self
.cmd_refresh
)
576 action_quit
=QAction("Quit MedScript", self
)
577 action_quit
.setShortcut("Ctrl+Q")
578 action_quit
.triggered
.connect(self
.cmd_quit
)
579 action_render
=QAction("Render Prescription", self
)
580 action_render
.setShortcut("Ctrl+R")
581 action_render2
=QAction(icon_render
, "Render", self
)
582 action_render
.triggered
.connect(self
.cmd_render
)
583 action_render2
.triggered
.connect(self
.cmd_render
)
584 action_unrender
=QAction("Quick Display", self
)
585 action_unrender
.setShortcut("Ctrl+D")
586 action_unrender2
=QAction(icon_view
, "Display", self
)
587 action_unrender
.triggered
.connect(self
.cmd_unrender
)
588 action_unrender2
.triggered
.connect(self
.cmd_unrender
)
589 action_sign
=QAction("Sign Prescription", self
)
590 action_sign
.triggered
.connect(self
.cmd_sign
)
591 action_unsign
=QAction("Delete Signature", self
)
592 action_unsign
.triggered
.connect(self
.cmd_unsign
)
593 action_verify
=QAction("Verify Signature", self
)
594 action_verify
.triggered
.connect(self
.cmd_verify
)
595 action_configuration
=QAction("Edit Configuration", self
)
596 action_configuration
.triggered
.connect(self
.cmd_configuration
)
597 action_preset
=QAction("Edit Presets", self
)
598 action_preset
.triggered
.connect(self
.cmd_preset
)
599 action_prescriber
=QAction("Select Prescriber", self
)
600 action_prescriber
.triggered
.connect(self
.cmd_prescriber
)
601 action_installer
=QAction("Package Installer", self
)
602 action_installer
.triggered
.connect(self
.cmd_installer
)
603 action_index
=QAction("Show Index", self
)
604 action_index
.triggered
.connect(self
.cmd_index
)
605 action_index
.setShortcut("Ctrl+I")
606 action_index2
=QAction(icon_index
, "Index", self
)
607 action_index2
.triggered
.connect(self
.cmd_index
)
608 action_update
=QAction("Check Update", self
)
609 action_update
.triggered
.connect(self
.cmd_update
)
610 action_about
=QAction("About MedScript", self
)
611 action_about
.triggered
.connect(self
.cmd_about
)
612 action_help
=QAction("Show Help", self
)
613 action_help
.setShortcut("F1")
614 action_help
.triggered
.connect(self
.cmd_help
)
616 menubar
=self
.menuBar()
617 menu_file
=menubar
.addMenu("File")
618 menu_file
.addAction(action_index
)
619 menu_file
.addAction(action_new
)
620 menu_file
.addAction(action_open
)
621 menu_file
.addAction(action_save
)
622 menu_file
.addAction(action_save_as
)
623 menu_file
.addAction(action_quit
)
624 menu_prepare
=menubar
.addMenu("Process")
625 menu_prepare
.addAction(action_unrender
)
626 menu_prepare
.addAction(action_render
)
627 menu_prepare
.addAction(action_refresh
)
629 menu_prepare
.addAction(action_sign
)
630 menu_prepare
.addAction(action_unsign
)
631 menu_prepare
.addAction(action_verify
)
632 menu_settings
=menubar
.addMenu("Settings")
633 menu_settings
.addAction(action_configuration
)
634 menu_settings
.addAction(action_preset
)
635 menu_settings
.addAction(action_prescriber
)
636 menu_settings
.addAction(action_installer
)
638 if(config
["enable_plugin"]):
641 for i
in self
.plugin
.commands():
642 action_plugin
.append(QAction(i
[1], self
))
643 action_plugin
[-1].triggered
.connect(self
.update_instance
)
644 action_plugin
[-1].triggered
.connect(partial(self
.plugin
.run
, i
[0], self
.prescription
))
645 action_plugin
[-1].triggered
.connect(self
.load_interface_from_instance
)
646 except Exception as e
:
648 menu_plugin
=menubar
.addMenu("Plugin")
649 for i
in action_plugin
:
650 menu_plugin
.addAction(i
)
652 menu_help
=menubar
.addMenu("Help")
653 menu_help
.addAction(action_update
)
654 menu_help
.addAction(action_about
)
655 menu_help
.addAction(action_help
)
657 toolbar
=QToolBar("Main Toolbar", floatable
=False, movable
=False)
658 toolbar
.setIconSize(QSize(16, 16))
659 toolbar
.addAction(action_index2
)
660 toolbar
.addAction(action_open2
)
661 toolbar
.addAction(action_save2
)
662 toolbar
.addAction(action_refresh2
)
663 toolbar
.addAction(action_unrender2
)
664 toolbar
.addAction(action_render2
)
665 toolbar
.addSeparator()
666 label_template
=QLabel("Template:")
667 toolbar
.addWidget(label_template
)
668 self
.input_template
=QComboBox(self
)
669 self
.input_template
.setMinimumWidth(200)
670 templates
=os
.listdir(config
["template_directory"])
672 templates
.remove(os
.path
.basename(config
["template"]))
673 templates
.insert(0, os
.path
.basename(config
["template"]))
674 except Exception as e
:
676 self
.input_template
.addItems(templates
)
677 toolbar
.addWidget(self
.input_template
)
679 spacer
.setSizePolicy(QSizePolicy
.Policy
.Expanding
, QSizePolicy
.Policy
.Expanding
)
680 toolbar
.addWidget(spacer
)
681 self
.label_prescriber
=QLabel(self
)
682 toolbar
.addWidget(self
.label_prescriber
)
683 self
.addToolBar(toolbar
)
685 tab_info
=QWidget(self
)
686 layout_info
=QFormLayout(tab_info
)
687 layout_info2
=QHBoxLayout()
688 self
.input_date
=QDateTimeEdit(self
)
689 self
.input_date
.setDisplayFormat("MMMM dd, yyyy hh:mm a")
690 self
.input_date
.setCalendarPopup(True)
691 self
.input_date
.setCalendarWidget(QCalendarWidget())
692 layout_info
.addRow("Date", self
.input_date
)
693 self
.input_id
=QLineEdit(self
)
694 layout_info
.addRow("Prescription ID", self
.input_id
)
695 self
.input_pid
=QLineEdit(self
)
696 layout_info
.addRow("Patient ID", self
.input_pid
)
697 self
.input_name
=QLineEdit(self
)
698 layout_info
.addRow("Name", self
.input_name
)
700 self
.input_dob
=QDateEdit(self
)
701 self
.input_dob
.setCalendarPopup(True)
702 self
.input_dob
.setCalendarWidget(QCalendarWidget())
703 self
.input_dob
.setEnabled(False)
704 layout_dobAge
=QHBoxLayout()
705 dobAge
=QButtonGroup()
706 self
.btnDob
=QRadioButton("Date of Birth")
707 self
.btnAge
=QRadioButton("Age")
708 dobAge
.addButton(self
.btnDob
)
709 dobAge
.addButton(self
.btnAge
)
710 layout_dobAge
.addWidget(self
.btnDob
)
711 layout_dobAge
.addWidget(self
.btnAge
)
712 self
.btnDob
.clicked
.connect(lambda: self
.toggleDobAge("dob"))
713 self
.btnAge
.clicked
.connect(lambda: self
.toggleDobAge("age"))
714 layout_info
.addRow("", layout_dobAge
)
715 layout_info
.addRow("Date of Birth", self
.input_dob
)
716 self
.input_age
=QLineEdit(self
)
717 layout_info
.addRow("Age", self
.input_age
)
718 self
.input_sex
=QComboBox(self
)
720 self
.input_sex
.addItems(["Male", "Female", "Other"])
721 self
.input_sex
.setEditable(True)
722 layout_info
.addRow("Sex", self
.input_sex
)
723 self
.input_address
=QLineEdit(self
)
724 layout_info
.addRow("Address", self
.input_address
)
725 self
.input_contact
=QLineEdit(self
)
726 layout_info
.addRow("Contact", self
.input_contact
)
727 self
.input_diagnosis
=QLineEdit(self
)
728 layout_info
.addRow("Diagnosis", self
.input_diagnosis
)
729 self
.input_extra
=QTextEdit(self
)
730 input_extra_preset_btn
=QPushButton("Insert")
731 layout_info
.addRow("Extra", self
.input_extra
)
732 self
.input_mode
=QComboBox(self
)
733 self
.input_mode
.addItems(["In-Person", "Tele-Consultation", "Other"])
734 self
.input_mode
.setEditable(True)
735 layout_info
.addRow("Mode", self
.input_mode
)
736 self
.input_daw
=QCheckBox("Dispense as written", self
)
737 layout_info
.addRow("DAW", self
.input_daw
)
739 tab_note
=QWidget(self
)
740 layout_note
=QVBoxLayout(tab_note
)
741 layout_note2
=QHBoxLayout()
742 label_note
=QLabel("Clinical Notes")
743 label_note
.setProperty("class", "info_head")
744 self
.input_note_preset
=QComboBox(self
)
745 self
.input_note_preset
.addItems(self
.preset_note
.data
.keys())
746 self
.input_note_preset
.setCurrentIndex(-1)
747 self
.input_note_preset
.setEditable(True)
748 self
.input_note_preset
.completer().setCompletionMode(QCompleter
.CompletionMode
.PopupCompletion
)
749 self
.input_note_preset
.completer().setFilterMode(Qt
.MatchFlag
.MatchContains
)
750 self
.input_note_preset
.setPlaceholderText("Select a preset")
751 input_note_preset_btn
=QPushButton("Insert")
752 input_note_preset_btn
.clicked
.connect(self
.insert_preset_note
)
753 layout_note2
.addWidget(self
.input_note_preset
, 5)
754 layout_note2
.addWidget(input_note_preset_btn
, 1)
755 self
.input_note
=QTextEdit(self
)
756 layout_note
.addWidget(label_note
)
757 layout_note
.addLayout(layout_note2
)
758 layout_note
.addWidget(self
.input_note
)
760 tab_report
=QWidget(self
)
761 layout_report
=QVBoxLayout(tab_report
)
762 layout_report2
=QHBoxLayout()
763 label_report
=QLabel("Available Reports")
764 label_report
.setProperty("class", "info_head")
765 self
.input_report_preset
=QComboBox(self
)
766 self
.input_report_preset
.addItems(self
.preset_report
.data
.keys())
767 self
.input_report_preset
.setCurrentIndex(-1)
768 self
.input_report_preset
.setEditable(True)
769 self
.input_report_preset
.completer().setCompletionMode(QCompleter
.CompletionMode
.PopupCompletion
)
770 self
.input_report_preset
.completer().setFilterMode(Qt
.MatchFlag
.MatchContains
)
771 self
.input_report_preset
.setPlaceholderText("Select a preset")
772 input_report_preset_btn
=QPushButton("Insert")
773 input_report_preset_btn
.clicked
.connect(self
.insert_preset_report
)
774 layout_report2
.addWidget(self
.input_report_preset
, 5)
775 layout_report2
.addWidget(input_report_preset_btn
, 1)
776 self
.input_report
=QTextEdit(self
)
777 layout_report
.addWidget(label_report
)
778 layout_report
.addLayout(layout_report2
)
779 layout_report
.addWidget(self
.input_report
)
781 tab_advice
=QWidget(self
)
782 layout_advice
=QVBoxLayout(tab_advice
)
783 layout_advice2
=QHBoxLayout()
784 label_advice
=QLabel("Advice")
785 label_advice
.setProperty("class", "info_head")
786 self
.input_advice_preset
=QComboBox(self
)
787 self
.input_advice_preset
.addItems(self
.preset_advice
.data
.keys())
788 self
.input_advice_preset
.setCurrentIndex(-1)
789 self
.input_advice_preset
.setEditable(True)
790 self
.input_advice_preset
.completer().setCompletionMode(QCompleter
.CompletionMode
.PopupCompletion
)
791 self
.input_advice_preset
.completer().setFilterMode(Qt
.MatchFlag
.MatchContains
)
792 self
.input_advice_preset
.setPlaceholderText("Select a preset")
793 input_advice_preset_btn
=QPushButton("Insert")
794 input_advice_preset_btn
.clicked
.connect(self
.insert_preset_advice
)
795 layout_advice2
.addWidget(self
.input_advice_preset
, 5)
796 layout_advice2
.addWidget(input_advice_preset_btn
, 1)
797 self
.input_advice
=QTextEdit(self
)
798 layout_advice
.addWidget(label_advice
)
799 layout_advice
.addLayout(layout_advice2
)
800 layout_advice
.addWidget(self
.input_advice
)
802 tab_investigation
=QWidget(self
)
803 layout_investigation
=QVBoxLayout(tab_investigation
)
804 layout_investigation2
=QHBoxLayout()
805 label_investigation
=QLabel("Recommended Investigations")
806 label_investigation
.setProperty("class", "info_head")
807 self
.input_investigation_preset
=QComboBox(self
)
808 self
.input_investigation_preset
.addItems(self
.preset_investigation
.data
.keys())
809 self
.input_investigation_preset
.setCurrentIndex(-1)
810 self
.input_investigation_preset
.setEditable(True)
811 self
.input_investigation_preset
.completer().setCompletionMode(QCompleter
.CompletionMode
.PopupCompletion
)
812 self
.input_investigation_preset
.completer().setFilterMode(Qt
.MatchFlag
.MatchContains
)
813 self
.input_investigation_preset
.setPlaceholderText("Select a preset")
814 input_investigation_preset_btn
=QPushButton("Insert")
815 input_investigation_preset_btn
.clicked
.connect(self
.insert_preset_investigation
)
816 layout_investigation2
.addWidget(self
.input_investigation_preset
, 5)
817 layout_investigation2
.addWidget(input_investigation_preset_btn
, 1)
818 self
.input_investigation
=QTextEdit(self
)
819 layout_investigation
.addWidget(label_investigation
)
820 layout_investigation
.addLayout(layout_investigation2
)
821 layout_investigation
.addWidget(self
.input_investigation
)
823 tab_medication
=QWidget(self
)
824 layout_medication
=QVBoxLayout(tab_medication
)
825 layout_medication2
=QHBoxLayout()
826 label_medication
=QLabel("Medication Advice")
827 label_medication
.setProperty("class", "info_head")
828 self
.input_medication_preset
=QComboBox(self
)
829 self
.input_medication_preset
.addItems(self
.preset_medication
.data
.keys())
830 self
.input_medication_preset
.setCurrentIndex(-1)
831 self
.input_medication_preset
.setEditable(True)
832 self
.input_medication_preset
.completer().setCompletionMode(QCompleter
.CompletionMode
.PopupCompletion
)
833 self
.input_medication_preset
.completer().setFilterMode(Qt
.MatchFlag
.MatchContains
)
834 self
.input_medication_preset
.setPlaceholderText("Select a preset")
835 input_medication_preset_btn
=QPushButton("Insert")
836 input_medication_preset_btn
.clicked
.connect(self
.insert_preset_medication
)
837 layout_medication2
.addWidget(self
.input_medication_preset
, 5)
838 layout_medication2
.addWidget(input_medication_preset_btn
, 1)
839 self
.input_medication
=QTextEdit(self
)
840 layout_medication
.addWidget(label_medication
)
841 layout_medication
.addLayout(layout_medication2
)
842 layout_medication
.addWidget(self
.input_medication
)
844 tab_additional
=QWidget(self
)
845 layout_additional
=QVBoxLayout(tab_additional
)
846 layout_additional2
=QHBoxLayout()
847 label_additional
=QLabel("Additional Advice")
848 label_additional
.setProperty("class", "info_head")
849 self
.input_additional_preset
=QComboBox(self
)
850 self
.input_additional_preset
.addItems(self
.preset_additional
.data
.keys())
851 self
.input_additional_preset
.setCurrentIndex(-1)
852 self
.input_additional_preset
.setEditable(True)
853 self
.input_additional_preset
.completer().setCompletionMode(QCompleter
.CompletionMode
.PopupCompletion
)
854 self
.input_additional_preset
.completer().setFilterMode(Qt
.MatchFlag
.MatchContains
)
855 self
.input_additional_preset
.setPlaceholderText("Select a preset")
856 input_additional_preset_btn
=QPushButton("Insert")
857 input_additional_preset_btn
.clicked
.connect(self
.insert_preset_additional
)
858 layout_additional2
.addWidget(self
.input_additional_preset
, 5)
859 layout_additional2
.addWidget(input_additional_preset_btn
, 1)
860 self
.input_additional
=QTextEdit(self
)
861 layout_additional
.addWidget(label_additional
)
862 layout_additional
.addLayout(layout_additional2
)
863 layout_additional
.addWidget(self
.input_additional
)
865 tab_certificate
=QWidget(self
)
866 layout_certificate
=QVBoxLayout(tab_certificate
)
867 layout_certificate2
=QHBoxLayout()
868 label_certificate
=QLabel("Certificate")
869 label_certificate
.setProperty("class", "info_head")
870 self
.input_certificate_preset
=QComboBox(self
)
871 self
.input_certificate_preset
.addItems(self
.preset_certificate
.data
.keys())
872 self
.input_certificate_preset
.setCurrentIndex(-1)
873 self
.input_certificate_preset
.setEditable(True)
874 self
.input_certificate_preset
.completer().setCompletionMode(QCompleter
.CompletionMode
.PopupCompletion
)
875 self
.input_certificate_preset
.completer().setFilterMode(Qt
.MatchFlag
.MatchContains
)
876 self
.input_certificate_preset
.setPlaceholderText("Select a preset")
877 input_certificate_preset_btn
=QPushButton("Insert")
878 input_certificate_preset_btn
.clicked
.connect(self
.insert_preset_certificate
)
879 layout_certificate2
.addWidget(self
.input_certificate_preset
, 5)
880 layout_certificate2
.addWidget(input_certificate_preset_btn
, 1)
881 self
.input_certificate
=QTextEdit(self
)
882 layout_certificate
.addWidget(label_certificate
)
883 layout_certificate
.addLayout(layout_certificate2
)
884 layout_certificate
.addWidget(self
.input_certificate
)
886 tab_attachment
=QWidget(self
)
887 layout_attachment
=QVBoxLayout(tab_attachment
)
888 layout_attachment2
=QHBoxLayout()
889 label_attachment
=QLabel("Attached files")
890 label_attachment
.setProperty("class", "info_head")
891 self
.input_attachment
=QListWidget(self
)
892 button_add
=QPushButton("Add")
893 button_add
.clicked
.connect(self
.add_attachment
)
894 button_remove
=QPushButton("Remove")
895 button_remove
.clicked
.connect(self
.remove_attachment
)
896 button_save
=QPushButton("Save")
897 button_save
.clicked
.connect(self
.save_attachment
)
898 layout_attachment
.addWidget(label_attachment
)
899 layout_attachment
.addLayout(layout_attachment2
)
900 layout_attachment
.addWidget(self
.input_attachment
)
901 layout_attachment2
.addWidget(button_add
)
902 layout_attachment2
.addWidget(button_remove
)
903 layout_attachment2
.addWidget(button_save
)
905 tab_custom
=QWidget(self
)
906 layout_custom
=QVBoxLayout(tab_custom
)
907 self
.input_custom
=CustomForm()
908 layout_custom
.addWidget(self
.input_custom
)
911 tab
.addTab(tab_info
, "Patient")
912 tab
.addTab(tab_note
, "Clinical")
913 tab
.addTab(tab_report
, "Report")
914 tab
.addTab(tab_advice
, "Advice")
915 tab
.addTab(tab_investigation
, "Investigation")
916 tab
.addTab(tab_medication
, "Medication")
917 tab
.addTab(tab_additional
, "Additional")
918 tab
.addTab(tab_certificate
, "Certificate")
919 if(config
["enable_form"]):
920 tab
.addTab(tab_custom
, "Custom")
923 tab
.addTab(tab_attachment
, "Attachment")
925 self
.setCentralWidget(tab
)
927 self
.statusbar
=QStatusBar()
928 self
.setStatusBar(self
.statusbar
)
930 self
.renderbox
=RenderBox()
931 self
.unrenderbox
=UnrenderBox()
932 self
.signal_view
.connect(self
.renderbox
.update
)
933 self
.editConfiguration
=EditConfiguration()
934 self
.editPrescriber
=EditPrescriber()
935 self
.editPrescriber
.signal_save
.connect(self
.change_prescriber
)
936 self
.selectPrescriber
=SelectPrescriber()
937 self
.selectPrescriber
.signal_edit
.connect(self
.edit_prescriber
)
938 self
.selectPrescriber
.signal_select
.connect(self
.change_prescriber
)
939 self
.viewbox
=ViewBox()
941 self
.editPreset
=EditPreset()
942 self
.editPreset
.presetEdited
.connect(self
.reload_presets
)
943 self
.installer
=Installer()
944 self
.index
.signal_open
.connect(self
.cmd_open
)
945 self
.index
.signal_copy
.connect(self
.cmd_copy
)
946 self
.plugin
.update
.connect(lambda: self
.load_interface_from_instance())
947 self
.signal_update
.connect(self
.show_update
)
950 if(config
["filename"]):
951 self
.cmd_open(config
["filename"])
953 if(len(self
.prescription
.prescriber
.name
.strip())<1):
954 self
.edit_prescriber()
956 if(config
["check_update"]):
957 threading
.Thread(target
=self
.cmd_update
, args
=[True]).start()
960 self
.setWindowIcon(QIcon(os
.path
.join(config
["resource"], "icon_medscript.ico")))