dock_widget.py
Go to the documentation of this file.
00001 # Copyright (c) 2011, Dirk Thomas, Dorian Scholz, TU Darmstadt
00002 # All rights reserved.
00003 #
00004 # Redistribution and use in source and binary forms, with or without
00005 # modification, are permitted provided that the following conditions
00006 # are met:
00007 #
00008 #   * Redistributions of source code must retain the above copyright
00009 #     notice, this list of conditions and the following disclaimer.
00010 #   * Redistributions in binary form must reproduce the above
00011 #     copyright notice, this list of conditions and the following
00012 #     disclaimer in the documentation and/or other materials provided
00013 #     with the distribution.
00014 #   * Neither the name of the TU Darmstadt nor the names of its
00015 #     contributors may be used to endorse or promote products derived
00016 #     from this software without specific prior written permission.
00017 #
00018 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00019 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00020 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
00021 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
00022 # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
00023 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
00024 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
00025 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
00026 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
00027 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
00028 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
00029 # POSSIBILITY OF SUCH DAMAGE.
00030 
00031 from python_qt_binding.QtCore import qDebug, QEvent, QPoint, QRect, Qt
00032 from python_qt_binding.QtGui import QApplication, QDockWidget, QMouseEvent
00033 
00034 from .dockable_main_window import DockableMainWindow
00035 from .reparent_event import ReparentEvent
00036 
00037 
00038 class DockWidget(QDockWidget):
00039 
00040     """Dock widget with the capability to be reparented via drag-and-drop to any other main window."""
00041 
00042     def __init__(self, container_manager):
00043         super(DockWidget, self).__init__()
00044         self._container_manager = container_manager
00045         if self._container_manager is not None:
00046             self.event = self._event
00047         self._dragging_parent = None
00048         self._dragging_local_pos = None
00049         self._releasing_and_repressing_while_dragging = False
00050         self._main_windows = []
00051 
00052     def _event(self, e):
00053         if e.type() == QEvent.MouseButtonPress and e.button() == Qt.LeftButton:
00054             qDebug('%spress, rel=%s, global=%s, diff=%s' % ((' - pseudo ' if self._releasing_and_repressing_while_dragging else ''), e.pos(), e.globalPos(), e.globalPos() - self.pos()))
00055         if e.type() == QEvent.MouseButtonRelease and e.button() == Qt.LeftButton:
00056             qDebug('%srelease, rel=%s, global=%s, diff=%s' % ((' - pseudo ' if self._releasing_and_repressing_while_dragging else ''), e.pos(), e.globalPos(), e.globalPos() - self.pos()))
00057 
00058         # store local position when pressing button before starting the custom drag'n'drop
00059         # only allow when layout is not frozen
00060         if self._dragging_parent is None and e.type() == QEvent.MouseButtonPress and e.button() == Qt.LeftButton and bool(self.features() & QDockWidget.DockWidgetMovable):
00061             self._dragging_local_pos = e.pos()
00062 
00063         if self._dragging_parent is None and self._dragging_local_pos is not None and e.type() == QEvent.Move and QApplication.mouseButtons() & Qt.LeftButton:
00064             if self._widget_at(e.pos()) is not None:
00065                 qDebug('DockWidget._event() start drag, dockwidget=%s, parent=%s, floating=%s, pos=%s' % (str(self), str(self.parent()), str(self.isFloating()), str(self._dragging_local_pos)))
00066                 self._dragging_parent = self.parent()
00067                 # ignore further mouse events so that the widget behind this dock widget can be determined
00068                 self.setAttribute(Qt.WA_TransparentForMouseEvents)
00069 
00070                 # collect all main windows (except self.main_window) to re-implement QApplication.widgetAt() in self._widget_at()
00071                 self._main_windows = [self._container_manager.get_root_main_window()]
00072                 for container in self._container_manager.get_containers():
00073                     if container == self:
00074                         continue
00075                     self._main_windows.append(container.main_window)
00076 
00077         # unset local position when releasing button even when custom drag'n'drop has not been started
00078         if self._dragging_local_pos is not None and e.type() == QEvent.MouseButtonRelease and e.button() == Qt.LeftButton and not self._releasing_and_repressing_while_dragging:
00079             self._dragging_local_pos = None
00080 
00081         if self._dragging_parent is not None and e.type() == QEvent.MouseButtonRelease and e.button() == Qt.LeftButton and not self._releasing_and_repressing_while_dragging:
00082             qDebug('DockWidget._event() stop drag, dockwidget=%s, parent=%s\n' % (self, self.parent()))
00083             self._dragging_parent = None
00084             self.setAttribute(Qt.WA_TransparentForMouseEvents, False)
00085             self._main_windows = []
00086 
00087         if self._dragging_parent is not None and e.type() == QEvent.MouseMove and e.buttons() & Qt.LeftButton and not self._releasing_and_repressing_while_dragging:
00088             widget = self._widget_at(e.globalPos())
00089             new_parent = self._get_new_parent(widget)
00090             #print 'new_parent', new_parent, (new_parent.objectName() if new_parent else '')
00091             if new_parent is not None and new_parent != self.parent():
00092                 self._releasing_and_repressing_while_dragging = True
00093 
00094                 # schedule stop of pseudo drag'n'drop and let it complete
00095                 mouse_release_event = QMouseEvent(QEvent.MouseButtonRelease, self._dragging_local_pos, e.globalPos(), Qt.LeftButton, Qt.NoButton, e.modifiers())
00096                 QApplication.instance().postEvent(self, mouse_release_event)
00097                 QApplication.sendPostedEvents()
00098 
00099                 # schedule reparent to hovered main window and let it complete
00100                 reparent_event = ReparentEvent(self, new_parent)
00101                 QApplication.instance().postEvent(self._container_manager, reparent_event)
00102                 QApplication.sendPostedEvents()
00103 
00104                 # reenable mouse events to be able to receive upcoming pseudo mouse events
00105                 self.setAttribute(Qt.WA_TransparentForMouseEvents, False)
00106 
00107                 # schedule restart of pseudo drag'n'drop and let it complete
00108                 mouse_repress_event = QMouseEvent(QEvent.MouseButtonPress, self._dragging_local_pos, e.globalPos(), Qt.LeftButton, Qt.LeftButton, e.modifiers())
00109                 QApplication.instance().postEvent(self, mouse_repress_event)
00110                 QApplication.sendPostedEvents()
00111 
00112                 # schedule move to trigger dock widget drag'n'drop required for snapping and showing rubber band and let it complete
00113                 # move forth...
00114                 mouse_move_event = QMouseEvent(QEvent.MouseMove, self._dragging_local_pos, e.globalPos() + QPoint(QApplication.startDragDistance(), 1), Qt.NoButton, Qt.LeftButton, e.modifiers())
00115                 QApplication.instance().postEvent(self, mouse_move_event)
00116                 QApplication.sendPostedEvents()
00117                 # ...and back
00118                 mouse_move_event = QMouseEvent(QEvent.MouseMove, self._dragging_local_pos, e.globalPos(), Qt.NoButton, Qt.LeftButton, e.modifiers())
00119                 QApplication.instance().postEvent(self, mouse_move_event)
00120                 QApplication.sendPostedEvents()
00121 
00122                 # restore attributes after repressing the button
00123                 self.setAttribute(Qt.WA_TransparentForMouseEvents)
00124 
00125                 self._releasing_and_repressing_while_dragging = False
00126 
00127         return super(DockWidget, self).event(e)
00128 
00129     def _get_new_parent(self, widget):
00130         from .dock_widget_container import DockWidgetContainer
00131         if isinstance(widget, DockWidgetContainer):
00132             if widget.isFloating():
00133                 return None
00134             widget = widget.parent()
00135         while widget is not None:
00136             if isinstance(widget, DockableMainWindow):
00137                 break
00138             if not callable(widget.parent):
00139                 return None
00140             widget = widget.parent()
00141         return widget
00142 
00143     def _widget_at(self, global_point):
00144         #print '_widget_at()', global_point#, local_point
00145         widget = QApplication.widgetAt(global_point)
00146         #print '- widget', widget, (widget.objectName() if widget is not None else '')
00147         root_main_window = self._container_manager.get_root_main_window()
00148 
00149         # work around bug where root main window is detected when point is near but not inside it
00150         if widget == root_main_window and not self._widget_contains(root_main_window, global_point):
00151             #print '- work around to large root main window'
00152             widget = None
00153 
00154         # work around bug where dock widget is floating and no widget is found
00155         if widget is None and self.isFloating():
00156             # determine all main windows which match the point
00157             overlapping = {}
00158             for main_window in self._main_windows:
00159                 if self._widget_contains(main_window, global_point):
00160                     parent = main_window.parent()
00161                     is_floating = parent is None or parent.isFloating()
00162                     overlapping[main_window] = is_floating
00163             #print 'overlapping', overlapping
00164 
00165             if len(overlapping) == 1:
00166                 # only found one candidate so pick it
00167                 widget, _ = overlapping.popitem()
00168             elif len(overlapping) > 1:
00169                 # try to determine which main window is the right one
00170                 # determined docked main windows
00171                 overlapping_docked = [mw for mw, floating in overlapping.iteritems() if not floating]
00172                 #print 'overlapping_docked', overlapping_docked
00173 
00174                 # if at max one of the main windows is floating
00175                 if len(overlapping_docked) >= len(overlapping) - 1:
00176                     # the floating main window is not considered because the docked ones are children of it
00177 
00178                     # consider all docked main windows and remove parent if both parent and child are in the list
00179                     #print 'considered docked main windows', overlapping_docked
00180                     parents = []
00181                     for mw1 in overlapping_docked:
00182                         # parent of the main window is a dock widget container
00183                         parent = mw1.parent()
00184                         if parent is None:
00185                             continue
00186                         # parent of the dock widget container is a main window
00187                         parent = parent.parent()
00188                         if parent is None:
00189                             continue
00190                         for mw2 in overlapping_docked:
00191                             if mw2 == parent:
00192                                 parents.append(mw2)
00193                     for parent in parents:
00194                         overlapping_docked.remove(parent)
00195                     #print 'considered non-parent main windows', overlapping_docked
00196 
00197                     # if only one docked main window is found then pick it
00198                     if len(overlapping_docked) == 1:
00199                         #print '- pick single remaining main window'
00200                         widget = overlapping_docked[0]
00201                     else:
00202                         #print '- found multiple main windows - could not determine which one is on top'
00203                         pass
00204                 # TODO any more heuristic possible?
00205                 # if all remaining docked main windows have a most common ancestor use the order of children() to determine topmost
00206         return widget
00207 
00208     def _widget_contains(self, widget, point):
00209         topleft = widget.mapToGlobal(widget.mapFromParent(widget.geometry().topLeft()))
00210         rect = QRect(topleft, widget.geometry().size())
00211         return rect.contains(point)
00212 
00213     def save_settings(self, settings):
00214         #print 'DockWidget.save_settings()', 'parent', self._parent_container_serial_number(), 'settings group', settings._group
00215         settings.set_value('parent', self._parent_container_serial_number())
00216 
00217         title_bar = self.titleBarWidget()
00218         title_bar.save_settings(settings)
00219 
00220     def restore_settings(self, settings):
00221         serial_number = settings.value('parent', None)
00222         #print 'DockWidget.restore_settings()', 'parent', serial_number, 'settings group', settings._group
00223         if serial_number is not None:
00224             serial_number = int(serial_number)
00225         if self._parent_container_serial_number() != serial_number and self._container_manager is not None:
00226             floating = self.isFloating()
00227             pos = self.pos()
00228             new_container = self._container_manager.get_container(serial_number)
00229             if new_container is not None:
00230                 new_parent = new_container.main_window
00231             else:
00232                 new_parent = self._container_manager.get_root_main_window()
00233             area = self.parent().dockWidgetArea(self)
00234             new_parent.addDockWidget(area, self)
00235             if floating:
00236                 self.setFloating(floating)
00237                 self.move(pos)
00238 
00239         title_bar = self.titleBarWidget()
00240         title_bar.restore_settings(settings)
00241 
00242     def _parent_container(self, dock_widget):
00243         from .dock_widget_container import DockWidgetContainer
00244         parent = dock_widget.parent()
00245         if parent is not None:
00246             parent = parent.parent()
00247             if isinstance(parent, DockWidgetContainer):
00248                 return parent
00249         return None
00250 
00251     def _parent_container_serial_number(self):
00252         serial_number = None
00253         parent = self._parent_container(self)
00254         if parent is not None:
00255             serial_number = parent.serial_number()
00256         return serial_number


qt_gui
Author(s): Dirk Thomas
autogenerated on Thu Jun 6 2019 18:07:34