#!/usr/bin/env python3 # # Copyright 2008 Jose Fonseca # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # import operator import re import sys import gi gi.require_version("Gtk", "3.0") import xdot.ui from gi.repository import Gdk, Gio, GObject, Gtk def __icon_button__(icon_name: str) -> Gtk.Button: icon = Gio.ThemedIcon(name=icon_name) image = Gtk.Image.new_from_gicon(icon, Gtk.IconSize.BUTTON) button = Gtk.Button() button.add(image) return button def get_handler_id(obj, signal_name): signal_id, detail = GObject.signal_parse_name(signal_name, obj, True) return GObject.signal_handler_find( obj, GObject.SignalMatchType.ID, signal_id, detail, None, None, None ) class MyDotWindow(Gtk.Window): def __init__(self): super().__init__(title="Test") self.set_border_width(10) self.set_default_size(400, 200) self.dot = xdot.ui.DotWidget() hb = Gtk.HeaderBar() hb.set_show_close_button(True) self.set_titlebar(hb) box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) Gtk.StyleContext.add_class(box.get_style_context(), "linked") button = __icon_button__("zoom-in") button.connect("clicked", self.dot.on_zoom_in) box.add(button) button = __icon_button__("zoom-out") button.connect("clicked", self.dot.on_zoom_out) box.add(button) box.add(Gtk.SeparatorToolItem()) button = __icon_button__("zoom-fit-best") button.connect("clicked", self.dot.on_zoom_fit) box.add(button) button = __icon_button__("zoom-original") button.connect("clicked", self.dot.on_zoom_100) box.add(button) hb.pack_start(box) box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) Gtk.StyleContext.add_class(box.get_style_context(), "linked") self.search_entry = Gtk.Entry() self.search_entry.connect("activate", self.search) self.search_entry.connect("changed", self.search) self.search_entry.connect("key-press-event", self.search_key_pressed) self.search_entry.set_placeholder_text("Search...") box.add(self.search_entry) button = __icon_button__("edit-find") button.connect("clicked", self.search) box.add(button) hb.pack_end(box) self.add(self.dot) self.set_focus(self.dot) self.dot.connect("clicked", self.on_click) self.dot.disconnect(get_handler_id(self.dot, "key-press-event")) self.dot.connect("key-press-event", self.on_key_pressed) def set_dotcode(self, code): self.dot.set_dotcode(code) def find_text(self, entry_text): found_items = [] dot_widget = self.dot try: regexp = re.compile(entry_text) except re.error as err: sys.stderr.write('warning: re.compile() failed with error "%s"\n' % err) return [] for element in ( dot_widget.graph.nodes + dot_widget.graph.edges + dot_widget.graph.shapes ): if element.search_text(regexp): found_items.append(element) return sorted(found_items, key=operator.methodcaller("get_text")) def search(self, _): txt = self.search_entry.get_text() if txt: items = self.find_text(txt) self.dot.set_highlight(items, search=True) else: self.dot.set_highlight(None, search=True) def search_key_pressed(self, _, event): if event.keyval == 65307: self.set_focus(self.dot) def on_click(self, _, url, event): print(url, event) # center element x, y = int(event.x), int(event.y) jump = self.dot.get_jump(x, y) if jump is not None: self.dot.animate_to(jump.x, jump.y) def on_key_pressed(self, _, event): if event.keyval in (Gdk.KEY_f, Gdk.KEY_slash): self.set_focus(self.search_entry) return True if event.keyval == Gdk.KEY_w: self.dot.zoom_to_fit() return True if event.keyval == Gdk.KEY_plus: self.dot.zoom_image(self.dot.zoom_ratio * 1.1) return True if event.keyval == Gdk.KEY_minus: self.dot.zoom_image(self.dot.zoom_ratio / 1.1) return True if event.keyval == Gdk.KEY_equal: self.dot.zoom_image(1.0) return True if event.keyval == Gdk.KEY_Left: self.dot.x -= self.dot.POS_INCREMENT / self.dot.zoom_ratio self.dot.queue_draw() return True if event.keyval == Gdk.KEY_Right: self.dot.x += self.dot.POS_INCREMENT / self.dot.zoom_ratio self.dot.queue_draw() return True if event.keyval == Gdk.KEY_Up: self.dot.y -= self.dot.POS_INCREMENT / self.dot.zoom_ratio self.dot.queue_draw() return True if event.keyval == Gdk.KEY_Down: self.dot.y += self.dot.POS_INCREMENT / self.dot.zoom_ratio self.dot.queue_draw() return True dotcode = b""" digraph G { node [fontname="Hack", shape=rect] Hello [URL="http://en.wikipedia.org/wiki/Hello"] World [URL="World,Test layout.set_attributes(attrs)"] Test Hello -> World } """ def main(): window = MyDotWindow() window.set_dotcode(dotcode) window.connect("delete-event", Gtk.main_quit) window.show_all() Gtk.main() if __name__ == "__main__": main()