#!/usr/bin/env python
# -*- coding: utf-8 -*-
###############################################################################
## ##
## Copyright 2011-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/>. ##
## ##
###############################################################################
from PyQt4 import QtGui, QtCore
from openmolar.qt4gui.appointment_gui_modules.draggable_list \
import DraggableList
from openmolar.qt4gui.appointment_gui_modules.list_models \
import SimpleListModel, BlockListModel
from lib_openmolar.client.qt4.dialogs import FindPatientDialog
from lib_openmolar.client.qt4.pt_diary_widget import PtDiaryWidget
[docs]class DiaryScheduleController(QtGui.QStackedWidget):
BROWSE_MODE = 0
SCHEDULE_MODE = 1
BLOCK_MODE = 2
NOTES_MODE = 3
mode = BROWSE_MODE
CLINICIAN_SELECTED = 0
CLINICIAN_ANY_DENT = 1
CLINICIAN_ANY_HYG = 2
CLINICIAN_ANY = 3
selection_mode = CLINICIAN_SELECTED
appointment_selected = QtCore.pyqtSignal(object)
patient_selected = QtCore.pyqtSignal(object)
show_first_appointment = QtCore.pyqtSignal()
chosen_slot_changed = QtCore.pyqtSignal()
move_on = QtCore.pyqtSignal(object)
find_appt = QtCore.pyqtSignal(object)
start_scheduling = QtCore.pyqtSignal()
pt = None
available_slots = []
hygienist_slots = []
_chosen_slot = None
excluded_days = []
ignore_emergency_spaces = False
finding_joint_appointments = False
use_last_slot = False
pt_diary_widget = None
[docs] def __init__(self, parent=None):
QtGui.QStackedWidget.__init__(self, parent)
self.patient_label = QtGui.QLabel()
icon = QtGui.QIcon(":/search.png")
self.get_patient_button = QtGui.QPushButton(icon, "")
self.get_patient_button.setMaximumWidth(40)
self.appt_listView = DraggableList(True, self)
self.block_listView = DraggableList(False, self)
self.appointment_model = SimpleListModel(self)
self.appt_listView.setModel(self.appointment_model)
self.appt_listView.setSelectionModel(
self.appointment_model.selection_model)
self.appt_listView.setSelectionMode(QtGui.QListView.SingleSelection)
block_model = BlockListModel(self)
self.block_listView.setModel(block_model)
icon = QtGui.QIcon(":vcalendar.png")
diary_button = QtGui.QPushButton(icon, "")
diary_button.setToolTip(_("Open the patient's diary"))
icon = QtGui.QIcon(":first.png")
self.first_appt_button = QtGui.QPushButton(icon,"")
self.first_appt_button.setToolTip(_("Launch the Appointment Wizard"))
icon = QtGui.QIcon(":back.png")
self.prev_appt_button = QtGui.QPushButton(icon, "")
self.prev_appt_button.setToolTip(_("Previous appointment"))
icon = QtGui.QIcon(":forward.png")
self.next_appt_button = QtGui.QPushButton(icon, "")
self.next_appt_button.setToolTip(_("Next available appointment"))
self.appt_controls_frame = QtGui.QWidget()
layout = QtGui.QGridLayout(self.appt_controls_frame)
layout.setMargin(1)
layout.addWidget(diary_button,0,0)
layout.addWidget(self.first_appt_button,0,1)
layout.addWidget(self.prev_appt_button,0,2)
layout.addWidget(self.next_appt_button,0,3)
self.appt_controls_frame.setSizePolicy(
QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred,
QtGui.QSizePolicy.Minimum))
# now arrange the stacked widget
#page 0 - Browsing mode
self.addWidget(QtGui.QLabel("Browsing"))
#page 1 -- scheduling mode
widg = QtGui.QWidget()
layout = QtGui.QGridLayout(widg)
layout.setMargin(0)
layout.addWidget(self.patient_label,0,0)
layout.addWidget(self.get_patient_button,0,1)
layout.addWidget(self.appt_listView,2,0,1,2)
layout.addWidget(self.appt_controls_frame,3,0,1,2)
self.addWidget(widg)
#page 2 -- blocking mode
widg = QtGui.QWidget()
layout = QtGui.QVBoxLayout(widg)
layout.addWidget(self.block_listView)
self.addWidget(widg)
#page 4 -- notes mode
self.addWidget(QtGui.QLabel("Notes"))
#connect signals
self.appointment_model.appointment_selected.connect(
self.update_selected_appointment)
self.get_patient_button.clicked.connect(self.find_patient)
self.first_appt_button.clicked.connect(self.show_first_appt)
self.prev_appt_button.clicked.connect(self.show_prev_appt)
self.next_appt_button.clicked.connect(self.show_next_appt)
diary_button.clicked.connect(self.show_pt_diary)
self.appt_listView.pressed.connect(self.appointment_pressed)
self.appt_listView.clicked.connect(self.appointment_clicked)
self.appt_listView.doubleClicked.connect(self.appointment_2x_clicked)
[docs] def set_mode(self, mode):
if self.mode == mode:
return
if mode == self.SCHEDULE_MODE:
self.update_patient_label()
self.enable_scheduling_buttons()
self.mode = mode
self.setCurrentIndex(mode)
[docs] def set_patient(self, pt):
self.clear()
self.pt = pt
if pt is not None:
self.appointment_model.load_from_database(self.pt)
self.patient_selected.emit(self.pt)
self.enable_scheduling_buttons()
[docs] def set_chosen_appointment(self, appointment):
self.appointment_model.set_current_appt(appointment)
[docs] def get_data(self):
if self.pt is None:
self.clear()
return
self.appointment_model.load_from_database(self.pt)
@property
[docs] def patient_text(self):
if self.pt:
return "%s %s (%s)"% (
self.pt.fname, self.pt.sname, self.pt.serialno)
else:
return _("No patient Selected")
[docs] def find_patient(self):
dl = FindPatientDialog(self)
if dl.exec_():
self.clear()
pt = BriefPatient(dl.chosen_sno)
self.set_patient(pt)
self.update_patient_label()
[docs] def update_patient_label(self):
self.patient_label.setText(self.patient_text)
@property
[docs] def min_slot_length(self):
msl = 0
if self.mode == self.SCHEDULE_MODE:
msl = self.appointment_model.min_slot_length
return msl
@property
[docs] def min_hyg_slot_length(self):
msl = None
if self.mode == self.SCHEDULE_MODE:
msl = self.appointment_model.min_unscheduled_hyg_slot_length
return msl
[docs] def set_selection_mode(self, mode):
assert mode in (
self.CLINICIAN_ANY,
self.CLINICIAN_ANY_DENT,
self.CLINICIAN_ANY_HYG,
self.CLINICIAN_SELECTED), "selection mode misunderstood"
self.selection_mode = mode
@property
[docs] def selectedClinicians(self):
if self.selection_mode == self.CLINICIAN_ANY_HYG:
return localsettings.activehyg_ixs
if self.selection_mode == self.CLINICIAN_ANY_DENT:
return localsettings.activedent_ixs
if self.selection_mode == self.CLINICIAN_ANY:
return localsettings.activedent_ixs + localsettings.activehyg_ixs
return self.appointment_model.selectedClinicians
@property
[docs] def involvedClinicians(self):
return self.appointment_model.involvedClinicians
[docs] def set_ignore_emergency_spaces(self, bool_):
self.ignore_emergency_spaces = bool_
[docs] def sizeHint(self):
return QtCore.QSize(150, 200)
[docs] def update_appt_selection(self, appt):
'''
sync with the "patient diary" via signal/slot
'''
if appt is None or self.pt is None:
return
if appt.serialno != self.pt.serialno:
return
index = self.appointment_model.set_current_appt(appt)
self.appt_listView.setCurrentIndex(index)
[docs] def update_selected_appointment(self, appt):
self.available_slots = []
self._chosen_slot = None
self.enable_scheduling_buttons()
[docs] def appointment_clicked(self, index):
logging.debug("ScheduleControl.appointment_clicked")
[docs] def appointment_pressed(self, index):
logging.debug("ScheduleControl.appointment_pressed")
self.appointment_selected.emit(self.appointment_model.currentAppt)
[docs] def appointment_2x_clicked(self, index):
logging.debug("ScheduleControl.appointment_clicked")
self.show_first_appointment.emit()
[docs] def clear(self):
self.appointment_model.clear()
self.reset()
[docs] def reset(self):
self.available_slots = []
self.hygienist_slots = []
self._chosen_slot = None
self.finding_joint_appointments = False
[docs] def show_first_appt(self):
'''
resets the chosen slot and emits show_first_appointment signal
'''
self._chosen_slot = None
self.show_first_appointment.emit()
@property
def _chosen_slot_no(self):
try:
return self.available_slots.index(self._chosen_slot)
except ValueError:
return 0
[docs] def show_next_appt(self):
try:
self._chosen_slot = self.available_slots[self._chosen_slot_no + 1]
self.chosen_slot_changed.emit()
except IndexError:
self._chosen_slot = None
self.move_on.emit(True)
[docs] def show_prev_appt(self):
try:
i = self._chosen_slot_no - 1
if i < 0:
raise IndexError
self._chosen_slot = self.available_slots[i]
self.chosen_slot_changed.emit()
except IndexError:
self._chosen_slot = None
self.move_on.emit(False)
[docs] def set_available_slots(self, slots):
self.available_slots = []
self.hygienist_slots = []
for slot in sorted(slots):
if (slot.dent in self.selectedClinicians
and slot.day_no not in self.excluded_days) :
self.available_slots.append(slot)
[docs] def set_joint_slots(self, dent_slots, hyg_slots, max_wait=10):
logging.debug(
"ScheduleControl.set join slots %s %s"% (dent_slots, hyg_slots))
self.available_slots = []
self.hygienist_slots = []
all_dent_slots = []
all_hyg_slots = []
for slot in sorted(dent_slots):
if (slot.dent in self.selectedClinicians
and slot.day_no not in self.excluded_days) :
all_dent_slots.append(slot)
for slot in sorted(hyg_slots):
if slot.day_no not in self.excluded_days :
all_hyg_slots.append(slot)
appt = self.appointment_model.currentAppt
for slot in all_dent_slots:
hyg_slot, wait = slot.best_joint(
appt.length,
self.min_hyg_slot_length,
all_hyg_slots)
if wait <= max_wait:
self.available_slots.append(slot)
self.hygienist_slots.append(hyg_slot)
@property
[docs] def chosen_hyg_slot(self):
if self.hygienist_slots == [] or self.chosen_slot is None:
return None
appt = self.appointment_model.currentAppt
best_slot, wait = self.chosen_slot.best_joint(
appt.length,
self.min_hyg_slot_length,
self.hygienist_slots)
logging.info("WAIT TIME FOR HYGIENIST = %s minutes"% wait)
return best_slot
@property
[docs] def last_appt_date(self):
'''
returns the latest date of patient's appointments,
or today's date if none found
'''
last_d = QtCore.QDate.currentDate().toPyDate()
for appt in self.appointment_model.scheduledList:
if appt.date > last_d:
last_d = appt.date
return last_d
@property
[docs] def is_searching(self):
appt = self.appointment_model.currentAppt
return appt is not None and appt.unscheduled
@property
[docs] def search_again(self):
'''
this determines whether it is worth continuing
'''
return ( self.is_searching and
len(self.selectedClinicians)>0 and
len(self.available_slots)==0
)
@property
[docs] def chosen_slot(self):
if self.available_slots == []:
return None
if self._chosen_slot is None:
if self.use_last_slot:
i = -1
self.use_last_slot = False
else:
i = 0
self._chosen_slot = self.available_slots[i]
return self._chosen_slot
[docs] def set_excluded_days(self, days):
self.excluded_days = days
[docs] def show_pt_diary(self):
if self.pt is None:
QtGui.QMessageBox.information(self, _("error"),
_("No patient selected"))
return
def _find_appt(appt):
dl.accept()
self.find_appt.emit(appt)
def _start_scheduling():
dl.accept()
QtCore.QTimer.singleShot(100, self.start_scheduling.emit)
pt_diary_widget = PtDiaryWidget()
pt_diary_widget.find_appt.connect(_find_appt)
pt_diary_widget.start_scheduling.connect(_start_scheduling)
pt_diary_widget.appointment_selected.connect(
self.appointment_model.set_current_appt)
pt_diary_widget.set_patient(self.pt)
pt_diary_widget.layout_ptDiary()
dl = QtGui.QDialog(self)
but_box = QtGui.QDialogButtonBox(dl)
but = but_box.addButton(_("Close"), but_box.AcceptRole)
but.clicked.connect(dl.accept)
layout = QtGui.QVBoxLayout(dl)
layout.addWidget(pt_diary_widget)
layout.addStretch()
layout.addWidget(but_box)
dl.exec_()
self.appointment_model.load_from_database(self.pt)
self.enable_scheduling_buttons()
#now force diary relayout
self.appointment_selected.emit(self.appointment_model.currentAppt)
[docs]class TestWindow(QtGui.QMainWindow):
MODES = ("Browse", "Schedule", "Block", "Notes")
[docs] def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.schedule_controller = DiaryScheduleController()
self.but = QtGui.QPushButton()
self.but.clicked.connect(self.change_mode)
self.mode = self.schedule_controller.BROWSE_MODE
frame = QtGui.QFrame()
layout = QtGui.QVBoxLayout(frame)
layout.addWidget(self.schedule_controller)
layout.addWidget(self.but)
self.set_but_text()
scroll_area = QtGui.QScrollArea()
scroll_area.setWidget(frame)
scroll_area.setWidgetResizable(True)
self.setCentralWidget(scroll_area)
self.schedule_controller.appointment_selected.connect(
self.sig_catcher)
self.schedule_controller.patient_selected.connect(self.sig_catcher)
self.schedule_controller.show_first_appointment.connect(
self.sig_catcher)
self.schedule_controller.chosen_slot_changed.connect(self.sig_catcher)
self.schedule_controller.move_on.connect(self.sig_catcher)
self.schedule_controller.find_appt.connect(self.sig_catcher)
self.schedule_controller.start_scheduling.connect(self.sig_catcher)
[docs] def set_but_text(self):
self.but.setText("set mode (current='%s')"% self.MODES[self.mode])
[docs] def change_mode(self):
'''
toggle through the modes
'''
self.mode += 1
if self.mode > self.schedule_controller.NOTES_MODE:
self.mode = self.schedule_controller.BROWSE_MODE
self.set_but_text()
self.schedule_controller.set_mode(self.mode)
[docs] def sig_catcher(self, *args):
QtGui.QMessageBox.information(self, "signal",
"signal emitted %s"% str(args))
if __name__ == "__main__":
from lib_openmolar.common.qt4.widgets import SignallingApplication
app = SignallingApplication("test_application")
from lib_openmolar.client.connect import DemoClientConnection
cc = DemoClientConnection()
cc.connect()
obj = TestWindow()
obj.show()
app.exec_()