Package Gnumed :: Package wxpython :: Module gmResizingWidgets
[frames] | no frames]

Source Code for Module Gnumed.wxpython.gmResizingWidgets

   1  """gmResizingWidgets - Resizing widgets for use in GNUmed. 
   2   
   3  Design by Richard Terry and Ian Haywood. 
   4  """ 
   5  #==================================================================== 
   6  # $Source: /home/ncq/Projekte/cvs2git/vcs-mirror/gnumed/gnumed/client/wxpython/gmResizingWidgets.py,v $ 
   7  # $Id: gmResizingWidgets.py,v 1.55 2009-03-10 14:24:15 ncq Exp $ 
   8  __version__ = "$Revision: 1.55 $" 
   9  __author__ = "Ian Haywood, Karsten Hilbert, Richard Terry" 
  10  __license__ = 'GPL v2 or later (details at http://www.gnu.org)' 
  11   
  12  import sys, logging, re as regex 
  13   
  14   
  15  import wx 
  16  import wx.stc 
  17   
  18   
  19  from Gnumed.pycommon import gmI18N, gmDispatcher, gmPG2 
  20  from Gnumed.wxpython import gmGuiHelpers, gmTimer 
  21   
  22  _log = logging.getLogger('gm.ui') 
  23  _log.info(__version__) 
  24   
  25  STYLE_ERROR=1 
  26  STYLE_TEXT=2 
  27  STYLE_EMBED=4 
  28   
  29  #==================================================================== 
30 -class cPickList(wx.ListBox):
31 - def __init__ (self, parent, pos, size, callback):
32 wx.ListBox.__init__(self, parent, -1, pos, size, style=wx.LB_SINGLE | wx.LB_NEEDED_SB) 33 self.callback = callback 34 self.alive = 1 # 0=dead, 1=alive, 2=must die 35 wx.EVT_LISTBOX (self, self.GetId(), self.OnList)
36 #------------------------------------------------
37 - def SetItems (self, items):
38 """ 39 Sets the items, Items is a dict with label, data, weight items 40 """ 41 items.sort (lambda a,b: cmp(b['weight'], a['weight'])) 42 self.Clear() 43 self.Set([item['label'] for item in items]) 44 n = 0 45 for item in items: 46 self.SetClientData(n, item['data']) 47 # n += 1 ?? 48 self.SetSelection(0)
49 #------------------------------------------------
50 - def Up(self):
51 line = self.GetSelection() 52 if line > 0: 53 self.SetSelection(line-1)
54 #------------------------------------------------
55 - def Down(self):
56 line = self.GetSelection() 57 if line < self.GetCount()-1: 58 self.SetSelection(line+1)
59 #------------------------------------------------
60 - def Enter (self):
61 line = self.GetSelection() 62 if line >= 0: 63 text = self.GetString(line) 64 data = self.GetClientData(line) 65 self.callback(text, data) 66 self.alive = 2 67 self.Destroy() # this is only safe when in the event handler of another widget
68 #------------------------------------------------
69 - def OnList(self, event):
70 event.Skip() 71 if self.alive != 2: 72 line = self.GetSelection() 73 if line >= 0: 74 text = self.GetString(line) 75 data = self.GetClientData(line) 76 self.callback (text, data) 77 self.alive = 2 78 else: 79 wx.CallAfter (self.Destroy) # in theory we shouldn't have to do this,
80 # but when we don't, wx segfaults. 81 #------------------------------------------------
82 - def Destroy (self):
83 self.alive = 0 84 wx.ListBox.Destroy (self)
85 #==================================================================== 86 # according to Ian there isn't really a particular reason 87 # why we do not use wxMiniFrame instead of wx.Frame or even a wxWindow
88 -class cPopupFrame(wx.Frame):
89 # def __init__ (self, embed_header, widget_class, originator=None, pos=wx.DefaultPosition): 90 # wx.Frame.__init__(self, None, wxNewId(), widget_class.__name__, pos=pos, style=wx.SIMPLE_BORDER) 91 # self.win = widget_class(self, -1, pos = pos, size = wx.Size(300, 150), complete = self.OnOK)
92 - def __init__ (self, embed_header, widget, originator=None, pos=wx.DefaultPosition):
93 wx.Frame.__init__(self, None, wx.NewId(), widget.__class__.__name__, pos=pos, style=wx.SIMPLE_BORDER) 94 widget.set_completion_callback(self.OnOK) 95 self.win = widget 96 self.embed_header = embed_header 97 self.originator = originator 98 99 self.__do_layout() 100 101 wx.EVT_BUTTON(self.__BTN_OK, self.__BTN_OK.GetId(), self.OnOK) 102 wx.EVT_BUTTON(self.__BTN_Cancel, self.__BTN_Cancel.GetId(), self._on_close) 103 self.win.SetFocus ()
104 #------------------------------------------------
105 - def __do_layout(self):
106 self.__BTN_OK = wx.Button (self, -1, _("OK"), style=wx.BU_EXACTFIT) 107 self.__BTN_Cancel = wx.Button (self, -1, _("Cancel"), style=wx.BU_EXACTFIT) 108 szr_btns = wx.BoxSizer (wx.HORIZONTAL) 109 szr_btns.Add(self.__BTN_OK, 0, 0) 110 szr_btns.Add(self.__BTN_Cancel, 0, 0) 111 112 szr_main = wx.BoxSizer(wx.VERTICAL) 113 szr_main.Add(self.win, 1, wx.EXPAND, 0) 114 szr_main.Add(szr_btns, 0, wx.EXPAND) 115 116 self.SetAutoLayout(1) 117 self.SetSizer(szr_main) 118 szr_main.Fit(self) 119 szr_main.SetSizeHints(self) 120 self.Layout()
121 #------------------------------------------------
122 - def _on_close (self, event):
123 self.Close()
124 #------------------------------------------------
125 - def OnOK (self, event=None):
126 if self.originator: 127 self.originator.Embed ("%s: %s" % (self.embed_header, self.win.GetSummary())) 128 self.Close ()
129 #====================================================================
130 -class cSTCval:
131 - def __init__(self):
132 self.text = None 133 self.data = None
134 #====================================================================
135 -class cResizingWindow(wx.ScrolledWindow):
136 """A vertically-scrolled window which allows subwindows 137 to change their size, and adjusts accordingly. 138 """
139 - def __init__ (self, parent, id, pos = wx.DefaultPosition, size = wx.DefaultSize):
140 141 wx.ScrolledWindow.__init__(self, parent, id, pos = pos, size = size, style=wx.VSCROLL) 142 self.SetScrollRate(0, 20) # suppresses X scrolling by setting X rate to zero 143 144 # self.__list = None 145 # self.complete = complete # ?? 146 147 self.__input_lines = [[]] 148 self.__szr_main = None 149 self.DoLayout() 150 self.__szr_main = wx.FlexGridSizer(len(self.__input_lines), 2) 151 for line in self.__input_lines: 152 if len(line) != 0: 153 # first label goes into column 1 154 if line[0]['label'] is not None: 155 self.__szr_main.Add(line[0]['label'], 1) 156 else: 157 self.__szr_main.Add((1, 1)) 158 # the rest gets crammed into column 2 159 h_szr = wx.BoxSizer (wx.HORIZONTAL) 160 h_szr.Add(line[0]['instance'], 1, wx.EXPAND) 161 for widget in line[1:]: 162 if widget['label'] is not None: 163 h_szr.Add(widget['label'], 0) 164 h_szr.Add(widget['instance'], 1, wx.EXPAND) 165 self.__szr_main.Add(h_szr, 1, wx.EXPAND) 166 self.__szr_main.AddGrowableCol(1) 167 self.__szr_main.Add((1, 1)) 168 169 self.SetSizer(self.__szr_main) 170 self.__szr_main.Fit(self) 171 self.FitInside()
172 #------------------------------------------------
173 - def AddWidget(self, widget, label=None):
174 """ 175 Adds a widget, optionally with label 176 177 @type label: string 178 @param label: text of the label 179 @type widgets: wx.Window descendant 180 """ 181 if label is None: 182 textbox = None 183 else: 184 textbox = wx.StaticText(self, -1, label, style=wx.ALIGN_RIGHT) 185 # append to last line 186 self.__input_lines[-1].append({'ID': label, 'label': textbox, 'instance': widget})
187 #------------------------------------------------
188 - def Newline (self):
189 """ 190 Starts a newline on the widget 191 """ 192 self.__input_lines.append([])
193 #------------------------------------------------
194 - def DoLayout (self):
195 """ 196 Overridden by descendants, this function uses AddWidget and Newline to form 197 the outline of the widget 198 """ 199 _log.error('[%s] forgot to override DoLayout()' % self.__class__.__name__)
200 #------------------------------------------------
201 - def ReSize (self, widget, new_height):
202 """Called when a child widget has a new height, redoes the layout. 203 """ 204 if self.__szr_main is not None: 205 self.__szr_main.SetItemMinSize(widget, -1, new_height) 206 self.__szr_main.FitInside(self)
207 #------------------------------------------------
208 - def EnsureVisible (self, widget, cur_x = 0, cur_y = 0):
209 """ 210 Ensures widget is visible 211 212 @param widget: a child widget 213 @type cur_x: integer 214 @param cur_x: the X co-ordinate of the cursor inside widget, if applicable 215 @type cur_y: integer 216 @param cur_y: the Y co-ordinate of the cursor inside widget 217 """ 218 # get widget position 219 x, y = widget.GetPositionTuple() 220 # adjust for cursor offset 221 x += cur_x 222 y += cur_y 223 # convert to virtual coordinates 224 x, y = self.CalcUnscrolledPosition(x, y) 225 x_dimension, y_dimension = self.GetScrollPixelsPerUnit() 226 y = y / y_dimension 227 # currently, don't bother with X direction 228 self.Scroll (-1, y)
229 #------------------------------------------------
230 - def SetValue(self, values):
231 """ 232 Runs SetValue() on all the fields 233 234 @type values: dictionary 235 @param values: keys are the labels, values are passed to SetValue() 236 """ 237 # FIXME: adapt to cSTCval 238 for line in self.__input_lines: 239 for widget in line: 240 if values.has_key(widget['ID']): 241 if isinstance(widget['instance'], wx.stc.StyledTextCtrl): 242 widget['instance'].SetText(values[widget['ID']]) 243 elif isinstance(widget['instance'], (wx.Choice, wx.RadioBox)): 244 widget['instance'].SetSelection(values[widget['ID']]) 245 else: 246 widget['instance'].SetValue(values[widget['ID']])
247 #------------------------------------------------
248 - def GetValue(self):
249 """Return dict of values of inner widgets. 250 251 Returns a dictionary of the results of GetValue() 252 called on all widgets, keyed by label 253 Unlabelled widgets don't get called 254 """ 255 # FIXME: this does not detect ID collisions between lines 256 vals = {} 257 for line in self.__input_lines: 258 for widget in line: 259 if widget['ID'] is None: 260 continue 261 result = cSTCval() 262 if isinstance(widget['instance'], cResizingSTC): 263 result.text = widget['instance'].GetText() 264 result.data = widget['instance'].GetData() 265 elif isinstance(widget['instance'], wx.stc.StyledTextCtrl): 266 result.text = widget['instance'].GetText() 267 elif isinstance(widget['instance'], (wx.Choice, wx.RadioBox)): 268 result.selection = widget['instance'].GetSelection() 269 else: 270 result.value = widget['instance'].GetValue() 271 vals[widget['ID']] = result 272 return vals
273 #------------------------------------------------
274 - def Clear (self):
275 """ 276 Clears all widgets where this makes sense 277 """ 278 for line in self.__input_lines: 279 for widget in line: 280 if isinstance (widget['instance'], wx.stc.StyledTextCtrl): 281 widget['instance'].ClearAll() 282 elif isinstance (widget['instance'], wx.TextCtrl): 283 widget['instance'].Clear() 284 elif isinstance (widget['instance'], (wx.ToggleButton, wx.CheckBox, wx.RadioButton, wx.Gauge)): 285 widget['instance'].SetValue(0) 286 elif isinstance (widget['instance'], (wx.Choice, wx.ComboBox, wx.RadioBox)): 287 widget['instance'].SetSelection(0) 288 elif isinstance (widget['instance'], wx.SpinCtrl): 289 widget['instance'].SetValue(widget['instance'].GetMin())
290 #------------------------------------------------
291 - def SetFocus (self):
292 # try to focus on the first line if we can. 293 try: 294 self.lines[0][0]['instance'].SetFocus() 295 except IndexError: 296 pass 297 except AttributeError: 298 pass
299 #------------------------------------------------
300 - def GetPickList (self, callback, x_intended, y_intended):
301 """ 302 Returns a pick list, destroying a pre-existing pick list for this widget 303 304 the alive member is true until the object is Destroy ()'ed 305 306 @param callback: called when a item is selected, 307 @type callback: callable 308 @param x_intended: the X-position where the list should appear 309 @type x_intended: int 310 @param x: the Y-position where the list should appear 311 @type y_intended: int 312 313 @return: PickList 314 """ 315 # # retire previous pick list 316 # if self.__list and self.__list.alive: 317 # self.__list.Destroy() 318 our_width, our_height = self.GetSizeTuple() 319 char_height = self.GetCharHeight() 320 # make list 9 lines of height char_height high 321 list_height = char_height * 9 322 # and find best placement 323 # - height 324 if (list_height + char_height) > our_height: 325 list_height = our_height 326 y_final = 0 327 elif (y_intended + list_height + char_height) > our_height: 328 y_final = our_height - list_height 329 else: 330 y_final = y_intended + char_height 331 # - width 332 list_width = int(list_height / 1.4) 333 if list_width > our_width: 334 list_width = our_width 335 x_final = 0 336 elif (x_intended + list_width) > our_width: 337 x_final = our_width - list_width 338 else: 339 x_final = x_intended 340 # self.__list = cPickList(self, wx.Point(x_final, y_final), wx.Size(list_width, list_height), callback=callback) 341 # return self.__list 342 list = cPickList(self, wx.Point(x_final, y_final), wx.Size(list_width, list_height), callback=callback) 343 return list
344 #------------------------------------------------ 345 # def set_completion_callback(self, callback): 346 # self.complete = callback 347 #------------------------------------------------
348 - def GetSummary (self):
349 """Gets a terse summary string for the data in the widget""" 350 return ""
351 #====================================================================
352 -class cResizingSTC(wx.stc.StyledTextCtrl):
353 """ 354 A StyledTextCrl that monitors the size of its internal text and 355 resizes the parent accordingly. 356 357 MUST ONLY be used inside ResizingWindow ! 358 359 FIXME: override standard STC popup menu 360 """
361 - def __init__ (self, parent, id, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0, data=None):
362 if not isinstance(parent, cResizingWindow): 363 raise ValueError, 'parent of %s MUST be a ResizingWindow' % self.__class__.__name__ 364 365 wx.stc.StyledTextCtrl.__init__ (self, parent, id, pos, size, style) 366 367 self.SetWrapMode (wx.stc.STC_WRAP_WORD) 368 # FIXME: configure 369 self.StyleSetSpec (STYLE_ERROR, "fore:#7F11010,bold") 370 self.StyleSetSpec (STYLE_EMBED, "fore:#4040B0") 371 self.StyleSetChangeable (STYLE_EMBED, 0) 372 # self.StyleSetHotSpot (STYLE_EMBED, 1) 373 self.SetEOLMode (wx.stc.STC_EOL_LF) 374 375 self.__register_interests() 376 377 self.next_in_tab_order = None 378 self.prev_in_tab_order = None 379 380 self.__parent = parent 381 382 self.__popup_keywords = {} 383 384 # FIXME: delay configurable 385 # self.__timer = gmTimer.cTimer ( 386 # callback = self._on_timer_fired, 387 # delay = 300 388 # ) 389 self.__matcher = None 390 391 self.__show_list = 1 392 self.__embed = {} 393 self.list = None 394 self.no_list = 0 # ?? 395 396 self.__data = data # this is just a placeholder for data to be attached to this STC, will be returned from GetData() 397 398 self.__keyword_separators = regex.compile("[!?'\".,:;)}\]\r\n\s\t]+")
399 #------------------------------------------------ 400 # public API 401 #------------------------------------------------
402 - def set_keywords(self, popup_keywords=None):
403 if popup_keywords is None: 404 return 405 self.__popup_keywords = popup_keywords
406 #------------------------------------------------
407 - def SetText(self, text):
408 self.__show_list = 0 409 wx.stc.StyledTextCtrl.SetText(self, text) 410 self.__show_list = 1
411 #------------------------------------------------
412 - def ReplaceText (self, start, end, text, style=None):
413 self.replace_text(start, end, text, style)
414 #------------------------------------------------
415 - def Embed (self, text, data=None):
416 self.no_list = 1 417 self.ReplaceText(self.fragment_start, self.fragment_end, text+';', STYLE_EMBED) 418 self.GotoPos(self.fragment_start+len (text)+1) 419 self.SetFocus() 420 # if data: 421 # self.__embed[text] = data 422 self.no_list = 0
423 #------------------------------------------------
424 - def DelPhrase (self, pos):
425 # FIXME: optimize 426 end = pos+1 427 while (end < self.GetLength()) and (self.GetCharAt(end) != ord(';')): 428 end += 1 429 start = pos 430 while (start > 0) and (self.GetCharAt(start and start-1) != ord(';')): 431 start -= 1 432 self.SetTargetStart(start) 433 self.SetTargetEnd(end) 434 self.ReplaceTarget('')
435 #------------------------------------------------
436 - def SetFocus(self, x=None, line=None):
437 """Set focus to current position in STC. 438 439 - make sure that's visible, too 440 """ 441 wx.stc.StyledTextCtrl.SetFocus(self) 442 # goto first line ? 443 if line == 1: 444 if x is None: 445 x = 0 446 self.GotoPos(self.PositionFromPoint(wx.Point(x,0))) 447 return 448 # goto last line ? 449 if line == -1: 450 _log.debug('going to last line in STC') 451 last_char_pos = self.GetLength() 452 if x is None: 453 self.GotoPos(last_char_pos) 454 _log.debug('no X given, use X=%s' % last_char_pos.x) 455 return 456 y = self.PointFromPosition(last_char_pos).y 457 _log.debug('going to given X=%s' % x) 458 self.GotoPos(self.PositionFromPoint(wx.Point(x,y))) 459 return 460 # goto last current position 461 cur = self.PointFromPosition(self.GetCurrentPos()) 462 self.__parent.EnsureVisible (self, cur.x, cur.y)
463 #------------------------------------------------
464 - def AttachMatcher (self, matcher):
465 """ 466 Attaches a gmMatchProvider to the STC,this will be used to drive auto-completion 467 """ 468 self.__matcher = matcher
469 #------------------------------------------------
470 - def SetData(self, data):
471 """ 472 Configures the data associated with this STC 473 @param data The associated data 474 @type data Any object 475 """ 476 self.__data = data
477 #------------------------------------------------
478 - def GetData(self):
479 """ 480 Retrieves the data associated with this STC 481 """ 482 return self.__data
483 #------------------------------------------------
484 - def replace_text(self, start=None, end=None, text=None, style=None):
485 """ 486 Oddly, the otherwise very rich wx.STC API does not provide an 487 easy way to replace text, so we provide it here. 488 489 @param start: the position in the text to start from 490 @param length: the length of the string to replace 491 @param text: the new string 492 @param style: the style for the replaced string 493 """ 494 self.SetTargetStart(start) 495 self.SetTargetEnd(end) 496 self.ReplaceTarget(text) 497 if style is not None: 498 self.StartStyling(start, 0xFF) 499 self.SetStyling(len(text), style)
500 #------------------------------------------------
501 - def replace_keyword_with_expansion(self, keyword=None, position=None):
502 503 if keyword == u'$$steffi': # Easter Egg ;-) 504 expansion = u'Hai, play! Versucht das! (Keks dazu?) :-)' 505 else: 506 expansion = gmPG2.expand_keyword(keyword = keyword) 507 508 if expansion is None: 509 return 510 511 if expansion == u'': 512 return 513 514 self.replace_text ( 515 start = position, 516 end = position + len(keyword), 517 text = expansion 518 ) 519 520 self.GotoPos(position + len(expansion) + 1) 521 #wx.stc.StyledTextCtrl.SetFocus(self) 522 cur = self.PointFromPosition(position + len(expansion) + 1) 523 self.__parent.EnsureVisible(self, cur.x, cur.y)
524 #------------------------------------------------ 525 # event handling 526 #------------------------------------------------
527 - def __register_interests(self):
528 self.SetModEventMask (wx.stc.STC_MOD_INSERTTEXT | wx.stc.STC_MOD_DELETETEXT | wx.stc.STC_PERFORMED_USER) 529 530 wx.stc.EVT_STC_MODIFIED (self, self.GetId(), self.__on_STC_modified) 531 532 wx.EVT_KEY_DOWN (self, self.__on_key_down) 533 wx.EVT_KEY_UP (self, self.__OnKeyUp) 534 wx.EVT_CHAR(self, self.__on_char)
535 #------------------------------------------------
536 - def __on_STC_modified(self, event):
537 538 # did the user do anything of note to us ? 539 if not (event.GetModificationType() & (wx.stc.STC_MOD_INSERTTEXT | wx.stc.STC_MOD_DELETETEXT)): 540 event.Skip() 541 return 542 543 last_char_pos = self.GetLength() 544 545 # stop timer if empty 546 if last_char_pos == 0: 547 # self.__timer.Stop() 548 return 549 550 # do we need to resize ? 551 line_height = self.TextHeight(0) 552 true_txt_height = (self.PointFromPosition(last_char_pos).y - self.PointFromPosition(0).y) + line_height 553 x, visible_height = self.GetSizeTuple() 554 if visible_height < true_txt_height: 555 # print "line:", line_height 556 # print "before resize: too small" 557 # print "visible height", visible_height 558 # print "true text hgt", true_txt_height 559 n, remainder = divmod((true_txt_height - visible_height), line_height) 560 if remainder > 0: n = n + 1 561 target_height = visible_height + (n * line_height) 562 self.__parent.ReSize(self, target_height) 563 # print "after resize" 564 x, y = self.GetSizeTuple() 565 # print "visible height", y 566 567 if ((visible_height - line_height) > true_txt_height): 568 # print "line:", line_height 569 # print "before resize: too big" 570 # print "visible height", visible_height 571 # print "true text hgt", true_txt_height 572 # n, delta = divmod((visible_height - true_txt_height), line_height) 573 # target_height = visible_height - (n * line_height) 574 target_height = visible_height - line_height 575 self.__parent.ReSize(self, target_height) 576 # print "after resize" 577 x, y = self.GetSizeTuple() 578 # print "visible height", y 579 580 # is currently relevant term a keyword for popping up an edit area or something ? 581 fragment = self.__get_focussed_fragment() 582 if fragment in self.__popup_keywords.keys(): 583 # self.__timer.Stop() 584 self.__handle_keyword(fragment) 585 return 586 # else restart timer for match list 587 # self.__timer.Start(oneShot = True) 588 # event.Skip() 589 return
590 #------------------------------------------------
591 - def __on_key_down(self, event):
592 """Act on some key presses we want to process ourselves.""" 593 594 # if (self.list is not None) and not self.list.alive: 595 # self.list = None # someone else has destroyed our list! 596 597 # curs_pos = self.GetCurrentPos() 598 599 # <DOWN> 600 # - if in list: scroll list 601 # - if in last line: goto first line, same character, in next_in_tab_order 602 # - else standard behaviour 603 #if event.GetKeyCode() == wx.WXK_DOWN: 604 # if (self.list is not None) and self.list.alive: 605 # self.list.Down() 606 # return 607 # print "arrow down @ %s (line %s of %s)" % (curs_pos, self.LineFromPosition(curs_pos), self.GetLineCount()) 608 # if self.LineFromPosition(curs_pos)+1 == self.GetLineCount(): 609 # if self.next_in_tab_order is not None: 610 # curs_coords = self.PointFromPosition(curs_pos) 611 # self.next_in_tab_order.SetFocus(x=curs_coords.x, line=1) 612 # return 613 614 # <UP> 615 # - if in list: scroll list 616 # - if in first line: goto last line, same character, in prev_in_tab_order 617 # - else standard behaviour 618 #if event.GetKeyCode() == wx.WXK_UP: 619 # _log.debug('<UP-ARROW> key press detected') 620 # if (self.list is not None) and self.list.alive: 621 # self.list.Up() 622 # return 623 # _log.debug('pos %s = line %s' % (curs_pos, self.LineFromPosition(curs_pos))) 624 # if self.LineFromPosition(curs_pos) == 0: 625 # _log.debug('first line of STC - special handling') 626 # if self.prev_in_tab_order is not None: 627 # _log.debug('prev_in_tab_order = %s' % str(self.prev_in_tab_order)) 628 # curs_coords = self.PointFromPosition(curs_pos) 629 # _log.debug('cursor coordinates in current STC: %s:%s' % (curs_coords.x, curs_coords.y)) 630 # self.prev_in_tab_order.SetFocus(x=curs_coords.x, line=-1) 631 # return 632 # else: 633 # _log.debug('not first line of STC - standard handling') 634 635 # <TAB> key 636 # - move to next/prev_in_tab_order 637 # FIXME: what about inside a list ? 638 if event.GetKeyCode() == wx.WXK_TAB: 639 if event.m_shiftDown: 640 if self.prev_in_tab_order is not None: 641 self.prev_in_tab_order.SetFocus() 642 else: 643 if self.next_in_tab_order is not None: 644 self.next_in_tab_order.SetFocus() 645 return 646 647 # <DEL> 648 # - if inside embedded string 649 # - delete entire string and data dict 650 # - else standard behaviour 651 # if event.GetKeyCode() == wx.WXK_DELETE: 652 # # FIXME: perhaps add check for regex, too ? 653 # if self.GetStyleAt(curs_pos) == STYLE_EMBED: 654 # self.DelPhrase(curs_pos) 655 # # FIXME: also delete corresponding "additional data" dict ... 656 # return 657 658 # <BACKSPACE> 659 # - if inside embedded string 660 # - delete entire string and data dict 661 # - else standard behaviour 662 # if event.GetKeyCode() == wx.WXK_BACK: 663 # # FIXME: perhaps add check for regex, too ? 664 # if self.GetStyleAt(curs_pos-1) == STYLE_EMBED: 665 # self.DelPhrase (curs_pos-1) 666 # # FIXME: also delete corresponding "additional data" dict ... 667 # return 668 669 event.Skip() # skip to next event handler to keep processing
670 #------------------------------------------------
671 - def __OnKeyUp (self, event):
672 if not self.list: 673 curs_pos = self.PointFromPosition(self.GetCurrentPos()) 674 self.__parent.EnsureVisible (self, curs_pos.x, curs_pos.y)
675 #------------------------------------------------
676 - def __on_char(self, evt):
677 678 char = unichr(evt.GetUnicodeKey()) 679 680 if self.__keyword_separators.match(char) is not None: 681 if self.GetLength() == 1: 682 evt.Skip() 683 return 684 685 line, caret_pos = self.GetCurLine() 686 word = self.__keyword_separators.split(line[:caret_pos])[-1] 687 if (word not in [ r[0] for r in gmPG2.get_text_expansion_keywords() ]) and (word != u'$$steffi'): # Easter Egg ;-) 688 evt.Skip() 689 return 690 691 start = self.GetCurrentPos() - len(word) 692 wx.CallAfter(self.replace_keyword_with_expansion, word, start) 693 evt.Skip() 694 return 695 696 evt.Skip()
697 #------------------------------------------------ 698 # def _cb_on_popup_completion(self, was_cancelled=False): 699 # """Callback for popup completion. 700 # 701 # - this is called when the user has signalled 702 # being done interacting with the popup 703 # - if was_cancelled is True the popup content should 704 # be ignored and no further action taken on it 705 # """ 706 # print "popup interaction completed" 707 # if was_cancelled: 708 # print "popup cancelled, ignoring data" 709 ## self.__popup.Destroy() 710 # self.__popup = None 711 # return 712 # print "getting data from popup and acting on it" 713 # print self.__popup.GetData() 714 # # FIXME: wxCallAfter(embed) and store 715 # # maybe be a little smarter here 716 # self.__popup.Destroy() 717 # self.__popup = None 718 #------------------------------------------------
719 - def _on_timer_fired(self, cookie):
720 # print 'timer <%s> fired' % cookie 721 fragment = self.__get_focussed_fragment() 722 if fragment.strip() == '': 723 return 1 724 # print 'should popup context pick list on <%s> now' % fragment 725 726 return 1 727 728 # - get matches and popup select list 729 if self.no_list: 730 return 731 if self.__matcher is None: 732 return 733 if not self.__show_list: 734 return 735 736 # do indeed show list 737 if len(fragment) == 0: 738 if (self.list is not None) and self.list.alive: 739 self.list.Destroy() 740 return 741 matches_found, matches = self.__matcher.getMatches(fragment) 742 if not matches_found: 743 if (self.list is not None) and self.list.alive: 744 self.list.Destroy() 745 return 746 if not ((self.list is not None) and self.list.alive): 747 x, y = self.GetPositionTuple() 748 p = self.PointFromPosition(curs_pos) 749 self.list = self.__parent.GetPickList(self.__userlist, x+p.x, y+p.y) 750 self.list.SetItems(matches)
751 #------------------------------------------------ 752 # internal API 753 #------------------------------------------------
754 - def __get_focussed_fragment(self):
755 curs_pos = self.GetCurrentPos() 756 text = self.GetText() 757 self.fragment_start = text.rfind(';', 0, curs_pos) # FIXME: ';' hardcoded as separator 758 if self.fragment_start == -1: 759 self.fragment_start = 0 760 else: 761 self.fragment_start += 1 762 last_char_pos = self.GetLength() 763 self.fragment_end = text.find(';', curs_pos, last_char_pos) # FIXME: ';' hardcoded as separator 764 if self.fragment_end == -1: 765 self.fragment_end = last_char_pos 766 return text[self.fragment_start:self.fragment_end].strip()
767 #------------------------------------------------
768 - def __get_best_popup_geom(self):
769 # print "calculating optimal popup geometry" 770 parent_width, parent_height = self.__parent.GetSizeTuple() 771 # print "parent size is %sx%s pixel" % (parent_width, parent_height) 772 # FIXME: this should be gotten from ourselves, not the parent, but how ? 773 parent_char_height = self.__parent.GetCharHeight() 774 # print "char height in parent is", parent_char_height, "pixel" 775 # make popup 9 lines of height parent_char_height high 776 # FIXME: better detect this, but how ? 777 popup_height = parent_char_height * 9 778 # print "hence intended popup height is", popup_height, "pixel" 779 # get STC displacement inside parent 780 stc_origin_x, stc_origin_y = self.GetPositionTuple() 781 # print "inside parent STC is @ %s:%s" % (stc_origin_x, stc_origin_y) 782 # get current cursor position inside STC in pixels 783 curs_pos = self.PointFromPosition(self.GetCurrentPos()) 784 # print "inside STC cursor is @ %s:%s" % (curs_pos.x, curs_pos.y) 785 # find best placement 786 # - height 787 if (popup_height + parent_char_height) > parent_height: 788 # don't let popup get bigger than parent window 789 popup_height = parent_height 790 popup_y_pos = 0 791 elif ((popup_height + parent_char_height) + (curs_pos.y + stc_origin_y)) > parent_height: 792 # if would fit inside but forced (partially) outside 793 # by cursor position then move inside 794 popup_y_pos = parent_height - popup_height 795 else: 796 popup_y_pos = (curs_pos.y + stc_origin_y) + parent_char_height 797 # - width 798 popup_width = int(popup_height / 1.4) # Golden Cut 799 if popup_width > parent_width: 800 # don't let popup get bigger than parent window 801 popup_width = parent_width 802 popup_x_pos = 0 803 elif (popup_width + (curs_pos.x + stc_origin_x)) > parent_width: 804 # if would fit inside but forced (partially) outside 805 # by cursor position then move inside 806 popup_x_pos = parent_width - popup_width 807 else: 808 popup_x_pos = curs_pos.x + stc_origin_x 809 # print "optimal geometry = %sx%s @ %s:%s" % (popup_width, popup_height, popup_x_pos, popup_y_pos) 810 return (wx.Point(popup_x_pos, popup_y_pos), wx.Size(popup_width, popup_height))
811 #------------------------------------------------
812 - def __handle_keyword(self, kwd=None):
813 try: 814 create_widget = self.__popup_keywords[kwd]['widget_factory'] 815 except KeyError: 816 gmDispatcher.send(signal='statustext', msg=_('No action configured for keyword [%s].') % kwd) 817 return False 818 819 # best_pos, best_size = self.__get_best_popup_geom() 820 screen_pos = self.ClientToScreen(self.PointFromPosition(self.GetCurrentPos())) 821 top_parent = wx.GetTopLevelParent(self) 822 best_pos = top_parent.ScreenToClient(screen_pos) 823 try: 824 popup = create_widget ( 825 parent = top_parent, 826 pos = best_pos, 827 size = wx.Size(400, 300), 828 style = wx.SUNKEN_BORDER, 829 data_sink = self.__popup_keywords[kwd]['widget_data_sink'] 830 ) 831 except StandardError: 832 _log.exception('cannot call [%s] on keyword [%s] to create widget' % (create_widget, kwd)) 833 gmGuiHelpers.gm_show_error ( 834 aMessage = _('Cannot invoke [%s] for keyword [%s].') % (create_widget, kwd), 835 aTitle = _('showing keyword popup') 836 ) 837 return False 838 839 if not isinstance(popup, wx.Dialog): 840 gmDispatcher.send(signal='statustext', msg=_('Action [%s] on keyword [%s] is invalid.') % (create_widget, kwd)) 841 _log.error('keyword [%s] triggered action [%s]' % (kwd, create_widget)) 842 _log.error('the result (%s) is not a wx.Dialog subclass instance, however' % str(popup)) 843 return False 844 845 # display widget 846 result = popup.ShowModal() 847 if result == wx.ID_OK: 848 summary = popup.get_summary() 849 wx.CallAfter(self.Embed, summary) 850 popup.Destroy()
851 #------------------------------------------------
852 - def __userlist (self, text, data=None):
853 # this is a callback 854 # --- old -------------- 855 # # FIXME: need explanation on instance/callable business, it seems complicated 856 # if issubclass(data, cResizingWindow): 857 # win = data ( 858 # self, 859 # -1, 860 # pos = self.ClientToScreen(self.PointFromPosition(self.GetCurrentPos())), 861 # size = wx.Size(300, 150) 862 # ) 863 # cPopupFrame ( 864 # embed_header = text, 865 # widget = win, 866 # originator = self, 867 # pos = self.ClientToScreen(self.PointFromPosition(self.GetCurrentPos())) 868 # ).Show() 869 # elif callable(data): 870 # data (text, self.__parent, self, self.ClientToScreen (self.PointFromPosition (self.GetCurrentPos ()))) 871 # --- old -------------- 872 if self.MakePopup (text, data, self, self.ClientToScreen (self.PointFromPosition (self.GetCurrentPos ()))): 873 pass 874 else: 875 self.Embed (text, data)
876 #--------------------------------------------------
877 - def MakePopup (self, text, data, parent, cursor_position):
878 """ 879 An overrideable method, called whenever a match is made in this STC 880 Designed for producing popups, but the overrider can in fact, do 881 whatever they please. 882 883 @return True if a poup-up or similar actually happened (which suppresses inserting the match string in the text 884 @rtype boolean 885 """ 886 #cPopupFrame(text, win, self, cursor_position)).Show() 887 return False
888 #==================================================================== 889 #==================================================================== 890 if __name__ == '__main__': 891 892 # from Gnumed.pycommon.gmMatchProvider import cMatchProvider_FixedList 893 # from Gnumed.pycommon import gmI18N 894
895 - def create_widget_on_test_kwd1(*args, **kwargs):
896 print "test keyword must have been typed..." 897 print "actually this would have to return a suitable wx.Window subclass instance" 898 print "args:", args 899 print "kwd args:" 900 for key in kwargs.keys(): 901 print key, "->", kwargs[key]
902 #================================================================
903 - def create_widget_on_test_kwd2(*args, **kwargs):
904 msg = ( 905 "test keyword must have been typed...\n" 906 "actually this would have to return a suitable wx.Window subclass instance\n" 907 ) 908 for arg in args: 909 msg = msg + "\narg ==> %s" % arg 910 for key in kwargs.keys(): 911 msg = msg + "\n%s ==> %s" % (key, kwargs[key]) 912 gmGuiHelpers.gm_show_info ( 913 aMessage = msg, 914 aTitle = 'msg box on create_widget from test_keyword' 915 )
916 #================================================================
917 - class cTestKwdPopupPanel(wx.Panel):
918 - def __init__(self, parent, pos, size, style, completion_callback):
919 wx.Panel.__init__ ( 920 self, 921 parent, 922 -1, 923 pos, 924 size, 925 style 926 ) 927 self.__completion_callback = completion_callback 928 self._wx.ID_BTN_OK = wx.NewId() 929 self._wx.ID_BTN_Cancel = wx.NewId() 930 self.__do_layout() 931 self.__register_interests() 932 self.Show()
933
934 - def __do_layout(self):
935 # message 936 msg = "test keyword popup" 937 text = wx.StaticText (self, -1, msg) 938 # buttons 939 self.btn_OK = wx.Button(self, self._wx.ID_BTN_OK, _("OK")) 940 self.btn_OK.SetToolTipString(_('dismiss popup and embed data')) 941 self.btn_Cancel = wx.Button(self, self._wx.ID_BTN_Cancel, _("Cancel")) 942 self.btn_Cancel.SetToolTipString(_('dismiss popup and throw away data')) 943 szr_buttons = wx.BoxSizer(wx.HORIZONTAL) 944 szr_buttons.Add(self.btn_OK, 1, wx.EXPAND | wx.ALL, 1) 945 szr_buttons.Add(5, 0, 0) 946 szr_buttons.Add(self.btn_Cancel, 1, wx.EXPAND | wx.ALL, 1) 947 # arrange 948 szr_main = wx.BoxSizer(wx.VERTICAL) 949 szr_main.Add(text, 1, wx.EXPAND | wx.ALL, 1) 950 szr_main.Add(szr_buttons, 0) 951 # layout 952 self.SetAutoLayout(True) 953 self.SetSizer(szr_main) 954 szr_main.Fit(self)
955
956 - def __register_interests(self):
957 wx.EVT_BUTTON(self.btn_OK, self._wx.ID_BTN_OK, self._on_ok) 958 wx.EVT_BUTTON(self.btn_Cancel, self._wx.ID_BTN_Cancel, self._on_cancel)
959
960 - def _on_ok(self, event):
961 self.__completion_callback(was_cancelled = False)
962
963 - def _on_cancel(self, event):
964 self.__completion_callback(was_cancelled = True)
965 #================================================================
966 - def create_widget_on_test_kwd3(parent, pos, size, style, completion_callback):
967 pnl = cTestKwdPopupPanel ( 968 parent = parent, 969 pos = pos, 970 size = size, 971 style = style, 972 completion_callback = completion_callback 973 ) 974 return pnl
975 #================================================================
976 - class cSoapWin (cResizingWindow):
977 - def DoLayout(self):
978 self.input1 = cResizingSTC(self, -1) 979 self.input2 = cResizingSTC(self, -1) 980 self.input3 = cResizingSTC(self, -1) 981 982 self.input1.prev_in_tab_order = None 983 self.input1.next_in_tab_order = self.input2 984 self.input2.prev_in_tab_order = self.input1 985 self.input2.next_in_tab_order = self.input3 986 self.input3.prev_in_tab_order = self.input2 987 self.input3.next_in_tab_order = None 988 989 self.AddWidget (widget=self.input1, label="S") 990 self.Newline() 991 self.AddWidget (widget=self.input2, label="O") 992 self.Newline() 993 self.AddWidget (widget=self.input3, label="A+P") 994 995 kwds = {} 996 kwds['$test_keyword'] = {'widget_factory': create_widget_on_test_kwd3} 997 self.input2.set_keywords(popup_keywords=kwds)
998 #================================================================
999 - class cSoapPanel(wx.Panel):
1000 - def __init__ (self, parent, id):
1001 wx.Panel.__init__(self, parent, id) 1002 sizer = wx.BoxSizer(wx.VERTICAL) 1003 self.soap = cSoapWin(self, -1) 1004 self.save = wx.Button (self, -1, _(" Save ")) 1005 self.delete = wx.Button (self, -1, _(" Delete ")) 1006 self.new = wx.Button (self, -1, _(" New ")) 1007 # self.list = wx.ListBox (self, -1, style=wx.LB_SINGLE | wx.LB_NEEDED_SB) 1008 wx.EVT_BUTTON (self.save, self.save.GetId (), self.OnSave) 1009 wx.EVT_BUTTON (self.delete, self.delete.GetId (), self.OnDelete) 1010 wx.EVT_BUTTON (self.new, self.new.GetId (), self.OnNew) 1011 # wx.EVT_LISTBOX (self.list, self.list.GetId (), self.OnList) 1012 self.__do_layout()
1013
1014 - def __do_layout (self):
1015 sizer_1 = wx.BoxSizer(wx.VERTICAL) 1016 sizer_1.Add(self.soap, 3, wx.EXPAND, 0) 1017 sizer_2 = wx.BoxSizer (wx.HORIZONTAL) 1018 sizer_2.Add(self.save, 0, 0) 1019 sizer_2.Add(self.delete, 0, 0) 1020 sizer_2.Add(self.new, 0, 0) 1021 sizer_1.Add(sizer_2, 0, wx.EXPAND) 1022 # sizer_1.Add(self.list, 3, wx.EXPAND, 0) 1023 self.SetAutoLayout(1) 1024 self.SetSizer(sizer_1) 1025 sizer_1.Fit(self) 1026 sizer_1.SetSizeHints(self) 1027 self.Layout()
1028
1029 - def OnDelete (self, event):
1030 self.soap.Clear()
1031 # sel = self.list.GetSelection () 1032 # if sel >= 0: 1033 # self.list.Delete (sel) 1034
1035 - def OnNew (self, event):
1036 # sel = self.list.GetSelection () 1037 # if sel >= 0: 1038 # self.OnSave (None) 1039 self.soap.Clear()
1040 # self.list.SetSelection (sel, 0) 1041
1042 - def OnSave (self, event):
1043 data = self.soap.GetValue() 1044 # title = data['Assessment'] or data['Subjective'] or data['Plan'] or data['Objective'] 1045 self.soap.Clear()
1046 # sel = self.list.GetSelection () 1047 # if sel < 0: 1048 # self.list.Append (title, data) 1049 # else: 1050 # self.list.SetClientData (sel, data) 1051 # self.list.SetString (sel, title) 1052 1053 # def OnList (self, event): 1054 # self.soap.SetValues (event.GetClientData ()) 1055 #================================================================
1056 - class testFrame(wx.Frame):
1057 - def __init__ (self, title):
1058 wx.Frame.__init__ (self, None, wx.NewId(), "test SOAP", size = wx.Size (350, 500)) # this frame will have big fat borders 1059 wx.EVT_CLOSE (self, self.OnClose) 1060 panel = cSoapPanel(self, -1) 1061 sizer = wx.BoxSizer(wx.VERTICAL) 1062 sizer.Add (panel, 1, wx.GROW) 1063 self.SetSizer(sizer) 1064 self.SetAutoLayout(1) 1065 sizer.Fit (self) 1066 self.Layout ()
1067
1068 - def OnClose (self, event):
1069 self.Destroy()
1070 #================================================================
1071 - class testApp(wx.App):
1072 - def OnInit (self):
1073 self.frame = testFrame ("testFrame") 1074 self.frame.Show() 1075 return 1
1076 #================================================================ 1077 app = testApp(0) 1078 app.MainLoop() 1079 #==================================================================== 1080