Ignore:
Location:
Orange/OrangeCanvas
Files:
1 added
10 edited

Legend:

Unmodified
Added
Removed
  • Orange/OrangeCanvas/application/canvasmain.py

    r11479 r11496  
    1616    QMainWindow, QWidget, QAction, QActionGroup, QMenu, QMenuBar, QDialog, 
    1717    QFileDialog, QMessageBox, QVBoxLayout, QSizePolicy, QColor, QKeySequence, 
    18     QIcon, QToolBar, QToolButton, QDockWidget, QDesktopServices, QApplication 
     18    QIcon, QToolBar, QToolButton, QDockWidget, QDesktopServices, QApplication, 
     19    QCursor 
    1920) 
    2021 
     
    4041from ..help import HelpManager 
    4142 
    42 from .canvastooldock import CanvasToolDock, QuickCategoryToolbar 
     43from .canvastooldock import CanvasToolDock, QuickCategoryToolbar, \ 
     44                            CategoryPopupMenu 
    4345from .aboutdialog import AboutDialog 
    4446from .schemeinfo import SchemeInfoDialog 
     
    769771        """ 
    770772        category = action.text() 
    771         for i in range(self.widgets_tool_box.count()): 
    772             cat_act = self.widgets_tool_box.tabAction(i) 
    773             if cat_act.text() == category: 
    774                 if not cat_act.isChecked(): 
    775                     # Trigger the action to expand the tool grid contained 
    776                     # within. 
    777                     cat_act.trigger() 
    778  
    779             else: 
    780                 if cat_act.isChecked(): 
    781                     # Trigger the action to hide the tool grid contained 
    782                     # within. 
    783                     cat_act.trigger() 
    784  
    785         self.dock_widget.expand() 
     773        if self.use_popover: 
     774            # Show a popup menu with the widgets in the category 
     775            m = CategoryPopupMenu(self.quick_category) 
     776            reg = self.widget_registry.model() 
     777            i = index(self.widget_registry.categories(), category, 
     778                      predicate=lambda name, cat: cat.name == name) 
     779            if i != -1: 
     780                m.setCategoryItem(reg.item(i)) 
     781                action = m.exec_(QCursor.pos()) 
     782                if action is not None: 
     783                    self.on_tool_box_widget_activated(action) 
     784 
     785        else: 
     786            for i in range(self.widgets_tool_box.count()): 
     787                cat_act = self.widgets_tool_box.tabAction(i) 
     788                cat_act.setChecked(cat_act.text() == category) 
     789 
     790            self.dock_widget.expand() 
    786791 
    787792    def set_scheme_margins_enabled(self, enabled): 
     
    964969        scheme_doc.setScheme(new_scheme) 
    965970 
    966         old_scheme.save_widget_settings() 
    967         old_scheme.close_all_open_widgets() 
    968         old_scheme.signal_manager.stop() 
     971        # Send a close event to the Scheme, it is responsible for 
     972        # closing/clearing all resources (widgets). 
     973        QApplication.sendEvent(old_scheme, QEvent(QEvent.Close)) 
     974 
    969975        old_scheme.deleteLater() 
    970976 
     
    15341540                return 
    15351541 
     1542        old_scheme = document.scheme() 
     1543 
    15361544        # Set an empty scheme to clear the document 
    15371545        document.setScheme(widgetsscheme.WidgetsScheme()) 
    15381546 
    1539         scheme = document.scheme() 
    1540         scheme.save_widget_settings() 
    1541         scheme.close_all_open_widgets() 
    1542         scheme.signal_manager.stop() 
    1543         scheme.deleteLater() 
     1547        QApplication.sendEvent(old_scheme, QEvent(QEvent.Close)) 
     1548 
     1549        old_scheme.deleteLater() 
    15441550 
    15451551        config.save_config() 
     
    17091715            triggers |= SchemeEditWidget.DoubleClicked 
    17101716 
    1711         left_click = settings.value("trigger-on-left-click", 
     1717        right_click = settings.value("trigger-on-right-click", 
    17121718                                    defaultValue=False, 
    17131719                                    type=bool) 
    1714         if left_click: 
    1715             triggers |= SchemeEditWidget.Clicked 
     1720        if right_click: 
     1721            triggers |= SchemeEditWidget.RightClicked 
    17161722 
    17171723        space_press = settings.value("trigger-on-space-key", 
     
    17771783        self.open_in_external_browser = \ 
    17781784            settings.value("open-in-external-browser", defaultValue=False, 
     1785                           type=bool) 
     1786 
     1787        self.use_popover = \ 
     1788            settings.value("toolbox-dock-use-popover-menu", defaultValue=True, 
    17791789                           type=bool) 
    17801790 
  • Orange/OrangeCanvas/application/canvastooldock.py

    r11243 r11495  
    33 
    44""" 
     5import sys 
     6 
    57from PyQt4.QtGui import ( 
    68    QWidget, QSplitter, QVBoxLayout, QTextEdit, QAction, QPalette, 
    7     QSizePolicy 
     9    QSizePolicy, QApplication, QDrag 
    810) 
    911 
    10 from PyQt4.QtCore import Qt, QSize, QObject, QPropertyAnimation, QEvent 
    11 from PyQt4.QtCore import pyqtProperty as Property 
     12from PyQt4.QtCore import ( 
     13    Qt, QSize, QObject, QPropertyAnimation, QEvent, QRect, 
     14    QModelIndex, QPersistentModelIndex, QEventLoop, QMimeData 
     15) 
     16 
     17from PyQt4.QtCore import pyqtProperty as Property, pyqtSignal as Signal 
    1218 
    1319from ..gui.toolgrid import ToolGrid 
    1420from ..gui.toolbar import DynamicResizeToolBar 
    1521from ..gui.quickhelp import QuickHelp 
     22from ..gui.framelesswindow import FramelessWindow 
     23from ..document.quickmenu import MenuPage 
    1624from .widgettoolbox import WidgetToolBox, iter_item 
    1725 
    1826from ..registry.qt import QtWidgetRegistry 
     27from ..utils.qtcompat import toPyObject 
    1928 
    2029 
     
    337346                action = self._gridSlots[index].action 
    338347                self.removeAction(action) 
     348 
     349 
     350class CategoryPopupMenu(FramelessWindow): 
     351    triggered = Signal(QAction) 
     352    hovered = Signal(QAction) 
     353 
     354    def __init__(self, parent=None, **kwargs): 
     355        FramelessWindow.__init__(self, parent, **kwargs) 
     356        self.setWindowFlags(self.windowFlags() | Qt.Popup) 
     357 
     358        layout = QVBoxLayout() 
     359        layout.setContentsMargins(6, 6, 6, 6) 
     360 
     361        self.__menu = MenuPage() 
     362        self.__menu.setActionRole(QtWidgetRegistry.WIDGET_ACTION_ROLE) 
     363 
     364        if sys.platform == "darwin": 
     365            self.__menu.view().setAttribute(Qt.WA_MacShowFocusRect, False) 
     366 
     367        self.__menu.triggered.connect(self.__onTriggered) 
     368        self.__menu.hovered.connect(self.hovered) 
     369 
     370        self.__dragListener = ItemViewDragStartEventListener(self) 
     371        self.__dragListener.dragStarted.connect(self.__onDragStarted) 
     372 
     373        self.__menu.view().viewport().installEventFilter(self.__dragListener) 
     374 
     375        layout.addWidget(self.__menu) 
     376 
     377        self.setLayout(layout) 
     378 
     379        self.__action = None 
     380        self.__loop = None 
     381        self.__item = None 
     382 
     383    def setCategoryItem(self, item): 
     384        """ 
     385        Set the category root item (:class:`QStandardItem`). 
     386        """ 
     387        self.__item = item 
     388        model = item.model() 
     389        self.__menu.setModel(model) 
     390        self.__menu.setRootIndex(item.index()) 
     391 
     392    def popup(self, pos=None): 
     393        if pos is None: 
     394            pos = self.pos() 
     395        geom = widget_popup_geometry(pos, self) 
     396        self.setGeometry(geom) 
     397        self.show() 
     398 
     399    def exec_(self, pos=None): 
     400        self.popup(pos) 
     401        self.__loop = QEventLoop() 
     402 
     403        self.__action = None 
     404        self.__loop.exec_() 
     405        self.__loop = None 
     406 
     407        if self.__action is not None: 
     408            action = self.__action 
     409        else: 
     410            action = None 
     411        return action 
     412 
     413    def hideEvent(self, event): 
     414        if self.__loop is not None: 
     415            self.__loop.exit(0) 
     416 
     417        return FramelessWindow.hideEvent(self, event) 
     418 
     419    def __onTriggered(self, action): 
     420        self.__action = action 
     421        self.triggered.emit(action) 
     422        self.hide() 
     423 
     424        if self.__loop: 
     425            self.__loop.exit(0) 
     426 
     427    def __onDragStarted(self, index): 
     428        desc = toPyObject(index.data(QtWidgetRegistry.WIDGET_DESC_ROLE)) 
     429        icon = toPyObject(index.data(Qt.DecorationRole)) 
     430 
     431        drag_data = QMimeData() 
     432        drag_data.setData( 
     433            "application/vnv.orange-canvas.registry.qualified-name", 
     434            desc.qualified_name 
     435        ) 
     436        drag = QDrag(self) 
     437        drag.setPixmap(icon.pixmap(38)) 
     438        drag.setMimeData(drag_data) 
     439 
     440        # TODO: Should animate (accept) hide. 
     441        self.hide() 
     442 
     443        # When a drag is started and the menu hidden the item's tool tip 
     444        # can still show for a short time UNDER the cursor preventing a 
     445        # drop. 
     446        viewport = self.__menu.view().viewport() 
     447        filter = ToolTipEventFilter() 
     448        viewport.installEventFilter(filter) 
     449 
     450        drag.exec_(Qt.CopyAction) 
     451 
     452        viewport.removeEventFilter(filter) 
     453 
     454 
     455class ItemViewDragStartEventListener(QObject): 
     456    dragStarted = Signal(QModelIndex) 
     457 
     458    def __init__(self, parent=None): 
     459        QObject.__init__(self, parent) 
     460        self._pos = None 
     461        self._index = None 
     462 
     463    def eventFilter(self, viewport, event): 
     464        view = viewport.parent() 
     465 
     466        if event.type() == QEvent.MouseButtonPress and \ 
     467                event.button() == Qt.LeftButton: 
     468 
     469            index = view.indexAt(event.pos()) 
     470 
     471            if index is not None: 
     472                self._pos = event.pos() 
     473                self._index = QPersistentModelIndex(index) 
     474 
     475        elif event.type() == QEvent.MouseMove and self._pos is not None and \ 
     476                ((self._pos - event.pos()).manhattanLength() >= 
     477                 QApplication.startDragDistance()): 
     478 
     479            if self._index.isValid(): 
     480                # Map to a QModelIndex in the model. 
     481                index = self._index 
     482                index = index.model().index(index.row(), index.column(), 
     483                                            index.parent()) 
     484                self._pos = None 
     485                self._index = None 
     486 
     487                self.dragStarted.emit(index) 
     488 
     489        return QObject.eventFilter(self, view, event) 
     490 
     491 
     492class ToolTipEventFilter(QObject): 
     493    def eventFilter(self, receiver, event): 
     494        if event.type() == QEvent.ToolTip: 
     495            return True 
     496 
     497        return QObject.eventFilter(self, receiver, event) 
     498 
     499 
     500def widget_popup_geometry(pos, widget): 
     501    widget.ensurePolished() 
     502 
     503    if widget.testAttribute(Qt.WA_Resized): 
     504        size = widget.size() 
     505    else: 
     506        size = widget.sizeHint() 
     507 
     508    desktop = QApplication.desktop() 
     509    screen_geom = desktop.availableGeometry(pos) 
     510 
     511    # Adjust the size to fit inside the screen. 
     512    if size.height() > screen_geom.height(): 
     513        size.setHeight(screen_geom.height()) 
     514    if size.width() > screen_geom.width(): 
     515        size.setWidth(screen_geom.width()) 
     516 
     517    geom = QRect(pos, size) 
     518 
     519    if geom.top() < screen_geom.top(): 
     520        geom.setTop(screen_geom.top()) 
     521 
     522    if geom.left() < screen_geom.left(): 
     523        geom.setLeft(screen_geom.left()) 
     524 
     525    bottom_margin = screen_geom.bottom() - geom.bottom() 
     526    right_margin = screen_geom.right() - geom.right() 
     527    if bottom_margin < 0: 
     528        # Falls over the bottom of the screen, move it up. 
     529        geom.translate(0, bottom_margin) 
     530 
     531    # TODO: right to left locale 
     532    if right_margin < 0: 
     533        # Falls over the right screen edge, move the menu to the 
     534        # other side of pos. 
     535        geom.translate(-size.width(), 0) 
     536 
     537    return geom 
  • Orange/OrangeCanvas/application/settings.py

    r11416 r11488  
    274274                                        "on an empty spot in the canvas")) 
    275275 
    276         cb2 = QCheckBox(self.tr("On left click"), 
    277                         toolTip=self.tr("Open quick menu on a left click " 
     276        cb2 = QCheckBox(self.tr("On right click"), 
     277                        toolTip=self.tr("Open quick menu on a right click " 
    278278                                        "on an empty spot in the canvas")) 
    279279 
     
    287287 
    288288        self.bind(cb1, "checked", "quickmenu/trigger-on-double-click") 
    289         self.bind(cb2, "checked", "quickmenu/trigger-on-left-click") 
     289        self.bind(cb2, "checked", "quickmenu/trigger-on-right-click") 
    290290        self.bind(cb3, "checked", "quickmenu/trigger-on-space-key") 
    291291        self.bind(cb4, "checked", "quickmenu/trigger-on-any-key") 
  • Orange/OrangeCanvas/config.py

    r11476 r11496  
    7171      "Is the canvas toolbox movable (between left and right edge)"), 
    7272 
     73     ("mainwindow/toolbox-dock-use-popover-menu", bool, True, 
     74      "Use a popover menu to select a widget when clicking on a category " 
     75      "button"), 
     76 
    7377     ("mainwindow/number-of-recent-schemes", int, 7, 
    7478      "Number of recent schemes to keep in history"), 
     
    8993      "Show quick menu on double click."), 
    9094 
    91      ("quickmenu/trigger-on-left-click", bool, False, 
    92       "Show quick menu on left click."), 
     95     ("quickmenu/trigger-on-right-click", bool, True, 
     96      "Show quick menu on right click."), 
    9397 
    9498     ("quickmenu/trigger-on-space-key", bool, True, 
  • Orange/OrangeCanvas/document/interactions.py

    r11450 r11490  
    760760            self.end() 
    761761 
    762     def create_new(self, pos): 
     762    def create_new(self, pos, search_text=""): 
    763763        """ 
    764764        Create a new widget with a `QuickMenu` at `pos` (in screen 
     
    769769        menu.setFilterFunc(None) 
    770770 
    771         action = menu.exec_(pos) 
     771        action = menu.exec_(pos, search_text) 
    772772        if action: 
    773773            item = action.property("item").toPyObject() 
  • Orange/OrangeCanvas/document/quickmenu.py

    r11462 r11494  
    2121    QStandardItemModel, QSortFilterProxyModel, QStyleOptionToolButton, 
    2222    QStylePainter, QStyle, QApplication, QStyledItemDelegate, 
    23     QStyleOptionViewItemV4, QSizeGrip, QKeySequence 
     23    QStyleOptionViewItemV4, QSizeGrip 
    2424) 
    2525 
     
    3535from ..gui.lineedit import LineEdit 
    3636from ..gui.tooltree import ToolTree, FlattenedTreeItemModel 
     37from ..gui.toolgrid import ToolButtonEventListener 
     38from ..gui.toolbox import create_tab_gradient 
    3739from ..gui.utils import StyledWidget_paintEvent 
    3840 
     
    4244 
    4345log = logging.getLogger(__name__) 
     46 
     47 
     48class _MenuItemDelegate(QStyledItemDelegate): 
     49    def __init__(self, parent=None): 
     50        QStyledItemDelegate.__init__(self, parent) 
     51 
     52    def sizeHint(self, option, index): 
     53        option = QStyleOptionViewItemV4(option) 
     54        self.initStyleOption(option, index) 
     55        size = QStyledItemDelegate.sizeHint(self, option, index) 
     56 
     57        # TODO: get the default QMenu item height from the current style. 
     58        size.setHeight(max(size.height(), 25)) 
     59        return size 
    4460 
    4561 
     
    6379        self.__icon = icon 
    6480 
     81        self.view().setItemDelegate(_MenuItemDelegate(self.view())) 
    6582        # Make sure the initial model is wrapped in a ItemDisableFilter. 
    6683        self.setModel(self.model()) 
     
    133150        proxyModel = self.view().model() 
    134151        return proxyModel.mapToSource(ToolTree.rootIndex(self)) 
     152 
     153    def sizeHint(self): 
     154        view = self.view() 
     155        hint = view.sizeHint() 
     156        model = view.model() 
     157 
     158        # This will not work for nested items (tree). 
     159        count = model.rowCount(view.rootIndex()) 
     160 
     161        width = view.sizeHintForColumn(0) 
     162 
     163        if count: 
     164            height = view.sizeHintForRow(0) 
     165            height = height * count 
     166        else: 
     167            height = hint.height() 
     168        return QSize(max(width, hint.width()), max(height, hint.height())) 
    135169 
    136170 
     
    293327        widget_hints = [default_size] 
    294328        for i in range(self.count()): 
    295             w = self.widget(i) 
    296             if isinstance(w, ToolTree): 
    297                 hint = self.__sizeHintForTreeView(w.view()) 
    298             else: 
    299                 hint = w.sizeHint() 
     329            hint = self.widget(i).sizeHint() 
    300330            widget_hints.append(hint) 
     331 
    301332        width = max([s.width() for s in widget_hints]) 
    302333        # Take the median for the height 
     
    341372 
    342373    def paintEvent(self, event): 
     374        opt = QStyleOptionToolButton() 
     375        self.initStyleOption(opt) 
     376        opt.features |= QStyleOptionToolButton.HasMenu 
    343377        if self.__flat: 
    344378            # Use default widget background/border styling. 
    345379            StyledWidget_paintEvent(self, event) 
    346380 
    347             opt = QStyleOptionToolButton() 
    348             self.initStyleOption(opt) 
    349381            p = QStylePainter(self) 
    350382            p.drawControl(QStyle.CE_ToolButtonLabel, opt) 
    351383        else: 
    352             QToolButton.paintEvent(self, event) 
    353  
     384            p = QStylePainter(self) 
     385            p.drawComplexControl(QStyle.CC_ToolButton, opt) 
     386 
     387    def sizeHint(self): 
     388        opt = QStyleOptionToolButton() 
     389        self.initStyleOption(opt) 
     390        opt.features |= QStyleOptionToolButton.HasMenu 
     391        style = self.style() 
     392 
     393        hint = style.sizeFromContents(QStyle.CT_ToolButton, opt, 
     394                                      opt.iconSize, self) 
     395        return hint 
    354396 
    355397_Tab = \ 
     
    364406 
    365407 
    366 # TODO: ..application.canvastooldock.QuickCategoryToolbar is very similar, 
    367 #       to TobBarWidget. Maybe common functionality could factored our. 
    368  
    369408class TabBarWidget(QWidget): 
    370409    """ 
     
    377416    def __init__(self, parent=None, **kwargs): 
    378417        QWidget.__init__(self, parent, **kwargs) 
    379         layout = QHBoxLayout() 
     418        layout = QVBoxLayout() 
    380419        layout.setContentsMargins(0, 0, 0, 0) 
    381420        layout.setSpacing(0) 
    382421        self.setLayout(layout) 
    383422 
    384         self.setSizePolicy(QSizePolicy.Expanding, 
    385                            QSizePolicy.Fixed) 
     423        self.setSizePolicy(QSizePolicy.Fixed, 
     424                           QSizePolicy.Expanding) 
    386425        self.__tabs = [] 
     426 
    387427        self.__currentIndex = -1 
     428        self.__changeOnHover = False 
     429 
     430        self.__iconSize = QSize(26, 26) 
     431 
    388432        self.__group = QButtonGroup(self, exclusive=True) 
    389433        self.__group.buttonPressed[QAbstractButton].connect( 
     
    391435        ) 
    392436 
     437        self.__hoverListener = ToolButtonEventListener(self) 
     438 
     439    def setChangeOnHover(self, changeOnHover): 
     440        """ 
     441        If set to ``True`` the tab widget will change the current index when 
     442        the mouse hovers over a tab button. 
     443 
     444        """ 
     445        if self.__changeOnHover != changeOnHover: 
     446            self.__changeOnHover = changeOnHover 
     447 
     448            if changeOnHover: 
     449                self.__hoverListener.buttonEnter.connect( 
     450                    self.__onButtonEnter 
     451                ) 
     452            else: 
     453                self.__hoverListener.buttonEnter.disconnect( 
     454                    self.__onButtonEnter 
     455                ) 
     456 
     457    def changeOnHover(self): 
     458        """ 
     459        Does the current tab index follow the mouse cursor. 
     460        """ 
     461        return self.__changeOnHover 
     462 
    393463    def count(self): 
    394464        """ 
     
    410480        button.setSizePolicy(QSizePolicy.Expanding, 
    411481                             QSizePolicy.Expanding) 
     482        button.setIconSize(self.__iconSize) 
    412483 
    413484        self.__group.addButton(button) 
     485 
     486        button.installEventFilter(self.__hoverListener) 
     487 
    414488        tab = _Tab(text, icon, toolTip, button, None, None) 
    415489        self.layout().insertWidget(index, button) 
     
    430504            tab = self.__tabs.pop(index) 
    431505            self.__group.removeButton(tab.button) 
     506 
     507            tab.button.removeEventFilter(self.__hoverListener) 
     508 
    432509            tab.button.deleteLater() 
    433510 
     
    490567        return self.__tabs[index].button 
    491568 
     569    def setIconSize(self, size): 
     570        if self.__iconSize != size: 
     571            self.__iconSize = size 
     572            for tab in self.__tabs: 
     573                tab.button.setIconSize(self.__iconSize) 
     574 
    492575    def __updateTab(self, index): 
    493576        """ 
     
    515598                break 
    516599 
     600    def __onButtonEnter(self, button): 
     601        if self.__changeOnHover: 
     602            button.click() 
     603 
    517604 
    518605class PagedMenu(QWidget): 
     
    531618        self.__currentIndex = -1 
    532619 
    533         layout = QVBoxLayout() 
     620        layout = QHBoxLayout() 
    534621        layout.setContentsMargins(0, 0, 0, 0) 
    535622        layout.setSpacing(0) 
    536623 
    537624        self.__tab = TabBarWidget(self) 
    538         self.__tab.setFixedHeight(25) 
    539625        self.__tab.currentChanged.connect(self.setCurrentIndex) 
     626        self.__tab.setChangeOnHover(True) 
    540627 
    541628        self.__stack = MenuStackWidget(self) 
    542629 
    543         layout.addWidget(self.__tab) 
     630        layout.addWidget(self.__tab, alignment=Qt.AlignTop) 
    544631        layout.addWidget(self.__stack) 
    545632 
     
    659746        self.layout().setContentsMargins(6, 6, 6, 6) 
    660747 
     748        self.__search = SearchWidget(self, objectName="search-line") 
     749 
     750        self.__search.setPlaceholderText( 
     751            self.tr("Search for widget or select from the list.") 
     752        ) 
     753 
     754        self.layout().addWidget(self.__search) 
     755 
    661756        self.__frame = QFrame(self, objectName="menu-frame") 
    662757        layout = QVBoxLayout() 
    663         layout.setContentsMargins(1, 1, 1, 1) 
     758        layout.setContentsMargins(0, 0, 0, 0) 
    664759        layout.setSpacing(2) 
    665760        self.__frame.setLayout(layout) 
     
    674769        self.__frame.layout().addWidget(self.__pages) 
    675770 
    676         self.__search = SearchWidget(self, objectName="search-line") 
    677  
    678         self.__search.setPlaceholderText( 
    679             self.tr("Search for widget or select from the list.") 
    680         ) 
    681  
    682         self.layout().addWidget(self.__search) 
    683771        self.setSizePolicy(QSizePolicy.Fixed, 
    684772                           QSizePolicy.Expanding) 
     
    691779            view = self.__suggestPage.view() 
    692780            view.verticalScrollBar().setAttribute(Qt.WA_MacMiniSize, True) 
    693             # Don't show the focus frame because it expands into the tab 
    694             # bar at the top. 
     781            # Don't show the focus frame because it expands into the tab bar. 
    695782            view.setAttribute(Qt.WA_MacShowFocusRect, False) 
    696783 
    697         self.addPage(self.tr("Quick Search"), self.__suggestPage) 
     784        i = self.addPage(self.tr("Quick Search"), self.__suggestPage) 
     785        button = self.__pages.tabButton(i) 
     786        button.setObjectName("search-tab-button") 
     787        button.setStyleSheet( 
     788            "TabButton {\n" 
     789            "    qproperty-flat_: false;\n" 
     790            "    border: none;" 
     791            "}\n") 
    698792 
    699793        self.__search.textEdited.connect(self.__on_textEdited) 
     
    756850        """ 
    757851        page = MenuPage(self) 
    758         view = page.view() 
    759         delegate = WidgetItemDelegate(view) 
    760         view.setItemDelegate(delegate) 
    761852 
    762853        page.setModel(index.model()) 
     
    797888            if brush.isValid(): 
    798889                brush = brush.toPyObject() 
     890                base_color = brush.color() 
    799891                button = self.__pages.tabButton(i) 
    800                 palette = button.palette() 
    801892                button.setStyleSheet( 
    802893                    "TabButton {\n" 
    803894                    "    qproperty-flat_: false;\n" 
    804                     "    background-color: %s;\n" 
     895                    "    background: %s;\n" 
    805896                    "    border: none;\n" 
     897                    "    border-bottom: 1px solid palette(dark);\n" 
    806898                    "}\n" 
    807899                    "TabButton:checked {\n" 
    808                     "    border: 1px solid %s;\n" 
    809                     "}" % (brush.color().name(), 
    810                            palette.color(palette.Mid).name()) 
     900                    "    background: %s\n" 
     901                    "}" % (create_css_gradient(base_color), 
     902                           create_css_gradient(base_color.darker(110))) 
    811903                ) 
    812904 
     
    823915                self.__pages.page(i).setFilterFunc(func) 
    824916 
    825     def popup(self, pos=None): 
    826         """ 
    827         Popup the menu at `pos` (in screen coordinates). 
     917    def popup(self, pos=None, searchText=""): 
     918        """ 
     919        Popup the menu at `pos` (in screen coordinates). 'Search' text field 
     920        is initialized with `searchText` if provided. 
    828921        """ 
    829922        if pos is None: 
    830923            pos = QPoint() 
    831924 
    832         self.__search.setText("") 
    833         self.__suggestPage.setFilterFixedString("") 
     925        self.__search.setText(searchText) 
     926        self.__suggestPage.setFilterFixedString(searchText) 
    834927 
    835928        self.ensurePolished() 
     
    873966        self.show() 
    874967 
    875     def exec_(self, pos=None): 
     968        if searchText: 
     969            self.setFocusProxy(self.__search) 
     970        else: 
     971            self.setFocusProxy(None) 
     972 
     973    def exec_(self, pos=None, searchText=""): 
    876974        """ 
    877975        Execute the menu at position `pos` (in global screen coordinates). 
    878976        Return the triggered :class:`QAction` or `None` if no action was 
    879         triggered. 
    880  
    881         """ 
    882         self.popup(pos) 
     977        triggered. 'Search' text field is initialized with `searchText` if 
     978        provided. 
     979 
     980        """ 
     981        self.popup(pos, searchText) 
    883982        self.setFocus(Qt.PopupFocusReason) 
    884983 
     
    9691068 
    9701069        return FramelessWindow.eventFilter(self, obj, event) 
    971  
    972  
    973 class WidgetItemDelegate(QStyledItemDelegate): 
    974     def __init__(self, parent=None): 
    975         QStyledItemDelegate.__init__(self, parent) 
    976  
    977     def sizeHint(self, option, index): 
    978         option = QStyleOptionViewItemV4(option) 
    979         self.initStyleOption(option, index) 
    980         size = QStyledItemDelegate.sizeHint(self, option, index) 
    981         size.setHeight(max(size.height(), 25)) 
    982         return size 
    9831070 
    9841071 
     
    11471234 
    11481235        self.move(x, y) 
     1236 
     1237 
     1238def create_css_gradient(base_color): 
     1239    """ 
     1240    Create a Qt css linear gradient fragment based on the `base_color`. 
     1241    """ 
     1242    grad = create_tab_gradient(base_color) 
     1243    stops = grad.stops() 
     1244    stops = "\n".join("    stop: {0:f} {1}".format(stop, color.name()) 
     1245                      for stop, color in stops) 
     1246    return ("qlineargradient(\n" 
     1247            "    x1: 0, y1: 0, x2: 0, y2: 1,\n" 
     1248            "{0})").format(stops) 
  • Orange/OrangeCanvas/document/schemeedit.py

    r11471 r11492  
    1010import logging 
    1111import itertools 
     12import unicodedata 
    1213 
    1314from operator import attrgetter 
     
    100101    # Quick Menu triggers 
    101102    (NoTriggers, 
    102      Clicked, 
     103     RightClicked, 
    103104     DoubleClicked, 
    104105     SpaceKey, 
     
    344345 
    345346        scene = CanvasScene() 
    346         scene.set_channel_names_visible(self.__channelNamesVisible) 
    347         scene.set_node_animation_enabled(self.__nodeAnimationEnabled) 
    348         scene.setFont(self.font()) 
     347        self.__setupScene(scene) 
    349348 
    350349        view = CanvasView(scene) 
     
    359358        self.__scene = scene 
    360359 
    361         self.__focusListener = GraphicsSceneFocusEventListener() 
    362         self.__focusListener.itemFocusedIn.connect(self.__onItemFocusedIn) 
    363         self.__focusListener.itemFocusedOut.connect(self.__onItemFocusedOut) 
    364         self.__scene.addItem(self.__focusListener) 
    365  
    366         self.__scene.selectionChanged.connect( 
    367             self.__onSelectionChanged 
    368         ) 
    369  
    370360        layout.addWidget(view) 
    371361        self.setLayout(layout) 
     362 
     363    def __setupScene(self, scene): 
     364        """ 
     365        Set up a :class:`CanvasScene` instance for use by the editor. 
     366 
     367        .. note:: If an existing scene is in use it must be teared down using 
     368            __teardownScene 
     369 
     370        """ 
     371        scene.set_channel_names_visible(self.__channelNamesVisible) 
     372        scene.set_node_animation_enabled( 
     373            self.__nodeAnimationEnabled 
     374        ) 
     375 
     376        scene.setFont(self.font()) 
     377 
     378        scene.installEventFilter(self) 
     379 
     380        scene.set_registry(self.__registry) 
     381 
     382        # Focus listener 
     383        self.__focusListener = GraphicsSceneFocusEventListener() 
     384        self.__focusListener.itemFocusedIn.connect( 
     385            self.__onItemFocusedIn 
     386        ) 
     387        self.__focusListener.itemFocusedOut.connect( 
     388            self.__onItemFocusedOut 
     389        ) 
     390        scene.addItem(self.__focusListener) 
     391 
     392        scene.selectionChanged.connect( 
     393            self.__onSelectionChanged 
     394        ) 
     395 
     396        scene.node_item_activated.connect( 
     397            self.__onNodeActivate 
     398        ) 
     399 
     400        scene.annotation_added.connect( 
     401            self.__onAnnotationAdded 
     402        ) 
     403 
     404        scene.annotation_removed.connect( 
     405            self.__onAnnotationRemoved 
     406        ) 
     407 
     408        self.__annotationGeomChanged = QSignalMapper(self) 
     409 
     410    def __teardownScene(self, scene): 
     411        """ 
     412        Tear down an instance of :class:`CanvasScene` that was used by the 
     413        editor. 
     414 
     415        """ 
     416        # Clear the current item selection in the scene so edit action 
     417        # states are updated accordingly. 
     418        scene.clearSelection() 
     419 
     420        # Clear focus from any item. 
     421        scene.setFocusItem(None) 
     422 
     423        # Clear the annotation mapper 
     424        self.__annotationGeomChanged.deleteLater() 
     425        self.__annotationGeomChanged = None 
     426 
     427        self.__focusListener.itemFocusedIn.disconnect( 
     428            self.__onItemFocusedIn 
     429        ) 
     430        self.__focusListener.itemFocusedOut.disconnect( 
     431            self.__onItemFocusedOut 
     432        ) 
     433 
     434        scene.selectionChanged.disconnect( 
     435            self.__onSelectionChanged 
     436        ) 
     437 
     438        scene.removeEventFilter(self) 
     439 
     440        # Clear all items from the scene 
     441        scene.blockSignals(True) 
     442        scene.clear_scene() 
    372443 
    373444    def toolbarActions(self): 
     
    440511 
    441512            - `SchemeEditWidget.NoTrigeres` 
    442             - `SchemeEditWidget.Clicked` 
     513            - `SchemeEditWidget.RightClicked` 
    443514            - `SchemeEditWidget.DoubleClicked` 
    444515            - `SchemeEditWidget.SpaceKey` 
     
    534605                self.__cleanSettings = [] 
    535606 
    536             # Clear the current item selection in the scene so edit action 
    537             # states are updated accordingly. 
    538             self.__scene.clearSelection() 
    539  
    540             self.__annotationGeomChanged.deleteLater() 
    541             self.__annotationGeomChanged = QSignalMapper(self) 
     607            self.__teardownScene(self.__scene) 
     608            self.__scene.deleteLater() 
    542609 
    543610            self.__undoStack.clear() 
    544611 
    545             self.__focusListener.itemFocusedIn.disconnect( 
    546                 self.__onItemFocusedIn 
    547             ) 
    548             self.__focusListener.itemFocusedOut.disconnect( 
    549                 self.__onItemFocusedOut 
    550             ) 
    551  
    552             self.__scene.selectionChanged.disconnect( 
    553                 self.__onSelectionChanged 
    554             ) 
    555  
    556             self.__scene.removeEventFilter(self) 
    557  
    558             # Clear all items from the scene 
    559             self.__scene.blockSignals(True) 
    560             self.__scene.clear_scene() 
    561  
    562             self.__scene.deleteLater() 
    563  
    564612            self.__scene = CanvasScene() 
     613            self.__setupScene(self.__scene) 
     614 
    565615            self.__view.setScene(self.__scene) 
    566             self.__scene.set_channel_names_visible(self.__channelNamesVisible) 
    567             self.__scene.set_node_animation_enabled( 
    568                 self.__nodeAnimationEnabled 
    569             ) 
    570  
    571             self.__scene.setFont(self.font()) 
    572  
    573             self.__scene.installEventFilter(self) 
    574  
    575             self.__scene.set_registry(self.__registry) 
    576  
    577             # Focus listener 
    578             self.__focusListener = GraphicsSceneFocusEventListener() 
    579             self.__focusListener.itemFocusedIn.connect( 
    580                 self.__onItemFocusedIn 
    581             ) 
    582             self.__focusListener.itemFocusedOut.connect( 
    583                 self.__onItemFocusedOut 
    584             ) 
    585             self.__scene.addItem(self.__focusListener) 
    586  
    587             self.__scene.selectionChanged.connect( 
    588                 self.__onSelectionChanged 
    589             ) 
    590  
    591             self.__scene.node_item_activated.connect( 
    592                 self.__onNodeActivate 
    593             ) 
    594  
    595             self.__scene.annotation_added.connect( 
    596                 self.__onAnnotationAdded 
    597             ) 
    598  
    599             self.__scene.annotation_removed.connect( 
    600                 self.__onAnnotationRemoved 
    601             ) 
    602616 
    603617            self.__scene.set_scheme(scheme) 
     
    974988 
    975989        any_item = scene.item_at(pos) 
     990        if not any_item: 
     991            self.__emptyClickButtons |= event.button() 
     992 
    976993        if not any_item and event.button() == Qt.LeftButton: 
    977             self.__emptyClickButtons |= Qt.LeftButton 
    978994            # Create a RectangleSelectionAction but do not set in on the scene 
    979995            # just yet (instead wait for the mouse move event). 
     
    10101026            handler = self.__possibleSelectionHandler 
    10111027            self._setUserInteractionHandler(handler) 
     1028            self.__possibleSelectionHandler = None 
    10121029            return handler.mouseMoveEvent(event) 
    10131030 
     
    10151032 
    10161033    def sceneMouseReleaseEvent(self, event): 
     1034        scene = self.__scene 
     1035        if scene.user_interaction_handler: 
     1036            return False 
     1037 
    10171038        if event.button() == Qt.LeftButton and self.__possibleMouseItemsMove: 
    10181039            self.__possibleMouseItemsMove = False 
     
    10471068                self.__itemsMoving.clear() 
    10481069                return True 
    1049  
    1050         if self.__emptyClickButtons & Qt.LeftButton and \ 
    1051                 event.button() & Qt.LeftButton: 
    1052             self.__emptyClickButtons &= ~Qt.LeftButton 
    1053  
    1054             if self.__quickMenuTriggers & SchemeEditWidget.Clicked and \ 
    1055                     mouse_drag_distance(event, Qt.LeftButton) < 1: 
    1056                 action = interactions.NewNodeAction(self) 
    1057  
    1058                 with nested(disabled(self.__undoAction), 
    1059                             disabled(self.__redoAction)): 
    1060                     action.create_new(event.screenPos()) 
    1061  
    1062                 event.accept() 
    1063                 return True 
     1070        elif event.button() == Qt.LeftButton: 
     1071            self.__possibleSelectionHandler = None 
    10641072 
    10651073        return False 
     
    11131121 
    11141122        handler = None 
     1123        searchText = "" 
    11151124        if (event.key() == Qt.Key_Space and \ 
    11161125                self.__quickMenuTriggers & SchemeEditWidget.SpaceKey): 
     
    11181127 
    11191128        elif len(event.text()) and \ 
    1120                 self.__quickMenuTriggers & SchemeEditWidget.AnyKey: 
     1129                self.__quickMenuTriggers & SchemeEditWidget.AnyKey and \ 
     1130                is_printable(unicode(event.text())[0]): 
    11211131            handler = interactions.NewNodeAction(self) 
     1132            searchText = unicode(event.text()) 
     1133 
    11221134            # TODO: set the search text to event.text() and set focus on the 
    11231135            # search line 
     
    11321144                        disabled(self.__undoAction), 
    11331145                        disabled(self.__redoAction)): 
    1134                 handler.create_new(QCursor.pos()) 
     1146                handler.create_new(QCursor.pos(), searchText) 
    11351147 
    11361148            event.accept() 
     
    14141426            return 
    14151427 
     1428        item = self.scene().item_at(scenePos) 
     1429        if not item and \ 
     1430                self.__quickMenuTriggers & SchemeEditWidget.RightClicked: 
     1431            action = interactions.NewNodeAction(self) 
     1432 
     1433            with nested(disabled(self.__undoAction), 
     1434                        disabled(self.__redoAction)): 
     1435                action.create_new(globalPos) 
     1436            return 
     1437 
    14161438    def __onRenameAction(self): 
    14171439        """ 
     
    15531575    for obj in objects: 
    15541576        obj.setEnabled(enable) 
     1577 
     1578 
     1579# All control character categories. 
     1580_control = set(["Cc", "Cf", "Cs", "Co", "Cn"]) 
     1581 
     1582 
     1583def is_printable(unichar): 
     1584    """ 
     1585    Return True if the unicode character `unichar` is a printable character. 
     1586    """ 
     1587    return unicodedata.category(unichar) not in _control 
  • Orange/OrangeCanvas/gui/tooltree.py

    r11370 r11493  
    5656 
    5757        view.activated.connect(self.__onActivated) 
    58         view.pressed.connect(self.__onPressed) 
     58        view.clicked.connect(self.__onActivated) 
    5959        view.entered.connect(self.__onEntered) 
    6060 
     
    144144                action.trigger() 
    145145                self.triggered.emit(action) 
    146  
    147     def __onPressed(self, index): 
    148         self.__onActivated(index) 
    149146 
    150147    def __onEntered(self, index): 
  • Orange/OrangeCanvas/scheme/widgetsscheme.py

    r11470 r11487  
    8484 
    8585        # Save settings to user global settings. 
    86         widget.saveSettings() 
     86        if not widget._settingsFromSchema: 
     87            widget.saveSettings() 
    8788 
    8889        # Notify the widget it will be deleted. 
     
    150151        return widget 
    151152 
    152     def close_all_open_widgets(self): 
    153         for widget in self.widget_for_node.values(): 
    154             widget.close() 
    155  
    156153    def widget_settings(self): 
    157154        """Return a list of dictionaries with widget settings. 
     
    159156        return [self.widget_for_node[node].getSettings(alsoContexts=False) 
    160157                for node in self.nodes] 
    161  
    162     def save_widget_settings(self): 
    163         """Save all widget settings to their global settings file. 
    164         """ 
    165         for node in self.nodes: 
    166             widget = self.widget_for_node[node] 
    167             widget.saveSettings() 
    168158 
    169159    def sync_node_properties(self): 
     
    189179        self.sync_node_properties() 
    190180        Scheme.save_to(self, stream, pretty, pickle_fallback) 
     181 
     182    def event(self, event): 
     183        """ 
     184        Reimplemented from `QObject.event`. 
     185 
     186        Responds to QEvent.Close event by stopping signal processing and 
     187        closing all widgets. 
     188 
     189        """ 
     190        if event.type() == QEvent.Close: 
     191            self.signal_manager.stop() 
     192 
     193            # Notify the widget instances. 
     194            for widget in self.widget_for_node.values(): 
     195                if not widget._settingsFromSchema: 
     196                    # First save global settings if necessary. 
     197                    widget.saveSettings() 
     198 
     199                widget.close() 
     200                widget.onDeleteWidget() 
     201 
     202            event.accept() 
     203            return True 
     204        else: 
     205            return Scheme.event(self, event) 
    191206 
    192207    def __on_help_request(self): 
  • Orange/OrangeCanvas/styles/orange.qss

    r11412 r11495  
    103103 
    104104 
    105  
    106 /* 
    107  *QuickCategoryToolbar _QuickCategoryButton { 
    108  *    qproperty-nativeStyling_: "true"; 
    109  *    background-color: palette(button); 
    110  *    border: none; 
    111  *    border-bottom: 1px solid palette(dark); 
    112  *} 
    113  */ 
     105/* QuickCategoryToolbar popup menus */ 
     106 
     107CategoryPopupMenu { 
     108    background-color: #E9EFF2; 
     109} 
     110 
     111CategoryPopupMenu ToolTree QTreeView::item { 
     112    height: 25px; 
     113    border-bottom: 1px solid #e9eff2; 
     114} 
     115 
     116CategoryPopupMenu QTreeView::item:hover { 
     117    background: qlineargradient( 
     118        x1: 0, y1: 0, x2: 0, y2: 1, 
     119        stop: 0 #688EF6, 
     120        stop: 0.5 #4047f4, 
     121        stop: 1.0 #2D68F3 
     122    ); 
     123    color: white; 
     124} 
     125 
     126CategoryPopupMenu QTreeView::item:selected { 
     127    background-color: blue; 
     128    color: white; 
     129} 
    114130 
    115131 
     
    359375 
    360376QuickMenu ToolTree QTreeView::item { 
    361     height: 25px; 
    362     border-top: 1px solid #e9eff2; 
    363     border-bottom: 1px solid #e9eff2; 
     377    height: 25px; 
     378    border-bottom: 1px solid #e9eff2; 
    364379} 
    365380 
    366381QuickMenu QTreeView::item:hover { 
    367     background-color: lightblue; 
    368     color: white; 
     382    background: qlineargradient( 
     383        x1: 0, y1: 0, x2: 0, y2: 1, 
     384        stop: 0 #688EF6, 
     385        stop: 0.5 #4047f4, 
     386        stop: 1.0 #2D68F3 
     387    ); 
     388    color: white; 
    369389} 
    370390 
    371391QuickMenu QTreeView::item:selected { 
    372     background-color: blue; 
    373     color: white; 
    374 } 
    375  
    376 /* Quick Menu search line edit  
     392    background-color: blue; 
     393    color: white; 
     394} 
     395 
     396QuickMenu TabBarWidget QToolButton { 
     397    width: 33px; 
     398    height: 25px; 
     399    border-bottom: 1px solid palette(dark); 
     400    padding-right: 5px; 
     401} 
     402 
     403QuickMenu TabBarWidget QToolButton#search-tab-button { 
     404    background-color: #9CACB4; 
     405} 
     406 
     407QuickMenu TabBarWidget QToolButton:menu-indicator { 
     408    image: url(canvas_icons:/arrow-right.svg); 
     409    subcontrol-position: center right; 
     410    height: 8px; 
     411    width: 8px; 
     412} 
     413 
     414/* Quick Menu search line edit 
    377415 */ 
    378416 
Note: See TracChangeset for help on using the changeset viewer.