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