123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473 |
- /********************************************************************
- # Copyright 2014-2022 Daniel 'grindhold' Brendle
- #
- # This file is part of libgtkflow.
- #
- # libgtkflow 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.
- #
- # libgtkflow 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 libgtkflow.
- # If not, see http://www.gnu.org/licenses/.
- *********************************************************************/
- namespace GtkFlow {
- public delegate Gtk.Widget NodeTitleFactory(Node node);
- public errordomain NodeError {
- TITLE_ALREADY_INITIALIZED;
- }
- /**
- * Defines an object that can be added to a Nodeview
- *
- * Implement this if you want custom nodes that have their own
- * drawing routines and special behaviour
- */
- public interface NodeRenderer : Gtk.Widget {
- /**
- * The {@link GFlow.Node} that this Node represents
- */
- public abstract GFlow.Node n {get; protected set;}
- /**
- * Expresses wheter this node is marked via rubberband selection
- */
- public abstract bool marked {get; internal set;}
- /**
- * Returns a {@link Dock} if the given {@link GFlow.Dock} resides
- * in this node.
- */
- public abstract Dock? retrieve_dock(GFlow.Dock d);
- /**
- * Returns the value of this node's margin
- */
- public abstract int get_margin();
- /**
- * Click offset: x coordinate
- *
- * Holds the offset-position relative to the origin of the
- * node at which this node has been clicked the last time.
- */
- public abstract double click_offset_x {get; protected set; default=0;}
- /**
- * Click offset: y coordinate
- *
- * Holds the offset-position relative to the origin of the
- * node at which this node has been clicked the last time.
- */
- public abstract double click_offset_y {get; protected set; default=0;}
- /**
- * Resize start width
- *
- * Hold the original width of the node when the last resize process
- * had been started
- */
- public abstract double resize_start_width {get; protected set; default=0;}
- /**
- * Resize start height
- *
- * Hold the original height of the node when the last resize process
- * had been started
- */
- public abstract double resize_start_height {get; protected set; default=0;}
- }
- public class NodeDockLabelWidgetFactory : Object {
- public GFlow.Node node {
- get;
- private set;
- }
- public NodeDockLabelWidgetFactory(GFlow.Node node) {
- this.node = node;
- }
- public virtual Gtk.Widget create_dock_label(GFlow.Dock dock) {
- var label = new Gtk.Label(dock.name);
- label.justify = Gtk.Justification.LEFT;
- label.hexpand = true;
- if (dock is GFlow.Source) {
- label.halign = Gtk.Align.END;
- } else {
- label.halign = Gtk.Align.START;
- }
- return label;
- }
- }
- /**
- * A Simple node representation
- *
- * The default {@link NodeRenderer} that comes with libgtkflow. Use this
- * To wrap your {@link GFlow.Node}s in order to add them to a {@link NodeView}
- */
- public class Node : Gtk.Widget, NodeRenderer {
- private static string CSS = "
- .gtkflow_node {
- background: rgba(0.6, 0.6, 0.6, 0.2);
- border-radius: 5px;
- border: 1px solid rgba(128, 128, 128, 0.8);
- box-shadow: 2px 2px 3px 3px rgba(153, 153, 153, 0.5);
- }
- .gtkflow_node_marked {
- background: rgba(0, 51, 128, 0.8);
- border-radius: 5px;
- border: 1px solid rgba(0, 51, 128, 0.8);
- box-shadow: 2px 2px 3px 3px rgba(0, 51, 153, 0.5);
- }
- ";
-
- private static Gtk.CssProvider css = new Gtk.CssProvider();
- private static bool initialized = false;
- private static void init() {
- if (Node.initialized) return;
- Node.css.load_from_data(Node.CSS.data);
- Node.initialized = true;
- }
- private bool title_initialized = false;
- construct {
- set_css_name("gtkflow_node");
- this.notify["marked"].connect(this.marked_changed);
- }
- private const int MARGIN_DEFAULT = 10;
- private Gtk.Grid pads_grid;
- private Gtk.Box node_box;
- private Gtk.GestureClick ctr_click;
- public GFlow.Node n {get; protected set;}
- private NodeDockLabelWidgetFactory dock_label_factory;
- /**
- * {@inheritDoc}
- */
- public bool marked {get; internal set;}
- public Gdk.RGBA? highlight_color {get; set; default=null;}
- /**
- * {@inheritDoc}
- */
- public double click_offset_x {get; protected set; default=0;}
- /**
- * {@inheritDoc}
- */
- public double click_offset_y {get; protected set; default=0;}
- /**
- * {@inheritDoc}
- */
- public double resize_start_width {get; protected set; default=0;}
- /**
- * {@inheritDoc}
- */
- public double resize_start_height {get; protected set; default=0;}
- private int n_docks = 0;
- private int margin = 0;
- ~Node() {
- this.pads_grid.unparent();
- this.node_box.unparent();
- }
- /**
- * Instantiate a new node
- *
- * You are required to pass a {@link GFlow.Node} to this constructor.
- */
- public Node(GFlow.Node n) {
- this.with_margin(n, MARGIN_DEFAULT, new NodeDockLabelWidgetFactory(n));
- var title_box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 3);
- var title_label = new Gtk.Label("");
- title_label.set_markup ("<b>%s</b>".printf(n.name));
- title_label.hexpand = true;
- title_label.halign = Gtk.Align.START;
- title_box.append(title_label);
- var delete_icon = new Gtk.Image.from_icon_name("edit-delete");
- var delete_button = new Gtk.Button();
- delete_button.child = delete_icon;
- delete_button.has_frame = false;
- delete_button.clicked.connect(this.remove);
- title_box.append(delete_button);
- try {
- set_title(title_box);
- } catch (NodeError e) {
- message("Could not set title in node");
- }
- }
- public Node.with_margin(GFlow.Node n, int margin, NodeDockLabelWidgetFactory dock_label_factory) {
- Node.init();
- this.n = n;
- this.dock_label_factory = dock_label_factory;
- this.margin = margin;
-
- this.set_layout_manager(new Gtk.BinLayout());
- this.add_css_class("gtkflow_node");
- this.get_style_context().add_provider(Node.css,Gtk.STYLE_PROVIDER_PRIORITY_USER);
- this.node_box = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
- this.node_box.set_parent(this);
- this.node_box.hexpand = true;
- this.node_box.vexpand = true;
- this.node_box.halign = Gtk.Align.FILL;
- this.node_box.valign = Gtk.Align.FILL;
- this.node_box.margin_top = this.margin;
- this.node_box.margin_bottom = this.margin;
- this.node_box.margin_start = this.margin;
- this.node_box.margin_end = this.margin;
- create_pads_grid();
- this.ctr_click = new Gtk.GestureClick();
- this.add_controller(this.ctr_click);
- this.ctr_click.pressed.connect(this.press_button);
- this.ctr_click.end.connect(this.release_button);
- var motion_controller = new Gtk.EventControllerMotion();
- motion_controller.motion.connect(this.hover_over);
- this.add_controller(motion_controller);
- }
- private void create_pads_grid() {
- this.pads_grid = new Gtk.Grid();
- this.pads_grid.column_homogeneous = false;
- this.pads_grid.column_spacing = 5;
- this.pads_grid.row_homogeneous = false;
- this.pads_grid.row_spacing = 5;
- node_box.append(this.pads_grid);
- foreach (GFlow.Source s in n.get_sources()) {
- this.source_added(s);
- }
- foreach (GFlow.Sink s in n.get_sinks()) {
- this.sink_added(s);
- }
- this.n.source_added.connect(this.source_added);
- this.n.source_removed.connect(this.source_removed);
- this.n.sink_added.connect(this.sink_added);
- this.n.sink_removed.connect(this.sink_removed);
- }
- /**
- * Retrieve a Dock-Widget from this node.
- *
- * Gives you the GtkFlow.Dock-object that corresponds to the given
- * GFlow.Dock. Returns null if the searched Dock is not associated
- * with any of the Dock-Widgets in this node.
- */
- public Dock? retrieve_dock (GFlow.Dock d) {
- var c = this.pads_grid.get_first_child();
- while (c != null) {
- if (!(c is Dock)) {
- c = c.get_next_sibling();
- continue;
- }
- var dw = (Dock)c;
- if (dw.d == d) return dw;
- c = c.get_next_sibling();
- }
- return null;
- }
- /**
- * {@inheritDoc}
- */
- public int get_margin() {
- return this.margin;
- }
- public void remove() {
- var nv = this.get_parent() as NodeView;
- nv.remove(this);
- }
- /**
- * Adds a child widget to this node
- */
- public void add_child(Gtk.Widget child) {
- this.node_box.append(child);
- }
- public void set_title(Gtk.Widget title) throws NodeError {
- if (!this.title_initialized) {
- this.pads_grid.attach(title, 0, 0, 3, 1);
- this.title_initialized = true;
- } else {
- throw new NodeError.TITLE_ALREADY_INITIALIZED("Title may only be initialized once");
- }
- }
- /**
- * Removes a child widget from this node
- */
- public void remove_child(Gtk.Widget child) {
- child.unparent();
- }
- protected override void dispose() {
- this.pads_grid.unparent();
- base.dispose();
- }
- /**
- * {@inheritDoc}
- */
- private void sink_added(GFlow.Sink s) {
- var dock = new Dock(s);
- var dock_label = dock_label_factory.create_dock_label(dock.d);
-
- this.pads_grid.attach(dock, 0, 1 + ++n_docks, 1, 1);
- this.pads_grid.attach(dock_label, 1, 1 + n_docks, 1, 1);
- }
- private void sink_removed(GFlow.Sink s) {
- var dock_widget = retrieve_dock(s);
- int column = -1;
- int row = -1;
- int width, height = 0;
- pads_grid.query_child(dock_widget, out column, out row, out width, out height);
- if (row != -1) {
- pads_grid.remove_row(row);
- }
- }
- private void source_added(GFlow.Source s) {
- var dock = new Dock(s);
- var dock_label = dock_label_factory.create_dock_label(dock.d);
- this.pads_grid.attach(dock, 2, 1 + ++n_docks, 1, 1);
- this.pads_grid.attach(dock_label, 1, 1 + n_docks, 1, 1);
- }
- private void source_removed(GFlow.Source s) {
- var dock_widget = retrieve_dock(s);
- int column = -1;
- int row = -1;
- int width, height = 0;
- pads_grid.query_child(dock_widget, out column, out row, out width, out height);
- if (row != -1) {
- pads_grid.remove_row(row);
- }
- }
- /**
- * Programmatically set a node's position
- */
- public void set_position(int x, int y) {
- var parent = this.get_parent();
- if (!(parent is NodeView)) {
- warning("Node is not a child of a NodeView");
- return;
- }
- var nodeview = ((NodeView)parent);
- var lc = (NodeViewLayoutChild)nodeview.layout_manager.get_layout_child(this);
- lc.x = x;
- lc.y = y;
- if (this.marked) {
- foreach (NodeRenderer n in nodeview.get_marked_nodes()) {
- if (n != this) continue;
- var mlc = (NodeViewLayoutChild) nodeview.layout_manager.get_layout_child(n);
- mlc.x -= x;
- mlc.y -= y;
- }
- }
- }
- private void press_button(int n_click, double x, double y) {
- var picked_widget = this.pick(x,y, Gtk.PickFlags.NON_TARGETABLE);
- bool do_processing = true;
- if (picked_widget is GtkFlow.Dock ) {
- do_processing = false;
- }
- if (!do_processing) return;
- Gdk.Rectangle resize_area = resize_area();
- var nv = this.get_parent() as NodeView;
- if (this.n.resizable && resize_area.contains_point((int)x,(int)y)) {
- nv.resize_node = this;
- this.resize_start_width = this.get_width();
- this.resize_start_height = this.get_height();
- } else {
- nv.move_node = this;
- }
- this.click_offset_x = x;
- this.click_offset_y = y;
- }
- private void hover_over(double x, double y) {
- if (!this.n.resizable) {
- return;
- }
- Gdk.Rectangle resize_area = resize_area();
- if (resize_area.contains_point((int)x,(int)y)) {
- this.set_cursor_from_name("nwse-resize");
- } else {
- this.set_cursor_from_name("default");
- }
- }
- private void release_button() {
- var nv = this.get_parent() as NodeView;
- nv.move_node = null;
- nv.resize_node = null;
- nv.queue_allocate();
- }
- /**
- * {@inheritDoc}
- */
- public new void set_parent(Gtk.Widget w) {
- if (!(w is NodeView)) {
- warning("Trying to add a GtkFlow.Node to something that is not a GtkFlow.NodeView!");
- return;
- }
- base.set_parent(w);
- }
- private void marked_changed() {
- if (this.marked) {
- this.add_css_class("gtkflow_node_marked");
- } else {
- this.remove_css_class("gtkflow_node_marked");
- }
- }
- private Gdk.Rectangle resize_area() {
- return {
- this.get_width() - 16,
- this.get_height() - 16,
- 16,
- 16
- };
- }
- }
- }
|