We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
`Viewer.window.add_dock_widget` does not support list of `FunctionGui` from `magicgui`
## 🐛 Bug

The `Viewer.window.add_dock_widget` expects a `Sequence` of `QWigets` or a single `FunctionGui`, it does not support a `Sequence` of `FunctionGui`, this possibly a bug from the changes of `magicgui v0.2`.

The programmer can circumvent this issue by sending the `FunctionGui.native` objects, but I'm not sure if this is the expected behavior.

## To Reproduce

Execute this code:

```python3
import napari
from magicgui import magicgui

viewer = napari.Viewer()

@magicgui(call_button='hi')
def _first_widget() -> None:
    print('hi')

@magicgui(call_button='bye')
def _second_widget() -> None:
    print('bye')

viewer.window.add_dock_widget([_first_widget, _second_widget])                                                                                                                                            
napari.run()
```

Outputting:
```python3
Traceback (most recent call last):
  File "error.py", line 14, in <module>
    viewer.window.add_dock_widget([_first_widget, _second_widget])
  File "/home/jordao/Softwares/napari/napari/_qt/qt_main_window.py", line 994, in add_dock_widget
    dock_widget = QtViewerDockWidget(
  File "/home/jordao/Softwares/napari/napari/_qt/widgets/qt_viewer_dock_widget.py", line 131, in __init__
    widget = combine_widgets(widget, vertical=is_vertical)
  File "/home/jordao/Softwares/napari/napari/_qt/utils.py", line 245, in combine_widgets
    raise TypeError(
TypeError: "widget" must be a QWidget or a sequence of QWidgets
```

It works if I change to:

```python3
import napari
from magicgui import magicgui

viewer = napari.Viewer()

@magicgui(call_button='hi')
def _first_widget() -> None:
    print('hi')

@magicgui(call_button='bye')
def _second_widget() -> None:
    print('bye')

viewer.window.add_dock_widget([_first_widget.native, _second_widget.native])                                                                                                                                            
napari.run()
```

## Environment

```
napari: 0.4.7
Platform: Linux-5.8.0-50-generic-x86_64-with-glibc2.10
System: Ubuntu 20.04.2 LTS
Python: 3.8.8 (default, Apr 13 2021, 19:58:26) [GCC 7.3.0]
Qt: 5.15.2
PyQt5: 5.15.4
NumPy: 1.20.3
SciPy: 1.6.3
Dask: 2021.05.0
VisPy: 0.6.6

OpenGL:
- GL version: 4.6 (Compatibility Profile) Mesa 20.2.6
- MAX_TEXTURE_SIZE: 16384

Screens:
- screen 1: resolution 3440x1440, scale 1.0

Plugins:
- console: 0.0.3
- napari-dexp: 0.1.dev2+g1c663ea.d20210517
- napari-ibacktracker: 0.1.dev1+gf589318.d20210517
- napari-segm-annotation: 0.1.dev1+g7fbf135.d20210518
- svg: 0.1.5
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `napari/_qt/utils.py`
Content:
```
1 from contextlib import contextmanager
2 from functools import lru_cache
3 from typing import Sequence, Union
4 
5 import numpy as np
6 import qtpy
7 from qtpy.QtCore import QByteArray, QSize, Qt
8 from qtpy.QtGui import QCursor, QDrag, QImage, QPainter, QPixmap
9 from qtpy.QtWidgets import (
10     QApplication,
11     QGraphicsOpacityEffect,
12     QHBoxLayout,
13     QListWidget,
14     QVBoxLayout,
15     QWidget,
16 )
17 
18 from ..utils.misc import is_sequence
19 from ..utils.translations import trans
20 
21 QBYTE_FLAG = "!QBYTE_"
22 
23 
24 def is_qbyte(string: str) -> bool:
25     """Check if a string is a QByteArray string.
26 
27     Parameters
28     ----------
29     string : bool
30         State string.
31     """
32     return isinstance(string, str) and string.startswith(QBYTE_FLAG)
33 
34 
35 def qbytearray_to_str(qbyte: QByteArray) -> str:
36     """Convert a window state to a string.
37 
38     Used for restoring the state of the main window.
39 
40     Parameters
41     ----------
42     qbyte : QByteArray
43         State array.
44     """
45     return QBYTE_FLAG + qbyte.toBase64().data().decode()
46 
47 
48 def str_to_qbytearray(string: str) -> QByteArray:
49     """Convert a string to a QbyteArray.
50 
51     Used for restoring the state of the main window.
52 
53     Parameters
54     ----------
55     string : str
56         State string.
57     """
58     if len(string) < len(QBYTE_FLAG) or not is_qbyte(string):
59         raise ValueError(
60             trans._(
61                 "Invalid QByte string. QByte strings start with '{QBYTE_FLAG}'",
62                 QBYTE_FLAG=QBYTE_FLAG,
63             )
64         )
65 
66     return QByteArray.fromBase64(string[len(QBYTE_FLAG) :].encode())
67 
68 
69 def QImg2array(img):
70     """Convert QImage to an array.
71 
72     Parameters
73     ----------
74     img : qtpy.QtGui.QImage
75         QImage to be converted.
76 
77     Returns
78     -------
79     arr : array
80         Numpy array of type ubyte and shape (h, w, 4). Index [0, 0] is the
81         upper-left corner of the rendered region.
82     """
83     # Fix when  image is provided in wrong format (ex. test on Azure pipelines)
84     if img.format() != QImage.Format_ARGB32:
85         img = img.convertToFormat(QImage.Format_ARGB32)
86     b = img.constBits()
87     h, w, c = img.height(), img.width(), 4
88 
89     # As vispy doesn't use qtpy we need to reconcile the differences
90     # between the `QImage` API for `PySide2` and `PyQt5` on how to convert
91     # a QImage to a numpy array.
92     if qtpy.API_NAME == 'PySide2':
93         arr = np.array(b).reshape(h, w, c)
94     else:
95         b.setsize(h * w * c)
96         arr = np.frombuffer(b, np.uint8).reshape(h, w, c)
97 
98     # Format of QImage is ARGB32_Premultiplied, but color channels are
99     # reversed.
100     arr = arr[:, :, [2, 1, 0, 3]]
101     return arr
102 
103 
104 @contextmanager
105 def qt_signals_blocked(obj):
106     """Context manager to temporarily block signals from `obj`"""
107     obj.blockSignals(True)
108     yield
109     obj.blockSignals(False)
110 
111 
112 @contextmanager
113 def event_hook_removed():
114     """Context manager to temporarily remove the PyQt5 input hook"""
115     from qtpy import QtCore
116 
117     if hasattr(QtCore, 'pyqtRemoveInputHook'):
118         QtCore.pyqtRemoveInputHook()
119     try:
120         yield
121     finally:
122         if hasattr(QtCore, 'pyqtRestoreInputHook'):
123             QtCore.pyqtRestoreInputHook()
124 
125 
126 def disable_with_opacity(obj, widget_list, disabled):
127     """Set enabled state on a list of widgets. If disabled, decrease opacity"""
128     for wdg in widget_list:
129         widget = getattr(obj, wdg)
130         widget.setEnabled(obj.layer.editable)
131         op = QGraphicsOpacityEffect(obj)
132         op.setOpacity(1 if obj.layer.editable else 0.5)
133         widget.setGraphicsEffect(op)
134 
135 
136 @lru_cache(maxsize=64)
137 def square_pixmap(size):
138     """Create a white/black hollow square pixmap. For use as labels cursor."""
139     size = max(int(size), 1)
140     pixmap = QPixmap(QSize(size, size))
141     pixmap.fill(Qt.transparent)
142     painter = QPainter(pixmap)
143     painter.setPen(Qt.white)
144     painter.drawRect(0, 0, size - 1, size - 1)
145     painter.setPen(Qt.black)
146     painter.drawRect(1, 1, size - 3, size - 3)
147     painter.end()
148     return pixmap
149 
150 
151 @lru_cache(maxsize=64)
152 def circle_pixmap(size: int):
153     """Create a white/black hollow circle pixmap. For use as labels cursor."""
154     size = max(int(size), 1)
155     pixmap = QPixmap(QSize(size, size))
156     pixmap.fill(Qt.transparent)
157     painter = QPainter(pixmap)
158     painter.setPen(Qt.white)
159     painter.drawEllipse(0, 0, size - 1, size - 1)
160     painter.setPen(Qt.black)
161     painter.drawEllipse(1, 1, size - 3, size - 3)
162     painter.end()
163     return pixmap
164 
165 
166 def drag_with_pixmap(list_widget: QListWidget) -> QDrag:
167     """Create a QDrag object with a pixmap of the currently select list item.
168 
169     This method is useful when you have a QListWidget that displays custom
170     widgets for each QListWidgetItem instance in the list (usually by calling
171     ``QListWidget.setItemWidget(item, widget)``).  When used in a
172     ``QListWidget.startDrag`` method, this function creates a QDrag object that
173     shows an image of the item being dragged (rather than an empty rectangle).
174 
175     Parameters
176     ----------
177     list_widget : QListWidget
178         The QListWidget for which to create a QDrag object.
179 
180     Returns
181     -------
182     QDrag
183         A QDrag instance with a pixmap of the currently selected item.
184 
185     Examples
186     --------
187     >>> class QListWidget:
188     ...     def startDrag(self, supportedActions):
189     ...         drag = drag_with_pixmap(self)
190     ...         drag.exec_(supportedActions, Qt.MoveAction)
191 
192     """
193     drag = QDrag(list_widget)
194     drag.setMimeData(list_widget.mimeData(list_widget.selectedItems()))
195     size = list_widget.viewport().visibleRegion().boundingRect().size()
196     pixmap = QPixmap(size)
197     pixmap.fill(Qt.transparent)
198     painter = QPainter(pixmap)
199     for index in list_widget.selectedIndexes():
200         rect = list_widget.visualRect(index)
201         painter.drawPixmap(rect, list_widget.viewport().grab(rect))
202     painter.end()
203     drag.setPixmap(pixmap)
204     drag.setHotSpot(list_widget.viewport().mapFromGlobal(QCursor.pos()))
205     return drag
206 
207 
208 def combine_widgets(
209     widgets: Union[QWidget, Sequence[QWidget]], vertical: bool = False
210 ) -> QWidget:
211     """Combine a list of widgets into a single QWidget with Layout.
212 
213     Parameters
214     ----------
215     widgets : QWidget or sequence of QWidget
216         A widget or a list of widgets to combine.
217     vertical : bool, optional
218         Whether the layout should be QVBoxLayout or not, by default
219         QHBoxLayout is used
220 
221     Returns
222     -------
223     QWidget
224         If ``widgets`` is a sequence, returns combined QWidget with `.layout`
225         property, otherwise returns the original widget.
226 
227     Raises
228     ------
229     TypeError
230         If ``widgets`` is neither a ``QWidget`` or a sequence of ``QWidgets``.
231     """
232     if isinstance(getattr(widgets, 'native', None), QWidget):
233         # compatibility with magicgui v0.2.0 which no longer uses QWidgets
234         # directly. Like vispy, the backend widget is at widget.native
235         return widgets.native  # type: ignore
236     elif isinstance(widgets, QWidget):
237         return widgets
238     elif is_sequence(widgets) and all(isinstance(i, QWidget) for i in widgets):
239         container = QWidget()
240         container.setLayout(QVBoxLayout() if vertical else QHBoxLayout())
241         for widget in widgets:
242             container.layout().addWidget(widget)
243         return container
244     else:
245         raise TypeError(
246             trans._('"widget" must be a QWidget or a sequence of QWidgets')
247         )
248 
249 
250 def delete_qapp(app):
251     """Delete a QApplication
252 
253     Parameters
254     ----------
255     app : qtpy.QApplication
256     """
257     try:
258         # Pyside2
259         from shiboken2 import delete
260     except ImportError:
261         # PyQt5
262         from sip import delete
263 
264     delete(app)
265     # calling a second time is necessary on PySide2...
266     # see: https://bugreports.qt.io/browse/PYSIDE-1470
267     QApplication.instance()
268
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/napari/_qt/utils.py b/napari/_qt/utils.py
--- a/napari/_qt/utils.py
+++ b/napari/_qt/utils.py
@@ -235,16 +235,21 @@
         return widgets.native  # type: ignore
     elif isinstance(widgets, QWidget):
         return widgets
-    elif is_sequence(widgets) and all(isinstance(i, QWidget) for i in widgets):
-        container = QWidget()
-        container.setLayout(QVBoxLayout() if vertical else QHBoxLayout())
-        for widget in widgets:
-            container.layout().addWidget(widget)
-        return container
-    else:
-        raise TypeError(
-            trans._('"widget" must be a QWidget or a sequence of QWidgets')
-        )
+    elif is_sequence(widgets):
+        # the same as above, compatibility with magicgui v0.2.0
+        widgets = [
+            i.native if isinstance(getattr(i, 'native', None), QWidget) else i
+            for i in widgets
+        ]
+        if all(isinstance(i, QWidget) for i in widgets):
+            container = QWidget()
+            container.setLayout(QVBoxLayout() if vertical else QHBoxLayout())
+            for widget in widgets:
+                container.layout().addWidget(widget)
+            return container
+    raise TypeError(
+        trans._('"widget" must be a QWidget or a sequence of QWidgets')
+    )
 
 
 def delete_qapp(app):

{"golden_diff": "diff --git a/napari/_qt/utils.py b/napari/_qt/utils.py\n--- a/napari/_qt/utils.py\n+++ b/napari/_qt/utils.py\n@@ -235,16 +235,21 @@\n         return widgets.native  # type: ignore\n     elif isinstance(widgets, QWidget):\n         return widgets\n-    elif is_sequence(widgets) and all(isinstance(i, QWidget) for i in widgets):\n-        container = QWidget()\n-        container.setLayout(QVBoxLayout() if vertical else QHBoxLayout())\n-        for widget in widgets:\n-            container.layout().addWidget(widget)\n-        return container\n-    else:\n-        raise TypeError(\n-            trans._('\"widget\" must be a QWidget or a sequence of QWidgets')\n-        )\n+    elif is_sequence(widgets):\n+        # the same as above, compatibility with magicgui v0.2.0\n+        widgets = [\n+            i.native if isinstance(getattr(i, 'native', None), QWidget) else i\n+            for i in widgets\n+        ]\n+        if all(isinstance(i, QWidget) for i in widgets):\n+            container = QWidget()\n+            container.setLayout(QVBoxLayout() if vertical else QHBoxLayout())\n+            for widget in widgets:\n+                container.layout().addWidget(widget)\n+            return container\n+    raise TypeError(\n+        trans._('\"widget\" must be a QWidget or a sequence of QWidgets')\n+    )\n \n \n def delete_qapp(app):\n", "issue": "`Viewer.window.add_dock_widget` does not support list of `FunctionGui` from `magicgui`\n## \ud83d\udc1b Bug\r\n\r\nThe `Viewer.window.add_dock_widget` expects a `Sequence` of `QWigets` or a single `FunctionGui`, it does not support a `Sequence` of `FunctionGui`, this possibly a bug from the changes of `magicgui v0.2`.\r\n\r\nThe programmer can circumvent this issue by sending the `FunctionGui.native` objects, but I'm not sure if this is the expected behavior.\r\n\r\n## To Reproduce\r\n\r\nExecute this code:\r\n\r\n```python3\r\nimport napari\r\nfrom magicgui import magicgui\r\n\r\nviewer = napari.Viewer()\r\n\r\n@magicgui(call_button='hi')\r\ndef _first_widget() -> None:\r\n    print('hi')\r\n\r\n@magicgui(call_button='bye')\r\ndef _second_widget() -> None:\r\n    print('bye')\r\n\r\nviewer.window.add_dock_widget([_first_widget, _second_widget])                                                                                                                                            \r\nnapari.run()\r\n```\r\n\r\nOutputting:\r\n```python3\r\nTraceback (most recent call last):\r\n  File \"error.py\", line 14, in <module>\r\n    viewer.window.add_dock_widget([_first_widget, _second_widget])\r\n  File \"/home/jordao/Softwares/napari/napari/_qt/qt_main_window.py\", line 994, in add_dock_widget\r\n    dock_widget = QtViewerDockWidget(\r\n  File \"/home/jordao/Softwares/napari/napari/_qt/widgets/qt_viewer_dock_widget.py\", line 131, in __init__\r\n    widget = combine_widgets(widget, vertical=is_vertical)\r\n  File \"/home/jordao/Softwares/napari/napari/_qt/utils.py\", line 245, in combine_widgets\r\n    raise TypeError(\r\nTypeError: \"widget\" must be a QWidget or a sequence of QWidgets\r\n```\r\n\r\nIt works if I change to:\r\n\r\n```python3\r\nimport napari\r\nfrom magicgui import magicgui\r\n\r\nviewer = napari.Viewer()\r\n\r\n@magicgui(call_button='hi')\r\ndef _first_widget() -> None:\r\n    print('hi')\r\n\r\n@magicgui(call_button='bye')\r\ndef _second_widget() -> None:\r\n    print('bye')\r\n\r\nviewer.window.add_dock_widget([_first_widget.native, _second_widget.native])                                                                                                                                            \r\nnapari.run()\r\n```\r\n\r\n## Environment\r\n\r\n```\r\nnapari: 0.4.7\r\nPlatform: Linux-5.8.0-50-generic-x86_64-with-glibc2.10\r\nSystem: Ubuntu 20.04.2 LTS\r\nPython: 3.8.8 (default, Apr 13 2021, 19:58:26) [GCC 7.3.0]\r\nQt: 5.15.2\r\nPyQt5: 5.15.4\r\nNumPy: 1.20.3\r\nSciPy: 1.6.3\r\nDask: 2021.05.0\r\nVisPy: 0.6.6\r\n\r\nOpenGL:\r\n- GL version: 4.6 (Compatibility Profile) Mesa 20.2.6\r\n- MAX_TEXTURE_SIZE: 16384\r\n\r\nScreens:\r\n- screen 1: resolution 3440x1440, scale 1.0\r\n\r\nPlugins:\r\n- console: 0.0.3\r\n- napari-dexp: 0.1.dev2+g1c663ea.d20210517\r\n- napari-ibacktracker: 0.1.dev1+gf589318.d20210517\r\n- napari-segm-annotation: 0.1.dev1+g7fbf135.d20210518\r\n- svg: 0.1.5\r\n```\r\n\n", "before_files": [{"content": "from contextlib import contextmanager\nfrom functools import lru_cache\nfrom typing import Sequence, Union\n\nimport numpy as np\nimport qtpy\nfrom qtpy.QtCore import QByteArray, QSize, Qt\nfrom qtpy.QtGui import QCursor, QDrag, QImage, QPainter, QPixmap\nfrom qtpy.QtWidgets import (\n    QApplication,\n    QGraphicsOpacityEffect,\n    QHBoxLayout,\n    QListWidget,\n    QVBoxLayout,\n    QWidget,\n)\n\nfrom ..utils.misc import is_sequence\nfrom ..utils.translations import trans\n\nQBYTE_FLAG = \"!QBYTE_\"\n\n\ndef is_qbyte(string: str) -> bool:\n    \"\"\"Check if a string is a QByteArray string.\n\n    Parameters\n    ----------\n    string : bool\n        State string.\n    \"\"\"\n    return isinstance(string, str) and string.startswith(QBYTE_FLAG)\n\n\ndef qbytearray_to_str(qbyte: QByteArray) -> str:\n    \"\"\"Convert a window state to a string.\n\n    Used for restoring the state of the main window.\n\n    Parameters\n    ----------\n    qbyte : QByteArray\n        State array.\n    \"\"\"\n    return QBYTE_FLAG + qbyte.toBase64().data().decode()\n\n\ndef str_to_qbytearray(string: str) -> QByteArray:\n    \"\"\"Convert a string to a QbyteArray.\n\n    Used for restoring the state of the main window.\n\n    Parameters\n    ----------\n    string : str\n        State string.\n    \"\"\"\n    if len(string) < len(QBYTE_FLAG) or not is_qbyte(string):\n        raise ValueError(\n            trans._(\n                \"Invalid QByte string. QByte strings start with '{QBYTE_FLAG}'\",\n                QBYTE_FLAG=QBYTE_FLAG,\n            )\n        )\n\n    return QByteArray.fromBase64(string[len(QBYTE_FLAG) :].encode())\n\n\ndef QImg2array(img):\n    \"\"\"Convert QImage to an array.\n\n    Parameters\n    ----------\n    img : qtpy.QtGui.QImage\n        QImage to be converted.\n\n    Returns\n    -------\n    arr : array\n        Numpy array of type ubyte and shape (h, w, 4). Index [0, 0] is the\n        upper-left corner of the rendered region.\n    \"\"\"\n    # Fix when  image is provided in wrong format (ex. test on Azure pipelines)\n    if img.format() != QImage.Format_ARGB32:\n        img = img.convertToFormat(QImage.Format_ARGB32)\n    b = img.constBits()\n    h, w, c = img.height(), img.width(), 4\n\n    # As vispy doesn't use qtpy we need to reconcile the differences\n    # between the `QImage` API for `PySide2` and `PyQt5` on how to convert\n    # a QImage to a numpy array.\n    if qtpy.API_NAME == 'PySide2':\n        arr = np.array(b).reshape(h, w, c)\n    else:\n        b.setsize(h * w * c)\n        arr = np.frombuffer(b, np.uint8).reshape(h, w, c)\n\n    # Format of QImage is ARGB32_Premultiplied, but color channels are\n    # reversed.\n    arr = arr[:, :, [2, 1, 0, 3]]\n    return arr\n\n\n@contextmanager\ndef qt_signals_blocked(obj):\n    \"\"\"Context manager to temporarily block signals from `obj`\"\"\"\n    obj.blockSignals(True)\n    yield\n    obj.blockSignals(False)\n\n\n@contextmanager\ndef event_hook_removed():\n    \"\"\"Context manager to temporarily remove the PyQt5 input hook\"\"\"\n    from qtpy import QtCore\n\n    if hasattr(QtCore, 'pyqtRemoveInputHook'):\n        QtCore.pyqtRemoveInputHook()\n    try:\n        yield\n    finally:\n        if hasattr(QtCore, 'pyqtRestoreInputHook'):\n            QtCore.pyqtRestoreInputHook()\n\n\ndef disable_with_opacity(obj, widget_list, disabled):\n    \"\"\"Set enabled state on a list of widgets. If disabled, decrease opacity\"\"\"\n    for wdg in widget_list:\n        widget = getattr(obj, wdg)\n        widget.setEnabled(obj.layer.editable)\n        op = QGraphicsOpacityEffect(obj)\n        op.setOpacity(1 if obj.layer.editable else 0.5)\n        widget.setGraphicsEffect(op)\n\n\n@lru_cache(maxsize=64)\ndef square_pixmap(size):\n    \"\"\"Create a white/black hollow square pixmap. For use as labels cursor.\"\"\"\n    size = max(int(size), 1)\n    pixmap = QPixmap(QSize(size, size))\n    pixmap.fill(Qt.transparent)\n    painter = QPainter(pixmap)\n    painter.setPen(Qt.white)\n    painter.drawRect(0, 0, size - 1, size - 1)\n    painter.setPen(Qt.black)\n    painter.drawRect(1, 1, size - 3, size - 3)\n    painter.end()\n    return pixmap\n\n\n@lru_cache(maxsize=64)\ndef circle_pixmap(size: int):\n    \"\"\"Create a white/black hollow circle pixmap. For use as labels cursor.\"\"\"\n    size = max(int(size), 1)\n    pixmap = QPixmap(QSize(size, size))\n    pixmap.fill(Qt.transparent)\n    painter = QPainter(pixmap)\n    painter.setPen(Qt.white)\n    painter.drawEllipse(0, 0, size - 1, size - 1)\n    painter.setPen(Qt.black)\n    painter.drawEllipse(1, 1, size - 3, size - 3)\n    painter.end()\n    return pixmap\n\n\ndef drag_with_pixmap(list_widget: QListWidget) -> QDrag:\n    \"\"\"Create a QDrag object with a pixmap of the currently select list item.\n\n    This method is useful when you have a QListWidget that displays custom\n    widgets for each QListWidgetItem instance in the list (usually by calling\n    ``QListWidget.setItemWidget(item, widget)``).  When used in a\n    ``QListWidget.startDrag`` method, this function creates a QDrag object that\n    shows an image of the item being dragged (rather than an empty rectangle).\n\n    Parameters\n    ----------\n    list_widget : QListWidget\n        The QListWidget for which to create a QDrag object.\n\n    Returns\n    -------\n    QDrag\n        A QDrag instance with a pixmap of the currently selected item.\n\n    Examples\n    --------\n    >>> class QListWidget:\n    ...     def startDrag(self, supportedActions):\n    ...         drag = drag_with_pixmap(self)\n    ...         drag.exec_(supportedActions, Qt.MoveAction)\n\n    \"\"\"\n    drag = QDrag(list_widget)\n    drag.setMimeData(list_widget.mimeData(list_widget.selectedItems()))\n    size = list_widget.viewport().visibleRegion().boundingRect().size()\n    pixmap = QPixmap(size)\n    pixmap.fill(Qt.transparent)\n    painter = QPainter(pixmap)\n    for index in list_widget.selectedIndexes():\n        rect = list_widget.visualRect(index)\n        painter.drawPixmap(rect, list_widget.viewport().grab(rect))\n    painter.end()\n    drag.setPixmap(pixmap)\n    drag.setHotSpot(list_widget.viewport().mapFromGlobal(QCursor.pos()))\n    return drag\n\n\ndef combine_widgets(\n    widgets: Union[QWidget, Sequence[QWidget]], vertical: bool = False\n) -> QWidget:\n    \"\"\"Combine a list of widgets into a single QWidget with Layout.\n\n    Parameters\n    ----------\n    widgets : QWidget or sequence of QWidget\n        A widget or a list of widgets to combine.\n    vertical : bool, optional\n        Whether the layout should be QVBoxLayout or not, by default\n        QHBoxLayout is used\n\n    Returns\n    -------\n    QWidget\n        If ``widgets`` is a sequence, returns combined QWidget with `.layout`\n        property, otherwise returns the original widget.\n\n    Raises\n    ------\n    TypeError\n        If ``widgets`` is neither a ``QWidget`` or a sequence of ``QWidgets``.\n    \"\"\"\n    if isinstance(getattr(widgets, 'native', None), QWidget):\n        # compatibility with magicgui v0.2.0 which no longer uses QWidgets\n        # directly. Like vispy, the backend widget is at widget.native\n        return widgets.native  # type: ignore\n    elif isinstance(widgets, QWidget):\n        return widgets\n    elif is_sequence(widgets) and all(isinstance(i, QWidget) for i in widgets):\n        container = QWidget()\n        container.setLayout(QVBoxLayout() if vertical else QHBoxLayout())\n        for widget in widgets:\n            container.layout().addWidget(widget)\n        return container\n    else:\n        raise TypeError(\n            trans._('\"widget\" must be a QWidget or a sequence of QWidgets')\n        )\n\n\ndef delete_qapp(app):\n    \"\"\"Delete a QApplication\n\n    Parameters\n    ----------\n    app : qtpy.QApplication\n    \"\"\"\n    try:\n        # Pyside2\n        from shiboken2 import delete\n    except ImportError:\n        # PyQt5\n        from sip import delete\n\n    delete(app)\n    # calling a second time is necessary on PySide2...\n    # see: https://bugreports.qt.io/browse/PYSIDE-1470\n    QApplication.instance()\n", "path": "napari/_qt/utils.py"}], "after_files": [{"content": "from contextlib import contextmanager\nfrom functools import lru_cache\nfrom typing import Sequence, Union\n\nimport numpy as np\nimport qtpy\nfrom qtpy.QtCore import QByteArray, QSize, Qt\nfrom qtpy.QtGui import QCursor, QDrag, QImage, QPainter, QPixmap\nfrom qtpy.QtWidgets import (\n    QApplication,\n    QGraphicsOpacityEffect,\n    QHBoxLayout,\n    QListWidget,\n    QVBoxLayout,\n    QWidget,\n)\n\nfrom ..utils.misc import is_sequence\nfrom ..utils.translations import trans\n\nQBYTE_FLAG = \"!QBYTE_\"\n\n\ndef is_qbyte(string: str) -> bool:\n    \"\"\"Check if a string is a QByteArray string.\n\n    Parameters\n    ----------\n    string : bool\n        State string.\n    \"\"\"\n    return isinstance(string, str) and string.startswith(QBYTE_FLAG)\n\n\ndef qbytearray_to_str(qbyte: QByteArray) -> str:\n    \"\"\"Convert a window state to a string.\n\n    Used for restoring the state of the main window.\n\n    Parameters\n    ----------\n    qbyte : QByteArray\n        State array.\n    \"\"\"\n    return QBYTE_FLAG + qbyte.toBase64().data().decode()\n\n\ndef str_to_qbytearray(string: str) -> QByteArray:\n    \"\"\"Convert a string to a QbyteArray.\n\n    Used for restoring the state of the main window.\n\n    Parameters\n    ----------\n    string : str\n        State string.\n    \"\"\"\n    if len(string) < len(QBYTE_FLAG) or not is_qbyte(string):\n        raise ValueError(\n            trans._(\n                \"Invalid QByte string. QByte strings start with '{QBYTE_FLAG}'\",\n                QBYTE_FLAG=QBYTE_FLAG,\n            )\n        )\n\n    return QByteArray.fromBase64(string[len(QBYTE_FLAG) :].encode())\n\n\ndef QImg2array(img):\n    \"\"\"Convert QImage to an array.\n\n    Parameters\n    ----------\n    img : qtpy.QtGui.QImage\n        QImage to be converted.\n\n    Returns\n    -------\n    arr : array\n        Numpy array of type ubyte and shape (h, w, 4). Index [0, 0] is the\n        upper-left corner of the rendered region.\n    \"\"\"\n    # Fix when  image is provided in wrong format (ex. test on Azure pipelines)\n    if img.format() != QImage.Format_ARGB32:\n        img = img.convertToFormat(QImage.Format_ARGB32)\n    b = img.constBits()\n    h, w, c = img.height(), img.width(), 4\n\n    # As vispy doesn't use qtpy we need to reconcile the differences\n    # between the `QImage` API for `PySide2` and `PyQt5` on how to convert\n    # a QImage to a numpy array.\n    if qtpy.API_NAME == 'PySide2':\n        arr = np.array(b).reshape(h, w, c)\n    else:\n        b.setsize(h * w * c)\n        arr = np.frombuffer(b, np.uint8).reshape(h, w, c)\n\n    # Format of QImage is ARGB32_Premultiplied, but color channels are\n    # reversed.\n    arr = arr[:, :, [2, 1, 0, 3]]\n    return arr\n\n\n@contextmanager\ndef qt_signals_blocked(obj):\n    \"\"\"Context manager to temporarily block signals from `obj`\"\"\"\n    obj.blockSignals(True)\n    yield\n    obj.blockSignals(False)\n\n\n@contextmanager\ndef event_hook_removed():\n    \"\"\"Context manager to temporarily remove the PyQt5 input hook\"\"\"\n    from qtpy import QtCore\n\n    if hasattr(QtCore, 'pyqtRemoveInputHook'):\n        QtCore.pyqtRemoveInputHook()\n    try:\n        yield\n    finally:\n        if hasattr(QtCore, 'pyqtRestoreInputHook'):\n            QtCore.pyqtRestoreInputHook()\n\n\ndef disable_with_opacity(obj, widget_list, disabled):\n    \"\"\"Set enabled state on a list of widgets. If disabled, decrease opacity\"\"\"\n    for wdg in widget_list:\n        widget = getattr(obj, wdg)\n        widget.setEnabled(obj.layer.editable)\n        op = QGraphicsOpacityEffect(obj)\n        op.setOpacity(1 if obj.layer.editable else 0.5)\n        widget.setGraphicsEffect(op)\n\n\n@lru_cache(maxsize=64)\ndef square_pixmap(size):\n    \"\"\"Create a white/black hollow square pixmap. For use as labels cursor.\"\"\"\n    size = max(int(size), 1)\n    pixmap = QPixmap(QSize(size, size))\n    pixmap.fill(Qt.transparent)\n    painter = QPainter(pixmap)\n    painter.setPen(Qt.white)\n    painter.drawRect(0, 0, size - 1, size - 1)\n    painter.setPen(Qt.black)\n    painter.drawRect(1, 1, size - 3, size - 3)\n    painter.end()\n    return pixmap\n\n\n@lru_cache(maxsize=64)\ndef circle_pixmap(size: int):\n    \"\"\"Create a white/black hollow circle pixmap. For use as labels cursor.\"\"\"\n    size = max(int(size), 1)\n    pixmap = QPixmap(QSize(size, size))\n    pixmap.fill(Qt.transparent)\n    painter = QPainter(pixmap)\n    painter.setPen(Qt.white)\n    painter.drawEllipse(0, 0, size - 1, size - 1)\n    painter.setPen(Qt.black)\n    painter.drawEllipse(1, 1, size - 3, size - 3)\n    painter.end()\n    return pixmap\n\n\ndef drag_with_pixmap(list_widget: QListWidget) -> QDrag:\n    \"\"\"Create a QDrag object with a pixmap of the currently select list item.\n\n    This method is useful when you have a QListWidget that displays custom\n    widgets for each QListWidgetItem instance in the list (usually by calling\n    ``QListWidget.setItemWidget(item, widget)``).  When used in a\n    ``QListWidget.startDrag`` method, this function creates a QDrag object that\n    shows an image of the item being dragged (rather than an empty rectangle).\n\n    Parameters\n    ----------\n    list_widget : QListWidget\n        The QListWidget for which to create a QDrag object.\n\n    Returns\n    -------\n    QDrag\n        A QDrag instance with a pixmap of the currently selected item.\n\n    Examples\n    --------\n    >>> class QListWidget:\n    ...     def startDrag(self, supportedActions):\n    ...         drag = drag_with_pixmap(self)\n    ...         drag.exec_(supportedActions, Qt.MoveAction)\n\n    \"\"\"\n    drag = QDrag(list_widget)\n    drag.setMimeData(list_widget.mimeData(list_widget.selectedItems()))\n    size = list_widget.viewport().visibleRegion().boundingRect().size()\n    pixmap = QPixmap(size)\n    pixmap.fill(Qt.transparent)\n    painter = QPainter(pixmap)\n    for index in list_widget.selectedIndexes():\n        rect = list_widget.visualRect(index)\n        painter.drawPixmap(rect, list_widget.viewport().grab(rect))\n    painter.end()\n    drag.setPixmap(pixmap)\n    drag.setHotSpot(list_widget.viewport().mapFromGlobal(QCursor.pos()))\n    return drag\n\n\ndef combine_widgets(\n    widgets: Union[QWidget, Sequence[QWidget]], vertical: bool = False\n) -> QWidget:\n    \"\"\"Combine a list of widgets into a single QWidget with Layout.\n\n    Parameters\n    ----------\n    widgets : QWidget or sequence of QWidget\n        A widget or a list of widgets to combine.\n    vertical : bool, optional\n        Whether the layout should be QVBoxLayout or not, by default\n        QHBoxLayout is used\n\n    Returns\n    -------\n    QWidget\n        If ``widgets`` is a sequence, returns combined QWidget with `.layout`\n        property, otherwise returns the original widget.\n\n    Raises\n    ------\n    TypeError\n        If ``widgets`` is neither a ``QWidget`` or a sequence of ``QWidgets``.\n    \"\"\"\n    if isinstance(getattr(widgets, 'native', None), QWidget):\n        # compatibility with magicgui v0.2.0 which no longer uses QWidgets\n        # directly. Like vispy, the backend widget is at widget.native\n        return widgets.native  # type: ignore\n    elif isinstance(widgets, QWidget):\n        return widgets\n    elif is_sequence(widgets):\n        # the same as above, compatibility with magicgui v0.2.0\n        widgets = [\n            i.native if isinstance(getattr(i, 'native', None), QWidget) else i\n            for i in widgets\n        ]\n        if all(isinstance(i, QWidget) for i in widgets):\n            container = QWidget()\n            container.setLayout(QVBoxLayout() if vertical else QHBoxLayout())\n            for widget in widgets:\n                container.layout().addWidget(widget)\n            return container\n    raise TypeError(\n        trans._('\"widget\" must be a QWidget or a sequence of QWidgets')\n    )\n\n\ndef delete_qapp(app):\n    \"\"\"Delete a QApplication\n\n    Parameters\n    ----------\n    app : qtpy.QApplication\n    \"\"\"\n    try:\n        # Pyside2\n        from shiboken2 import delete\n    except ImportError:\n        # PyQt5\n        from sip import delete\n\n    delete(app)\n    # calling a second time is necessary on PySide2...\n    # see: https://bugreports.qt.io/browse/PYSIDE-1470\n    QApplication.instance()\n", "path": "napari/_qt/utils.py"}]}