widgets.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. """!
  2. @package gui_core.widgets
  3. @brief Core GUI widgets
  4. Classes:
  5. - widgets::GNotebook
  6. - widgets::ScrolledPanel
  7. - widgets::NTCValidator
  8. - widgets::NumTextCtrl
  9. - widgets::FloatSlider
  10. - widgets::SymbolButton
  11. - widgets::StaticWrapText
  12. - widgets::FloatValidator
  13. - widgets::ItemTree
  14. (C) 2008-2011 by the GRASS Development Team
  15. This program is free software under the GNU General Public License
  16. (>=v2). Read the file COPYING that comes with GRASS for details.
  17. @author Martin Landa <landa.martin gmail.com> (Google SoC 2008/2010)
  18. @author Enhancements by Michael Barton <michael.barton asu.edu>
  19. @author Anna Kratochvilova <kratochanna gmail.com> (Google SoC 2011)
  20. """
  21. import string
  22. import wx
  23. import wx.lib.scrolledpanel as SP
  24. try:
  25. import wx.lib.agw.flatnotebook as FN
  26. except ImportError:
  27. import wx.lib.flatnotebook as FN
  28. try:
  29. from wx.lib.buttons import ThemedGenBitmapTextButton as BitmapTextButton
  30. except ImportError: # not sure about TGBTButton version
  31. from wx.lib.buttons import GenBitmapTextButton as BitmapTextButton
  32. try:
  33. import wx.lib.agw.customtreectrl as CT
  34. except ImportError:
  35. import wx.lib.customtreectrl as CT
  36. from core import globalvar
  37. from core.debug import Debug
  38. class GNotebook(FN.FlatNotebook):
  39. """!Generic notebook widget
  40. """
  41. def __init__(self, parent, style, **kwargs):
  42. if globalvar.hasAgw:
  43. FN.FlatNotebook.__init__(self, parent, id = wx.ID_ANY, agwStyle = style, **kwargs)
  44. else:
  45. FN.FlatNotebook.__init__(self, parent, id = wx.ID_ANY, style = style, **kwargs)
  46. self.notebookPages = {}
  47. def AddPage(self, **kwargs):
  48. """!Add a new page
  49. """
  50. if 'name' in kwargs:
  51. self.notebookPages[kwargs['name']] = kwargs['page']
  52. del kwargs['name']
  53. super(GNotebook, self).AddPage(**kwargs)
  54. def InsertPage(self, **kwargs):
  55. """!Insert a new page
  56. """
  57. if 'name' in kwargs:
  58. self.notebookPages[kwargs['name']] = kwargs['page']
  59. del kwargs['name']
  60. super(GNotebook, self).InsertPage(**kwargs)
  61. def SetSelectionByName(self, page):
  62. """!Set notebook
  63. @param page names, eg. 'layers', 'output', 'search', 'pyshell', 'nviz'
  64. """
  65. idx = self.GetPageIndexByName(page)
  66. if self.GetSelection() != idx:
  67. self.SetSelection(idx)
  68. def GetPageIndexByName(self, page):
  69. """!Get notebook page index
  70. @param page name
  71. """
  72. if page not in self.notebookPages:
  73. return -1
  74. return self.GetPageIndex(self.notebookPages[page])
  75. class ScrolledPanel(SP.ScrolledPanel):
  76. """!Custom ScrolledPanel to avoid strange behaviour concerning focus"""
  77. def __init__(self, parent):
  78. SP.ScrolledPanel.__init__(self, parent = parent, id = wx.ID_ANY)
  79. def OnChildFocus(self, event):
  80. pass
  81. class NTCValidator(wx.PyValidator):
  82. """!validates input in textctrls, taken from wxpython demo"""
  83. def __init__(self, flag = None):
  84. wx.PyValidator.__init__(self)
  85. self.flag = flag
  86. self.Bind(wx.EVT_CHAR, self.OnChar)
  87. def Clone(self):
  88. return NTCValidator(self.flag)
  89. def OnChar(self, event):
  90. key = event.GetKeyCode()
  91. if key < wx.WXK_SPACE or key == wx.WXK_DELETE or key > 255:
  92. event.Skip()
  93. return
  94. if self.flag == 'DIGIT_ONLY' and chr(key) in string.digits + '.-':
  95. event.Skip()
  96. return
  97. if not wx.Validator_IsSilent():
  98. wx.Bell()
  99. # Returning without calling even.Skip eats the event before it
  100. # gets to the text control
  101. return
  102. class NumTextCtrl(wx.TextCtrl):
  103. """!Class derived from wx.TextCtrl for numerical values only"""
  104. def __init__(self, parent, **kwargs):
  105. ## self.precision = kwargs.pop('prec')
  106. wx.TextCtrl.__init__(self, parent = parent,
  107. validator = NTCValidator(flag = 'DIGIT_ONLY'), **kwargs)
  108. def SetValue(self, value):
  109. super(NumTextCtrl, self).SetValue( str(value))
  110. def GetValue(self):
  111. val = super(NumTextCtrl, self).GetValue()
  112. if val == '':
  113. val = '0'
  114. try:
  115. return float(val)
  116. except ValueError:
  117. val = ''.join(''.join(val.split('-')).split('.'))
  118. return float(val)
  119. def SetRange(self, min, max):
  120. pass
  121. class FloatSlider(wx.Slider):
  122. """!Class derived from wx.Slider for floats"""
  123. def __init__(self, **kwargs):
  124. Debug.msg(1, "FloatSlider.__init__()")
  125. wx.Slider.__init__(self, **kwargs)
  126. self.coef = 1.
  127. #init range
  128. self.minValueOrig = 0
  129. self.maxValueOrig = 1
  130. def SetValue(self, value):
  131. value *= self.coef
  132. if abs(value) < 1 and value != 0:
  133. while abs(value) < 1:
  134. value *= 100
  135. self.coef *= 100
  136. super(FloatSlider, self).SetRange(self.minValueOrig * self.coef, self.maxValueOrig * self.coef)
  137. super(FloatSlider, self).SetValue(value)
  138. Debug.msg(4, "FloatSlider.SetValue(): value = %f" % value)
  139. def SetRange(self, minValue, maxValue):
  140. self.coef = 1.
  141. self.minValueOrig = minValue
  142. self.maxValueOrig = maxValue
  143. if abs(minValue) < 1 or abs(maxValue) < 1:
  144. while (abs(minValue) < 1 and minValue != 0) or (abs(maxValue) < 1 and maxValue != 0):
  145. minValue *= 100
  146. maxValue *= 100
  147. self.coef *= 100
  148. super(FloatSlider, self).SetValue(super(FloatSlider, self).GetValue() * self.coef)
  149. super(FloatSlider, self).SetRange(minValue, maxValue)
  150. Debug.msg(4, "FloatSlider.SetRange(): minValue = %f, maxValue = %f" % (minValue, maxValue))
  151. def GetValue(self):
  152. val = super(FloatSlider, self).GetValue()
  153. Debug.msg(4, "FloatSlider.GetValue(): value = %f" % (val/self.coef))
  154. return val/self.coef
  155. class SymbolButton(BitmapTextButton):
  156. """!Button with symbol and label."""
  157. def __init__(self, parent, usage, label, **kwargs):
  158. """!Constructor
  159. @param parent parent (usually wx.Panel)
  160. @param usage determines usage and picture
  161. @param label displayed label
  162. """
  163. size = (15, 15)
  164. buffer = wx.EmptyBitmap(*size)
  165. BitmapTextButton.__init__(self, parent = parent, label = " " + label, bitmap = buffer, **kwargs)
  166. dc = wx.MemoryDC()
  167. dc.SelectObject(buffer)
  168. maskColor = wx.Color(255, 255, 255)
  169. dc.SetBrush(wx.Brush(maskColor))
  170. dc.Clear()
  171. if usage == 'record':
  172. self.DrawRecord(dc, size)
  173. elif usage == 'stop':
  174. self.DrawStop(dc, size)
  175. elif usage == 'play':
  176. self.DrawPlay(dc, size)
  177. elif usage == 'pause':
  178. self.DrawPause(dc, size)
  179. buffer.SetMaskColour(maskColor)
  180. self.SetBitmapLabel(buffer)
  181. dc.SelectObject(wx.NullBitmap)
  182. def DrawRecord(self, dc, size):
  183. """!Draw record symbol"""
  184. dc.SetBrush(wx.Brush(wx.Color(255, 0, 0)))
  185. dc.DrawCircle(size[0]/2, size[1] / 2, size[0] / 2)
  186. def DrawStop(self, dc, size):
  187. """!Draw stop symbol"""
  188. dc.SetBrush(wx.Brush(wx.Color(50, 50, 50)))
  189. dc.DrawRectangle(0, 0, size[0], size[1])
  190. def DrawPlay(self, dc, size):
  191. """!Draw play symbol"""
  192. dc.SetBrush(wx.Brush(wx.Color(0, 255, 0)))
  193. points = (wx.Point(0, 0), wx.Point(0, size[1]), wx.Point(size[0], size[1] / 2))
  194. dc.DrawPolygon(points)
  195. def DrawPause(self, dc, size):
  196. """!Draw pause symbol"""
  197. dc.SetBrush(wx.Brush(wx.Color(50, 50, 50)))
  198. dc.DrawRectangle(0, 0, 2 * size[0] / 5, size[1])
  199. dc.DrawRectangle(3 * size[0] / 5, 0, 2 * size[0] / 5, size[1])
  200. class StaticWrapText(wx.StaticText):
  201. """!A Static Text field that wraps its text to fit its width,
  202. enlarging its height if necessary.
  203. """
  204. def __init__(self, parent, id = wx.ID_ANY, label = '', *args, **kwds):
  205. self.parent = parent
  206. self.originalLabel = label
  207. wx.StaticText.__init__(self, parent, id, label = '', *args, **kwds)
  208. self.SetLabel(label)
  209. self.Bind(wx.EVT_SIZE, self.OnResize)
  210. def SetLabel(self, label):
  211. self.originalLabel = label
  212. self.wrappedSize = None
  213. self.OnResize(None)
  214. def OnResize(self, event):
  215. if not getattr(self, "resizing", False):
  216. self.resizing = True
  217. newSize = wx.Size(self.parent.GetSize().width - 50,
  218. self.GetSize().height)
  219. if self.wrappedSize != newSize:
  220. wx.StaticText.SetLabel(self, self.originalLabel)
  221. self.Wrap(newSize.width)
  222. self.wrappedSize = newSize
  223. self.SetSize(self.wrappedSize)
  224. del self.resizing
  225. class FloatValidator(wx.PyValidator):
  226. """!Validator for floating-point input"""
  227. def __init__(self):
  228. wx.PyValidator.__init__(self)
  229. self.Bind(wx.EVT_TEXT, self.OnText)
  230. def Clone(self):
  231. """!Clone validator"""
  232. return FloatValidator()
  233. def Validate(self):
  234. """Validate input"""
  235. textCtrl = self.GetWindow()
  236. text = textCtrl.GetValue()
  237. if text:
  238. try:
  239. float(text)
  240. except ValueError:
  241. textCtrl.SetBackgroundColour("grey")
  242. textCtrl.SetFocus()
  243. textCtrl.Refresh()
  244. return False
  245. sysColor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW)
  246. textCtrl.SetBackgroundColour(sysColor)
  247. textCtrl.Refresh()
  248. return True
  249. def OnText(self, event):
  250. """!Do validation"""
  251. self.Validate()
  252. event.Skip()
  253. def TransferToWindow(self):
  254. return True # Prevent wxDialog from complaining.
  255. def TransferFromWindow(self):
  256. return True # Prevent wxDialog from complaining.
  257. class ItemTree(CT.CustomTreeCtrl):
  258. def __init__(self, parent, id = wx.ID_ANY,
  259. ctstyle = CT.TR_HIDE_ROOT | CT.TR_FULL_ROW_HIGHLIGHT | CT.TR_HAS_BUTTONS |
  260. CT.TR_LINES_AT_ROOT | CT.TR_SINGLE, **kwargs):
  261. if globalvar.hasAgw:
  262. super(ItemTree, self).__init__(parent, id, agwStyle = ctstyle, **kwargs)
  263. else:
  264. super(ItemTree, self).__init__(parent, id, style = ctstyle, **kwargs)
  265. self.root = self.AddRoot(_("Menu tree"))
  266. self.itemsMarked = [] # list of marked items
  267. self.itemSelected = None
  268. def SearchItems(self, element, value):
  269. """!Search item
  270. @param element element index (see self.searchBy)
  271. @param value
  272. @return list of found tree items
  273. """
  274. items = list()
  275. if not value:
  276. return items
  277. item = self.GetFirstChild(self.root)[0]
  278. self._processItem(item, element, value, items)
  279. self.itemsMarked = items
  280. self.itemSelected = None
  281. return items
  282. def _processItem(self, item, element, value, listOfItems):
  283. """!Search items (used by SearchItems)
  284. @param item reference item
  285. @param listOfItems list of found items
  286. """
  287. while item and item.IsOk():
  288. subItem = self.GetFirstChild(item)[0]
  289. if subItem:
  290. self._processItem(subItem, element, value, listOfItems)
  291. data = self.GetPyData(item)
  292. if data and element in data and \
  293. value.lower() in data[element].lower():
  294. listOfItems.append(item)
  295. item = self.GetNextSibling(item)
  296. def GetSelected(self):
  297. """!Get selected item"""
  298. return self.itemSelected
  299. def OnShowItem(self, event):
  300. """!Highlight first found item in menu tree"""
  301. if len(self.itemsMarked) > 0:
  302. if self.GetSelected():
  303. self.ToggleItemSelection(self.GetSelected())
  304. idx = self.itemsMarked.index(self.GetSelected()) + 1
  305. else:
  306. idx = 0
  307. try:
  308. self.ToggleItemSelection(self.itemsMarked[idx])
  309. self.itemSelected = self.itemsMarked[idx]
  310. self.EnsureVisible(self.itemsMarked[idx])
  311. except IndexError:
  312. self.ToggleItemSelection(self.itemsMarked[0]) # reselect first item
  313. self.EnsureVisible(self.itemsMarked[0])
  314. self.itemSelected = self.itemsMarked[0]
  315. else:
  316. for item in self.root.GetChildren():
  317. self.Collapse(item)
  318. itemSelected = self.GetSelection()
  319. if itemSelected:
  320. self.ToggleItemSelection(itemSelected)
  321. self.itemSelected = None