GRASS Programmer's Manual  6.5.svn(2014)-r66266
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
widgets.py
Go to the documentation of this file.
1 """!
2 @package gui_core.widgets
3 
4 @brief Core GUI widgets
5 
6 Classes:
7  - widgets::GNotebook
8  - widgets::ScrolledPanel
9  - widgets::NumTextCtrl
10  - widgets::FloatSlider
11  - widgets::SymbolButton
12  - widgets::StaticWrapText
13  - widgets::BaseValidator
14  - widgets::IntegerValidator
15  - widgets::FloatValidator
16  - widgets::ItemTree
17 
18 (C) 2008-2011 by the GRASS Development Team
19 
20 This program is free software under the GNU General Public License
21 (>=v2). Read the file COPYING that comes with GRASS for details.
22 
23 @author Martin Landa <landa.martin gmail.com> (Google SoC 2008/2010)
24 @author Enhancements by Michael Barton <michael.barton asu.edu>
25 @author Anna Kratochvilova <kratochanna gmail.com> (Google SoC 2011)
26 """
27 
28 import os
29 import sys
30 import string
31 
32 import wx
33 import wx.lib.scrolledpanel as SP
34 try:
35  import wx.lib.agw.flatnotebook as FN
36 except ImportError:
37  import wx.lib.flatnotebook as FN
38 try:
39  from wx.lib.buttons import ThemedGenBitmapTextButton as BitmapTextButton
40 except ImportError: # not sure about TGBTButton version
41  from wx.lib.buttons import GenBitmapTextButton as BitmapTextButton
42 try:
43  import wx.lib.agw.customtreectrl as CT
44 except ImportError:
45  import wx.lib.customtreectrl as CT
46 
47 from core import globalvar
48 from core.debug import Debug
49 
50 from wx.lib.newevent import NewEvent
51 wxSymbolSelectionChanged, EVT_SYMBOL_SELECTION_CHANGED = NewEvent()
52 
53 class GNotebook(FN.FlatNotebook):
54  """!Generic notebook widget
55  """
56  def __init__(self, parent, style, **kwargs):
57  if globalvar.hasAgw:
58  FN.FlatNotebook.__init__(self, parent, id = wx.ID_ANY, agwStyle = style, **kwargs)
59  else:
60  FN.FlatNotebook.__init__(self, parent, id = wx.ID_ANY, style = style, **kwargs)
61 
62  self.notebookPages = {}
63 
64  def AddPage(self, **kwargs):
65  """!Add a page
66  """
67  if 'name' in kwargs:
68  self.notebookPages[kwargs['name']] = kwargs['page']
69  del kwargs['name']
70  super(GNotebook, self).AddPage(**kwargs)
71 
72  def InsertPage(self, **kwargs):
73  """!Insert a new page
74  """
75  if 'name' in kwargs:
76  self.notebookPages[kwargs['name']] = kwargs['page']
77  del kwargs['name']
78  super(GNotebook, self).InsertPage(**kwargs)
79 
80  def SetSelectionByName(self, page):
81  """!Set notebook
82 
83  @param page names, eg. 'layers', 'output', 'search', 'pyshell', 'nviz'
84  """
85  idx = self.GetPageIndexByName(page)
86  if self.GetSelection() != idx:
87  self.SetSelection(idx)
88 
89  def GetPageIndexByName(self, page):
90  """!Get notebook page index
91 
92  @param page name
93  """
94  if page not in self.notebookPages:
95  return -1
96 
97  return self.GetPageIndex(self.notebookPages[page])
98 
99 class ScrolledPanel(SP.ScrolledPanel):
100  """!Custom ScrolledPanel to avoid strange behaviour concerning focus"""
101  def __init__(self, parent, style = wx.TAB_TRAVERSAL):
102  SP.ScrolledPanel.__init__(self, parent = parent, id = wx.ID_ANY, style = style)
103 
104  def OnChildFocus(self, event):
105  pass
106 
107 class NumTextCtrl(wx.TextCtrl):
108  """!Class derived from wx.TextCtrl for numerical values only"""
109  def __init__(self, parent, **kwargs):
110 ## self.precision = kwargs.pop('prec')
111  wx.TextCtrl.__init__(self, parent = parent,
112  validator = NTCValidator(flag = 'DIGIT_ONLY'), **kwargs)
113 
114 
115  def SetValue(self, value):
116  super(NumTextCtrl, self).SetValue( str(value))
117 
118  def GetValue(self):
119  val = super(NumTextCtrl, self).GetValue()
120  if val == '':
121  val = '0'
122  try:
123  return float(val)
124  except ValueError:
125  val = ''.join(''.join(val.split('-')).split('.'))
126  return float(val)
127 
128  def SetRange(self, min, max):
129  pass
130 
131 class FloatSlider(wx.Slider):
132  """!Class derived from wx.Slider for floats"""
133  def __init__(self, **kwargs):
134  Debug.msg(1, "FloatSlider.__init__()")
135  wx.Slider.__init__(self, **kwargs)
136  self.coef = 1.
137  #init range
138  self.minValueOrig = 0
139  self.maxValueOrig = 1
140 
141  def SetValue(self, value):
142  value *= self.coef
143  if abs(value) < 1 and value != 0:
144  while abs(value) < 1:
145  value *= 100
146  self.coef *= 100
147  super(FloatSlider, self).SetRange(self.minValueOrig * self.coef, self.maxValueOrig * self.coef)
148  super(FloatSlider, self).SetValue(value)
149 
150  Debug.msg(4, "FloatSlider.SetValue(): value = %f" % value)
151 
152  def SetRange(self, minValue, maxValue):
153  self.coef = 1.
154  self.minValueOrig = minValue
155  self.maxValueOrig = maxValue
156  if abs(minValue) < 1 or abs(maxValue) < 1:
157  while (abs(minValue) < 1 and minValue != 0) or (abs(maxValue) < 1 and maxValue != 0):
158  minValue *= 100
159  maxValue *= 100
160  self.coef *= 100
161  super(FloatSlider, self).SetValue(super(FloatSlider, self).GetValue() * self.coef)
162  super(FloatSlider, self).SetRange(minValue, maxValue)
163  Debug.msg(4, "FloatSlider.SetRange(): minValue = %f, maxValue = %f" % (minValue, maxValue))
164 
165  def GetValue(self):
166  val = super(FloatSlider, self).GetValue()
167  Debug.msg(4, "FloatSlider.GetValue(): value = %f" % (val/self.coef))
168  return val/self.coef
169 
170 class SymbolButton(BitmapTextButton):
171  """!Button with symbol and label."""
172  def __init__(self, parent, usage, label, **kwargs):
173  """!Constructor
174 
175  @param parent parent (usually wx.Panel)
176  @param usage determines usage and picture
177  @param label displayed label
178  """
179  size = (15, 15)
180  buffer = wx.EmptyBitmap(*size)
181  BitmapTextButton.__init__(self, parent = parent, label = " " + label, bitmap = buffer, **kwargs)
182 
183  dc = wx.MemoryDC()
184  dc.SelectObject(buffer)
185  maskColor = wx.Colour(255, 255, 255)
186  dc.SetBrush(wx.Brush(maskColor))
187  dc.Clear()
188 
189  if usage == 'record':
190  self.DrawRecord(dc, size)
191  elif usage == 'stop':
192  self.DrawStop(dc, size)
193  elif usage == 'play':
194  self.DrawPlay(dc, size)
195  elif usage == 'pause':
196  self.DrawPause(dc, size)
197 
198  if sys.platform != "win32":
199  buffer.SetMaskColour(maskColor)
200  self.SetBitmapLabel(buffer)
201  dc.SelectObject(wx.NullBitmap)
202 
203  def DrawRecord(self, dc, size):
204  """!Draw record symbol"""
205  dc.SetBrush(wx.Brush(wx.Colour(255, 0, 0)))
206  dc.DrawCircle(size[0]/2, size[1] / 2, size[0] / 2)
207 
208  def DrawStop(self, dc, size):
209  """!Draw stop symbol"""
210  dc.SetBrush(wx.Brush(wx.Colour(50, 50, 50)))
211  dc.DrawRectangle(0, 0, size[0], size[1])
212 
213  def DrawPlay(self, dc, size):
214  """!Draw play symbol"""
215  dc.SetBrush(wx.Brush(wx.Colour(0, 255, 0)))
216  points = (wx.Point(0, 0), wx.Point(0, size[1]), wx.Point(size[0], size[1] / 2))
217  dc.DrawPolygon(points)
218 
219  def DrawPause(self, dc, size):
220  """!Draw pause symbol"""
221  dc.SetBrush(wx.Brush(wx.Colour(50, 50, 50)))
222  dc.DrawRectangle(0, 0, 2 * size[0] / 5, size[1])
223  dc.DrawRectangle(3 * size[0] / 5, 0, 2 * size[0] / 5, size[1])
224 
225 class StaticWrapText(wx.StaticText):
226  """!A Static Text field that wraps its text to fit its width,
227  enlarging its height if necessary.
228  """
229  def __init__(self, parent, id = wx.ID_ANY, label = '', *args, **kwds):
230  self.parent = parent
231  self.originalLabel = label
232 
233  wx.StaticText.__init__(self, parent, id, label = '', *args, **kwds)
234 
235  self.SetLabel(label)
236  self.Bind(wx.EVT_SIZE, self.OnResize)
237 
238  def SetLabel(self, label):
239  self.originalLabel = label
240  self.wrappedSize = None
241  self.OnResize(None)
242 
243  def OnResize(self, event):
244  if not getattr(self, "resizing", False):
245  self.resizing = True
246  newSize = wx.Size(self.parent.GetSize().width - 50,
247  self.GetSize().height)
248  if self.wrappedSize != newSize:
249  wx.StaticText.SetLabel(self, self.originalLabel)
250  self.Wrap(newSize.width)
251  self.wrappedSize = newSize
252 
253  self.SetSize(self.wrappedSize)
254  del self.resizing
255 
256 class BaseValidator(wx.PyValidator):
257  def __init__(self):
258  wx.PyValidator.__init__(self)
259 
260  self.Bind(wx.EVT_TEXT, self.OnText)
261 
262  def OnText(self, event):
263  """!Do validation"""
264  self.Validate()
265 
266  event.Skip()
267 
268  def Validate(self):
269  """Validate input"""
270  textCtrl = self.GetWindow()
271  text = textCtrl.GetValue()
272 
273  if text:
274  try:
275  self.type(text)
276  except ValueError:
277  textCtrl.SetBackgroundColour("grey")
278  textCtrl.SetFocus()
279  textCtrl.Refresh()
280  return False
281 
282  sysColor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)
283  textCtrl.SetBackgroundColour(sysColor)
284 
285  textCtrl.Refresh()
286 
287  return True
288 
289  def TransferToWindow(self):
290  return True # Prevent wxDialog from complaining.
291 
293  return True # Prevent wxDialog from complaining.
294 
296  """!Validator for floating-point input"""
297  def __init__(self):
298  BaseValidator.__init__(self)
299  self.type = int
300 
301  def Clone(self):
302  """!Clone validator"""
303  return IntegerValidator()
304 
306  """!Validator for floating-point input"""
307  def __init__(self):
308  BaseValidator.__init__(self)
309  self.type = float
310 
311  def Clone(self):
312  """!Clone validator"""
313  return FloatValidator()
314 
315 class NTCValidator(wx.PyValidator):
316  """!validates input in textctrls, taken from wxpython demo"""
317  def __init__(self, flag = None):
318  wx.PyValidator.__init__(self)
319  self.flag = flag
320  self.Bind(wx.EVT_CHAR, self.OnChar)
321 
322  def Clone(self):
323  return NTCValidator(self.flag)
324 
325  def OnChar(self, event):
326  key = event.GetKeyCode()
327  if key < wx.WXK_SPACE or key == wx.WXK_DELETE or key > 255:
328  event.Skip()
329  return
330  if self.flag == 'DIGIT_ONLY' and chr(key) in string.digits + '.-':
331  event.Skip()
332  return
333  if not wx.Validator_IsSilent():
334  wx.Bell()
335  # Returning without calling even.Skip eats the event before it
336  # gets to the text control
337  return
338 
339 
340 class GenericValidator(wx.PyValidator):
341  """ This validator checks condition and calls callback
342  in case the condition is not fulfilled.
343  """
344  def __init__(self, condition, callback):
345  """ Standard constructor.
346 
347  @param condition function which accepts string value and returns T/F
348  @param callback function which is called when condition is not fulfilled
349  """
350  wx.PyValidator.__init__(self)
351  self._condition = condition
352  self._callback = callback
353 
354  def Clone(self):
355  """ Standard cloner.
356 
357  Note that every validator must implement the Clone() method.
358  """
359  return GenericValidator(self._condition, self._callback)
360 
361  def Validate(self, win):
362  """ Validate the contents of the given text control.
363  """
364  ctrl = self.GetWindow()
365  text = ctrl.GetValue()
366  if not self._condition(text):
367  self._callback(ctrl)
368  return False
369  else:
370  return True
371 
372  def TransferToWindow(self):
373  """ Transfer data from validator to window.
374  """
375  return True # Prevent wxDialog from complaining.
376 
377 
379  """ Transfer data from window to validator.
380  """
381  return True # Prevent wxDialog from complaining.
382 
383 
384 class ItemTree(CT.CustomTreeCtrl):
385  def __init__(self, parent, id = wx.ID_ANY,
386  ctstyle = CT.TR_HIDE_ROOT | CT.TR_FULL_ROW_HIGHLIGHT | CT.TR_HAS_BUTTONS |
387  CT.TR_LINES_AT_ROOT | CT.TR_SINGLE, **kwargs):
388  if globalvar.hasAgw:
389  super(ItemTree, self).__init__(parent, id, agwStyle = ctstyle, **kwargs)
390  else:
391  super(ItemTree, self).__init__(parent, id, style = ctstyle, **kwargs)
392 
393  self.root = self.AddRoot(_("Menu tree"))
394  self.itemsMarked = [] # list of marked items
395  self.itemSelected = None
396 
397  def SearchItems(self, element, value):
398  """!Search item
399 
400  @param element element index (see self.searchBy)
401  @param value
402 
403  @return list of found tree items
404  """
405  items = list()
406  if not value:
407  return items
408 
409  item = self.GetFirstChild(self.root)[0]
410  self._processItem(item, element, value, items)
411 
412  self.itemsMarked = items
413  self.itemSelected = None
414 
415  return items
416 
417  def _processItem(self, item, element, value, listOfItems):
418  """!Search items (used by SearchItems)
419 
420  @param item reference item
421  @param listOfItems list of found items
422  """
423  while item and item.IsOk():
424  subItem = self.GetFirstChild(item)[0]
425  if subItem:
426  self._processItem(subItem, element, value, listOfItems)
427  data = self.GetPyData(item)
428 
429  if data and element in data and \
430  value.lower() in data[element].lower():
431  listOfItems.append(item)
432 
433  item = self.GetNextSibling(item)
434 
435  def GetSelected(self):
436  """!Get selected item"""
437  return self.itemSelected
438 
439  def OnShowItem(self, event):
440  """!Highlight first found item in menu tree"""
441  if len(self.itemsMarked) > 0:
442  if self.GetSelected():
443  self.ToggleItemSelection(self.GetSelected())
444  idx = self.itemsMarked.index(self.GetSelected()) + 1
445  else:
446  idx = 0
447  try:
448  self.ToggleItemSelection(self.itemsMarked[idx])
449  self.itemSelected = self.itemsMarked[idx]
450  self.EnsureVisible(self.itemsMarked[idx])
451  except IndexError:
452  self.ToggleItemSelection(self.itemsMarked[0]) # reselect first item
453  self.EnsureVisible(self.itemsMarked[0])
454  self.itemSelected = self.itemsMarked[0]
455  else:
456  for item in self.root.GetChildren():
457  self.Collapse(item)
458  itemSelected = self.GetSelection()
459  if itemSelected:
460  self.ToggleItemSelection(itemSelected)
461  self.itemSelected = None
462 
463 class SingleSymbolPanel(wx.Panel):
464  """!Panel for displaying one symbol.
465 
466  Changes background when selected. Assumes that parent will catch
467  events emitted on mouse click. Used in gui_core::dialog::SymbolDialog.
468  """
469  def __init__(self, parent, symbolPath):
470  """!Panel constructor
471 
472  @param parent parent (gui_core::dialog::SymbolDialog)
473  @param symbolPath absolute path to symbol
474  """
475  wx.Panel.__init__(self, parent, id = wx.ID_ANY, style = wx.BORDER_RAISED)
476  self.SetName(os.path.splitext(os.path.basename(symbolPath))[0])
477  self.sBmp = wx.StaticBitmap(self, wx.ID_ANY, wx.Bitmap(symbolPath))
478 
479  self.selected = False
480  self.selectColor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)
481  self.deselectColor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)
482 
483  sizer = wx.BoxSizer()
484  sizer.Add(item = self.sBmp, proportion = 0, flag = wx.ALL | wx.ALIGN_CENTER, border = 5)
485  self.SetBackgroundColour(self.deselectColor)
486  self.SetMinSize(self.GetBestSize())
487  self.SetSizerAndFit(sizer)
488 
489  # binding to both (staticBitmap, Panel) necessary
490  self.sBmp.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
491  self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
492  self.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick)
493  self.sBmp.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick)
494 
495  def OnLeftDown(self, event):
496  """!Panel selected, background changes"""
497  self.selected = True
498  self.SetBackgroundColour(self.selectColor)
499  self.Refresh()
500  event.Skip()
501 
502  event = wxSymbolSelectionChanged(name = self.GetName(), doubleClick = False)
503  wx.PostEvent(self.GetParent(), event)
504 
505  def OnDoubleClick(self, event):
506  event = wxSymbolSelectionChanged(name = self.GetName(), doubleClick = True)
507  wx.PostEvent(self.GetParent(), event)
508 
509  def Deselect(self):
510  """!Panel deselected, background changes back to default"""
511  self.selected = False
512  self.SetBackgroundColour(self.deselectColor)
513  self.Refresh()
514 
515  def Select(self):
516  """!Select panel, no event emitted"""
517  self.selected = True
518  self.SetBackgroundColour(self.selectColor)
519  self.Refresh()
520 
def OnLeftDown
Panel selected, background changes.
Definition: widgets.py:495
def GetPageIndexByName
Get notebook page index.
Definition: widgets.py:89
def GetValue
Definition: widgets.py:118
Validator for floating-point input.
Definition: widgets.py:295
def Clone
Clone validator.
Definition: widgets.py:301
Button with symbol and label.
Definition: widgets.py:170
def SetSelectionByName
Set notebook.
Definition: widgets.py:80
def InsertPage
Insert a new page.
Definition: widgets.py:72
wxGUI debugging
def SetValue
Definition: widgets.py:115
def DrawRecord
Draw record symbol.
Definition: widgets.py:203
Custom ScrolledPanel to avoid strange behaviour concerning focus.
Definition: widgets.py:99
def __init__
Class derived from wx.Slider for floats.
Definition: widgets.py:133
def _processItem
Search items (used by SearchItems)
Definition: widgets.py:417
def split
Platform spefic shlex.split.
Definition: core/utils.py:37
Validator for floating-point input.
Definition: widgets.py:305
A Static Text field that wraps its text to fit its width, enlarging its height if necessary...
Definition: widgets.py:225
def OnText
Do validation.
Definition: widgets.py:262
def AddPage
Add a page.
Definition: widgets.py:64
def Clone
Clone validator.
Definition: widgets.py:311
def Deselect
Panel deselected, background changes back to default.
Definition: widgets.py:509
def __init__
Constructor.
Definition: widgets.py:172
Generic notebook widget.
Definition: widgets.py:53
Panel for displaying one symbol.
Definition: widgets.py:463
def SetRange
Definition: widgets.py:128
def __init__
Panel constructor.
Definition: widgets.py:469
def SearchItems
Search item.
Definition: widgets.py:397
validates input in textctrls, taken from wxpython demo
Definition: widgets.py:315
def DrawPause
Draw pause symbol.
Definition: widgets.py:219
def DrawPlay
Draw play symbol.
Definition: widgets.py:213
def DrawStop
Draw stop symbol.
Definition: widgets.py:208
def OnShowItem
Highlight first found item in menu tree.
Definition: widgets.py:439
def GetSelected
Get selected item.
Definition: widgets.py:435
def Select
Select panel, no event emitted.
Definition: widgets.py:515