1"""
2@package iscatt.frame
3
4@brief Main scatter plot widgets.
5
6Classes:
7 - frame::IClassIScattPanel
8 - frame::IScattDialog
9 - frame::MapDispIScattPanel
10 - frame::ScatterPlotsPanel
11 - frame::CategoryListCtrl
12
13(C) 2013 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 Stepan Turek <stepan.turek seznam.cz> (mentor: Martin Landa)
19"""
20
21from __future__ import print_function
22
23import os
24import sys
25import six
26
27import wx
28import wx.lib.scrolledpanel as scrolled
29import wx.lib.mixins.listctrl as listmix
30
31from core import globalvar
32from core.gcmd import GException, GError, RunCommand
33
34from gui_core.gselect import Select
35from gui_core.dialogs import SetOpacityDialog
36from gui_core.wrap import StaticBox, Menu, ListCtrl
37from iscatt.controllers import ScattsManager
38from iscatt.toolbars import MainToolbar, EditingToolbar, CategoryToolbar
39from iscatt.iscatt_core import idScattToidBands
40from iscatt.dialogs import ManageBusyCursorMixin, RenameClassDialog
41from iscatt.plots import ScatterPlotWidget
42from iclass.dialogs import ContrastColor
43
44try:
45    from agw import aui
46except ImportError:
47    import wx.lib.agw.aui as aui
48
49
50class IClassIScattPanel(wx.Panel, ManageBusyCursorMixin):
51
52    def __init__(self, parent, giface, iclass_mapwin=None,
53                 id=wx.ID_ANY):
54
55        # wx.SplitterWindow.__init__(self, parent = parent, id = id,
56        #                           style = wx.SP_LIVE_UPDATE)
57        wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
58        ManageBusyCursorMixin.__init__(self, window=self)
59
60        self.scatt_mgr = self._createScattMgr(guiparent=parent, giface=giface,
61                                              iclass_mapwin=iclass_mapwin)
62
63        # toobars
64        self.toolbars = {}
65        self.toolbars['mainToolbar'] = self._createMainToolbar()
66        self.toolbars['editingToolbar'] = EditingToolbar(
67            parent=self, scatt_mgr=self.scatt_mgr)
68
69        self._createCategoryPanel(self)
70
71        self.plot_panel = ScatterPlotsPanel(self, self.scatt_mgr)
72
73        self.mainsizer = wx.BoxSizer(wx.VERTICAL)
74        self.mainsizer.Add(
75            self.toolbars['mainToolbar'],
76            proportion=0, flag=wx.EXPAND)
77        self.mainsizer.Add(
78            self.toolbars['editingToolbar'],
79            proportion=0, flag=wx.EXPAND)
80        self.mainsizer.Add(self.catsPanel, proportion=0,
81                           flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=5)
82        self.mainsizer.Add(self.plot_panel, proportion=1, flag=wx.EXPAND)
83
84        self.catsPanel.Hide()
85        self.toolbars['editingToolbar'].Hide()
86
87        self.SetSizer(self.mainsizer)
88
89        self.scatt_mgr.computingStarted.connect(
90            lambda: self.UpdateCur(busy=True))
91        self.scatt_mgr.renderingStarted.connect(
92            lambda: self.UpdateCur(busy=True))
93        self.scatt_mgr.renderingFinished.connect(
94            lambda: self.UpdateCur(busy=False))
95
96        # self.SetSashGravity(0.5)
97        #self.SplitHorizontally(self.head_panel, self.plot_panel, -50)
98        self.Layout()
99
100    def CloseWindow(self):
101        self.scatt_mgr.CleanUp()
102
103    def UpdateCur(self, busy):
104        self.plot_panel.SetBusy(busy)
105        ManageBusyCursorMixin.UpdateCur(self, busy)
106
107    def _selCatInIScatt(self):
108        return False
109
110    def _createMainToolbar(self):
111        return MainToolbar(parent=self, scatt_mgr=self.scatt_mgr)
112
113    def _createScattMgr(self, guiparent, giface, iclass_mapwin):
114        return ScattsManager(guiparent=self, giface=giface,
115                             iclass_mapwin=iclass_mapwin)
116
117    def NewScatterPlot(self, scatt_id, transpose):
118        return self.plot_panel.NewScatterPlot(scatt_id, transpose)
119
120    def ShowPlotEditingToolbar(self, show):
121        self.toolbars["editingToolbar"].Show(show)
122        self.Layout()
123
124    def ShowCategoryPanel(self, show):
125        self.catsPanel.Show(show)
126
127        # if show:
128        #    self.SetSashSize(5)
129        # else:
130        #    self.SetSashSize(0)
131        self.plot_panel.SetVirtualSize(self.plot_panel.GetBestVirtualSize())
132        self.Layout()
133
134    def _createCategoryPanel(self, parent):
135        self.catsPanel = wx.Panel(parent=parent)
136        self.cats_list = CategoryListCtrl(
137            parent=self.catsPanel,
138            cats_mgr=self.scatt_mgr.GetCategoriesManager(),
139            sel_cats_in_iscatt=self._selCatInIScatt())
140
141        self.catsPanel.SetMinSize((-1, 100))
142        self.catsPanel.SetInitialSize((-1, 150))
143
144        box_capt = StaticBox(parent=self.catsPanel, id=wx.ID_ANY,
145                             label=' %s ' % _("Classes"),)
146        catsSizer = wx.StaticBoxSizer(box_capt, wx.VERTICAL)
147
148        self.toolbars['categoryToolbar'] = self._createCategoryToolbar(
149            self.catsPanel)
150
151        catsSizer.Add(
152            self.cats_list,
153            proportion=1,
154            flag=wx.EXPAND | wx.TOP,
155            border=5)
156        if self.toolbars['categoryToolbar']:
157            catsSizer.Add(self.toolbars['categoryToolbar'], proportion=0)
158
159        self.catsPanel.SetSizer(catsSizer)
160
161    def _createCategoryToolbar(self, parent):
162        return CategoryToolbar(parent=parent,
163                               scatt_mgr=self.scatt_mgr,
164                               cats_list=self.cats_list)
165
166
167class IScattDialog(wx.Dialog):
168
169    def __init__(
170            self, parent, giface,
171            title=_("GRASS GIS Interactive Scatter Plot Tool"),
172            id=wx.ID_ANY, style=wx.DEFAULT_FRAME_STYLE, **kwargs):
173        wx.Dialog.__init__(
174            self,
175            parent,
176            id,
177            style=style,
178            title=title,
179            **kwargs)
180        self.SetIcon(
181            wx.Icon(
182                os.path.join(
183                    globalvar.ICONDIR,
184                    'grass.ico'),
185                wx.BITMAP_TYPE_ICO))
186
187        self.iscatt_panel = MapDispIScattPanel(self, giface)
188
189        mainsizer = wx.BoxSizer(wx.VERTICAL)
190        mainsizer.Add(self.iscatt_panel, proportion=1, flag=wx.EXPAND)
191
192        self.SetSizer(mainsizer)
193
194        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
195
196        self.SetMinSize((300, 300))
197
198    def OnCloseWindow(self, event):
199        event.Skip()
200        # self.
201
202
203class MapDispIScattPanel(IClassIScattPanel):
204
205    def __init__(self, parent, giface,
206                 id=wx.ID_ANY, **kwargs):
207        IClassIScattPanel.__init__(self, parent=parent, giface=giface,
208                                   id=id, **kwargs)
209
210    def _createScattMgr(self, guiparent, giface, iclass_mapwin):
211        return ScattsManager(guiparent=self, giface=giface)
212
213    def _createMainToolbar(self):
214        return MainToolbar(
215            parent=self, scatt_mgr=self.scatt_mgr, opt_tools=['add_group'])
216
217    def _selCatInIScatt(self):
218        return True
219
220
221class ScatterPlotsPanel(scrolled.ScrolledPanel):
222
223    def __init__(self, parent, scatt_mgr, id=wx.ID_ANY):
224
225        scrolled.ScrolledPanel.__init__(self, parent)
226        self.SetupScrolling(scroll_x=False, scroll_y=True, scrollToTop=False)
227
228        self.scatt_mgr = scatt_mgr
229
230        self.mainPanel = wx.Panel(parent=self, id=wx.ID_ANY)
231
232        # self._createCategoryPanel()
233        # Fancy gui
234        self._mgr = aui.AuiManager(self.mainPanel)
235        # self._mgr.SetManagedWindow(self)
236
237        self._mgr.Update()
238
239        self._doLayout()
240        self.Bind(wx.EVT_SCROLLWIN, self.OnScroll)
241        self.Bind(wx.EVT_SCROLL_CHANGED, self.OnScrollChanged)
242
243        self.Bind(aui.EVT_AUI_PANE_CLOSE, self.OnPlotPaneClosed)
244
245        dlgSize = (-1, 400)
246        # self.SetBestSize(dlgSize)
247        # self.SetInitialSize(dlgSize)
248        self.SetAutoLayout(1)
249        # fix goutput's pane size (required for Mac OSX)
250        # if self.gwindow:
251        #    self.gwindow.SetSashPosition(int(self.GetSize()[1] * .75))
252        self.ignore_scroll = 0
253        self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
254
255        self.scatt_i = 1
256        self.scatt_id_scatt_i = {}
257        self.transpose = {}
258        self.scatts = {}
259
260        self.Bind(wx.EVT_CLOSE, self.OnClose)
261
262        self.scatt_mgr.cursorPlotMove.connect(self.CursorPlotMove)
263
264    def SetBusy(self, busy):
265        for scatt in six.itervalues(self.scatts):
266            scatt.UpdateCur(busy)
267
268    def CursorPlotMove(self, x, y, scatt_id):
269
270        try:
271            x = int(round(x))
272            y = int(round(y))
273            coords = True
274        except:
275            coords = False
276
277        pane = self._getPane(scatt_id)
278        caption = self._creteCaption(scatt_id)
279        if coords:
280            caption += "   %d, %d" % (x, y)
281
282        pane.Caption(caption)
283        self._mgr.RefreshCaptions()
284
285    def _getPane(self, scatt_id):
286        scatt_i = self.scatt_id_scatt_i[scatt_id]
287        name = self._getScatterPlotName(scatt_i)
288        return self._mgr.GetPane(name)
289
290    def ScatterPlotClosed(self, scatt_id):
291
292        scatt_i = self.scatt_id_scatt_i[scatt_id]
293
294        name = self._getScatterPlotName(scatt_i)
295        pane = self._mgr.GetPane(name)
296
297        del self.scatt_id_scatt_i[scatt_id]
298        del self.scatts[scatt_id]
299
300        if pane.IsOk():
301            self._mgr.ClosePane(pane)
302        self._mgr.Update()
303
304    def OnMouseWheel(self, event):
305        # TODO very ugly find some better solution
306        self.ignore_scroll = 3
307        event.Skip()
308
309    def ScrollChildIntoView(self, child):
310        # For aui manager it does not work and returns position always to the
311        # top -> deactivated.
312        pass
313
314    def OnPlotPaneClosed(self, event):
315        if isinstance(event.pane.window, ScatterPlotWidget):
316            event.pane.window.CleanUp()
317
318    def OnScrollChanged(self, event):
319        wx.CallAfter(self.Layout)
320
321    def OnScroll(self, event):
322        if self.ignore_scroll > 0:
323            self.ignore_scroll -= 1
324        else:
325            event.Skip()
326
327        # wx.CallAfter(self._mgr.Update)
328        # wx.CallAfter(self.Layout)
329
330    def _doLayout(self):
331
332        mainsizer = wx.BoxSizer(wx.VERTICAL)
333        mainsizer.Add(self.mainPanel, proportion=1, flag=wx.EXPAND)
334        self.SetSizer(mainsizer)
335
336        self.Layout()
337        self.SetupScrolling()
338
339    def OnClose(self, event):
340        """Close dialog"""
341        # TODO: why todo here, why print? just delete?
342        print("closed")
343        self.scatt_mgr.CleanUp()
344        self.Destroy()
345
346    def OnSettings(self, event):
347        pass
348
349    def _newScatterPlotName(self, scatt_id):
350        name = self._getScatterPlotName(self.scatt_i)
351        self.scatt_id_scatt_i[scatt_id] = self.scatt_i
352        self.scatt_i += 1
353        return name
354
355    def _getScatterPlotName(self, i):
356        name = "scatter plot %d" % i
357        return name
358
359    def NewScatterPlot(self, scatt_id, transpose):
360        # TODO needs to be resolved (should be in this class)
361
362        scatt = ScatterPlotWidget(parent=self.mainPanel,
363                                  scatt_mgr=self.scatt_mgr,
364                                  scatt_id=scatt_id,
365                                  transpose=transpose)
366        scatt.plotClosed.connect(self.ScatterPlotClosed)
367        self.transpose[scatt_id] = transpose
368
369        caption = self._creteCaption(scatt_id)
370        self._mgr.AddPane(scatt,
371                          aui.AuiPaneInfo().Dockable(True).Floatable(True).
372                          Name(self._newScatterPlotName(scatt_id)).MinSize((-1, 300)).
373                          Caption(caption).
374                          Center().Position(1).MaximizeButton(True).
375                          MinimizeButton(True).CaptionVisible(True).
376                          CloseButton(True).Layer(0))
377
378        self._mgr.Update()
379
380        self.SetVirtualSize(self.GetBestVirtualSize())
381        self.Layout()
382
383        self.scatts[scatt_id] = scatt
384
385        return scatt
386
387    def _creteCaption(self, scatt_id):
388
389        transpose = self.transpose[scatt_id]
390        bands = self.scatt_mgr.GetBands()
391
392        # TODO too low level
393        b1_id, b2_id = idScattToidBands(scatt_id, len(bands))
394
395        x_b = bands[b1_id].split('@')[0]
396        y_b = bands[b2_id].split('@')[0]
397
398        if transpose:
399            tmp = x_b
400            x_b = y_b
401            y_b = tmp
402
403        return "%s x: %s y: %s" % (_("scatter plot"), x_b, y_b)
404
405    def GetScattMgr(self):
406        return self.scatt_mgr
407
408
409class CategoryListCtrl(ListCtrl,
410                       listmix.ListCtrlAutoWidthMixin):
411                       # listmix.TextEditMixin):
412
413    def __init__(self, parent, cats_mgr, sel_cats_in_iscatt, id=wx.ID_ANY):
414
415        ListCtrl.__init__(
416            self, parent, id, style=wx.LC_REPORT | wx.LC_VIRTUAL | wx.LC_HRULES |
417            wx.LC_VRULES | wx.LC_SINGLE_SEL | wx.LC_NO_HEADER)
418        self.columns = ((_('Class name'), 'name'), )
419        #(_('Color'), 'color'))
420
421        self.sel_cats_in_iscatt = sel_cats_in_iscatt
422
423        self.Populate(columns=self.columns)
424
425        self.cats_mgr = cats_mgr
426        self.SetItemCount(len(self.cats_mgr.GetCategories()))
427
428        self.rightClickedItemIdx = wx.NOT_FOUND
429
430        listmix.ListCtrlAutoWidthMixin.__init__(self)
431
432        # listmix.TextEditMixin.__init__(self)
433
434        self.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnCategoryRightUp)  # wxMSW
435        self.Bind(wx.EVT_RIGHT_UP, self.OnCategoryRightUp)  # wxGTK
436
437        #self.Bind(wx.EVT_LIST_BEGIN_LABEL_EDIT, self.OnEdit)
438        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnSel)
439
440        self.cats_mgr.setCategoryAttrs.connect(self.Update)
441        self.cats_mgr.deletedCategory.connect(self.Update)
442        self.cats_mgr.addedCategory.connect(self.Update)
443
444    def Update(self, **kwargs):
445        self.SetItemCount(len(self.cats_mgr.GetCategories()))
446        if len(self.cats_mgr.GetCategories()):
447            self.RefreshItems(0, len(self.cats_mgr.GetCategories()) - 1)
448
449    def SetVirtualData(self, row, column, text):
450        attr = self.columns[column][1]
451        if attr == 'name':
452            try:
453                text.encode('ascii')
454            except UnicodeEncodeError:
455                GMessage(parent=self, message=_(
456                    "Please use only ASCII characters."))
457                return
458
459        cat_id = self.cats_mgr.GetCategories()[row]
460
461        self.cats_mgr.setCategoryAttrs.disconnect(self.Update)
462        self.cats_mgr.SetCategoryAttrs(cat_id, {attr: text})
463        self.cats_mgr.setCategoryAttrs.connect(self.Update)
464
465        self.Select(row)
466
467    def Populate(self, columns):
468        for i, col in enumerate(columns):
469            self.InsertColumn(i, col[0])  # wx.LIST_FORMAT_RIGHT
470
471        #self.SetColumnWidth(0, 100)
472        #self.SetColumnWidth(1, 100)
473
474    def AddCategory(self):
475
476        self.cats_mgr.addedCategory.disconnect(self.Update)
477        cat_id = self.cats_mgr.AddCategory()
478        self.cats_mgr.addedCategory.connect(self.Update)
479
480        if cat_id < 0:
481            GError(_("Maximum limit of categories number was reached."))
482            return
483        self.SetItemCount(len(self.cats_mgr.GetCategories()))
484
485    def DeleteCategory(self):
486        indexList = sorted(self.GetSelectedIndices(), reverse=True)
487        cats = []
488        for i in indexList:
489            # remove temporary raster
490            cat_id = self.cats_mgr.GetCategories()[i]
491
492            cats.append(cat_id)
493
494            self.cats_mgr.deletedCategory.disconnect(self.Update)
495            self.cats_mgr.DeleteCategory(cat_id)
496            self.cats_mgr.deletedCategory.connect(self.Update)
497
498        self.SetItemCount(len(self.cats_mgr.GetCategories()))
499
500    def OnSel(self, event):
501        if self.sel_cats_in_iscatt:
502            indexList = self.GetSelectedIndices()
503            sel_cats = []
504            cats = self.cats_mgr.GetCategories()
505            for i in indexList:
506                sel_cats.append(cats[i])
507
508            if sel_cats:
509                self.cats_mgr.SetSelectedCat(sel_cats[0])
510        event.Skip()
511
512    def GetSelectedIndices(self, state=wx.LIST_STATE_SELECTED):
513        indices = []
514        lastFound = -1
515        while True:
516            index = self.GetNextItem(lastFound, wx.LIST_NEXT_ALL, state)
517            if index == -1:
518                break
519            else:
520                lastFound = index
521                indices.append(index)
522        return indices
523
524    def DeselectAll(self):
525        """Deselect all items"""
526        indexList = self.GetSelectedIndices()
527        for i in indexList:
528            self.Select(i, on=0)
529
530        # no highlight
531        self.OnCategorySelected(None)
532
533    def OnGetItemText(self, item, col):
534        attr = self.columns[col][1]
535        cat_id = self.cats_mgr.GetCategories()[item]
536
537        return self.cats_mgr.GetCategoryAttrs(cat_id)[attr]
538
539    def OnGetItemImage(self, item):
540        return -1
541
542    def OnGetItemAttr(self, item):
543        cat_id = self.cats_mgr.GetCategories()[item]
544
545        cattr = self.cats_mgr.GetCategoryAttrs(cat_id)
546
547        if cattr['show']:
548            c = cattr['color']
549
550            back_c = wx.Colour(*map(int, c.split(':')))
551            text_c = wx.Colour(*ContrastColor(back_c))
552        else:
553            back_c = wx.SystemSettings.GetColour(wx.SYS_COLOUR_INACTIVECAPTION)
554            text_c = wx.SystemSettings.GetColour(
555                wx.SYS_COLOUR_INACTIVECAPTIONTEXT)
556
557        # if it is in scope of the method, gui falls, using self solved it
558        self.l = wx.ListItemAttr()
559        self.l.SetBackgroundColour(back_c)
560        self.l.SetTextColour(text_c)
561        return self.l
562
563    def OnCategoryRightUp(self, event):
564        """Show context menu on right click"""
565        item, flags = self.HitTest((event.GetX(), event.GetY()))
566        if item != wx.NOT_FOUND and flags & wx.LIST_HITTEST_ONITEM:
567            self.rightClickedItemIdx = item
568
569        # generate popup-menu
570        cat_idx = self.rightClickedItemIdx
571
572        cats = self.cats_mgr.GetCategories()
573        cat_id = cats[cat_idx]
574        showed = self.cats_mgr.GetCategoryAttrs(cat_id)['show']
575
576        menu = Menu()
577
578        item = menu.Append(wx.ID_ANY, _("Rename class"))
579        self.Bind(wx.EVT_MENU, self.OnRename, item)
580
581        item = menu.Append(wx.ID_ANY, _("Set color"))
582        self.Bind(wx.EVT_MENU, self.OnSetColor, item)
583
584        item = menu.Append(item_id, _("Change opacity level"))
585        self.Bind(wx.EVT_MENU, self.OnPopupOpacityLevel, item)
586
587        if showed:
588            text = _("Hide")
589        else:
590            text = _("Show")
591
592        item = menu.Append(wx.ID_ANY, text)
593        self.Bind(
594            wx.EVT_MENU,
595            lambda event: self._setCatAttrs(
596                cat_id=cat_id,
597                attrs={
598                    'show': not showed}),
599            item)
600
601        menu.AppendSeparator()
602
603        item = menu.Append(wx.ID_ANY, _("Move to top"))
604        self.Bind(wx.EVT_MENU, self.OnMoveTop, item)
605        if cat_idx == 0:
606            menu.Enable(item.GetId(), False)
607
608        item = menu.Append(wx.ID_ANY, _("Move to bottom"))
609        self.Bind(wx.EVT_MENU, self.OnMoveBottom, item)
610        if cat_idx == len(cats) - 1:
611            menu.Enable(item.GetId(), False)
612
613        menu.AppendSeparator()
614
615        item = menu.Append(wx.ID_ANY, _("Move category up"))
616        self.Bind(wx.EVT_MENU, self.OnMoveUp, item)
617        if cat_idx == 0:
618            menu.Enable(item.GetId(), False)
619
620        item = menu.Append(wx.ID_ANY, _("Move category down"))
621        self.Bind(wx.EVT_MENU, self.OnMoveDown, item)
622        if cat_idx == len(cats) - 1:
623            menu.Enable(item.GetId(), False)
624
625        menu.AppendSeparator()
626
627        item = menu.Append(wx.ID_ANY, _("Export class raster"))
628        self.Bind(wx.EVT_MENU, self.OnExportCatRast, item)
629
630        self.PopupMenu(menu)
631        menu.Destroy()
632
633    def OnExportCatRast(self, event):
634        """Export training areas"""
635        # TODO
636        #   GMessage(parent=self, message=_("No class raster to export."))
637        #    return
638
639        cat_idx = self.rightClickedItemIdx
640        cat_id = self.cats_mgr.GetCategories()[cat_idx]
641
642        self.cats_mgr.ExportCatRast(cat_id)
643
644    def OnMoveUp(self, event):
645        cat_idx = self.rightClickedItemIdx
646        cat_id = self.cats_mgr.GetCategories()[cat_idx]
647        self.cats_mgr.ChangePosition(cat_id, cat_idx - 1)
648        self.RefreshItems(0, len(self.cats_mgr.GetCategories()))
649
650    def OnMoveDown(self, event):
651        cat_idx = self.rightClickedItemIdx
652        cat_id = self.cats_mgr.GetCategories()[cat_idx]
653        self.cats_mgr.ChangePosition(cat_id, cat_idx + 1)
654        self.RefreshItems(0, len(self.cats_mgr.GetCategories()))
655
656    def OnMoveTop(self, event):
657        cat_idx = self.rightClickedItemIdx
658        cat_id = self.cats_mgr.GetCategories()[cat_idx]
659        self.cats_mgr.ChangePosition(cat_id, 0)
660        self.RefreshItems(0, len(self.cats_mgr.GetCategories()))
661
662    def OnMoveBottom(self, event):
663        cat_idx = self.rightClickedItemIdx
664        cats = self.cats_mgr.GetCategories()
665        cat_id = cats[cat_idx]
666        self.cats_mgr.ChangePosition(cat_id, len(cats) - 1)
667        self.RefreshItems(0, len(self.cats_mgr.GetCategories()))
668
669    def OnSetColor(self, event):
670        """Popup opacity level indicator"""
671        cat_idx = self.rightClickedItemIdx
672        cat_id = self.cats_mgr.GetCategories()[cat_idx]
673
674        col = self.cats_mgr.GetCategoryAttrs(cat_id)['color']
675        col = map(int, col.split(':'))
676
677        col_data = wx.ColourData()
678        col_data.SetColour(wx.Colour(*col))
679
680        dlg = wx.ColourDialog(self, col_data)
681        dlg.GetColourData().SetChooseFull(True)
682
683        if dlg.ShowModal() == wx.ID_OK:
684            color = dlg.GetColourData().GetColour().Get()
685            color = ':'.join(map(str, color))
686            self.cats_mgr.SetCategoryAttrs(cat_id, {"color": color})
687
688        dlg.Destroy()
689
690    def OnPopupOpacityLevel(self, event):
691        """Popup opacity level indicator"""
692
693        cat_id = self.cats_mgr.GetCategories()[self.rightClickedItemIdx]
694        cat_attrs = self.cats_mgr.GetCategoryAttrs(cat_id)
695        value = cat_attrs['opacity'] * 100
696        name = cat_attrs['name']
697
698        dlg = SetOpacityDialog(self, opacity=value,
699                               title=_("Change opacity of class <%s>" % name))
700
701        dlg.applyOpacity.connect(
702            lambda value: self._setCatAttrs(
703                cat_id=cat_id, attrs={
704                    'opacity': value}))
705        dlg.CentreOnParent()
706
707        if dlg.ShowModal() == wx.ID_OK:
708            self._setCatAttrs(cat_id=cat_id, attrs={'opacity': value})
709
710        dlg.Destroy()
711
712    def OnRename(self, event):
713        cat_id = self.cats_mgr.GetCategories()[self.rightClickedItemIdx]
714        cat_attrs = self.cats_mgr.GetCategoryAttrs(cat_id)
715
716        dlg = RenameClassDialog(self, old_name=cat_attrs['name'])
717        dlg.CentreOnParent()
718
719        while True:
720            if dlg.ShowModal() == wx.ID_OK:
721                name = dlg.GetNewName().strip()
722                if not name:
723                    GMessage(parent=self, message=_(
724                        "Empty name was inserted."))
725                else:
726                    self.cats_mgr.SetCategoryAttrs(cat_id, {"name": name})
727                    break
728            else:
729                break
730
731        dlg.Destroy()
732
733    def _setCatAttrs(self, cat_id, attrs):
734        self.cats_mgr.setCategoryAttrs.disconnect(self.Update)
735        self.cats_mgr.SetCategoryAttrs(cat_id, attrs)
736        self.cats_mgr.setCategoryAttrs.connect(self.Update)
737