1
0
Fork 0
mirror of https://github.com/borgbase/vorta synced 2024-12-22 07:43:09 +00:00

Add tooltips to settings. (#1521)

This adds tooltips to the settings as well as a 'info' button that shows the tooltip when hovering over it.

* Add tooltips to settings.

* src/vorta/store/models.py (SettingsModel): Add `tooltip` column.

* src/vorta/store/migrations.py (run_migrations): Create `tooltip` column.

* src/vorta/store/connection.py (init_db): Populate `tooltip` column. Increase `SCHEMA_VERSION`.

* src/vorta/views/misc_tab.py (MiscTab.populate): Set tooltip of checkbox widgets.

* src/vorta/store/settings.py : Add tooltips and update label of `override_mount_permissions`

* Add *help* button to settings.

* src/vorta/assets/icons/help-about.svg: Add info icon.

* src/vorta/views/partials/tooltip_button.py: Implement `ToolTipButton`.

* src/vorta/views/misc_tab.py: Add `ToolTipButton` for each setting with a tooltip.
	Add `set_icons` and connect it to palette change.

* tests/test_misc.py (test_autostart): Update test.

---------

Co-authored-by: real-yfprojects <real-yfprojects@users.noreply.github.com>
This commit is contained in:
yfprojects 2023-02-25 10:10:43 +00:00 committed by GitHub
parent bcc126b634
commit 618a1fe278
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 188 additions and 6 deletions

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg viewBox="0 0 64 64" version="1.1" id="svg6" sodipodi:docname="help-about.svg" width="64" height="64" inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview id="namedview8" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" inkscape:pageshadow="2" inkscape:pageopacity="0.0" inkscape:pagecheckerboard="0" showgrid="false" inkscape:zoom="13.068182" inkscape:cx="52.37913" inkscape:cy="41.972174" inkscape:window-width="3840" inkscape:window-height="2107" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" inkscape:current-layer="svg6" />
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#000000;
}
</style>
</defs>
<path style="fill:currentColor;fill-opacity:1;stroke:none;stroke-width:2.90909" d="m 31.999999,8.7272725 c -12.893091,0 -23.2727265,10.3796355 -23.2727265,23.2727265 0,12.893091 10.3796355,23.272729 23.2727265,23.272729 C 44.89309,55.272728 55.272728,44.89309 55.272728,32 55.272727,19.106908 44.89309,8.7272725 31.999999,8.7272725 Z m 0,2.9090905 c 11.281455,0 20.363637,9.082182 20.363637,20.363636 0,11.281455 -9.082182,20.363637 -20.363637,20.363637 -11.281454,0 -20.363636,-9.082182 -20.363636,-20.363637 0,-11.281454 9.082182,-20.363636 20.363636,-20.363636 z m -2.909091,5.818182 v 5.818182 h 5.818182 v -5.818182 z m 0,8.727272 V 46.545454 H 34.90909 V 26.181817 Z" class="ColorScheme-Text" id="path4" />
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -18,7 +18,7 @@
)
from .settings import get_misc_settings
SCHEMA_VERSION = 19
SCHEMA_VERSION = 20
@signals.post_save(sender=SettingsModel)
@ -91,5 +91,7 @@ def init_db(con=None):
if 'group' in setting:
s.group = setting['group']
if 'tooltip' in setting:
s.tooltip = setting['tooltip']
s.save()

View file

@ -219,6 +219,13 @@ def run_migrations(current_schema, db_connection):
migrator.add_column(SettingsModel._meta.table_name, 'group', pw.CharField(default='')),
)
if current_schema.version < 20:
_apply_schema_update(
current_schema,
20,
migrator.add_column(SettingsModel._meta.table_name, 'tooltip', pw.CharField(default='')),
)
def _apply_schema_update(current_schema, version_after, *operations):
with DB.atomic():

View file

@ -185,6 +185,7 @@ class SettingsModel(BaseModel):
str_value = pw.CharField(default='')
label = pw.CharField()
group = pw.CharField(default='') # Settings group name and label
tooltip = pw.CharField(default='') # optional tooltip for `checkbox` type
type = pw.CharField()
class Meta:

View file

@ -32,7 +32,7 @@ def get_misc_settings() -> List[Dict[str, str]]:
'value': False,
'type': 'checkbox',
'group': notifications,
'label': trans_late('settings', 'Also notify about successful background tasks'),
'label': trans_late('settings', 'Notify about successful background tasks'),
},
{
'key': 'autostart',
@ -40,6 +40,7 @@ def get_misc_settings() -> List[Dict[str, str]]:
'type': 'checkbox',
'group': startup,
'label': trans_late('settings', 'Automatically start Vorta at login'),
'tooltip': trans_late('settings', 'Add Vorta to the systems autostart list'),
},
{
'key': 'foreground',
@ -47,6 +48,7 @@ def get_misc_settings() -> List[Dict[str, str]]:
'type': 'checkbox',
'group': startup,
'label': trans_late('settings', 'Open main window on startup'),
'tooltip': trans_late('settings', 'Open main window when the application is launched'),
},
{
'key': 'get_srcpath_datasize',
@ -54,6 +56,7 @@ def get_misc_settings() -> List[Dict[str, str]]:
'type': 'checkbox',
'group': information,
'label': trans_late('settings', 'Get statistics of file/folder when added'),
'tooltip': trans_late('settings', 'When adding a new source, calculate its size and the number of files.'),
},
{
'key': 'use_system_keyring',
@ -64,6 +67,9 @@ def get_misc_settings() -> List[Dict[str, str]]:
'settings',
'Store repository passwords in system keychain, if available',
),
'tooltip': trans_late(
'settings', "Otherwise Vorta's configuration database stores the password in plaintext."
),
},
{
'key': 'override_mount_permissions',
@ -72,8 +78,9 @@ def get_misc_settings() -> List[Dict[str, str]]:
'group': security,
'label': trans_late(
'settings',
'Try to replace existing permissions when mounting an archive',
'Try to replace file permissions when mounting an archive',
),
'tooltip': trans_late('settings', 'Set owner to current user and umask to 0277'),
},
{
'key': 'previous_profile_id',

View file

@ -1,13 +1,15 @@
import logging
from PyQt5 import uic
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QCheckBox, QFormLayout, QLabel, QSizePolicy, QSpacerItem
from PyQt5.QtWidgets import QApplication, QCheckBox, QFormLayout, QHBoxLayout, QLabel, QSizePolicy, QSpacerItem
from vorta._version import __version__
from vorta.config import LOG_DIR
from vorta.i18n import translate
from vorta.store.models import BackupProfileMixin, SettingsModel
from vorta.store.settings import get_misc_settings
from vorta.utils import get_asset, search
from vorta.views.partials.tooltip_button import ToolTipButton
from vorta.views.utils import get_colored_icon
uifile = get_asset('UI/misctab.ui')
MiscTabUI, MiscTabBase = uic.loadUiType(uifile)
@ -29,10 +31,15 @@ def __init__(self, parent=None):
self.checkboxLayout.setSpacing(4)
self.checkboxLayout.setHorizontalSpacing(8)
self.checkboxLayout.setContentsMargins(0, 0, 0, 12)
self.checkboxLayout.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.FieldsStayAtSizeHint)
self.checkboxLayout.setFormAlignment(Qt.AlignmentFlag.AlignHCenter)
self.tooltip_buttons = []
self.populate()
# Connect to palette change
QApplication.instance().paletteChanged.connect(lambda p: self.set_icons())
def populate(self):
"""
Populate the misc tab with the settings widgets.
@ -45,6 +52,7 @@ def populate(self):
self.checkboxLayout.removeItem(child)
if child.widget():
child.widget().deleteLater()
self.tooltip_buttons = []
# dynamically add widgets for settings
misc_settings = get_misc_settings()
@ -78,16 +86,34 @@ def populate(self):
# create widget
cb = QCheckBox(translate('settings', setting.label))
cb.setToolTip(setting.tooltip)
cb.setCheckState(setting.value)
cb.setTristate(False)
cb.stateChanged.connect(lambda v, key=setting.key: self.save_setting(key, v))
tb = ToolTipButton()
tb.setToolTip(setting.tooltip)
cbl = QHBoxLayout()
cbl.addWidget(cb)
if setting.tooltip:
cbl.addWidget(tb)
cbl.addItem(QSpacerItem(0, 0, hPolicy=QSizePolicy.Policy.Expanding))
# add widget
self.checkboxLayout.setWidget(i, QFormLayout.ItemRole.FieldRole, cb)
self.checkboxLayout.setLayout(i, QFormLayout.ItemRole.FieldRole, cbl)
self.tooltip_buttons.append(tb)
# increase i
i += 1
self.set_icons()
def set_icons(self):
"""Set or update the icons in this view."""
for button in self.tooltip_buttons:
button.setIcon(get_colored_icon('help-about'))
def save_setting(self, key, new_value):
setting = SettingsModel.get(key=key)
setting.value = bool(new_value)

View file

@ -0,0 +1,127 @@
from typing import Optional
from PyQt5.QtCore import QCoreApplication, QEvent, QSize, Qt
from PyQt5.QtGui import QHelpEvent, QIcon, QMouseEvent, QPaintEvent
from PyQt5.QtWidgets import QSizePolicy, QStyle, QStylePainter, QToolTip, QWidget
class ToolTipButton(QWidget):
"""
A flat button showing a tooltip when the mouse moves over it.
The default icon is `help-about`.
Parameters
----------
icon : QIcon, optional
The icon to display, by default `help-about`
parent : QWidget, optional
The parent of this widget, by default None
"""
def __init__(self, icon: Optional[QIcon] = None, parent: Optional[QWidget] = None) -> None:
"""
Init.
"""
super().__init__(parent)
self.setCursor(Qt.CursorShape.WhatsThisCursor)
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
self.setMouseTracking(True)
self._icon = icon or QIcon()
def sizeHint(self) -> QSize:
"""
Get the recommended size for the widget.
Returns
-------
QSize
See Also
--------
https://doc.qt.io/qt-5/qwidget.html#sizeHint-prop
"""
size = self.style().pixelMetric(QStyle.PixelMetric.PM_ButtonIconSize)
return QSize(size, size)
def paintEvent(self, event: QPaintEvent) -> None:
"""
Repaint the widget on receiving a paint event.
A paint event is a request to repaint all or part of a widget.
It can happen for one of the following reasons:
- repaint() or update() was invoked,
- the widget was obscured and has now been uncovered, or
- many other reasons.
Many widgets can simply repaint their entire surface when asked to,
but some slow widgets need to optimize by painting only the
requested region: QPaintEvent::region().
This speed optimization does not change the result,
as painting is clipped to that region during event processing.
QListView and QTableView do this, for example.
Parameters
----------
event : QPaintEvent
The paint event
See Also
--------
https://doc.qt.io/qt-5/qwidget.html#paintEvent
"""
painter = QStylePainter(self)
if self._icon:
painter.drawPixmap(
event.rect(),
self._icon.pixmap(event.rect().size(), QIcon.Mode.Normal if self.isEnabled() else QIcon.Mode.Disabled),
)
painter.end()
def mouseMoveEvent(self, event: QMouseEvent) -> None:
"""
Process mouse move events for this widget.
If mouse tracking is switched off, mouse move events only occur if a
mouse button is pressed while the mouse is being moved.
If mouse tracking is switched on, mouse move events occur even
if no mouse button is pressed.
Parameters
----------
event : QMouseEvent
The corresponding mouse event.
See Also
--------
setMouseTracking
https://doc.qt.io/qt-5/qwidget.html#mouseMoveEvent
"""
super().mouseMoveEvent(event)
QToolTip.showText(event.globalPos(), self.toolTip(), self)
QCoreApplication.postEvent(self, QHelpEvent(QEvent.Type.ToolTip, event.pos(), event.globalPos()))
def setIcon(self, icon: QIcon):
"""
Set the icon displayed by the widget.
This triggers a repaint event.
Parameters
----------
icon : QIcon
The new icon.
"""
self._icon = icon
self.update()
def icon(self) -> QIcon:
"""
Get the icon displayed by the widget.
Returns
-------
QIcon
The current icon.
"""
return self._icon

View file

@ -17,7 +17,7 @@ def click_autostart():
item = tab.checkboxLayout.itemAt(x, QFormLayout.ItemRole.FieldRole)
if not item:
continue
checkbox = item.widget()
checkbox = item.itemAt(0).widget()
checkbox.__class__ = QCheckBox
if checkbox.text().startswith("Automatically"):
# Have to use pos to click checkbox correctly