dialogs.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821
  1. """
  2. @package vdigit.dialogs
  3. @brief wxGUI vector digitizer dialogs
  4. Classes:
  5. - dialogs::VDigitCategoryDialog
  6. - dialogs::CategoryListCtrl
  7. - dialogs::VDigitZBulkDialog
  8. - dialogs::VDigitDuplicatesDialog
  9. - dialogs::CheckListFeature
  10. (C) 2007-2011 by the GRASS Development Team
  11. This program is free software under the GNU General Public License
  12. (>=v2). Read the file COPYING that comes with GRASS for details.
  13. @author Martin Landa <landa.martin gmail.com>
  14. """
  15. import sys
  16. import copy
  17. import six
  18. import wx
  19. import wx.lib.mixins.listctrl as listmix
  20. from core.gcmd import RunCommand, GError
  21. from core.debug import Debug
  22. from core.settings import UserSettings
  23. from core.utils import _
  24. from gui_core.wrap import SpinCtrl, Button, StaticText, \
  25. StaticBox, Menu
  26. class VDigitCategoryDialog(wx.Dialog, listmix.ColumnSorterMixin):
  27. def __init__(self, parent, title,
  28. vectorName, query=None, cats=None,
  29. style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs):
  30. """Dialog used to display/modify categories of vector objects
  31. :param parent:
  32. :param title: dialog title
  33. :param query: {coordinates, qdist} - used by v.edit/v.what
  34. :param cats: directory of lines (layer/categories) - used by vdigit
  35. :param style: dialog style
  36. """
  37. self.parent = parent # map window class instance
  38. self.digit = parent.digit
  39. # map name
  40. self.vectorName = vectorName
  41. # line : {layer: [categories]}
  42. self.cats = {}
  43. # do not display dialog if no line is found (-> self.cats)
  44. if cats is None:
  45. if self._getCategories(query[0], query[1]) == 0 or not self.line:
  46. Debug.msg(3, "VDigitCategoryDialog(): nothing found!")
  47. else:
  48. self.cats = cats
  49. for line in cats.keys():
  50. for layer in cats[line].keys():
  51. self.cats[line][layer] = list(cats[line][layer])
  52. layers = []
  53. for layer in self.digit.GetLayers():
  54. layers.append(str(layer))
  55. # make copy of cats (used for 'reload')
  56. self.cats_orig = copy.deepcopy(self.cats)
  57. wx.Dialog.__init__(self, parent=self.parent, id=wx.ID_ANY, title=title,
  58. style=style, **kwargs)
  59. # list of categories
  60. box = StaticBox(
  61. parent=self, id=wx.ID_ANY, label=" %s " %
  62. _("List of categories - right-click to delete"))
  63. listSizer = wx.StaticBoxSizer(box, wx.VERTICAL)
  64. self.list = CategoryListCtrl(parent=self, id=wx.ID_ANY,
  65. style=wx.LC_REPORT |
  66. wx.BORDER_NONE |
  67. wx.LC_SORT_ASCENDING |
  68. wx.LC_HRULES |
  69. wx.LC_VRULES)
  70. # sorter
  71. self.fid = self.cats.keys()[0]
  72. self.itemDataMap = self.list.Populate(self.cats[self.fid])
  73. listmix.ColumnSorterMixin.__init__(self, 2)
  74. self.fidMulti = wx.Choice(parent=self, id=wx.ID_ANY,
  75. size=(150, -1))
  76. self.fidMulti.Bind(wx.EVT_CHOICE, self.OnFeature)
  77. self.fidText = StaticText(parent=self, id=wx.ID_ANY)
  78. if len(self.cats.keys()) == 1:
  79. self.fidMulti.Show(False)
  80. self.fidText.SetLabel(str(self.fid))
  81. else:
  82. self.fidText.Show(False)
  83. choices = []
  84. for fid in self.cats.keys():
  85. choices.append(str(fid))
  86. self.fidMulti.SetItems(choices)
  87. self.fidMulti.SetSelection(0)
  88. listSizer.Add(self.list, proportion=1, flag=wx.EXPAND)
  89. # add new category
  90. box = StaticBox(parent=self, id=wx.ID_ANY,
  91. label=" %s " % _("Add new category"))
  92. addSizer = wx.StaticBoxSizer(box, wx.VERTICAL)
  93. flexSizer = wx.FlexGridSizer(cols=5, hgap=5, vgap=5)
  94. flexSizer.AddGrowableCol(3)
  95. layerNewTxt = StaticText(parent=self, id=wx.ID_ANY,
  96. label="%s:" % _("Layer"))
  97. self.layerNew = wx.Choice(parent=self, id=wx.ID_ANY, size=(75, -1),
  98. choices=layers)
  99. if len(layers) > 0:
  100. self.layerNew.SetSelection(0)
  101. catNewTxt = StaticText(parent=self, id=wx.ID_ANY,
  102. label="%s:" % _("Category"))
  103. try:
  104. newCat = max(self.cats[self.fid][1]) + 1
  105. except KeyError:
  106. newCat = 1
  107. self.catNew = SpinCtrl(parent=self, id=wx.ID_ANY, size=(75, -1),
  108. initial=newCat, min=0, max=1e9)
  109. btnAddCat = Button(self, wx.ID_ADD)
  110. flexSizer.Add(layerNewTxt, proportion=0,
  111. flag=wx.FIXED_MINSIZE | wx.ALIGN_CENTER_VERTICAL)
  112. flexSizer.Add(self.layerNew, proportion=0,
  113. flag=wx.FIXED_MINSIZE | wx.ALIGN_CENTER_VERTICAL)
  114. flexSizer.Add(catNewTxt, proportion=0,
  115. flag=wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.LEFT,
  116. border=10)
  117. flexSizer.Add(self.catNew, proportion=0,
  118. flag=wx.FIXED_MINSIZE | wx.ALIGN_CENTER_VERTICAL)
  119. flexSizer.Add(btnAddCat, proportion=0,
  120. flag=wx.EXPAND | wx.ALIGN_RIGHT | wx.FIXED_MINSIZE)
  121. addSizer.Add(
  122. flexSizer,
  123. proportion=1,
  124. flag=wx.ALL | wx.EXPAND,
  125. border=5)
  126. # buttons
  127. btnApply = Button(self, wx.ID_APPLY)
  128. btnApply.SetToolTip(_("Apply changes"))
  129. btnCancel = Button(self, wx.ID_CANCEL)
  130. btnCancel.SetToolTip(_("Ignore changes and close dialog"))
  131. btnOk = Button(self, wx.ID_OK)
  132. btnOk.SetToolTip(_("Apply changes and close dialog"))
  133. btnOk.SetDefault()
  134. # sizers
  135. btnSizer = wx.StdDialogButtonSizer()
  136. btnSizer.AddButton(btnCancel)
  137. # btnSizer.AddButton(btnReload)
  138. # btnSizer.SetNegativeButton(btnReload)
  139. btnSizer.AddButton(btnApply)
  140. btnSizer.AddButton(btnOk)
  141. btnSizer.Realize()
  142. mainSizer = wx.BoxSizer(wx.VERTICAL)
  143. mainSizer.Add(listSizer, proportion=1,
  144. flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5)
  145. mainSizer.Add(addSizer, proportion=0,
  146. flag=wx.EXPAND | wx.ALIGN_CENTER |
  147. wx.LEFT | wx.RIGHT | wx.BOTTOM, border=5)
  148. fidSizer = wx.BoxSizer(wx.HORIZONTAL)
  149. fidSizer.Add(StaticText(parent=self, id=wx.ID_ANY,
  150. label=_("Feature id:")),
  151. proportion=0, border=5,
  152. flag=wx.ALIGN_CENTER_VERTICAL)
  153. fidSizer.Add(self.fidMulti, proportion=0,
  154. flag=wx.EXPAND | wx.ALL, border=5)
  155. fidSizer.Add(self.fidText, proportion=0,
  156. flag=wx.EXPAND | wx.ALL, border=5)
  157. mainSizer.Add(fidSizer, proportion=0,
  158. flag=wx.EXPAND | wx.ALL, border=5)
  159. mainSizer.Add(btnSizer, proportion=0,
  160. flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5)
  161. self.SetSizer(mainSizer)
  162. mainSizer.Fit(self)
  163. self.SetAutoLayout(True)
  164. # set min size for dialog
  165. self.SetMinSize(self.GetBestSize())
  166. # bindings
  167. btnApply.Bind(wx.EVT_BUTTON, self.OnApply)
  168. btnOk.Bind(wx.EVT_BUTTON, self.OnOK)
  169. btnAddCat.Bind(wx.EVT_BUTTON, self.OnAddCat)
  170. btnCancel.Bind(wx.EVT_BUTTON, self.OnCancel)
  171. self.Bind(wx.EVT_CLOSE, lambda evt: self.Hide())
  172. # list
  173. self.list.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnRightUp) # wxMSW
  174. self.list.Bind(wx.EVT_RIGHT_UP, self.OnRightUp) # wxGTK
  175. self.Bind(wx.EVT_LIST_BEGIN_LABEL_EDIT, self.OnBeginEdit, self.list)
  176. self.Bind(wx.EVT_LIST_END_LABEL_EDIT, self.OnEndEdit, self.list)
  177. self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick, self.list)
  178. def GetListCtrl(self):
  179. """Used by ColumnSorterMixin
  180. """
  181. return self.list
  182. def OnColClick(self, event):
  183. """Click on column header (order by)
  184. """
  185. event.Skip()
  186. def OnBeginEdit(self, event):
  187. """Editing of item started
  188. """
  189. event.Allow()
  190. def OnEndEdit(self, event):
  191. """Finish editing of item
  192. """
  193. itemIndex = event.GetIndex()
  194. layerOld = int(self.list.GetItem(itemIndex, 0).GetText())
  195. catOld = int(self.list.GetItem(itemIndex, 1).GetText())
  196. if event.GetColumn() == 0:
  197. layerNew = int(event.GetLabel())
  198. catNew = catOld
  199. else:
  200. layerNew = layerOld
  201. catNew = int(event.GetLabel())
  202. try:
  203. if layerNew not in self.cats[self.fid].keys():
  204. self.cats[self.fid][layerNew] = []
  205. self.cats[self.fid][layerNew].append(catNew)
  206. self.cats[self.fid][layerOld].remove(catOld)
  207. except:
  208. event.Veto()
  209. self.list.SetStringItem(itemIndex, 0, str(layerNew))
  210. self.list.SetStringItem(itemIndex, 1, str(catNew))
  211. dlg = wx.MessageDialog(
  212. self, _(
  213. "Unable to add new layer/category <%(layer)s/%(category)s>.\n"
  214. "Layer and category number must be integer.\n"
  215. "Layer number must be greater than zero.") %
  216. {
  217. 'layer': self.layerNew.GetStringSelection(), 'category': str(
  218. self.catNew.GetValue())}, _("Error"), wx.OK | wx.ICON_ERROR)
  219. dlg.ShowModal()
  220. dlg.Destroy()
  221. return False
  222. def OnRightDown(self, event):
  223. """Mouse right button down
  224. """
  225. x = event.GetX()
  226. y = event.GetY()
  227. item, flags = self.list.HitTest((x, y))
  228. if item != wx.NOT_FOUND and \
  229. flags & wx.LIST_HITTEST_ONITEM:
  230. self.list.Select(item)
  231. event.Skip()
  232. def OnRightUp(self, event):
  233. """Mouse right button up
  234. """
  235. if not hasattr(self, "popupID1"):
  236. self.popupID1 = wx.NewId()
  237. self.popupID2 = wx.NewId()
  238. self.popupID3 = wx.NewId()
  239. self.Bind(wx.EVT_MENU, self.OnItemDelete, id=self.popupID1)
  240. self.Bind(wx.EVT_MENU, self.OnItemDeleteAll, id=self.popupID2)
  241. self.Bind(wx.EVT_MENU, self.OnReload, id=self.popupID3)
  242. # generate popup-menu
  243. menu = Menu()
  244. menu.Append(self.popupID1, _("Delete selected"))
  245. if self.list.GetFirstSelected() == -1:
  246. menu.Enable(self.popupID1, False)
  247. menu.Append(self.popupID2, _("Delete all"))
  248. menu.AppendSeparator()
  249. menu.Append(self.popupID3, _("Reload"))
  250. self.PopupMenu(menu)
  251. menu.Destroy()
  252. def OnItemSelected(self, event):
  253. """Item selected
  254. """
  255. event.Skip()
  256. def OnItemDelete(self, event):
  257. """Delete selected item(s) from the list (layer/category pair)
  258. """
  259. item = self.list.GetFirstSelected()
  260. while item != -1:
  261. layer = int(self.list.GetItem(item, 0).GetText())
  262. cat = int(self.list.GetItem(item, 1).GetText())
  263. self.list.DeleteItem(item)
  264. self.cats[self.fid][layer].remove(cat)
  265. item = self.list.GetFirstSelected()
  266. event.Skip()
  267. def OnItemDeleteAll(self, event):
  268. """Delete all items from the list
  269. """
  270. self.list.DeleteAllItems()
  271. self.cats[self.fid] = {}
  272. event.Skip()
  273. def OnFeature(self, event):
  274. """Feature id changed (on duplicates)
  275. """
  276. self.fid = int(event.GetString())
  277. self.itemDataMap = self.list.Populate(self.cats[self.fid],
  278. update=True)
  279. try:
  280. newCat = max(self.cats[self.fid][1]) + 1
  281. except KeyError:
  282. newCat = 1
  283. self.catNew.SetValue(newCat)
  284. event.Skip()
  285. def _getCategories(self, coords, qdist):
  286. """Get layer/category pairs for all available
  287. layers
  288. :return: True line found or False if not found
  289. """
  290. ret = RunCommand('v.what',
  291. parent=self,
  292. quiet=True,
  293. map=self.vectorName,
  294. east_north='%f,%f' %
  295. (float(coords[0]), float(coords[1])),
  296. distance=qdist)
  297. if not ret:
  298. return False
  299. for item in ret.splitlines():
  300. litem = item.lower()
  301. if "id:" in litem: # get line id
  302. self.line = int(item.split(':')[1].strip())
  303. elif "layer:" in litem: # add layer
  304. layer = int(item.split(':')[1].strip())
  305. if layer not in self.cats.keys():
  306. self.cats[layer] = []
  307. elif "category:" in litem: # add category
  308. self.cats[layer].append(int(item.split(':')[1].strip()))
  309. return True
  310. def OnReload(self, event):
  311. """Reload button pressed
  312. """
  313. # restore original list
  314. self.cats = copy.deepcopy(self.cats_orig)
  315. # polulate list
  316. self.itemDataMap = self.list.Populate(self.cats[self.fid],
  317. update=True)
  318. event.Skip()
  319. def OnCancel(self, event):
  320. """Cancel button pressed
  321. """
  322. self.parent.parent.dialogs['category'] = None
  323. if self.digit:
  324. self.digit.GetDisplay().SetSelected([])
  325. self.parent.UpdateMap(render=False)
  326. else:
  327. self.parent.parent.OnRender(None)
  328. self.Close()
  329. def OnApply(self, event):
  330. """Apply button pressed
  331. """
  332. for fid in self.cats.keys():
  333. newfid = self.ApplyChanges(fid)
  334. if fid == self.fid and newfid > 0:
  335. self.fid = newfid
  336. def ApplyChanges(self, fid):
  337. """Apply changes
  338. :param fid: feature id
  339. """
  340. cats = self.cats[fid]
  341. cats_orig = self.cats_orig[fid]
  342. # action : (catsFrom, catsTo)
  343. check = {'catadd': (cats, cats_orig),
  344. 'catdel': (cats_orig, cats)}
  345. newfid = -1
  346. # add/delete new category
  347. for action, catsCurr in six.iteritems(check):
  348. for layer in catsCurr[0].keys():
  349. catList = []
  350. for cat in catsCurr[0][layer]:
  351. if layer not in catsCurr[1].keys() or \
  352. cat not in catsCurr[1][layer]:
  353. catList.append(cat)
  354. if catList != []:
  355. if action == 'catadd':
  356. add = True
  357. else:
  358. add = False
  359. newfid = self.digit.SetLineCats(fid, layer,
  360. catList, add)
  361. if len(self.cats.keys()) == 1:
  362. self.fidText.SetLabel("%d" % newfid)
  363. else:
  364. choices = self.fidMulti.GetItems()
  365. choices[choices.index(str(fid))] = str(newfid)
  366. self.fidMulti.SetItems(choices)
  367. self.fidMulti.SetStringSelection(str(newfid))
  368. self.cats[newfid] = self.cats[fid]
  369. del self.cats[fid]
  370. fid = newfid
  371. if self.fid < 0:
  372. wx.MessageBox(
  373. parent=self,
  374. message=_("Unable to update vector map."),
  375. caption=_("Error"),
  376. style=wx.OK | wx.ICON_ERROR)
  377. self.cats_orig[fid] = copy.deepcopy(cats)
  378. return newfid
  379. def OnOK(self, event):
  380. """OK button pressed
  381. """
  382. self.OnApply(event)
  383. self.OnCancel(event)
  384. def OnAddCat(self, event):
  385. """Button 'Add' new category pressed
  386. """
  387. try:
  388. layer = int(self.layerNew.GetStringSelection())
  389. cat = int(self.catNew.GetValue())
  390. if layer <= 0:
  391. raise ValueError
  392. except ValueError:
  393. GError(
  394. parent=self,
  395. message=_(
  396. "Unable to add new layer/category <%(layer)s/%(category)s>.\n"
  397. "Layer and category number must be integer.\n"
  398. "Layer number must be greater than zero.") % {
  399. 'layer': str(
  400. self.layerNew.GetValue()),
  401. 'category': str(
  402. self.catNew.GetValue())})
  403. return False
  404. if layer not in self.cats[self.fid].keys():
  405. self.cats[self.fid][layer] = []
  406. self.cats[self.fid][layer].append(cat)
  407. # reload list
  408. self.itemDataMap = self.list.Populate(self.cats[self.fid],
  409. update=True)
  410. # update category number for add
  411. self.catNew.SetValue(cat + 1)
  412. event.Skip()
  413. return True
  414. def GetLine(self):
  415. """Get id of selected line of 'None' if no line is selected
  416. """
  417. return self.cats.keys()
  418. def UpdateDialog(self, query=None, cats=None):
  419. """Update dialog
  420. :param query: {coordinates, distance} - v.what
  421. :param cats: directory layer/cats - vdigit
  422. :return: True if updated otherwise False
  423. """
  424. # line: {layer: [categories]}
  425. self.cats = {}
  426. # do not display dialog if no line is found (-> self.cats)
  427. if cats is None:
  428. ret = self._getCategories(query[0], query[1])
  429. else:
  430. self.cats = cats
  431. for line in cats.keys():
  432. for layer in cats[line].keys():
  433. self.cats[line][layer] = list(cats[line][layer])
  434. ret = 1
  435. if ret == 0 or len(self.cats.keys()) < 1:
  436. Debug.msg(3, "VDigitCategoryDialog(): nothing found!")
  437. return False
  438. # make copy of cats (used for 'reload')
  439. self.cats_orig = copy.deepcopy(self.cats)
  440. # polulate list
  441. self.fid = self.cats.keys()[0]
  442. self.itemDataMap = self.list.Populate(self.cats[self.fid],
  443. update=True)
  444. try:
  445. newCat = max(self.cats[self.fid][1]) + 1
  446. except KeyError:
  447. newCat = 1
  448. self.catNew.SetValue(newCat)
  449. if len(self.cats.keys()) == 1:
  450. self.fidText.Show(True)
  451. self.fidMulti.Show(False)
  452. self.fidText.SetLabel("%d" % self.fid)
  453. else:
  454. self.fidText.Show(False)
  455. self.fidMulti.Show(True)
  456. choices = []
  457. for fid in self.cats.keys():
  458. choices.append(str(fid))
  459. self.fidMulti.SetItems(choices)
  460. self.fidMulti.SetSelection(0)
  461. self.Layout()
  462. return True
  463. class CategoryListCtrl(wx.ListCtrl,
  464. listmix.ListCtrlAutoWidthMixin,
  465. listmix.TextEditMixin):
  466. def __init__(self, parent, id, pos=wx.DefaultPosition,
  467. size=wx.DefaultSize, style=0):
  468. """List of layers/categories"""
  469. self.parent = parent
  470. wx.ListCtrl.__init__(self, parent, id, pos, size, style)
  471. listmix.ListCtrlAutoWidthMixin.__init__(self)
  472. listmix.TextEditMixin.__init__(self)
  473. def Populate(self, cats, update=False):
  474. """Populate the list
  475. """
  476. itemData = {} # requested by sorter
  477. if not update:
  478. self.InsertColumn(0, _("Layer"))
  479. self.InsertColumn(1, _("Category"))
  480. else:
  481. self.DeleteAllItems()
  482. i = 1
  483. for layer in cats.keys():
  484. catsList = cats[layer]
  485. for cat in catsList:
  486. index = self.InsertStringItem(self.GetItemCount(), str(catsList[0]))
  487. self.SetStringItem(index, 0, str(layer))
  488. self.SetStringItem(index, 1, str(cat))
  489. self.SetItemData(index, i)
  490. itemData[i] = (str(layer), str(cat))
  491. i = i + 1
  492. if not update:
  493. self.SetColumnWidth(0, 100)
  494. self.SetColumnWidth(1, wx.LIST_AUTOSIZE)
  495. self.currentItem = 0
  496. return itemData
  497. class VDigitZBulkDialog(wx.Dialog):
  498. def __init__(self, parent, title, nselected,
  499. style=wx.DEFAULT_DIALOG_STYLE):
  500. """Dialog used for Z bulk-labeling tool
  501. """
  502. wx.Dialog.__init__(
  503. self,
  504. parent=parent,
  505. id=wx.ID_ANY,
  506. title=title,
  507. style=style)
  508. self.parent = parent # map window class instance
  509. # panel = wx.Panel(parent=self, id=wx.ID_ANY)
  510. border = wx.BoxSizer(wx.VERTICAL)
  511. txt = StaticText(
  512. parent=self,
  513. label=_("%d lines selected for z bulk-labeling") %
  514. nselected)
  515. border.Add(txt, proportion=0, flag=wx.ALL | wx.EXPAND, border=5)
  516. box = StaticBox(
  517. parent=self,
  518. id=wx.ID_ANY,
  519. label=" %s " %
  520. _("Set value"))
  521. sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
  522. flexSizer = wx.FlexGridSizer(cols=2, hgap=5, vgap=5)
  523. flexSizer.AddGrowableCol(0)
  524. # starting value
  525. txt = StaticText(parent=self,
  526. label=_("Starting value"))
  527. self.value = SpinCtrl(parent=self, id=wx.ID_ANY, size=(150, -1),
  528. initial=0,
  529. min=-1e6, max=1e6)
  530. flexSizer.Add(txt, proportion=0, flag=wx.ALIGN_CENTER_VERTICAL)
  531. flexSizer.Add(
  532. self.value,
  533. proportion=0,
  534. flag=wx.ALIGN_CENTER | wx.FIXED_MINSIZE)
  535. # step
  536. txt = StaticText(parent=self,
  537. label=_("Step"))
  538. self.step = SpinCtrl(parent=self, id=wx.ID_ANY, size=(150, -1),
  539. initial=0,
  540. min=0, max=1e6)
  541. flexSizer.Add(txt, proportion=0, flag=wx.ALIGN_CENTER_VERTICAL)
  542. flexSizer.Add(
  543. self.step,
  544. proportion=0,
  545. flag=wx.ALIGN_CENTER | wx.FIXED_MINSIZE)
  546. sizer.Add(
  547. flexSizer,
  548. proportion=1,
  549. flag=wx.ALL | wx.EXPAND,
  550. border=1)
  551. border.Add(sizer, proportion=1, flag=wx.ALL | wx.EXPAND, border=0)
  552. # buttons
  553. btnCancel = Button(self, wx.ID_CANCEL)
  554. btnOk = Button(self, wx.ID_OK)
  555. btnOk.SetDefault()
  556. # sizers
  557. btnSizer = wx.StdDialogButtonSizer()
  558. btnSizer.AddButton(btnCancel)
  559. btnSizer.AddButton(btnOk)
  560. btnSizer.Realize()
  561. mainSizer = wx.BoxSizer(wx.VERTICAL)
  562. mainSizer.Add(
  563. border,
  564. proportion=1,
  565. flag=wx.EXPAND | wx.ALL,
  566. border=5)
  567. mainSizer.Add(btnSizer, proportion=0,
  568. flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5)
  569. self.SetSizer(mainSizer)
  570. mainSizer.Fit(self)
  571. class VDigitDuplicatesDialog(wx.Dialog):
  572. def __init__(self, parent, data, title=_("List of duplicates"),
  573. style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER,
  574. pos=wx.DefaultPosition):
  575. """Show duplicated feature ids
  576. """
  577. wx.Dialog.__init__(
  578. self,
  579. parent=parent,
  580. id=wx.ID_ANY,
  581. title=title,
  582. style=style,
  583. pos=pos)
  584. self.parent = parent # map window instance
  585. self.data = data
  586. self.winList = []
  587. # panel = wx.Panel(parent=self, id=wx.ID_ANY)
  588. # notebook
  589. self.notebook = wx.Notebook(
  590. parent=self, id=wx.ID_ANY, style=wx.BK_DEFAULT)
  591. id = 1
  592. for key in self.data.keys():
  593. panel = wx.Panel(parent=self.notebook, id=wx.ID_ANY)
  594. self.notebook.AddPage(page=panel, text=" %d " % (id))
  595. # notebook body
  596. border = wx.BoxSizer(wx.VERTICAL)
  597. win = CheckListFeature(parent=panel, data=list(self.data[key]))
  598. self.winList.append(win.GetId())
  599. border.Add(win, proportion=1,
  600. flag=wx.ALL | wx.EXPAND, border=5)
  601. panel.SetSizer(border)
  602. id += 1
  603. # buttons
  604. btnCancel = Button(self, wx.ID_CANCEL)
  605. btnOk = Button(self, wx.ID_OK)
  606. btnOk.SetDefault()
  607. # sizers
  608. btnSizer = wx.StdDialogButtonSizer()
  609. btnSizer.AddButton(btnCancel)
  610. btnSizer.AddButton(btnOk)
  611. btnSizer.Realize()
  612. mainSizer = wx.BoxSizer(wx.VERTICAL)
  613. mainSizer.Add(
  614. self.notebook,
  615. proportion=1,
  616. flag=wx.EXPAND | wx.ALL,
  617. border=5)
  618. mainSizer.Add(btnSizer, proportion=0,
  619. flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5)
  620. self.SetSizer(mainSizer)
  621. mainSizer.Fit(self)
  622. self.SetAutoLayout(True)
  623. # set min size for dialog
  624. self.SetMinSize((250, 180))
  625. def GetUnSelected(self):
  626. """Get unselected items (feature id)
  627. :return: list of ids
  628. """
  629. ids = []
  630. for id in self.winList:
  631. wlist = self.FindWindowById(id)
  632. for item in range(wlist.GetItemCount()):
  633. if not wlist.IsChecked(item):
  634. ids.append(int(wlist.GetItem(item, 0).GetText()))
  635. return ids
  636. class CheckListFeature(
  637. wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.CheckListCtrlMixin):
  638. def __init__(self, parent, data,
  639. pos=wx.DefaultPosition, log=None):
  640. """List of mapset/owner/group
  641. """
  642. self.parent = parent
  643. self.data = data
  644. wx.ListCtrl.__init__(self, parent, wx.ID_ANY,
  645. style=wx.LC_REPORT)
  646. listmix.CheckListCtrlMixin.__init__(self)
  647. self.log = log
  648. # setup mixins
  649. listmix.ListCtrlAutoWidthMixin.__init__(self)
  650. self.LoadData(self.data)
  651. def LoadData(self, data):
  652. """Load data into list
  653. """
  654. self.InsertColumn(0, _('Feature id'))
  655. self.InsertColumn(1, _('Layer (Categories)'))
  656. for item in data:
  657. index = self.InsertStringItem(self.GetItemCount(), str(item[0]))
  658. self.SetStringItem(index, 1, str(item[1]))
  659. # enable all items by default
  660. for item in range(self.GetItemCount()):
  661. self.CheckItem(item, True)
  662. self.SetColumnWidth(col=0, width=wx.LIST_AUTOSIZE_USEHEADER)
  663. self.SetColumnWidth(col=1, width=wx.LIST_AUTOSIZE_USEHEADER)
  664. def OnCheckItem(self, index, flag):
  665. """Mapset checked/unchecked
  666. """
  667. pass