GRASS Programmer's Manual  6.5.svn(2012)-r51648
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
colorrules.py
Go to the documentation of this file.
00001 """
00002 @package module.colorrules
00003 
00004 @brief Dialog for interactive management of raster/vector color tables
00005 and color rules.
00006 
00007 Classes:
00008  - colorrules::RulesPanel
00009  - colorrules::ColorTable
00010  - colorrules::RasterColorTable
00011  - colorrules::VectorColorTable
00012  - colorrules::BufferedWindow
00013 
00014 (C) 2008, 2010-2011 by the GRASS Development Team
00015 
00016 This program is free software under the GNU General Public License
00017 (>=v2). Read the file COPYING that comes with GRASS for details.
00018 
00019 @author Michael Barton (Arizona State University)
00020 @author Martin Landa <landa.martin gmail.com> (various updates)
00021 @author Anna Kratochvilova <kratochanna gmail.com> (split to base and derived classes)
00022 """
00023 
00024 import os
00025 import shutil
00026 import copy
00027 import tempfile
00028 
00029 import wx
00030 import wx.lib.colourselect     as csel
00031 import wx.lib.scrolledpanel    as scrolled
00032 import wx.lib.filebrowsebutton as filebrowse
00033 
00034 import grass.script as grass
00035 
00036 from core             import globalvar
00037 from core             import utils
00038 from core.gcmd        import GMessage, RunCommand, GError
00039 from gui_core.gselect import Select, LayerSelect, ColumnSelect, VectorDBInfo
00040 from core.render      import Map
00041 from gui_core.forms   import GUI
00042 from core.debug       import Debug as Debug
00043 from core.settings    import UserSettings
00044 
00045 class RulesPanel:
00046     def __init__(self, parent, mapType, attributeType, properties, panelWidth = 180):
00047         """!Create rules panel
00048         
00049         @param mapType raster/vector
00050         @param attributeType color/size for choosing widget type
00051         @param properties properties of classes derived from ColorTable
00052         @param panelWidth width of scroll panel"""
00053         
00054         self.ruleslines = {}
00055         self.mapType = mapType
00056         self.attributeType = attributeType
00057         self.properties = properties
00058         self.parent = parent
00059         self.panelWidth = panelWidth
00060         
00061         self.mainSizer = wx.FlexGridSizer(cols = 3, vgap = 6, hgap = 4)
00062         # put small border at the top of panel
00063         for i in range(3):
00064             self.mainSizer.Add(item = wx.Size(3, 3))
00065         
00066         self.mainPanel = scrolled.ScrolledPanel(parent, id = wx.ID_ANY,
00067                                           size = (self.panelWidth, 300),
00068                                           style = wx.TAB_TRAVERSAL | wx.SUNKEN_BORDER)
00069                 
00070         # (un)check all
00071         self.checkAll = wx.CheckBox(parent, id = wx.ID_ANY, label = _("Check all"))
00072         self.checkAll.SetValue(True)
00073         # clear button
00074         self.clearAll = wx.Button(parent, id = wx.ID_ANY, label = _("Clear all"))
00075         #  determines how many rules should be added
00076         self.numRules = wx.SpinCtrl(parent, id = wx.ID_ANY,
00077                                     min = 1, max = 1e6, initial = 1)
00078         # add rules
00079         self.btnAdd = wx.Button(parent, id = wx.ID_ADD)
00080         
00081         self.btnAdd.Bind(wx.EVT_BUTTON, self.OnAddRules)
00082         self.checkAll.Bind(wx.EVT_CHECKBOX, self.OnCheckAll)
00083         self.clearAll.Bind(wx.EVT_BUTTON, self.OnClearAll)
00084 
00085         self.mainPanel.SetSizer(self.mainSizer)
00086         self.mainPanel.SetAutoLayout(True)
00087         self.mainPanel.SetupScrolling()    
00088     
00089     def Clear(self):
00090         """!Clear and widgets and delete information"""
00091         self.ruleslines.clear()
00092         self.mainSizer.Clear(deleteWindows=True)
00093     
00094     def OnCheckAll(self, event):
00095         """!(Un)check all rules"""
00096         check = event.GetInt()
00097         for child in self.mainPanel.GetChildren():
00098             if child.GetName() == 'enable':
00099                 child.SetValue(check)
00100             else:
00101                 child.Enable(check)
00102                 
00103     def OnClearAll(self, event):
00104         """!Delete all widgets in panel"""
00105         self.Clear()
00106         
00107     def OnAddRules(self, event):
00108         """!Add rules button pressed"""
00109         nrules = self.numRules.GetValue()
00110         self.AddRules(nrules)
00111         
00112     def AddRules(self, nrules, start = False):
00113         """!Add rules 
00114         @param start set widgets (not append)"""
00115        
00116         snum = len(self.ruleslines.keys())
00117         if start:
00118             snum = 0
00119         for num in range(snum, snum + nrules):
00120             # enable
00121             enable = wx.CheckBox(parent = self.mainPanel, id = num)
00122             enable.SetValue(True)
00123             enable.SetName('enable')
00124             enable.Bind(wx.EVT_CHECKBOX, self.OnRuleEnable)
00125             # value
00126             txt_ctrl = wx.TextCtrl(parent = self.mainPanel, id = 1000 + num,
00127                                    size = (80, -1),
00128                                    style = wx.TE_NOHIDESEL)
00129             if self.mapType == 'vector':
00130                 txt_ctrl.SetToolTipString(_("Enter vector attribute values"))
00131             txt_ctrl.Bind(wx.EVT_TEXT, self.OnRuleValue)
00132             txt_ctrl.SetName('source')
00133             if self.attributeType == 'color':
00134                 # color
00135                 columnCtrl = csel.ColourSelect(self.mainPanel, id = 2000 + num,
00136                                                size  =  globalvar.DIALOG_COLOR_SIZE)
00137                 columnCtrl.Bind(csel.EVT_COLOURSELECT, self.OnRuleColor)
00138                 columnCtrl.SetName('target')
00139                 if not start:
00140                     self.ruleslines[enable.GetId()] = { 'value' : '',
00141                                                         'color': "0:0:0" }
00142             else:
00143                 # size or width
00144                 init = 2
00145                 if self.attributeType == 'size':
00146                     init = 100
00147                 columnCtrl = wx.SpinCtrl(self.mainPanel, id = 2000 + num,
00148                                          size = (50, -1), min = 1, max = 1e4,
00149                                          initial = init)
00150                 columnCtrl.Bind(wx.EVT_SPINCTRL, self.OnRuleSize)
00151                 columnCtrl.Bind(wx.EVT_TEXT, self.OnRuleSize)
00152                 columnCtrl.SetName('target')
00153                 if not start:
00154                     self.ruleslines[enable.GetId()] = { 'value' : '',
00155                                                         self.attributeType: init }
00156         
00157             self.mainSizer.Add(item = enable, proportion = 0,
00158                               flag = wx.ALIGN_CENTER_VERTICAL)
00159             self.mainSizer.Add(item = txt_ctrl, proportion = 0,
00160                               flag = wx.ALIGN_CENTER | wx.RIGHT, border = 5)
00161             self.mainSizer.Add(item = columnCtrl, proportion = 0,
00162                               flag = wx.ALIGN_CENTER | wx.RIGHT, border = 10)
00163         
00164         self.mainPanel.Layout()
00165         self.mainPanel.SetupScrolling(scroll_x = False)
00166     
00167     def OnRuleEnable(self, event):
00168         """!Rule enabled/disabled"""
00169         id = event.GetId()
00170         
00171         if event.IsChecked():
00172             self.mainPanel.FindWindowById(id + 1000).Enable()
00173             self.mainPanel.FindWindowById(id + 2000).Enable()
00174             if self.mapType == 'vector' and not self.parent.GetParent().colorTable:
00175                 vals = []
00176                 vals.append(self.mainPanel.FindWindowById(id + 1000).GetValue())
00177                 try:
00178                     vals.append(self.mainPanel.FindWindowById(id + 1 + 1000).GetValue())
00179                 except AttributeError:
00180                     vals.append(None)
00181                 value = self.SQLConvert(vals)
00182             else:
00183                 value = self.mainPanel.FindWindowById(id + 1000).GetValue()
00184             color = self.mainPanel.FindWindowById(id + 2000).GetValue()
00185             
00186             if self.attributeType == 'color':
00187             # color
00188                 color_str = str(color[0]) + ':' \
00189                           + str(color[1]) + ':' \
00190                           + str(color[2])
00191                 self.ruleslines[id] = {'value' : value,
00192                                        'color' : color_str }
00193                 
00194             else:
00195             # size or width
00196                 self.ruleslines[id] = {'value' : value,
00197                                        self.attributeType  : float(color) }
00198         
00199         else:
00200             self.mainPanel.FindWindowById(id + 1000).Disable()
00201             self.mainPanel.FindWindowById(id + 2000).Disable()
00202             del self.ruleslines[id]
00203         
00204     def OnRuleColor(self, event):
00205         """!Rule color changed"""
00206         num = event.GetId()
00207         
00208         rgba_color = event.GetValue()
00209         
00210         rgb_string = str(rgba_color[0]) + ':' \
00211                    + str(rgba_color[1]) + ':' \
00212                    + str(rgba_color[2])
00213         
00214         self.ruleslines[num-2000]['color'] = rgb_string
00215      
00216     def OnRuleSize(self, event):
00217         """!Rule size changed"""
00218         num = event.GetId()
00219         size = event.GetInt()
00220         
00221         self.ruleslines[num - 2000][self.attributeType] = size
00222         
00223     def OnRuleValue(self, event):
00224         """!Rule value changed"""
00225         num = event.GetId()
00226         val = event.GetString().strip()
00227         
00228         if val == '':
00229             return
00230         try:
00231             table = self.parent.colorTable
00232         except AttributeError:
00233             # due to panel/scrollpanel in vector dialog
00234             if isinstance(self.parent.GetParent(), RasterColorTable):
00235                 table = self.parent.GetParent().colorTable
00236             else:
00237                 table = self.parent.GetParent().GetParent().colorTable
00238         if table:
00239             self.SetRasterRule(num, val)
00240         else:
00241             self.SetVectorRule(num, val)
00242 
00243     def SetRasterRule(self, num, val): 
00244         """!Set raster rule"""       
00245         self.ruleslines[num - 1000]['value'] = val
00246 
00247     def SetVectorRule(self, num, val):
00248         """!Set vector rule"""
00249         vals = []
00250         vals.append(val)
00251         try:
00252             vals.append(self.mainPanel.FindWindowById(num + 1).GetValue())
00253         except AttributeError:
00254             vals.append(None)
00255         self.ruleslines[num - 1000]['value'] = self.SQLConvert(vals)
00256             
00257     def Enable(self, enable = True):
00258         """!Enable/Disable all widgets"""
00259         for child in self.mainPanel.GetChildren():
00260             child.Enable(enable)
00261         sql = True
00262         self.LoadRulesline(sql)# todo
00263         self.btnAdd.Enable(enable)
00264         self.numRules.Enable(enable)
00265         self.checkAll.Enable(enable)
00266         self.clearAll.Enable(enable)
00267         
00268         
00269     def LoadRules(self):
00270         message = ""        
00271         for item in range(len(self.ruleslines)):
00272             try:
00273                 self.mainPanel.FindWindowById(item + 1000).SetValue(self.ruleslines[item]['value'])
00274                 r, g, b = (0, 0, 0) # default
00275                 if not self.ruleslines[item][self.attributeType]:
00276                     if self.attributeType == 'color':
00277                         self.ruleslines[item][self.attributeType] = '%d:%d:%d' % (r, g, b)
00278                     elif self.attributeType == 'size':
00279                         self.ruleslines[item][self.attributeType] = 100                
00280                     elif self.attributeType == 'width':
00281                         self.ruleslines[item][self.attributeType] = 2
00282                     
00283                 if self.attributeType == 'color':
00284                     try:
00285                         r, g, b = map(int, self.ruleslines[item][self.attributeType].split(':'))
00286                     except ValueError, e:
00287                         message =  _("Bad color format. Use color format '0:0:0'")
00288                     self.mainPanel.FindWindowById(item + 2000).SetValue((r, g, b))
00289                 else:
00290                     value = float(self.ruleslines[item][self.attributeType])
00291                     self.mainPanel.FindWindowById(item + 2000).SetValue(value)
00292             except:
00293                 continue
00294                 
00295         if message:
00296             GMessage(parent = self.parent, message = message)
00297             return False
00298         
00299         return True
00300                 
00301     def SQLConvert(self, vals):
00302         """!Prepare value for SQL query"""
00303         if vals[0].isdigit():
00304             sqlrule = '%s=%s' % (self.properties['sourceColumn'], vals[0])
00305             if vals[1]:
00306                 sqlrule += ' AND %s<%s' % (self.properties['sourceColumn'], vals[1])
00307         else:
00308             sqlrule = '%s=%s' % (self.properties['sourceColumn'], vals[0])
00309         
00310         return sqlrule  
00311 
00312 class ColorTable(wx.Frame):
00313     def __init__(self, parent, title, id = wx.ID_ANY,
00314                  style = wx.DEFAULT_FRAME_STYLE | wx.RESIZE_BORDER,
00315                  **kwargs):
00316         """!Dialog for interactively entering rules for map management
00317         commands
00318         """
00319         self.parent = parent # GMFrame
00320         wx.Frame.__init__(self, parent, id, title, style = style, **kwargs)
00321         
00322         self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
00323         
00324         self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
00325         
00326         # instance of render.Map to be associated with display
00327         self.Map = Map() 
00328         
00329         # input map to change
00330         self.inmap = ''
00331         # reference to layer with preview
00332         self.layer = None     
00333         # layout
00334         self._doLayout()
00335         
00336         # bindings
00337         self.Bind(wx.EVT_BUTTON, self.OnHelp, self.btnHelp)
00338         self.selectionInput.Bind(wx.EVT_TEXT, self.OnSelectionInput)
00339         self.Bind(wx.EVT_BUTTON, self.OnCancel, self.btnCancel)
00340         self.Bind(wx.EVT_BUTTON, self.OnApply, self.btnApply)
00341         self.Bind(wx.EVT_BUTTON, self.OnOK, self.btnOK)
00342         self.Bind(wx.EVT_CLOSE,  self.OnCloseWindow)
00343        
00344         self.Bind(wx.EVT_BUTTON, self.OnPreview, self.btnPreview)
00345         
00346     def _initLayer(self):
00347         """!Set initial layer when opening dialog"""
00348         # set map layer from layer tree, first selected,
00349         # if not the right type, than select another
00350         try:
00351             sel = self.parent.curr_page.maptree.layer_selected
00352             if sel and self.parent.curr_page.maptree.GetPyData(sel)[0]['type'] == self.mapType:
00353                 layer = sel
00354             else:
00355                 layer = self.parent.curr_page.maptree.FindItemByData(key = 'type', value = self.mapType)
00356         except:
00357             layer = None
00358         if layer:
00359             mapLayer = self.parent.curr_page.maptree.GetPyData(layer)[0]['maplayer']
00360             name = mapLayer.GetName()
00361             type = mapLayer.GetType()
00362             self.selectionInput.SetValue(name)
00363             self.inmap = name
00364     
00365     def _createMapSelection(self, parent):
00366         """!Create map selection part of dialog"""
00367         # top controls
00368         if self.mapType == 'raster':
00369             maplabel = _('Select raster map:')
00370         else:
00371             maplabel = _('Select vector map:')
00372         inputBox = wx.StaticBox(parent, id = wx.ID_ANY,
00373                                 label = " %s " % maplabel)
00374         inputSizer = wx.StaticBoxSizer(inputBox, wx.VERTICAL)
00375 
00376         self.selectionInput = Select(parent = parent, id = wx.ID_ANY,
00377                                      size = globalvar.DIALOG_GSELECT_SIZE,
00378                                      type = self.mapType)
00379         # layout
00380         inputSizer.Add(item = self.selectionInput,
00381                        flag = wx.ALIGN_CENTER_VERTICAL | wx.ALL | wx.EXPAND, border = 5)
00382         
00383         return inputSizer
00384     
00385     def _createFileSelection(self, parent):
00386         """!Create file (open/save rules) selection part of dialog"""
00387         inputBox = wx.StaticBox(parent, id = wx.ID_ANY,
00388                                 label = " %s " % _("Import or export color table:"))
00389         inputSizer = wx.StaticBoxSizer(inputBox, wx.VERTICAL)
00390         
00391         self.loadRules = filebrowse.FileBrowseButton(parent = parent, id = wx.ID_ANY, fileMask = '*',
00392                                                      size = globalvar.DIALOG_GSELECT_SIZE,
00393                                                      labelText = _('Load color table from file:'),
00394                                                      dialogTitle = _('Choose file to load color table'),
00395                                                      buttonText = _('Load'),
00396                                                      toolTip = _("Type filename or click to choose "
00397                                                                  "file and load color table"),
00398                                                      startDirectory = os.getcwd(), fileMode = wx.OPEN,
00399                                                      changeCallback = self.OnLoadRulesFile)
00400         self.saveRules = filebrowse.FileBrowseButton(parent = parent, id = wx.ID_ANY, fileMask = '*',
00401                                                      size = globalvar.DIALOG_GSELECT_SIZE,
00402                                                      labelText = _('Save color table to file:'),
00403                                                      dialogTitle = _('Choose file to save color table'),
00404                                                      toolTip = _("Type filename or click to choose "
00405                                                                  "file and save color table"),
00406                                                      buttonText = _('Save'),
00407                                                      startDirectory = os.getcwd(), fileMode = wx.SAVE,
00408                                                      changeCallback = self.OnSaveRulesFile)
00409         
00410         default = wx.Button(parent = parent, id = wx.ID_ANY, label = _("Reload default table"))   
00411         # layout
00412         sizer = wx.BoxSizer(wx.HORIZONTAL)
00413         sizer.Add(item = self.loadRules, proportion = 1,
00414                   flag = wx.RIGHT | wx.EXPAND, border = 10)
00415         sizer.Add(item = default, flag = wx.ALIGN_CENTER_VERTICAL)
00416         inputSizer.Add(item = sizer,
00417                        flag = wx.TOP | wx.LEFT | wx.RIGHT | wx.EXPAND, border = 5)
00418         sizer = wx.BoxSizer(wx.HORIZONTAL)
00419         sizer.Add(item = self.saveRules, proportion = 1,
00420                   flag = wx.ALIGN_CENTER_VERTICAL | wx.EXPAND)
00421         inputSizer.Add(item = sizer, proportion = 1,
00422                        flag = wx.ALL | wx.EXPAND, border = 5)
00423         
00424         default.Bind(wx.EVT_BUTTON, self.OnLoadDefaultTable)
00425         
00426         if self.mapType == 'vector':
00427             # parent is collapsible pane
00428             parent.SetSizer(inputSizer)
00429         
00430         return inputSizer   
00431          
00432     def _createPreview(self, parent):
00433         """!Create preview"""
00434         # initialize preview display
00435         self.InitDisplay()
00436         self.preview = BufferedWindow(parent, id = wx.ID_ANY, size = (400, 300),
00437                                       Map = self.Map)
00438         self.preview.EraseMap()
00439         
00440     def _createButtons(self, parent):
00441         """!Create buttons for leaving dialog"""
00442         self.btnHelp   = wx.Button(parent, id = wx.ID_HELP)
00443         self.btnCancel = wx.Button(parent, id = wx.ID_CANCEL)
00444         self.btnApply  = wx.Button(parent, id = wx.ID_APPLY) 
00445         self.btnOK     = wx.Button(parent, id = wx.ID_OK)
00446         
00447         self.btnOK.SetDefault()
00448         self.btnOK.Enable(False)
00449         self.btnApply.Enable(False)
00450         
00451         # layout
00452         btnSizer = wx.BoxSizer(wx.HORIZONTAL)
00453         btnSizer.Add(wx.Size(-1, -1), proportion = 1)
00454         btnSizer.Add(self.btnHelp,
00455                      flag = wx.LEFT | wx.RIGHT, border = 5)
00456         btnSizer.Add(self.btnCancel,
00457                      flag = wx.LEFT | wx.RIGHT, border = 5)
00458         btnSizer.Add(self.btnApply,
00459                      flag = wx.LEFT | wx.RIGHT, border = 5)
00460         btnSizer.Add(self.btnOK,
00461                      flag = wx.LEFT | wx.RIGHT, border = 5)
00462         
00463         return btnSizer
00464     
00465     def _createBody(self, parent):
00466         """!Create dialog body consisting of rules and preview"""
00467         bodySizer =  wx.GridBagSizer(hgap = 5, vgap = 5)
00468         bodySizer.AddGrowableRow(1)
00469         bodySizer.AddGrowableCol(2)
00470 
00471         row = 0
00472         # label with range
00473         self.cr_label = wx.StaticText(parent, id = wx.ID_ANY)
00474         bodySizer.Add(item = self.cr_label, pos = (row, 0), span = (1, 3),
00475                       flag = wx.ALL, border = 5)
00476 
00477         row += 1
00478         # color table
00479         self.rulesPanel = RulesPanel(parent = parent, mapType = self.mapType,
00480                                      attributeType = self.attributeType, properties = self.properties)
00481         
00482         bodySizer.Add(item = self.rulesPanel.mainPanel, pos = (row, 0),
00483                       span = (1, 2), flag = wx.EXPAND)
00484         # add two rules as default
00485         self.rulesPanel.AddRules(2)
00486         
00487         # preview window
00488         self._createPreview(parent = parent)
00489         bodySizer.Add(item = self.preview, pos = (row, 2),
00490                       flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER)
00491         
00492         row += 1
00493         # add ckeck all and clear all
00494         bodySizer.Add(item = self.rulesPanel.checkAll, flag = wx.ALIGN_CENTER_VERTICAL, 
00495                       pos = (row, 0))
00496         bodySizer.Add(item = self.rulesPanel.clearAll, pos = (row, 1))
00497         
00498         # preview button
00499         self.btnPreview = wx.Button(parent, id = wx.ID_ANY,
00500                                     label = _("Preview"))
00501         bodySizer.Add(item = self.btnPreview, pos = (row, 2),
00502                       flag = wx.ALIGN_RIGHT)
00503         self.btnPreview.Enable(False)
00504         self.btnPreview.SetToolTipString(_("Show preview of map "
00505                                            "(current Map Display extent is used)."))
00506         
00507         row +=1
00508         # add rules button and spin to sizer
00509         bodySizer.Add(item = self.rulesPanel.numRules, pos = (row, 0),
00510                       flag = wx.ALIGN_CENTER_VERTICAL)
00511         bodySizer.Add(item = self.rulesPanel.btnAdd, pos = (row, 1))
00512         
00513         return bodySizer    
00514         
00515     def InitDisplay(self):
00516         """!Initialize preview display, set dimensions and region
00517         """
00518         self.width = self.Map.width = 400
00519         self.height = self.Map.height = 300
00520         self.Map.geom = self.width, self.height
00521 
00522     def OnCloseWindow(self, event):
00523         """!Window closed
00524         """
00525         self.OnCancel(event)
00526           
00527     def OnApply(self, event):
00528         """!Apply selected color table
00529         
00530         @return True on success otherwise False
00531         """
00532         ret = self.CreateColorTable()
00533         if not ret:
00534             GMessage(parent = self, message = _("No valid color rules given."))
00535         else:
00536             # re-render preview and current map window
00537             self.OnPreview(None)
00538             display = self.parent.GetLayerTree().GetMapDisplay()
00539             if display and display.IsAutoRendered():
00540                 display.GetWindow().UpdateMap(render = True)
00541         
00542         return ret
00543 
00544     def OnOK(self, event):
00545         """!Apply selected color table and close the dialog"""
00546         if self.OnApply(event):
00547             self.OnCancel(event)
00548     
00549     def OnCancel(self, event):
00550         """!Do not apply any changes, remove associated
00551             rendered images and close the dialog"""
00552         self.Map.Clean()
00553         self.Destroy()
00554         
00555     def OnSaveRulesFile(self, event):
00556         """!Save color table to file"""
00557         path = event.GetString()
00558         if not os.path.exists(path):
00559             return
00560         
00561         rulestxt = ''   
00562         for rule in self.rulesPanel.ruleslines.itervalues():
00563             if 'value' not in rule:
00564                 continue
00565             rulestxt += rule['value'] + ' ' + rule['color'] + '\n'
00566         if not rulestxt:
00567             GMessage(message = _("Nothing to save."),
00568                      parent = self)
00569             return
00570         
00571         fd = open(path, 'w')
00572         fd.write(rulestxt)
00573         fd.close()            
00574          
00575     def OnLoadRulesFile(self, event):
00576         """!Load color table from file"""
00577         path = event.GetString()
00578         if not os.path.exists(path):
00579             return
00580         
00581         self.rulesPanel.Clear()
00582         
00583         file = open(path, 'r')
00584         ctable = file.read()
00585         self.ReadColorTable(ctable = ctable)
00586         
00587     def ReadColorTable(self, ctable):
00588         """!Read color table
00589         
00590         @param table color table in format coming from r.colors.out"""
00591         
00592         rulesNumber = len(ctable.splitlines())
00593         self.rulesPanel.AddRules(rulesNumber)
00594         
00595         minim = maxim = count = 0
00596         for line in ctable.splitlines():
00597             try:
00598                 value, color = map(lambda x: x.strip(), line.split(' '))
00599             except ValueError:
00600                 GMessage(parent = self, message = _("Invalid color table format"))
00601                 self.rulesPanel.Clear()
00602                 return
00603             
00604             self.rulesPanel.ruleslines[count]['value'] = value
00605             self.rulesPanel.ruleslines[count]['color'] = color
00606             self.rulesPanel.mainPanel.FindWindowById(count + 1000).SetValue(value)
00607             rgb = list()
00608             for c in color.split(':'):
00609                 rgb.append(int(c))
00610             self.rulesPanel.mainPanel.FindWindowById(count + 2000).SetColour(rgb)
00611             # range
00612             try:
00613                 if float(value) < minim:
00614                     minim = float(value)
00615                 if float(value) > maxim:
00616                     maxim = float(value)
00617             except ValueError: # nv, default
00618                 pass
00619             count += 1
00620         
00621         if self.mapType == 'vector':
00622             # raster min, max is known from r.info
00623             self.properties['min'], self.properties['max'] = minim, maxim
00624             self.SetRangeLabel()
00625             
00626         self.OnPreview(tmp = True)  
00627          
00628     def OnLoadDefaultTable(self, event):
00629         """!Load internal color table"""
00630         self.LoadTable()
00631         
00632     def LoadTable(self, mapType = 'raster'):
00633         """!Load current color table (using `r(v).colors.out`)
00634         
00635         @param mapType map type (raster or vector)"""
00636         self.rulesPanel.Clear()
00637 
00638         if mapType == 'raster':
00639             cmd = ['r.colors.out',
00640                    'read=True',
00641                    'map=%s' % self.inmap,
00642                    'rules=-']
00643         else:
00644             cmd = ['v.colors.out',
00645                    'read=True',
00646                    'map=%s' % self.inmap,
00647                    'rules=-']
00648             
00649             if self.properties['sourceColumn'] and self.properties['sourceColumn'] != 'cat':
00650                 cmd.append('column=%s' % self.properties['sourceColumn'])
00651             
00652         cmd = utils.CmdToTuple(cmd)
00653         
00654         if self.inmap:
00655             ctable = RunCommand(cmd[0], **cmd[1])
00656         else:
00657             self.OnPreview()
00658             return
00659         
00660         self.ReadColorTable(ctable = ctable)     
00661     
00662     def CreateColorTable(self, tmp = False):
00663         """!Creates color table
00664 
00665         @return True on success
00666         @return False on failure
00667         """
00668         rulestxt = ''
00669         
00670         for rule in self.rulesPanel.ruleslines.itervalues():
00671             if 'value' not in rule: # skip empty rules
00672                 continue
00673             
00674             if rule['value'] not in ('nv', 'default') and \
00675                     rule['value'][-1] != '%' and \
00676                     not self._IsNumber(rule['value']):
00677                 GError(_("Invalid rule value '%s'. Unable to apply color table.") % rule['value'],
00678                        parent = self)
00679                 return False
00680             
00681             rulestxt += rule['value'] + ' ' + rule['color'] + '\n'
00682            
00683         if not rulestxt:
00684             return False
00685         
00686         gtemp = utils.GetTempfile()
00687         output = open(gtemp, "w")
00688         try:
00689             output.write(rulestxt)
00690         finally:
00691             output.close()
00692         
00693         cmd = ['%s.colors' % self.mapType[0], #r.colors/v.colors
00694                 'map=%s' % self.inmap,
00695                 'rules=%s' % gtemp]
00696         if self.mapType == 'vector' and self.properties['sourceColumn'] \
00697                 and self.properties['sourceColumn'] != 'cat':
00698             cmd.append('column=%s' % self.properties['sourceColumn'])
00699         cmd = utils.CmdToTuple(cmd)
00700         ret = RunCommand(cmd[0], **cmd[1])               
00701         if ret != 0:
00702             return False
00703         
00704         return True
00705     
00706     def DoPreview(self, ltype, cmdlist):
00707         """!Update preview (based on computational region)"""
00708         
00709         if not self.layer:
00710             self.layer = self.Map.AddLayer(type = ltype, name = 'preview', command = cmdlist,
00711                                            l_active = True, l_hidden = False, l_opacity = 1.0,
00712                                            l_render = False) 
00713         else:
00714             self.layer.SetCmd(cmdlist)
00715         
00716         # apply new color table and display preview
00717         self.CreateColorTable(tmp = True)
00718         self.preview.UpdatePreview()
00719         
00720     def RunHelp(self, cmd):
00721         """!Show GRASS manual page"""
00722         RunCommand('g.manual',
00723                    quiet = True,
00724                    parent = self,
00725                    entry = cmd)
00726         
00727     def _IsNumber(self, s):
00728         """!Check if 's' is a number"""
00729         try:
00730             float(s)
00731             return True
00732         except ValueError:
00733             return False
00734         
00735 
00736 class RasterColorTable(ColorTable):
00737     def __init__(self, parent, **kwargs):
00738         """!Dialog for interactively entering color rules for raster maps"""
00739 
00740         self.mapType = 'raster'
00741         self.attributeType = 'color' 
00742         self.colorTable = True 
00743         # raster properties
00744         self.properties = {
00745             # min cat in raster map
00746             'min' : None,
00747             # max cat in raster map
00748             'max' : None,
00749             }        
00750         
00751         ColorTable.__init__(self, parent,
00752                             title = _('Create new color table for raster map'), **kwargs)
00753         
00754         self._initLayer()
00755         
00756         # self.SetMinSize(self.GetSize()) 
00757         self.SetMinSize((650, 700))
00758         
00759         self.CentreOnScreen()
00760         self.Show()
00761     
00762     def _doLayout(self):
00763         """!Do main layout"""
00764         sizer = wx.BoxSizer(wx.VERTICAL)
00765         #
00766         # map selection
00767         #
00768         mapSelection = self._createMapSelection(parent = self.panel)
00769         sizer.Add(item = mapSelection, proportion = 0,
00770                   flag = wx.ALL | wx.EXPAND, border = 5)
00771         #
00772         # manage extern tables
00773         #
00774         fileSelection = self._createFileSelection(parent = self.panel)
00775         sizer.Add(item = fileSelection, proportion = 0,
00776                   flag = wx.ALL | wx.EXPAND, border = 5)
00777         #
00778         # body & preview
00779         #
00780         bodySizer = self._createBody(parent = self.panel)
00781         sizer.Add(item = bodySizer, proportion = 1,
00782                   flag = wx.ALL | wx.EXPAND, border = 5)
00783         #
00784         # buttons
00785         #
00786         btnSizer = self._createButtons(parent = self.panel)
00787         sizer.Add(item = wx.StaticLine(parent = self.panel, id = wx.ID_ANY,
00788                                        style = wx.LI_HORIZONTAL), proportion = 0,
00789                                        flag = wx.EXPAND | wx.ALL, border = 5) 
00790         
00791         sizer.Add(item = btnSizer, proportion = 0,
00792                   flag = wx.ALL | wx.ALIGN_RIGHT, border = 5)
00793         
00794         self.panel.SetSizer(sizer)
00795         sizer.Layout()
00796         sizer.Fit(self.panel)
00797         self.Layout()
00798     
00799     def OnSelectionInput(self, event):
00800         """!Raster map selected"""
00801         if event:
00802             self.inmap = event.GetString()
00803     
00804         self.loadRules.SetValue('')
00805         self.saveRules.SetValue('')
00806         if self.inmap:
00807             if not grass.find_file(name = self.inmap, element = 'cell')['file']:
00808                 self.inmap = None
00809         
00810         if not self.inmap:
00811             self.btnPreview.Enable(False)
00812             self.btnOK.Enable(False)
00813             self.btnApply.Enable(False)
00814             self.LoadTable()
00815             return
00816         
00817         info = grass.raster_info(map = self.inmap)
00818         
00819         if info:
00820             self.properties['min'] = info['min']
00821             self.properties['max'] = info['max']
00822             self.LoadTable()
00823         else:
00824             self.inmap = ''
00825             self.properties['min'] = self.properties['max'] = None
00826             self.btnPreview.Enable(False)
00827             self.btnOK.Enable(False)
00828             self.btnApply.Enable(False)
00829             self.preview.EraseMap()
00830             self.cr_label.SetLabel(_('Enter raster category values or percents'))
00831             return
00832         
00833         if info['datatype'] == 'CELL':
00834             mapRange = _('range')
00835         else:
00836             mapRange = _('fp range')
00837         self.cr_label.SetLabel(_('Enter raster category values or percents (%(range)s = %(min)d-%(max)d)') %
00838                                  { 'range' : mapRange,
00839                                    'min' : self.properties['min'],
00840                                    'max' : self.properties['max'] })                       
00841             
00842         self.btnPreview.Enable()
00843         self.btnOK.Enable()
00844         self.btnApply.Enable()
00845             
00846           
00847     def OnPreview(self, tmp = True):
00848         """!Update preview (based on computational region)"""
00849         if not self.inmap:
00850             self.preview.EraseMap()
00851             return
00852         
00853         cmdlist = ['d.rast',
00854                    'map=%s' % self.inmap]
00855         ltype = 'raster'
00856         
00857         # find existing color table and copy to temp file
00858         try:
00859             name, mapset = self.inmap.split('@')
00860         except ValueError:
00861             name = self.inmap
00862             mapset = grass.find_file(self.inmap, element = 'cell')['mapset']
00863             if not mapset:
00864                 return
00865         old_colrtable = None
00866         if mapset == grass.gisenv()['MAPSET']:
00867             old_colrtable = grass.find_file(name = name, element = 'colr')['file']
00868         else:
00869             old_colrtable = grass.find_file(name = name, element = 'colr2/' + mapset)['file']
00870         
00871         if old_colrtable:
00872             colrtemp = utils.GetTempfile()
00873             shutil.copyfile(old_colrtable, colrtemp)
00874             
00875         ColorTable.DoPreview(self, ltype, cmdlist)  
00876         
00877         # restore previous color table
00878         if tmp:
00879             if old_colrtable:
00880                 shutil.copyfile(colrtemp, old_colrtable)
00881                 os.remove(colrtemp)
00882             else:
00883                 RunCommand('r.colors',
00884                            parent = self,
00885                            flags = 'r',
00886                            map = self.inmap)
00887         
00888     def OnHelp(self, event):
00889         """!Show GRASS manual page"""
00890         cmd = 'r.colors'
00891         ColorTable.RunHelp(self, cmd = cmd)
00892                      
00893 class VectorColorTable(ColorTable):
00894     def __init__(self, parent, attributeType, **kwargs):
00895         """!Dialog for interactively entering color rules for vector maps"""
00896         # dialog attributes
00897         self.mapType = 'vector'
00898         self.attributeType = attributeType # color, size, width
00899         # in version 7 v.colors used, otherwise color column only
00900         self.version7 = int(grass.version()['version'].split('.')[0]) >= 7
00901         self.colorTable = False
00902         self.updateColumn = True
00903         # vector properties
00904         self.properties = {
00905             # vector layer for attribute table to use for setting color
00906             'layer' : 1, 
00907             # vector attribute table used for setting color         
00908             'table' : '',
00909             # vector attribute column for assigning colors
00910             'sourceColumn' : '', 
00911             # vector attribute column to use for loading colors
00912             'loadColumn' : '',
00913             # vector attribute column to use for storing colors
00914             'storeColumn' : '',    
00915             # vector attribute column for temporary storing colors   
00916             'tmpColumn' : 'tmp_0',
00917             # min value of attribute column/vector color table
00918             'min': None,
00919             # max value of attribute column/vector color table            
00920             'max': None
00921             }     
00922         self.columnsProp = {'color': {'name': 'GRASSRGB', 'type1': 'varchar(11)', 'type2': ['character']},
00923                             'size' : {'name': 'GRASSSIZE', 'type1': 'integer', 'type2': ['integer']},
00924                             'width': {'name': 'GRASSWIDTH', 'type1': 'integer', 'type2': ['integer']}}
00925         ColorTable.__init__(self, parent = parent,
00926                             title = _('Create new color rules for vector map'), **kwargs)
00927         
00928         # additional bindings for vector color management
00929         self.Bind(wx.EVT_COMBOBOX, self.OnLayerSelection, self.layerSelect)
00930         self.Bind(wx.EVT_COMBOBOX, self.OnSourceColumnSelection, self.sourceColumn)
00931         self.Bind(wx.EVT_COMBOBOX, self.OnFromColSelection, self.fromColumn)
00932         self.Bind(wx.EVT_COMBOBOX, self.OnToColSelection, self.toColumn)
00933         self.Bind(wx.EVT_BUTTON, self.OnAddColumn, self.addColumn)
00934         
00935         self._initLayer()
00936         if self.colorTable:
00937             self.cr_label.SetLabel(_("Enter vector attribute values or percents:"))
00938         else:
00939             self.cr_label.SetLabel(_("Enter vector attribute values:"))
00940         
00941         #self.SetMinSize(self.GetSize()) 
00942         self.SetMinSize((650, 700))
00943         
00944         self.CentreOnScreen()
00945         self.Show()
00946     
00947     def _createVectorAttrb(self, parent):
00948         """!Create part of dialog with layer/column selection"""
00949         inputBox = wx.StaticBox(parent = parent, id = wx.ID_ANY,
00950                                 label = " %s " % _("Select vector columns"))
00951         cb_vl_label = wx.StaticText(parent, id = wx.ID_ANY,
00952                                              label = _('Layer:'))
00953         cb_vc_label = wx.StaticText(parent, id = wx.ID_ANY,
00954                                          label = _('Attribute column:'))
00955                                         
00956         if self.attributeType == 'color':
00957             labels =  [_("Load color from column:"), _("Save color to column:")]
00958         elif self.attributeType == 'size':
00959             labels =  [_("Load size from column:"), _("Save size to column:")]
00960         elif self.attributeType == 'width':
00961             labels =  [_("Load width from column:"), _("Save width to column:")]
00962             
00963         if self.version7 and self.attributeType == 'color':
00964             self.useColumn = wx.CheckBox(parent, id = wx.ID_ANY,
00965                                   label = _("Use color column instead of color table:"))
00966             self.useColumn.Bind(wx.EVT_CHECKBOX, self.OnCheckColumn)
00967         
00968         fromColumnLabel = wx.StaticText(parent, id = wx.ID_ANY,
00969                                             label = labels[0])
00970         toColumnLabel = wx.StaticText(parent, id = wx.ID_ANY,
00971                                             label = labels[1])
00972                                                 
00973         self.rgb_range_label = wx.StaticText(parent, id = wx.ID_ANY)
00974         self.layerSelect = LayerSelect(parent)
00975         self.sourceColumn = ColumnSelect(parent)
00976         self.fromColumn = ColumnSelect(parent)
00977         self.toColumn = ColumnSelect(parent)
00978         self.addColumn = wx.Button(parent, id = wx.ID_ANY,
00979                                              label = _('Add column'))
00980         self.addColumn.SetToolTipString(_("Add GRASSRGB column to current attribute table."))
00981         
00982         # layout
00983         inputSizer = wx.StaticBoxSizer(inputBox, wx.VERTICAL)
00984         vSizer = wx.GridBagSizer(hgap = 5, vgap = 5)
00985         row = 0
00986         vSizer.Add(cb_vl_label, pos = (row, 0),
00987                    flag = wx.ALIGN_CENTER_VERTICAL)
00988         vSizer.Add(self.layerSelect,  pos = (row, 1),
00989                    flag = wx.ALIGN_CENTER_VERTICAL)
00990         row += 1
00991         vSizer.Add(cb_vc_label, pos = (row, 0),
00992                    flag = wx.ALIGN_CENTER_VERTICAL)
00993         vSizer.Add(self.sourceColumn, pos = (row, 1),
00994                    flag = wx.ALIGN_CENTER_VERTICAL)
00995         vSizer.Add(self.rgb_range_label, pos = (row, 2),
00996                    flag = wx.ALIGN_CENTER_VERTICAL)
00997         row += 1   
00998         if self.version7 and self.attributeType == 'color':
00999             vSizer.Add(self.useColumn, pos = (row, 0), span = (1, 2),
01000                        flag = wx.ALIGN_CENTER_VERTICAL)
01001             row += 1
01002             
01003         vSizer.Add(fromColumnLabel, pos = (row, 0),
01004                   flag = wx.ALIGN_CENTER_VERTICAL)
01005         vSizer.Add(self.fromColumn, pos = (row, 1),
01006                    flag = wx.ALIGN_CENTER_VERTICAL)
01007         row += 1
01008         vSizer.Add(toColumnLabel, pos = (row, 0),
01009                   flag = wx.ALIGN_CENTER_VERTICAL)
01010         vSizer.Add(self.toColumn, pos = (row, 1),
01011                    flag = wx.ALIGN_CENTER_VERTICAL)
01012         vSizer.Add(self.addColumn, pos = (row, 2),
01013                    flag = wx.ALIGN_CENTER_VERTICAL)
01014         inputSizer.Add(item = vSizer,
01015                        flag = wx.ALIGN_CENTER_VERTICAL | wx.ALL | wx.EXPAND, border = 5)
01016         self.colorColumnSizer = vSizer        
01017         return inputSizer 
01018        
01019     def _doLayout(self):
01020         """!Do main layout"""
01021         scrollPanel = scrolled.ScrolledPanel(parent = self.panel, id = wx.ID_ANY,
01022                                              style = wx.TAB_TRAVERSAL)
01023         scrollPanel.SetupScrolling()
01024         sizer = wx.BoxSizer(wx.VERTICAL)
01025         #
01026         # map selection
01027         #
01028         mapSelection = self._createMapSelection(parent = scrollPanel)
01029         sizer.Add(item = mapSelection, proportion = 0,
01030                   flag = wx.ALL | wx.EXPAND, border = 5)
01031         #
01032         # manage extern tables
01033         #
01034         if self.version7 and self.attributeType == 'color':
01035             self.cp = wx.CollapsiblePane(scrollPanel, label = _("Import or export color table"),
01036                                          winid = wx.ID_ANY,
01037                                         style = wx.CP_DEFAULT_STYLE|wx.CP_NO_TLW_RESIZE)
01038             self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.OnPaneChanged, self.cp)
01039         
01040             self._createFileSelection(parent = self.cp.GetPane())
01041             sizer.Add(item = self.cp, proportion = 0,
01042                       flag = wx.ALL | wx.EXPAND, border = 5)
01043         #
01044         # set vector attributes
01045         #
01046         vectorAttrb = self._createVectorAttrb(parent = scrollPanel)
01047         sizer.Add(item = vectorAttrb, proportion = 0,
01048                   flag = wx.ALL | wx.EXPAND, border = 5)
01049         #
01050         # body & preview
01051         #
01052         bodySizer = self._createBody(parent = scrollPanel)
01053         sizer.Add(item = bodySizer, proportion = 1,
01054                   flag = wx.ALL | wx.EXPAND, border = 5)
01055         
01056         scrollPanel.SetSizer(sizer)
01057         scrollPanel.Fit()        
01058         
01059         #
01060         # buttons
01061         #
01062         btnSizer = self._createButtons(self.panel)
01063         
01064         mainsizer = wx.BoxSizer(wx.VERTICAL)
01065         mainsizer.Add(scrollPanel, proportion = 1, flag = wx.EXPAND | wx.ALL, border = 5)
01066         mainsizer.Add(item = wx.StaticLine(parent = self.panel, id = wx.ID_ANY,
01067                                        style = wx.LI_HORIZONTAL), proportion = 0,
01068                                        flag = wx.EXPAND | wx.ALL, border = 5) 
01069         mainsizer.Add(item = btnSizer, proportion = 0,
01070                   flag = wx.ALL | wx.ALIGN_RIGHT | wx.EXPAND, border = 5)
01071         
01072         self.panel.SetSizer(mainsizer)
01073         mainsizer.Layout()
01074         mainsizer.Fit(self.panel)     
01075         self.Layout()
01076         
01077     def OnPaneChanged(self, event = None):
01078         # redo the layout
01079         self.Layout()
01080         # and also change the labels
01081         if self.cp.IsExpanded():
01082             self.cp.SetLabel('')
01083         else:
01084             self.cp.SetLabel(_("Import or export color table"))
01085         
01086     def CheckMapset(self):
01087         """!Check if current vector is in current mapset"""
01088         if grass.find_file(name = self.inmap,
01089                            element = 'vector')['mapset'] == grass.gisenv()['MAPSET']:
01090             return True
01091         else:
01092             return False 
01093          
01094     def NoConnection(self, vectorName):
01095         dlg = wx.MessageDialog(parent = self,
01096                                 message = _("Database connection for vector map <%s> "
01097                                             "is not defined in DB file.  Do you want to create and "
01098                                             "connect new attribute table?") % vectorName,
01099                                 caption = _("No database connection defined"),
01100                                 style = wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION | wx.CENTRE)
01101         if dlg.ShowModal() == wx.ID_YES:
01102             dlg.Destroy()
01103             GUI(parent = self).ParseCommand(['v.db.addtable', 'map=' + self.inmap], 
01104                                             completed = (self.CreateAttrTable, self.inmap, ''))
01105         else:
01106             dlg.Destroy()
01107   
01108     def OnCheckColumn(self, event):
01109         """!Use color column instead of color table"""
01110         if self.useColumn.GetValue():
01111             self.properties['loadColumn'] = self.fromColumn.GetStringSelection()
01112             self.properties['storeColumn'] = self.toColumn.GetStringSelection()
01113             self.fromColumn.Enable(True)
01114             self.toColumn.Enable(True)
01115             self.colorTable = False
01116             
01117             if self.properties['loadColumn']:
01118                 self.LoadTable()
01119             else:
01120                 self.rulesPanel.Clear()
01121         else:
01122             self.properties['loadColumn'] = ''
01123             self.properties['storeColumn'] = ''
01124             self.fromColumn.Enable(False)
01125             self.toColumn.Enable(False)
01126             self.colorTable = True
01127             self.LoadTable()
01128             
01129     def EnableVectorAttributes(self, enable):
01130         """!Enable/disable part of dialog connected with db"""
01131         for child in self.colorColumnSizer.GetChildren():
01132             child.GetWindow().Enable(enable)
01133     
01134     def DisableClearAll(self):
01135         """!Enable, disable the whole dialog"""
01136         self.rulesPanel.Clear()
01137         self.EnableVectorAttributes(False)
01138         self.btnPreview.Enable(False)
01139         self.btnOK.Enable(False)
01140         self.btnApply.Enable(False)
01141         self.preview.EraseMap()
01142         
01143     def OnSelectionInput(self, event):
01144         """!Vector map selected"""
01145         if event:
01146             if self.inmap:
01147                 # switch to another map -> delete temporary column
01148                 self.DeleteTemporaryColumn()
01149             self.inmap = event.GetString()
01150             
01151         if self.version7 and self.attributeType == 'color': 
01152             self.loadRules.SetValue('')
01153             self.saveRules.SetValue('')
01154         
01155         if self.inmap:
01156             if not grass.find_file(name = self.inmap, element = 'vector')['file']:
01157                 self.inmap = None
01158         
01159         self.UpdateDialog()
01160        
01161     def UpdateDialog(self):
01162         """!Update dialog after map selection"""  
01163         
01164         if not self.inmap:
01165             self.DisableClearAll()
01166             return
01167         
01168         if self.inmap and not self.CheckMapset():
01169             # currently v.colors need the map to be in current mapset
01170             if self.version7 and self.attributeType == 'color':
01171                 message = _("Selected map <%(map)s> is not in current mapset <%(mapset)s>. "
01172                             "Color rules cannot be edited.") % \
01173                             { 'map' : self.inmap,
01174                               'mapset' : grass.gisenv()['MAPSET'] }
01175             else:
01176                 message = _("Selected map <%(map)s> is not in current mapset <%(mapset)s>. "
01177                             "Attribute table cannot be edited.") % \
01178                             { 'map' : self.inmap,
01179                               'mapset' : grass.gisenv()['MAPSET'] }
01180             wx.CallAfter(GMessage, parent = self, message = message)
01181             self.DisableClearAll()
01182             return
01183               
01184         # check for db connection
01185         self.dbInfo = VectorDBInfo(self.inmap)
01186         enable = True
01187         if not len(self.dbInfo.layers): # no connection
01188             if not (self.version7 and self.attributeType == 'color'): # otherwise it doesn't matter
01189                 wx.CallAfter(self.NoConnection, self.inmap)
01190                 enable = False
01191             for combo in (self.layerSelect, self.sourceColumn, self.fromColumn, self.toColumn):
01192                 combo.SetValue("")
01193                 combo.Clear()
01194             for prop in ('sourceColumn', 'loadColumn', 'storeColumn'):
01195                 self.properties[prop] = ''
01196             self.EnableVectorAttributes(False)
01197         else: # db connection exist
01198         # initialize layer selection combobox
01199             self.EnableVectorAttributes(True)
01200             self.layerSelect.InsertLayers(self.inmap)
01201             # initialize attribute table for layer=1
01202             self.properties['layer'] = self.layerSelect.GetString(0)
01203             self.layerSelect.SetStringSelection(self.properties['layer'])
01204             layer = int(self.properties['layer'])
01205             self.properties['table'] = self.dbInfo.layers[layer]['table']
01206             
01207             if self.attributeType == 'color':
01208                 self.AddTemporaryColumn(type = 'varchar(11)')
01209             else:
01210                 self.AddTemporaryColumn(type = 'integer')
01211             
01212             # initialize column selection comboboxes 
01213             
01214             self.OnLayerSelection(event = None)
01215 
01216         if self.version7 and self.attributeType == 'color':
01217             self.useColumn.SetValue(False)
01218             self.OnCheckColumn(event = None) 
01219                     
01220         self.LoadTable()
01221             
01222         self.btnPreview.Enable(enable)
01223         self.btnOK.Enable(enable)
01224         self.btnApply.Enable(enable)   
01225     
01226     def AddTemporaryColumn(self, type):
01227         """!Add temporary column to not overwrite the original values,
01228         need to be deleted when closing dialog and unloading map
01229         
01230         @param type type of column (e.g. vachar(11))"""
01231         # because more than one dialog with the same map can be opened we must test column name and
01232         # create another one
01233         while self.properties['tmpColumn'] in self.dbInfo.GetTableDesc(self.properties['table']).keys():
01234             name, idx = self.properties['tmpColumn'].split('_')
01235             idx = int(idx)
01236             idx += 1
01237             self.properties['tmpColumn'] = name + '_' + str(idx)
01238         
01239         if self.version7:
01240             modul = 'v.db.addcolumn'
01241         else:
01242             modul = 'v.db.addcol'
01243         ret = RunCommand(modul,
01244                          parent = self,
01245                          map = self.inmap,
01246                          layer = self.properties['layer'],
01247                          column = '%s %s' % (self.properties['tmpColumn'], type))
01248         
01249     def DeleteTemporaryColumn(self):
01250         """!Delete temporary column"""
01251         if self.inmap:
01252             if self.version7:
01253                 modul = 'v.db.dropcolumn'
01254             else:
01255                 modul = 'v.db.dropcol'
01256             ret = RunCommand(modul,
01257                              map = self.inmap,
01258                              layer = self.properties['layer'],
01259                              column = self.properties['tmpColumn'])
01260         
01261     def OnLayerSelection(self, event):
01262         # reset choices in column selection comboboxes if layer changes
01263         vlayer = int(self.layerSelect.GetStringSelection())
01264         self.sourceColumn.InsertColumns(vector = self.inmap, layer = vlayer,
01265                                         type = ['integer', 'double precision'], dbInfo = self.dbInfo,
01266                                         excludeCols = ['tmpColumn'])
01267         self.sourceColumn.SetStringSelection('cat')
01268         self.properties['sourceColumn'] = self.sourceColumn.GetString(0)
01269         
01270         if self.attributeType == 'color':
01271             type = ['character']
01272         else:
01273             type = ['integer']
01274         self.fromColumn.InsertColumns(vector = self.inmap, layer = vlayer, type = type,
01275                                       dbInfo = self.dbInfo, excludeCols = ['tmpColumn'])
01276         self.toColumn.InsertColumns(vector = self.inmap, layer = vlayer, type = type,
01277                                     dbInfo = self.dbInfo, excludeCols = ['tmpColumn'])
01278         
01279         found = self.fromColumn.FindString(self.columnsProp[self.attributeType]['name'])
01280         if found != wx.NOT_FOUND:
01281             self.fromColumn.SetSelection(found)
01282             self.toColumn.SetSelection(found)
01283             self.properties['loadColumn'] = self.fromColumn.GetString(found)
01284             self.properties['storeColumn'] = self.toColumn.GetString(found)
01285         else:
01286             self.properties['loadColumn'] = ''
01287             self.properties['storeColumn'] = ''
01288         
01289         if event:
01290             self.LoadTable()
01291         self.Update()
01292         
01293     def OnSourceColumnSelection(self, event):
01294         self.properties['sourceColumn'] = event.GetString()
01295         
01296         self.LoadTable()
01297     
01298     def OnAddColumn(self, event):
01299         """!Add GRASS(RGB,SIZE,WIDTH) column if it doesn't exist"""
01300         if self.columnsProp[self.attributeType]['name'] not in self.fromColumn.GetItems():
01301             if self.version7:
01302                 modul = 'v.db.addcolumn'
01303             else:
01304                 modul = 'v.db.addcol'
01305             ret = RunCommand(modul,
01306                              map = self.inmap,
01307                              layer = self.properties['layer'],
01308                              columns = '%s %s' % (self.columnsProp[self.attributeType]['name'],
01309                                                   self.columnsProp[self.attributeType]['type1']))
01310             self.toColumn.InsertColumns(self.inmap, self.properties['layer'],
01311                                         type = self.columnsProp[self.attributeType]['type2'])
01312             self.toColumn.SetStringSelection(self.columnsProp[self.attributeType]['name'])
01313             self.properties['storeColumn'] = self.toColumn.GetStringSelection()
01314             
01315             self.LoadTable()
01316         else:
01317             GMessage(parent = self,
01318                      message = _("%s column already exists.") % \
01319                          self.columnsProp[self.attributeType]['name'])
01320                         
01321     def CreateAttrTable(self, dcmd, layer, params, propwin):
01322         """!Create attribute table"""
01323         if dcmd:
01324             cmd = utils.CmdToTuple(dcmd)
01325             ret = RunCommand(cmd[0], **cmd[1])
01326             if ret == 0:
01327                 self.OnSelectionInput(None)
01328                 return True
01329             
01330         for combo in (self.layerSelect, self.sourceColumn, self.fromColumn, self.toColumn):
01331             combo.SetValue("")
01332             combo.Disable()
01333         return False    
01334     
01335     def LoadTable(self):
01336         """!Load table"""
01337         if self.colorTable:
01338             ColorTable.LoadTable(self, mapType = 'vector')
01339         else:
01340             self.LoadRulesFromColumn()
01341             
01342     def LoadRulesFromColumn(self):
01343         """!Load current column (GRASSRGB, size column)"""
01344         
01345         self.rulesPanel.Clear()
01346         if not self.properties['sourceColumn']:
01347             self.preview.EraseMap()
01348             return
01349         
01350         busy = wx.BusyInfo(message = _("Please wait, loading data from attribute table..."),
01351                            parent = self)
01352         wx.Yield()
01353         
01354         columns = self.properties['sourceColumn']
01355         if self.properties['loadColumn']:
01356             columns += ',' + self.properties['loadColumn']
01357         
01358         sep = ';'            
01359         if self.inmap:
01360             outFile = tempfile.NamedTemporaryFile(mode = 'w+b')
01361             ret = RunCommand('v.db.select',
01362                              quiet = True,
01363                              flags = 'c',
01364                              map = self.inmap,
01365                              layer = self.properties['layer'],
01366                              columns = columns,
01367                              fs = sep,
01368                              stdout = outFile)
01369         else:
01370             self.preview.EraseMap()
01371             busy.Destroy()
01372             return
01373         
01374         outFile.seek(0)
01375         i = 0
01376         minim = maxim = 0.0
01377         limit = 1000
01378         
01379         colvallist = []
01380         readvals = False
01381         
01382         while True:
01383             # os.linesep doesn't work here (MSYS)
01384             record = outFile.readline().replace('\n', '')
01385             if not record:
01386                 break
01387             self.rulesPanel.ruleslines[i] = {}
01388             
01389             if not self.properties['loadColumn']:
01390                 col1 = record
01391                 col2 = None
01392             else:
01393                 col1, col2 = record.split(sep)
01394             
01395             if float(col1) < minim:
01396                 minim = float(col1)
01397             if float(col1) > maxim:
01398                 maxim = float(col1)
01399 
01400                 
01401             # color rules list should only have unique values of col1, not all records
01402             if col1 not in colvallist:                
01403                 self.rulesPanel.ruleslines[i]['value'] = col1
01404                 self.rulesPanel.ruleslines[i][self.attributeType] = col2
01405 
01406                 colvallist.append(col1)            
01407                 i += 1
01408             
01409             if i > limit and readvals == False:
01410                 dlg = wx.MessageDialog (parent = self, message = _(
01411                                         "Number of loaded records reached %d, "
01412                                         "displaying all the records will be time-consuming "
01413                                         "and may lead to computer freezing, "
01414                                         "do you still want to continue?") % i,
01415                                         caption = _("Too many records"),
01416                                         style  =  wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
01417                 if dlg.ShowModal() == wx.ID_YES:
01418                     readvals = True
01419                     dlg.Destroy()
01420                 else:
01421                     busy.Destroy()
01422                     dlg.Destroy()
01423                     self.updateColumn = False
01424                     return
01425             
01426         self.rulesPanel.AddRules(i, start = True)
01427         ret = self.rulesPanel.LoadRules()
01428         
01429         self.properties['min'], self.properties['max'] = minim, maxim
01430         self.SetRangeLabel()
01431         
01432         if ret:
01433             self.OnPreview()   
01434         else:
01435             self.rulesPanel.Clear()
01436     
01437         busy.Destroy()
01438         
01439     def SetRangeLabel(self):
01440         """!Set labels with info about attribute column range"""
01441         
01442         if self.properties['sourceColumn']:
01443             ctype = self.dbInfo.GetTableDesc(self.properties['table'])[self.properties['sourceColumn']]['ctype']
01444         else:
01445             ctype = int
01446         
01447         range = ''
01448         if self.properties['min'] or self.properties['max']:
01449             if ctype == float:
01450                 range = "%s: %.1f - %.1f)" % (_("range"),
01451                                               self.properties['min'], self.properties['max'])
01452             elif ctype == int:
01453                 range = "%s: %d - %d)" % (_("range"),
01454                                           self.properties['min'], self.properties['max'])
01455         if range:
01456             if self.colorTable:
01457                 self.cr_label.SetLabel(_("Enter vector attribute values or percents %s:") % range)
01458             else:
01459                 self.cr_label.SetLabel(_("Enter vector attribute values %s:") % range)
01460         else:
01461             if self.colorTable:
01462                 self.cr_label.SetLabel(_("Enter vector attribute values or percents:"))
01463             else:
01464                 self.cr_label.SetLabel(_("Enter vector attribute values:"))
01465                 
01466     def OnFromColSelection(self, event):
01467         """!Selection in combobox (for loading values) changed"""
01468         self.properties['loadColumn'] = event.GetString()
01469         
01470         self.LoadTable()
01471     
01472     def OnToColSelection(self, event):
01473         """!Selection in combobox (for storing values) changed"""
01474         self.properties['storeColumn'] = event.GetString()
01475     
01476     def OnPreview(self, event = None, tmp = True):
01477         """!Update preview (based on computational region)"""
01478         if self.colorTable:
01479             self.OnTablePreview(tmp)
01480         else:
01481             self.OnColumnPreview() 
01482                                  
01483     def OnTablePreview(self, tmp):
01484         """!Update preview (based on computational region)"""
01485         if not self.inmap:
01486             self.preview.EraseMap()
01487             return
01488         
01489         ltype = 'vector'
01490         cmdlist = ['d.vect',
01491                    'map=%s' % self.inmap]
01492         
01493         # find existing color table and copy to temp file
01494         old_colrtable = None
01495         path = grass.find_file(name = self.inmap, element = 'vector')['file']
01496         
01497         if os.path.exists(os.path.join(path, 'colr')):
01498             old_colrtable = os.path.join(path, 'colr')
01499             colrtemp = utils.GetTempfile()
01500             shutil.copyfile(old_colrtable, colrtemp)
01501             
01502         ColorTable.DoPreview(self, ltype, cmdlist)  
01503         
01504         # restore previous color table
01505         if tmp:
01506             if old_colrtable:
01507                 shutil.copyfile(colrtemp, old_colrtable)
01508                 os.remove(colrtemp)
01509             else:
01510                 RunCommand('v.colors',
01511                            parent = self,
01512                            flags = 'r',
01513                            map = self.inmap)
01514         
01515     def OnColumnPreview(self):
01516         """!Update preview (based on computational region)"""
01517         if not self.inmap or not self.properties['tmpColumn']:
01518             self.preview.EraseMap()
01519             return
01520         
01521         cmdlist = ['d.vect',
01522                    'map=%s' % self.inmap,
01523                    'type=point,line,boundary,area']
01524                 
01525         if self.attributeType == 'color':
01526             cmdlist.append('flags=a')
01527             cmdlist.append('rgb_column=%s' % self.properties['tmpColumn'])
01528         elif self.attributeType == 'size':
01529             cmdlist.append('size_column=%s' % self.properties['tmpColumn'])
01530         elif self.attributeType == 'width':
01531             cmdlist.append('width_column=%s' % self.properties['tmpColumn'])
01532             
01533         ltype = 'vector'
01534         
01535         ColorTable.DoPreview(self, ltype, cmdlist)
01536         
01537     def OnHelp(self, event):
01538         """!Show GRASS manual page"""
01539         cmd = 'v.colors'
01540         ColorTable.RunHelp(self, cmd = cmd)
01541         
01542     def UseAttrColumn(self, useAttrColumn):
01543         """!Find layers and apply the changes in d.vect command"""
01544         layers = self.parent.curr_page.maptree.FindItemByData(key = 'name', value = self.inmap)
01545         if not layers:
01546             return
01547         for layer in layers:
01548             if self.parent.curr_page.maptree.GetPyData(layer)[0]['type'] != 'vector':
01549                 continue
01550             cmdlist = self.parent.curr_page.maptree.GetPyData(layer)[0]['maplayer'].GetCmd()
01551             
01552             if self.attributeType == 'color':
01553                 if useAttrColumn:
01554                     cmdlist[1].update({'flags': 'a'})
01555                     cmdlist[1].update({'rgb_column': self.properties['storeColumn']})
01556                 else:
01557                     if 'flags' in cmdlist[1]:
01558                         cmdlist[1]['flags'] = cmdlist[1]['flags'].replace('a', '')
01559                     cmdlist[1].pop('rgb_column', None)
01560             elif self.attributeType == 'size':
01561                 cmdlist[1].update({'size_column': self.properties['storeColumn']})
01562             elif self.attributeType == 'width':
01563                 cmdlist[1].update({'width_column' :self.properties['storeColumn']})
01564             self.parent.curr_page.maptree.GetPyData(layer)[0]['cmd'] = cmdlist
01565         
01566     def CreateColorTable(self, tmp = False):
01567         """!Create color rules (color table or color column)"""
01568         if self.colorTable:
01569             ret = ColorTable.CreateColorTable(self)
01570         else:
01571             if self.updateColumn:
01572                 ret = self.UpdateColorColumn(tmp)
01573             else:
01574                 ret = True
01575         
01576         return ret
01577         
01578     def UpdateColorColumn(self, tmp):
01579         """!Creates color table
01580 
01581         @return True on success
01582         @return False on failure
01583         """
01584         rulestxt = ''
01585         
01586         for rule in self.rulesPanel.ruleslines.itervalues():
01587             if 'value' not in rule: # skip empty rules
01588                 break
01589             
01590             if tmp:
01591                 rgb_col = self.properties['tmpColumn']
01592             else:
01593                 rgb_col = self.properties['storeColumn']
01594                 if not self.properties['storeColumn']:
01595                     GMessage(parent = self.parent,
01596                              message = _("Please select column to save values to."))
01597             
01598             rulestxt += "UPDATE %s SET %s='%s' WHERE %s ;\n" % (self.properties['table'],
01599                                                                 rgb_col,
01600                                                                 rule[self.attributeType],
01601                                                                 rule['value'])
01602         if not rulestxt:
01603             return False
01604         
01605         gtemp = utils.GetTempfile()
01606         output = open(gtemp, "w")
01607         try:
01608             output.write(rulestxt)
01609         finally:
01610             output.close()
01611         
01612         RunCommand('db.execute',
01613                    parent = self,
01614                    input = gtemp)
01615         
01616         return True
01617     
01618     def OnCancel(self, event):
01619         """!Do not apply any changes and close the dialog"""
01620         self.DeleteTemporaryColumn()
01621         self.Map.Clean()
01622         self.Destroy()
01623 
01624     def OnApply(self, event):
01625         """!Apply selected color table
01626         
01627         @return True on success otherwise False
01628         """
01629         if self.colorTable:
01630             self.UseAttrColumn(False)
01631         else:
01632             if not self.properties['storeColumn']:
01633                 GError(_("No color column defined. Operation canceled."),
01634                        parent = self)
01635                 return
01636             
01637             self.UseAttrColumn(True)
01638         
01639         return ColorTable.OnApply(self, event)
01640            
01641 class BufferedWindow(wx.Window):
01642     """!A Buffered window class"""
01643     def __init__(self, parent, id,
01644                  style = wx.NO_FULL_REPAINT_ON_RESIZE,
01645                  Map = None, **kwargs):
01646         
01647         wx.Window.__init__(self, parent, id, style = style, **kwargs)
01648 
01649         self.parent = parent
01650         self.Map = Map
01651         
01652         # re-render the map from GRASS or just redraw image
01653         self.render = True
01654         # indicates whether or not a resize event has taken place
01655         self.resize = False 
01656 
01657         #
01658         # event bindings
01659         #
01660         self.Bind(wx.EVT_PAINT,        self.OnPaint)
01661         self.Bind(wx.EVT_IDLE,         self.OnIdle)
01662         self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: None)
01663 
01664         #
01665         # render output objects
01666         #
01667         # image file to be rendered
01668         self.mapfile = None 
01669         # wx.Image object (self.mapfile)
01670         self.img = None
01671 
01672         self.pdc = wx.PseudoDC()
01673         # will store an off screen empty bitmap for saving to file
01674         self._Buffer = None 
01675 
01676         # make sure that extents are updated at init
01677         self.Map.region = self.Map.GetRegion()
01678         self.Map.SetRegion()
01679 
01680     def Draw(self, pdc, img = None, pdctype = 'image'):
01681         """!Draws preview or clears window"""
01682         pdc.BeginDrawing()
01683 
01684         Debug.msg (3, "BufferedWindow.Draw(): pdctype=%s" % (pdctype))
01685 
01686         if pdctype == 'clear': # erase the display
01687             bg = wx.WHITE_BRUSH
01688             pdc.SetBackground(bg)
01689             pdc.Clear()
01690             self.Refresh()
01691             pdc.EndDrawing()
01692             return
01693 
01694         if pdctype == 'image' and img:
01695             bg = wx.TRANSPARENT_BRUSH
01696             pdc.SetBackground(bg)
01697             bitmap = wx.BitmapFromImage(img)
01698             w, h = bitmap.GetSize()
01699             pdc.DrawBitmap(bitmap, 0, 0, True) # draw the composite map
01700             
01701         pdc.EndDrawing()
01702         self.Refresh()
01703 
01704     def OnPaint(self, event):
01705         """!Draw pseudo DC to buffer"""
01706         self._Buffer = wx.EmptyBitmap(self.Map.width, self.Map.height)
01707         dc = wx.BufferedPaintDC(self, self._Buffer)
01708         
01709         # use PrepareDC to set position correctly
01710         self.PrepareDC(dc)
01711         
01712         # we need to clear the dc BEFORE calling PrepareDC
01713         bg = wx.Brush(self.GetBackgroundColour())
01714         dc.SetBackground(bg)
01715         dc.Clear()
01716         
01717         # create a clipping rect from our position and size
01718         # and the Update Region
01719         rgn = self.GetUpdateRegion()
01720         r = rgn.GetBox()
01721         
01722         # draw to the dc using the calculated clipping rect
01723         self.pdc.DrawToDCClipped(dc, r)
01724         
01725     def OnSize(self, event):
01726         """!Init image size to match window size"""
01727         # set size of the input image
01728         self.Map.width, self.Map.height = self.GetClientSize()
01729 
01730         # Make new off screen bitmap: this bitmap will always have the
01731         # current drawing in it, so it can be used to save the image to
01732         # a file, or whatever.
01733         self._Buffer = wx.EmptyBitmap(self.Map.width, self.Map.height)
01734 
01735         # get the image to be rendered
01736         self.img = self.GetImage()
01737 
01738         # update map display
01739         if self.img and self.Map.width + self.Map.height > 0: # scale image during resize
01740             self.img = self.img.Scale(self.Map.width, self.Map.height)
01741             self.render = False
01742             self.UpdatePreview()
01743 
01744         # re-render image on idle
01745         self.resize = True
01746 
01747     def OnIdle(self, event):
01748         """!Only re-render a preview image from GRASS during
01749         idle time instead of multiple times during resizing.
01750         """
01751         if self.resize:
01752             self.render = True
01753             self.UpdatePreview()
01754         event.Skip()
01755 
01756     def GetImage(self):
01757         """!Converts files to wx.Image"""
01758         if self.Map.mapfile and os.path.isfile(self.Map.mapfile) and \
01759                 os.path.getsize(self.Map.mapfile):
01760             img = wx.Image(self.Map.mapfile, wx.BITMAP_TYPE_ANY)
01761         else:
01762             img = None
01763         
01764         return img
01765     
01766     def UpdatePreview(self, img = None):
01767         """!Update canvas if window changes geometry"""
01768         Debug.msg (2, "BufferedWindow.UpdatePreview(%s): render=%s" % (img, self.render))
01769         oldfont = ""
01770         oldencoding = ""
01771         
01772         if self.render:
01773             # extent is taken from current map display
01774             try:
01775                 self.Map.region = copy.deepcopy(self.parent.parent.curr_page.maptree.Map.region)
01776             except AttributeError:
01777                 self.Map.region = self.Map.GetRegion()
01778             # render new map images
01779             self.mapfile = self.Map.Render(force = self.render)
01780             self.img = self.GetImage()
01781             self.resize = False
01782         
01783         if not self.img:
01784             return
01785         
01786         # paint images to PseudoDC
01787         self.pdc.Clear()
01788         self.pdc.RemoveAll()
01789         # draw map image background
01790         self.Draw(self.pdc, self.img, pdctype = 'image')
01791         
01792         self.resize = False
01793         
01794     def EraseMap(self):
01795         """!Erase preview"""
01796         self.Draw(self.pdc, pdctype = 'clear')