4
@brief Main scatter plot widgets.
7
- frame::IClassIScattPanel
9
- frame::MapDispIScattPanel
10
- frame::ScatterPlotsPanel
11
- frame::CategoryListCtrl
13
(C) 2013 by the GRASS Development Team
15
This program is free software under the GNU General Public License
16
(>=v2). Read the file COPYING that comes with GRASS for details.
18
@author Stepan Turek <stepan.turek seznam.cz> (mentor: Martin Landa)
24
import wx.lib.scrolledpanel as scrolled
25
import wx.lib.mixins.listctrl as listmix
27
from core import globalvar
28
from core.gcmd import GException, GError, RunCommand
30
from gui_core.gselect import Select
31
from gui_core.dialogs import SetOpacityDialog
32
from iscatt.controllers import ScattsManager
33
from iscatt.toolbars import MainToolbar, EditingToolbar, CategoryToolbar
34
from iscatt.iscatt_core import idScattToidBands
35
from iscatt.dialogs import ManageBusyCursorMixin, RenameClassDialog
36
from iscatt.plots import ScatterPlotWidget
37
from iclass.dialogs import ContrastColor
42
import wx.lib.agw.aui as aui
44
class IClassIScattPanel(wx.Panel, ManageBusyCursorMixin):
45
def __init__(self, parent, giface, iclass_mapwin = None,
48
#wx.SplitterWindow.__init__(self, parent = parent, id = id,
49
# style = wx.SP_LIVE_UPDATE)
50
wx.Panel.__init__(self, parent = parent, id = wx.ID_ANY)
51
ManageBusyCursorMixin.__init__(self, window=self)
53
self.scatt_mgr = self._createScattMgr(guiparent=parent, giface=giface,
54
iclass_mapwin=iclass_mapwin)
58
self.toolbars['mainToolbar'] = self._createMainToolbar()
59
self.toolbars['editingToolbar'] = EditingToolbar(parent = self, scatt_mgr = self.scatt_mgr)
61
self._createCategoryPanel(self)
63
self.plot_panel = ScatterPlotsPanel(self, self.scatt_mgr)
65
self.mainsizer = wx.BoxSizer(wx.VERTICAL)
66
self.mainsizer.Add(item = self.toolbars['mainToolbar'], proportion = 0, flag = wx.EXPAND)
67
self.mainsizer.Add(item = self.toolbars['editingToolbar'], proportion = 0, flag = wx.EXPAND)
68
self.mainsizer.Add(item = self.catsPanel, proportion = 0,
69
flag = wx.EXPAND | wx.LEFT | wx.RIGHT , border = 5)
70
self.mainsizer.Add(item = self.plot_panel, proportion = 1, flag = wx.EXPAND)
73
self.toolbars['editingToolbar'].Hide()
75
self.SetSizer(self.mainsizer)
77
self.scatt_mgr.computingStarted.connect(lambda : self.UpdateCur(busy=True))
78
self.scatt_mgr.renderingStarted.connect(lambda : self.UpdateCur(busy=True))
79
self.scatt_mgr.renderingFinished.connect(lambda : self.UpdateCur(busy=False))
81
#self.SetSashGravity(0.5)
82
#self.SplitHorizontally(self.head_panel, self.plot_panel, -50)
85
def CloseWindow(self):
86
self.scatt_mgr.CleanUp()
88
def UpdateCur(self, busy):
89
self.plot_panel.SetBusy(busy)
90
ManageBusyCursorMixin.UpdateCur(self, busy)
92
def _selCatInIScatt(self):
95
def _createMainToolbar(self):
96
return MainToolbar(parent = self, scatt_mgr = self.scatt_mgr)
98
def _createScattMgr(self, guiparent, giface, iclass_mapwin):
99
return ScattsManager(guiparent=self, giface=giface, iclass_mapwin=iclass_mapwin)
102
def NewScatterPlot(self, scatt_id, transpose):
103
return self.plot_panel.NewScatterPlot(scatt_id, transpose)
105
def ShowPlotEditingToolbar(self, show):
106
self.toolbars["editingToolbar"].Show(show)
109
def ShowCategoryPanel(self, show):
110
self.catsPanel.Show(show)
113
# self.SetSashSize(5)
115
# self.SetSashSize(0)
116
self.plot_panel.SetVirtualSize(self.plot_panel.GetBestVirtualSize())
119
def _createCategoryPanel(self, parent):
120
self.catsPanel = wx.Panel(parent=parent)
121
self.cats_list = CategoryListCtrl(parent=self.catsPanel,
122
cats_mgr=self.scatt_mgr.GetCategoriesManager(),
123
sel_cats_in_iscatt=self._selCatInIScatt())
125
self.catsPanel.SetMinSize((-1, 100))
126
self.catsPanel.SetInitialSize((-1, 150))
128
box_capt = wx.StaticBox(parent=self.catsPanel, id=wx.ID_ANY,
129
label=' %s ' % _("Classes"),)
130
catsSizer = wx.StaticBoxSizer(box_capt, wx.VERTICAL)
132
self.toolbars['categoryToolbar'] = self._createCategoryToolbar(self.catsPanel)
134
catsSizer.Add(item=self.cats_list, proportion=1, flag=wx.EXPAND | wx.TOP, border = 5)
135
if self.toolbars['categoryToolbar']:
136
catsSizer.Add(item=self.toolbars['categoryToolbar'], proportion=0)
138
self.catsPanel.SetSizer(catsSizer)
140
def _createCategoryToolbar(self, parent):
141
return CategoryToolbar(parent=parent,
142
scatt_mgr=self.scatt_mgr,
143
cats_list=self.cats_list)
145
class IScattDialog(wx.Dialog):
146
def __init__(self, parent, giface, title=_("GRASS GIS Interactive Scatter Plot Tool"),
147
id=wx.ID_ANY, style=wx.DEFAULT_FRAME_STYLE, **kwargs):
148
wx.Dialog.__init__(self, parent, id, style=style, title = title, **kwargs)
149
self.SetIcon(wx.Icon(os.path.join(globalvar.ICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO))
151
self.iscatt_panel = MapDispIScattPanel(self, giface)
153
mainsizer = wx.BoxSizer(wx.VERTICAL)
154
mainsizer.Add(item=self.iscatt_panel, proportion=1, flag=wx.EXPAND)
156
self.SetSizer(mainsizer)
158
self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
160
self.SetMinSize((300, 300))
162
def OnCloseWindow(self, event):
166
class MapDispIScattPanel(IClassIScattPanel):
167
def __init__(self, parent, giface,
168
id=wx.ID_ANY, **kwargs):
169
IClassIScattPanel.__init__(self, parent=parent, giface=giface,
172
def _createScattMgr(self, guiparent, giface, iclass_mapwin):
173
return ScattsManager(guiparent = self, giface = giface)
175
def _createMainToolbar(self):
176
return MainToolbar(parent = self, scatt_mgr = self.scatt_mgr, opt_tools=['add_group'])
178
def _selCatInIScatt(self):
181
class ScatterPlotsPanel(scrolled.ScrolledPanel):
182
def __init__(self, parent, scatt_mgr, id=wx.ID_ANY):
184
scrolled.ScrolledPanel.__init__(self, parent)
185
self.SetupScrolling(scroll_x=False, scroll_y=True, scrollToTop=False)
187
self.scatt_mgr = scatt_mgr
189
self.mainPanel = wx.Panel(parent=self, id=wx.ID_ANY)
191
#self._createCategoryPanel()
193
self._mgr = aui.AuiManager(self.mainPanel)
194
#self._mgr.SetManagedWindow(self)
199
self.Bind(wx.EVT_SCROLLWIN, self.OnScroll)
200
self.Bind(wx.EVT_SCROLL_CHANGED, self.OnScrollChanged)
202
self.Bind(aui.EVT_AUI_PANE_CLOSE, self.OnPlotPaneClosed)
205
#self.SetBestSize(dlgSize)
206
#self.SetInitialSize(dlgSize)
207
self.SetAutoLayout(1)
208
#fix goutput's pane size (required for Mac OSX)
210
# self.gwindow.SetSashPosition(int(self.GetSize()[1] * .75))
211
self.ignore_scroll = 0
212
self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
215
self.scatt_id_scatt_i = {}
219
self.Bind(wx.EVT_CLOSE, self.OnClose)
221
self.scatt_mgr.cursorPlotMove.connect(self.CursorPlotMove)
223
def SetBusy(self, busy):
224
for scatt in self.scatts.itervalues():
225
scatt.UpdateCur(busy)
227
def CursorPlotMove(self, x, y, scatt_id):
236
pane = self._getPane(scatt_id)
237
caption = self._creteCaption(scatt_id)
239
caption += " %d, %d" % (x, y)
241
pane.Caption(caption)
242
self._mgr.RefreshCaptions()
244
def _getPane(self, scatt_id):
245
scatt_i = self.scatt_id_scatt_i[scatt_id]
246
name = self._getScatterPlotName(scatt_i)
247
return self._mgr.GetPane(name)
249
def ScatterPlotClosed(self, scatt_id):
251
scatt_i = self.scatt_id_scatt_i[scatt_id]
253
name = self._getScatterPlotName(scatt_i)
254
pane = self._mgr.GetPane(name)
256
del self.scatt_id_scatt_i[scatt_id]
257
del self.scatts[scatt_id]
260
self._mgr.ClosePane(pane)
263
def OnMouseWheel(self, event):
264
#TODO very ugly find some better solution
265
self.ignore_scroll = 3
268
def ScrollChildIntoView(self, child):
269
#For aui manager it does not work and returns position always to the top -> deactivated.
272
def OnPlotPaneClosed(self, event):
273
if isinstance(event.pane.window, ScatterPlotWidget):
274
event.pane.window.CleanUp()
276
def OnScrollChanged(self, event):
277
wx.CallAfter(self.Layout)
279
def OnScroll(self, event):
280
if self.ignore_scroll > 0:
281
self.ignore_scroll -= 1
285
#wx.CallAfter(self._mgr.Update)
286
#wx.CallAfter(self.Layout)
290
mainsizer = wx.BoxSizer(wx.VERTICAL)
291
mainsizer.Add(item = self.mainPanel, proportion = 1, flag = wx.EXPAND)
292
self.SetSizer(mainsizer)
295
self.SetupScrolling()
297
def OnClose(self, event):
301
self.scatt_mgr.CleanUp()
304
def OnSettings(self, event):
307
def _newScatterPlotName(self, scatt_id):
308
name = self._getScatterPlotName(self.scatt_i)
309
self.scatt_id_scatt_i[scatt_id] = self.scatt_i
313
def _getScatterPlotName(self, i):
314
name = "scatter plot %d" % i
317
def NewScatterPlot(self, scatt_id, transpose):
318
#TODO needs to be resolved (should be in this class)
320
scatt = ScatterPlotWidget(parent = self.mainPanel,
321
scatt_mgr = self.scatt_mgr,
323
transpose = transpose)
324
scatt.plotClosed.connect(self.ScatterPlotClosed)
325
self.transpose[scatt_id] = transpose
327
caption = self._creteCaption(scatt_id)
328
self._mgr.AddPane(scatt,
329
aui.AuiPaneInfo().Dockable(True).Floatable(True).
330
Name(self._newScatterPlotName(scatt_id)).MinSize((-1, 300)).
332
Center().Position(1).MaximizeButton(True).
333
MinimizeButton(True).CaptionVisible(True).
334
CloseButton(True).Layer(0))
338
self.SetVirtualSize(self.GetBestVirtualSize())
341
self.scatts[scatt_id] = scatt
345
def _creteCaption(self, scatt_id):
347
transpose = self.transpose[scatt_id]
348
bands = self.scatt_mgr.GetBands()
351
b1_id, b2_id = idScattToidBands(scatt_id, len(bands))
353
x_b = bands[b1_id].split('@')[0]
354
y_b = bands[b2_id].split('@')[0]
361
return "%s x: %s y: %s" % (_("scatter plot"), x_b, y_b)
363
def GetScattMgr(self):
364
return self.scatt_mgr
366
class CategoryListCtrl(wx.ListCtrl,
367
listmix.ListCtrlAutoWidthMixin):
368
#listmix.TextEditMixin):
370
def __init__(self, parent, cats_mgr, sel_cats_in_iscatt, id = wx.ID_ANY):
372
wx.ListCtrl.__init__(self, parent, id,
373
style = wx.LC_REPORT|wx.LC_VIRTUAL|wx.LC_HRULES|
374
wx.LC_VRULES|wx.LC_SINGLE_SEL|wx.LC_NO_HEADER)
375
self.columns = ((_('Class name'), 'name'), )
376
#(_('Color'), 'color'))
378
self.sel_cats_in_iscatt = sel_cats_in_iscatt
380
self.Populate(columns = self.columns)
382
self.cats_mgr = cats_mgr
383
self.SetItemCount(len(self.cats_mgr.GetCategories()))
385
self.rightClickedItemIdx = wx.NOT_FOUND
387
listmix.ListCtrlAutoWidthMixin.__init__(self)
389
#listmix.TextEditMixin.__init__(self)
391
self.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnCategoryRightUp) #wxMSW
392
self.Bind(wx.EVT_RIGHT_UP, self.OnCategoryRightUp) #wxGTK
394
#self.Bind(wx.EVT_LIST_BEGIN_LABEL_EDIT, self.OnEdit)
395
self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnSel)
397
self.cats_mgr.setCategoryAttrs.connect(self.Update)
398
self.cats_mgr.deletedCategory.connect(self.Update)
399
self.cats_mgr.addedCategory.connect(self.Update)
401
def Update(self, **kwargs):
402
self.SetItemCount(len(self.cats_mgr.GetCategories()))
403
if len(self.cats_mgr.GetCategories()):
404
self.RefreshItems(0, len(self.cats_mgr.GetCategories()) - 1)
406
def SetVirtualData(self, row, column, text):
407
attr = self.columns[column][1]
411
except UnicodeEncodeError:
412
GMessage(parent = self, message = _("Please use only ASCII characters."))
415
cat_id = self.cats_mgr.GetCategories()[row]
417
self.cats_mgr.setCategoryAttrs.disconnect(self.Update)
418
self.cats_mgr.SetCategoryAttrs(cat_id, {attr : text})
419
self.cats_mgr.setCategoryAttrs.connect(self.Update)
423
def Populate(self, columns):
424
for i, col in enumerate(columns):
425
self.InsertColumn(i, col[0])#wx.LIST_FORMAT_RIGHT
427
#self.SetColumnWidth(0, 100)
428
#self.SetColumnWidth(1, 100)
430
def AddCategory(self):
432
self.cats_mgr.addedCategory.disconnect(self.Update)
433
cat_id = self.cats_mgr.AddCategory()
434
self.cats_mgr.addedCategory.connect(self.Update)
437
GError(_("Maximum limit of categories number was reached."))
439
self.SetItemCount(len(self.cats_mgr.GetCategories()))
441
def DeleteCategory(self):
442
indexList = sorted(self.GetSelectedIndices(), reverse = True)
445
# remove temporary raster
446
cat_id = self.cats_mgr.GetCategories()[i]
450
self.cats_mgr.deletedCategory.disconnect(self.Update)
451
self.cats_mgr.DeleteCategory(cat_id)
452
self.cats_mgr.deletedCategory.connect(self.Update)
454
self.SetItemCount(len(self.cats_mgr.GetCategories()))
456
def OnSel(self, event):
457
if self.sel_cats_in_iscatt:
458
indexList = self.GetSelectedIndices()
460
cats = self.cats_mgr.GetCategories()
462
sel_cats.append(cats[i])
465
self.cats_mgr.SetSelectedCat(sel_cats[0])
468
def GetSelectedIndices(self, state = wx.LIST_STATE_SELECTED):
472
index = self.GetNextItem(lastFound, wx.LIST_NEXT_ALL, state)
477
indices.append(index)
480
def DeselectAll(self):
481
"""Deselect all items"""
482
indexList = self.GetSelectedIndices()
484
self.Select(i, on = 0)
487
self.OnCategorySelected(None)
489
def OnGetItemText(self, item, col):
490
attr = self.columns[col][1]
491
cat_id = self.cats_mgr.GetCategories()[item]
493
return self.cats_mgr.GetCategoryAttrs(cat_id)[attr]
495
def OnGetItemImage(self, item):
498
def OnGetItemAttr(self, item):
499
cat_id = self.cats_mgr.GetCategories()[item]
501
cattr = self.cats_mgr.GetCategoryAttrs(cat_id)
506
back_c = wx.Colour(*map(int, c.split(':')))
507
text_c = wx.Colour(*ContrastColor(back_c))
509
back_c = wx.SystemSettings.GetColour(wx.SYS_COLOUR_INACTIVECAPTION)
510
text_c = wx.SystemSettings.GetColour(wx.SYS_COLOUR_INACTIVECAPTIONTEXT)
512
# if it is in scope of the method, gui falls, using self solved it
513
self.l = wx.ListItemAttr(colText=text_c, colBack=back_c)
516
def OnCategoryRightUp(self, event):
517
"""Show context menu on right click"""
518
item, flags = self.HitTest((event.GetX(), event.GetY()))
519
if item != wx.NOT_FOUND and flags & wx.LIST_HITTEST_ONITEM:
520
self.rightClickedItemIdx = item
522
# generate popup-menu
523
cat_idx = self.rightClickedItemIdx
525
cats = self.cats_mgr.GetCategories()
526
cat_id = cats[cat_idx]
527
showed = self.cats_mgr.GetCategoryAttrs(cat_id)['show']
532
menu.Append(item_id, text=_("Rename class"))
533
self.Bind(wx.EVT_MENU, self.OnRename, id=item_id)
536
menu.Append(item_id, text=_("Set color"))
537
self.Bind(wx.EVT_MENU, self.OnSetColor, id=item_id)
540
menu.Append(item_id, text=_("Change opacity level"))
541
self.Bind(wx.EVT_MENU, self.OnPopupOpacityLevel, id=item_id)
549
menu.Append(item_id, text = text)
550
self.Bind(wx.EVT_MENU, lambda event : self._setCatAttrs(cat_id=cat_id,
551
attrs={'show' : not showed}),
554
menu.AppendSeparator()
557
menu.Append(item_id, text=_("Move to top"))
558
self.Bind(wx.EVT_MENU, self.OnMoveTop, id=item_id)
560
menu.Enable(item_id, False)
563
menu.Append(item_id, text=_("Move to bottom"))
564
self.Bind(wx.EVT_MENU, self.OnMoveBottom, id=item_id)
565
if cat_idx == len(cats) - 1:
566
menu.Enable(item_id, False)
568
menu.AppendSeparator()
571
menu.Append(item_id, text=_("Move category up"))
572
self.Bind(wx.EVT_MENU, self.OnMoveUp, id=item_id)
574
menu.Enable(item_id, False)
577
menu.Append(item_id, text=_("Move category down"))
578
self.Bind(wx.EVT_MENU, self.OnMoveDown, id=item_id)
579
if cat_idx == len(cats) - 1:
580
menu.Enable(item_id, False)
582
menu.AppendSeparator()
585
menu.Append(item_id, text=_("Export class raster"))
586
self.Bind(wx.EVT_MENU, self.OnExportCatRast, id=item_id)
591
def OnExportCatRast(self, event):
592
"""Export training areas"""
594
# GMessage(parent=self, message=_("No class raster to export."))
597
cat_idx = self.rightClickedItemIdx
598
cat_id = self.cats_mgr.GetCategories()[cat_idx]
600
self.cats_mgr.ExportCatRast(cat_id)
602
def OnMoveUp(self, event):
603
cat_idx = self.rightClickedItemIdx
604
cat_id = self.cats_mgr.GetCategories()[cat_idx]
605
self.cats_mgr.ChangePosition(cat_id, cat_idx - 1)
606
self.RefreshItems(0, len(self.cats_mgr.GetCategories()))
608
def OnMoveDown(self, event):
609
cat_idx = self.rightClickedItemIdx
610
cat_id = self.cats_mgr.GetCategories()[cat_idx]
611
self.cats_mgr.ChangePosition(cat_id, cat_idx + 1)
612
self.RefreshItems(0, len(self.cats_mgr.GetCategories()))
614
def OnMoveTop(self, event):
615
cat_idx = self.rightClickedItemIdx
616
cat_id = self.cats_mgr.GetCategories()[cat_idx]
617
self.cats_mgr.ChangePosition(cat_id, 0)
618
self.RefreshItems(0, len(self.cats_mgr.GetCategories()))
620
def OnMoveBottom(self, event):
621
cat_idx = self.rightClickedItemIdx
622
cats = self.cats_mgr.GetCategories()
623
cat_id = cats[cat_idx]
624
self.cats_mgr.ChangePosition(cat_id, len(cats) - 1)
625
self.RefreshItems(0, len(self.cats_mgr.GetCategories()))
627
def OnSetColor(self, event):
628
"""Popup opacity level indicator"""
629
cat_idx = self.rightClickedItemIdx
630
cat_id = self.cats_mgr.GetCategories()[cat_idx]
632
col = self.cats_mgr.GetCategoryAttrs(cat_id)['color']
633
col = map(int, col.split(':'))
635
col_data = wx.ColourData()
636
col_data.SetColour(wx.Colour(*col))
638
dlg = wx.ColourDialog(self, col_data)
639
dlg.GetColourData().SetChooseFull(True)
641
if dlg.ShowModal() == wx.ID_OK:
642
color = dlg.GetColourData().GetColour().Get()
643
color = ':'.join(map(str, color))
644
self.cats_mgr.SetCategoryAttrs(cat_id, {"color" : color})
648
def OnPopupOpacityLevel(self, event):
649
"""Popup opacity level indicator"""
651
cat_id = self.cats_mgr.GetCategories()[self.rightClickedItemIdx]
652
cat_attrs = self.cats_mgr.GetCategoryAttrs(cat_id)
653
value = cat_attrs['opacity'] * 100
654
name = cat_attrs['name']
656
dlg = SetOpacityDialog(self, opacity = value,
657
title = _("Change opacity of class <%s>" % name))
659
dlg.applyOpacity.connect(lambda value:
660
self._setCatAttrs(cat_id=cat_id, attrs={'opacity' : value}))
663
if dlg.ShowModal() == wx.ID_OK:
664
self._setCatAttrs(cat_id=cat_id, attrs={'opacity' : value})
668
def OnRename(self, event):
669
cat_id = self.cats_mgr.GetCategories()[self.rightClickedItemIdx]
670
cat_attrs = self.cats_mgr.GetCategoryAttrs(cat_id)
672
dlg = RenameClassDialog(self, old_name=cat_attrs['name'])
676
if dlg.ShowModal() == wx.ID_OK:
677
name = dlg.GetNewName().strip()
679
GMessage(parent=self, message=_("Empty name was inserted."))
681
self.cats_mgr.SetCategoryAttrs(cat_id, {"name" : name})
688
def _setCatAttrs(self, cat_id, attrs):
689
self.cats_mgr.setCategoryAttrs.disconnect(self.Update)
690
self.cats_mgr.SetCategoryAttrs(cat_id, attrs)
691
self.cats_mgr.setCategoryAttrs.connect(self.Update)