mirror of
https://github.com/arsenetar/dupeguru.git
synced 2026-01-22 06:37:17 +00:00
Format files with black
- Format all files with black - Update tox.ini flake8 arguments to be compatible - Add black to requirements-extra.txt - Reduce ignored flake8 rules and fix a few violations
This commit is contained in:
@@ -8,16 +8,29 @@
|
||||
|
||||
from PyQt5.QtCore import Qt, QCoreApplication
|
||||
from PyQt5.QtGui import QPixmap, QFont
|
||||
from PyQt5.QtWidgets import (QDialog, QDialogButtonBox, QSizePolicy, QHBoxLayout, QVBoxLayout,
|
||||
QLabel, QApplication)
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialog,
|
||||
QDialogButtonBox,
|
||||
QSizePolicy,
|
||||
QHBoxLayout,
|
||||
QVBoxLayout,
|
||||
QLabel,
|
||||
QApplication,
|
||||
)
|
||||
|
||||
from hscommon.trans import trget
|
||||
|
||||
tr = trget('qtlib')
|
||||
tr = trget("qtlib")
|
||||
|
||||
|
||||
class AboutBox(QDialog):
|
||||
def __init__(self, parent, app, **kwargs):
|
||||
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint | Qt.MSWindowsFixedSizeDialogHint
|
||||
flags = (
|
||||
Qt.CustomizeWindowHint
|
||||
| Qt.WindowTitleHint
|
||||
| Qt.WindowSystemMenuHint
|
||||
| Qt.MSWindowsFixedSizeDialogHint
|
||||
)
|
||||
super().__init__(parent, flags, **kwargs)
|
||||
self.app = app
|
||||
self._setupUi()
|
||||
@@ -26,7 +39,9 @@ class AboutBox(QDialog):
|
||||
self.buttonBox.rejected.connect(self.reject)
|
||||
|
||||
def _setupUi(self):
|
||||
self.setWindowTitle(tr("About {}").format(QCoreApplication.instance().applicationName()))
|
||||
self.setWindowTitle(
|
||||
tr("About {}").format(QCoreApplication.instance().applicationName())
|
||||
)
|
||||
self.resize(400, 190)
|
||||
sizePolicy = QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
@@ -35,7 +50,7 @@ class AboutBox(QDialog):
|
||||
self.setSizePolicy(sizePolicy)
|
||||
self.horizontalLayout = QHBoxLayout(self)
|
||||
self.logoLabel = QLabel(self)
|
||||
self.logoLabel.setPixmap(QPixmap(':/%s_big' % self.app.LOGO_NAME))
|
||||
self.logoLabel.setPixmap(QPixmap(":/%s_big" % self.app.LOGO_NAME))
|
||||
self.horizontalLayout.addWidget(self.logoLabel)
|
||||
self.verticalLayout = QVBoxLayout()
|
||||
self.nameLabel = QLabel(self)
|
||||
@@ -46,7 +61,9 @@ class AboutBox(QDialog):
|
||||
self.nameLabel.setText(QCoreApplication.instance().applicationName())
|
||||
self.verticalLayout.addWidget(self.nameLabel)
|
||||
self.versionLabel = QLabel(self)
|
||||
self.versionLabel.setText(tr("Version {}").format(QCoreApplication.instance().applicationVersion()))
|
||||
self.versionLabel.setText(
|
||||
tr("Version {}").format(QCoreApplication.instance().applicationVersion())
|
||||
)
|
||||
self.verticalLayout.addWidget(self.versionLabel)
|
||||
self.label_3 = QLabel(self)
|
||||
self.verticalLayout.addWidget(self.label_3)
|
||||
@@ -64,13 +81,14 @@ class AboutBox(QDialog):
|
||||
self.horizontalLayout.addLayout(self.verticalLayout)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
app = QApplication([])
|
||||
QCoreApplication.setOrganizationName('Hardcoded Software')
|
||||
QCoreApplication.setApplicationName('FooApp')
|
||||
QCoreApplication.setApplicationVersion('1.2.3')
|
||||
app.LOGO_NAME = ''
|
||||
QCoreApplication.setOrganizationName("Hardcoded Software")
|
||||
QCoreApplication.setApplicationName("FooApp")
|
||||
QCoreApplication.setApplicationVersion("1.2.3")
|
||||
app.LOGO_NAME = ""
|
||||
dialog = AboutBox(None, app)
|
||||
dialog.show()
|
||||
sys.exit(app.exec_())
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, QTimer, QObject
|
||||
|
||||
|
||||
class Application(QObject):
|
||||
finishedLaunching = pyqtSignal()
|
||||
|
||||
@@ -18,4 +19,3 @@ class Application(QObject):
|
||||
|
||||
def __launchTimerTimedOut(self):
|
||||
self.finishedLaunching.emit()
|
||||
|
||||
|
||||
@@ -9,8 +9,18 @@
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QHeaderView
|
||||
|
||||
|
||||
class Column:
|
||||
def __init__(self, attrname, defaultWidth, editor=None, alignment=Qt.AlignLeft, cantTruncate=False, painter=None, resizeToFit=False):
|
||||
def __init__(
|
||||
self,
|
||||
attrname,
|
||||
defaultWidth,
|
||||
editor=None,
|
||||
alignment=Qt.AlignLeft,
|
||||
cantTruncate=False,
|
||||
painter=None,
|
||||
resizeToFit=False,
|
||||
):
|
||||
self.attrname = attrname
|
||||
self.defaultWidth = defaultWidth
|
||||
self.editor = editor
|
||||
@@ -28,6 +38,7 @@ class Columns:
|
||||
self.model = model
|
||||
self._headerView = headerView
|
||||
self._headerView.setDefaultAlignment(Qt.AlignLeft)
|
||||
|
||||
def setspecs(col, modelcol):
|
||||
modelcol.default_width = col.defaultWidth
|
||||
modelcol.editor = col.editor
|
||||
@@ -35,12 +46,13 @@ class Columns:
|
||||
modelcol.resizeToFit = col.resizeToFit
|
||||
modelcol.alignment = col.alignment
|
||||
modelcol.cantTruncate = col.cantTruncate
|
||||
|
||||
if columns:
|
||||
for col in columns:
|
||||
modelcol = self.model.column_by_name(col.attrname)
|
||||
setspecs(col, modelcol)
|
||||
else:
|
||||
col = Column('', 100)
|
||||
col = Column("", 100)
|
||||
for modelcol in self.model.column_list:
|
||||
setspecs(col, modelcol)
|
||||
self.model.view = self
|
||||
@@ -50,16 +62,18 @@ class Columns:
|
||||
# See moneyguru #14 and #15. This was added in order to allow automatic resizing of columns.
|
||||
for column in self.model.column_list:
|
||||
if column.resizeToFit:
|
||||
self._headerView.setSectionResizeMode(column.logical_index, QHeaderView.ResizeToContents)
|
||||
self._headerView.setSectionResizeMode(
|
||||
column.logical_index, QHeaderView.ResizeToContents
|
||||
)
|
||||
|
||||
#--- Public
|
||||
# --- Public
|
||||
def setColumnsWidth(self, widths):
|
||||
#`widths` can be None. If it is, then default widths are set.
|
||||
# `widths` can be None. If it is, then default widths are set.
|
||||
columns = self.model.column_list
|
||||
if not widths:
|
||||
widths = [column.default_width for column in columns]
|
||||
for column, width in zip(columns, widths):
|
||||
if width == 0: # column was hidden before.
|
||||
if width == 0: # column was hidden before.
|
||||
width = column.default_width
|
||||
self._headerView.resizeSection(column.logical_index, width)
|
||||
|
||||
@@ -71,7 +85,7 @@ class Columns:
|
||||
visualIndex = self._headerView.visualIndex(columnIndex)
|
||||
self._headerView.moveSection(visualIndex, destIndex)
|
||||
|
||||
#--- Events
|
||||
# --- Events
|
||||
def headerSectionMoved(self, logicalIndex, oldVisualIndex, newVisualIndex):
|
||||
attrname = self.model.column_by_index(logicalIndex).name
|
||||
self.model.move_column(attrname, newVisualIndex)
|
||||
@@ -80,7 +94,7 @@ class Columns:
|
||||
attrname = self.model.column_by_index(logicalIndex).name
|
||||
self.model.resize_column(attrname, newSize)
|
||||
|
||||
#--- model --> view
|
||||
# --- model --> view
|
||||
def restore_columns(self):
|
||||
columns = self.model.ordered_columns
|
||||
indexes = [col.logical_index for col in columns]
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# Created By: Virgil Dupras
|
||||
# Created On: 2009-05-23
|
||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
||||
#
|
||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
#
|
||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
import traceback
|
||||
@@ -11,13 +11,21 @@ import sys
|
||||
import os
|
||||
|
||||
from PyQt5.QtCore import Qt, QCoreApplication, QSize
|
||||
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPlainTextEdit, QPushButton
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialog,
|
||||
QVBoxLayout,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QPlainTextEdit,
|
||||
QPushButton,
|
||||
)
|
||||
|
||||
from hscommon.trans import trget
|
||||
from hscommon.desktop import open_url
|
||||
from .util import horizontalSpacer
|
||||
|
||||
tr = trget('qtlib')
|
||||
tr = trget("qtlib")
|
||||
|
||||
|
||||
class ErrorReportDialog(QDialog):
|
||||
def __init__(self, parent, github_url, error, **kwargs):
|
||||
@@ -26,15 +34,17 @@ class ErrorReportDialog(QDialog):
|
||||
self._setupUi()
|
||||
name = QCoreApplication.applicationName()
|
||||
version = QCoreApplication.applicationVersion()
|
||||
errorText = "Application Name: {}\nVersion: {}\n\n{}".format(name, version, error)
|
||||
errorText = "Application Name: {}\nVersion: {}\n\n{}".format(
|
||||
name, version, error
|
||||
)
|
||||
# Under windows, we end up with an error report without linesep if we don't mangle it
|
||||
errorText = errorText.replace('\n', os.linesep)
|
||||
errorText = errorText.replace("\n", os.linesep)
|
||||
self.errorTextEdit.setPlainText(errorText)
|
||||
self.github_url = github_url
|
||||
|
||||
|
||||
self.sendButton.clicked.connect(self.goToGithub)
|
||||
self.dontSendButton.clicked.connect(self.reject)
|
||||
|
||||
|
||||
def _setupUi(self):
|
||||
self.setWindowTitle(tr("Error Report"))
|
||||
self.resize(553, 349)
|
||||
@@ -70,15 +80,15 @@ class ErrorReportDialog(QDialog):
|
||||
self.sendButton.setDefault(True)
|
||||
self.horizontalLayout.addWidget(self.sendButton)
|
||||
self.verticalLayout.addLayout(self.horizontalLayout)
|
||||
|
||||
|
||||
def goToGithub(self):
|
||||
open_url(self.github_url)
|
||||
|
||||
|
||||
|
||||
def install_excepthook(github_url):
|
||||
def my_excepthook(exctype, value, tb):
|
||||
s = ''.join(traceback.format_exception(exctype, value, tb))
|
||||
s = "".join(traceback.format_exception(exctype, value, tb))
|
||||
dialog = ErrorReportDialog(None, github_url, s)
|
||||
dialog.exec_()
|
||||
|
||||
|
||||
sys.excepthook = my_excepthook
|
||||
|
||||
@@ -11,28 +11,30 @@ from PyQt5.QtCore import Qt, QSettings, QRect, QObject, pyqtSignal
|
||||
from hscommon.trans import trget
|
||||
from hscommon.util import tryint
|
||||
|
||||
tr = trget('qtlib')
|
||||
tr = trget("qtlib")
|
||||
|
||||
|
||||
def get_langnames():
|
||||
return {
|
||||
'en': tr("English"),
|
||||
'fr': tr("French"),
|
||||
'de': tr("German"),
|
||||
'el': tr("Greek"),
|
||||
'zh_CN': tr("Chinese (Simplified)"),
|
||||
'cs': tr("Czech"),
|
||||
'it': tr("Italian"),
|
||||
'hy': tr("Armenian"),
|
||||
'ko': tr("Korean"),
|
||||
'ru': tr("Russian"),
|
||||
'uk': tr("Ukrainian"),
|
||||
'nl': tr('Dutch'),
|
||||
'pl_PL': tr("Polish"),
|
||||
'pt_BR': tr("Brazilian"),
|
||||
'es': tr("Spanish"),
|
||||
'vi': tr("Vietnamese"),
|
||||
"en": tr("English"),
|
||||
"fr": tr("French"),
|
||||
"de": tr("German"),
|
||||
"el": tr("Greek"),
|
||||
"zh_CN": tr("Chinese (Simplified)"),
|
||||
"cs": tr("Czech"),
|
||||
"it": tr("Italian"),
|
||||
"hy": tr("Armenian"),
|
||||
"ko": tr("Korean"),
|
||||
"ru": tr("Russian"),
|
||||
"uk": tr("Ukrainian"),
|
||||
"nl": tr("Dutch"),
|
||||
"pl_PL": tr("Polish"),
|
||||
"pt_BR": tr("Brazilian"),
|
||||
"es": tr("Spanish"),
|
||||
"vi": tr("Vietnamese"),
|
||||
}
|
||||
|
||||
|
||||
def normalize_for_serialization(v):
|
||||
# QSettings doesn't consider set/tuple as "native" typs for serialization, so if we don't
|
||||
# change them into a list, we get a weird serialized QVariant value which isn't a very
|
||||
@@ -43,6 +45,7 @@ def normalize_for_serialization(v):
|
||||
v = [normalize_for_serialization(item) for item in v]
|
||||
return v
|
||||
|
||||
|
||||
def adjust_after_deserialization(v):
|
||||
# In some cases, when reading from prefs, we end up with strings that are supposed to be
|
||||
# bool or int. Convert these.
|
||||
@@ -50,18 +53,20 @@ def adjust_after_deserialization(v):
|
||||
return [adjust_after_deserialization(sub) for sub in v]
|
||||
if isinstance(v, str):
|
||||
# might be bool or int, try them
|
||||
if v == 'true':
|
||||
if v == "true":
|
||||
return True
|
||||
elif v == 'false':
|
||||
elif v == "false":
|
||||
return False
|
||||
else:
|
||||
return tryint(v, v)
|
||||
return v
|
||||
|
||||
|
||||
# About QRect conversion:
|
||||
# I think Qt supports putting basic structures like QRect directly in QSettings, but I prefer not
|
||||
# to rely on it and stay with generic structures.
|
||||
|
||||
|
||||
class Preferences(QObject):
|
||||
prefsChanged = pyqtSignal()
|
||||
|
||||
@@ -123,12 +128,11 @@ class Preferences(QObject):
|
||||
self.set_value(name, [m] + rectAsList)
|
||||
|
||||
def restoreGeometry(self, name, widget):
|
||||
l = self.get_value(name)
|
||||
if l and len(l) == 5:
|
||||
m, x, y, w, h = l
|
||||
geometry = self.get_value(name)
|
||||
if geometry and len(geometry) == 5:
|
||||
m, x, y, w, h = geometry
|
||||
if m:
|
||||
widget.setWindowState(Qt.WindowMaximized)
|
||||
else:
|
||||
r = QRect(x, y, w, h)
|
||||
widget.setGeometry(r)
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
from PyQt5.QtCore import Qt, QTimer
|
||||
from PyQt5.QtWidgets import QProgressDialog
|
||||
|
||||
|
||||
class ProgressWindow:
|
||||
def __init__(self, parent, model):
|
||||
self._window = None
|
||||
@@ -19,7 +20,7 @@ class ProgressWindow:
|
||||
self.model.progressdesc_textfield.view = self
|
||||
|
||||
# --- Callbacks
|
||||
def refresh(self): # Labels
|
||||
def refresh(self): # Labels
|
||||
if self._window is not None:
|
||||
self._window.setWindowTitle(self.model.jobdesc_textfield.text)
|
||||
self._window.setLabelText(self.model.progressdesc_textfield.text)
|
||||
@@ -30,7 +31,7 @@ class ProgressWindow:
|
||||
|
||||
def show(self):
|
||||
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint
|
||||
self._window = QProgressDialog('', "Cancel", 0, 100, self.parent, flags)
|
||||
self._window = QProgressDialog("", "Cancel", 0, 100, self.parent, flags)
|
||||
self._window.setModal(True)
|
||||
self._window.setAutoReset(False)
|
||||
self._window.setAutoClose(False)
|
||||
@@ -52,4 +53,3 @@ class ProgressWindow:
|
||||
self._window.close()
|
||||
self._window.setParent(None)
|
||||
self._window = None
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Created On: 2010-06-02
|
||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
||||
#
|
||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
#
|
||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal
|
||||
@@ -10,6 +10,7 @@ from PyQt5.QtWidgets import QWidget, QHBoxLayout, QRadioButton
|
||||
|
||||
from .util import horizontalSpacer
|
||||
|
||||
|
||||
class RadioBox(QWidget):
|
||||
def __init__(self, parent=None, items=None, spread=True, **kwargs):
|
||||
# If spread is False, insert a spacer in the layout so that the items don't use all the
|
||||
@@ -23,17 +24,17 @@ class RadioBox(QWidget):
|
||||
self._spacer = horizontalSpacer() if not spread else None
|
||||
self._layout = QHBoxLayout(self)
|
||||
self._update_buttons()
|
||||
|
||||
#--- Private
|
||||
|
||||
# --- Private
|
||||
def _update_buttons(self):
|
||||
if self._spacer is not None:
|
||||
self._layout.removeItem(self._spacer)
|
||||
to_remove = self._buttons[len(self._labels):]
|
||||
to_remove = self._buttons[len(self._labels) :]
|
||||
for button in to_remove:
|
||||
self._layout.removeWidget(button)
|
||||
button.setParent(None)
|
||||
del self._buttons[len(self._labels):]
|
||||
to_add = self._labels[len(self._buttons):]
|
||||
del self._buttons[len(self._labels) :]
|
||||
to_add = self._labels[len(self._buttons) :]
|
||||
for _ in to_add:
|
||||
button = QRadioButton(self)
|
||||
self._buttons.append(button)
|
||||
@@ -46,43 +47,42 @@ class RadioBox(QWidget):
|
||||
for button, label in zip(self._buttons, self._labels):
|
||||
button.setText(label)
|
||||
self._update_selection()
|
||||
|
||||
|
||||
def _update_selection(self):
|
||||
self._selected_index = max(0, min(self._selected_index, len(self._buttons)-1))
|
||||
self._selected_index = max(0, min(self._selected_index, len(self._buttons) - 1))
|
||||
selected = self._buttons[self._selected_index]
|
||||
selected.setChecked(True)
|
||||
|
||||
#--- Event Handlers
|
||||
|
||||
# --- Event Handlers
|
||||
def buttonToggled(self):
|
||||
for i, button in enumerate(self._buttons):
|
||||
if button.isChecked():
|
||||
self._selected_index = i
|
||||
self.itemSelected.emit(i)
|
||||
break
|
||||
|
||||
#--- Signals
|
||||
|
||||
# --- Signals
|
||||
itemSelected = pyqtSignal(int)
|
||||
|
||||
#--- Properties
|
||||
|
||||
# --- Properties
|
||||
@property
|
||||
def buttons(self):
|
||||
return self._buttons[:]
|
||||
|
||||
|
||||
@property
|
||||
def items(self):
|
||||
return self._labels[:]
|
||||
|
||||
|
||||
@items.setter
|
||||
def items(self, value):
|
||||
self._labels = value
|
||||
self._update_buttons()
|
||||
|
||||
|
||||
@property
|
||||
def selected_index(self):
|
||||
return self._selected_index
|
||||
|
||||
|
||||
@selected_index.setter
|
||||
def selected_index(self, value):
|
||||
self._selected_index = value
|
||||
self._update_selection()
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# Created By: Virgil Dupras
|
||||
# Created On: 2009-11-12
|
||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
||||
#
|
||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
#
|
||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
from collections import namedtuple
|
||||
@@ -14,9 +14,10 @@ from PyQt5.QtWidgets import QAction
|
||||
from hscommon.trans import trget
|
||||
from hscommon.util import dedupe
|
||||
|
||||
tr = trget('qtlib')
|
||||
tr = trget("qtlib")
|
||||
|
||||
MenuEntry = namedtuple("MenuEntry", "menu fixedItemCount")
|
||||
|
||||
MenuEntry = namedtuple('MenuEntry', 'menu fixedItemCount')
|
||||
|
||||
class Recent(QObject):
|
||||
def __init__(self, app, prefName, maxItemCount=10, **kwargs):
|
||||
@@ -27,19 +28,19 @@ class Recent(QObject):
|
||||
self._maxItemCount = maxItemCount
|
||||
self._items = []
|
||||
self._loadFromPrefs()
|
||||
|
||||
|
||||
self._app.willSavePrefs.connect(self._saveToPrefs)
|
||||
|
||||
#--- Private
|
||||
|
||||
# --- Private
|
||||
def _loadFromPrefs(self):
|
||||
items = getattr(self._app.prefs, self._prefName)
|
||||
if not isinstance(items, list):
|
||||
items = []
|
||||
self._items = items
|
||||
|
||||
|
||||
def _insertItem(self, item):
|
||||
self._items = dedupe([item] + self._items)[:self._maxItemCount]
|
||||
|
||||
self._items = dedupe([item] + self._items)[: self._maxItemCount]
|
||||
|
||||
def _refreshMenu(self, menuEntry):
|
||||
menu, fixedItemCount = menuEntry
|
||||
for action in menu.actions()[fixedItemCount:]:
|
||||
@@ -53,43 +54,41 @@ class Recent(QObject):
|
||||
action = QAction(tr("Clear List"), menu)
|
||||
action.triggered.connect(self.clear)
|
||||
menu.addAction(action)
|
||||
|
||||
|
||||
def _refreshAllMenus(self):
|
||||
for menuEntry in self._menuEntries:
|
||||
self._refreshMenu(menuEntry)
|
||||
|
||||
|
||||
def _saveToPrefs(self):
|
||||
setattr(self._app.prefs, self._prefName, self._items)
|
||||
|
||||
#--- Public
|
||||
|
||||
# --- Public
|
||||
def addMenu(self, menu):
|
||||
menuEntry = MenuEntry(menu, len(menu.actions()))
|
||||
self._menuEntries.append(menuEntry)
|
||||
self._refreshMenu(menuEntry)
|
||||
|
||||
|
||||
def clear(self):
|
||||
self._items = []
|
||||
self._refreshAllMenus()
|
||||
self.itemsChanged.emit()
|
||||
|
||||
|
||||
def insertItem(self, item):
|
||||
self._insertItem(str(item))
|
||||
self._refreshAllMenus()
|
||||
self.itemsChanged.emit()
|
||||
|
||||
|
||||
def isEmpty(self):
|
||||
return not bool(self._items)
|
||||
|
||||
#--- Event Handlers
|
||||
|
||||
# --- Event Handlers
|
||||
def menuItemWasClicked(self):
|
||||
action = self.sender()
|
||||
if action is not None:
|
||||
item = action.data()
|
||||
self.mustOpenItem.emit(item)
|
||||
self._refreshAllMenus()
|
||||
|
||||
#--- Signals
|
||||
|
||||
# --- Signals
|
||||
mustOpenItem = pyqtSignal(str)
|
||||
itemsChanged = pyqtSignal()
|
||||
|
||||
|
||||
|
||||
@@ -12,15 +12,16 @@ from PyQt5.QtWidgets import QToolButton, QLineEdit, QStyle, QStyleOptionFrame
|
||||
|
||||
from hscommon.trans import trget
|
||||
|
||||
tr = trget('qtlib')
|
||||
tr = trget("qtlib")
|
||||
|
||||
# IMPORTANT: For this widget to work propertly, you have to add "search_clear_13" from the
|
||||
# "images" folder in your resources.
|
||||
|
||||
|
||||
class LineEditButton(QToolButton):
|
||||
def __init__(self, parent, **kwargs):
|
||||
super().__init__(parent, **kwargs)
|
||||
pixmap = QPixmap(':/search_clear_13')
|
||||
pixmap = QPixmap(":/search_clear_13")
|
||||
self.setIcon(QIcon(pixmap))
|
||||
self.setIconSize(pixmap.size())
|
||||
self.setCursor(Qt.ArrowCursor)
|
||||
@@ -44,7 +45,7 @@ class ClearableEdit(QLineEdit):
|
||||
self._clearButton.clicked.connect(self._clearSearch)
|
||||
self.textChanged.connect(self._textChanged)
|
||||
|
||||
#--- Private
|
||||
# --- Private
|
||||
def _clearSearch(self):
|
||||
self.clear()
|
||||
|
||||
@@ -54,7 +55,7 @@ class ClearableEdit(QLineEdit):
|
||||
def _hasClearableContent(self):
|
||||
return bool(self.text())
|
||||
|
||||
#--- QLineEdit overrides
|
||||
# --- QLineEdit overrides
|
||||
def resizeEvent(self, event):
|
||||
if self._is_clearable:
|
||||
frameWidth = self.style().pixelMetric(QStyle.PM_DefaultFrameWidth)
|
||||
@@ -64,7 +65,7 @@ class ClearableEdit(QLineEdit):
|
||||
rightY = (rect.bottom() - rightHint.height()) // 2
|
||||
self._clearButton.move(rightX, rightY)
|
||||
|
||||
#--- Event Handlers
|
||||
# --- Event Handlers
|
||||
def _textChanged(self, text):
|
||||
if self._is_clearable:
|
||||
self._updateClearButton()
|
||||
@@ -79,7 +80,7 @@ class SearchEdit(ClearableEdit):
|
||||
|
||||
self.returnPressed.connect(self._returnPressed)
|
||||
|
||||
#--- Overrides
|
||||
# --- Overrides
|
||||
def _clearSearch(self):
|
||||
ClearableEdit._clearSearch(self)
|
||||
self.searchChanged.emit()
|
||||
@@ -101,20 +102,27 @@ class SearchEdit(ClearableEdit):
|
||||
if not bool(self.text()) and self.inactiveText and not self.hasFocus():
|
||||
panel = QStyleOptionFrame()
|
||||
self.initStyleOption(panel)
|
||||
textRect = self.style().subElementRect(QStyle.SE_LineEditContents, panel, self)
|
||||
textRect = self.style().subElementRect(
|
||||
QStyle.SE_LineEditContents, panel, self
|
||||
)
|
||||
leftMargin = 2
|
||||
rightMargin = self._clearButton.iconSize().width()
|
||||
textRect.adjust(leftMargin, 0, -rightMargin, 0)
|
||||
painter = QPainter(self)
|
||||
disabledColor = self.palette().brush(QPalette.Disabled, QPalette.Text).color()
|
||||
disabledColor = (
|
||||
self.palette().brush(QPalette.Disabled, QPalette.Text).color()
|
||||
)
|
||||
painter.setPen(disabledColor)
|
||||
painter.drawText(textRect, Qt.AlignLeft|Qt.AlignVCenter, self.inactiveText)
|
||||
painter.drawText(
|
||||
textRect, Qt.AlignLeft | Qt.AlignVCenter, self.inactiveText
|
||||
)
|
||||
|
||||
#--- Event Handlers
|
||||
# --- Event Handlers
|
||||
def _returnPressed(self):
|
||||
if not self.immediate:
|
||||
self.searchChanged.emit()
|
||||
|
||||
#--- Signals
|
||||
searchChanged = pyqtSignal() # Emitted when return is pressed or when the test is cleared
|
||||
|
||||
# --- Signals
|
||||
searchChanged = (
|
||||
pyqtSignal()
|
||||
) # Emitted when return is pressed or when the test is cleared
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
from PyQt5.QtCore import Qt, QAbstractListModel, QItemSelection, QItemSelectionModel
|
||||
|
||||
|
||||
class SelectableList(QAbstractListModel):
|
||||
def __init__(self, model, view, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
@@ -17,7 +18,7 @@ class SelectableList(QAbstractListModel):
|
||||
self.view.setModel(self)
|
||||
self.model.view = self
|
||||
|
||||
#--- Override
|
||||
# --- Override
|
||||
def data(self, index, role):
|
||||
if not index.isValid():
|
||||
return None
|
||||
@@ -31,14 +32,14 @@ class SelectableList(QAbstractListModel):
|
||||
return 0
|
||||
return len(self.model)
|
||||
|
||||
#--- Virtual
|
||||
# --- Virtual
|
||||
def _updateSelection(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def _restoreSelection(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
#--- model --> view
|
||||
# --- model --> view
|
||||
def refresh(self):
|
||||
self._updating = True
|
||||
self.beginResetModel()
|
||||
@@ -49,12 +50,13 @@ class SelectableList(QAbstractListModel):
|
||||
def update_selection(self):
|
||||
self._restoreSelection()
|
||||
|
||||
|
||||
class ComboboxModel(SelectableList):
|
||||
def __init__(self, model, view, **kwargs):
|
||||
super().__init__(model, view, **kwargs)
|
||||
self.view.currentIndexChanged[int].connect(self.selectionChanged)
|
||||
|
||||
#--- Override
|
||||
# --- Override
|
||||
def _updateSelection(self):
|
||||
index = self.view.currentIndex()
|
||||
if index != self.model.selected_index:
|
||||
@@ -65,20 +67,24 @@ class ComboboxModel(SelectableList):
|
||||
if index is not None:
|
||||
self.view.setCurrentIndex(index)
|
||||
|
||||
#--- Events
|
||||
# --- Events
|
||||
def selectionChanged(self, index):
|
||||
if not self._updating:
|
||||
self._updateSelection()
|
||||
|
||||
|
||||
class ListviewModel(SelectableList):
|
||||
def __init__(self, model, view, **kwargs):
|
||||
super().__init__(model, view, **kwargs)
|
||||
self.view.selectionModel().selectionChanged[(QItemSelection, QItemSelection)].connect(
|
||||
self.selectionChanged)
|
||||
self.view.selectionModel().selectionChanged[
|
||||
(QItemSelection, QItemSelection)
|
||||
].connect(self.selectionChanged)
|
||||
|
||||
#--- Override
|
||||
# --- Override
|
||||
def _updateSelection(self):
|
||||
newIndexes = [modelIndex.row() for modelIndex in self.view.selectionModel().selectedRows()]
|
||||
newIndexes = [
|
||||
modelIndex.row() for modelIndex in self.view.selectionModel().selectedRows()
|
||||
]
|
||||
if newIndexes != self.model.selected_indexes:
|
||||
self.model.select(newIndexes)
|
||||
|
||||
@@ -86,13 +92,17 @@ class ListviewModel(SelectableList):
|
||||
newSelection = QItemSelection()
|
||||
for index in self.model.selected_indexes:
|
||||
newSelection.select(self.createIndex(index, 0), self.createIndex(index, 0))
|
||||
self.view.selectionModel().select(newSelection, QItemSelectionModel.ClearAndSelect)
|
||||
self.view.selectionModel().select(
|
||||
newSelection, QItemSelectionModel.ClearAndSelect
|
||||
)
|
||||
if len(newSelection.indexes()):
|
||||
currentIndex = newSelection.indexes()[0]
|
||||
self.view.selectionModel().setCurrentIndex(currentIndex, QItemSelectionModel.Current)
|
||||
self.view.selectionModel().setCurrentIndex(
|
||||
currentIndex, QItemSelectionModel.Current
|
||||
)
|
||||
self.view.scrollTo(currentIndex)
|
||||
#--- Events
|
||||
|
||||
# --- Events
|
||||
def selectionChanged(self, index):
|
||||
if not self._updating:
|
||||
self._updateSelection()
|
||||
|
||||
|
||||
@@ -1,53 +1,72 @@
|
||||
# Created By: Virgil Dupras
|
||||
# Created On: 2009-11-01
|
||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
||||
#
|
||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
#
|
||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
from PyQt5.QtCore import Qt, QAbstractTableModel, QModelIndex, QItemSelectionModel, QItemSelection
|
||||
from PyQt5.QtCore import (
|
||||
Qt,
|
||||
QAbstractTableModel,
|
||||
QModelIndex,
|
||||
QItemSelectionModel,
|
||||
QItemSelection,
|
||||
)
|
||||
|
||||
from .column import Columns
|
||||
|
||||
|
||||
class Table(QAbstractTableModel):
|
||||
# Flags you want when index.isValid() is False. In those cases, _getFlags() is never called.
|
||||
INVALID_INDEX_FLAGS = Qt.ItemIsEnabled
|
||||
COLUMNS = []
|
||||
|
||||
|
||||
def __init__(self, model, view, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.model = model
|
||||
self.view = view
|
||||
self.view.setModel(self)
|
||||
self.model.view = self
|
||||
if hasattr(self.model, 'columns'):
|
||||
self.columns = Columns(self.model.columns, self.COLUMNS, view.horizontalHeader())
|
||||
|
||||
self.view.selectionModel().selectionChanged[(QItemSelection, QItemSelection)].connect(self.selectionChanged)
|
||||
|
||||
if hasattr(self.model, "columns"):
|
||||
self.columns = Columns(
|
||||
self.model.columns, self.COLUMNS, view.horizontalHeader()
|
||||
)
|
||||
|
||||
self.view.selectionModel().selectionChanged[
|
||||
(QItemSelection, QItemSelection)
|
||||
].connect(self.selectionChanged)
|
||||
|
||||
def _updateModelSelection(self):
|
||||
# Takes the selection on the view's side and update the model with it.
|
||||
# an _updateViewSelection() call will normally result in an _updateModelSelection() call.
|
||||
# to avoid infinite loops, we check that the selection will actually change before calling
|
||||
# model.select()
|
||||
newIndexes = [modelIndex.row() for modelIndex in self.view.selectionModel().selectedRows()]
|
||||
newIndexes = [
|
||||
modelIndex.row() for modelIndex in self.view.selectionModel().selectedRows()
|
||||
]
|
||||
if newIndexes != self.model.selected_indexes:
|
||||
self.model.select(newIndexes)
|
||||
|
||||
|
||||
def _updateViewSelection(self):
|
||||
# Takes the selection on the model's side and update the view with it.
|
||||
newSelection = QItemSelection()
|
||||
columnCount = self.columnCount(QModelIndex())
|
||||
for index in self.model.selected_indexes:
|
||||
newSelection.select(self.createIndex(index, 0), self.createIndex(index, columnCount-1))
|
||||
self.view.selectionModel().select(newSelection, QItemSelectionModel.ClearAndSelect)
|
||||
newSelection.select(
|
||||
self.createIndex(index, 0), self.createIndex(index, columnCount - 1)
|
||||
)
|
||||
self.view.selectionModel().select(
|
||||
newSelection, QItemSelectionModel.ClearAndSelect
|
||||
)
|
||||
if len(newSelection.indexes()):
|
||||
currentIndex = newSelection.indexes()[0]
|
||||
self.view.selectionModel().setCurrentIndex(currentIndex, QItemSelectionModel.Current)
|
||||
self.view.selectionModel().setCurrentIndex(
|
||||
currentIndex, QItemSelectionModel.Current
|
||||
)
|
||||
self.view.scrollTo(currentIndex)
|
||||
|
||||
#--- Data Model methods
|
||||
|
||||
# --- Data Model methods
|
||||
# Virtual
|
||||
def _getData(self, row, column, role):
|
||||
if role in (Qt.DisplayRole, Qt.EditRole):
|
||||
@@ -56,41 +75,41 @@ class Table(QAbstractTableModel):
|
||||
elif role == Qt.TextAlignmentRole:
|
||||
return column.alignment
|
||||
return None
|
||||
|
||||
|
||||
# Virtual
|
||||
def _getFlags(self, row, column):
|
||||
flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
|
||||
if row.can_edit_cell(column.name):
|
||||
flags |= Qt.ItemIsEditable
|
||||
return flags
|
||||
|
||||
|
||||
# Virtual
|
||||
def _setData(self, row, column, value, role):
|
||||
if role == Qt.EditRole:
|
||||
attrname = column.name
|
||||
if attrname == 'from':
|
||||
attrname = 'from_'
|
||||
if attrname == "from":
|
||||
attrname = "from_"
|
||||
setattr(row, attrname, value)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def columnCount(self, index):
|
||||
return self.model.columns.columns_count()
|
||||
|
||||
|
||||
def data(self, index, role):
|
||||
if not index.isValid():
|
||||
return None
|
||||
row = self.model[index.row()]
|
||||
column = self.model.columns.column_by_index(index.column())
|
||||
return self._getData(row, column, role)
|
||||
|
||||
|
||||
def flags(self, index):
|
||||
if not index.isValid():
|
||||
return self.INVALID_INDEX_FLAGS
|
||||
row = self.model[index.row()]
|
||||
column = self.model.columns.column_by_index(index.column())
|
||||
return self._getFlags(row, column)
|
||||
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
if orientation != Qt.Horizontal:
|
||||
return None
|
||||
@@ -103,50 +122,50 @@ class Table(QAbstractTableModel):
|
||||
return column.alignment
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def revert(self):
|
||||
self.model.cancel_edits()
|
||||
|
||||
|
||||
def rowCount(self, index):
|
||||
if index.isValid():
|
||||
return 0
|
||||
return len(self.model)
|
||||
|
||||
|
||||
def setData(self, index, value, role):
|
||||
if not index.isValid():
|
||||
return False
|
||||
row = self.model[index.row()]
|
||||
column = self.model.columns.column_by_index(index.column())
|
||||
return self._setData(row, column, value, role)
|
||||
|
||||
|
||||
def sort(self, section, order):
|
||||
column = self.model.columns.column_by_index(section)
|
||||
attrname = column.name
|
||||
self.model.sort_by(attrname, desc=order==Qt.DescendingOrder)
|
||||
|
||||
self.model.sort_by(attrname, desc=order == Qt.DescendingOrder)
|
||||
|
||||
def submit(self):
|
||||
self.model.save_edits()
|
||||
return True
|
||||
|
||||
#--- Events
|
||||
|
||||
# --- Events
|
||||
def selectionChanged(self, selected, deselected):
|
||||
self._updateModelSelection()
|
||||
|
||||
#--- model --> view
|
||||
|
||||
# --- model --> view
|
||||
def refresh(self):
|
||||
self.beginResetModel()
|
||||
self.endResetModel()
|
||||
self._updateViewSelection()
|
||||
|
||||
|
||||
def show_selected_row(self):
|
||||
if self.model.selected_index is not None:
|
||||
self.view.showRow(self.model.selected_index)
|
||||
|
||||
|
||||
def start_editing(self):
|
||||
self.view.editSelected()
|
||||
|
||||
|
||||
def stop_editing(self):
|
||||
self.view.setFocus() # enough to stop editing
|
||||
|
||||
self.view.setFocus() # enough to stop editing
|
||||
|
||||
def update_selection(self):
|
||||
self._updateViewSelection()
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
# Created On: 2012/01/23
|
||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
||||
#
|
||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
#
|
||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
|
||||
class TextField:
|
||||
def __init__(self, model, view):
|
||||
self.model = model
|
||||
self.view = view
|
||||
self.model.view = self
|
||||
# Make TextField also work for QLabel, which doesn't allow editing
|
||||
if hasattr(self.view, 'editingFinished'):
|
||||
if hasattr(self.view, "editingFinished"):
|
||||
self.view.editingFinished.connect(self.editingFinished)
|
||||
|
||||
|
||||
def editingFinished(self):
|
||||
self.model.text = self.view.text()
|
||||
|
||||
|
||||
# model --> view
|
||||
def refresh(self):
|
||||
self.view.setText(self.model.text)
|
||||
|
||||
|
||||
@@ -1,35 +1,36 @@
|
||||
# Created By: Virgil Dupras
|
||||
# Created On: 2009-09-14
|
||||
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
|
||||
#
|
||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
#
|
||||
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
|
||||
# which should be included with this package. The terms are also available at
|
||||
# http://www.gnu.org/licenses/gpl-3.0.html
|
||||
|
||||
import logging
|
||||
|
||||
from PyQt5.QtCore import QAbstractItemModel, QModelIndex
|
||||
|
||||
|
||||
class NodeContainer:
|
||||
def __init__(self):
|
||||
self._subnodes = None
|
||||
self._ref2node = {}
|
||||
|
||||
#--- Protected
|
||||
|
||||
# --- Protected
|
||||
def _createNode(self, ref, row):
|
||||
# This returns a TreeNode instance from ref
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def _getChildren(self):
|
||||
# This returns a list of ref instances, not TreeNode instances
|
||||
raise NotImplementedError()
|
||||
|
||||
#--- Public
|
||||
|
||||
# --- Public
|
||||
def invalidate(self):
|
||||
# Invalidates cached data and list of subnodes without resetting ref2node.
|
||||
self._subnodes = None
|
||||
|
||||
#--- Properties
|
||||
|
||||
# --- Properties
|
||||
@property
|
||||
def subnodes(self):
|
||||
if self._subnodes is None:
|
||||
@@ -44,7 +45,7 @@ class NodeContainer:
|
||||
self._ref2node[child] = node
|
||||
self._subnodes.append(node)
|
||||
return self._subnodes
|
||||
|
||||
|
||||
|
||||
class TreeNode(NodeContainer):
|
||||
def __init__(self, model, parent, row):
|
||||
@@ -52,38 +53,42 @@ class TreeNode(NodeContainer):
|
||||
self.model = model
|
||||
self.parent = parent
|
||||
self.row = row
|
||||
|
||||
|
||||
@property
|
||||
def index(self):
|
||||
return self.model.createIndex(self.row, 0, self)
|
||||
|
||||
|
||||
|
||||
class RefNode(TreeNode):
|
||||
"""Node pointing to a reference node.
|
||||
|
||||
|
||||
Use this if your Qt model wraps around a tree model that has iterable nodes.
|
||||
"""
|
||||
|
||||
def __init__(self, model, parent, ref, row):
|
||||
TreeNode.__init__(self, model, parent, row)
|
||||
self.ref = ref
|
||||
|
||||
|
||||
def _createNode(self, ref, row):
|
||||
return RefNode(self.model, self, ref, row)
|
||||
|
||||
|
||||
def _getChildren(self):
|
||||
return list(self.ref)
|
||||
|
||||
|
||||
|
||||
# We use a specific TreeNode subclass to easily spot dummy nodes, especially in exception tracebacks.
|
||||
class DummyNode(TreeNode):
|
||||
pass
|
||||
|
||||
|
||||
class TreeModel(QAbstractItemModel, NodeContainer):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self._dummyNodes = set() # dummy nodes' reference have to be kept to avoid segfault
|
||||
|
||||
#--- Private
|
||||
self._dummyNodes = (
|
||||
set()
|
||||
) # dummy nodes' reference have to be kept to avoid segfault
|
||||
|
||||
# --- Private
|
||||
def _createDummyNode(self, parent, row):
|
||||
# In some cases (drag & drop row removal, to be precise), there's a temporary discrepancy
|
||||
# between a node's subnodes and what the model think it has. This leads to invalid indexes
|
||||
@@ -91,18 +96,18 @@ class TreeModel(QAbstractItemModel, NodeContainer):
|
||||
# just have rows with empty data replacing removed rows for the millisecond that the drag &
|
||||
# drop lasts. Override this to return a node of the correct type.
|
||||
return DummyNode(self, parent, row)
|
||||
|
||||
|
||||
def _lastIndex(self):
|
||||
"""Index of the very last item in the tree.
|
||||
"""
|
||||
currentIndex = QModelIndex()
|
||||
rowCount = self.rowCount(currentIndex)
|
||||
while rowCount > 0:
|
||||
currentIndex = self.index(rowCount-1, 0, currentIndex)
|
||||
currentIndex = self.index(rowCount - 1, 0, currentIndex)
|
||||
rowCount = self.rowCount(currentIndex)
|
||||
return currentIndex
|
||||
|
||||
#--- Overrides
|
||||
|
||||
# --- Overrides
|
||||
def index(self, row, column, parent):
|
||||
if not self.subnodes:
|
||||
return QModelIndex()
|
||||
@@ -110,13 +115,17 @@ class TreeModel(QAbstractItemModel, NodeContainer):
|
||||
try:
|
||||
return self.createIndex(row, column, node.subnodes[row])
|
||||
except IndexError:
|
||||
logging.debug("Wrong tree index called (%r, %r, %r). Returning DummyNode",
|
||||
row, column, node)
|
||||
logging.debug(
|
||||
"Wrong tree index called (%r, %r, %r). Returning DummyNode",
|
||||
row,
|
||||
column,
|
||||
node,
|
||||
)
|
||||
parentNode = parent.internalPointer() if parent.isValid() else None
|
||||
dummy = self._createDummyNode(parentNode, row)
|
||||
self._dummyNodes.add(dummy)
|
||||
return self.createIndex(row, column, dummy)
|
||||
|
||||
|
||||
def parent(self, index):
|
||||
if not index.isValid():
|
||||
return QModelIndex()
|
||||
@@ -125,22 +134,22 @@ class TreeModel(QAbstractItemModel, NodeContainer):
|
||||
return QModelIndex()
|
||||
else:
|
||||
return self.createIndex(node.parent.row, 0, node.parent)
|
||||
|
||||
|
||||
def reset(self):
|
||||
super().beginResetModel()
|
||||
self.invalidate()
|
||||
self._ref2node = {}
|
||||
self._dummyNodes = set()
|
||||
super().endResetModel()
|
||||
|
||||
|
||||
def rowCount(self, parent=QModelIndex()):
|
||||
node = parent.internalPointer() if parent.isValid() else self
|
||||
return len(node.subnodes)
|
||||
|
||||
#--- Public
|
||||
|
||||
# --- Public
|
||||
def findIndex(self, rowPath):
|
||||
"""Returns the QModelIndex at `rowPath`
|
||||
|
||||
|
||||
`rowPath` is a sequence of node rows. For example, [1, 2, 1] is the 2nd child of the
|
||||
3rd child of the 2nd child of the root.
|
||||
"""
|
||||
@@ -148,7 +157,7 @@ class TreeModel(QAbstractItemModel, NodeContainer):
|
||||
for row in rowPath:
|
||||
result = self.index(row, 0, result)
|
||||
return result
|
||||
|
||||
|
||||
@staticmethod
|
||||
def pathForIndex(index):
|
||||
reversedPath = []
|
||||
@@ -156,10 +165,10 @@ class TreeModel(QAbstractItemModel, NodeContainer):
|
||||
reversedPath.append(index.row())
|
||||
index = index.parent()
|
||||
return list(reversed(reversedPath))
|
||||
|
||||
|
||||
def refreshData(self):
|
||||
"""Updates the data on all nodes, but without having to perform a full reset.
|
||||
|
||||
|
||||
A full reset on a tree makes us lose selection and expansion states. When all we ant to do
|
||||
is to refresh the data on the nodes without adding or removing a node, a call on
|
||||
dataChanged() is better. But of course, Qt makes our life complicated by asking us topLeft
|
||||
@@ -168,6 +177,5 @@ class TreeModel(QAbstractItemModel, NodeContainer):
|
||||
columnCount = self.columnCount()
|
||||
topLeft = self.index(0, 0, QModelIndex())
|
||||
bottomLeft = self._lastIndex()
|
||||
bottomRight = self.sibling(bottomLeft.row(), columnCount-1, bottomLeft)
|
||||
bottomRight = self.sibling(bottomLeft.row(), columnCount - 1, bottomLeft)
|
||||
self.dataChanged.emit(topLeft, bottomRight)
|
||||
|
||||
|
||||
@@ -16,25 +16,35 @@ from hscommon.util import first
|
||||
|
||||
from PyQt5.QtCore import QStandardPaths
|
||||
from PyQt5.QtGui import QPixmap, QIcon
|
||||
from PyQt5.QtWidgets import QDesktopWidget, QSpacerItem, QSizePolicy, QAction, QHBoxLayout
|
||||
from PyQt5.QtWidgets import (
|
||||
QDesktopWidget,
|
||||
QSpacerItem,
|
||||
QSizePolicy,
|
||||
QAction,
|
||||
QHBoxLayout,
|
||||
)
|
||||
|
||||
|
||||
def moveToScreenCenter(widget):
|
||||
frame = widget.frameGeometry()
|
||||
frame.moveCenter(QDesktopWidget().availableGeometry().center())
|
||||
widget.move(frame.topLeft())
|
||||
|
||||
|
||||
def verticalSpacer(size=None):
|
||||
if size:
|
||||
return QSpacerItem(1, size, QSizePolicy.Fixed, QSizePolicy.Fixed)
|
||||
else:
|
||||
return QSpacerItem(1, 1, QSizePolicy.Fixed, QSizePolicy.MinimumExpanding)
|
||||
|
||||
|
||||
def horizontalSpacer(size=None):
|
||||
if size:
|
||||
return QSpacerItem(size, 1, QSizePolicy.Fixed, QSizePolicy.Fixed)
|
||||
else:
|
||||
return QSpacerItem(1, 1, QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)
|
||||
|
||||
|
||||
def horizontalWrap(widgets):
|
||||
"""Wrap all widgets in `widgets` in a horizontal layout.
|
||||
|
||||
@@ -49,18 +59,20 @@ def horizontalWrap(widgets):
|
||||
layout.addWidget(widget)
|
||||
return layout
|
||||
|
||||
|
||||
def createActions(actions, target):
|
||||
# actions = [(name, shortcut, icon, desc, func)]
|
||||
for name, shortcut, icon, desc, func in actions:
|
||||
action = QAction(target)
|
||||
if icon:
|
||||
action.setIcon(QIcon(QPixmap(':/' + icon)))
|
||||
action.setIcon(QIcon(QPixmap(":/" + icon)))
|
||||
if shortcut:
|
||||
action.setShortcut(shortcut)
|
||||
action.setText(desc)
|
||||
action.triggered.connect(func)
|
||||
setattr(target, name, action)
|
||||
|
||||
|
||||
def setAccelKeys(menu):
|
||||
actions = menu.actions()
|
||||
titles = [a.text() for a in actions]
|
||||
@@ -71,18 +83,21 @@ def setAccelKeys(menu):
|
||||
if c is None:
|
||||
continue
|
||||
i = text.index(c)
|
||||
newtext = text[:i] + '&' + text[i:]
|
||||
newtext = text[:i] + "&" + text[i:]
|
||||
available_characters.remove(c.lower())
|
||||
action.setText(newtext)
|
||||
|
||||
|
||||
def getAppData():
|
||||
return QStandardPaths.standardLocations(QStandardPaths.DataLocation)[0]
|
||||
|
||||
|
||||
class SysWrapper(io.IOBase):
|
||||
def write(self, s):
|
||||
if s.strip(): # don't log empty stuff
|
||||
if s.strip(): # don't log empty stuff
|
||||
logging.warning(s)
|
||||
|
||||
|
||||
def setupQtLogging(level=logging.WARNING, log_to_stdout=False):
|
||||
# Under Qt, we log in "debug.log" in appdata. Moreover, when under cx_freeze, we have a
|
||||
# problem because sys.stdout and sys.stderr are None, so we need to replace them with a
|
||||
@@ -90,20 +105,21 @@ def setupQtLogging(level=logging.WARNING, log_to_stdout=False):
|
||||
appdata = getAppData()
|
||||
if not op.exists(appdata):
|
||||
os.makedirs(appdata)
|
||||
# Setup logging
|
||||
# Setup logging
|
||||
# Have to use full configuration over basicConfig as FileHandler encoding was not being set.
|
||||
filename = op.join(appdata, 'debug.log') if not log_to_stdout else None
|
||||
filename = op.join(appdata, "debug.log") if not log_to_stdout else None
|
||||
log = logging.getLogger()
|
||||
handler = logging.FileHandler(filename, 'a', 'utf-8')
|
||||
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
||||
handler = logging.FileHandler(filename, "a", "utf-8")
|
||||
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
|
||||
handler.setFormatter(formatter)
|
||||
log.addHandler(handler)
|
||||
if sys.stderr is None: # happens under a cx_freeze environment
|
||||
if sys.stderr is None: # happens under a cx_freeze environment
|
||||
sys.stderr = SysWrapper()
|
||||
if sys.stdout is None:
|
||||
sys.stdout = SysWrapper()
|
||||
|
||||
|
||||
def escapeamp(s):
|
||||
# Returns `s` with escaped ampersand (& --> &&). QAction text needs to have & escaped because
|
||||
# that character is used to define "accel keys".
|
||||
return s.replace('&', '&&')
|
||||
return s.replace("&", "&&")
|
||||
|
||||
Reference in New Issue
Block a user