From baa35ebb34a22d240b891370c5903f793580b145 Mon Sep 17 00:00:00 2001 From: pnrao Date: Sat, 12 Apr 2014 18:01:40 +0200 Subject: [PATCH 01/18] Three buttons for Next, Previous, Exit --- xdot.py | 146 ++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 125 insertions(+), 21 deletions(-) diff --git a/xdot.py b/xdot.py index 657ccee..150a699 100755 --- a/xdot.py +++ b/xdot.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python2 # # Copyright 2008 Jose Fonseca # @@ -522,7 +522,7 @@ def __init__(self, parser, buf): self.parser = parser self.buf = buf self.pos = 0 - + self.pen = Pen() self.shapes = [] @@ -691,7 +691,7 @@ def parse(self): sys.exit(1) return self.shapes - + def transform(self, x, y): return self.parser.transform(x, y) @@ -763,7 +763,7 @@ def __init__(self, msg=None, filename=None, line=None, col=None): def __str__(self): return ':'.join([str(part) for part in (self.filename, self.line, self.col, self.msg) if part != None]) - + class Scanner: """Stateless scanner.""" @@ -902,9 +902,9 @@ def __init__(self, lexer): def match(self, type): if self.lookahead.type != type: raise ParseError( - msg = 'unexpected token %r' % self.lookahead.text, - filename = self.lexer.filename, - line = self.lookahead.line, + msg = 'unexpected token %r' % self.lookahead.text, + filename = self.lexer.filename, + line = self.lookahead.line, col = self.lookahead.col) def skip(self, type): @@ -1007,7 +1007,7 @@ def filter(self, type, text): text = text.replace('\\\r\n', '') text = text.replace('\\\r', '') text = text.replace('\\\n', '') - + # quotes text = text.replace('\\"', '"') @@ -1151,7 +1151,7 @@ class XDotParser(DotParser): def __init__(self, xdotcode): lexer = DotLexer(buf = xdotcode) DotParser.__init__(self, lexer) - + self.nodes = [] self.edges = [] self.shapes = [] @@ -1219,7 +1219,7 @@ def handle_edge(self, src_id, dst_id, attrs): pos = attrs['pos'] except KeyError: return - + points = self.parse_edge_pos(pos) shapes = [] for attr in ("_draw_", "_ldraw_", "_hdraw_", "_tdraw_", "_hldraw_", "_tldraw_"): @@ -1903,6 +1903,10 @@ class DotWindow(gtk.Window): + + + + @@ -1924,6 +1928,8 @@ def __init__(self, widget=None): self.widget = widget or DotWidget() + self.connect('key-press-event', self.on_window_key_press_event) + # Create a UIManager instance uimanager = self.uimanager = gtk.UIManager() @@ -1944,6 +1950,9 @@ def __init__(self, widget=None): ('ZoomOut', gtk.STOCK_ZOOM_OUT, None, None, None, self.widget.on_zoom_out), ('ZoomFit', gtk.STOCK_ZOOM_FIT, None, None, None, self.widget.on_zoom_fit), ('Zoom100', gtk.STOCK_ZOOM_100, None, None, None, self.widget.on_zoom_100), + ('Previous', gtk.STOCK_GO_BACK, None, None, None, self.on_prev), + ('Next', gtk.STOCK_GO_FORWARD, None, None, None, self.on_next), + ('Quit', gtk.STOCK_QUIT, None, None, None, self.on_quit), )) find_action = FindMenuToolAction("Find", None, @@ -1960,6 +1969,14 @@ def __init__(self, widget=None): toolbar = uimanager.get_widget('/ToolBar') vbox.pack_start(toolbar, False) + # Create a text entry box for the filename + file_entry = gtk.Entry() + self.file_entry = file_entry + file_entry.set_has_frame(True) + file_entry.set_text('') + file_entry.connect("activate", self.on_text_open) + vbox.pack_start(file_entry, False) + vbox.pack_start(self.widget) self.last_open_dir = "." @@ -2021,7 +2038,7 @@ def set_xdotcode(self, xdotcode, filename=None): if self.widget.set_xdotcode(xdotcode): self.update_title(filename) self.widget.zoom_to_fit() - + def update_title(self, filename=None): if filename is None: self.set_title(self.base_title) @@ -2030,6 +2047,7 @@ def update_title(self, filename=None): def open_file(self, filename): try: + self.file_entry.set_text(filename) fp = file(filename, 'rt') self.set_dotcode(fp.read(), filename) fp.close() @@ -2041,6 +2059,30 @@ def open_file(self, filename): dlg.run() dlg.destroy() + def build_file_list(self, filepath): + try: + filepath = os.path.abspath(filepath) + if os.path.isdir(filepath): + directory = filepath + else: + directory = os.path.dirname(filepath) + sori = lambda x: (int(x) if x.isdigit() else x) # returns s(tring) or i(nteger) + natkey = lambda x: [sori(y) for y in re.split(r'(\d+)', x)] + self.files_in_dir = sorted([ os.path.join(directory, f) for f in os.listdir(directory) if f.endswith('.dot')], + key=natkey) + if os.path.isdir(filepath): + self.file_index = 0 + else: + self.file_index = self.files_in_dir.index(filepath) + self.file_dir = directory + except Exception as ex: + dlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR, + message_format=str(ex), + buttons=gtk.BUTTONS_OK) + dlg.set_title(self.base_title) + dlg.run() + dlg.destroy() + def on_open(self, action): chooser = gtk.FileChooserDialog(title="Open dot File", action=gtk.FILE_CHOOSER_ACTION_OPEN, @@ -2058,17 +2100,78 @@ def on_open(self, action): filter.set_name("All files") filter.add_pattern("*") chooser.add_filter(filter) + try: + chooser.set_current_folder(self.file_dir) + except AttributeError: + pass # Will happen on the first call, because self.file_dir would not exist + except: + raise if chooser.run() == gtk.RESPONSE_OK: filename = chooser.get_filename() self.last_open_dir = chooser.get_current_folder() chooser.destroy() + self.build_file_list(filename) self.open_file(filename) else: chooser.destroy() + def on_text_open(self, action): + fileentry = self.file_entry.get_text() + if os.path.exists(fileentry): + fileentry = os.path.abspath(fileentry) + self.file_entry.set_text(fileentry) + self.file_entry.set_position(1000) # some large number to push the cursor to the end + + if os.path.isfile(fileentry): + self.build_file_list(fileentry) + self.open_file(fileentry) + self.child_focus(gtk.DIR_TAB_FORWARD) + elif os.path.isdir(fileentry): + self.build_file_list(fileentry) + if self.files_in_dir: # found .dot files + self.open_file(self.files_in_dir[0]) + self.child_focus(gtk.DIR_TAB_FORWARD) + def on_reload(self, action): self.widget.reload() + def on_quit(self, action): + gtk.main_quit() + + def on_next(self, action): + try: + self.file_index += 1 + if self.file_index >= len(self.files_in_dir): + self.file_index = 0 + self.open_file(self.files_in_dir[self.file_index]) + except: + # can happen when the button is pushed with no file loaded + pass + + def on_prev(self, action): + try: + self.file_index -= 1 + if self.file_index < 0: + self.file_index = len(self.files_in_dir)-1 + self.open_file(self.files_in_dir[self.file_index]) + except: + # can happen when the button is pushed with no file loaded + pass + + def on_window_key_press_event(self, widget, event): + if self.file_entry.is_focus(): + return False + if event.keyval == gtk.keysyms.p or event.keyval == gtk.keysyms.j: + self.on_prev(None) + return True + if event.keyval == gtk.keysyms.n or event.keyval == gtk.keysyms.k: + self.on_next(None) + return True + if event.keyval == gtk.keysyms.o: + self.on_open(None) + return True + return False + class OptionParser(optparse.OptionParser): @@ -2120,30 +2223,31 @@ def main(): if args[0] == '-': win.set_dotcode(sys.stdin.read()) else: + win.build_file_list(args[0]) win.open_file(args[0]) gtk.main() # Apache-Style Software License for ColorBrewer software and ColorBrewer Color # Schemes, Version 1.1 -# +# # Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The Pennsylvania State # University. All rights reserved. -# +# # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: -# +# # 1. Redistributions as source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. +# this list of conditions and the following disclaimer. # # 2. The end-user documentation included with the redistribution, if any, # must include the following acknowledgment: -# +# # This product includes color specifications and designs developed by # Cynthia Brewer (http://colorbrewer.org/). -# +# # Alternately, this acknowledgment may appear in the software itself, if and -# wherever such third-party acknowledgments normally appear. +# wherever such third-party acknowledgments normally appear. # # 3. The name "ColorBrewer" must not be used to endorse or promote products # derived from this software without prior written permission. For written @@ -2151,8 +2255,8 @@ def main(): # # 4. Products derived from this software may not be called "ColorBrewer", # nor may "ColorBrewer" appear in their name, without prior written -# permission of Cynthia Brewer. -# +# permission of Cynthia Brewer. +# # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND # FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CYNTHIA @@ -2162,7 +2266,7 @@ def main(): # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. brewer_colors = { 'accent3': [(127, 201, 127), (190, 174, 212), (253, 192, 134)], 'accent4': [(127, 201, 127), (190, 174, 212), (253, 192, 134), (255, 255, 153)], From 2bc9bc335cb12896b24a915aba07c92c40eddf55 Mon Sep 17 00:00:00 2001 From: RHOsanghoon Date: Tue, 22 Apr 2014 13:59:05 +0900 Subject: [PATCH 02/18] remove null action's highlight reset --- xdot.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xdot.py b/xdot.py index 657ccee..0aa3faf 100755 --- a/xdot.py +++ b/xdot.py @@ -1408,7 +1408,8 @@ def on_motion_notify(self, event): dot_widget.set_highlight(item.highlight) else: dot_widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.ARROW)) - dot_widget.set_highlight(None) + # don't reset highlights when just move + # dot_widget.set_highlight(None) class PanAction(DragAction): From 7183abfc3b2f882a23104eaf904d534757c29365 Mon Sep 17 00:00:00 2001 From: RHOsanghoon Date: Tue, 22 Apr 2014 14:51:19 +0900 Subject: [PATCH 03/18] navigate to highlighted item by F3 key --- .gitignore | 1 + xdot.py | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index dd51734..09c8183 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .*.sw? *.pyc *.egg-info +.project build dist diff --git a/xdot.py b/xdot.py index 0aa3faf..8c02a94 100755 --- a/xdot.py +++ b/xdot.py @@ -1479,7 +1479,8 @@ class DotWidget(gtk.DrawingArea): } filter = 'dot' - + focused = None + def __init__(self): gtk.DrawingArea.__init__(self) @@ -1741,8 +1742,23 @@ def on_key_press_event(self, widget, event): if event.keyval == gtk.keysyms.p: self.on_print() return True + if event.keyval == gtk.keysyms.F3: + # drnol: jump to next highlighted item + self.jump_to_next_highlight() + return True return False + # drnol: jump to next highlighted item + def jump_to_next_highlight(self): + if self.highlight: + if len(self.highlight) > 0: + if self.focused == None: + self.focused = 0 + else: + self.focused = (self.focused+1) % len(self.highlight) + item = self.highlight[self.focused] + self.animate_to(item.x, item.y) + print_settings = None def on_print(self, action=None): print_op = gtk.PrintOperation() From 5cb884e6d68113a6f70947b7319c8f13626bceb7 Mon Sep 17 00:00:00 2001 From: RHOsanghoon Date: Tue, 22 Apr 2014 14:57:37 +0900 Subject: [PATCH 04/18] jump to prev highlighted item by F2 key added --- README.markdown | 2 ++ xdot.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/README.markdown b/README.markdown index 09ffad0..99651cc 100644 --- a/README.markdown +++ b/README.markdown @@ -103,6 +103,8 @@ Command Line Escape halt animation Ctrl-drag zoom in/out Shift-drag zooms an area + F2 prev highlighted item + F3 next highlighted item If no input file is given then it will read the dot graph from the standard input. diff --git a/xdot.py b/xdot.py index 8c02a94..1e34726 100755 --- a/xdot.py +++ b/xdot.py @@ -1742,12 +1742,27 @@ def on_key_press_event(self, widget, event): if event.keyval == gtk.keysyms.p: self.on_print() return True + if event.keyval == gtk.keysyms.F2: + # drnol: jump to next highlighted item + self.jump_to_prev_highlight() + return True if event.keyval == gtk.keysyms.F3: # drnol: jump to next highlighted item self.jump_to_next_highlight() return True return False + # drnol: jump to prev highlighted item + def jump_to_prev_highlight(self): + if self.highlight: + if len(self.highlight) > 0: + if self.focused == None: + self.focused = 0 + else: + self.focused = (self.focused+1) % len(self.highlight) + item = self.highlight[len(self.highlight)-self.focused-1] + self.animate_to(item.x, item.y) + # drnol: jump to next highlighted item def jump_to_next_highlight(self): if self.highlight: @@ -2111,6 +2126,8 @@ def main(): Escape halt animation Ctrl-drag zoom in/out Shift-drag zooms an area + F2 next highlighted item + F3 next highlighted item ''' ) parser.add_option( From 634b83aad6c789de01a2db99a6e743a9856bd37c Mon Sep 17 00:00:00 2001 From: RHOsanghoon Date: Tue, 22 Apr 2014 16:10:41 +0900 Subject: [PATCH 05/18] * keyboard edge selection feature added ([,] key) * keyboard edge following feature added (Enter key) --- README.markdown | 3 ++ xdot.py | 131 +++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 117 insertions(+), 17 deletions(-) diff --git a/README.markdown b/README.markdown index 99651cc..bbaa1fe 100644 --- a/README.markdown +++ b/README.markdown @@ -105,6 +105,9 @@ Command Line Shift-drag zooms an area F2 prev highlighted item F3 next highlighted item + [ select prev focused node's edge + ] select next focused node's edge + Enter follow selected edge If no input file is given then it will read the dot graph from the standard input. diff --git a/xdot.py b/xdot.py index 1e34726..a1e5532 100755 --- a/xdot.py +++ b/xdot.py @@ -458,7 +458,23 @@ def __init__(self, width=1, height=1, shapes=(), nodes=(), edges=()): self.shapes = shapes self.nodes = nodes self.edges = edges - + self.build_edge_map() # drnol: build edge lookup map + + # drnol: build edge lookup map + def build_edge_map(self): + self.edgemap = {} + for edge in self.edges: + # add src + if self.edgemap.has_key(edge.src): + self.edgemap[edge.src].append(edge) + else: + self.edgemap[edge.src] = [edge] + # add dst + if self.edgemap.has_key(edge.dst): + self.edgemap[edge.dst].append(edge) + else: + self.edgemap[edge.dst] = [edge] + def get_size(self): return self.width, self.height @@ -491,7 +507,7 @@ def get_url(self, x, y): if url is not None: return url return None - + def get_jump(self, x, y): for edge in self.edges: jump = edge.get_jump(x, y) @@ -1479,7 +1495,9 @@ class DotWidget(gtk.DrawingArea): } filter = 'dot' - focused = None + focused_index = None + selected_node = None + selected_edge = None def __init__(self): gtk.DrawingArea.__init__(self) @@ -1625,6 +1643,9 @@ def set_current_pos(self, x, y): def set_highlight(self, items): if self.highlight != items: + self.focused_index = None # drnol: reset node focus + self.selected_node = None # drnol: reset node selection + self.selected_edge_index = None # drnol: reset edge selection self.highlight = items self.queue_draw() @@ -1743,37 +1764,105 @@ def on_key_press_event(self, widget, event): self.on_print() return True if event.keyval == gtk.keysyms.F2: - # drnol: jump to next highlighted item + # drnol: jump to prev highlighted item self.jump_to_prev_highlight() return True if event.keyval == gtk.keysyms.F3: # drnol: jump to next highlighted item self.jump_to_next_highlight() return True + if event.keyval == gtk.keysyms.bracketleft: + # drnol: select prev edge of focused node + self.select_prev_edge_of_focused_node() + return True + if event.keyval == gtk.keysyms.bracketright: + # drnol: select next edge of focused node + self.select_next_edge_of_focused_node() + return True + if event.keyval == gtk.keysyms.Return: + # drnol: follow selected edge + self.follow_selected_edge() + return True return False # drnol: jump to prev highlighted item def jump_to_prev_highlight(self): if self.highlight: if len(self.highlight) > 0: - if self.focused == None: - self.focused = 0 + if (self.focused_index == None) or (self.focused_index == 0): + self.focused_index = len(self.highlight)-1 else: - self.focused = (self.focused+1) % len(self.highlight) - item = self.highlight[len(self.highlight)-self.focused-1] - self.animate_to(item.x, item.y) + self.focused_index = self.focused_index-1 + node = self.highlight[self.focused_index] + self.focus_node(node) # drnol: jump to next highlighted item def jump_to_next_highlight(self): if self.highlight: if len(self.highlight) > 0: - if self.focused == None: - self.focused = 0 + if self.focused_index == None: + self.focused_index = 0 else: - self.focused = (self.focused+1) % len(self.highlight) - item = self.highlight[self.focused] - self.animate_to(item.x, item.y) - + self.focused_index = (self.focused_index+1) % len(self.highlight) + node = self.highlight[self.focused_index] + self.focus_node(node) + + def focus_node(self, node): + self.selected_edge = None + self.selected_node = node + self.animate_to(node.x, node.y) + + # drnol: select prev edge of focused node + def select_prev_edge_of_focused_node(self): + if self.selected_node != None: + if self.graph.edgemap.has_key(self.selected_node): + edges = self.graph.edgemap[self.selected_node] + # if there is more than one edge in the selected node + if len(edges) > 0: + if (self.selected_edge_index == None) or (self.selected_edge_index == 0): + self.selected_edge_index = len(selected_edge_index)-1 + else: + self.selected_edge_index = self.selected_edge_index-1 + edge = edges[self.selected_edge_index] + self.select_edge_with_highlight(edge) + + # drnol: select next edge of focused node + def select_next_edge_of_focused_node(self): + if self.selected_node != None: + if self.graph.edgemap.has_key(self.selected_node): + edges = self.graph.edgemap[self.selected_node] + # if there is more than one edge in the selected node + if len(edges) > 0: + if self.selected_edge_index == None: + self.selected_edge_index = 0 + else: + self.selected_edge_index = (self.selected_edge_index+1) % len(edges) + edge = edges[self.selected_edge_index] + self.select_edge_with_highlight(edge) + + #drnol: select edge + def select_edge_with_highlight(self, edge): + # don't use set_highlight, because set_highlight performs resetting selections + self.highlight = [self.selected_node, edge] + self.queue_draw() + + #drnol: follow selected edge + def follow_selected_edge(self): + if (self.selected_node != None) and (self.selected_edge_index != None): + edges = self.graph.edgemap[self.selected_node] + edge = edges[self.selected_edge_index] + + # follow to opposite node + if edge.src == self.selected_node: + target = edge.dst + else: + target = edge.src + + # jump to selected node + self.focus_node(target) + self.highlight = [target] + self.queue_draw() + print_settings = None def on_print(self, action=None): print_op = gtk.PrintOperation() @@ -1860,6 +1949,11 @@ def on_area_button_release(self, area, event): jump = self.get_jump(x, y) if jump is not None: self.animate_to(jump.x, jump.y) + + # drnol: node selection + element = self.get_element(x, y) + if isinstance(element, Node): + self.selected_node = element return True @@ -2125,9 +2219,12 @@ def main(): P print Escape halt animation Ctrl-drag zoom in/out - Shift-drag zooms an area - F2 next highlighted item + Shift-drag zooms an area + F2 prev highlighted item F3 next highlighted item + [ select prev focused node's edge + ] select next focused node's edge + Enter follow selected edge ''' ) parser.add_option( From b0276210200168444dbaac22c53c484ecdfc843c Mon Sep 17 00:00:00 2001 From: RHOsanghoon Date: Tue, 22 Apr 2014 17:02:57 +0900 Subject: [PATCH 06/18] edge following improvement * automatically select the other edge when following edge * if there is no other edge then select same one --- xdot.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/xdot.py b/xdot.py index a1e5532..67689a4 100755 --- a/xdot.py +++ b/xdot.py @@ -1860,9 +1860,25 @@ def follow_selected_edge(self): # jump to selected node self.focus_node(target) - self.highlight = [target] + + # automatically select one of other edges + self.selected_edge_index, new_edge = self.try_find_other_edge_index(edge) + self.highlight = [target, new_edge] self.queue_draw() + #drnol: try finding the other edge + def try_find_other_edge_index(self, edge): + edges = self.graph.edgemap[self.selected_node] + + edge_index = 0 + new_edge = edges[0] + for i in range(1, len(edges)): + if edges[i] != edge: + edge_index = i + new_edge = edges[i] + + return edge_index, new_edge + print_settings = None def on_print(self, action=None): print_op = gtk.PrintOperation() From f53308e0bca2d121bda2134b7cae2b5adc24258f Mon Sep 17 00:00:00 2001 From: RHOsanghoon Date: Tue, 22 Apr 2014 17:20:01 +0900 Subject: [PATCH 07/18] prev edge bugfix and change keybiding * fixed prev edge's bug * keybinding changed from [ ] to , . --- README.markdown | 4 ++-- xdot.py | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.markdown b/README.markdown index bbaa1fe..4b59bb4 100644 --- a/README.markdown +++ b/README.markdown @@ -105,8 +105,8 @@ Command Line Shift-drag zooms an area F2 prev highlighted item F3 next highlighted item - [ select prev focused node's edge - ] select next focused node's edge + , select prev focused node's edge + . select next focused node's edge Enter follow selected edge If no input file is given then it will read the dot graph from the standard input. diff --git a/xdot.py b/xdot.py index 67689a4..06043a6 100755 --- a/xdot.py +++ b/xdot.py @@ -1771,11 +1771,11 @@ def on_key_press_event(self, widget, event): # drnol: jump to next highlighted item self.jump_to_next_highlight() return True - if event.keyval == gtk.keysyms.bracketleft: + if event.keyval == gtk.keysyms.comma: # drnol: select prev edge of focused node self.select_prev_edge_of_focused_node() return True - if event.keyval == gtk.keysyms.bracketright: + if event.keyval == gtk.keysyms.period: # drnol: select next edge of focused node self.select_next_edge_of_focused_node() return True @@ -1819,8 +1819,8 @@ def select_prev_edge_of_focused_node(self): edges = self.graph.edgemap[self.selected_node] # if there is more than one edge in the selected node if len(edges) > 0: - if (self.selected_edge_index == None) or (self.selected_edge_index == 0): - self.selected_edge_index = len(selected_edge_index)-1 + if (self.selected_edge_index == None) or (self.selected_edge_index <= 0): + self.selected_edge_index = len(edges)-1 else: self.selected_edge_index = self.selected_edge_index-1 edge = edges[self.selected_edge_index] @@ -2238,8 +2238,8 @@ def main(): Shift-drag zooms an area F2 prev highlighted item F3 next highlighted item - [ select prev focused node's edge - ] select next focused node's edge + , select prev focused node's edge + . select next focused node's edge Enter follow selected edge ''' ) From 59dd667f79a818f526d6be683a8dda2dc49ba029 Mon Sep 17 00:00:00 2001 From: RHOsanghoon Date: Tue, 22 Apr 2014 17:52:12 +0900 Subject: [PATCH 08/18] improvement node selection now hover selection supported --- xdot.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/xdot.py b/xdot.py index 06043a6..9a2e3ea 100755 --- a/xdot.py +++ b/xdot.py @@ -1422,9 +1422,12 @@ def on_motion_notify(self, event): if item is not None: dot_widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2)) dot_widget.set_highlight(item.highlight) + + # drnol: selection nodes + dot_widget.focus_node_at(x,y) else: dot_widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.ARROW)) - # don't reset highlights when just move + # drnoL: don't reset highlights when just move # dot_widget.set_highlight(None) @@ -1643,9 +1646,12 @@ def set_current_pos(self, x, y): def set_highlight(self, items): if self.highlight != items: + # drnol: focusing and selection / reset self.focused_index = None # drnol: reset node focus self.selected_node = None # drnol: reset node selection - self.selected_edge_index = None # drnol: reset edge selection + self.selected_edge_index = None # drnol: reset edge selection + + # original action self.highlight = items self.queue_draw() @@ -1807,11 +1813,19 @@ def jump_to_next_highlight(self): node = self.highlight[self.focused_index] self.focus_node(node) + # drnol: focus node def focus_node(self, node): self.selected_edge = None self.selected_node = node self.animate_to(node.x, node.y) + # drnol: focus node by coordinate + def focus_node_at(self, x, y): + elt = self.get_element(x, y) + if (elt != None) and (isinstance(elt,Node)): + self.focused_index = 0 + self.selected_node = elt + # drnol: select prev edge of focused node def select_prev_edge_of_focused_node(self): if self.selected_node != None: From 69e5184992e5e31f7f69d1846d6bfd8bd96facf9 Mon Sep 17 00:00:00 2001 From: RHOsanghoon Date: Tue, 22 Apr 2014 20:42:06 +0900 Subject: [PATCH 09/18] Display shortest path * hover selection removed, now you must CLICK to select * click start node and ctrl-click end node, then shortest path will be displayed --- README.markdown | 1 + xdot.py | 105 +++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 96 insertions(+), 10 deletions(-) diff --git a/README.markdown b/README.markdown index 4b59bb4..314597f 100644 --- a/README.markdown +++ b/README.markdown @@ -108,6 +108,7 @@ Command Line , select prev focused node's edge . select next focused node's edge Enter follow selected edge + Ctrl-click display shortest path If no input file is given then it will read the dot graph from the standard input. diff --git a/xdot.py b/xdot.py index 9a2e3ea..3706c00 100755 --- a/xdot.py +++ b/xdot.py @@ -519,6 +519,57 @@ def get_jump(self, x, y): return jump return None + # drnol: get ui shortest path + # use directed graph + def get_shortest_element_path(self, start, end): + node_path = self.find_shortest_node_path(start, end) + + if (node_path != None) and (len(node_path)>0): + path = [node_path[0]] + for i in range(0, (len(node_path)-1)): + src = node_path[i] + dst = node_path[i+1] + path.append(self.lookup_edge(src,dst)) + path.append(dst) + + return path + else: + dlg = gtk.MessageDialog(type=gtk.MESSAGE_INFO, + message_format="There is no path", + buttons=gtk.BUTTONS_OK) + dlg.set_title("Path Info") + dlg.run() + dlg.destroy() + return [start] + + # drnol: edge lookup by src,dst + def lookup_edge(self,src,dst): + for edge in self.edgemap[src]: + if edge.dst == dst: + return edge + return None + + # drnol: simple shortest path calculator + # just get node path + # TODO: must be optimize + def find_shortest_node_path(self, start, end, path=[]): + path = list(path) # + path.append(start) + + if start == end: + return path + + if not self.edgemap.has_key(start): + return None + + shortest = None + for edge in self.edgemap[start]: + if (edge.src == start) and (edge.dst not in path): + newpath = self.find_shortest_node_path(edge.dst, end, path) + if newpath: + if not shortest or len(newpath) < len(shortest): + shortest = newpath + return shortest BOLD = 1 ITALIC = 2 @@ -1410,27 +1461,32 @@ def abort(self): class NullAction(DragAction): - def on_motion_notify(self, event): + def on_motion_notify(self, event): if event.is_hint: x, y, state = event.window.get_pointer() else: x, y, state = event.x, event.y, event.state + + # drnol: skip when shift is pressed + if state & gtk.gdk.CONTROL_MASK: + return + dot_widget = self.dot_widget item = dot_widget.get_url(x, y) if item is None: item = dot_widget.get_jump(x, y) if item is not None: dot_widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2)) - dot_widget.set_highlight(item.highlight) - + # drnol: don't reset highlights when hover + # dot_widget.set_highlight(item.highlight) + # drnol: selection nodes - dot_widget.focus_node_at(x,y) + # dot_widget.focus_node_at(x,y) else: dot_widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.ARROW)) - # drnoL: don't reset highlights when just move + # drnol: don't reset highlights when just move # dot_widget.set_highlight(None) - - + class PanAction(DragAction): def start(self): @@ -1962,6 +2018,31 @@ def on_click(self, element, event): (click on empty space).""" return False + def show_path(self, end_node): + if (self.highlight != None) and (len(self.highlight) == 1): + elt = list(self.highlight)[0] + if (isinstance(elt,Node)): + self.build_path(elt, end_node) + self.set_highlight(self.path) + self.queue_draw() + else: + dlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR, + message_format="Start node must be NODE", + buttons=gtk.BUTTONS_OK) + dlg.set_title("Path Selection Error!") + dlg.run() + dlg.destroy() + else: + dlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR, + message_format="Only one node allowed as start node", + buttons=gtk.BUTTONS_OK) + dlg.set_title("Path Selection Error!") + dlg.run() + dlg.destroy() + + def build_path(self, start, end): + self.path = self.graph.get_shortest_element_path(start, end) + def on_area_button_release(self, area, event): self.drag_action.on_button_release(event) self.drag_action = NullAction(self) @@ -1982,9 +2063,12 @@ def on_area_button_release(self, area, event): # drnol: node selection element = self.get_element(x, y) - if isinstance(element, Node): - self.selected_node = element - + if isinstance(element, Node): + if event.state & gtk.gdk.CONTROL_MASK: + self.show_path(element) + else: + self.set_highlight([element]) + self.selected_node = element return True if event.button == 1 or event.button == 2: @@ -2255,6 +2339,7 @@ def main(): , select prev focused node's edge . select next focused node's edge Enter follow selected edge + Ctrl-click display shortest path ''' ) parser.add_option( From b9a48ccb97cee17303a1a4179fd75638703d998c Mon Sep 17 00:00:00 2001 From: RHOsanghoon Date: Wed, 23 Apr 2014 13:32:14 +0900 Subject: [PATCH 10/18] Path feature improvement * retargetable path - first node will be pivot node and second node can be change by ctrl-click * reverse path - display reverse path by ctrl-shift-click --- README.markdown | 3 ++- xdot.py | 60 ++++++++++++++++++++++++++++++++----------------- 2 files changed, 42 insertions(+), 21 deletions(-) diff --git a/README.markdown b/README.markdown index 314597f..3486368 100644 --- a/README.markdown +++ b/README.markdown @@ -108,7 +108,8 @@ Command Line , select prev focused node's edge . select next focused node's edge Enter follow selected edge - Ctrl-click display shortest path + Ctrl-click display shortest path (retargetable) + Ctrl-shift-click display reverse shortest path (retargetable) If no input file is given then it will read the dot graph from the standard input. diff --git a/xdot.py b/xdot.py index 3706c00..96e45ef 100755 --- a/xdot.py +++ b/xdot.py @@ -1554,9 +1554,6 @@ class DotWidget(gtk.DrawingArea): } filter = 'dot' - focused_index = None - selected_node = None - selected_edge = None def __init__(self): gtk.DrawingArea.__init__(self) @@ -1586,6 +1583,12 @@ def __init__(self): self.drag_action = NullAction(self) self.presstime = None self.highlight = None + + # drnol: selections and path related variables + self.focused_index = None + self.selected_node = None + self.selected_edge = None + self.path_pivot_node = None def set_filter(self, filter): self.filter = filter @@ -2018,27 +2021,39 @@ def on_click(self, element, event): (click on empty space).""" return False - def show_path(self, end_node): - if (self.highlight != None) and (len(self.highlight) == 1): - elt = list(self.highlight)[0] - if (isinstance(elt,Node)): - self.build_path(elt, end_node) - self.set_highlight(self.path) - self.queue_draw() + # drnol: display shortest path + # if there is one selected node A -> path between A and just before ctrl-clicked node B + # if there are previously displayed path and pivot node A -> path between A and just before ctrl-clicked node B + def show_path(self, end_node, reverse=False): + if self.path_pivot_node == None: + if (self.highlight != None) and (len(self.highlight) == 1): + elt = list(self.highlight)[0] + if (isinstance(elt,Node)): + self.path_pivot_node = elt + else: + dlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR, + message_format="Start node must be NODE", + buttons=gtk.BUTTONS_OK) + dlg.set_title("Path Selection Error!") + dlg.run() + dlg.destroy() + return else: dlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR, - message_format="Start node must be NODE", + message_format="Only one node allowed as start node", buttons=gtk.BUTTONS_OK) dlg.set_title("Path Selection Error!") dlg.run() dlg.destroy() + return + # build path + if reverse: + self.build_path(end_node, self.path_pivot_node) else: - dlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR, - message_format="Only one node allowed as start node", - buttons=gtk.BUTTONS_OK) - dlg.set_title("Path Selection Error!") - dlg.run() - dlg.destroy() + self.build_path(self.path_pivot_node, end_node) + # display path + self.set_highlight(self.path) + self.queue_draw() def build_path(self, start, end): self.path = self.graph.get_shortest_element_path(start, end) @@ -2065,10 +2080,14 @@ def on_area_button_release(self, area, event): element = self.get_element(x, y) if isinstance(element, Node): if event.state & gtk.gdk.CONTROL_MASK: - self.show_path(element) + if event.state & gtk.gdk.SHIFT_MASK: + self.show_path(element, True) # reverse path + else: + self.show_path(element) # normal path else: self.set_highlight([element]) - self.selected_node = element + self.selected_node = element + self.path_pivot_node = None return True if event.button == 1 or event.button == 2: @@ -2339,7 +2358,8 @@ def main(): , select prev focused node's edge . select next focused node's edge Enter follow selected edge - Ctrl-click display shortest path + Ctrl-click display shortest path (retargetable) + Ctrl-shift-click display reverse shortest path (retargetable) ''' ) parser.add_option( From e50e566ff4af5ab0f49513205392602f18d50fe1 Mon Sep 17 00:00:00 2001 From: RHOsanghoon Date: Wed, 23 Apr 2014 14:05:52 +0900 Subject: [PATCH 11/18] Edge selection and node focusing improvement * now edge selection will highlight destination node, too * F3 focus traverse well work with edge/node mixed highlights (i.e. path) --- xdot.py | 45 +++++++++++++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/xdot.py b/xdot.py index 96e45ef..e92aa55 100755 --- a/xdot.py +++ b/xdot.py @@ -1461,7 +1461,7 @@ def abort(self): class NullAction(DragAction): - def on_motion_notify(self, event): + def on_motion_notify(self, event): if event.is_hint: x, y, state = event.window.get_pointer() else: @@ -1554,7 +1554,7 @@ class DotWidget(gtk.DrawingArea): } filter = 'dot' - + def __init__(self): gtk.DrawingArea.__init__(self) @@ -1709,6 +1709,7 @@ def set_highlight(self, items): self.focused_index = None # drnol: reset node focus self.selected_node = None # drnol: reset node selection self.selected_edge_index = None # drnol: reset edge selection + self.path_pivot_node = None # drnol: reset path pivot # original action self.highlight = items @@ -1852,25 +1853,38 @@ def on_key_press_event(self, widget, event): # drnol: jump to prev highlighted item def jump_to_prev_highlight(self): - if self.highlight: + if self.exists_highlighted_node(): if len(self.highlight) > 0: if (self.focused_index == None) or (self.focused_index == 0): self.focused_index = len(self.highlight)-1 else: - self.focused_index = self.focused_index-1 - node = self.highlight[self.focused_index] - self.focus_node(node) + self.focused_index = self.focused_index-1 + element = self.highlight[self.focused_index] + if isinstance(element,Node): + self.focus_node(element) + else: + self.jump_to_prev_highlight() # skip edge # drnol: jump to next highlighted item - def jump_to_next_highlight(self): - if self.highlight: + def jump_to_next_highlight(self): + if self.exists_highlighted_node(): if len(self.highlight) > 0: if self.focused_index == None: self.focused_index = 0 else: self.focused_index = (self.focused_index+1) % len(self.highlight) - node = self.highlight[self.focused_index] - self.focus_node(node) + element = self.highlight[self.focused_index] + if isinstance(element,Node): + self.focus_node(element) + else: + self.jump_to_next_highlight() # skip edge + + # drnol: check existence of highlighted node in highlight list + def exists_highlighted_node(self): + for element in self.highlight: + if isinstance(element,Node): + return True + return False # drnol: focus node def focus_node(self, node): @@ -1916,7 +1930,10 @@ def select_next_edge_of_focused_node(self): #drnol: select edge def select_edge_with_highlight(self, edge): # don't use set_highlight, because set_highlight performs resetting selections - self.highlight = [self.selected_node, edge] + if self.selected_node==edge.src: + self.highlight = [edge.src, edge, edge.dst] + else: + self.highlight = [edge.dst, edge, edge.src] self.queue_draw() #drnol: follow selected edge @@ -2087,7 +2104,11 @@ def on_area_button_release(self, area, event): else: self.set_highlight([element]) self.selected_node = element - self.path_pivot_node = None + self.focused_index = 0 + self.path_pivot_node = element + else: + self.selected_node = None + self.path_pivot_node = None return True if event.button == 1 or event.button == 2: From e50489e493e39a6e84ac9c4d5458695b7dff0f03 Mon Sep 17 00:00:00 2001 From: RHOsanghoon Date: Wed, 23 Apr 2014 15:37:50 +0900 Subject: [PATCH 12/18] retargetable path fail bug fix --- xdot.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/xdot.py b/xdot.py index e92aa55..f6c42d6 100755 --- a/xdot.py +++ b/xdot.py @@ -1709,7 +1709,6 @@ def set_highlight(self, items): self.focused_index = None # drnol: reset node focus self.selected_node = None # drnol: reset node selection self.selected_edge_index = None # drnol: reset edge selection - self.path_pivot_node = None # drnol: reset path pivot # original action self.highlight = items @@ -2108,7 +2107,7 @@ def on_area_button_release(self, area, event): self.path_pivot_node = element else: self.selected_node = None - self.path_pivot_node = None + self.path_pivot_node = None return True if event.button == 1 or event.button == 2: From fa153f6d6bb366170f38bf92ea3b0556d1448270 Mon Sep 17 00:00:00 2001 From: RHOsanghoon Date: Thu, 24 Apr 2014 17:41:55 +0900 Subject: [PATCH 13/18] shortest path improvement and url open support * shortest path supports bidirectional edge * dot url open support (linux only) url descripted like this URL="dots://file1;file2..." the dot files will be opened by right-click * pivot node bug fixed when unreachable node ctrl-click --- README.markdown | 1 + xdot.py | 121 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 83 insertions(+), 39 deletions(-) diff --git a/README.markdown b/README.markdown index 3486368..ec5785f 100644 --- a/README.markdown +++ b/README.markdown @@ -110,6 +110,7 @@ Command Line Enter follow selected edge Ctrl-click display shortest path (retargetable) Ctrl-shift-click display reverse shortest path (retargetable) + right click dot url open ([URL="dots://file1;file2;..."]) (Linux only) If no input file is given then it will read the dot graph from the standard input. diff --git a/xdot.py b/xdot.py index f6c42d6..9e17ea7 100755 --- a/xdot.py +++ b/xdot.py @@ -38,7 +38,6 @@ import pango import pangocairo - # See http://www.graphviz.org/pub/scm/graphviz-cairo/plugin/cairo/gvrender_cairo.c # For pygtk inspiration and guidance see: @@ -416,10 +415,11 @@ def square_distance(x1, y1, x2, y2): class Edge(Element): - def __init__(self, src, dst, points, shapes): + def __init__(self, src, dst, points, shapes, dir): Element.__init__(self, shapes) self.src = src self.dst = dst + self.dir = dir self.points = points RADIUS = 10 @@ -507,7 +507,7 @@ def get_url(self, x, y): if url is not None: return url return None - + def get_jump(self, x, y): for edge in self.edges: jump = edge.get_jump(x, y) @@ -529,18 +529,12 @@ def get_shortest_element_path(self, start, end): for i in range(0, (len(node_path)-1)): src = node_path[i] dst = node_path[i+1] - path.append(self.lookup_edge(src,dst)) + path.append(self.lookup_bidirection_edge(src,dst)) path.append(dst) return path else: - dlg = gtk.MessageDialog(type=gtk.MESSAGE_INFO, - message_format="There is no path", - buttons=gtk.BUTTONS_OK) - dlg.set_title("Path Info") - dlg.run() - dlg.destroy() - return [start] + return None # drnol: edge lookup by src,dst def lookup_edge(self,src,dst): @@ -549,12 +543,17 @@ def lookup_edge(self,src,dst): return edge return None + def lookup_bidirection_edge(self,src,dst): + edge = self.lookup_edge(src, dst) + if not edge: + edge = self.lookup_edge(dst, src) + return edge + # drnol: simple shortest path calculator # just get node path # TODO: must be optimize def find_shortest_node_path(self, start, end, path=[]): - path = list(path) # - path.append(start) + path = path + [start] if start == end: return path @@ -569,6 +568,11 @@ def find_shortest_node_path(self, start, end, path=[]): if newpath: if not shortest or len(newpath) < len(shortest): shortest = newpath + elif (edge.dir=="none") and (edge.dst == start) and (edge.src not in path): # bi-direction support + newpath = self.find_shortest_node_path(edge.src, end, path) + if newpath: + if not shortest or len(newpath) < len(shortest): + shortest = newpath return shortest BOLD = 1 @@ -1287,6 +1291,11 @@ def handle_edge(self, src_id, dst_id, attrs): except KeyError: return + try: + dir = attrs['dir'] + except: + dir="" + points = self.parse_edge_pos(pos) shapes = [] for attr in ("_draw_", "_ldraw_", "_hdraw_", "_tdraw_", "_hldraw_", "_tldraw_"): @@ -1296,7 +1305,7 @@ def handle_edge(self, src_id, dst_id, attrs): if shapes: src = self.node_by_name[src_id] dst = self.node_by_name[dst_id] - self.edges.append(Edge(src, dst, points, shapes)) + self.edges.append(Edge(src, dst, points, shapes, dir)) def parse(self): DotParser.parse(self) @@ -1550,7 +1559,9 @@ class DotWidget(gtk.DrawingArea): __gsignals__ = { 'expose-event': 'override', - 'clicked' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING, gtk.gdk.Event)) + 'clicked' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING, gtk.gdk.Event)), + # drnol; url right click action + 'url_right_clicked' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING, gtk.gdk.Event)) } filter = 'dot' @@ -2067,13 +2078,35 @@ def show_path(self, end_node, reverse=False): self.build_path(end_node, self.path_pivot_node) else: self.build_path(self.path_pivot_node, end_node) - # display path - self.set_highlight(self.path) - self.queue_draw() - + + if not self.path: + dlg = gtk.MessageDialog(type=gtk.MESSAGE_INFO, + message_format="There is no path", + buttons=gtk.BUTTONS_OK) + dlg.set_title("Path Info") + dlg.run() + dlg.destroy() + else: + # display path + self.set_highlight(self.path) + self.queue_draw() + def build_path(self, start, end): self.path = self.graph.get_shortest_element_path(start, end) + # drnol: url right click implementation + def do_url_right_clicked(self, url, event): + # drnol: open new dot files directives + # currently depend on operating system + if (url.index("dots://")==0) and (os.uname()[0]=="Linux"): + dots = url[7:].split(';') + dirname = os.path.dirname(self.openfilename) + if dirname!="": + dirname=dirname+"/" + for dot in dots: + subprocess.Popen([sys.argv[0], dirname+dot]) + return + def on_area_button_release(self, area, event): self.drag_action.on_button_release(event) self.drag_action = NullAction(self) @@ -2087,28 +2120,34 @@ def on_area_button_release(self, area, event): url = self.get_url(x, y) if url is not None: self.emit('clicked', unicode(url.url), event) - else: - jump = self.get_jump(x, y) - if jump is not None: - self.animate_to(jump.x, jump.y) - - # drnol: node selection - element = self.get_element(x, y) - if isinstance(element, Node): - if event.state & gtk.gdk.CONTROL_MASK: - if event.state & gtk.gdk.SHIFT_MASK: - self.show_path(element, True) # reverse path - else: - self.show_path(element) # normal path + # drnol: disable url node's skipping action because of node selection + # else: + jump = self.get_jump(x, y) + if jump is not None: + self.animate_to(jump.x, jump.y) + + # drnol: node selection + element = self.get_element(x, y) + if isinstance(element, Node): + if event.state & gtk.gdk.CONTROL_MASK: + if event.state & gtk.gdk.SHIFT_MASK: + self.show_path(element, True) # reverse path else: - self.set_highlight([element]) - self.selected_node = element - self.focused_index = 0 - self.path_pivot_node = element + self.show_path(element) # normal path else: - self.selected_node = None - self.path_pivot_node = None + self.set_highlight([element]) + self.selected_node = element + self.focused_index = 0 + self.path_pivot_node = element + else: + self.selected_node = None + self.path_pivot_node = None return True + elif event.button == 3: + url = self.get_url(x, y) + if url is not None: + self.emit('url_right_clicked', unicode(url.url), event) + if event.button == 1 or event.button == 2: return True @@ -2287,6 +2326,10 @@ def textentry_activate(self, widget, entry): dot_widget.set_highlight(found_items) if(len(found_items) == 1): dot_widget.animate_to(found_items[0].x, found_items[0].y) + # drnol: select/focus node + dot_widget.selected_node = found_items[0] + dot_widget.path_pivot_node = found_items[0] + dot_widget.focused_index = 0 def set_filter(self, filter): self.widget.set_filter(filter) @@ -2358,7 +2401,6 @@ def format_epilog(self, formatter): def main(): - parser = OptionParser( usage='\n\t%prog [file]', epilog=''' @@ -2380,6 +2422,7 @@ def main(): Enter follow selected edge Ctrl-click display shortest path (retargetable) Ctrl-shift-click display reverse shortest path (retargetable) + right click dot url open ([URL="dots://file1;file2;..."]) (Linux only) ''' ) parser.add_option( From b7b0424b348b19ca53ab610b532e8176127ac2f6 Mon Sep 17 00:00:00 2001 From: RHOsanghoon Date: Mon, 28 Apr 2014 21:51:03 +0900 Subject: [PATCH 14/18] Separate highlights * now, 3 kinds highlights. HOVER, SELECTED, PATH * HOVER is used for just hover SELECTED is used for text search / focusing(=pivot) * PATH is used for shortest path * each highlight type given by pen_type parameter instead of highlight parameter --- xdot.py | 557 ++++++++++++++++++++++++++++++++------------------------ 1 file changed, 321 insertions(+), 236 deletions(-) diff --git a/xdot.py b/xdot.py index 8864dc6..40fd792 100755 --- a/xdot.py +++ b/xdot.py @@ -38,12 +38,16 @@ import pango import pangocairo + # See http://www.graphviz.org/pub/scm/graphviz-cairo/plugin/cairo/gvrender_cairo.c # For pygtk inspiration and guidance see: # - http://mirageiv.berlios.de/ # - http://comix.sourceforge.net/ +HOVER_PEN=1 +SELECTED_PEN=2 +PATH_PEN=3 class Pen: """Store pen attributes.""" @@ -63,12 +67,27 @@ def copy(self): pen.__dict__ = self.__dict__.copy() return pen - def highlighted(self): + # drnol: pen type extends to hover, selected, path + def hover(self): pen = self.copy() + pen.linewidth = 2.0 pen.color = (1, 0, 0, 1) pen.fillcolor = (1, .8, .8, 1) return pen + def selected(self): + pen = self.copy() + pen.linewidth = 2.0 + pen.color = (0, 1, 0, 1) + pen.fillcolor = (1, .8, .8, 1) + return pen + + def path(self): + pen = self.copy() + pen.linewidth = 3.0 + pen.color = (1, 0, 1, 1) + pen.fillcolor = (1, .8, .8, 1) + return pen class Shape: """Abstract base class for all the drawing shapes.""" @@ -76,15 +95,24 @@ class Shape: def __init__(self): pass - def draw(self, cr, highlight=False): + def draw(self, cr, pen_type=None): """Draw this shape with the given cairo context""" raise NotImplementedError - def select_pen(self, highlight): - if highlight: - if not hasattr(self, 'highlight_pen'): - self.highlight_pen = self.pen.highlighted() - return self.highlight_pen + # drnol: pen type extends to hover, selected, path + def select_pen(self, pen_type): + if pen_type == HOVER_PEN: + if not hasattr(self, 'hover_pen'): + self.hover_pen = self.pen.hover() + return self.hover_pen + elif pen_type == SELECTED_PEN: + if not hasattr(self, 'selected_pen'): + self.selected_pen = self.pen.selected() + return self.selected_pen + elif pen_type == PATH_PEN: + if not hasattr(self, 'path_pen'): + self.path_pen = self.pen.path() + return self.path_pen else: return self.pen @@ -105,7 +133,8 @@ def __init__(self, pen, x, y, j, w, t): self.w = w self.t = t - def draw(self, cr, highlight=False): + # drnol: change highlight flag to pen_type + def draw(self, cr, pen_type=None): try: layout = self.layout @@ -171,7 +200,8 @@ def draw(self, cr, highlight=False): cr.save() cr.scale(f, f) - cr.set_source_rgba(*self.select_pen(highlight).color) + # drnol: change highlight flag to pen_type + cr.set_source_rgba(*self.select_pen(pen_type).color) cr.show_layout(layout) cr.restore() @@ -203,7 +233,8 @@ def __init__(self, pen, x0, y0, w, h, path): self.h = h self.path = path - def draw(self, cr, highlight=False): + # drnol: change highlight flag to pen_type + def draw(self, cr, pen_type=None): cr2 = gtk.gdk.CairoContext(cr) pixbuf = gtk.gdk.pixbuf_new_from_file(self.path) sx = float(self.w)/float(pixbuf.get_width()) @@ -227,14 +258,16 @@ def __init__(self, pen, x0, y0, w, h, filled=False): self.h = h self.filled = filled - def draw(self, cr, highlight=False): + # drnol: change highlight flag to pen_type + def draw(self, cr, pen_type=None): cr.save() cr.translate(self.x0, self.y0) cr.scale(self.w, self.h) cr.move_to(1.0, 0.0) cr.arc(0.0, 0.0, 1.0, 0, 2.0*math.pi) cr.restore() - pen = self.select_pen(highlight) + # drnol: change highlight flag to pen_type + pen = self.select_pen(pen_type) if self.filled: cr.set_source_rgba(*pen.fillcolor) cr.fill() @@ -253,13 +286,15 @@ def __init__(self, pen, points, filled=False): self.points = points self.filled = filled - def draw(self, cr, highlight=False): + # drnol: change highlight flag to pen_type + def draw(self, cr, pen_type=None): x0, y0 = self.points[-1] cr.move_to(x0, y0) for x, y in self.points: cr.line_to(x, y) cr.close_path() - pen = self.select_pen(highlight) + # drnol: change highlight flag to pen_type + pen = self.select_pen(pen_type) if self.filled: cr.set_source_rgba(*pen.fillcolor) cr.fill_preserve() @@ -278,12 +313,14 @@ def __init__(self, pen, points): self.pen = pen.copy() self.points = points - def draw(self, cr, highlight=False): + # drnol: change highlight flag to pen_type + def draw(self, cr, pen_type=None): x0, y0 = self.points[0] cr.move_to(x0, y0) for x1, y1 in self.points[1:]: cr.line_to(x1, y1) - pen = self.select_pen(highlight) + # drnol: change highlight flag to pen_type + pen = self.select_pen(pen_type) cr.set_dash(pen.dash) cr.set_line_width(pen.linewidth) cr.set_source_rgba(*pen.color) @@ -298,7 +335,8 @@ def __init__(self, pen, points, filled=False): self.points = points self.filled = filled - def draw(self, cr, highlight=False): + # drnol: change highlight flag to pen_type + def draw(self, cr, pen_type=None): x0, y0 = self.points[0] cr.move_to(x0, y0) for i in xrange(1, len(self.points), 3): @@ -306,7 +344,8 @@ def draw(self, cr, highlight=False): x2, y2 = self.points[i + 1] x3, y3 = self.points[i + 2] cr.curve_to(x1, y1, x2, y2, x3, y3) - pen = self.select_pen(highlight) + # drnol: change highlight flag to pen_type + pen = self.select_pen(pen_type) if self.filled: cr.set_source_rgba(*pen.fillcolor) cr.fill_preserve() @@ -324,9 +363,10 @@ def __init__(self, shapes): Shape.__init__(self) self.shapes = shapes - def draw(self, cr, highlight=False): + # drnol: change highlight flag to pen + def draw(self, cr, pen_type=None): for shape in self.shapes: - shape.draw(cr, highlight=highlight) + shape.draw(cr, pen_type=pen_type) def search_text(self, regexp): for shape in self.shapes: @@ -337,23 +377,23 @@ def search_text(self, regexp): class Url(object): - def __init__(self, item, url, highlight=None): + def __init__(self, item, url, hover=None): self.item = item self.url = url - if highlight is None: - highlight = set([item]) - self.highlight = highlight + if hover is None: + hover = set([item]) + self.hover = hover class Jump(object): - def __init__(self, item, x, y, highlight=None): + def __init__(self, item, x, y, hover=None): self.item = item self.x = x self.y = y - if highlight is None: - highlight = set([item]) - self.highlight = highlight + if hover is None: + hover = set([item]) + self.hover = hover class Element(CompoundShape): @@ -439,9 +479,9 @@ def is_inside(self, x, y): def get_jump(self, x, y): if self.is_inside_begin(x, y): - return Jump(self, self.dst.x, self.dst.y, highlight=set([self, self.dst])) + return Jump(self, self.dst.x, self.dst.y, hover=set([self, self.dst])) if self.is_inside_end(x, y): - return Jump(self, self.src.x, self.src.y, highlight=set([self, self.src])) + return Jump(self, self.src.x, self.src.y, hover=set([self, self.src])) return None def __repr__(self): @@ -458,29 +498,35 @@ def __init__(self, width=1, height=1, shapes=(), nodes=(), edges=()): self.shapes = shapes self.nodes = nodes self.edges = edges - self.build_edge_map() # drnol: build edge lookup map - - # drnol: build edge lookup map + self.build_edge_map() # drnol: build edge lookup map + + # drnol: build edge lookup map def build_edge_map(self): - self.edgemap = {} + self.edgemap = {} for edge in self.edges: # add src if self.edgemap.has_key(edge.src): - self.edgemap[edge.src].append(edge) + self.edgemap[edge.src].append(edge) else: self.edgemap[edge.src] = [edge] - # add dst + # add dst if self.edgemap.has_key(edge.dst): - self.edgemap[edge.dst].append(edge) + self.edgemap[edge.dst].append(edge) else: - self.edgemap[edge.dst] = [edge] - + self.edgemap[edge.dst] = [edge] + def get_size(self): return self.width, self.height - def draw(self, cr, highlight_items=None): - if highlight_items is None: - highlight_items = () + # drnol: highlight_items split to (hover...,selected...,path...) + def draw(self, cr, hover_items=None, selected_items=None, path_items=None): + if hover_items is None: + hover_items = () + if selected_items is None: + selected_items = () + if path_items is None: + path_items = () + cr.set_source_rgba(0.0, 0.0, 0.0, 1.0) cr.set_line_cap(cairo.LINE_CAP_BUTT) @@ -488,10 +534,26 @@ def draw(self, cr, highlight_items=None): for shape in self.shapes: shape.draw(cr) + for edge in self.edges: - edge.draw(cr, highlight=(edge in highlight_items)) + if edge in hover_items: + edge.draw(cr,HOVER_PEN) + elif edge in selected_items: + edge.draw(cr,SELECTED_PEN) + elif edge in path_items: + edge.draw(cr,PATH_PEN) + else: + edge.draw(cr) + for node in self.nodes: - node.draw(cr, highlight=(node in highlight_items)) + if node in hover_items: + node.draw(cr,HOVER_PEN) + elif node in selected_items: + node.draw(cr,SELECTED_PEN) + elif node in path_items: + node.draw(cr,PATH_PEN) + else: + node.draw(cr) def get_element(self, x, y): for node in self.nodes: @@ -523,7 +585,7 @@ def get_jump(self, x, y): # use directed graph def get_shortest_element_path(self, start, end): node_path = self.find_shortest_node_path(start, end) - + if (node_path != None) and (len(node_path)>0): path = [node_path[0]] for i in range(0, (len(node_path)-1)): @@ -531,8 +593,8 @@ def get_shortest_element_path(self, start, end): dst = node_path[i+1] path.append(self.lookup_bidirection_edge(src,dst)) path.append(dst) - - return path + + return path else: return None @@ -550,17 +612,17 @@ def lookup_bidirection_edge(self,src,dst): return edge # drnol: simple shortest path calculator - # just get node path - # TODO: must be optimize + # just get node path + # TODO: must be optimize def find_shortest_node_path(self, start, end, path=[]): path = path + [start] - + if start == end: return path - + if not self.edgemap.has_key(start): return None - + shortest = None for edge in self.edgemap[start]: if (edge.src == start) and (edge.dst not in path): @@ -593,7 +655,7 @@ def __init__(self, parser, buf): self.parser = parser self.buf = buf self.pos = 0 - + self.pen = Pen() self.shapes = [] @@ -687,7 +749,7 @@ def lookup_color(self, c): b = b*s a = 1.0 return r, g, b, a - + sys.stderr.write("warning: unknown color '%s'\n" % c) return None @@ -762,7 +824,7 @@ def parse(self): sys.exit(1) return self.shapes - + def transform(self, x, y): return self.parser.transform(x, y) @@ -834,7 +896,7 @@ def __init__(self, msg=None, filename=None, line=None, col=None): def __str__(self): return ':'.join([str(part) for part in (self.filename, self.line, self.col, self.msg) if part != None]) - + class Scanner: """Stateless scanner.""" @@ -973,9 +1035,9 @@ def __init__(self, lexer): def match(self, type): if self.lookahead.type != type: raise ParseError( - msg = 'unexpected token %r' % self.lookahead.text, - filename = self.lexer.filename, - line = self.lookahead.line, + msg = 'unexpected token %r' % self.lookahead.text, + filename = self.lexer.filename, + line = self.lookahead.line, col = self.lookahead.col) def skip(self, type): @@ -1078,7 +1140,7 @@ def filter(self, type, text): text = text.replace('\\\r\n', '') text = text.replace('\\\r', '') text = text.replace('\\\n', '') - + # quotes text = text.replace('\\"', '"') @@ -1222,7 +1284,7 @@ class XDotParser(DotParser): def __init__(self, xdotcode): lexer = DotLexer(buf = xdotcode) DotParser.__init__(self, lexer) - + self.nodes = [] self.edges = [] self.shapes = [] @@ -1259,7 +1321,7 @@ def handle_graph(self, attrs): self.height = max(ymax - ymin, 1) self.top_graph = False - + for attr in ("_draw_", "_ldraw_", "_hdraw_", "_tdraw_", "_hldraw_", "_tldraw_"): if attr in attrs: parser = XDotAttrParser(self, attrs[attr]) @@ -1290,7 +1352,7 @@ def handle_edge(self, src_id, dst_id, attrs): pos = attrs['pos'] except KeyError: return - + try: dir = attrs['dir'] except: @@ -1470,32 +1532,29 @@ def abort(self): class NullAction(DragAction): - def on_motion_notify(self, event): + def on_motion_notify(self, event): if event.is_hint: x, y, state = event.window.get_pointer() else: x, y, state = event.x, event.y, event.state - + # drnol: skip when shift is pressed if state & gtk.gdk.CONTROL_MASK: - return - + return + dot_widget = self.dot_widget item = dot_widget.get_url(x, y) if item is None: item = dot_widget.get_jump(x, y) if item is not None: dot_widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.HAND2)) - # drnol: don't reset highlights when hover - # dot_widget.set_highlight(item.highlight) - - # drnol: selection nodes - # dot_widget.focus_node_at(x,y) + # drnol: now hover instead highlight + dot_widget.set_hover(item.hover) else: dot_widget.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.ARROW)) - # drnol: don't reset highlights when just move - # dot_widget.set_highlight(None) - + # drnol: now hover instead highlight + dot_widget.set_hover(None) + class PanAction(DragAction): def start(self): @@ -1593,13 +1652,16 @@ def __init__(self): self.animation = NoAnimation(self) self.drag_action = NullAction(self) self.presstime = None - self.highlight = None - + # drnol: selections and path related variables self.focused_index = None - self.selected_node = None - self.selected_edge = None - self.path_pivot_node = None + self.pivot_node = None + self.selected_edge_index = None + + # drnol: 3 type highlights + self.hover = None + self.selected = None + self.path = None def set_filter(self, filter): self.filter = filter @@ -1699,7 +1761,7 @@ def do_expose_event(self, event): cr.scale(self.zoom_ratio, self.zoom_ratio) cr.translate(-self.x, -self.y) - self.graph.draw(cr, highlight_items=self.highlight) + self.graph.draw(cr, hover_items=self.hover, selected_items=self.selected, path_items=self.path) cr.restore() self.drag_action.draw(cr) @@ -1714,17 +1776,56 @@ def set_current_pos(self, x, y): self.y = y self.queue_draw() - def set_highlight(self, items): - if self.highlight != items: - # drnol: focusing and selection / reset - self.focused_index = None # drnol: reset node focus - self.selected_node = None # drnol: reset node selection - self.selected_edge_index = None # drnol: reset edge selection - - # original action - self.highlight = items + # drnol: highlight changed to hover + def set_hover(self, items): + if self.hover != items: + self.hover = items self.queue_draw() + # drnol: select node + def set_pivot_node(self, node): + self.pivot_node = node + + # reset focus/edge selection/path + if self.pivot_node != None: + self.focused_index = None + self.selected_edge_index = None + self.path = None + + # highlight + if node != None: + self.selected = [node] + else: + self.selected = None + + self.queue_draw() + + # drnol: select edge + def set_selected_edge(self, edge): + if self.selected_edge_index != edge: + if self.pivot_node == edge.src: + self.selected = [edge.src, edge, edge.dst] + elif self.pivot_node == edge.dst: + self.selected = [edge.dst, edge, edge.src] + else: + self.set_pivot_node(edge.src) + self.selected = [edge.src, edge, edge.dst] + self.queue_draw() + + # drnol: renew selected items + def set_selected(self, items): + self.focused_index = None + self.pivot_node = None + self.selected_edge_index = None + if self.selected != items: + self.selected = items + self.queue_draw() + + # drnol: set path + def set_path(self, path): + self.path = path + self.queue_draw() + def zoom_image(self, zoom_ratio, center=False, pos=None): # Constrain zoom ratio to a sane range to prevent numeric instability. zoom_ratio = min(zoom_ratio, 1E4) @@ -1840,20 +1941,20 @@ def on_key_press_event(self, widget, event): self.on_print() return True if event.keyval == gtk.keysyms.F2: - # drnol: jump to prev highlighted item - self.jump_to_prev_highlight() + # drnol: jump to prev highlighted item + self.jump_to_prev_selected_node() return True if event.keyval == gtk.keysyms.F3: - # drnol: jump to next highlighted item - self.jump_to_next_highlight() + # drnol: jump to next highlighted item + self.jump_to_next_selected_node() return True if event.keyval == gtk.keysyms.comma: # drnol: select prev edge of focused node - self.select_prev_edge_of_focused_node() + self.select_prev_edge_of_pivot_node() return True - if event.keyval == gtk.keysyms.period: + if event.keyval == gtk.keysyms.period: # drnol: select next edge of focused node - self.select_next_edge_of_focused_node() + self.select_next_edge_of_pivot_node() return True if event.keyval == gtk.keysyms.Return: # drnol: follow selected edge @@ -1861,124 +1962,122 @@ def on_key_press_event(self, widget, event): return True return False - # drnol: jump to prev highlighted item - def jump_to_prev_highlight(self): - if self.exists_highlighted_node(): - if len(self.highlight) > 0: - if (self.focused_index == None) or (self.focused_index == 0): - self.focused_index = len(self.highlight)-1 - else: - self.focused_index = self.focused_index-1 - element = self.highlight[self.focused_index] + # drnol: jump to prev selected item + def jump_to_prev_selected_node(self): + list = self.get_traversal_list() + if list: + if (self.focused_index == None) or (self.focused_index == 0): + self.focused_index = len(list)-1 + else: + self.focused_index = self.focused_index-1 + element = list[self.focused_index] + if isinstance(element,Node): + self.focus_node(element) + else: + self.jump_to_prev_selected_node() # skip edge + + # drnol: jump to next selected item + def jump_to_next_selected_node(self): + list = self.get_traversal_list() + if list != None: + if self.focused_index == None: + self.focused_index = 0 + else: + self.focused_index = (self.focused_index+1) % len(list) + element = list[self.focused_index] + if isinstance(element,Node): + self.focus_node(element) + else: + self.jump_to_next_selected_node() # skip edge + + # drnol: if path is displayed then traversal list is path otherwise selected nodes + def get_traversal_list(self): + if self.path != None: + for element in self.path: if isinstance(element,Node): - self.focus_node(element) - else: - self.jump_to_prev_highlight() # skip edge - - # drnol: jump to next highlighted item - def jump_to_next_highlight(self): - if self.exists_highlighted_node(): - if len(self.highlight) > 0: - if self.focused_index == None: - self.focused_index = 0 - else: - self.focused_index = (self.focused_index+1) % len(self.highlight) - element = self.highlight[self.focused_index] + return self.path + else: + for element in self.selected: if isinstance(element,Node): - self.focus_node(element) - else: - self.jump_to_next_highlight() # skip edge - - # drnol: check existence of highlighted node in highlight list - def exists_highlighted_node(self): - for element in self.highlight: - if isinstance(element,Node): - return True - return False + return self.selected + return None # drnol: focus node def focus_node(self, node): self.selected_edge = None - self.selected_node = node + self.pivot_node = node + if self.path != None: + self.selected=[self.pivot_node] + self.queue_draw() self.animate_to(node.x, node.y) - - # drnol: focus node by coordinate + + # drnol: focus node by coordinate def focus_node_at(self, x, y): elt = self.get_element(x, y) if (elt != None) and (isinstance(elt,Node)): self.focused_index = 0 - self.selected_node = elt - + self.pivot_node = elt + # drnol: select prev edge of focused node - def select_prev_edge_of_focused_node(self): - if self.selected_node != None: - if self.graph.edgemap.has_key(self.selected_node): - edges = self.graph.edgemap[self.selected_node] + def select_prev_edge_of_pivot_node(self): + if self.pivot_node != None: + if self.graph.edgemap.has_key(self.pivot_node): + edges = self.graph.edgemap[self.pivot_node] # if there is more than one edge in the selected node - if len(edges) > 0: + if len(edges) > 0: if (self.selected_edge_index == None) or (self.selected_edge_index <= 0): self.selected_edge_index = len(edges)-1 else: self.selected_edge_index = self.selected_edge_index-1 edge = edges[self.selected_edge_index] - self.select_edge_with_highlight(edge) - + self.set_selected_edge(edge) + # drnol: select next edge of focused node - def select_next_edge_of_focused_node(self): - if self.selected_node != None: - if self.graph.edgemap.has_key(self.selected_node): - edges = self.graph.edgemap[self.selected_node] + def select_next_edge_of_pivot_node(self): + if self.pivot_node != None: + if self.graph.edgemap.has_key(self.pivot_node): + edges = self.graph.edgemap[self.pivot_node] # if there is more than one edge in the selected node - if len(edges) > 0: + if len(edges) > 0: if self.selected_edge_index == None: self.selected_edge_index = 0 else: self.selected_edge_index = (self.selected_edge_index+1) % len(edges) edge = edges[self.selected_edge_index] - self.select_edge_with_highlight(edge) - - #drnol: select edge - def select_edge_with_highlight(self, edge): - # don't use set_highlight, because set_highlight performs resetting selections - if self.selected_node==edge.src: - self.highlight = [edge.src, edge, edge.dst] - else: - self.highlight = [edge.dst, edge, edge.src] - self.queue_draw() - + self.set_selected_edge(edge) + #drnol: follow selected edge def follow_selected_edge(self): - if (self.selected_node != None) and (self.selected_edge_index != None): - edges = self.graph.edgemap[self.selected_node] + if (self.pivot_node != None) and (self.selected_edge_index != None): + edges = self.graph.edgemap[self.pivot_node] edge = edges[self.selected_edge_index] - - # follow to opposite node - if edge.src == self.selected_node: + + # follow to opposite node + if edge.src == self.pivot_node: target = edge.dst else: target = edge.src - + # jump to selected node self.focus_node(target) - - # automatically select one of other edges + + # automatically select one of other edges self.selected_edge_index, new_edge = self.try_find_other_edge_index(edge) - self.highlight = [target, new_edge] - self.queue_draw() - - #drnol: try finding the other edge - def try_find_other_edge_index(self, edge): - edges = self.graph.edgemap[self.selected_node] - + self.set_selected_edge(new_edge) + + #drnol: try finding the other edge + def try_find_other_edge_index(self, edge): + edges = self.graph.edgemap[self.pivot_node] + edge_index = 0 new_edge = edges[0] for i in range(1, len(edges)): if edges[i] != edge: edge_index = i new_edge = edges[i] - + return edge_index, new_edge - + print_settings = None def on_print(self, action=None): print_op = gtk.PrintOperation() @@ -2006,7 +2105,7 @@ def draw_page(self, operation, context, page_nr): cr.scale(self.zoom_ratio, self.zoom_ratio) cr.translate(-self.x, -self.y) - self.graph.draw(cr, highlight_items=self.highlight) + self.graph.draw(cr, hover_items=self.hover, selected_items=self.selected, path_items=self.path) def get_drag_action(self, event): state = event.state @@ -2049,35 +2148,27 @@ def on_click(self, element, event): return False # drnol: display shortest path - # if there is one selected node A -> path between A and just before ctrl-clicked node B - # if there are previously displayed path and pivot node A -> path between A and just before ctrl-clicked node B + # if there is one selected node A -> path between A and just before ctrl-clicked node B + # if there are previously displayed path and pivot node A -> path between A and just before ctrl-clicked node B def show_path(self, end_node, reverse=False): - if self.path_pivot_node == None: - if (self.highlight != None) and (len(self.highlight) == 1): - elt = list(self.highlight)[0] - if (isinstance(elt,Node)): - self.path_pivot_node = elt - else: - dlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR, - message_format="Start node must be NODE", - buttons=gtk.BUTTONS_OK) - dlg.set_title("Path Selection Error!") - dlg.run() - dlg.destroy() - return - else: - dlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR, - message_format="Only one node allowed as start node", - buttons=gtk.BUTTONS_OK) - dlg.set_title("Path Selection Error!") - dlg.run() - dlg.destroy() - return - # build path + # start node check + if self.pivot_node == None: + dlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR, + message_format="You must select start node.", + buttons=gtk.BUTTONS_OK) + dlg.set_title("Path Selection Error!") + dlg.run() + dlg.destroy() + return + + self.focused_index = 0 + self.selected = [self.pivot_node] + + # build path if reverse: - self.build_path(end_node, self.path_pivot_node) + self.build_path(end_node, self.pivot_node) else: - self.build_path(self.path_pivot_node, end_node) + self.build_path(self.pivot_node, end_node) if not self.path: dlg = gtk.MessageDialog(type=gtk.MESSAGE_INFO, @@ -2088,12 +2179,12 @@ def show_path(self, end_node, reverse=False): dlg.destroy() else: # display path - self.set_highlight(self.path) + self.set_path(self.path) self.queue_draw() def build_path(self, start, end): self.path = self.graph.get_shortest_element_path(start, end) - + # drnol: url right click implementation def do_url_right_clicked(self, url, event): # drnol: open new dot files directives @@ -2134,21 +2225,16 @@ def on_area_button_release(self, area, event): self.show_path(element, True) # reverse path else: self.show_path(element) # normal path - else: - self.set_highlight([element]) - self.selected_node = element - self.focused_index = 0 - self.path_pivot_node = element - else: - self.selected_node = None - self.path_pivot_node = None + else: # start node selection + self.set_pivot_node(element) + else: # edge selection + self.set_selected_edge(element) return True elif event.button == 3: url = self.get_url(x, y) if url is not None: self.emit('url_right_clicked', unicode(url.url), event) - if event.button == 1 or event.button == 2: return True return False @@ -2307,29 +2393,28 @@ def find_text(self, entry_text): def textentry_changed(self, widget, entry): entry_text = entry.get_text() - dot_widget = self.widget + dot_widget = self.widget if not entry_text: - dot_widget.set_highlight(None) + dot_widget.set_selected(None) return - + found_items = self.find_text(entry_text) - dot_widget.set_highlight(found_items) + dot_widget.set_selected(found_items) def textentry_activate(self, widget, entry): entry_text = entry.get_text() - dot_widget = self.widget + dot_widget = self.widget if not entry_text: - dot_widget.set_highlight(None) + dot_widget.set_selected(None) return; - + found_items = self.find_text(entry_text) - dot_widget.set_highlight(found_items) if(len(found_items) == 1): - dot_widget.animate_to(found_items[0].x, found_items[0].y) # drnol: select/focus node - dot_widget.selected_node = found_items[0] - dot_widget.path_pivot_node = found_items[0] - dot_widget.focused_index = 0 + dot_widget.set_pivot_node(found_items[0]) + dot_widget.focus_node(found_items[0]) + else: + dot_widget.set_selected(found_items) def set_filter(self, filter): self.widget.set_filter(filter) @@ -2343,7 +2428,7 @@ def set_xdotcode(self, xdotcode, filename=None): if self.widget.set_xdotcode(xdotcode): self.update_title(filename) self.widget.zoom_to_fit() - + def update_title(self, filename=None): if filename is None: self.set_title(self.base_title) @@ -2414,7 +2499,7 @@ def main(): P print Escape halt animation Ctrl-drag zoom in/out - Shift-drag zooms an area + Shift-drag zooms an area F2 prev highlighted item F3 next highlighted item , select prev focused node's edge @@ -2455,24 +2540,24 @@ def main(): # Apache-Style Software License for ColorBrewer software and ColorBrewer Color # Schemes, Version 1.1 -# +# # Copyright (c) 2002 Cynthia Brewer, Mark Harrower, and The Pennsylvania State # University. All rights reserved. -# +# # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: -# +# # 1. Redistributions as source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. +# this list of conditions and the following disclaimer. # # 2. The end-user documentation included with the redistribution, if any, # must include the following acknowledgment: -# +# # This product includes color specifications and designs developed by # Cynthia Brewer (http://colorbrewer.org/). -# +# # Alternately, this acknowledgment may appear in the software itself, if and -# wherever such third-party acknowledgments normally appear. +# wherever such third-party acknowledgments normally appear. # # 3. The name "ColorBrewer" must not be used to endorse or promote products # derived from this software without prior written permission. For written @@ -2480,8 +2565,8 @@ def main(): # # 4. Products derived from this software may not be called "ColorBrewer", # nor may "ColorBrewer" appear in their name, without prior written -# permission of Cynthia Brewer. -# +# permission of Cynthia Brewer. +# # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND # FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CYNTHIA @@ -2491,7 +2576,7 @@ def main(): # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. brewer_colors = { 'accent3': [(127, 201, 127), (190, 174, 212), (253, 192, 134)], 'accent4': [(127, 201, 127), (190, 174, 212), (253, 192, 134), (255, 255, 153)], From 44b2a9f6b330ca28d8e466d18f9a2c1082b92e97 Mon Sep 17 00:00:00 2001 From: RHOsanghoon Date: Tue, 29 Apr 2014 15:19:52 +0900 Subject: [PATCH 15/18] Key binding bugfixes and improvement * now F2, F3 navigation is available at any time (even if textentry focused) * reset focus when input return key at textentry --- xdot.py | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/xdot.py b/xdot.py index 0d574b3..eac6e56 100755 --- a/xdot.py +++ b/xdot.py @@ -1641,7 +1641,7 @@ def __init__(self): self.connect("scroll-event", self.on_area_scroll_event) self.connect("size-allocate", self.on_area_size_allocate) - self.connect('key-press-event', self.on_key_press_event) + #self.connect('key-press-event', self.on_key_press_event) self.last_mtime = None gobject.timeout_add(1000, self.update) @@ -1652,10 +1652,10 @@ def __init__(self): self.animation = NoAnimation(self) self.drag_action = NullAction(self) self.presstime = None - + self.doc_init() - + # drnol: doc init for multiple open def doc_init(self): # drnol: selections and path related variables @@ -1947,14 +1947,6 @@ def on_key_press_event(self, widget, event): if event.keyval == gtk.keysyms.p: self.on_print() return True - if event.keyval == gtk.keysyms.F2: - # drnol: jump to prev highlighted item - self.jump_to_prev_selected_node() - return True - if event.keyval == gtk.keysyms.F3: - # drnol: jump to next highlighted item - self.jump_to_next_selected_node() - return True if event.keyval == gtk.keysyms.comma: # drnol: select prev edge of focused node self.select_prev_edge_of_pivot_node() @@ -2440,6 +2432,8 @@ def textentry_activate(self, widget, entry): else: dot_widget.set_selected(found_items) + win = widget.get_toplevel() + win.set_focus(win.uimanager.get_widget('/')) def set_filter(self, filter): self.widget.set_filter(filter) @@ -2573,18 +2567,28 @@ def on_prev(self, action): pass def on_window_key_press_event(self, widget, event): - if self.file_entry.is_focus(): + if event.keyval == gtk.keysyms.F2: + # drnol: jump to prev highlighted item + self.widget.jump_to_prev_selected_node() + return True + if event.keyval == gtk.keysyms.F3: + # drnol: jump to next highlighted item + self.widget.jump_to_next_selected_node() + return True + + if self.file_entry.is_focus() or self.textentry.is_focus(): return False if event.keyval == gtk.keysyms.p or event.keyval == gtk.keysyms.j: self.on_prev(None) return True - if event.keyval == gtk.keysyms.n or event.keyval == gtk.keysyms.k: + elif event.keyval == gtk.keysyms.n or event.keyval == gtk.keysyms.k: self.on_next(None) return True - if event.keyval == gtk.keysyms.o: + elif event.keyval == gtk.keysyms.o: self.on_open(None) return True - return False + else: + return self.widget.on_key_press_event(widget, event) class OptionParser(optparse.OptionParser): From cf8675a5d75029e014475f1670344dc926afb234 Mon Sep 17 00:00:00 2001 From: RHOsanghoon Date: Tue, 29 Apr 2014 15:26:14 +0900 Subject: [PATCH 16/18] Keybinding manual updated --- README.markdown | 3 +++ xdot.py | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.markdown b/README.markdown index ec5785f..7cc7b16 100644 --- a/README.markdown +++ b/README.markdown @@ -107,6 +107,9 @@ Command Line F3 next highlighted item , select prev focused node's edge . select next focused node's edge + p,j prev file + n,k next file + o browse file Enter follow selected edge Ctrl-click display shortest path (retargetable) Ctrl-shift-click display reverse shortest path (retargetable) diff --git a/xdot.py b/xdot.py index eac6e56..197a385 100755 --- a/xdot.py +++ b/xdot.py @@ -1641,7 +1641,6 @@ def __init__(self): self.connect("scroll-event", self.on_area_scroll_event) self.connect("size-allocate", self.on_area_size_allocate) - #self.connect('key-press-event', self.on_key_press_event) self.last_mtime = None gobject.timeout_add(1000, self.update) @@ -2571,13 +2570,14 @@ def on_window_key_press_event(self, widget, event): # drnol: jump to prev highlighted item self.widget.jump_to_prev_selected_node() return True - if event.keyval == gtk.keysyms.F3: + elif event.keyval == gtk.keysyms.F3: # drnol: jump to next highlighted item self.widget.jump_to_next_selected_node() return True if self.file_entry.is_focus() or self.textentry.is_focus(): return False + if event.keyval == gtk.keysyms.p or event.keyval == gtk.keysyms.j: self.on_prev(None) return True @@ -2619,6 +2619,9 @@ def main(): , select prev focused node's edge . select next focused node's edge Enter follow selected edge + p,j prev file + n,k next file + o browse file Ctrl-click display shortest path (retargetable) Ctrl-shift-click display reverse shortest path (retargetable) right click dot url open ([URL="dots://file1;file2;..."]) (Linux only) From 8ef35e3bd20c286eade65fe8a44f1feb68b98bbe Mon Sep 17 00:00:00 2001 From: Dov Feldstern Date: Wed, 25 Jun 2014 11:43:33 +0300 Subject: [PATCH 17/18] README: 'sample.py', not 'example.py' --- README.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.markdown b/README.markdown index 09ffad0..b7af58d 100644 --- a/README.markdown +++ b/README.markdown @@ -109,7 +109,7 @@ If no input file is given then it will read the dot graph from the standard inpu Embedding --------- -See included `example.py` script for an example of how to embedded _xdot.py_ into another application. +See included `sample.py` script for an example of how to embedded _xdot.py_ into another application. [![Screenshot](https://raw.github.com/wiki/jrfonseca/xdot.py/xdot-sample_small.png)](https://raw.github.com/wiki/jrfonseca/xdot.py/xdot-sample.png) From 9cc9a1623106ff883d50b0285bdcde59eaf0f8b2 Mon Sep 17 00:00:00 2001 From: nol Date: Tue, 1 Jul 2014 18:57:57 +0900 Subject: [PATCH 18/18] readme tab fixed --- README.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.markdown b/README.markdown index 7cc7b16..797807a 100644 --- a/README.markdown +++ b/README.markdown @@ -103,8 +103,8 @@ Command Line Escape halt animation Ctrl-drag zoom in/out Shift-drag zooms an area - F2 prev highlighted item - F3 next highlighted item + F2 prev highlighted item + F3 next highlighted item , select prev focused node's edge . select next focused node's edge p,j prev file