mirror of
https://github.com/arsenetar/dupeguru.git
synced 2026-02-05 04:41:39 +00:00
Merge branch 'exclude_list' into dev
This commit is contained in:
54
qt/app.py
54
qt/app.py
@@ -27,6 +27,7 @@ from .result_window import ResultWindow
|
||||
from .directories_dialog import DirectoriesDialog
|
||||
from .problem_dialog import ProblemDialog
|
||||
from .ignore_list_dialog import IgnoreListDialog
|
||||
from .exclude_list_dialog import ExcludeListDialog
|
||||
from .deletion_options import DeletionOptions
|
||||
from .se.details_dialog import DetailsDialog as DetailsDialogStandard
|
||||
from .me.details_dialog import DetailsDialog as DetailsDialogMusic
|
||||
@@ -86,11 +87,17 @@ class DupeGuru(QObject):
|
||||
"IgnoreListDialog",
|
||||
parent=self.main_window,
|
||||
model=self.model.ignore_list_dialog)
|
||||
self.ignoreListDialog.accepted.connect(self.main_window.onDialogAccepted)
|
||||
|
||||
self.excludeListDialog = self.main_window.createPage(
|
||||
"ExcludeListDialog",
|
||||
app=self,
|
||||
parent=self.main_window,
|
||||
model=self.model.exclude_list_dialog)
|
||||
else:
|
||||
self.ignoreListDialog = IgnoreListDialog(
|
||||
parent=parent_window, model=self.model.ignore_list_dialog
|
||||
)
|
||||
parent=parent_window, model=self.model.ignore_list_dialog)
|
||||
self.excludeDialog = ExcludeListDialog(
|
||||
app=self, parent=parent_window, model=self.model.exclude_list_dialog)
|
||||
|
||||
self.deletionOptions = DeletionOptions(
|
||||
parent=parent_window,
|
||||
@@ -130,6 +137,7 @@ class DupeGuru(QObject):
|
||||
tr("Clear Picture Cache"),
|
||||
self.clearPictureCacheTriggered,
|
||||
),
|
||||
("actionExcludeList", "", "", tr("Exclusion Filters"), self.excludeListTriggered),
|
||||
("actionShowHelp", "F1", "", tr("dupeGuru Help"), self.showHelpTriggered),
|
||||
("actionAbout", "", "", tr("About dupeGuru"), self.showAboutBoxTriggered),
|
||||
(
|
||||
@@ -223,6 +231,10 @@ class DupeGuru(QObject):
|
||||
def showResultsWindow(self):
|
||||
if self.resultWindow is not None:
|
||||
if self.use_tabs:
|
||||
if self.main_window.indexOfWidget(self.resultWindow) < 0:
|
||||
self.main_window.addTab(
|
||||
self.resultWindow, "Results", switch=True)
|
||||
return
|
||||
self.main_window.showTab(self.resultWindow)
|
||||
else:
|
||||
self.resultWindow.show()
|
||||
@@ -267,19 +279,25 @@ class DupeGuru(QObject):
|
||||
|
||||
def ignoreListTriggered(self):
|
||||
if self.use_tabs:
|
||||
# Fetch the index in the TabWidget or the StackWidget (depends on class):
|
||||
index = self.main_window.indexOfWidget(self.ignoreListDialog)
|
||||
if index < 0:
|
||||
# we have not instantiated and populated it in their internal list yet
|
||||
index = self.main_window.addTab(
|
||||
self.ignoreListDialog, "Ignore List", switch=True)
|
||||
# if not self.main_window.tabWidget.isTabVisible(index):
|
||||
self.main_window.setTabVisible(index, True)
|
||||
self.main_window.setCurrentIndex(index)
|
||||
return
|
||||
else:
|
||||
self.showTriggeredTabbedDialog(self.ignoreListDialog, "Ignore List")
|
||||
else: # floating windows
|
||||
self.model.ignore_list_dialog.show()
|
||||
|
||||
def excludeListTriggered(self):
|
||||
if self.use_tabs:
|
||||
self.showTriggeredTabbedDialog(self.excludeListDialog, "Exclusion Filters")
|
||||
else: # floating windows
|
||||
self.model.exclude_list_dialog.show()
|
||||
|
||||
def showTriggeredTabbedDialog(self, dialog, desc_string):
|
||||
"""Add tab for dialog, name the tab with desc_string, then show it."""
|
||||
index = self.main_window.indexOfWidget(dialog)
|
||||
# Create the tab if it doesn't exist already
|
||||
if index < 0: # or (not dialog.isVisible() and not self.main_window.isTabVisible(index)):
|
||||
index = self.main_window.addTab(dialog, desc_string, switch=True)
|
||||
# Show the tab for that widget
|
||||
self.main_window.setCurrentIndex(index)
|
||||
|
||||
def openDebugLogTriggered(self):
|
||||
debugLogPath = op.join(self.model.appdata, "debug.log")
|
||||
desktop.open_path(debugLogPath)
|
||||
@@ -344,15 +362,15 @@ class DupeGuru(QObject):
|
||||
# or simply delete it on close which is probably cleaner:
|
||||
self.details_dialog.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.details_dialog.close()
|
||||
# self.details_dialog.setParent(None) # seems unnecessary
|
||||
# if we don't do the following, Qt will crash when we recreate the Results dialog
|
||||
self.details_dialog.setParent(None)
|
||||
if self.resultWindow is not None:
|
||||
self.resultWindow.close()
|
||||
self.resultWindow.setParent(None)
|
||||
# This is better for tabs, as it takes care of duplicate items in menu bar
|
||||
self.resultWindow.deleteLater() if self.use_tabs else self.resultWindow.setParent(None)
|
||||
if self.use_tabs:
|
||||
self.resultWindow = self.main_window.createPage(
|
||||
"ResultWindow", parent=self.main_window, app=self)
|
||||
self.main_window.addTab(
|
||||
self.resultWindow, "Results", switch=False)
|
||||
else: # We don't use a tab widget, regular floating QMainWindow
|
||||
self.resultWindow = ResultWindow(self.directories_dialog, self)
|
||||
self.directories_dialog._updateActionsState()
|
||||
|
||||
@@ -10,5 +10,6 @@
|
||||
<file alias="zoom_out">../images/old_zoom_out.png</file>
|
||||
<file alias="zoom_original">../images/old_zoom_original.png</file>
|
||||
<file alias="zoom_best_fit">../images/old_zoom_best_fit.png</file>
|
||||
<file alias="error">../images/dialog-error.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -132,6 +132,7 @@ class DirectoriesDialog(QMainWindow):
|
||||
self.menuView.addAction(self.app.actionDirectoriesWindow)
|
||||
self.menuView.addAction(self.actionShowResultsWindow)
|
||||
self.menuView.addAction(self.app.actionIgnoreList)
|
||||
self.menuView.addAction(self.app.actionExcludeList)
|
||||
self.menuView.addSeparator()
|
||||
self.menuView.addAction(self.app.actionPreferences)
|
||||
|
||||
|
||||
164
qt/exclude_list_dialog.py
Normal file
164
qt/exclude_list_dialog.py
Normal file
@@ -0,0 +1,164 @@
|
||||
# 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 re
|
||||
from PyQt5.QtCore import Qt, pyqtSlot
|
||||
from PyQt5.QtWidgets import (
|
||||
QPushButton, QLineEdit, QVBoxLayout, QGridLayout, QDialog,
|
||||
QTableView, QAbstractItemView, QSpacerItem, QSizePolicy, QHeaderView
|
||||
)
|
||||
from .exclude_list_table import ExcludeListTable
|
||||
|
||||
from core.exclude import AlreadyThereException
|
||||
from hscommon.trans import trget
|
||||
tr = trget("ui")
|
||||
|
||||
|
||||
class ExcludeListDialog(QDialog):
|
||||
def __init__(self, app, parent, model, **kwargs):
|
||||
flags = Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowSystemMenuHint
|
||||
super().__init__(parent, flags, **kwargs)
|
||||
self.app = app
|
||||
self.specific_actions = frozenset()
|
||||
self._setupUI()
|
||||
self.model = model # ExcludeListDialogCore
|
||||
self.model.view = self
|
||||
self.table = ExcludeListTable(app, view=self.tableView) # Qt ExcludeListTable
|
||||
self._row_matched = False # test if at least one row matched our test string
|
||||
self._input_styled = False
|
||||
|
||||
self.buttonAdd.clicked.connect(self.addStringFromLineEdit)
|
||||
self.buttonRemove.clicked.connect(self.removeSelected)
|
||||
self.buttonRestore.clicked.connect(self.restoreDefaults)
|
||||
self.buttonClose.clicked.connect(self.accept)
|
||||
self.buttonHelp.clicked.connect(self.display_help_message)
|
||||
self.buttonTestString.clicked.connect(self.onTestStringButtonClicked)
|
||||
self.inputLine.textEdited.connect(self.reset_input_style)
|
||||
self.testLine.textEdited.connect(self.reset_input_style)
|
||||
self.testLine.textEdited.connect(self.reset_table_style)
|
||||
|
||||
def _setupUI(self):
|
||||
layout = QVBoxLayout(self)
|
||||
gridlayout = QGridLayout()
|
||||
self.buttonAdd = QPushButton(tr("Add"))
|
||||
self.buttonRemove = QPushButton(tr("Remove Selected"))
|
||||
self.buttonRestore = QPushButton(tr("Restore defaults"))
|
||||
self.buttonTestString = QPushButton(tr("Test string"))
|
||||
self.buttonClose = QPushButton(tr("Close"))
|
||||
self.buttonHelp = QPushButton(tr("Help"))
|
||||
self.inputLine = QLineEdit()
|
||||
self.testLine = QLineEdit()
|
||||
self.tableView = QTableView()
|
||||
triggers = (
|
||||
QAbstractItemView.DoubleClicked
|
||||
| QAbstractItemView.EditKeyPressed
|
||||
| QAbstractItemView.SelectedClicked
|
||||
)
|
||||
self.tableView.setEditTriggers(triggers)
|
||||
self.tableView.setSelectionMode(QTableView.ExtendedSelection)
|
||||
self.tableView.setSelectionBehavior(QTableView.SelectRows)
|
||||
self.tableView.setShowGrid(False)
|
||||
vheader = self.tableView.verticalHeader()
|
||||
vheader.setSectionsMovable(True)
|
||||
vheader.setVisible(False)
|
||||
hheader = self.tableView.horizontalHeader()
|
||||
hheader.setSectionsMovable(False)
|
||||
hheader.setSectionResizeMode(QHeaderView.Fixed)
|
||||
hheader.setStretchLastSection(True)
|
||||
hheader.setHighlightSections(False)
|
||||
hheader.setVisible(True)
|
||||
gridlayout.addWidget(self.inputLine, 0, 0)
|
||||
gridlayout.addWidget(self.buttonAdd, 0, 1, Qt.AlignLeft)
|
||||
gridlayout.addWidget(self.buttonRemove, 1, 1, Qt.AlignLeft)
|
||||
gridlayout.addWidget(self.buttonRestore, 2, 1, Qt.AlignLeft)
|
||||
gridlayout.addWidget(self.buttonHelp, 3, 1, Qt.AlignLeft)
|
||||
gridlayout.addWidget(self.buttonClose, 4, 1)
|
||||
gridlayout.addWidget(self.tableView, 1, 0, 6, 1)
|
||||
gridlayout.addItem(QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding), 4, 1)
|
||||
gridlayout.addWidget(self.buttonTestString, 6, 1)
|
||||
gridlayout.addWidget(self.testLine, 6, 0)
|
||||
|
||||
layout.addLayout(gridlayout)
|
||||
self.inputLine.setPlaceholderText("Type a python regular expression here...")
|
||||
self.inputLine.setFocus()
|
||||
self.testLine.setPlaceholderText("Type a file system path or filename here...")
|
||||
self.testLine.setClearButtonEnabled(True)
|
||||
|
||||
# --- model --> view
|
||||
def show(self):
|
||||
super().show()
|
||||
self.inputLine.setFocus()
|
||||
|
||||
@pyqtSlot()
|
||||
def addStringFromLineEdit(self):
|
||||
text = self.inputLine.text()
|
||||
if not text:
|
||||
return
|
||||
try:
|
||||
self.model.add(text)
|
||||
except AlreadyThereException:
|
||||
self.app.show_message("Expression already in the list.")
|
||||
return
|
||||
except Exception as e:
|
||||
self.app.show_message(f"Expression is invalid: {e}")
|
||||
return
|
||||
self.inputLine.clear()
|
||||
|
||||
def removeSelected(self):
|
||||
self.model.remove_selected()
|
||||
|
||||
def restoreDefaults(self):
|
||||
self.model.restore_defaults()
|
||||
|
||||
def onTestStringButtonClicked(self):
|
||||
input_text = self.testLine.text()
|
||||
if not input_text:
|
||||
self.reset_input_style()
|
||||
return
|
||||
# if at least one row matched, we know whether table is highlighted or not
|
||||
self._row_matched = self.model.test_string(input_text)
|
||||
self.tableView.update()
|
||||
|
||||
input_regex = self.inputLine.text()
|
||||
if not input_regex:
|
||||
self.reset_input_style()
|
||||
return
|
||||
try:
|
||||
compiled = re.compile(input_regex)
|
||||
except re.error:
|
||||
self.reset_input_style()
|
||||
return
|
||||
match = compiled.match(input_text)
|
||||
if match:
|
||||
self._input_styled = True
|
||||
self.inputLine.setStyleSheet("background-color: rgb(10, 120, 10);")
|
||||
else:
|
||||
self.reset_input_style()
|
||||
|
||||
def reset_input_style(self):
|
||||
"""Reset regex input line background"""
|
||||
if self._input_styled:
|
||||
self._input_styled = False
|
||||
self.inputLine.setStyleSheet(self.styleSheet())
|
||||
|
||||
def reset_table_style(self):
|
||||
if self._row_matched:
|
||||
self._row_matched = False
|
||||
self.model.reset_rows_highlight()
|
||||
self.tableView.update()
|
||||
|
||||
def display_help_message(self):
|
||||
self.app.show_message(tr("""\
|
||||
These (case sensitive) python regular expressions will filter out files during scans.<br>\
|
||||
Directores will also have their <strong>default state</strong> set to Excluded \
|
||||
in the Directories tab if their name happen to match one of the regular expressions.<br>\
|
||||
For each file collected two tests are perfomed on each of them to determine whether or not to filter them out:<br>\
|
||||
<li>1. Regular expressions with no path separator in them will be compared to the file name only.</li>
|
||||
<li>2. Regular expressions with no path separator in them will be compared to the full path to the file.</li><br>
|
||||
Example: if you want to filter out .PNG files from the "My Pictures" directory only:<br>\
|
||||
<code>.*My\\sPictures\\\\.*\\.png</code><br><br>\
|
||||
You can test the regular expression with the test string feature by pasting a fake path in it:<br>\
|
||||
<code>C:\\\\User\\My Pictures\\test.png</code><br><br>
|
||||
Matching regular expressions will be highlighted.<br><br>
|
||||
Directories and files starting with a period '.' are filtered out by default.<br><br>"""))
|
||||
75
qt/exclude_list_table.py
Normal file
75
qt/exclude_list_table.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# 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
|
||||
from PyQt5.QtGui import QFont, QFontMetrics, QIcon, QColor
|
||||
|
||||
from qtlib.column import Column
|
||||
from qtlib.table import Table
|
||||
|
||||
|
||||
class ExcludeListTable(Table):
|
||||
"""Model for exclude list"""
|
||||
COLUMNS = [
|
||||
Column("marked", defaultWidth=15),
|
||||
Column("regex", defaultWidth=230)
|
||||
]
|
||||
|
||||
def __init__(self, app, view, **kwargs):
|
||||
model = app.model.exclude_list_dialog.exclude_list_table # pointer to GUITable
|
||||
super().__init__(model, view, **kwargs)
|
||||
font = view.font()
|
||||
font.setPointSize(app.prefs.tableFontSize)
|
||||
view.setFont(font)
|
||||
fm = QFontMetrics(font)
|
||||
view.verticalHeader().setDefaultSectionSize(fm.height() + 2)
|
||||
# app.willSavePrefs.connect(self.appWillSavePrefs)
|
||||
|
||||
def _getData(self, row, column, role):
|
||||
if column.name == "marked":
|
||||
if role == Qt.CheckStateRole and row.markable:
|
||||
return Qt.Checked if row.marked else Qt.Unchecked
|
||||
if role == Qt.ToolTipRole and not row.markable:
|
||||
return "Compilation error: " + row.get_cell_value("error")
|
||||
if role == Qt.DecorationRole and not row.markable:
|
||||
return QIcon.fromTheme("dialog-error", QIcon(":/error"))
|
||||
return None
|
||||
if role == Qt.DisplayRole:
|
||||
return row.data[column.name]
|
||||
elif role == Qt.FontRole:
|
||||
return QFont(self.view.font())
|
||||
elif role == Qt.BackgroundRole and column.name == "regex":
|
||||
if row.highlight:
|
||||
return QColor(10, 120, 10) # green
|
||||
elif role == Qt.EditRole:
|
||||
if column.name == "regex":
|
||||
return row.data[column.name]
|
||||
return None
|
||||
|
||||
def _getFlags(self, row, column):
|
||||
flags = Qt.ItemIsEnabled
|
||||
if column.name == "marked":
|
||||
if row.markable:
|
||||
flags |= Qt.ItemIsUserCheckable
|
||||
elif column.name == "regex":
|
||||
flags |= Qt.ItemIsEditable | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled
|
||||
return flags
|
||||
|
||||
def _setData(self, row, column, value, role):
|
||||
if role == Qt.CheckStateRole:
|
||||
if column.name == "marked":
|
||||
row.marked = bool(value)
|
||||
return True
|
||||
elif role == Qt.EditRole:
|
||||
if column.name == "regex":
|
||||
return self.model.rename_selected(value)
|
||||
return False
|
||||
|
||||
# def sort(self, column, order):
|
||||
# column = self.model.COLUMNS[column]
|
||||
# self.model.sort(column.name, order == Qt.AscendingOrder)
|
||||
|
||||
# # --- Events
|
||||
# def appWillSavePrefs(self):
|
||||
# self.model.columns.save_columns()
|
||||
@@ -10,6 +10,8 @@ from qtlib.table import Table
|
||||
|
||||
|
||||
class IgnoreListTable(Table):
|
||||
""" Ignore list model"""
|
||||
|
||||
COLUMNS = [
|
||||
Column("path1", defaultWidth=230),
|
||||
Column("path2", defaultWidth=230),
|
||||
|
||||
@@ -9,7 +9,6 @@ from PyQt5.QtWidgets import (
|
||||
QAbstractItemView, QSizePolicy, QGridLayout, QSplitter, QFrame)
|
||||
from PyQt5.QtGui import QResizeEvent
|
||||
from hscommon.trans import trget
|
||||
from hscommon.plat import ISWINDOWS
|
||||
from ..details_dialog import DetailsDialog as DetailsDialogBase
|
||||
from ..details_table import DetailsTable
|
||||
from .image_viewer import (
|
||||
@@ -102,14 +101,14 @@ class DetailsDialog(DetailsDialogBase):
|
||||
self.vController.updateBothImages()
|
||||
|
||||
def show(self):
|
||||
# Compute the maximum size the table view can reach
|
||||
# Assuming all rows below headers have the same height
|
||||
# Give the splitter a maximum height to reach. This is assuming that
|
||||
# all rows below their headers have the same height
|
||||
self.tableView.setMaximumHeight(
|
||||
self.tableView.rowHeight(1)
|
||||
* self.tableModel.model.row_count()
|
||||
+ self.tableView.verticalHeader().sectionSize(0)
|
||||
# Windows seems to add a few pixels more to the table somehow
|
||||
+ (5 if ISWINDOWS else 0))
|
||||
# looks like the handle is taken into account by the splitter
|
||||
+ self.splitter.handle(1).size().height())
|
||||
DetailsDialogBase.show(self)
|
||||
self.ensure_same_sizes()
|
||||
self._update()
|
||||
|
||||
@@ -161,28 +161,31 @@ On MacOS, the tab bar will fill up the window's width instead."))
|
||||
self.ui_groupbox.setLayout(layout)
|
||||
self.displayVLayout.addWidget(self.ui_groupbox)
|
||||
|
||||
gridlayout = QFormLayout()
|
||||
gridlayout = QGridLayout()
|
||||
gridlayout.setColumnStretch(2, 2)
|
||||
formlayout = QFormLayout()
|
||||
result_groupbox = QGroupBox("&Result Table")
|
||||
self.fontSizeSpinBox = QSpinBox()
|
||||
self.fontSizeSpinBox.setMinimum(5)
|
||||
gridlayout.addRow(tr("Font size:"), self.fontSizeSpinBox)
|
||||
formlayout.addRow(tr("Font size:"), self.fontSizeSpinBox)
|
||||
self._setupAddCheckbox("reference_bold_font",
|
||||
tr("Use bold font for references"))
|
||||
gridlayout.addRow(self.reference_bold_font)
|
||||
formlayout.addRow(self.reference_bold_font)
|
||||
|
||||
self.result_table_ref_foreground_color = ColorPickerButton(self)
|
||||
gridlayout.addRow(tr("Reference foreground color:"),
|
||||
formlayout.addRow(tr("Reference foreground color:"),
|
||||
self.result_table_ref_foreground_color)
|
||||
self.result_table_ref_background_color = ColorPickerButton(self)
|
||||
gridlayout.addRow(tr("Reference background color:"),
|
||||
self.result_table_ref_background_color)
|
||||
self.result_table_delta_foreground_color = ColorPickerButton(self)
|
||||
gridlayout.addRow(tr("Delta foreground color:"),
|
||||
formlayout.addRow(tr("Delta foreground color:"),
|
||||
self.result_table_delta_foreground_color)
|
||||
gridlayout.setLabelAlignment(Qt.AlignLeft)
|
||||
formlayout.setLabelAlignment(Qt.AlignLeft)
|
||||
|
||||
# Keep same vertical spacing as parent layout for consistency
|
||||
gridlayout.setVerticalSpacing(self.displayVLayout.spacing())
|
||||
formlayout.setVerticalSpacing(self.displayVLayout.spacing())
|
||||
gridlayout.addLayout(formlayout, 0, 0)
|
||||
result_groupbox.setLayout(gridlayout)
|
||||
self.displayVLayout.addWidget(result_groupbox)
|
||||
|
||||
@@ -205,12 +208,13 @@ use the modifier key to drag the floating window around") if ISLINUX else
|
||||
self.details_dialog_titlebar_enabled.stateChanged.connect(
|
||||
self.details_dialog_vertical_titlebar.setEnabled)
|
||||
gridlayout = QGridLayout()
|
||||
self.details_table_delta_foreground_color_label = QLabel(tr("Delta foreground color:"))
|
||||
gridlayout.addWidget(self.details_table_delta_foreground_color_label, 4, 0)
|
||||
formlayout = QFormLayout()
|
||||
self.details_table_delta_foreground_color = ColorPickerButton(self)
|
||||
gridlayout.addWidget(self.details_table_delta_foreground_color, 4, 2, 1, 1, Qt.AlignLeft)
|
||||
# Padding on the right side and space between label and widget to keep it somewhat consistent across themes
|
||||
gridlayout.setColumnStretch(1, 1)
|
||||
gridlayout.setColumnStretch(3, 4)
|
||||
formlayout.setHorizontalSpacing(50)
|
||||
formlayout.addRow(tr("Delta foreground color:"), self.details_table_delta_foreground_color)
|
||||
gridlayout.addLayout(formlayout, 0, 0)
|
||||
self.details_groupbox_layout.addLayout(gridlayout)
|
||||
details_groupbox.setLayout(self.details_groupbox_layout)
|
||||
self.displayVLayout.addWidget(details_groupbox)
|
||||
|
||||
@@ -18,6 +18,7 @@ from qtlib.util import moveToScreenCenter, createActions
|
||||
from .directories_dialog import DirectoriesDialog
|
||||
from .result_window import ResultWindow
|
||||
from .ignore_list_dialog import IgnoreListDialog
|
||||
from .exclude_list_dialog import ExcludeListDialog
|
||||
tr = trget("ui")
|
||||
|
||||
|
||||
@@ -25,7 +26,7 @@ class TabWindow(QMainWindow):
|
||||
def __init__(self, app, **kwargs):
|
||||
super().__init__(None, **kwargs)
|
||||
self.app = app
|
||||
self.pages = {}
|
||||
self.pages = {} # This is currently not used anywhere
|
||||
self.menubar = None
|
||||
self.menuList = set()
|
||||
self.last_index = -1
|
||||
@@ -108,7 +109,7 @@ class TabWindow(QMainWindow):
|
||||
self.menuList.add(self.menuHelp)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def updateMenuBar(self, page_index=None):
|
||||
def updateMenuBar(self, page_index=-1):
|
||||
if page_index < 0:
|
||||
return
|
||||
current_index = self.getCurrentIndex()
|
||||
@@ -141,6 +142,9 @@ class TabWindow(QMainWindow):
|
||||
and not page_type == "IgnoreListDialog" else False)
|
||||
self.app.actionDirectoriesWindow.setEnabled(
|
||||
False if page_type == "DirectoriesDialog" else True)
|
||||
self.app.actionExcludeList.setEnabled(
|
||||
True if self.app.excludeListDialog is not None
|
||||
and not page_type == "ExcludeListDialog" else False)
|
||||
|
||||
self.previous_widget_actions = active_widget.specific_actions
|
||||
self.last_index = current_index
|
||||
@@ -157,7 +161,14 @@ class TabWindow(QMainWindow):
|
||||
parent = kwargs.get("parent", self)
|
||||
model = kwargs.get("model")
|
||||
page = IgnoreListDialog(parent, model)
|
||||
self.pages[cls] = page
|
||||
page.accepted.connect(self.onDialogAccepted)
|
||||
elif cls == "ExcludeListDialog":
|
||||
app = kwargs.get("app", app)
|
||||
parent = kwargs.get("parent", self)
|
||||
model = kwargs.get("model")
|
||||
page = ExcludeListDialog(app, parent, model)
|
||||
page.accepted.connect(self.onDialogAccepted)
|
||||
self.pages[cls] = page # Not used, might remove
|
||||
return page
|
||||
|
||||
def addTab(self, page, title, switch=False):
|
||||
@@ -173,7 +184,6 @@ class TabWindow(QMainWindow):
|
||||
|
||||
def showTab(self, page):
|
||||
index = self.indexOfWidget(page)
|
||||
self.setTabVisible(index, True)
|
||||
self.setCurrentIndex(index)
|
||||
|
||||
def indexOfWidget(self, widget):
|
||||
@@ -182,9 +192,6 @@ class TabWindow(QMainWindow):
|
||||
def setCurrentIndex(self, index):
|
||||
return self.tabWidget.setCurrentIndex(index)
|
||||
|
||||
def setTabVisible(self, index, value):
|
||||
return self.tabWidget.setTabVisible(index, value)
|
||||
|
||||
def removeTab(self, index):
|
||||
return self.tabWidget.removeTab(index)
|
||||
|
||||
@@ -202,7 +209,7 @@ class TabWindow(QMainWindow):
|
||||
|
||||
# --- Events
|
||||
def appWillSavePrefs(self):
|
||||
# Right now this is useless since the first spawn dialog inside the
|
||||
# Right now this is useless since the first spawned dialog inside the
|
||||
# QTabWidget will assign its geometry after restoring it
|
||||
prefs = self.app.prefs
|
||||
prefs.mainWindowIsMaximized = self.isMaximized()
|
||||
@@ -223,14 +230,13 @@ class TabWindow(QMainWindow):
|
||||
# menu or shortcut. But this is useless if we don't have a button
|
||||
# set up to make a close request anyway. This check could be removed.
|
||||
return
|
||||
current_widget.close()
|
||||
self.setTabVisible(index, False)
|
||||
# current_widget.close() # seems unnecessary
|
||||
# self.tabWidget.widget(index).hide()
|
||||
self.removeTab(index)
|
||||
|
||||
@pyqtSlot()
|
||||
def onDialogAccepted(self):
|
||||
"""Remove tabbed dialog when Accepted/Done."""
|
||||
"""Remove tabbed dialog when Accepted/Done (close button clicked)."""
|
||||
widget = self.sender()
|
||||
index = self.indexOfWidget(widget)
|
||||
if index > -1:
|
||||
@@ -268,7 +274,7 @@ class TabBarWindow(TabWindow):
|
||||
self.verticalLayout.addLayout(self.horizontalLayout)
|
||||
self.verticalLayout.addWidget(self.stackedWidget)
|
||||
|
||||
self.tabBar.currentChanged.connect(self.showWidget)
|
||||
self.tabBar.currentChanged.connect(self.showTabIndex)
|
||||
self.tabBar.tabCloseRequested.connect(self.onTabCloseRequested)
|
||||
|
||||
self.stackedWidget.currentChanged.connect(self.updateMenuBar)
|
||||
@@ -278,50 +284,48 @@ class TabBarWindow(TabWindow):
|
||||
self.restoreGeometry()
|
||||
|
||||
def addTab(self, page, title, switch=True):
|
||||
stack_index = self.stackedWidget.insertWidget(-1, page)
|
||||
tab_index = self.tabBar.addTab(title)
|
||||
stack_index = self.stackedWidget.addWidget(page)
|
||||
self.tabBar.insertTab(stack_index, title)
|
||||
|
||||
if isinstance(page, DirectoriesDialog):
|
||||
self.tabBar.setTabButton(
|
||||
tab_index, QTabBar.RightSide, None)
|
||||
stack_index, QTabBar.RightSide, None)
|
||||
if switch: # switch to the added tab immediately upon creation
|
||||
self.setTabIndex(tab_index)
|
||||
self.stackedWidget.setCurrentWidget(page)
|
||||
self.setTabIndex(stack_index)
|
||||
return stack_index
|
||||
|
||||
@pyqtSlot(int)
|
||||
def showWidget(self, index):
|
||||
if index >= 0 and index <= self.stackedWidget.count() - 1:
|
||||
def showTabIndex(self, index):
|
||||
# The tab bar's indices should be aligned with the stackwidget's
|
||||
if index >= 0 and index <= self.stackedWidget.count():
|
||||
self.stackedWidget.setCurrentIndex(index)
|
||||
# if not self.tabBar.isTabVisible(index):
|
||||
self.setTabVisible(index, True)
|
||||
|
||||
def indexOfWidget(self, widget):
|
||||
# Warning: this may return -1 if widget is not a child of stackedwidget
|
||||
return self.stackedWidget.indexOf(widget)
|
||||
|
||||
def setCurrentIndex(self, tab_index):
|
||||
# The signal will handle switching the stackwidget's widget
|
||||
self.setTabIndex(tab_index)
|
||||
# The signal will handle switching the stackwidget's widget
|
||||
# self.stackedWidget.setCurrentWidget(self.stackedWidget.widget(tab_index))
|
||||
|
||||
def setCurrentWidget(self, widget):
|
||||
"""Sets the current Tab on TabBar for this widget."""
|
||||
self.tabBar.setCurrentIndex(self.indexOfWidget(widget))
|
||||
|
||||
@pyqtSlot(int)
|
||||
def setTabIndex(self, index):
|
||||
if index is None:
|
||||
return
|
||||
self.tabBar.setCurrentIndex(index)
|
||||
|
||||
def setTabVisible(self, index, value):
|
||||
return self.tabBar.setTabVisible(index, value)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def onRemovedWidget(self, index):
|
||||
self.removeTab(index)
|
||||
|
||||
@pyqtSlot(int)
|
||||
def removeTab(self, index):
|
||||
# No need to remove the widget here:
|
||||
# self.stackedWidget.removeWidget(self.stackedWidget.widget(index))
|
||||
"""Remove the tab, but not the widget (it should already be removed)"""
|
||||
return self.tabBar.removeTab(index)
|
||||
|
||||
@pyqtSlot(int)
|
||||
@@ -348,13 +352,18 @@ class TabBarWindow(TabWindow):
|
||||
|
||||
@pyqtSlot(int)
|
||||
def onTabCloseRequested(self, index):
|
||||
current_widget = self.getWidgetAtIndex(index)
|
||||
if isinstance(current_widget, DirectoriesDialog):
|
||||
target_widget = self.getWidgetAtIndex(index)
|
||||
if isinstance(target_widget, DirectoriesDialog):
|
||||
# On MacOS, the tab has a close button even though we explicitely
|
||||
# set it to None in order to hide it. This should prevent
|
||||
# the "Directories" tab from closing by mistake.
|
||||
return
|
||||
current_widget.close()
|
||||
self.stackedWidget.removeWidget(current_widget)
|
||||
# In this case the signal will take care of the tab itself after removing the widget
|
||||
# self.removeTab(index)
|
||||
# target_widget.close() # seems unnecessary
|
||||
# Removing the widget should trigger tab removal via the signal
|
||||
self.removeWidget(self.getWidgetAtIndex(index))
|
||||
|
||||
@pyqtSlot()
|
||||
def onDialogAccepted(self):
|
||||
"""Remove tabbed dialog when Accepted/Done (close button clicked)."""
|
||||
widget = self.sender()
|
||||
self.removeWidget(widget)
|
||||
|
||||
Reference in New Issue
Block a user