Browse code

Shift-dragging zooms an area.

Refactor all mouse dragging code into helper classes. This changes the
behaviour when you press or release Shift or Ctrl during the drag: now
this is ignored and the action that you started continues.

From: Marius Gedminas <marius@gedmin.as>

Jose.R.Fonseca authored on 13/07/2008 03:21:38
Showing 1 changed files

  • xdot.py index 278f7cd..999270e 100755
... ...
@@ -688,6 +688,103 @@ class ZoomToAnimation(MoveToAnimation):
688 688
         MoveToAnimation.animate(self, t)
689 689
 
690 690
 
691
+class DragAction(object):
692
+
693
+    def __init__(self, dot_widget):
694
+        self.dot_widget = dot_widget
695
+
696
+    def on_button_press(self, event):
697
+        self.startmousex = self.prevmousex = event.x
698
+        self.startmousey = self.prevmousey = event.y
699
+        self.start()
700
+
701
+    def on_motion_notify(self, event):
702
+        deltax = self.prevmousex - event.x
703
+        deltay = self.prevmousey - event.y
704
+        self.drag(deltax, deltay)
705
+        self.prevmousex = event.x
706
+        self.prevmousey = event.y
707
+
708
+    def on_button_release(self, event):
709
+        self.stopmousex = event.x
710
+        self.stopmousey = event.y
711
+        self.stop()
712
+
713
+    def draw(self, cr):
714
+        pass
715
+
716
+    def start(self):
717
+        pass
718
+
719
+    def drag(self, deltax, deltay):
720
+        pass
721
+
722
+    def stop(self):
723
+        pass
724
+
725
+
726
+class NullAction(DragAction):
727
+
728
+    def on_motion_notify(self, event):
729
+        dot_widget = self.dot_widget
730
+        if dot_widget.get_url(event.x, event.y) is not None:
731
+            dot_widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
732
+        else:
733
+            dot_widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.ARROW))
734
+
735
+
736
+class PanAction(DragAction):
737
+
738
+    def start(self):
739
+        self.dot_widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.FLEUR))
740
+
741
+    def drag(self, deltax, deltay):
742
+        self.dot_widget.x += deltax / self.dot_widget.zoom_ratio
743
+        self.dot_widget.y += deltay / self.dot_widget.zoom_ratio
744
+        self.dot_widget.queue_draw()
745
+
746
+    def stop(self):
747
+        self.dot_widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.ARROW))
748
+
749
+
750
+class ZoomAction(DragAction):
751
+
752
+    def drag(self, deltax, deltay):
753
+        self.dot_widget.zoom_ratio *= 1.005 ** (deltax + deltay)
754
+        self.dot_widget.queue_draw()
755
+
756
+    def stop(self):
757
+        self.dot_widget.queue_draw()
758
+
759
+
760
+class ZoomAreaAction(DragAction):
761
+
762
+    def drag(self, deltax, deltay):
763
+        self.dot_widget.queue_draw()
764
+
765
+    def draw(self, cr):
766
+        cr.save()
767
+        cr.set_source_rgba(.5, .5, 1.0, 0.25)
768
+        cr.rectangle(self.startmousex, self.startmousey,
769
+                     self.prevmousex - self.startmousex,
770
+                     self.prevmousey - self.startmousey)
771
+        cr.fill()
772
+        cr.set_source_rgba(.5, .5, 1.0, 1.0)
773
+        cr.set_line_width(1)
774
+        cr.rectangle(self.startmousex - .5, self.startmousey - .5,
775
+                     self.prevmousex - self.startmousex + 1,
776
+                     self.prevmousey - self.startmousey + 1)
777
+        cr.stroke()
778
+        cr.restore()
779
+
780
+    def stop(self):
781
+        x1, y1 = self.dot_widget.window2graph(self.startmousex,
782
+                                              self.startmousey)
783
+        x2, y2 = self.dot_widget.window2graph(self.stopmousex,
784
+                                              self.stopmousey)
785
+        self.dot_widget.zoom_to_area(x1, y1, x2, y2)
786
+
787
+
691 788
 class DotWidget(gtk.DrawingArea):
692 789
     """PyGTK widget that draws dot graphs."""
693 790
 
... ...
@@ -715,6 +812,7 @@ class DotWidget(gtk.DrawingArea):
715 812
         self.x, self.y = 0.0, 0.0
716 813
         self.zoom_ratio = 1.0
717 814
         self.animation = NoAnimation(self)
815
+        self.drag_action = NullAction(self)
718 816
         self.presstime = None
719 817
 
720 818
     def set_dotcode(self, dotcode):
... ...
@@ -747,12 +845,16 @@ class DotWidget(gtk.DrawingArea):
747 845
         cr.set_source_rgba(1.0, 1.0, 1.0, 1.0)
748 846
         cr.paint()
749 847
 
848
+        cr.save()
750 849
         rect = self.get_allocation()
751 850
         cr.translate(0.5*rect.width, 0.5*rect.height)
752 851
         cr.scale(self.zoom_ratio, self.zoom_ratio)
753 852
         cr.translate(-self.x, -self.y)
754 853
 
755 854
         self.graph.draw(cr)
855
+        cr.restore()
856
+
857
+        self.drag_action.draw(cr)
756 858
 
757 859
         return False
758 860
 
... ...
@@ -771,6 +873,18 @@ class DotWidget(gtk.DrawingArea):
771 873
         self.zoom_ratio = zoom_ratio
772 874
         self.queue_draw()
773 875
 
876
+    def zoom_to_area(self, x1, y1, x2, y2):
877
+        rect = self.get_allocation()
878
+        width = abs(x1 - x2)
879
+        height = abs(y1 - y2)
880
+        self.zoom_ratio = min(
881
+            float(rect.width)/float(width),
882
+            float(rect.height)/float(height)
883
+        )
884
+        self.x = (x1 + x2) / 2
885
+        self.y = (y1 + y2) / 2
886
+        self.queue_draw()
887
+
774 888
     ZOOM_INCREMENT = 1.25
775 889
 
776 890
     def on_zoom_in(self, action):
... ...
@@ -819,17 +933,25 @@ class DotWidget(gtk.DrawingArea):
819 933
             return True
820 934
         return False
821 935
 
936
+    def get_drag_action(self, event):
937
+        state = event.state
938
+        if event.button in (1, 2): # left or middle button
939
+            if state & gtk.gdk.CONTROL_MASK:
940
+                return ZoomAction
941
+            elif state & gtk.gdk.SHIFT_MASK:
942
+                return ZoomAreaAction
943
+            else:
944
+                return PanAction
945
+        return NullAction
946
+
822 947
     def on_area_button_press(self, area, event):
823
-        if event.button == 2 or event.button == 1:
824
-            area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.FLEUR))
825
-            self.prevmousex = self.startmousex = event.x
826
-            self.prevmousey = self.startmousey = event.y
827
-            self.presstime = time.time()
828
-            self.animation.stop()
829
-
830
-        if event.type not in (gtk.gdk.BUTTON_PRESS, gtk.gdk.BUTTON_RELEASE):
831
-            return False
832
-        x, y = int(event.x), int(event.y)
948
+        self.animation.stop()
949
+        action_type = self.get_drag_action(event)
950
+        self.drag_action = action_type(self)
951
+        self.drag_action.on_button_press(event)
952
+        self.presstime = time.time()
953
+        self.pressx = event.x
954
+        self.pressy = event.y
833 955
         return False
834 956
 
835 957
     def is_click(self, event, click_fuzz=4, click_timeout=1.0):
... ...
@@ -839,29 +961,28 @@ class DotWidget(gtk.DrawingArea):
839 961
             return False
840 962
         # XXX instead of doing this complicated logic, shouldn't we listen
841 963
         # for gtk's clicked event instead?
842
-        deltax = self.startmousex - event.x
843
-        deltay = self.startmousey - event.y
964
+        deltax = self.pressx - event.x
965
+        deltay = self.pressy - event.y
844 966
         return (time.time() < self.presstime + click_timeout
845 967
                 and math.hypot(deltax, deltay) < click_fuzz)
846 968
 
847 969
     def on_area_button_release(self, area, event):
848
-        if event.button == 2 or event.button == 1:
849
-            area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.ARROW))
850
-            self.prevmousex = None
851
-            self.prevmousey = None
852
-
853
-            if event.button == 1 and self.is_click(event):
854
-                x, y = int(event.x), int(event.y)
855
-                url = self.get_url(x, y)
856
-                if url is not None:
857
-                    self.emit('clicked', unicode(url), event)
858
-                else:
859
-                    jump = self.get_jump(x, y)
860
-                    if jump is not None:
861
-                        jumpx, jumpy = jump
862
-                        self.animate_to(jumpx, jumpy)
970
+        self.drag_action.on_button_release(event)
971
+        self.drag_action = NullAction(self)
972
+        if event.button == 1 and self.is_click(event):
973
+            x, y = int(event.x), int(event.y)
974
+            url = self.get_url(x, y)
975
+            if url is not None:
976
+                self.emit('clicked', unicode(url), event)
977
+            else:
978
+                jump = self.get_jump(x, y)
979
+                if jump is not None:
980
+                    jumpx, jumpy = jump
981
+                    self.animate_to(jumpx, jumpy)
863 982
 
864 983
             return True
984
+        if event.button == 1 or event.button == 2:
985
+            return True
865 986
         return False
866 987
 
867 988
     def on_area_scroll_event(self, area, event):
... ...
@@ -874,31 +995,7 @@ class DotWidget(gtk.DrawingArea):
874 995
         return False
875 996
 
876 997
     def on_area_motion_notify(self, area, event):
877
-        x, y = int(event.x), int(event.y)
878
-        state = event.state
879
-
880
-        if state & gtk.gdk.BUTTON2_MASK or state & gtk.gdk.BUTTON1_MASK:
881
-            deltax = self.prevmousex - event.x
882
-            deltay = self.prevmousey - event.y
883
-            if state & gtk.gdk.CONTROL_MASK:
884
-                # zoom the image
885
-                self.zoom_ratio *= 1.005 ** (deltax + deltay)
886
-                self.queue_draw()
887
-            else:
888
-                # pan the image
889
-                self.x += deltax/self.zoom_ratio
890
-                self.y += deltay/self.zoom_ratio
891
-                self.queue_draw()
892
-            self.prevmousex = x
893
-            self.prevmousey = y
894
-            self.animation.stop()
895
-        else:
896
-            # set cursor
897
-            if self.get_url(x, y) is not None:
898
-                area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2))
899
-            else:
900
-                area.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.ARROW))
901
-
998
+        self.drag_action.on_motion_notify(event)
902 999
         return True
903 1000
 
904 1001
     def animate_to(self, x, y):