1""" 2Copyright 2007, 2008, 2009, 2010 Free Software Foundation, Inc. 3This file is part of GNU Radio 4 5GNU Radio Companion is free software; you can redistribute it and/or 6modify it under the terms of the GNU General Public License 7as published by the Free Software Foundation; either version 2 8of the License, or (at your option) any later version. 9 10GNU Radio Companion is distributed in the hope that it will be useful, 11but WITHOUT ANY WARRANTY; without even the implied warranty of 12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13GNU General Public License for more details. 14 15You should have received a copy of the GNU General Public License 16along with this program; if not, write to the Free Software 17Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 18""" 19 20from __future__ import absolute_import 21 22from gi.repository import Gtk, Gdk 23 24from .canvas.colors import FLOWGRAPH_BACKGROUND_COLOR 25from . import Constants 26from . import Actions 27 28 29class DrawingArea(Gtk.DrawingArea): 30 """ 31 DrawingArea is the gtk pixel map that graphical elements may draw themselves on. 32 The drawing area also responds to mouse and key events. 33 """ 34 35 def __init__(self, flow_graph): 36 """ 37 DrawingArea constructor. 38 Connect event handlers. 39 40 Args: 41 main_window: the main_window containing all flow graphs 42 """ 43 Gtk.DrawingArea.__init__(self) 44 45 self._flow_graph = flow_graph 46 self.set_property('can_focus', True) 47 48 self.zoom_factor = 1.0 49 self._update_after_zoom = False 50 self.ctrl_mask = False 51 self.mod1_mask = False 52 self.button_state = [False] * 10 53 54 # self.set_size_request(MIN_WINDOW_WIDTH, MIN_WINDOW_HEIGHT) 55 self.connect('realize', self._handle_window_realize) 56 self.connect('draw', self.draw) 57 self.connect('motion-notify-event', self._handle_mouse_motion) 58 self.connect('button-press-event', self._handle_mouse_button_press) 59 self.connect('button-release-event', self._handle_mouse_button_release) 60 self.connect('scroll-event', self._handle_mouse_scroll) 61 self.add_events( 62 Gdk.EventMask.BUTTON_PRESS_MASK | 63 Gdk.EventMask.POINTER_MOTION_MASK | 64 Gdk.EventMask.BUTTON_RELEASE_MASK | 65 Gdk.EventMask.SCROLL_MASK | 66 Gdk.EventMask.LEAVE_NOTIFY_MASK | 67 Gdk.EventMask.ENTER_NOTIFY_MASK 68 # Gdk.EventMask.FOCUS_CHANGE_MASK 69 ) 70 71 # This may not be the correct place to be handling the user events 72 # Should this be in the page instead? 73 # Or should more of the page functionality move here? 74 75 # setup drag and drop 76 self.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY) 77 self.connect('drag-data-received', self._handle_drag_data_received) 78 self.drag_dest_set_target_list(None) 79 self.drag_dest_add_text_targets() 80 81 # setup the focus flag 82 self._focus_flag = False 83 self.get_focus_flag = lambda: self._focus_flag 84 85 def _handle_notify_event(widget, event, focus_flag): 86 self._focus_flag = focus_flag 87 88 self.connect('leave-notify-event', _handle_notify_event, False) 89 self.connect('enter-notify-event', _handle_notify_event, True) 90 91 self.set_can_focus(True) 92 self.connect('focus-out-event', self._handle_focus_lost_event) 93 94 95 ########################################################################## 96 # Handlers 97 ########################################################################## 98 def _handle_drag_data_received(self, widget, drag_context, x, y, selection_data, info, time): 99 """ 100 Handle a drag and drop by adding a block at the given coordinate. 101 """ 102 coords = x / self.zoom_factor, y / self.zoom_factor 103 self._flow_graph.add_new_block(selection_data.get_text(), coords) 104 105 def _handle_mouse_scroll(self, widget, event): 106 if event.get_state() & Gdk.ModifierType.CONTROL_MASK: 107 change = 1.2 if event.direction == Gdk.ScrollDirection.UP else 1/1.2 108 zoom_factor = min(max(self.zoom_factor * change, 0.1), 5.0) 109 110 if zoom_factor != self.zoom_factor: 111 self.zoom_factor = zoom_factor 112 self._update_after_zoom = True 113 self.queue_draw() 114 return True 115 116 return False 117 118 def _handle_mouse_button_press(self, widget, event): 119 """ 120 Forward button click information to the flow graph. 121 """ 122 self.grab_focus() 123 124 self.ctrl_mask = event.get_state() & Gdk.ModifierType.CONTROL_MASK 125 self.mod1_mask = event.get_state() & Gdk.ModifierType.MOD1_MASK 126 self.button_state[event.button] = True 127 128 if event.button == 1: 129 double_click = (event.type == Gdk.EventType._2BUTTON_PRESS) 130 self.button_state[1] = not double_click 131 self._flow_graph.handle_mouse_selector_press( 132 double_click=double_click, 133 coordinate=self._translate_event_coords(event), 134 ) 135 elif event.button == 3: 136 self._flow_graph.handle_mouse_context_press( 137 coordinate=self._translate_event_coords(event), 138 event=event, 139 ) 140 141 def _handle_mouse_button_release(self, widget, event): 142 """ 143 Forward button release information to the flow graph. 144 """ 145 self.ctrl_mask = event.get_state() & Gdk.ModifierType.CONTROL_MASK 146 self.mod1_mask = event.get_state() & Gdk.ModifierType.MOD1_MASK 147 self.button_state[event.button] = False 148 if event.button == 1: 149 self._flow_graph.handle_mouse_selector_release( 150 coordinate=self._translate_event_coords(event), 151 ) 152 153 def _handle_mouse_motion(self, widget, event): 154 """ 155 Forward mouse motion information to the flow graph. 156 """ 157 self.ctrl_mask = event.get_state() & Gdk.ModifierType.CONTROL_MASK 158 self.mod1_mask = event.get_state() & Gdk.ModifierType.MOD1_MASK 159 160 if self.button_state[1]: 161 self._auto_scroll(event) 162 163 self._flow_graph.handle_mouse_motion( 164 coordinate=self._translate_event_coords(event), 165 ) 166 167 def _update_size(self): 168 w, h = self._flow_graph.get_extents()[2:] 169 self.set_size_request(w * self.zoom_factor + 100, h * self.zoom_factor + 100) 170 171 def _auto_scroll(self, event): 172 x, y = event.x, event.y 173 scrollbox = self.get_parent().get_parent() 174 175 self._update_size() 176 177 def scroll(pos, adj): 178 """scroll if we moved near the border""" 179 adj_val = adj.get_value() 180 adj_len = adj.get_page_size() 181 if pos - adj_val > adj_len - Constants.SCROLL_PROXIMITY_SENSITIVITY: 182 adj.set_value(adj_val + Constants.SCROLL_DISTANCE) 183 adj.emit('changed') 184 elif pos - adj_val < Constants.SCROLL_PROXIMITY_SENSITIVITY: 185 adj.set_value(adj_val - Constants.SCROLL_DISTANCE) 186 adj.emit('changed') 187 188 scroll(x, scrollbox.get_hadjustment()) 189 scroll(y, scrollbox.get_vadjustment()) 190 191 def _handle_window_realize(self, widget): 192 """ 193 Called when the window is realized. 194 Update the flowgraph, which calls new pixmap. 195 """ 196 self._flow_graph.update() 197 self._update_size() 198 199 def draw(self, widget, cr): 200 width = widget.get_allocated_width() 201 height = widget.get_allocated_height() 202 203 cr.set_source_rgba(*FLOWGRAPH_BACKGROUND_COLOR) 204 cr.rectangle(0, 0, width, height) 205 cr.fill() 206 207 cr.scale(self.zoom_factor, self.zoom_factor) 208 cr.set_line_width(2.0 / self.zoom_factor) 209 210 if self._update_after_zoom: 211 self._flow_graph.create_labels(cr) 212 self._flow_graph.create_shapes() 213 self._update_size() 214 self._update_after_zoom = False 215 216 self._flow_graph.draw(cr) 217 218 def _translate_event_coords(self, event): 219 return event.x / self.zoom_factor, event.y / self.zoom_factor 220 221 def _handle_focus_lost_event(self, widget, event): 222 # don't clear selection while context menu is active 223 if not self._flow_graph.get_context_menu()._menu.get_visible(): 224 self._flow_graph.unselect() 225 self._flow_graph.update_selected() 226 self.queue_draw() 227 Actions.ELEMENT_SELECT() 228