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:
parent
bcc126b634
commit
618a1fe278
8 changed files with 188 additions and 6 deletions
12
src/vorta/assets/icons/help-about.svg
Normal file
12
src/vorta/assets/icons/help-about.svg
Normal 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 |
|
@ -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()
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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)
|
||||
|
|
127
src/vorta/views/partials/tooltip_button.py
Normal file
127
src/vorta/views/partials/tooltip_button.py
Normal 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
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue