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