|
GRASS Programmer's Manual
6.5.svn(2012)-r51648
|
00001 """! 00002 @package gui_core.widgets 00003 00004 @brief Core GUI widgets 00005 00006 Classes: 00007 - widgets::GNotebook 00008 - widgets::ScrolledPanel 00009 - widgets::NumTextCtrl 00010 - widgets::FloatSlider 00011 - widgets::SymbolButton 00012 - widgets::StaticWrapText 00013 - widgets::BaseValidator 00014 - widgets::IntegerValidator 00015 - widgets::FloatValidator 00016 - widgets::ItemTree 00017 00018 (C) 2008-2011 by the GRASS Development Team 00019 00020 This program is free software under the GNU General Public License 00021 (>=v2). Read the file COPYING that comes with GRASS for details. 00022 00023 @author Martin Landa <landa.martin gmail.com> (Google SoC 2008/2010) 00024 @author Enhancements by Michael Barton <michael.barton asu.edu> 00025 @author Anna Kratochvilova <kratochanna gmail.com> (Google SoC 2011) 00026 """ 00027 00028 import os 00029 import sys 00030 import string 00031 00032 import wx 00033 import wx.lib.scrolledpanel as SP 00034 try: 00035 import wx.lib.agw.flatnotebook as FN 00036 except ImportError: 00037 import wx.lib.flatnotebook as FN 00038 try: 00039 from wx.lib.buttons import ThemedGenBitmapTextButton as BitmapTextButton 00040 except ImportError: # not sure about TGBTButton version 00041 from wx.lib.buttons import GenBitmapTextButton as BitmapTextButton 00042 try: 00043 import wx.lib.agw.customtreectrl as CT 00044 except ImportError: 00045 import wx.lib.customtreectrl as CT 00046 00047 from core import globalvar 00048 from core.debug import Debug 00049 00050 from wx.lib.newevent import NewEvent 00051 wxSymbolSelectionChanged, EVT_SYMBOL_SELECTION_CHANGED = NewEvent() 00052 00053 class GNotebook(FN.FlatNotebook): 00054 """!Generic notebook widget 00055 """ 00056 def __init__(self, parent, style, **kwargs): 00057 if globalvar.hasAgw: 00058 FN.FlatNotebook.__init__(self, parent, id = wx.ID_ANY, agwStyle = style, **kwargs) 00059 else: 00060 FN.FlatNotebook.__init__(self, parent, id = wx.ID_ANY, style = style, **kwargs) 00061 00062 self.notebookPages = {} 00063 00064 def AddPage(self, **kwargs): 00065 """!Add a page 00066 """ 00067 if 'name' in kwargs: 00068 self.notebookPages[kwargs['name']] = kwargs['page'] 00069 del kwargs['name'] 00070 super(GNotebook, self).AddPage(**kwargs) 00071 00072 def InsertPage(self, **kwargs): 00073 """!Insert a new page 00074 """ 00075 if 'name' in kwargs: 00076 self.notebookPages[kwargs['name']] = kwargs['page'] 00077 del kwargs['name'] 00078 super(GNotebook, self).InsertPage(**kwargs) 00079 00080 def SetSelectionByName(self, page): 00081 """!Set notebook 00082 00083 @param page names, eg. 'layers', 'output', 'search', 'pyshell', 'nviz' 00084 """ 00085 idx = self.GetPageIndexByName(page) 00086 if self.GetSelection() != idx: 00087 self.SetSelection(idx) 00088 00089 def GetPageIndexByName(self, page): 00090 """!Get notebook page index 00091 00092 @param page name 00093 """ 00094 if page not in self.notebookPages: 00095 return -1 00096 00097 return self.GetPageIndex(self.notebookPages[page]) 00098 00099 class ScrolledPanel(SP.ScrolledPanel): 00100 """!Custom ScrolledPanel to avoid strange behaviour concerning focus""" 00101 def __init__(self, parent, style = wx.TAB_TRAVERSAL | wx.SUNKEN_BORDER): 00102 SP.ScrolledPanel.__init__(self, parent = parent, id = wx.ID_ANY, style = style) 00103 00104 def OnChildFocus(self, event): 00105 pass 00106 00107 class NumTextCtrl(wx.TextCtrl): 00108 """!Class derived from wx.TextCtrl for numerical values only""" 00109 def __init__(self, parent, **kwargs): 00110 ## self.precision = kwargs.pop('prec') 00111 wx.TextCtrl.__init__(self, parent = parent, 00112 validator = NTCValidator(flag = 'DIGIT_ONLY'), **kwargs) 00113 00114 00115 def SetValue(self, value): 00116 super(NumTextCtrl, self).SetValue( str(value)) 00117 00118 def GetValue(self): 00119 val = super(NumTextCtrl, self).GetValue() 00120 if val == '': 00121 val = '0' 00122 try: 00123 return float(val) 00124 except ValueError: 00125 val = ''.join(''.join(val.split('-')).split('.')) 00126 return float(val) 00127 00128 def SetRange(self, min, max): 00129 pass 00130 00131 class FloatSlider(wx.Slider): 00132 """!Class derived from wx.Slider for floats""" 00133 def __init__(self, **kwargs): 00134 Debug.msg(1, "FloatSlider.__init__()") 00135 wx.Slider.__init__(self, **kwargs) 00136 self.coef = 1. 00137 #init range 00138 self.minValueOrig = 0 00139 self.maxValueOrig = 1 00140 00141 def SetValue(self, value): 00142 value *= self.coef 00143 if abs(value) < 1 and value != 0: 00144 while abs(value) < 1: 00145 value *= 100 00146 self.coef *= 100 00147 super(FloatSlider, self).SetRange(self.minValueOrig * self.coef, self.maxValueOrig * self.coef) 00148 super(FloatSlider, self).SetValue(value) 00149 00150 Debug.msg(4, "FloatSlider.SetValue(): value = %f" % value) 00151 00152 def SetRange(self, minValue, maxValue): 00153 self.coef = 1. 00154 self.minValueOrig = minValue 00155 self.maxValueOrig = maxValue 00156 if abs(minValue) < 1 or abs(maxValue) < 1: 00157 while (abs(minValue) < 1 and minValue != 0) or (abs(maxValue) < 1 and maxValue != 0): 00158 minValue *= 100 00159 maxValue *= 100 00160 self.coef *= 100 00161 super(FloatSlider, self).SetValue(super(FloatSlider, self).GetValue() * self.coef) 00162 super(FloatSlider, self).SetRange(minValue, maxValue) 00163 Debug.msg(4, "FloatSlider.SetRange(): minValue = %f, maxValue = %f" % (minValue, maxValue)) 00164 00165 def GetValue(self): 00166 val = super(FloatSlider, self).GetValue() 00167 Debug.msg(4, "FloatSlider.GetValue(): value = %f" % (val/self.coef)) 00168 return val/self.coef 00169 00170 class SymbolButton(BitmapTextButton): 00171 """!Button with symbol and label.""" 00172 def __init__(self, parent, usage, label, **kwargs): 00173 """!Constructor 00174 00175 @param parent parent (usually wx.Panel) 00176 @param usage determines usage and picture 00177 @param label displayed label 00178 """ 00179 size = (15, 15) 00180 buffer = wx.EmptyBitmap(*size) 00181 BitmapTextButton.__init__(self, parent = parent, label = " " + label, bitmap = buffer, **kwargs) 00182 00183 dc = wx.MemoryDC() 00184 dc.SelectObject(buffer) 00185 maskColor = wx.Color(255, 255, 255) 00186 dc.SetBrush(wx.Brush(maskColor)) 00187 dc.Clear() 00188 00189 if usage == 'record': 00190 self.DrawRecord(dc, size) 00191 elif usage == 'stop': 00192 self.DrawStop(dc, size) 00193 elif usage == 'play': 00194 self.DrawPlay(dc, size) 00195 elif usage == 'pause': 00196 self.DrawPause(dc, size) 00197 00198 if sys.platform != "win32": 00199 buffer.SetMaskColour(maskColor) 00200 self.SetBitmapLabel(buffer) 00201 dc.SelectObject(wx.NullBitmap) 00202 00203 def DrawRecord(self, dc, size): 00204 """!Draw record symbol""" 00205 dc.SetBrush(wx.Brush(wx.Color(255, 0, 0))) 00206 dc.DrawCircle(size[0]/2, size[1] / 2, size[0] / 2) 00207 00208 def DrawStop(self, dc, size): 00209 """!Draw stop symbol""" 00210 dc.SetBrush(wx.Brush(wx.Color(50, 50, 50))) 00211 dc.DrawRectangle(0, 0, size[0], size[1]) 00212 00213 def DrawPlay(self, dc, size): 00214 """!Draw play symbol""" 00215 dc.SetBrush(wx.Brush(wx.Color(0, 255, 0))) 00216 points = (wx.Point(0, 0), wx.Point(0, size[1]), wx.Point(size[0], size[1] / 2)) 00217 dc.DrawPolygon(points) 00218 00219 def DrawPause(self, dc, size): 00220 """!Draw pause symbol""" 00221 dc.SetBrush(wx.Brush(wx.Color(50, 50, 50))) 00222 dc.DrawRectangle(0, 0, 2 * size[0] / 5, size[1]) 00223 dc.DrawRectangle(3 * size[0] / 5, 0, 2 * size[0] / 5, size[1]) 00224 00225 class StaticWrapText(wx.StaticText): 00226 """!A Static Text field that wraps its text to fit its width, 00227 enlarging its height if necessary. 00228 """ 00229 def __init__(self, parent, id = wx.ID_ANY, label = '', *args, **kwds): 00230 self.parent = parent 00231 self.originalLabel = label 00232 00233 wx.StaticText.__init__(self, parent, id, label = '', *args, **kwds) 00234 00235 self.SetLabel(label) 00236 self.Bind(wx.EVT_SIZE, self.OnResize) 00237 00238 def SetLabel(self, label): 00239 self.originalLabel = label 00240 self.wrappedSize = None 00241 self.OnResize(None) 00242 00243 def OnResize(self, event): 00244 if not getattr(self, "resizing", False): 00245 self.resizing = True 00246 newSize = wx.Size(self.parent.GetSize().width - 50, 00247 self.GetSize().height) 00248 if self.wrappedSize != newSize: 00249 wx.StaticText.SetLabel(self, self.originalLabel) 00250 self.Wrap(newSize.width) 00251 self.wrappedSize = newSize 00252 00253 self.SetSize(self.wrappedSize) 00254 del self.resizing 00255 00256 class BaseValidator(wx.PyValidator): 00257 def __init__(self): 00258 wx.PyValidator.__init__(self) 00259 00260 self.Bind(wx.EVT_TEXT, self.OnText) 00261 00262 def OnText(self, event): 00263 """!Do validation""" 00264 self.Validate() 00265 00266 event.Skip() 00267 00268 def Validate(self): 00269 """Validate input""" 00270 textCtrl = self.GetWindow() 00271 text = textCtrl.GetValue() 00272 00273 if text: 00274 try: 00275 self.type(text) 00276 except ValueError: 00277 textCtrl.SetBackgroundColour("grey") 00278 textCtrl.SetFocus() 00279 textCtrl.Refresh() 00280 return False 00281 00282 sysColor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW) 00283 textCtrl.SetBackgroundColour(sysColor) 00284 00285 textCtrl.Refresh() 00286 00287 return True 00288 00289 def TransferToWindow(self): 00290 return True # Prevent wxDialog from complaining. 00291 00292 def TransferFromWindow(self): 00293 return True # Prevent wxDialog from complaining. 00294 00295 class IntegerValidator(BaseValidator): 00296 """!Validator for floating-point input""" 00297 def __init__(self): 00298 BaseValidator.__init__(self) 00299 self.type = int 00300 00301 def Clone(self): 00302 """!Clone validator""" 00303 return IntegerValidator() 00304 00305 class FloatValidator(BaseValidator): 00306 """!Validator for floating-point input""" 00307 def __init__(self): 00308 BaseValidator.__init__(self) 00309 self.type = float 00310 00311 def Clone(self): 00312 """!Clone validator""" 00313 return FloatValidator() 00314 00315 class NTCValidator(wx.PyValidator): 00316 """!validates input in textctrls, taken from wxpython demo""" 00317 def __init__(self, flag = None): 00318 wx.PyValidator.__init__(self) 00319 self.flag = flag 00320 self.Bind(wx.EVT_CHAR, self.OnChar) 00321 00322 def Clone(self): 00323 return NTCValidator(self.flag) 00324 00325 def OnChar(self, event): 00326 key = event.GetKeyCode() 00327 if key < wx.WXK_SPACE or key == wx.WXK_DELETE or key > 255: 00328 event.Skip() 00329 return 00330 if self.flag == 'DIGIT_ONLY' and chr(key) in string.digits + '.-': 00331 event.Skip() 00332 return 00333 if not wx.Validator_IsSilent(): 00334 wx.Bell() 00335 # Returning without calling even.Skip eats the event before it 00336 # gets to the text control 00337 return 00338 00339 class ItemTree(CT.CustomTreeCtrl): 00340 def __init__(self, parent, id = wx.ID_ANY, 00341 ctstyle = CT.TR_HIDE_ROOT | CT.TR_FULL_ROW_HIGHLIGHT | CT.TR_HAS_BUTTONS | 00342 CT.TR_LINES_AT_ROOT | CT.TR_SINGLE, **kwargs): 00343 if globalvar.hasAgw: 00344 super(ItemTree, self).__init__(parent, id, agwStyle = ctstyle, **kwargs) 00345 else: 00346 super(ItemTree, self).__init__(parent, id, style = ctstyle, **kwargs) 00347 00348 self.root = self.AddRoot(_("Menu tree")) 00349 self.itemsMarked = [] # list of marked items 00350 self.itemSelected = None 00351 00352 def SearchItems(self, element, value): 00353 """!Search item 00354 00355 @param element element index (see self.searchBy) 00356 @param value 00357 00358 @return list of found tree items 00359 """ 00360 items = list() 00361 if not value: 00362 return items 00363 00364 item = self.GetFirstChild(self.root)[0] 00365 self._processItem(item, element, value, items) 00366 00367 self.itemsMarked = items 00368 self.itemSelected = None 00369 00370 return items 00371 00372 def _processItem(self, item, element, value, listOfItems): 00373 """!Search items (used by SearchItems) 00374 00375 @param item reference item 00376 @param listOfItems list of found items 00377 """ 00378 while item and item.IsOk(): 00379 subItem = self.GetFirstChild(item)[0] 00380 if subItem: 00381 self._processItem(subItem, element, value, listOfItems) 00382 data = self.GetPyData(item) 00383 00384 if data and element in data and \ 00385 value.lower() in data[element].lower(): 00386 listOfItems.append(item) 00387 00388 item = self.GetNextSibling(item) 00389 00390 def GetSelected(self): 00391 """!Get selected item""" 00392 return self.itemSelected 00393 00394 def OnShowItem(self, event): 00395 """!Highlight first found item in menu tree""" 00396 if len(self.itemsMarked) > 0: 00397 if self.GetSelected(): 00398 self.ToggleItemSelection(self.GetSelected()) 00399 idx = self.itemsMarked.index(self.GetSelected()) + 1 00400 else: 00401 idx = 0 00402 try: 00403 self.ToggleItemSelection(self.itemsMarked[idx]) 00404 self.itemSelected = self.itemsMarked[idx] 00405 self.EnsureVisible(self.itemsMarked[idx]) 00406 except IndexError: 00407 self.ToggleItemSelection(self.itemsMarked[0]) # reselect first item 00408 self.EnsureVisible(self.itemsMarked[0]) 00409 self.itemSelected = self.itemsMarked[0] 00410 else: 00411 for item in self.root.GetChildren(): 00412 self.Collapse(item) 00413 itemSelected = self.GetSelection() 00414 if itemSelected: 00415 self.ToggleItemSelection(itemSelected) 00416 self.itemSelected = None 00417 00418 class SingleSymbolPanel(wx.Panel): 00419 """!Panel for displaying one symbol. 00420 00421 Changes background when selected. Assumes that parent will catch 00422 events emitted on mouse click. Used in gui_core::dialog::SymbolDialog. 00423 """ 00424 def __init__(self, parent, symbolPath): 00425 """!Panel constructor 00426 00427 @param parent parent (gui_core::dialog::SymbolDialog) 00428 @param symbolPath absolute path to symbol 00429 """ 00430 wx.Panel.__init__(self, parent, id = wx.ID_ANY, style = wx.BORDER_RAISED) 00431 self.SetName(os.path.splitext(os.path.basename(symbolPath))[0]) 00432 self.sBmp = wx.StaticBitmap(self, wx.ID_ANY, wx.Bitmap(symbolPath)) 00433 00434 self.selected = False 00435 self.selectColor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT) 00436 self.deselectColor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW) 00437 00438 sizer = wx.BoxSizer() 00439 sizer.Add(item = self.sBmp, proportion = 0, flag = wx.ALL | wx.ALIGN_CENTER, border = 5) 00440 self.SetBackgroundColour(self.deselectColor) 00441 self.SetMinSize(self.GetBestSize()) 00442 self.SetSizerAndFit(sizer) 00443 00444 # binding to both (staticBitmap, Panel) necessary 00445 self.sBmp.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) 00446 self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) 00447 self.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick) 00448 self.sBmp.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick) 00449 00450 def OnLeftDown(self, event): 00451 """!Panel selected, background changes""" 00452 self.selected = True 00453 self.SetBackgroundColour(self.selectColor) 00454 event.Skip() 00455 00456 event = wxSymbolSelectionChanged(name = self.GetName(), doubleClick = False) 00457 wx.PostEvent(self.GetParent(), event) 00458 00459 def OnDoubleClick(self, event): 00460 event = wxSymbolSelectionChanged(name = self.GetName(), doubleClick = True) 00461 wx.PostEvent(self.GetParent(), event) 00462 00463 def Deselect(self): 00464 """!Panel deselected, background changes back to default""" 00465 self.selected = False 00466 self.SetBackgroundColour(self.deselectColor) 00467 00468 def Select(self): 00469 """!Select panel, no event emitted""" 00470 self.selected = True 00471 self.SetBackgroundColour(self.selectColor) 00472