diff --git a/core/gui/problem_table.py b/core/gui/problem_table.py index f283f0eb..62b04721 100644 --- a/core/gui/problem_table.py +++ b/core/gui/problem_table.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # Created By: Virgil Dupras # Created On: 2010-04-12 # Copyright 2011 Hardcoded Software (http://www.hardcoded.net) @@ -9,11 +8,21 @@ from hscommon.notify import Listener from hscommon.gui.table import GUITable, Row +from hscommon.gui.column import Column, Columns +from hscommon.trans import trget + +coltr = trget('columns') class ProblemTable(GUITable, Listener): + COLUMNS = [ + Column('path', coltr("File Path")), + Column('msg', coltr("Error Message")), + ] + def __init__(self, view, problem_dialog): GUITable.__init__(self) Listener.__init__(self, problem_dialog) + self.columns = Columns(self) self.view = view self.dialog = problem_dialog diff --git a/qt/base/app.py b/qt/base/app.py index c941eac3..725ebf9b 100644 --- a/qt/base/app.py +++ b/qt/base/app.py @@ -53,6 +53,12 @@ class DupeGuru(QObject): LOGO_NAME = '' NAME = '' + DETAILS_DIALOG_CLASS = None + RESULT_WINDOW_CLASS = ResultWindow + RESULT_MODEL_CLASS = None + PREFERENCES_CLASS = None + PREFERENCES_DIALOG_CLASS = None + def __init__(self): QObject.__init__(self) appdata = str(QDesktopServices.storageLocation(QDesktopServices.DataLocation)) @@ -65,7 +71,7 @@ class DupeGuru(QObject): sys.stderr = SysWrapper() if sys.stdout is None: sys.stdout = SysWrapper() - self.prefs = self._create_preferences() + self.prefs = self.PREFERENCES_CLASS() self.prefs.load() self.model = self.MODELCLASS(view=self, appdata=appdata) self._setup() @@ -77,12 +83,12 @@ class DupeGuru(QObject): self._update_options() self.recentResults = Recent(self, 'recentResults') self.recentResults.mustOpenItem.connect(self.model.load_from) - self.resultWindow = self._create_result_window() + self.resultWindow = self.RESULT_WINDOW_CLASS(self) self._progress = Progress(self.resultWindow) self.directories_dialog = DirectoriesDialog(self.resultWindow, self) - self.details_dialog = self._create_details_dialog(self.resultWindow) + self.details_dialog = self.DETAILS_DIALOG_CLASS(self.resultWindow, self) self.problemDialog = ProblemDialog(parent=self.resultWindow, app=self) - self.preferences_dialog = self._create_preferences_dialog(self.resultWindow) + self.preferences_dialog = self.PREFERENCES_DIALOG_CLASS(self.resultWindow, self) self.about_box = AboutBox(self.resultWindow, self) self.directories_dialog.show() @@ -119,19 +125,6 @@ class DupeGuru(QObject): self.model.options['clean_empty_dirs'] = self.prefs.remove_empty_folders self.model.options['ignore_hardlink_matches'] = self.prefs.ignore_hardlink_matches - #--- Virtual - def _create_details_dialog(self, parent): - raise NotImplementedError() - - def _create_result_window(self): - return ResultWindow(app=self) - - def _create_preferences(self): - raise NotImplementedError() - - def _create_preferences_dialog(self, parent): - raise NotImplementedError() - #--- Public def add_selected_to_ignore_list(self): dupes = self.model.without_ref(self.model.selected_dupes) diff --git a/qt/base/preferences.py b/qt/base/preferences.py index f57c507d..2a42e85e 100644 --- a/qt/base/preferences.py +++ b/qt/base/preferences.py @@ -11,13 +11,6 @@ from PyQt4.QtGui import QApplication from qtlib.preferences import Preferences as PreferencesBase class Preferences(PreferencesBase): - # (width, is_visible) - COLUMNS_DEFAULT_ATTRS = [] - - def __init__(self): - PreferencesBase.__init__(self) - self.reset_columns() - def _load_specific(self, settings): # load prefs specific to the dg edition pass @@ -34,13 +27,6 @@ class Preferences(PreferencesBase): self.custom_command = get('CustomCommand', self.custom_command) self.language = get('Language', self.language) - widths = get('ColumnsWidth', self.columns_width) - # only set nonzero values - for index, width in enumerate(widths[:len(self.columns_width)]): - if width > 0: - self.columns_width[index] = width - self.columns_visible = get('ColumnsVisible', self.columns_visible) - self.tableFontSize = get('TableFontSize', self.tableFontSize) self.resultWindowIsMaximized = get('ResultWindowIsMaximized', self.resultWindowIsMaximized) self.resultWindowRect = self.get_rect('ResultWindowRect', self.resultWindowRect) @@ -76,10 +62,6 @@ class Preferences(PreferencesBase): self._reset_specific() - def reset_columns(self): - self.columns_width = [width for width, _ in self.COLUMNS_DEFAULT_ATTRS] - self.columns_visible = [visible for _, visible in self.COLUMNS_DEFAULT_ATTRS] - def _save_specific(self, settings): # save prefs specific to the dg edition pass @@ -94,8 +76,6 @@ class Preferences(PreferencesBase): set_('DebugMode', self.debug_mode) set_('DestinationType', self.destination_type) set_('CustomCommand', self.custom_command) - set_('ColumnsWidth', self.columns_width) - set_('ColumnsVisible', self.columns_visible) set_('Language', self.language) set_('TableFontSize', self.tableFontSize) diff --git a/qt/base/problem_table.py b/qt/base/problem_table.py index 9b814380..96444afc 100644 --- a/qt/base/problem_table.py +++ b/qt/base/problem_table.py @@ -6,17 +6,14 @@ # which should be included with this package. The terms are also available at # http://www.hardcoded.net/licenses/bsd_license -from hscommon.trans import trget from qtlib.column import Column from qtlib.table import Table from core.gui.problem_table import ProblemTable as ProblemTableModel -tr = trget('ui') - class ProblemTable(Table): COLUMNS = [ - Column('path', tr("File Path"), 150), - Column('msg', tr("Error Message"), 150), + Column('path', defaultWidth=150), + Column('msg', defaultWidth=150), ] def __init__(self, problem_dialog, view): diff --git a/qt/base/result_window.py b/qt/base/result_window.py index 1115bddb..d0de8b25 100644 --- a/qt/base/result_window.py +++ b/qt/base/result_window.py @@ -7,16 +7,16 @@ # http://www.hardcoded.net/licenses/bsd_license from PyQt4.QtCore import Qt, SIGNAL, QUrl, QRect -from PyQt4.QtGui import (QMainWindow, QMenu, QLabel, QHeaderView, QMessageBox, QInputDialog, - QLineEdit, QDesktopServices, QFileDialog, QMenuBar, QWidget, QVBoxLayout, QAbstractItemView, - QStatusBar, QDialog) +from PyQt4.QtGui import (QMainWindow, QMenu, QLabel, QMessageBox, QInputDialog, QLineEdit, + QDesktopServices, QFileDialog, QMenuBar, QWidget, QVBoxLayout, QAbstractItemView, QStatusBar, + QDialog) from hscommon.plat import ISOSX, ISLINUX from hscommon.trans import trget from hscommon.util import nonone from qtlib.util import moveToScreenCenter -from .results_model import ResultsModel, ResultsView +from .results_model import ResultsView from .stats_label import StatsLabel from .util import createActions from .prioritize_dialog import PrioritizeDialog @@ -29,9 +29,8 @@ class ResultWindow(QMainWindow): self.app = app self._last_filter = None self._setupUi() - self.resultsModel = ResultsModel(self.app, self.resultsView) + self.resultsModel = app.RESULT_MODEL_CLASS(self.app, self.resultsView) self.stats = StatsLabel(app, self.statusLabel) - self._load_columns() self._update_column_actions_status() self.connect(self.menuColumns, SIGNAL('triggered(QAction*)'), self.columnToggled) @@ -141,14 +140,15 @@ class ResultWindow(QMainWindow): # Columns menu menu = self.menuColumns self._column_actions = [] - for index, column in enumerate(self.app.model.COLUMNS): - action = menu.addAction(column.display) + for index, (display, visible) in enumerate(self.app.model.result_table.columns.menu_items()): + action = menu.addAction(display) action.setCheckable(True) - action.column_index = index + action.setChecked(visible) + action.item_index = index self._column_actions.append(action) menu.addSeparator() action = menu.addAction(tr("Reset to Defaults")) - action.column_index = -1 + action.item_index = -1 # Action menu actionMenu = QMenu(tr("Actions"), self.menubar) @@ -203,21 +203,11 @@ class ResultWindow(QMainWindow): moveToScreenCenter(self) #--- Private - def _load_columns(self): - h = self.resultsView.horizontalHeader() - h.setResizeMode(QHeaderView.Interactive) - prefs = self.app.prefs - attrs = list(zip(prefs.columns_width, prefs.columns_visible)) - for index, (width, visible) in enumerate(attrs): - h.resizeSection(index, width) - h.setSectionHidden(index, not visible) - h.setResizeMode(0, QHeaderView.Stretch) - def _update_column_actions_status(self): - h = self.resultsView.horizontalHeader() - for action in self._column_actions: - colid = action.column_index - action.setChecked(not h.isSectionHidden(colid)) + # Update menu checked state + menu_items = self.app.model.result_table.columns.menu_items() + for action, (display, visible) in zip(self._column_actions, menu_items): + action.setChecked(visible) #--- Actions def actionsTriggered(self): @@ -352,26 +342,17 @@ class ResultWindow(QMainWindow): #--- Events def appWillSavePrefs(self): prefs = self.app.prefs - h = self.resultsView.horizontalHeader() - widths = [] - visible = [] - for i in range(len(self.app.model.COLUMNS)): - widths.append(h.sectionSize(i)) - visible.append(not h.isSectionHidden(i)) - prefs.columns_width = widths - prefs.columns_visible = visible prefs.resultWindowIsMaximized = self.isMaximized() prefs.resultWindowRect = self.geometry() def columnToggled(self, action): - colid = action.column_index - if colid == -1: - self.app.prefs.reset_columns() - self._load_columns() + index = action.item_index + if index == -1: + self.app.model.result_table.columns.reset_to_defaults() + self._update_column_actions_status() else: - h = self.resultsView.horizontalHeader() - h.setSectionHidden(colid, not h.isSectionHidden(colid)) - self._update_column_actions_status() + visible = self.app.model.result_table.columns.toggle_menu_item(index) + action.setChecked(visible) def contextMenuEvent(self, event): self.actionActions.menu().exec_(event.globalPos()) diff --git a/qt/base/results_model.py b/qt/base/results_model.py index 5123f60f..28f80dd5 100644 --- a/qt/base/results_model.py +++ b/qt/base/results_model.py @@ -15,31 +15,31 @@ from core.gui.result_table import ResultTable as ResultTableModel class ResultsModel(Table): def __init__(self, app, view): - model = ResultTableModel(self, app.model) - self._app = app.model - self._delta_columns = app.model.DELTA_COLUMNS + model = app.model.result_table Table.__init__(self, model, view) self.model.connect() app.prefsChanged.connect(self.appPrefsChanged) + app.willSavePrefs.connect(self.appWillSavePrefs) def columnCount(self, parent): - return len(self._app.COLUMNS) + return len(self.model.COLUMNS) def data(self, index, role): if not index.isValid(): return None row = self.model[index.row()] + column = self.model.COLUMNS[index.column()] if role == Qt.DisplayRole: data = row.data_delta if self.model.delta_values else row.data - return data[index.column()] + return data[column.name] elif role == Qt.CheckStateRole: if index.column() == 0 and row.markable: return Qt.Checked if row.marked else Qt.Unchecked elif role == Qt.ForegroundRole: if row.isref: return QBrush(Qt.blue) - elif self.model.delta_values and index.column() in self._delta_columns: + elif self.model.delta_values and column.name in self.model.DELTA_COLUMNS: return QBrush(QColor(255, 142, 40)) # orange elif role == Qt.FontRole: isBold = row.isref @@ -47,8 +47,8 @@ class ResultsModel(Table): font.setBold(isBold) return font elif role == Qt.EditRole: - if index.column() == 0: - return row.data[index.column()] + if column.name == 'name': + return row.data[column.name] return None def flags(self, index): @@ -64,26 +64,28 @@ class ResultsModel(Table): def headerData(self, section, orientation, role): if role == Qt.DisplayRole: - if orientation == Qt.Horizontal and section < len(self._app.COLUMNS): - return self._app.COLUMNS[section].display + if orientation == Qt.Horizontal and section < len(self.model.COLUMNS): + return self.model.COLUMNS[section].display return None def setData(self, index, value, role): if not index.isValid(): return False row = self.model[index.row()] + column = self.model.COLUMNS[index.column()] if role == Qt.CheckStateRole: - if index.column() == 0: - self._app.mark_dupe(row._dupe, value.toBool()) + if column.name == 'name': + self.model.app.mark_dupe(row._dupe, value.toBool()) return True elif role == Qt.EditRole: - if index.column() == 0: + if column.name == 'name': value = str(value.toString()) return self.model.rename_selected(value) return False def sort(self, column, order): - self.model.sort(column, order == Qt.AscendingOrder) + column = self.model.COLUMNS[column] + self.model.sort(column.name, order == Qt.AscendingOrder) #--- Properties @property @@ -110,6 +112,9 @@ class ResultsModel(Table): fm = QFontMetrics(font) self.view.verticalHeader().setDefaultSectionSize(fm.height()+2) + def appWillSavePrefs(self): + self.model.columns.save_columns() + #--- model --> view def invalidate_markings(self): # redraw view diff --git a/qt/me/app.py b/qt/me/app.py index a647ea08..3d2e3554 100644 --- a/qt/me/app.py +++ b/qt/me/app.py @@ -11,6 +11,7 @@ from core_me.app import DupeGuru as DupeGuruModel from ..base.app import DupeGuru as DupeGuruBase from .details_dialog import DetailsDialog +from .results_model import ResultsModel from .preferences import Preferences from .preferences_dialog import PreferencesDialog @@ -20,6 +21,11 @@ class DupeGuru(DupeGuruBase): LOGO_NAME = 'logo_me' NAME = __appname__ + DETAILS_DIALOG_CLASS = DetailsDialog + RESULT_MODEL_CLASS = ResultsModel + PREFERENCES_CLASS = Preferences + PREFERENCES_DIALOG_CLASS = PreferencesDialog + def _update_options(self): DupeGuruBase._update_options(self) self.model.scanner.min_match_percentage = self.prefs.filter_hardness @@ -41,12 +47,3 @@ class DupeGuru(DupeGuruBase): scanned_tags.add('year') self.model.scanner.scanned_tags = scanned_tags - def _create_details_dialog(self, parent): - return DetailsDialog(parent, self) - - def _create_preferences(self): - return Preferences() - - def _create_preferences_dialog(self, parent): - return PreferencesDialog(parent, self) - diff --git a/qt/me/preferences.py b/qt/me/preferences.py index 14a29123..94f177fa 100644 --- a/qt/me/preferences.py +++ b/qt/me/preferences.py @@ -11,28 +11,6 @@ from core.scanner import ScanType from ..base.preferences import Preferences as PreferencesBase class Preferences(PreferencesBase): - # (width, is_visible) - COLUMNS_DEFAULT_ATTRS = [ - (200, True), # name - (180, True), # path - (60, True), # size - (60, True), # Time - (50, True), # Bitrate - (60, False), # Sample Rate - (40, False), # Kind - (120, False), # modification - (120, False), # Title - (120, False), # Artist - (120, False), # Album - (80, False), # Genre - (40, False), # Year - (40, False), # Track Number - (120, False), # Comment - (60, True), # match % - (120, False), # Words Used - (80, False), # dupe count - ] - def _load_specific(self, settings): get = self.get_value self.scan_type = get('ScanType', self.scan_type) diff --git a/qt/pe/app.py b/qt/pe/app.py index 92020f0b..74be6f8c 100644 --- a/qt/pe/app.py +++ b/qt/pe/app.py @@ -18,6 +18,7 @@ from ..base.app import DupeGuru as DupeGuruBase from .block import getblocks from .details_dialog import DetailsDialog from .result_window import ResultWindow +from .results_model import ResultsModel from .preferences import Preferences from .preferences_dialog import PreferencesDialog @@ -70,6 +71,12 @@ class DupeGuru(DupeGuruBase): LOGO_NAME = 'logo_pe' NAME = __appname__ + DETAILS_DIALOG_CLASS = DetailsDialog + RESULT_WINDOW_CLASS = ResultWindow + RESULT_MODEL_CLASS = ResultsModel + PREFERENCES_CLASS = Preferences + PREFERENCES_DIALOG_CLASS = PreferencesDialog + def _setup(self): self.model.directories.fileclasses = [File] DupeGuruBase._setup(self) @@ -80,15 +87,3 @@ class DupeGuru(DupeGuruBase): self.model.scanner.match_scaled = self.prefs.match_scaled self.model.scanner.threshold = self.prefs.filter_hardness - def _create_details_dialog(self, parent): - return DetailsDialog(parent, self) - - def _create_result_window(self): - return ResultWindow(app=self) - - def _create_preferences(self): - return Preferences() - - def _create_preferences_dialog(self, parent): - return PreferencesDialog(parent, self) - diff --git a/qt/pe/preferences.py b/qt/pe/preferences.py index effa1126..154d2601 100644 --- a/qt/pe/preferences.py +++ b/qt/pe/preferences.py @@ -11,18 +11,6 @@ from core.scanner import ScanType from ..base.preferences import Preferences as PreferencesBase class Preferences(PreferencesBase): - # (width, is_visible) - COLUMNS_DEFAULT_ATTRS = [ - (200, True), # name - (180, True), # path - (60, True), # size - (40, False), # kind - (100, True), # dimensions - (120, False), # modification - (60, True), # match % - (80, False), # dupe count - ] - def _load_specific(self, settings): get = self.get_value self.scan_type = get('ScanType', self.scan_type) diff --git a/qt/se/app.py b/qt/se/app.py index 8b82c893..6b8080dc 100644 --- a/qt/se/app.py +++ b/qt/se/app.py @@ -12,6 +12,7 @@ from core.directories import Directories as DirectoriesBase, DirectoryState from ..base.app import DupeGuru as DupeGuruBase from .details_dialog import DetailsDialog +from .results_model import ResultsModel from .preferences import Preferences from .preferences_dialog import PreferencesDialog @@ -30,6 +31,11 @@ class DupeGuru(DupeGuruBase): LOGO_NAME = 'logo_se' NAME = __appname__ + DETAILS_DIALOG_CLASS = DetailsDialog + RESULT_MODEL_CLASS = ResultsModel + PREFERENCES_CLASS = Preferences + PREFERENCES_DIALOG_CLASS = PreferencesDialog + def _setup(self): self.directories = Directories() DupeGuruBase._setup(self) @@ -43,12 +49,3 @@ class DupeGuru(DupeGuruBase): threshold = self.prefs.small_file_threshold if self.prefs.ignore_small_files else 0 self.model.scanner.size_threshold = threshold * 1024 # threshold is in KB. the scanner wants bytes - def _create_details_dialog(self, parent): - return DetailsDialog(parent, self) - - def _create_preferences(self): - return Preferences() - - def _create_preferences_dialog(self, parent): - return PreferencesDialog(parent, self) - diff --git a/qt/se/preferences.py b/qt/se/preferences.py index b243c93b..dce59c2f 100644 --- a/qt/se/preferences.py +++ b/qt/se/preferences.py @@ -11,18 +11,6 @@ from core.scanner import ScanType from ..base.preferences import Preferences as PreferencesBase class Preferences(PreferencesBase): - # (width, is_visible) - COLUMNS_DEFAULT_ATTRS = [ - (200, True), # name - (180, True), # path - (60, True), # size - (40, False), # Kind - (120, False), # modification - (60, True), # match % - (120, False), # Words Used - (80, False), # dupe count - ] - def _load_specific(self, settings): get = self.get_value self.scan_type = get('ScanType', self.scan_type)