colorrules.py 71 KB

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