Ignore:
Location:
Orange/OrangeCanvas
Files:
7 edited

Legend:

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

    r11446 r11469  
    223223 
    224224        self.scheme_widget = SchemeEditWidget() 
    225         self.scheme_widget.setScheme(widgetsscheme.WidgetsScheme()) 
     225        self.scheme_widget.setScheme(widgetsscheme.WidgetsScheme(parent=self)) 
    226226 
    227227        w.layout().addWidget(self.scheme_widget) 
     
    810810                return QDialog.Rejected 
    811811 
    812         new_scheme = widgetsscheme.WidgetsScheme() 
     812        new_scheme = widgetsscheme.WidgetsScheme(parent=self) 
    813813 
    814814        settings = QSettings() 
     
    884884 
    885885        """ 
    886         new_scheme = widgetsscheme.WidgetsScheme() 
     886        new_scheme = widgetsscheme.WidgetsScheme(parent=self) 
    887887        errors = [] 
    888888        try: 
     
    940940        manager = new_scheme.signal_manager 
    941941        if self.freeze_action.isChecked(): 
    942             manager.freeze().push() 
     942            manager.pause() 
    943943 
    944944        scheme_doc.setScheme(new_scheme) 
     
    946946        old_scheme.save_widget_settings() 
    947947        old_scheme.close_all_open_widgets() 
    948  
     948        old_scheme.signal_manager.stop() 
    949949        old_scheme.deleteLater() 
    950950 
     
    13211321    def set_signal_freeze(self, freeze): 
    13221322        scheme = self.current_document().scheme() 
     1323        manager = scheme.signal_manager 
    13231324        if freeze: 
    1324             scheme.signal_manager.freeze().push() 
     1325            manager.pause() 
    13251326        else: 
    1326             scheme.signal_manager.freeze().pop() 
     1327            manager.resume() 
    13271328 
    13281329    def remove_selected(self): 
     
    13341335        """Quit the application. 
    13351336        """ 
    1336         self.close() 
     1337        if QApplication.activePopupWidget(): 
     1338            # On OSX the actions in the global menu bar are triggered 
     1339            # even if an popup widget is running it's own event loop 
     1340            # (in exec_) 
     1341            log.debug("Ignoring a quit shortcut during an active " 
     1342                      "popup dialog.") 
     1343        else: 
     1344            self.close() 
    13371345 
    13381346    def select_all(self): 
     
    14831491                return 
    14841492 
     1493        # Set an empty scheme to clear the document 
     1494        document.setScheme(widgetsscheme.WidgetsScheme()) 
     1495 
    14851496        scheme = document.scheme() 
    14861497        scheme.save_widget_settings() 
    14871498        scheme.close_all_open_widgets() 
    1488  
    1489         # Set an empty scheme to clear the document 
    1490         document.setScheme(widgetsscheme.WidgetsScheme()) 
    1491         document.deleteLater() 
     1499        scheme.signal_manager.stop() 
     1500        scheme.deleteLater() 
    14921501 
    14931502        config.save_config() 
  • Orange/OrangeCanvas/canvas/layout.py

    r11207 r11463  
    99import sip 
    1010 
    11 from PyQt4.QtGui import QGraphicsObject 
    12 from PyQt4.QtCore import QRectF, QLineF, QTimer 
     11from PyQt4.QtGui import QGraphicsObject, QApplication 
     12from PyQt4.QtCore import QRectF, QLineF, QEvent 
    1313 
    1414from .items import NodeItem, LinkItem, SourceAnchorItem, SinkAnchorItem 
     
    135135        if self.isEnabled() and not self.__layoutPending: 
    136136            self.__layoutPending = True 
    137             QTimer.singleShot(0, self.__delayedActivate) 
     137            QApplication.postEvent(self, QEvent(QEvent.LayoutRequest)) 
    138138 
    139139    def __delayedActivate(self): 
    140140        if self.__layoutPending: 
    141141            self.activate() 
     142 
     143    def event(self, event): 
     144        if event.type() == QEvent.LayoutRequest: 
     145            self.activate() 
     146            return True 
     147 
     148        return QGraphicsObject.event(self, event) 
    142149 
    143150 
  • Orange/OrangeCanvas/canvas/scene.py

    r11442 r11464  
    127127        Clear (reset) the scene. 
    128128        """ 
     129        if self.scheme is not None: 
     130            self.scheme.node_added.disconnect(self.add_node) 
     131            self.scheme.node_removed.disconnect(self.remove_node) 
     132 
     133            self.scheme.link_added.disconnect(self.add_link) 
     134            self.scheme.link_removed.disconnect(self.remove_link) 
     135 
     136            self.scheme.annotation_added.disconnect(self.add_annotation) 
     137            self.scheme.annotation_removed.disconnect(self.remove_annotation) 
     138 
     139            self.scheme.node_state_changed.disconnect( 
     140                self.on_widget_state_change 
     141            ) 
     142            self.scheme.channel_state_changed.disconnect( 
     143                self.on_link_state_change 
     144            ) 
     145 
     146            # Remove all items to make sure all signals from scheme items 
     147            # to canvas items are disconnected. 
     148 
     149            for annot in self.scheme.annotations: 
     150                if annot in self.__item_for_annotation: 
     151                    self.remove_annotation(annot) 
     152 
     153            for link in self.scheme.links: 
     154                if link in self.__item_for_link: 
     155                    self.remove_link(link) 
     156 
     157            for node in self.scheme.nodes: 
     158                if node in self.__item_for_node: 
     159                    self.remove_node(node) 
     160 
    129161        self.scheme = None 
    130162        self.__node_items = [] 
     
    155187        if self.scheme is not None: 
    156188            # Clear the old scheme 
    157             self.scheme.node_added.disconnect(self.add_node) 
    158             self.scheme.node_removed.disconnect(self.remove_node) 
    159  
    160             self.scheme.link_added.disconnect(self.add_link) 
    161             self.scheme.link_removed.disconnect(self.remove_link) 
    162  
    163             self.scheme.annotation_added.disconnect(self.add_annotation) 
    164             self.scheme.annotation_removed.disconnect(self.remove_annotation) 
    165  
    166             self.scheme.node_state_changed.disconnect( 
    167                 self.on_widget_state_change 
    168             ) 
    169             self.scheme.channel_state_changed.disconnect( 
    170                 self.on_link_state_change 
    171             ) 
    172  
    173189            self.clear_scene() 
    174190 
     
    490506        item = self.__item_for_link.pop(scheme_link) 
    491507        scheme_link.enabled_changed.disconnect(item.setEnabled) 
     508 
     509        if scheme_link.is_dynamic(): 
     510            scheme_link.dynamic_enabled_changed.disconnect( 
     511                item.setDynamicEnabled 
     512            ) 
     513 
    492514        self.remove_link_item(item) 
    493515 
  • Orange/OrangeCanvas/document/quickmenu.py

    r11420 r11462  
    2121    QStandardItemModel, QSortFilterProxyModel, QStyleOptionToolButton, 
    2222    QStylePainter, QStyle, QApplication, QStyledItemDelegate, 
    23     QStyleOptionViewItemV4, QSizeGrip 
     23    QStyleOptionViewItemV4, QSizeGrip, QKeySequence 
    2424) 
    2525 
     
    948948        FramelessWindow.keyPressEvent(self, event) 
    949949        event.accept() 
     950 
     951    def event(self, event): 
     952        if event.type() == QEvent.ShortcutOverride: 
     953            log.debug("Overriding shortcuts") 
     954            event.accept() 
     955            return True 
     956        return FramelessWindow.event(self, event) 
    950957 
    951958    def eventFilter(self, obj, event): 
  • Orange/OrangeCanvas/document/schemeedit.py

    r11451 r11471  
    554554            ) 
    555555 
    556             self.__scene.clear() 
    557556            self.__scene.removeEventFilter(self) 
     557 
     558            # Clear all items from the scene 
     559            self.__scene.blockSignals(True) 
     560            self.__scene.clear_scene() 
     561 
    558562            self.__scene.deleteLater() 
    559563 
  • Orange/OrangeCanvas/scheme/signalmanager.py

    r11269 r11467  
    1010 
    1111import logging 
    12  
    13 from collections import namedtuple, defaultdict 
     12import itertools 
     13 
     14from collections import namedtuple, defaultdict, deque 
    1415from operator import attrgetter, add 
     16from functools import partial 
    1517 
    1618 
     
    171173        # NOTE: This does not remove output signals this node. In particular 
    172174        # the final 'None' values might be left on the queue. 
    173         log.info("Node %r removed. Removing pending signals.") 
     175        log.info("Node %r removed. Removing pending signals.", 
     176                 node.title) 
    174177        self.remove_pending_signals(node) 
    175178 
     
    296299 
    297300        log.info("Processing queued signals") 
    298         scheme = self.scheme() 
    299  
    300         blocking_nodes = set(self.blocking_nodes()) 
    301  
    302         blocked_nodes = reduce(set.union, 
    303                                map(scheme.downstream_nodes, blocking_nodes), 
    304                                set(blocking_nodes)) 
    305  
    306         pending = set(self.pending_nodes()) 
    307  
    308         pending_downstream = reduce(set.union, 
    309                                     map(scheme.downstream_nodes, pending), 
    310                                     set()) 
    311  
    312         log.debug("Pending nodes: %s", pending) 
    313         log.debug("Blocking nodes: %s", blocking_nodes) 
    314  
    315         # nodes on the update front (they have no ancestor which is 
    316         # either scheduled for update or is in blocking state) 
    317         node_update_front = list(pending - pending_downstream - blocked_nodes) 
     301 
     302        node_update_front = self.node_update_front() 
    318303 
    319304        if max_nodes is not None: 
     
    325310        self._set_runtime_state(SignalManager.Processing) 
    326311        try: 
     312            # TODO: What if the update front changes in the loop? 
    327313            for node in node_update_front: 
    328314                self.process_node(node) 
     
    411397        return False 
    412398 
     399    def node_update_front(self): 
     400        """ 
     401        Return a list of nodes on the update front, i.e. nodes scheduled for 
     402        an update that have no ancestor which is either itself scheduled 
     403        for update or is in a blocking state) 
     404 
     405        .. note:: 
     406            The node's ancestors are only computed over enabled links. 
     407 
     408        """ 
     409        scheme = self.scheme() 
     410 
     411        blocking_nodes = set(self.blocking_nodes()) 
     412 
     413        dependents = partial(dependent_nodes, scheme) 
     414 
     415        blocked_nodes = reduce(set.union, 
     416                               map(dependents, blocking_nodes), 
     417                               set(blocking_nodes)) 
     418 
     419        pending = set(self.pending_nodes()) 
     420 
     421        pending_downstream = reduce(set.union, 
     422                                    map(dependents, pending), 
     423                                    set()) 
     424 
     425        log.debug("Pending nodes: %s", pending) 
     426        log.debug("Blocking nodes: %s", blocking_nodes) 
     427 
     428        return list(pending - pending_downstream - blocked_nodes) 
     429 
    413430    def event(self, event): 
    414431        if event.type() == QEvent.UpdateRequest: 
    415432            if not self.__state == SignalManager.Running: 
    416                 log.debug("Received UpdateRequest event while not " 
     433                log.debug("Received 'UpdateRequest' event while not " 
    417434                          "in 'Running' state") 
    418435                event.setAccepted(False) 
     
    420437 
    421438            if self.__runtime_state == SignalManager.Processing: 
    422                 log.debug("received UpdateRequest event while in " 
     439                log.debug("Received 'UpdateRequest' event while in " 
    423440                          "'process_queued'") 
    424441                # This happens if someone calls QCoreApplication.processEvents 
     
    428445                return True 
    429446 
    430             log.debug("UpdateRequest event, queued signals: %i", 
     447            log.info("'UpdateRequest' event, queued signals: %i", 
    431448                      len(self._input_queue)) 
    432449            if self._input_queue: 
    433                 self.process_queued() 
     450                self.process_queued(max_nodes=1) 
    434451            event.accept() 
    435452 
    436453            if self.__reschedule: 
    437                 log.debug("Rescheduling UpdateRequest event") 
     454                log.debug("Rescheduling 'UpdateRequest' event") 
    438455                self._update() 
    439456                self.__reschedule = False 
     457            elif self.node_update_front(): 
     458                log.debug("More nodes are eligible for an update. " 
     459                          "Scheduling another update.") 
     460                self._update() 
    440461 
    441462            return True 
     
    476497 
    477498    return list(reversed(signals)) 
     499 
     500 
     501def dependent_nodes(scheme, node): 
     502    """ 
     503    Return a list of all nodes (in breadth first order) in `scheme` that 
     504    are dependent on `node`, 
     505 
     506    .. note:: 
     507        This does not include nodes only reachable by disables links. 
     508 
     509    """ 
     510    def expand(node): 
     511        return [link.sink_node 
     512                for link in scheme.find_links(source_node=node) 
     513                if link.enabled] 
     514 
     515    nodes = list(traverse_bf(node, expand)) 
     516    assert nodes[0] is node 
     517    # Remove the first item (`node`). 
     518    return nodes[1:] 
     519 
     520 
     521def traverse_bf(start, expand): 
     522    queue = deque([start]) 
     523    visited = set() 
     524    while queue: 
     525        item = queue.popleft() 
     526        if item not in visited: 
     527            yield item 
     528            visited.add(item) 
     529            queue.extend(expand(item)) 
    478530 
    479531 
  • Orange/OrangeCanvas/scheme/widgetsscheme.py

    r11411 r11470  
    215215        SignalManager.__init__(self, scheme) 
    216216 
     217        scheme.installEventFilter(self) 
    217218        # We keep a mapping from node->widget after the node/widget has been 
    218219        # removed from the scheme until we also process all the outgoing signal 
    219220        # updates. The reason is the old OWBaseWidget's MULTI channel protocol 
    220221        # where the actual source widget instance is passed to the signal 
    221         # handler, and in the delaeyd update the mapping in `scheme()` is no 
     222        # handler, and in the delayed update the mapping in `scheme()` is no 
    222223        # longer available. 
    223224        self._widget_backup = {} 
    224  
     225        self._widgets_to_delete = set() 
     226        self._active_node = None 
    225227        self.freezing = 0 
     228 
     229        self.__scheme_deleted = False 
     230        scheme.destroyed.connect(self.__on_scheme_destroyed) 
    226231 
    227232    def on_node_removed(self, node): 
    228233        widget = self.scheme().widget_for_node[node] 
     234 
     235        assert not self.scheme().find_links(sink_node=node), \ 
     236            "Node removed but still has input links" 
     237 
     238        signals = self.compress_signals(self.pending_input_signals(node)) 
     239        if not all(signal.value is None for signal in signals): 
     240            log.error("Non 'None' signals pending for a removed node %r", 
     241                         node.title) 
     242 
    229243        SignalManager.on_node_removed(self, node) 
    230244 
     245        if self.runtime_state() == SignalManager.Processing and \ 
     246                node is self._active_node or self.is_blocking(node): 
     247            # Delay the widget delete until it finishes. 
     248            # Keep a reference to the widget and install a filter. 
     249            self._widgets_to_delete.add(widget) 
     250            widget.installEventFilter(self) 
     251 
    231252        # Store the node->widget mapping for possible delayed signal id. 
    232         # It will be removes in `process_queued` when all signals 
     253        # It will be removed in `process_queued` when all signals 
    233254        # originating from this widget are delivered. 
    234255        self._widget_backup[node] = widget 
     
    239260        """ 
    240261        scheme = self.scheme() 
     262 
     263        if widget not in scheme.node_for_widget: 
     264            # The Node/Widget was already removed from the scheme 
     265            return 
     266 
    241267        node = scheme.node_for_widget[widget] 
    242268 
     
    259285 
    260286        """ 
    261         widget = self.scheme().widget_for_node[node] 
     287        if node in self.scheme().widget_for_node: 
     288            widget = self.scheme().widget_for_node[node] 
     289        else: 
     290            widget = self._widget_backup[node] 
     291 
     292        self._active_node = node 
    262293        self.process_signals_for_widget(node, widget, signals) 
     294        self._active_node = None 
     295 
     296        if widget in self._widgets_to_delete: 
     297            # If this node/widget was removed during the 
     298            # 'process_signals_for_widget' 
     299            self._widgets_to_delete.remove(widget) 
     300            widget.deleteLater() 
    263301 
    264302    def compress_signals(self, signals): 
     
    354392 
    355393        if widget.processingHandler: 
    356             widget.processingHandler(self, 0) 
     394            widget.processingHandler(widget, 0) 
    357395 
    358396    def scheduleSignalProcessing(self, widget=None): 
     
    480518                return False 
    481519 
     520            if self.__scheme_deleted: 
     521                log.debug("Scheme has been/is being deleted. No more " 
     522                          "signals will be delivered to any nodes.") 
     523                event.setAccepted(True) 
     524                return True 
     525        # Retain a reference to the scheme until the 'process_queued' finishes 
     526        # in SignalManager.event. 
     527        scheme = self.scheme() 
    482528        return SignalManager.event(self, event) 
     529 
     530    def eventFilter(self, receiver, event): 
     531        if receiver is self.scheme() and event.type() == QEvent.DeferredDelete: 
     532            if self.runtime_state() == SignalManager.Processing: 
     533                log.info("Deferring a 'DeferredDelete' event for the Scheme " 
     534                         "instance until SignalManager exits the current " 
     535                         "update loop.") 
     536                event.setAccepted(False) 
     537                self.processingFinished.connect(self.scheme().deleteLater) 
     538                self.__scheme_deleted = True 
     539                return True 
     540        elif receiver in self._widgets_to_delete and \ 
     541                event.type() == QEvent.DeferredDelete: 
     542            if self._widget_backup.get(self._active_node, None) is receiver: 
     543                # The widget is still being updated. We need to keep it alive, 
     544                # it will be deleted in `send_to_node`. 
     545                log.info("Deferring a 'DeferredDelete' until widget exits " 
     546                         "the 'process_signals_for_widget'.") 
     547                event.setAccepted(False) 
     548                return True 
     549 
     550        return SignalManager.eventFilter(self, receiver, event) 
     551 
     552    def __on_scheme_destroyed(self, obj): 
     553        self.__scheme_deleted = True 
    483554 
    484555 
    485556class SignalLink(object): 
    486557    """ 
    487     Back compatiblity with old orngSignalManager, do not use. 
     558    Back compatibility with old orngSignalManager, do not use. 
    488559    """ 
    489560    def __init__(self, widgetFrom, outputSignal, widgetTo, inputSignal, 
Note: See TracChangeset for help on using the changeset viewer.