mirror of https://github.com/borgbase/vorta
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:
parent
c8ba273079
commit
7003d69577
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 |
|
@ -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 |
|
@ -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()
|
||||
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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',
|
||||
|
|
Loading…
Reference in New Issue