1
0
Fork 0
mirror of https://github.com/borgbase/vorta synced 2025-01-02 21:25:48 +00:00

Improve extract dialog GUI.

Adds a contextmenu and a simplified tree view mode, a button to collapse the tree
and the option to keep folders on top of the list.

* src/vorta/assets/UI/extractdialog.ui
* src/vorta/views/extract_dialog.py

* src/vorta/views/extract_dialog.py (ExtractFileItem): Add this variable holding the type
	 `FileSystemItem[FileData]`.
This commit is contained in:
real-yfprojects 2022-07-02 10:11:25 +02:00
parent e7772f517b
commit 372b7107dd
No known key found for this signature in database
GPG key ID: 00F630DFDEE25747
2 changed files with 157 additions and 18 deletions

View file

@ -52,6 +52,58 @@
</property> </property>
</spacer> </spacer>
</item> </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>
</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> </layout>
</item> </item>
<item> <item>
@ -61,13 +113,6 @@
</attribute> </attribute>
</widget> </widget>
</item> </item>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Note: If you select a top-level folder and deselect its children, they will still be restored.</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QDialogButtonBox" name="buttonBox"> <widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons"> <property name="standardButtons">

View file

@ -7,11 +7,14 @@
from typing import Optional from typing import Optional
from PyQt5 import uic from PyQt5 import uic
from PyQt5.QtCore import QDateTime, QLocale, QModelIndex, Qt, QThread from PyQt5.QtCore import (QDateTime, QLocale, QMimeData, QModelIndex, QPoint,
from PyQt5.QtGui import QColor Qt, QThread, QUrl)
from PyQt5.QtWidgets import QDialogButtonBox, QHeaderView, QPushButton from PyQt5.QtGui import QColor, QKeySequence
from PyQt5.QtWidgets import (QApplication, QDialogButtonBox, QHeaderView,
QMenu, QPushButton, QShortcut)
from vorta.utils import get_asset, pretty_bytes, uses_dark_mode from vorta.utils import get_asset, pretty_bytes, uses_dark_mode
from vorta.views.utils import get_colored_icon
from .partials.treemodel import (FileSystemItem, FileTreeModel, from .partials.treemodel import (FileSystemItem, FileTreeModel,
FileTreeSortProxyModel, path_to_str, FileTreeSortProxyModel, path_to_str,
@ -60,6 +63,12 @@ def __init__(self, archive, model):
view.setAlternatingRowColors(True) view.setAlternatingRowColors(True)
view.setUniformRowHeights(True) # Allows for scrolling optimizations. view.setUniformRowHeights(True) # Allows for scrolling optimizations.
# custom context menu
self.treeView.setContextMenuPolicy(
Qt.ContextMenuPolicy.CustomContextMenu)
self.treeView.customContextMenuRequested.connect(
self.treeview_context_menu)
# add sort proxy model # add sort proxy model
self.sortproxy = ExtractSortProxyModel(self) self.sortproxy = ExtractSortProxyModel(self)
self.sortproxy.setSourceModel(self.model) self.sortproxy.setSourceModel(self.model)
@ -76,6 +85,10 @@ def __init__(self, archive, model):
header.setSectionResizeMode(3, QHeaderView.ResizeToContents) header.setSectionResizeMode(3, QHeaderView.ResizeToContents)
header.setSectionResizeMode(0, QHeaderView.Stretch) header.setSectionResizeMode(0, QHeaderView.Stretch)
# shortcuts
shortcut_copy = QShortcut(QKeySequence.StandardKey.Copy, self.treeView)
shortcut_copy.activated.connect(self.copy_item)
# add extract button to button box # add extract button to button box
self.extractButton = QPushButton(self) self.extractButton = QPushButton(self)
self.extractButton.setObjectName("extractButton") self.extractButton.setObjectName("extractButton")
@ -88,9 +101,20 @@ def __init__(self, archive, model):
self.archiveNameLabel.setText(f"{archive.name}, {archive.time}") self.archiveNameLabel.setText(f"{archive.name}, {archive.time}")
# connect signals # connect signals
self.comboBoxDisplayMode.currentIndexChanged.connect(
self.change_display_mode)
self.bFoldersOnTop.toggled.connect(self.sortproxy.keepFoldersOnTop)
self.bCollapseAll.clicked.connect(self.treeView.collapseAll)
self.buttonBox.rejected.connect(self.close) self.buttonBox.rejected.connect(self.close)
self.buttonBox.accepted.connect(self.accept) self.buttonBox.accepted.connect(self.accept)
self.set_icons()
# Connect to palette change
QApplication.instance().paletteChanged.connect(
lambda p: self.set_icons())
def retranslateUi(self, dialog): def retranslateUi(self, dialog):
"""Retranslate strings in ui.""" """Retranslate strings in ui."""
super().retranslateUi(dialog) super().retranslateUi(dialog)
@ -99,13 +123,80 @@ def retranslateUi(self, dialog):
if hasattr(self, "extractButton"): if hasattr(self, "extractButton"):
self.extractButton.setText(self.tr("Extract")) self.extractButton.setText(self.tr("Extract"))
def set_icons(self):
"""Set or update the icons in the right color scheme."""
self.bCollapseAll.setIcon(get_colored_icon('angle-up-solid'))
def slot_sorted(self, column, order): def slot_sorted(self, column, order):
"""React the tree view being sorted.""" """React to the tree view being sorted."""
# reveal selection # reveal selection
selectedRows = self.treeView.selectionModel().selectedRows() selectedRows = self.treeView.selectionModel().selectedRows()
if selectedRows: if selectedRows:
self.treeView.scrollTo(selectedRows[0]) self.treeView.scrollTo(selectedRows[0])
def copy_item(self, index: QModelIndex = None):
"""
Copy an 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: ExtractFileItem = 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
else:
raise Exception(
"Unknown item in comboBoxDisplayMode with index {}".format(
selection))
self.model.setMode(mode)
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.copy_item(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 parse_json_lines(lines, model: "ExtractTree"): def parse_json_lines(lines, model: "ExtractTree"):
"""Parse json output of `borg list`.""" """Parse json output of `borg list`."""
@ -150,7 +241,7 @@ class ExtractSortProxyModel(FileTreeSortProxyModel):
def choose_data(self, index: QModelIndex): def choose_data(self, index: QModelIndex):
"""Choose the data of index used for comparison.""" """Choose the data of index used for comparison."""
item: FileSystemItem[FileData] = index.internalPointer() item: ExtractFileItem = index.internalPointer()
column = index.column() column = index.column()
if column == 0: if column == 0:
@ -198,6 +289,9 @@ class FileData:
checked_children: int = 0 # number of children checked checked_children: int = 0 # number of children checked
ExtractFileItem = FileSystemItem[FileData]
class ExtractTree(FileTreeModel[FileData]): class ExtractTree(FileTreeModel[FileData]):
"""The file tree model for diff results.""" """The file tree model for diff results."""
@ -217,7 +311,7 @@ def _flat_filter(self, item):
""" """
return item.data and not item.children return item.data and not item.children
def _simplify_filter(self, item: FileSystemItem[FileData]) -> bool: def _simplify_filter(self, item: ExtractFileItem) -> bool:
""" """
Return whether an item may be merged in simplified mode. Return whether an item may be merged in simplified mode.
@ -353,7 +447,7 @@ def data(self, index: QModelIndex, role: int = Qt.ItemDataRole.DisplayRole):
if not index.isValid(): if not index.isValid():
return None return None
item: FileSystemItem[FileData] = index.internalPointer() item: ExtractFileItem = index.internalPointer()
column = index.column() column = index.column()
if role == Qt.ItemDataRole.DisplayRole: if role == Qt.ItemDataRole.DisplayRole:
@ -466,7 +560,7 @@ def setData(
if role != Qt.ItemDataRole.CheckStateRole: if role != Qt.ItemDataRole.CheckStateRole:
return False return False
item: FileSystemItem[FileData] = index.internalPointer() item: ExtractFileItem = index.internalPointer()
if value == item.data.checkstate: if value == item.data.checkstate:
return True return True
@ -504,10 +598,10 @@ def setData(
super_item.data.checked_children += 1 super_item.data.checked_children += 1
# update parent's state and possibly the parent's parent's state # update parent's state and possibly the parent's parent's state
if parent.data.checked_children: if super_item.data.checked_children:
self.setData(index.parent(), Qt.CheckState.PartiallyChecked, role) self.setData(super_index, Qt.CheckState.PartiallyChecked, role)
else: else:
self.setData(index.parent(), Qt.CheckState.Unchecked, role) self.setData(super_index, Qt.CheckState.Unchecked, role)
# update state of the children without changing their parents' states # update state of the children without changing their parents' states
if value != Qt.CheckState.PartiallyChecked: if value != Qt.CheckState.PartiallyChecked: