GRASS Programmer's Manual  6.4.2(2012)
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
colorrules.py
Go to the documentation of this file.
1 """
2 @package colorrules.py
3 
4 @brief Dialog for interactive management of raster color tables and
5 vector rgb_column attributes.
6 
7 Classes:
8  - ColorTable
9  - BuferedWindow
10 
11 (C) 2008, 2010 by the GRASS Development Team
12 This program is free software under the GNU General Public License
13 (>=v2). Read the file COPYING that comes with GRASS for details.
14 
15 @author Michael Barton (Arizona State University)
16 @author Martin Landa <landa.martin gmail.com> (various updates)
17 @author Anna Kratochvilova (load/save raster color tables)
18 """
19 
20 import os
21 import sys
22 import shutil
23 
24 import wx
25 import wx.lib.colourselect as csel
26 import wx.lib.scrolledpanel as scrolled
27 
28 import grass.script as grass
29 
30 import dbm
31 import gcmd
32 import globalvar
33 import gselect
34 import render
35 import utils
36 from debug import Debug as Debug
37 from preferences import globalSettings as UserSettings
38 
39 class ColorTable(wx.Frame):
40  def __init__(self, parent, raster, id=wx.ID_ANY, title = _("Set color table"),
41  style=wx.DEFAULT_FRAME_STYLE | wx.RESIZE_BORDER,
42  **kwargs):
43  """!Dialog for interactively entering rules for map management
44  commands
45 
46  @param raster True to raster otherwise vector
47  """
48  self.parent = parent # GMFrame
49  self.raster = raster
50 
51  wx.Frame.__init__(self, parent, id, title, style = style, **kwargs)
52 
53  self.SetIcon(wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
54 
55  # input map to change
56  self.inmap = ''
57 
58  if self.raster:
59  # raster properties
60  self.properties = {
61  # min cat in raster map
62  'min' : None,
63  # max cat in raster map
64  'max' : None,
65  }
66  else:
67  # vector properties
68  self.properties = {
69  # list of database layers for vector (minimum of 1)
70  'layers' : ['1'],
71  # list of database columns for vector
72  'columns' : [],
73  # vector layer for attribute table to use for setting color
74  'layer' : 1,
75  # vector attribute table used for setting color
76  'table' : '',
77  # vector attribute column for assigning colors
78  'column' : '',
79  # vector attribute column to use for storing colors
80  'rgb' : '',
81  }
82 
83  # rules for creating colortable
84  self.ruleslines = {}
85 
86  # instance of render.Map to be associated with display
87  self.Map = render.Map()
88 
89  # reference to layer with preview
90  self.layer = None
91 
92  if self.raster:
93  self.SetTitle(_('Create new color table for raster map'))
94  crlabel = _('Enter raster category values or percents')
95  else:
96  self.SetTitle(_('Create new color table for vector map'))
97  crlabel = _('Enter vector attribute values or ranges (n or n1 to n2)')
98 
99  # top controls
100  if self.raster:
101  maplabel = _('Select raster map:')
102  else:
103  maplabel = _('Select vector map:')
104  inputBox = wx.StaticBox(parent=self, id=wx.ID_ANY,
105  label=" %s " % maplabel)
106  self.inputSizer = wx.StaticBoxSizer(inputBox, wx.VERTICAL)
107  if self.raster:
108  elem = 'cell'
109  else:
110  elem = 'vector'
111  self.selectionInput = gselect.Select(parent=self, id=wx.ID_ANY,
112  size=globalvar.DIALOG_GSELECT_SIZE,
113  type=elem)
114 
115  self.ovrwrtcheck = wx.CheckBox(parent=self, id=wx.ID_ANY,
116  label=_('replace existing color table'))
117  self.ovrwrtcheck.SetValue(UserSettings.Get(group='cmd', key='overwrite', subkey='enabled'))
118 
119  if self.raster:
120  self.btnSave = wx.Button(parent=self, id=wx.ID_SAVE)
121  self.btnSave.SetToolTipString(_('Save color table to file'))
122 
123  if not self.raster:
124  self.cb_vl_label = wx.StaticText(parent=self, id=wx.ID_ANY,
125  label=_('Layer:'))
126  self.cb_vc_label = wx.StaticText(parent=self, id=wx.ID_ANY,
127  label=_('Attribute column:'))
128  self.cb_vrgb_label = wx.StaticText(parent=self, id=wx.ID_ANY,
129  label=_('RGB color column:'))
133 
134  # color table and preview window
135  self.cr_label = wx.StaticText(parent=self, id=wx.ID_ANY,
136  label=crlabel)
138  # add two rules as default
139  self.AddRules(2)
140 
141  self.numRules = wx.SpinCtrl(parent=self, id=wx.ID_ANY,
142  min=1, max=1e6)
143 
144  # initialize preview display
145  self.InitDisplay()
146  self.preview = BufferedWindow(self, id=wx.ID_ANY, size=(400, 300),
147  Map=self.Map)
148  self.preview.EraseMap()
149 
150  self.btnCancel = wx.Button(parent=self, id=wx.ID_CANCEL)
151  self.btnApply = wx.Button(parent=self, id=wx.ID_APPLY)
152  self.btnOK = wx.Button(parent=self, id=wx.ID_OK)
153  self.btnOK.SetDefault()
154  self.btnOK.Enable(False)
155  self.btnApply.Enable(False)
156 
157  self.btnPreview = wx.Button(parent=self, id=wx.ID_ANY,
158  label=_("Preview"))
159  self.btnPreview.Enable(False)
160  self.btnAdd = wx.Button(parent=self, id=wx.ID_ADD)
161  self.helpbtn = wx.Button(parent=self, id=wx.ID_HELP)
162 
163 
164  # bindings
165  self.Bind(wx.EVT_BUTTON, self.OnHelp, self.helpbtn)
166  self.selectionInput.Bind(wx.EVT_TEXT, self.OnSelectionInput)
167  self.Bind(wx.EVT_BUTTON, self.OnCancel, self.btnCancel)
168  self.Bind(wx.EVT_BUTTON, self.OnApply, self.btnApply)
169  self.Bind(wx.EVT_BUTTON, self.OnOK, self.btnOK)
170  self.Bind(wx.EVT_BUTTON, self.OnPreview, self.btnPreview)
171  self.Bind(wx.EVT_BUTTON, self.OnAddRules, self.btnAdd)
172  self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
173 
174  # additional bindings for raster/vector color management
175  if self.raster:
176  self.Bind(wx.EVT_BUTTON, self.OnSaveTable, self.btnSave)
177  else:
178  self.Bind(wx.EVT_COMBOBOX, self.OnLayerSelection, self.cb_vlayer)
179  self.Bind(wx.EVT_COMBOBOX, self.OnColumnSelection, self.cb_vcol)
180  self.Bind(wx.EVT_COMBOBOX, self.OnRGBColSelection, self.cb_vrgb)
181 
182  # set map layer from layer tree
183  try:
184  layer = self.parent.curr_page.maptree.layer_selected
185  except:
186  layer = None
187  if layer:
188  mapLayer = self.parent.curr_page.maptree.GetPyData(layer)[0]['maplayer']
189  name = mapLayer.GetName()
190  type = mapLayer.GetType()
191  self.selectionInput.SetValue(name)
192  self.inmap = name
193  self.OnSelectionInput(None)
194 
195  # layout
196  self.__doLayout()
197  self.SetMinSize(self.GetSize())
198 
199  self.CentreOnScreen()
200  self.Show()
201 
202  def __doLayout(self):
203  sizer = wx.BoxSizer(wx.VERTICAL)
204 
205  #
206  # input
207  #
208  self.inputSizer.Add(item=self.selectionInput,
209  flag=wx.ALIGN_CENTER_VERTICAL | wx.ALL | wx.EXPAND, border=5)
210  replaceSizer = wx.BoxSizer(wx.HORIZONTAL)
211  replaceSizer.Add(item=self.ovrwrtcheck, proportion=1,
212  flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=1)
213  if self.raster:
214  replaceSizer.Add(item=self.btnSave, proportion=0,
215  flag=wx.ALIGN_RIGHT | wx.ALL, border=5)
216 
217  self.inputSizer.Add(item=replaceSizer, proportion=1,
218  flag=wx.ALL | wx.EXPAND, border=0)
219 
220  #
221  # body & preview
222  #
223  bodySizer = wx.GridBagSizer(hgap=5, vgap=5)
224  row = 0
225  bodySizer.Add(item=self.cr_label, pos=(row, 0), span=(1, 3),
226  flag=wx.ALL, border=5)
227 
228  if not self.raster:
229  vSizer = wx.GridBagSizer(hgap=5, vgap=5)
230  vSizer.Add(self.cb_vl_label, pos=(0, 0),
231  flag=wx.ALIGN_CENTER_VERTICAL)
232  vSizer.Add(self.cb_vlayer, pos=(0, 1),
233  flag=wx.ALIGN_CENTER_VERTICAL)
234  vSizer.Add(self.cb_vc_label, pos=(0, 2),
235  flag=wx.ALIGN_CENTER_VERTICAL)
236  vSizer.Add(self.cb_vcol, pos=(0, 3),
237  flag=wx.ALIGN_CENTER_VERTICAL)
238  vSizer.Add(self.cb_vrgb_label, pos=(1, 2),
239  flag=wx.ALIGN_CENTER_VERTICAL)
240  vSizer.Add(self.cb_vrgb, pos=(1, 3),
241  flag=wx.ALIGN_CENTER_VERTICAL)
242  row += 1
243  bodySizer.Add(item=vSizer, pos=(row, 0), span=(1, 3))
244 
245  row += 1
246  bodySizer.Add(item=self.cr_panel, pos=(row, 0), span=(1, 2))
247 
248  bodySizer.Add(item=self.preview, pos=(row, 2),
249  flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=10)
250  bodySizer.AddGrowableRow(row)
251  bodySizer.AddGrowableCol(2)
252 
253  row += 1
254  bodySizer.Add(item=self.numRules, pos=(row, 0),
255  flag=wx.ALIGN_CENTER_VERTICAL)
256 
257  bodySizer.Add(item=self.btnAdd, pos=(row, 1))
258  bodySizer.Add(item=self.btnPreview, pos=(row, 2),
259  flag=wx.ALIGN_RIGHT)
260 
261  btnSizer = wx.BoxSizer(wx.HORIZONTAL)
262  btnSizer.Add(self.helpbtn,
263  flag=wx.LEFT | wx.RIGHT, border=5)
264  btnSizer.Add(self.btnCancel,
265  flag=wx.LEFT | wx.RIGHT, border=5)
266  btnSizer.Add(self.btnApply,
267  flag=wx.LEFT | wx.RIGHT, border=5)
268  btnSizer.Add(self.btnOK,
269  flag=wx.LEFT | wx.RIGHT, border=5)
270 
271  sizer.Add(item=self.inputSizer, proportion=0,
272  flag=wx.ALL | wx.EXPAND, border=5)
273 
274  sizer.Add(item=bodySizer, proportion=1,
275  flag=wx.ALL | wx.EXPAND, border=5)
276 
277  sizer.Add(item=wx.StaticLine(parent=self, id=wx.ID_ANY,
278  style=wx.LI_HORIZONTAL),
279  proportion=0,
280  flag=wx.EXPAND | wx.ALL, border=5)
281 
282  sizer.Add(item=btnSizer, proportion=0,
283  flag=wx.ALL | wx.ALIGN_RIGHT, border=5)
284 
285  self.SetSizer(sizer)
286  sizer.Fit(self)
287  self.Layout()
288 
289  def _colorRulesPanel(self):
290  """!Create rules panel"""
291  cr_panel = scrolled.ScrolledPanel(parent=self, id=wx.ID_ANY,
292  size=(180, 300),
293  style=wx.TAB_TRAVERSAL | wx.SUNKEN_BORDER)
294  cr_panel.SetupScrolling(scroll_x = False)
295  self.cr_sizer = wx.GridBagSizer(vgap=2, hgap=4)
296 
297  cr_panel.SetSizer(self.cr_sizer)
298  cr_panel.SetAutoLayout(True)
299 
300  return cr_panel
301 
302  def OnAddRules(self, event):
303  """!Add rules button pressed"""
304  nrules = self.numRules.GetValue()
305  self.AddRules(nrules)
306 
307  def AddRules(self, nrules):
308  """!Add rules"""
309  snum = len(self.ruleslines.keys())
310  for num in range(snum, snum + nrules):
311  # enable
312  enable = wx.CheckBox(parent=self.cr_panel, id=num)
313  enable.SetValue(True)
314  self.Bind(wx.EVT_CHECKBOX, self.OnRuleEnable, enable)
315  # value
316  txt_ctrl = wx.TextCtrl(parent=self.cr_panel, id=1000 + num,
317  size=(90, -1),
318  style=wx.TE_NOHIDESEL)
319  self.Bind(wx.EVT_TEXT, self.OnRuleValue, txt_ctrl)
320  # color
321  color_ctrl = csel.ColourSelect(self.cr_panel, id=2000 + num,
322  size = globalvar.DIALOG_COLOR_SIZE)
323  self.Bind(csel.EVT_COLOURSELECT, self.OnRuleColor, color_ctrl)
324  self.ruleslines[enable.GetId()] = { 'value' : '',
325  'color': "0:0:0" }
326 
327  self.cr_sizer.Add(item=enable, pos=(num, 0),
328  flag=wx.ALIGN_CENTER_VERTICAL)
329  self.cr_sizer.Add(item=txt_ctrl, pos=(num, 1),
330  flag=wx.ALIGN_CENTER | wx.RIGHT, border=5)
331  self.cr_sizer.Add(item=color_ctrl, pos=(num, 2),
332  flag=wx.ALIGN_CENTER | wx.RIGHT, border=10)
333 
334  self.cr_panel.Layout()
335  self.cr_panel.SetupScrolling(scroll_x = False)
336 
337  def InitDisplay(self):
338  """!Initialize preview display, set dimensions and region
339  """
340  self.width = self.Map.width = 400
341  self.height = self.Map.height = 300
342  self.Map.geom = self.width, self.height
343 
344  def OnErase(self, event):
345  """!Erase the histogram display
346  """
347  self.PreviewWindow.Draw(self.HistWindow.pdc, pdctype='clear')
348 
349  def OnCloseWindow(self, event):
350  """!Window closed
351  Also remove associated rendered images
352  """
353  self.Map.Clean()
354  self.Destroy()
355 
356  def OnSelectionInput(self, event):
357  """!Raster/vector map selected"""
358  if event:
359  self.inmap = event.GetString()
360 
361  if self.inmap:
362  if self.raster:
363  mapType = 'cell'
364  else:
365  mapType = 'vector'
366  if not grass.find_file(name = self.inmap, element = mapType)['file']:
367  self.inmap = None
368 
369  if not self.inmap:
370  self.btnPreview.Enable(False)
371  self.btnOK.Enable(False)
372  self.btnApply.Enable(False)
373  self.OnLoadTable(event)
374  return
375 
376  if self.raster:
377  info = grass.raster_info(map = self.inmap)
378 
379  if info:
380  self.properties['min'] = info['min']
381  self.properties['max'] = info['max']
382  self.OnLoadTable(event)
383  else:
384  self.inmap = ''
385  self.properties['min'] = self.properties['max'] = None
386  self.btnPreview.Enable(False)
387  self.btnOK.Enable(False)
388  self.btnApply.Enable(False)
389  self.preview.EraseMap()
390  self.cr_label.SetLabel(_('Enter raster category values or percents'))
391  return
392 
393  if info['datatype'] == 'CELL':
394  mapRange = _('range')
395  else:
396  mapRange = _('fp range')
397  self.cr_label.SetLabel(_('Enter raster category values or percents (%(range)s = %(min)d-%(max)d)') %
398  { 'range' : mapRange,
399  'min' : self.properties['min'],
400  'max' : self.properties['max'] })
401 
402  else:
403  # initialize layer selection combobox
404  self.cb_vlayer.InsertLayers(self.inmap)
405  # initialize attribute table for layer=1
406  layer = int(self.properties['layer'])
407  self.properties['table'] = gselect.VectorDBInfo(self.inmap).layers[layer]['table']
408  # initialize column selection comboboxes
409  self.cb_vcol.InsertColumns(vector=self.inmap, layer=layer)
410  self.cb_vrgb.InsertColumns(vector=self.inmap, layer=layer)
411  self.Update()
412 
413  self.btnPreview.Enable(True)
414  self.btnOK.Enable(True)
415  self.btnApply.Enable(True)
416 
417  def OnLayerSelection(self, event):
418  # reset choices in column selection comboboxes if layer changes
419  self.vlayer = int(event.GetString())
420  self.vtable = gselect.VectorDBInfo(self.inmap).layers[str(self.vlayer)]
421  self.cb_vcol.InsertColumns(vector=self.inmap, layer=self.vlayer)
422  self.cb_vrgb.InsertColumns(vector=self.inmap, layer=self.vlayer)
423  self.Update()
424 
425  def OnColumnSelection(self, event):
426  self.properties['column'] = event.GetString()
427 
428  def OnRGBColSelection(self, event):
429  self.properties['rgb'] = event.GetString()
430 
431  def OnRuleEnable(self, event):
432  """!Rule enabled/disabled"""
433  id = event.GetId()
434 
435  if event.IsChecked():
436  value = self.FindWindowById(id+1000).GetValue()
437  color = self.FindWindowById(id+2000).GetValue()
438  color_str = str(color[0]) + ':' \
439  + str(color[1]) + ':' + \
440  str(color[2])
441 
442  self.ruleslines[id] = {
443  'value' : value,
444  'color' : color_str }
445  else:
446  del self.ruleslines[id]
447 
448  def OnRuleValue(self, event):
449  """!Rule value changed"""
450  num = event.GetId()
451  vals = event.GetString().strip()
452 
453  if vals == '':
454  return
455 
456  tc = self.FindWindowById(num)
457 
458  if self.raster:
459  self.ruleslines[num-1000]['value'] = vals
460 
461  else:
462  if self.properties['column'] == '' or self.properties['rgb'] == '':
463  tc.SetValue('')
464  gcmd.GMessage(parent=self,
465  message=_("Please select attribute column "
466  "and RGB color column first"))
467  else:
468  try:
469  self.ruleslines[num-1000]['value'] = self.SQLConvert(vals)
470  except ValueError:
471  tc.SetValue('')
472  self.ruleslines[num-1000]['value'] = ''
473  return
474 
475  def OnRuleColor(self, event):
476  """!Rule color changed"""
477  num = event.GetId()
478 
479  rgba_color = event.GetValue()
480 
481  rgb_string = str(rgba_color[0]) + ':' \
482  + str(rgba_color[1]) + ':' + \
483  str(rgba_color[2])
484 
485  self.ruleslines[num-2000]['color'] = rgb_string
486 
487  def SQLConvert(self, vals):
488  valslist = []
489  valslist = vals.split('to')
490  if len(valslist) == 1:
491  sqlrule = '%s=%s' % (self.properties['column'], valslist[0])
492  elif len(valslist) > 1:
493  sqlrule = '%s>=%s AND %s<=%s' % (self.properties['column'], valslist[0],
494  self.properties['column'], valslist[1])
495  else:
496  return None
497 
498  return sqlrule
499 
500  def OnLoadTable(self, event):
501  """!Load current color table (using `r.colors.out`)"""
502  self.ruleslines.clear()
503  self.cr_panel.DestroyChildren()
504  if self.inmap:
505  ctable = gcmd.RunCommand('r.colors.out',
506  parent = self,
507  read = True,
508  map = self.inmap,
509  rules = '-')
510  else:
511  self.OnPreview(event)
512  return
513 
514  rulesNumber = len(ctable.splitlines())
515  self.AddRules(rulesNumber)
516 
517  count = 0
518  for line in ctable.splitlines():
519  value, color = map(lambda x: x.strip(), line.split(' '))
520  self.ruleslines[count]['value'] = value
521  self.ruleslines[count]['color'] = color
522  self.FindWindowById(count + 1000).SetValue(value)
523  rgb = list()
524  for c in color.split(':'):
525  rgb.append(int(c))
526  self.FindWindowById(count + 2000).SetColour(rgb)
527  count += 1
528 
529  self.OnPreview(tmp = False)
530 
531  def OnSaveTable(self, event):
532  """!Save color table to file"""
533  rulestxt = ''
534  for rule in self.ruleslines.itervalues():
535  if not rule['value']:
536  continue
537  rulestxt += rule['value'] + ' ' + rule['color'] + '\n'
538  if not rulestxt:
539  gcmd.GMessage(message = _("Nothing to save."),
540  parent = self)
541  return
542 
543  dlg = wx.FileDialog(parent = self,
544  message = _("Save color table to file"),
545  defaultDir = os.getcwd(), style = wx.SAVE | wx.OVERWRITE_PROMPT)
546  if dlg.ShowModal() == wx.ID_OK:
547  path = dlg.GetPath()
548  fd = open(path, 'w')
549  fd.write(rulestxt)
550  fd.close()
551  dlg.Destroy()
552 
553  def OnApply(self, event):
554  """!Apply selected color table
555 
556  @return True on success otherwise False
557  """
558  ret = self.CreateColorTable()
559  display = self.parent.GetLayerTree().GetMapDisplay()
560  if display and display.IsAutoRendered():
561  display.GetWindow().UpdateMap(render = True)
562 
563  return ret
564 
565  def OnOK(self, event):
566  """!Apply selected color table and close the dialog"""
567  if self.OnApply(event):
568  self.Destroy()
569 
570  def OnCancel(self, event):
571  """!Do not apply any changes and close the dialog"""
572  self.Destroy()
573 
574  def OnPreview(self, event = None, tmp = True):
575  """!Update preview (based on computational region)"""
576  if not self.inmap:
577  self.preview.EraseMap()
578  return
579 
580  # raster
581  if self.raster:
582  cmdlist = ['d.rast',
583  'map=%s' % self.inmap]
584  ltype = 'raster'
585 
586  # find existing color table and copy to temp file
587  try:
588  name, mapset = self.inmap.split('@')
589  except ValueError:
590  name = self.inmap
591  mapset = grass.find_file(self.inmap, element = 'cell')['mapset']
592  if not mapset:
593  return
594  old_colrtable = None
595  if mapset == grass.gisenv()['MAPSET']:
596  old_colrtable = grass.find_file(name=name, element='colr')['file']
597  else:
598  old_colrtable = grass.find_file(name=name, element='colr2/' + mapset)['file']
599 
600  if old_colrtable:
601  colrtemp = utils.GetTempfile()
602  shutil.copyfile(old_colrtable, colrtemp)
603  # vector
604  else:
605  cmdlist = ['d.vect',
606  '-a',
607  'map=%s' % self.inmap,
608  'rgb_column=%s' % self.properties["rgb"],
609  'type=point,line,boundary,area']
610  ltype = 'vector'
611 
612  if not self.layer:
613  self.layer = self.Map.AddLayer(type=ltype, name='preview', command=cmdlist,
614  l_active=True, l_hidden=False, l_opacity=1.0,
615  l_render=False)
616  else:
617  self.layer.SetCmd(cmdlist)
618 
619  # apply new color table and display preview
620  self.CreateColorTable(force = True)
621  self.preview.UpdatePreview()
622 
623  # restore previous color table
624  if self.raster and tmp:
625  if old_colrtable:
626  shutil.copyfile(colrtemp, old_colrtable)
627  os.remove(colrtemp)
628  else:
629  gcmd.RunCommand('r.colors',
630  parent = self,
631  flags = 'r',
632  map = self.inmap)
633 
634  def OnHelp(self, event):
635  """!Show GRASS manual page"""
636  if self.raster:
637  cmd = 'r.colors'
638  else:
639  cmd = 'vcolors'
640  gcmd.RunCommand('g.manual',
641  quiet = True,
642  parent = self,
643  entry = cmd)
644 
645  def _IsNumber(self, s):
646  """!Check if 's' is a number"""
647  try:
648  float(s)
649  return True
650  except ValueError:
651  return False
652 
653  def CreateColorTable(self, force = False):
654  """!Creates color table
655 
656  @return True on success
657  @return False on failure
658  """
659  rulestxt = ''
660 
661  for rule in self.ruleslines.itervalues():
662  if not rule['value']: # skip empty rules
663  continue
664 
665  if self.raster:
666  if rule['value'] not in ('nv', 'default') and \
667  rule['value'][-1] != '%' and \
668  not self._IsNumber(rule['value']):
669  gcmd.GError(_("Invalid rule value '%s'. Unable to apply color table.") % rule['value'],
670  parent = self)
671  return False
672 
673  rulestxt += rule['value'] + ' ' + rule['color'] + '\n'
674  else:
675  rulestxt += "UPDATE %s SET %s='%s' WHERE %s ;\n" % (self.properties['table'],
676  self.properties['rgb'],
677  rule['color'],
678  rule['value'])
679  if not rulestxt:
680  return False
681 
682  gtemp = utils.GetTempfile()
683  output = open(gtemp, "w")
684  try:
685  output.write(rulestxt)
686  finally:
687  output.close()
688 
689  if self.raster:
690  if not force and \
691  not self.ovrwrtcheck.IsChecked():
692  flags = 'w'
693  else:
694  flags = ''
695 
696  ret = gcmd.RunCommand('r.colors',
697  flags = flags,
698  map = self.inmap,
699  rules = gtemp)
700  if ret != 0:
701  gcmd.GMessage(_("Color table already exists. "
702  "Check out 'replace existing color table' to "
703  "overwrite it."),
704  parent = self)
705  return False
706 
707  else:
708  gcmd.RunCommand('db.execute',
709  parent = self,
710  input = gtemp)
711 
712  return True
713 
714 class BufferedWindow(wx.Window):
715  """!A Buffered window class"""
716  def __init__(self, parent, id,
717  style=wx.NO_FULL_REPAINT_ON_RESIZE,
718  Map=None, **kwargs):
719 
720  wx.Window.__init__(self, parent, id, style = style, **kwargs)
721 
722  self.parent = parent
723  self.Map = Map
724 
725  # re-render the map from GRASS or just redraw image
726  self.render = True
727  # indicates whether or not a resize event has taken place
728  self.resize = False
729 
730  #
731  # event bindings
732  #
733  self.Bind(wx.EVT_PAINT, self.OnPaint)
734  self.Bind(wx.EVT_IDLE, self.OnIdle)
735  self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: None)
736 
737  #
738  # render output objects
739  #
740  # image file to be rendered
741  self.mapfile = None
742  # wx.Image object (self.mapfile)
743  self.img = None
744 
745  self.pdc = wx.PseudoDC()
746  # will store an off screen empty bitmap for saving to file
747  self._Buffer = None
748 
749  # make sure that extents are updated at init
750  self.Map.region = self.Map.GetRegion()
751  self.Map.SetRegion()
752 
753  def Draw(self, pdc, img=None, pdctype='image'):
754  """!Draws preview or clears window"""
755  pdc.BeginDrawing()
756 
757  Debug.msg (3, "BufferedWindow.Draw(): pdctype=%s" % (pdctype))
758 
759  if pdctype == 'clear': # erase the display
760  bg = wx.WHITE_BRUSH
761  pdc.SetBackground(bg)
762  pdc.Clear()
763  self.Refresh()
764  pdc.EndDrawing()
765  return
766 
767  if pdctype == 'image' and img:
768  bg = wx.TRANSPARENT_BRUSH
769  pdc.SetBackground(bg)
770  bitmap = wx.BitmapFromImage(img)
771  w, h = bitmap.GetSize()
772  pdc.DrawBitmap(bitmap, 0, 0, True) # draw the composite map
773 
774  pdc.EndDrawing()
775  self.Refresh()
776 
777  def OnPaint(self, event):
778  """!Draw pseudo DC to buffer"""
779  self._Buffer = wx.EmptyBitmap(self.Map.width, self.Map.height)
780  dc = wx.BufferedPaintDC(self, self._Buffer)
781 
782  # use PrepareDC to set position correctly
783  self.PrepareDC(dc)
784 
785  # we need to clear the dc BEFORE calling PrepareDC
786  bg = wx.Brush(self.GetBackgroundColour())
787  dc.SetBackground(bg)
788  dc.Clear()
789 
790  # create a clipping rect from our position and size
791  # and the Update Region
792  rgn = self.GetUpdateRegion()
793  r = rgn.GetBox()
794 
795  # draw to the dc using the calculated clipping rect
796  self.pdc.DrawToDCClipped(dc, r)
797 
798  def OnSize(self, event):
799  """!Init image size to match window size"""
800  # set size of the input image
801  self.Map.width, self.Map.height = self.GetClientSize()
802 
803  # Make new off screen bitmap: this bitmap will always have the
804  # current drawing in it, so it can be used to save the image to
805  # a file, or whatever.
806  self._Buffer = wx.EmptyBitmap(self.Map.width, self.Map.height)
807 
808  # get the image to be rendered
809  self.img = self.GetImage()
810 
811  # update map display
812  if self.img and self.Map.width + self.Map.height > 0: # scale image during resize
813  self.img = self.img.Scale(self.Map.width, self.Map.height)
814  self.render = False
815  self.UpdatePreview()
816 
817  # re-render image on idle
818  self.resize = True
819 
820  def OnIdle(self, event):
821  """!Only re-render a preview image from GRASS during
822  idle time instead of multiple times during resizing.
823  """
824  if self.resize:
825  self.render = True
826  self.UpdatePreview()
827  event.Skip()
828 
829  def GetImage(self):
830  """!Converts files to wx.Image"""
831  if self.Map.mapfile and os.path.isfile(self.Map.mapfile) and \
832  os.path.getsize(self.Map.mapfile):
833  img = wx.Image(self.Map.mapfile, wx.BITMAP_TYPE_ANY)
834  else:
835  img = None
836 
837  return img
838 
839  def UpdatePreview(self, img=None):
840  """!Update canvas if window changes geometry"""
841  Debug.msg (2, "BufferedWindow.UpdatePreview(%s): render=%s" % (img, self.render))
842  oldfont = ""
843  oldencoding = ""
844 
845  if self.render:
846  # make sure that extents are updated
847  self.Map.region = self.Map.GetRegion()
848  self.Map.SetRegion()
849 
850  # render new map images
851  self.mapfile = self.Map.Render(force=self.render)
852  self.img = self.GetImage()
853  self.resize = False
854 
855  if not self.img:
856  return
857 
858  # paint images to PseudoDC
859  self.pdc.Clear()
860  self.pdc.RemoveAll()
861  # draw map image background
862  self.Draw(self.pdc, self.img, pdctype='image')
863 
864  self.resize = False
865 
866  def EraseMap(self):
867  """!Erase preview"""
868  self.Draw(self.pdc, pdctype='clear')
869