Changeset 11151:e1bfd808981c in orange


Ignore:
Timestamp:
10/24/12 15:47:49 (18 months ago)
Author:
Ales Erjavec <ales.erjavec@…>
Branch:
default
Message:

Refactored user interactions (and implementing undo/redo).

Only annotation control point editing does not yet have undo/redo.

Location:
Orange/OrangeCanvas
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • Orange/OrangeCanvas/canvas/items/annotationitem.py

    r11102 r11151  
    487487 
    488488    """ 
     489    editingFinished = Signal() 
     490    textEdited = Signal() 
    489491 
    490492    def __init__(self, parent=None, **kwargs): 
     
    618620        # Install event filter to find out when the text item loses focus. 
    619621        self.__textItem.installSceneEventFilter(self) 
     622        self.__textItem.document().contentsChanged.connect( 
     623            self.textEdited 
     624        ) 
    620625 
    621626    def endEdit(self): 
     
    627632        self.__textItem.setTextInteractionFlags(Qt.NoTextInteraction) 
    628633        self.__textItem.removeSceneEventFilter(self) 
     634        self.__textItem.document().contentsChanged.disconnect( 
     635            self.textEdited 
     636        ) 
     637        self.editingFinished.emit() 
    629638 
    630639    def __onDocumentSizeChanged(self, size): 
  • Orange/OrangeCanvas/canvas/scene.py

    r11148 r11151  
    2121from . import items 
    2222from . import quickmenu 
    23 from . import interactions 
    24  
    2523 
    2624log = logging.getLogger(__name__) 
     
    275273        self.__item_for_node[node] = item 
    276274 
     275        node.position_changed.connect(self.__on_node_pos_changed) 
    277276        node.title_changed.connect(item.setTitle) 
    278277        node.progress_changed.connect(item.setProgress) 
     
    322321        """ 
    323322        item = self.__item_for_node.pop(node) 
     323 
     324        node.position_changed.disconnect(self.__on_node_pos_changed) 
     325        node.title_changed.disconnect(item.setTitle) 
     326        node.progress_changed.disconnect(item.setProgress) 
     327        node.processing_state_changed.disconnect(item.setProcessingState) 
     328 
    324329        self.remove_node_item(item) 
    325330 
     
    360365 
    361366        item.setEnabled(scheme_link.enabled) 
     367 
    362368        scheme_link.enabled_changed.connect(item.setEnabled) 
     369 
    363370        self.add_link_item(item) 
    364371        self.__item_for_link[scheme_link] = item 
     
    394401        """ 
    395402        item = self.__item_for_link.pop(scheme_link) 
     403        scheme_link.enabled_changed.disconnect(item.setEnabled) 
    396404        self.remove_link_item(item) 
    397405 
     
    428436            item.resize(w, h) 
    429437            item.setTextInteractionFlags(Qt.TextEditorInteraction) 
     438            scheme_annot.text_changed.connect(item.setPlainText) 
     439 
    430440        elif isinstance(scheme_annot, scheme.SchemeArrowAnnotation): 
    431441            item = items.ArrowAnnotation() 
     
    433443            item.setLine(QLineF(QPointF(*start), QPointF(*end))) 
    434444 
     445        scheme_annot.geometry_changed.connect( 
     446            self.__on_scheme_annot_geometry_change 
     447        ) 
     448 
    435449        self.add_annotation_item(item) 
    436450        self.__item_for_annotation[scheme_annot] = item 
     
    448462    def remove_annotation(self, scheme_annotation): 
    449463        item = self.__item_for_annotation.pop(scheme_annotation) 
     464 
     465        scheme_annotation.geometry_changed.disconnect( 
     466            self.__on_scheme_annot_geometry_change 
     467        ) 
     468 
     469        if isinstance(scheme_annotation, scheme.SchemeTextAnnotation): 
     470            scheme_annotation.text_changed.disconnect( 
     471                item.setPlainText 
     472            ) 
     473 
    450474        self.remove_annotation_item(item) 
    451475 
     
    543567    def _on_position_change(self, item): 
    544568        self.node_item_position_changed.emit(item, item.pos()) 
     569 
     570    def __on_node_pos_changed(self, pos): 
     571        node = self.sender() 
     572        item = self.__item_for_node[node] 
     573        item.setPos(*pos) 
     574 
     575    def __on_scheme_annot_geometry_change(self): 
     576        annot = self.sender() 
     577        item = self.__item_for_annotation[annot] 
     578        if isinstance(annot, scheme.SchemeTextAnnotation): 
     579            item.setGeometry(*annot.rect) 
     580        elif isinstance(annot, scheme.SchemeArrowAnnotation): 
     581            p1 = item.mapFromScene(QPointF(*annot.start_pos)) 
     582            p2 = item.mapFromScene(QPointF(*annot.end_pos)) 
     583            item.setLine(QLineF(p1, p2)) 
     584        else: 
     585            pass 
    545586 
    546587    def item_at(self, pos, type_or_tuple=None): 
     
    587628    def mousePressEvent(self, event): 
    588629        if self.user_interaction_handler and \ 
    589                 self.user_interaction_handler.mouse_press_event(event): 
     630                self.user_interaction_handler.mousePressEvent(event): 
    590631            return 
    591632 
    592         pos = event.scenePos() 
    593         anchor_item = self.item_at(pos, items.NodeAnchorItem) 
    594  
    595         if anchor_item and event.button() == Qt.LeftButton: 
    596             # Start a new link starting at item 
    597             if isinstance(anchor_item, items.SourceAnchorItem): 
    598                 direction = interactions.NewLinkAction.FROM_SOURCE 
    599             else: 
    600                 direction = interactions.NewLinkAction.FROM_SINK 
    601             from_item = anchor_item.parentWidgetItem 
    602             self.set_user_interaction_handler( 
    603                 interactions.NewLinkAction(self, from_item, direction) 
    604             ) 
    605             event.accept() 
    606             return 
    607  
    608         any_item = self.item_at(pos) 
    609         if not any_item and event.button() == Qt.LeftButton: 
    610             # Start rect selection 
    611             self.set_user_interaction_handler( 
    612                 interactions.RectangleSelectionAction(self) 
    613             ) 
    614             self.user_interaction_handler.mouse_press_event(event) 
    615             return 
    616  
    617         # Right (context) click on the widget item. If the widget is not 
     633        # Right (context) click on the node item. If the widget is not 
    618634        # in the current selection then select the widget (only the widget). 
    619635        # Else simply return and let customContextMenuReqested signal 
    620636        # handle it 
    621         shape_item = self.item_at(pos, items.NodeItem) 
     637        shape_item = self.item_at(event.pos(), items.NodeItem) 
    622638        if shape_item and event.button() == Qt.RightButton and \ 
    623639                shape_item.flags() & QGraphicsItem.ItemIsSelectable: 
     
    630646    def mouseMoveEvent(self, event): 
    631647        if self.user_interaction_handler and \ 
    632                 self.user_interaction_handler.mouse_move_event(event): 
     648                self.user_interaction_handler.mouseMoveEvent(event): 
    633649            return 
    634650 
     
    637653    def mouseReleaseEvent(self, event): 
    638654        if self.user_interaction_handler and \ 
    639                 self.user_interaction_handler.mouse_release_event(event): 
     655                self.user_interaction_handler.mouseReleaseEvent(event): 
    640656            return 
    641657        return QGraphicsScene.mouseReleaseEvent(self, event) 
     
    643659    def mouseDoubleClickEvent(self, event): 
    644660        if self.user_interaction_handler and \ 
    645                 self.user_interaction_handler.mouse_double_click_event(event): 
     661                self.user_interaction_handler.mouseDoubleClickEvent(event): 
    646662            return 
    647663 
    648         item = self.item_at(event.scenePos()) 
    649         if not item: 
    650             # Double click on an empty spot 
    651             # Create a new node quick 
    652             action = interactions.NewNodeAction(self) 
    653             action.create_new(event) 
    654             event.accept() 
     664        return QGraphicsScene.mouseDoubleClickEvent(self, event) 
     665 
     666    def keyPressEvent(self, event): 
     667        if self.user_interaction_handler and \ 
     668                self.user_interaction_handler.keyPressEvent(event): 
    655669            return 
    656  
    657         return QGraphicsScene.mouseDoubleClickEvent(self, event) 
     670        return QGraphicsScene.keyPressEvent(self, event) 
     671 
     672    def keyReleaseEvent(self, event): 
     673        if self.user_interaction_handler and \ 
     674                self.user_interaction_handler.keyReleaseEvent(event): 
     675            return 
     676        return QGraphicsScene.keyReleaseEvent(self, event) 
    658677 
    659678    def set_user_interaction_handler(self, handler): 
  • Orange/OrangeCanvas/document/commands.py

    r11149 r11151  
    140140 
    141141 
     142class AnnotationGeometryChange(QUndoCommand): 
     143    def __init__(self, scheme, annotation, old, new, parent=None): 
     144        QUndoCommand.__init__(self, "Change Annotation Geometry", parent) 
     145        self.scheme = scheme 
     146        self.annotation = annotation 
     147        self.old = old 
     148        self.new = new 
     149 
     150    def redo(self): 
     151        self.annotation.geometry = self.new 
     152 
     153    def undo(self): 
     154        self.annotation.geometry = self.old 
     155 
     156 
    142157class RenameNodeCommand(QUndoCommand): 
    143158    def __init__(self, scheme, node, old_name, new_name, parent=None): 
     
    153168    def undo(self): 
    154169        self.node.set_title(self.old_name) 
     170 
     171 
     172class TextChangeCommand(QUndoCommand): 
     173    def __init__(self, scheme, annotation, old, new, parent=None): 
     174        QUndoCommand.__init__(self, "Change text", parent) 
     175        self.scheme = scheme 
     176        self.annotation = annotation 
     177        self.old = old 
     178        self.new = new 
     179 
     180    def redo(self): 
     181        self.annotation.text = self.new 
     182 
     183    def undo(self): 
     184        self.annotation.text = self.old 
  • Orange/OrangeCanvas/document/interactions.py

    r11150 r11151  
    55import logging 
    66 
    7 from PyQt4.QtGui import QApplication, QGraphicsRectItem, QPen, QBrush, QColor 
     7from PyQt4.QtGui import ( 
     8    QApplication, QGraphicsRectItem, QPen, QBrush, QColor 
     9) 
     10 
    811from PyQt4.QtCore import Qt, QSizeF, QRectF, QLineF 
    912 
    1013from ..registry.qt import QtWidgetRegistry 
    1114from .. import scheme 
    12 from . import items 
    13  
     15from ..canvas import items 
    1416 
    1517log = logging.getLogger(__name__) 
     
    1719 
    1820class UserInteraction(object): 
    19     def __init__(self, scene): 
    20         self.scene = scene 
    21         self.scheme = scene.scheme 
     21    def __init__(self, document): 
     22        self.document = document 
     23        self.scene = document.scene() 
     24        self.scheme = document.scheme() 
    2225        self.finished = False 
     26        self.canceled = False 
    2327 
    2428    def start(self): 
     
    3135 
    3236    def cancel(self): 
    33         pass 
    34  
    35     def mouse_press_event(self, event): 
     37        self.canceled = True 
     38        self.end() 
     39 
     40    def mousePressEvent(self, event): 
    3641        return False 
    3742 
    38     def mouse_move_event(self, event): 
     43    def mouseMoveEvent(self, event): 
    3944        return False 
    4045 
    41     def mouse_release_event(self, event): 
     46    def mouseReleaseEvent(self, event): 
    4247        return False 
    4348 
    44     def mouse_double_click_event(self, event): 
     49    def mouseDoubleClickEvent(self, event): 
     50        return False 
     51 
     52    def keyPressEvent(self, event): 
     53        return False 
     54 
     55    def keyReleaseEvent(self, event): 
    4556        return False 
    4657 
     
    6172    FROM_SINK = 2 
    6273 
    63     def __init__(self, scene, from_item, direction): 
    64         UserInteraction.__init__(self, scene) 
     74    def __init__(self, document): 
     75        UserInteraction.__init__(self, document) 
    6576        self.source_item = None 
    6677        self.sink_item = None 
    67         self.from_item = from_item 
    68         self.direction = direction 
    69         assert(direction in [self.FROM_SINK, self.FROM_SOURCE]) 
    70  
    71         if direction == self.FROM_SINK: 
    72             self.sink_item = from_item 
    73         else: 
    74             self.source_item = from_item 
     78        self.from_item = None 
     79        self.direction = None 
     80 
    7581        self.current_target_item = None 
    7682        self.tmp_link_item = None 
     
    117123            self.tmp_link_item.setSourceItem(None, anchor) 
    118124 
    119     def mouse_move_event(self, event): 
     125    def mousePressEvent(self, event): 
     126        anchor_item = self.scene.item_at(event.scenePos(), 
     127                                         items.NodeAnchorItem) 
     128        if anchor_item and event.button() == Qt.LeftButton: 
     129            # Start a new link starting at item 
     130            self.from_item = anchor_item.parentWidgetItem 
     131            if isinstance(anchor_item, items.SourceAnchorItem): 
     132                self.direction = NewLinkAction.FROM_SOURCE 
     133                self.source_item = self.from_item 
     134            else: 
     135                self.direction = NewLinkAction.FROM_SINK 
     136                self.sink_item = self.from_item 
     137 
     138            event.accept() 
     139            return True 
     140        else: 
     141            # Whoerver put us in charge did not know what he was doing. 
     142            self.cancel() 
     143            return False 
     144 
     145    def mouseMoveEvent(self, event): 
    120146        if not self.tmp_link_item: 
    121147            # On first mouse move event create the temp link item and 
     
    169195        self.cursor_anchor_point.setPos(event.scenePos()) 
    170196 
    171         # TODO: need a better interface in LinkItem 
    172 #        self.tmp_link_item._updateCurve() 
    173197        return True 
    174198 
    175     def mouse_release_event(self, event): 
     199    def mouseReleaseEvent(self, event): 
    176200        if self.tmp_link_item: 
    177             item = self.scene.items(event.scenePos()) 
    178             item = [i for i in item if isinstance(i, items.NodeItem)] 
     201            item = self.scene.item_at(event.scenePos(), items.NodeItem) 
    179202            node = None 
     203            stack = self.document.undoStack() 
     204            stack.beginMacro("Add link") 
     205 
    180206            if item: 
    181207                # If the release was over a widget item 
    182208                # then connect them 
    183                 node = self.scene.node_for_item(item[0]) 
     209                node = self.scene.node_for_item(item) 
    184210            else: 
    185211                # Release on an empty canvas part 
    186212                # Show a quick menu popup for a new widget creation. 
    187                 new_action = NewNodeAction(self.scene) 
    188213                try: 
    189                     node = new_action.create_new(event) 
     214                    node = self.create_new(event) 
     215                    self.document.addNode(node) 
    190216                except Exception: 
    191                     log.error("'NewNodeAction' failed to create a new node, " 
    192                               "ending.") 
     217                    log.error("Failed to create a new node, ending.") 
    193218                    node = None 
    194219 
     
    197222            else: 
    198223                self.end() 
     224 
     225            stack.endMacro() 
    199226        else: 
    200227            self.end() 
    201228            return False 
     229 
     230    def create_new(self, event): 
     231        """Create and return a new node with a QuickWidgetMenu. 
     232        """ 
     233        pos = event.screenPos() 
     234        quick_menu = self.scene.quick_menu() 
     235 
     236        action = quick_menu.exec_(pos) 
     237 
     238        if action: 
     239            item = action.property("item").toPyObject() 
     240            desc = item.data(QtWidgetRegistry.WIDGET_DESC_ROLE).toPyObject() 
     241            pos = event.scenePos() 
     242            node = scheme.SchemeNode(desc, position=(pos.x(), pos.y())) 
     243            return node 
    202244 
    203245    def connect_existing(self, node): 
     
    229271                    # If there are ties in the weights a detailed link 
    230272                    # dialog is presented to the user. 
    231                     links_action = EditNodeLinksAction(self.scene, source_node, 
    232                                                       sink_node) 
     273                    links_action = EditNodeLinksAction( 
     274                                    self.document, source_node, sink_node) 
    233275                    try: 
    234276                        links = links_action.edit_links() 
     
    249291                                    sink_node=sink_node, 
    250292                                    sink_channel=sink) 
    251                 self.scheme.remove_link(existing_link) 
     293 
     294                self.document.removeLink(existing_link) 
    252295 
    253296            for source, sink in links_to_add: 
     
    259302 
    260303                    if existing_link: 
    261                         self.scheme.remove_link(existing_link[0]) 
     304                        self.document.removeLink(existing_link[0]) 
    262305 
    263306                # Check if the new link is a duplicate of an existing link 
     
    274317 
    275318                link = scheme.SchemeLink(source_node, source, sink_node, sink) 
    276                 self.scene.add_link(link) 
    277                 self.scene.commit_scheme_link(link) 
     319                self.document.addLink(link) 
    278320 
    279321        except scheme.IncompatibleChannelTypeError: 
     
    329371    """ 
    330372 
    331     def __init__(self, scene): 
    332         UserInteraction.__init__(self, scene) 
    333  
    334     def mouse_press_event(self, event): 
     373    def __init__(self, document): 
     374        UserInteraction.__init__(self, document) 
     375 
     376    def mousePressEvent(self, event): 
    335377        if event.button() == Qt.RightButton: 
    336378            self.create_new(event) 
     
    349391            pos = event.scenePos() 
    350392            node = scheme.SchemeNode(desc, position=(pos.x(), pos.y())) 
    351             self.scene.add_node(node) 
    352             self.scene.commit_scheme_node(node) 
     393            self.document.addNode(node) 
    353394            return node 
    354395 
     
    357398    """Select items in the scene using a Rectangle selection 
    358399    """ 
    359     def __init__(self, scene): 
    360         UserInteraction.__init__(self, scene) 
     400    def __init__(self, document): 
     401        UserInteraction.__init__(self, document) 
    361402        self.initial_selection = None 
    362403 
    363     def mouse_press_event(self, event): 
     404    def mousePressEvent(self, event): 
    364405        pos = event.scenePos() 
    365         self.selection_rect = QRectF(pos, QSizeF(0, 0)) 
    366         self.rect_item = QGraphicsRectItem(self.selection_rect.normalized()) 
    367         self.rect_item.setPen( 
    368             QPen(QBrush(QColor(51, 153, 255, 192)), 
    369                  0.4, Qt.SolidLine, Qt.RoundCap) 
    370         ) 
    371         self.rect_item.setBrush( 
    372             QBrush(QColor(168, 202, 236, 192)) 
    373         ) 
    374         self.rect_item.setZValue(-100) 
    375         self.scene.addItem(self.rect_item) 
    376  
    377     def mouse_move_event(self, event): 
     406        any_item = self.scene.item_at(pos) 
     407        if not any_item and event.button() & Qt.LeftButton: 
     408            self.selection_rect = QRectF(pos, QSizeF(0, 0)) 
     409            self.rect_item = QGraphicsRectItem( 
     410                self.selection_rect.normalized() 
     411            ) 
     412 
     413            self.rect_item.setPen( 
     414                QPen(QBrush(QColor(51, 153, 255, 192)), 
     415                     0.4, Qt.SolidLine, Qt.RoundCap) 
     416            ) 
     417 
     418            self.rect_item.setBrush( 
     419                QBrush(QColor(168, 202, 236, 192)) 
     420            ) 
     421 
     422            self.rect_item.setZValue(-100) 
     423 
     424            # Clear the focus if necessary. 
     425            if not self.scene.stickyFocus(): 
     426                self.scene.clearFocus() 
     427            event.accept() 
     428            return True 
     429        else: 
     430            self.cancel() 
     431            return False 
     432 
     433    def mouseMoveEvent(self, event): 
     434        if not self.rect_item.scene(): 
     435            self.scene.addItem(self.rect_item) 
    378436        self.update_selection(event) 
    379437 
    380     def mouse_release_event(self, event): 
     438    def mouseReleaseEvent(self, event): 
    381439        self.update_selection(event) 
    382440        self.end() 
     
    413471 
    414472class EditNodeLinksAction(UserInteraction): 
    415     def __init__(self, scene, source_node, sink_node): 
    416         UserInteraction.__init__(self, scene) 
     473    def __init__(self, document, source_node, sink_node): 
     474        UserInteraction.__init__(self, document) 
    417475        self.source_node = source_node 
    418476        self.sink_node = sink_node 
    419477 
    420478    def edit_links(self): 
    421         from .editlinksdialog import EditLinksDialog 
     479        from ..canvas.editlinksdialog import EditLinksDialog 
    422480 
    423481        log.info("Constructing a Link Editor dialog.") 
     
    442500            links_to_add = set(links) - set(existing_links) 
    443501            links_to_remove = set(existing_links) - set(links) 
     502 
     503            stack = self.document.undoStack() 
     504            stack.beginMacro("Edit Links") 
    444505 
    445506            for source_channel, sink_channel in links_to_remove: 
     
    448509                                               sink_node=self.sink_node, 
    449510                                               sink_channel=sink_channel) 
    450                 self.scheme.remove_link(links[0]) 
     511 
     512                self.document.removeLink(links[0]) 
    451513 
    452514            for source_channel, sink_channel in links_to_add: 
    453515                link = scheme.SchemeLink(self.source_node, source_channel, 
    454516                                         self.sink_node, sink_channel) 
    455                 self.scheme.add_link(link) 
     517 
     518                self.document.addLink(link) 
     519            stack.endMacro() 
    456520 
    457521 
     
    463527    """Create a new arrow annotation. 
    464528    """ 
    465     def __init__(self, scene, ): 
    466         UserInteraction.__init__(self, scene) 
     529    def __init__(self, document): 
     530        UserInteraction.__init__(self, document) 
    467531        self.down_pos = None 
    468532        self.arrow_item = None 
    469533        self.annotation = None 
    470534 
    471     def mouse_press_event(self, event): 
     535    def start(self): 
     536        self.document.view().setCursor(Qt.CrossCursor) 
     537        UserInteraction.start(self) 
     538 
     539    def mousePressEvent(self, event): 
    472540        if event.button() == Qt.LeftButton: 
    473541            self.down_pos = event.scenePos() 
     
    475543            return True 
    476544 
    477     def mouse_move_event(self, event): 
     545    def mouseMoveEvent(self, event): 
    478546        if event.buttons() & Qt.LeftButton: 
    479547            if self.arrow_item is None and \ 
     
    495563            return True 
    496564 
    497     def mouse_release_event(self, event): 
     565    def mouseReleaseEvent(self, event): 
    498566        if event.button() == Qt.LeftButton: 
    499567            if self.arrow_item is not None: 
     
    503571                self.annotation.set_line(point_to_tuple(line.p1()), 
    504572                                         point_to_tuple(line.p2())) 
    505                 self.scheme.add_annotation(self.annotation) 
     573                self.document.addAnnotation(self.annotation) 
    506574 
    507575                self.arrow_item.setLine(line) 
     
    515583        self.arrow_item = None 
    516584        self.annotation = None 
     585        self.document.view().setCursor(Qt.ArrowCursor) 
    517586        UserInteraction.end(self) 
    518587 
     
    523592 
    524593class NewTextAnnotation(UserInteraction): 
    525     def __init__(self, scene): 
    526         UserInteraction.__init__(self, scene) 
     594    def __init__(self, document): 
     595        UserInteraction.__init__(self, document) 
    527596        self.down_pos = None 
    528597        self.annotation_item = None 
    529598        self.annotation = None 
    530599 
    531     def mouse_press_event(self, event): 
     600    def start(self): 
     601        self.document.view().setCursor(Qt.CrossCursor) 
     602        UserInteraction.start(self) 
     603 
     604    def mousePressEvent(self, event): 
    532605        if event.button() == Qt.LeftButton: 
    533606            self.down_pos = event.scenePos() 
    534607            return True 
    535608 
    536     def mouse_move_event(self, event): 
     609    def mouseMoveEvent(self, event): 
    537610        if event.buttons() & Qt.LeftButton: 
    538611            if self.annotation_item is None and \ 
     
    553626            return True 
    554627 
    555     def mouse_release_event(self, event): 
     628    def mouseReleaseEvent(self, event): 
    556629        if event.button() == Qt.LeftButton: 
    557630            if self.annotation_item is not None: 
     
    560633                # Commit the annotation to the scheme. 
    561634                self.annotation.rect = rect_to_tuple(rect) 
    562                 self.scheme.add_annotation(self.annotation) 
     635                self.document.addAnnotation(self.annotation) 
    563636 
    564637                self.annotation_item.setGeometry(rect) 
     
    574647        self.annotation_item = None 
    575648        self.annotation = None 
     649        self.document.view().setCursor(Qt.ArrowCursor) 
    576650        UserInteraction.end(self) 
  • Orange/OrangeCanvas/document/schemeedit.py

    r11149 r11151  
    33 
    44""" 
     5import logging 
     6from operator import attrgetter 
     7 
    58from PyQt4.QtGui import ( 
    6     QWidget, QVBoxLayout, QUndoStack, QGraphicsItem, QPainter 
     9    QWidget, QVBoxLayout, QInputDialog, QUndoStack, QGraphicsItem, QPainter, 
     10    QGraphicsObject 
    711) 
    812 
    9 from PyQt4.QtCore import Qt, QObject, QEvent 
     13from PyQt4.QtCore import Qt, QObject, QEvent, QSignalMapper, QRectF 
    1014from PyQt4.QtCore import pyqtProperty as Property, pyqtSignal as Signal 
    1115 
     
    1418from ..canvas.view import CanvasView 
    1519from ..canvas import items 
    16 from ..canvas import interactions 
     20from . import interactions 
    1721from . import commands 
    1822 
    1923 
    20 class SchemeDocument(QObject): 
    21     def __init__(self, *args, **kwargs): 
    22         QObject.__init__(self, *args, **kwargs) 
    23         self.__editable = True 
     24log = logging.getLogger(__name__) 
     25 
     26 
     27# TODO: Should this be moved to CanvasScene? 
     28class GraphicsSceneFocusEventListener(QGraphicsObject): 
     29 
     30    itemFocusedIn = Signal(QGraphicsItem) 
     31    itemFocusedOut = Signal(QGraphicsItem) 
     32 
     33    def __init__(self, parent=None): 
     34        QGraphicsObject.__init__(self, parent) 
     35        self.setFlag(QGraphicsItem.ItemHasNoContents) 
     36 
     37    def sceneEventFilter(self, obj, event): 
     38        if event.type() == QEvent.FocusIn and \ 
     39                obj.flags() & QGraphicsItem.ItemIsFocusable: 
     40            obj.focusInEvent(event) 
     41            if obj.hasFocus(): 
     42                self.itemFocusedIn.emit(obj) 
     43            return True 
     44        elif event.type() == QEvent.FocusOut: 
     45            obj.focusOutEvent(event) 
     46            if not obj.hasFocus(): 
     47                self.itemFocusedOut.emit(obj) 
     48            return True 
     49 
     50        return QGraphicsObject.sceneEventFilter(self, obj, event) 
     51 
     52    def boundingRect(self): 
     53        return QRectF() 
    2454 
    2555 
     
    4171        self.__undoStack = QUndoStack(self) 
    4272        self.__undoStack.cleanChanged[bool].connect(self.__onCleanChanged) 
     73        self.__possibleMouseItemsMove = False 
     74        self.__itemsMoving = {} 
     75 
     76        self.__editFinishedMapper = QSignalMapper(self) 
     77        self.__editFinishedMapper.mapped[QObject].connect( 
     78            self.__onEditingFinished 
     79        ) 
     80 
     81        self.__annotationGeomChanged = QSignalMapper(self) 
     82 
    4383        self.__setupUi() 
    4484 
     
    5393        self.__view = view 
    5494        self.__scene = scene 
     95 
     96        self.__focusListener = GraphicsSceneFocusEventListener() 
     97        self.__focusListener.itemFocusedIn.connect(self.__onItemFocusedIn) 
     98        self.__focusListener.itemFocusedOut.connect(self.__onItemFocusedOut) 
     99        self.__scene.addItem(self.__focusListener) 
     100 
     101        self.__scene.selectionChanged.connect( 
     102            self.__onSelectionChanged 
     103        ) 
    55104 
    56105        layout.addWidget(view) 
     
    77126        if self.__scheme is not scheme: 
    78127            self.__scheme = scheme 
     128 
     129            self.__annotationGeomChanged.deleteLater() 
     130            self.__annotationGeomChanged = QSignalMapper(self) 
     131 
    79132            self.__undoStack.clear() 
     133 
     134            self.__focusListener.itemFocusedIn.disconnect( 
     135                self.__onItemFocusedIn 
     136            ) 
     137            self.__focusListener.itemFocusedOut.disconnect( 
     138                self.__onItemFocusedOut 
     139            ) 
     140 
     141            self.__scene.selectionChanged.disconnect( 
     142                self.__onSelectionChanged 
     143            ) 
    80144 
    81145            self.__scene.clear() 
     
    91155            self.__scene.set_registry(self.__registry) 
    92156            self.__scene.set_scheme(scheme) 
     157 
     158            self.__scene.selectionChanged.connect( 
     159                self.__onSelectionChanged 
     160            ) 
     161 
     162            self.__scene.node_item_activated.connect( 
     163                self.__onNodeActivate 
     164            ) 
     165 
     166            self.__scene.annotation_added.connect( 
     167                self.__onAnnotationAdded 
     168            ) 
     169 
     170            self.__scene.annotation_removed.connect( 
     171                self.__onAnnotationRemoved 
     172            ) 
     173 
     174            self.__focusListener = GraphicsSceneFocusEventListener() 
     175            self.__focusListener.itemFocusedIn.connect( 
     176                self.__onItemFocusedIn 
     177            ) 
     178            self.__focusListener.itemFocusedOut.connect( 
     179                self.__onItemFocusedOut 
     180            ) 
     181            self.__scene.addItem(self.__focusListener) 
    93182 
    94183    def scheme(self): 
     
    114203 
    115204    def createNewNode(self, description): 
    116         """Create a new SchemeNode adn at it to the document. 
     205        """Create a new SchemeNode add at it to the document at left of the 
     206        last added node. 
     207 
    117208        """ 
    118209        node = scheme.SchemeNode(description) 
     210 
     211        if self.scheme().nodes: 
     212            x, y = self.scheme().nodes[-1].position 
     213            node.position = (x + 150, y) 
     214        else: 
     215            node.position = (150, 150) 
     216 
    119217        self.addNode(node) 
    120218 
     
    145243    def removeSelected(self): 
    146244        selected = self.scene().selectedItems() 
     245        if not selected: 
     246            return 
     247 
    147248        self.__undoStack.beginMacro(self.tr("Remove")) 
    148249        for item in selected: 
     
    166267 
    167268    def newArrowAnnotation(self): 
    168         handler = interactions.NewArrowAnnotation(self.__scene) 
     269        handler = interactions.NewArrowAnnotation(self) 
    169270        self.__scene.set_user_interaction_handler(handler) 
    170271 
    171272    def newTextAnnotation(self): 
    172         handler = interactions.NewTextAnnotation(self.__scene) 
     273        handler = interactions.NewTextAnnotation(self) 
    173274        self.__scene.set_user_interaction_handler(handler) 
    174275 
    175276    def alignToGrid(self): 
    176         pass 
     277        """Align nodes to a grid. 
     278        """ 
     279        tile_size = 150 
     280        tiles = {} 
     281 
     282        nodes = sorted(self.scheme().nodes, key=attrgetter("position")) 
     283 
     284        if nodes: 
     285            self.__undoStack.beginMacro(self.tr("Align To Grid")) 
     286 
     287            for node in nodes: 
     288                x, y = node.position 
     289                x = int(round(float(x) / tile_size) * tile_size) 
     290                y = int(round(float(y) / tile_size) * tile_size) 
     291                while (x, y) in tiles: 
     292                    x += tile_size 
     293 
     294                self.__undoStack.push( 
     295                    commands.MoveNodeCommand(self.scheme(), node, 
     296                                             node.position, (x, y)) 
     297                ) 
     298 
     299                tiles[x, y] = node 
     300                self.__scene.item_for_node(node).setPos(x, y) 
     301 
     302            self.__undoStack.endMacro() 
    177303 
    178304    def selectedNodes(self): 
     
    184310 
    185311    def editNodeTitle(self, node): 
    186         pass 
     312        name, ok = QInputDialog.getText( 
     313                    self, self.tr("Rename"), 
     314                    unicode(self.tr("Enter a new name for the %r widget")) \ 
     315                    % node.title, 
     316                    text=node.title 
     317                    ) 
     318 
     319        if ok: 
     320            self.__undoStack.push( 
     321                commands.RenameNodeCommand(self.__scheme, node, node.title, 
     322                                           unicode(name)) 
     323            ) 
    187324 
    188325    def __onCleanChanged(self, clean): 
     
    216353            elif etype == QEvent.GraphicsSceneMousePress: 
    217354                return self.sceneMousePressEvent(event) 
    218 #            elif etype == QEvent.GraphicsSceneMouseMove: 
    219 #                return self.sceneMouseMoveEvent(event) 
    220 #            elif etype == QEvent.GraphicsSceneMouseRelease: 
    221 #                return self.sceneMouseReleaseEvent(event) 
    222 #            elif etype == QEvent.GraphicsSceneMouseDoubleClick: 
    223 #                return self.sceneMouseDoubleClickEvent(event) 
    224 #            elif etype == QEvent.KeyRelease: 
    225 #                return self.sceneKeyPressEvent(event) 
    226 #            elif etype == QEvent.KeyRelease: 
    227 #                return self.sceneKeyReleaseEvent(event) 
     355            elif etype == QEvent.GraphicsSceneMouseMove: 
     356                return self.sceneMouseMoveEvent(event) 
     357            elif etype == QEvent.GraphicsSceneMouseRelease: 
     358                return self.sceneMouseReleaseEvent(event) 
     359            elif etype == QEvent.GraphicsSceneMouseDoubleClick: 
     360                return self.sceneMouseDoubleClickEvent(event) 
     361            elif etype == QEvent.KeyRelease: 
     362                return self.sceneKeyPressEvent(event) 
     363            elif etype == QEvent.KeyRelease: 
     364                return self.sceneKeyReleaseEvent(event) 
     365            elif etype == QEvent.GraphicsSceneContextMenu: 
     366                return self.sceneContextMenuEvent(event) 
    228367 
    229368        return QWidget.eventFilter(self, obj, event) 
     
    239378        if anchor_item and event.button() == Qt.LeftButton: 
    240379            # Start a new link starting at item 
    241             handler = interactions.NewLinkAction(scene) 
     380            handler = interactions.NewLinkAction(self) 
    242381            scene.set_user_interaction_handler(handler) 
    243382 
     
    250389            pass 
    251390 
    252         return False 
    253  
    254 #        any_item = self.item_at(pos) 
    255 #        if not any_item and event.button() == Qt.LeftButton: 
    256 #            # Start rect selection 
    257 #            self.set_user_interaction_handler( 
    258 #                interactions.RectangleSelectionAction(self) 
    259 #            ) 
    260 #            self.user_interaction_handler.mouse_press_event(event) 
    261 #            return 
    262  
    263         # Right (context) click on the widget item. If the widget is not 
    264         # in the current selection then select the widget (only the widget). 
    265         # Else simply return and let customContextMenuReqested signal 
    266         # handle it 
    267 #        shape_item = self.item_at(pos, items.NodeItem) 
    268 #        if shape_item and event.button() == Qt.RightButton and \ 
    269 #                shape_item.flags() & QGraphicsItem.ItemIsSelectable: 
    270 #            if not shape_item.isSelected(): 
    271 #                self.clearSelection() 
    272 #                shape_item.setSelected(True) 
    273 # 
    274 #        return QGraphicsScene.mousePressEvent(self, event) 
     391        any_item = scene.item_at(pos) 
     392        if not any_item and event.button() == Qt.LeftButton: 
     393            # Start rect selection 
     394            handler = interactions.RectangleSelectionAction(self) 
     395            scene.set_user_interaction_handler(handler) 
     396            return handler.mousePressEvent(event) 
     397 
     398        if any_item and event.button() == Qt.LeftButton: 
     399            self.__possibleMouseItemsMove = True 
     400            self.__itemsMoving.clear() 
     401            self.__scene.node_item_position_changed.connect( 
     402                self.__onNodePositionChanged 
     403            ) 
     404            self.__annotationGeomChanged.mapped[QObject].connect( 
     405                self.__onAnnotationGeometryChanged 
     406            ) 
     407 
     408        return False 
     409 
     410    def sceneMouseMoveEvent(self, event): 
     411        scene = self.__scene 
     412        if scene.user_interaction_handler: 
     413            return False 
     414 
     415        return False 
     416 
     417    def sceneMouseReleaseEvent(self, event): 
     418        if event.button() == Qt.LeftButton and self.__possibleMouseItemsMove: 
     419            self.__possibleMouseItemsMove = False 
     420            self.__scene.node_item_position_changed.disconnect( 
     421                self.__onNodePositionChanged 
     422            ) 
     423            self.__annotationGeomChanged.mapped[QObject].disconnect( 
     424                self.__onAnnotationGeometryChanged 
     425            ) 
     426 
     427            if self.__itemsMoving: 
     428                self.__scene.mouseReleaseEvent(event) 
     429                stack = self.undoStack() 
     430                stack.beginMacro(self.tr("Move")) 
     431                for scheme_item, (old, new) in self.__itemsMoving.items(): 
     432                    if isinstance(scheme_item, scheme.SchemeNode): 
     433                        command = commands.MoveNodeCommand( 
     434                            self.scheme(), scheme_item, old, new 
     435                        ) 
     436                    elif isinstance(scheme_item, scheme.BaseSchemeAnnotation): 
     437                        command = commands.AnnotationGeometryChange( 
     438                            self.scheme(), scheme_item, old, new 
     439                        ) 
     440                    else: 
     441                        continue 
     442 
     443                    stack.push(command) 
     444                stack.endMacro() 
     445 
     446                self.__itemsMoving.clear() 
     447                return True 
     448        return False 
    275449 
    276450    def sceneMouseDoubleClickEvent(self, event): 
    277         if self.__scene.user_interaction_handler: 
     451        scene = self.__scene 
     452        if scene.user_interaction_handler: 
    278453            return False 
    279         scene = self.__scene 
    280454 
    281455        item = scene.item_at(event.scenePos()) 
     
    283457            # Double click on an empty spot 
    284458            # Create a new node quick 
    285             action = interactions.NewNodeAction(scene) 
     459            action = interactions.NewNodeAction(self) 
    286460            action.create_new(event) 
    287461            event.accept() 
    288462            return True 
    289         return False 
     463 
     464        return False 
     465 
     466    def sceneKeyPressEvent(self, event): 
     467        return False 
     468 
     469    def sceneKeyReleaseEvent(self, event): 
     470        return False 
     471 
     472    def sceneContextMenuEvent(self, event): 
     473        return False 
     474 
     475    def __onSelectionChanged(self): 
     476        pass 
     477 
     478    def __onNodeActivate(self, item): 
     479        node = self.__scene.node_for_item(item) 
     480        widget = self.scheme().widget_for_node[node] 
     481        widget.show() 
     482        widget.raise_() 
     483 
     484    def __onNodePositionChanged(self, item, pos): 
     485        node = self.__scene.node_for_item(item) 
     486        new = (pos.x(), pos.y()) 
     487        if node not in self.__itemsMoving: 
     488            self.__itemsMoving[node] = (node.position, new) 
     489        else: 
     490            old, _ = self.__itemsMoving[node] 
     491            self.__itemsMoving[node] = (old, new) 
     492 
     493    def __onAnnotationGeometryChanged(self, item): 
     494        annot = self.scene().annotation_for_item(item) 
     495        if annot not in self.__itemsMoving: 
     496            self.__itemsMoving[annot] = (annot.geometry, 
     497                                         geometry_from_annotation_item(item)) 
     498        else: 
     499            old, _ = self.__itemsMoving[annot] 
     500            self.__itemsMoving[annot] = (old, 
     501                                         geometry_from_annotation_item(item)) 
     502 
     503    def __onAnnotationAdded(self, item): 
     504        item.setFlag(QGraphicsItem.ItemIsSelectable) 
     505        if isinstance(item, items.ArrowAnnotation): 
     506            pass 
     507        elif isinstance(item, items.TextAnnotation): 
     508            self.__editFinishedMapper.setMapping(item, item) 
     509            item.editingFinished.connect( 
     510                self.__editFinishedMapper.map 
     511            ) 
     512        self.__annotationGeomChanged.setMapping(item, item) 
     513        item.geometryChanged.connect( 
     514            self.__annotationGeomChanged.map 
     515        ) 
     516 
     517    def __onAnnotationRemoved(self, item): 
     518        if isinstance(item, items.ArrowAnnotation): 
     519            pass 
     520        elif isinstance(item, items.TextAnnotation): 
     521            item.editingFinished.disconnect( 
     522                self.__editFinishedMapper.map 
     523            ) 
     524        self.__annotationGeomChanged.removeMappings(item) 
     525        item.geometryChanged.disconnect( 
     526            self.__annotationGeomChanged.map 
     527        ) 
     528 
     529    def __onItemFocusedIn(self, item): 
     530        pass 
     531 
     532    def __onItemFocusedOut(self, item): 
     533        pass 
     534 
     535    def __onEditingFinished(self, item): 
     536        annot = self.__scene.annotation_for_item(item) 
     537        text = unicode(item.toPlainText()) 
     538        if annot.text != text: 
     539            self.__undoStack.push( 
     540                commands.TextChangeCommand(self.scheme(), annot, 
     541                                           annot.text, text) 
     542            ) 
     543 
     544 
     545def geometry_from_annotation_item(item): 
     546    if isinstance(item, items.ArrowAnnotation): 
     547        line = item.line() 
     548        p1 = item.mapToScene(line.p1()) 
     549        p2 = item.mapToScene(line.p2()) 
     550        return ((p1.x(), p1.y()), (p2.x(), p2.y())) 
     551    elif isinstance(item, items.TextAnnotation): 
     552        geom = item.geometry() 
     553        return (geom.x(), geom.y(), geom.width(), geom.height()) 
  • Orange/OrangeCanvas/scheme/annotations.py

    r11111 r11151  
    4141    end_pos = Property(tuple, fget=end_pos) 
    4242 
     43    def set_geometry(self, (start_pos, end_pos)): 
     44        self.set_line(start_pos, end_pos) 
     45 
     46    def geometry(self): 
     47        return (self.start_pos, self.end_pos) 
     48 
     49    geometry = Property(tuple, fget=geometry, fset=set_geometry) 
     50 
    4351 
    4452class SchemeTextAnnotation(BaseSchemeAnnotation): 
     
    6472    rect = Property(tuple, fget=rect, fset=set_rect) 
    6573 
     74    def set_geometry(self, rect): 
     75        self.set_rect(rect) 
     76 
     77    def geometry(self): 
     78        return self.rect 
     79 
     80    geometry = Property(tuple, fget=geometry, fset=set_geometry) 
     81 
    6682    def set_text(self, text): 
    6783        if self.__text != text: 
Note: See TracChangeset for help on using the changeset viewer.