dock_widget.py
Go to the documentation of this file.
1 # Copyright (c) 2011, Dirk Thomas, Dorian Scholz, TU Darmstadt
2 # All rights reserved.
3 #
4 # Redistribution and use in source and binary forms, with or without
5 # modification, are permitted provided that the following conditions
6 # are met:
7 #
8 # * Redistributions of source code must retain the above copyright
9 # notice, this list of conditions and the following disclaimer.
10 # * Redistributions in binary form must reproduce the above
11 # copyright notice, this list of conditions and the following
12 # disclaimer in the documentation and/or other materials provided
13 # with the distribution.
14 # * Neither the name of the TU Darmstadt nor the names of its
15 # contributors may be used to endorse or promote products derived
16 # from this software without specific prior written permission.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 # POSSIBILITY OF SUCH DAMAGE.
30 
31 from python_qt_binding.QtCore import qDebug, QEvent, QPoint, QRect, Qt
32 from python_qt_binding.QtGui import QMouseEvent
33 from python_qt_binding.QtWidgets import QApplication, QDockWidget
34 
35 from qt_gui.dockable_main_window import DockableMainWindow
36 from qt_gui.reparent_event import ReparentEvent
37 
38 
39 class DockWidget(QDockWidget):
40  """Widget with the capability to be reparented via drag-and-drop to any other main window."""
41 
42  def __init__(self, container_manager):
43  super(DockWidget, self).__init__()
44  self._container_manager = container_manager
45  if self._container_manager is not None:
46  self.event = self._event
47  self._dragging_parent = None
48  self._dragging_local_pos = None
50  self._main_windows = []
51 
52  def _event(self, e):
53  if e.type() == QEvent.MouseButtonPress and e.button() == Qt.LeftButton:
54  qDebug('%spress, rel=%s, global=%s, diff=%s' % (
55  (' - pseudo ' if self._releasing_and_repressing_while_dragging else ''),
56  e.pos(), e.globalPos(), e.globalPos() - self.pos()))
57 
58  if e.type() == QEvent.MouseButtonRelease and e.button() == Qt.LeftButton:
59  qDebug('%srelease, rel=%s, global=%s, diff=%s' % (
60  (' - pseudo ' if self._releasing_and_repressing_while_dragging else ''),
61  e.pos(), e.globalPos(), e.globalPos() - self.pos()))
62 
63  # store local position when pressing button before starting the custom drag'n'drop
64  # only allow when layout is not frozen
65  if self._dragging_parent is None and \
66  e.type() == QEvent.MouseButtonPress and \
67  e.button() == Qt.LeftButton and \
68  bool(self.features() & QDockWidget.DockWidgetMovable):
69  self._dragging_local_pos = e.pos()
70 
71  if self._dragging_parent is None and \
72  self._dragging_local_pos is not None and \
73  e.type() == QEvent.Move and \
74  QApplication.mouseButtons() & Qt.LeftButton:
75  if self._widget_at(e.pos()) is not None:
76  qDebug(
77  'DockWidget._event() start drag, dockwidget=%s, parent=%s, '
78  'floating=%s, pos=%s' % (
79  str(self), str(self.parent()),
80  str(self.isFloating()),
81  str(self._dragging_local_pos)))
82  self._dragging_parent = self.parent()
83  # ignore further mouse events so that the widget behind this dock widget
84  # can be determined
85  self.setAttribute(Qt.WA_TransparentForMouseEvents)
86 
87  # collect all main windows (except self.main_window) to re-implement
88  # QApplication.widgetAt() in self._widget_at()
89  self._main_windows = [self._container_manager.get_root_main_window()]
90  for container in self._container_manager.get_containers():
91  if container == self:
92  continue
93  self._main_windows.append(container.main_window)
94 
95  # unset local position when releasing button even when custom drag'n'drop
96  # has not been started
97  if self._dragging_local_pos is not None and \
98  e.type() == QEvent.MouseButtonRelease and \
99  e.button() == Qt.LeftButton \
101  self._dragging_local_pos = None
102 
103  if self._dragging_parent is not None and \
104  e.type() == QEvent.MouseButtonRelease and \
105  e.button() == Qt.LeftButton and \
107  qDebug('DockWidget._event() stop drag, dockwidget=%s, parent=%s\n' %
108  (self, self.parent()))
109  self._dragging_parent = None
110  self.setAttribute(Qt.WA_TransparentForMouseEvents, False)
111  self._main_windows = []
112 
113  if self._dragging_parent is not None and \
114  e.type() == QEvent.MouseMove and \
115  e.buttons() & Qt.LeftButton and \
117  widget = self._widget_at(e.globalPos())
118  new_parent = self._get_new_parent(widget)
119  # print 'new_parent', new_parent, (new_parent.objectName() if new_parent else '')
120  if new_parent is not None and new_parent != self.parent():
122 
123  # schedule stop of pseudo drag'n'drop and let it complete
124  mouse_release_event = QMouseEvent(
125  QEvent.MouseButtonRelease, self._dragging_local_pos,
126  e.globalPos(), Qt.LeftButton, Qt.NoButton, e.modifiers())
127  QApplication.instance().postEvent(self, mouse_release_event)
128  QApplication.sendPostedEvents()
129 
130  # schedule reparent to hovered main window and let it complete
131  reparent_event = ReparentEvent(self, new_parent)
132  QApplication.instance().postEvent(self._container_manager, reparent_event)
133  QApplication.sendPostedEvents()
134 
135  # reenable mouse events to be able to receive upcoming pseudo mouse events
136  self.setAttribute(Qt.WA_TransparentForMouseEvents, False)
137 
138  # schedule restart of pseudo drag'n'drop and let it complete
139  mouse_repress_event = QMouseEvent(
140  QEvent.MouseButtonPress, self._dragging_local_pos, e.globalPos(),
141  Qt.LeftButton, Qt.LeftButton, e.modifiers())
142  QApplication.instance().postEvent(self, mouse_repress_event)
143  QApplication.sendPostedEvents()
144 
145  # schedule move to trigger dock widget drag'n'drop required for snapping and
146  # showing rubber band and let it complete move forth...
147  mouse_move_event = QMouseEvent(
148  QEvent.MouseMove,
149  self._dragging_local_pos,
150  e.globalPos() + QPoint(QApplication.startDragDistance(), 1),
151  Qt.NoButton,
152  Qt.LeftButton,
153  e.modifiers())
154  QApplication.instance().postEvent(self, mouse_move_event)
155  QApplication.sendPostedEvents()
156  # ...and back
157  mouse_move_event = QMouseEvent(
158  QEvent.MouseMove, self._dragging_local_pos, e.globalPos(),
159  Qt.NoButton, Qt.LeftButton, e.modifiers())
160  QApplication.instance().postEvent(self, mouse_move_event)
161  QApplication.sendPostedEvents()
162 
163  # restore attributes after repressing the button
164  self.setAttribute(Qt.WA_TransparentForMouseEvents)
165 
167 
168  return super(DockWidget, self).event(e)
169 
170  def _get_new_parent(self, widget):
171  from .dock_widget_container import DockWidgetContainer
172  if isinstance(widget, DockWidgetContainer):
173  if widget.isFloating():
174  return None
175  widget = widget.parent()
176  while widget is not None:
177  if isinstance(widget, DockableMainWindow):
178  break
179  if not callable(widget.parent):
180  return None
181  widget = widget.parent()
182  return widget
183 
184  def _widget_at(self, global_point):
185  # print '_widget_at()', global_point#, local_point
186  widget = QApplication.widgetAt(global_point)
187  # print '- widget', widget, (widget.objectName() if widget is not None else '')
188  root_main_window = self._container_manager.get_root_main_window()
189 
190  # work around bug where root main window is detected when point is near but not inside it
191  if widget == root_main_window and \
192  not self._widget_contains(root_main_window, global_point):
193  # print '- work around to large root main window'
194  widget = None
195 
196  # work around bug where dock widget is floating and no widget is found
197  if widget is None and self.isFloating():
198  # determine all main windows which match the point
199  overlapping = {}
200  for main_window in self._main_windows:
201  if self._widget_contains(main_window, global_point):
202  parent = main_window.parent()
203  is_floating = parent is None or parent.isFloating()
204  overlapping[main_window] = is_floating
205  # print 'overlapping', overlapping
206 
207  if len(overlapping) == 1:
208  # only found one candidate so pick it
209  widget, _ = overlapping.popitem()
210  elif len(overlapping) > 1:
211  # try to determine which main window is the right one
212  # determined docked main windows
213  overlapping_docked = [mw for mw, floating in overlapping.items() if not floating]
214  # print 'overlapping_docked', overlapping_docked
215 
216  # if at max one of the main windows is floating
217  if len(overlapping_docked) >= len(overlapping) - 1:
218  # the floating main window is not considered because the docked ones are
219  # children of it
220 
221  # consider all docked main windows and remove parent if both parent and child
222  # are in the list print 'considered docked main windows', overlapping_docked
223  parents = []
224  for mw1 in overlapping_docked:
225  # parent of the main window is a dock widget container
226  parent = mw1.parent()
227  if parent is None:
228  continue
229  # parent of the dock widget container is a main window
230  parent = parent.parent()
231  if parent is None:
232  continue
233  for mw2 in overlapping_docked:
234  if mw2 == parent:
235  parents.append(mw2)
236  for parent in parents:
237  overlapping_docked.remove(parent)
238  # print 'considered non-parent main windows', overlapping_docked
239 
240  # if only one docked main window is found then pick it
241  if len(overlapping_docked) == 1:
242  # print '- pick single remaining main window'
243  widget = overlapping_docked[0]
244  else:
245  # print '- found multiple main windows - could not determine which one is
246  # on top'
247  pass
248  # TODO any more heuristic possible?
249  # if all remaining docked main windows have a most common ancestor use the
250  # order of children() to determine topmost
251  return widget
252 
253  def _widget_contains(self, widget, point):
254  topleft = widget.mapToGlobal(widget.mapFromParent(widget.geometry().topLeft()))
255  rect = QRect(topleft, widget.geometry().size())
256  return rect.contains(point)
257 
258  def save_settings(self, settings):
259  # print 'DockWidget.save_settings()', 'parent',
260  # self._parent_container_serial_number(), 'settings group',
261  # settings._group
262  settings.set_value('parent', self._parent_container_serial_number())
263 
264  title_bar = self.titleBarWidget()
265  title_bar.save_settings(settings)
266 
267  def restore_settings(self, settings):
268  serial_number = settings.value('parent', None)
269  # print 'DockWidget.restore_settings()', 'parent', serial_number,
270  # 'settings group', settings._group
271  if serial_number is not None:
272  serial_number = int(serial_number)
273  if self._parent_container_serial_number() != serial_number and \
274  self._container_manager is not None:
275  floating = self.isFloating()
276  pos = self.pos()
277  new_container = self._container_manager.get_container(serial_number)
278  if new_container is not None:
279  new_parent = new_container.main_window
280  else:
281  new_parent = self._container_manager.get_root_main_window()
282  area = self.parent().dockWidgetArea(self)
283  new_parent.addDockWidget(area, self)
284  if floating:
285  self.setFloating(floating)
286  self.move(pos)
287 
288  title_bar = self.titleBarWidget()
289  title_bar.restore_settings(settings)
290 
291  def _parent_container(self, dock_widget):
292  from .dock_widget_container import DockWidgetContainer
293  parent = dock_widget.parent()
294  if parent is not None:
295  parent = parent.parent()
296  if isinstance(parent, DockWidgetContainer):
297  return parent
298  return None
299 
301  serial_number = None
302  parent = self._parent_container(self)
303  if parent is not None:
304  serial_number = parent.serial_number()
305  return serial_number
def _parent_container_serial_number(self)
Definition: dock_widget.py:300
def _get_new_parent(self, widget)
Definition: dock_widget.py:170
def __init__(self, container_manager)
Definition: dock_widget.py:42
def _parent_container(self, dock_widget)
Definition: dock_widget.py:291
def restore_settings(self, settings)
Definition: dock_widget.py:267
def _widget_contains(self, widget, point)
Definition: dock_widget.py:253
def _widget_at(self, global_point)
Definition: dock_widget.py:184
def save_settings(self, settings)
Definition: dock_widget.py:258


qt_gui
Author(s): Dirk Thomas
autogenerated on Tue Apr 13 2021 03:03:12