#!/usr/bin/python import goocanvas import gtk import cairo class Stack: def __init__(self, canvas, x = 0, y = 0): self.dragging = False self.cards = [] self.x = x self.y = y self.canvas = canvas self.root = canvas.get_root_item() self.drawables = goocanvas.Group(parent = self.root) self.drawables.set_canvas(self.canvas) self.drawables.connect ("leave_notify_event", self.on_leave_notify) self.drawables.connect ("enter_notify_event", self.on_enter_notify) self.drawables.connect ("motion_notify_event", self.on_motion_notify) self.drawables.connect ("button_press_event", self.on_button_press) self.drawables.connect ("button_release_event", self.on_button_release) self.drawables.stack = self self.parent_stack = False def on_leave_notify(self, item, target, event): try: card = self.cards[len(self.cards) - 1] card.border.props.line_width = card.border_width except: pass def on_enter_notify(self, item, target, event): card = self.cards[len(self.cards)-1] card.border.props.line_width = 4 def append(self, card): # TODO: don't do uniform translations on every card, ends up making stacks too large # and cutting the spacing down uniformly makes a solid wall of black where all the borders # line up perfectly if not card.drawables: for x in self.cards: x.drawables.translate(3, 3) card.draw(self.drawables) else: card.drawables.props.parent = self.drawables self.cards.append(card) def on_motion_notify (self, item, target, event): if self.dragging and event.state and gtk.gdk.BUTTON1_MASK: self.drawables.translate (event.x - self.drag_x, event.y - self.drag_y) area = self.drawables.get_bounds() below = self.canvas.get_items_in_area(area, True, True, True) if below is not None: drop_candidates = [] for drawables in below: try: drag_candidate = drawables.props.parent.stack if drag_candidate is not self: drop_candidates.append(drag_candidate) except: pass current_selection = self.canvas.drag_selection # TODO select closest based on distance if len(drop_candidates) > 0: drop_target = drop_candidates[0] if drop_target is not current_selection: if current_selection: current_selection.on_drag_unselect() drop_target.on_drag_select(self) else: if current_selection: current_selection.on_drag_unselect() drop_target = False self.canvas.drag_selection = drop_target return True def on_drag_select(self, dragging): if dragging.parent_stack == self: card = self.cards[len(self.cards) - 2] else: card = self.cards[len(self.cards) - 1] card.border.props.line_width = 3 def on_drag_unselect(self): card = self.cards[len(self.cards) - 1] card.border.props.line_width = card.border_width def on_button_press (self, item, target, event): if event.button == 1: # are we dragging a stack or a single card if event.state & gtk.gdk.SHIFT_MASK: drug = self else: # create a new stack for the single card to go into drug = Stack(self.canvas, self.x, self.y) moved_card = self.cards[len(self.cards)-1] bounds = goocanvas.Bounds() bounds = moved_card.drawables.get_bounds() moved_card.drawables.set_transform(cairo.Matrix()) moved_card.drawables.props.parent = drug.drawables drug.append(moved_card) drug.parent_stack = self drug.drawables.translate(bounds.x1 + moved_card.border_width, bounds.y1 + moved_card.border_width) drug.drawables.raise_(None) fleur = gtk.gdk.Cursor (gtk.gdk.FLEUR) self.canvas.pointer_grab (drug.drawables, gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.BUTTON_RELEASE_MASK, fleur, event.time) drug.dragging = True drug.drag_x = event.x drug.drag_y = event.y def on_button_release (self, item, target, event): self.canvas.pointer_ungrab (self.drawables, event.time) self.dragging = False if self.parent_stack: if self.canvas.drag_selection is not self.parent_stack: self.parent_stack.cards.pop() self.parent_stack = False else: print "Drug back to parent." class Card: width = 200 height = 300 rim_width = 12 border_width = 2 border_color = "gray" background_color = "tan" backside_color = "brown" flipped = False title = None drawables = False def __init__(self, title = None): self.title = title def draw(self, root): self.drawables = goocanvas.Group(parent = root) self.border = goocanvas.Rect (parent = self.drawables, x = 0, y = 0, width = self.width, height = self.height, fill_color = self.border_color, radius_x = 10, radius_y = 10) if self.flipped: goocanvas.Rect (parent = self.drawables, x = self.rim_width, y = self.rim_width, width = self.width - (2 * self.rim_width), height = self.height - (2 * self.rim_width), fill_color = self.backside_color) else: goocanvas.Rect (parent = self.drawables, x = self.rim_width, y = self.rim_width, width = self.width - (2 * self.rim_width), height = self.height - (2 * self.rim_width), fill_color = self.background_color) if self.title is not None: text = goocanvas.Text(parent = self.drawables, text = self.title, x = self.rim_width * 1.5, y = self.rim_width * 1.5) def setup_canvas (canvas): s = Stack(canvas) for i in range(10): s.append(Card(title = "TEST " + str(i))) #s = Stack(canvas, 500, 500) #for i in range(10): # s.append(Card(title = "TEST " + str(i))) def create_card_page (): canvas = PlaySurface() canvas.set_size_request (600, 450) canvas.set_bounds (0, 0, 1000, 1000) vbox = gtk.VBox (False, 4) vbox.set_border_width (4) hbox = gtk.HBox (False, 4) vbox.pack_start (hbox, False, False, 0) w = gtk.Label ("Zoom:") hbox.pack_start (w, False, False, 0) adj = gtk.Adjustment (1.00, 0.05, 1.00, 0.05, 0.50, 0.50) adj.connect("value_changed", canvas.zoom_changed, canvas) w = gtk.SpinButton (adj, 0.0, 2) w.set_size_request (50, -1) hbox.pack_start (w, False, False, 0) scrolled_win = gtk.ScrolledWindow () scrolled_win.set_shadow_type (gtk.SHADOW_IN) vbox.add (scrolled_win) canvas.scrolled_win = scrolled_win scrolled_win.add (canvas) setup_canvas (canvas) return vbox class PlaySurface(goocanvas.Canvas): dragging = False drag_selection = False def __init__(self): gtk.Widget.__init__(self) #self.connect ("motion_notify_event", self.on_motion_notify) #self.connect ("button_press_event", self.on_button_press) #self.connect ("button_release_event", self.on_button_release) self.root = self.get_root_item() self.scroll_speed = 1 def zoom_changed (self, adj, canvas): canvas.set_scale (adj.get_value()) def on_motion_notify (self, item, event): if self.dragging and event.state and gtk.gdk.BUTTON1_MASK: self.scrolled_win.get_hadjustment().set_value(self.scrolled_win.get_hadjustment().value + ((event.x - self.drag_x) * self.scroll_speed)) self.scrolled_win.get_vadjustment().set_value(self.scrolled_win.get_vadjustment().value + ((event.y - self.drag_y) * self.scroll_speed)) self.drag_x = event.x self.drag_y = event.y def on_button_press (self, item, event): if event.button == 1: fleur = gtk.gdk.Cursor (gtk.gdk.FLEUR) self.pointer_grab (self.root, gtk.gdk.POINTER_MOTION_MASK | gtk.gdk.BUTTON_RELEASE_MASK, fleur, event.time) self.dragging = True self.drag_x = event.x self.drag_y = event.y def on_button_release (self, item, event): self.pointer_ungrab (self.root, event.time) self.dragging = False def main (): v = create_card_page () w = gtk.Window() w.connect("destroy", gtk.main_quit) w.add(v) w.show_all() gtk.main() if __name__ == "__main__": main()