| Home | Trees | Indices | Help |
|
|---|
|
|
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 #====================================================================
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 #------------------------------------------------
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 #------------------------------------------------
51 #------------------------------------------------
56 #------------------------------------------------
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 #------------------------------------------------
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 #------------------------------------------------
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
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)
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 #------------------------------------------------
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 #------------------------------------------------
121 #------------------------------------------------
123 if self.originator:
124 self.originator.Embed ("%s: %s" % (self.embed_header, self.win.GetSummary()))
125 self.Close ()
126 #====================================================================
131 #====================================================================
133 """A vertically-scrolled window which allows subwindows
134 to change their size, and adjusts accordingly.
135 """
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 #------------------------------------------------
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 #------------------------------------------------
190 #------------------------------------------------
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 #------------------------------------------------
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 #------------------------------------------------
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 #------------------------------------------------
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 #------------------------------------------------
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 #------------------------------------------------
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 #------------------------------------------------
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 #------------------------------------------------
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 #------------------------------------------------
348 #====================================================================
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 #------------------------------------------------
403 #------------------------------------------------
408 #------------------------------------------------
411 #------------------------------------------------
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 #------------------------------------------------
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 #------------------------------------------------
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 #------------------------------------------------
462 """
463 Attaches a gmMatchProvider to the STC,this will be used to drive auto-completion
464 """
465 self.__matcher = matcher
466 #------------------------------------------------
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 #------------------------------------------------
480 #------------------------------------------------
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 #------------------------------------------------
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 #------------------------------------------------
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 #------------------------------------------------
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 #------------------------------------------------
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 #------------------------------------------------
669 if not self.list:
670 curs_pos = self.PointFromPosition(self.GetCurrentPos())
671 self.__parent.EnsureVisible (self, curs_pos.x, curs_pos.y)
672 #------------------------------------------------
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 #------------------------------------------------
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 #------------------------------------------------
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 #------------------------------------------------
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 #------------------------------------------------
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 #------------------------------------------------
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 #--------------------------------------------------
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
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 #================================================================
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 #================================================================
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
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
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
959
962 #================================================================
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 #================================================================
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 #================================================================
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
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
1028 # sel = self.list.GetSelection ()
1029 # if sel >= 0:
1030 # self.list.Delete (sel)
1031
1033 # sel = self.list.GetSelection ()
1034 # if sel >= 0:
1035 # self.OnSave (None)
1036 self.soap.Clear()
1037 # self.list.SetSelection (sel, 0)
1038
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 #================================================================
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
1066 self.Destroy()
1067 #================================================================
1073 #================================================================
1074 app = testApp(0)
1075 app.MainLoop()
1076 #====================================================================
1077
| Home | Trees | Indices | Help |
|
|---|
| Generated by Epydoc 3.0.1 on Sat Oct 5 03:56:26 2013 | http://epydoc.sourceforge.net |