Skip to content

Commit

Permalink
Add tooltips to settings. (#1521)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
real-yfprojects and real-yfprojects authored Feb 25, 2023
1 parent bcc126b commit 618a1fe
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 6 deletions.
12 changes: 12 additions & 0 deletions src/vorta/assets/icons/help-about.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion src/vorta/store/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
)
from .settings import get_misc_settings

SCHEMA_VERSION = 19
SCHEMA_VERSION = 20


@signals.post_save(sender=SettingsModel)
Expand Down Expand Up @@ -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()
7 changes: 7 additions & 0 deletions src/vorta/store/migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
1 change: 1 addition & 0 deletions src/vorta/store/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
11 changes: 9 additions & 2 deletions src/vorta/store/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,31 @@ 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',
'value': False,
'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',
'value': True,
'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',
'value': True,
'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',
Expand All @@ -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',
Expand All @@ -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',
Expand Down
30 changes: 28 additions & 2 deletions src/vorta/views/misc_tab.py
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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.
Expand All @@ -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()
Expand Down Expand Up @@ -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)
Expand Down
127 changes: 127 additions & 0 deletions src/vorta/views/partials/tooltip_button.py
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion tests/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 618a1fe

Please sign in to comment.