Skip to content

Commit bee63b7

Browse files
authored
Merge pull request #2514 from ales-erjavec/widget-status-bar-buttons
[ENH] Widget status bar buttons
2 parents 53c1bf5 + 36d86ce commit bee63b7

File tree

9 files changed

+164
-64
lines changed

9 files changed

+164
-64
lines changed

Orange/canvas/scheme/widgetsscheme.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
import sip
2727

28-
from AnyQt.QtWidgets import QWidget, QShortcut, QLabel, QSizePolicy
28+
from AnyQt.QtWidgets import QWidget, QShortcut, QLabel, QSizePolicy, QAction
2929
from AnyQt.QtGui import QKeySequence, QWhatsThisClickedEvent
3030

3131
from AnyQt.QtCore import Qt, QObject, QCoreApplication, QTimer, QEvent
@@ -514,8 +514,11 @@ def create_widget_instance(self, node):
514514
node.set_progress(widget.progressBarValue)
515515

516516
# Install a help shortcut on the widget
517-
help_shortcut = QShortcut(QKeySequence("F1"), widget)
518-
help_shortcut.activated.connect(self.__on_help_request)
517+
help_action = widget.findChild(QAction, "action-help")
518+
if help_action is not None:
519+
help_action.setEnabled(True)
520+
help_action.setVisible(True)
521+
help_action.triggered.connect(self.__on_help_request)
519522

520523
# Up shortcut (activate/open parent)
521524
up_shortcut = QShortcut(

Orange/widgets/icons/chart.svg

Lines changed: 5 additions & 0 deletions
Loading

Orange/widgets/icons/help.svg

Lines changed: 4 additions & 0 deletions
Loading

Orange/widgets/icons/report.svg

Lines changed: 10 additions & 0 deletions
Loading

Orange/widgets/tests/test_widget.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
from unittest.mock import patch, MagicMock
55

6+
from AnyQt.QtWidgets import QAction
7+
68
from Orange.widgets.gui import OWComponent
79
from Orange.widgets.settings import Setting
810
from Orange.widgets.tests.base import WidgetTest
@@ -69,6 +71,12 @@ def test_widget_tests_do_not_use_stored_settings(self):
6971
widget2 = self.create_widget(MyWidget)
7072
self.assertEqual(widget2.field, 42)
7173

74+
def test_widget_help_action(self):
75+
widget = self.create_widget(MyWidget)
76+
help_action = widget.findChild(QAction, "action-help")
77+
help_action.setEnabled(True)
78+
help_action.setVisible(True)
79+
7280

7381
class WidgetMsgTestCase(WidgetTest):
7482

Orange/widgets/utils/buttons.py

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
from AnyQt.QtWidgets import QPushButton, QStyle, QStyleOptionButton
1+
from AnyQt.QtWidgets import (
2+
QPushButton, QAbstractButton, QFocusFrame, QStyle, QStylePainter,
3+
QStyleOptionButton
4+
)
5+
from AnyQt.QtGui import QPalette, QIcon
26
from AnyQt.QtCore import Qt, QSize
37

48

@@ -59,3 +63,68 @@ def sizeHint(self):
5963
style.sizeFromContents(QStyle.CT_PushButton, option,
6064
size, self))
6165
return sh
66+
67+
68+
class SimpleButton(QAbstractButton):
69+
"""
70+
A simple icon button widget.
71+
"""
72+
def __init__(self, parent=None, **kwargs):
73+
super().__init__(parent, **kwargs)
74+
self.__focusframe = None
75+
76+
def focusInEvent(self, event):
77+
# reimplemented
78+
event.accept()
79+
if self.__focusframe is None:
80+
self.__focusframe = QFocusFrame(self)
81+
self.__focusframe.setWidget(self)
82+
palette = self.palette()
83+
palette.setColor(QPalette.Foreground,
84+
palette.color(QPalette.Highlight))
85+
self.__focusframe.setPalette(palette)
86+
87+
def focusOutEvent(self, event):
88+
# reimplemented
89+
event.accept()
90+
if self.__focusframe is not None:
91+
self.__focusframe.hide()
92+
self.__focusframe.deleteLater()
93+
self.__focusframe = None
94+
95+
def sizeHint(self):
96+
# reimplemented
97+
self.ensurePolished()
98+
iconsize = self.iconSize()
99+
icon = self.icon()
100+
if not icon.isNull():
101+
iconsize = icon.actualSize(iconsize)
102+
return iconsize
103+
104+
def minimumSizeHint(self):
105+
# reimplemented
106+
return self.sizeHint()
107+
108+
def paintEvent(self, event):
109+
painter = QStylePainter(self)
110+
option = QStyleOptionButton()
111+
option.initFrom(self)
112+
option.text = ""
113+
option.icon = self.icon()
114+
option.iconSize = self.iconSize()
115+
option.features = QStyleOptionButton.Flat
116+
if self.isDown():
117+
option.state |= QStyle.State_Sunken
118+
painter.drawPrimitive(QStyle.PE_PanelButtonBevel, option)
119+
120+
if not option.icon.isNull():
121+
if option.state & QStyle.State_Active:
122+
mode = (QIcon.Normal if option.state & QStyle.State_MouseOver
123+
else QIcon.Active)
124+
else:
125+
mode = QIcon.Disabled
126+
if self.isChecked():
127+
state = QIcon.On
128+
else:
129+
state = QIcon.Off
130+
option.icon.paint(painter, option.rect, Qt.AlignCenter, mode, state)

Orange/widgets/utils/overlay.py

Lines changed: 3 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@
1414

1515
from AnyQt.QtWidgets import (
1616
QHBoxLayout, QPushButton, QLabel, QSizePolicy, QStyle, QAbstractButton,
17-
QStyleOptionButton, QStylePainter, QFocusFrame, QWidget, QStyleOption
17+
QWidget, QStyleOption
1818
)
1919
from AnyQt.QtGui import QIcon, QPixmap, QPainter
2020
from AnyQt.QtCore import Qt, QSize, QRect, QPoint, QEvent
2121
from AnyQt.QtCore import pyqtSignal as Signal, pyqtSlot as Slot
2222

23+
from .buttons import SimpleButton
24+
2325

2426
class OverlayWidget(QWidget):
2527
"""
@@ -198,60 +200,6 @@ def __on_destroyed(self):
198200
self.hide()
199201

200202

201-
class SimpleButton(QAbstractButton):
202-
"""
203-
A simple icon button widget.
204-
"""
205-
def __init__(self, parent=None, **kwargs):
206-
super().__init__(parent, **kwargs)
207-
self.__focusframe = None
208-
209-
def focusInEvent(self, event):
210-
# reimplemented
211-
event.accept()
212-
self.__focusframe = QFocusFrame(self)
213-
self.__focusframe.setWidget(self)
214-
215-
def focusOutEvent(self, event):
216-
# reimplemented
217-
event.accept()
218-
self.__focusframe.deleteLater()
219-
self.__focusframe = None
220-
221-
def sizeHint(self):
222-
# reimplemented
223-
self.ensurePolished()
224-
iconsize = self.iconSize()
225-
icon = self.icon()
226-
if not icon.isNull():
227-
iconsize = icon.actualSize(iconsize)
228-
return iconsize
229-
230-
def minimumSizeHint(self):
231-
# reimplemented
232-
return self.sizeHint()
233-
234-
def paintEvent(self, event):
235-
# reimplemented
236-
painter = QStylePainter(self)
237-
option = QStyleOptionButton()
238-
option.initFrom(self)
239-
option.icon = self.icon()
240-
option.iconSize = self.iconSize()
241-
242-
icon = self.icon()
243-
244-
if not icon.isNull():
245-
if option.state & QStyle.State_Active:
246-
mode = (QIcon.Normal if option.state & QStyle.State_MouseOver
247-
else QIcon.Active)
248-
else:
249-
mode = QIcon.Disabled
250-
pixmap = icon.pixmap(option.iconSize, mode, )
251-
252-
painter.drawItemPixmap(option.rect, Qt.AlignCenter, pixmap)
253-
254-
255203
class MessageWidget(QWidget):
256204
"""
257205
A widget displaying a simple message to the user.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
from AnyQt.QtCore import Qt
2+
from AnyQt.QtGui import QFocusEvent
3+
from AnyQt.QtWidgets import QStyle, QApplication
4+
from Orange.widgets.tests.base import GuiTest
5+
from Orange.widgets.utils import buttons
6+
7+
8+
class SimpleButtonTest(GuiTest):
9+
def test_button(self):
10+
# Run through various state change and drawing code for coverage
11+
b = buttons.SimpleButton()
12+
b.setIcon(b.style().standardIcon(QStyle.SP_ComputerIcon))
13+
14+
QApplication.sendEvent(b, QFocusEvent(QFocusEvent.FocusIn))
15+
QApplication.sendEvent(b, QFocusEvent(QFocusEvent.FocusOut))
16+
17+
b.grab()
18+
b.setDown(True)
19+
b.grab()
20+
b.setCheckable(True)
21+
b.setChecked(True)
22+
b.grab()

Orange/widgets/widget.py

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from AnyQt.QtWidgets import (
1010
QWidget, QDialog, QVBoxLayout, QSizePolicy, QApplication, QStyle,
1111
QShortcut, QSplitter, QSplitterHandle, QPushButton, QStatusBar,
12-
QProgressBar
12+
QProgressBar, QAction
1313
)
1414
from AnyQt.QtCore import (
1515
Qt, QByteArray, QSettings, QUrl, pyqtSignal as Signal
@@ -33,6 +33,7 @@
3333
from Orange.widgets.utils.signals import \
3434
WidgetSignalsMixin, Input, Output, AttributeList
3535
from Orange.widgets.utils.overlay import MessageOverlayWidget, OverlayWidget
36+
from Orange.widgets.utils.buttons import SimpleButton
3637

3738
# Msg is imported and renamed, so widgets can import it from this module rather
3839
# than the one with the mixin (Orange.widgets.utils.messages). Assignment is
@@ -170,6 +171,7 @@ class OWWidget(QDialog, OWComponent, Report, ProgressBarMixin,
170171
contextOpened = Signal()
171172
contextClosed = Signal()
172173

174+
# pylint: disable=protected-access
173175
def __new__(cls, *args, captionTitle=None, **kwargs):
174176
self = super().__new__(cls, None, cls.get_flags())
175177
QDialog.__init__(self, None, self.get_flags())
@@ -208,6 +210,12 @@ def __new__(cls, *args, captionTitle=None, **kwargs):
208210
self.__msgwidget = None
209211
self.__msgchoice = 0
210212

213+
self.__help_action = QAction(
214+
"Help", self, objectName="action-help", toolTip="Show help",
215+
enabled=False, visible=False, shortcut=QKeySequence(Qt.Key_F1)
216+
)
217+
self.addAction(self.__help_action)
218+
211219
self.left_side = None
212220
self.controlArea = self.mainArea = self.buttonsArea = None
213221
self.splitter = None
@@ -219,6 +227,7 @@ def __new__(cls, *args, captionTitle=None, **kwargs):
219227

220228
sc = QShortcut(QKeySequence.Copy, self)
221229
sc.activated.connect(self.copy_to_clipboard)
230+
222231
if self.controlArea is not None:
223232
# Otherwise, the first control has focus
224233
self.controlArea.setFocus(Qt.ActiveWindowFocusReason)
@@ -319,10 +328,6 @@ def _insert_buttons_area(self):
319328
self.buttonsArea = gui.widgetBox(
320329
self.left_side, addSpace=0, spacing=9,
321330
orientation=self.buttons_area_orientation)
322-
if self.graphButton is not None:
323-
self.buttonsArea.layout().addWidget(self.graphButton)
324-
if self.report_button is not None:
325-
self.buttonsArea.layout().addWidget(self.report_button)
326331

327332
def _insert_main_area(self):
328333
self.mainArea = gui.vBox(
@@ -380,6 +385,32 @@ def set_basic_layout(self):
380385
sb.setSizeGripEnabled(self.resizing_enabled)
381386
c.layout().addWidget(sb)
382387

388+
help = self.__help_action
389+
help_button = SimpleButton(
390+
icon=QIcon(gui.resource_filename("icons/help.svg")),
391+
toolTip="Show widget help", visible=help.isVisible(),
392+
)
393+
@help.changed.connect
394+
def _():
395+
help_button.setVisible(help.isVisible())
396+
help_button.setEnabled(help.isEnabled())
397+
help_button.clicked.connect(help.trigger)
398+
sb.addWidget(help_button)
399+
400+
if self.graph_name is not None:
401+
b = SimpleButton(
402+
icon=QIcon(gui.resource_filename("icons/chart.svg")),
403+
toolTip="Save Image",
404+
)
405+
b.clicked.connect(self.save_graph)
406+
sb.addWidget(b)
407+
if hasattr(self, "send_report"):
408+
b = SimpleButton(
409+
icon=QIcon(gui.resource_filename("icons/report.svg")),
410+
toolTip="Report"
411+
)
412+
b.clicked.connect(self.show_report)
413+
sb.addWidget(b)
383414
self.message_bar = MessagesWidget(self)
384415
self.message_bar.setSizePolicy(QSizePolicy.Preferred,
385416
QSizePolicy.Preferred)

0 commit comments

Comments
 (0)