colorrules.py 73 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907
  1. """
  2. @package module.colorrules
  3. @brief Dialog for interactive management of raster/vector color tables
  4. and color rules.
  5. Classes:
  6. - colorrules::RulesPanel
  7. - colorrules::ColorTable
  8. - colorrules::RasterColorTable
  9. - colorrules::VectorColorTable
  10. - colorrules::ThematicVectorTable
  11. - colorrules::BufferedWindow
  12. (C) 2008, 2010-2012 by the GRASS Development Team
  13. This program is free software under the GNU General Public License
  14. (>=v2). Read the file COPYING that comes with GRASS for details.
  15. @author Michael Barton (Arizona State University)
  16. @author Martin Landa <landa.martin gmail.com> (various updates, pre-defined color table)
  17. @author Anna Kratochvilova <kratochanna gmail.com> (split to base and derived classes)
  18. """
  19. import os
  20. import shutil
  21. import copy
  22. import tempfile
  23. import wx
  24. import wx.lib.colourselect as csel
  25. import wx.lib.scrolledpanel as scrolled
  26. import wx.lib.filebrowsebutton as filebrowse
  27. import grass.script as grass
  28. from core import globalvar
  29. from core import utils
  30. from core.utils import _
  31. from core.gcmd import GMessage, RunCommand, GError
  32. from gui_core.gselect import Select, LayerSelect, ColumnSelect, VectorDBInfo
  33. from core.render import Map
  34. from gui_core.forms import GUI
  35. from core.debug import Debug as Debug
  36. from core.settings import UserSettings
  37. from gui_core.widgets import ColorTablesComboBox
  38. class RulesPanel:
  39. def __init__(self, parent, mapType, attributeType, properties, panelWidth = 180):
  40. """Create rules panel
  41. :param mapType: raster/vector
  42. :param attributeType: color/size for choosing widget type
  43. :param properties: properties of classes derived from ColorTable
  44. :param panelWidth: width of scroll panel"""
  45. self.ruleslines = {}
  46. self.mapType = mapType
  47. self.attributeType = attributeType
  48. self.properties = properties
  49. self.parent = parent
  50. self.panelWidth = panelWidth
  51. self.mainSizer = wx.FlexGridSizer(cols = 3, vgap = 6, hgap = 4)
  52. # put small border at the top of panel
  53. for i in range(3):
  54. self.mainSizer.Add(item = wx.Size(3, 3))
  55. self.mainPanel = scrolled.ScrolledPanel(parent, id = wx.ID_ANY,
  56. size = (self.panelWidth, 300),
  57. style = wx.TAB_TRAVERSAL | wx.SUNKEN_BORDER)
  58. # (un)check all
  59. self.checkAll = wx.CheckBox(parent, id = wx.ID_ANY, label = _("Check all"))
  60. self.checkAll.SetValue(True)
  61. # clear button
  62. self.clearAll = wx.Button(parent, id = wx.ID_ANY, label = _("Clear all"))
  63. # determines how many rules should be added
  64. self.numRules = wx.SpinCtrl(parent, id = wx.ID_ANY,
  65. min = 1, max = 1e6, initial = 1)
  66. # add rules
  67. self.btnAdd = wx.Button(parent, id = wx.ID_ADD)
  68. self.btnAdd.Bind(wx.EVT_BUTTON, self.OnAddRules)
  69. self.checkAll.Bind(wx.EVT_CHECKBOX, self.OnCheckAll)
  70. self.clearAll.Bind(wx.EVT_BUTTON, self.OnClearAll)
  71. self.mainPanel.SetSizer(self.mainSizer)
  72. self.mainPanel.SetAutoLayout(True)
  73. self.mainPanel.SetupScrolling()
  74. def Clear(self):
  75. """Clear and widgets and delete information"""
  76. self.ruleslines.clear()
  77. self.mainSizer.Clear(deleteWindows=True)
  78. def OnCheckAll(self, event):
  79. """(Un)check all rules"""
  80. check = event.GetInt()
  81. for child in self.mainPanel.GetChildren():
  82. if child.GetName() == 'enable':
  83. child.SetValue(check)
  84. else:
  85. child.Enable(check)
  86. def OnClearAll(self, event):
  87. """Delete all widgets in panel"""
  88. self.Clear()
  89. def OnAddRules(self, event):
  90. """Add rules button pressed"""
  91. nrules = self.numRules.GetValue()
  92. self.AddRules(nrules)
  93. def AddRules(self, nrules, start = False):
  94. """Add rules
  95. :param start: set widgets (not append)
  96. """
  97. snum = len(self.ruleslines.keys())
  98. if start:
  99. snum = 0
  100. for num in range(snum, snum + nrules):
  101. # enable
  102. enable = wx.CheckBox(parent = self.mainPanel, id = num)
  103. enable.SetValue(True)
  104. enable.SetName('enable')
  105. enable.Bind(wx.EVT_CHECKBOX, self.OnRuleEnable)
  106. # value
  107. txt_ctrl = wx.TextCtrl(parent = self.mainPanel, id = 1000 + num,
  108. size = (80, -1),
  109. style = wx.TE_NOHIDESEL)
  110. if self.mapType == 'vector':
  111. txt_ctrl.SetToolTipString(_("Enter vector attribute values"))
  112. txt_ctrl.Bind(wx.EVT_TEXT, self.OnRuleValue)
  113. txt_ctrl.SetName('source')
  114. if self.attributeType == 'color':
  115. # color
  116. columnCtrl = csel.ColourSelect(self.mainPanel, id = 2000 + num,
  117. size = globalvar.DIALOG_COLOR_SIZE)
  118. columnCtrl.Bind(csel.EVT_COLOURSELECT, self.OnRuleColor)
  119. columnCtrl.SetName('target')
  120. if not start:
  121. self.ruleslines[enable.GetId()] = { 'value' : '',
  122. 'color': "0:0:0" }
  123. else:
  124. # size or width
  125. init = 2
  126. if self.attributeType == 'size':
  127. init = 100
  128. columnCtrl = wx.SpinCtrl(self.mainPanel, id = 2000 + num,
  129. size = (50, -1), min = 1, max = 1e4,
  130. initial = init)
  131. columnCtrl.Bind(wx.EVT_SPINCTRL, self.OnRuleSize)
  132. columnCtrl.Bind(wx.EVT_TEXT, self.OnRuleSize)
  133. columnCtrl.SetName('target')
  134. if not start:
  135. self.ruleslines[enable.GetId()] = { 'value' : '',
  136. self.attributeType: init }
  137. self.mainSizer.Add(item = enable, proportion = 0,
  138. flag = wx.ALIGN_CENTER_VERTICAL)
  139. self.mainSizer.Add(item = txt_ctrl, proportion = 0,
  140. flag = wx.ALIGN_CENTER | wx.RIGHT, border = 5)
  141. self.mainSizer.Add(item = columnCtrl, proportion = 0,
  142. flag = wx.ALIGN_CENTER | wx.RIGHT, border = 10)
  143. self.mainPanel.Layout()
  144. self.mainPanel.SetupScrolling(scroll_x = False)
  145. def OnRuleEnable(self, event):
  146. """Rule enabled/disabled"""
  147. id = event.GetId()
  148. if event.IsChecked():
  149. self.mainPanel.FindWindowById(id + 1000).Enable()
  150. self.mainPanel.FindWindowById(id + 2000).Enable()
  151. if self.mapType == 'vector' and not self.parent.GetParent().colorTable:
  152. vals = []
  153. vals.append(self.mainPanel.FindWindowById(id + 1000).GetValue())
  154. try:
  155. vals.append(self.mainPanel.FindWindowById(id + 1 + 1000).GetValue())
  156. except AttributeError:
  157. vals.append(None)
  158. value = self.SQLConvert(vals)
  159. else:
  160. value = self.mainPanel.FindWindowById(id + 1000).GetValue()
  161. color = self.mainPanel.FindWindowById(id + 2000).GetValue()
  162. if self.attributeType == 'color':
  163. # color
  164. color_str = str(color[0]) + ':' \
  165. + str(color[1]) + ':' \
  166. + str(color[2])
  167. self.ruleslines[id] = {'value' : value,
  168. 'color' : color_str }
  169. else:
  170. # size or width
  171. self.ruleslines[id] = {'value' : value,
  172. self.attributeType : float(color) }
  173. else:
  174. self.mainPanel.FindWindowById(id + 1000).Disable()
  175. self.mainPanel.FindWindowById(id + 2000).Disable()
  176. del self.ruleslines[id]
  177. def OnRuleColor(self, event):
  178. """Rule color changed"""
  179. num = event.GetId()
  180. rgba_color = event.GetValue()
  181. rgb_string = str(rgba_color[0]) + ':' \
  182. + str(rgba_color[1]) + ':' \
  183. + str(rgba_color[2])
  184. self.ruleslines[num-2000]['color'] = rgb_string
  185. def OnRuleSize(self, event):
  186. """Rule size changed"""
  187. num = event.GetId()
  188. size = event.GetInt()
  189. self.ruleslines[num - 2000][self.attributeType] = size
  190. def OnRuleValue(self, event):
  191. """Rule value changed"""
  192. num = event.GetId()
  193. val = event.GetString().strip()
  194. if val == '':
  195. return
  196. try:
  197. table = self.parent.colorTable
  198. except AttributeError:
  199. # due to panel/scrollpanel in vector dialog
  200. if isinstance(self.parent.GetParent(), RasterColorTable):
  201. table = self.parent.GetParent().colorTable
  202. else:
  203. table = self.parent.GetParent().GetParent().colorTable
  204. if table:
  205. self.SetRasterRule(num, val)
  206. else:
  207. self.SetVectorRule(num, val)
  208. def SetRasterRule(self, num, val):
  209. """Set raster rule"""
  210. self.ruleslines[num - 1000]['value'] = val
  211. def SetVectorRule(self, num, val):
  212. """Set vector rule"""
  213. vals = []
  214. vals.append(val)
  215. try:
  216. vals.append(self.mainPanel.FindWindowById(num + 1).GetValue())
  217. except AttributeError:
  218. vals.append(None)
  219. self.ruleslines[num - 1000]['value'] = self.SQLConvert(vals)
  220. def Enable(self, enable = True):
  221. """Enable/Disable all widgets"""
  222. for child in self.mainPanel.GetChildren():
  223. child.Enable(enable)
  224. sql = True
  225. self.LoadRulesline(sql)# todo
  226. self.btnAdd.Enable(enable)
  227. self.numRules.Enable(enable)
  228. self.checkAll.Enable(enable)
  229. self.clearAll.Enable(enable)
  230. def LoadRules(self):
  231. message = ""
  232. for item in range(len(self.ruleslines)):
  233. try:
  234. self.mainPanel.FindWindowById(item + 1000).SetValue(self.ruleslines[item]['value'])
  235. r, g, b = (0, 0, 0) # default
  236. if not self.ruleslines[item][self.attributeType]:
  237. if self.attributeType == 'color':
  238. self.ruleslines[item][self.attributeType] = '%d:%d:%d' % (r, g, b)
  239. elif self.attributeType == 'size':
  240. self.ruleslines[item][self.attributeType] = 100
  241. elif self.attributeType == 'width':
  242. self.ruleslines[item][self.attributeType] = 2
  243. if self.attributeType == 'color':
  244. try:
  245. r, g, b = map(int, self.ruleslines[item][self.attributeType].split(':'))
  246. except ValueError as e:
  247. message = _("Bad color format. Use color format '0:0:0'")
  248. self.mainPanel.FindWindowById(item + 2000).SetValue((r, g, b))
  249. else:
  250. value = float(self.ruleslines[item][self.attributeType])
  251. self.mainPanel.FindWindowById(item + 2000).SetValue(value)
  252. except:
  253. continue
  254. if message:
  255. GMessage(parent = self.parent, message = message)
  256. return False
  257. return True
  258. def SQLConvert(self, vals):
  259. """Prepare value for SQL query"""
  260. if vals[0].isdigit():
  261. sqlrule = '%s=%s' % (self.properties['sourceColumn'], vals[0])
  262. if vals[1]:
  263. sqlrule += ' AND %s<%s' % (self.properties['sourceColumn'], vals[1])
  264. else:
  265. sqlrule = '%s=%s' % (self.properties['sourceColumn'], vals[0])
  266. return sqlrule
  267. class ColorTable(wx.Frame):
  268. def __init__(self, parent, title, layerTree = None, id = wx.ID_ANY,
  269. style = wx.DEFAULT_FRAME_STYLE | wx.RESIZE_BORDER,
  270. **kwargs):
  271. """Dialog for interactively entering rules for map management
  272. commands
  273. :param raster: True to raster otherwise vector
  274. :param nviz: True if ColorTable is called from nviz thematic mapping
  275. """
  276. self.parent = parent # GMFrame ?
  277. self.layerTree = layerTree # LayerTree or None
  278. wx.Frame.__init__(self, parent, id, title, style = style, **kwargs)
  279. self.SetIcon(wx.Icon(os.path.join(globalvar.ICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
  280. self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
  281. # instance of render.Map to be associated with display
  282. self.Map = Map()
  283. # input map to change
  284. self.inmap = ''
  285. # reference to layer with preview
  286. self.layer = None
  287. # layout
  288. self._doLayout()
  289. # bindings
  290. self.Bind(wx.EVT_BUTTON, self.OnHelp, self.btnHelp)
  291. self.selectionInput.Bind(wx.EVT_TEXT, self.OnSelectionInput)
  292. self.Bind(wx.EVT_BUTTON, self.OnCancel, self.btnCancel)
  293. self.Bind(wx.EVT_BUTTON, self.OnApply, self.btnApply)
  294. self.Bind(wx.EVT_BUTTON, self.OnOK, self.btnOK)
  295. self.Bind(wx.EVT_BUTTON, self.OnLoadDefaultTable, self.btnDefault)
  296. self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
  297. self.Bind(wx.EVT_BUTTON, self.OnPreview, self.btnPreview)
  298. def _initLayer(self):
  299. """Set initial layer when opening dialog"""
  300. # set map layer from layer tree, first selected,
  301. # if not the right type, than select another
  302. try:
  303. sel = self.layerTree.layer_selected
  304. if sel and self.layerTree.GetLayerInfo(sel, key = 'type') == self.mapType:
  305. layer = sel
  306. else:
  307. layer = self.layerTree.FindItemByData(key = 'type', value = self.mapType)
  308. except:
  309. layer = None
  310. if layer:
  311. mapLayer = self.layerTree.GetLayerInfo(layer, key = 'maplayer')
  312. name = mapLayer.GetName()
  313. type = mapLayer.GetType()
  314. self.selectionInput.SetValue(name)
  315. self.inmap = name
  316. def _createMapSelection(self, parent):
  317. """Create map selection part of dialog"""
  318. # top controls
  319. if self.mapType == 'raster':
  320. maplabel = _('Select raster map:')
  321. else:
  322. maplabel = _('Select vector map:')
  323. inputBox = wx.StaticBox(parent, id = wx.ID_ANY,
  324. label = " %s " % maplabel)
  325. inputSizer = wx.StaticBoxSizer(inputBox, wx.VERTICAL)
  326. self.selectionInput = Select(parent = parent, id = wx.ID_ANY,
  327. size = globalvar.DIALOG_GSELECT_SIZE,
  328. type = self.mapType)
  329. # layout
  330. inputSizer.Add(item = self.selectionInput,
  331. flag = wx.ALIGN_CENTER_VERTICAL | wx.ALL | wx.EXPAND, border = 5)
  332. return inputSizer
  333. def _createFileSelection(self, parent):
  334. """Create file (open/save rules) selection part of dialog"""
  335. inputBox = wx.StaticBox(parent, id = wx.ID_ANY,
  336. label = " %s " % _("Import or export color table:"))
  337. inputSizer = wx.StaticBoxSizer(inputBox, wx.HORIZONTAL)
  338. self.loadRules = filebrowse.FileBrowseButton(parent = parent, id = wx.ID_ANY, fileMask = '*',
  339. labelText = '',
  340. dialogTitle = _('Choose file to load color table'),
  341. buttonText = _('Load'),
  342. toolTip = _("Type filename or click to choose "
  343. "file and load color table"),
  344. startDirectory = os.getcwd(), fileMode = wx.OPEN,
  345. changeCallback = self.OnLoadRulesFile)
  346. self.saveRules = filebrowse.FileBrowseButton(parent = parent, id = wx.ID_ANY, fileMask = '*',
  347. labelText = '',
  348. dialogTitle = _('Choose file to save color table'),
  349. toolTip = _("Type filename or click to choose "
  350. "file and save color table"),
  351. buttonText = _('Save'),
  352. startDirectory = os.getcwd(), fileMode = wx.SAVE,
  353. changeCallback = self.OnSaveRulesFile)
  354. colorTable = ColorTablesComboBox(parent = parent, size=globalvar.DIALOG_COMBOBOX_SIZE,
  355. choices = utils.GetColorTables(), name="colorTableChoice")
  356. self.btnSet = wx.Button(parent = parent, id = wx.ID_ANY, label = _("&Set"), name = 'btnSet')
  357. self.btnSet.Bind(wx.EVT_BUTTON, self.OnSetTable)
  358. self.btnSet.Enable(False)
  359. # layout
  360. gridSizer = wx.GridBagSizer(hgap = 2, vgap = 2)
  361. gridSizer.Add(item = wx.StaticText(parent, label = _("Load color table:")),
  362. pos = (0, 0), flag = wx.ALIGN_CENTER_VERTICAL)
  363. gridSizer.Add(item = colorTable, pos = (0, 1))
  364. gridSizer.Add(item = self.btnSet, pos = (0, 2), flag = wx.ALIGN_RIGHT)
  365. gridSizer.Add(item = wx.StaticText(parent, label = _('Load color table from file:')),
  366. pos = (1, 0), flag = wx.ALIGN_CENTER_VERTICAL)
  367. gridSizer.Add(item = self.loadRules, pos = (1, 1), span = (1, 2), flag = wx.EXPAND)
  368. gridSizer.Add(item = wx.StaticText(parent, label = _('Save color table to file:')),
  369. pos = (2, 0), flag = wx.ALIGN_CENTER_VERTICAL)
  370. gridSizer.Add(item = self.saveRules, pos = (2, 1), span = (1, 2), flag = wx.EXPAND)
  371. gridSizer.AddGrowableCol(1)
  372. inputSizer.Add(gridSizer, proportion = 1, flag = wx.EXPAND | wx.ALL,
  373. border = 5)
  374. if self.mapType == 'vector':
  375. # parent is collapsible pane
  376. parent.SetSizer(inputSizer)
  377. return inputSizer
  378. def _createPreview(self, parent):
  379. """Create preview"""
  380. # initialize preview display
  381. self.InitDisplay()
  382. self.preview = BufferedWindow(parent, id = wx.ID_ANY, size = (400, 300),
  383. Map = self.Map)
  384. self.preview.EraseMap()
  385. def _createButtons(self, parent):
  386. """Create buttons for leaving dialog"""
  387. self.btnHelp = wx.Button(parent, id = wx.ID_HELP)
  388. self.btnCancel = wx.Button(parent, id = wx.ID_CANCEL)
  389. self.btnApply = wx.Button(parent, id = wx.ID_APPLY)
  390. self.btnOK = wx.Button(parent, id = wx.ID_OK)
  391. self.btnDefault = wx.Button(parent, id = wx.ID_ANY,
  392. label = _("Reload default table"))
  393. self.btnOK.SetDefault()
  394. self.btnOK.Enable(False)
  395. self.btnApply.Enable(False)
  396. self.btnDefault.Enable(False)
  397. # layout
  398. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  399. btnSizer.Add(wx.Size(-1, -1), proportion = 1)
  400. btnSizer.Add(self.btnDefault,
  401. flag = wx.LEFT | wx.RIGHT, border = 5)
  402. btnSizer.Add(self.btnHelp,
  403. flag = wx.LEFT | wx.RIGHT, border = 5)
  404. btnSizer.Add(self.btnCancel,
  405. flag = wx.LEFT | wx.RIGHT, border = 5)
  406. btnSizer.Add(self.btnApply,
  407. flag = wx.LEFT | wx.RIGHT, border = 5)
  408. btnSizer.Add(self.btnOK,
  409. flag = wx.LEFT | wx.RIGHT, border = 5)
  410. return btnSizer
  411. def _createBody(self, parent):
  412. """Create dialog body consisting of rules and preview"""
  413. bodySizer = wx.GridBagSizer(hgap = 5, vgap = 5)
  414. bodySizer.AddGrowableRow(1)
  415. bodySizer.AddGrowableCol(2)
  416. row = 0
  417. # label with range
  418. self.cr_label = wx.StaticText(parent, id = wx.ID_ANY)
  419. bodySizer.Add(item = self.cr_label, pos = (row, 0), span = (1, 3),
  420. flag = wx.ALL, border = 5)
  421. row += 1
  422. # color table
  423. self.rulesPanel = RulesPanel(parent = parent, mapType = self.mapType,
  424. attributeType = self.attributeType, properties = self.properties)
  425. bodySizer.Add(item = self.rulesPanel.mainPanel, pos = (row, 0),
  426. span = (1, 2), flag = wx.EXPAND)
  427. # add two rules as default
  428. self.rulesPanel.AddRules(2)
  429. # preview window
  430. self._createPreview(parent = parent)
  431. bodySizer.Add(item = self.preview, pos = (row, 2),
  432. flag = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_CENTER)
  433. row += 1
  434. # add ckeck all and clear all
  435. bodySizer.Add(item = self.rulesPanel.checkAll, flag = wx.ALIGN_CENTER_VERTICAL,
  436. pos = (row, 0))
  437. bodySizer.Add(item = self.rulesPanel.clearAll, pos = (row, 1))
  438. # preview button
  439. self.btnPreview = wx.Button(parent, id = wx.ID_ANY,
  440. label = _("Preview"))
  441. bodySizer.Add(item = self.btnPreview, pos = (row, 2),
  442. flag = wx.ALIGN_RIGHT)
  443. self.btnPreview.Enable(False)
  444. self.btnPreview.SetToolTipString(_("Show preview of map "
  445. "(current Map Display extent is used)."))
  446. row +=1
  447. # add rules button and spin to sizer
  448. bodySizer.Add(item = self.rulesPanel.numRules, pos = (row, 0),
  449. flag = wx.ALIGN_CENTER_VERTICAL)
  450. bodySizer.Add(item = self.rulesPanel.btnAdd, pos = (row, 1))
  451. return bodySizer
  452. def InitDisplay(self):
  453. """Initialize preview display, set dimensions and region
  454. """
  455. self.width = self.Map.width = 400
  456. self.height = self.Map.height = 300
  457. self.Map.geom = self.width, self.height
  458. def OnCloseWindow(self, event):
  459. """Window closed
  460. """
  461. self.OnCancel(event)
  462. def OnApply(self, event):
  463. """Apply selected color table
  464. :return: True on success otherwise False
  465. """
  466. ret = self.CreateColorTable()
  467. if not ret:
  468. GMessage(parent = self, message = _("No valid color rules given."))
  469. else:
  470. # re-render preview and current map window
  471. self.OnPreview(None)
  472. display = self.layerTree.GetMapDisplay()
  473. if display and display.IsAutoRendered():
  474. display.GetWindow().UpdateMap(render = True)
  475. return ret
  476. def OnOK(self, event):
  477. """Apply selected color table and close the dialog"""
  478. if self.OnApply(event):
  479. self.OnCancel(event)
  480. def OnCancel(self, event):
  481. """Do not apply any changes, remove associated
  482. rendered images and close the dialog"""
  483. self.Map.Clean()
  484. self.Destroy()
  485. def OnSetTable(self, event):
  486. """Load pre-defined color table"""
  487. ct = self.FindWindowByName("colorTableChoice").GetValue()
  488. # save original color table
  489. ctOriginal = RunCommand('r.colors.out', read = True, map = self.inmap, rules = '-')
  490. # set new color table
  491. ret, err = RunCommand('r.colors', map = self.inmap, color = ct, getErrorMsg = True)
  492. if ret != 0:
  493. GError(err, parent = self)
  494. return
  495. ctNew = RunCommand('r.colors.out', read = True, map = self.inmap, rules = '-')
  496. # restore original table
  497. RunCommand('r.colors', map = self.inmap, rules = '-', stdin = ctOriginal)
  498. # load color table
  499. self.rulesPanel.Clear()
  500. self.ReadColorTable(ctable = ctNew)
  501. def OnSaveRulesFile(self, event):
  502. """Save color table to file"""
  503. path = event.GetString()
  504. if not os.path.exists(path):
  505. return
  506. rulestxt = ''
  507. for rule in self.rulesPanel.ruleslines.itervalues():
  508. if 'value' not in rule:
  509. continue
  510. rulestxt += rule['value'] + ' ' + rule['color'] + '\n'
  511. if not rulestxt:
  512. GMessage(message = _("Nothing to save."),
  513. parent = self)
  514. return
  515. fd = open(path, 'w')
  516. fd.write(rulestxt)
  517. fd.close()
  518. def OnLoadRulesFile(self, event):
  519. """Load color table from file"""
  520. path = event.GetString()
  521. if not os.path.exists(path):
  522. return
  523. self.rulesPanel.Clear()
  524. fd = open(path, 'r')
  525. self.ReadColorTable(ctable = fd.read())
  526. fd.close()
  527. def ReadColorTable(self, ctable):
  528. """Read color table
  529. :param table: color table in format coming from r.colors.out"""
  530. rulesNumber = len(ctable.splitlines())
  531. self.rulesPanel.AddRules(rulesNumber)
  532. minim = maxim = count = 0
  533. for line in ctable.splitlines():
  534. try:
  535. value, color = map(lambda x: x.strip(), line.split(' '))
  536. except ValueError:
  537. GMessage(parent = self, message = _("Invalid color table format"))
  538. self.rulesPanel.Clear()
  539. return
  540. self.rulesPanel.ruleslines[count]['value'] = value
  541. self.rulesPanel.ruleslines[count]['color'] = color
  542. self.rulesPanel.mainPanel.FindWindowById(count + 1000).SetValue(value)
  543. rgb = list()
  544. for c in color.split(':'):
  545. rgb.append(int(c))
  546. self.rulesPanel.mainPanel.FindWindowById(count + 2000).SetColour(rgb)
  547. # range
  548. try:
  549. if float(value) < minim:
  550. minim = float(value)
  551. if float(value) > maxim:
  552. maxim = float(value)
  553. except ValueError: # nv, default
  554. pass
  555. count += 1
  556. if self.mapType == 'vector':
  557. # raster min, max is known from r.info
  558. self.properties['min'], self.properties['max'] = minim, maxim
  559. self.SetRangeLabel()
  560. self.OnPreview(tmp = True)
  561. def OnLoadDefaultTable(self, event):
  562. """Load internal color table"""
  563. self.LoadTable()
  564. def LoadTable(self, mapType = 'raster'):
  565. """Load current color table (using `r(v).colors.out`)
  566. :param mapType: map type (raster or vector)"""
  567. self.rulesPanel.Clear()
  568. if mapType == 'raster':
  569. cmd = ['r.colors.out',
  570. 'read=True',
  571. 'map=%s' % self.inmap,
  572. 'rules=-']
  573. else:
  574. cmd = ['v.colors.out',
  575. 'read=True',
  576. 'map=%s' % self.inmap,
  577. 'rules=-']
  578. if self.properties['sourceColumn'] and self.properties['sourceColumn'] != 'cat':
  579. cmd.append('column=%s' % self.properties['sourceColumn'])
  580. cmd = utils.CmdToTuple(cmd)
  581. if self.inmap:
  582. ctable = RunCommand(cmd[0], **cmd[1])
  583. else:
  584. self.OnPreview()
  585. return
  586. self.ReadColorTable(ctable = ctable)
  587. def CreateColorTable(self, tmp = False):
  588. """Creates color table
  589. :return: True on success
  590. :return: False on failure
  591. """
  592. rulestxt = ''
  593. for rule in self.rulesPanel.ruleslines.itervalues():
  594. if 'value' not in rule: # skip empty rules
  595. continue
  596. if rule['value'] not in ('nv', 'default') and \
  597. rule['value'][-1] != '%' and \
  598. not self._IsNumber(rule['value']):
  599. GError(_("Invalid rule value '%s'. Unable to apply color table.") % rule['value'],
  600. parent = self)
  601. return False
  602. rulestxt += rule['value'] + ' ' + rule['color'] + '\n'
  603. if not rulestxt:
  604. return False
  605. gtemp = utils.GetTempfile()
  606. output = open(gtemp, "w")
  607. try:
  608. output.write(rulestxt)
  609. finally:
  610. output.close()
  611. cmd = ['%s.colors' % self.mapType[0], #r.colors/v.colors
  612. 'map=%s' % self.inmap,
  613. 'rules=%s' % gtemp]
  614. if self.mapType == 'vector' and self.properties['sourceColumn'] \
  615. and self.properties['sourceColumn'] != 'cat':
  616. cmd.append('column=%s' % self.properties['sourceColumn'])
  617. cmd = utils.CmdToTuple(cmd)
  618. ret = RunCommand(cmd[0], **cmd[1])
  619. if ret != 0:
  620. return False
  621. return True
  622. def DoPreview(self, ltype, cmdlist):
  623. """Update preview (based on computational region)"""
  624. if not self.layer:
  625. self.layer = self.Map.AddLayer(ltype = ltype, name = 'preview', command = cmdlist,
  626. active = True, hidden = False, opacity = 1.0,
  627. render = False)
  628. else:
  629. self.layer.SetCmd(cmdlist)
  630. # apply new color table and display preview
  631. self.CreateColorTable(tmp = True)
  632. self.preview.UpdatePreview()
  633. def RunHelp(self, cmd):
  634. """Show GRASS manual page"""
  635. RunCommand('g.manual',
  636. quiet = True,
  637. parent = self,
  638. entry = cmd)
  639. def SetMap(self, name):
  640. """Set map name and update dialog"""
  641. self.selectionInput.SetValue(name)
  642. def _IsNumber(self, s):
  643. """Check if 's' is a number"""
  644. try:
  645. float(s)
  646. return True
  647. except ValueError:
  648. return False
  649. class RasterColorTable(ColorTable):
  650. def __init__(self, parent, **kwargs):
  651. """Dialog for interactively entering color rules for raster maps"""
  652. self.mapType = 'raster'
  653. self.attributeType = 'color'
  654. self.colorTable = True
  655. # raster properties
  656. self.properties = {
  657. # min cat in raster map
  658. 'min' : None,
  659. # max cat in raster map
  660. 'max' : None,
  661. }
  662. ColorTable.__init__(self, parent,
  663. title = _('Create new color table for raster map'), **kwargs)
  664. self._initLayer()
  665. # self.SetMinSize(self.GetSize())
  666. self.SetMinSize((650, 700))
  667. def _doLayout(self):
  668. """Do main layout"""
  669. sizer = wx.BoxSizer(wx.VERTICAL)
  670. #
  671. # map selection
  672. #
  673. mapSelection = self._createMapSelection(parent = self.panel)
  674. sizer.Add(item = mapSelection, proportion = 0,
  675. flag = wx.ALL | wx.EXPAND, border = 5)
  676. #
  677. # manage extern tables
  678. #
  679. fileSelection = self._createFileSelection(parent = self.panel)
  680. sizer.Add(item = fileSelection, proportion = 0,
  681. flag = wx.LEFT | wx.RIGHT | wx.EXPAND, border = 5)
  682. #
  683. # body & preview
  684. #
  685. bodySizer = self._createBody(parent = self.panel)
  686. sizer.Add(item = bodySizer, proportion = 1,
  687. flag = wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border = 5)
  688. #
  689. # buttons
  690. #
  691. btnSizer = self._createButtons(parent = self.panel)
  692. sizer.Add(item = wx.StaticLine(parent = self.panel, id = wx.ID_ANY,
  693. style = wx.LI_HORIZONTAL), proportion = 0,
  694. flag = wx.EXPAND | wx.ALL, border = 5)
  695. sizer.Add(item = btnSizer, proportion = 0,
  696. flag = wx.ALL | wx.ALIGN_RIGHT, border = 5)
  697. self.panel.SetSizer(sizer)
  698. sizer.Layout()
  699. sizer.Fit(self.panel)
  700. self.Layout()
  701. def OnSelectionInput(self, event):
  702. """Raster map selected"""
  703. if event:
  704. self.inmap = event.GetString()
  705. self.loadRules.SetValue('')
  706. self.saveRules.SetValue('')
  707. if self.inmap:
  708. if not grass.find_file(name = self.inmap, element = 'cell')['file']:
  709. self.inmap = None
  710. if not self.inmap:
  711. for btn in (self.btnPreview, self.btnOK,
  712. self.btnApply, self.btnDefault, self.btnSet):
  713. btn.Enable(False)
  714. self.LoadTable()
  715. return
  716. info = grass.raster_info(map = self.inmap)
  717. if info:
  718. self.properties['min'] = info['min']
  719. self.properties['max'] = info['max']
  720. self.LoadTable()
  721. else:
  722. self.inmap = ''
  723. self.properties['min'] = self.properties['max'] = None
  724. for btn in (self.btnPreview, self.btnOK,
  725. self.btnApply, self.btnDefault, self.btnSet):
  726. btn.Enable(False)
  727. self.preview.EraseMap()
  728. self.cr_label.SetLabel(_('Enter raster category values or percents'))
  729. return
  730. if info['datatype'] == 'CELL':
  731. mapRange = _('range')
  732. else:
  733. mapRange = _('fp range')
  734. self.cr_label.SetLabel(_('Enter raster category values or percents (%(range)s = %(min)d-%(max)d)') %
  735. { 'range' : mapRange,
  736. 'min' : self.properties['min'],
  737. 'max' : self.properties['max'] })
  738. for btn in (self.btnPreview, self.btnOK,
  739. self.btnApply, self.btnDefault, self.btnSet):
  740. btn.Enable()
  741. def OnPreview(self, tmp = True):
  742. """Update preview (based on computational region)"""
  743. if not self.inmap:
  744. self.preview.EraseMap()
  745. return
  746. cmdlist = ['d.rast',
  747. 'map=%s' % self.inmap]
  748. ltype = 'raster'
  749. # find existing color table and copy to temp file
  750. try:
  751. name, mapset = self.inmap.split('@')
  752. except ValueError:
  753. name = self.inmap
  754. mapset = grass.find_file(self.inmap, element = 'cell')['mapset']
  755. if not mapset:
  756. return
  757. old_colrtable = None
  758. if mapset == grass.gisenv()['MAPSET']:
  759. old_colrtable = grass.find_file(name = name, element = 'colr')['file']
  760. else:
  761. old_colrtable = grass.find_file(name = name, element = 'colr2/' + mapset)['file']
  762. if old_colrtable:
  763. colrtemp = utils.GetTempfile()
  764. shutil.copyfile(old_colrtable, colrtemp)
  765. ColorTable.DoPreview(self, ltype, cmdlist)
  766. # restore previous color table
  767. if tmp:
  768. if old_colrtable:
  769. shutil.copyfile(colrtemp, old_colrtable)
  770. os.remove(colrtemp)
  771. else:
  772. RunCommand('r.colors',
  773. parent = self,
  774. flags = 'r',
  775. map = self.inmap)
  776. def OnHelp(self, event):
  777. """Show GRASS manual page"""
  778. cmd = 'r.colors'
  779. ColorTable.RunHelp(self, cmd = cmd)
  780. class VectorColorTable(ColorTable):
  781. def __init__(self, parent, attributeType, **kwargs):
  782. """Dialog for interactively entering color rules for vector maps"""
  783. # dialog attributes
  784. self.mapType = 'vector'
  785. self.attributeType = attributeType # color, size, width
  786. # in version 7 v.colors used, otherwise color column only
  787. self.version7 = int(grass.version()['version'].split('.')[0]) >= 7
  788. self.colorTable = False
  789. self.updateColumn = True
  790. # vector properties
  791. self.properties = {
  792. # vector layer for attribute table to use for setting color
  793. 'layer' : 1,
  794. # vector attribute table used for setting color
  795. 'table' : '',
  796. # vector attribute column for assigning colors
  797. 'sourceColumn' : '',
  798. # vector attribute column to use for loading colors
  799. 'loadColumn' : '',
  800. # vector attribute column to use for storing colors
  801. 'storeColumn' : '',
  802. # vector attribute column for temporary storing colors
  803. 'tmpColumn' : 'tmp_0',
  804. # min value of attribute column/vector color table
  805. 'min': None,
  806. # max value of attribute column/vector color table
  807. 'max': None
  808. }
  809. self.columnsProp = {'color': {'name': 'GRASSRGB', 'type1': 'varchar(11)', 'type2': ['character']},
  810. 'size' : {'name': 'GRASSSIZE', 'type1': 'integer', 'type2': ['integer']},
  811. 'width': {'name': 'GRASSWIDTH', 'type1': 'integer', 'type2': ['integer']}}
  812. ColorTable.__init__(self, parent = parent,
  813. title = _('Create new color rules for vector map'), **kwargs)
  814. # additional bindings for vector color management
  815. self.Bind(wx.EVT_COMBOBOX, self.OnLayerSelection, self.layerSelect)
  816. self.sourceColumn.Bind(wx.EVT_TEXT, self.OnSourceColumnSelection)
  817. self.fromColumn.Bind(wx.EVT_TEXT, self.OnFromColSelection)
  818. self.toColumn.Bind(wx.EVT_TEXT, self.OnToColSelection)
  819. self.Bind(wx.EVT_BUTTON, self.OnAddColumn, self.addColumn)
  820. self._initLayer()
  821. if self.colorTable:
  822. self.cr_label.SetLabel(_("Enter vector attribute values or percents:"))
  823. else:
  824. self.cr_label.SetLabel(_("Enter vector attribute values:"))
  825. #self.SetMinSize(self.GetSize())
  826. self.SetMinSize((650, 700))
  827. self.CentreOnScreen()
  828. self.Show()
  829. def _createVectorAttrb(self, parent):
  830. """Create part of dialog with layer/column selection"""
  831. inputBox = wx.StaticBox(parent = parent, id = wx.ID_ANY,
  832. label = " %s " % _("Select vector columns"))
  833. cb_vl_label = wx.StaticText(parent, id = wx.ID_ANY,
  834. label = _('Layer:'))
  835. cb_vc_label = wx.StaticText(parent, id = wx.ID_ANY,
  836. label = _('Attribute column:'))
  837. if self.attributeType == 'color':
  838. labels = [_("Load color from column:"), _("Save color to column:")]
  839. elif self.attributeType == 'size':
  840. labels = [_("Load size from column:"), _("Save size to column:")]
  841. elif self.attributeType == 'width':
  842. labels = [_("Load width from column:"), _("Save width to column:")]
  843. if self.version7 and self.attributeType == 'color':
  844. self.useColumn = wx.CheckBox(parent, id = wx.ID_ANY,
  845. label = _("Use color column instead of color table:"))
  846. self.useColumn.Bind(wx.EVT_CHECKBOX, self.OnCheckColumn)
  847. fromColumnLabel = wx.StaticText(parent, id = wx.ID_ANY,
  848. label = labels[0])
  849. toColumnLabel = wx.StaticText(parent, id = wx.ID_ANY,
  850. label = labels[1])
  851. self.rgb_range_label = wx.StaticText(parent, id = wx.ID_ANY)
  852. self.layerSelect = LayerSelect(parent)
  853. self.sourceColumn = ColumnSelect(parent)
  854. self.fromColumn = ColumnSelect(parent)
  855. self.toColumn = ColumnSelect(parent)
  856. self.addColumn = wx.Button(parent, id = wx.ID_ANY,
  857. label = _('Add column'))
  858. self.addColumn.SetToolTipString(_("Add GRASSRGB column to current attribute table."))
  859. # layout
  860. inputSizer = wx.StaticBoxSizer(inputBox, wx.VERTICAL)
  861. vSizer = wx.GridBagSizer(hgap = 5, vgap = 5)
  862. row = 0
  863. vSizer.Add(cb_vl_label, pos = (row, 0),
  864. flag = wx.ALIGN_CENTER_VERTICAL)
  865. vSizer.Add(self.layerSelect, pos = (row, 1),
  866. flag = wx.ALIGN_CENTER_VERTICAL)
  867. row += 1
  868. vSizer.Add(cb_vc_label, pos = (row, 0),
  869. flag = wx.ALIGN_CENTER_VERTICAL)
  870. vSizer.Add(self.sourceColumn, pos = (row, 1),
  871. flag = wx.ALIGN_CENTER_VERTICAL)
  872. vSizer.Add(self.rgb_range_label, pos = (row, 2),
  873. flag = wx.ALIGN_CENTER_VERTICAL)
  874. row += 1
  875. if self.version7 and self.attributeType == 'color':
  876. vSizer.Add(self.useColumn, pos = (row, 0), span = (1, 2),
  877. flag = wx.ALIGN_CENTER_VERTICAL)
  878. row += 1
  879. vSizer.Add(fromColumnLabel, pos = (row, 0),
  880. flag = wx.ALIGN_CENTER_VERTICAL)
  881. vSizer.Add(self.fromColumn, pos = (row, 1),
  882. flag = wx.ALIGN_CENTER_VERTICAL)
  883. row += 1
  884. vSizer.Add(toColumnLabel, pos = (row, 0),
  885. flag = wx.ALIGN_CENTER_VERTICAL)
  886. vSizer.Add(self.toColumn, pos = (row, 1),
  887. flag = wx.ALIGN_CENTER_VERTICAL)
  888. vSizer.Add(self.addColumn, pos = (row, 2),
  889. flag = wx.ALIGN_CENTER_VERTICAL)
  890. inputSizer.Add(item = vSizer,
  891. flag = wx.ALIGN_CENTER_VERTICAL | wx.ALL | wx.EXPAND, border = 5)
  892. self.colorColumnSizer = vSizer
  893. return inputSizer
  894. def _doLayout(self):
  895. """Do main layout"""
  896. scrollPanel = scrolled.ScrolledPanel(parent = self.panel, id = wx.ID_ANY,
  897. style = wx.TAB_TRAVERSAL)
  898. scrollPanel.SetupScrolling()
  899. sizer = wx.BoxSizer(wx.VERTICAL)
  900. #
  901. # map selection
  902. #
  903. mapSelection = self._createMapSelection(parent = scrollPanel)
  904. sizer.Add(item = mapSelection, proportion = 0,
  905. flag = wx.ALL | wx.EXPAND, border = 5)
  906. #
  907. # manage extern tables
  908. #
  909. if self.version7 and self.attributeType == 'color':
  910. self.cp = wx.CollapsiblePane(scrollPanel, label = _("Import or export color table"),
  911. winid = wx.ID_ANY,
  912. style = wx.CP_DEFAULT_STYLE|wx.CP_NO_TLW_RESIZE)
  913. self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.OnPaneChanged, self.cp)
  914. self._createFileSelection(parent = self.cp.GetPane())
  915. sizer.Add(item = self.cp, proportion = 0,
  916. flag = wx.ALL | wx.EXPAND, border = 5)
  917. #
  918. # set vector attributes
  919. #
  920. vectorAttrb = self._createVectorAttrb(parent = scrollPanel)
  921. sizer.Add(item = vectorAttrb, proportion = 0,
  922. flag = wx.ALL | wx.EXPAND, border = 5)
  923. #
  924. # body & preview
  925. #
  926. bodySizer = self._createBody(parent = scrollPanel)
  927. sizer.Add(item = bodySizer, proportion = 1,
  928. flag = wx.ALL | wx.EXPAND, border = 5)
  929. scrollPanel.SetSizer(sizer)
  930. scrollPanel.Fit()
  931. #
  932. # buttons
  933. #
  934. btnSizer = self._createButtons(self.panel)
  935. mainsizer = wx.BoxSizer(wx.VERTICAL)
  936. mainsizer.Add(scrollPanel, proportion = 1, flag = wx.EXPAND | wx.ALL, border = 5)
  937. mainsizer.Add(item = wx.StaticLine(parent = self.panel, id = wx.ID_ANY,
  938. style = wx.LI_HORIZONTAL), proportion = 0,
  939. flag = wx.EXPAND | wx.ALL, border = 5)
  940. mainsizer.Add(item = btnSizer, proportion = 0,
  941. flag = wx.ALL | wx.ALIGN_RIGHT | wx.EXPAND, border = 5)
  942. self.panel.SetSizer(mainsizer)
  943. mainsizer.Layout()
  944. mainsizer.Fit(self.panel)
  945. self.Layout()
  946. def OnPaneChanged(self, event = None):
  947. # redo the layout
  948. self.Layout()
  949. # and also change the labels
  950. if self.cp.IsExpanded():
  951. self.cp.SetLabel('')
  952. else:
  953. self.cp.SetLabel(_("Import or export color table"))
  954. def CheckMapset(self):
  955. """Check if current vector is in current mapset"""
  956. if grass.find_file(name = self.inmap,
  957. element = 'vector')['mapset'] == grass.gisenv()['MAPSET']:
  958. return True
  959. else:
  960. return False
  961. def NoConnection(self, vectorName):
  962. dlg = wx.MessageDialog(parent = self,
  963. message = _("Database connection for vector map <%s> "
  964. "is not defined in DB file. Do you want to create and "
  965. "connect new attribute table?") % vectorName,
  966. caption = _("No database connection defined"),
  967. style = wx.YES_NO | wx.YES_DEFAULT | wx.ICON_QUESTION | wx.CENTRE)
  968. if dlg.ShowModal() == wx.ID_YES:
  969. dlg.Destroy()
  970. GUI(parent = self).ParseCommand(['v.db.addtable', 'map=' + self.inmap],
  971. completed = (self.CreateAttrTable, self.inmap, ''))
  972. else:
  973. dlg.Destroy()
  974. def OnCheckColumn(self, event):
  975. """Use color column instead of color table"""
  976. if self.useColumn.GetValue():
  977. self.properties['loadColumn'] = self.fromColumn.GetValue()
  978. self.properties['storeColumn'] = self.toColumn.GetValue()
  979. self.fromColumn.Enable(True)
  980. self.toColumn.Enable(True)
  981. self.colorTable = False
  982. if self.properties['loadColumn']:
  983. self.LoadTable()
  984. else:
  985. self.rulesPanel.Clear()
  986. else:
  987. self.properties['loadColumn'] = ''
  988. self.properties['storeColumn'] = ''
  989. self.fromColumn.Enable(False)
  990. self.toColumn.Enable(False)
  991. self.colorTable = True
  992. self.LoadTable()
  993. def EnableVectorAttributes(self, enable):
  994. """Enable/disable part of dialog connected with db"""
  995. for child in self.colorColumnSizer.GetChildren():
  996. child.GetWindow().Enable(enable)
  997. def DisableClearAll(self):
  998. """Enable, disable the whole dialog"""
  999. self.rulesPanel.Clear()
  1000. self.EnableVectorAttributes(False)
  1001. self.btnPreview.Enable(False)
  1002. self.btnOK.Enable(False)
  1003. self.btnApply.Enable(False)
  1004. self.preview.EraseMap()
  1005. def OnSelectionInput(self, event):
  1006. """Vector map selected"""
  1007. if event:
  1008. if self.inmap:
  1009. # switch to another map -> delete temporary column
  1010. self.DeleteTemporaryColumn()
  1011. self.inmap = event.GetString()
  1012. if self.version7 and self.attributeType == 'color':
  1013. self.loadRules.SetValue('')
  1014. self.saveRules.SetValue('')
  1015. if self.inmap:
  1016. if not grass.find_file(name = self.inmap, element = 'vector')['file']:
  1017. self.inmap = None
  1018. self.UpdateDialog()
  1019. def UpdateDialog(self):
  1020. """Update dialog after map selection"""
  1021. if not self.inmap:
  1022. self.DisableClearAll()
  1023. return
  1024. if not self.CheckMapset():
  1025. # v.colors doesn't need the map to be in current mapset
  1026. if not (self.version7 and self.attributeType == 'color'):
  1027. message = _("Selected map <%(map)s> is not in current mapset <%(mapset)s>. "
  1028. "Attribute table cannot be edited.") % \
  1029. { 'map' : self.inmap,
  1030. 'mapset' : grass.gisenv()['MAPSET'] }
  1031. wx.CallAfter(GMessage, parent = self, message = message)
  1032. self.DisableClearAll()
  1033. return
  1034. # check for db connection
  1035. self.dbInfo = VectorDBInfo(self.inmap)
  1036. enable = True
  1037. if not len(self.dbInfo.layers): # no connection
  1038. if not (self.version7 and self.attributeType == 'color'): # otherwise it doesn't matter
  1039. wx.CallAfter(self.NoConnection, self.inmap)
  1040. enable = False
  1041. for combo in (self.layerSelect, self.sourceColumn, self.fromColumn, self.toColumn):
  1042. combo.SetValue("")
  1043. combo.Clear()
  1044. for prop in ('sourceColumn', 'loadColumn', 'storeColumn'):
  1045. self.properties[prop] = ''
  1046. self.EnableVectorAttributes(False)
  1047. else: # db connection exist
  1048. # initialize layer selection combobox
  1049. self.EnableVectorAttributes(True)
  1050. self.layerSelect.InsertLayers(self.inmap)
  1051. # initialize attribute table for layer=1
  1052. self.properties['layer'] = self.layerSelect.GetString(0)
  1053. self.layerSelect.SetStringSelection(self.properties['layer'])
  1054. layer = int(self.properties['layer'])
  1055. self.properties['table'] = self.dbInfo.layers[layer]['table']
  1056. if self.attributeType == 'color':
  1057. self.AddTemporaryColumn(type = 'varchar(11)')
  1058. else:
  1059. self.AddTemporaryColumn(type = 'integer')
  1060. # initialize column selection comboboxes
  1061. self.OnLayerSelection(event = None)
  1062. if self.version7 and self.attributeType == 'color':
  1063. self.useColumn.SetValue(False)
  1064. self.OnCheckColumn(event = None)
  1065. self.useColumn.Enable(self.CheckMapset())
  1066. self.LoadTable()
  1067. self.btnPreview.Enable(enable)
  1068. self.btnOK.Enable(enable)
  1069. self.btnApply.Enable(enable)
  1070. def AddTemporaryColumn(self, type):
  1071. """Add temporary column to not overwrite the original values,
  1072. need to be deleted when closing dialog and unloading map
  1073. :param type: type of column (e.g. vachar(11))"""
  1074. if not self.CheckMapset():
  1075. return
  1076. # because more than one dialog with the same map can be opened we must test column name and
  1077. # create another one
  1078. while self.properties['tmpColumn'] in self.dbInfo.GetTableDesc(self.properties['table']).keys():
  1079. name, idx = self.properties['tmpColumn'].split('_')
  1080. idx = int(idx)
  1081. idx += 1
  1082. self.properties['tmpColumn'] = name + '_' + str(idx)
  1083. if self.version7:
  1084. modul = 'v.db.addcolumn'
  1085. else:
  1086. modul = 'v.db.addcol'
  1087. ret = RunCommand(modul,
  1088. parent = self,
  1089. map = self.inmap,
  1090. layer = self.properties['layer'],
  1091. column = '%s %s' % (self.properties['tmpColumn'], type))
  1092. def DeleteTemporaryColumn(self):
  1093. """Delete temporary column"""
  1094. if not self.CheckMapset():
  1095. return
  1096. if self.inmap:
  1097. if self.version7:
  1098. modul = 'v.db.dropcolumn'
  1099. else:
  1100. modul = 'v.db.dropcol'
  1101. ret = RunCommand(modul,
  1102. map = self.inmap,
  1103. layer = self.properties['layer'],
  1104. column = self.properties['tmpColumn'])
  1105. def OnLayerSelection(self, event):
  1106. # reset choices in column selection comboboxes if layer changes
  1107. vlayer = int(self.layerSelect.GetStringSelection())
  1108. self.sourceColumn.InsertColumns(vector = self.inmap, layer = vlayer,
  1109. type = ['integer', 'double precision'], dbInfo = self.dbInfo,
  1110. excludeCols = ['tmpColumn'])
  1111. self.sourceColumn.SetValue('cat')
  1112. self.properties['sourceColumn'] = self.sourceColumn.GetValue()
  1113. if self.attributeType == 'color':
  1114. type = ['character']
  1115. else:
  1116. type = ['integer']
  1117. self.fromColumn.InsertColumns(vector = self.inmap, layer = vlayer, type = type,
  1118. dbInfo = self.dbInfo, excludeCols = ['tmpColumn'])
  1119. self.toColumn.InsertColumns(vector = self.inmap, layer = vlayer, type = type,
  1120. dbInfo = self.dbInfo, excludeCols = ['tmpColumn'])
  1121. v = self.columnsProp[self.attributeType]['name']
  1122. found = False
  1123. if v in self.fromColumn.GetColumns():
  1124. found = True
  1125. if found != wx.NOT_FOUND:
  1126. self.fromColumn.SetValue(v)
  1127. self.toColumn.SetValue(v)
  1128. self.properties['loadColumn'] = v
  1129. self.properties['storeColumn'] = v
  1130. else:
  1131. self.properties['loadColumn'] = ''
  1132. self.properties['storeColumn'] = ''
  1133. if event:
  1134. self.LoadTable()
  1135. self.Update()
  1136. def OnSourceColumnSelection(self, event):
  1137. self.properties['sourceColumn'] = event.GetString()
  1138. self.LoadTable()
  1139. def OnAddColumn(self, event):
  1140. """Add GRASS(RGB,SIZE,WIDTH) column if it doesn't exist"""
  1141. if self.columnsProp[self.attributeType]['name'] not in self.fromColumn.GetColumns():
  1142. if self.version7:
  1143. modul = 'v.db.addcolumn'
  1144. else:
  1145. modul = 'v.db.addcol'
  1146. ret = RunCommand(modul,
  1147. map = self.inmap,
  1148. layer = self.properties['layer'],
  1149. columns = '%s %s' % (self.columnsProp[self.attributeType]['name'],
  1150. self.columnsProp[self.attributeType]['type1']))
  1151. self.toColumn.InsertColumns(self.inmap, self.properties['layer'],
  1152. type = self.columnsProp[self.attributeType]['type2'])
  1153. self.toColumn.SetValue(self.columnsProp[self.attributeType]['name'])
  1154. self.properties['storeColumn'] = self.toColumn.GetValue()
  1155. self.LoadTable()
  1156. else:
  1157. GMessage(parent = self,
  1158. message = _("%s column already exists.") % \
  1159. self.columnsProp[self.attributeType]['name'])
  1160. def CreateAttrTable(self, dcmd, layer, params, propwin):
  1161. """Create attribute table"""
  1162. if dcmd:
  1163. cmd = utils.CmdToTuple(dcmd)
  1164. ret = RunCommand(cmd[0], **cmd[1])
  1165. if ret == 0:
  1166. self.OnSelectionInput(None)
  1167. return True
  1168. for combo in (self.layerSelect, self.sourceColumn, self.fromColumn, self.toColumn):
  1169. combo.SetValue("")
  1170. combo.Disable()
  1171. return False
  1172. def LoadTable(self):
  1173. """Load table"""
  1174. if self.colorTable:
  1175. ColorTable.LoadTable(self, mapType = 'vector')
  1176. else:
  1177. self.LoadRulesFromColumn()
  1178. def LoadRulesFromColumn(self):
  1179. """Load current column (GRASSRGB, size column)"""
  1180. self.rulesPanel.Clear()
  1181. if not self.properties['sourceColumn']:
  1182. self.preview.EraseMap()
  1183. return
  1184. busy = wx.BusyInfo(message = _("Please wait, loading data from attribute table..."),
  1185. parent = self)
  1186. wx.Yield()
  1187. columns = self.properties['sourceColumn']
  1188. if self.properties['loadColumn']:
  1189. columns += ',' + self.properties['loadColumn']
  1190. sep = ';'
  1191. if self.inmap:
  1192. outFile = tempfile.NamedTemporaryFile(mode = 'w+b')
  1193. ret = RunCommand('v.db.select',
  1194. quiet = True,
  1195. flags = 'c',
  1196. map = self.inmap,
  1197. layer = self.properties['layer'],
  1198. columns = columns,
  1199. sep = sep,
  1200. stdout = outFile)
  1201. else:
  1202. self.preview.EraseMap()
  1203. busy.Destroy()
  1204. return
  1205. outFile.seek(0)
  1206. i = 0
  1207. minim = maxim = 0.0
  1208. limit = 1000
  1209. colvallist = []
  1210. readvals = False
  1211. while True:
  1212. # os.linesep doesn't work here (MSYS)
  1213. record = outFile.readline().replace('\n', '')
  1214. if not record:
  1215. break
  1216. self.rulesPanel.ruleslines[i] = {}
  1217. if not self.properties['loadColumn']:
  1218. col1 = record
  1219. col2 = None
  1220. else:
  1221. col1, col2 = record.split(sep)
  1222. if float(col1) < minim:
  1223. minim = float(col1)
  1224. if float(col1) > maxim:
  1225. maxim = float(col1)
  1226. # color rules list should only have unique values of col1, not all records
  1227. if col1 not in colvallist:
  1228. self.rulesPanel.ruleslines[i]['value'] = col1
  1229. self.rulesPanel.ruleslines[i][self.attributeType] = col2
  1230. colvallist.append(col1)
  1231. i += 1
  1232. if i > limit and readvals == False:
  1233. dlg = wx.MessageDialog (parent = self, message = _(
  1234. "Number of loaded records reached %d, "
  1235. "displaying all the records will be time-consuming "
  1236. "and may lead to computer freezing, "
  1237. "do you still want to continue?") % i,
  1238. caption = _("Too many records"),
  1239. style = wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
  1240. if dlg.ShowModal() == wx.ID_YES:
  1241. readvals = True
  1242. dlg.Destroy()
  1243. else:
  1244. busy.Destroy()
  1245. dlg.Destroy()
  1246. self.updateColumn = False
  1247. return
  1248. self.rulesPanel.AddRules(i, start = True)
  1249. ret = self.rulesPanel.LoadRules()
  1250. self.properties['min'], self.properties['max'] = minim, maxim
  1251. self.SetRangeLabel()
  1252. if ret:
  1253. self.OnPreview()
  1254. else:
  1255. self.rulesPanel.Clear()
  1256. busy.Destroy()
  1257. def SetRangeLabel(self):
  1258. """Set labels with info about attribute column range"""
  1259. if self.properties['sourceColumn']:
  1260. ctype = self.dbInfo.GetTableDesc(self.properties['table'])[self.properties['sourceColumn']]['ctype']
  1261. else:
  1262. ctype = int
  1263. range = ''
  1264. if self.properties['min'] or self.properties['max']:
  1265. if ctype == float:
  1266. range = "%s: %.1f - %.1f)" % (_("range"),
  1267. self.properties['min'], self.properties['max'])
  1268. elif ctype == int:
  1269. range = "%s: %d - %d)" % (_("range"),
  1270. self.properties['min'], self.properties['max'])
  1271. if range:
  1272. if self.colorTable:
  1273. self.cr_label.SetLabel(_("Enter vector attribute values or percents %s:") % range)
  1274. else:
  1275. self.cr_label.SetLabel(_("Enter vector attribute values %s:") % range)
  1276. else:
  1277. if self.colorTable:
  1278. self.cr_label.SetLabel(_("Enter vector attribute values or percents:"))
  1279. else:
  1280. self.cr_label.SetLabel(_("Enter vector attribute values:"))
  1281. def OnFromColSelection(self, event):
  1282. """Selection in combobox (for loading values) changed"""
  1283. self.properties['loadColumn'] = event.GetString()
  1284. self.LoadTable()
  1285. def OnToColSelection(self, event):
  1286. """Selection in combobox (for storing values) changed"""
  1287. self.properties['storeColumn'] = event.GetString()
  1288. def OnPreview(self, event = None, tmp = True):
  1289. """Update preview (based on computational region)"""
  1290. if self.colorTable:
  1291. self.OnTablePreview(tmp)
  1292. else:
  1293. self.OnColumnPreview()
  1294. def OnTablePreview(self, tmp):
  1295. """Update preview (based on computational region)"""
  1296. if not self.inmap:
  1297. self.preview.EraseMap()
  1298. return
  1299. ltype = 'vector'
  1300. cmdlist = ['d.vect',
  1301. 'map=%s' % self.inmap]
  1302. # find existing color table and copy to temp file
  1303. try:
  1304. name, mapset = self.inmap.split('@')
  1305. except ValueError:
  1306. name = self.inmap
  1307. mapset = grass.find_file(self.inmap, element = 'cell')['mapset']
  1308. if not mapset:
  1309. return
  1310. old_colrtable = None
  1311. if mapset == grass.gisenv()['MAPSET']:
  1312. old_colrtable = grass.find_file(name = 'colr', element = os.path.join('vector', name))['file']
  1313. else:
  1314. old_colrtable = grass.find_file(name = name, element = os.path.join('vcolr2', mapset))['file']
  1315. if old_colrtable:
  1316. colrtemp = utils.GetTempfile()
  1317. shutil.copyfile(old_colrtable, colrtemp)
  1318. ColorTable.DoPreview(self, ltype, cmdlist)
  1319. # restore previous color table
  1320. if tmp:
  1321. if old_colrtable:
  1322. shutil.copyfile(colrtemp, old_colrtable)
  1323. os.remove(colrtemp)
  1324. else:
  1325. RunCommand('v.colors',
  1326. parent = self,
  1327. flags = 'r',
  1328. map = self.inmap)
  1329. def OnColumnPreview(self):
  1330. """Update preview (based on computational region)"""
  1331. if not self.inmap or not self.properties['tmpColumn']:
  1332. self.preview.EraseMap()
  1333. return
  1334. cmdlist = ['d.vect',
  1335. 'map=%s' % self.inmap,
  1336. 'type=point,line,boundary,area']
  1337. if self.attributeType == 'color':
  1338. cmdlist.append('rgb_column=%s' % self.properties['tmpColumn'])
  1339. elif self.attributeType == 'size':
  1340. cmdlist.append('size_column=%s' % self.properties['tmpColumn'])
  1341. elif self.attributeType == 'width':
  1342. cmdlist.append('width_column=%s' % self.properties['tmpColumn'])
  1343. ltype = 'vector'
  1344. ColorTable.DoPreview(self, ltype, cmdlist)
  1345. def OnHelp(self, event):
  1346. """Show GRASS manual page"""
  1347. cmd = 'v.colors'
  1348. ColorTable.RunHelp(self, cmd = cmd)
  1349. def UseAttrColumn(self, useAttrColumn):
  1350. """Find layers and apply the changes in d.vect command"""
  1351. layers = self.layerTree.FindItemByData(key = 'name', value = self.inmap)
  1352. if not layers:
  1353. return
  1354. for layer in layers:
  1355. if self.layerTree.GetLayerInfo(layer, key = 'type') != 'vector':
  1356. continue
  1357. cmdlist = self.layerTree.GetLayerInfo(layer, key = 'maplayer').GetCmd()
  1358. if self.attributeType == 'color':
  1359. if useAttrColumn:
  1360. cmdlist[1].update({'rgb_column': self.properties['storeColumn']})
  1361. else:
  1362. cmdlist[1].pop('rgb_column', None)
  1363. elif self.attributeType == 'size':
  1364. cmdlist[1].update({'size_column': self.properties['storeColumn']})
  1365. elif self.attributeType == 'width':
  1366. cmdlist[1].update({'width_column' :self.properties['storeColumn']})
  1367. self.layerTree.SetLayerInfo(layer, key = 'cmd', value = cmdlist)
  1368. def CreateColorTable(self, tmp = False):
  1369. """Create color rules (color table or color column)"""
  1370. if self.colorTable:
  1371. ret = ColorTable.CreateColorTable(self)
  1372. else:
  1373. if self.updateColumn:
  1374. ret = self.UpdateColorColumn(tmp)
  1375. else:
  1376. ret = True
  1377. return ret
  1378. def UpdateColorColumn(self, tmp):
  1379. """Creates color table
  1380. :return: True on success
  1381. :return: False on failure
  1382. """
  1383. rulestxt = ''
  1384. for rule in self.rulesPanel.ruleslines.itervalues():
  1385. if 'value' not in rule: # skip empty rules
  1386. break
  1387. if tmp:
  1388. rgb_col = self.properties['tmpColumn']
  1389. else:
  1390. rgb_col = self.properties['storeColumn']
  1391. if not self.properties['storeColumn']:
  1392. GMessage(parent = self.parent,
  1393. message = _("Please select column to save values to."))
  1394. rulestxt += "UPDATE %s SET %s='%s' WHERE %s ;\n" % (self.properties['table'],
  1395. rgb_col,
  1396. rule[self.attributeType],
  1397. rule['value'])
  1398. if not rulestxt:
  1399. return False
  1400. gtemp = utils.GetTempfile()
  1401. output = open(gtemp, "w")
  1402. try:
  1403. output.write(rulestxt)
  1404. finally:
  1405. output.close()
  1406. RunCommand('db.execute',
  1407. parent = self,
  1408. input = gtemp)
  1409. return True
  1410. def OnCancel(self, event):
  1411. """Do not apply any changes and close the dialog"""
  1412. self.DeleteTemporaryColumn()
  1413. self.Map.Clean()
  1414. self.Destroy()
  1415. def OnApply(self, event):
  1416. """Apply selected color table
  1417. :return: True on success otherwise False
  1418. """
  1419. if self.colorTable:
  1420. self.UseAttrColumn(False)
  1421. else:
  1422. if not self.properties['storeColumn']:
  1423. GError(_("No color column defined. Operation canceled."),
  1424. parent = self)
  1425. return
  1426. self.UseAttrColumn(True)
  1427. return ColorTable.OnApply(self, event)
  1428. class ThematicVectorTable(VectorColorTable):
  1429. def __init__(self, parent, vectorType, **kwargs):
  1430. """Dialog for interactively entering color/size rules
  1431. for vector maps for thematic mapping in nviz"""
  1432. self.vectorType = vectorType
  1433. VectorColorTable.__init__(self, parent = parent, **kwargs)
  1434. self.SetTitle(_("Thematic mapping for vector map in 3D view"))
  1435. def _initLayer(self):
  1436. """Set initial layer when opening dialog"""
  1437. self.inmap = self.parent.GetLayerData(nvizType = 'vector', nameOnly = True)
  1438. self.selectionInput.SetValue(self.inmap)
  1439. self.selectionInput.Disable()
  1440. def OnApply(self, event):
  1441. """Apply selected color table
  1442. :return: True on success otherwise False
  1443. """
  1444. ret = self.CreateColorTable()
  1445. if not ret:
  1446. GMessage(parent = self, message = _("No valid color rules given."))
  1447. data = self.parent.GetLayerData(nvizType = 'vector')
  1448. data['vector']['points']['thematic']['layer'] = int(self.properties['layer'])
  1449. value = None
  1450. if self.properties['storeColumn']:
  1451. value = self.properties['storeColumn']
  1452. if not self.colorTable:
  1453. if self.attributeType == 'color':
  1454. data['vector'][self.vectorType]['thematic']['rgbcolumn'] = value
  1455. else:
  1456. data['vector'][self.vectorType]['thematic']['sizecolumn'] = value
  1457. else:
  1458. if self.attributeType == 'color':
  1459. data['vector'][self.vectorType]['thematic']['rgbcolumn'] = None
  1460. else:
  1461. data['vector'][self.vectorType]['thematic']['sizecolumn'] = None
  1462. data['vector'][self.vectorType]['thematic']['update'] = None
  1463. from nviz.main import haveNviz
  1464. if haveNviz:
  1465. from nviz.mapwindow import wxUpdateProperties
  1466. event = wxUpdateProperties(data = data)
  1467. wx.PostEvent(self.parent.mapWindow, event)
  1468. self.parent.mapWindow.Refresh(False)
  1469. return ret
  1470. class BufferedWindow(wx.Window):
  1471. """A Buffered window class"""
  1472. def __init__(self, parent, id,
  1473. style = wx.NO_FULL_REPAINT_ON_RESIZE,
  1474. Map = None, **kwargs):
  1475. wx.Window.__init__(self, parent, id, style = style, **kwargs)
  1476. self.parent = parent
  1477. self.Map = Map
  1478. # re-render the map from GRASS or just redraw image
  1479. self.render = True
  1480. # indicates whether or not a resize event has taken place
  1481. self.resize = False
  1482. #
  1483. # event bindings
  1484. #
  1485. self.Bind(wx.EVT_PAINT, self.OnPaint)
  1486. self.Bind(wx.EVT_IDLE, self.OnIdle)
  1487. self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: None)
  1488. #
  1489. # render output objects
  1490. #
  1491. # image file to be rendered
  1492. self.mapfile = None
  1493. # wx.Image object (self.mapfile)
  1494. self.img = None
  1495. self.pdc = wx.PseudoDC()
  1496. # will store an off screen empty bitmap for saving to file
  1497. self._Buffer = None
  1498. # make sure that extents are updated at init
  1499. self.Map.region = self.Map.GetRegion()
  1500. self.Map.SetRegion()
  1501. def Draw(self, pdc, img = None, pdctype = 'image'):
  1502. """Draws preview or clears window"""
  1503. pdc.BeginDrawing()
  1504. Debug.msg (3, "BufferedWindow.Draw(): pdctype=%s" % (pdctype))
  1505. if pdctype == 'clear': # erase the display
  1506. bg = wx.WHITE_BRUSH
  1507. pdc.SetBackground(bg)
  1508. pdc.Clear()
  1509. self.Refresh()
  1510. pdc.EndDrawing()
  1511. return
  1512. if pdctype == 'image' and img:
  1513. bg = wx.TRANSPARENT_BRUSH
  1514. pdc.SetBackground(bg)
  1515. bitmap = wx.BitmapFromImage(img)
  1516. w, h = bitmap.GetSize()
  1517. pdc.DrawBitmap(bitmap, 0, 0, True) # draw the composite map
  1518. pdc.EndDrawing()
  1519. self.Refresh()
  1520. def OnPaint(self, event):
  1521. """Draw pseudo DC to buffer"""
  1522. self._Buffer = wx.EmptyBitmap(self.Map.width, self.Map.height)
  1523. dc = wx.BufferedPaintDC(self, self._Buffer)
  1524. # use PrepareDC to set position correctly
  1525. # probably does nothing, removed from wxPython 2.9
  1526. # self.PrepareDC(dc)
  1527. # we need to clear the dc BEFORE calling PrepareDC
  1528. bg = wx.Brush(self.GetBackgroundColour())
  1529. dc.SetBackground(bg)
  1530. dc.Clear()
  1531. # create a clipping rect from our position and size
  1532. # and the Update Region
  1533. rgn = self.GetUpdateRegion()
  1534. r = rgn.GetBox()
  1535. # draw to the dc using the calculated clipping rect
  1536. self.pdc.DrawToDCClipped(dc, r)
  1537. def OnSize(self, event):
  1538. """Init image size to match window size"""
  1539. # set size of the input image
  1540. self.Map.width, self.Map.height = self.GetClientSize()
  1541. # Make new off screen bitmap: this bitmap will always have the
  1542. # current drawing in it, so it can be used to save the image to
  1543. # a file, or whatever.
  1544. self._Buffer = wx.EmptyBitmap(self.Map.width, self.Map.height)
  1545. # get the image to be rendered
  1546. self.img = self.GetImage()
  1547. # update map display
  1548. if self.img and self.Map.width + self.Map.height > 0: # scale image during resize
  1549. self.img = self.img.Scale(self.Map.width, self.Map.height)
  1550. self.render = False
  1551. self.UpdatePreview()
  1552. # re-render image on idle
  1553. self.resize = True
  1554. def OnIdle(self, event):
  1555. """Only re-render a preview image from GRASS during
  1556. idle time instead of multiple times during resizing.
  1557. """
  1558. if self.resize:
  1559. self.render = True
  1560. self.UpdatePreview()
  1561. event.Skip()
  1562. def GetImage(self):
  1563. """Converts files to wx.Image"""
  1564. if self.Map.mapfile and os.path.isfile(self.Map.mapfile) and \
  1565. os.path.getsize(self.Map.mapfile):
  1566. img = wx.Image(self.Map.mapfile, wx.BITMAP_TYPE_ANY)
  1567. else:
  1568. img = None
  1569. return img
  1570. def UpdatePreview(self, img = None):
  1571. """Update canvas if window changes geometry"""
  1572. Debug.msg (2, "BufferedWindow.UpdatePreview(%s): render=%s" % (img, self.render))
  1573. oldfont = ""
  1574. oldencoding = ""
  1575. if self.render:
  1576. # extent is taken from current map display
  1577. try:
  1578. self.Map.region = copy.deepcopy(self.parent.parent.GetLayerTree().GetMap().GetCurrentRegion())
  1579. except AttributeError:
  1580. self.Map.region = self.Map.GetRegion()
  1581. # render new map images
  1582. self.mapfile = self.Map.Render(force = self.render)
  1583. self.img = self.GetImage()
  1584. self.resize = False
  1585. if not self.img:
  1586. return
  1587. # paint images to PseudoDC
  1588. self.pdc.Clear()
  1589. self.pdc.RemoveAll()
  1590. # draw map image background
  1591. self.Draw(self.pdc, self.img, pdctype = 'image')
  1592. self.resize = False
  1593. def EraseMap(self):
  1594. """Erase preview"""
  1595. self.Draw(self.pdc, pdctype = 'clear')