diff options
Diffstat (limited to 'raphodo/viewutils.py')
-rw-r--r-- | raphodo/viewutils.py | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/raphodo/viewutils.py b/raphodo/viewutils.py new file mode 100644 index 0000000..6b2775b --- /dev/null +++ b/raphodo/viewutils.py @@ -0,0 +1,252 @@ +# Copyright (C) 2015-2017 Damon Lynch <damonlynch@gmail.com> + +# This file is part of Rapid Photo Downloader. +# +# Rapid Photo Downloader is free software: you can redistribute it and/or +# modify it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Rapid Photo Downloader is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Rapid Photo Downloader. If not, +# see <http://www.gnu.org/licenses/>. + +__author__ = 'Damon Lynch' +__copyright__ = "Copyright 2015-2017, Damon Lynch" + +from typing import List, Dict +from collections import namedtuple + +from gettext import gettext as _ + +from PyQt5.QtWidgets import ( + QStyleOptionFrame, QStyle, QStylePainter, QWidget, QLabel, QListWidget, QProxyStyle, + QStyleOption, QDialogButtonBox +) +from PyQt5.QtGui import QFontMetrics, QFont, QPainter +from PyQt5.QtCore import QSize, Qt + + +class RowTracker: + r""" + Simple class to map model rows to ids and vice versa, used in + table and list views. + + >>> r = RowTracker() + >>> r[0] = 100 + >>> r + {0: 100} {100: 0} + >>> r[1] = 110 + >>> r[2] = 120 + >>> len(r) + 3 + >>> r.insert_row(1, 105) + >>> r[1] + 105 + >>> r[2] + 110 + >>> len(r) + 4 + >>> 1 in r + True + >>> 3 in r + True + >>> 4 in r + False + >>> r.remove_rows(1) + [105] + >>> len(r) + 3 + >>> r[0] + 100 + >>> r[1] + 110 + >>> r.remove_rows(100) + [] + >>> len(r) + 3 + >>> r.insert_row(0, 90) + >>> r[0] + 90 + >>> r[1] + 100 + """ + def __init__(self) -> None: + self.row_to_id = {} # type: Dict[int, int] + self.id_to_row = {} # type: Dict[int, int] + + def __getitem__(self, row) -> int: + return self.row_to_id[row] + + def __setitem__(self, row, id_value) -> None: + self.row_to_id[row] = id_value + self.id_to_row[id_value] = row + + def __len__(self) -> int: + return len(self.row_to_id) + + def __contains__(self, row) -> bool: + return row in self.row_to_id + + def __delitem__(self, row) -> None: + id_value = self.row_to_id[row] + del self.row_to_id[row] + del self.id_to_row[id_value] + + def __repr__(self) -> str: + return '%r %r' % (self.row_to_id, self.id_to_row) + + def __str__(self) -> str: + return 'Row to id: %r\nId to row: %r' % (self.row_to_id, self.id_to_row) + + def row(self, id_value) -> int: + """ + :param id_value: the ID, e.g. scan_id, uid, row_id + :return: the row associated with the ID + """ + return self.id_to_row[id_value] + + def insert_row(self, position: int, id_value) -> List: + """ + Inserts row into the model at the given position, assigning + the id_id_value. + + :param position: the position of the first row to insert + :param id_value: the id to be associated with the new row + """ + + ids = [id_value for row, id_value in self.row_to_id.items() if row < position] + ids_to_move = [id_value for row, id_value in self.row_to_id.items() if row >= position] + ids.append(id_value) + ids.extend(ids_to_move) + self.row_to_id = dict(enumerate(ids)) + self.id_to_row = dict(((y, x) for x, y in list(enumerate(ids)))) + + def remove_rows(self, position, rows=1) -> List: + """ + :param position: the position of the first row to remove + :param rows: how many rows to remove + :return: the ids of those rows which were removed + """ + final_pos = position + rows - 1 + ids_to_keep = [id_value for row, id_value in self.row_to_id.items() if + row < position or row > final_pos] + ids_to_remove = [idValue for row, idValue in self.row_to_id.items() if + row >= position and row <= final_pos] + self.row_to_id = dict(enumerate(ids_to_keep)) + self.id_to_row = dict(((y, x) for x, y in list(enumerate(ids_to_keep)))) + return ids_to_remove + + +ThumbnailDataForProximity = namedtuple( + 'ThumbnailDataForProximity', 'uid, ctime, file_type, previously_downloaded' +) + + +class QFramedWidget(QWidget): + """ + Draw a Frame around the widget in the style of the application. + + Use this instead of using a stylesheet to draw a widget's border. + """ + + def paintEvent(self, *opts): + painter = QStylePainter(self) + option = QStyleOptionFrame() + option.initFrom(self) + painter.drawPrimitive(QStyle.PE_Frame, option) + super().paintEvent(*opts) + + +class QFramedLabel(QLabel): + """ + Draw a Frame around the label in the style of the application. + + Use this instead of using a stylesheet to draw a label's border. + """ + + def paintEvent(self, *opts): + painter = QStylePainter(self) + option = QStyleOptionFrame() + option.initFrom(self) + painter.drawPrimitive(QStyle.PE_Frame, option) + super().paintEvent(*opts) + + +class ProxyStyleNoFocusRectangle(QProxyStyle): + """ + Remove the focus rectangle from a widget + """ + + def drawPrimitive(self, element: QStyle.PrimitiveElement, + option: QStyleOption, painter: QPainter, + widget: QWidget) -> None: + + if QStyle.PE_FrameFocusRect == element: + pass + else: + super().drawPrimitive(element, option, painter, widget) + + +class QNarrowListWidget(QListWidget): + """ + Create a list widget that is not by default enormously wide. + + See http://stackoverflow.com/questions/6337589/qlistwidget-adjust-size-to-content + """ + + def __init__(self, minimum_rows: int=0, + minimum_width: int=0, + no_focus_recentangle: bool=False, + parent=None) -> None: + super().__init__(parent=parent) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self._minimum_rows = minimum_rows + self._minimum_width = minimum_width + if no_focus_recentangle: + self.setStyle(ProxyStyleNoFocusRectangle()) + + @property + def minimum_width(self) -> int: + return self._minimum_width + + @minimum_width.setter + def minimum_width(self, width: int) -> None: + self._minimum_width = width + self.updateGeometry() + + def sizeHint(self): + s = QSize() + if self._minimum_rows: + s.setHeight(self.count() * self.sizeHintForRow(0) + self.frameWidth() * 2) + else: + s.setHeight(super().sizeHint().height()) + s.setWidth(max(self.sizeHintForColumn(0) + self.frameWidth() * 2, self._minimum_width)) + return s + + +def standardIconSize() -> QSize: + size = QFontMetrics(QFont()).height() * 6 + return QSize(size, size) + + +def translateButtons(buttonBox: QDialogButtonBox) -> None: + buttons = ( + (QDialogButtonBox.Ok, _('&OK')), + (QDialogButtonBox.Close, _('&Close') ), + (QDialogButtonBox.Cancel, _('&Cancel')), + (QDialogButtonBox.Save, _('&Save')), + (QDialogButtonBox.Help, _('&Help')), + (QDialogButtonBox.RestoreDefaults, _('Restore Defaults')), + (QDialogButtonBox.Yes, _('&Yes')), + (QDialogButtonBox.No, _('&No')), + ) + for role, text in buttons: + button = buttonBox.button(role) + if button: + button.setText(text) |