[GRASS-SVN] r47500 - in grass/trunk/gui/wxpython: . gui_modules

svn_grass at osgeo.org svn_grass at osgeo.org
Tue Aug 9 05:44:00 EDT 2011


Author: annakrat
Date: 2011-08-09 02:43:59 -0700 (Tue, 09 Aug 2011)
New Revision: 47500

Modified:
   grass/trunk/gui/wxpython/gui_modules/colorrules.py
   grass/trunk/gui/wxpython/gui_modules/nviz_mapdisp.py
   grass/trunk/gui/wxpython/gui_modules/nviz_tools.py
   grass/trunk/gui/wxpython/wxgui.py
Log:
wxNviz: ColorTable class changed to base class for deriving RasterColorTable, VectorColorTable, ThematicVectorTable

Modified: grass/trunk/gui/wxpython/gui_modules/colorrules.py
===================================================================
--- grass/trunk/gui/wxpython/gui_modules/colorrules.py	2011-08-08 23:08:41 UTC (rev 47499)
+++ grass/trunk/gui/wxpython/gui_modules/colorrules.py	2011-08-09 09:43:59 UTC (rev 47500)
@@ -5,7 +5,11 @@
 vector rgb_column attributes.
 
 Classes:
+ - RulesPanel
  - ColorTable
+ - RasterColorTable
+ - VectorColorTable
+ - ThematicVectorTable
  - BuferedWindow
 
 (C) 2008, 2010-2011 by the GRASS Development Team
@@ -14,12 +18,14 @@
 
 @author Michael Barton (Arizona State University)
 @author Martin Landa <landa.martin gmail.com> (various updates)
- at author Anna Kratochvilova (load/save raster color tables)
+ at author Anna Kratochvilova <kratochanna gmail.com> (split to base and derived classes)
 """
 
 import os
 import sys
 import shutil
+import copy
+import tempfile
 
 import wx
 import wx.lib.colourselect as csel
@@ -38,8 +44,241 @@
 from preferences import globalSettings as UserSettings
 from nviz_mapdisp import wxUpdateProperties
 
+
+class RulesPanel:
+    def __init__(self, parent, mapType, columnType, properties, panelWidth = 180):
+        """!Create rules panel
+        
+        @param mapType raster/vector
+        @param columnType color/size for choosing widget type
+        @param properties properties of classes derived from ColorTabel
+        @param panelWidth width of scroll panel"""
+        
+        self.ruleslines = {}
+        self.mapType = mapType
+        self.columnType = columnType
+        self.properties = properties
+        self.parent = parent
+        
+        self.mainPanel = scrolled.ScrolledPanel(parent, id = wx.ID_ANY,
+                                          size = (panelWidth, 300),
+                                          style = wx.TAB_TRAVERSAL | wx.SUNKEN_BORDER)
+        self.mainPanel.SetupScrolling(scroll_x = False)
+        self.mainSizer = wx.FlexGridSizer(cols = 3, vgap = 6, hgap = 4)
+        
+        self.mainPanel.SetSizer(self.mainSizer)
+        self.mainPanel.SetAutoLayout(True)
+        
+        if self.mapType == 'vector' and self.columnType == 'color':
+            self.label = wx.StaticText(parent, id = wx.ID_ANY, label =  _("Set color for attribute values:"))
+        elif self.mapType == 'vector' and self.columnType == 'size':
+            self.label = wx.StaticText(parent, id = wx.ID_ANY, label = _("Set size for attribute values:"))
+        self.numRules = wx.SpinCtrl(parent, id = wx.ID_ANY,
+                                    min = 1, max = 1e6)
+        self.btnAdd = wx.Button(parent, id = wx.ID_ADD)
+        
+        self.btnAdd.Bind(wx.EVT_BUTTON, self.OnAddRules)
+    
+    def Clear(self):
+        """!Clear and widgets and delete information"""
+        self.ruleslines.clear()
+        self.mainPanel.DestroyChildren()
+    
+    def OnAddRules(self, event):
+        """!Add rules button pressed"""
+        nrules = self.numRules.GetValue()
+        self.AddRules(nrules)
+        
+    def AddRules(self, nrules, start = False):
+        """!Add rules
+        
+        @param start set widgets (not append)"""
+        
+        snum = len(self.ruleslines.keys())
+        if start:
+            snum = 0
+        for num in range(snum, snum + nrules):
+            # enable
+            enable = wx.CheckBox(parent = self.mainPanel, id = num)
+            enable.SetValue(True)
+            enable.Bind(wx.EVT_CHECKBOX, self.OnRuleEnable)
+            # value
+            txt_ctrl = wx.TextCtrl(parent = self.mainPanel, id = 1000 + num,
+                                   size = (80, -1),
+                                   style = wx.TE_NOHIDESEL)
+            if self.mapType == 'vector':
+                txt_ctrl.SetToolTipString(_("Enter vector attribute values (e.g. 5) "
+                                            "or ranges (e.g. 5 to 10)"))
+            txt_ctrl.Bind(wx.EVT_TEXT, self.OnRuleValue)
+            if self.columnType == 'color':
+                # color
+                columnCtrl = csel.ColourSelect(self.mainPanel, id = 2000 + num,
+                                               size  =  globalvar.DIALOG_COLOR_SIZE)
+                columnCtrl.Bind(csel.EVT_COLOURSELECT, self.OnRuleColor)
+                if not start:
+                    self.ruleslines[enable.GetId()] = { 'value' : '',
+                                                        'color': "0:0:0" }
+            if self.columnType == 'size':
+                # size
+                columnCtrl = wx.SpinCtrl(self.mainPanel, id = 2000 + num,
+                                         size = (50, -1), min = 1, max = 1e3,
+                                         initial = 5)
+                columnCtrl.Bind(wx.EVT_SPINCTRL, self.OnRuleSize)
+                if not start:
+                    self.ruleslines[enable.GetId()] = { 'value' : '',
+                                                        'size': 5 }
+            
+            self.mainSizer.Add(item = enable, proportion = 0,
+                              flag = wx.ALIGN_CENTER_VERTICAL)
+            self.mainSizer.Add(item = txt_ctrl, proportion = 0,
+                              flag = wx.ALIGN_CENTER | wx.RIGHT, border = 5)
+            self.mainSizer.Add(item = columnCtrl, proportion = 0,
+                              flag = wx.ALIGN_CENTER | wx.RIGHT, border = 10)
+        
+        self.mainPanel.Layout()
+        self.mainPanel.SetupScrolling(scroll_x = False)
+       
+    
+    def OnRuleEnable(self, event):
+        """!Rule enabled/disabled"""
+        id = event.GetId()
+        
+        if event.IsChecked():
+            self.mainPanel.FindWindowById(id + 1000).Enable()
+            self.mainPanel.FindWindowById(id + 2000).Enable()
+            if self.mapType == 'vector':
+                value = self.SQLConvert(self.mainPanel.FindWindowById(id + 1000).GetValue(), self.columnType)
+            else:
+                value = self.mainPanel.FindWindowById(id + 1000).GetValue()
+            color = self.mainPanel.FindWindowById(id + 2000).GetValue()
+            
+            if self.columnType == 'color':
+            # color
+                color_str = str(color[0]) + ':' \
+                          + str(color[1]) + ':' \
+                          + str(color[2])
+                self.ruleslines[id] = {'value' : value,
+                                       'color' : color_str }
+                
+            else:
+            # size
+                self.ruleslines[id] = {'value' : value,
+                                       'size'  : float(color) }
+        
+        else:
+            self.mainPanel.FindWindowById(id + 1000).Disable()
+            self.mainPanel.FindWindowById(id + 2000).Disable()
+            del self.ruleslines[id]
+        
+    def OnRuleColor(self, event):
+        """!Rule color changed"""
+        num = event.GetId()
+        
+        rgba_color = event.GetValue()
+        
+        rgb_string = str(rgba_color[0]) + ':' \
+                   + str(rgba_color[1]) + ':' \
+                   + str(rgba_color[2])
+        
+        self.ruleslines[num-2000]['color'] = rgb_string
+     
+    def OnRuleSize(self, event):
+        """!Rule size changed"""
+        num = event.GetId()
+        size = event.GetInt()
+        
+        self.ruleslines[num - 2000]['size'] = size
+        
+    def OnRuleValue(self, event):
+        """!Rule value changed"""
+        num = event.GetId()
+        vals = event.GetString().strip()
+        
+        if vals == '':
+            return
+        
+        if self.mapType == 'vector':
+            self.SetVectorRule(num, vals)
+        else:
+            self.SetRasterRule(num, vals)
+
+    def SetRasterRule(self, num, vals): 
+        """!Set raster rule"""       
+        self.ruleslines[num-1000]['value'] = vals
+
+    def SetVectorRule(self, num, vals):
+        """!Set vector rule"""
+        tc = self.mainPanel.FindWindowById(num)
+        if self.properties['source_rgb'] == '' or self.properties['rgb'] == '':
+            tc.SetValue('')
+            gcmd.GMessage(parent = self,
+                          message = _("Please select attribute column "
+                                    "and RGB color column first"))
+        else:
+            try:
+                self.ruleslines[num-1000]['value'] = self.SQLConvert(vals, self.columnType)
+            except ValueError:
+                tc.SetValue('')
+                self.ruleslines[num-1000]['value'] = ''
+                
+                return
+            
+    def Enable(self, enable = True):
+        """!Enable/Disable all widgets"""
+        for child in self.mainPanel.GetChildren():
+            child.Enable(enable)
+        self.btnAdd.Enable(enable)
+        self.numRules.Enable(enable)
+        
+        
+    def LoadRules(self):
+        """!Fill rule widgets from ruleslines"""
+        message = ""
+        for item in range(len(self.ruleslines)):
+            self.mainPanel.FindWindowById(item + 1000).SetValue(self.ruleslines[item]['value'])
+            r, g, b = (255, 255, 255) # default
+            if self.ruleslines[item][self.columnType] == '':
+                if self.columnType == 'color':
+                    self.ruleslines[item][self.columnType] = (r, g, b)
+                elif self.columnType == 'size':
+                    self.ruleslines[item][self.columnType] = 100
+                
+            if self.columnType == 'color':
+                try:
+                    r, g, b = map(int, self.ruleslines[item][self.columnType].split(':'))
+                except ValueError, e:
+                    message =  _("Bad color format. Use color format '0:0:0'")
+                self.mainPanel.FindWindowById(item + 2000).SetValue((r, g, b))
+            else:
+                value = float(self.ruleslines[item][self.columnType])
+                self.mainPanel.FindWindowById(item + 2000).SetValue(value)
+                
+        if message:
+            gcmd.GMessage(parent = self.parent, message = message)
+            return False
+        
+        return True    
+    
+    def SQLConvert(self, vals, type = 'color'):
+        """!Prepare value for SQL query"""
+        if type == 'color':
+            source = 'source_rgb'
+        elif type == 'size':
+            source = 'source_size'
+        valslist = []
+        valslist = vals.split('to')
+        if len(valslist) == 1:
+            sqlrule = '%s=%s' % (self.properties[source], valslist[0])
+        elif len(valslist) > 1:
+            sqlrule = '%s>=%s AND %s<=%s' % (self.properties[source], valslist[0],
+                                             self.properties[source], valslist[1])
+        else:
+            return None
+        
+        return sqlrule  
+
 class ColorTable(wx.Frame):
-    def __init__(self, parent, raster, nviz = False, id = wx.ID_ANY, title = _("Set color table"),
+    def __init__(self, parent, title, id = wx.ID_ANY,
                  style = wx.DEFAULT_FRAME_STYLE | wx.RESIZE_BORDER,
                  **kwargs):
         """!Dialog for interactively entering rules for map management
@@ -49,57 +288,21 @@
         @param nviz True if ColorTable is called from nviz thematic mapping
         """
         self.parent = parent # GMFrame
-        self.raster = raster
-        self.nviz = nviz # called from nviz - thematic mapping
         
         wx.Frame.__init__(self, parent, id, title, style = style, **kwargs)
         
         self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
         
+        # instance of render.Map to be associated with display
+        self.Map  = render.Map() 
+        
         # input map to change
         self.inmap = ''
-
-        if self.raster:
-            # raster properties
-            self.properties = {
-                # min cat in raster map
-                'min' : None,
-                # max cat in raster map
-                'max' : None,
-                }
-        else:
-            # vector properties
-            self.properties = {
-                # list of database layers for vector (minimum of 1)
-                'layers' : [],
-                # list of database columns for vector
-                'columns' : [],
-                # vector layer for attribute table to use for setting color
-                'layer' : 1, 
-                # vector attribute table used for setting color         
-                'table' : '',
-                # vector attribute column for assigning colors
-                'column' : '', 
-                # vector attribute column to use for storing colors
-                'rgb' : '',
-                }
-
-        # rules for creating colortable
-        self.ruleslines = {}
-
-        # instance of render.Map to be associated with display
-        self.Map   = render.Map()
-
         # reference to layer with preview
         self.layer = None          
-        
-        # set title
-        if self.raster:
-            self.SetTitle(_('Create new color table for raster map'))
-        else:
-            self.SetTitle(_('Create new color table for vector map'))    
+           
         # layout
-        self.__doLayout()
+        self._doLayout()
         
         # bindings
         self.Bind(wx.EVT_BUTTON, self.OnHelp, self.btnHelp)
@@ -107,148 +310,34 @@
         self.Bind(wx.EVT_BUTTON, self.OnCancel, self.btnCancel)
         self.Bind(wx.EVT_BUTTON, self.OnApply, self.btnApply)
         self.Bind(wx.EVT_BUTTON, self.OnOK, self.btnOK)
+        self.Bind(wx.EVT_CLOSE,  self.OnCloseWindow)
+       
         self.Bind(wx.EVT_BUTTON, self.OnPreview, self.btnPreview)
-        self.Bind(wx.EVT_BUTTON, self.OnAddRules, self.btnAdd)
-        self.Bind(wx.EVT_CLOSE,  self.OnCloseWindow)
         
-        # additional bindings for raster/vector color management
-        if self.raster:
-            self.Bind(wx.EVT_BUTTON, self.OnSaveTable, self.btnSave)
-        else:
-            self.Bind(wx.EVT_COMBOBOX, self.OnLayerSelection, self.cb_vlayer)
-            self.Bind(wx.EVT_COMBOBOX, self.OnColumnSelection, self.cb_vcol)
-            self.Bind(wx.EVT_COMBOBOX, self.OnRGBColSelection, self.cb_vrgb)
-            self.Bind(wx.EVT_BUTTON, self.OnAddColumn, self.btn_addCol)
-        if not self.nviz:
-            # set map layer from layer tree, first selected,
-            # if not the right type, than select another
-            if self.raster:
-                elem = 'raster'
+    def _initLayer(self):
+        """!Set initial layer when opening dialog"""
+        # set map layer from layer tree, first selected,
+        # if not the right type, than select another
+        try:
+            sel = self.parent.curr_page.maptree.layer_selected
+            if sel and self.parent.curr_page.maptree.GetPyData(sel)[0]['type'] == self.type:
+                layer = sel
             else:
-                elem = 'vector'
-            try:
-                sel = self.parent.curr_page.maptree.layer_selected
-                if sel and self.parent.curr_page.maptree.GetPyData(sel)[0]['type'] == elem:
-                    layer = sel
-                else:
-                    layer = self.parent.curr_page.maptree.FindItemByData(key = 'type', value = elem)
-            except:
-                layer = None
-            if layer:
-                mapLayer = self.parent.curr_page.maptree.GetPyData(layer)[0]['maplayer']
-                name = mapLayer.GetName()
-                type = mapLayer.GetType()
-                self.selectionInput.SetValue(name)
-                self.inmap = name
-        else:
-            self.inmap = self.parent.GetLayerData(nvizType = 'vector', nameOnly = True)
-            self.OnSelectionInput(None)
-            self.nvizInfo.SetLabel(_("Set color rules for vector map <%s>:") % self.inmap)
+                layer = self.parent.curr_page.maptree.FindItemByData(key = 'type', value = self.type)
+        except:
+            layer = None
+        if layer:
+            mapLayer = self.parent.curr_page.maptree.GetPyData(layer)[0]['maplayer']
+            name = mapLayer.GetName()
+            type = mapLayer.GetType()
+            self.selectionInput.SetValue(name)
+            self.inmap = name
             
-        self.SetMinSize(self.GetSize())
-        
-        self.CentreOnScreen()
-        self.Show()
-    
-    def _createMapSelection(self):
-        """!Create map selection part of dialog"""
-        # top controls
-        if self.raster:
-            maplabel = _('Select raster map:')
-        else:
-            maplabel = _('Select vector map:')
-        inputBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
-                                label = " %s " % maplabel)
-        self.inputSizer = wx.StaticBoxSizer(inputBox, wx.VERTICAL)
-        if self.raster:
-            elem = 'cell'
-        else:
-            elem = 'vector'
-        self.selectionInput = gselect.Select(parent = self, id = wx.ID_ANY,
-                                             size = globalvar.DIALOG_GSELECT_SIZE,
-                                             type = elem)
-        if self.raster:
-            self.ovrwrtcheck = wx.CheckBox(parent = self, id = wx.ID_ANY,
-                                           label = _('replace existing color table'))
-            self.ovrwrtcheck.SetValue(UserSettings.Get(group = 'cmd', key = 'overwrite', subkey = 'enabled'))
-        
-        if self.raster:
-            self.btnSave = wx.Button(parent = self, id = wx.ID_SAVE)
-            self.btnSave.SetToolTipString(_('Save color table to file'))
-            
-        # layout
-        self.inputSizer.Add(item = self.selectionInput,
-                       flag = wx.ALIGN_CENTER_VERTICAL | wx.ALL | wx.EXPAND, border = 5)
-        replaceSizer = wx.BoxSizer(wx.HORIZONTAL)
-        if self.raster:
-            replaceSizer.Add(item = self.ovrwrtcheck, proportion = 1,
-                             flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL, border = 1)
-        
-            replaceSizer.Add(item = self.btnSave, proportion = 0,
-                            flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
-        
-        self.inputSizer.Add(item = replaceSizer, proportion = 1,
-                       flag = wx.ALL | wx.EXPAND, border = 0)
-    
-        return self.inputSizer
-    
-    def _createVectorAttrb(self):
-        """!Create part of dialog with layer/column selection"""
-        self.cb_vl_label = wx.StaticText(parent = self, id = wx.ID_ANY,
-                                             label = _('Layer:'))
-        self.cb_vc_label = wx.StaticText(parent = self, id = wx.ID_ANY,
-                                         label = _('Attribute column:'))
-        self.cb_vrgb_label = wx.StaticText(parent = self, id = wx.ID_ANY,
-                                           label = _('RGB color column:'))
-        self.cb_vlayer = gselect.LayerSelect(self)
-        self.cb_vcol = gselect.ColumnSelect(self)
-        self.cb_vrgb = gselect.ColumnSelect(self)
-        self.btn_addCol = wx.Button(parent = self, id = wx.ID_ANY,
-                                             label = _('Add column'))
-        self.btn_addCol.SetToolTipString(_("Add GRASSRGB column to current attribute table."))
-        
-        # layout
-        inputBox = wx.StaticBox(parent = self, id = wx.ID_ANY,
-                                label = " %s " % _("Select vector columns"))
-        inputSizer = wx.StaticBoxSizer(inputBox, wx.VERTICAL)
-        vSizer = wx.GridBagSizer(hgap = 5, vgap = 5)
-        vSizer.Add(self.cb_vl_label, pos = (0, 0),
-                   flag = wx.ALIGN_CENTER_VERTICAL)
-        vSizer.Add(self.cb_vlayer,  pos = (0, 1),
-                   flag = wx.ALIGN_CENTER_VERTICAL)
-        vSizer.Add(self.cb_vc_label, pos = (1, 0),
-                   flag = wx.ALIGN_CENTER_VERTICAL)
-        vSizer.Add(self.cb_vcol, pos = (1, 1),
-                   flag = wx.ALIGN_CENTER_VERTICAL)
-        vSizer.Add(self.cb_vrgb_label, pos = (2, 0),
-                  flag = wx.ALIGN_CENTER_VERTICAL)
-        vSizer.Add(self.cb_vrgb, pos = (2, 1),
-                   flag = wx.ALIGN_CENTER_VERTICAL)
-        vSizer.Add(self.btn_addCol, pos = (2, 2),
-                   flag = wx.ALIGN_CENTER_VERTICAL)
-        inputSizer.Add(item = vSizer,
-                       flag = wx.ALIGN_CENTER_VERTICAL | wx.ALL | wx.EXPAND, border = 5)
-                
-        return inputSizer
-    
-    def _createColorRulesPanel(self):
-        """!Create rules panel"""
-        cr_panel = scrolled.ScrolledPanel(parent = self, id = wx.ID_ANY,
-                                          size = (180, 300),
-                                          style = wx.TAB_TRAVERSAL | wx.SUNKEN_BORDER)
-        cr_panel.SetupScrolling(scroll_x = False)
-        self.cr_sizer = wx.GridBagSizer(vgap = 2, hgap = 4)
-        
-        cr_panel.SetSizer(self.cr_sizer)
-        cr_panel.SetAutoLayout(True)
-        
-        return cr_panel        
-
-    def _createPreview(self):
+    def _createPreview(self, parent):
         """!Create preview"""
         # initialize preview display
         self.InitDisplay()
-        self.preview = BufferedWindow(self, id = wx.ID_ANY, size = (400, 300),
+        self.preview = BufferedWindow(parent, id = wx.ID_ANY, size = (400, 300),
                                       Map = self.Map)
         self.preview.EraseMap()
         
@@ -262,7 +351,6 @@
         self.btnOK.SetDefault()
         self.btnOK.Enable(False)
         self.btnApply.Enable(False)
-
         
         # layout
         btnSizer = wx.BoxSizer(wx.HORIZONTAL)
@@ -277,82 +365,199 @@
         
         return btnSizer
     
-    def __doLayout(self):
-        """!Do main layout"""
-        sizer = wx.BoxSizer(wx.VERTICAL)
-        
-        # map selection
-        mapSelection = self._createMapSelection()
-        sizer.Add(item = mapSelection, proportion = 0,
-                  flag = wx.ALL | wx.EXPAND, border = 5)
-        if self.nviz:
-            sizerNviz = wx.BoxSizer(wx.HORIZONTAL)
-            self.nvizInfo = wx.StaticText(parent = self, id = wx.ID_ANY) # set later
-            sizerNviz.Add(self.nvizInfo, proportion = 0, flag = wx.LEFT | wx.EXPAND, border = 0)
-            sizer.Add(item = sizerNviz, proportion = 0,
-                  flag = wx.LEFT | wx.BOTTOM | wx.EXPAND, border = 5)
-            sizer.Hide(mapSelection)
-            # doesn't work
-            sizer.Layout()
-        
-        # set vector attributes
-        if not self.raster:
-            vectorAttrb = self._createVectorAttrb()
-            sizer.Add(item = vectorAttrb, proportion = 0,
-                      flag = wx.ALL | wx.EXPAND, border = 5)
-        
-        # body & preview
+    def _createBody(self, parent):
+        """!Create dialog body consisting of rules and preview"""
         bodySizer =  wx.GridBagSizer(hgap = 5, vgap = 5)
         row = 0
         
         # label with range
-        if self.raster:
-            crlabel = _('Enter raster category values or percents')
-        else:
-            crlabel = _('Enter vector attribute values or ranges')
-        self.cr_label = wx.StaticText(parent = self, id = wx.ID_ANY, label = crlabel)
+        self.cr_label = wx.StaticText(parent, id = wx.ID_ANY, label = '')
         bodySizer.Add(item = self.cr_label, pos = (row, 0), span = (1, 3),
                       flag = wx.ALL, border = 5)
         row += 1
         
         # color table
-        self.cr_panel = self._createColorRulesPanel()
-        bodySizer.Add(item = self.cr_panel, pos = (row, 0), span = (1, 2))
+        self.colorRulesPanel = RulesPanel(parent = parent, mapType = self.type,
+                                          columnType = 'color', properties = self.properties)
+        if self.type == 'vector':
+            bodySizer.Add(item = self.colorRulesPanel.label, pos = (row, 0), span = (1, 2))
+            row += 1
+        
+        bodySizer.Add(item = self.colorRulesPanel.mainPanel, pos = (row, 0), span = (1, 2))
+        
         # add two rules as default
-        self.AddRules(2)
+        self.colorRulesPanel.AddRules(2)
         
         # preview window
-        self._createPreview()
+        self._createPreview(parent = parent)
         bodySizer.Add(item = self.preview, pos = (row, 2),
                       flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border = 10)
         bodySizer.AddGrowableRow(row)
         bodySizer.AddGrowableCol(2)
         row += 1
         
-        # add rules
-        self.numRules = wx.SpinCtrl(parent = self, id = wx.ID_ANY,
-                                    min = 1, max = 1e6)
-        self.btnAdd = wx.Button(parent = self, id = wx.ID_ADD)
-        bodySizer.Add(item = self.numRules, pos = (row, 0),
+        # add rules button and spin to sizer
+        bodySizer.Add(item = self.colorRulesPanel.numRules, pos = (row, 0),
                       flag = wx.ALIGN_CENTER_VERTICAL)
-        bodySizer.Add(item = self.btnAdd, pos = (row, 1))
+        bodySizer.Add(item = self.colorRulesPanel.btnAdd, pos = (row, 1))
         
         # preview button
-        self.btnPreview = wx.Button(parent = self, id = wx.ID_ANY,
+        self.btnPreview = wx.Button(parent, id = wx.ID_ANY,
                                     label = _("Preview"))
         bodySizer.Add(item = self.btnPreview, pos = (row, 2),
                       flag = wx.ALIGN_RIGHT)
         self.btnPreview.Enable(False)
-        self.btnPreview.SetToolTipString(_("Show preview of vector map "
+        self.btnPreview.SetToolTipString(_("Show preview of map "
                                            "(current Map Display extent is used)."))
+                
+        return bodySizer
+    
         
+    def InitDisplay(self):
+        """!Initialize preview display, set dimensions and region
+        """
+        self.width = self.Map.width = 400
+        self.height = self.Map.height = 300
+        self.Map.geom = self.width, self.height
+
+    def OnCloseWindow(self, event):
+        """!Window closed
+        Also remove associated rendered images
+        """
+        self.Map.Clean()
+        self.Destroy()
+          
+    def OnApply(self, event):
+        """!Apply selected color table
+
+        @return True on success otherwise False
+        """
+        ret = self.CreateColorTable()
+        if ret:
+            display = self.parent.GetLayerTree().GetMapDisplay()
+            if display and display.IsAutoRendered():
+                display.GetWindow().UpdateMap(render = True)
+            
+        return ret
+
+    def OnOK(self, event):
+        """!Apply selected color table and close the dialog"""
+        if self.OnApply(event):
+            self.Destroy()
+    
+    def OnCancel(self, event):
+        """!Do not apply any changes and close the dialog"""
+        self.Destroy()
         
+    def DoPreview(self, ltype, cmdlist):
+        """!Update preview (based on computational region)"""
+        
+        if not self.layer:
+            self.layer = self.Map.AddLayer(type = ltype, name = 'preview', command = cmdlist,
+                                           l_active = True, l_hidden = False, l_opacity = 1.0,
+                                           l_render = False) 
+        else:
+            self.layer.SetCmd(cmdlist)
+        
+        # apply new color table and display preview
+        self.CreateColorTable(force = True)
+        self.preview.UpdatePreview()
+        
+    def RunHelp(self, cmd):
+        """!Show GRASS manual page"""
+        gcmd.RunCommand('g.manual',
+                        quiet = True,
+                        parent = self,
+                        entry = cmd)
+        
+    def _IsNumber(self, s):
+        """!Check if 's' is a number"""
+        try:
+            float(s)
+            return True
+        except ValueError:
+            return False
+        
+
+class RasterColorTable(ColorTable):
+    def __init__(self, parent, **kwargs):
+        """!Dialog for interactively entering color rules for raster maps"""
+
+        self.type = 'raster'
+        
+        # raster properties
+        self.properties = {
+            # min cat in raster map
+            'min' : None,
+            # max cat in raster map
+            'max' : None,
+            }        
+        
+        ColorTable.__init__(self, parent,
+                            title = _('Create new color table for raster map'), **kwargs)
+       
+       
+        # additional bindings for raster color management
+        self.Bind(wx.EVT_BUTTON, self.OnSaveTable, self.btnSave)
+        
+        self._initLayer()
+        
+        self.SetMinSize(self.GetSize()) 
+        self.CentreOnScreen()
+        self.Show()
+    
+    def _createMapSelection(self, parent):
+        """!Create map selection part of dialog"""
+        # top controls
+        maplabel = _('Select raster map:')
+        inputBox = wx.StaticBox(parent, id = wx.ID_ANY,
+                                label = " %s " % maplabel)
+        inputSizer = wx.StaticBoxSizer(inputBox, wx.VERTICAL)
+
+        self.selectionInput = gselect.Select(parent, id = wx.ID_ANY,
+                                             size = globalvar.DIALOG_GSELECT_SIZE,
+                                             type = 'cell')
+        self.ovrwrtcheck = wx.CheckBox(parent, id = wx.ID_ANY,
+                                       label = _('replace existing color table'))
+        self.ovrwrtcheck.SetValue(UserSettings.Get(group = 'cmd', key = 'overwrite', subkey = 'enabled'))
+        
+        self.btnSave = wx.Button(parent, id = wx.ID_SAVE)
+        self.btnSave.SetToolTipString(_('Save color table to file'))
+            
+        # layout
+        inputSizer.Add(item = self.selectionInput,
+                       flag = wx.ALIGN_CENTER_VERTICAL | wx.ALL | wx.EXPAND, border = 5)
+        replaceSizer = wx.BoxSizer(wx.HORIZONTAL)
+        replaceSizer.Add(item = self.ovrwrtcheck, proportion = 1,
+                         flag = wx.ALL | wx.ALIGN_CENTER_VERTICAL, border = 1)
+    
+        replaceSizer.Add(item = self.btnSave, proportion = 0,
+                        flag = wx.ALIGN_RIGHT | wx.ALL, border = 5)
+        
+        inputSizer.Add(item = replaceSizer, proportion = 1,
+                       flag = wx.ALL | wx.EXPAND, border = 0)
+    
+        return inputSizer
+    
+    def _doLayout(self):
+        """!Do main layout"""
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        #
+        # map selection
+        #
+        mapSelection = self._createMapSelection(parent = self)
+        sizer.Add(item = mapSelection, proportion = 0,
+                  flag = wx.ALL | wx.EXPAND, border = 5)
+        #
+        # body & preview
+        #
+        bodySizer = self._createBody(parent = self)
         sizer.Add(item = bodySizer, proportion = 1,
                   flag = wx.ALL | wx.EXPAND, border = 5)
-        
+        #
         # buttons
+        #
         btnSizer = self._createButtons()
-        
         sizer.Add(item = wx.StaticLine(parent = self, id = wx.ID_ANY,
                                        style = wx.LI_HORIZONTAL), proportion = 0,
                                        flag = wx.EXPAND | wx.ALL, border = 5) 
@@ -364,75 +569,14 @@
         sizer.Layout()
         sizer.Fit(self)
         self.Layout()
-        
-    def OnAddRules(self, event):
-        """!Add rules button pressed"""
-        nrules = self.numRules.GetValue()
-        self.AddRules(nrules)
-        
-    def AddRules(self, nrules):
-        """!Add rules"""
-        snum = len(self.ruleslines.keys())
-        for num in range(snum, snum + nrules):
-            # enable
-            enable = wx.CheckBox(parent = self.cr_panel, id = num)
-            enable.SetValue(True)
-            self.Bind(wx.EVT_CHECKBOX, self.OnRuleEnable, enable)
-            # value
-            txt_ctrl = wx.TextCtrl(parent = self.cr_panel, id = 1000 + num,
-                                   size = (90, -1),
-                                   style = wx.TE_NOHIDESEL)
-            if not self.raster:
-                txt_ctrl.SetToolTipString(_("Enter vector attribute values (e.g. 5) "
-                                            "or ranges (e.g. 5 to 10)"))
-            self.Bind(wx.EVT_TEXT, self.OnRuleValue, txt_ctrl)
-            # color
-            color_ctrl = csel.ColourSelect(self.cr_panel, id = 2000 + num,
-                                           size  =  globalvar.DIALOG_COLOR_SIZE)
-            self.Bind(csel.EVT_COLOURSELECT, self.OnRuleColor, color_ctrl)
-            self.ruleslines[enable.GetId()] = { 'value' : '',
-                                                'color': "0:0:0" }
-            
-            self.cr_sizer.Add(item = enable, pos = (num, 0),
-                              flag = wx.ALIGN_CENTER_VERTICAL)
-            self.cr_sizer.Add(item = txt_ctrl, pos = (num, 1),
-                              flag = wx.ALIGN_CENTER | wx.RIGHT, border = 5)
-            self.cr_sizer.Add(item = color_ctrl, pos = (num, 2),
-                              flag = wx.ALIGN_CENTER | wx.RIGHT, border = 10)
-        
-        self.cr_panel.Layout()
-        self.cr_panel.SetupScrolling(scroll_x = False)
-        
-    def InitDisplay(self):
-        """!Initialize preview display, set dimensions and region
-        """
-        self.width = self.Map.width = 400
-        self.height = self.Map.height = 300
-        self.Map.geom = self.width, self.height
-
-    def OnErase(self, event):
-        """!Erase the histogram display
-        """
-        self.PreviewWindow.Draw(self.HistWindow.pdc, pdctype = 'clear')
-
-    def OnCloseWindow(self, event):
-        """!Window closed
-        Also remove associated rendered images
-        """
-        self.Map.Clean()
-        self.Destroy()
-        
+    
     def OnSelectionInput(self, event):
-        """!Raster/vector map selected"""
+        """!Raster map selected"""
         if event:
             self.inmap = event.GetString()
 
         if self.inmap:
-            if self.raster:
-                mapType = 'cell'
-            else:
-                mapType = 'vector'
-            if not grass.find_file(name = self.inmap, element = mapType)['file']:
+            if not grass.find_file(name = self.inmap, element = 'cell')['file']:
                 self.inmap = None
         
         if not self.inmap:
@@ -442,74 +586,372 @@
             self.OnLoadTable(event)
             return
         
-        if self.raster:
-            info = grass.raster_info(map = self.inmap)
+        info = grass.raster_info(map = self.inmap)
+        
+        if info:
+            self.properties['min'] = info['min']
+            self.properties['max'] = info['max']
+            self.OnLoadTable(event)
+        else:
+            self.inmap = ''
+            self.properties['min'] = self.properties['max'] = None
+            self.btnPreview.Enable(False)
+            self.btnOK.Enable(False)
+            self.btnApply.Enable(False)
+            self.preview.EraseMap()
+            self.cr_label.SetLabel(_('Enter raster category values or percents'))
+            return
+        
+        if info['datatype'] == 'CELL':
+            mapRange = _('range')
+        else:
+            mapRange = _('fp range')
+        self.cr_label.SetLabel(_('Enter raster category values or percents (%(range)s = %(min)d-%(max)d)') %
+                                 { 'range' : mapRange,
+                                   'min' : self.properties['min'],
+                                   'max' : self.properties['max'] })                       
             
-            if info:
-                self.properties['min'] = info['min']
-                self.properties['max'] = info['max']
-                self.OnLoadTable(event)
-            else:
-                self.inmap = ''
-                self.properties['min'] = self.properties['max'] = None
-                self.btnPreview.Enable(False)
-                self.btnOK.Enable(False)
-                self.btnApply.Enable(False)
-                self.preview.EraseMap()
-                self.cr_label.SetLabel(_('Enter raster category values or percents'))
+        self.btnPreview.Enable()
+        self.btnOK.Enable()
+        self.btnApply.Enable()
+    
+    def OnLoadTable(self, event):
+        """!Load current color table (using `r.colors.out`)"""
+        
+        self.colorRulesPanel.Clear()
+        
+        if self.inmap:
+            ctable = gcmd.RunCommand('r.colors.out',
+                                     parent = self,
+                                     read = True,
+                                     map = self.inmap,
+                                     rules = '-')
+        else:
+            self.OnPreview(event)
+            return
+        
+        rulesNumber = len(ctable.splitlines())
+        self.colorRulesPanel.AddRules(rulesNumber)
+        
+        count = 0
+        for line in ctable.splitlines():
+            value, color = map(lambda x: x.strip(), line.split(' '))
+            self.colorRulesPanel.ruleslines[count]['value'] = value
+            self.colorRulesPanel.ruleslines[count]['color'] = color
+            self.colorRulesPanel.mainPanel.FindWindowById(count + 1000).SetValue(value)
+            rgb = list()
+            for c in color.split(':'):
+                rgb.append(int(c))
+            self.colorRulesPanel.mainPanel.FindWindowById(count + 2000).SetColour(rgb)
+            count += 1
+        
+        self.OnPreview(tmp = False)
+            
+          
+    def OnPreview(self, event = None, tmp = True):
+        """!Update preview (based on computational region)"""
+        if not self.inmap:
+            self.preview.EraseMap()
+            return
+        
+        cmdlist = ['d.rast',
+                   'map=%s' % self.inmap]
+        ltype = 'raster'
+        
+        # find existing color table and copy to temp file
+        try:
+            name, mapset = self.inmap.split('@')
+        except ValueError:
+            name = self.inmap
+            mapset = grass.find_file(self.inmap, element = 'cell')['mapset']
+            if not mapset:
                 return
+        old_colrtable = None
+        if mapset == grass.gisenv()['MAPSET']:
+            old_colrtable = grass.find_file(name = name, element = 'colr')['file']
+        else:
+            old_colrtable = grass.find_file(name = name, element = 'colr2/' + mapset)['file']
+        
+        if old_colrtable:
+            colrtemp = utils.GetTempfile()
+            shutil.copyfile(old_colrtable, colrtemp)
             
-            if info['datatype'] == 'CELL':
-                mapRange = _('range')
+        ColorTable.DoPreview(self, ltype, cmdlist)  
+        
+        # restore previous color table
+        if tmp:
+            if old_colrtable:
+                shutil.copyfile(colrtemp, old_colrtable)
+                os.remove(colrtemp)
             else:
-                mapRange = _('fp range')
-            self.cr_label.SetLabel(_('Enter raster category values or percents (%(range)s = %(min)d-%(max)d)') %
-                                     { 'range' : mapRange,
-                                       'min' : self.properties['min'],
-                                       'max' : self.properties['max'] })
-            enable = True                        
+                gcmd.RunCommand('r.colors',
+                                parent = self,
+                                flags = 'r',
+                                map = self.inmap)
+    
+    def OnSaveTable(self, event):
+        """!Save color table to file"""
+        rulestxt = ''   
+        for rule in self.ruleslines.itervalues():
+            if not rule['value']:
+                continue
+            rulestxt += rule['value'] + ' ' + rule['color'] + '\n'
+        if not rulestxt:
+            gcmd.GMessage(message = _("Nothing to save."),
+                          parent = self)
+            return
+        
+        dlg = wx.FileDialog(parent = self,
+                            message = _("Save color table to file"),
+                            defaultDir = os.getcwd(), style = wx.SAVE | wx.OVERWRITE_PROMPT)
+        if dlg.ShowModal() == wx.ID_OK:
+            path = dlg.GetPath()
+            fd = open(path, 'w')
+            fd.write(rulestxt)
+            fd.close()            
+        dlg.Destroy()
+        
+    def OnHelp(self, event):
+        """!Show GRASS manual page"""
+        cmd = 'r.colors'
+        ColorTable.RunHelp(self, cmd = cmd)
+    
+    def CreateColorTable(self, force = False):
+        """!Creates color table
+
+        @return True on success
+        @return False on failure
+        """
+        rulestxt = ''
+        
+        for rule in self.colorRulesPanel.ruleslines.itervalues():
+            if not rule['value']: # skip empty rules
+                continue
+            
+            if rule['value'] not in ('nv', 'default') and \
+                    rule['value'][-1] != '%' and \
+                    not self._IsNumber(rule['value']):
+                gcmd.GError(_("Invalid rule value '%s'. Unable to apply color table.") % rule['value'],
+                            parent = self)
+                return False
+            
+            rulestxt += rule['value'] + ' ' + rule['color'] + '\n'
+           
+        if not rulestxt:
+            gcmd.GMessage(parent = self, message = _("No color rules given."))
+            return False
+        
+        gtemp = utils.GetTempfile()
+        output = open(gtemp, "w")
+        try:
+            output.write(rulestxt)
+        finally:
+            output.close()
+        
+        if not force and not self.ovrwrtcheck.IsChecked():
+            flags = 'w'
         else:
-            # check for db connection
-            self.dbInfo = gselect.VectorDBInfo(self.inmap)
-            if not len(self.dbInfo.layers):
-                wx.CallAfter(self.NoConnection, self.inmap)
-                for combo in (self.cb_vlayer, self.cb_vcol, self.cb_vrgb):
-                    combo.SetValue("")
-                    combo.Disable()
-                    combo.Clear()
-                enable = False
-                
-            else:
-            # initialize layer selection combobox
-                for combo in (self.cb_vlayer, self.cb_vcol, self.cb_vrgb):
-                    combo.Enable()
-                self.cb_vlayer.InsertLayers(self.inmap)
-                # initialize attribute table for layer=1
-                self.properties['layer'] = self.cb_vlayer.GetString(0)
-                self.cb_vlayer.SetStringSelection(self.properties['layer'])
-                layer = int(self.properties['layer'])
-                self.properties['table'] = self.dbInfo.layers[layer]['table']
-                
-                # initialize column selection comboboxes 
-                self.OnLayerSelection(event = None)
-                
-                if self.CheckMapset():
-                    enable = True
-                    self.btn_addCol.Enable(True)
-                else:
-                    enable = False
-                    wx.CallAfter(gcmd.GMessage, parent = self, 
-                                 message = _("Selected map <%s> is not in current mapset <%s>. "
-                                            "Attribute table cannot be edited. "
-                                            "Color rules will not be saved.") % 
-                                            (self.inmap, grass.gisenv()['MAPSET']))
-                                  
-                    self.btn_addCol.Enable(False)
+            flags = ''
+        
+        ret = gcmd.RunCommand('r.colors',
+                              flags = flags,
+                              map = self.inmap,
+                              rules = gtemp)
+        if ret != 0:
+            gcmd.GMessage(_("Color table already exists. "
+                            "Check out 'replace existing color table' to "
+                            "overwrite it."),
+                          parent = self)
+            return False
+        
+        return True
+                      
+class VectorColorTable(ColorTable):
+    def __init__(self, parent, **kwargs):
+        """!Dialog for interactively entering color rules for vector maps"""
+        
+        self.type = 'vector'
+        # vector properties
+        self.properties = {
+            # list of database layers for vector (minimum of 1)
+            'layers' : [],
+            # list of database columns for vector
+            'columns' : [],
+            # vector layer for attribute table to use for setting color
+            'layer' : 1, 
+            # vector attribute table used for setting color         
+            'table' : '',
+            # vector attribute column for assigning colors
+            'source_rgb' : '', 
+            # vector attribute column to use for storing colors
+            'rgb' : '',       
+            # vector attribute column for assigning size
+            'source_size' : '', 
+            # vector attribute column to use for storing size
+            'size' : '',
+            }         
+        
+        ColorTable.__init__(self, parent = parent,
+                            title = _('Create new color table for vector map'), **kwargs)   
+        
+        # additional bindings for vector color management
+        self.Bind(wx.EVT_COMBOBOX, self.OnLayerSelection, self.cb_vlayer)
+        self.Bind(wx.EVT_COMBOBOX, self.OnColumnSelection, self.cb_color_att)
+        self.Bind(wx.EVT_COMBOBOX, self.OnRGBColSelection, self.cb_rgb_col)
+        self.Bind(wx.EVT_BUTTON, self.OnAddColumn, self.btn_add_RGB)    
+        
+        self._initLayer()
+        self.cr_label.SetLabel(_("Enter vector attribute values or ranges"))
+        self.SetMinSize(self.GetSize()) 
+        self.CentreOnScreen()
+        self.Show()
+
+    def _createMapSelection(self, parent):
+        """!Create map selection part of dialog"""
+        # top controls
+        maplabel = _('Select vector map:')
+        inputBox = wx.StaticBox(parent, id = wx.ID_ANY,
+                                label = " %s " % maplabel)
+        inputSizer = wx.StaticBoxSizer(inputBox, wx.VERTICAL)
+        
+        self.selectionInput = gselect.Select(parent, id = wx.ID_ANY,
+                                             size = globalvar.DIALOG_GSELECT_SIZE,
+                                             type = 'vector')
             
-        self.btnPreview.Enable(enable)
-        self.btnOK.Enable(enable)
-        self.btnApply.Enable(enable)
+        # layout
+        inputSizer.Add(item = self.selectionInput,
+                       flag = wx.ALIGN_CENTER_VERTICAL | wx.ALL | wx.EXPAND, border = 5)
+        replaceSizer = wx.BoxSizer(wx.HORIZONTAL)
+        
+        inputSizer.Add(item = replaceSizer, proportion = 1,
+                       flag = wx.ALL | wx.EXPAND, border = 0)
     
+        return inputSizer
+    
+    def _createVectorAttrb(self, parent):
+        """!Create part of dialog with layer/column selection"""
+        cb_vl_label = wx.StaticText(parent, id = wx.ID_ANY,
+                                             label = _('Layer:'))
+        cb_vc_label = wx.StaticText(parent, id = wx.ID_ANY,
+                                         label = _('Attribute column:'))
+        cb_vrgb_label = wx.StaticText(parent, id = wx.ID_ANY,
+                                           label = _('RGB color column:'))
+        self.rgb_range_label = wx.StaticText(parent, id = wx.ID_ANY)
+        self.cb_vlayer = gselect.LayerSelect(parent)
+        self.cb_color_att = gselect.ColumnSelect(parent)
+        self.cb_rgb_col = gselect.ColumnSelect(parent)
+        self.btn_add_RGB = wx.Button(parent, id = wx.ID_ANY,
+                                             label = _('Add column'))
+        self.btn_add_RGB.SetToolTipString(_("Add GRASSRGB column to current attribute table."))
+        
+        # layout
+        inputBox = wx.StaticBox(parent = parent, id = wx.ID_ANY,
+                                label = " %s " % _("Select vector columns"))
+        inputSizer = wx.StaticBoxSizer(inputBox, wx.VERTICAL)
+        vSizer = wx.GridBagSizer(hgap = 5, vgap = 5)
+        vSizer.Add(cb_vl_label, pos = (0, 0),
+                   flag = wx.ALIGN_CENTER_VERTICAL)
+        vSizer.Add(self.cb_vlayer,  pos = (0, 1),
+                   flag = wx.ALIGN_CENTER_VERTICAL)
+        vSizer.Add(cb_vc_label, pos = (1, 0),
+                   flag = wx.ALIGN_CENTER_VERTICAL)
+        vSizer.Add(self.cb_color_att, pos = (1, 1),
+                   flag = wx.ALIGN_CENTER_VERTICAL)
+        vSizer.Add(self.rgb_range_label, pos = (1, 2),
+                   flag = wx.ALIGN_CENTER_VERTICAL)
+        vSizer.Add(cb_vrgb_label, pos = (2, 0),
+                  flag = wx.ALIGN_CENTER_VERTICAL)
+        vSizer.Add(self.cb_rgb_col, pos = (2, 1),
+                   flag = wx.ALIGN_CENTER_VERTICAL)
+        vSizer.Add(self.btn_add_RGB, pos = (2, 2),
+                   flag = wx.ALIGN_CENTER_VERTICAL)
+        inputSizer.Add(item = vSizer,
+                       flag = wx.ALIGN_CENTER_VERTICAL | wx.ALL | wx.EXPAND, border = 5)
+                
+        return inputSizer 
+       
+    def _doLayout(self):
+        """!Do main layout"""
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        #
+        # map selection
+        #
+        mapSelection = self._createMapSelection(parent = self)
+        sizer.Add(item = mapSelection, proportion = 0,
+                  flag = wx.ALL | wx.EXPAND, border = 5)
+        #
+        # set vector attributes
+        #
+        vectorAttrb = self._createVectorAttrb(parent = self)
+        sizer.Add(item = vectorAttrb, proportion = 0,
+                  flag = wx.ALL | wx.EXPAND, border = 5)
+        #
+        # body & preview
+        #
+        bodySizer = self._createBody(parent = self)
+        sizer.Add(item = bodySizer, proportion = 1,
+                  flag = wx.ALL | wx.EXPAND, border = 5)
+        #
+        # buttons
+        #
+        btnSizer = self._createButtons()
+        
+        sizer.Add(item = wx.StaticLine(parent = self, id = wx.ID_ANY,
+                                       style = wx.LI_HORIZONTAL), proportion = 0,
+                                       flag = wx.EXPAND | wx.ALL, border = 5) 
+        
+        sizer.Add(item = btnSizer, proportion = 0,
+                  flag = wx.ALL | wx.ALIGN_RIGHT, border = 5)
+        
+        self.SetSizer(sizer)
+        sizer.Layout()
+        sizer.Fit(self)
+        self.Layout()
+                 
+    def SetInfoString(self):
+        """!Show information about vector map column type/range"""
+        driver, db = self.dbInfo.GetDbSettings(int(self.properties['layer']))
+        nrows = grass.db_describe(table = self.properties['table'], driver = driver, database = db)['nrows']
+        self.properties['min'] = self.properties['max'] = ''
+        type = self.dbInfo.GetTableDesc(self.properties['table'])\
+                                                    [self.properties['source_rgb']]['type']
+        ctype = self.dbInfo.GetTableDesc(self.properties['table'])\
+                                                    [self.properties['source_rgb']]['ctype']
+        if ctype == int or ctype == float:  
+            if nrows < 500: # not too large
+                ret = gcmd.RunCommand('v.db.select',
+                                      quiet = True,
+                                      read  = True,
+                                      flags = 'c',
+                                      map = self.inmap,
+                                      layer = self.properties['layer'],
+                                      columns = self.properties['source_rgb']).strip('\n')
+                records = ret.split('\n')
+                try:
+                    self.properties['min'] = min(map(float, records))
+                    self.properties['max'] = max(map(float, records))
+                except ValueError:
+                    self.properties['min'] = self.properties['max'] = ''
+                    
+        if self.properties['min'] or self.properties['max']:
+            if ctype == int:
+                self.cr_label.SetLabel(_("Enter vector attribute values or ranges (type: %s, range: %d - %d)")
+                            % (type, self.properties['min'], self.properties['max']))
+            elif ctype == float:
+                self.cr_label.SetLabel(_("Enter vector attribute values or ranges (type: %s, range: %.1f - %.1f )")
+                            % (type, self.properties['min'], self.properties['max']))
+        else:
+            self.cr_label.SetLabel(_("Enter vector attribute values or ranges (type: %s)") % type)
+               
+    
+    def CheckMapset(self):
+        """!Check if current vector is in current mapset"""
+        if grass.find_file(name = self.inmap,
+                           element = 'vector')['mapset'] == grass.gisenv()['MAPSET']:
+            return True
+        else:
+            return False 
+         
     def NoConnection(self, vectorName):
         dlg = wx.MessageDialog(parent = self,
                                 message = _("Database connection for vector map <%s> "
@@ -523,48 +965,104 @@
                                                       completed = (self.CreateTable, self.inmap, ''))
         else:
             dlg.Destroy()
-    
-    def CheckMapset(self):
-        """!Check if current layer is in current mapset"""
-        if self.raster:
-            element = 'cell'
+            
+    def OnSelectionInput(self, event):
+        """!Vector map selected"""
+        if event:
+            self.inmap = event.GetString()
+
+        if self.inmap:
+            if not grass.find_file(name = self.inmap, element = 'vector')['file']:
+                self.inmap = None
+        
+        self.UpdateDialog()
+        
+    def UpdateDialog(self):
+        """!Update dialog after map selection"""    
+        if not self.inmap:
+            self.colorRulesPanel.ruleslines.Clear()
+            self.btnPreview.Enable(False)
+            self.btnOK.Enable(False)
+            self.btnApply.Enable(False)
+            self.preview.EraseMap()
+            return
+                               
+        # check for db connection
+        self.dbInfo = gselect.VectorDBInfo(self.inmap)
+        if not len(self.dbInfo.layers):
+            wx.CallAfter(self.NoConnection, self.inmap)
+            for combo in (self.cb_vlayer, self.cb_color_att, self.cb_rgb_col):
+                combo.SetValue("")
+                combo.Disable()
+                combo.Clear()
+            enable = False   
         else:
-            element = 'vector'
-        if grass.find_file(name = self.inmap,
-                           element = element)['mapset'] == grass.gisenv()['MAPSET']:
-            return True
-        else:
-            return False
-    
+        # initialize layer selection combobox
+            for combo in (self.cb_vlayer, self.cb_color_att, self.cb_rgb_col):
+                combo.Enable()
+            self.cb_vlayer.InsertLayers(self.inmap)
+            # initialize attribute table for layer=1
+            self.properties['layer'] = self.cb_vlayer.GetString(0)
+            self.cb_vlayer.SetStringSelection(self.properties['layer'])
+            layer = int(self.properties['layer'])
+            self.properties['table'] = self.dbInfo.layers[layer]['table']
+            
+            # initialize column selection comboboxes 
+            self.OnLayerSelection(event = None)
+            
+            if self.CheckMapset():
+                enable = True
+                self.btn_add_RGB.Enable(True)
+            else:
+                enable = False
+                wx.CallAfter(gcmd.GMessage, parent = self, 
+                             message = _("Selected map <%s> is not in current mapset <%s>. "
+                                        "Attribute table cannot be edited. "
+                                        "Color rules will not be saved.") % 
+                                        (self.inmap, grass.gisenv()['MAPSET']))
+                              
+                self.btn_add_RGB.Enable(False)
+            
+        self.btnPreview.Enable(enable)
+        self.btnOK.Enable(enable)
+        self.btnApply.Enable(enable)   
+        
     def OnLayerSelection(self, event):
         # reset choices in column selection comboboxes if layer changes
         vlayer = int(self.cb_vlayer.GetStringSelection())
-        self.cb_vcol.InsertColumns(vector = self.inmap, layer = vlayer, dbInfo = self.dbInfo)
-        self.cb_vcol.SetSelection(0)
-        self.properties['column'] = self.cb_vcol.GetString(0)
-        self.cb_vrgb.InsertColumns(vector = self.inmap, layer = vlayer, type = ["character"], dbInfo = self.dbInfo)
-        found = self.cb_vrgb.FindString('GRASSRGB')
+        self.cb_color_att.InsertColumns(vector = self.inmap, layer = vlayer, dbInfo = self.dbInfo)
+        self.cb_color_att.SetSelection(0)
+        self.properties['source_rgb'] = self.cb_color_att.GetString(0)
+        self.cb_rgb_col.InsertColumns(vector = self.inmap, layer = vlayer, type = ["character"], dbInfo = self.dbInfo)
+        found = self.cb_rgb_col.FindString('GRASSRGB')
         if found != wx.NOT_FOUND:
-            self.cb_vrgb.SetSelection(found)
-            self.properties['rgb'] = self.cb_vrgb.GetString(found)
+            self.cb_rgb_col.SetSelection(found)
+            self.properties['rgb'] = self.cb_rgb_col.GetString(found)
         else:
             self.properties['rgb'] = ''
-        self.SetInfoString()
+##        self.SetInfoString()
+        
+        self.LoadTable(attColumn = self.properties['source_rgb'],
+                       rgbColumn = self.properties['rgb'], rulesPanel = self.colorRulesPanel)
         self.Update()
         
     def OnColumnSelection(self, event):
-        self.properties['column'] = event.GetString()
-        self.SetInfoString()
+        self.properties['source_rgb'] = event.GetString()
+##        self.SetInfoString()
+        
+        self.LoadTable(attColumn = self.properties['source_rgb'],
+                       rgbColumn = self.properties['rgb'], rulesPanel = self.colorRulesPanel)
     
     def OnAddColumn(self, event):
         """!Add GRASSRGB column if it doesn't exist"""
-        if 'GRASSRGB' not in self.cb_vrgb.GetItems():
+        if 'GRASSRGB' not in self.cb_rgb_col.GetItems():
             ret = gcmd.RunCommand('v.db.addcolumn',
                                    map = self.inmap,
                                   layer = self.properties['layer'],
                                   columns = 'GRASSRGB varchar(20)')
-            self.cb_vrgb.InsertColumns(self.inmap, self.properties['layer'], type = ["character"])
-            self.cb_vrgb.SetStringSelection('GRASSRGB')
+            self.cb_rgb_col.InsertColumns(self.inmap, self.properties['layer'], type = ["character"])
+            self.cb_rgb_col.SetStringSelection('GRASSRGB')
+            self.properties['rgb'] = self.cb_rgb_col.GetStringSelection()
         else:
             gcmd.GMessage(parent = self,
                           message = _("GRASSRGB column already exists."))
@@ -573,173 +1071,457 @@
         """!Create attribute table"""
         if dcmd:
             cmd = utils.CmdToTuple(dcmd)
-            gcmd.RunCommand(cmd[0], **cmd[1])
-            self.OnSelectionInput(None)
-        else:
-            for combo in (self.cb_vlayer, self.cb_vcol, self.cb_vrgb):
-                combo.SetValue("")
-                combo.Disable()
+            ret = gcmd.RunCommand(cmd[0], **cmd[1])
+            if ret == 0:
+                self.OnSelectionInput(None)
+                return True
             
-    def SetInfoString(self):
-        """!Show information about vector map column type/range"""
-        driver, db = self.dbInfo.GetDbSettings(int(self.properties['layer']))
-        nrows = grass.db_describe(table = self.properties['table'], driver = driver, database = db)['nrows']
-        self.properties['min'] = self.properties['max'] = ''
-        type = self.dbInfo.GetTableDesc(self.properties['table'])\
-                                                    [self.properties['column']]['type']
-        ctype = self.dbInfo.GetTableDesc(self.properties['table'])\
-                                                    [self.properties['column']]['ctype']
-        if ctype == int or ctype == float:  
-            if nrows < 500: # not too large
-                ret = gcmd.RunCommand('v.db.select',
+        for combo in (self.cb_vlayer, self.cb_color_att, self.cb_rgb_col):
+            combo.SetValue("")
+            combo.Disable()
+        return False    
+    
+    def LoadTable(self, attColumn, rgbColumn, rulesPanel, type = 'color'):
+        """!Load current column (GRASSRGB, size column)"""
+        
+        rulesPanel.Clear()
+        
+        if self.inmap:
+            outFile = tempfile.NamedTemporaryFile(mode='w+b')
+            sep = '|'
+            ret = gcmd.RunCommand('v.db.select',
                                       quiet = True,
-                                      read  = True,
                                       flags = 'c',
                                       map = self.inmap,
                                       layer = self.properties['layer'],
-                                      columns = self.properties['column']).strip('\n')
-                records = ret.split('\n')
-                try:
-                    self.properties['min'] = min(map(float, records))
-                    self.properties['max'] = max(map(float, records))
-                except ValueError:
-                    self.properties['min'] = self.properties['max'] = ''
-                    
-        if self.properties['min'] or self.properties['max']:
-            if ctype == int:
-                self.cr_label.SetLabel(_("Enter vector attribute values or ranges (type: %s, range: %d - %d)")
-                            % (type, self.properties['min'], self.properties['max']))
-            elif ctype == float:
-                self.cr_label.SetLabel(_("Enter vector attribute values or ranges (type: %s, range: %.1f - %.1f )")
-                            % (type, self.properties['min'], self.properties['max']))
+                                      columns = attColumn + ',' + rgbColumn,
+                                      fs = sep,
+                                      stdout = outFile)
+            
         else:
-            self.cr_label.SetLabel(_("Enter vector attribute values or ranges (type: %s)") % type)
+            self.OnPreview(event)
+            return
+        if type == 'color':
+            ctype = self.dbInfo.GetTableDesc(self.properties['table'])\
+                                            [self.properties['source_rgb']]['ctype']
+        elif type == 'size':
+            ctype = self.dbInfo.GetTableDesc(self.properties['table'])\
+                                            [self.properties['source_size']]['ctype']
+        outFile.seek(0)
+        i = 0
+        minim = maxim = 0.0
+        while True:
+            # os.linesep doesn't work here (MSYS)
+            record = outFile.readline().replace('\n', '')
+            
+            if not record:
+                break
+            rulesPanel.ruleslines[i] = {}
+            
+            value = record.split(sep)[0]
+            if ctype not in (int, float):
+                value = "'" + value + "'"
+            else:
+                if float(value) < minim:
+                    minim = float(value)
+                if float(value) > maxim:
+                    maxim = float(value)
+            rulesPanel.ruleslines[i]['value'] = value
+            rulesPanel.ruleslines[i][type] = record.split(sep)[1]
+            i += 1
         
+        rulesPanel.AddRules(i, start = True)
+        ret = rulesPanel.LoadRules()
+        self.SetRangeLabel(type, ctype, minim, maxim)
+        
+        if ret:
+            self.OnPreview()    
+        else:
+            rulesPanel.Clear()
+    
+    def SetRangeLabel(self, type, ctype, minim, maxim):
+        """!Set labels with info about attribute column range"""
+        if type == 'color':
+            if minim or maxim:
+                if ctype == int:
+                    self.rgb_range_label.SetLabel(_("range: %.1f to %.1f") % (minim, maxim))
+                elif ctype == float:
+                    self.rgb_range_label.SetLabel(_("range: %d to %d") % (minim, maxim))
+            else:
+                self.rgb_range_label.SetLabel('')
+        elif type == 'size':
+            if minim or maxim:
+                if ctype == int:
+                    self.size_range_label.SetLabel(_("range: %.1f to %.1f") % (minim, maxim))
+                elif ctype == float:
+                    self.size_range_label.SetLabel(_("range: %d to %d") % (minim, maxim))
+            else:
+                self.size_range_label.SetLabel('')
+                
+                
     def OnRGBColSelection(self, event):
         self.properties['rgb'] = event.GetString()
         
-    def OnRuleEnable(self, event):
-        """!Rule enabled/disabled"""
-        id = event.GetId()
-        
-        if event.IsChecked():
-            value = self.FindWindowById(id+1000).GetValue()
-            color = self.FindWindowById(id+2000).GetValue()
-            color_str = str(color[0]) + ':' \
-                + str(color[1]) + ':' + \
-                str(color[2])
+        self.LoadTable(attColumn = self.properties['source_rgb'],
+                       rgbColumn = self.properties['rgb'], rulesPanel = self.colorRulesPanel)
             
-            self.ruleslines[id] = {
-                'value' : value,
-                'color' : color_str }
-        else:
-            del self.ruleslines[id]
+    def OnPreview(self, event = None):
+        """!Update preview (based on computational region)"""
+        if not self.inmap:
+            self.preview.EraseMap()
+            return
         
-    def OnRuleValue(self, event):
-        """!Rule value changed"""
-        num = event.GetId()
-        vals = event.GetString().strip()
+        cmdlist = ['d.vect',
+                    '-a',
+                   'map=%s' % self.inmap,
+                   'rgb_column=%s' % self.properties["rgb"],
+                   'type=point,line,boundary,area']
+        ltype = 'vector'
         
-        if vals == '':
-            return
+        ColorTable.DoPreview(self, ltype, cmdlist)
+        
+    def OnHelp(self, event):
+        """!Show GRASS manual page"""
+        cmd = 'v.colors'
+        ColorTable.RunHelp(self, cmd = cmd)
+        
+    def CreateColorTable(self, force = False):
+        """!Creates color table
 
-        tc = self.FindWindowById(num)
+        @return True on success
+        @return False on failure
+        """
+        rulestxt = ''
         
-        if self.raster:
-            self.ruleslines[num-1000]['value'] = vals
+        for rule in self.colorRulesPanel.ruleslines.itervalues():
+            if not rule['value']: # skip empty rules
+                continue
+            
+            rulestxt += "UPDATE %s SET %s='%s' WHERE %s ;\n" % (self.properties['table'],
+                                                                self.properties['rgb'],
+                                                                rule['color'],
+                                                                rule['value'])
+        if not rulestxt:
+            return False
         
+        gtemp = utils.GetTempfile()
+        output = open(gtemp, "w")
+        try:
+            output.write(rulestxt)
+        finally:
+            output.close()
+        
+        gcmd.RunCommand('db.execute',
+                        parent = self,
+                        input = gtemp)
+        return True
+    
+##    def ColorFromString(self, rgb):
+##        """!Convert color string '255:255:255' to tuple"""
+##        try:
+##            r, g, b = rgb.split(':')
+##            return (r, g, b)
+##        
+##        except ValueError:
+##            return False
+        
+class ThematicVectorTable(VectorColorTable):
+    def __init__(self, parent, **kwargs):
+        """!Dialog for interactively entering color/size rules
+            for vector maps for thematic mapping in nviz"""
+        VectorColorTable.__init__(self, parent = parent, **kwargs)
+        
+        # additional bingings
+        self.Bind(wx.EVT_COMBOBOX, self.OnSizeSourceSelection, self.cb_size_att)
+        self.Bind(wx.EVT_COMBOBOX, self.OnSizeSelection, self.cb_size_col)
+        self.Bind(wx.EVT_BUTTON, self.OnAddSizeColumn, self.btn_add_size)
+        self.Bind(wx.EVT_CHECKBOX, self.OnColorChecked,  self.rgb_check)
+        self.Bind(wx.EVT_CHECKBOX, self.OnSizeChecked,  self.size_check)
+        
+        self.SetTitle(_("Thematic mapping for vector map in 3D view"))
+        
+    def UpdateDialog(self):
+        """!Update dialog according to selected map"""
+        VectorColorTable.UpdateDialog(self)
+                               
+        if not len(self.dbInfo.layers):
+            for combo in (self.cb_size_att, self.cb_size_col):
+                combo.SetValue("")
+                combo.Disable()
+                combo.Clear()
+            enable = False   
         else:
-            if self.properties['column'] == '' or self.properties['rgb'] == '':
-                tc.SetValue('')
-                gcmd.GMessage(parent = self,
-                              message = _("Please select attribute column "
-                                        "and RGB color column first"))
+        # initialize layer selection combobox
+            for combo in (self.cb_vlayer, self.cb_size_att, self.cb_size_col):
+                combo.Enable()
+            
+            if self.CheckMapset():
+                self.btn_add_size.Enable(True)
             else:
-                try:
-                    self.ruleslines[num-1000]['value'] = self.SQLConvert(vals)
-                except ValueError:
-                    tc.SetValue('')
-                    self.ruleslines[num-1000]['value'] = ''
-                    return
+                self.btn_add_RGB.Enable(False)
+                
+    def OnLayerSelection(self, event):
+        VectorColorTable.OnLayerSelection(self, event)
+        # reset choices in column selection comboboxes if layer changes
+        vlayer = int(self.cb_vlayer.GetStringSelection())
+        self.cb_size_att.InsertColumns(vector = self.inmap, layer = vlayer, dbInfo = self.dbInfo)
+        self.cb_size_att.SetSelection(0)
+        self.properties['source_size'] = self.cb_size_att.GetString(0)
+        self.cb_size_col.InsertColumns(vector = self.inmap, layer = vlayer,
+                                       type = ["integer", "double precision"], dbInfo = self.dbInfo)
+        for item in self.cb_size_col.GetItems():
+            if item.lower().find('size') >= 0:
+                self.cb_size_col.SetStringSelection(item)
+                self.properties['size'] = item
+            else:
+                self.properties['size'] = ''
+##        self.SetInfoString()
+        self.LoadTable(attColumn = self.properties['source_size'], rgbColumn = self.properties['size'],
+                       rulesPanel = self.sizeRulesPanel, type = 'size')
+        self.Update()  
         
-    def OnRuleColor(self, event):
-        """!Rule color changed"""
-        num = event.GetId()
+    def OnSizeSelection(self, event):
+        self.properties['size'] = event.GetString()
         
-        rgba_color = event.GetValue()
+        self.LoadTable(attColumn = self.properties['source_size'], rgbColumn = self.properties['size'],
+                       rulesPanel = self.sizeRulesPanel, type = 'size')
+    
+    def OnSizeSourceSelection(self, event):
+        self.properties['source_size'] = event.GetString()
         
-        rgb_string = str(rgba_color[0]) + ':' \
-            + str(rgba_color[1]) + ':' + \
-            str(rgba_color[2])
+##        self.SetInfoString()
         
-        self.ruleslines[num-2000]['color'] = rgb_string
+        self.LoadTable(attColumn = self.properties['source_size'], rgbColumn = self.properties['size'],
+                       rulesPanel = self.sizeRulesPanel, type = 'size') 
+                    
+    def _initLayer(self):
+        """!Set initial layer when opening dialog"""
+        self.inmap = self.parent.GetLayerData(nvizType = 'vector', nameOnly = True)
+        self.selectionInput.SetValue(self.inmap)
+        self.selectionInput.Disable()
         
-    def SQLConvert(self, vals):
-        valslist = []
-        valslist = vals.split('to')
-        if len(valslist) == 1:
-            sqlrule = '%s=%s' % (self.properties['column'], valslist[0])
-        elif len(valslist) > 1:
-            sqlrule = '%s>=%s AND %s<=%s' % (self.properties['column'], valslist[0],
-                                             self.properties['column'], valslist[1])
-        else:
-            return None
+    def _doLayout(self):
+        """!Do main layout"""
+        sizer = wx.BoxSizer(wx.VERTICAL)
+        #
+        # map selection
+        #
+        mapSelection = self._createMapSelection(parent = self)
+        sizer.Add(item = mapSelection, proportion = 0,
+                  flag = wx.ALL | wx.EXPAND, border = 5)
+        #
+        # set vector attributes
+        #
+        vectorAttrb = self._createVectorAttrb(parent = self)
+        sizer.Add(item = vectorAttrb, proportion = 0,
+                  flag = wx.ALL | wx.EXPAND, border = 5)
+        #
+        # body & preview
+        #
+        bodySizer = self._createBody(parent = self)
+        sizer.Add(item = bodySizer, proportion = 1,
+                  flag = wx.ALL | wx.EXPAND, border = 5)
+        #
+        # buttons
+        #
+        btnSizer = self._createButtons()
         
-        return sqlrule
+        sizer.Add(item = wx.StaticLine(parent = self, id = wx.ID_ANY,
+                                       style = wx.LI_HORIZONTAL), proportion = 0,
+                                       flag = wx.EXPAND | wx.ALL, border = 5) 
         
-    def OnLoadTable(self, event):
-        """!Load current color table (using `r.colors.out`)"""
-        self.ruleslines.clear()
-        self.cr_panel.DestroyChildren()
-        if self.inmap:
-            ctable = gcmd.RunCommand('r.colors.out',
-                                     parent = self,
-                                     read = True,
-                                     map = self.inmap,
-                                     rules = '-')
-        else:
-            self.OnPreview(event)
-            return
+        sizer.Add(item = btnSizer, proportion = 0,
+                  flag = wx.ALL | wx.ALIGN_RIGHT, border = 5)
         
-        rulesNumber = len(ctable.splitlines())
-        self.AddRules(rulesNumber)
+        self.SetSizer(sizer)
+        sizer.Layout()
+        sizer.Fit(self)
+        self.Layout()
+    
+    def _createBody(self, parent):
+        """!Create dialog body consisting of rules and preview"""
+        bodySizer =  wx.GridBagSizer(hgap = 5, vgap = 5)
+        row = 0
         
-        count = 0
-        for line in ctable.splitlines():
-            value, color = map(lambda x: x.strip(), line.split(' '))
-            self.ruleslines[count]['value'] = value
-            self.ruleslines[count]['color'] = color
-            self.FindWindowById(count + 1000).SetValue(value)
-            rgb = list()
-            for c in color.split(':'):
-                rgb.append(int(c))
-            self.FindWindowById(count + 2000).SetColour(rgb)
-            count += 1
+        # label with instructions - don't want it now
+        self.cr_label = wx.StaticText(parent, id = wx.ID_ANY)
+        self.cr_label.Hide()
+
+        # color table
+        self.colorRulesPanel = RulesPanel(parent = parent, mapType = self.type,
+                                          columnType = 'color', properties = self.properties,
+                                          panelWidth = 200)
+        # size table        
+        self.sizeRulesPanel = RulesPanel(parent = parent, mapType = self.type,
+                                         columnType = 'size', properties = self.properties,
+                                         panelWidth = 200)
+                                        
+        bodySizer.Add(item = self.colorRulesPanel.label, pos = (row, 0), span = (1, 2))
+        bodySizer.Add(item = self.sizeRulesPanel.label, pos = (row, 2), span = (1, 2))
+        row += 1
         
-        self.OnPreview(tmp = False)
+        bodySizer.Add(item = self.colorRulesPanel.mainPanel, pos = (row, 0), span = (1, 2))
+        # add two rules as default
+        self.colorRulesPanel.AddRules(2)
+        bodySizer.Add(item = self.sizeRulesPanel.mainPanel, pos = (row, 2), span = (1, 2))
+        # add two rules as default
+        self.sizeRulesPanel.AddRules(2)
         
-    def OnSaveTable(self, event):
-        """!Save color table to file"""
-        rulestxt = ''   
-        for rule in self.ruleslines.itervalues():
-            if not rule['value']:
-                continue
-            rulestxt += rule['value'] + ' ' + rule['color'] + '\n'
-        if not rulestxt:
-            gcmd.GMessage(message = _("Nothing to save."),
-                          parent = self)
+        # preview window
+        self._createPreview(parent = parent)
+        bodySizer.Add(item = self.preview, pos = (row, 4),
+                      flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border = 10)
+        bodySizer.AddGrowableRow(row)
+        bodySizer.AddGrowableCol(4)
+        row += 1
+        
+        # add rules button and spin to sizer
+        bodySizer.Add(item = self.colorRulesPanel.numRules, pos = (row, 0),
+                      flag = wx.ALIGN_CENTER_VERTICAL)
+        bodySizer.Add(item = self.colorRulesPanel.btnAdd, pos = (row, 1))
+        bodySizer.Add(item = self.sizeRulesPanel.numRules, pos = (row, 2),
+                      flag = wx.ALIGN_CENTER_VERTICAL)
+        bodySizer.Add(item = self.sizeRulesPanel.btnAdd, pos = (row, 3), 
+                        flag = wx.ALIGN_LEFT)
+
+        # preview button
+        self.btnPreview = wx.Button(parent, id = wx.ID_ANY,
+                                    label = _("Preview"))
+        bodySizer.Add(item = self.btnPreview, pos = (row, 4),
+                      flag = wx.ALIGN_RIGHT)
+        self.btnPreview.Enable(False)
+        self.btnPreview.SetToolTipString(_("Show preview of map "
+                                           "(current Map Display extent is used)."))
+                
+        return bodySizer
+    
+    def _createVectorAttrb(self, parent):
+        """!Create part of dialog with layer/column selection"""
+        layer_label = wx.StaticText(parent, id = wx.ID_ANY, label = _('Layer:'))
+        self.rgb_check = wx.CheckBox(parent, id = wx.ID_ANY, label = _('Use color for thematic mapping:'))
+        self.size_check = wx.CheckBox(parent, id = wx.ID_ANY, label = _('Use size for thematic mapping:'))
+        self.rgb_check.SetValue(True)
+        self.size_check.SetValue(True)
+                                            
+        color_att_label = wx.StaticText(parent, id = wx.ID_ANY,
+                                         label = _('Attribute column:'))
+        size_att_label = wx.StaticText(parent, id = wx.ID_ANY,
+                                         label = _('Attribute column:'))
+        rgb_col_label = wx.StaticText(parent, id = wx.ID_ANY,
+                                           label = _('RGB color column:'))
+        size_col_label = wx.StaticText(parent, id = wx.ID_ANY,
+                                           label = _('Size column:'))
+        self.rgb_range_label = wx.StaticText(parent, id = wx.ID_ANY)                                        
+        self.size_range_label = wx.StaticText(parent, id = wx.ID_ANY)
+        cb_size = (150, -1)
+        self.cb_vlayer = gselect.LayerSelect(parent, size = cb_size)
+        self.cb_color_att = gselect.ColumnSelect(parent, size = cb_size)
+        self.cb_size_att = gselect.ColumnSelect(parent, size = cb_size)
+        self.cb_rgb_col = gselect.ColumnSelect(parent, size = cb_size)
+        self.cb_size_col = gselect.ColumnSelect(parent, size = cb_size)
+        self.btn_add_RGB = wx.Button(parent, id = wx.ID_ANY,
+                                             label = _('Add column'))
+        self.btn_add_size = wx.Button(parent, id = wx.ID_ANY,
+                                             label = _('Add column'))
+        self.btn_add_RGB.SetToolTipString(_("Add GRASSRGB column to current attribute table."))
+        self.btn_add_size.SetToolTipString(_("Add size column to current attribute table."))
+        
+        # layout
+        inputBox = wx.StaticBox(parent = parent, id = wx.ID_ANY,
+                                label = " %s " % _("Select vector columns"))
+        inputSizer = wx.StaticBoxSizer(inputBox, wx.VERTICAL)
+        vSizer = wx.GridBagSizer(hgap = 5, vgap = 5)
+        vSizer.AddGrowableCol(2)
+        
+        vSizer.Add(layer_label, pos = (0, 0),
+                   flag = wx.ALIGN_CENTER_VERTICAL)
+        vSizer.Add(self.cb_vlayer,  pos = (0, 1),
+                   flag = wx.ALIGN_CENTER_VERTICAL)
+                
+        vSizer.Add(self.rgb_check,  pos = (1, 0), span = (1, 3),
+                   flag = wx.ALIGN_CENTER_VERTICAL)
+        vSizer.Add(self.size_check,  pos = (1, 3), span = (1, 3),
+                   flag = wx.ALIGN_CENTER_VERTICAL)
+                
+        vSizer.Add(color_att_label, pos = (2, 0),
+                   flag = wx.ALIGN_CENTER_VERTICAL)
+        vSizer.Add(size_att_label, pos = (2, 3),
+                   flag = wx.ALIGN_CENTER_VERTICAL)
+                
+        vSizer.Add(rgb_col_label, pos = (4, 0),
+                  flag = wx.ALIGN_CENTER_VERTICAL)
+        vSizer.Add(size_col_label, pos = (4, 3),
+                  flag = wx.ALIGN_CENTER_VERTICAL)
+                
+        vSizer.Add(self.cb_color_att, pos = (2, 1),
+                   flag = wx.ALIGN_CENTER_VERTICAL)
+        vSizer.Add(self.cb_size_att, pos = (2, 4),
+                   flag = wx.ALIGN_CENTER_VERTICAL)
+                
+        vSizer.Add(self.rgb_range_label, pos = (3, 1),
+                   flag = wx.ALIGN_CENTER_VERTICAL)
+        vSizer.Add(self.size_range_label, pos = (3, 4),
+                   flag = wx.ALIGN_CENTER_VERTICAL)
+                
+        vSizer.Add(self.cb_rgb_col, pos = (4, 1),
+                   flag = wx.ALIGN_CENTER_VERTICAL)
+        vSizer.Add(self.cb_size_col, pos = (4, 4),
+                   flag = wx.ALIGN_CENTER_VERTICAL)
+                
+        vSizer.Add(self.btn_add_RGB, pos = (4, 2),
+                  flag = wx.ALIGN_CENTER_VERTICAL)
+        vSizer.Add(self.btn_add_size, pos = (4, 5),
+                  flag = wx.ALIGN_CENTER_VERTICAL)
+        inputSizer.Add(item = vSizer,
+                       flag = wx.ALIGN_CENTER_VERTICAL | wx.ALL | wx.EXPAND, border = 5)
+                
+        return inputSizer 
+    
+    def OnAddSizeColumn(self, event):
+        """!Add size column if it doesn't exist"""
+        name = 'GRASSSIZE'
+        ret = gcmd.RunCommand('v.db.addcolumn',
+                               map = self.inmap,
+                              layer = self.properties['layer'],
+                              columns = '%s integer' % name)
+        self.cb_size_col.InsertColumns(self.inmap, self.properties['layer'], type = ["integer"])
+        self.cb_size_col.SetStringSelection(name)
+        self.properties['size'] = name
+        
+        self.LoadTable(attColumn = self.properties['source_size'], rgbColumn = self.properties['size'],
+                       rulesPanel = self.sizeRulesPanel, type = 'size')
+
+    def OnColorChecked(self, event):
+        """!Use color for thematic mapping"""
+        if self.rgb_check.IsChecked():
+            self.colorRulesPanel.Enable(True)
+        else:
+            self.colorRulesPanel.Enable(False)
+        
+    def OnSizeChecked(self, event):
+        """!Use size for thematic mapping"""
+        if self.size_check.IsChecked():
+            self.sizeRulesPanel.Enable(True)
+        else:
+            self.sizeRulesPanel.Enable(False)
+        
+    def OnPreview(self, event = None):
+        """!Update preview (based on computational region)"""
+        if not self.inmap:
+            self.preview.EraseMap()
             return
         
-        dlg = wx.FileDialog(parent = self,
-                            message = _("Save color table to file"),
-                            defaultDir = os.getcwd(), style = wx.SAVE | wx.OVERWRITE_PROMPT)
-        if dlg.ShowModal() == wx.ID_OK:
-            path = dlg.GetPath()
-            fd = open(path, 'w')
-            fd.write(rulestxt)
-            fd.close()            
-        dlg.Destroy()
-          
+        cmdlist = ['d.vect',
+                    '-a',
+                   'map=%s' % self.inmap,
+                   'type=point,line,boundary,area']
+        if self.size_check.IsChecked() and self.properties["size"]:
+            cmdlist.append('size_column=%s' % self.properties["size"])
+        if self.rgb_check.IsChecked() and self.properties["rgb"]:
+            cmdlist.append('rgb_column=%s' % self.properties["rgb"])
+        ltype = 'vector'
+        ColorTable.DoPreview(self, ltype, cmdlist)
+    
     def OnApply(self, event):
         """!Apply selected color table
 
@@ -747,137 +1529,45 @@
         """
         ret = self.CreateColorTable()
         if not ret:
-            gcmd.GMessage(parent = self, message = _("No color rules given."))
+            GMessage(parent = self, message = _("No color rules given."))
+        
+        data = self.parent.GetLayerData(nvizType = 'vector')
+        data['vector']['points']['thematic']['layer'] = int(self.properties['layer'])
+        if self.size_check.IsChecked() and self.properties['size']:
+            data['vector']['points']['thematic']['sizecolumn'] = self.properties['size']
+        else:
+            data['vector']['points']['thematic']['sizecolumn'] = None
             
-        if not self.nviz:
-            display = self.parent.GetLayerTree().GetMapDisplay()
-            if display and display.IsAutoRendered():
-                display.GetWindow().UpdateMap(render = True)
-        else:
-            data = self.parent.GetLayerData(nvizType = 'vector')
-            data['vector']['points']['thematic']['layer'] = int(self.properties['layer'])
+        if self.rgb_check.IsChecked() and self.properties['rgb']:
             data['vector']['points']['thematic']['rgbcolumn'] = self.properties['rgb']
-            data['vector']['points']['thematic']['update'] = None
-            
-            event = wxUpdateProperties(data = data)
-            wx.PostEvent(self.parent.mapWindow, event)
-            self.parent.mapWindow.Refresh(False)
-        return ret
-
-    def OnOK(self, event):
-        """!Apply selected color table and close the dialog"""
-        if self.OnApply(event):
-            self.Destroy()
-    
-    def OnCancel(self, event):
-        """!Do not apply any changes and close the dialog"""
-        self.Destroy()
-        
-    def OnPreview(self, event = None, tmp = True):
-        """!Update preview (based on computational region)"""
-        if not self.inmap:
-            self.preview.EraseMap()
-            return
-        
-        # raster
-        if self.raster:
-            cmdlist = ['d.rast',
-                       'map=%s' % self.inmap]
-            ltype = 'raster'
-            
-            # find existing color table and copy to temp file
-            try:
-                name, mapset = self.inmap.split('@')
-            except ValueError:
-                name = self.inmap
-                mapset = grass.find_file(self.inmap, element = 'cell')['mapset']
-                if not mapset:
-                    return
-            old_colrtable = None
-            if mapset == grass.gisenv()['MAPSET']:
-                old_colrtable = grass.find_file(name = name, element = 'colr')['file']
-            else:
-                old_colrtable = grass.find_file(name = name, element = 'colr2/' + mapset)['file']
-            
-            if old_colrtable:
-                colrtemp = utils.GetTempfile()
-                shutil.copyfile(old_colrtable, colrtemp)
-        # vector
         else:
-            cmdlist = ['d.vect',
-                        '-a',
-                       'map=%s' % self.inmap,
-                       'rgb_column=%s' % self.properties["rgb"],
-                       'type=point,line,boundary,area']
-            ltype = 'vector'
+            data['vector']['points']['thematic']['rgbcolumn'] = None
         
-        if not self.layer:
-            self.layer = self.Map.AddLayer(type = ltype, name = 'preview', command = cmdlist,
-                                           l_active = True, l_hidden = False, l_opacity = 1.0,
-                                           l_render = False) 
-        else:
-            self.layer.SetCmd(cmdlist)
+        data['vector']['points']['thematic']['update'] = None
         
-        # apply new color table and display preview
-        self.CreateColorTable(force = True)
-        self.preview.UpdatePreview()
+        event = wxUpdateProperties(data = data)
+        wx.PostEvent(self.parent.mapWindow, event)
+        self.parent.mapWindow.Refresh(False)
         
-        # restore previous color table
-        if self.raster and tmp:
-            if old_colrtable:
-                shutil.copyfile(colrtemp, old_colrtable)
-                os.remove(colrtemp)
-            else:
-                gcmd.RunCommand('r.colors',
-                                parent = self,
-                                flags = 'r',
-                                map = self.inmap)
-        
-    def OnHelp(self, event):
-        """!Show GRASS manual page"""
-        if self.raster:
-            cmd = 'r.colors'
-        else:
-            cmd = 'v.colors'
-        gcmd.RunCommand('g.manual',
-                        quiet = True,
-                        parent = self,
-                        entry = cmd)
-        
-    def _IsNumber(self, s):
-        """!Check if 's' is a number"""
-        try:
-            float(s)
-            return True
-        except ValueError:
-            return False
-        
+        return ret
+    
     def CreateColorTable(self, force = False):
         """!Creates color table
 
         @return True on success
         @return False on failure
         """
+        VectorColorTable.CreateColorTable(self)
         rulestxt = ''
         
-        for rule in self.ruleslines.itervalues():
+        for rule in self.sizeRulesPanel.ruleslines.itervalues():
             if not rule['value']: # skip empty rules
                 continue
             
-            if self.raster:
-                if rule['value'] not in ('nv', 'default') and \
-                        rule['value'][-1] != '%' and \
-                        not self._IsNumber(rule['value']):
-                    gcmd.GError(_("Invalid rule value '%s'. Unable to apply color table.") % rule['value'],
-                                parent = self)
-                    return False
-                
-                rulestxt += rule['value'] + ' ' + rule['color'] + '\n'
-            else:
-                rulestxt += "UPDATE %s SET %s='%s' WHERE %s ;\n" % (self.properties['table'],
-                                                                    self.properties['rgb'],
-                                                                    rule['color'],
-                                                                    rule['value'])
+            rulestxt += "UPDATE %s SET %s='%s' WHERE %s ;\n" % (self.properties['table'],
+                                                                self.properties['size'],
+                                                                rule['size'],
+                                                                rule['value'])
         if not rulestxt:
             return False
         
@@ -888,31 +1578,11 @@
         finally:
             output.close()
         
-        if self.raster:
-            if not force and \
-                    not self.ovrwrtcheck.IsChecked():
-                flags = 'w'
-            else:
-                flags = ''
-            
-            ret = gcmd.RunCommand('r.colors',
-                                  flags = flags,
-                                  map = self.inmap,
-                                  rules = gtemp)
-            if ret != 0:
-                gcmd.GMessage(_("Color table already exists. "
-                                "Check out 'replace existing color table' to "
-                                "overwrite it."),
-                              parent = self)
-                return False
+        gcmd.RunCommand('db.execute',
+                        parent = self,
+                        input = gtemp)
+        return True
         
-        else:
-            gcmd.RunCommand('db.execute',
-                            parent = self,
-                            input = gtemp)
-        
-        return True
-    
 class BufferedWindow(wx.Window):
     """!A Buffered window class"""
     def __init__(self, parent, id,
@@ -1047,7 +1717,7 @@
         if self.render:
             # extent is taken from current map display
             try:
-                self.Map.region = self.parent.parent.curr_page.maptree.Map.region
+                self.Map.region = copy.deepcopy(self.parent.parent.curr_page.maptree.Map.region)
             except AttributeError:
                 self.Map.region = self.Map.GetRegion()
             # render new map images

Modified: grass/trunk/gui/wxpython/gui_modules/nviz_mapdisp.py
===================================================================
--- grass/trunk/gui/wxpython/gui_modules/nviz_mapdisp.py	2011-08-08 23:08:41 UTC (rev 47499)
+++ grass/trunk/gui/wxpython/gui_modules/nviz_mapdisp.py	2011-08-09 09:43:59 UTC (rev 47500)
@@ -1895,7 +1895,7 @@
         cmd += subcmd
         
         # background
-        subcmd  = "bgcolor=%d:%d:%d " % (self.view['background']['color'])
+        subcmd  = "bgcolor=%d:%d:%d " % (self.view['background']['color'][:3])
         if self.view['background']['color'] != (255, 255, 255):
             cmd += subcmd
         cmd += "\\\n"

Modified: grass/trunk/gui/wxpython/gui_modules/nviz_tools.py
===================================================================
--- grass/trunk/gui/wxpython/gui_modules/nviz_tools.py	2011-08-08 23:08:41 UTC (rev 47499)
+++ grass/trunk/gui/wxpython/gui_modules/nviz_tools.py	2011-08-09 09:43:59 UTC (rev 47500)
@@ -3207,7 +3207,7 @@
             
     def OnSetThematic(self, event):
         """!Set options for thematic points"""
-        ctable = colorrules.ColorTable(self, raster = False, nviz = True)
+        ctable = colorrules.ThematicVectorTable(self)
         ctable.CentreOnScreen()
         ctable.Show()
         

Modified: grass/trunk/gui/wxpython/wxgui.py
===================================================================
--- grass/trunk/gui/wxpython/wxgui.py	2011-08-08 23:08:41 UTC (rev 47499)
+++ grass/trunk/gui/wxpython/wxgui.py	2011-08-09 09:43:59 UTC (rev 47500)
@@ -1039,9 +1039,9 @@
         cmd = self.GetMenuCmd(event)
         
         if cmd[0] == 'r.colors':
-            ctable = colorrules.ColorTable(self, raster = True)
+            ctable = colorrules.RasterColorTable(self)
         else:
-            ctable = colorrules.ColorTable(self, raster = False)
+            ctable = colorrules.VectorColorTable(self)
         ctable.CentreOnScreen()
         ctable.Show()
         



More information about the grass-commit mailing list