#! /usr/bin/env python
# -*- coding: utf-8 -*-
###############################################################################
## ##
## Copyright 2010-2012, Neil Wallace <neil@openmolar.com> ##
## ##
## This program 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. ##
## ##
## This program 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. ##
## ##
## You should have received a copy of the GNU General Public License ##
## along with this program. If not, see <http://www.gnu.org/licenses/>. ##
## ##
###############################################################################
import sys, traceback
from PyQt4 import QtCore, QtGui
from lib_openmolar.client.qt4.widgets import (ControlPanel, DetailsBrowser, NotesWidget, PatientInterfaceOptionsWidget)
from lib_openmolar.client.qt4.widgets.procedures.proc_code_widget \
import ProcCodeDockWidget
from pages import *
from lib_openmolar.client.qt4 import dialogs
from lib_openmolar.client import db_orm
[docs]class PatientInterface(QtGui.QWidget):
'''
PatientInterface
A composite widget containing all elements for viewing a patient record
'''
[docs] def __init__(self, parent = None):
QtGui.QWidget.__init__(self, parent)
self._proc_code_dock_widget = None #initialise if needed.
self.control_panel = ControlPanel(self)
'''a pointer to the :doc:`ControlPanel`'''
self.details_browser = DetailsBrowser(self)
'''a pointer to the :doc:`DetailsBrowser`'''
self.reception_page = ReceptionPage(self)
'''a pointer to the :doc:`ReceptionPage`'''
self.charts_page = ChartsPage(self)
'''a pointer to the :doc:`ChartsPage`'''
self.treatment_page = TreatmentPage(self)
'''a pointer to the :doc:`TreatmentPage`'''
self.notes_page = NotesWidget(self)
'''a pointer to the :doc:`NotesWidget`'''
self.estimates_page = EstimatesPage(self)
'''a pointer to the :doc:`EstimatesPage`'''
self.history_page = HistoryPage(self)
'''a pointer to the :doc:`HistoryPage`'''
# summary_page shares the "model" of the static chart
model = self.charts_page.static.chart_data_model
self.summary_page = SummaryPage(model, self)
'''a pointer to the :doc:`SummaryPage`'''
self.options_widget = PatientInterfaceOptionsWidget(self)
'''a pointer to the :doc:`PatientInterfaceOptionsWidget`'''
self.options_widget.tab_index_changed(0)
self.tab_widget = QtGui.QTabWidget(self)
'''a pointer to the TabWidget'''
self.tab_widget.tabBar().setFocusPolicy(QtCore.Qt.NoFocus)
self.tab_widget.addTab(self.reception_page, _("Reception"))
self.tab_widget.addTab(self.summary_page, _("Summary"))
self.tab_widget.addTab(self.charts_page, _("Charts"))
self.tab_widget.addTab(self.treatment_page, _("Treatment"))
self.tab_widget.addTab(self.estimates_page, _("Estimates"))
self.tab_widget.addTab(self.notes_page, _("Notes"))
self.tab_widget.addTab(self.history_page, _("History"))
self.tab_widget.setCornerWidget(self.options_widget)
left_widget = QtGui.QWidget()
left_widget.setMaximumWidth(240)
layout = QtGui.QVBoxLayout(left_widget)
layout.setMargin(0)
layout.addWidget(self.control_panel)
layout.addWidget(self.details_browser)
layout = QtGui.QHBoxLayout(self)
layout.addWidget(left_widget)
layout.addWidget(self.tab_widget)
## keep this dialog everpresent..
## it can then keep a record of previous searches etc...
self._find_dialog = None
self.load_history, self.history_pos = [], -1
self.pt = None
#self.clear()
self.connect_signals()
@property
[docs] def is_dirty(self):
return self.ok_to_leave_record()
@property
[docs] def find_dialog(self):
if self._find_dialog is None:
self._find_dialog = dialogs.FindPatientDialog(self)
return self._find_dialog
[docs] def connect_signals(self):
app = QtGui.QApplication.instance()
self.control_panel.refresh_clicked.connect(self.reload_patient)
self.control_panel.new_patient_clicked.connect(self.new_patient)
self.control_panel.next_patient_clicked.connect(self.next_patient)
self.control_panel.last_patient_clicked.connect(self.last_patient)
self.control_panel.related_patient_clicked.connect(self.related_patient)
self.control_panel.find_patient_clicked.connect(self.find_patient)
self.control_panel.home_clicked.connect(self.go_home)
self.connect(self.find_dialog, QtCore.SIGNAL("Advise"), self.Advise)
self.connect(self.find_dialog, QtCore.SIGNAL("Load Serial Number"),
self.load_patient)
self.connect(self.treatment_page, QtCore.SIGNAL("Advise"), self.Advise)
self.connect(self.summary_page, QtCore.SIGNAL("Advise"), self.Advise)
self.connect(app, QtCore.SIGNAL("proc code selected"),
self.treatment_page.proc_code_selected)
self.connect(app, QtCore.SIGNAL("treatment item generated"),
self.treatment_page.add_treatment_item)
self.connect(self.details_browser,
QtCore.SIGNAL("Edit Patient Details"), self.edit_patient)
self.connect(self.details_browser,
QtCore.SIGNAL("Edit Patient Address"), self.edit_address)
self.connect(self.details_browser,
QtCore.SIGNAL("Edit Patient Phone"), self.edit_phone)
self.connect(self.summary_page.summary_chart,
QtCore.SIGNAL("key_press"),
self.charts_page.tooth_data_editor.keyPressEvent)
self.connect(self.summary_page.summary_chart,
QtCore.SIGNAL("current tooth changed"),
self.charts_page.sync_static)
self.connect(self.charts_page.static,
QtCore.SIGNAL("current tooth changed"),
self.summary_page.sync_static)
self.connect(self.charts_page,
QtCore.SIGNAL("clear summary chart selection"),
self.summary_page.clear_static)
self.connect(self.summary_page.summary_chart, QtCore.SIGNAL("Focused"),
self.charts_page.tooth_data_editor.set_mode)
self.connect(self.charts_page, QtCore.SIGNAL("teeth present changed"),
self.summary_page.summary_chart.set_known_teeth)
self.connect(self.charts_page, QtCore.SIGNAL("add treatment"),
self.treatment_page.chart_treatment_added)
self.connect(self.treatment_page, QtCore.SIGNAL("garbage chart tx"),
self.charts_page.tooth_data_editor.invalid_input)
self.connect(self.treatment_page, QtCore.SIGNAL("valid chart tx"),
self.charts_page.tooth_data_editor.valid_input)
self.connect(self.summary_page.summary_chart,
QtCore.SIGNAL("teeth present changed"),
self.charts_page.known_teeth_changed)
self.connect(self.charts_page,
QtCore.SIGNAL("update patient teeth present"),
self.update_patient_teeth_present)
for page in (self.charts_page, self.treatment_page, self.summary_page):
self.connect(page, QtCore.SIGNAL("Show Fee Widget"),
self.call_fee_widget)
self.connect(self.summary_page, QtCore.SIGNAL("clinical_memo_changed"),
self.update_patient_memo_clinical)
self.connect(self.details_browser,
QtCore.SIGNAL("Edit clerical memo"),
self.update_patient_memo_clerical)
self.tab_widget.currentChanged.connect(self.tab_index_changed)
self.connect(self.options_widget,
QtCore.SIGNAL("chart style"), self.set_chart_style)
self.connect(self.summary_page.bpe_widget,
QtCore.SIGNAL("New BPE"), self.new_bpe)
self.connect(self.summary_page.bpe_widget,
QtCore.SIGNAL("Show BPE"), self.list_bpes)
[docs] def Advise(self, *args):
if __name__ == "__main__":
print args
self.emit(QtCore.SIGNAL("Advise"), *args)
[docs] def clear(self):
'''
clears all displayed information by iterating over child widgets
and calling their clear functions
'''
self.details_browser.clear()
self.apply_mode()
for i in range(self.tab_widget.count()):
self.tab_widget.widget(i).clear()
## not sure why I have to call these again??
self.summary_page.clear()
self.options_widget.clear()
self.notes_page.clear()
self.pt = None
self.emit(QtCore.SIGNAL("Patient Loaded"), None)
[docs] def apply_mode(self):
'''
applies the chosen "mode" which can be either a surgery machine or
reception machine
'''
mode = SETTINGS.PERSISTANT_SETTINGS.get("mode", 0)
self.tab_widget.setCurrentIndex(mode)
self.options_widget.tab_index_changed(mode)
[docs] def tab_index_changed(self, i):
self.options_widget.tab_index_changed(i)
page = self.tab_widget.widget(i)
if page == self.notes_page:
self.notes_page.load_patient()
elif page == self.treatment_page:
self.treatment_page.load_patient()
elif page == self.history_page:
self.history_page.load_patient()
[docs] def edit_patient(self):
'''
functionality for editing parameters stored in the patients table
for the loaded patient
'''
pt = self.pt['patient']
dl = dialogs.EditPatientDialog(pt, self)
if dl.exec_():
dl.apply()
if pt.is_dirty:
if pt.commit_changes():
self.refresh_patient()
[docs] def edit_address(self, index):
'''
functionality for editing and adding address information
for the loaded patient
'''
address_db = self.pt['addresses']
dl = dialogs.AddressDialog(address_db, index, self)
refresh_needed = False
if dl.exec_():
self.Advise("update addresses!")
self.refresh_patient()
else:
self.Advise("addresses unchanged!")
[docs] def edit_phone(self):
self.Advise("edit phone numbers")
@property
[docs] def update_patient_teeth_present(self, key):
if not self.pt:
return
self.pt.set_dent_key(key)
[docs] def update_patient_memo_clinical(self, memo):
if not self.pt:
return
self.pt["memo_clinical"].setValue("memo", memo)
[docs] def update_patient_memo_clerical(self):
if not self.pt:
return
dl = QtGui.QInputDialog(self)
dl.setWindowTitle(_("Edit Memo"))
dl.setLabelText(_("Edit Memo"))
dl.setTextValue(self.pt.clerical_memo)
dl.resize(QtCore.QSize(400,200))
if dl.exec_():
self.pt["memo_clerical"].setValue("memo", dl.textValue())
self.details_browser.setHtml(self.pt.details_html())
[docs] def set_chart_style(self, enum):
for chart in (
self.summary_page.summary_chart,
self.charts_page.static,
self.charts_page.treatment,
self.charts_page.completed):
chart.setStyle(enum)
[docs] def new_bpe(self):
'''
raises a dialog and creates a new bpe record for the current patient
'''
if not self.pt:
return
dl = dialogs.NewBpeDialog(self)
if dl.exec_():
self.pt.refresh_bpe()
self.summary_page.bpe_widget.set_values(self.pt.current_bpe)
[docs] def list_bpes(self):
'''
raises a dialog showing all bpes for the current patient
'''
dl = dialogs.ListBpeDialog(self)
dl.exec_()
[docs] def new_patient(self):
'''
raises a dialog and creates a new patient record in the database
'''
dl = dialogs.NewPatientDialog(self)
self.connect(dl, QtCore.SIGNAL("Advise"), self.Advise)
self.connect(dl, QtCore.SIGNAL("Load Serial Number"),
self.load_patient)
dl.exec_()
[docs] def next_patient(self):
'''
cycle forwards through the list of recently visited records
'''
self.history_pos += 1
try:
self.load_patient(self.load_history[self.history_pos], True)
except IndexError:
self.history_pos = len(self.load_history) - 1
self.Advise(_("Reached end of the List"))
[docs] def last_patient(self):
'''
cycle backwards through recently visited records
'''
self.history_pos -= 1
try:
self.load_patient(self.load_history[self.history_pos], True)
except IndexError:
self.history_pos = 0
self.Advise(_("Reached Start of the List"))
[docs] def find_patient(self):
'''
raise a dialog which allows for patient search
I keep a pointer to this dialog at all times
(ie,it is not garbage collected)
when the dialog wants a patient loaded it communicates via a signal
'''
if self.find_dialog.exec_():
self.find_dialog.apply()
[docs] def go_home(self):
'''
user has called for a clearing of the current record
'''
if not self.ok_to_leave_record():
return
self.clear()
[docs] def reload_patient(self):
'''
reload from the database (has record been altered by another terminal?)
'''
if self.pt:
self.load_patient(self.pt.patient_id, True)
[docs] def refresh_patient(self):
'''
refresh after internal (possibly non-committed) changes
'''
if self.pt:
self._load_patient()
def _load_patient(self):
'''
updates the ui after a pt load from db, or a dialog box change
'''
SETTINGS.set_current_patient(self.pt)
self.details_browser.setHtml(self.pt.details_html())
self.options_widget.clear()
self.history_page.clear()
self.estimates_page.clear()
for page in (
self.reception_page,
self.summary_page,
self.charts_page,
self.treatment_page,
):
page.load_patient()
self.summary_page.summary_chart.chart_data_model.endResetModel()
self.pt.treatment_model.update_views()
self.apply_mode()
[docs] def load_patient(self, patient_id, called_via_history = False):
'''
load patient with id patient_id
if optional 2nd arg is passed, this means don't alter the history list
'''
if not self.ok_to_leave_record():
return
self.clear()
self.Advise(u"%s<br />%d"% (_("Loading Record Number"), patient_id))
QtGui.QApplication.instance().setOverrideCursor(QtCore.Qt.WaitCursor)
if __name__ == "__main__":
exception_ = db_orm.PatientNotFoundError
else:
exception_ = Exception
try:
self.pt = db_orm.PatientModel(patient_id)
self._load_patient()
self.emit(QtCore.SIGNAL("Patient Loaded"), self.pt)
if not called_via_history:
self.load_history.append(patient_id)
self.history_pos = len(self.load_history) - 1
except db_orm.PatientNotFoundError:
self.Advise(
u"%s %d %s"% (_("Record"), patient_id, _("not found!")), 1)
except exception_ as e:
QtGui.QApplication.instance().restoreOverrideCursor()
exceptionType, exceptionValue, exceptionTraceback = sys.exc_info()
readable_ex = traceback.format_exception(
exceptionType, exceptionValue, exceptionTraceback)
message = u"%s\n\n"% _("Unhandled exception - please file a bug")
for line in readable_ex:
message += "%s\n" % line
self.Advise(message, 2)
QtGui.QApplication.instance().restoreOverrideCursor()
#@property
[docs] def ok_to_leave_record(self):
LOGGER.debug("checking state before leaving record")
if self.pt is None:
return True
LOGGER.debug("pt is not none")
self.update_patient()
LOGGER.debug("updated patient charts")
if self.pt.is_dirty:
result = self.save_patient(True)
LOGGER.debug("save patient - %s"% result)
if not result:
return False
return True
[docs] def update_patient(self):
'''
applies any changes from the underlying pages into the db ORM
'''
self.charts_page.update_patient()
[docs] def save_patient(self, closing=False):
if self.pt is None:
return
self.update_patient()
if self.pt.is_dirty:
message = "You have unsaved changes to record %s"% (
self.pt.patient_id)
dl = dialogs.SaveDiscardCancelDialog(message,
self.pt.what_has_changed, self)
dl.discard_but.setVisible(closing)
if dl.exec_():
if dl.save_on_exit:
self.pt.commit_changes()
return True
else:
return False
else:
self.Advise(_("No changes Found"))
if __name__ == "__main__":
def _test_dock_widget(dw):
dw.show()
dw.setFloating(True)
from lib_openmolar.common.qt4.widgets import SignallingApplication
app = SignallingApplication("openmolar-client")
mw = QtGui.QMainWindow()
mw.setMinimumSize(500,300)
from lib_openmolar.client.connect import DemoClientConnection
cc = DemoClientConnection()
SETTINGS.load_plugins()
pi = PatientInterface(mw)
cc.connect()
pi.load_patient(1)
mw.setCentralWidget(pi)
mw.connect(pi, QtCore.SIGNAL("Show Fee Widget"), _test_dock_widget)
mw.show()
app.exec_()