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