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, QMainWindow, QMouseEvent
00033 
00034 from .reparent_event import ReparentEvent
00035 
00036 
00037 class DockWidget(QDockWidget):
00038 
00039     """Dock widget with the capability to be reparented via drag-and-drop to any other main window."""
00040 
00041     def __init__(self, container_manager):
00042         super(DockWidget, self).__init__()
00043         self._container_manager = container_manager
00044         if self._container_manager is not None:
00045             self.event = self._event
00046         self._dragging_parent = None
00047         self._dragging_local_pos = None
00048         self._releasing_and_repressing_while_dragging = False
00049         self._main_windows = []
00050 
00051     def _event(self, e):
00052         if e.type() == QEvent.MouseButtonPress and e.button() == Qt.LeftButton:
00053             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()))
00054         if e.type() == QEvent.MouseButtonRelease and e.button() == Qt.LeftButton:
00055             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()))
00056 
00057         # store local position when pressing button before starting the custom drag'n'drop
00058         if self._dragging_parent is None and e.type() == QEvent.MouseButtonPress and e.button() == Qt.LeftButton:
00059             self._dragging_local_pos = e.pos()
00060 
00061         if self._dragging_parent is None and self._dragging_local_pos is not None and e.type() == QEvent.Move and QApplication.mouseButtons() & Qt.LeftButton:
00062             if self._widget_at(e.pos()) is not None:
00063                 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)))
00064                 self._dragging_parent = self.parent()
00065                 # ignore further mouse events so that the widget behind this dock widget can be determined
00066                 self.setAttribute(Qt.WA_TransparentForMouseEvents)
00067 
00068                 # collect all main windows (except self.main_window) to re-implement QApplication.widgetAt() in self._widget_at()
00069                 self._main_windows = [self._container_manager.get_root_main_window()]
00070                 for container in self._container_manager.get_containers():
00071                     if container == self:
00072                         continue
00073                     self._main_windows.append(container.main_window)
00074 
00075         # unset local position when releasing button even when custom drag'n'drop has not been started
00076         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:
00077             self._dragging_local_pos = None
00078 
00079         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:
00080             qDebug('DockWidget._event() stop drag, dockwidget=%s, parent=%s\n' % (self, self.parent()))
00081             self._dragging_parent = None
00082             self.setAttribute(Qt.WA_TransparentForMouseEvents, False)
00083             self._main_windows = []
00084 
00085         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:
00086             widget = self._widget_at(e.globalPos())
00087             new_parent = self._get_new_parent(widget)
00088             #print 'new_parent', new_parent, (new_parent.objectName() if new_parent else '')
00089             if new_parent is not None and new_parent != self.parent():
00090                 self._releasing_and_repressing_while_dragging = True
00091 
00092                 # schedule stop of pseudo drag'n'drop and let it complete
00093                 mouse_release_event = QMouseEvent(QEvent.MouseButtonRelease, self._dragging_local_pos, e.globalPos(), Qt.LeftButton, Qt.NoButton, e.modifiers())
00094                 QApplication.instance().postEvent(self, mouse_release_event)
00095                 QApplication.sendPostedEvents()
00096 
00097                 # schedule reparent to hovered main window and let it complete
00098                 reparent_event = ReparentEvent(self, new_parent)
00099                 QApplication.instance().postEvent(self._container_manager, reparent_event)
00100                 QApplication.sendPostedEvents()
00101 
00102                 # reenable mouse events to be able to receive upcoming pseudo mouse events
00103                 self.setAttribute(Qt.WA_TransparentForMouseEvents, False)
00104 
00105                 # schedule restart of pseudo drag'n'drop and let it complete
00106                 mouse_repress_event = QMouseEvent(QEvent.MouseButtonPress, self._dragging_local_pos, e.globalPos(), Qt.LeftButton, Qt.LeftButton, e.modifiers())
00107                 QApplication.instance().postEvent(self, mouse_repress_event)
00108                 QApplication.sendPostedEvents()
00109 
00110                 # schedule move to trigger dock widget drag'n'drop required for snapping and showing rubber band and let it complete
00111                 # move forth...
00112                 mouse_move_event = QMouseEvent(QEvent.MouseMove, self._dragging_local_pos, e.globalPos() + QPoint(QApplication.startDragDistance(), 1), Qt.NoButton, Qt.LeftButton, e.modifiers())
00113                 QApplication.instance().postEvent(self, mouse_move_event)
00114                 QApplication.sendPostedEvents()
00115                 # ...and back
00116                 mouse_move_event = QMouseEvent(QEvent.MouseMove, self._dragging_local_pos, e.globalPos(), Qt.NoButton, Qt.LeftButton, e.modifiers())
00117                 QApplication.instance().postEvent(self, mouse_move_event)
00118                 QApplication.sendPostedEvents()
00119 
00120                 # restore attributes after repressing the button
00121                 self.setAttribute(Qt.WA_TransparentForMouseEvents)
00122 
00123                 self._releasing_and_repressing_while_dragging = False
00124 
00125         return super(DockWidget, self).event(e)
00126 
00127     def _get_new_parent(self, widget):
00128         from .dock_widget_container import DockWidgetContainer
00129         if isinstance(widget, DockWidgetContainer):
00130             if widget.isFloating():
00131                 return None
00132             widget = widget.parent()
00133         while widget is not None:
00134             if isinstance(widget, QMainWindow):
00135                 break
00136             widget = widget.parent()
00137         return widget
00138 
00139     def _widget_at(self, global_point):
00140         #print '_widget_at()', global_point#, local_point
00141         widget = QApplication.widgetAt(global_point)
00142         #print '- widget', widget, (widget.objectName() if widget is not None else '')
00143         root_main_window = self._container_manager.get_root_main_window()
00144 
00145         # work around bug where root main window is detected when point is near but not inside it
00146         if widget == root_main_window and not self._widget_contains(root_main_window, global_point):
00147             #print '- work around to large root main window'
00148             widget = None
00149 
00150         # work around bug where dock widget is floating and no widget is found
00151         if widget is None and self.isFloating():
00152             # determine all main windows which match the point
00153             overlapping = {}
00154             for main_window in self._main_windows:
00155                 if self._widget_contains(main_window, global_point):
00156                     parent = main_window.parent()
00157                     is_floating = parent is None or parent.isFloating()
00158                     overlapping[main_window] = is_floating
00159             #print 'overlapping', overlapping
00160 
00161             if len(overlapping) == 1:
00162                 # only found one candidate so pick it
00163                 widget, _ = overlapping.popitem()
00164             elif len(overlapping) > 1:
00165                 # try to determine which main window is the right one
00166                 # determined docked main windows
00167                 overlapping_docked = [mw for mw, floating in overlapping.iteritems() if not floating]
00168                 #print 'overlapping_docked', overlapping_docked
00169 
00170                 # if at max one of the main windows is floating
00171                 if len(overlapping_docked) >= len(overlapping) - 1:
00172                     # the floating main window is not considered because the docked ones are children of it
00173 
00174                     # consider all docked main windows and remove parent if both parent and child are in the list
00175                     #print 'considered docked main windows', overlapping_docked
00176                     parents = []
00177                     for mw1 in overlapping_docked:
00178                         # parent of the main window is a dock widget container
00179                         parent = mw1.parent()
00180                         if parent is None:
00181                             continue
00182                         # parent of the dock widget container is a main window
00183                         parent = parent.parent()
00184                         if parent is None:
00185                             continue
00186                         for mw2 in overlapping_docked:
00187                             if mw2 == parent:
00188                                 parents.append(mw2)
00189                     for parent in parents:
00190                         overlapping_docked.remove(parent)
00191                     #print 'considered non-parent main windows', overlapping_docked
00192 
00193                     # if only one docked main window is found then pick it
00194                     if len(overlapping_docked) == 1:
00195                         #print '- pick single remaining main window'
00196                         widget = overlapping_docked[0]
00197                     else:
00198                         #print '- found multiple main windows - could not determine which one is on top'
00199                         pass
00200                 # TODO any more heuristic possible?
00201                 # if all remaining docked main windows have a most common ancestor use the order of children() to determine topmost
00202         return widget
00203 
00204     def _widget_contains(self, widget, point):
00205         topleft = widget.mapToGlobal(widget.mapFromParent(widget.geometry().topLeft()))
00206         rect = QRect(topleft, widget.geometry().size())
00207         return rect.contains(point)
00208 
00209     def save_settings(self, settings):
00210         #print 'DockWidget.save_settings()', 'parent', self._parent_container_serial_number(), 'settings group', settings._group
00211         settings.set_value('parent', self._parent_container_serial_number())
00212 
00213         title_bar = self.titleBarWidget()
00214         title_bar.save_settings(settings)
00215 
00216     def restore_settings(self, settings):
00217         serial_number = settings.value('parent', None)
00218         #print 'DockWidget.restore_settings()', 'parent', serial_number, 'settings group', settings._group
00219         if serial_number is not None:
00220             serial_number = int(serial_number)
00221         if self._parent_container_serial_number() != serial_number and self._container_manager is not None:
00222             floating = self.isFloating()
00223             pos = self.pos()
00224             new_container = self._container_manager.get_container(serial_number)
00225             if new_container is not None:
00226                 new_parent = new_container.main_window
00227             else:
00228                 new_parent = self._container_manager.get_root_main_window()
00229             area = self.parent().dockWidgetArea(self)
00230             new_parent.addDockWidget(area, self)
00231             if floating:
00232                 self.setFloating(floating)
00233                 self.move(pos)
00234 
00235         title_bar = self.titleBarWidget()
00236         title_bar.restore_settings(settings)
00237 
00238     def _parent_container(self, dock_widget):
00239         from .dock_widget_container import DockWidgetContainer
00240         parent = dock_widget.parent()
00241         if parent is not None:
00242             parent = parent.parent()
00243             if isinstance(parent, DockWidgetContainer):
00244                 return parent
00245         return None
00246 
00247     def _parent_container_serial_number(self):
00248         serial_number = None
00249         parent = self._parent_container(self)
00250         if parent is not None:
00251             serial_number = parent.serial_number()
00252         return serial_number


qt_gui
Author(s): Dirk Thomas
autogenerated on Fri Jan 3 2014 11:44:00