Update `diffresult.ui` with new features and prettyfy it.

* src/vorta/assets/UI/diffresult.ui : Replace `okButton` with `QDialogButtonBox`
	that has a `close` button.

* src/vorta/views/diff_result.py : Connect `buttonBox` instead of `okButton` to Dialog.

* src/vorta/assets/UI/diffresult.ui : Add title to `DiffResult` dialog and simplify file name display.

* src/vorta/assets/UI/diffresult.ui : Add comboBox to change display mode of tree view.

* src/vorta/views/diff_result.py : Connect comboBox to `DiffTree`.

* src/vorta/application.py : Remove `eventFilter` setup for palette changes.

* src/vorta/tray_menu.py : Connect directly to `app.paletteChanged`.
* src/vorta/views/archive_tab.py
* src/vorta/views/repo_tab.py
* src/vorta/views/schedule_tab.py
* src/vorta/views/source_tab.py

* src/vorta/assets/UI/diffresult.ui : Add buttons to keep folders on top when sorting and
	to expand and collapse all items.

* src/vorta/views/diff_result.py : Connect the added buttons.

* src/vorta/assets/icons/angle-up-solid.svg : Add icon for `bCollapseAll`.

* src/vorta/views/diff_result.py : Add context menu to `treeView` that allows expanding
	and copying of an item.

* src/vorta/views/diff_result.py : Add copy shortcut to `treeView`.
This commit is contained in:
real-yfprojects 2022-02-05 19:24:56 +01:00
parent c8ba273079
commit 7003d69577
No known key found for this signature in database
GPG Key ID: 00F630DFDEE25747
11 changed files with 209 additions and 67 deletions

View File

@ -3,9 +3,10 @@ import signal
import sys
from peewee import SqliteDatabase
from vorta._version import __version__
from vorta.i18n import trans_late, translate
from vorta.config import SETTINGS_DIR
from vorta.i18n import trans_late, translate
from vorta.log import init_logger, logger
from vorta.store.connection import init_db
from vorta.updater import get_updater
@ -15,6 +16,7 @@ from vorta.utils import parse_args
def main():
def exception_handler(type, value, tb):
from traceback import format_exception
from PyQt5.QtWidgets import QMessageBox
logger.critical("Uncaught exception, file a report at https://github.com/borgbase/vorta/issues/new",
exc_info=(type, value, tb))

View File

@ -88,8 +88,6 @@ class VortaApp(QtSingleApplication):
if sys.platform == 'darwin':
self.check_darwin_permissions()
self.installEventFilter(self)
def create_backups_cmdline(self, profile_name):
profile = BackupProfileModel.get_or_none(name=profile_name)
if profile is not None:
@ -99,17 +97,6 @@ class VortaApp(QtSingleApplication):
else:
logger.warning(f"Invalid profile name {profile_name}")
def eventFilter(self, source, event):
if event.type() == QtCore.QEvent.ApplicationPaletteChange and isinstance(source, MainWindow):
self.main_window.set_icons()
self.main_window.repoTab.set_icons()
self.main_window.archiveTab.set_icons()
self.main_window.scheduleTab.set_icons()
self.main_window.sourceTab.set_icons()
if event.type() == QtCore.QEvent.ApplicationPaletteChange and source == self.tray.contextMenu():
self.tray.set_tray_icon()
return False
def quit_app_action(self):
self.backup_cancelled_event.emit()
del self.main_window

View File

@ -16,24 +16,14 @@
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="spacing">
<number>4</number>
</property>
<property name="topMargin">
<number>10</number>
<number>0</number>
</property>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Difference between</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="archiveNameLabel_1">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>nyx2.local-2018-11-16T09:49:58 </string>
</property>
@ -42,18 +32,12 @@
<item>
<widget class="QLabel" name="label_3">
<property name="text">
<string>and</string>
<string></string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="archiveNameLabel_2">
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>nyx2.local-2018-10-16T09:49:58 </string>
</property>
@ -72,6 +56,63 @@
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="bFoldersOnTop">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="toolTip">
<string>Keep folders on top when sorting</string>
</property>
<property name="text">
<string>Folders On Top</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBoxDisplayMode">
<property name="toolTip">
<string>Set display mode of diff view</string>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToContents</enum>
</property>
<item>
<property name="text">
<string>Tree</string>
</property>
</item>
<item>
<property name="text">
<string>Tree, simplified</string>
</property>
</item>
<item>
<property name="text">
<string>Flat</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QToolButton" name="bCollapseAll">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="toolTip">
<string>Collapse All</string>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
@ -83,22 +124,9 @@
<number>10</number>
</property>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="okButton">
<property name="text">
<string>Ok</string>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
@ -106,6 +134,10 @@
</item>
</layout>
</widget>
<tabstops>
<tabstop>treeView</tabstop>
<tabstop>comboBoxDisplayMode</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg viewBox="0 0 512 512" version="1.1" id="svg3" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<defs id="defs7" />
<!--! Font Awesome Pro 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<path d="m 255.985,368 c -8.188,0 -16.38,-3.125 -22.62,-9.375 l -160,-160 c -12.5,-12.5 -12.5,-32.75 0,-45.25 12.5,-12.5 32.75,-12.5 45.25,0 l 137.37,137.425 137.4,-137.4 c 12.5,-12.5 32.75,-12.5 45.25,0 12.5,12.5 12.5,32.75 0,45.25 l -160,160 c -6.25,6.25 -14.45,9.35 -22.65,9.35 z" id="path2" style="fill:#000000;fill-opacity:1;stroke:none" />
</svg>

After

Width:  |  Height:  |  Size: 735 B

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg viewBox="0 0 512 512" version="1.1" id="svg3" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<!--! Font Awesome Pro 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. -->
<path fill="#000000" d="m 416.025,367.925 c -8.188,0 -16.38,-3.125 -22.62,-9.375 l -137.38,-137.325 -137.4,137.4 c -12.5,12.5 -32.75,12.5 -45.25,0 -12.5,-12.5 -12.5,-32.75 0,-45.25 l 160,-160 c 12.5,-12.5 32.75,-12.5 45.25,0 l 160,160 c 12.5,12.5 12.5,32.75 0,45.25 -6.2,6.2 -14.4,9.3 -22.6,9.3 z" id="path2" style="fill:#000000;fill-opacity:1;stroke:none;stroke-opacity:1" />
</svg>

After

Width:  |  Height:  |  Size: 742 B

View File

@ -1,13 +1,14 @@
import os
from PyQt5.QtWidgets import QMenu, QSystemTrayIcon
from PyQt5.QtGui import QIcon
from PyQt5.QtWidgets import QApplication, QMenu, QSystemTrayIcon
from vorta.store.models import BackupProfileModel
from vorta.utils import get_asset
class TrayMenu(QSystemTrayIcon):
def __init__(self, parent=None):
def __init__(self, parent: QApplication = None):
QSystemTrayIcon.__init__(self, parent)
self.app = parent
self.set_tray_icon()
@ -19,6 +20,7 @@ class TrayMenu(QSystemTrayIcon):
self.setContextMenu(menu)
self.activated.connect(self.on_activation)
self.app.paletteChanged.connect(lambda p: self.set_tray_icon())
self.setVisible(True)
self.show()

View File

@ -118,6 +118,9 @@ class ArchiveTab(ArchiveTabBase, ArchiveTabUI, BackupProfileMixin):
self.selected_archives = None
self.set_icons()
# Connect to palette change
self.app.paletteChanged.connect(lambda p: self.set_icons())
def set_icons(self):
"Used when changing between light- and dark mode"
self.bCheck.setIcon(get_colored_icon('check-circle'))

View File

@ -7,14 +7,16 @@ from pathlib import PurePath
from typing import List, Optional, Tuple
from PyQt5 import uic
from PyQt5.QtCore import QModelIndex, Qt
from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import QHeaderView, QTreeView
from PyQt5.QtCore import QMimeData, QModelIndex, QPoint, Qt, QUrl
from PyQt5.QtGui import QColor, QKeySequence
from PyQt5.QtWidgets import (QApplication, QHeaderView, QMenu, QShortcut,
QTreeView)
from vorta.utils import get_asset, pretty_bytes, uses_dark_mode
from vorta.views.partials.treemodel import (FileSystemItem, FileTreeModel,
FileTreeSortProxyModel,
path_to_str, relative_path)
from vorta.views.utils import get_colored_icon
uifile = get_asset('UI/diffresult.ui')
DiffResultUI, DiffResultBase = uic.loadUiType(uifile)
@ -50,11 +52,22 @@ class DiffResultDialog(DiffResultBase, DiffResultUI):
parse_diff_lines(lines, self.model)
self.treeView: QTreeView
self.treeView.setUniformRowHeights(True) # Allows for scrolling optimizations.
self.treeView.setUniformRowHeights(
True) # Allows for scrolling optimizations.
self.treeView.setAlternatingRowColors(True)
self.treeView.setTextElideMode(
Qt.TextElideMode.ElideMiddle) # to better see name of paths
# custom context menu
self.treeView.setContextMenuPolicy(
Qt.ContextMenuPolicy.CustomContextMenu)
self.treeView.customContextMenuRequested.connect(
self.treeview_context_menu)
# shortcuts
shortcut_copy = QShortcut(QKeySequence.StandardKey.Copy, self.treeView)
shortcut_copy.activated.connect(self.diff_item_copy)
# add sort proxy model
self.sortproxy = DiffSortProxyModel(self)
self.sortproxy.setSourceModel(self.model)
@ -63,15 +76,100 @@ class DiffResultDialog(DiffResultBase, DiffResultUI):
self.treeView.setSortingEnabled(True)
# header
header = self.treeView.header()
header.setStretchLastSection(False) # stretch only first section
header.setSectionResizeMode(0, QHeaderView.Stretch)
header.setSectionResizeMode(1, QHeaderView.ResizeToContents)
header.setSectionResizeMode(2, QHeaderView.ResizeToContents)
# signals
self.archiveNameLabel_1.setText(f'{archive_newer.name}')
self.archiveNameLabel_2.setText(f'{archive_older.name}')
self.okButton.clicked.connect(self.accept)
self.comboBoxDisplayMode.currentIndexChanged.connect(
self.change_display_mode)
self.bFoldersOnTop.toggled.connect(self.sortproxy.keepFoldersOnTop)
self.bCollapseAll.clicked.connect(self.treeView.collapseAll)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
self.set_icons()
# Connect to palette change
QApplication.instance().paletteChanged.connect(
lambda p: self.set_icons())
def set_icons(self):
"""Set or update the icons in the right color scheme."""
self.bCollapseAll.setIcon(get_colored_icon('angle-up-solid'))
def treeview_context_menu(self, pos: QPoint):
"""Display a context menu for `treeView`."""
index = self.treeView.indexAt(pos)
if not index.isValid():
# popup only for items
return
menu = QMenu(self.treeView)
menu.addAction(get_colored_icon('copy'), self.tr("Copy"),
lambda: self.diff_item_copy(index))
if self.model.getMode() != self.model.DisplayMode.FLAT:
menu.addSeparator()
menu.addAction(get_colored_icon('angle-down-solid'),
self.tr("Expand recursively"),
lambda: self.treeView.expandRecursively(index))
menu.popup(self.treeView.viewport().mapToGlobal(pos))
def diff_item_copy(self, index: QModelIndex = None):
"""
Copy a diff item path to the clipboard.
Copies the first selected item if no index is specified.
"""
if index is None or (not index.isValid()):
indexes = self.treeView.selectionModel().selectedRows()
if not indexes:
return
index = indexes[0]
index = self.sortproxy.mapToSource(index)
item: DiffItem = index.internalPointer()
path = PurePath('/', *item.path)
data = QMimeData()
data.setUrls([QUrl(path.as_uri())])
data.setText(str(path))
QApplication.clipboard().setMimeData(data)
def change_display_mode(self, selection: int):
"""
Change the display mode of the tree view
The `selection` parameter specifies the index of the selected mode in
`comboBoxDisplayMode`.
"""
if selection == 0:
mode = FileTreeModel.DisplayMode.TREE
elif selection == 1:
mode = FileTreeModel.DisplayMode.SIMPLIFIED_TREE
elif selection == 2:
mode = FileTreeModel.DisplayMode.FLAT
else:
raise Exception(
"Unknown item in comboBoxDisplayMode with index {}".format(
selection))
self.model.setMode(mode)
def slot_sorted(self, column, order):
"""React the tree view being sorted."""
@ -138,6 +236,7 @@ def parse_diff_json(diffs: List[dict], model: 'DiffTree'):
change_type = ChangeType.ADDED
else:
change_type = ChangeType.REMOVED
size = -size
changed_size = size
@ -176,13 +275,13 @@ pattern_owner = r'\[(?P<old_user>[\w ]+):(?P<old_group>[\w ]+) -> (?P<new_user>[
pattern_path = r'(?P<path>.*)'
pattern_changed_file = (
r'(({ar} )|((?P<cl>{cl} )|' +
r'((?P<modified>{modified}\s+)?)(?P<owner>{owner}\s+)?(?P<mode>{mode}\s+)?))' +
r'{path}').format(ar=pattern_ar,
cl=pattern_cl,
modified=pattern_modified,
mode=pattern_mode,
owner=pattern_owner,
path=pattern_path)
r'((?P<modified>{modified}\s+)?)(?P<owner>{owner}\s+)?(?P<mode>{mode}\s+)?))'
+ r'{path}').format(ar=pattern_ar,
cl=pattern_cl,
modified=pattern_modified,
mode=pattern_mode,
owner=pattern_owner,
path=pattern_path)
re_changed_file = re.compile(pattern_changed_file)
@ -519,7 +618,7 @@ class DiffTree(FileTreeModel[DiffData]):
size = child.data.size
changed_size = child.data.changed_size
def add_size(parent: DiffItem):
def add_size(parent):
if parent is self.root:
return

View File

@ -68,6 +68,9 @@ class RepoTab(RepoBase, RepoUI, BackupProfileMixin):
self.populate_from_profile()
self.set_icons()
# Connect to palette change
QApplication.instance().paletteChanged.connect(lambda p: self.set_icons())
def set_icons(self):
self.bAddSSHKey.setIcon(get_colored_icon("plus"))
self.bAddRepo.setIcon(get_colored_icon("plus"))

View File

@ -96,6 +96,9 @@ class ScheduleTab(ScheduleBase, ScheduleUI, BackupProfileMixin):
# Connect to schedule update
self.app.scheduler.schedule_changed.connect(lambda pid: self.draw_next_scheduled_backup())
# Connect to palette change
self.app.paletteChanged.connect(lambda p: self.set_icons())
def on_scheduler_change(self, _):
profile = self.profile()
# Save scheduler settings, apply new scheduler and display next task for profile.

View File

@ -111,7 +111,7 @@ def test_archive_diff_parser(line, expected):
'changes': [{
'type': 'changed link'
}]
}, ('some/changed/link', FileType.LINK, ChangeType.CHANGED_LINK, 0,0,
}, ('some/changed/link', FileType.LINK, ChangeType.CHANGED_LINK, 0, 0,
None, None, None)),
({
'path': 'some/changed/file',