widgets.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. """!
  2. @package vnet.widgets
  3. @brief Base class for list of points.
  4. Classes:
  5. - widgets::PointsList
  6. - widgets::EditItem
  7. (C) 2012 by the GRASS Development Team
  8. This program is free software under the GNU General Public License
  9. (>=v2). Read the file COPYING that comes with GRASS for details.
  10. @author Original author Michael Barton
  11. @author Original version improved by Martin Landa <landa.martin gmail.com>
  12. @author Rewritten by Markus Metz redesign georectfier -> GCP Manage
  13. @author Stepan Turek <stepan.turek seznam.cz> (Created PointsList from GCPList) (GSoC 2012, mentor: Martin Landa)
  14. """
  15. import os
  16. import wx
  17. from copy import copy, deepcopy
  18. import wx
  19. from wx.lib.mixins.listctrl import CheckListCtrlMixin, ColumnSorterMixin, ListCtrlAutoWidthMixin, TextEditMixin
  20. from core import globalvar
  21. from core.utils import _
  22. class PointsList(wx.ListCtrl,
  23. CheckListCtrlMixin,
  24. ListCtrlAutoWidthMixin,
  25. ColumnSorterMixin):
  26. def __init__(self, parent, cols, id=wx.ID_ANY,
  27. pos=wx.DefaultPosition, size=wx.DefaultSize,
  28. style=wx.LC_REPORT | wx.SUNKEN_BORDER | wx.LC_HRULES |
  29. wx.LC_SINGLE_SEL):
  30. """!Creates list for points.
  31. PointsList class was created from GCPList class in GCP manager. It is possible
  32. to be shared by GCP and VNET front end.
  33. Important parameters:
  34. @param cols is list containing list items. which represents columns.
  35. This columns will be added in order as they are in list.
  36. Class will add as first column "use" with number of point and checkbox.
  37. Structure of list item must be this:
  38. -1. item: column name
  39. -2. item: column label
  40. -3. item: If column is editable by user, it must contain convert function to convert
  41. inserted string to it's type for sorting. Use None for not editable
  42. columns. Values for insertion can be in list. This allows insert
  43. just values in the list.
  44. -4. item: Default value for column cell. Value should be given in it's type
  45. in order to sorting would work properly. If 3. item is list, it must be index
  46. of some item in the list.
  47. Example of cols parameter:
  48. column name, column label, convert function, default val
  49. @code
  50. cols = [
  51. ['E', _('source E'), float, 0.0],
  52. ['N', _('source N'), float, 0.0],
  53. ['E', _('target E'), float, 0.0],
  54. ['N', _('target N'), float, 0.0],
  55. ['F_Err', _('Forward error'), None, 0],
  56. ['B_Err', _(Backward error'), None, 0]
  57. ['type', _('type'), [_(""), _("Start point"), _("End point")], 0] # Select from 3 choices ("Start point", "End point"),
  58. # Choice with index 0 ("") is default.
  59. ]
  60. @endcode
  61. """
  62. wx.ListCtrl.__init__(self, parent, id, pos, size, style)
  63. # Mixin settings
  64. CheckListCtrlMixin.__init__(self)
  65. ListCtrlAutoWidthMixin.__init__(self)
  66. # TextEditMixin.__init__(self)
  67. # inserts first column with points numbers and checkboxes
  68. cols.insert(0, ['use', _('use'), False, 0])
  69. self.colsData = cols
  70. self.dataTypes = {"colName" : 0,
  71. "colLabel" : 1,
  72. "colEditable" : 2,
  73. "itemDefaultValue" : 3} # just for better understanding
  74. # information whether list items are checked or not
  75. self.CheckList = []
  76. self._createCols()
  77. self.hiddenCols = {}
  78. self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected)
  79. self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated)
  80. self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick)
  81. self.selected = wx.NOT_FOUND
  82. self.selectedkey = -1
  83. # CheckListCtrlMixin must set an ImageList first
  84. self.il = self.GetImageList(wx.IMAGE_LIST_SMALL)
  85. # images for column sorting
  86. SmallUpArrow = wx.BitmapFromImage(self.getSmallUpArrowImage())
  87. SmallDnArrow = wx.BitmapFromImage(self.getSmallDnArrowImage())
  88. self.sm_dn = self.il.Add(SmallDnArrow)
  89. self.sm_up = self.il.Add(SmallUpArrow)
  90. # initialize column sorter
  91. self.itemDataMap = []
  92. ncols = self.GetColumnCount()
  93. ColumnSorterMixin.__init__(self, ncols)
  94. # init to ascending sort on first click
  95. self._colSortFlag = [1] * ncols
  96. # same structure as itemDataMap, information about choice index selected
  97. # if cell is in column without values to choose then is -1
  98. self.selIdxs = []
  99. self.ResizeColumns()
  100. self.SetColumnWidth(0, 50)
  101. def _createCols(self):
  102. """!Creates columns in list"""
  103. if 0:
  104. # normal, simple columns
  105. for col in enumerate(self.colsData):
  106. iLabel = self.dataTypes["colLabel"]
  107. self.InsertColumn(col[0], col[1][iLabel])
  108. else:
  109. # the hard way: we want images on the column header
  110. info = wx.ListItem()
  111. info.SetMask(wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT)
  112. info.SetImage(-1)
  113. info.m_format = wx.LIST_FORMAT_LEFT
  114. for col in enumerate(self.colsData):
  115. iLabel = self.dataTypes["colLabel"]
  116. info.SetText(col[1][iLabel])
  117. self.InsertColumnInfo(col[0], info)
  118. def AddItem(self):
  119. """!Appends an item to list with default values"""
  120. iDefVal = self.dataTypes["itemDefaultValue"]
  121. iColEd = self.dataTypes["colEditable"]
  122. itemData = []
  123. itemIndexes = []
  124. for col in self.colsData:
  125. if type(col[iColEd]).__name__ == "list":
  126. itemData.append(col[iColEd][col[iDefVal]])
  127. itemIndexes.append(col[iDefVal])
  128. else:
  129. itemData.append(col[iDefVal])
  130. itemIndexes.append(-1)# not a choise column
  131. self.selIdxs.append(itemIndexes)
  132. for hCol in self.hiddenCols.itervalues():
  133. defVal = hCol['colsData'][iDefVal]
  134. if type(hCol['colsData'][iColEd]).__name__ == "list":
  135. hCol['itemDataMap'].append(hCol['colsData'][iColEd][defVal])
  136. hCol['selIdxs'].append(defVal)
  137. else:
  138. hCol['itemDataMap'].append(defVal)
  139. hCol['selIdxs'].append(-1)
  140. self.selectedkey = self.GetItemCount()
  141. itemData[0] = self.selectedkey + 1
  142. self.itemDataMap.append(copy(itemData))
  143. self.Append(map(str, itemData))
  144. self.selected = self.GetItemCount() - 1
  145. self.SetItemData(self.selected, self.selectedkey)
  146. self.SetItemState(self.selected,
  147. wx.LIST_STATE_SELECTED,
  148. wx.LIST_STATE_SELECTED)
  149. self.ResizeColumns()
  150. return self.selected
  151. def GetCellValue(self, key, colName):
  152. """!Get value in cell of list using key (same regardless of sorting)"""
  153. colNum = self._getColumnNum(colName)
  154. if colNum < 0:
  155. return None
  156. iColEd = self.dataTypes["colEditable"]
  157. if self.selIdxs[key][colNum] != -1:
  158. return self.selIdxs[key][colNum]
  159. return self.itemDataMap[key][colNum]
  160. def GetCellSelIdx(self, key, colName):
  161. """!Get selected index in cell of list using key (same regardless of sorting)
  162. @return number of chosen value, if column has values to choose
  163. @return -1 if column does not has values to choose
  164. """
  165. colNum = self._getColumnNum(colName)
  166. iColEd = self.dataTypes["colEditable"]
  167. return self.selIdxs[key][colNum]
  168. def EditCellIndex(self, index, colName, cellData):
  169. """!Changes value in list using key (same regardless of sorting)"""
  170. colNum = self._getColumnNum(colName)
  171. key = self.GetItemData(index)
  172. iColEd = self.dataTypes["colEditable"]
  173. if type(self.colsData[colNum][iColEd]).__name__ == "list":
  174. cellVal = self.colsData[colNum][iColEd][cellData]
  175. self.selIdxs[key][colNum] = cellData
  176. else:
  177. cellVal = cellData
  178. self.selIdxs[key][colNum] = -1
  179. self.itemDataMap[key][colNum] = cellVal
  180. self.SetStringItem(index, colNum, str(cellVal))
  181. def EditCellKey(self, key, colName, cellData):
  182. """!Changes value in list using index (changes during sorting)"""
  183. colNum = self._getColumnNum(colName)
  184. iColEd = self.dataTypes["colEditable"]
  185. if type(self.colsData[colNum][iColEd]).__name__ == "list":
  186. cellVal = self.colsData[colNum][iColEd][cellData]
  187. self.selIdxs[key][colNum] = cellData
  188. else:
  189. cellVal = cellData
  190. self.selIdxs[key][colNum] = -1
  191. self.itemDataMap[key][colNum] = cellVal
  192. index = self._findIndex(key)
  193. if index != -1:
  194. self.SetStringItem(index, colNum, str(cellVal))
  195. def _findIndex(self, key):
  196. """!Find index for key"""
  197. index = -1
  198. while True:
  199. index = self.GetNextItem(index,
  200. wx.LIST_NEXT_BELOW)
  201. if key == self.GetItemData(index):
  202. return index
  203. if index == -1:
  204. break
  205. return -1
  206. def ChangeColEditable(self, colName, colType):
  207. """!Change 3. item in constructor parameter cols (see the class constructor hint)"""
  208. colNum = self._getColumnNum(colName)
  209. iColEd = self.dataTypes["colEditable"]
  210. self.colsData[colNum][iColEd] = colType
  211. def DeleteItem(self):
  212. """!Delete selected item in list"""
  213. if self.selected == wx.NOT_FOUND:
  214. return
  215. key = self.GetItemData(self.selected)
  216. wx.ListCtrl.DeleteItem(self, self.selected)
  217. del self.itemDataMap[key]
  218. self.selIdxs.pop(key)
  219. # update hidden columns
  220. for hCol in self.hiddenCols.itervalues():
  221. hCol['itemDataMap'].pop(key)
  222. hCol['selIdxs'].pop(key)
  223. # update key and point number
  224. for newkey in range(key, len(self.itemDataMap)):
  225. index = self.FindItemData(-1, newkey + 1)
  226. self.itemDataMap[newkey][0] = newkey
  227. self.SetStringItem(index, 0, str(newkey + 1))
  228. self.SetItemData(index, newkey)
  229. # update selected
  230. if self.GetItemCount() > 0:
  231. if self.selected < self.GetItemCount():
  232. self.selectedkey = self.GetItemData(self.selected)
  233. else:
  234. self.selected = self.GetItemCount() - 1
  235. self.selectedkey = self.GetItemData(self.selected)
  236. self.SetItemState(self.selected,
  237. wx.LIST_STATE_SELECTED,
  238. wx.LIST_STATE_SELECTED)
  239. else:
  240. self.selected = wx.NOT_FOUND
  241. self.selectedkey = -1
  242. def ClearItem(self, event):
  243. """"!Set all values to default in selected item of points list and uncheck it."""
  244. if self.selected == wx.NOT_FOUND:
  245. return
  246. index = self.selected
  247. iDefVal = self.dataTypes["itemDefaultValue"]
  248. iColEd = self.dataTypes["colEditable"]
  249. i = 0
  250. for col in self.colsData:
  251. if i == 0:
  252. i += 1
  253. continue
  254. if type(col[iColEd]).__name__ == "list":
  255. self.EditCell(index, i, col[iColEd][col[iDefVal]])
  256. else:
  257. self.EditCell(index, i, col[iDefVal])
  258. i += 1
  259. self.CheckItem(index, False)
  260. def ResizeColumns(self, minWidth = [90, 120]):
  261. """!Resize columns"""
  262. for i in range(self.GetColumnCount()):
  263. self.SetColumnWidth(i, wx.LIST_AUTOSIZE)
  264. # first column is checkbox, don't set to minWidth
  265. if i > 0 and self.GetColumnWidth(i) < minWidth[i > 4]:
  266. self.SetColumnWidth(i, minWidth[i > 4])
  267. self.SendSizeEvent()
  268. def GetSelected(self):
  269. """!Get index of selected item."""
  270. return self.selected
  271. # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py
  272. def GetSortImages(self):
  273. return (self.sm_dn, self.sm_up)
  274. # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py
  275. def GetListCtrl(self):
  276. return self
  277. def OnItemActivated(self, event):
  278. """!When item is double clicked, open editor to edit editable columns."""
  279. data = []
  280. index = event.GetIndex()
  281. key = self.GetItemData(index)
  282. changed = False
  283. iColEd = self.dataTypes["colEditable"]
  284. for col in enumerate(self.colsData):
  285. if col[1][iColEd]:
  286. data.append([col[0], #culumn number
  287. self.GetItem(index, col[0]).GetText(), #cell value
  288. col[1][iColEd]]) #convert function for type check
  289. if not data:
  290. return
  291. dlg = self.CreateEditDialog(data = data, pointNo = key)
  292. if dlg.ShowModal() == wx.ID_OK:
  293. editedData = dlg.GetValues() # string
  294. if len(editedData) == 0:
  295. GError(parent = self,
  296. message=_("Invalid value inserted. Operation canceled."))
  297. else:
  298. i = 0
  299. for editedCell in editedData:
  300. if editedCell[1] != data[i][1]:
  301. self.SetStringItem(index, editedCell[0], str(editedCell[1]))
  302. self.itemDataMap[key][editedCell[0]] = editedCell[1]
  303. changed = True
  304. i += 1
  305. self.selIdxs[key] = dlg.GetSelectionIndexes()
  306. dlg.Destroy()
  307. return changed, key
  308. def CreateEditDialog(self, data, pointNo):
  309. """!Helper function
  310. It is possible to define in child derived class
  311. and adapt created dialog (e. g. it's title...)
  312. """
  313. return EditItem(parent=self, id=wx.ID_ANY, data = data, pointNo=pointNo)
  314. def OnColClick(self, event):
  315. """!ListCtrl forgets selected item..."""
  316. self.selected = self.FindItemData(-1, self.selectedkey)
  317. self.SetItemState(self.selected,
  318. wx.LIST_STATE_SELECTED,
  319. wx.LIST_STATE_SELECTED)
  320. event.Skip()
  321. def OnItemSelected(self, event):
  322. """!Updates class attributes holding information about selected item"""
  323. if self.selected != event.GetIndex():
  324. self.selected = event.GetIndex()
  325. self.selectedkey = self.GetItemData(self.selected)
  326. event.Skip()
  327. def getSmallUpArrowImage(self):
  328. """!Get arrow up symbol for indication of sorting"""
  329. stream = open(os.path.join(globalvar.IMGDIR, 'small_up_arrow.png'), 'rb')
  330. try:
  331. img = wx.ImageFromStream(stream)
  332. finally:
  333. stream.close()
  334. return img
  335. def getSmallDnArrowImage(self):
  336. """!Get arrow down symbol for indication of sorting"""
  337. stream = open(os.path.join(globalvar.IMGDIR, 'small_down_arrow.png'), 'rb')
  338. try:
  339. img = wx.ImageFromStream(stream)
  340. finally:
  341. stream.close()
  342. return img
  343. def _getColumnNum(self, colName):
  344. """!Get position of column among showed columns
  345. @param colName - name of column
  346. @return index of columns or -1 if col was not found
  347. """
  348. for iCol, col in enumerate(self.colsData):
  349. if colName == col[0]:
  350. return iCol
  351. return -1
  352. def HideColumn(self, colName):
  353. """!Hide column (hidden columns are not editable)
  354. @param colName - name of column
  355. @return True - if column was hidden
  356. @return False - if position is not valid or column is not showed
  357. """
  358. colNum = self._getColumnNum(colName)
  359. if colNum == -1:
  360. return False
  361. hiddenCol = self.GetColumn(colNum)
  362. self.DeleteColumn(colNum)
  363. self.hiddenCols[colName] = {}
  364. self.hiddenCols[colName]['wxCol'] = hiddenCol
  365. hiddenMaps = []
  366. hiddenSelIdxs = []
  367. for item in self.itemDataMap:
  368. hiddenMaps.append(item.pop(colNum))
  369. for item in self.selIdxs:
  370. hiddenSelIdxs.append(item.pop(colNum))
  371. self.hiddenCols[colName]['itemDataMap'] = hiddenMaps
  372. self.hiddenCols[colName]['selIdxs'] = hiddenSelIdxs
  373. self.hiddenCols[colName]['colsData'] = self.colsData.pop(colNum)
  374. self.ResizeColumns()
  375. return True
  376. def ShowColumn(self, colName, pos):
  377. """!Show column
  378. @param colName - name of column
  379. @param pos - zero based index of position among showed columns (including added 'use' column)
  380. @return True - if column was shown
  381. @return False - if position is not valid or column is not hidden
  382. """
  383. if pos < 0 and pos >= self.self.GetColumnCount():
  384. return False
  385. if self.hiddenCols.has_key(colName):
  386. col = self.hiddenCols[colName]
  387. for item in enumerate(self.itemDataMap):
  388. item[1].insert(pos, col['itemDataMap'][item[0]])
  389. for item in enumerate(self.selIdxs):
  390. item[1].insert(pos, col['selIdxs'][item[0]])
  391. self.colsData.insert(pos, col['colsData'])
  392. self.InsertColumnItem(pos, col['wxCol'])
  393. self.ResizeColumns()
  394. del self.hiddenCols[colName]
  395. return True
  396. return False
  397. def IsShown(self, colName):
  398. """!Is column shown
  399. @param colName - name of column
  400. @return True - if is shown
  401. @return False - if is not shown
  402. """
  403. if self._getColumnNum(colName) == -1:
  404. return False
  405. else:
  406. return True
  407. class EditItem(wx.Dialog):
  408. def __init__(self, parent, data, pointNo, itemCap = "Point No." ,id=wx.ID_ANY,
  409. title =_("Edit point"), style=wx.DEFAULT_DIALOG_STYLE):
  410. """!Dialog for editing item cells in list"""
  411. wx.Dialog.__init__(self, parent, id, title=_(title), style=style)
  412. self.parent = parent
  413. panel = wx.Panel(parent=self)
  414. sizer = wx.BoxSizer(wx.VERTICAL)
  415. box = wx.StaticBox (parent=panel, id=wx.ID_ANY,
  416. label=" %s %s " % (_(itemCap), str(pointNo + 1)))
  417. boxSizer = wx.StaticBoxSizer(box, wx.VERTICAL)
  418. # source coordinates
  419. gridSizer = wx.GridBagSizer(vgap=5, hgap=5)
  420. self.fields = []
  421. self.data = deepcopy(data)
  422. col = 0
  423. row = 0
  424. iField = 0
  425. for cell in self.data:
  426. # Select
  427. if type(cell[2]).__name__ == "list":
  428. self.fields.append(wx.ComboBox(parent = panel, id = wx.ID_ANY,
  429. choices = cell[2],
  430. style = wx.CB_READONLY,
  431. size = (110, -1)))
  432. # Text field
  433. else:
  434. if cell[2] == float:
  435. validator = FloatValidator()
  436. elif cell[2] == int:
  437. validator = IntegerValidator()
  438. else:
  439. validator = None
  440. if validator:
  441. self.fields.append(wx.TextCtrl(parent=panel, id=wx.ID_ANY,
  442. validator = validator, size=(150, -1)))
  443. else:
  444. self.fields.append(wx.TextCtrl(parent=panel, id=wx.ID_ANY,
  445. size=(150, -1)))
  446. self.fields[iField].SetValue(str(cell[1]))
  447. label = wx.StaticText(parent = panel, id=wx.ID_ANY,
  448. label = _(parent.GetColumn(cell[0]).GetText()) + ":") # name of column)
  449. gridSizer.Add(item=label,
  450. flag=wx.ALIGN_CENTER_VERTICAL,
  451. pos=(row, col))
  452. col += 1
  453. gridSizer.Add(item=self.fields[iField],
  454. pos=(row, col))
  455. if col%3 == 0:
  456. col = 0
  457. row += 1
  458. else:
  459. col += 1
  460. iField += 1
  461. boxSizer.Add(item=gridSizer, proportion=1,
  462. flag=wx.EXPAND | wx.ALL, border=5)
  463. sizer.Add(item=boxSizer, proportion=1,
  464. flag=wx.EXPAND | wx.ALL, border=5)
  465. #
  466. # buttons
  467. #
  468. self.btnCancel = wx.Button(panel, wx.ID_CANCEL)
  469. self.btnOk = wx.Button(panel, wx.ID_OK)
  470. self.btnOk.SetDefault()
  471. btnSizer = wx.StdDialogButtonSizer()
  472. btnSizer.AddButton(self.btnCancel)
  473. btnSizer.AddButton(self.btnOk)
  474. btnSizer.Realize()
  475. sizer.Add(item=btnSizer, proportion=0,
  476. flag=wx.ALIGN_RIGHT | wx.ALL, border=5)
  477. panel.SetSizer(sizer)
  478. sizer.Fit(self)
  479. def GetValues(self):
  480. """!Return list of values (as strings).
  481. """
  482. iField = 0
  483. for cell in self.data:
  484. value = self.fields[iField].GetValue()
  485. if type(cell[2]).__name__ == "list":
  486. cell[1] = value
  487. else:
  488. try:
  489. cell[1] = cell[2](value)
  490. except ValueError:
  491. return []
  492. iField += 1
  493. return self.data
  494. def GetSelectionIndexes(self):
  495. """!Return indexes of selected values (works just for choice columns).
  496. """
  497. iField = 0
  498. itemIndexes = []
  499. for cell in self.parent.colsData:
  500. if type(cell[2]).__name__ == "list":
  501. itemIndexes.append(self.fields[iField].GetSelection())
  502. else:
  503. itemIndexes.append(-1) # not a choise column
  504. if cell[2]:
  505. iField += 1
  506. return itemIndexes