GithubHelp home page GithubHelp logo

pyapp-kit / superqt Goto Github PK

View Code? Open in Web Editor NEW
188.0 12.0 34.0 2.58 MB

Missing widgets and components for Qt-python

Home Page: https://pyapp-kit.github.io/superqt/

License: BSD 3-Clause "New" or "Revised" License

Python 100.00%
qt widgets components pyside pyqt gui python

superqt's Introduction

tiny superqt!

License PyPI Python Version Test codecov

"missing" widgets and components for PyQt/PySide

This repository aims to provide high-quality community-contributed Qt widgets and components for PyQt & PySide that are not provided in the native QtWidgets module.

Components are tested on:

  • macOS, Windows, & Linux
  • Python 3.8 and above
  • PyQt5 (5.11 and above) & PyQt6
  • PySide2 (5.11 and above) & PySide6

Documentation

Documentation is available at https://pyapp-kit.github.io/superqt/

Widgets

superqt provides a variety of widgets that are not included in the native QtWidgets module, including multihandle (range) sliders, comboboxes, and more.

See the widgets documentation for a full list of widgets.

range sliders

range sliders

range sliders

Utilities

superqt includes a number of utilities for working with Qt, including:

See the utilities documentation for a full list of utilities.

Contributing

We welcome contributions!

Please see the Contributing Guide

superqt's People

Contributors

ahmetcansolak avatar andy-sweet avatar czaki avatar dalthviz avatar dependabot[bot] avatar haesleinhuepf avatar kianmeng avatar kne42 avatar minmotech avatar mosgeo avatar mstabrin avatar ppwadhwa avatar pre-commit-ci[bot] avatar psobolewskiphd avatar sfhbarnett avatar tlambert03 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

superqt's Issues

Error with sliders when using Pyside6 on Mac M1 Ventura

Describe the bug
I can not see the left handle of any range slider at all when i use this widget in a gui created using Pyside6 and qt designer. I have checked the style sheet of the empty widget whose place the slider took. There seems to be no issues there as well. The slider example runs perfectly when copied and pasted. But if i import QtCore and QtWidgets using Pyside6 then, i have issues instantiating the slider. But it is not a big issue. I could get over that using pyside as well. But the main issue that persists is why the left handle of the slider disappears.

Screenshot 2023-05-11 at 2 18 33 AM

Desktop (please complete the following information):

  • OS version: 13.3.1 (a) (22E772610a)
  • Qt Backend :Pyside6
  • Python version:3.10.8

Setting slider tracking and SliderRelease signal not working

Describe the bug
I love the widgets! I've been waiting for someone to properly make these for ages, much better than my own homebrew attempts.

On to the issue(s):

  • I have a QLabeledRangeSlider with two handles.
  • I expect to be able to access the sliderReleased() signal, however it does not appear to work.
  • In its I place I can use valueChanged() - but, because tracking is on by default, it updates continuously as I move a slider.
  • I have attempted to turn tracking off but that seems to have no effect: ".setTracking(False)".
  • I want to use these signals to get the final resting values of the slider when the mouse releases a handle.

MWE

from PyQt5 import QtCore,QtWidgets
from superqt import QLabeledRangeSlider
import sys

class MainWindow(QtWidgets.QMainWindow):
	def __init__(self):
		super().__init__()
		self.setWindowTitle('MyWindow')

		self.slider = QLabeledRangeSlider(QtCore.Qt.Horizontal)
		self.slider.setTracking(False)
		self.slider.valueChanged.connect(self.valueChangedCallback)
		self.slider.sliderReleased.connect(self.sliderReleasedCallback)

		self.setCentralWidget(self.slider)

	def valueChangedCallback(self, value):
		print(f"valueChangedCallback: {value}")

	def sliderReleasedCallback(self, value):
		print(f"sliderReleasedCallback: {value}")

if __name__ == "__main__":
	app = QtWidgets.QApplication(sys.argv)
	window = MainWindow()
	window.show()
	app.installEventFilter(window)
	sys.exit(app.exec_())

Desktop (please complete the following information):

  • macOS 10.15.7
  • PyQt5=5.15.4
  • Python 3.8.13

fonticon deprecation warning

Traceback (most recent call last):
[30](https://github.com/tlambert03/app-model/runs/7313156399?check_suite_focus=true#step:6:31)
  File "/Users/runner/hostedtoolcache/Python/3.9.13/x64/lib/python3.9/site-packages/superqt/fonticon/_qfont_icon.py", line 246, in pixmap
[31](https://github.com/tlambert03/app-model/runs/7313156399?check_suite_focus=true#step:6:32)
    pm = QPixmapCache.find(pmckey) if pmckey else None
[32](https://github.com/tlambert03/app-model/runs/7313156399?check_suite_focus=true#step:6:33)
DeprecationWarning: QPixmapCache.find(const QString & key) is deprecated

pyside2 (but not pyqt5, and apparently not pyside6 or pyqt6)

Older version typing-extensions causing trouble with superqt

Describe the bug
Older version of typing-extensions causing trouble with superqt during import time. I am happy to contribute a fix upon a decision together about how to address this(pinning typing-extensions version or something else?).

To Reproduce
Install tensorflow==2.6.0(this installs typing-extensions==3.7.4.3), install superqt(or napari), and import superqt(or napari).

Expected behavior
superqt doesn't cause trouble with imports in an ideal world. With latest typing-extensions(or tensorflow==2.7.0) there is no problem.

Screenshots

superqt/utils/_qthreading.py", line 23, in <module>
    from typing_extensions import Literal, ParamSpec
ImportError: cannot import name 'ParamSpec' from 'typing_extensions' 

Desktop (please complete the following information):

  • OS with version [e.g macOS 10.15.7] OSX Mojave, Linux 18.04
  • Qt Backend [e.g PyQt5, PySide2] PYQT5
  • Python version Python3.9

Fonticon color matching theme

I try to use superqt.fonticon in napari widget. I observe that icons are dark when enabled. It does not look best when selecting a dark theme.

Is there a way to such an icon automatically match the text color for the current qss? Or does the QSS for the theme need to be updated to handle icons?

Slider labels clipping in high resolution screens

Describe the bug
The labels of the range sliders are clipped when displayed on high resolution screens. You can see in the snapshots that even at 100% scaling in the Surface Book 3 (3240x2160 recommended resolution), it is clipping the labels. The issue is even worse at 200% scaling (which is the recommended scaling for this screen).

To Reproduce
Steps to reproduce the behavior:

  1. Create a napari dock widget with a QVBoxLayout
  2. Add a QLabeledRangeSlider
  3. Add the widget to napari
  4. Labels are clipped as in the picture example

A simple example code

self.layout = QVBoxLayout()
histogram_slider = QLabeledRangeSlider()
histogram_slider.setRange(0,255)
histogram_slider.setOrientation(Qt.Orientation.Horizontal)
self.layout.addWidget(histogram_slider)

Expected behavior
Labels are automatically scaled to the screen and are not clipped.

Screenshots
Slider at recommended scaling (200%)
200_percent_scaling_recommended

Sliders at not-recommended scaling (100%)
100_percent_scaling

Desktop (please complete the following information):

  • OS with version [e.g Win64 21H2]
  • Qt Backend [PyQt5]
  • Python 3.9.7 64bit

Dependency version conflict with noise2void/keras~=2.3/tensorflow~=2.5

Describe the bug
Hi @tlambert03 ,

I'm working on a napari-plugin for noise2void, which is based on a slightly outdated version of tensorflow. It is compatible with tensorflow versions about 2.5 which is just about 1 year old.

When installing it together with recent napari, I'm receiving a dependency error because of superqt. I'm wondering if it would be possible to relax the constraint typing-extensions>=3.10.0.0 to something more compatible to typing-extensions~=3.7.4:

https://github.com/napari/superqt/blob/a3b0f1b1150b055ab25e1b462293368e68255f28/setup.cfg#L41

To Reproduce
In an empty environment

pip install superqt==0.3.3 tensorflow==2.5

causes this error:

ERROR: Cannot install superqt==0.3.3 and tensorflow==2.5.0 because these package versions have conflicting dependencies.

The conflict is caused by:
    superqt 0.3.3 depends on typing-extensions>=3.10.0.0
    tensorflow 2.5.0 depends on typing-extensions~=3.7.4

See also:

A bit more context:

https://github.com/juglab/n2v/blob/11669b7e09633f007c5920feb713fc955dbe4da8/setup.py#L49

https://github.com/keras-team/keras/blob/b5cb82c689eac0e50522be9d2f55093dadfba24c/setup.py#L35

https://github.com/tensorflow/tensorflow/blob/a4dfb8d1a71385bd6d122e4f27f86dcebb96712d/tensorflow/tools/pip_package/setup.py#L93

Thanks!

Best,
Robert

Sliders are not presented when the screen size is small

Describe the bug
The range sliders are not displayed sometimes and sometimes they do.

That is really a matter of proper width and height . I am not sure..

To Reproduce
This is not minimal, I admit.

pip install --no-binary :all: git+https://github.com/eyalk11/compare-my-stocks
python -m compare_my_stocks 

Try on big screen , and on small, you wont.
Also, I tried resizing the window to smaller size(can't repaint), and increasing it and they disappeared.

Given the same code, on a 21' screen it worked , and on 14' it didn't.

Screenshots
Screenshots and GIFS are much appreciated when reporting visual bugs.
image

Following resize

image

Desktop (please complete the following information):

  • OS with version Windows 10
  • Qt Backend PySide6.2.0 (last time I checked PySide 6.2.1.1 failed )
  • Python version 3.9.6

[Proposal] QCheckComboBox

I propose the inclusion of a custom widget for check combobox. This is useful in some scenarios (e.g., select multiple samples). If there is interest, I will clean up my current code, add tests and create an initial pull request for it.

I think it deserves a place in superqt as the default combobox:

  • has a relatively awkward (non-pythonic) way of adding checkable item. It needs multiple lines and you cannot use the additem function.
  • Does not change the checked states of the items when clicked
  • Automatically close when you select any item (to change the checked status).
  • Has the last selected item as a title.

The above can be fixed by overriding some of the functions in combobox in qt. A good starting point is https://stackoverflow.com/questions/47575880/qcombobox-set-title-text-regardless-of-items. There is still things to add to it to make it more user friendly (e.g., additem override, signal for item check state change, ...).

Screenshots
The top one is the default combobox. The bottom one is one possible implementation of CheckComboBox.

python3 9_j59ge6Raij

Dragging the bar on range sliders causes a crash with PyQt6

Describe the bug
Dragging the bar (as opposed to the handles at the end) causes a crash with PyQt6 (and not PyQt5) with the following message:

  File "/Users/sbarnett/opt/anaconda3/envs/PyV2/lib/python3.9/site-packages/superqt/sliders/_generic_range_slider.py", line 149, in mouseMoveEvent
    delta = self._clickOffset - self._pixelPosToRangeValue(self._pick(ev.pos()))
AttributeError: 'QMouseEvent' object has no attribute 'pos'

Seemingly this is the result of a change in the Qt API https://stackoverflow.com/questions/67496362/qmouseevent-object-has-no-attribute-pos

A fix is to change ev.pos() to ev.position() on line 149 in _generic_range_slider.py. I'd do this and make a pull request but this breaks backwards compatibility with PyQt5 and I don't know how to maintain that (or if there's a way).

To Reproduce
Run the labeled.py example script with PyQt6 and click and drag the bar of a slider

Expected behavior
Slider bar drags

Screenshots
Screenshots and GIFS are much appreciated when reporting visual bugs.

Desktop (please complete the following information):

  • Mac OS 12.4
  • PyQT 6
  • Python 3.10

With PyQt6 & QRangeSlider "Must construct a QApplication before a QWidget"

With PyQt6, Python core dumps "QWidget: Must construct a QApplication before a QWidget" when trying to use QRangeSlider. This doesn't happen with PyQt5.


import sys
from PyQt6 import QtWidgets
from superqt import QRangeSlider

class Main(QtWidgets.QMainWindow):
   def __init__(self):
      super().__init__()

      slider = QRangeSlider()

app = QtWidgets.QApplication(sys.argv)
mainwindow = Main()
mainwindow.show()
sys.exit(app.exec())

QWidget: Must construct a QApplication before a QWidget

bug: __version__ missing after move to hatch

@Czaki noted that the init needs to be updated with

from importlib.metadata import PackageNotFoundError, version

try:
    __version__ = version("superqt")
except PackageNotFoundError:  # pragma: no cover
    __version__ = "uninstalled"

Nested Workers are Failing Silently

๐Ÿ› Bug

If you create a worker and then that worker creates another worker, the function isn't called and it fails silently. I'm not sure if nested workers is supposed to be supported or not, but it shouldn't be failing silently either way.

To Reproduce

Steps to reproduce the behavior:

  1. Create a worker
  2. Create another worker from that worker
  3. Nested worker never gets called and there is no exception raised
import napari
from napari.qt.threading import create_worker
from qtpy.QtWidgets import QWidget, QVBoxLayout, QPushButton


class TestWidget(QWidget):
    def __init__(self):
        super().__init__()
        # setup simple GUI elements
        layout = QVBoxLayout()
        self.test1_btn = QPushButton("No Workers", self)
        self.test1_btn.clicked.connect(self.test1)
        layout.addWidget(self.test1_btn)
        self.test2_btn = QPushButton("1 Worker", self)
        self.test2_btn.clicked.connect(self.test2)
        layout.addWidget(self.test2_btn)
        self.test3_btn = QPushButton("2 Nested Workers", self)
        self.test3_btn.clicked.connect(self.test3)
        layout.addWidget(self.test3_btn)
        # activate layout
        self.setLayout(layout)

    def test1(self):
        print("hello from test1")

    def test2(self):
        worker = create_worker(self._test2)
        worker.start()

    def _test2(self):
        print("hello from test2")

    def test3(self):
        worker = create_worker(self._test3a)
        worker.start()

    def _test3a(self):
        worker = create_worker(self._test3b)
        worker.start()

    def _test3b(self):
        print("hello from test3")


viewer = napari.Viewer(title="test threading")
test_widget = TestWidget()
viewer.window.add_dock_widget(test_widget, name="test widget", area="right")

napari.run()

Expected behavior

I expected to to be able to create a worker from within another worker. Ideally, any functions that connect on callback would be executed in the GUI thread, since the original worker may be finished with its task and needs to be made available in the worker pool. Also, it would be nice to have an "easy" path back to the GUI thread in this case. If nested workers isn't supported, then I expected calling start on the nested worker would throw an exception.

Environment

  • Please copy and paste the information at napari info option in help menubar here:
    napari: 0.4.13
    Platform: Linux-4.18.0-305.el8.x86_64-x86_64-with-glibc2.28
    Python: 3.9.7 (default, Sep 16 2021, 13:09:58) [GCC 7.5.0]
    Qt: 5.15.2
    PyQt5: 5.15.6
    NumPy: 1.22.1
    SciPy: 1.7.3
    Dask: 2021.12.0
    VisPy: 0.9.4

OpenGL:

  • GL version: 4.6.0 NVIDIA 460.80
  • MAX_TEXTURE_SIZE: 32768

Screens:

  • screen 1: resolution 3840x2160, scale 1.0

Plugins:

  • animation: 0.0.2

  • napari-ome-zarr: 0.3.2

  • scikit-image: 0.4.13

  • svg: 0.1.6

  • Any other relevant information:

Additional context

Focus events getting lost?

Hey, I need something to update when a widget looses focus and I can do that with something simple like

class CustomRangeSlider(QtWidgets.QSlider):
    def focusOutEvent(self, event):
        # Call the base class focusOutEvent method
        super().focusOutEvent(event)
        # Handle the focus loss here
        print("lost focus")

But if I try that with any superqt slider it looks like this signal is never sent? Or is superseded or intercepted by something?

I tried setting the Focus Policy using
rangeSlider.setFocusPolicy(Qt.FocusPolicy.ClickFocus)

and tried looking at events by using

class QLabeledRangeSlider_(QLabeledRangeSlider):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
    def event(self, ev: QEvent) -> bool:
        print("_"*20)
        print(f"{ev.type()}")
        print("_"*20)
        if ev.type() in [QEvent.Type.FocusIn, QEvent.Type.FocusOut]:
            print("Focused in/out")
        if ev.type() == QEvent.Type.WindowActivate:
            self.update()
        elif ev.type() in (QEvent.Type.HoverEnter, QEvent.Type.HoverMove):
            self._updateHoverControl(self._event_position(ev))
        elif ev.type() == QEvent.Type.HoverLeave:
            self._hoverControl = QtWidgets.QStyle.SubControl.SC_None
            lastHoverRect, self._hoverRect = self._hoverRect, QRect()
            self.update(lastHoverRect)
        return super().event(ev)

But I just don't understand what is going on or why the focus event wouldnt be showing up anywhere.
I'm really lost here if anyone could point me in the right direction I would really appreciate it.

Bug: signal type overrides broken on PySide6.2.1.1

Describe the bug
haven't figured out what yet... but it's all of the failures in this test

________________________________________________________________________________
Traceback (most recent call last):
  File "/Users/talley/Dropbox (HMS)/Python/forks/superqt/src/superqt/sliders/_labeled.py", line 313, in _on_value_changed
    if len(v) != len(self._handle_labels):
TypeError: object of type 'int' has no len()

that's in the RangeSlider, where value should be a tuple... so i wonder whether class inheritance has somehow been slightly altered

Python 3.10 TypeError on rangeChanged for QLabeledRangeSlider

There seems to be some kind of type mismatch in expected signal values in the rangeChanged with Python 3.10. When loading a UI with a QLabeledRangeSlider, Python exits with code 134 and the message 'TypeError: rangeChanged(self, int, int).emit(): argument 1 has unexpected type 'float''

Steps to reproduce the behavior:

  1. Create a python 3.10 environment
  2. Import PyQT5 and superqt
  3. Create a UI with a QLabeledRangeSlider

Desktop (please complete the following information):

  • Fedora 36 KDE
  • PyQT5
  • Python 3.10.6

sliderReleased event not firing with QLabeledSlider

Describe the bug
QSlider has the event sliderReleased that fires when releasing the slider.
This currently does not work with QLabeledSlider.

To Reproduce
Not working with QLabeledSlider:

import sys
from PyQt5.QtWidgets import QApplication, QHBoxLayout, QWidget
from superqt.sliders import QLabeledSlider

if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = QWidget()

    def test_print():
        print("slider changed")

    layout = QHBoxLayout()

    slider = QLabeledSlider()
    slider.setRange(0, 255)
    slider.setPageStep(1)
    slider.sliderReleased.connect(test_print)

    layout.addWidget(slider)
    w.setLayout(layout)
    w.show()
    sys.exit(app.exec_())

Working with QSlider:

import sys
from PyQt5.QtWidgets import QApplication, QHBoxLayout, QSlider, QWidget

if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = QWidget()

    def test_print():
        print("slider changed")

    layout = QHBoxLayout()

    slider = QSlider()
    slider.setRange(0, 255)
    slider.setPageStep(1)
    slider.sliderReleased.connect(test_print)

    layout.addWidget(slider)
    w.setLayout(layout)
    w.show()
    sys.exit(app.exec_())

Expected behavior
QLabeledSlider should behave the same as QSlider

Screenshots
Screenshots and GIFS are much appreciated when reporting visual bugs.

Desktop (please complete the following information):

  • OS with version: archlinux
  • Qt Backend: pyqt5
  • Python version: 3.10

Feature: symmetric range slider adjustment

via conversation with @sofroniewn:
The range slider currently lets you "symmetrically" adjust (around some center point). by option-scrolling. but that doesn't work great for touchpad scrolling. Perhaps an option-drag feature to drag symettrically?

qtrangeslider doesn't respond to drag properly on macOS 12 with Qt5.15

Describe the bug
Qtrangeslider is not behaving properly. Dragging one of the handles causes the other handle to disappear (It looks like the slider thinks I have grabbed the top slider when I grab the bottom one). I've attached a video of the behaviour.

To Reproduce
Steps to reproduce the behavior:
The code below can reproduce the behaviour on my computer

    import sys
    from PyQt5 import QtCore, QtWidgets
    from superqt import QRangeSlider
    
    
    class MainWindow(QtWidgets.QMainWindow):
    
        def __init__(self, *args, **kwargs):
            super(MainWindow, self).__init__(*args, **kwargs)
    
            vlayout2 = QtWidgets.QHBoxLayout()
            self.qtr = QRangeSlider(QtCore.Qt.Orientation.Vertical)
            vlayout2.addWidget(self.qtr)
            widget = QtWidgets.QWidget()
            widget.setLayout(vlayout2)
            self.setCentralWidget(widget)
    
    
    def main():
        if not QtWidgets.QApplication.instance():
            app = QtWidgets.QApplication(sys.argv)
        else:
            app = QtWidgets.QApplication.instance()
        app.setQuitOnLastWindowClosed(True)
        main = MainWindow()
        main.show()
        sys.exit(app.exec_())
    
    if __name__ == '__main__':
        m = main()

Expected behavior
Normal slider behaviour

Screenshots

Screen.Recording.2022-03-21.at.09.48.18.mov

Desktop (please complete the following information):

  • macOS 12.1
  • PyQt5 5.15.6
  • superqt 0.3.1
  • Python 3.9.7

[progress tracker] tree view backed by nestable selectable evented lists

I'm opening this issue to track progress towards achieving the following

I can imagine us getting to a place where we could say "you tell us what your tree structure looks like and we'll give you a UI for it".

I think we want the model for this to be a nested set of SelectableEventedLists in psygnal - in superqt we can provide a view of this model similar to the QtTreeView in napari

  • tlambert03/psygnal#64 to psygnal
  • tlambert03/psygnal#78
  • add support for nesting to the evented list in psygnal
  • make Qt view classes

You can probably tell I'm a little vague on the details in the last step ๐Ÿ˜†

qtrangeslider has multiple slider controls that are either faded or non-functional

Describe the bug
The qtrangeslider doesn't display properly. The range controls appear faded and there is a non-faded unmovable control at the bottom (screenshot below).

This code produces the error:

import sys
from PyQt5 import QtCore, QtWidgets
from qtrangeslider import QRangeSlider


class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        vlayout2 = QtWidgets.QHBoxLayout()
        self.qtr = QRangeSlider(QtCore.Qt.Orientation.Vertical)
        self.qtr.setValue((11, 33))
        vlayout2.addWidget(self.qtr)
        widget = QtWidgets.QWidget()
        widget.setLayout(vlayout2)
        self.setCentralWidget(widget)

def main():
    if not QtWidgets.QApplication.instance():
        app = QtWidgets.QApplication(sys.argv)
    else:
        app = QtWidgets.QApplication.instance()
    app.setQuitOnLastWindowClosed(True)
    main = MainWindow()
    main.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    m = main()

Expected behavior
normal slider behaviour

Screenshots
image

Desktop (please complete the following information):

  • MacOS 12.1
  • PyQT 5.9.2, qtrangeslider 0.1.5
  • Python version 3.9

Throttling mechanism does not allow to cancel timers.

Describe the bug

lack of possibility to stop timer behind throttling mechanism may lead to failing of strict tests like in napari by keep objects alive

https://github.com/napari/napari/runs/8043907763?check_suite_focus=true#step:8:377

To Reproduce
Steps to reproduce the behavior:

  1. Go to '...'
  2. Click on '....'
  3. Scroll down to '....'
  4. See error

Expected behavior
A clear and concise description of what you expected to happen.

There should be option to stop timer on close.

Screenshots
Screenshots and GIFS are much appreciated when reporting visual bugs.

Desktop (please complete the following information):

  • OS with version [e.g macOS 10.15.7]
  • Qt Backend [e.g PyQt5, PySide2]
  • Python version

QRangeSlider is not displayed correctly

Description
When enabling a stylesheet (https://pypi.org/project/pyqtdarktheme/) the QRangeSlider and all its derivatives are not rendered/displayed correctly.

To Reproduce
Steps to reproduce the behavior:

  1. Install pyqtdarktheme version 1.1.0
  2. Install qtpy
  3. Install superqt
  4. Execute minimal example
from qtpy.QtCore import Qt
from qtpy.QtWidgets import QApplication
from superqt import QRangeSlider
import qdarktheme

app = QApplication([])
app.setStyleSheet(qdarktheme.load_stylesheet())
slider = QRangeSlider(Qt.Orientation.Horizontal)
slider.setValue((20, 80))
slider.show()

app.exec_()

Expected behavior
Correct rendering of the widget

Desktop (please complete the following information):

  • Win10
  • PyQt5
  • Python 3.9
    2022-10-17 10_22_53-Window

Proposal: Collapsible Section Control

Justification

A lot of scientific plugins require numerous parameters and settings. It is not user-friendly to show all the settings in the plugin window. I propose a collapsible section control that simplifies hiding unimportant/optional settings in a plugin.

Possible solutions

  1. QTreeWidget and QTreeWidgetItem can be used by inserting a composite widget in QTeeWidgetItem. The main issue with this solution is the indentation which does not look great. Also, it does not have a lot of flexibility.
  2. Design a control from scratch using button (to hide and show) and content widget.

Note

There are possible solutions that are available online (e.g., https://github.com/By0ute/pyqt-collapsible-widget). I think having one in superqt would be better than relying on a separate package.

Prototype

Here is a simple prototype with nice animation too. it is based on a solution found in StackOverflow for C++ Qt.

juuUxO3FvN

'''A collapsible widget'''

import sys
from typing import Union
from qtpy.QtWidgets import QLabel, QLayout
from qtpy.QtCore import QAbstractAnimation, QEasingCurve, \
                        QParallelAnimationGroup, QPropertyAnimation
from qtpy.QtWidgets import QPushButton, QVBoxLayout, QApplication, QWidget
from qtpy.QtCore import Qt

# =================================================================================================
class QCollapsibleSection(QPushButton):
    '''
    A collapsible frame for widgets. This is based on simonxeko solution in 
    https://stackoverflow.com/questions/32476006/how-to-make-an-expandable-collapsable-section-widget-in-qt
    '''
    def __init__(self, content:Union[QWidget,QLayout]=None, min_height=None, **kwargs):
        '''Initializes the component'''
        super().__init__(**kwargs)

        if isinstance(content, QLayout):
            content_widget = QWidget()
            content_widget.setLayout(content)
        else:
            content_widget = content

        self.title = self.text()
        self.setCheckable(True)
        self.setStyleSheet("background:rgba(255, 255, 255, 0); text-align:left;")
        self.animator = QParallelAnimationGroup()
        if content is not None:
            self.set_content_widget(content_widget, min_height)
            self.toggle_hidden()
        self.clicked.connect(self.toggle_hidden)
    # ===========================================
    def toggle_hidden(self) -> None:
        '''Toggle the hidden state of the frame'''
        if self.isChecked():
            self.setText("โ–ผ " + self.title)
            self.show_content()
        else:
            self.setText("โ–ฒ " + self.title)
            self.hide_content()
    # ===========================================
    def set_content_widget(self, content:QWidget, min_height=None):
        '''Sets the content'''
        animation = QPropertyAnimation(content, b"maximumHeight")
        animation.setStartValue(0)
        animation.setEasingCurve(QEasingCurve.Type.InOutQuad)
        animation.setDuration(300)
        print(content.geometry().height())
        if min_height is None:
            animation.setEndValue(content.geometry().height()+10)
        else:
            animation.setEndValue(min_height)
        # animation.setEndValue(200)
        self.animator.addAnimation(animation)
        if not self.isChecked():
            content.setMaximumHeight(0)
    # ===========================================
    def hide_content(self):
        '''Hides the content'''
        self.animator.setDirection(QAbstractAnimation.Direction.Backward)
        self.animator.start()
    # ===========================================
    def show_content(self):
        '''Show the content'''
        self.animator.setDirection(QAbstractAnimation.Direction.Forward)
        self.animator.start()
# =================================================================================================
if __name__ == "__main__":
    app = QApplication(sys.argv)

    main_widget = QWidget()
    layout = QVBoxLayout()
    layout.setAlignment(Qt.AlignTop)
    extra       = QPushButton(text="Content button")
    collapsible = QCollapsibleSection(text='Advanced analysis', content=extra)

    layout.addWidget(collapsible)
    layout.addWidget(extra)
    layout.addWidget(QLabel("I am a label"))

    main_widget.setLayout(layout)
    main_widget.show()
    sys.exit(app.exec_())
# =================================================================================================

TypeError observed in pycharm

received this error report via email...

I had this error message on PyCharm. Nevertheless, it doesn't forbid the program to run.

Traceback (most recent call last):
 File "python3.7/site-packages/superqt/sliders/_generic_range_slider.py", line 150, in event
   update_styles_from_stylesheet(self)
 File "python3.7/site-packages/superqt/sliders/_range_style.py", line 267, in update_styles_from_stylesheet
   parent = parent.parent()
TypeError: 'Main' object is not callable

If it can help you, my configuration :
Python 3.7.5 (virtual environment from miniConda)
PyQt 5.15.2
Linux OpenSUSE 15.2

QLabeledRangeSlider glitchy with negative minRange

Describe the bug
I'm running into some unexpected behavior with the range slider when clim values are negative.

To Reproduce
Steps to reproduce the behavior:

  1. Run python examples/add_image.py set
  2. viewer.layers[0].contrast_limits_range = [-1, 1]
  3. viewer.layers[0].contrast_limits = [-1, 1]
  4. right click on contrast limits slider to get popup
  5. try and drag the left hand side handle, it doesn't move properly/ is glitchy

Expected behavior
Smooth movements

Desktop (please complete the following information):
mac, latest napari

QLabeledDoubleSlider text input broken

Describe the bug
Editing the slider value using the text input field doesn't work correctly. If I input a non-integer value (having some nonzero decimal values), it is converted to an int.

To Reproduce
Steps to reproduce the behavior:

  1. Create a QLabeledDoubleSlider with a range of 0-1 (if you have napari installed, you can use the opacity parameter of a layer)
  2. Write 0.5 into the text field of the slider and press Enter.
  3. Notice that the value is immediately converted to 0.0
  4. Write 0.5 into the text field again.
  5. This time the text field keeps this value, but the slider is not updated, meaning the slider value is in fact not updated (in napari you can easily verify it by checking the opacity value of the layer in the console)

Expected behavior
The input value shouldn't be casted to int

Screenshots
Original state:
image
After writing 0.5 and pressing Enter:
image
After writing 0.5 and pressing Enter again:
image

Desktop (please complete the following information):

  • Windows 11
  • PyQt5==5.15.9
  • Python 3.10.11

Potential fix:
I was able to fix this for my own project by adding the following function to QLabeledDoubleSlider:

def _setValue(self, value: float):
    self._slider.setValue(value)

This works because QLabeledDoubleSlider inherits from QLabeledSlider, which converts the input value to int before setting it to the slider.

Collapsible shows black bars

Describe the bug
The Collapsible component from superqt shows a black bar instead of the arrow and the collapsible name.
image

To Reproduce
Code resulting in the above screenshot is hosted here: https://github.com/MinmoTech/collapsible-test
It is simply the example from this repository.

Expected behavior
There should be no black bars, the collapsible name and arrow should show normally.
Similar to this image in the original proposal:
image

Desktop (please complete the following information):

  • Archlinux
  • Tested with PyQt5 and PySide6
  • Python 3.10
  • superqt version: 0.3.1

bug: ensure_main_thread prevents proper signature evaluation

Just noticed that using the @ensure_main_thread decorator prevents Qt from knowing not to pass too many arguments. (A nice thing about signal.emit() is that callbacks are supposed to be able to accept up to the number of arguments that the signal emits, but less should be ok). with ensure_main_thread, the callback must accept all the arguments.

still looking into how to fix it ... but @Czaki, let me know if happen to know how pyside/pyqt determines this (unfortunately, just setting __signature__ doesn't work :) )

[Proposal] Proper Documentation and Website

As this library grows, it would be good to provide documentation on usage. If the proper infrastructure/base for documentation is added, contributors (e.g., me) can add documentations/tutorials as new features/widgets are added. In addition, more users and contributors will come around to the repo.

I don't have much experience with documentation. I started playing to sphinx for one of my side projects but didn't get to actually go deep into it yet. So having the initial structure created by more experienced person would be better. Slowly, we can start to populate the pages.

QLabeledRangeSlider does not support methods using the bar

moving this issue posted by @OMGSuperZappie at tlambert03/QtRangeSlider#19

QLabeledRangeSlider does not seem to support methods using the bar, while there is a bar present.
Feature request: support bar methods such as setBarIsVisible, setBarMovesAllHandles and setBarIsRigid.

a = QLabeledRangeSlider()
a.hideBar()
AttributeError: 'QLabeledRangeSlider' object has no attribute 'hideBar'

Two tests for eliding_label fail at high resolution screens

Describe the bug
I am noting that two tests in the eliding_label fails at my laptop. I believe it is related to the fact I am using high resolution screen with some scaling similar to #26

To Reproduce
Steps to reproduce the behavior:

  1. Test test_eliding_label.py at high resolution laptop

Expected behavior
Test pass

Screenshots
image

Desktop (please complete the following information):

  • OS with version: Windows 10 64bit
  • Qt Backend: PySide2
  • Python version: Python 3.8.10 64bit
  • Environment is setup using the development requirements listed in the repository

Dev install failing, no matching distribution for pyside2

Describe the bug

Super minor but running pip install -e .[dev] locally fails for me with

ERROR: Could not find a version that satisfies the requirement pyside2 (from versions: none)
ERROR: No matching distribution found for pyside2

Not sure if this is an M1 thing or if the package is just misspecified

Autogenerate docs

Need to convert the current docs format to something autogenerated... but also want to keep it very easy to read, and similar to the Qt docs. Should establish some conventions and patterns

QLabeledRangeSlider/QRangeSlider does not work properly with parent widget background

Describe the bug
QLabeledRangeSlider (as well as QRangeSlider) does not work properly with parent widget background. Parent background hovers minimal handle and all the bar-range. I've tested with PyQt6.QtWidgets.QSlider, it works fine with background.

To Reproduce
Steps to reproduce the behavior:

  1. Create PyQt application instance and main widget with layout (QVBoxLayout e.g.).
  2. Create QRangeSlider/QLabeledRangeSlider, add values and range.
  3. Set stylesheet for main widget with background: black;.
  4. See that Slider does not have minimal handle and bar (for labeled Slider labels will be present, figured it out with another background color.

Example code:

import sys
from PyQt6 import QtWidgets, QtCore
from superqt import QLabeledRangeSlider, QRangeSlider
app = QtWidgets.QApplication([])
main_widget = QtWidgets.QWidget()
# main_widget.setStyleSheet("background: black;")
range_slider = QLabeledRangeSlider(QtCore.Qt.Orientation.Horizontal)
range_slider.setValue((15, 25))
range_slider.setRange(10, 40)
lay = QtWidgets.QVBoxLayout()
main_widget.setLayout(lay)
lay.addWidget(range_slider)
main_widget.setMinimumSize(600, 400)
main_widget.show()
sys.exit(app.exec())

Expected behavior
It was expected that the parent widget background would not affect the child widget.

Screenshots
QLabeledRangeSlider without parent widget background
Screenshot 2023-09-12 at 21 16 43
QLabeledRangeSlider with parent widget background
Screenshot 2023-09-12 at 21 16 14

Desktop (please complete the following information):

  • macOS 12.2.1, Apple M1 chip
  • PyQt6 (6.5.1)
  • Python 3.10.10

How to setStyleSheet on second handle and groove

Hi, i'm trying to use your QLabeledRangeSlider with PySide6 but setStyleSheet doesn't work for most of the things you can edit from QSlider class.

I tried to do

self.range_slider = QLabeledRangeSlider(Qt.Horizontal)
QSS = """
   QSlider::groove:horizontal {
       border: 1px solid #999999;
       height: 8px; 
       background-color: green;
       margin: 2px 0;
   }
    QSlider::handle {
        background-color: red;
        height: 20px;
        width: 20px;
        border-radius: 10px;
   }
self.range_slider.setStyleSheet(QSS)
"""

But the only thing working is the handle being red. How can i style the second handle and the groove using the QLabeledRangeSilder widget ? Thanks.

Add TreeViz widget to allow for expandable tree like metadata structures

Create a widget that when given a TreeLike structure (Dict, xml.etree.ElementTree, anything else?) display a collapsed view of the structure and allow click on the branches to expand.

Much like existing ome-types tree viewer but generalized to all TreeLike structures.
image

Didn't get around to it last week but going to try to dedicate some time to it this week. Sorry about that. If I could self assign the issue, I would ๐Ÿ˜‚


Original discussion from zulip:

@JacksonMaxfield

@Talley Lambert I am tempted to port your OMETree widget to some other repo as a more generalized "given a tree like structure (etree.ElementTree or Dict), add widget component" so that we can just toss CZI and LIF metadata in there too. Any thoughts as to where this should go? Is this a stupid idea / someone else has already done that?

This: https://github.com/tlambert03/ome-types/blob/9954ae63c162ce91d516167009590bcc20a77174/src/ome_types/widgets.py#L21

@tlambert03

yeah, I agree, it's a very generic component... how about our new superqt repo? https://github.com/napari/superqt
the goal there is to collect things that have no dependencies but "some qt backend" in the environment... so it seems well suited. We'll just want to modify some of the method names and stuff to try to make it feel as native as possible

is that something you'd want to help out with?

@JacksonMaxfield

Yea that's what I was thinking as well. Figured superqt would be the way to go.

Happy to help on that. I may have some time tonight even!

Problem with themes (e.g. qdarkstyle)

Describe the bug
Using global stylesheets (such as qdarkstyle) breaks the QRangeSlider visualization.

To Reproduce
Steps to reproduce the behavior:
Used the example stylesheet for QLabeledRangeSlider and then set the qdarkstyle style sheet to the MainWindow.

This is my style sheet for the slider:

QSS = """
QSlider {
    min-height: 10px;
}

QSlider::groove:horizontal {
    border: 0px;
    background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #888, stop:1 #ddd);
    height: 10px;
    border-radius: 5px;
}

QSlider:focus {
  border: none;
}

QSlider::handle {
    background: qradialgradient(cx:0, cy:0, radius: 1.2, fx:0.35,
                                fy:0.3, stop:0 #eef, stop:1 #002);
    height: 10px;
    width: 10px;
    border-radius: 5px;
}

QSlider::handle:horizontal:hover {
    background: qradialgradient(cx:0, cy:0, radius: 1.2, fx:0.35,
                                fy:0.3, stop:0 #efe, stop:1 #222);
}

QSlider::sub-page:horizontal {
    background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #227, stop:1 #77a);
    border-top-left-radius: 5px;
    border-bottom-left-radius: 5px;
}
"""

QSS += """QRangeSlider {
    # This function just defines the stops for the gradient
    qproperty-barColor: """ + gen_style_string_from_hex_colors(
    colormap='viridis') + \
       """\n}"""

This is how I set the qdarkstyle theme:

import qdarkstyle
...
app = QtWidgets.QApplication(sys.argv)
m = MainWindow()
style_sheet = qdarkstyle.load_stylesheet()
m.setStyleSheet(style_sheet)

Screenshots

Without qdarkstyle (I've customized the barColor with a gradient):

image

With qdarkstyle:

image

Desktop (please complete the following information):

  • OS with version = macOS 12.1
  • Qt Backend = PyQt5
  • Python version = 3.9

Adding Multi-range slider to Main Window in PyQt5 application

Hi,

I am trying to add multi-range slider as a widget to the MainWindow of an application. When I run the demo_widget.py everything is alright. However if I try to add multi-range slider to a frame or layout in my MainWindow, it looks like that the style is changed and I just see one slider and nothing else.

Would you please let me know how should I solve it?

RangeSlider handle not clickable when both have the same value

Describe the bug
When both handles of a QRangeSlider have the same value, i.e. slider.value() = (10,10), the clickable handle is the lower of the two handles (_pressedIndex = 0). When they both take the value of 0, i.e. slider.value() = (0,0), this makes it impossible to click and drag the handles. They can still be moved by either clicking in the slider, which will move the upper handle by pageStep, or scrolling, which moves both (and tends to add some distance between them, which seems like a separate bug).

To Reproduce
Steps to reproduce the behavior:

  1. Run rangeSliderTest.py
  2. Scroll within the bottom left range slider (styled_range_hslider) such that the whole range moves to the farthest left point (styled_range_hslider.value()[0] = 0)
  3. Drag the upper handle as far left as possible, such that it overlaps the lower handle. It seems to be a feature that the two handles will not take the same value. This results in styled_range_hslider.value() = (0,1). Though, in custom apps this is not necessarily true when the handles can be moved programatically or through interaction with a QSPinBox, for example.
  4. Try to click+drag the upper handle to expand the range and nothing happens because self.sender()._pressedIndex = 0
  5. This behaviour can also be reproduced with the multi_range_hslider.

Expected behavior
In the instance where both handles overlap at an arbitrary position, this behaviour is fine; however, when the upper handle size is large enough to overlap the lower handle and slider.minimum(), the default _pressedIndex should give preference to the upper handle.

Screenshots
N/A

Desktop (please complete the following information):

  • Windows 10 Enterprise 21H2
  • PyQt5 v5.15.9
  • Python v3.10.4

QLabeledRangeSlider no labels after setValue() int the second time

update values the second time removes all labels
When I create my_slider QLabeledRangeSlider object then set the values with my_slider.setValue(mylist_1) every thing works fine but when I set a new dataset with my_slider.setValue(mylist_2), only handles updated all labels disappear.

To Reproduce
Steps to reproduce the behavior:

  1. create a QWidget to host the QLabeledRangeSlider slider
  2. create a QLabeledRangeSlider object as child then set its value with .setValue() method
  3. create a QPushButton to set new values then connect its signal clicked to an update method
  4. see error: the slider updates with no label at each handle, only range label displayed.

example code

import sys, os

from PySide6.QtWidgets import QApplication, QWidget, QPushButton
from PySide6.QtCore import Qt
from superqt import QLabeledRangeSlider

class Window(QWidget):
  def __init__(self, parent=None):
    super(Window, self).__init__(parent)
    sl = tuple([0, 90, 210, 259, 374])

    self.main_slider = QLabeledRangeSlider(parent=self)
    self.main_slider.setOrientation(Qt.Horizontal)
    width = self.geometry().width()
    self.main_slider.setMinimumWidth(width)

    self.main_slider.setRange(sl[0], sl[-1])
    self.main_slider.setValue(tuple(sl))

    btn = QPushButton(self)

    btn.clicked.connect(self.update)


  def update(self):
    sl = tuple([1261, 1281, 1334, 1365, 1697, 1871, 1916])
    self.main_slider.setRange(sl[0], sl[-1])
    self.main_slider.setValue(sl)


if __name__ == '__main__':
  app = QApplication([])
  win = Window()
  win.show()
  sys.exit(app.exec())

Expected behavior
expected labels .at each handle.

Screenshots
image
image

Desktop (please complete the following information):

  • Windows 10
  • PySide6
  • Python 3.7.13, conda 4.10.1

scope question

love the name you went with! I know they're not widgets per-se, but I was wondering if you were thinking of including your SelectableEventedList/Tree models and associated views in this repo or if they were out of scope?

Pseudo-States can't be used to change qproperty-barColor

Description
Using CSS pseudo-states in conjunction with qproperty-barColor does not apply it.

To Reproduce

import sys

from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QMainWindow
from superqt import QLabeledRangeSlider

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = QMainWindow()
    slider = QLabeledRangeSlider(Qt.Horizontal)
    slider.setStyleSheet("""QRangeSlider:focus { qproperty-barColor: #000; }""")
    window.setCentralWidget(slider)
    window.show()
    sys.exit(app.exec_())
  1. Execute code fragment
  2. Slider color is not affected by QSS
  3. Remove pseudo-state
  4. Bar between handles is black

Expected behavior
Pseudo-states should also work to change the bar color.

Desktop (please complete the following information):

  • OS: Windows 10, Build 19042.1165
  • Qt Backend: PyQt5
  • Python: 3.8

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.