Edouard@2334: #!/usr/bin/env python
Edouard@2334: # -*- coding: utf-8 -*-
Edouard@2334: 
Edouard@2334: # See COPYING file for copyrights details.
Edouard@2334: 
Edouard@2334: from __future__ import absolute_import
Edouard@2334: import wx
Edouard@2334: import wx.dataview as dv
Edouard@2339: import PSKManagement as PSK
Edouard@2428: from PSKManagement import *
Edouard@2428: from dialogs.IDMergeDialog import IDMergeDialog
Edouard@2334: 
edouard@2492: 
Edouard@2336: class IDBrowserModel(dv.PyDataViewIndexListModel):
Edouard@2340:     def __init__(self, project_path, columncount):
Edouard@2340:         self.project_path = project_path
Edouard@2334:         self.columncount = columncount
Edouard@2340:         self.data = PSK.GetData(project_path)
Edouard@2334:         dv.PyDataViewIndexListModel.__init__(self, len(self.data))
Edouard@2334: 
Edouard@2334:     def _saveData(self):
Edouard@2340:         PSK.SaveData(self.project_path, self.data)
Edouard@2334: 
Edouard@2334:     def GetColumnType(self, col):
Edouard@2334:         return "string"
Edouard@2334: 
Edouard@2334:     def GetValueByRow(self, row, col):
Edouard@2334:         return self.data[row][col]
Edouard@2334: 
Edouard@2334:     def SetValueByRow(self, value, row, col):
Edouard@2334:         self.data[row][col] = value
Edouard@2334:         self._saveData()
Edouard@2334: 
Edouard@2334:     def GetColumnCount(self):
Edouard@2334:         return len(self.data[0]) if self.data else self.columncount
Edouard@2334: 
Edouard@2334:     def GetCount(self):
Edouard@2334:         return len(self.data)
edouard@2492: 
Edouard@2334:     def Compare(self, item1, item2, col, ascending):
edouard@2492:         if not ascending:  # swap sort order?
Edouard@2334:             item2, item1 = item1, item2
Edouard@2334:         row1 = self.GetRow(item1)
Edouard@2334:         row2 = self.GetRow(item2)
Edouard@2334:         if col == 0:
Edouard@2334:             return cmp(int(self.data[row1][col]), int(self.data[row2][col]))
Edouard@2334:         else:
Edouard@2334:             return cmp(self.data[row1][col], self.data[row2][col])
Edouard@2334: 
Edouard@2334:     def DeleteRows(self, rows):
Edouard@2334:         rows = list(rows)
Edouard@2334:         rows.sort(reverse=True)
edouard@2492: 
Edouard@2334:         for row in rows:
Edouard@2340:             PSK.DeleteID(self.project_path, self.data[row][COL_ID])
Edouard@2334:             del self.data[row]
Edouard@2334:             self.RowDeleted(row)
Edouard@2334:         self._saveData()
edouard@2492: 
Edouard@2334:     def AddRow(self, value):
Edouard@2334:         self.data.append(value)
Edouard@2334:         self.RowAppended()
Edouard@2334:         self._saveData()
Edouard@2334: 
Edouard@2340:     def Import(self, filepath, sircb):
Edouard@2428:         data = PSK.ImportIDs(self.project_path, filepath, sircb)
Edouard@2428:         if data is not None:
Edouard@2428:             self.data = data
edouard@2492:             self.Reset(len(self.data))
Edouard@2340: 
Edouard@2340:     def Export(self, filepath):
Edouard@2340:         PSK.ExportIDs(self.project_path, filepath)
Edouard@2340: 
edouard@2492: 
edouard@2492: colflags = dv.DATAVIEW_COL_RESIZABLE | dv.DATAVIEW_COL_SORTABLE
edouard@2492: 
Edouard@2334: 
Edouard@2336: class IDBrowser(wx.Panel):
Edouard@2335:     def __init__(self, parent, ctr, SelectURICallBack=None, SelectIDCallBack=None, **kwargs):
edouard@2492:         big = self.isManager = SelectURICallBack is None and SelectIDCallBack is None
Edouard@2458:         wx.Panel.__init__(self, parent, -1, size=(800 if big else 450,
Edouard@2340:                                                   600 if big else 200))
Edouard@2334: 
Edouard@2334:         self.SelectURICallBack = SelectURICallBack
Edouard@2335:         self.SelectIDCallBack = SelectIDCallBack
Edouard@2334: 
Edouard@2334:         dvStyle = wx.BORDER_THEME | dv.DV_ROW_LINES
edouard@2492:         if self.isManager:
Edouard@2334:             # no multiple selection in selector mode
Edouard@2334:             dvStyle |= dv.DV_MULTIPLE
edouard@2492:         self.dvc = dv.DataViewCtrl(self, style=dvStyle)
edouard@2492: 
edouard@2492:         def args(*a, **k):
edouard@2492:             return (a, k)
Edouard@2334: 
Edouard@2334:         ColumnsDesc = [
edouard@2492:             args(_("ID"), COL_ID, width=70),
edouard@2492:             args(_("Last URI"), COL_URI, width=300 if big else 80),
edouard@2492:             args(_("Description"), COL_DESC, width=300 if big else 200,
edouard@2492:                  mode=dv.DATAVIEW_CELL_EDITABLE
andrej@2537:                  if self.isManager
andrej@2537:                  else dv.DATAVIEW_CELL_INERT),
edouard@2492:             args(_("Last connection"), COL_LAST, width=120),
Edouard@2334:         ]
Edouard@2334: 
Edouard@2339:         self.model = IDBrowserModel(ctr.ProjectPath, len(ColumnsDesc))
Edouard@2334:         self.dvc.AssociateModel(self.model)
Edouard@2334: 
Edouard@2458:         col_list = []
edouard@2492:         for a, k in ColumnsDesc:
Edouard@2458:             col_list.append(
edouard@2492:                 self.dvc.AppendTextColumn(*a, **dict(k, flags=colflags)))
Edouard@2458:         col_list[COL_LAST].SetSortOrder(False)
Edouard@2458: 
Edouard@2458:         # TODO : sort by last bvisit by default
Edouard@2334: 
edouard@2492:         self.Sizer = wx.BoxSizer(wx.VERTICAL)
Edouard@2334:         self.Sizer.Add(self.dvc, 1, wx.EXPAND)
Edouard@2334: 
Edouard@2334:         btnbox = wx.BoxSizer(wx.HORIZONTAL)
edouard@2492:         if self.isManager:
Edouard@2334: 
Edouard@2334:             # deletion of secret and metadata
Edouard@2334:             deleteButton = wx.Button(self, label=_("Delete ID"))
Edouard@2334:             self.Bind(wx.EVT_BUTTON, self.OnDeleteButton, deleteButton)
edouard@2492:             btnbox.Add(deleteButton, 0, wx.LEFT | wx.RIGHT, 5)
Edouard@2334: 
Edouard@2334:             # export all
Edouard@2334:             exportButton = wx.Button(self, label=_("Export all"))
Edouard@2334:             self.Bind(wx.EVT_BUTTON, self.OnExportButton, exportButton)
edouard@2492:             btnbox.Add(exportButton, 0, wx.LEFT | wx.RIGHT, 5)
Edouard@2334: 
Edouard@2334:             # import with a merge -> duplicates are asked for
Edouard@2334:             importButton = wx.Button(self, label=_("Import"))
Edouard@2334:             self.Bind(wx.EVT_BUTTON, self.OnImportButton, importButton)
edouard@2492:             btnbox.Add(importButton, 0, wx.LEFT | wx.RIGHT, 5)
edouard@2492: 
edouard@2492:         else:
Edouard@2334:             # selector mode
Edouard@2335:             self.useURIButton = wx.Button(self, label=_("Use last URI"))
Edouard@2335:             self.Bind(wx.EVT_BUTTON, self.OnUseURIButton, self.useURIButton)
Edouard@2335:             self.useURIButton.Disable()
edouard@2492:             btnbox.Add(self.useURIButton, 0, wx.LEFT | wx.RIGHT, 5)
edouard@2492: 
edouard@2492:         self.Sizer.Add(btnbox, 0, wx.TOP | wx.BOTTOM, 5)
Edouard@2335:         self.Bind(dv.EVT_DATAVIEW_SELECTION_CHANGED, self.OnSelectionChanged, self.dvc)
Edouard@2335: 
Edouard@2334:     def OnDeleteButton(self, evt):
Edouard@2334:         items = self.dvc.GetSelections()
Edouard@2334:         rows = [self.model.GetRow(item) for item in items]
Edouard@2334: 
Edouard@2334:         # Ask if user really wants to delete
Edouard@2334:         if wx.MessageBox(_('Are you sure to delete selected IDs?'),
Edouard@2334:                          _('Delete IDs'),
edouard@2492:                          wx.YES_NO | wx.CENTRE | wx.NO_DEFAULT) != wx.YES:
Edouard@2334:             return
Edouard@2334: 
Edouard@2334:         self.model.DeleteRows(rows)
Edouard@2334: 
Edouard@2335:     def OnSelectionChanged(self, evt):
edouard@2492:         if not self.isManager:
Edouard@2335:             items = self.dvc.GetSelections()
Edouard@2335:             somethingSelected = len(items) > 0
Edouard@2335:             self.useURIButton.Enable(somethingSelected)
Edouard@2335:             if somethingSelected:
Edouard@2335:                 row = self.model.GetRow(items[0])
Edouard@2335:                 ID = self.model.GetValueByRow(row, COL_ID)
Edouard@2335:                 self.SelectIDCallBack(ID)
Edouard@2335: 
Edouard@2335:     def OnUseURIButton(self, evt):
Edouard@2335:         row = self.model.GetRow(self.dvc.GetSelections()[0])
Edouard@2335:         URI = self.model.GetValueByRow(row, COL_URI)
Edouard@2335:         if URI:
Edouard@2335:             self.SelectURICallBack(URI)
Edouard@2334: 
Edouard@2334:     def OnExportButton(self, evt):
Edouard@2340:         dialog = wx.FileDialog(self, _("Choose a file"),
edouard@2492:                                wildcard=_("PSK ZIP files (*.zip)|*.zip"),
edouard@2492:                                style=wx.SAVE | wx.OVERWRITE_PROMPT)
Edouard@2340:         if dialog.ShowModal() == wx.ID_OK:
Edouard@2340:             self.model.Export(dialog.GetPath())
Edouard@2340: 
edouard@2492:     # pylint: disable=unused-variable
edouard@2492:     def ShouldIReplaceCallback(self, existing, replacement):
edouard@2492:         ID, URI, DESC, LAST = existing
edouard@2492:         _ID, _URI, _DESC, _LAST = replacement
edouard@2492:         dlg = IDMergeDialog(
edouard@2492:             self,
edouard@2492:             _("Import IDs"),
Edouard@2428:             (_("Replace information for ID {ID} ?") + "\n\n" +
Edouard@2428:              _("Existing:") + "\n    " +
Edouard@2428:              _("Description:") + " {DESC}\n    " +
Edouard@2428:              _("Last known URI:") + " {URI}\n    " +
Edouard@2428:              _("Last connection:") + " {LAST}\n\n" +
Edouard@2428:              _("Replacement:") + "\n    " +
Edouard@2428:              _("Description:") + " {_DESC}\n    " +
Edouard@2428:              _("Last known URI:") + " {_URI}\n    " +
Edouard@2428:              _("Last connection:") + " {_LAST}\n").format(**locals()),
Edouard@2428:             _("Do the same for following IDs"),
edouard@2492:             [_("Replace"), _("Keep"), _("Cancel")])
edouard@2492: 
edouard@2492:         answer = dlg.ShowModal()  # return value ignored as we have "Ok" only anyhow
Edouard@2428:         if answer == wx.ID_CANCEL:
Edouard@2428:             return CANCEL
Edouard@2428: 
Edouard@2428:         if dlg.OptionChecked():
Edouard@2428:             if answer == wx.ID_YES:
Edouard@2428:                 return REPLACE_ALL
Edouard@2428:             return KEEP_ALL
Edouard@2428:         else:
Edouard@2428:             if answer == wx.ID_YES:
Edouard@2428:                 return REPLACE
Edouard@2428:             return KEEP
Edouard@2334: 
Edouard@2334:     def OnImportButton(self, evt):
Edouard@2340:         dialog = wx.FileDialog(self, _("Choose a file"),
edouard@2492:                                wildcard=_("PSK ZIP files (*.zip)|*.zip"),
edouard@2492:                                style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
Edouard@2340:         if dialog.ShowModal() == wx.ID_OK:
Edouard@2340:             self.model.Import(dialog.GetPath(),
Edouard@2340:                               self.ShouldIReplaceCallback)