dialogs.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. """!
  2. @package iclass::dialogs
  3. @brief wxIClass dialogs
  4. Classes:
  5. - dialogs::IClassGroupDialog
  6. - dialogs::IClassMapDialog
  7. - dialogs::IClassCategoryManagerDialog
  8. - dialogs::CategoryListCtrl
  9. - dialogs::IClassSignatureFileDialog
  10. (C) 2006-2011 by the GRASS Development Team
  11. This program is free software under the GNU General Public
  12. License (>=v2). Read the file COPYING that comes with GRASS
  13. for details.
  14. @author Vaclav Petras <wenzeslaus gmail.com>
  15. @author Anna Kratochvilova <kratochanna gmail.com>
  16. """
  17. import os
  18. import wx
  19. import wx.lib.mixins.listctrl as listmix
  20. import wx.lib.scrolledpanel as scrolled
  21. from core import globalvar
  22. from gui_core.dialogs import ElementDialog, GroupDialog
  23. from gui_core import gselect
  24. from iclass.statistics import Statistics, BandStatistics
  25. import grass.script as grass
  26. class IClassGroupDialog(ElementDialog):
  27. """!Dialog for imagery group selection"""
  28. def __init__(self, parent, group = None, title = _("Select imagery group"), id = wx.ID_ANY):
  29. """!
  30. Does post init and layout.
  31. @param gui parent
  32. @param title dialog window title
  33. @param id wx id
  34. """
  35. ElementDialog.__init__(self, parent, title, label = _("Name of imagery group:"))
  36. self.element = gselect.Select(parent = self.panel, type = 'group',
  37. mapsets = [grass.gisenv()['MAPSET']],
  38. size = globalvar.DIALOG_GSELECT_SIZE)
  39. if group:
  40. self.element.SetValue(group)
  41. self.editGroup = wx.Button(parent = self.panel, id = wx.ID_ANY,
  42. label = _("Create/edit group..."))
  43. self.editGroup.Bind(wx.EVT_BUTTON, self.OnEditGroup)
  44. self.PostInit()
  45. self.__Layout()
  46. self.SetMinSize(self.GetSize())
  47. def __Layout(self):
  48. """!Do layout"""
  49. self.dataSizer.Add(self.element, proportion = 0,
  50. flag = wx.EXPAND | wx.ALL, border = 5)
  51. self.dataSizer.Add(self.editGroup, proportion = 0,
  52. flag = wx.ALL, border = 5)
  53. self.panel.SetSizer(self.sizer)
  54. self.sizer.Fit(self)
  55. def GetGroup(self):
  56. """!Returns selected group"""
  57. return self.GetElement()
  58. def OnEditGroup(self, event):
  59. """!Launch edit group dialog"""
  60. dlg = GroupDialog(parent = self, defaultGroup = self.element.GetValue())
  61. dlg.ShowModal()
  62. gr = dlg.GetSelectedGroup()
  63. if gr in dlg.GetExistGroups():
  64. self.element.SetValue(gr)
  65. dlg.Destroy()
  66. class IClassMapDialog(ElementDialog):
  67. """!Dialog for adding raster map"""
  68. def __init__(self, parent, title = _("Add raster map"), id = wx.ID_ANY):
  69. """!
  70. Does post init and layout.
  71. @param gui parent
  72. @param title dialog window title
  73. @param id wx id
  74. """
  75. ElementDialog.__init__(self, parent, title, label = _("Name of raster map:"))
  76. self.element = gselect.Select(parent = self.panel, type = 'raster',
  77. size = globalvar.DIALOG_GSELECT_SIZE)
  78. self.PostInit()
  79. self.__Layout()
  80. self.SetMinSize(self.GetSize())
  81. def __Layout(self):
  82. """!Do layout"""
  83. self.dataSizer.Add(self.element, proportion = 0,
  84. flag = wx.EXPAND | wx.ALL, border = 5)
  85. self.panel.SetSizer(self.sizer)
  86. self.sizer.Fit(self)
  87. def GetRasterMap(self):
  88. """!Returns selected raster map"""
  89. return self.GetElement()
  90. class IClassCategoryManagerDialog(wx.Dialog):
  91. """!Dialog for managing categories (classes).
  92. Alows adding, deleting class and changing its name and color.
  93. """
  94. def __init__(self, parent, title = _("Class manager"), id = wx.ID_ANY):
  95. """!
  96. Does post init and layout.
  97. @param gui parent
  98. @param title dialog window title
  99. @param id wx id
  100. """
  101. wx.Dialog.__init__(self, parent = parent, title = title, id = id)
  102. panel = wx.Panel(parent = self, id = wx.ID_ANY)
  103. mainSizer = wx.BoxSizer(wx.VERTICAL)
  104. box = wx.StaticBox(panel, id = wx.ID_ANY,
  105. label = " %s " % _("Classes"))
  106. sizer = wx.StaticBoxSizer(box, wx.VERTICAL)
  107. gridSizer = wx.GridBagSizer(hgap = 5, vgap = 5)
  108. gridSizer.AddGrowableCol(0)
  109. gridSizer.AddGrowableRow(2)
  110. self.catList = CategoryListCtrl(panel, mapwindow = parent, statistics = parent.statisticsDict,
  111. statisticsList = parent.statisticsList)
  112. addButton = wx.Button(panel, id = wx.ID_ADD)
  113. deleteButton = wx.Button(panel, id = wx.ID_DELETE)
  114. gridSizer.Add(item = self.catList, pos = (0, 0), span = (3, 1), flag = wx.EXPAND)
  115. gridSizer.Add(item = addButton, pos = (0, 1), flag = wx.EXPAND)
  116. gridSizer.Add(item = deleteButton, pos = (1, 1), flag = wx.EXPAND)
  117. sizer.Add(item = gridSizer, proportion = 1, flag = wx.EXPAND | wx.ALL, border = 5)
  118. mainSizer.Add(item = sizer, proportion = 1, flag = wx.EXPAND | wx.ALL, border = 5)
  119. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  120. closeButton = wx.Button(panel, id = wx.ID_CLOSE)
  121. btnSizer.Add(item = wx.Size(-1, -1), proportion = 1, flag = wx.EXPAND)
  122. btnSizer.Add(item = closeButton, proportion = 0, flag = wx.ALIGN_RIGHT)
  123. mainSizer.Add(item = btnSizer, proportion = 0, flag = wx.EXPAND | wx.ALL, border = 5)
  124. addButton.Bind(wx.EVT_BUTTON, self.OnAddCategory)
  125. deleteButton.Bind(wx.EVT_BUTTON, self.OnDeleteCategory)
  126. closeButton.Bind(wx.EVT_BUTTON, self.OnClose)
  127. self.Bind(wx.EVT_CLOSE, self.OnClose)
  128. panel.SetSizer(mainSizer)
  129. mainSizer.Fit(panel)
  130. self.SetSize((-1, 250))
  131. self.Layout()
  132. def OnAddCategory(self, event):
  133. self.catList.AddCategory()
  134. def OnDeleteCategory(self, event):
  135. self.catList.DeleteCategory()
  136. def OnClose(self, event):
  137. self.catList.DeselectAll()
  138. self.catList.UpdateChoice()
  139. if not isinstance(event, wx.CloseEvent):
  140. self.Destroy()
  141. event.Skip()
  142. class CategoryListCtrl(wx.ListCtrl,
  143. listmix.ListCtrlAutoWidthMixin,
  144. listmix.TextEditMixin):
  145. """! Widget for controling list of classes (categories).
  146. CategoryListCtrl updates choice in mapwindow and removes raster map
  147. when deleting class (category).
  148. It uses virtual data in the terms of @c wx.ListCtrl.
  149. @todo statistics and categories are managed here directly,
  150. it could be better to use some interface
  151. @todo delete vector features after deleting class
  152. """
  153. def __init__(self, parent, mapwindow, statistics, statisticsList, id = wx.ID_ANY):
  154. """!
  155. @param parent gui parent
  156. @param mapwindow mapwindow instance with iclass toolbar and remove raster method
  157. @param statistics dictionary of statistics (defined in statistics.py)
  158. @param statisticsList list of statistics
  159. @param id wx id
  160. """
  161. wx.ListCtrl.__init__(self, parent, id,
  162. style = wx.LC_REPORT|wx.LC_VIRTUAL|wx.LC_HRULES|wx.LC_VRULES)
  163. self.columns = ((_('Class name'), 'name'),
  164. (_('Color'), 'color'))
  165. self.Populate(columns = self.columns)
  166. self.mapWindow = mapwindow
  167. self.statisticsDict = statistics
  168. self.statisticsList = statisticsList
  169. self.SetItemCount(len(statisticsList))
  170. self.rightClickedItemIdx = wx.NOT_FOUND
  171. listmix.ListCtrlAutoWidthMixin.__init__(self)
  172. listmix.TextEditMixin.__init__(self)
  173. self.Bind(wx.EVT_LIST_BEGIN_LABEL_EDIT, self.OnEdit)
  174. self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnCategorySelected)
  175. self.Bind(wx.EVT_COMMAND_RIGHT_CLICK, self.OnClassRightUp) #wxMSW
  176. self.Bind(wx.EVT_RIGHT_UP, self.OnClassRightUp) #wxGTK
  177. def SetVirtualData(self, row, column, text):
  178. attr = self.columns[column][1]
  179. setattr(self.statisticsDict[self.statisticsList[row]], attr, text)
  180. self.UpdateChoice()
  181. toolbar = self.mapWindow.toolbars['iClass']
  182. toolbar.choice.SetSelection(row)
  183. self.Select(row)
  184. if attr == 'name':
  185. self.mapWindow.UpdateRasterName(text, toolbar.GetSelectedCategoryIdx())
  186. self.mapWindow.UpdateChangeState(changes = True)
  187. def Populate(self, columns):
  188. for i, col in enumerate(columns):
  189. self.InsertColumn(i, col[0])#wx.LIST_FORMAT_RIGHT
  190. self.SetColumnWidth(0, 100)
  191. self.SetColumnWidth(1, 100)
  192. def AddCategory(self):
  193. st = Statistics()
  194. if self.statisticsList:
  195. cat = max(self.statisticsList) + 1
  196. else:
  197. cat = 1
  198. defaultName = 'class' + '_' + str(cat) # intentionally not translatable
  199. defaultColor = '0:0:0'
  200. st.SetBaseStatistics(cat = cat, name = defaultName, color = defaultColor)
  201. self.statisticsDict[cat] = st
  202. self.statisticsList.append(cat)
  203. self.SetItemCount(len(self.statisticsList))
  204. self.UpdateChoice()
  205. self.mapWindow.UpdateChangeState(changes = True)
  206. def DeleteCategory(self):
  207. indexList = sorted(self.GetSelectedIndices(), reverse = True)
  208. cats = []
  209. for i in indexList:
  210. # remove temporary raster
  211. name = self.statisticsDict[self.statisticsList[i]].rasterName
  212. self.mapWindow.RemoveTempRaster(name)
  213. cats.append(self.statisticsList[i])
  214. del self.statisticsDict[self.statisticsList[i]]
  215. del self.statisticsList[i]
  216. self.SetItemCount(len(self.statisticsList))
  217. self.UpdateChoice()
  218. self.mapWindow.UpdateChangeState(changes = True)
  219. self.mapWindow.DeleteAreas(cats = cats)
  220. def UpdateChoice(self):
  221. toolbar = self.mapWindow.toolbars['iClass']
  222. name = toolbar.GetSelectedCategoryName()
  223. catNames = []
  224. for cat in self.statisticsList:
  225. catNames.append(self.statisticsDict[cat].name)
  226. toolbar.SetCategories(catNames = catNames, catIdx = self.statisticsList)
  227. if name in catNames:
  228. toolbar.choice.SetStringSelection(name)
  229. elif catNames:
  230. toolbar.choice.SetSelection(0)
  231. if toolbar.choice.IsEmpty():
  232. toolbar.EnableControls(False)
  233. else:
  234. toolbar.EnableControls(True)
  235. # don't forget to update maps, histo, ...
  236. def GetSelectedIndices(self, state = wx.LIST_STATE_SELECTED):
  237. indices = []
  238. lastFound = -1
  239. while True:
  240. index = self.GetNextItem(lastFound, wx.LIST_NEXT_ALL, state)
  241. if index == -1:
  242. break
  243. else:
  244. lastFound = index
  245. indices.append(index)
  246. return indices
  247. def OnEdit(self, event):
  248. currentItem = event.m_itemIndex
  249. currentCol = event.m_col
  250. if currentCol == 1:
  251. dlg = wx.ColourDialog(self)
  252. dlg.GetColourData().SetChooseFull(True)
  253. if dlg.ShowModal() == wx.ID_OK:
  254. color = dlg.GetColourData().GetColour().Get()
  255. color = ':'.join(map(str, color))
  256. self.SetVirtualData(currentItem, currentCol, color)
  257. dlg.Destroy()
  258. event.Skip()
  259. def OnCategorySelected(self, event):
  260. """!Highlight selected areas"""
  261. indexList = self.GetSelectedIndices()
  262. cats = []
  263. for i in indexList:
  264. cats.append(self.statisticsList[i])
  265. self.mapWindow.HighlightCategory(cats)
  266. if event:
  267. event.Skip()
  268. def OnClassRightUp(self, event):
  269. """!Show context menu on right click"""
  270. item, flags = self.HitTest((event.GetX(), event.GetY()))
  271. if item != wx.NOT_FOUND and flags & wx.LIST_HITTEST_ONITEM:
  272. self.rightClickedItemIdx = item
  273. if not hasattr(self, "popupZoomtoAreas"):
  274. self.popupZoomtoAreas = wx.NewId()
  275. self.Bind(wx.EVT_MENU, self.OnZoomToAreasByCat, id = self.popupZoomtoAreas)
  276. # generate popup-menu
  277. menu = wx.Menu()
  278. menu.Append(self.popupZoomtoAreas, _("Zoom to training areas of selected class"))
  279. self.PopupMenu(menu)
  280. menu.Destroy()
  281. def OnZoomToAreasByCat(self, event):
  282. """!Zoom to areas of given category"""
  283. cat = self.statisticsList[self.rightClickedItemIdx]
  284. self.mapWindow.ZoomToAreasByCat(cat)
  285. def DeselectAll(self):
  286. """!Deselect all items"""
  287. indexList = self.GetSelectedIndices()
  288. for i in indexList:
  289. self.Select(i, on = 0)
  290. # no highlight
  291. self.OnCategorySelected(None)
  292. def OnGetItemText(self, item, col):
  293. cat = self.statisticsList[item]
  294. return getattr(self.statisticsDict[cat], self.columns[col][1])
  295. def OnGetItemImage(self, item):
  296. return -1
  297. def OnGetItemAttr(self, item):
  298. return None
  299. class IClassSignatureFileDialog(wx.Dialog):
  300. def __init__(self, parent, group, file = None, title = _("Save signature file"), id = wx.ID_ANY,
  301. style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER,
  302. **kwargs):
  303. """!Dialog for saving signature file
  304. @param parent window
  305. @param group group name
  306. @param file signature file name
  307. @param title window title
  308. """
  309. wx.Dialog.__init__(self, parent, id, title, style = style, **kwargs)
  310. self.fileName = file
  311. env = grass.gisenv()
  312. # inconsistent group and subgroup name
  313. # path: grassdata/nc_spm_08/landsat/group/test_group/subgroup/test_group@landsat/sig/sigFile
  314. self.baseFilePath = os.path.join(env['GISDBASE'],
  315. env['LOCATION_NAME'],
  316. env['MAPSET'],
  317. 'group', group.split('@')[0], # !
  318. 'subgroup', group,
  319. 'sig')
  320. self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
  321. self.btnCancel = wx.Button(parent = self.panel, id = wx.ID_CANCEL)
  322. self.btnOK = wx.Button(parent = self.panel, id = wx.ID_OK)
  323. self.btnOK.SetDefault()
  324. self.btnOK.Enable(False)
  325. self.__layout()
  326. self.fileNameCtrl.Bind(wx.EVT_TEXT, self.OnTextChanged)
  327. self.OnTextChanged(None)
  328. def OnTextChanged(self, event):
  329. """!Name for signature file given"""
  330. file = self.fileNameCtrl.GetValue()
  331. if len(file) > 0:
  332. self.btnOK.Enable(True)
  333. else:
  334. self.btnOK.Enable(False)
  335. path = os.path.join(self.baseFilePath, file)
  336. self.filePathText.SetLabel(path)
  337. bestSize = self.pathPanel.GetBestVirtualSize()
  338. self.pathPanel.SetVirtualSize(bestSize)
  339. self.pathPanel.Scroll(*bestSize)
  340. def __layout(self):
  341. """!Do layout"""
  342. sizer = wx.BoxSizer(wx.VERTICAL)
  343. dataSizer = wx.BoxSizer(wx.VERTICAL)
  344. dataSizer.Add(item = wx.StaticText(parent = self.panel, id = wx.ID_ANY,
  345. label = _("Enter name of signature file:")),
  346. proportion = 0, flag = wx.ALL, border = 3)
  347. self.fileNameCtrl = wx.TextCtrl(parent = self.panel, id = wx.ID_ANY, size = (400, -1))
  348. if self.fileName:
  349. self.fileNameCtrl.SetValue(self.fileName)
  350. dataSizer.Add(item = self.fileNameCtrl,
  351. proportion = 0, flag = wx.ALL | wx.EXPAND, border = 3)
  352. dataSizer.Add(item = wx.StaticText(parent = self.panel, id = wx.ID_ANY,
  353. label = _("Signature file path:")),
  354. proportion = 0, flag = wx.ALL, border = 3)
  355. self.pathPanel = scrolled.ScrolledPanel(self.panel, size = (-1, 40))
  356. pathSizer = wx.BoxSizer()
  357. self.filePathText = wx.StaticText(parent = self.pathPanel, id = wx.ID_ANY,
  358. label = self.baseFilePath)
  359. pathSizer.Add(self.filePathText, proportion = 1, flag = wx.ALL | wx.EXPAND, border = 1)
  360. self.pathPanel.SetupScrolling(scroll_x = True, scroll_y = False)
  361. self.pathPanel.SetSizer(pathSizer)
  362. dataSizer.Add(item = self.pathPanel,
  363. proportion = 0, flag = wx.ALL | wx.EXPAND, border = 3)
  364. # buttons
  365. btnSizer = wx.StdDialogButtonSizer()
  366. btnSizer.AddButton(self.btnCancel)
  367. btnSizer.AddButton(self.btnOK)
  368. btnSizer.Realize()
  369. sizer.Add(item = dataSizer, proportion = 1,
  370. flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
  371. sizer.Add(item = btnSizer, proportion = 0,
  372. flag = wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border = 5)
  373. self.panel.SetSizer(sizer)
  374. sizer.Fit(self)
  375. self.SetMinSize(self.GetSize())
  376. def GetFileName(self, fullPath = False):
  377. """!Returns signature file name
  378. @param fullPath return full path of sig. file
  379. """
  380. if fullPath:
  381. return os.path.join(self.baseFilePath, self.fileNameCtrl.GetValue())
  382. return self.fileNameCtrl.GetValue()