From 7dcd8d8dc85144a816698dd36701d52259ad9de7 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Fri, 29 Jul 2022 19:12:48 +0200 Subject: [PATCH] Qt receive_tab: fix receive_tabs widget on macOS QTabWidget with "West" tab pos and horizontal text looks completely broken on macOS (despite looking good on e.g. Ubuntu GNOME and Windows). The alternative here looks ok on all three OSes. fixes https://github.com/spesmilo/electrum/issues/7908 --- electrum/gui/qt/receive_tab.py | 4 +- electrum/gui/qt/util.py | 106 ++++++++++++++++++++------------- 2 files changed, 66 insertions(+), 44 deletions(-) diff --git a/electrum/gui/qt/receive_tab.py b/electrum/gui/qt/receive_tab.py index 56b5802d7..5e74ff0ac 100644 --- a/electrum/gui/qt/receive_tab.py +++ b/electrum/gui/qt/receive_tab.py @@ -155,7 +155,7 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger): from .util import VTabWidget self.receive_tabs = VTabWidget() - self.receive_tabs.setMinimumHeight(ReceiveTabWidget.min_size.height() + 4) # for margins + #self.receive_tabs.setMinimumHeight(ReceiveTabWidget.min_size.height() + 4) # for margins self.receive_tabs.addTab(self.receive_URI_widget, read_QIcon("link.png"), _('URI')) self.receive_tabs.addTab(self.receive_address_widget, read_QIcon("bitcoin.png"), _('Address')) self.receive_tabs.addTab(self.receive_lightning_widget, read_QIcon("lightning.png"), _('Lightning')) @@ -392,7 +392,7 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger): class ReceiveTabWidget(QWidget): min_size = QSize(200, 200) - def __init__(self, receive_tab: 'ReceiveTab', textedit, qr, help_widget): + def __init__(self, receive_tab: 'ReceiveTab', textedit: QWidget, qr: QWidget, help_widget: QWidget): self.textedit = textedit self.qr = qr self.help_widget = help_widget diff --git a/electrum/gui/qt/util.py b/electrum/gui/qt/util.py index 313a74235..021a8eb42 100644 --- a/electrum/gui/qt/util.py +++ b/electrum/gui/qt/util.py @@ -12,6 +12,7 @@ from functools import partial, lru_cache, wraps from typing import (NamedTuple, Callable, Optional, TYPE_CHECKING, Union, List, Dict, Any, Sequence, Iterable, Tuple) +from PyQt5 import QtWidgets, QtCore from PyQt5.QtGui import (QFont, QColor, QCursor, QPixmap, QStandardItem, QImage, QPalette, QIcon, QFontMetrics, QShowEvent, QPainter, QHelpEvent) from PyQt5.QtCore import (Qt, QPersistentModelIndex, QModelIndex, pyqtSignal, @@ -24,7 +25,7 @@ from PyQt5.QtWidgets import (QPushButton, QLabel, QMessageBox, QHBoxLayout, QFileDialog, QWidget, QToolButton, QTreeView, QPlainTextEdit, QHeaderView, QApplication, QToolTip, QTreeWidget, QStyledItemDelegate, QMenu, QStyleOptionViewItem, QLayout, QLayoutItem, QAbstractButton, - QGraphicsEffect, QGraphicsScene, QGraphicsPixmapItem) + QGraphicsEffect, QGraphicsScene, QGraphicsPixmapItem, QSizePolicy) from electrum.i18n import _, languages from electrum.util import FileImportFailed, FileExportFailed, make_aiohttp_session, resource_path @@ -1570,54 +1571,75 @@ class ImageGraphicsEffect(QObject): return result -# vertical tabs -# from https://stackoverflow.com/questions/51230544/pyqt5-how-to-set-tabwidget-west-but-keep-the-text-horizontal -from PyQt5 import QtWidgets, QtCore - -class VTabBar(QtWidgets.QTabBar): - - def tabSizeHint(self, index): - s = QtWidgets.QTabBar.tabSizeHint(self, index) - s.transpose() - return s - - def paintEvent(self, event): - painter = QtWidgets.QStylePainter(self) - opt = QtWidgets.QStyleOptionTab() - - for i in range(self.count()): - self.initStyleOption(opt, i) - painter.drawControl(QtWidgets.QStyle.CE_TabBarTabShape, opt) - painter.save() - - s = opt.rect.size() - s.transpose() - r = QtCore.QRect(QtCore.QPoint(), s) - r.moveCenter(opt.rect.center()) - opt.rect = r - - c = self.tabRect(i).center() - painter.translate(c) - painter.rotate(90) - painter.translate(-c) - painter.drawControl(QtWidgets.QStyle.CE_TabBarTabLabel, opt); - painter.restore() - - -class VTabWidget(QtWidgets.QTabWidget): - def __init__(self, *args, **kwargs): - QtWidgets.QTabWidget.__init__(self, *args, **kwargs) - self.setTabBar(VTabBar(self)) - self.setTabPosition(QtWidgets.QTabWidget.West) - +class SquareTabWidget(QtWidgets.QTabWidget): def resizeEvent(self, e): # keep square aspect ratio when resized size = e.size() - w = self.tabBar().width() + size.height() + w = size.height() + w += self.tabBar().width() if self.tabBar().isVisible() else 0 self.setFixedWidth(w) return super().resizeEvent(e) +class VTabWidget(QWidget): + """QtWidgets.QTabWidget alternative with "West" tab positions and horizontal tab-text.""" + def __init__(self): + QWidget.__init__(self) + + hbox = QHBoxLayout() + self.setLayout(hbox) + hbox.setContentsMargins(0, 0, 0, 0) + hbox.setSpacing(0) + + self._tabs_vbox = tabs_vbox = QVBoxLayout() + self._tab_btns = [] # type: List[QPushButton] + tabs_vbox.setContentsMargins(0, 0, 0, 0) + tabs_vbox.setSpacing(0) + + _tabs_vbox_outer_w = QWidget() + _tabs_vbox_outer = QVBoxLayout() + _tabs_vbox_outer_w.setLayout(_tabs_vbox_outer) + _tabs_vbox_outer_w.setSizePolicy(QSizePolicy(QSizePolicy.Maximum, _tabs_vbox_outer_w.sizePolicy().verticalPolicy())) + _tabs_vbox_outer.setContentsMargins(0, 0, 0, 0) + _tabs_vbox_outer.setSpacing(0) + _tabs_vbox_outer.addLayout(tabs_vbox) + _tabs_vbox_outer.addStretch(1) + + self.content_w = content_w = SquareTabWidget() + content_w.setStyleSheet("QTabBar::tab {height: 0px;}") # TODO Linux/mac: still insufficient to rm top padding... + content_w.tabBar().hide() + + hbox.addStretch(1) + hbox.addWidget(_tabs_vbox_outer_w) + hbox.addWidget(content_w) + + self.currentChanged = content_w.currentChanged + self.currentIndex = content_w.currentIndex + + def addTab(self, widget: QWidget, icon: QIcon, text: str): + btn = QPushButton(icon, text) + btn.setCheckable(True) + btn.setSizePolicy(QSizePolicy.Preferred, btn.sizePolicy().verticalPolicy()) + + def on_btn_click(): + self.content_w.setCurrentIndex(idx) + for btn2 in self._tab_btns: + if btn2 != btn: + btn2.setChecked(False) + btn.clicked.connect(on_btn_click) + idx = len(self._tab_btns) + self._tab_btns.append(btn) + + self._tabs_vbox.addWidget(btn) + self.content_w.addTab(widget, "") + + def setTabIcon(self, idx: int, icon: QIcon): + self._tab_btns[idx].setIcon(icon) + + def setCurrentIndex(self, idx: int): + self._tab_btns[idx].click() + + class QtEventListener(EventListener): qt_callback_signal = QtCore.pyqtSignal(tuple)