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