widgets.py 17 KB

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