123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699 |
- """
- @package vnet.widgets
- @brief Base class for list of points.
- Classes:
- - widgets::PointsList
- - widgets::EditItem
- (C) 2012 by the GRASS Development Team
- This program is free software under the GNU General Public License
- (>=v2). Read the file COPYING that comes with GRASS for details.
- @author Original author Michael Barton
- @author Original version improved by Martin Landa <landa.martin gmail.com>
- @author Rewritten by Markus Metz redesign georectfier -> GCP Manage
- @author Stepan Turek <stepan.turek seznam.cz> (Created PointsList from GCPList) (GSoC 2012, mentor: Martin Landa)
- """
- import os
- import sys
- import six
- from copy import copy, deepcopy
- import wx
- from wx.lib.mixins.listctrl import ColumnSorterMixin, ListCtrlAutoWidthMixin
- from core import globalvar
- from core.gcmd import GError
- from gui_core.widgets import FloatValidator, IntegerValidator
- from gui_core.wrap import (
- BitmapFromImage,
- Button,
- ComboBox,
- ListCtrl,
- Panel,
- StaticBox,
- StaticText,
- TextCtrl,
- CheckListCtrlMixin,
- )
- if sys.version_info.major >= 3:
- basestring = str
- class PointsList(
- ListCtrl, CheckListCtrlMixin, ListCtrlAutoWidthMixin, ColumnSorterMixin
- ):
- def __init__(
- self,
- parent,
- cols,
- id=wx.ID_ANY,
- pos=wx.DefaultPosition,
- size=wx.DefaultSize,
- style=wx.LC_REPORT | wx.SUNKEN_BORDER | wx.LC_HRULES | wx.LC_SINGLE_SEL,
- ):
- """Creates list for points.
- PointsList class was created from GCPList class in GCP manager. It is possible
- to be shared by GCP and VNET front end.
- Important parameters:
- :param cols: is list containing list items. which represents columns.
- This columns will be added in order as they are in list.
- Class will add as first column "use" with number of point and checkbox.
- Structure of list item must be this:
- -1. item: column name
- -2. item: column label
- -3. item: If column is editable by user, it must contain convert function to convert
- inserted string to it's type for sorting. Use None for not editable
- columns. Values for insertion can be in list. This allows insert
- just values in the list.
- -4. item: Default value for column cell. Value should be given in it's type
- in order to sorting would work properly. If 3. item is list, it must be index
- of some item in the list.
- Example of cols parameter:
- column name, column label, convert function, default val
- @code
- cols = [
- ['E', _('source E'), float, 0.0],
- ['N', _('source N'), float, 0.0],
- ['E', _('target E'), float, 0.0],
- ['N', _('target N'), float, 0.0],
- ['F_Err', _('Forward error'), None, 0],
- ['B_Err', _(Backward error'), None, 0]
- ['type', _('type'), [_(""), _("Start point"), _("End point")], 0] # Select from 3 choices ("Start point", "End point"),
- # Choice with index 0 ("") is default.
- ]
- @endcode
- """
- ListCtrl.__init__(self, parent, id, pos, size, style)
- # Mixin settings
- CheckListCtrlMixin.__init__(self)
- ListCtrlAutoWidthMixin.__init__(self)
- # TextEditMixin.__init__(self)
- # inserts first column with points numbers and checkboxes
- cols.insert(0, ["use", _("use"), False, 0])
- self.colsData = cols
- self.dataTypes = {
- "colName": 0,
- "colLabel": 1,
- "colEditable": 2,
- "itemDefaultValue": 3,
- } # just for better understanding
- # information whether list items are checked or not
- self.CheckList = []
- self._createCols()
- self.hiddenCols = {}
- self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnItemSelected)
- self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnItemActivated)
- self.Bind(wx.EVT_LIST_COL_CLICK, self.OnColClick)
- self.selected = wx.NOT_FOUND
- self.selectedkey = -1
- # CheckListCtrlMixin must set an ImageList first
- self.il = self.GetImageList(wx.IMAGE_LIST_SMALL)
- # images for column sorting
- SmallUpArrow = BitmapFromImage(self.getSmallUpArrowImage())
- SmallDnArrow = BitmapFromImage(self.getSmallDnArrowImage())
- self.sm_dn = self.il.Add(SmallDnArrow)
- self.sm_up = self.il.Add(SmallUpArrow)
- # initialize column sorter
- self.itemDataMap = []
- ncols = self.GetColumnCount()
- ColumnSorterMixin.__init__(self, ncols)
- # init to ascending sort on first click
- self._colSortFlag = [1] * ncols
- # same structure as itemDataMap, information about choice index selected
- # if cell is in column without values to choose then is -1
- self.selIdxs = []
- self.ResizeColumns()
- self.SetColumnWidth(0, 50)
- def _createCols(self):
- """Creates columns in list"""
- if 0:
- # normal, simple columns
- for col in enumerate(self.colsData):
- iLabel = self.dataTypes["colLabel"]
- self.InsertColumn(col[0], col[1][iLabel])
- else:
- # the hard way: we want images on the column header
- info = wx.ListItem()
- info.SetMask(wx.LIST_MASK_TEXT | wx.LIST_MASK_IMAGE | wx.LIST_MASK_FORMAT)
- info.SetImage(-1)
- if globalvar.wxPythonPhoenix:
- info.Format = wx.LIST_FORMAT_LEFT
- else:
- info.m_format = wx.LIST_FORMAT_LEFT
- for col in enumerate(self.colsData):
- iLabel = self.dataTypes["colLabel"]
- info.SetText(col[1][iLabel])
- if globalvar.wxPythonPhoenix:
- self.InsertColumn(col[0], info)
- else:
- self.InsertColumnInfo(col[0], info)
- def AddItem(self):
- """Appends an item to list with default values"""
- iDefVal = self.dataTypes["itemDefaultValue"]
- iColEd = self.dataTypes["colEditable"]
- itemData = []
- itemIndexes = []
- for col in self.colsData:
- if type(col[iColEd]).__name__ == "list":
- itemData.append(col[iColEd][col[iDefVal]])
- itemIndexes.append(col[iDefVal])
- else:
- itemData.append(col[iDefVal])
- itemIndexes.append(-1) # not a choise column
- self.selIdxs.append(itemIndexes)
- for hCol in six.itervalues(self.hiddenCols):
- defVal = hCol["colsData"][iDefVal]
- if type(hCol["colsData"][iColEd]).__name__ == "list":
- hCol["itemDataMap"].append(hCol["colsData"][iColEd][defVal])
- hCol["selIdxs"].append(defVal)
- else:
- hCol["itemDataMap"].append(defVal)
- hCol["selIdxs"].append(-1)
- self.selectedkey = self.GetItemCount()
- itemData[0] = self.selectedkey + 1
- self.itemDataMap.append(copy(itemData))
- self.Append(list(map(str, itemData)))
- self.selected = self.GetItemCount() - 1
- self.SetItemData(self.selected, self.selectedkey)
- self.SetItemState(self.selected, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED)
- self.ResizeColumns()
- return self.selected
- def GetCellValue(self, key, colName):
- """Get value in cell of list using key (same regardless of sorting)"""
- colNum = self._getColumnNum(colName)
- if colNum < 0:
- return None
- iColEd = self.dataTypes["colEditable"]
- if self.selIdxs[key][colNum] != -1:
- return self.selIdxs[key][colNum]
- return self.itemDataMap[key][colNum]
- def GetCellSelIdx(self, key, colName):
- """Get selected index in cell of list using key (same regardless of sorting)
- :return: number of chosen value, if column has values to choose
- :return: -1 if column does not has values to choose
- """
- colNum = self._getColumnNum(colName)
- iColEd = self.dataTypes["colEditable"]
- return self.selIdxs[key][colNum]
- def EditCellIndex(self, index, colName, cellData):
- """Changes value in list using key (same regardless of sorting)"""
- colNum = self._getColumnNum(colName)
- key = self.GetItemData(index)
- iColEd = self.dataTypes["colEditable"]
- if type(self.colsData[colNum][iColEd]).__name__ == "list":
- cellVal = self.colsData[colNum][iColEd][cellData]
- self.selIdxs[key][colNum] = cellData
- else:
- cellVal = cellData
- self.selIdxs[key][colNum] = -1
- self.itemDataMap[key][colNum] = cellVal
- if not isinstance(cellVal, basestring):
- cellVal = str(cellVal)
- self.SetItem(index, colNum, cellVal)
- def EditCellKey(self, key, colName, cellData):
- """Changes value in list using index (changes during sorting)"""
- colNum = self._getColumnNum(colName)
- iColEd = self.dataTypes["colEditable"]
- if type(self.colsData[colNum][iColEd]).__name__ == "list":
- cellVal = self.colsData[colNum][iColEd][cellData]
- self.selIdxs[key][colNum] = cellData
- else:
- cellVal = cellData
- self.selIdxs[key][colNum] = -1
- self.itemDataMap[key][colNum] = cellVal
- index = self._findIndex(key)
- if index != -1:
- if not isinstance(cellVal, basestring):
- cellVal = str(cellVal)
- self.SetItem(index, colNum, cellVal)
- def _findIndex(self, key):
- """Find index for key"""
- index = -1
- while True:
- index = self.GetNextItem(index, wx.LIST_NEXT_BELOW)
- if key == self.GetItemData(index):
- return index
- if index == -1:
- break
- return -1
- def ChangeColEditable(self, colName, colType):
- """Change 3. item in constructor parameter cols (see the class constructor hint)"""
- colNum = self._getColumnNum(colName)
- iColEd = self.dataTypes["colEditable"]
- self.colsData[colNum][iColEd] = colType
- def DeleteItem(self):
- """Delete selected item in list"""
- if self.selected == wx.NOT_FOUND:
- return
- key = self.GetItemData(self.selected)
- ListCtrl.DeleteItem(self, self.selected)
- del self.itemDataMap[key]
- self.selIdxs.pop(key)
- # update hidden columns
- for hCol in six.itervalues(self.hiddenCols):
- hCol["itemDataMap"].pop(key)
- hCol["selIdxs"].pop(key)
- # update key and point number
- for newkey in range(key, len(self.itemDataMap)):
- index = self.FindItem(-1, newkey + 1)
- self.itemDataMap[newkey][0] = newkey
- self.SetItem(index, 0, str(newkey + 1))
- self.SetItemData(index, newkey)
- # update selected
- if self.GetItemCount() > 0:
- if self.selected < self.GetItemCount():
- self.selectedkey = self.GetItemData(self.selected)
- else:
- self.selected = self.GetItemCount() - 1
- self.selectedkey = self.GetItemData(self.selected)
- self.SetItemState(
- self.selected, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED
- )
- else:
- self.selected = wx.NOT_FOUND
- self.selectedkey = -1
- def ClearItem(self, event):
- """Set all values to default in selected item of points list and uncheck it."""
- if self.selected == wx.NOT_FOUND:
- return
- index = self.selected
- iDefVal = self.dataTypes["itemDefaultValue"]
- iColEd = self.dataTypes["colEditable"]
- i = 0
- for col in self.colsData:
- if i == 0:
- i += 1
- continue
- if type(col[iColEd]).__name__ == "list":
- self.EditCell(index, i, col[iColEd][col[iDefVal]])
- else:
- self.EditCell(index, i, col[iDefVal])
- i += 1
- self.CheckItem(index, False)
- def ResizeColumns(self, minWidth=[90, 120]):
- """Resize columns"""
- for i in range(self.GetColumnCount()):
- self.SetColumnWidth(i, wx.LIST_AUTOSIZE)
- # first column is checkbox, don't set to minWidth
- if i > 0 and self.GetColumnWidth(i) < minWidth[i > 4]:
- self.SetColumnWidth(i, minWidth[i > 4])
- self.SendSizeEvent()
- def GetSelected(self):
- """Get index of selected item."""
- return self.selected
- # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py
- def GetSortImages(self):
- return (self.sm_dn, self.sm_up)
- # Used by the ColumnSorterMixin, see wx/lib/mixins/listctrl.py
- def GetListCtrl(self):
- return self
- def OnItemActivated(self, event):
- """When item is double clicked, open editor to edit editable columns."""
- data = []
- index = event.GetIndex()
- key = self.GetItemData(index)
- changed = False
- iColEd = self.dataTypes["colEditable"]
- for col in enumerate(self.colsData):
- if col[1][iColEd]:
- data.append(
- [
- col[0], # culumn number
- self.GetItem(index, col[0]).GetText(), # cell value
- col[1][iColEd],
- ]
- ) # convert function for type check
- if not data:
- return
- dlg = self.CreateEditDialog(data=data, pointNo=key)
- if dlg.ShowModal() == wx.ID_OK:
- editedData = dlg.GetValues() # string
- if len(editedData) == 0:
- GError(
- parent=self,
- message=_("Invalid value inserted. Operation canceled."),
- )
- else:
- i = 0
- for editedCell in editedData:
- if editedCell[1] != data[i][1]:
- value = editedCell[1]
- if not isinstance(editedCell[1], basestring):
- value = str(editedCell[1])
- self.SetItem(index, editedCell[0], value)
- self.itemDataMap[key][editedCell[0]] = editedCell[1]
- changed = True
- i += 1
- self.selIdxs[key] = dlg.GetSelectionIndexes()
- dlg.Destroy()
- return changed, key
- def CreateEditDialog(self, data, pointNo):
- """Helper function
- It is possible to define in child derived class
- and adapt created dialog (e. g. it's title...)
- """
- return EditItem(parent=self, id=wx.ID_ANY, data=data, pointNo=pointNo)
- def OnColClick(self, event):
- """ListCtrl forgets selected item..."""
- self.selected = self.FindItem(-1, self.selectedkey)
- self.SetItemState(self.selected, wx.LIST_STATE_SELECTED, wx.LIST_STATE_SELECTED)
- event.Skip()
- def OnItemSelected(self, event):
- """Updates class attributes holding information about selected item"""
- if self.selected != event.GetIndex():
- self.selected = event.GetIndex()
- self.selectedkey = self.GetItemData(self.selected)
- event.Skip()
- def getSmallUpArrowImage(self):
- """Get arrow up symbol for indication of sorting"""
- stream = open(os.path.join(globalvar.IMGDIR, "small_up_arrow.png"), "rb")
- try:
- img = wx.Image(stream)
- finally:
- stream.close()
- return img
- def getSmallDnArrowImage(self):
- """Get arrow down symbol for indication of sorting"""
- stream = open(os.path.join(globalvar.IMGDIR, "small_down_arrow.png"), "rb")
- try:
- img = wx.Image(stream)
- finally:
- stream.close()
- return img
- def _getColumnNum(self, colName):
- """Get position of column among showed columns
- :param colName: name of column
- :type colName: str
- :return: index of columns or -1 if col was not found
- """
- for iCol, col in enumerate(self.colsData):
- if colName == col[0]:
- return iCol
- return -1
- def HideColumn(self, colName):
- """Hide column (hidden columns are not editable)
- :param colName: name of column
- :type colName: str
- :return: True - if column was hidden
- :return: False - if position is not valid or column is not showed
- """
- colNum = self._getColumnNum(colName)
- if colNum == -1:
- return False
- hiddenCol = self.GetColumn(colNum)
- self.DeleteColumn(colNum)
- self.hiddenCols[colName] = {}
- self.hiddenCols[colName]["wxCol"] = hiddenCol
- hiddenMaps = []
- hiddenSelIdxs = []
- for item in self.itemDataMap:
- hiddenMaps.append(item.pop(colNum))
- for item in self.selIdxs:
- hiddenSelIdxs.append(item.pop(colNum))
- self.hiddenCols[colName]["itemDataMap"] = hiddenMaps
- self.hiddenCols[colName]["selIdxs"] = hiddenSelIdxs
- self.hiddenCols[colName]["colsData"] = self.colsData.pop(colNum)
- self.ResizeColumns()
- return True
- def ShowColumn(self, colName, pos):
- """Show column
- :param colName: name of column
- :type colName: str
- :param pos: zero based index of position among showed columns (including added 'use' column)
- :return: True if column was shown
- :return: False if position is not valid or column is not hidden
- """
- if pos < 0 and pos >= self.self.GetColumnCount():
- return False
- if colName in self.hiddenCols:
- col = self.hiddenCols[colName]
- for item in enumerate(self.itemDataMap):
- item[1].insert(pos, col["itemDataMap"][item[0]])
- for item in enumerate(self.selIdxs):
- item[1].insert(pos, col["selIdxs"][item[0]])
- self.colsData.insert(pos, col["colsData"])
- self.InsertColumnItem(pos, col["wxCol"])
- self.ResizeColumns()
- del self.hiddenCols[colName]
- return True
- return False
- def IsShown(self, colName):
- """Is column shown
- :param colName: name of column
- :type colName: str
- :return: True - if is shown
- :return: False - if is not shown
- """
- if self._getColumnNum(colName) == -1:
- return False
- else:
- return True
- class EditItem(wx.Dialog):
- def __init__(
- self,
- parent,
- data,
- pointNo,
- itemCap="Point No.",
- id=wx.ID_ANY,
- title=_("Edit point"),
- style=wx.DEFAULT_DIALOG_STYLE,
- ):
- """Dialog for editing item cells in list"""
- wx.Dialog.__init__(self, parent, id, title=_(title), style=style)
- self.parent = parent
- panel = Panel(parent=self)
- sizer = wx.BoxSizer(wx.VERTICAL)
- box = StaticBox(
- parent=panel, id=wx.ID_ANY, label=" %s %s " % (_(itemCap), str(pointNo + 1))
- )
- boxSizer = wx.StaticBoxSizer(box, wx.VERTICAL)
- # source coordinates
- gridSizer = wx.GridBagSizer(vgap=5, hgap=5)
- self.fields = []
- self.data = deepcopy(data)
- col = 0
- row = 0
- iField = 0
- for cell in self.data:
- # Select
- if type(cell[2]).__name__ == "list":
- self.fields.append(
- ComboBox(
- parent=panel,
- id=wx.ID_ANY,
- choices=cell[2],
- style=wx.CB_READONLY,
- size=(110, -1),
- )
- )
- # Text field
- else:
- if cell[2] == float:
- validator = FloatValidator()
- elif cell[2] == int:
- validator = IntegerValidator()
- else:
- validator = None
- if validator:
- self.fields.append(
- TextCtrl(
- parent=panel,
- id=wx.ID_ANY,
- validator=validator,
- size=(150, -1),
- )
- )
- else:
- self.fields.append(
- TextCtrl(parent=panel, id=wx.ID_ANY, size=(150, -1))
- )
- value = cell[1]
- if not isinstance(cell[1], basestring):
- value = str(cell[1])
- self.fields[iField].SetValue(value)
- label = StaticText(
- parent=panel,
- id=wx.ID_ANY,
- label=_(parent.GetColumn(cell[0]).GetText()) + ":",
- ) # name of column)
- gridSizer.Add(label, flag=wx.ALIGN_CENTER_VERTICAL, pos=(row, col))
- col += 1
- gridSizer.Add(self.fields[iField], pos=(row, col))
- if col % 3 == 0:
- col = 0
- row += 1
- else:
- col += 1
- iField += 1
- boxSizer.Add(gridSizer, proportion=1, flag=wx.EXPAND | wx.ALL, border=5)
- sizer.Add(boxSizer, proportion=1, flag=wx.EXPAND | wx.ALL, border=5)
- #
- # buttons
- #
- self.btnCancel = Button(panel, wx.ID_CANCEL)
- self.btnOk = Button(panel, wx.ID_OK)
- self.btnOk.SetDefault()
- btnSizer = wx.StdDialogButtonSizer()
- btnSizer.AddButton(self.btnCancel)
- btnSizer.AddButton(self.btnOk)
- btnSizer.Realize()
- sizer.Add(btnSizer, proportion=0, flag=wx.ALIGN_RIGHT | wx.ALL, border=5)
- panel.SetSizer(sizer)
- sizer.Fit(self)
- def GetValues(self):
- """Return list of values (as strings)."""
- iField = 0
- for cell in self.data:
- value = self.fields[iField].GetValue()
- if type(cell[2]).__name__ == "list":
- cell[1] = value
- else:
- try:
- cell[1] = cell[2](value)
- except ValueError:
- return []
- iField += 1
- return self.data
- def GetSelectionIndexes(self):
- """Return indexes of selected values (works just for choice
- columns).
- """
- iField = 0
- itemIndexes = []
- for cell in self.parent.colsData:
- if type(cell[2]).__name__ == "list":
- itemIndexes.append(self.fields[iField].GetSelection())
- else:
- itemIndexes.append(-1) # not a choise column
- if cell[2]:
- iField += 1
- return itemIndexes
|