Magicgui and docked widget questions

Hi all,

Firstly, thanks to napari team! great software!

Today I have 2 basic questions about magicgui and 1 about docked widgets. I list them here:

  1. It is possible to change the string automatically generated in the widget defined by @magicgui decorator? For instance, this fragment draw a QSpinBox with the “var_name” string left to the widget
@magicgui(var_name={"widget_type": QSpinBox})
def callback(var_name: int = 0):
    pass

I would like show other string in the gui, it is possible?

  1. How I can use the ‘grid’ layout option in magicgui decorator? At the moment, I use layout=‘vertical’ to stack 3 QSpinBox and 3 QCheckBox (6 rows) but I want to arranged the 3 QSpinbox vertically and the other 3 QCheckBox too vertically but in a second column (in few words, a grid with 3 rows and 2 columns). So, I don’t know where I should put the (3, 2) specification :man_shrugging:

  2. Finally, I created dock widget generated by .add_dock_widget() method, but I can’t resize it, I tried using .resize(w, h) but doesn’t work. I partially solve this issue by .setMaximumHeight/Width Qt methods, but really I don’t want set a maximum, only need resizing.

Thanks again!

Cheers

  1. I don’t think this is possible right now, but it should not be too hard to add this. e.g. var_name={"widget_type": QSpinBox, "display_name": "Much Better Var Name"}. I suggest creating a feature request issue at https://github.com/napari/magicgui/

  2. I think grid layout has also not been implemented! (@talley would need to verify this.) Based on this file, when actually creating the layout (last function), the QGridLayout option is not checked.

  3. This one I don’t know at all, but I suspect it might also be a work in progress! :joy:

Sorry that wasn’t more helpful but at least it might let you stop banging your head against a wall for now. :joy: Talley might have better answers…

1 Like

thanks @scaracciolo & thanks @jni

  1. @jni is correct. not possible yet to change the label, but definitely something that should be possible and wouldn’t be too hard. please do create a feature request for that one!
    sidenote here: just want to point out that "widget_type": QSpinBox is unnecessary in your case. SpinBox is the default widget for type int … that feature is really mostly there if you need to further customize the widget for some reason (such as to use a slider instead of a spinbox)

  2. @jni is correct: I’m afraid there’s no grid layout yet (sorry: would also be a good feature). It is planned, but not yet implemented.

  3. resizing dock widgets is possible, but the Qt API for it is kind of unintuitive. you have to use the resizeDocks method on the QMainWindow object… something like this:

import napari
from qtpy.QtCore import Qt

viewer = napari.Viewer()
# ... do what you need to create the magicgui widget
widget = callback.Gui()
# add dock widget, and save the returned reference
dock_widget = viewer.window.add_dock_widget(widget, area='right')

# use this unfortunate method:
width = 200  # for example
viewer.window._qt_window.resizeDocks([dock_widget], [width], Qt.Horizontal)
1 Like

Thanks! I think my wall is more important than the “display_name” :rofl: :rofl:

Thanks, @talley,

I fought with resizeDocks but I didn’t get what I wanted, I think because I have several docks and I need to set QSizePolicy correctly for each one. I should read more about Qt layouts in the future.

At the moment, .setMaximumWidth/Height() and works easely.

Thanks anyway!

great, glad you’ve got something working for you. feel free to post code/screenshots if you have any specific sizing questions.

There’s a lot of nesting going on, so it’s definitely confusing. One detail is that the dockwidget contains the original “Central” widget. so resizeDocks controls the size of the dock window (i.e. the “container”), but you may additionally need to modify the size policy (or min/max width) of the interior “central widget” to get exactly the behavior you’re after (as you’ve discovered).

I think because I have several docks and I need to set QSizePolicy correctly for each one

also note that each parameter to resizeDocks is a list, so you can provide a list of dock widgets and a list of widths/heights if you need to resize multiple widgets (but again… the size policy of their interior widgets also matters).

I’m really grateful @talley!

These are my docks with your specific areas,

    commands_dock = viewer.window.add_dock_widget(spatial_cursor_selector, name='Commands', area = 'bottom')
    colorbars_dock = viewer.window.add_dock_widget(colobars_widget, name='Colorbars', area = 'bottom')
    tunning_dock = viewer.window.add_dock_widget(tunning_plots_layout, name='Tunning plots', area = 'bottom')
    potential_dock = viewer.window.add_dock_widget(potential_plots_widget, name='Potential plots', area = 'right')

These are size variables:

    window_height = viewer.window._qt_window.sizeHint().height()
    window_width = viewer.window._qt_window.sizeHint().width()
    commands_width = commands_dock.sizeHint().width()*1.1

When I use these instructions, I get the layout that I want:

colorbars_dock.setMaximumWidth(1.5*commands_width)
commands_dock.setMaximumWidth(commands_width)
commands_dock.setMaximumHeight(window_height/3)
viewer.window._qt_window.showMaximized()

Here the screenshot,

However. if I switch the .setMaximumWidth/Height() methods by:

    viewer.window._qt_window.resizeDocks([commands_dock, colorbars_dock], [commands_width, 1.5*commands_width], Qt.Horizontal)
    viewer.window._qt_window.resizeDocks([commands_dock], [window_height/3], Qt.Vertical)

I get this,

:man_shrugging: as you can see, I need to learn about Qt layouts :rofl:

All docks have default SizePolicy … maybe the problem is related to units, I have to learn more about this …

3 Likes

First of all, that’s gorgeous :heart_eyes: … and it’s lovely to see stuff like that getting built with napari/magicgui.

at the end of the day, just go with whatever works for you! but here’s a couple thoughts/ tips:

setting sizes with setMaximumWidth/Height can be great for a given window size, but sometimes doesn’t “adapt” well if the user resizes the window. size policies on the other hand, are more prescriptive, and suggest how a given widget should (or shouldn’t) take advantage of available space as it grows/shrinks (see the available SizePolicy options here). Usually you’ll need a combination of fixed widget sizes and size policies to get the desired outcome depending on the content/intent of the widgets and the viewer window size.

When I use these instructions, I get the layout that I want:

colorbars_dock.setMaximumWidth(1.5*commands_width)
commands_dock.setMaximumWidth(commands_width)
commands_dock.setMaximumHeight(window_height/3)
viewer.window._qt_window.showMaximized()

What you’re more or less saying with this code is “I want the commands and colorbars docks to be a fixed width… based on the initial size of the commands” and by omission that leaves the tunning_dock to fill in the remainder of the space.

Rather than getting and pinning the commands_dock width, just give the original widget a horizontal SizePolicy of “fixed” (meaning it should use the sizeHint() as the required size, and should never grow or shrink), and the containing dock will “obey”:

from qtpy.QtWidgets import QSizePolicy
# horizontal size is fixed
# vertical sizeHint should be considered "minimum", but allow widget to grow...
# note, we're adjusting the original widget, not the dock widget
spatial_cursor_selector.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Minimum)

looks like the colorbar widget is a good candidate for a fixed with. doesn’t really matter how wide the napari viewer is, a few hundred pixels might be sufficient (leaving the remaining space to the tunning_dock).

colorbars_widget.setFixedWidth(300)

that leaves the tunning_dock with the default sizePolicy of “preferred” meaning "The sizeHint() is best, but the widget can be shrunk and still be useful. The widget can be expanded, but there is no advantage to it being larger than sizeHint()"

all together:

import napari
from qtpy.QtWidgets import QLabel, QSizePolicy
from magicgui import magicgui


@magicgui(layout='vertical')
def commands(
    heart_node: int,
    heart_validation_node: int,
    torso_node: int,
    heart_potential_normalize: bool,
    heart_validation_potential_normalize: bool,
    torso_potential_normalize: bool,
):
    pass


with napari.gui_qt():
    commands = commands.Gui()
    colorbars = QLabel("Colorbars")
    tunning = QLabel("Tunning")

    colorbars.setStyleSheet("background: blue")
    tunning.setStyleSheet("background: red")

    commands.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Minimum)
    colorbars.setFixedWidth(300)

    viewer = napari.Viewer()
    commands_dock = viewer.window.add_dock_widget(commands, area='bottom')
    colorbars_dock = viewer.window.add_dock_widget(colorbars, area='bottom')
    tunning_dock = viewer.window.add_dock_widget(tunning, area='bottom')

gives:

or with a larger window size (only tunning expands):

you can also then use viewer.window._qt_window.resize(width, height) (in addition to showMaximized as you already did) to set the full viewer size.

(also, note that I was mostly adjusting the originally widgets before creating dock widgets there).

edit: if you wanted to do some more complicated/adaptive space sharing between colorbars and tunning widgets, then you could use the resizeDocks command as above as an initial command, and (I believe) it should maintain the ratio of widths as you resize the window.

2 Likes

Wow @talley, really I’m very greatful, your answer is perfect :heart_eyes: I reproduced your concepts and all it works fine. This explication will be useful for this and next proyects where I’ll work with Qt … I don’t know how to thank you!

2 Likes

glad you found it helpful! look forward to seeing what you do with it

1 Like