# Copyright 2008 by Peter Moxhay and Wade Brainerd. # This file is part of Math. # # Math is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # Math is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Math. If not, see . from objectarea import Object from droptargetobject import DropTargetObject from vector import Vector DEBUG = False import gtk, math class MovableObject(Object): """ Extends Objects with the ability to move. """ def __init__(self): Object.__init__(self) self.draggable = True self.movable = True self.rotatable = True self.drag_type = None self.drag_offset = Vector(0, 0) self.drop_targets = [] # Capture the mouse press position and angle self.press_pos = Vector(0, 0) self.press_angle = 0 self.rotate_only = False # If the mouse is within this radius, it will drag rathern than rotate. self.move_only_radius = 100 self.in_answer_box = False #self.drag_copy = False self.moving_by_key_press = False self.drop_origin = None def get_drag_type(self, event): if not self.draggable: return None if self.movable and self.rotatable: # Hold Alt to force rotate. if event.state & gtk.gdk.MOD1_MASK: return 'rotate' # Automatically choose between move and rotate based on cursor position. epos = Vector(event.x, event.y) # If the mouse is pressed near the center, then move the object. if self.inside_move_area(epos): return 'move' # Otherwise, rotate. else: return 'rotate' else: # Work out the type of drag based on capability flags. if self.rotatable: return 'rotate' elif self.movable: return 'move' return None def inside_move_area(self, point): self.point = point boolean = False # If the point is near the center of the object, return True. if self.contains_point(point): boolean = True return boolean def on_mouse(self, event): if DEBUG: print "MovableObject: on_mouse called" if not self.selectable: if DEBUG: print "MovableObject: not self.selectable" return else: if DEBUG: print "MovableObject: IS self.selectable!" epos = Vector(event.x, event.y) if DEBUG: print "MovableObject: self.drag_type =", self.drag_type # Process in progress dragging, if active. if self.drag_type: if self.drag_type == 'rotate': cursor_type = gtk.gdk.CIRCLE else: cursor_type = gtk.gdk.FLEUR if self.container: self.container.queue_cursor(cursor_type) #print "Movable Object: event.type =", event.type if event.type == gtk.gdk.MOTION_NOTIFY: if self.container and self.container.drag_object: self.container.drag_object.dragged = True if self.drag_type == 'rotate': self.rotate(self.press_angle + math.atan2(epos.y - self.pos.y, epos.x - self.pos.x) - \ math.atan2(self.press_pos.y - self.pos.y, self.press_pos.x - self.pos.x) ) else: #print "Movable Object: trying to move:", self self.move(epos - self.drag_offset) for o in self.drop_targets: if o.contains(self.get_bounds()) and not o.full: o.hilite = True #print "MoveableObject: DropTarget's hilite set to True, queue draw" o.queue_draw() else: o.hilite = False #print "MoveableObject: DropTarget's hilite set to False, queue draw" o.queue_draw() elif event.type == gtk.gdk.BUTTON_RELEASE: if DEBUG: print "MovableObject: BUTTON_RELEASE: ", self if self.container: self.container.drag_object.dragged = False self.container.drag_object = None self.container.snap_to_grid(self) has_drop_target = False inside_drop_target = False #Check whether the drop is inside a drop target. for o in self.drop_targets: has_drop_target = True if DEBUG: print "MovableObject: has a drop target" if o.contains(self.get_bounds()): inside_drop_target = True if DEBUG: print "MovableObject: is inside target" #if not has_drop_target: if DEBUG: print "MovableObject: try to call processDrop" if o.processDrop(self): if DEBUG: print "MovableObject: finished calling processDrop" return True self.drag_type = None if not has_drop_target: if DEBUG: print "MovableObject: No drop target" else: if DEBUG: print "MovableObject: Has a drop target" if has_drop_target and not inside_drop_target: if DEBUG: print "MovableObject: has_drop_target and not inside_drop_target" self.container.remove_object(self) if self.drop_origin: self.drop_origin.select_primary_object() # Remember the drag position self.press_pos = epos self.press_angle = self.angle return True elif self.contains_point(epos): drag_type = self.get_drag_type(event) if drag_type == 'rotate': cursor_type = gtk.gdk.CIRCLE else: cursor_type = gtk.gdk.FLEUR if self.container: self.container.queue_cursor(cursor_type) if event.type == gtk.gdk.BUTTON_PRESS: self.select_by_button_press() #print "MovableObject: BUTTON_PRESS" #print "MovableObject: len(self.drop_targets) =",len(self.drop_targets) #Check whether the button press is inside a drop target. # TODO- Check whether this makes multiple copies of movable in drop target. i = 0 for o in self.drop_targets: #print "MovableObject: Is Movable object in drop target", i, "?" #print "MovableObject: drop target bounds =",o.get_bounds() #print "MovableObject: movable object bounds =",self.get_bounds() if o.contains(self.get_bounds()): #print "MovableObject: button press IS inside a drop target!" self.drop_target = None o.full = False o.contents = None i += 1 self.drag_type = self.get_drag_type(event) if self.drag_type: self.container.drag_object = self self.drag_offset = epos - self.pos # Remember the press position self.press_pos = epos self.press_angle = self.angle return True def select_by_button_press(self): # So we can override for ShapeObject inside answer box. self.container.select_object(self) def on_key(self, event): if self.container: GRID_SIZE = self.container.GRID_SIZE RADIAL_GRID_SIZE = self.container.RADIAL_GRID_SIZE else: GRID_SIZE = 50 RADIAL_GRID_SIZE = math.pi/4 if not self.selectable or not self.draggable: return key_name = gtk.gdk.keyval_name(event.keyval) #print "key_name =", key_name if event.state & gtk.gdk.MOD1_MASK: if key_name == 'Up' or key_name == 'Left': self.rotate(self.angle - RADIAL_GRID_SIZE) elif key_name == 'Down' or key_name == 'Right': self.rotate(self.angle + RADIAL_GRID_SIZE) elif key_name == 'KP_Page_Up': self.rotate(self.angle + RADIAL_GRID_SIZE) else: if key_name == 'Up' or key_name == 'KP_Up': self.move(self.pos + Vector(0, -GRID_SIZE)) elif key_name == 'Down' or key_name == 'KP_Down': self.move(self.pos + Vector(0, GRID_SIZE)) elif key_name == 'Left' or key_name == 'KP_Left': self.move(self.pos + Vector(-GRID_SIZE, 0)) elif key_name == 'Right' or key_name == 'KP_Right': self.move(self.pos + Vector(GRID_SIZE, 0)) elif key_name == 'BackSpace' or key_name == 'KP_Page_Down': #print "BackSpace pressed, try to stop the drag" if self.drop_origin: self.drop_origin.remove_object(self) return # Default implementation which may be overridden by derived objects. def is_in_container(self): if not self.container: return False mn, mx = self.get_bounds() if mn.x < -2 or mx.x > self.container.DRAGGING_RECT_WIDTH + 2 or \ mn.y < -1 or mx.y > self.container.DRAGGING_RECT_HEIGHT: return False return True # Default implementation which may be overridden by derived objects. def calculate_bounds(self): pass def move(self, pos): self.queue_draw() # Tentatively place in the object in the new position last_pos = self.pos self.pos = pos self.calculate_bounds() # If any point is out of bounds, move object back to last position. if not self.is_in_container(): self.pos = last_pos self.calculate_bounds() self.queue_draw() def rotate(self, angle): self.queue_draw() # Tentatively rotate the object to the new angle last_angle = self.angle self.angle = angle # If any point is out of bounds, move object back to last angle. if not self.is_in_container(): self.angle = last_angle self.calculate_bounds() self.queue_draw()