# 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 . DEBUG = False from objectarea import Object from droptargetobject import DropTargetObject from vector import Vector import gtk, math class DraggableObject(Object): """ Extends Objects with the ability to Drag and Drop. Derived classes must implement: get_bounds() - Returns min, max bounds of the Object. """ def __init__(self): Object.__init__(self) self.draggable = True self.drag_type = None self.drop_origin = None self.drop_targets = [] self.drag_offset = Vector(0, 0) # The mouse press position. self.press_pos = Vector(0, 0) #self.drag_copy = False self.moving_by_key_press = False def get_bounds(self): pass def get_drag_type(self, event): if not self.draggable: return None return 'move' def on_mouse(self, event): if not self.selectable or not self.draggable: return # The position of the mouse event. epos = Vector(event.x, event.y) # If drag is in progress. if self.drag_type: # Remember the drag position. self.press_pos = epos drag_type = self.get_drag_type(event) # Perform the drag. if event.type == gtk.gdk.MOTION_NOTIFY: drag_type = self.get_drag_type(event) if drag_type == 'move': cursor_type = gtk.gdk.FLEUR if self.container: self.container.queue_cursor(cursor_type) if self.dragged: self.move(epos - self.drag_offset) i = 0; for o in self.drop_targets: i += 1 if o.contains(self.get_bounds()) and not o.full: o.hilite = True if DEBUG: print "DraggableObject: DropTarget's hilite set to True, queue draw" o.queue_draw() else: o.hilite = False if DEBUG: print "DraggableObject: DropTarget's hilite set to False, queue draw" o.queue_draw() # End the drag. elif event.type == gtk.gdk.BUTTON_RELEASE: if DEBUG: print "DraggableObject: BUTTON_RELEASE" self.dragged = False if self.container: self.container.snap_to_grid(self) b = False #Check whether the drop is inside a drop target. if DEBUG: print "DraggableObject: has ", len(self.drop_targets), "drop targets" for o in self.drop_targets: #if o.contains(self.get_bounds()): # Try this: 6/4/09 if o.hilite: b = True if DEBUG: print "DraggableObject: trying to call processDrop" if o.processDrop(self): return True if not b: if DEBUG: print "DraggableObject: not in a DropTarget" o.hilite = False self.drop_origin.reset() else: if DEBUG: print "DraggableObject: IS in a DropTarget!" # If drag is not already in progress. elif self.contains_point(epos): drag_type = self.get_drag_type(event) if drag_type == 'move': cursor_type = gtk.gdk.FLEUR if self.container: self.container.queue_cursor(cursor_type) # Start the drag. if event.type == gtk.gdk.BUTTON_PRESS: if self.container: self.container.select_object(self) self.drag_type = self.get_drag_type(event) if self.drag_type: if self.container: self.container.drag_object = self self.drag_offset = epos - self.pos # Remember the press position self.press_pos = epos self.dragged = True return True def on_key(self, event): if self.container: GRID_SIZE = self.container.GRID_SIZE else: GRID_SIZE = 50 if not self.selectable or not self.draggable: return key_name = gtk.gdk.keyval_name(event.keyval) #print "key_name =", key_name if key_name == 'Return' or key_name == 'KP_End': #print "Return key pressed on a draggable object" if self.selected and not self.dragged: #print "Draggable object is selected, so make it dragged." self.dragged = True self.moving_by_key_press = True if self.container: self.container.queue_draw() elif self.moving_by_key_press and self.dragged: #print "Draggable object is moving_by_key_press and dragged, prepare to try to drop it." #Check whether the drop is inside a drop target. for o in self.drop_targets: if o.hilite: b = True if o.processDrop(self): return True #TODO- ordering of next 4 lines is unclear. if self.container: self.container.drag_object = None self.container.snap_to_grid(self) #self.drag_type = None if self.container: #print "DraggableObject on_key calling check_problem_solved" self.container.check_problem_solved() elif self.moving_by_key_press and self.dragged: #print "Draggable object is dragged, so move it." 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" self.drop_origin.reset() return i = 0; for o in self.drop_targets: i += 1 if o.contains(self.get_bounds()) and not o.full: o.hilite = True o.queue_draw() else: o.hilite = False o.queue_draw() if self.drop_target: if self.drop_target.contains_rectangle(self.get_bounds()): self.drop_target.hilite = False self.drop_target = None for o in self.container.objects: if isinstance(o, DropTargetObject): # If a drop target contains epos, and doesn't already contain some other object... #if o.contains_point(self.pos) and not o.contents: if o.contains_rectangle(self.get_bounds()) and not o.contents: # ..set the dragged object's drop target to be _this_ drop target... self.drop_target = o # ... and hilight the drop target. self.drop_target.hilite = True if self.container: self.container.queue_draw() # 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 def move(self, pos): self.queue_draw() # Tentatively place in the object in the new position last_pos = self.pos self.pos = pos # If any point is out of bounds, move object back to last position. if not self.is_in_container(): self.pos = last_pos self.queue_draw()