#!/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 class ControlPanel(Gtk.Box): def __init__(self): super().__init__(orientation=Gtk.Orientation.VERTICAL, spacing=5) # Place the control panel in the top right self.set_halign(Gtk.Align.END) self.set_valign(Gtk.Align.START) # Add the .control-panel CSS class to this widget context = self.get_style_context() context.add_class("control-panel") def add_group(self, group): self.pack_start(group, False, False, 0) class ControlPanelGroup(Gtk.Expander): def __init__(self, title: str): Gtk.Expander.__init__(self, label=title) self._inner = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=5) # Set the size request to 200 pixels wide self.set_size_request(200, -1) # Add a bit of margin after the header self._inner.set_margin_top(5) self.add(self._inner) def add_row(self, widget): self._inner.pack_start(widget, False, False, 0) class MyControlPanel(ControlPanel): def __init__(self): super().__init__() self._first_panel = ControlPanelGroup("Some Buttons") self._first_panel.add_row(Gtk.Button(label="Button 1")) self._first_panel.add_row(Gtk.Button(label="Button 2")) self.add_group(self._first_panel) self._second_panel = ControlPanelGroup("Extra Settings") self._second_panel.add_row(Gtk.Button(label="Button 3")) self._second_panel.add_row(Gtk.Button(label="Button 4")) self._second_panel.add_row(Gtk.CheckButton.new_with_label("First checkbox")) self._second_panel.add_row(Gtk.CheckButton.new_with_label("Second checkbox")) combo = Gtk.ComboBoxText() combo.append("first", "First Choice") combo.append("second", "Second Choice") combo.append("third", "Third Choice") combo.append("forth", "This one is quite long") combo.set_active_id("first") self._second_panel.add_row(combo) self.add_group(self._second_panel) 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 MainView(Gtk.Grid): def __init__(self): super().__init__() self.searchbar = Gtk.SearchBar() self.dot = xdot.ui.DotWidget() self.searchentry = Gtk.SearchEntry() self.searchbar.connect_entry(self.searchentry) self.searchbar.add(self.searchentry) self.searchentry.connect("search-changed", self.search) self.searchentry.connect("stop-search", self.search_close) # Create the overlay widget self._overlay = Gtk.Overlay() # Add our editor and control panel as overlays self._overlay.add_overlay(self.dot) self._overlay.add_overlay(MyControlPanel()) self.attach(self.searchbar, 0, 1, 1, 1) self.attach(self._overlay, 0, 0, 1, 1) self.dot.set_hexpand(True) self.dot.set_vexpand(True) self.dot.grab_focus() 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) self.dot.connect("button-press-event", self.on_focus) def open_dotcode(self, path): fp = open(path, "rb") self.dot.set_dotcode(fp.read(), path) def set_dotcode(self, code): try: self.dot.set_dotcode(code) except AssertionError as e: print("something wrong:", e) 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.searchentry.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_close( self, _, ): self.dot.grab_focus() 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_focus(self, widget, _): self.dot.grab_focus() def on_key_pressed(self, _, event): if event.keyval in (Gdk.KEY_f, Gdk.KEY_slash): if self.searchbar.get_search_mode(): self.searchentry.grab_focus() else: self.searchbar.set_search_mode(True) 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 class MyDotWindow(Gtk.Window): def __init__(self): super().__init__(title="Test") self.set_default_size(400, 200) self._main_view = MainView() box = Gtk.VBox() paned1 = Gtk.Paned() paned2 = Gtk.Paned() button1 = Gtk.Button(label="Button1") button2 = Gtk.Button(label="Button2") paned1.add1(button1) paned1.add2(paned2) paned2.pack1(self._main_view, True, False) paned2.pack2(button2, False, True) box.pack_start(paned1, True, True, 0) self.add(box) self.show_all() def open_dotcode(self, path): self._main_view.open_dotcode(path) def install_css(): screen = Gdk.Screen.get_default() provider = Gtk.CssProvider() provider.load_from_data( b""" .control-panel { background-color: rgba(255,255,255, 0.8); padding: 4px; border-bottom-left-radius: 4px; } """ ) Gtk.StyleContext.add_provider_for_screen(screen, provider, 600) def main(): install_css() window = MyDotWindow() window.open_dotcode("../output/Test.dot") window.connect("delete-event", Gtk.main_quit) window.show_all() Gtk.main() if __name__ == "__main__": main()