2 # Copyright (C) 2023 Dr. Agnibho Mondal
3 # This file is part of MedScript.
4 # MedScript is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
5 # MedScript is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
6 # You should have received a copy of the GNU General Public License along with MedScript. If not, see <https://www.gnu.org/licenses/>.
8 import os
, sys
, datetime
, dateutil
.parser
, shutil
, json
, threading
9 from PyQt6
.QtCore
import Qt
, QDateTime
, QSize
, pyqtSignal
10 from PyQt6
.QtWidgets
import QWidget
, QMainWindow
, QMessageBox
, QLabel
, QPushButton
, QLineEdit
, QTextEdit
, QDateTimeEdit
, QListWidget
, QComboBox
, QCheckBox
, QVBoxLayout
, QHBoxLayout
, QFormLayout
, QToolBar
, QTabWidget
, QStatusBar
, QFileDialog
, QInputDialog
, QCompleter
, QSizePolicy
11 from PyQt6
.QtGui
import QAction
, QIcon
12 from pathlib
import Path
13 from hashlib
import md5
14 from urllib
import request
15 from packaging
import version
16 from functools
import partial
18 from config
import config
, info
, real_dir
19 from prescription
import Prescription
20 from renderer
import Renderer
21 from filehandler
import FileHandler
22 from renderbox
import RenderBox
23 from setting
import EditConfiguration
, EditPrescriber
24 from editpreset
import EditPreset
25 from viewbox
import ViewBox
26 from preset
import Preset
27 from tabular
import Tabular
28 from index
import Index
29 from plugin
import Plugin
31 class MainWindow(QMainWindow
):
33 signal_view
=pyqtSignal(str)
34 signal_update
=pyqtSignal(str)
36 current_file
=FileHandler()
37 prescription
=Prescription()
40 save_state
=md5("".encode()).hexdigest()
44 if(self
.confirm_close()):
47 def cmd_open(self
, file=None):
48 if(self
.confirm_close()):
50 self
.current_file
.reset()
52 self
.current_file
.set_file(file)
54 self
.current_file
.set_file(QFileDialog
.getOpenFileName(self
, "Open File", config
["document_directory"], "Prescriptions (*.mpaz);; All Files (*)")[0])
55 self
.current_file
.open()
56 self
.prescription
.read_from(os
.path
.join(self
.current_file
.directory
.name
,"prescription.json"))
57 self
.plugin
.open(self
.prescription
)
58 self
.load_interface_from_instance()
60 self
.save_state
=md5(self
.prescription
.get_json().encode()).hexdigest()
61 self
.load_attachment(self
.current_file
.list())
62 self
.unchanged_state
=True
63 except Exception as e
:
64 QMessageBox
.warning(self
,"Open failed", "Failed to open file.")
67 def cmd_copy(self
, data
):
69 self
.prescription
.name
=data
["name"]
70 self
.prescription
.age
=data
["age"]
71 self
.prescription
.sex
=data
["sex"]
72 self
.prescription
.address
=data
["address"]
73 self
.prescription
.contact
=data
["contact"]
74 self
.load_interface_from_instance()
76 def cmd_save(self
, save_as
=False):
77 self
.update_instance()
78 self
.plugin
.save(self
.prescription
)
79 if(self
.input_template
.currentText()!="<unchanged>"):
81 template
=self
.input_template
.currentText()
84 self
.load_interface_from_instance()
85 suggest
=self
.prescription
.id if(self
.prescription
.id) else self
.prescription
.name
86 suggest
=os
.path
.abspath(os
.path
.join(config
["document_directory"], suggest
)+".mpaz")
87 if(save_as
or not self
.unchanged_state
or QMessageBox
.StandardButton
.Yes
==QMessageBox
.question(self
,"Confirm change", "Modify the original file?")):
89 if not os
.path
.exists(self
.current_file
.file):
90 filename
=QFileDialog
.getSaveFileName(self
, "Save File", suggest
, "Prescriptions (*.mpaz);; All Files (*)")[0]
91 if(not filename
.endswith(".mpaz")):
92 filename
=filename
+".mpaz"
93 self
.current_file
.set_file(filename
)
94 for i
in range(self
.input_attachment
.count()):
95 self
.current_file
.copy(self
.input_attachment
.item(i
).text())
96 self
.prescription
.write_to(os
.path
.join(self
.current_file
.directory
.name
, "prescription.json"))
98 config
["template"]=os
.path
.join(config
["template_directory"], template
)
99 self
.current_file
.save(change_template
=change_template
)
100 self
.unchanged_state
=False
101 self
.load_interface_from_instance()
102 self
.save_state
=md5(self
.prescription
.get_json().encode()).hexdigest()
103 except Exception as e
:
104 QMessageBox
.warning(self
,"Save failed", "Failed to save file.")
107 def cmd_save_as(self
):
108 suggest
=self
.prescription
.id if(self
.prescription
.id) else self
.prescription
.name
109 suggest
=os
.path
.abspath(os
.path
.join(config
["document_directory"], suggest
)+".mpaz")
110 self
.current_file
.set_file(QFileDialog
.getSaveFileName(self
, "Save File", suggest
, "Prescriptions (*.mpaz);; All Files (*)")[0])
111 Path(self
.current_file
.file).touch()
112 self
.cmd_save(save_as
=True)
114 def cmd_refresh(self
):
115 self
.update_instance()
116 self
.plugin
.refresh(self
.prescription
)
117 self
.load_interface_from_instance()
121 if(self
.confirm_close()):
124 def cmd_render(self
):
126 if(self
.save_state
==md5(self
.prescription
.get_json().encode()).hexdigest()):
128 target
=self
.renderer
.render(self
.current_file
.directory
.name
)
129 self
.signal_view
.emit(target
)
130 self
.renderbox
.showMaximized()
131 except FileNotFoundError
as e
:
133 QMessageBox
.information(self
, "Save first", "Please save the file before rendering.")
136 QMessageBox
.information(self
, "Save first", "Please save the file before rendering.")
140 if(self
.save_state
==md5(self
.prescription
.get_json().encode()).hexdigest()):
141 ok
=True #password, ok=QInputDialog.getText(self, "Enter password", "Private key password", QLineEdit.EchoMode.Password)
145 self
.current_file
.sign()
146 #self.current_file.sign(password)
148 except FileNotFoundError
as e
:
150 QMessageBox
.information(self
, "Save first", "Please save the file before signing.")
151 except TypeError as e
:
153 QMessageBox
.information(self
, "Configure", "Please add valid key and certificate to the config file.")
154 except EVPError
as e
:
156 QMessageBox
.information(self
, "Check password", "Failed to load key. Please check if password is correct.")
157 except BIOError
as e
:
159 QMessageBox
.information(self
, "Not found", "Certifcate and/or key not found.")
160 except SMIME_Error
as e
:
162 QMessageBox
.information(self
, "Failed to load", "Failed to sign. Please check if certificate and key match.")
163 except Exception as e
:
165 QMessageBox
.information(self
, "Failed", "Failed to sign.")
166 except Exception as e
:
169 QMessageBox
.information(self
, "Save first", "Please save the file before signing.")
171 def cmd_unsign(self
):
172 self
.current_file
.delete_sign()
176 def cmd_verify(self
):
178 result
=self
.current_file
.verify()
180 QMessageBox
.critical(self
, "Verification failed", "Signature is invalid.")
182 QMessageBox
.warning(self
, "No Siganture", "No signature was found.")
185 QMessageBox
.information(self
, "Valid signature", "Valid signature found with the following information:\n"+result
)
186 except FileNotFoundError
as e
:
188 QMessageBox
.warning(self
, "No Siganture", "No signature was found.")
189 except Exception as e
:
191 QMessageBox
.warning(self
, "Failed", "Failed to verify.")
193 def cmd_tabular(self
):
195 filename
=QFileDialog
.getSaveFileName(self
, "Export CSV File", os
.path
.join(config
["data_directory"], "data.csv"), "CSV (*.csv);; All Files (*)")[0]
196 Tabular
.export(filename
)
197 QMessageBox
.information(self
, "Data Exported", "Data exported to."+filename
)
198 except Exception as e
:
200 QMessageBox
.critical(self
, "Export failed", "Failed to export the data.")
206 def cmd_configuration(self
):
207 self
.edit_configuration
.show()
209 def cmd_prescriber(self
):
210 self
.edit_prescriber
.show()
212 def cmd_prescriber_reload(self
, file=None):
213 self
.prescription
.reload_prescriber(file=None)
216 def cmd_switch(self
):
218 self
.prescription
.reload_prescriber(QFileDialog
.getOpenFileName(self
, "Open File", config
["prescriber_directory"], "JSON (*.json);; All Files (*)")[0])
220 except FileNotFoundError
as e
:
223 def cmd_preset(self
):
224 self
.edit_preset
.show()
227 year
=datetime
.datetime
.now().year
229 copy
="2023"+"-"+str(year
)
232 txt
="<h1>MedScript</h1>"
233 txt
=txt
+"<p>Version "+info
["version"]+"</p>"
234 txt
=txt
+"<p>The Prescription Writing Software</p>"
235 txt
=txt
+"<p><a href='"+info
["url"]+"'>Website</a></p>"
236 txt
=txt
+"<p>Copyright © "+copy
+" Dr. Agnibho Mondal</p>"
237 QMessageBox
.about(self
, "MedScript", txt
)
240 self
.viewbox
.md(os
.path
.join(real_dir
, "README"))
243 def cmd_update(self
, silent
=False):
245 print("Current version "+info
["version"])
246 with request
.urlopen(info
["url"]+"/info.json") as response
:
247 latest
=json
.loads(response
.read().decode())
248 print("Latest version "+latest
["version"])
249 if(version
.parse(info
["version"]) < version
.parse(latest
["version"])):
250 self
.signal_update
.emit("New version <strong>"+latest
["version"]+"</strong> available.<br>Visit <a href='"+latest
["url"]+"'>"+latest
["url"]+"</a> to get the latest version.")
252 self
.signal_update
.emit("No update available. You are using version "+info
["version"]+".")
253 except Exception as e
:
254 self
.signal_update
.emit("Failed to check available update.")
257 def show_update(self
, message
):
258 QMessageBox
.information(self
, "Check update", message
)
260 def insert_preset_extra(self
):
262 self
.input_extra
.insertPlainText(self
.preset_extra
.data
[self
.input_extra_preset
.currentText()])
264 self
.input_extra
.insertPlainText(self
.input_extra_preset
.currentText())
266 self
.input_extra_preset
.setCurrentIndex(-1)
267 if config
["preset_newline"]:
268 self
.input_extra
.insertPlainText("\n")
270 def insert_preset_note(self
):
272 self
.input_note
.insertPlainText(self
.preset_note
.data
[self
.input_note_preset
.currentText()])
274 self
.input_note
.insertPlainText(self
.input_note_preset
.currentText())
276 self
.input_note_preset
.setCurrentIndex(-1)
277 if config
["preset_newline"]:
278 self
.input_note
.insertPlainText("\n")
280 def insert_preset_report(self
):
282 self
.input_report
.insertPlainText(self
.preset_report
.data
[self
.input_report_preset
.currentText()])
284 self
.input_report
.insertPlainText(self
.input_report_preset
.currentText())
286 self
.input_report_preset
.setCurrentIndex(-1)
287 if config
["preset_newline"]:
288 self
.input_report
.insertPlainText("\n")
290 def insert_preset_advice(self
):
292 self
.input_advice
.insertPlainText(self
.preset_advice
.data
[self
.input_advice_preset
.currentText()])
294 self
.input_advice
.insertPlainText(self
.input_advice_preset
.currentText())
296 self
.input_advice_preset
.setCurrentIndex(-1)
297 if config
["preset_newline"]:
298 self
.input_advice
.insertPlainText("\n")
300 def insert_preset_investigation(self
):
302 self
.input_investigation
.insertPlainText(self
.preset_investigation
.data
[self
.input_investigation_preset
.currentText()])
304 self
.input_investigation
.insertPlainText(self
.input_investigation_preset
.currentText())
306 self
.input_investigation_preset
.setCurrentIndex(-1)
307 if config
["preset_newline"]:
308 self
.input_investigation
.insertPlainText("\n")
310 def insert_preset_medication(self
):
312 self
.input_medication
.insertPlainText(self
.preset_medication
.data
[self
.input_medication_preset
.currentText()])
314 self
.input_medication
.insertPlainText(self
.input_medication_preset
.currentText())
316 self
.input_medication_preset
.setCurrentIndex(-1)
317 if config
["preset_newline"]:
318 self
.input_medication
.insertPlainText("\n")
320 def insert_preset_additional(self
):
322 self
.input_additional
.insertPlainText(self
.preset_additional
.data
[self
.input_additional_preset
.currentText()])
324 self
.input_additional
.insertPlainText(self
.input_additional_preset
.currentText())
326 self
.input_additional_preset
.setCurrentIndex(-1)
327 if config
["preset_newline"]:
328 self
.input_additional
.insertPlainText("\n")
330 def load_interface(self
, file="", date
=None, id="", name
="", age
="", sex
="", address
="", contact
="", extra
="", mode
="", daw
="", diagnosis
="", note
="", report
="", advice
="", investigation
="", medication
="", additional
=""):
332 file_msg
=self
.current_file
.file if self
.current_file
.file else "New file"
333 sign_msg
="(signed)" if config
["smime"] and self
.current_file
.is_signed() else ""
334 self
.statusbar
.showMessage(file_msg
+" "+sign_msg
)
336 d
=QDateTime
.currentDateTime()
339 pdate
=dateutil
.parser
.parse(date
)
340 d
=QDateTime
.fromString(pdate
.strftime("%Y-%m-%d %H:%M:%S"), "yyyy-MM-dd hh:mm:ss")
341 except Exception as e
:
342 QMessageBox
.warning(self
,"Failed to load", str(e
))
344 self
.input_date
.setDateTime(d
)
345 self
.input_id
.setText(id)
346 self
.input_name
.setText(name
)
347 self
.input_age
.setText(age
)
348 self
.input_sex
.setCurrentText(sex
)
349 self
.input_address
.setText(address
)
350 self
.input_contact
.setText(contact
)
351 self
.input_extra
.setText(extra
)
352 self
.input_mode
.setCurrentText(mode
)
353 self
.input_daw
.setChecked(bool(daw
))
354 self
.input_diagnosis
.setText(diagnosis
)
355 self
.input_note
.setText(note
)
356 self
.input_report
.setText(report
)
357 self
.input_advice
.setText(advice
)
358 self
.input_investigation
.setText(investigation
)
359 self
.input_medication
.setText(medication
)
360 self
.input_additional
.setText(additional
)
361 self
.label_prescriber
.setText(self
.prescription
.prescriber
.name
)
362 except Exception as e
:
363 QMessageBox
.warning(self
,"Failed to load", "Failed to load the data into the application.")
366 def load_interface_from_instance(self
):
367 if(self
.current_file
.has_template()):
368 if(self
.input_template
.findText("<unchanged>")==-1):
369 self
.input_template
.addItem("<unchanged>")
370 self
.input_template
.setCurrentText("<unchanged>")
372 self
.input_template
.removeItem(self
.input_template
.findText("<unchanged>"))
374 file=self
.prescription
.file,
375 date
=self
.prescription
.date
,
376 id=self
.prescription
.id,
377 name
=self
.prescription
.name
,
378 age
=self
.prescription
.age
,
379 sex
=self
.prescription
.sex
,
380 address
=self
.prescription
.address
,
381 contact
=self
.prescription
.contact
,
382 extra
=self
.prescription
.extra
,
383 mode
=self
.prescription
.mode
,
384 daw
=self
.prescription
.daw
,
385 diagnosis
=self
.prescription
.diagnosis
,
386 note
=self
.prescription
.note
,
387 report
=self
.prescription
.report
,
388 advice
=self
.prescription
.advice
,
389 investigation
=self
.prescription
.investigation
,
390 medication
=self
.prescription
.medication
,
391 additional
=self
.prescription
.additional
394 def update_instance(self
):
396 self
.prescription
.set_data(
397 date
=self
.input_date
.dateTime().toString("yyyy-MM-dd hh:mm:ss"),
398 id=self
.input_id
.text(),
399 name
=self
.input_name
.text(),
400 age
=self
.input_age
.text(),
401 sex
=self
.input_sex
.currentText(),
402 address
=self
.input_address
.text(),
403 contact
=self
.input_contact
.text(),
404 extra
=self
.input_extra
.toPlainText(),
405 mode
=self
.input_mode
.currentText(),
406 daw
=self
.input_daw
.isChecked(),
407 diagnosis
=self
.input_diagnosis
.text(),
408 note
=self
.input_note
.toPlainText(),
409 report
=self
.input_report
.toPlainText(),
410 advice
=self
.input_advice
.toPlainText(),
411 investigation
=self
.input_investigation
.toPlainText(),
412 medication
=self
.input_medication
.toPlainText(),
413 additional
=self
.input_additional
.toPlainText()
415 except Exception as e
:
416 QMessageBox
.critical(self
,"Failed", "Critical failure happned. Please check console for more info.")
420 self
.current_file
.reset()
421 self
.prescription
.set_data()
422 self
.input_attachment
.clear()
423 self
.load_interface()
424 self
.update_instance()
425 self
.plugin
.new(self
.prescription
)
426 self
.load_interface_from_instance()
427 self
.save_state
=md5(self
.prescription
.get_json().encode()).hexdigest()
430 self
.update_instance()
431 self
.load_interface_from_instance()
433 def add_attachment(self
):
435 new
=QFileDialog
.getOpenFileName(self
, "Open File", config
["document_directory"], "PDF (*.pdf);; Images (*.jpg, *.jpeg, *.png, *.gif);; All Files (*)")[0]
437 self
.input_attachment
.addItem(new
)
438 except Exception as e
:
439 QMessageBox
.warning(self
,"Attach failed", "Failed to attach file.")
442 def remove_attachment(self
):
443 index
=self
.input_attachment
.currentRow()
445 self
.current_file
.delete_attachment(self
.input_attachment
.item(index
).text())
446 self
.input_attachment
.takeItem(index
)
448 QMessageBox
.warning(self
, "Select item", "Please select an attachment to remove.")
450 def save_attachment(self
):
452 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])
453 except Exception as e
:
456 def load_attachment(self
, attachments
):
457 for attach
in attachments
:
458 self
.input_attachment
.addItem(attach
)
460 def confirm_close(self
):
462 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?"))
465 def closeEvent(self
, event
):
466 if(self
.confirm_close()):
471 def __init__(self
, *args
, **kwargs
):
472 super().__init
__(*args
, **kwargs
)
474 self
.setWindowTitle("MedScript")
475 self
.setGeometry(100, 100, 600, 400)
476 self
.setWindowIcon(QIcon(os
.path
.join(config
["resource"], "icon_medscript.ico")))
478 icon_open
=QIcon(os
.path
.join(config
["resource"], "icon_open.svg"))
479 icon_save
=QIcon(os
.path
.join(config
["resource"], "icon_save.svg"))
480 icon_render
=QIcon(os
.path
.join(config
["resource"], "icon_render.svg"))
481 icon_refresh
=QIcon(os
.path
.join(config
["resource"], "icon_refresh.svg"))
483 self
.preset_extra
=Preset(os
.path
.join(config
["preset_directory"], "certify.csv"))
484 self
.preset_note
=Preset(os
.path
.join(config
["preset_directory"], "note.csv"))
485 self
.preset_report
=Preset(os
.path
.join(config
["preset_directory"], "report.csv"))
486 self
.preset_advice
=Preset(os
.path
.join(config
["preset_directory"], "advice.csv"))
487 self
.preset_investigation
=Preset(os
.path
.join(config
["preset_directory"], "investigation.csv"))
488 self
.preset_medication
=Preset(os
.path
.join(config
["preset_directory"], "medication.csv"), text_as_key
=True)
489 self
.preset_additional
=Preset(os
.path
.join(config
["preset_directory"], "additional.csv"))
491 action_new
=QAction("New", self
)
492 action_new
.setShortcut("Ctrl+N")
493 action_new
.triggered
.connect(self
.cmd_new
)
494 action_open
=QAction("Open", self
)
495 action_open2
=QAction(icon_open
, "Open", self
)
496 action_open
.setShortcut("Ctrl+O")
497 action_open
.triggered
.connect(self
.cmd_open
)
498 action_open2
.triggered
.connect(self
.cmd_open
)
499 action_save
=QAction("Save", self
)
500 action_save2
=QAction(icon_save
, "Save", self
)
501 action_save
.setShortcut("Ctrl+S")
502 action_save
.triggered
.connect(self
.cmd_save
)
503 action_save2
.triggered
.connect(self
.cmd_save
)
504 action_save_as
=QAction("Save As", self
)
505 action_save_as
.setShortcut("Ctrl+Shift+S")
506 action_save_as
.triggered
.connect(self
.cmd_save_as
)
507 action_refresh
=QAction("Refresh", self
)
508 action_refresh
.setShortcut("F5")
509 action_refresh2
=QAction(icon_refresh
, "Refresh", self
)
510 action_refresh
.triggered
.connect(self
.cmd_refresh
)
511 action_refresh2
.triggered
.connect(self
.cmd_refresh
)
512 action_quit
=QAction("Quit", self
)
513 action_quit
.setShortcut("Ctrl+Q")
514 action_quit
.triggered
.connect(self
.cmd_quit
)
515 action_render
=QAction("Render", self
)
516 action_render
.setShortcut("Ctrl+R")
517 action_render2
=QAction(icon_render
, "Render", self
)
518 action_render
.triggered
.connect(self
.cmd_render
)
519 action_render2
.triggered
.connect(self
.cmd_render
)
520 action_sign
=QAction("Sign", self
)
521 action_sign
.triggered
.connect(self
.cmd_sign
)
522 action_unsign
=QAction("Unsign", self
)
523 action_unsign
.triggered
.connect(self
.cmd_unsign
)
524 action_verify
=QAction("Verify", self
)
525 action_verify
.triggered
.connect(self
.cmd_verify
)
526 action_configuration
=QAction("Configuration", self
)
527 action_configuration
.triggered
.connect(self
.cmd_configuration
)
528 action_prescriber
=QAction("Prescriber", self
)
529 action_prescriber
.triggered
.connect(self
.cmd_prescriber
)
530 action_switch
=QAction("Switch", self
)
531 action_switch
.triggered
.connect(self
.cmd_switch
)
532 action_preset
=QAction("Preset", self
)
533 action_preset
.triggered
.connect(self
.cmd_preset
)
534 action_tabular
=QAction("Tabular", self
)
535 action_tabular
.triggered
.connect(self
.cmd_tabular
)
536 action_index
=QAction("Index", self
)
537 action_index
.triggered
.connect(self
.cmd_index
)
538 action_update
=QAction("Update", self
)
539 action_update
.triggered
.connect(self
.cmd_update
)
540 action_about
=QAction("About", self
)
541 action_about
.triggered
.connect(self
.cmd_about
)
542 action_help
=QAction("Help", self
)
543 action_help
.setShortcut("F1")
544 action_help
.triggered
.connect(self
.cmd_help
)
546 menubar
=self
.menuBar()
547 menu_file
=menubar
.addMenu("File")
548 menu_file
.addAction(action_new
)
549 menu_file
.addAction(action_open
)
550 menu_file
.addAction(action_save
)
551 menu_file
.addAction(action_save_as
)
552 menu_file
.addAction(action_quit
)
553 menu_prepare
=menubar
.addMenu("Prepare")
554 menu_prepare
.addAction(action_render
)
555 menu_prepare
.addAction(action_refresh
)
557 menu_prepare
.addAction(action_sign
)
558 menu_prepare
.addAction(action_unsign
)
559 menu_prepare
.addAction(action_verify
)
560 menu_settings
=menubar
.addMenu("Settings")
561 menu_settings
.addAction(action_configuration
)
562 menu_settings
.addAction(action_prescriber
)
563 menu_settings
.addAction(action_switch
)
564 menu_settings
.addAction(action_preset
)
565 menu_data
=menubar
.addMenu("Data")
566 menu_data
.addAction(action_index
)
567 menu_data
.addAction(action_tabular
)
569 if(config
["enable_plugin"]):
572 for i
in self
.plugin
.commands():
573 action_plugin
.append(QAction(i
[1], self
))
574 action_plugin
[-1].triggered
.connect(self
.update_instance
)
575 action_plugin
[-1].triggered
.connect(partial(self
.plugin
.run
, i
[0], self
.prescription
))
576 action_plugin
[-1].triggered
.connect(self
.load_interface_from_instance
)
577 except Exception as e
:
579 menu_plugin
=menubar
.addMenu("Plugin")
580 for i
in action_plugin
:
581 menu_plugin
.addAction(i
)
583 menu_help
=menubar
.addMenu("Help")
584 menu_help
.addAction(action_update
)
585 menu_help
.addAction(action_about
)
586 menu_help
.addAction(action_help
)
588 toolbar
=QToolBar("Main Toolbar", floatable
=False, movable
=False)
589 toolbar
.setIconSize(QSize(16, 16))
590 toolbar
.addAction(action_open2
)
591 toolbar
.addAction(action_save2
)
592 toolbar
.addAction(action_refresh2
)
593 toolbar
.addAction(action_render2
)
594 toolbar
.addSeparator()
595 label_template
=QLabel("Template:")
596 toolbar
.addWidget(label_template
)
597 self
.input_template
=QComboBox(self
)
598 self
.input_template
.setMinimumWidth(200)
599 templates
=os
.listdir(config
["template_directory"])
601 templates
.remove(os
.path
.basename(config
["template"]))
602 templates
.insert(0, os
.path
.basename(config
["template"]))
603 except Exception as e
:
605 self
.input_template
.addItems(templates
)
606 toolbar
.addWidget(self
.input_template
)
608 spacer
.setSizePolicy(QSizePolicy
.Policy
.Expanding
, QSizePolicy
.Policy
.Expanding
)
609 toolbar
.addWidget(spacer
)
610 self
.label_prescriber
=QLabel(self
)
611 toolbar
.addWidget(self
.label_prescriber
)
612 self
.addToolBar(toolbar
)
614 tab_info
=QWidget(self
)
615 layout_info
=QFormLayout(tab_info
)
616 layout_info2
=QHBoxLayout()
617 self
.input_date
=QDateTimeEdit(self
)
618 self
.input_date
.setDisplayFormat("MMMM dd, yyyy hh:mm a")
619 layout_info
.addRow("Date", self
.input_date
)
620 self
.input_id
=QLineEdit(self
)
621 layout_info
.addRow("ID", self
.input_id
)
622 self
.input_name
=QLineEdit(self
)
623 layout_info
.addRow("Name", self
.input_name
)
624 self
.input_age
=QLineEdit(self
)
625 layout_info
.addRow("Age", self
.input_age
)
626 self
.input_sex
=QComboBox(self
)
627 self
.input_sex
.addItems(["Male", "Female", "Other"])
628 self
.input_sex
.setEditable(True)
629 layout_info
.addRow("Sex", self
.input_sex
)
630 self
.input_address
=QLineEdit(self
)
631 layout_info
.addRow("Address", self
.input_address
)
632 self
.input_contact
=QLineEdit(self
)
633 layout_info
.addRow("Contact", self
.input_contact
)
634 self
.input_diagnosis
=QLineEdit(self
)
635 layout_info
.addRow("Diagnosis", self
.input_diagnosis
)
636 self
.input_extra
=QTextEdit(self
)
637 self
.input_extra_preset
=QComboBox(self
)
638 self
.input_extra_preset
.addItems(self
.preset_extra
.data
.keys())
639 self
.input_extra_preset
.setCurrentIndex(-1)
640 self
.input_extra_preset
.setEditable(True)
641 self
.input_extra_preset
.completer().setCompletionMode(QCompleter
.CompletionMode
.PopupCompletion
)
642 self
.input_extra_preset
.completer().setFilterMode(Qt
.MatchFlag
.MatchContains
)
643 self
.input_extra_preset
.setPlaceholderText("Select a preset")
644 input_extra_preset_btn
=QPushButton("Insert")
645 input_extra_preset_btn
.clicked
.connect(self
.insert_preset_extra
)
646 layout_info2
.addWidget(self
.input_extra_preset
, 5)
647 layout_info2
.addWidget(input_extra_preset_btn
, 1)
648 layout_info
.addRow("Certify\nPreset", layout_info2
)
649 layout_info
.addRow("Certify /\nExtra", self
.input_extra
)
650 self
.input_mode
=QComboBox(self
)
651 self
.input_mode
.addItems(["In-Person", "Tele-Consultation", "Other"])
652 self
.input_mode
.setEditable(True)
653 layout_info
.addRow("Mode", self
.input_mode
)
654 self
.input_daw
=QCheckBox("Dispense as written", self
)
655 layout_info
.addRow("DAW", self
.input_daw
)
657 tab_note
=QWidget(self
)
658 layout_note
=QVBoxLayout(tab_note
)
659 layout_note2
=QHBoxLayout()
660 label_note
=QLabel("Clinical Notes")
661 label_note
.setProperty("class", "info_head")
662 self
.input_note_preset
=QComboBox(self
)
663 self
.input_note_preset
.addItems(self
.preset_note
.data
.keys())
664 self
.input_note_preset
.setCurrentIndex(-1)
665 self
.input_note_preset
.setEditable(True)
666 self
.input_note_preset
.completer().setCompletionMode(QCompleter
.CompletionMode
.PopupCompletion
)
667 self
.input_note_preset
.completer().setFilterMode(Qt
.MatchFlag
.MatchContains
)
668 self
.input_note_preset
.setPlaceholderText("Select a preset")
669 input_note_preset_btn
=QPushButton("Insert")
670 input_note_preset_btn
.clicked
.connect(self
.insert_preset_note
)
671 layout_note2
.addWidget(self
.input_note_preset
, 5)
672 layout_note2
.addWidget(input_note_preset_btn
, 1)
673 self
.input_note
=QTextEdit(self
)
674 layout_note
.addWidget(label_note
)
675 layout_note
.addLayout(layout_note2
)
676 layout_note
.addWidget(self
.input_note
)
678 tab_report
=QWidget(self
)
679 layout_report
=QVBoxLayout(tab_report
)
680 layout_report2
=QHBoxLayout()
681 label_report
=QLabel("Available Reports")
682 label_report
.setProperty("class", "info_head")
683 self
.input_report_preset
=QComboBox(self
)
684 self
.input_report_preset
.addItems(self
.preset_report
.data
.keys())
685 self
.input_report_preset
.setCurrentIndex(-1)
686 self
.input_report_preset
.setEditable(True)
687 self
.input_report_preset
.completer().setCompletionMode(QCompleter
.CompletionMode
.PopupCompletion
)
688 self
.input_report_preset
.completer().setFilterMode(Qt
.MatchFlag
.MatchContains
)
689 self
.input_report_preset
.setPlaceholderText("Select a preset")
690 input_report_preset_btn
=QPushButton("Insert")
691 input_report_preset_btn
.clicked
.connect(self
.insert_preset_report
)
692 layout_report2
.addWidget(self
.input_report_preset
, 5)
693 layout_report2
.addWidget(input_report_preset_btn
, 1)
694 self
.input_report
=QTextEdit(self
)
695 layout_report
.addWidget(label_report
)
696 layout_report
.addLayout(layout_report2
)
697 layout_report
.addWidget(self
.input_report
)
699 tab_advice
=QWidget(self
)
700 layout_advice
=QVBoxLayout(tab_advice
)
701 layout_advice2
=QHBoxLayout()
702 label_advice
=QLabel("Advice")
703 label_advice
.setProperty("class", "info_head")
704 self
.input_advice_preset
=QComboBox(self
)
705 self
.input_advice_preset
.addItems(self
.preset_advice
.data
.keys())
706 self
.input_advice_preset
.setCurrentIndex(-1)
707 self
.input_advice_preset
.setEditable(True)
708 self
.input_advice_preset
.completer().setCompletionMode(QCompleter
.CompletionMode
.PopupCompletion
)
709 self
.input_advice_preset
.completer().setFilterMode(Qt
.MatchFlag
.MatchContains
)
710 self
.input_advice_preset
.setPlaceholderText("Select a preset")
711 input_advice_preset_btn
=QPushButton("Insert")
712 input_advice_preset_btn
.clicked
.connect(self
.insert_preset_advice
)
713 layout_advice2
.addWidget(self
.input_advice_preset
, 5)
714 layout_advice2
.addWidget(input_advice_preset_btn
, 1)
715 self
.input_advice
=QTextEdit(self
)
716 layout_advice
.addWidget(label_advice
)
717 layout_advice
.addLayout(layout_advice2
)
718 layout_advice
.addWidget(self
.input_advice
)
720 tab_investigation
=QWidget(self
)
721 layout_investigation
=QVBoxLayout(tab_investigation
)
722 layout_investigation2
=QHBoxLayout()
723 label_investigation
=QLabel("Recommended Investigations")
724 label_investigation
.setProperty("class", "info_head")
725 self
.input_investigation_preset
=QComboBox(self
)
726 self
.input_investigation_preset
.addItems(self
.preset_investigation
.data
.keys())
727 self
.input_investigation_preset
.setCurrentIndex(-1)
728 self
.input_investigation_preset
.setEditable(True)
729 self
.input_investigation_preset
.completer().setCompletionMode(QCompleter
.CompletionMode
.PopupCompletion
)
730 self
.input_investigation_preset
.completer().setFilterMode(Qt
.MatchFlag
.MatchContains
)
731 self
.input_investigation_preset
.setPlaceholderText("Select a preset")
732 input_investigation_preset_btn
=QPushButton("Insert")
733 input_investigation_preset_btn
.clicked
.connect(self
.insert_preset_investigation
)
734 layout_investigation2
.addWidget(self
.input_investigation_preset
, 5)
735 layout_investigation2
.addWidget(input_investigation_preset_btn
, 1)
736 self
.input_investigation
=QTextEdit(self
)
737 layout_investigation
.addWidget(label_investigation
)
738 layout_investigation
.addLayout(layout_investigation2
)
739 layout_investigation
.addWidget(self
.input_investigation
)
741 tab_medication
=QWidget(self
)
742 layout_medication
=QVBoxLayout(tab_medication
)
743 layout_medication2
=QHBoxLayout()
744 label_medication
=QLabel("Medication Advice")
745 label_medication
.setProperty("class", "info_head")
746 self
.input_medication_preset
=QComboBox(self
)
747 self
.input_medication_preset
.addItems(self
.preset_medication
.data
.keys())
748 self
.input_medication_preset
.setCurrentIndex(-1)
749 self
.input_medication_preset
.setEditable(True)
750 self
.input_medication_preset
.completer().setCompletionMode(QCompleter
.CompletionMode
.PopupCompletion
)
751 self
.input_medication_preset
.completer().setFilterMode(Qt
.MatchFlag
.MatchContains
)
752 self
.input_medication_preset
.setPlaceholderText("Select a preset")
753 input_medication_preset_btn
=QPushButton("Insert")
754 input_medication_preset_btn
.clicked
.connect(self
.insert_preset_medication
)
755 layout_medication2
.addWidget(self
.input_medication_preset
, 5)
756 layout_medication2
.addWidget(input_medication_preset_btn
, 1)
757 self
.input_medication
=QTextEdit(self
)
758 layout_medication
.addWidget(label_medication
)
759 layout_medication
.addLayout(layout_medication2
)
760 layout_medication
.addWidget(self
.input_medication
)
762 tab_additional
=QWidget(self
)
763 layout_additional
=QVBoxLayout(tab_additional
)
764 layout_additional2
=QHBoxLayout()
765 label_additional
=QLabel("Additional Advice")
766 label_additional
.setProperty("class", "info_head")
767 self
.input_additional_preset
=QComboBox(self
)
768 self
.input_additional_preset
.addItems(self
.preset_additional
.data
.keys())
769 self
.input_additional_preset
.setCurrentIndex(-1)
770 self
.input_additional_preset
.setEditable(True)
771 self
.input_additional_preset
.completer().setCompletionMode(QCompleter
.CompletionMode
.PopupCompletion
)
772 self
.input_additional_preset
.completer().setFilterMode(Qt
.MatchFlag
.MatchContains
)
773 self
.input_additional_preset
.setPlaceholderText("Select a preset")
774 input_additional_preset_btn
=QPushButton("Insert")
775 input_additional_preset_btn
.clicked
.connect(self
.insert_preset_additional
)
776 layout_additional2
.addWidget(self
.input_additional_preset
, 5)
777 layout_additional2
.addWidget(input_additional_preset_btn
, 1)
778 self
.input_additional
=QTextEdit(self
)
779 layout_additional
.addWidget(label_additional
)
780 layout_additional
.addLayout(layout_additional2
)
781 layout_additional
.addWidget(self
.input_additional
)
783 tab_attachment
=QWidget(self
)
784 layout_attachment
=QVBoxLayout(tab_attachment
)
785 layout_attachment2
=QHBoxLayout()
786 label_attachment
=QLabel("Attached files")
787 label_attachment
.setProperty("class", "info_head")
788 self
.input_attachment
=QListWidget(self
)
789 button_add
=QPushButton("Add")
790 button_add
.clicked
.connect(self
.add_attachment
)
791 button_remove
=QPushButton("Remove")
792 button_remove
.clicked
.connect(self
.remove_attachment
)
793 button_save
=QPushButton("Save")
794 button_save
.clicked
.connect(self
.save_attachment
)
795 layout_attachment
.addWidget(label_attachment
)
796 layout_attachment
.addLayout(layout_attachment2
)
797 layout_attachment
.addWidget(self
.input_attachment
)
798 layout_attachment2
.addWidget(button_add
)
799 layout_attachment2
.addWidget(button_remove
)
800 layout_attachment2
.addWidget(button_save
)
803 tab
.addTab(tab_info
, "Patient")
804 tab
.addTab(tab_note
, "Clinical")
805 tab
.addTab(tab_report
, "Report")
806 tab
.addTab(tab_advice
, "Advice")
807 tab
.addTab(tab_investigation
, "Investigation")
808 tab
.addTab(tab_medication
, "Medication")
809 tab
.addTab(tab_additional
, "Additional")
810 tab
.addTab(tab_attachment
, "Attachment")
812 self
.setCentralWidget(tab
)
814 self
.statusbar
=QStatusBar()
815 self
.setStatusBar(self
.statusbar
)
817 self
.renderbox
=RenderBox()
818 self
.signal_view
.connect(self
.renderbox
.update
)
819 self
.edit_configuration
=EditConfiguration()
820 self
.edit_prescriber
=EditPrescriber()
821 self
.edit_prescriber
.signal_save
.connect(self
.cmd_prescriber_reload
)
822 self
.viewbox
=ViewBox()
824 self
.edit_preset
=EditPreset()
825 self
.index
.signal_open
.connect(self
.cmd_open
)
826 self
.index
.signal_copy
.connect(self
.cmd_copy
)
827 self
.signal_update
.connect(self
.show_update
)
830 if(config
["filename"]):
831 self
.cmd_open(config
["filename"])
833 if(len(self
.prescription
.prescriber
.name
.strip())<1):
834 self
.cmd_prescriber()
836 if(config
["check_update"]):
837 threading
.Thread(target
=self
.cmd_update
, args
=[True]).start()
839 self
.setWindowIcon(QIcon(os
.path
.join(config
["resource"], "icon_medscript.ico")))