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             widget = widget.parent()
00139         return widget
00140 
00141     def _widget_at(self, global_point):
00142         #print '_widget_at()', global_point#, local_point
00143         widget = QApplication.widgetAt(global_point)
00144         #print '- widget', widget, (widget.objectName() if widget is not None else '')
00145         root_main_window = self._container_manager.get_root_main_window()
00146 
00147         # work around bug where root main window is detected when point is near but not inside it
00148         if widget == root_main_window and not self._widget_contains(root_main_window, global_point):
00149             #print '- work around to large root main window'
00150             widget = None
00151 
00152         # work around bug where dock widget is floating and no widget is found
00153         if widget is None and self.isFloating():
00154             # determine all main windows which match the point
00155             overlapping = {}
00156             for main_window in self._main_windows:
00157                 if self._widget_contains(main_window, global_point):
00158                     parent = main_window.parent()
00159                     is_floating = parent is None or parent.isFloating()
00160                     overlapping[main_window] = is_floating
00161             #print 'overlapping', overlapping
00162 
00163             if len(overlapping) == 1:
00164                 # only found one candidate so pick it
00165                 widget, _ = overlapping.popitem()
00166             elif len(overlapping) > 1:
00167                 # try to determine which main window is the right one
00168                 # determined docked main windows
00169                 overlapping_docked = [mw for mw, floating in overlapping.iteritems() if not floating]
00170                 #print 'overlapping_docked', overlapping_docked
00171 
00172                 # if at max one of the main windows is floating
00173                 if len(overlapping_docked) >= len(overlapping) - 1:
00174                     # the floating main window is not considered because the docked ones are children of it
00175 
00176                     # consider all docked main windows and remove parent if both parent and child are in the list
00177                     #print 'considered docked main windows', overlapping_docked
00178                     parents = []
00179                     for mw1 in overlapping_docked:
00180                         # parent of the main window is a dock widget container
00181                         parent = mw1.parent()
00182                         if parent is None:
00183                             continue
00184                         # parent of the dock widget container is a main window
00185                         parent = parent.parent()
00186                         if parent is None:
00187                             continue
00188                         for mw2 in overlapping_docked:
00189                             if mw2 == parent:
00190                                 parents.append(mw2)
00191                     for parent in parents:
00192                         overlapping_docked.remove(parent)
00193                     #print 'considered non-parent main windows', overlapping_docked
00194 
00195                     # if only one docked main window is found then pick it
00196                     if len(overlapping_docked) == 1:
00197                         #print '- pick single remaining main window'
00198                         widget = overlapping_docked[0]
00199                     else:
00200                         #print '- found multiple main windows - could not determine which one is on top'
00201                         pass
00202                 # TODO any more heuristic possible?
00203                 # if all remaining docked main windows have a most common ancestor use the order of children() to determine topmost
00204         return widget
00205 
00206     def _widget_contains(self, widget, point):
00207         topleft = widget.mapToGlobal(widget.mapFromParent(widget.geometry().topLeft()))
00208         rect = QRect(topleft, widget.geometry().size())
00209         return rect.contains(point)
00210 
00211     def save_settings(self, settings):
00212         #print 'DockWidget.save_settings()', 'parent', self._parent_container_serial_number(), 'settings group', settings._group
00213         settings.set_value('parent', self._parent_container_serial_number())
00214 
00215         title_bar = self.titleBarWidget()
00216         title_bar.save_settings(settings)
00217 
00218     def restore_settings(self, settings):
00219         serial_number = settings.value('parent', None)
00220         #print 'DockWidget.restore_settings()', 'parent', serial_number, 'settings group', settings._group
00221         if serial_number is not None:
00222             serial_number = int(serial_number)
00223         if self._parent_container_serial_number() != serial_number and self._container_manager is not None:
00224             floating = self.isFloating()
00225             pos = self.pos()
00226             new_container = self._container_manager.get_container(serial_number)
00227             if new_container is not None:
00228                 new_parent = new_container.main_window
00229             else:
00230                 new_parent = self._container_manager.get_root_main_window()
00231             area = self.parent().dockWidgetArea(self)
00232             new_parent.addDockWidget(area, self)
00233             if floating:
00234                 self.setFloating(floating)
00235                 self.move(pos)
00236 
00237         title_bar = self.titleBarWidget()
00238         title_bar.restore_settings(settings)
00239 
00240     def _parent_container(self, dock_widget):
00241         from .dock_widget_container import DockWidgetContainer
00242         parent = dock_widget.parent()
00243         if parent is not None:
00244             parent = parent.parent()
00245             if isinstance(parent, DockWidgetContainer):
00246                 return parent
00247         return None
00248 
00249     def _parent_container_serial_number(self):
00250         serial_number = None
00251         parent = self._parent_container(self)
00252         if parent is not None:
00253             serial_number = parent.serial_number()
00254         return serial_number


qt_gui
Author(s): Dirk Thomas
autogenerated on Fri Aug 28 2015 12:15:40