GRASS Programmer's Manual  6.5.svn(2012)-r51648
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
widgets.py
Go to the documentation of this file.
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