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