diff --git a/xdot.py b/xdot.py index 08300d8..972a776 100755 --- a/xdot.py +++ b/xdot.py @@ -30,6 +30,9 @@ import re import optparse +import gi +gi.require_version('PangoCairo', '1.0') +gi.require_version('Gtk', '3.0') from gi.repository import GLib from gi.repository import GObject from gi.repository import Gtk @@ -226,6 +229,9 @@ def draw(self, cr, highlight=False): def search_text(self, regexp): return regexp.search(self.t) is not None + def __repr__(self): + return ""%self.t + class ImageShape(Shape): @@ -477,6 +483,12 @@ def get_jump(self, x, y): return Jump(self, self.src.x, self.src.y, highlight=set([self, self.src])) return None + def search_text(self, text): + for shape in self.shapes: + if shape.search_text(text): + return True + return False + def __repr__(self): return " %s>" % (self.src, self.dst) @@ -1929,7 +1941,10 @@ class DotWindow(Gtk.Window): ui = ''' + + + @@ -1945,7 +1960,7 @@ class DotWindow(Gtk.Window): base_title = 'Dot Viewer' - def __init__(self, widget=None): + def __init__(self, command_line_files, widget=None): Gtk.Window.__init__(self) self.graph = Graph() @@ -1973,12 +1988,15 @@ def __init__(self, widget=None): # Create actions actiongroup.add_actions(( ('Open', Gtk.STOCK_OPEN, None, None, None, self.on_open), + ('CloseFile', Gtk.STOCK_CLOSE, None, None, "Close File", self.on_close_file), ('Reload', Gtk.STOCK_REFRESH, None, None, None, self.on_reload), ('Print', Gtk.STOCK_PRINT, None, None, "Prints the currently visible part of the graph", self.dotwidget.on_print), ('ZoomIn', Gtk.STOCK_ZOOM_IN, None, None, None, self.dotwidget.on_zoom_in), ('ZoomOut', Gtk.STOCK_ZOOM_OUT, None, None, None, self.dotwidget.on_zoom_out), ('ZoomFit', Gtk.STOCK_ZOOM_FIT, None, None, None, self.dotwidget.on_zoom_fit), ('Zoom100', Gtk.STOCK_ZOOM_100, None, None, None, self.dotwidget.on_zoom_100), + ('NextFile', Gtk.STOCK_GO_BACK, None, None, None, self.on_go_back), + ('PrevFile', Gtk.STOCK_GO_FORWARD, None, None, None, self.on_go_forward), )) find_action = FindMenuToolAction("Find", None, @@ -1997,6 +2015,20 @@ def __init__(self, widget=None): vbox.pack_start(self.dotwidget, True, True, 0) + # Check whether the files from the command line exist + self.open_files = [] + for filename in command_line_files: + if not os.path.exists(filename): + sys.stderr.write('Could not open file, ignore it: %st\n' % filename) + self.open_files.append(os.path.abspath(filename)) + self.open_file_idx = 0 + + # Connect the Button click event of the drawing menu, in order + # to display a file menu + self.dotwidget.connect("button-press-event", self.on_file_menu) + + self.connect('key-press-event', self.on_key_press_event) + self.last_open_dir = "." self.set_focus(self.dotwidget) @@ -2017,6 +2049,9 @@ def find_text(self, entry_text): found_items = [] dot_widget = self.dotwidget regexp = re.compile(entry_text) + for edge in dot_widget.graph.edges: + if edge.search_text(regexp): + found_items.append(edge) for node in dot_widget.graph.nodes: if node.search_text(regexp): found_items.append(node) @@ -2065,6 +2100,14 @@ def update_title(self, filename=None): def open_file(self, filename): try: + filename = os.path.abspath(filename) + if filename in self.open_files: + self.open_file_idx = self.open_files.index(filename) + else: + idx = self.open_file_idx + self.open_files = self.open_files[:idx] + [filename] \ + + self.open_files[idx:] + fp = file(filename, 'rt') self.set_dotcode(fp.read(), filename) fp.close() @@ -2104,6 +2147,53 @@ def on_open(self, action): def on_reload(self, action): self.dotwidget.reload() + def on_close_file(self, action): + """Close the currently displayed file""" + if len(self.open_files) == 1: + dlg = gtk.MessageDialog(type=gtk.MESSAGE_ERROR, + message_format="Cannot close last open file", + buttons=gtk.BUTTONS_OK) + dlg.set_title(self.base_title) + dlg.run() + dlg.destroy() + return + + del self.open_files[self.open_file_idx] + self.open_file_idx = min(len(self.open_files)-1, self.open_file_idx) + self.open_file(self.open_files[self.open_file_idx]) + + def on_go_forward(self, action = None): + """Display the next file""" + self.open_file_idx = min(self.open_file_idx + 1, len(self.open_files) - 1) + self.open_file(self.open_files[self.open_file_idx]) + + def on_go_back(self, action = None): + """Display the previous file""" + self.open_file_idx = max(0, self.open_file_idx - 1) + self.open_file(self.open_files[self.open_file_idx]) + + def on_file_menu(self, widget, event): + if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3: + menu = Gtk.Menu() + for filename in self.open_files: + label = os.path.basename(filename) + item = Gtk.MenuItem() + item.set_label(label) + item.connect("activate", lambda _, f: self.open_file(f), filename) + menu.append(item) + menu.show_all() + self.menu = menu + menu.popup(None, None, None, None, + event.button, event.time) + return True + + def on_key_press_event(self, widget, event): + if event.keyval == Gdk.KEY_bracketleft: + self.on_go_back() + return True + if event.keyval == Gdk.KEY_bracketright: + self.on_go_forward() + return True class OptionParser(optparse.OptionParser): @@ -2122,6 +2212,8 @@ def main(): Up, Down, Left, Right scroll PageUp, +, = zoom in PageDown, - zoom out + [ previous open file + ] next open file R reload dot file F find Q quit @@ -2142,10 +2234,10 @@ def main(): help='assume input is already filtered into xdot format (use e.g. dot -Txdot)') (options, args) = parser.parse_args(sys.argv[1:]) - if len(args) > 1: + if len(args) == 0: parser.error('incorrect number of arguments') - win = DotWindow() + win = DotWindow(args) win.connect('delete-event', Gtk.main_quit) win.set_filter(options.filter) if len(args) >= 1: