def GetWildcard(self): if self._extensions: if len(self._extensions) < 5: label = '%s (%s)' % (self._label, ','.join(self._extensions)) else: label = self._label menu = [wildcard_list(self._to_local(label), self._extensions)] else: menu = [] menu.append(self._all_files + '|*') return '|'.join(menu)
class Grid(droplet.Mixin, gridlib.Grid): border = 4 Table = Table wildcard = '|'.join([wildcard.wildcard_list(_('Images'), formField.IMAGE_READ_EXTENSIONS), _('All files'), '*']) corner_logo = _corner_logo = None def __init__(self, parent, thumb_size=THUMB_SIZE): super(Grid, self).__init__(parent) #table self.table = self.Table(thumb_size) self.image_table = self.table.table self.SetTable(self.table, True) self.SetRowLabelSize(260) self._rows_number = self.GetNumberRows() self._cols_number = self.GetNumberCols() self._cols_sized = [] #bitmap self.PENCIL_BITMAP = getPencilBitmap() self.PENCIL_BITMAP_SIZE = self.PENCIL_BITMAP.GetSize() self.GRAY_BRUSH = wx.Brush("WHEAT", wx.TRANSPARENT) self.GRAY_PEN = wx.Pen(GRAY) #editor self.SetDefaultEditor(gridlib.GridCellTextEditor()) #drop self.SetAsFileDropTarget(self, self.OnDrop) self.SetAsFileDropTarget(self.GetEmpty(), self.OnDrop) self.SetAsFileDropTarget(self.GetTopLevelParent(), self.OnDrop) #events self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) self.Bind(gridlib.EVT_GRID_CELL_CHANGE, self.OnGridCellChange) self.Bind(gridlib.EVT_GRID_CELL_LEFT_CLICK, self.OnGridCellLeftClick) self.Bind(gridlib.EVT_GRID_CELL_RIGHT_CLICK, self.OnGridCellRightClicked) self.Bind(gridlib.EVT_GRID_CMD_LABEL_RIGHT_CLICK, self.OnGridLabelRightClicked) self.Bind(gridlib.EVT_GRID_LABEL_LEFT_DCLICK, self.OnGridLabelLeftDclicked) self.GetGridRowLabelWindow().Bind(wx.EVT_PAINT, self.OnRowLabelPaint) self.GetGridColLabelWindow().Bind(wx.EVT_PAINT, self.OnColLabelPaint) #FIXME: logo might get corrupted #self.GetGridCornerLabelWindow().Bind(wx.EVT_PAINT, # self.OnCornerLabelPaint) self.Bind(gridlib.EVT_GRID_EDITOR_HIDDEN, self.OnGridEditorHidden) def OnGridEditorHidden(self, evt): wx.CallAfter(self.ShowLog) def ShowLog(self): log = self.table.log self.table.log = '' if log: self.ShowError(log) #---refresh def UpdateIfNeeded(self): needs_update = False for image in self.image_table.images: needs_update = needs_update or image.update_if_modified() if needs_update: self.RefreshAll(update_column=True, force_thumbs=True) def UpdateRowsColsNumbers(self): """Only consider adding or removing rows.""" for current, new, delmsg, addmsg in [ (self._rows_number, self.table.GetNumberRows(), gridlib.GRIDTABLE_NOTIFY_ROWS_DELETED, gridlib.GRIDTABLE_NOTIFY_ROWS_APPENDED), (self._cols_number, self.table.GetNumberCols(), gridlib.GRIDTABLE_NOTIFY_COLS_DELETED, gridlib.GRIDTABLE_NOTIFY_COLS_APPENDED), ]: if new < current: msg = gridlib.GridTableMessage(self.table, delmsg, new, current - new) self.ProcessTableMessage(msg) elif new > current: msg = gridlib.GridTableMessage(self.table, addmsg, new - current) self.ProcessTableMessage(msg) self._rows_number = self.GetNumberRows() self._cols_number = self.GetNumberCols() def UpdateValues(self): """Update all displayed values""" # This sends an event to the grid table to update all of the values msg = gridlib.GridTableMessage(self.table, gridlib.GRIDTABLE_REQUEST_VIEW_GET_VALUES) self.ProcessTableMessage(msg) def UpdateThumbs(self, force_thumbs=False): if self.image_table.images: heights = [] for image in self.image_table.images: if force_thumbs or not hasattr(image, 'thumb_wx'): image.thumb_wx = wxPil.pil_wxBitmap(image.thumb) heights.append(image.thumb.size[1]) self.SetColLabelSize(max(heights) + 2 * self.border) def RefreshAll(self, update_column=False, force_thumbs=False): self.BeginBatch() self.UpdateThumbs(force_thumbs) self.GetGridColLabelWindow().Refresh() self.UpdateRowsColsNumbers() self.UpdateValues() self.EndBatch() self.AdjustScrollbars() if update_column: for col in range(self.GetNumberCols()): if col not in self._cols_sized: self.SetColSize(col, COL_WIDTH) self._cols_sized.append(col) self.ForceRefresh() #---events def OnGridCellLeftClick(self, evt): col = evt.GetCol() self.SetTitleFilename(self.image_table.images[col].filename) evt.Skip() def SetTitleFilename(self, filename): wx.GetTopLevelParent(self).SetTitleFilename(filename) def OnColLabelPaint(self, evt): window = self.GetGridColLabelWindow() rect = window.GetClientRect() dc = wx.PaintDC(window) dc.Clear() pen = dc.GetPen() dc.SetPen(self.GRAY_PEN) dc.DrawLine(rect[0], rect[1] + rect[3] - 1, rect[0] + rect[2], rect[1] + rect[3] - 1) def label_rect(position, col): col_width = self.GetColSize(col) col_height = self.GetColLabelSize() return (position, 0, col_width, col_height) def get_bitmap(index): return self.image_table.images[index].thumb_wx self._LabelPaint(dc, co=0, amount=self.GetNumberCols(), label_rect=label_rect, get_size=self.GetColSize, get_label=None, # self.GetColLabelValue, get_bitmap=get_bitmap, border=False, center_bitmap=True, pen=pen) def OnCornerLabelPaint(self, evt): if not self.corner_logo: return evt.Skip() if not self._corner_logo: self._corner_logo = graphics.bitmap(self.corner_logo) window = self.GetGridCornerLabelWindow() rect = window.GetClientRect() dc = wx.PaintDC(window) pen = dc.GetPen() dc.SetPen(self.GRAY_PEN) dc.DrawLine(rect[0], rect[1] + rect[3] - 1, rect[0] + rect[2], rect[1] + rect[3] - 1) def label_rect(position, col): return rect def get_bitmap(index): return self._corner_logo self._LabelPaint(dc, co=0, amount=1, label_rect=label_rect, get_size=self.GetColSize, get_label=None, # self.GetColLabelValue, get_bitmap=get_bitmap, border=False, center_bitmap=True, pen=pen) def CopyCellValue(self, row, col): if self.table.GetNumberCols(): clipboard.copy_text(unicode(self.table.GetValue(row, col))) def OnDrop(self, filenames, x, y): self.OpenImages(filenames) def OnGridCellChange(self, event): wx.CallAfter(self.RefreshAll) def OnGridCellRightClicked(self, event): """(row, evt) -> display a popup menu when a row label is right clicked""" # Did we click on a row or a column? event.Skip() row, col = event.GetRow(), event.GetCol() #bind menu events def on_copy(event): self.CopyCellValue(row, col) def on_add(event): self.AddColumnRow(col) def on_delete_cell(event): self.DeleteCell(row, col) #build menu control menu = wx.Menu() self._AppendMenuItem(menu, _('&Copy Value'), on_copy, id=wx.ID_COPY) if pyexiv2: self._AppendMenuItem(menu, _('&Add Tag'), on_add, 'Ctrl+N', id=wx.ID_ADD) if self.image_table.is_cell_deletable(row, col): self._AppendMenuItem(menu, _("&Delete Tag"), on_delete_cell, 'Del', id=wx.ID_DELETE) #show menu self.PopupMenu(menu) menu.Destroy() def OnGridLabelRightClicked(self, event): event.Skip() row, col = event.GetRow(), event.GetCol() if row == -1: self.OnGridColLabelRightClicked(col) else: self.OnGridRowLabelRightClicked(row) def OnGridLabelLeftDclicked(self, event): event.Skip() row, col = event.GetRow(), event.GetCol() if row == -1: system.start(self.image_table.get_image_filename(col)) def OnGridColLabelRightClicked(self, col): menu = wx.Menu() #open self._AppendMenuItem(menu, _('&Open...'), self.OnOpen, id=wx.ID_OPEN) #open url self._AppendMenuItem(menu, _('Open &Url...'), self.OnOpenUrl, 'Shift+Ctrl+O') #remove image def on_remove(event): self.DeleteCols(col) self._AppendMenuItem(menu, _('&Remove Image'), on_remove) #show menu self.PopupMenu(menu) menu.Destroy() def _AppendMenuItem(self, menu, label, method, shortcut='', id=None): if id is None: id = wx.NewId() menu.Append(id, '%s\t%s' % (label, shortcut)) self.Bind(wx.EVT_MENU, method, id=id) def OnGridRowLabelRightClicked(self, row): #build menu control menu = wx.Menu() self.CreateRowLabelMenu(menu, row) #show menu self.PopupMenu(menu) menu.Destroy() def CreateRowLabelMenu(self, menu, row): #bind menu events def on_add(event): self.AddRow() def on_copy(event): self.CopyRowLabel(row) def on_delete_row(event): self.DeleteRows(row) def on_set_row_label(event): self.RenameRowLabelValue(row) def on_set_row_values(event): self.ChangeRowValues(row) self._AppendMenuItem(menu, _('&Copy Tag'), on_copy, 'Shift+Ctrl+C') if pyexiv2: self._AppendMenuItem(menu, _('&Add Tag to All Images...'), on_add, 'Shift+Ctrl+N') if self.image_table.is_row_editable(row) and pyexiv2: self._AppendMenuItem(menu, _("&Delete Tag from All Images..."), on_delete_row, 'Shift+Del') self._AppendMenuItem(menu, _("&Rename Tag for All Images..."), on_set_row_label, 'Shift+Ctrl+R') self._AppendMenuItem(menu, _("&Modify Value for All Images..."), on_set_row_values, 'Shift+Ctrl+M') def CopyRowLabel(self, row): clipboard.copy_text('<%s>' % self.table.GetRowLabelValue(row)) def AddRow(self): key, value = AddTagDialog(self, self.image_table.keys).GetModal() if key: row = self.table.GetNumberRows() col = self.GetGridCursorCol() log = self.image_table.add_key(key, value) if log: self.ShowError(log, _('Unable to add tag <%s>') % key) self.RefreshAll() wx.CallAfter(self.MakeCellVisible, row, col) def AddColumnRow(self, col): key, value = AddTagDialog(self, self.image_table.keys).GetModal() if key: row = self.table.GetNumberRows() image = self.image_table.images[col] log = self.image_table.add_image_key(image, key, value) if log: self.ShowError(log, _('Unable to add tag <%s>') % key) self.RefreshAll() wx.CallAfter(self.MakeCellVisible, row, col) def DeleteCell(self, row, col): key = self.GetRowLabelValue(row) image_name = self.GetColLabelValue(col) if self.Ask('%s\n<%s>' % (CONFIRM_DELETE_TAG % image_name, key)) \ == wx.ID_YES: log = self.image_table.delete_cell(row, col) if log: self.ShowError(log, _('Unable to delete tag <%s>') % key) self.RefreshAll() def DeleteRows(self, pos=0, num=1): key = self.GetRowLabelValue(pos) if self.Ask('%s\n<%s>' % (CONFIRM_DELETE_TAG_ALL, key)) \ == wx.ID_YES: log = self.table.DeleteRows(pos, num) if log: self.ShowError(log, _('Unable to delete tag <%s>') % key) self.RefreshAll() def DeleteCols(self, pos=0, num=1): log = self.table.DeleteCols(pos, num) if log: self.ShowError(log, _('Unable to remove image')) if not self.CheckEmpty(): self.RefreshAll() def RenameRowLabelValue(self, row): key_old = self.GetRowLabelValue(row) key_new = self.AskText(_('Rename tag for all images to:'), title=TITLE, value=key_old) if key_new: log = self.table.SetRowLabelValue(row, key_new) if log: self.ShowError(log, _('Unable to rename tag <%s>')\ % key_old) self.RefreshAll() def ChangeRowValues(self, row): value = self.AskText(_('Change value for all images to:'), title=TITLE) if value: key = self.GetRowLabelValue(row) log = self.image_table.set_key_value(key, value) if log: self.ShowError(log, _('Unable to change tag <%s>')\ % key) self.RefreshAll() def OnKeyDown(self, event): key_code = event.GetKeyCode() #print key_code row, col = self.GetCellRowCol() shift = event.ShiftDown() ctrl = event.ControlDown() alt = event.AltDown() if self.ProcessKey(key_code, row, col, shift, ctrl, alt): event.Skip() def ProcessKey(self, key_code, row, col, shift, ctrl, alt): if key_code == 127 \ and self.image_table.is_cell_deletable(row, col): if shift: self.DeleteRows(row) else: self.DeleteCell(row, col) elif ctrl: if key_code == 67: #Ctrl+C if shift: self.CopyRowLabel(row) else: self.CopyCellValue(row, col) elif key_code == 78: #Ctrl+N if shift: self.AddRow() else: self.AddColumnRow(col) elif key_code == 82 and shift: #Ctrl+R self.RenameRowLabelValue(row) elif key_code == 77 and shift: #Ctrl+M self.ChangeRowValues(row) else: return True def OnRowLabelPaint(self, evt): window = self.GetGridRowLabelWindow() rect = window.GetClientRect() dc = wx.PaintDC(window) pen = dc.GetPen() dc.SetPen(self.GRAY_PEN) dc.DrawLine(rect[0] + rect[2] - 1, rect[1], rect[0] + rect[2] - 1, rect[1] + rect[3]) def label_rect(position, row): row_width = self.GetRowLabelSize() row_height = self.GetRowSize(row) return (0, position, row_width, row_height) def get_bitmap(index): if self.image_table.is_row_editable(index): return self.PENCIL_BITMAP self._LabelPaint(dc, co=1, amount=self.GetNumberRows(), label_rect=label_rect, get_size=self.GetRowSize, get_label=self.GetRowLabelValue, get_bitmap=get_bitmap, border=True, center_bitmap=False, pen=pen) #---paint def _LabelPaint(self, dc, co, amount, label_rect, get_size, get_label, get_bitmap, border, center_bitmap, pen): position = -self.GetViewStart()[co]\ * self.GetScrollPixelsPerUnit()[co] dc.SetBrush(self.GRAY_BRUSH) for index in range(amount): size = get_size(index) rect = label_rect(position, index) position += size if position < 0: continue if border: dc.DrawLine(rect[0], rect[1] + rect[3] - 1, rect[0] + rect[2], rect[1] + rect[3] - 1) bitmap = get_bitmap(index) if bitmap: bitmap_size = bitmap.GetSize() if center_bitmap: #centered offset_x = (rect[2] - bitmap_size[0]) / 2 else: #right aligned offset_x = rect[2] - bitmap_size[0] - self.border offset_y = (rect[3] - bitmap_size[1]) / 2 dc.DrawBitmap(bitmap, rect[0] + offset_x, rect[1] + offset_y, True) else: offset_y = (rect[3] - self.PENCIL_BITMAP_SIZE[1]) / 2 dc.SetPen(pen) if get_label: dc.DrawText(get_label(index), rect[0] + self.border, rect[1] + offset_y) #---dialogs def Ask(self, message, title=''): return self.ShowMessage(message, title, style=wx.YES_NO | wx.ICON_QUESTION) def AskText(self, question, value='', title=''): dlg = wx.TextEntryDialog(self, question, title, value) if dlg.ShowModal() == wx.ID_OK: answer = dlg.GetValue() else: answer = None dlg.Destroy() return answer def ShowMessage(self, message, title='', style=wx.OK | wx.ICON_EXCLAMATION): dlg = wx.MessageDialog(self, message, title, style, ) answer = dlg.ShowModal() dlg.Destroy() return answer def ShowError(self, message, title=TITLE): return self.ShowMessage(message, title, style=wx.OK | wx.ICON_ERROR) def OpenImage(self, filename): try: self.image_table.open_image(filename, encoding=WX_ENCODING) except IOError, message: self.show_error(_('Sorry, %s.') % str(message), title=_('Image Inspector')) return self.image_table.update() self.UpdateThumbs() self.SetTitleFilename(filename) self.RefreshAll(update_column=True)