diff --git a/gpfile.py b/gpfile.py index 598e984..b2cda09 100644 --- a/gpfile.py +++ b/gpfile.py @@ -52,8 +52,8 @@ def __init__(self, filename): try: self.graph = model.Graph(datastore, head_ptr) # after this, should be all loaded - except model.Error: - print 'failed to open gp file' + except model.Error as e: + print 'failed to open gp file:', e raise ValueError def load_default_config(self): diff --git a/graphpaper.py b/graphpaper.py index 55dc8df..84bbbe1 100755 --- a/graphpaper.py +++ b/graphpaper.py @@ -46,11 +46,27 @@ def __init__(self, master, gpfile): self.canvas.bind("", self.mousemove) self.canvas.bind("", self.resize) # load cards - self.cards = [ViewportCard(self, self.gpfile, card) for card in self.data.get_cards()] + cards_by_id = {} + self.cards = [] + for card in self.data.get_cards(): + new = ViewportCard(self, self.gpfile, card) + self.cards.append(new) + cards_by_id[card.obj.oid] = new self.reset_scroll_region() + # load edges + self.edges = [] + for edge in self.data.get_edges(): + new = ViewportEdge( + self, + self.gpfile, + edge, + cards_by_id[edge.orig.obj.oid], + cards_by_id[edge.dest.obj.oid] + ) + self.edges.append(new) # test edges - edge = model.Edge(self.data, orig=self.data.get_cards()[0], dest=self.data.get_cards()[1]) - self.edge = ViewportEdge(self, self.gpfile, edge, self.cards[0], self.cards[1]) +# edge = model.Edge(self.data, orig=self.data.get_cards()[0], dest=self.data.get_cards()[1]) +# self.edge = ViewportEdge(self, self.gpfile, edge, self.cards[0], self.cards[1]) # set up scrolling self.yscroll["command"] = self.canvas.yview self.xscroll["command"] = self.canvas.xview diff --git a/model.py b/model.py index c9b0fdb..41ba81f 100644 --- a/model.py +++ b/model.py @@ -65,6 +65,10 @@ def get_cards(self): ''' return self.cards + def get_edges(self): + "as get_cards()" + return self.edges + def new_card(self, x=0, y=0, w=MIN_CARD_SIZE, h=MIN_CARD_SIZE): c = Card(self, None) c.x = x diff --git a/viewportcard.py b/viewportcard.py index 72e5ab4..1b2dcb0 100644 --- a/viewportcard.py +++ b/viewportcard.py @@ -5,11 +5,27 @@ from tkex import ResizableCanvasFrame from slot import Slot +from viewportedge import ViewportEdge + class ViewportCard(object): ''' Manages the graphical representation of a card in a Tkinter canvas. Creates and destroys items as necessary, facilitates editing, and so on and so forth. + + Members: + * card: model.Card + * viewport: GPViewport + * gpfile: gpfile.GraphPaperFile, contains model.graph() + * canvas: TKinter canvas we get drawn on + * editing: bool, text is being edited + * moving: bool, being dragged + * moving_edgescroll_id: callback id to scroll periodicall when hovering + near edge of screen + * resize_state: {} + * resize_edgescroll_id: as moving_edgescroll_id + * slot: calls callbacks whenever geometry changes + * new_edge: if an edge is being dragged out from a handle, this is it. ''' def __init__(self, viewport, gpfile, card): self.card = card @@ -25,6 +41,7 @@ def __init__(self, viewport, gpfile, card): # slot triggered when geometry (pos/size) changes # fn args: (self, x, y, w, h) self.slot = Slot() + self.new_edge = None def draw(self): self.frame_thickness = 5 @@ -78,9 +95,9 @@ def move(event): def dblclick(event): print 'item double-click' return 'break' - self.canvas.tag_bind(new, '', foo) - self.canvas.tag_bind(new, '', move) - self.canvas.tag_bind(new, '', dblclick) + self.canvas.tag_bind(new, '', self.handle_click) + self.canvas.tag_bind(new, '', self.handle_mousemove) + self.canvas.tag_bind(new, '', self.handle_mouseup) return new x, y = self.window.canvas_coords() w, h = self.window.winfo_width(), self.window.winfo_height() @@ -182,6 +199,28 @@ def mouseup(self, event): self.cancel_moving_edgescroll_callback() self.geometry_callback() + # next several functions are bound to the circular edge handles + def handle_click(self, event): + # create new edge + self.new_edge = ViewportEdge( + self.viewport, + self.gpfile, + None, + self, + None + ) + self.new_edge.mousemove(event) # give it a real start pos + + def handle_mousemove(self, event): + if self.new_edge: + self.new_edge.mousemove(event) + + def handle_mouseup(self, event): + if self.new_edge: + self.new_edge.mouseup(event) + self.new_edge = None + + def configure(self, event): print 'configure' self.redraw_edge_handles() diff --git a/viewportedge.py b/viewportedge.py index aa29e7f..1549c1d 100644 --- a/viewportedge.py +++ b/viewportedge.py @@ -3,6 +3,7 @@ ''' from math import sqrt +import model class ViewportEdge(object): ''' @@ -26,20 +27,49 @@ class ViewportEdge(object): ''' def __init__(self, viewport, gpfile, edge, orig, dest): ''' + Either load an edge from the datastore, or start creating + a new one. If edge is None, we're creating a new edge and + one of orig or dest should be None. Otherwise, edge should be a + model.Edge and orig and dest should both be ViewportCards + corresponding to the cards in edge. + Arguments: * viewport: GPViewport this edge lives in * gpfile: GPFile, needed for committing - * edge: model.Edge that we will be managing. + * edge: model.Edge that we will be managing, or None if creating a new edge. + * orig: ViewportCard or None, if dragging a new edge + * dest: as above, but more likely. ''' # store all the arguments self.edge = edge self.viewport = viewport self.canvas = viewport.canvas self.gpfile = gpfile + # store nodes + self.orig_callback = self.dest_callback = None # callback attrs needed before setting nodes + self.orig = orig + self.dest = dest + # we basically need to decide whether to start off dragging + if edge: + # member vars are all good, theoretically. + # just need to self self.coords + self.reset_coords() + # not dragging. + self.dragging_end = None # or 0 or 1 + else: + # we start off dragging + if self.orig: + self.dragging_end = 1 + nondrag = self.orig + elif self.dest: + self.dragging_end = 0 + nondrag = self.dest + # use fake initial pos + initpos = nondrag.canvas_coords() + self.coords = [initpos, (initpos[0] + 10, initpos[1] + 10)] + self._highlighted_card = None # draw self - self.reset_coords() self.itemid = self.canvas.create_line( - #0, 0, 100, 0, 100, 100, 200, 100, # have to unpack self.get_coords as first args, not last *(self.get_coords()), arrow='last', @@ -51,13 +81,6 @@ def __init__(self, viewport, gpfile, edge, orig, dest): self.canvas.tag_bind(self.itemid, "", self.click) self.canvas.tag_bind(self.itemid, "", self.mousemove) self.canvas.tag_bind(self.itemid, "", self.mouseup) - # set up state - self.orig_callback = self.dest_callback = None - self.orig = orig - self.dest = dest - # drag state - self.dragging_end = None # or 0 or 1 - self._highlighted_card = None def refresh(self): self.canvas.coords(self.itemid, *self.get_coords()) @@ -65,11 +88,7 @@ def refresh(self): def reset_coords(self): ''' Set self.coords based on current cards. Only call when orig and - dest are valid. - - Calculates from the position of self.edge.orig/dest, - and at some point from the mouse position I guess. - Straight line between the centers of orig and dest. + dest are valid. Straight line between the centers of orig and dest. ''' # watch out for loss of sync between viewport cards and model card # also, this will have to be rewritten at some point so any @@ -163,11 +182,18 @@ def mouseup(self, event): self.orig = card else: self.dest = card + # create edge if needed (if this is first time edge is finished) + if self.edge is None: + self.edge = self.gpfile.graph.new_edge( + orig = self.orig.card, + dest = self.dest.card + ) # update graphics self.reset_coords() self.refresh() self.dragging_end = None self.highlighted_card = None + self.gpfile.commit() def get_highlighted_card(self): return self._highlighted_card @@ -189,7 +215,8 @@ def set_orig(self, orig): self._orig = orig if orig: self.orig_callback = orig.add_signal(self.geometry_callback) - self.edge.orig = orig.card + if self.edge: + self.edge.orig = orig.card orig = property(get_orig, set_orig) def get_dest(self): @@ -200,9 +227,17 @@ def set_dest(self, dest): self._dest = dest if dest: self.dest_callback = dest.add_signal(self.geometry_callback) - self.edge.dest = dest.card + if self.edge: + self.edge.dest = dest.card dest = property(get_dest, set_dest) + @property + def non_dragging_end(self): + if self.dragging_end is not None: + # assume correct value of 0 or 1 + return int(not self.dragging_end) + return None + def adjust_point(p1, box, p2): ''' Moves p1 along the line p1<->p2 to be on an edge of box