gnumed-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Gnumed-devel] Using queues to store parameters for parameterized functi


From: catmat
Subject: [Gnumed-devel] Using queues to store parameters for parameterized functions put on wxCallAfter queue.
Date: Thu, 09 Dec 2004 22:23:00 +1100
User-agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.3) Gecko/20040913


The problem was ghost Edit Controls in the tree if the user rapidly clicks off an edit control after creating a new node. The new node is supposed to be deleted if it isn't labelled, because the database doesn't want default labelled nodes to be stored
only to be deleted later , as it clutters up the auditing.

I think the bug was in using wxCallAfter. I used wxCallAfter because I found changing the tree from within an event
handler caused core dumps.
But the functions that have to be called from wxCallAfter need parameters, so the solution
seemed to be  to queue the parameters as well.

When the function in wxCallAfter gets executed, it reads off the parameter queue, and if it isn't the last parameter
stored on the queue, it discards it, and never creates the node.

Previously , the node was created outside of wxCallAfter, and the event handlers to delete it if it wasn't used during edit mode , was added from within wxCallAfter. So rapid clicking could move the node from edit mode, to saved mode, before the event handlers could be added later in wxCallAfter, and adding the event handlers when the node was already saved
with the default label was too late.

Isn't that an interesting bug ? :-)



>
> import Queue
>
317c320
<
---
>               self.q, self.q_edit  = Queue.Queue(), Queue.Queue()
348,349c351,353
<
<               self.start_edit_node( root_node, start_edit_text)
---
>
>               self.q_edit.put( (root_node, start_edit_text) )
>               wx.wxCallAfter( self.start_edit_node)
359c363,365
<               self.start_edit_node( root_node, start_edit_text)
---
>
>               self.q_edit.put( (root_node, start_edit_text) )
>               wx.wxCallAfter( self.start_edit_node)
362,371c368,394
<       def start_edit_node( self, root_node, start_edit_text):
< node= self.get_emr_tree().AppendItem(root_node, start_edit_text)
<               self.get_emr_tree().EnsureVisible(node)
<               self.get_emr_tree().EditLabel(node)
<               self.edit_control = self.get_emr_tree().GetEditControl()
<               print self.edit_control
<               wx.EVT_KEY_DOWN( self.edit_control,  self.__key_down)
<               wx.EVT_KILL_FOCUS(self.edit_control, self.__kill_focus)
<               self.start_edit_text = start_edit_text
<               self.edit_node = node
---
>       def start_edit_node( self):
>               node = None
>               while True:
>                       try:
>                               print "removed edit request"
>                               old_node = node
> (node, start_edit_text) = self.q_edit.get_nowait()
>
>                               if not old_node is None:
> print "request on old root_node discarded : ", old_node, start_edit_text
>
>                       except Queue.Empty:
>                               break
>
>               if not node is None:
>                       root_node = node
> node= self.get_emr_tree().AppendItem(root_node, start_edit_text)
>                       self.get_emr_tree().EnsureVisible(node)
>                       self.get_emr_tree().EditLabel(node)
> self.edit_control = self.get_emr_tree().GetEditControl()
>                       print self.edit_control
>                       self.start_edit_text = start_edit_text
>                       self.edit_node = node
> wx.EVT_KEY_DOWN( self.edit_control, self.__key_down) > wx.EVT_KILL_FOCUS(self.edit_control, self.__kill_focus)
>               else:
>                       print "No node was found"
385c408,409
<                       self.__item_to_delete = tree_event.GetItem()
---
>                       #self.__item_to_delete = tree_event.GetItem()
>                       self.q.put(tree_event.GetItem())
422,424c446,449
<                       if text == self.start_edit_text:
<                               self.__item_to_delete = self.edit_node
<                               wx.wxCallAfter(self.__delete_item)
---
> if self.edit_node and text == self.start_edit_text:
>                               self.q.put( self.edit_node )
>                               self.edit_node =None
>                               wx.wxCallAfter(self.__delete_item)
427,428c452,458
<               self.get_emr_tree().Delete(self.__item_to_delete)
<
---
>                       while not self.q.empty():
>                               try:
>                                       item = self.q.get_nowait()
>                                       self.get_emr_tree().Delete(item)
>                               except Queue.Empty:
>                                       break
>
525d554
<

"""GnuMed patient EMR tree browser.
"""
#================================================================
# $Source: /cvsroot/gnumed/gnumed/gnumed/client/wxpython/gmEMRBrowser.py,v $
# $Id: gmEMRBrowser.py,v 1.6 2004/10/31 00:37:13 cfmoro Exp $
__version__ = "$Revision: 1.6 $"
__author__ = "address@hidden"
__license__ = "GPL"

import os.path, sys

from wxPython import wx

from Gnumed.pycommon import gmLog, gmI18N, gmPG, gmDispatcher, gmSignals
from Gnumed.exporters import gmPatientExporter
from Gnumed.business import gmEMRStructItems, gmPatient
from Gnumed.wxpython import gmRegetMixin
from Gnumed.pycommon.gmPyCompat import *

_log = gmLog.gmDefLog
_log.Log(gmLog.lInfo, __version__)
#============================================================
class cEMRBrowserPanel(wx.wxPanel, gmRegetMixin.cRegetOnPaintMixin):

        def __init__(self, parent, id):
                """
                Contructs a new instance of EMR browser panel

                parent - Wx parent widget
                id - Wx widget id
                """
                # Call parents constructors
                wx.wxPanel.__init__ (
                        self,
                        parent,
                        id,
                        wx.wxPyDefaultPosition,
                        wx.wxPyDefaultSize,
                        wx.wxNO_BORDER
                )
                gmRegetMixin.cRegetOnPaintMixin.__init__(self)

                self.__pat = gmPatient.gmCurrentPatient()
                self.__exporter = gmPatientExporter.cEmrExport(patient = 
self.__pat)

                self.__do_layout()
                self.__register_interests()
                self.__reset_ui_content()
                
                self.__init_popup()

        #--------------------------------------------------------
        def __do_layout(self):
                """
                Arranges EMR browser layout
                """
                # splitter window
                self.__tree_narr_splitter = wx.wxSplitterWindow(self, -1)
                # emr tree
                self.__emr_tree = wx.wxTreeCtrl (
                        self.__tree_narr_splitter,
                        -1,
                        style=wx.wxTR_HAS_BUTTONS | wx.wxNO_BORDER
                )
                # narrative details text control
                self.__narr_TextCtrl = wx.wxTextCtrl (
                        self.__tree_narr_splitter,
                        -1,
                        style=wx.wxTE_MULTILINE | wx.wxTE_READONLY | 
wx.wxTE_DONTWRAP
                )
                # set up splitter
                # FIXME: read/save value from/into backend
                self.__tree_narr_splitter.SetMinimumPaneSize(20)
                self.__tree_narr_splitter.SplitVertically(self.__emr_tree, 
self.__narr_TextCtrl)

                self.__szr_main = wx.wxBoxSizer(wx.wxVERTICAL)
                self.__szr_main.Add(self.__tree_narr_splitter, 1, wx.wxEXPAND, 
0)

                self.SetAutoLayout(1)
                self.SetSizer(self.__szr_main)
                self.__szr_main.Fit(self)
                self.__szr_main.SetSizeHints(self)
        #--------------------------------------------------------
        # event handling
        #--------------------------------------------------------
        def __register_interests(self):
                """
                Configures enabled event signals
                """
                # wx.wxPython events
                wx.EVT_TREE_SEL_CHANGED(self.__emr_tree, 
self.__emr_tree.GetId(), self._on_tree_item_selected)
                # client internal signals
                gmDispatcher.connect(signal=gmSignals.patient_selected(), 
receiver=self._on_patient_selected)
        #--------------------------------------------------------
        def _on_patient_selected(self):
                """Patient changed."""
                self.__exporter.set_patient(self.__pat)
                self._schedule_data_reget()
        #--------------------------------------------------------
        def _on_tree_item_selected(self, event):
                """
                Displays information for a selected tree node
                """
                sel_item = event.GetItem()
                sel_item_obj = self.__emr_tree.GetPyData(sel_item)
        
                if(isinstance(sel_item_obj, gmEMRStructItems.cEncounter)):
                        header = _('Encounter\n=========\n\n')
                        epi = 
self.__emr_tree.GetPyData(self.__emr_tree.GetItemParent(sel_item))
                        txt = self.__exporter.dump_encounter_info(episode=epi, 
encounter=sel_item_obj)

                elif (isinstance(sel_item_obj, gmEMRStructItems.cEpisode)):
                        header = _('Episode\n=======\n\n')
                        txt = 
self.__exporter.dump_episode_info(episode=sel_item_obj)

                elif (isinstance(sel_item_obj, gmEMRStructItems.cHealthIssue)):
                        header = _('Health Issue\n============\n\n')
                        txt = 
self.__exporter.dump_issue_info(issue=sel_item_obj)

                else:
                        header = _('Summary\n=======\n\n')
                        txt = self.__exporter.dump_summary_info()

                self.__narr_TextCtrl.Clear()
                self.__narr_TextCtrl.WriteText(header)
                self.__narr_TextCtrl.WriteText(txt)
                
                        
                self.popup.SetPopupContext(sel_item)
                 
                
        #--------------------------------------------------------
        # reget mixin API
        #--------------------------------------------------------
        def _populate_with_data(self):
                """
                Fills UI with data.
                """
                self.__reset_ui_content()
                if self.refresh_tree():
                        return True
                return False
                
        #--------------------------------------------------------
        # public API
        #--------------------------------------------------------               
        def refresh_tree(self):
                """
                Updates EMR browser data
                """
                # EMR tree root item
                demos = self.__pat.get_demographic_record()
                names = demos.get_names()
                root_item = self.__emr_tree.AddRoot(_('%s %s EMR') % 
(names['first'], names['last']))

                # Obtain all the tree from exporter
                self.__exporter.get_historical_tree(self.__emr_tree)

                # Expand root node and display patient summary info
                self.__emr_tree.Expand(root_item)
                self.__narr_TextCtrl.WriteText(_('Summary\n=======\n\n'))
                
self.__narr_TextCtrl.WriteText(self.__exporter.dump_summary_info(0))

                # Set sash position
                
self.__tree_narr_splitter.SetSashPosition(self.__tree_narr_splitter.GetSizeTuple()[0]/3,
 True)

                # FIXME: error handling
                return True

        #--------------------------------------------------------
        # internal API
        #--------------------------------------------------------
        def __reset_ui_content(self):
                """
                Clear all information displayed in browser (tree and details 
area)
                """
                self.__emr_tree.DeleteAllItems()
                self.__narr_TextCtrl.Clear()
        #--------------------------------------------------------
#       def set_patient(self, patient):
#               """
#               Configures EMR browser patient and instantiates exporter.
#               Appropiate for standalaone use.
#               patient - The patient to display EMR for
#               """
#               self.__patient = patient
#               self.__exporter.set_patient(patient)

        def get_emr_tree(self):
                return self.__emr_tree  

        def get_EMR_item(self, selected_tree_item):
                return self.__emr_tree.GetPyData(selected_tree_item)
                
        def get_parent_EMR_item(self, selected_tree_item):
                return  
self.__emr_tree.GetPyData(self.__emr_tree.GetItemParent(selected_tree_item))
                
        def repopulate(self):
                self._populate_with_data()      

#------------POPUP methods -------------------------------
        def __init_popup(self):
                """
                initializes the popup for the tree
                """
                self.popup=gmPopupMenuEMRBrowser(self)
                wx.EVT_RIGHT_DOWN(self.__emr_tree, self.__show_popup)
         

                
        def __show_popup(self, event):
                 self.PopupMenu(self.popup, (event.GetX(), event.GetY() ))

#== Module convenience functions (for standalone use) =======================
def prompted_input(prompt, default=None):
        """
        Obtains entry from standard input
        
        promp - Promt text to display in standard output
        default - Default value (for user to press only intro)
        """
        usr_input = raw_input(prompt)
        if usr_input == '':
                return default
        return usr_input
#------------------------------------------------------------                   
         
def askForPatient():
        """
                Main module application patient selection function.
        """
        
        # Variable initializations
        pat_searcher = gmPatient.cPatientSearcher_SQL()

        # Ask patient to dump and set in exporter object
        patient_term = prompted_input("\nPatient search term (or 'bye' to exit) 
(eg. Kirk): ")
        if patient_term == 'bye':
                return None
        search_ids = pat_searcher.get_patient_ids(search_term = patient_term)
        if search_ids is None or len(search_ids) == 0:
                prompted_input("No patient matches the query term. Press any 
key to continue.")
                return None
        elif len(search_ids) > 1:
                prompted_input("Various patients match the query term. Press 
any key to continue.")
                return None
        patient_id = search_ids[0]
        patient = gmPatient.gmCurrentPatient(patient_id)
        return patient



import Queue

class gmPopupMenuEMRBrowser(wx.wxMenu):
        """
        popup menu for updating the EMR tree.
        """
        def __init__(self , browser):
                wx.wxMenu.__init__(self)
                self.ID_NEW_ENCOUNTER=1 
                self.ID_NEW_HEALTH_ISSUE=2
                self.ID_NEW_EPISODE=3
                self.__browser = browser
                self.__mediator = NarrativeTreeItemMediator1(browser)
                wx.EVT_MENU(self.__browser, self.ID_NEW_HEALTH_ISSUE , 
self.__mediator.new_health_issue)
                wx.EVT_MENU(self.__browser, self.ID_NEW_EPISODE , 
self.__mediator.new_episode)
                
        def Clear(self):
                for item in self.GetMenuItems():
                        self.Remove(item.GetId())
                                
        def SetPopupContext( self, sel_item):
        
                self.Clear()
                
                sel_item_obj = self.__browser.get_EMR_item(sel_item)
                
                if(isinstance(sel_item_obj, gmEMRStructItems.cEncounter)):
                        header = _('Encounter\n=========\n\n')
                        
                        
self.__append_new_encounter_menuitem(episode=self.__browser.get_parent_EMR_item(sel_item)
 )
                        
                        
                elif (isinstance(sel_item_obj, gmEMRStructItems.cEpisode)):
                        header = _('Episode\n=======\n\n')
                        
                        
self.__append_new_encounter_menuitem(episode=self.__browser.get_EMR_item(sel_item)
 )
                        
                        
self.__append_new_episode_menuitem(health_issue=self.__browser.get_parent_EMR_item(sel_item))
                        
                        
                elif (isinstance(sel_item_obj, gmEMRStructItems.cHealthIssue)):
                        header = _('Health Issue\n============\n\n')
                        
                        
self.__append_new_episode_menuitem(health_issue=self.__browser.get_EMR_item(sel_item))
                        
                        self.Append(self.ID_NEW_HEALTH_ISSUE, "New Health 
Issue")
                        

                else:
                        header = _('Summary\n=======\n\n')
                        self.Append(self.ID_NEW_HEALTH_ISSUE, "New Health 
Issue")
                        

        def __append_new_encounter_menuitem(self, episode):
                self.Append(self.ID_NEW_ENCOUNTER, "New Encounter (of episode 
'%s')" % episode['description'] )
                
        def __append_new_episode_menuitem(self, health_issue):
                self.Append(self.ID_NEW_EPISODE, "New Episode(of health issue 
'%s')" % health_issue['description'] )

        
                
class NarrativeTreeItemMediator1:
        """
        handler for popup menu actions.
        Handles the unchanged new item problem , where no tree events are 
fired, by listening
        on the edit control events.
        """
        def __init__(self, browser):
                self.q, self.q_edit  = Queue.Queue(), Queue.Queue()
                self.__browser = browser
                wx.EVT_TREE_END_LABEL_EDIT(self.get_emr_tree(), 
self.get_emr_tree().GetId(), self.__end_label_edit)
                
                self.HEALTH_ISSUE_START_LABEL="NEW HEALTH ISSUE"
                self.EPISODE_START_LABEL="NEW EPISODE"
                
        def get_browser(self):
                return self.__browser
        
                
        def get_emr_tree(self):
                return self.__browser.get_emr_tree()    
                
        def new_health_issue(self, menu_event):
                """
                entry from MenuItem New Health Issue
                """
                self.start_edit_root_node( self.HEALTH_ISSUE_START_LABEL)
        
        def new_episode(self, menu_event):
                """
                entry from MenuItem New Episode Issue
                """
                self.start_edit_child_node( self.EPISODE_START_LABEL)   
                
        def start_edit_child_node(self, start_edit_text):
                root_node = self.get_emr_tree().GetSelection()
                if start_edit_text == self.EPISODE_START_LABEL and \
                isinstance(self.get_browser().get_EMR_item(root_node), 
gmEMRStructItems.cEpisode):
                        root_node = self.get_emr_tree().GetItemParent(root_node)

                self.q_edit.put( (root_node, start_edit_text) )
                wx.wxCallAfter( self.start_edit_node)
                
        def start_edit_root_node(self, start_edit_text ):
                """
                this handles the problem of no event fired if label unchanged.
                By detecting for the return key on the edit control, the start 
text 
                can be compared, and if no change, the node is deleted
                in the __key_down handler
                """
                root_node = self.get_emr_tree().GetRootItem()

                self.q_edit.put( (root_node, start_edit_text) )
                wx.wxCallAfter( self.start_edit_node)
        
                        
        def start_edit_node( self):
                node = None             
                while True:
                        try:
                                print "removed edit request"
                                old_node = node
                                (node, start_edit_text) = 
self.q_edit.get_nowait()      

                                if not old_node is None:
                                        print "request on old root_node 
discarded : ", old_node, start_edit_text

                        except Queue.Empty:
                                break
                        
                if not node is None:    
                        root_node = node
                        node= self.get_emr_tree().AppendItem(root_node, 
start_edit_text)
                        self.get_emr_tree().EnsureVisible(node)
                        self.get_emr_tree().EditLabel(node)
                        self.edit_control = self.get_emr_tree().GetEditControl()
                        print self.edit_control
                        self.start_edit_text = start_edit_text
                        self.edit_node = node   
                        wx.EVT_KEY_DOWN( self.edit_control,  self.__key_down)
                        wx.EVT_KILL_FOCUS(self.edit_control, self.__kill_focus)
                else:
                        print "No node was found"
                
        def __end_label_edit(self, tree_event):
                """
                check to see if editing cancelled , and if not, then do update 
for each kind of label
                """
                print "end label edit Handled" 
                print "tree_event is ", tree_event
                print "after ", tree_event.__dict__
                print "label is ", tree_event.GetLabel()
                 
                        
                if tree_event.IsEditCancelled() or 
len(tree_event.GetLabel().strip()) == 0:
                        tree_event.Skip()
                        #self.__item_to_delete = tree_event.GetItem()
                        self.q.put(tree_event.GetItem())
                        wx.wxCallAfter(self.__delete_item)
                else:
                        if self.start_edit_text == 
self.HEALTH_ISSUE_START_LABEL:
                                
wx.wxCallAfter(self.__add_new_health_issue_to_record)
                        
                        elif self.start_edit_text == self.EPISODE_START_LABEL:
                                
wx.wxCallAfter(self.__add_new_episode_to_record)        
                        
        def __key_down(self, event):
                """
                this event on the EditControl needs to be handled because
                1) pressing enter whilst no change in label , does not fire any 
END_LABEL event, so tentative 
                tree items cannot be deleted.
                
                 .
                """
                print "Item is ", event.GetKeyCode()
                event.Skip()
                
                if event.GetKeyCode() == wx.WXK_RETURN:
                        self.__check_for_unchanged_item()
                        
        
                                
        def __kill_focus(self, event):
                print "kill focuse"
                
                self.__check_for_unchanged_item()
                event.Skip()
        
        def __check_for_unchanged_item(self):   
                        text = self.edit_control.GetValue().strip() 
                        print "Text was ", text
                        print "Text unchanged ", text == self.start_edit_text
                        
                        #if the text is unchanged, then delete the new node.
                        if self.edit_node and text == self.start_edit_text:
                                self.q.put( self.edit_node )
                                self.edit_node =None
                                wx.wxCallAfter(self.__delete_item)
                
        def __delete_item(self):
                        while not self.q.empty():
                                try:
                                        item = self.q.get_nowait()
                                        self.get_emr_tree().Delete(item)
                                except Queue.Empty:
                                        break
                                        
        def __add_new_health_issue_to_record(self):
                print "add new health issue to record"
                pat = gmPatient.gmCurrentPatient()
                rec = pat.get_clinical_record()
                issue = rec.add_health_issue( 
self.get_emr_tree().GetItemText(self.edit_node).strip() )
                if not issue is None and isinstance(issue, 
gmEMRStructItems.cHealthIssue):
                        self.get_emr_tree().SetPyData( self.edit_node, issue)
                        
                 
                
        def __add_new_episode_to_record(self):
                print "add new episode to record"
                pat = gmPatient.gmCurrentPatient()
                rec = pat.get_clinical_record()
                print "health_issue pk = ", 
self.get_browser().get_parent_EMR_item(self.edit_node).pk_obj
                print "text = ", 
self.get_emr_tree().GetItemText(self.edit_node).strip()
                
                episode = rec.add_episode( 
self.get_emr_tree().GetItemText(self.edit_node).strip(), 
self.get_browser().get_parent_EMR_item(self.edit_node).pk_obj )
                 
                if not episode is None and isinstance(episode, 
gmEMRStructItems.cEpisode):
                        self.get_emr_tree().SetPyData( self.edit_node, episode) 
                
#================================================================
# MAIN
#----------------------------------------------------------------
if __name__ == '__main__':

        from Gnumed.pycommon import gmCfg

        _log.SetAllLogLevels(gmLog.lData)
        _log.Log (gmLog.lInfo, "starting emr browser...")

        _cfg = gmCfg.gmDefCfgFile        
        if _cfg is None:
                _log.Log(gmLog.lErr, "Cannot run without config file.")
                sys.exit("Cannot run without config file.")

        try:
                # make sure we have a db connection
                gmPG.set_default_client_encoding('latin1')
                pool = gmPG.ConnectionPool()
                
                # obtain patient
                patient = askForPatient()
                if patient is None:
                        print "No patient. Exiting gracefully..."
                        sys.exit(0)

                # display standalone browser
                application = wx.wxPyWidgetTester(size=(800,600))
                emr_browser = cEMRBrowserPanel(application.frame, -1)
#               emr_browser.set_patient(patient)                
                emr_browser.refresh_tree()
                
                application.frame.Show(True)
                application.MainLoop()
                
                # clean up
                if patient is not None:
                        try:
                                patient.cleanup()
                        except:
                                print "error cleaning up patient"
        except StandardError:
                _log.LogException("unhandled exception caught !", 
sys.exc_info(), 1)
                # but re-raise them
                raise
        try:
                pool.StopListeners()
        except:
                _log.LogException('unhandled exception caught', sys.exc_info(), 
verbose=1)
                raise

        _log.Log (gmLog.lInfo, "closing emr browser...")

#================================================================
# $Log: gmEMRBrowser.py,v $
# Revision 1.6  2004/10/31 00:37:13  cfmoro
# Fixed some method names. Refresh function made public for easy reload, eg. 
standalone. Refresh browser at startup in standalone mode
#
# Revision 1.5  2004/09/06 18:57:27  ncq
# - Carlos pluginized the lot ! :-)
# - plus some fixes/tabified it
#
# Revision 1.4  2004/09/01 22:01:45      ncq
# - actually use Carlos' issue/episode summary code
#
# Revision 1.3  2004/08/11 09:46:24      ncq
# - now that EMR exporter supports SOAP notes - display them
#
# Revision 1.2  2004/07/26 00:09:27      ncq
# - Carlos brings us data display for the encounters - can REALLY browse EMR 
now !
#
# Revision 1.1  2004/07/21 12:30:25      ncq
# - initial checkin
#

reply via email to

[Prev in Thread] Current Thread [Next in Thread]