1/*
2* Copyright (c) 2018 (https://github.com/phase1geo/Minder)
3*
4* This program is free software; you can redistribute it and/or
5* modify it under the terms of the GNU General Public
6* License as published by the Free Software Foundation; either
7* version 2 of the License, or (at your option) any later version.
8*
9* This program is distributed in the hope that it will be useful,
10* but WITHOUT ANY WARRANTY; without even the implied warranty of
11* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12* General Public License for more details.
13*
14* You should have received a copy of the GNU General Public
15* License along with this program; if not, write to the
16* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17* Boston, MA 02110-1301 USA
18*
19* Authored by: Trevor Williams <phase1geo@gmail.com>
20*/
21
22using Gtk;
23using GLib;
24using Gdk;
25using Cairo;
26using Gee;
27
28public enum DragTypes {
29  URI,
30  STICKER
31}
32
33public class DrawArea : Gtk.DrawingArea {
34
35  private const CursorType move_cursor = CursorType.HAND1;
36  private const CursorType url_cursor  = CursorType.HAND2;
37  private const CursorType text_cursor = CursorType.XTERM;
38
39  public static const Gtk.TargetEntry[] DRAG_TARGETS = {
40    {"text/uri-list", 0,                    DragTypes.URI},
41    {"STRING",        TargetFlags.SAME_APP, DragTypes.STICKER}
42  };
43
44  private struct SelectBox {
45    double x;
46    double y;
47    double w;
48    double h;
49    bool   valid;
50  }
51
52  private Document         _doc;
53  private GLib.Settings    _settings;
54  private double           _press_x;
55  private double           _press_y;
56  private double           _scaled_x;
57  private double           _scaled_y;
58  private double           _origin_x;
59  private double           _origin_y;
60  private double           _scale_factor;
61  private double           _store_origin_x;
62  private double           _store_origin_y;
63  private double           _store_scale_factor;
64  private bool             _pressed      = false;
65  private EventType        _press_type   = EventType.NOTHING;
66  private bool             _press_middle = false;
67  private bool             _resize       = false;
68  private bool             _motion       = false;
69  private Node?            _last_node    = null;
70  private Connection?      _last_connection = null;
71  private Array<Node>      _nodes;
72  private Connections      _connections;
73  private Stickers         _stickers;
74  private Theme            _theme;
75  private CanvasText       _orig_text;
76  private NodeSide         _orig_side;
77  private Array<NodeInfo?> _orig_info;
78  private int              _orig_width;
79  private string           _orig_title;
80  private Node?            _last_match     = null;
81  private Node?            _attach_node    = null;
82  private Connection?      _attach_conn    = null;
83  private Sticker?         _attach_sticker = null;
84  private NodeMenu         _node_menu;
85  private ConnectionMenu   _conn_menu;
86  private ConnectionsMenu  _conns_menu;
87  private NodesMenu        _nodes_menu;
88  private GroupsMenu       _groups_menu;
89  private EmptyMenu        _empty_menu;
90  private TextMenu         _text_menu;
91  private uint?            _auto_save_id = null;
92  private ImageEditor      _image_editor;
93  private UrlEditor        _url_editor;
94  private IMMulticontext   _im_context;
95  private bool             _debug        = true;
96  private bool             _focus_mode   = false;
97  private double           _focus_alpha  = 0.05;
98  private bool             _create_new_from_edit;
99  private Selection        _selected;
100  private SelectBox        _select_box;
101  private Tagger           _tagger;
102  private TextCompletion   _completion;
103  private double           _sticker_posx;
104  private double           _sticker_posy;
105  private NodeGroups       _groups;
106  private uint             _select_hover_id = 0;
107  private int              _next_node_id    = 0;
108
109  public MainWindow     win           { private set; get; }
110  public UndoBuffer     undo_buffer   { set; get; }
111  public UndoTextBuffer undo_text     { set; get; }
112  public Layouts        layouts       { set; get; default = new Layouts(); }
113  public Animator       animator      { set; get; }
114  public ImageManager   image_manager { set; get; default = new ImageManager(); }
115  public bool           is_loaded     { get; private set; default = false; }
116
117  public GLib.Settings settings {
118    get {
119      return( _settings );
120    }
121  }
122  public double origin_x {
123    set {
124      _store_origin_x = _origin_x = value;
125    }
126    get {
127      return( _origin_x );
128    }
129  }
130  public double origin_y {
131    set {
132      _store_origin_y = _origin_y = value;
133    }
134    get {
135      return( _origin_y );
136    }
137  }
138  public double sfactor {
139    set {
140      _store_scale_factor = _scale_factor = value;
141    }
142    get {
143      return( _scale_factor );
144    }
145  }
146  public UrlEditor url_editor {
147    get {
148      return( _url_editor );
149    }
150  }
151  public Tagger tagger {
152    get {
153      return( _tagger );
154    }
155  }
156  public Stickers stickers {
157    get {
158      return( _stickers );
159    }
160  }
161  public NodeGroups groups {
162    get {
163      return( _groups );
164    }
165  }
166  public int next_node_id {
167    get {
168      return( _next_node_id++ );
169    }
170  }
171
172  /* Allocate static parsers */
173  public MarkdownParser markdown_parser { get; private set; }
174  public TaggerParser   tagger_parser   { get; private set; }
175  public UrlParser      url_parser      { get; private set; }
176
177  public signal void changed();
178  public signal void current_changed( DrawArea da );
179  public signal void theme_changed( DrawArea da );
180  public signal void scale_changed( double scale );
181  public signal void show_properties( string? tab, PropertyGrab grab_type );
182  public signal void hide_properties();
183  public signal void loaded();
184
185  /* Default constructor */
186  public DrawArea( MainWindow w, GLib.Settings settings, AccelGroup accel_group ) {
187
188    win = w;
189
190    _doc      = new Document( this );
191    _settings = settings;
192
193    /* Create the selection */
194    _selected = new Selection( this );
195    _selected.selection_changed.connect( selection_changed );
196
197    /* Create the array of root nodes in the map */
198    _nodes = new Array<Node>();
199
200    /* Create the connections */
201    _connections = new Connections();
202
203    /* Create the stickers */
204    _stickers = new Stickers();
205
206    /* Create groups */
207    _groups = new NodeGroups( this );
208
209    /* Allocate memory for the animator */
210    animator = new Animator( this );
211
212    /* Allocate memory for the undo buffer */
213    undo_buffer = new UndoBuffer( this );
214
215    /* Allocate the image editor popover */
216    _image_editor = new ImageEditor( this );
217    _image_editor.changed.connect( current_image_edited );
218
219    /* Allocate the URL editor popover */
220    _url_editor = new UrlEditor( this );
221
222    /* Initialize the selection box */
223    _select_box = {0, 0, 0, 0, false};
224
225    /* Create the popup menu */
226    _node_menu   = new NodeMenu( this, accel_group );
227    _conn_menu   = new ConnectionMenu( this, accel_group );
228    _conns_menu  = new ConnectionsMenu( this, accel_group );
229    _empty_menu  = new EmptyMenu( this, accel_group );
230    _nodes_menu  = new NodesMenu( this, accel_group );
231    _groups_menu = new GroupsMenu( this, accel_group );
232    _text_menu   = new TextMenu( this, accel_group );
233
234    /* Create the node information array */
235    _orig_info = new Array<NodeInfo?>();
236
237    /* Create the parsers */
238    tagger_parser   = new TaggerParser( this );
239    markdown_parser = new MarkdownParser( this );
240    url_parser      = new UrlParser();
241
242    markdown_parser.enable = settings.get_boolean( "enable-markdown" );
243    url_parser.enable      = settings.get_boolean( "auto-parse-embedded-urls" );
244
245    /* Create text completion */
246    _completion = new TextCompletion( this );
247
248    /* Get the value of the new node from edit */
249    update_focus_mode_alpha( settings );
250    update_create_new_from_edit( settings );
251    settings.changed.connect(() => {
252      update_focus_mode_alpha( settings );
253      update_create_new_from_edit( settings );
254    });
255
256    /* Set the theme to the default theme */
257    set_theme( win.themes.get_theme( settings.get_string( "default-theme" ) ), false );
258
259    /* Create the undo text buffer */
260    undo_text   = new UndoTextBuffer( this );
261
262    /* Add event listeners */
263    this.draw.connect( on_draw );
264    this.button_press_event.connect( on_press );
265    this.motion_notify_event.connect( on_motion );
266    this.button_release_event.connect( on_release );
267    this.key_press_event.connect( on_keypress );
268    this.key_release_event.connect( on_keyrelease );
269    this.scroll_event.connect( on_scroll );
270
271    /* Make sure the above events are listened for */
272    this.add_events(
273      EventMask.BUTTON_PRESS_MASK |
274      EventMask.BUTTON_RELEASE_MASK |
275      EventMask.BUTTON1_MOTION_MASK |
276      EventMask.POINTER_MOTION_MASK |
277      EventMask.KEY_PRESS_MASK |
278      EventMask.SMOOTH_SCROLL_MASK |
279      EventMask.STRUCTURE_MASK
280    );
281
282    /* Set ourselves up to be a drag target */
283    Gtk.drag_dest_set( this, DestDefaults.MOTION | DestDefaults.DROP, DRAG_TARGETS, Gdk.DragAction.COPY );
284
285    this.drag_motion.connect( handle_drag_motion );
286    this.drag_data_received.connect( handle_drag_data_received );
287
288    /* Make sure the drawing area can receive keyboard focus */
289    this.can_focus = true;
290
291    /*
292     Make sure that we add a CSS class name to ourselves so we can color
293     our background with the theme.
294    */
295    get_style_context().add_class( "canvas" );
296
297    /* Make sure that we us the ImContextSimple input method */
298    _im_context = new IMMulticontext();
299    _im_context.set_client_window( this.get_window() );
300    _im_context.set_use_preedit( false );
301    _im_context.commit.connect( handle_im_commit );
302    _im_context.retrieve_surrounding.connect( handle_im_retrieve_surrounding );
303    _im_context.delete_surrounding.connect( handle_im_delete_surrounding );
304
305  }
306
307  /* If the current selection ever changes, let the sidebar know about it. */
308  private void selection_changed() {
309    update_focus_mode();
310    current_changed( this );
311  }
312
313  /* Returns the stored document */
314  public Document get_doc() {
315    return( _doc );
316  }
317
318  /* Returns the name of the currently selected theme */
319  public string get_theme_name() {
320    return( _theme.name );
321  }
322
323  /* Returns the current theme */
324  public Theme get_theme() {
325    return( _theme );
326  }
327
328
329  /* Sets the theme to the given value */
330  public void set_theme( Theme theme, bool save ) {
331    Theme? orig_theme = _theme;
332    _theme        = theme;
333    _theme.index  = (orig_theme != null) ? orig_theme.index : -1;
334    _theme.rotate = _settings.get_boolean( "rotate-main-link-colors" );
335    update_css();
336    if( orig_theme != null ) {
337      map_theme_colors( orig_theme );
338    }
339    theme_changed( this );
340    queue_draw();
341    if( save ) {
342      auto_save();
343    }
344  }
345
346  /* Updates the CSS for the current theme */
347  public void update_css() {
348    StyleContext.add_provider_for_screen(
349      Screen.get_default(),
350      _theme.get_css_provider( win.text_size ),
351      STYLE_PROVIDER_PRIORITY_APPLICATION
352    );
353  }
354
355  /* Updates all nodes with the new theme colors */
356  private void map_theme_colors( Theme old_theme ) {
357    for( int i=0; i<_nodes.length; i++ ) {
358      _nodes.index( i ).map_theme_colors( old_theme, _theme );
359    }
360  }
361
362  /* Sets the layout to the given value */
363  public void set_layout( string name, Node? root_node, bool undoable = true ) {
364    var old_layout = (root_node == null) ? _nodes.index( 0 ).layout : root_node.layout;
365    var new_layout = layouts.get_layout( name );
366    if( undoable ) {
367      undo_buffer.add_item( new UndoNodeLayout( old_layout, new_layout, root_node ) );
368    }
369    var old_balanceable = old_layout.balanceable;
370    animator.add_nodes( _nodes, "set layout" );
371    if( root_node == null ) {
372      for( int i=0; i<_nodes.length; i++ ) {
373        _nodes.index( i ).layout = new_layout;
374        new_layout.initialize( _nodes.index( i ) );
375      }
376    } else {
377      root_node.layout = new_layout;
378      new_layout.initialize( root_node );
379    }
380    if( !old_balanceable && new_layout.balanceable ) {
381      balance_nodes( false, false );
382    }
383    animator.animate();
384  }
385
386  /* Updates all of the node sizes */
387  public void update_node_sizes() {
388    for( int i=0; i<_nodes.length; i++ ) {
389      _nodes.index( i ).update_tree();
390    }
391    queue_draw();
392  }
393
394  /* Returns the list of nodes */
395  public Array<Node> get_nodes() {
396    return( _nodes );
397  }
398
399  /* Returns the connections list */
400  public Connections get_connections() {
401    return( _connections );
402  }
403
404  /* Gets the top and bottom y position of this draw area */
405  public void get_window_ys( out int top, out int bottom ) {
406    var vp = parent.parent as Viewport;
407    var vh = vp.get_allocated_height();
408    var sw = parent.parent.parent as ScrolledWindow;
409    top    = (int)sw.vadjustment.value;
410    bottom = top + vh;
411  }
412
413  /* Returns the current focus mode value */
414  public bool get_focus_mode() {
415    return( _focus_mode );
416  }
417
418  /* Searches for and returns the node with the specified ID */
419  public Node? get_node( Array<Node> nodes, int id ) {
420    for( int i=0; i<nodes.length; i++ ) {
421      Node? node = nodes.index( i ).get_node( id );
422      if( node != null ) {
423        return( node );
424      }
425    }
426    return( null );
427  }
428
429  /* Sets the cursor of the drawing area */
430  private void set_cursor( CursorType? type = null ) {
431
432    var     win    = get_window();
433    Cursor? cursor = win.get_cursor();
434
435    if( type == null ) {
436      win.set_cursor( null );
437    } else if( (cursor == null) || (cursor.cursor_type != type) ) {
438      win.set_cursor( new Cursor.for_display( get_display(), type ) );
439    }
440
441  }
442
443  /* Sets the cursor of the drawing area to the named cursor */
444  private void set_cursor_from_name( string name ) {
445    var win = get_window();
446    win.set_cursor( new Cursor.from_name( get_display(), name ) );
447  }
448
449  /* Loads the drawing area origin from the XML node */
450  private void load_drawarea( Xml.Node* n ) {
451
452    string? x = n->get_prop( "x" );
453    if( x != null ) {
454      origin_x = double.parse( x );
455    }
456
457    string? y = n->get_prop( "y" );
458    if( y != null ) {
459      origin_y = double.parse( y );
460    }
461
462    string? sf = n->get_prop( "scale" );
463    if( sf != null ) {
464      sfactor = double.parse( sf );
465      scale_changed( (sfactor > 0) ? sfactor : 1.0 );
466    }
467
468  }
469
470  /* Loads the given theme from the list of available options */
471  private void load_theme( Xml.Node* n ) {
472
473    /* Load the theme */
474    var theme       = new Theme.from_theme( win.themes.get_theme( "default" ) );
475    theme.temporary = true;
476    theme.rotate    = _settings.get_boolean( "rotate-main-link-colors" );
477
478    var valid = theme.load( n );
479
480    /* If this theme does not currently exist, add the theme temporarily */
481    if( !win.themes.exists( theme ) ) {
482      if( valid ) {
483        theme.name = win.themes.uniquify_name( theme.name );
484        win.themes.add_theme( theme );
485      } else {
486        theme.name = "default";
487      }
488    }
489
490    /* Get the theme */
491    _theme = win.themes.get_theme( theme.name );
492
493    /* If we are the current drawarea, update the CSS and indicate the theme change */
494    if( win.get_current_da() == this ) {
495      update_css();
496      theme_changed( this );
497    }
498
499  }
500
501  /*
502   We don't store the layout, but if it is found, we need to initialize the
503   layout information for all nodes to this value.
504  */
505  private void load_layout( Xml.Node* n, ref Layout? layout ) {
506
507    string? name = n->get_prop( "name" );
508    if( name != null ) {
509      layout = layouts.get_layout( name );
510    }
511
512  }
513
514  /* Loads the contents of the data input stream */
515  public void load( Xml.Node* n ) {
516
517    Layout? use_layout = null;
518    var     id_map     = new HashMap<int,int>();
519    var     link_ids   = new Array<NodeLinkInfo?>();
520
521    /* Disable animations while we are loading */
522    var animate = animator.enable;
523    animator.enable = false;
524
525    /* Clear the existing nodes */
526    _nodes.remove_range( 0, _nodes.length );
527
528    /* Load the contents of the file */
529    for( Xml.Node* it = n->children; it != null; it = it->next ) {
530      if( it->type == Xml.ElementType.ELEMENT_NODE ) {
531        switch( it->name ) {
532          case "theme"       :  load_theme( it );   break;
533          case "layout"      :  load_layout( it, ref use_layout );  break;
534          case "styles"      :  StyleInspector.styles.load( it );  break;
535          case "drawarea"    :  load_drawarea( it );  break;
536          case "images"      :  image_manager.load( it );  break;
537          case "connections" :  _connections.load( this, it, null, _nodes, id_map );  break;
538          case "groups"      :  groups.load( this, it, id_map );  break;
539          case "stickers"    :  _stickers.load( it );  break;
540          case "nodes"       :
541            for( Xml.Node* it2 = it->children; it2 != null; it2 = it2->next ) {
542              if( (it2->type == Xml.ElementType.ELEMENT_NODE) && (it2->name == "node") ) {
543                var node = new Node.with_name( this, "", null );
544                node.load( this, it2, true, id_map, link_ids );
545                if( use_layout != null ) {
546                  node.layout = use_layout;
547                }
548                _nodes.append_val( node );
549              }
550            }
551            break;
552        }
553      }
554    }
555
556    /* Handle node links */
557    for( int i=0; i<link_ids.length; i++ ) {
558      link_ids.index( i ).node.linked_node = get_node( _nodes, id_map.get( int.parse( link_ids.index( i ).id_str ) ) );
559    }
560
561    /* Perform the layout process again to make sure that everything is accounted for */
562    for( int i=0; i<_nodes.length; i++ ) {
563      _nodes.index( i ).layout.initialize( _nodes.index( i ) );
564    }
565
566    queue_draw();
567
568    /* Indicate to anyone listening that we have loaded a new file */
569    is_loaded = true;
570    loaded();
571
572    /* Make sure that the inspector is updated */
573    current_changed( this );
574
575    /* Reset the animator enable */
576    animator.enable = animate;
577
578  }
579
580  /* Saves the contents of the drawing area to the data output stream */
581  public bool save( Xml.Node* parent ) {
582
583    parent->add_child( _theme.save() );
584
585    StyleInspector.styles.save( parent );
586
587    Xml.Node* origin = new Xml.Node( null, "drawarea" );
588    origin->new_prop( "x", _store_origin_x.to_string() );
589    origin->new_prop( "y", _store_origin_y.to_string() );
590    origin->new_prop( "scale", _store_scale_factor.to_string() );
591    parent->add_child( origin );
592
593    Xml.Node* images = new Xml.Node( null, "images" );
594    image_manager.save( images );
595    parent->add_child( images );
596
597    Xml.Node* nodes = new Xml.Node( null, "nodes" );
598    for( int i=0; i<_nodes.length; i++ ) {
599      _nodes.index( i ).save( nodes );
600    }
601    parent->add_child( nodes );
602    parent->add_child( groups.save() );
603
604    _connections.save( parent );
605    parent->add_child( _stickers.save() );
606
607    return( true );
608
609  }
610
611  /* Imports the OPML data, creating a mind map */
612  public void import_opml( Xml.Node* n, ref Array<int>? expand_state) {
613
614    int node_id = 1;
615
616    /* Clear the existing nodes */
617    _nodes.remove_range( 0, _nodes.length );
618
619    /* Load the contents of the file */
620    for( Xml.Node* it = n->children; it != null; it = it->next ) {
621      if( it->type == Xml.ElementType.ELEMENT_NODE ) {
622        if( it->name == "outline") {
623          var root = new Node( this, layouts.get_default() );
624          root.import_opml( this, it, node_id, ref expand_state, _theme );
625          if (_nodes.length == 0) {
626            root.posx = (get_allocated_width()  / 2) - 30;
627            root.posy = (get_allocated_height() / 2) - 10;
628          } else {
629            _nodes.index( _nodes.length - 1 ).layout.position_root( _nodes.index( _nodes.length - 1 ), root );
630          }
631          _nodes.append_val( root );
632        }
633      }
634    }
635
636  }
637
638  /* Exports all of the nodes in OPML format */
639  public void export_opml( Xml.Node* parent, out string expand_state ) {
640    Array<int> estate  = new Array<int>();
641    int        node_id = 1;
642    for( int i=0; i<_nodes.length; i++ ) {
643      _nodes.index( i ).export_opml( parent, ref node_id, ref estate );
644    }
645    expand_state = "";
646    for( int i=0; i<estate.length; i++ ) {
647      if( i > 0 ) {
648        expand_state += ",";
649      }
650      expand_state += estate.index( i ).to_string();
651    }
652  }
653
654  /* Initializes the canvas to prepare it for a document that will be loaded */
655  public void initialize_for_open() {
656
657    /* Clear the list of existing nodes */
658    _nodes.remove_range( 0, _nodes.length );
659
660    /* Clear the list of connections */
661    _connections.clear_all_connections();
662
663    /* Clear the stickers */
664    _stickers.clear();
665
666    /* Clear the groups */
667    _groups.clear();
668
669    /* Clear the undo buffer */
670    undo_buffer.clear();
671
672    /* Clear the selection */
673    _selected.clear();
674
675    /* Initialize variables */
676    origin_x            = 0.0;
677    origin_y            = 0.0;
678    sfactor             = 1.0;
679    _pressed            = false;
680    _press_type         = EventType.NOTHING;
681    _motion             = false;
682    _attach_node        = null;
683    _attach_conn        = null;
684    _attach_sticker     = null;
685    _orig_text          = new CanvasText( this );
686    _last_connection    = null;
687
688    set_current_node( null );
689
690    queue_draw();
691
692  }
693
694  /* Retrieves canvas size settings and returns the approximate dimensions */
695  public void get_dimensions( out int width, out int height ) {
696    var sidebar_width = _settings.get_boolean( "current-properties-shown" ) ||
697                        _settings.get_boolean( "map-properties-shown" )     ||
698                        _settings.get_boolean( "sticker-properties-shown" ) ||
699                        _settings.get_boolean( "style-properties-shown" ) ? _settings.get_int( "properties-width" ) : 0;
700    width  = _settings.get_int( "window-w" ) - sidebar_width;
701    height = _settings.get_int( "window-h" );
702  }
703
704  /* Initialize the empty drawing area with a node */
705  public void initialize_for_new() {
706
707    /* Clear the list of existing nodes */
708    _nodes.remove_range( 0, _nodes.length );
709
710    /* Clear the list of connections */
711    _connections.clear_all_connections();
712
713    /* Clear the stickers */
714    _stickers.clear();
715
716    /* Clear the groups */
717    _groups.clear();
718
719    /* Clear the undo buffer */
720    undo_buffer.clear();
721
722    /* Clear the selection */
723    _selected.clear();
724
725    /* Initialize variables */
726    origin_x            = 0.0;
727    origin_y            = 0.0;
728    sfactor             = 1.0;
729    _pressed            = false;
730    _press_type         = EventType.NOTHING;
731    _motion             = false;
732    _attach_node        = null;
733    _attach_conn        = null;
734    _attach_sticker     = null;
735    _orig_text          = new CanvasText( this );
736    _last_connection    = null;
737
738    /* Create the main idea node */
739    var n = new Node.with_name( this, _("Main Idea"), layouts.get_default() );
740
741    /* Get the rough dimensions of the canvas */
742    int wwidth, wheight;
743    get_dimensions( out wwidth, out wheight );
744
745    /* Set the node information */
746    n.posx  = (wwidth  / 2) - 30;
747    n.posy  = (wheight / 2) - 10;
748    n.style = StyleInspector.styles.get_global_style();
749
750    _nodes.append_val( n );
751
752    /* Make this initial node the current node */
753    set_current_node( n );
754    set_node_mode( n, NodeMode.EDITABLE, false );
755
756    /* Redraw the canvas */
757    queue_draw();
758
759  }
760
761  /* Returns the current node */
762  public Node? get_current_node() {
763    return( _selected.current_node() );
764  }
765
766  /* Returns the current connection */
767  public Connection? get_current_connection() {
768    return( _selected.current_connection() );
769  }
770
771  /* Returns the array of selected nodes */
772  public Array<Node> get_selected_nodes() {
773    return( _selected.nodes() );
774  }
775
776  /* Returns the array of selected connections */
777  public Array<Connection> get_selected_connections() {
778    return( _selected.connections() );
779  }
780
781  /* Returns the array of selected groups */
782  public Array<NodeGroup> get_selected_groups() {
783    return( _selected.groups() );
784  }
785
786  /* Returns the selection instance associated with this DrawArea */
787  public Selection get_selections() {
788    return( _selected );
789  }
790
791  /*
792   Populates the list of matches with any nodes that match the given string
793   pattern.
794  */
795  public void get_match_items(string tabname, string pattern, bool[] search_opts, ref Gtk.ListStore matches ) {
796    if( search_opts[0] ) {
797      for( int i=0; i<_nodes.length; i++ ) {
798        _nodes.index( i ).get_match_items( tabname, pattern, search_opts, ref matches );
799      }
800    }
801    if( search_opts[1] ) {
802      _connections.get_match_items( tabname, pattern, search_opts, ref matches );
803    }
804  }
805
806  /* Sets the current node to the given node */
807  public void set_current_node( Node? n ) {
808    if( n == null ) {
809      _selected.clear_nodes();
810    } else if( _selected.is_node_selected( n ) && (_selected.num_nodes() == 1) ) {
811      set_node_mode( _selected.nodes().index( 0 ), NodeMode.CURRENT );
812    } else {
813      _selected.clear_nodes( false );
814      var last_folded = n.folded_ancestor();
815      if( last_folded != null ) {
816        last_folded.set_fold_only( false );
817        undo_buffer.add_item( new UndoNodeFolds.single( last_folded ) );
818      }
819      _selected.add_node( n );
820    }
821  }
822
823  /* Needs to be called whenever the user changes the mode of the current node */
824  public void set_node_mode( Node node, NodeMode mode, bool undoable = true ) {
825    if( (node.mode != NodeMode.EDITABLE) && (mode == NodeMode.EDITABLE) ) {
826      update_im_cursor( node.name );
827      _im_context.focus_in();
828      if( node.name.is_within( _scaled_x, _scaled_y ) ) {
829        set_cursor( text_cursor );
830      }
831      undo_text.orig.copy( node.name );
832      undo_text.ct      = node.name;
833      undo_text.do_undo = undoable;
834      node.mode = mode;
835    } else if( (node.mode == NodeMode.EDITABLE) && (mode != NodeMode.EDITABLE) ) {
836      _im_context.reset();
837      _im_context.focus_out();
838      if( node.name.is_within( _scaled_x, _scaled_y ) ) {
839        set_cursor( null );
840      }
841      undo_text.clear();
842      if( undo_text.do_undo ) {
843        undo_buffer.add_item( new UndoNodeName( this, node, undo_text.orig ) );
844      }
845      undo_text.ct      = null;
846      undo_text.do_undo = false;
847      node.mode = mode;
848      auto_save();
849    } else {
850      node.mode = mode;
851    }
852  }
853
854  /* Needs to be called whenever the user changes the mode of the current connection */
855  public void set_connection_mode( Connection conn, ConnMode mode, bool undoable = true ) {
856    if( (conn.mode != ConnMode.EDITABLE) && (mode == ConnMode.EDITABLE) ) {
857      update_im_cursor( conn.title );
858      _im_context.focus_in();
859      if( (conn.title != null) && conn.title.is_within( _scaled_x, _scaled_y ) ) {
860        set_cursor( text_cursor );
861      }
862      undo_text.orig.copy( conn.title );
863      undo_text.ct      = conn.title;
864      undo_text.do_undo = undoable;
865    } else if( (conn.mode == ConnMode.EDITABLE) && (mode != ConnMode.EDITABLE) ) {
866      _im_context.reset();
867      _im_context.focus_out();
868      if( (conn.title != null) && conn.title.is_within( _scaled_x, _scaled_y ) ) {
869        set_cursor( null );
870      }
871      undo_text.clear();
872      if( undo_text.do_undo ) {
873        undo_buffer.add_item( new UndoConnectionTitle( this, conn, undo_text.orig ) );
874      }
875      undo_text.ct      = null;
876      undo_text.do_undo = false;
877    }
878    conn.mode = mode;
879  }
880
881  /* Returns the undo buffer associated with the current state */
882  public UndoBuffer current_undo_buffer() {
883    var current = _selected.current_node();
884    if( (current != null) && (current.mode == NodeMode.EDITABLE) ) {
885      return( undo_text );
886    }
887    return( undo_buffer );
888  }
889
890  /* Updates the IM context cursor location based on the canvas text position */
891  private void update_im_cursor( CanvasText ct ) {
892    var int_posx   = (int)ct.posx;
893    var int_posy   = (int)ct.posy;
894    var int_height = (int)ct.height;
895    Gdk.Rectangle rect = {int_posx, int_posy, 0, int_height};
896    _im_context.set_cursor_location( rect );
897  }
898
899  /* Sets the current connection to the given node */
900  public void set_current_connection( Connection? c ) {
901    if( c != null ) {
902      _selected.set_current_connection( c );
903      c.from_node.last_selected_connection = c;
904      c.to_node.last_selected_connection   = c;
905    } else {
906      _selected.clear_connections();
907    }
908  }
909
910  /* Sets the current selected sticker to the specified sticker */
911  public void set_current_sticker( Sticker? s ) {
912    _selected.set_current_sticker( s );
913    _stickers.select_sticker( s );
914  }
915
916  /* Sets the current selected group to the specified group */
917  public void set_current_group( NodeGroup? g ) {
918    _selected.set_current_group( g );
919  }
920
921  /* Toggles the value of the specified node, if possible */
922  public void toggle_task( Node n ) {
923    var changes = new Array<NodeTaskInfo?>();
924    n.toggle_task_done( ref changes );
925    undo_buffer.add_item( new UndoNodeTasks( changes ) );
926    queue_draw();
927    auto_save();
928  }
929
930  /* Toggles the fold for the given node */
931  public void toggle_fold( Node n ) {
932    var fold    = !n.folded;
933    var changes = new Array<Node>();
934    n.set_fold( fold, changes );
935    undo_buffer.add_item( new UndoNodeFolds( changes ) );
936    queue_draw();
937    auto_save();
938  }
939
940  /* Toggles the folding of all selected nodes that can be folded */
941  public void toggle_folds() {
942    var parents = new Array<Node>();
943    var changes = new Array<Node>();
944    _selected.get_parents( ref parents );
945    if( parents.length > 0 ) {
946      for( int i=0; i<parents.length; i++ ) {
947        var node = parents.index( i );
948        node.set_fold( !node.folded, changes );
949      }
950      undo_buffer.add_item( new UndoNodeFolds( changes ) );
951      queue_draw();
952      auto_save();
953    }
954  }
955
956  /* Adds a new group for the given list of nodes */
957  public void add_group() {
958    if( _selected.num_groups() > 1 ) {
959      var selgroups = _selected.groups();
960      var merged    = groups.merge_groups( selgroups );
961      if( merged != null ) {
962        undo_buffer.add_item( new UndoGroupsMerge( selgroups, merged ) );
963        _selected.set_current_group( merged );
964        queue_draw();
965        auto_save();
966      }
967    } else if( _selected.num_nodes() > 0 ) {
968      var nodes = _selected.nodes();
969      var group = new NodeGroup.array( this, nodes );
970      groups.add_group( group );
971      undo_buffer.add_item( new UndoGroupAdd( group ) );
972      queue_draw();
973      auto_save();
974    }
975  }
976
977  /* Removes the currently selected group */
978  public void remove_groups() {
979    var selgroups = _selected.groups();
980    if( selgroups.length == 0 ) return;
981    for( int i=0; i<selgroups.length; i++ ) {
982      groups.remove_group( selgroups.index( i ) );
983    }
984    undo_buffer.add_item( new UndoGroupsRemove( selgroups ) );
985    _selected.clear();
986    queue_draw();
987    auto_save();
988  }
989
990  public void change_group_color( RGBA color ) {
991    var selgroups = _selected.groups();
992    if( selgroups.length == 0 ) return;
993    undo_buffer.add_item( new UndoGroupsColor( selgroups, color ) );
994    for( int i=0; i<selgroups.length; i++ ) {
995      selgroups.index( i ).color = color;
996    }
997    queue_draw();
998    auto_save();
999  }
1000
1001  /*
1002   Changes the current connection's title to the given value.
1003  */
1004  public void change_current_connection_title( string title ) {
1005    var conns = _selected.connections();
1006    if( conns.length == 1 ) {
1007      var current = conns.index( 0 );
1008      if( current.title.text.text != title ) {
1009        var orig_title = new CanvasText( this );
1010        orig_title.copy( current.title );
1011        current.change_title( this, title );
1012        // if( !_current_new ) {
1013          undo_buffer.add_item( new UndoConnectionTitle( this, current, orig_title ) );
1014        // }
1015        queue_draw();
1016        auto_save();
1017      }
1018    }
1019  }
1020
1021  /* Changes the state of the given task if it differs from the desired values */
1022  private void change_task( Node node, bool enable, bool done, Array<NodeTaskInfo?> changes ) {
1023    if( (node.task_enabled() == enable) && (node.task_done() == done) ) return;
1024    changes.append_val( NodeTaskInfo( node.task_enabled(), node.task_done(), node ) );
1025    node.enable_task( enable );
1026    node.set_task_done( done );
1027  }
1028
1029  /*
1030   Changes the current node's task to the given values.  Updates the layout,
1031   adds the undo item, and redraws the canvas.
1032  */
1033  public void change_current_task( bool enable, bool done ) {
1034    var nodes = _selected.nodes();
1035    if( nodes.length != 1 ) return;
1036    var changes = new Array<NodeTaskInfo?>();
1037    change_task( nodes.index( 0 ), enable, done, changes );
1038    if( changes.length > 0 ) {
1039      undo_buffer.add_item( new UndoNodeTasks( changes ) );
1040      queue_draw();
1041      auto_save();
1042    }
1043  }
1044
1045  /* Toggles the task values of the selected nodes */
1046  public void change_selected_tasks() {
1047    var parents     = new Array<Node>();
1048    var changes     = new Array<NodeTaskInfo?>();
1049    var all_enabled = true;
1050    var all_done    = true;
1051    _selected.get_parents( ref parents );
1052    for( int i=0; i<parents.length; i++ ) {
1053      var node = parents.index( i );
1054      all_enabled &= node.task_enabled();
1055      all_done    &= node.task_done();
1056    }
1057    if( all_enabled ) {
1058      if( all_done ) {
1059        for( int i=0; i<parents.length; i++ ) {
1060          change_task( parents.index( i ), false, false, changes );
1061        }
1062      } else {
1063        for( int i=0; i<parents.length; i++ ) {
1064          change_task( parents.index( i ), true, true, changes );
1065        }
1066      }
1067    } else {
1068      for( int i=0; i<parents.length; i++ ) {
1069        change_task( parents.index( i ), true, false, changes );
1070      }
1071    }
1072    if( changes.length > 0 ) {
1073      undo_buffer.add_item( new UndoNodeTasks( changes ) );
1074      queue_draw();
1075      auto_save();
1076    }
1077  }
1078
1079  /*
1080   Changes the current node's folded state to the given value.  Updates the
1081   layout, adds the undo item and redraws the canvas.
1082  */
1083  public void change_current_fold( bool folded ) {
1084    var nodes = _selected.nodes();
1085    if( nodes.length == 1 ) {
1086      var current = nodes.index( 0 );
1087      var changes = new Array<Node>();
1088      current.set_fold( folded, changes );
1089      undo_buffer.add_item( new UndoNodeFolds( changes ) );
1090      queue_draw();
1091      auto_save();
1092    }
1093  }
1094
1095  /*
1096   Changes the current node's note to the given value.  Updates the
1097   layout, adds the undo item and redraws the canvas.
1098  */
1099  public void change_current_node_note( string note ) {
1100    var nodes = _selected.nodes();
1101    if( nodes.length == 1 ) {
1102      nodes.index( 0 ).note = note;
1103      queue_draw();
1104      auto_save();
1105    }
1106  }
1107
1108  /*
1109   Changes the current connection's note to the given value.
1110  */
1111  public void change_current_connection_note( string note ) {
1112    var conns = _selected.connections();
1113    if( conns.length == 1 ) {
1114      conns.index( 0 ).note = note;
1115      queue_draw();
1116      auto_save();
1117    }
1118  }
1119
1120  /*
1121   Adds an image to the current node by allowing the user to select an image file
1122   from the file system and, optionally, editing the image prior to assigning it
1123   to a node.  Updates the layout, adds the undo item and redraws the canvas.
1124   item and redraws the canvas.
1125  */
1126  public void add_current_image() {
1127    var nodes = _selected.nodes();
1128    if( nodes.length == 1 ) {
1129      var current = nodes.index( 0 );
1130      if( current.image == null ) {
1131        var parent = (Gtk.Window)get_toplevel();
1132        var id     = image_manager.choose_image( parent );
1133        if( id != -1 ) {
1134          current.set_image( image_manager, new NodeImage( image_manager, id, current.style.node_width ) );
1135          if( current.image != null ) {
1136            undo_buffer.add_item( new UndoNodeImage( current, null ) );
1137            queue_draw();
1138            current_changed( this );
1139            auto_save();
1140          }
1141        }
1142      }
1143    }
1144  }
1145
1146  /*
1147   Deletes the image from the current node.  Updates the layout, adds the undo
1148   item and redraws the canvas.
1149  */
1150  public void delete_current_image() {
1151    var nodes = _selected.nodes();
1152    if( nodes.length == 1 ) {
1153      var current = nodes.index( 0 );
1154      NodeImage? orig_image = current.image;
1155      if( orig_image != null ) {
1156        current.set_image( image_manager, null );
1157        undo_buffer.add_item( new UndoNodeImage( current, orig_image ) );
1158        queue_draw();
1159        current_changed( this );
1160        auto_save();
1161      }
1162    }
1163  }
1164
1165  /*
1166   Causes the current node's image to be edited.
1167  */
1168  public void edit_current_image() {
1169    var nodes = _selected.nodes();
1170    if( nodes.length == 1 ) {
1171      var current = nodes.index( 0 );
1172      if( current.image != null ) {
1173        _image_editor.edit_image( image_manager, current, current.posx, current.posy );
1174      }
1175    }
1176  }
1177
1178  /* Called whenever the current node's image is changed */
1179  private void current_image_edited( NodeImage? orig_image ) {
1180    var current = _selected.current_node();
1181    undo_buffer.add_item( new UndoNodeImage( current, orig_image ) );
1182    queue_draw();
1183    current_changed( this );
1184    auto_save();
1185  }
1186
1187  /* Called when the linking process has successfully completed */
1188  private void end_link( Node node ) {
1189    if( _selected.num_connections() == 0 ) return;
1190    _selected.clear_connections();
1191    _last_node.linked_node = node;
1192    undo_buffer.add_item( new UndoNodeLink( _last_node, null ) );
1193    _last_connection  = null;
1194    _last_node        = null;
1195    set_node_mode( _attach_node, NodeMode.NONE );
1196    _attach_node      = null;
1197    auto_save();
1198    queue_draw();
1199  }
1200
1201  /* Returns true if any of the selected nodes contain node links */
1202  public bool any_selected_nodes_linked() {
1203    var nodes = _selected.nodes();
1204    for( int i=0; i<nodes.length; i++ ) {
1205      if( nodes.index( i ).linked_node != null ) {
1206        return( true );
1207      }
1208    }
1209    return( false );
1210  }
1211
1212  /* Creates links between selected nodes */
1213  public void create_links() {
1214    var nodes = _selected.nodes();
1215    if( nodes.length < 2 ) return;
1216    undo_buffer.add_item( new UndoNodesLink( nodes ) );
1217    for( int i=0; i<(nodes.length - 1); i++ ) {
1218      nodes.index( i ).linked_node = nodes.index( i + 1 );
1219    }
1220    auto_save();
1221    queue_draw();
1222  }
1223
1224  /* Deletes all of the selected node links */
1225  public void delete_links() {
1226    var nodes = _selected.nodes();
1227    undo_buffer.add_item( new UndoNodesLink( nodes ) );
1228    for( int i=0; i<nodes.length; i++ ) {
1229      if( nodes.index( i ).linked_node != null ) {
1230        nodes.index( i ).linked_node = null;
1231      }
1232    }
1233    auto_save();
1234    queue_draw();
1235  }
1236
1237  /* Toggles the node links */
1238  public void toggle_links() {
1239    var nodes = _selected.nodes();
1240    if( any_selected_nodes_linked() ) {
1241      delete_links();
1242    } else if( nodes.length == 1 ) {
1243      start_connection( true, true );
1244    } else {
1245      create_links();
1246    }
1247  }
1248
1249  /*
1250   Changes the current node's link color and propagates that color to all
1251   descendants.
1252  */
1253  public void change_current_link_color( RGBA color ) {
1254    var current = _selected.current_node();
1255    if( current != null ) {
1256      RGBA orig_color = current.link_color;
1257      if( orig_color != color ) {
1258        current.link_color = color;
1259        undo_buffer.add_item( new UndoNodeLinkColor( current, orig_color ) );
1260        queue_draw();
1261        auto_save();
1262      }
1263    }
1264  }
1265
1266  /* Changes the link colors of all selected nodes to the specified color */
1267  public void change_link_colors( RGBA color ) {
1268    var nodes = _selected.nodes();
1269    undo_buffer.add_item( new UndoNodesLinkColor( nodes, color ) );
1270    for( int i=0; i<nodes.length; i++ ) {
1271      nodes.index( i ).link_color = color;
1272    }
1273    queue_draw();
1274    auto_save();
1275  }
1276
1277  public void randomize_current_link_color() {
1278    var current = _selected.current_node();
1279    if( current != null ) {
1280      RGBA orig_color = current.link_color;
1281      do {
1282        current.link_color = _theme.random_link_color();
1283      } while( orig_color.equal( current.link_color ) );
1284      undo_buffer.add_item( new UndoNodeLinkColor( current, orig_color ) );
1285      queue_draw();
1286      auto_save();
1287      current_changed( this );
1288    }
1289  }
1290
1291  /* Randomizes the link colors of the selected nodes */
1292  public void randomize_link_colors() {
1293    var nodes  = _selected.nodes();
1294    var colors = new Array<RGBA?>();
1295    for( int i=0; i<nodes.length; i++ ) {
1296      colors.append_val( nodes.index( i ).link_color );
1297      nodes.index( i ).link_color = _theme.random_link_color();
1298    }
1299    undo_buffer.add_item( new UndoNodesRandLinkColor( nodes, colors ) );
1300    queue_draw();
1301    auto_save();
1302  }
1303
1304  /* Reparents the current node's link color */
1305  public void reparent_current_link_color() {
1306    var current = _selected.current_node();
1307    if( current != null ) {
1308      undo_buffer.add_item( new UndoNodeReparentLinkColor( current ) );
1309      current.link_color_root = false;
1310      queue_draw();
1311      auto_save();
1312      current_changed( this );
1313    }
1314  }
1315
1316  /* Causes the selected nodes to use the link color of their parent */
1317  public void reparent_link_colors() {
1318    var nodes = _selected.nodes();
1319    undo_buffer.add_item( new UndoNodesReparentLinkColor( nodes ) );
1320    for( int i=0; i<nodes.length; i++ ) {
1321      nodes.index( i ).link_color_root = false;
1322    }
1323    queue_draw();
1324    auto_save();
1325  }
1326
1327  /*
1328   Changes the current connection's color to the specified color.
1329  */
1330  public void change_current_connection_color( RGBA? color ) {
1331    var conn = _selected.current_connection();
1332    if( conn == null ) return;
1333    var orig_color = conn.color;
1334    if( orig_color != color ) {
1335      conn.color = color;
1336      undo_buffer.add_item( new UndoConnectionColor( conn, orig_color ) );
1337      queue_draw();
1338      auto_save();
1339      current_changed( this );
1340    }
1341  }
1342
1343  /* Clears the current connection (if it is set) and updates the UI accordingly */
1344  private void clear_current_connection( bool signal_change ) {
1345    if( _selected.num_connections() > 0 ) {
1346      _selected.clear_connections( signal_change );
1347      _last_connection = null;
1348    }
1349  }
1350
1351  /* Clears the current node (if it is set) and updates the UI accordingly */
1352  private void clear_current_node( bool signal_change ) {
1353    if( _selected.num_nodes() > 0 ) {
1354      _selected.clear_nodes( signal_change );
1355    }
1356  }
1357
1358  /* Clears the current sticker (if it is set) and updates the UI accordingly */
1359  private void clear_current_sticker( bool signal_change ) {
1360    if( _selected.num_stickers() > 0 ) {
1361      _selected.clear_stickers( signal_change );
1362    }
1363  }
1364
1365  /* Clears the current group (if it is set) and updates the UI accordingly */
1366  private void clear_current_group( bool signal_change ) {
1367    if( _selected.num_groups() > 0 ) {
1368      _selected.clear_groups( signal_change );
1369    }
1370  }
1371
1372  /* Called whenever the user clicks on a valid connection */
1373  private bool set_current_connection_from_position( Connection conn, EventButton e ) {
1374
1375    var shift = (bool)(e.state & ModifierType.SHIFT_MASK);
1376
1377    if( _selected.is_current_connection( conn ) ) {
1378      if( conn.mode == ConnMode.EDITABLE ) {
1379        switch( e.type ) {
1380          case EventType.BUTTON_PRESS        :
1381            conn.title.set_cursor_at_char( e.x, e.y, shift );
1382            _im_context.reset();
1383            break;
1384          case EventType.DOUBLE_BUTTON_PRESS :
1385            conn.title.set_cursor_at_word( e.x, e.y, shift );
1386            _im_context.reset();
1387            break;
1388          case EventType.TRIPLE_BUTTON_PRESS :
1389            conn.title.set_cursor_all( false );
1390            _im_context.reset();
1391            break;
1392        }
1393      } else if( e.type == EventType.DOUBLE_BUTTON_PRESS ) {
1394        var current = _selected.current_connection();
1395        _orig_title = (current.title != null) ? current.title.text.text : "";
1396        current.edit_title_begin( this );
1397        set_connection_mode( current, ConnMode.EDITABLE );
1398      }
1399      return( true );
1400    } else {
1401      if( shift ) {
1402        _selected.add_connection( conn );
1403        handle_connection_edit_on_creation( conn );
1404      } else {
1405        set_current_connection( conn );
1406      }
1407    }
1408
1409    return( false );
1410
1411  }
1412
1413  /* Called whenever the user clicks on node */
1414  private bool set_current_node_from_position( Node node, EventButton e ) {
1415
1416    var scaled_x = scale_value( e.x );
1417    var scaled_y = scale_value( e.y );
1418    var shift    = (bool)(e.state & ModifierType.SHIFT_MASK);
1419    var control  = (bool)(e.state & ModifierType.CONTROL_MASK);
1420    var dpress   = e.type == EventType.DOUBLE_BUTTON_PRESS;
1421    var tpress   = e.type == EventType.TRIPLE_BUTTON_PRESS;
1422    var tag      = FormatTag.LENGTH;
1423    var url      = "";
1424    var left     = 0.0;
1425
1426    set_tooltip_markup( null );
1427
1428    /* Check to see if the user clicked anywhere within the node which is itself a clickable target */
1429    if( node.is_within_task( scaled_x, scaled_y ) ) {
1430      toggle_task( node );
1431      current_changed( this );
1432      return( false );
1433    } else if( node.is_within_linked_node( scaled_x, scaled_y ) ) {
1434      select_linked_node( node );
1435      return( false );
1436    } else if( node.is_within_fold( scaled_x, scaled_y ) ) {
1437      toggle_fold( node );
1438      current_changed( this );
1439      return( false );
1440    } else if( node.is_within_resizer( scaled_x, scaled_y ) ) {
1441      _resize     = true;
1442      _orig_width = node.style.node_width;
1443      return( true );
1444    } else if( !shift && control && node.name.is_within_clickable( scaled_x, scaled_y, out tag, out url ) ) {
1445      if( tag == FormatTag.URL ) {
1446        Utils.open_url( url );
1447      }
1448      return( false );
1449    }
1450
1451    _orig_side = node.side;
1452    _orig_info.remove_range( 0, _orig_info.length );
1453    node.get_node_info( ref _orig_info );
1454
1455    /* If the node is being edited, go handle the click */
1456    if( node.mode == NodeMode.EDITABLE ) {
1457      switch( e.type ) {
1458        case EventType.BUTTON_PRESS        :
1459          node.name.set_cursor_at_char( scaled_x, scaled_y, shift );
1460          _im_context.reset();
1461          break;
1462        case EventType.DOUBLE_BUTTON_PRESS :
1463          node.name.set_cursor_at_word( scaled_x, scaled_y, shift );
1464          _im_context.reset();
1465          break;
1466        case EventType.TRIPLE_BUTTON_PRESS :
1467          node.name.set_cursor_all( false );
1468          _im_context.reset();
1469          break;
1470      }
1471      return( true );
1472
1473    /*
1474     If the user double-clicked a node.  If an image was clicked on, edit the image;
1475     otherwise, set the node's mode to editable.
1476    */
1477    } else if( !control && !shift && (e.type == EventType.DOUBLE_BUTTON_PRESS) ) {
1478      if( node.is_within_image( scaled_x, scaled_y ) ) {
1479        edit_current_image();
1480        return( false );
1481      } else {
1482        set_node_mode( node, NodeMode.EDITABLE );
1483      }
1484      return( true );
1485
1486    /* Otherwise, we need to adjust the selection */
1487    } else {
1488
1489      /* The shift key has a toggling effect */
1490      if( shift ) {
1491        if( control ) {
1492          if( tpress ) {
1493            if( !_selected.remove_nodes_at_level( node ) ) {
1494              _selected.add_nodes_at_level( node );
1495            }
1496          } else if( dpress ) {
1497            if( !_selected.remove_node_tree( node ) ) {
1498              _selected.add_node_tree( node );
1499            }
1500          } else {
1501            if( !_selected.remove_child_nodes( node ) ) {
1502              _selected.add_child_nodes( node );
1503            }
1504          }
1505        } else {
1506          if( !_selected.remove_node( node ) ) {
1507            _selected.add_node( node );
1508          }
1509        }
1510
1511      /*
1512       The Control key + single click will select the current node's children
1513       The Control key + double click will select the current node tree.
1514       The Control key + triple click will select all nodes at the same level.
1515      */
1516      } else if( control ) {
1517        _selected.clear_nodes();
1518        if( tpress ) {
1519          _selected.add_nodes_at_level( node );
1520        } else if( dpress ) {
1521          _selected.add_node_tree( node );
1522        } else {
1523          _selected.add_child_nodes( node );
1524        }
1525
1526      /* Otherwise, just select the current node */
1527      } else {
1528        _selected.set_current_node( node );
1529      }
1530
1531      if( node.parent != null ) {
1532        node.parent.last_selected_child = node;
1533      }
1534      return( true );
1535    }
1536
1537  }
1538
1539  /* Handles a click on the specified sticker */
1540  public bool set_current_sticker_from_position( Sticker sticker, EventButton e ) {
1541
1542    var scaled_x = scale_value( e.x );
1543    var scaled_y = scale_value( e.y );
1544
1545    /* If the sticker is selected, check to see if the cursor is over other parts */
1546    if( sticker.mode == StickerMode.SELECTED ) {
1547      if( sticker.is_within_resizer( scaled_x, scaled_y ) ) {
1548        _resize     = true;
1549        _orig_width = (int)sticker.width;
1550        return( true );
1551      }
1552
1553    /* Otherwise, add the sticker to the selection */
1554    } else {
1555      set_current_sticker( sticker );
1556    }
1557
1558    /* Save the location of the sticker */
1559    _sticker_posx = sticker.posx;
1560    _sticker_posy = sticker.posy;
1561
1562    return( true );
1563
1564  }
1565
1566  /* Handles a click on the specified group */
1567  public bool set_current_group_from_position( NodeGroup group, EventButton e ) {
1568
1569    var shift = (bool)(e.state & ModifierType.SHIFT_MASK);
1570
1571    /* Select the current group */
1572    if( shift ) {
1573      _selected.add_group( group );
1574    } else {
1575      set_current_group( group );
1576    }
1577
1578    return( true );
1579
1580  }
1581
1582  /*
1583   Checks to see if the user has clicked a connection that was not previously
1584   selected.  If this is the case, select the connection.
1585  */
1586  private bool select_connection_if_unselected( double x, double y ) {
1587    var conn = _connections.within_title( x, y );
1588    if( conn == null ) {
1589      conn = _connections.on_curve( x, y );
1590    }
1591    if( conn != null ) {
1592      if( !_selected.is_connection_selected( conn ) ) {
1593        _selected.set_current_connection( conn );
1594        queue_draw();
1595      }
1596      return( true );
1597    }
1598    return( false );
1599  }
1600
1601  /*
1602   Checks to see if the user has clicked a node that was not previously selected.
1603   If this is the case, select the node.
1604  */
1605  private bool select_node_if_unselected( double x, double y ) {
1606    for( int i=0; i<_nodes.length; i++ ) {
1607      var node = _nodes.index( i ).contains( x, y, null );
1608      if( node != null ) {
1609        if( !_selected.is_node_selected( node ) ) {
1610          _selected.set_current_node( node );
1611          queue_draw();
1612        }
1613        return( true );
1614      }
1615    }
1616    return( false );
1617  }
1618
1619  /* Handles a right click to deal with any selection changes. */
1620  private void handle_right_click( double x, double y ) {
1621    if( select_connection_if_unselected( x, y ) ||
1622        select_node_if_unselected( x, y ) ) {
1623      /* Nothing else to do */
1624    }
1625  }
1626
1627  /*
1628   Sets the current node pointer to the node that is within the given coordinates.
1629   Returns true if we sucessfully set current_node to a valid node and made it
1630   selected.
1631  */
1632  private bool set_current_at_position( double x, double y, EventButton e ) {
1633
1634    var current_conn = _selected.current_connection();
1635    var shift        = (bool)(e.state & ModifierType.SHIFT_MASK);
1636
1637    /* If the user clicked on a selected connection endpoint, disconnect that endpoint */
1638    if( (current_conn != null) && (current_conn.mode == ConnMode.SELECTED) ) {
1639      if( current_conn.within_drag_handle( x, y ) ) {
1640        set_connection_mode( current_conn, ConnMode.ADJUSTING );
1641        return( true );
1642      } else if( current_conn.within_from_handle( x, y ) ) {
1643        _last_connection = new Connection.from_connection( this, current_conn );
1644        current_conn.disconnect_from_node( true );
1645        return( true );
1646      } else if( current_conn.within_to_handle( x, y ) ) {
1647        _last_connection = new Connection.from_connection( this, current_conn );
1648        current_conn.disconnect_from_node( false );
1649        return( true );
1650      }
1651    }
1652
1653    if( (_attach_node == null) || (current_conn == null) ||
1654        ((current_conn.mode != ConnMode.CONNECTING) && (current_conn.mode != ConnMode.LINKING)) ) {
1655      Connection? match_conn = current_conn;
1656      if( current_conn == null ) {
1657        if( (match_conn = _connections.within_title( x, y )) == null ) {
1658          match_conn = _connections.on_curve( x, y );
1659        }
1660      } else if( !current_conn.within_drag_handle( x, y ) ) {
1661        if( (match_conn = _connections.within_title( x, y )) == null ) {
1662          match_conn = _connections.on_curve( x, y );
1663        }
1664      }
1665      if( match_conn != null ) {
1666        clear_current_node( false );
1667        clear_current_sticker( false );
1668        clear_current_group( false );
1669        return( set_current_connection_from_position( match_conn, e ) );
1670      } else {
1671        for( int i=0; i<_nodes.length; i++ ) {
1672          var match_node = _nodes.index( i ).contains( x, y, null );
1673          if( match_node != null ) {
1674            clear_current_connection( false );
1675            clear_current_sticker( false );
1676            clear_current_group( false );
1677            return( set_current_node_from_position( match_node, e ) );
1678          }
1679        }
1680        var sticker = _stickers.is_within( x, y );
1681        if( sticker != null ) {
1682          clear_current_node( false );
1683          clear_current_connection( false );
1684          clear_current_group( false );
1685          return( set_current_sticker_from_position( sticker, e ) );
1686        }
1687        var group = groups.node_group_containing( _scaled_x, _scaled_y );
1688        if( group != null ) {
1689          clear_current_node( false );
1690          clear_current_connection( false );
1691          clear_current_sticker( false );
1692          return( set_current_group_from_position( group, e ) );
1693        }
1694        _select_box.x     = x;
1695        _select_box.y     = y;
1696        _select_box.valid = true;
1697        if( !shift ) {
1698          clear_current_node( true );
1699        }
1700        clear_current_connection( true );
1701        clear_current_sticker( true );
1702        clear_current_group( true );
1703        if( _last_node != null ) {
1704          _selected.set_current_node( _last_node );
1705        }
1706      }
1707    }
1708
1709    return( true );
1710
1711  }
1712
1713  /* Returns the supported scale points */
1714  public static double[] get_scale_marks() {
1715    double[] marks = {10, 25, 50, 75, 100, 150, 200, 250, 300, 350, 400};
1716    return( marks );
1717  }
1718
1719  /* Returns a properly scaled version of the given value */
1720  private double scale_value( double val ) {
1721    return( val / sfactor );
1722  }
1723
1724  /*
1725   Sets the scaling factor for the drawing area, causing the center pixel
1726   to remain in the center and forces a redraw.
1727  */
1728  public void set_scaling_factor( double sf ) {
1729    if( sfactor != sf ) {
1730      int    width  = get_allocated_width()  / 2;
1731      int    height = get_allocated_height() / 2;
1732      double diff_x = (width  / sfactor) - (width  / sf);
1733      double diff_y = (height / sfactor) - (height / sf);
1734      move_origin( diff_x, diff_y );
1735      sfactor = sf;
1736      scale_changed( sfactor );
1737    }
1738  }
1739
1740  /* Returns the scaling factor based on the given width and height */
1741  private double get_scaling_factor( double width, double height ) {
1742    double w  = get_allocated_width() / width;
1743    double h  = get_allocated_height() / height;
1744    double sf = (w < h) ? w : h;
1745    return( (sf > 4) ? 4 : sf );
1746  }
1747
1748  /*
1749   Zooms into the image by one scale mark.  Returns true if the zoom was successful;
1750   otherwise, returns false.
1751  */
1752  public bool zoom_in() {
1753    var value = sfactor * 100;
1754    var marks = get_scale_marks();
1755    if( value < marks[0] ) {
1756      value = marks[0];
1757    }
1758    foreach (double mark in marks) {
1759      if( value < mark ) {
1760        animator.add_scale( "zoom in" );
1761        set_scaling_factor( mark / 100 );
1762        animator.animate();
1763        return( true );
1764      }
1765    }
1766    return( false );
1767  }
1768
1769  /*
1770   Zooms out of the image by one scale mark.  Returns true if the zoom was successful;
1771   otherwise, returns false.
1772  */
1773  public bool zoom_out() {
1774    double value = sfactor * 100;
1775    var    marks = get_scale_marks();
1776    double last  = marks[0];
1777    if( value > marks[marks.length-1] ) {
1778      value = marks[marks.length-1];
1779    }
1780    foreach (double mark in marks) {
1781      if( value <= mark ) {
1782        animator.add_scale( "zoom out" );
1783        set_scaling_factor( last / 100 );
1784        animator.animate();
1785        return( true );
1786      }
1787      last = mark;
1788    }
1789    return( false );
1790  }
1791
1792  /*
1793   Positions the given box in the canvas based on the provided
1794   x and y positions (values between 0 and 1).
1795  */
1796  private void position_box( double x, double y, double w, double h, double xpos, double ypos ) {
1797    double ccx = scale_value( get_allocated_width()  * xpos );
1798    double ccy = scale_value( get_allocated_height() * ypos );
1799    double ncx = x + (w * xpos);
1800    double ncy = y + (h * ypos);
1801    move_origin( (ncx - ccx), (ncy - ccy) );
1802  }
1803
1804  /*
1805   Returns the scaling factor required to display the currently selected node.
1806   If no node is currently selected, returns a value of 0.
1807  */
1808  public void zoom_to_selected() {
1809    var current = _selected.current_node();
1810    if( current == null ) return;
1811    animator.add_pan_scale( "zoom to selected" );
1812    var nb = current.tree_bbox;
1813    position_box( nb.x, nb.y, nb.width, nb.height, 0.5, 0.5 );
1814    set_scaling_factor( get_scaling_factor( nb.width, nb.height ) );
1815    animator.animate();
1816  }
1817
1818  /* Figures out the boundaries of the document primarily for the purposes of printing */
1819  public void document_rectangle( out double x, out double y, out double width, out double height ) {
1820
1821    double x1 =  10000000;
1822    double y1 =  10000000;
1823    double x2 = -10000000;
1824    double y2 = -10000000;
1825
1826    /* Calculate the overall size of the map */
1827    for( int i=0; i<_nodes.length; i++ ) {
1828      var nb = _nodes.index( i ).tree_bbox;
1829      x1 = (x1 < nb.x) ? x1 : nb.x;
1830      y1 = (y1 < nb.y) ? y1 : nb.y;
1831      x2 = (x2 < (nb.x + nb.width))  ? (nb.x + nb.width)  : x2;
1832      y2 = (y2 < (nb.y + nb.height)) ? (nb.y + nb.height) : y2;
1833    }
1834
1835    /* Include the connection and sticker extents */
1836    _connections.add_extents( ref x1, ref y1, ref x2, ref y2 );
1837    _stickers.add_extents( ref x1, ref y1, ref x2, ref y2 );
1838
1839    /* Set the outputs */
1840    x      = x1;
1841    y      = y1;
1842    width  = (x2 - x1);
1843    height = (y2 - y1);
1844
1845  }
1846
1847  /* Returns the scaling factor required to display all nodes */
1848  public void zoom_to_fit() {
1849
1850    animator.add_scale( "zoom to fit" );
1851
1852    /* Get the document rectangle */
1853    double x, y, w, h;
1854    document_rectangle( out x, out y, out w, out h );
1855
1856    /* Center the map and scale it to fit */
1857    position_box( x, y, w, h, 0.5, 0.5 );
1858    set_scaling_factor( get_scaling_factor( w, h ) );
1859
1860    /* Animate the scaling */
1861    animator.animate();
1862
1863  }
1864
1865  /* Scale to actual size */
1866  public void zoom_actual() {
1867
1868    /* Start animation */
1869    animator.add_scale( "action_zoom_actual" );
1870
1871    /* Scale to a full scale */
1872    set_scaling_factor( 1.0 );
1873
1874    /* Animate the scaling */
1875    animator.animate();
1876
1877  }
1878
1879  /* Centers the given node within the canvas by adjusting the origin */
1880  public void center_node( Node n ) {
1881    double x, y, w, h;
1882    n.bbox( out x, out y, out w, out h );
1883    animator.add_pan( "center node" );
1884    position_box( x, y, w, h, 0.5, 0.5 );
1885    animator.animate();
1886  }
1887
1888  /* Centers the currently selected node */
1889  public void center_current_node() {
1890    var current = _selected.current_node();
1891    if( current != null ) {
1892      center_node( current );
1893    }
1894  }
1895
1896  /* Brings the given node into view in its entirety including the given amount of padding */
1897  public void see( double width_adjust = 0, double pad = 100.0 ) {
1898
1899    double x, y, w, h;
1900
1901    var current_conn = _selected.current_connection();
1902    var current_node = _selected.current_node();
1903
1904    if( current_conn != null ) {
1905      current_conn.bbox( out x, out y, out w, out h );
1906    } else if( current_node != null ) {
1907      current_node.bbox( out x, out y, out w, out h );
1908    } else {
1909      return;
1910    }
1911
1912    double diff_x = 0;
1913    double diff_y = 0;
1914    double sw     = scale_value( get_allocated_width() + width_adjust );
1915    double sh     = scale_value( get_allocated_height() );
1916    double sf     = get_scaling_factor( (w + (pad * 2)), (h + (pad * 2)) );
1917
1918    if( (x - pad) < 0 ) {
1919      diff_x = (x - pad);
1920    } else if( (x + w) > sw ) {
1921      diff_x = (x + w + pad) - sw;
1922    }
1923
1924    if( (y - pad) < 0 ) {
1925      diff_y = (y - pad);
1926    } else if( (y + h) > sh ) {
1927      diff_y = (y + h + pad) - sh;
1928    }
1929
1930    if( (diff_x != 0) || (diff_y != 0) ) {
1931      if( sf >= sfactor ) {
1932        animator.add_pan( "see" );
1933        move_origin( diff_x, diff_y );
1934      } else {
1935        animator.add_pan_scale( "see" );
1936        sfactor = sf;
1937        scale_changed( sfactor );
1938        move_origin( diff_x, diff_y );
1939      }
1940      animator.animate();
1941    }
1942
1943  }
1944
1945  /* Returns the attachable node if one is found */
1946  private Node? attachable_node( double x, double y ) {
1947    var current = _selected.current_node();
1948    for( int i=0; i<_nodes.length; i++ ) {
1949      Node tmp = _nodes.index( i ).contains( x, y, current );
1950      if( (tmp != null) && (tmp != current.parent) && !current.contains_node( tmp ) ) {
1951        return( tmp );
1952      }
1953    }
1954    return( null );
1955  }
1956
1957  /* Returns the droppable node or connection if one is found */
1958  private void get_droppable( double x, double y, out Node? node, out Connection? conn, out Sticker? sticker ) {
1959    node = null;
1960    conn = null;
1961    for( int i=0; i<_nodes.length; i++ ) {
1962      Node tmp = _nodes.index( i ).contains( x, y, null );
1963      if( tmp != null ) {
1964        node = tmp;
1965        return;
1966      }
1967    }
1968    conn = _connections.within_title_box( x, y );
1969    if( conn == null ) {
1970      conn = _connections.on_curve( x, y );
1971    }
1972    sticker = _stickers.is_within( x, y );
1973  }
1974
1975  /* Returns the origin */
1976  public void get_origin( out double x, out double y ) {
1977    x = origin_x;
1978    y = origin_y;
1979  }
1980
1981  /* Sets the origin to the given x and y coordinates */
1982  public void set_origin( double x, double y ) {
1983    move_origin( (x - origin_x), (y - origin_y) );
1984  }
1985
1986  /* Checks to see if the boundary of the map never goes out of view */
1987  private bool out_of_bounds( double diff_x, double diff_y ) {
1988
1989    double x, y, w, h;
1990    double aw = scale_value( get_allocated_width() );
1991    double ah = scale_value( get_allocated_height() );
1992    double s  = 40;
1993
1994    document_rectangle( out x, out y, out w, out h );
1995
1996    x -= diff_x;
1997    y -= diff_y;
1998
1999    return( ((x + w) < s) || ((y + h) < s) || ((aw - x) < s) || ((ah - y) < s) );
2000
2001  }
2002
2003  /*
2004   Adjusts the x and y origins, panning all elements by the given amount.
2005   Important Note:  When the canvas is panned to the left (causing all
2006   nodes to be moved to the left, the origin_x value becomes a positive
2007   number.
2008  */
2009  public void move_origin( double diff_x, double diff_y ) {
2010    if( out_of_bounds( diff_x, diff_y ) ) {
2011      return;
2012    }
2013    origin_x += diff_x;
2014    origin_y += diff_y;
2015    for( int i=0; i<_nodes.length; i++ ) {
2016      _nodes.index( i ).pan( -diff_x, -diff_y );
2017    }
2018    _stickers.pan( -diff_x, -diff_y );
2019  }
2020
2021  /* Draw the background from the stylesheet */
2022  public void draw_background( Context ctx ) {
2023    get_style_context().render_background( ctx, 0, 0, (get_allocated_width() / _scale_factor), (get_allocated_height() / _scale_factor) );
2024  }
2025
2026  /* Draws the selection box, if one is set */
2027  public void draw_select_box( Context ctx ) {
2028    if( !_select_box.valid ) return;
2029    Utils.set_context_color_with_alpha( ctx, _theme.get_color( "nodesel_background" ), 0.1 );
2030    ctx.rectangle( _select_box.x, _select_box.y, _select_box.w, _select_box.h );
2031    ctx.fill();
2032  }
2033
2034  /* Draws all of the root node trees */
2035  public void draw_all( Context ctx ) {
2036
2037    /* Draw the links first */
2038    for( int i=0; i<_nodes.length; i++ ) {
2039      _nodes.index( i ).draw_links( ctx, _theme );
2040    }
2041
2042    /* Draw groups next */
2043    _groups.draw_all( ctx, _theme );
2044
2045    var current_node = _selected.current_node();
2046    var current_conn = _selected.current_connection();
2047    for( int i=0; i<_nodes.length; i++ ) {
2048      _nodes.index( i ).draw_all( ctx, _theme, current_node, false );
2049    }
2050
2051    /* Draw the current node on top of all others */
2052    if( (current_node != null) && (current_node.folded_ancestor() == null) ) {
2053      current_node.draw_all( ctx, _theme, null, (!is_node_editable() && _pressed && _motion && !_resize) );
2054    }
2055
2056    /* Draw the current connection on top of everything else */
2057    _connections.draw_all( ctx, _theme );
2058    if( current_conn != null ) {
2059      current_conn.draw( ctx, _theme );
2060    }
2061
2062    /* Draw the floating stickers */
2063    _stickers.draw_all( ctx, _theme, 1.0 /*TBD*/ );
2064
2065    /* Draw the select box if one exists */
2066    draw_select_box( ctx );
2067
2068  }
2069
2070  /* Draw the available nodes */
2071  public bool on_draw( Context ctx ) {
2072    ctx.scale( sfactor, sfactor );
2073    draw_background( ctx );
2074    draw_all( ctx );
2075    return( false );
2076  }
2077
2078  /* Displays the contextual menu based on what is currently selected */
2079  private void show_contextual_menu( Event event ) {
2080
2081    var current_node = _selected.current_node();
2082    var current_conn = _selected.current_connection();
2083
2084    if( current_node != null ) {
2085      if( current_node.mode == NodeMode.EDITABLE ) {
2086        Utils.popup_menu( _text_menu, event );
2087      } else {
2088        Utils.popup_menu( _node_menu, event );
2089      }
2090    } else if( _selected.num_nodes() > 1 ) {
2091      Utils.popup_menu( _nodes_menu, event );
2092    } else if( current_conn != null ) {
2093      Utils.popup_menu( _conn_menu, event );
2094    } else if( _selected.num_connections() > 1 ) {
2095      Utils.popup_menu( _conns_menu, event );
2096    } else if( _selected.num_groups() > 0 ) {
2097      Utils.popup_menu( _groups_menu, event );
2098    } else {
2099      Utils.popup_menu( _empty_menu, event );
2100    }
2101
2102  }
2103
2104  /* Handle button press event */
2105  private bool on_press( EventButton event ) {
2106    var scaled_x = scale_value( event.x );
2107    var scaled_y = scale_value( event.y );
2108    switch( event.button ) {
2109      case Gdk.BUTTON_PRIMARY :
2110      case Gdk.BUTTON_MIDDLE  :
2111        grab_focus();
2112        _press_x      = scaled_x;
2113        _press_y      = scaled_y;
2114        _pressed      = set_current_at_position( _press_x, _press_y, event );
2115        _press_type   = event.type;
2116        _press_middle = event.button == Gdk.BUTTON_MIDDLE;
2117        _motion       = false;
2118        queue_draw();
2119        break;
2120      case Gdk.BUTTON_SECONDARY :
2121        handle_right_click( scaled_x, scaled_y );
2122        show_contextual_menu( event );
2123        break;
2124    }
2125    return( false );
2126  }
2127
2128  /* Selects all nodes within the selected box */
2129  private void select_nodes_within_box( bool shift ) {
2130    Gdk.Rectangle box = {
2131      (int)((_select_box.w < 0) ? (_select_box.x + _select_box.w) : _select_box.x),
2132      (int)((_select_box.h < 0) ? (_select_box.y + _select_box.h) : _select_box.y),
2133      (int)((_select_box.w < 0) ? (0 - _select_box.w) : _select_box.w),
2134      (int)((_select_box.h < 0) ? (0 - _select_box.h) : _select_box.h)
2135    };
2136    if( !shift ) {
2137      _selected.clear_nodes();
2138    }
2139    for( int i=0; i<_nodes.length; i++ ) {
2140      _nodes.index( i ).select_within_box( box, _selected );
2141    }
2142  }
2143
2144  /* Updates the last_match */
2145  private void update_last_match( Node? match ) {
2146    if( match != _last_match ) {
2147      if( _last_match != null ) {
2148        _last_match.show_fold = false;
2149        queue_draw();
2150      }
2151      _last_match = match;
2152    }
2153  }
2154
2155  /* Handle mouse motion */
2156  private bool on_motion( EventMotion event ) {
2157
2158    /* Clear the hover */
2159    if( _select_hover_id > 0 ) {
2160      Source.remove( _select_hover_id );
2161      _select_hover_id = 0;
2162    }
2163
2164    var control = (bool)(event.state & ModifierType.CONTROL_MASK);
2165    var shift   = (bool)(event.state & ModifierType.SHIFT_MASK);
2166    var alt     = (bool)(event.state & ModifierType.MOD1_MASK);
2167
2168    /* If the node is attached, clear it */
2169    if( _attach_node != null ) {
2170      set_node_mode( _attach_node, NodeMode.NONE );
2171      _attach_node = null;
2172      queue_draw();
2173    }
2174
2175    var last_x = _scaled_x;
2176    var last_y = _scaled_y;
2177    _scaled_x = scale_value( event.x );
2178    _scaled_y = scale_value( event.y );
2179
2180    var current_node    = _selected.current_node();
2181    var current_conn    = _selected.current_connection();
2182    var current_sticker = _selected.current_sticker();
2183
2184    /* If the mouse button is current pressed, handle it */
2185    if( _pressed ) {
2186
2187      /* If we are dealing with a connection, update it based on its mode */
2188      if( current_conn != null ) {
2189        switch( current_conn.mode ) {
2190          case ConnMode.ADJUSTING :
2191            current_conn.move_drag_handle( _scaled_x, _scaled_y );
2192            queue_draw();
2193            break;
2194          case ConnMode.CONNECTING :
2195          case ConnMode.LINKING    :
2196            update_connection( event.x, event.y );
2197            for( int i=0; i<_nodes.length; i++ ) {
2198              Node? match = _nodes.index( i ).contains( _scaled_x, _scaled_y, null );
2199              if( match != null ) {
2200                _attach_node = match;
2201                set_node_mode( _attach_node, NodeMode.ATTACHABLE );
2202                break;
2203              }
2204            }
2205            break;
2206        }
2207
2208      /* If we are dealing with a node, handle it based on its mode */
2209      } else if( (current_node != null) && !_select_box.valid ) {
2210        double diffx = _scaled_x - _press_x;
2211        double diffy = _scaled_y - _press_y;
2212        if( current_node.mode == NodeMode.CURRENT ) {
2213          if( _resize ) {
2214            current_node.resize( diffx );
2215            auto_save();
2216          } else {
2217            Node attach_node = attachable_node( _scaled_x, _scaled_y );
2218            if( attach_node != null ) {
2219              set_node_mode( attach_node, NodeMode.ATTACHABLE );
2220              _attach_node = attach_node;
2221            }
2222            current_node.posx += diffx;
2223            current_node.posy += diffy;
2224            current_node.layout.set_side( current_node );
2225          }
2226        } else {
2227          switch( _press_type ) {
2228            case EventType.BUTTON_PRESS        :  current_node.name.set_cursor_at_char( _scaled_x, _scaled_y, true );  break;
2229            case EventType.DOUBLE_BUTTON_PRESS :  current_node.name.set_cursor_at_word( _scaled_x, _scaled_y, true );  break;
2230          }
2231        }
2232        queue_draw();
2233
2234      /* If we are dealing with a sticker, handle it */
2235      } else if( current_sticker != null ) {
2236        double diffx = _scaled_x - _press_x;
2237        double diffy = _scaled_y - _press_y;
2238        if( _resize ) {
2239          current_sticker.resize( diffx );
2240        } else {
2241          current_sticker.posx += diffx;
2242          current_sticker.posy += diffy;
2243        }
2244        queue_draw();
2245        auto_save();
2246
2247      /* If we are holding the middle mouse button while moving, pan the canvas */
2248      } else if( _press_middle ) {
2249        double diff_x = last_x - _scaled_x;
2250        double diff_y = last_y - _scaled_y;
2251        move_origin( diff_x, diff_y );
2252        queue_draw();
2253        auto_save();
2254
2255      /* Otherwise, we are drawing a selection rectangle */
2256      } else {
2257        _select_box.w = (_scaled_x - _select_box.x);
2258        _select_box.h = (_scaled_y - _select_box.y);
2259        select_nodes_within_box( shift );
2260        queue_draw();
2261      }
2262
2263      if( !_motion && !_resize && (current_node != null) && (current_node.mode != NodeMode.EDITABLE) && current_node.is_within( _scaled_x, _scaled_y ) ) {
2264        current_node.alpha = 0.3;
2265      }
2266      _press_x = _scaled_x;
2267      _press_y = _scaled_y;
2268      _motion  = true;
2269
2270    /* If the Alt key is held down, we are panning the canvas */
2271    } else if( alt ) {
2272
2273      double diff_x = last_x - _scaled_x;
2274      double diff_y = last_y - _scaled_y;
2275      move_origin( diff_x, diff_y );
2276      queue_draw();
2277      auto_save();
2278
2279    } else {
2280
2281      var tag = FormatTag.LENGTH;
2282      var url = "";
2283      if( current_sticker != null ) {
2284        if( current_sticker.is_within_resizer( _scaled_x, _scaled_y ) ) {
2285          set_cursor( CursorType.SB_H_DOUBLE_ARROW );
2286          return( false );
2287        }
2288      }
2289      if( current_conn != null )  {
2290        if( (current_conn.mode == ConnMode.CONNECTING) || (current_conn.mode == ConnMode.LINKING) ) {
2291          update_connection( event.x, event.y );
2292        }
2293        if( current_conn.within_drag_handle( _scaled_x, _scaled_y ) ||
2294            current_conn.within_from_handle( _scaled_x, _scaled_y ) ||
2295            current_conn.within_to_handle( _scaled_x, _scaled_y ) ) {
2296          set_cursor_from_name( "move" );
2297          return( false );
2298        } else if( current_conn.within_note( _scaled_x, _scaled_y ) ) {
2299          set_tooltip_markup( prepare_note_markup( current_conn.note ) );
2300          return( false );
2301        } else {
2302          Connection? match_conn = _connections.on_curve( _scaled_x, _scaled_y );
2303          if( (match_conn != null) && select_connection_on_hover( match_conn, shift ) ) {
2304            return( false );
2305          }
2306        }
2307      } else {
2308        Connection? match_conn = _connections.on_curve( _scaled_x, _scaled_y );
2309        if( match_conn != null ) {
2310          if( match_conn.within_note( _scaled_x, _scaled_y ) ) {
2311            set_tooltip_markup( prepare_note_markup( match_conn.note ) );
2312            return( false );
2313          } else if( select_connection_on_hover( match_conn, shift ) ) {
2314            return( false );
2315          }
2316        }
2317      }
2318      for( int i=0; i<_nodes.length; i++ ) {
2319        Node match = _nodes.index( i ).contains( _scaled_x, _scaled_y, null );
2320        if( match != null ) {
2321          update_last_match( match );
2322          if( (current_conn != null) && ((current_conn.mode == ConnMode.CONNECTING) || (current_conn.mode == ConnMode.LINKING)) ) {
2323            _attach_node = match;
2324            set_node_mode( _attach_node, NodeMode.ATTACHABLE );
2325          } else if( match.is_within_task( _scaled_x, _scaled_y ) ) {
2326            set_cursor( CursorType.HAND2 );
2327            set_tooltip_markup( _( "%0.3g%% complete" ).printf( match.task_completion_percentage() ) );
2328          } else if( match.is_within_note( _scaled_x, _scaled_y ) ) {
2329            set_tooltip_markup( prepare_note_markup( match.note ) );
2330          } else if( match.is_within_fold( _scaled_x, _scaled_y ) ) {
2331            set_cursor( CursorType.HAND2 );
2332            if( match.folded ) {
2333              set_tooltip_markup( prepare_folded_count_markup( match ) );
2334            }
2335          } else if( match.is_within_linked_node( _scaled_x, _scaled_y ) ) {
2336            set_cursor( CursorType.HAND2 );
2337          } else if( match.is_within_resizer( _scaled_x, _scaled_y ) ) {
2338            set_cursor( CursorType.SB_H_DOUBLE_ARROW );
2339            set_tooltip_markup( null );
2340          } else if( control && match.name.is_within_clickable( _scaled_x, _scaled_y, out tag, out url ) ) {
2341            if( tag == FormatTag.URL ) {
2342              set_cursor( url_cursor );
2343              set_tooltip_markup( url );
2344            }
2345          } else if( match.mode == NodeMode.EDITABLE ) {
2346            set_cursor( text_cursor );
2347            set_tooltip_markup( null );
2348          } else {
2349            if( !match.folded ) {
2350              match.show_fold = true;
2351              queue_draw();
2352            }
2353            set_cursor( null );
2354            set_tooltip_markup( null );
2355            select_node_on_hover( match, shift );
2356          }
2357          return( false );
2358        }
2359      }
2360
2361
2362      update_last_match( null );
2363      set_cursor( null );
2364      set_tooltip_markup( null );
2365      select_sticker_group_on_hover( shift );
2366
2367    }
2368
2369    return( false );
2370
2371  }
2372
2373  /* Selects the given node on hover, if enabled */
2374  private bool select_node_on_hover( Node node, bool shift ) {
2375    if( _settings.get_boolean( "select-on-hover" ) ) {
2376      var timeout = _settings.get_int( "select-on-hover-timeout" );
2377      _select_hover_id = Timeout.add( timeout, () => {
2378        _select_hover_id = 0;
2379        if( !shift || (_selected.num_nodes() == 0) ) {
2380          _selected.set_current_node( node );
2381        } else {
2382          _selected.add_node( node );
2383        }
2384        queue_draw();
2385        return( false );
2386      });
2387      return( true );
2388    }
2389    return( false );
2390  }
2391
2392  /* Selects the given connection on hover, if enabled */
2393  private bool select_connection_on_hover( Connection conn, bool shift ) {
2394    if( _settings.get_boolean( "select-on-hover" ) ) {
2395      var timeout = _settings.get_int( "select-on-hover-timeout" );
2396      _select_hover_id = Timeout.add( timeout, () => {
2397        _select_hover_id = 0;
2398        if( !shift || (_selected.num_connections() == 0) ) {
2399          _selected.set_current_connection( conn );
2400        } else {
2401          _selected.add_connection( conn );
2402        }
2403        queue_draw();
2404        return( false );
2405      });
2406      return( true );
2407    }
2408    return( false );
2409  }
2410
2411  /* Selects the current sticker/group on hover */
2412  private bool select_sticker_group_on_hover( bool shift ) {
2413    if( _settings.get_boolean( "select-on-hover" ) ) {
2414      var timeout = _settings.get_int( "select-on-hover-timeout" );
2415      var sticker = _stickers.is_within( _scaled_x, _scaled_y );
2416      if( sticker != null ) {
2417        _select_hover_id = Timeout.add( timeout, () => {
2418          _select_hover_id = 0;
2419          if( !shift || (_selected.num_stickers() == 0) ) {
2420            _selected.set_current_sticker( sticker );
2421          } else {
2422            _selected.add_sticker( sticker );
2423          }
2424          queue_draw();
2425          return( false );
2426        });
2427        return( true );
2428      }
2429      var group = _groups.node_group_containing( _scaled_x, _scaled_y );
2430      if( group != null ) {
2431        _select_hover_id = Timeout.add( timeout, () => {
2432          _select_hover_id = 0;
2433          if( !shift || (_selected.num_groups() == 0) ) {
2434            _selected.set_current_group( group );
2435          } else {
2436            _selected.add_group( group );
2437          }
2438          queue_draw();
2439          return( false );
2440        });
2441        return( true );
2442      }
2443    }
2444    return( false );
2445  }
2446
2447  /* Prepares the given note string for use in a markup tooltip */
2448  private string prepare_note_markup( string note ) {
2449    return( note.replace( "<", "&lt;" ) );
2450  }
2451
2452  /* Prepare the given folded count for use in a markup tooltip */
2453  private string prepare_folded_count_markup( Node node ) {
2454    var tooltip = "";
2455    tooltip += _( "Children: %u\n" ).printf( node.children().length );
2456    tooltip += _( "Total: %d" ).printf( node.descendant_count() );
2457    return( tooltip );
2458  }
2459
2460  /* Handle button release event */
2461  private bool on_release( EventButton event ) {
2462
2463    var current_node    = _selected.current_node();
2464    var current_conn    = _selected.current_connection();
2465    var current_sticker = _selected.current_sticker();
2466
2467    _pressed = false;
2468
2469    if( _select_box.valid ) {
2470      _select_box = {0, 0, 0, 0, false};
2471      queue_draw();
2472    }
2473
2474    /* Return the cursor to the default cursor */
2475    if( _motion ) {
2476      set_cursor( null );
2477    }
2478
2479    /* If we were resizing a node, end the resize */
2480    if( _resize ) {
2481      _resize = false;
2482      if( current_sticker != null ) {
2483        undo_buffer.add_item( new UndoStickerResize( current_sticker, _orig_width ) );
2484      } else if( current_node != null ) {
2485        undo_buffer.add_item( new UndoNodeResize( current_node, _orig_width ) );
2486      }
2487      return( false );
2488    }
2489
2490    /* If a connection is selected, deal with the possibilities */
2491    if( current_conn != null ) {
2492
2493      /* If the connection end is released on an attachable node, attach the connection to the node */
2494      if( _attach_node != null ) {
2495        if( current_conn.mode == ConnMode.LINKING ) {
2496          end_link( _attach_node );
2497        } else {
2498          end_connection( _attach_node );
2499          if( _last_connection != null ) {
2500            undo_buffer.add_item( new UndoConnectionChange( _( "connection endpoint change" ), _last_connection, current_conn ) );
2501          }
2502        }
2503        _last_connection = null;
2504
2505      /* If we were dragging the connection midpoint, change the connection mode to SELECTED */
2506      } else if( current_conn.mode == ConnMode.ADJUSTING ) {
2507        undo_buffer.add_item( new UndoConnectionChange( _( "connection drag" ), _last_connection, current_conn ) );
2508        _selected.set_current_connection( current_conn );
2509        auto_save();
2510
2511      /* If we were dragging a connection end and failed to attach it to a node, return the connection to where it was prior to the drag */
2512      } else if( _last_connection != null ) {
2513        current_conn.copy( this, _last_connection );
2514        _last_connection = null;
2515      }
2516
2517      queue_draw();
2518
2519    /* If a node is selected, deal with the possibilities */
2520    } else if( current_node != null ) {
2521
2522      if( current_node.mode == NodeMode.CURRENT ) {
2523
2524        /* If we are hovering over an attach node, perform the attachment */
2525        if( _attach_node != null ) {
2526          attach_current_node();
2527
2528        /* If we are not in motion, set the cursor */
2529        } else if( !_motion ) {
2530          current_node.name.set_cursor_all( false );
2531          _orig_text.copy( current_node.name );
2532          current_node.name.move_cursor_to_end();
2533
2534        /* If we are not a root node, move the node into the appropriate position */
2535        } else if( current_node.parent != null ) {
2536          int orig_index = current_node.index();
2537          animator.add_nodes( _nodes, "move to position" );
2538          current_node.parent.move_to_position( current_node, _orig_side, scale_value( event.x ), scale_value( event.y ) );
2539          undo_buffer.add_item( new UndoNodeMove( current_node, _orig_side, orig_index ) );
2540          animator.animate();
2541
2542        /* Otherwise, redraw everything after the move */
2543        } else {
2544          queue_draw();
2545        }
2546
2547      }
2548
2549    /* If a sticker is selected, deal with the possiblities */
2550    } else if( current_sticker != null ) {
2551      if( current_sticker.mode == StickerMode.SELECTED ) {
2552        undo_buffer.add_item( new UndoStickerMove( current_sticker, _sticker_posx, _sticker_posy ) );
2553      }
2554    }
2555
2556    /* If motion is set, clear it and clear the alpha */
2557    if( _motion ) {
2558      if( current_node != null ) {
2559        current_node.alpha = 1.0;
2560      }
2561      _motion = false;
2562    }
2563
2564    return( false );
2565
2566  }
2567
2568  /* Attaches the current node to the attach node */
2569  private void attach_current_node() {
2570
2571    Node? orig_parent = null;
2572    var   orig_index  = -1;
2573    var   current     = _selected.current_node();
2574    var   isroot      = current.is_root();
2575
2576    /* Remove the current node from its current location */
2577    if( isroot ) {
2578      for( int i=0; i<_nodes.length; i++ ) {
2579        if( _nodes.index( i ) == current ) {
2580          _nodes.remove_index( i );
2581          orig_index = i;
2582          break;
2583        }
2584      }
2585    } else {
2586      orig_parent = current.parent;
2587      orig_index  = current.index();
2588      current.detach( _orig_side );
2589    }
2590
2591    /* Attach the node */
2592    current.attach( _attach_node, -1, _theme );
2593    set_node_mode( _attach_node, NodeMode.NONE );
2594    _attach_node = null;
2595
2596    /* Add the attachment information to the undo buffer */
2597    if( isroot ) {
2598      undo_buffer.add_item( new UndoNodeAttach.for_root( current, orig_index, _orig_info ) );
2599    } else {
2600      undo_buffer.add_item( new UndoNodeAttach( current, orig_parent, _orig_side, orig_index, _orig_info ) );
2601    }
2602
2603    queue_draw();
2604    auto_save();
2605    current_changed( this );
2606
2607  }
2608
2609  /* Returns true if we are connecting a connection title */
2610  public bool is_connection_connecting() {
2611    var current = _selected.current_connection();
2612    return( (current != null) && (current.mode == ConnMode.CONNECTING) );
2613  }
2614
2615  /* Returns true if we are editing a connection title */
2616  public bool is_connection_editable() {
2617    var current = _selected.current_connection();
2618    return( (current != null) && (current.mode == ConnMode.EDITABLE) );
2619  }
2620
2621  /* Returns true if the current connection is in the selected state */
2622  public bool is_connection_selected() {
2623    var current = _selected.current_connection();
2624    return( (current != null) && (current.mode == ConnMode.SELECTED) );
2625  }
2626
2627  /* Returns true if we are in node edit mode */
2628  public bool is_node_editable() {
2629    var current = _selected.current_node();
2630    return( (current != null) && (current.mode == NodeMode.EDITABLE) );
2631  }
2632
2633  /* Returns true if we are in node selected mode */
2634  public bool is_node_selected() {
2635    var current = _selected.current_node();
2636    return( (current != null) && (current.mode == NodeMode.CURRENT) );
2637  }
2638
2639  /* Returns true if we are in sticker selected mode */
2640  public bool is_sticker_selected() {
2641    var current = _selected.current_sticker();
2642    return( (current != null) && (current.mode == StickerMode.SELECTED) );
2643  }
2644
2645  /* Returns true if we are in group selected mode */
2646  public bool is_group_selected() {
2647    var current = _selected.current_group();
2648    return( (current != null) && (current.mode == GroupMode.SELECTED) );
2649  }
2650
2651  /* Returns the next node to select after the current node is removed */
2652  private Node? next_node_to_select() {
2653    var current = _selected.current_node();
2654    if( current != null ) {
2655      if( current.is_root() ) {
2656        if( _nodes.length > 1 ) {
2657          for( int i=0; i<_nodes.length; i++ ) {
2658            if( _nodes.index( i ) == current ) {
2659              if( i == 0 ) {
2660                return( _nodes.index( 1 ) );
2661              } else if( (i + 1) == _nodes.length ) {
2662                return( _nodes.index( i - 1 ) );
2663              }
2664              break;
2665            }
2666          }
2667        }
2668      } else {
2669        Node? next = current.parent.next_child( current );
2670        if( next == null ) {
2671          next = current.parent.prev_child( current );
2672          if( next == null ) {
2673            next = current.parent;
2674          }
2675        }
2676        return( next );
2677      }
2678    }
2679    return( null );
2680  }
2681
2682  /* If the specified node is not null, selects the node and makes it the current node */
2683  private bool select_node( Node? n ) {
2684    if( n != null ) {
2685      if( n != _selected.current_node() ) {
2686        var folded = n.folded_ancestor();
2687        if( folded != null ) {
2688          folded.set_fold_only( false );
2689        }
2690        _selected.set_current_node( n, (_focus_mode ? _focus_alpha : 1.0) );
2691        if( n.parent != null ) {
2692          n.parent.last_selected_child = n;
2693        }
2694        see();
2695      }
2696      return( true );
2697    }
2698    return( false );
2699  }
2700
2701  /* Returns true if there is a root that is available for selection */
2702  public bool root_selectable() {
2703    var current_node = _selected.current_node();
2704    var current_conn = _selected.current_connection();
2705    return( (current_conn == null) && ((current_node == null) ? (_nodes.length > 0) : (current_node.get_root() != current_node)) );
2706  }
2707
2708  /*
2709   If there is no current node, selects the first root node; otherwise, selects
2710   the current node's root node.
2711  */
2712  public void select_root_node() {
2713    if( _selected.current_connection() != null ) return;
2714    var current = _selected.current_node();
2715    if( current == null ) {
2716      if( _nodes.length > 0 ) {
2717        if( select_node( _nodes.index( 0 ) ) ) {
2718          queue_draw();
2719        }
2720      }
2721    } else if( select_node( current.get_root() ) ) {
2722      queue_draw();
2723    }
2724  }
2725
2726  /* Returns true if there is a sibling available for selection */
2727  public bool sibling_selectable() {
2728    var current = _selected.current_node();
2729    return( (current != null) && (current.is_root() ? (_nodes.length > 1) : (current.parent.children().length > 1)) );
2730  }
2731
2732  /* Returns the sibling node in the given direction of the current node */
2733  public Node? sibling_node( int dir ) {
2734    var current = _selected.current_node();
2735    if( current != null ) {
2736      if( current.is_root() ) {
2737        for( int i=0; i<_nodes.length; i++ ) {
2738          if( _nodes.index( i ) == current ) {
2739            return( (((i + dir) < 0) || ((i + dir) >= _nodes.length)) ? null : _nodes.index( i + dir ) );
2740          }
2741        }
2742      } else if( dir == 1 ) {
2743        return( current.parent.next_child( current ) );
2744      } else {
2745        return( current.parent.prev_child( current ) );
2746      }
2747    }
2748    return( null );
2749  }
2750
2751  /* Selects the next (dir = 1) or previous (dir = -1) sibling */
2752  public void select_sibling_node( int dir ) {
2753    var current = _selected.current_node();
2754    if( current != null ) {
2755      Array<Node> nodes;
2756      int         index = 0;
2757      if( current.is_root() ) {
2758        nodes = _nodes;
2759        for( int i=0; i<_nodes.length; i++ ) {
2760          if( _nodes.index( i ) == current ) {
2761            index = i;
2762            break;
2763          }
2764        }
2765      } else {
2766        nodes = current.parent.children();
2767        index = current.index();
2768      }
2769      if( (index + dir) < 0 ) {
2770        if( select_node( nodes.index( nodes.length - 1 ) ) ) {
2771          queue_draw();
2772        }
2773      } else {
2774        if( select_node( nodes.index( (index + dir) % nodes.length ) ) ) {
2775          queue_draw();
2776        }
2777      }
2778    }
2779  }
2780
2781  /* Returns true if there is a child node of the current node */
2782  public bool children_selectable() {
2783    var nodes      = _selected.nodes();
2784    var selectable = false;
2785    for( int i=0; i<nodes.length; i++ ) {
2786      var node = nodes.index( i );
2787      selectable |= (!node.is_leaf() && !node.folded);
2788    }
2789    return( selectable );
2790  }
2791
2792  /* Selects the last selected child node of the current node */
2793  public void select_child_node() {
2794    var current = _selected.current_node();
2795    if( (current != null) && !current.is_leaf() && !current.folded ) {
2796      if( select_node( current.last_selected_child ?? current.children().index( 0 ) ) ) {
2797        queue_draw();
2798      }
2799    }
2800  }
2801
2802  /* Selects all of the child nodes */
2803  public void select_child_nodes() {
2804    var nodes   = _selected.nodes_copy();
2805    var changed = _selected.clear_nodes( false );
2806    for( int i=0; i<nodes.length; i++ ) {
2807      changed |= _selected.add_child_nodes( nodes.index( i ), false );
2808    }
2809    if( changed ) {
2810      current_changed( this );
2811    }
2812    queue_draw();
2813  }
2814
2815  /* Selects all of the nodes in the current node's tree */
2816  public void select_node_tree() {
2817    var current = _selected.current_node();
2818    _selected.add_node_tree( current );
2819    queue_draw();
2820  }
2821
2822  /* Returns true if there is a parent node of the current node */
2823  public bool parent_selectable() {
2824    var nodes      = _selected.nodes();
2825    var selectable = false;
2826    for( int i=0; i<nodes.length; i++ ) {
2827      selectable |= !nodes.index( i ).is_root();
2828    }
2829    return( selectable );
2830  }
2831
2832  /* Selects the parent nodes of the selected nodes */
2833  public void select_parent_nodes() {
2834    var child_nodes  = _selected.nodes();
2835    var parent_nodes = new Array<Node>();
2836    for( int i=0; i<child_nodes.length; i++ ) {
2837      var node = child_nodes.index( i );
2838      if( (node != null) && !node.is_root() ) {
2839        parent_nodes.append_val( node.parent );
2840      }
2841    }
2842    if( parent_nodes.length > 0 ) {
2843      _selected.clear_nodes();
2844      for( int i=0; i<parent_nodes.length; i++ ) {
2845        _selected.add_node( parent_nodes.index( i ) );
2846      }
2847      queue_draw();
2848    }
2849  }
2850
2851  /* Selects the node that is linked to this node */
2852  public void select_linked_node( Node? node = null ) {
2853    var n = node;
2854    if( n == null ) {
2855      n = _selected.current_node();
2856    }
2857    if( (n != null) && (n.linked_node != null) ) {
2858      if( select_node( n.linked_node ) ) {
2859        queue_draw();
2860      }
2861    }
2862  }
2863
2864  /* Selects the given connection node */
2865  public void select_connection_node( bool start ) {
2866    var current = _selected.current_connection();
2867    if( current != null ) {
2868      if( select_node( start ? current.from_node : current.to_node ) ) {
2869        clear_current_connection( true );
2870        queue_draw();
2871      }
2872    }
2873  }
2874
2875  /* Selects the next connection in the list */
2876  public void select_connection( int dir ) {
2877    var current = _selected.current_connection();
2878    if( current == null ) return;
2879    var conn = _connections.get_connection( current, dir );
2880    if( conn != null ) {
2881      set_current_connection( conn );
2882      see();
2883      queue_draw();
2884    }
2885  }
2886
2887  /* Selects the first connection in the list */
2888  public void select_attached_connection() {
2889    var current = _selected.current_node();
2890    if( current == null ) return;
2891    if( current.last_selected_connection != null ) {
2892      set_current_connection( current.last_selected_connection );
2893      see();
2894      queue_draw();
2895    } else {
2896      var conn = _connections.get_attached_connection( current );
2897      if( conn != null ) {
2898        set_current_connection( conn );
2899        see();
2900        queue_draw();
2901      }
2902    }
2903  }
2904
2905  /* Deletes the given node */
2906  public void delete_node() {
2907    var current = _selected.current_node();
2908    if( current == null ) return;
2909    Node? next_node = next_node_to_select();
2910    var   conns     = new Array<Connection>();
2911    UndoNodeGroups? undo_groups = null;
2912    _connections.node_deleted( current, conns );
2913    _groups.remove_node( current, ref undo_groups );
2914    if( current.is_root() ) {
2915      for( int i=0; i<_nodes.length; i++ ) {
2916        if( _nodes.index( i ) == current ) {
2917          undo_buffer.add_item( new UndoNodeDelete( current, i, conns, undo_groups ) );
2918          _nodes.remove_index( i );
2919          break;
2920        }
2921      }
2922    } else {
2923      undo_buffer.add_item( new UndoNodeDelete( current, current.index(), conns, undo_groups ) );
2924      current.delete();
2925    }
2926    _selected.remove_node( current );
2927    select_node( next_node );
2928    queue_draw();
2929    auto_save();
2930  }
2931
2932  /* Deletes all selected nodes */
2933  public void delete_nodes() {
2934    if( _selected.num_nodes() == 0 ) return;
2935    var nodes = _selected.ordered_nodes();
2936    var conns = new Array<Connection>();
2937    Array<UndoNodeGroups?> undo_groups = null;
2938    for( int i=0; i<nodes.length; i++ ) {
2939      _connections.node_only_deleted( nodes.index( i ), conns );
2940    }
2941    _groups.remove_nodes( nodes, out undo_groups );
2942    undo_buffer.add_item( new UndoNodesDelete( nodes, conns, undo_groups ) );
2943    for( int i=0; i<nodes.length; i++ ) {
2944      nodes.index( i ).delete_only();
2945    }
2946    _selected.clear_nodes();
2947    queue_draw();
2948    auto_save();
2949  }
2950
2951  /* Deletes the currently selected sticker */
2952  public void remove_sticker() {
2953    var current = _selected.current_sticker();
2954    if( current == null ) return;
2955    undo_buffer.add_item( new UndoStickerRemove( current ) );
2956    _stickers.remove_sticker( current );
2957    _selected.remove_sticker( current );
2958    queue_draw();
2959    auto_save();
2960  }
2961
2962  /* Called whenever the backspace character is entered in the drawing area */
2963  private void handle_backspace() {
2964    if( is_connection_editable() ) {
2965      _selected.current_connection().title.backspace( undo_text );
2966      queue_draw();
2967      auto_save();
2968    } else if( is_connection_selected() ) {
2969      delete_connection();
2970    } else if( _selected.num_connections() > 0 ) {
2971      delete_connections();
2972    } else if( is_node_editable() ) {
2973      _selected.current_node().name.backspace( undo_text );
2974      queue_draw();
2975      auto_save();
2976    } else if( is_node_selected() ) {
2977      Node? next;
2978      var   current = _selected.current_node();
2979      if( ((next = sibling_node( 1 )) == null) && ((next = sibling_node( -1 )) == null) && current.is_root() ) {
2980        delete_node();
2981      } else {
2982        if( next == null ) {
2983          next = current.parent;
2984        }
2985        delete_node();
2986        if( select_node( next ) ) {
2987          queue_draw();
2988        }
2989      }
2990    } else if( _selected.num_nodes() > 0 ) {
2991      delete_nodes();
2992    } else if( is_sticker_selected() ) {
2993      remove_sticker();
2994    } else if( _selected.num_groups() > 0 ) {
2995      remove_groups();
2996    }
2997  }
2998
2999  /* Called whenever the delete character is entered in the drawing area */
3000  private void handle_delete() {
3001    if( is_connection_editable() ) {
3002      _selected.current_connection().title.delete( undo_text );
3003      queue_draw();
3004      auto_save();
3005    } else if( is_connection_selected() ) {
3006      delete_connection();
3007    } else if( _selected.num_connections() > 0 ) {
3008      delete_connections();
3009    } else if( is_node_editable() ) {
3010      _selected.current_node().name.delete( undo_text );
3011      queue_draw();
3012      auto_save();
3013    } else if( is_node_selected() ) {
3014      delete_node();
3015    } else if( _selected.num_nodes() > 0 ) {
3016      delete_nodes();
3017    } else if( is_sticker_selected() ) {
3018      remove_sticker();
3019    } else if( _selected.num_groups() > 0 ) {
3020      remove_groups();
3021    }
3022  }
3023
3024  /* Called whenever the escape character is entered in the drawing area */
3025  private void handle_escape() {
3026    if( is_connection_editable() ) {
3027      var current = _selected.current_connection();
3028      _im_context.reset();
3029      current.edit_title_end();
3030      set_connection_mode( current, ConnMode.SELECTED );
3031      current_changed( this );
3032      queue_draw();
3033      auto_save();
3034    } else if( is_node_editable() ) {
3035      var current = _selected.current_node();
3036      _im_context.reset();
3037      set_node_mode( current, NodeMode.CURRENT );
3038      current_changed( this );
3039      queue_draw();
3040      auto_save();
3041    } else if( is_connection_connecting() ) {
3042      var current = _selected.current_connection();
3043      _connections.remove_connection( current, true );
3044      _selected.remove_connection( current );
3045      if( _attach_node != null ) {
3046        set_node_mode( _attach_node, NodeMode.NONE );
3047        _attach_node = null;
3048      }
3049      _selected.set_current_node( _last_node );
3050      _last_connection = null;
3051      queue_draw();
3052    } else if( is_node_selected() ) {
3053      hide_properties();
3054    }
3055  }
3056
3057  /* Positions the given node that will added as a root prior to adding it */
3058  public void position_root_node( Node node ) {
3059    if( _nodes.length == 0 ) {
3060      node.posx = (get_allocated_width()  / 2) - 30;
3061      node.posy = (get_allocated_height() / 2) - 10;
3062    } else {
3063      _nodes.index( _nodes.length - 1 ).layout.position_root( _nodes.index( _nodes.length - 1 ), node );
3064    }
3065  }
3066
3067  /*
3068   Creates a root node with the given name, positions it and appends it to the
3069   root node list.
3070  */
3071  public Node create_root_node( string name = "" ) {
3072    var node = new Node.with_name( this, name, ((_nodes.length == 0) ? layouts.get_default() : _nodes.index( 0 ).layout) );
3073    node.style = StyleInspector.styles.get_global_style();
3074    position_root_node( node );
3075    _nodes.append_val( node );
3076    return( node );
3077  }
3078
3079  /*
3080   Creates a sibling node, positions it and appends immediately after the given
3081   sibling node.
3082  */
3083  public Node create_main_node( Node root, NodeSide side, string name = "" ) {
3084    var node   = new Node.with_name( this, name, layouts.get_default() );
3085    node.side  = side;
3086    node.style = StyleInspector.styles.get_style_for_level( 1, null );
3087    if( root.layout.balanceable && ((side == NodeSide.LEFT) || (side == NodeSide.TOP)) ) {
3088      node.attach( root, root.side_count( side ), _theme, false );
3089    } else {
3090      node.attach( root, -1, _theme, false );
3091    }
3092    return( node );
3093  }
3094
3095  /*
3096   Creates a sibling node, positions it and appends immediately after the given
3097   sibling node.
3098  */
3099  public Node create_sibling_node( Node sibling, string name = "" ) {
3100    var node   = new Node.with_name( this, name, layouts.get_default() );
3101    node.side  = sibling.side;
3102    node.style = StyleInspector.styles.get_style_for_level( sibling.get_level(), sibling.style );
3103    node.attach( sibling.parent, (sibling.index() + 1), _theme );
3104    return( node );
3105  }
3106
3107  /*
3108   Creates a parent node, positions it, and inserts it just above the child node.
3109  */
3110  public Node create_parent_node( Node child, string name = "" ) {
3111    var node  = new Node.with_name( this, name, layouts.get_default() );
3112    var color = child.link_color;
3113    node.side  = child.side;
3114    node.style = StyleInspector.styles.get_style_for_level( child.get_level(), child.style );
3115    node.attach( child.parent, child.index(), null );
3116    node.link_color = color;
3117    child.detach( node.side );
3118    child.attach( node, -1, null );
3119    return( node );
3120  }
3121
3122  /*
3123   Creates a child node, positions it, and inserts it into the parent node.
3124  */
3125  public Node create_child_node( Node parent, string name = "" ) {
3126    var node = new Node.with_name( this, name, layouts.get_default() );
3127    if( !parent.is_root() ) {
3128      node.side = parent.side;
3129    }
3130    if( parent.children().length > 0 ) {
3131      node.style = parent.last_child().style;
3132    } else {
3133      node.style = parent.style;
3134    }
3135    node.style = StyleInspector.styles.get_style_for_level( (parent.get_level() + 1), parent.style );
3136    node.attach( parent, -1, _theme );
3137    parent.set_fold( false );
3138    return( node );
3139  }
3140
3141  /* Adds a new root node to the canvas */
3142  public void add_root_node() {
3143    var node         = create_root_node( _( "Another Idea" ) );
3144    var int_node_len = (int)(_nodes.length - 1);
3145    undo_buffer.add_item( new UndoNodeInsert( node, int_node_len ) );
3146    if( select_node( node ) ) {
3147      set_node_mode( node, NodeMode.EDITABLE, false );
3148      queue_draw();
3149    }
3150    see();
3151    auto_save();
3152  }
3153
3154  /* Adds a connected node to the currently selected node */
3155  public void add_connected_node() {
3156    var index = (int)_nodes.length;
3157    var node  = create_root_node( _( "Another Idea" ) );
3158    var conn  = new Connection( this, _selected.current_node() );
3159    conn.connect_to( _selected.current_node() );
3160    conn.connect_to( node );
3161    _connections.add_connection( conn );
3162    undo_buffer.add_item( new UndoConnectedNode( node, index, conn ) );
3163    if( select_node( node ) ) {
3164      set_node_mode( node, NodeMode.EDITABLE, false );
3165      queue_draw();
3166    }
3167    see();
3168    auto_save();
3169  }
3170
3171  /* Adds a new sibling node to the current node */
3172  public void add_sibling_node() {
3173    var node = create_sibling_node( _selected.current_node() );
3174    undo_buffer.add_item( new UndoNodeInsert( node, node.index() ) );
3175    set_current_node( node );
3176    set_node_mode( node, NodeMode.EDITABLE, false );
3177    queue_draw();
3178    see();
3179    auto_save();
3180  }
3181
3182  /*
3183   Re-parents a node by creating a new node whose parent matches the current node's parent
3184   and then makes the current node's parent match the new node.
3185  */
3186  public void add_parent_node() {
3187    var current = _selected.current_node();
3188    if( current.is_root() ) return;
3189    var node  = create_parent_node( current );
3190    undo_buffer.add_item( new UndoNodeAddParent( node, current ) );
3191    set_current_node( node );
3192    set_node_mode( node, NodeMode.EDITABLE, false );
3193    queue_draw();
3194    see();
3195    auto_save();
3196  }
3197
3198  /* Adds a child node to the current node */
3199  public void add_child_node() {
3200    var current = _selected.current_node();
3201    var node    = create_child_node( current );
3202    undo_buffer.add_item( new UndoNodeInsert( node, node.index() ) );
3203    set_current_node( node );
3204    set_node_mode( node, NodeMode.EDITABLE, false );
3205    queue_draw();
3206    see();
3207    auto_save();
3208  }
3209
3210  /*
3211   Replaces the original node with the new node.  The new_node must not
3212   have any children.  Returns true if the replacement was successful; otherwise,
3213   returns false.
3214  */
3215  public void replace_node( Node orig_node, Node new_node ) {
3216
3217    var parent = orig_node.parent;
3218    var index  = (parent == null) ? root_index( orig_node ) : orig_node.index();
3219
3220    /* Perform the replacement */
3221    if( parent == null ) {
3222      remove_root_node( orig_node );
3223      add_root( new_node, index );
3224      new_node.posx = orig_node.posx;
3225      new_node.posy = orig_node.posy;
3226    } else {
3227      orig_node.detach( orig_node.side );
3228      new_node.attach( parent, index, null );
3229    }
3230
3231    /* Copy over a few attributes */
3232    if( new_node.main_branch() ) {
3233      new_node.link_color = orig_node.link_color;
3234    }
3235
3236  }
3237
3238  /* Called whenever the return character is entered in the drawing area */
3239  private void handle_return( bool shift ) {
3240    if( is_connection_editable() ) {
3241      var current = _selected.current_connection();
3242      current.edit_title_end();
3243      set_connection_mode( current, ConnMode.SELECTED );
3244      current_changed( this );
3245      queue_draw();
3246    } else if( is_node_editable() ) {
3247      var current = _selected.current_node();
3248      set_node_mode( current, NodeMode.CURRENT );
3249      if( _create_new_from_edit ) {
3250        if( !current.is_root() ) {
3251          add_sibling_node();
3252        } else {
3253          add_root_node();
3254        }
3255      } else {
3256        current_changed( this );
3257        queue_draw();
3258      }
3259    } else if( is_connection_connecting() && (_attach_node != null) ) {
3260      end_connection( _attach_node );
3261    } else if( is_node_selected() ) {
3262      if( !_selected.current_node().is_root() ) {
3263        add_sibling_node();
3264      } else if( shift ) {
3265        add_connected_node();
3266      } else {
3267        add_root_node();
3268      }
3269    } else if( _selected.num_nodes() == 0 ) {
3270      add_root_node();
3271    }
3272  }
3273
3274  /* Called whenever the user hits a Control-Return key.  Causes a newline to be inserted */
3275  private void handle_control_return() {
3276    if( is_connection_editable() ) {
3277      _selected.current_connection().title.insert( "\n", undo_text );
3278      current_changed( this );
3279      queue_draw();
3280    } else if( is_node_editable() ) {
3281      _selected.current_node().name.insert( "\n", undo_text );
3282      see();
3283      current_changed( this );
3284      queue_draw();
3285    }
3286  }
3287
3288  /* Returns the index of the given root node */
3289  public int root_index( Node root ) {
3290    for( int i=0; i<_nodes.length; i++ ) {
3291      if( _nodes.index( i ) == root ) {
3292        return( i );
3293      }
3294    }
3295    return( -1 );
3296  }
3297
3298  /* Adds the given node to the list of root nodes */
3299  public void add_root( Node n, int index ) {
3300    if( index == -1 ) {
3301      _nodes.append_val( n );
3302    } else {
3303      _nodes.insert_val( index, n );
3304    }
3305  }
3306
3307  /* Removes the node at the given root index from the list of root nodes */
3308  public void remove_root( int index ) {
3309    _nodes.remove_index( index );
3310  }
3311
3312  /* Removes the given root node from the node array */
3313  public void remove_root_node( Node node ) {
3314    for( int i=0; i<_nodes.length; i++ ) {
3315      if( _nodes.index( i ) == node ) {
3316        _nodes.remove_index( i );
3317      }
3318    }
3319  }
3320
3321  /* Returns true if the drawing area has a node that is available for detaching */
3322  public bool detachable() {
3323    var current = _selected.current_node();
3324    return( (current != null) && (current.parent != null) );
3325  }
3326
3327  /* Detaches the current node from its parent and adds it as a root node */
3328  public void detach() {
3329    if( !detachable() ) return;
3330    var current    = _selected.current_node();
3331    var parent     = current.parent;
3332    var index      = current.index();
3333    var side       = current.side;
3334    var root_index = (int)_nodes.length;
3335    current.detach( side );
3336    add_root( current, -1 );
3337    undo_buffer.add_item( new UndoNodeDetach( current, root_index, parent, side, index ) );
3338    queue_draw();
3339    auto_save();
3340  }
3341
3342  /* Balances the existing nodes based on the current layout */
3343  public void balance_nodes( bool undoable, bool animate ) {
3344    var current   = _selected.current_node();
3345    var root_node = (current == null) ? null : current.get_root();
3346    if( undoable ) {
3347      undo_buffer.add_item( new UndoNodeBalance( this, root_node ) );
3348    }
3349    if( (current == null) || !undoable ) {
3350      if( animate ) {
3351        animator.add_nodes( _nodes, "balance nodes" );
3352      }
3353      for( int i=0; i<_nodes.length; i++ ) {
3354        var partitioner = new Partitioner();
3355        partitioner.partition_node( _nodes.index( i ) );
3356      }
3357    } else {
3358      if( animate ) {
3359        animator.add_node( root_node, "balance tree" );
3360      }
3361      var partitioner = new Partitioner();
3362      partitioner.partition_node( root_node );
3363    }
3364    if( animate ) {
3365      animator.animate();
3366    }
3367    grab_focus();
3368  }
3369
3370  /* Returns true if there is at least one node that can be folded due to completed tasks */
3371  public bool completed_tasks_foldable() {
3372    var current = _selected.current_node();
3373    if( current != null ) {
3374      return( current.get_root().completed_tasks_foldable() );
3375    } else {
3376      for( int i=0; i<_nodes.length; i++ ) {
3377        if( _nodes.index( i ).completed_tasks_foldable() ) {
3378          return( true );
3379        }
3380      }
3381    }
3382    return( false );
3383  }
3384
3385  /* Folds all completed tasks found in any tree */
3386  public void fold_completed_tasks() {
3387    var changes = new Array<Node>();
3388    var current = _selected.current_node();
3389    if( current == null ) {
3390      for( int i=0; i<_nodes.length; i++ ) {
3391        _nodes.index( i ).fold_completed_tasks( changes );
3392      }
3393    } else {
3394      current.get_root().fold_completed_tasks( changes );
3395    }
3396    if( changes.length > 0 ) {
3397      undo_buffer.add_item( new UndoNodeFolds( changes ) );
3398      queue_draw();
3399      auto_save();
3400      current_changed( this );
3401    }
3402  }
3403
3404  /* Returns true if there is at least one node that is unfoldable */
3405  public bool unfoldable() {
3406    var current = _selected.current_node();
3407    if( current != null ) {
3408      return( current.get_root().unfoldable() );
3409    } else {
3410      for( int i=0; i<_nodes.length; i++ ) {
3411        if( _nodes.index( i ).unfoldable() ) {
3412          return( true );
3413        }
3414      }
3415    }
3416    return( false );
3417  }
3418
3419  /* Unfolds all nodes in the document */
3420  public void unfold_all_nodes() {
3421    var changes = new Array<Node>();
3422    var current = _selected.current_node();
3423    if( current != null ) {
3424      current.get_root().set_fold( false, changes );
3425    } else {
3426      for( int i=0; i<_nodes.length; i++ ) {
3427        _nodes.index( i ).set_fold( false, changes );
3428      }
3429    }
3430    if( changes.length > 0 ) {
3431      undo_buffer.add_item( new UndoNodeFolds( changes ) );
3432      queue_draw();
3433      auto_save();
3434      current_changed( this );
3435    }
3436  }
3437
3438
3439  /* Called whenever the tab character is entered in the drawing area */
3440  private void handle_tab() {
3441    if( is_node_editable() ) {
3442      var current = _selected.current_node();
3443      set_node_mode( current, NodeMode.CURRENT );
3444      if( _create_new_from_edit ) {
3445        add_child_node();
3446      } else {
3447        current_changed( this );
3448        queue_draw();
3449      }
3450    } else if( is_node_selected() ) {
3451      add_child_node();
3452    }
3453  }
3454
3455  /*
3456   Called whenever the Control-Tab key combo is entered.  Causes a tab character
3457   to be inserted into the title.
3458  */
3459  private void handle_control_tab() {
3460    if( is_node_editable() ) {
3461      _selected.current_node().name.insert( "\t", undo_text );
3462      see();
3463      current_changed( this );
3464      queue_draw();
3465    }
3466  }
3467
3468  /* Returns the node to the right of the given node */
3469  private Node? get_node_right( Node node ) {
3470    if( node.is_root() ) {
3471      return( node.last_selected_child ?? node.first_child( NodeSide.RIGHT ) );
3472    } else {
3473      switch( node.side ) {
3474        case NodeSide.TOP    :
3475        case NodeSide.BOTTOM :  return( node.parent.next_child( node ) );
3476        case NodeSide.LEFT   :  return( node.parent );
3477        default              :  return( node.last_selected_child ?? node.first_child( NodeSide.RIGHT ) );
3478      }
3479    }
3480  }
3481
3482  /* Returns the node to the left of the given node */
3483  private Node? get_node_left( Node node ) {
3484    if( node.is_root() ) {
3485      return( node.last_selected_child ?? node.first_child( NodeSide.LEFT ) );
3486    } else {
3487      switch( node.side ) {
3488        case NodeSide.TOP :
3489        case NodeSide.BOTTOM :  return( node.parent.prev_child( node ) );
3490        case NodeSide.LEFT   :  return( node.last_selected_child ?? node.first_child( NodeSide.LEFT ) );
3491        default              :  return( node.parent );
3492      }
3493    }
3494  }
3495
3496  /* Returns the node above the given node */
3497  private Node? get_node_up( Node node ) {
3498    if( node.is_root() ) {
3499      for( int i=0; i<_nodes.length; i++ ) {
3500        if( _nodes.index( i ) == node ) {
3501          return( (i > 0) ? _nodes.index( i - 1 ) : null );
3502        }
3503      }
3504      return( null );
3505    } else {
3506      switch( node.side ) {
3507        case NodeSide.TOP    :  return( node.last_selected_child ?? node.first_child( NodeSide.TOP ) );
3508        case NodeSide.BOTTOM :  return( node.parent );
3509        default              :  return( node.parent.prev_child( node ) );
3510      }
3511    }
3512  }
3513
3514  /* Returns the node below the given node */
3515  private Node? get_node_down( Node node ) {
3516    if( node.is_root() ) {
3517      for( int i=0; i<_nodes.length; i++ ) {
3518        if( _nodes.index( i ) == node ) {
3519          return( ((i + 1) < _nodes.length) ? _nodes.index( i + 1 ) : null );
3520        }
3521      }
3522      return( null );
3523    } else {
3524      switch( node.side ) {
3525        case NodeSide.TOP    :  return( node.parent );
3526        case NodeSide.BOTTOM :  return( node.last_selected_child ?? node.first_child( NodeSide.BOTTOM ) );
3527        default              :  return( node.parent.next_child( node ) );
3528      }
3529    }
3530  }
3531
3532  /* Returns the node at the top of the sibling list */
3533  private Node? get_node_pageup( Node node ) {
3534    if( node.is_root() ) {
3535      return( (_nodes.length > 0) ? _nodes.index( 0 ) : null );
3536    } else {
3537      return( node.parent.first_child() );
3538    }
3539  }
3540
3541  /* Returns the node at the top of the sibling list */
3542  private Node? get_node_pagedn( Node node ) {
3543    if( node.is_root() ) {
3544      return( (_nodes.length > 0) ? _nodes.index( _nodes.length - 1 ) : null );
3545    } else {
3546      return( node.parent.last_child() );
3547    }
3548  }
3549
3550  /* Called whenever the right key is entered in the drawing area */
3551  private void handle_right( bool shift, bool alt ) {
3552    if( is_connection_editable() ) {
3553      if( shift ) {
3554        _selected.current_connection().title.selection_by_char( 1 );
3555      } else {
3556        _selected.current_connection().title.move_cursor( 1 );
3557      }
3558      queue_draw();
3559    } else if( is_node_editable() ) {
3560      if( shift ) {
3561        _selected.current_node().name.selection_by_char( 1 );
3562      } else {
3563        _selected.current_node().name.move_cursor( 1 );
3564      }
3565      queue_draw();
3566    } else if( is_connection_connecting() && (_attach_node != null) ) {
3567      update_connection_by_node( get_node_right( _attach_node ) );
3568    } else if( is_connection_selected() ) {
3569      select_connection( 1 );
3570    } else if( is_node_selected() ) {
3571      var current    = _selected.current_node();
3572      var right_node = get_node_right( current );
3573      if( alt ) {
3574        if( current.swap_with_sibling( right_node ) ||
3575            current.make_children_siblings( right_node ) ) {
3576          queue_draw();
3577          auto_save();
3578        }
3579      } else if( select_node( right_node ) ) {
3580        queue_draw();
3581      }
3582    }
3583  }
3584
3585  /*
3586   Called whenever the Control-right key combo is entered.  Moves the cursor
3587   one word to the right.
3588  */
3589  private void handle_control_right( bool shift ) {
3590    if( is_connection_editable() ) {
3591      if( shift ) {
3592        _selected.current_connection().title.selection_by_word( 1 );
3593      } else {
3594        _selected.current_connection().title.move_cursor_by_word( 1 );
3595      }
3596      queue_draw();
3597    } else if( is_node_editable() ) {
3598      if( shift ) {
3599        _selected.current_node().name.selection_by_word( 1 );
3600      } else {
3601        _selected.current_node().name.move_cursor_by_word( 1 );
3602      }
3603      queue_draw();
3604    }
3605  }
3606
3607  /* Called whenever the left key is entered in the drawing area */
3608  private void handle_left( bool shift, bool alt ) {
3609    if( is_connection_editable() ) {
3610      if( shift ) {
3611        _selected.current_connection().title.selection_by_char( -1 );
3612      } else {
3613        _selected.current_connection().title.move_cursor( -1 );
3614      }
3615      queue_draw();
3616    } else if( is_node_editable() ) {
3617      if( shift ) {
3618        _selected.current_node().name.selection_by_char( -1 );
3619      } else {
3620        _selected.current_node().name.move_cursor( -1 );
3621      }
3622      queue_draw();
3623    } else if( is_connection_connecting() && (_attach_node != null) ) {
3624      update_connection_by_node( get_node_left( _attach_node ) );
3625    } else if( is_connection_selected() ) {
3626      select_connection( -1 );
3627    } else if( is_node_selected() ) {
3628      var current   = _selected.current_node();
3629      var left_node = get_node_left( current );
3630      if( alt ) {
3631        if( current.swap_with_sibling( left_node ) ||
3632            current.make_children_siblings( left_node ) ) {
3633          queue_draw();
3634          auto_save();
3635        }
3636      } else if( select_node( left_node ) ) {
3637        queue_draw();
3638      }
3639    }
3640  }
3641
3642  /*
3643   If Control is used, jumps the cursor to the end of the previous word.  If Control-Shift
3644   is used, adds the previous word to the selection.
3645  */
3646  private void handle_control_left( bool shift ) {
3647    if( is_connection_editable() ) {
3648      if( shift ) {
3649        _selected.current_connection().title.selection_by_word( -1 );
3650      } else {
3651        _selected.current_connection().title.move_cursor_by_word( -1 );
3652      }
3653      queue_draw();
3654    } else if( is_node_editable() ) {
3655      if( shift ) {
3656        _selected.current_node().name.selection_by_word( -1 );
3657      } else {
3658        _selected.current_node().name.move_cursor_by_word( -1 );
3659      }
3660      queue_draw();
3661    }
3662  }
3663
3664  /* Selects all of the text in the current node */
3665  private void select_all() {
3666    if( is_connection_editable() ) {
3667      _selected.current_connection().title.set_cursor_all( false );
3668      queue_draw();
3669    } else if( is_node_editable() ) {
3670      _selected.current_node().name.set_cursor_all( false );
3671      queue_draw();
3672    }
3673  }
3674
3675  /* Deselects all of the text in the current node */
3676  private void deselect_all() {
3677    if( is_connection_editable() ) {
3678      _selected.current_connection().title.clear_selection();
3679      queue_draw();
3680    } else if( is_node_editable() ) {
3681      _selected.current_node().name.clear_selection();
3682      queue_draw();
3683    }
3684  }
3685
3686  /* Handles the emoji insertion process for the given text item */
3687  private void insert_emoji( CanvasText text ) {
3688    var overlay = (Overlay)get_parent();
3689    var entry = new Entry();
3690    int x, ytop, ybot;
3691    text.get_cursor_pos( out x, out ytop, out ybot );
3692    entry.margin_start = x;
3693    entry.margin_top   = ytop + ((ybot - ytop) / 2);
3694    entry.changed.connect(() => {
3695      text.insert( entry.text, undo_text );
3696      queue_draw();
3697      entry.unparent();
3698      grab_focus();
3699    });
3700    overlay.add_overlay( entry );
3701    entry.insert_emoji();
3702  }
3703
3704  /* Called whenever the period key is entered with the control key */
3705  public void handle_control_period() {
3706    if( is_node_editable() ) {
3707      insert_emoji( _selected.current_node().name );
3708    } else if( is_connection_editable() ) {
3709      insert_emoji( _selected.current_connection().title );
3710    }
3711  }
3712
3713  /* Displays the quick entry UI in insertion mode */
3714  public void handle_control_E() {
3715    var quick_entry = new QuickEntry( this, false, _settings );
3716    quick_entry.show_all();
3717  }
3718
3719  /* Displays the quick entry UI in replacement mode */
3720  public void handle_control_R() {
3721    var quick_entry = new QuickEntry( this, true, _settings );
3722    var export      = (ExportText)win.exports.get_by_name( "text" );
3723    quick_entry.preload( export.export_node( _selected.current_node(), "" ) );
3724    quick_entry.show_all();
3725  }
3726
3727  /* Closes the current tab */
3728  private void handle_control_w() {
3729    win.close_current_tab();
3730  }
3731
3732  /* Called whenever the Control+home key is entered in the drawing area */
3733  private void handle_control_home( bool shift ) {
3734    if( is_connection_editable() ) {
3735      if( shift ) {
3736        _selected.current_connection().title.selection_to_start( true );
3737      } else {
3738        _selected.current_connection().title.move_cursor_to_start();
3739      }
3740      _im_context.reset();
3741      queue_draw();
3742    } else if( is_node_editable() ) {
3743      if( shift ) {
3744        _selected.current_node().name.selection_to_start( true );
3745      } else {
3746        _selected.current_node().name.move_cursor_to_start();
3747      }
3748      _im_context.reset();
3749      queue_draw();
3750    }
3751  }
3752
3753  /* Called whenever the home key is entered in the drawing area */
3754  private void handle_home( bool shift ) {
3755    if( is_connection_editable() ) {
3756      if( shift ) {
3757        _selected.current_connection().title.selection_to_start_of_line( true );
3758      } else {
3759        _selected.current_connection().title.move_cursor_to_start_of_line();
3760      }
3761      _im_context.reset();
3762      queue_draw();
3763    } else if( is_node_editable() ) {
3764      if( shift ) {
3765        _selected.current_node().name.selection_to_start_of_line( true );
3766      } else {
3767        _selected.current_node().name.move_cursor_to_start_of_line();
3768      }
3769      _im_context.reset();
3770      queue_draw();
3771    }
3772  }
3773
3774  /* Called whenever the Control+end key is entered in the drawing area */
3775  private void handle_control_end( bool shift ) {
3776    if( is_connection_editable() ) {
3777      if( shift ) {
3778        _selected.current_connection().title.selection_to_end( true );
3779      } else {
3780        _selected.current_connection().title.move_cursor_to_end();
3781      }
3782      _im_context.reset();
3783      queue_draw();
3784    } else if( is_node_editable() ) {
3785      if( shift ) {
3786        _selected.current_node().name.selection_to_end( true );
3787      } else {
3788        _selected.current_node().name.move_cursor_to_end();
3789      }
3790      _im_context.reset();
3791      queue_draw();
3792    }
3793  }
3794
3795  /* Called whenever the end key is entered in the drawing area */
3796  private void handle_end( bool shift ) {
3797    if( is_connection_editable() ) {
3798      if( shift ) {
3799        _selected.current_connection().title.selection_to_end_of_line( true );
3800      } else {
3801        _selected.current_connection().title.move_cursor_to_end_of_line();
3802      }
3803      _im_context.reset();
3804      queue_draw();
3805    } else if( is_node_editable() ) {
3806      if( shift ) {
3807        _selected.current_node().name.selection_to_end_of_line( true );
3808      } else {
3809        _selected.current_node().name.move_cursor_to_end_of_line();
3810      }
3811      _im_context.reset();
3812      queue_draw();
3813    }
3814  }
3815
3816  /* Called whenever the up key is entered in the drawing area */
3817  private void handle_up( bool shift, bool alt ) {
3818    if( is_connection_editable() ) {
3819      if( shift ) {
3820        _selected.current_connection().title.selection_vertically( -1 );
3821      } else {
3822        _selected.current_connection().title.move_cursor_vertically( -1 );
3823      }
3824      _im_context.reset();
3825      queue_draw();
3826    } else if( is_node_editable() ) {
3827      if( shift ) {
3828        _selected.current_node().name.selection_vertically( -1 );
3829      } else {
3830        _selected.current_node().name.move_cursor_vertically( -1 );
3831      }
3832      _im_context.reset();
3833      queue_draw();
3834    } else if( is_connection_connecting() && (_attach_node != null) ) {
3835      update_connection_by_node( get_node_up( _attach_node ) );
3836    } else if( is_node_selected() ) {
3837      var current = _selected.current_node();
3838      var up_node = get_node_up( current );
3839      if( alt ) {
3840        if( current.swap_with_sibling( up_node ) ||
3841            current.make_children_siblings( up_node ) ) {
3842          queue_draw();
3843          auto_save();
3844        }
3845      } else if( select_node( up_node ) ) {
3846        queue_draw();
3847      }
3848    }
3849  }
3850
3851  /*
3852   If the Control key is used, jumps the cursor to the beginning of the text.  If Control-Shift
3853   is used, selects everything from the beginnning of the string to the cursor position.
3854  */
3855  private void handle_control_up( bool shift ) {
3856    if( is_connection_editable() ) {
3857      if( shift ) {
3858        _selected.current_connection().title.selection_to_start( false );
3859      } else {
3860        _selected.current_connection().title.move_cursor_to_start();
3861      }
3862      _im_context.reset();
3863      queue_draw();
3864    } else if( is_node_editable() ) {
3865      if( shift ) {
3866        _selected.current_node().name.selection_to_start( false );
3867      } else {
3868        _selected.current_node().name.move_cursor_to_start();
3869      }
3870      _im_context.reset();
3871      queue_draw();
3872    }
3873  }
3874
3875  /* Called whenever the down key is entered in the drawing area */
3876  private void handle_down( bool shift, bool alt ) {
3877    if( is_connection_editable() ) {
3878      if( shift ) {
3879        _selected.current_connection().title.selection_vertically( 1 );
3880      } else {
3881        _selected.current_connection().title.move_cursor_vertically( 1 );
3882      }
3883      _im_context.reset();
3884      queue_draw();
3885    } else if( is_node_editable() ) {
3886      if( shift ) {
3887        _selected.current_node().name.selection_vertically( 1 );
3888      } else {
3889        _selected.current_node().name.move_cursor_vertically( 1 );
3890      }
3891      _im_context.reset();
3892      queue_draw();
3893    } else if( is_connection_connecting() && (_attach_node != null) ) {
3894      update_connection_by_node( get_node_down( _attach_node ) );
3895    } else if( is_node_selected() ) {
3896      var current   = _selected.current_node();
3897      var down_node = get_node_down( current );
3898      if( alt ) {
3899        if( current.swap_with_sibling( down_node ) ||
3900            current.make_children_siblings( down_node ) ) {
3901          queue_draw();
3902          auto_save();
3903        }
3904      } else if( select_node( down_node ) ) {
3905        queue_draw();
3906      }
3907    }
3908  }
3909
3910  /*
3911   If the Control key is used, jumps the cursor to the end of the text.  If Control-Shift is
3912   used, selects all text from the current cursor position to the end of the string.
3913  */
3914  private void handle_control_down( bool shift ) {
3915    if( is_connection_editable() ) {
3916      if( shift ) {
3917        _selected.current_connection().title.selection_to_end( false );
3918      } else {
3919        _selected.current_connection().title.move_cursor_to_end();
3920      }
3921      _im_context.reset();
3922      queue_draw();
3923    } else if( is_node_editable() ) {
3924      if( shift ) {
3925        _selected.current_node().name.selection_to_end( false );
3926      } else {
3927        _selected.current_node().name.move_cursor_to_end();
3928      }
3929      _im_context.reset();
3930      queue_draw();
3931    }
3932  }
3933
3934  /* Called whenever the page up key is entered in the drawing area */
3935  private void handle_pageup() {
3936    if( is_connection_connecting() && (_attach_node != null) ) {
3937      update_connection_by_node( get_node_pageup( _attach_node ) );
3938    } else if( is_node_selected() ) {
3939      if( select_node( get_node_pageup( _selected.current_node() ) ) ) {
3940        queue_draw();
3941      }
3942    }
3943  }
3944
3945  /* Called whenever the page down key is entered in the drawing area */
3946  private void handle_pagedn() {
3947    if( is_connection_connecting() && (_attach_node != null) ) {
3948      update_connection_by_node( get_node_pagedn( _attach_node ) );
3949    } else if( is_node_selected() ) {
3950      if( select_node( get_node_pagedn( _selected.current_node() ) ) ) {
3951        queue_draw();
3952      }
3953    }
3954  }
3955
3956  /* Handle input method */
3957  private void handle_im_commit( string str ) {
3958    insert_text( str );
3959  }
3960
3961  /* Inserts text */
3962  private bool insert_text( string str ) {
3963    if( !str.get_char( 0 ).isprint() ) return( false );
3964    if( is_connection_editable() ) {
3965      _selected.current_connection().title.insert( str, undo_text );
3966      queue_draw();
3967    } else if( is_node_editable() ) {
3968      _selected.current_node().name.insert( str, undo_text );
3969      see();
3970      queue_draw();
3971    } else {
3972      return( false );
3973    }
3974    return( true );
3975  }
3976
3977  /* Helper class for the handle_im_retrieve_surrounding method */
3978  private void retrieve_surrounding_in_text( CanvasText ct ) {
3979    int    cursor, selstart, selend;
3980    string text = ct.text.text;
3981    ct.get_cursor_info( out cursor, out selstart, out selend );
3982    _im_context.set_surrounding( text, text.length, text.index_of_nth_char( cursor ) );
3983  }
3984
3985  /* Called in IMContext callback of the same name */
3986  private bool handle_im_retrieve_surrounding() {
3987    if( is_node_editable() ) {
3988      retrieve_surrounding_in_text( _selected.current_node().name );
3989      return( true );
3990    } else if( is_connection_editable() ) {
3991      retrieve_surrounding_in_text( _selected.current_connection().title );
3992      return( true );
3993    }
3994    return( false );
3995  }
3996
3997  /* Helper class for the handle_im_delete_surrounding method */
3998  private void delete_surrounding_in_text( CanvasText ct, int offset, int chars ) {
3999    int cursor, selstart, selend;
4000    ct.get_cursor_info( out cursor, out selstart, out selend );
4001    var startpos = cursor - offset;
4002    var endpos   = startpos + chars;
4003    ct.delete_range( startpos, endpos, undo_text );
4004  }
4005
4006  /* Called in IMContext callback of the same name */
4007  private bool handle_im_delete_surrounding( int offset, int nchars ) {
4008    if( is_node_editable() ) {
4009      delete_surrounding_in_text( _selected.current_node().name, offset, nchars );
4010      return( true );
4011    } else if( is_connection_editable() ) {
4012      delete_surrounding_in_text( _selected.current_connection().title, offset, nchars );
4013      return( true );
4014    }
4015    return( false );
4016  }
4017
4018  /*
4019   Returns true if the following key was found to be pressed (regardless of
4020   keyboard layout).
4021  */
4022  private bool has_key( uint[] kvs, uint key ) {
4023    foreach( uint kv in kvs ) {
4024      if( kv == key ) return( true );
4025    }
4026    return( false );
4027  }
4028
4029  /* Handle a key event */
4030  private bool on_keypress( EventKey e ) {
4031
4032    /* Figure out which modifiers were used */
4033    var control      = (bool)(e.state & ModifierType.CONTROL_MASK);
4034    var shift        = (bool)(e.state & ModifierType.SHIFT_MASK);
4035    var alt          = (bool)(e.state & ModifierType.MOD1_MASK);
4036    var nomod        = !(control || shift || alt);
4037    var current_node = _selected.current_node();
4038    var current_conn = _selected.current_connection();
4039    var keymap       = Keymap.get_for_display( Display.get_default() );
4040    KeymapKey[] ks   = {};
4041    uint[] kvs       = {};
4042
4043    keymap.get_entries_for_keycode( e.hardware_keycode, out ks, out kvs );
4044
4045    /* If there is a current node or connection selected, operate on it */
4046    if( (current_node != null) || (current_conn != null) ) {
4047      if( control ) {
4048        if( !shift && has_key( kvs, Key.c ) )           { do_copy(); }
4049        else if( !shift && has_key( kvs, Key.x ) )      { do_cut(); }
4050        else if( !shift && has_key( kvs, Key.v ) )      { do_paste( false ); }
4051        else if(  shift && has_key( kvs, Key.V ) )      { do_paste( true ); }
4052        else if( has_key( kvs, Key.Return ) )           { handle_control_return(); }
4053        else if( has_key( kvs, Key.Tab ) )              { handle_control_tab(); }
4054        else if( has_key( kvs, Key.Right ) )            { handle_control_right( shift ); }
4055        else if( has_key( kvs, Key.Left ) )             { handle_control_left( shift ); }
4056        else if( has_key( kvs, Key.Up ) )               { handle_control_up( shift ); }
4057        else if( has_key( kvs, Key.Down ) )             { handle_control_down( shift ); }
4058        else if( has_key( kvs, Key.Home ) )             { handle_control_home( shift ); }
4059        else if( has_key( kvs, Key.End ) )              { handle_control_end( shift ); }
4060        else if( !shift && has_key( kvs, Key.a ) )      { select_all(); }
4061        else if(  shift && has_key( kvs, Key.A ) )      { deselect_all(); }
4062        else if( !shift && has_key( kvs, Key.period ) ) { handle_control_period(); }
4063        else if(  shift && has_key( kvs, Key.E ) )      { handle_control_E(); }
4064        else if(  shift && has_key( kvs, Key.R ) )      { handle_control_R(); }
4065        else if( !shift && has_key( kvs, Key.w ) )      { handle_control_w(); }
4066        else return( false );
4067      } else if( nomod || shift || alt) {
4068        if( has_key( kvs, Key.BackSpace ) )      { handle_backspace(); }
4069        else if( has_key( kvs, Key.Delete ) )    { handle_delete(); }
4070        else if( has_key( kvs, Key.Escape ) )    { handle_escape(); }
4071        else if( has_key( kvs, Key.Return ) )    { handle_return( shift ); }
4072        else if( has_key( kvs, Key.Tab ) )       { handle_tab(); }
4073        else if( has_key( kvs, Key.Right ) )     { handle_right( shift, alt ); }
4074        else if( has_key( kvs, Key.Left ) )      { handle_left( shift, alt ); }
4075        else if( has_key( kvs, Key.Home ) )      { handle_home( shift ); }
4076        else if( has_key( kvs, Key.End ) )       { handle_end( shift ); }
4077        else if( has_key( kvs, Key.Up ) )        { handle_up( shift, alt ); }
4078        else if( has_key( kvs, Key.Down ) )      { handle_down( shift, alt ); }
4079        else if( has_key( kvs, Key.Page_Up ) )   { handle_pageup(); }
4080        else if( has_key( kvs, Key.Page_Down ) ) { handle_pagedn(); }
4081        else if( has_key( kvs, Key.Control_L ) ) { handle_control( true ); }
4082        else if( has_key( kvs, Key.F10 ) )       { if( shift ) show_contextual_menu( e ); }
4083        else if( has_key( kvs, Key.Menu ) )      { show_contextual_menu( e ); }
4084        else {
4085          if( (current_node != null) && (current_node.mode != NodeMode.EDITABLE) ) {
4086            return( handle_node_keypress( e, kvs ) );
4087          } else if( (current_conn != null) && (current_conn.mode != ConnMode.EDITABLE) ) {
4088            return( handle_connection_keypress( e, kvs ) );
4089          } else {
4090            _im_context.filter_keypress( e );
4091            return( false );
4092          }
4093        }
4094      }
4095
4096    /* If there is no current node, allow some of the keyboard shortcuts */
4097    } else if( control ) {
4098      if( shift && has_key( kvs, Key.E ) )       { handle_control_E(); }
4099      else if( !shift && has_key( kvs, Key.c ) ) { do_copy(); }
4100      else if( !shift && has_key( kvs, Key.x ) ) { do_cut(); }
4101      else return( false );
4102
4103    } else if( nomod || shift ) {
4104      if( !shift && has_key( kvs, Key.minus ) )             { if( nodes_alignable() ) NodeAlign.align_top( this, _selected.nodes() ); }
4105      else if( !shift && has_key( kvs, Key.equal ) )        { if( nodes_alignable() ) NodeAlign.align_hcenter( this, _selected.nodes() ); }
4106      else if(  shift && has_key( kvs, Key.Z ) )            { zoom_in(); }
4107      else if( !shift && has_key( kvs, Key.bracketleft ) )  { if( nodes_alignable() ) NodeAlign.align_left( this, _selected.nodes() ); }
4108      else if( !shift && has_key( kvs, Key.bracketright ) ) { if( nodes_alignable() ) NodeAlign.align_right( this, _selected.nodes() ); }
4109      else if(  shift && has_key( kvs, Key.underscore ) )   { if( nodes_alignable() ) NodeAlign.align_bottom( this, _selected.nodes() ); }
4110      else if( !shift && has_key( kvs, Key.a ) )            { select_parent_nodes(); }
4111      else if( !shift && has_key( kvs, Key.d ) )            { select_child_nodes(); }
4112      else if( !shift && has_key( kvs, Key.f ) )            { toggle_folds(); }
4113      else if( !shift && has_key( kvs, Key.g ) )            { add_group(); }
4114      else if( !shift && has_key( kvs, Key.m ) )            { select_root_node(); }
4115      else if( !shift && has_key( kvs, Key.r ) )            { if( undo_buffer.redoable() ) undo_buffer.redo(); }
4116      else if( !shift && has_key( kvs, Key.t ) )            { change_selected_tasks(); }
4117      else if( !shift && has_key( kvs, Key.u ) )            { if( undo_buffer.undoable() ) undo_buffer.undo(); }
4118      else if( !shift && has_key( kvs, Key.z ) )            { zoom_out(); }
4119      else if(  shift && has_key( kvs, Key.bar ) )          { if( nodes_alignable() ) NodeAlign.align_vcenter( this, _selected.nodes() ); }
4120      else if( has_key( kvs, Key.BackSpace ) )              { handle_backspace(); }
4121      else if( has_key( kvs, Key.Delete ) )                 { handle_delete(); }
4122      else if( has_key( kvs, Key.Return ) )                 { handle_return( shift ); }
4123      else if( has_key( kvs, Key.Control_L ) )              { handle_control( true ); }
4124      else if( has_key( kvs, Key.F10 ) )                    { if( shift ) show_contextual_menu( e ); }
4125      else if( has_key( kvs, Key.Menu ) )                   { show_contextual_menu( e ); }
4126      else if( !shift && has_key( kvs, Key.x ) )            { create_connection(); }
4127      else if( !shift && has_key( kvs, Key.y ) )            { toggle_links(); }
4128      else return( false );
4129    }
4130    return( true );
4131  }
4132
4133  private bool handle_connection_keypress( EventKey e, uint[] kvs ) {
4134    var current = _selected.current_connection();
4135    var shift   = (bool)(e.state & ModifierType.SHIFT_MASK);
4136    if( shift && has_key( kvs, Key.E ) )       { show_properties( "current", PropertyGrab.NOTE ); }
4137    else if(  shift && has_key( kvs, Key.Z ) )  { zoom_in(); }
4138    else if( !shift && has_key( kvs, Key.e ) ) {
4139      current.edit_title_begin( this );
4140      set_connection_mode( current, ConnMode.EDITABLE );
4141      queue_draw();
4142    }
4143    else if( !shift && has_key( kvs, Key.f ) ) { select_connection_node( true ); }
4144    else if( !shift && has_key( kvs, Key.i ) ) { show_properties( "current", PropertyGrab.FIRST ); }
4145    else if( !shift && has_key( kvs, Key.n ) ) { select_connection( 1 ); }
4146    else if( !shift && has_key( kvs, Key.p ) ) { select_connection( -1 ); }
4147    else if( !shift && has_key( kvs, Key.r ) ) {  // Perform redo
4148      if( undo_buffer.redoable() ) {
4149        undo_buffer.redo();
4150      }
4151    }
4152    else if( !shift && has_key( kvs, Key.s ) ) { see(); }
4153    else if( !shift && has_key( kvs, Key.t ) ) { select_connection_node( false ); }
4154    else if( !shift && has_key( kvs, Key.u ) ) {  // Perform undo
4155      if( undo_buffer.undoable() ) {
4156        undo_buffer.undo();
4157      }
4158    }
4159    else if( !shift && has_key( kvs, Key.z ) ) { zoom_out(); }
4160    else return( false );
4161    return( true );
4162  }
4163
4164  /* Handles keypresses when a single node is currenly selected */
4165  private bool handle_node_keypress( EventKey e, uint[] kvs ) {
4166    var current = _selected.current_node();
4167    var shift   = (bool)(e.state & ModifierType.SHIFT_MASK);
4168    if( shift && has_key( kvs, Key.C ) )      { center_current_node(); }
4169    else if( shift && has_key( kvs, Key.D ) ) { select_node_tree(); }
4170    else if( shift && has_key( kvs, Key.E ) ) { show_properties( "current", PropertyGrab.NOTE ); }
4171    else if( shift && has_key( kvs, Key.I ) ) {
4172      if( _debug ) {
4173        current.display();
4174      }
4175    }
4176    else if(  shift && has_key( kvs, Key.S ) ) { sort_alphabetically(); }
4177    else if(  shift && has_key( kvs, Key.X ) ) { select_attached_connection(); }
4178    else if(  shift && has_key( kvs, Key.Y ) ) { select_linked_node(); }
4179    else if(  shift && has_key( kvs, Key.Z ) ) { zoom_in(); }
4180    else if( !shift && has_key( kvs, Key.a ) ) { select_parent_nodes(); }
4181    else if( !shift && has_key( kvs, Key.c ) ) { select_child_node(); }
4182    else if( !shift && has_key( kvs, Key.d ) ) { select_child_nodes(); }
4183    else if( !shift && has_key( kvs, Key.e ) ) {
4184      set_node_mode( current, NodeMode.EDITABLE );
4185      queue_draw();
4186    }
4187    else if( !shift && has_key( kvs, Key.f ) ) { toggle_fold( current ); }
4188    else if( !shift && has_key( kvs, Key.g ) ) { add_group(); }
4189    else if( !shift && has_key( kvs, Key.h ) ) { handle_left( false, false ); }
4190    else if( !shift && has_key( kvs, Key.i ) ) { show_properties( "current", PropertyGrab.FIRST ); }
4191    else if( !shift && has_key( kvs, Key.j ) ) { handle_down( false, false ); }
4192    else if( !shift && has_key( kvs, Key.k ) ) { handle_up( false, false ); }
4193    else if( !shift && has_key( kvs, Key.l ) ) { handle_right( false, false ); }
4194    else if( !shift && has_key( kvs, Key.m ) ) { select_root_node(); }
4195    else if( !shift && has_key( kvs, Key.n ) ) { select_sibling_node( 1 ); }
4196    else if( !shift && has_key( kvs, Key.p ) ) { select_sibling_node( -1 ); }
4197    else if( !shift && has_key( kvs, Key.r ) ) {  // Perform redo
4198      if( undo_buffer.redoable() ) {
4199        undo_buffer.redo();
4200      }
4201    }
4202    else if( !shift && has_key( kvs, Key.s ) ) { see(); }
4203    else if( !shift && has_key( kvs, Key.t ) ) {  // Toggle the task done indicator
4204      if( current.task_enabled() ) {
4205        if( current.task_done() ) {
4206          change_current_task( false, false );
4207        } else {
4208          change_current_task( true, true );
4209        }
4210      } else {
4211        change_current_task( true, false );
4212      }
4213    }
4214    else if( !shift && has_key( kvs, Key.u ) ) {  // Perform undo
4215      if( undo_buffer.undoable() ) {
4216        undo_buffer.undo();
4217      }
4218    }
4219    else if( !shift && has_key( kvs, Key.x ) ) { start_connection( true, false ); }
4220    else if( !shift && has_key( kvs, Key.y ) ) { toggle_links(); }
4221    else if( !shift && has_key( kvs, Key.z ) ) { zoom_out(); }
4222    else return( false );
4223    return( true );
4224  }
4225
4226  /* Handles a key release event */
4227  private bool on_keyrelease( EventKey e ) {
4228    if( e.keyval == 65507 ) {
4229      handle_control( false );
4230    }
4231    return( true );
4232  }
4233
4234  /*
4235   Handles a key press/release of the control key.  Checks to see if the current
4236   cursor is over a URL.  If it is, sets the cursor appropriately.
4237  */
4238  private void handle_control( bool pressed ) {
4239    var tag = FormatTag.LENGTH;
4240    var url = "";
4241    for( int i=0; i<_nodes.length; i++ ) {
4242      var match = _nodes.index( i ).contains( _scaled_x, _scaled_y, null );
4243      if( (match != null) && match.name.is_within_clickable( _scaled_x, _scaled_y, out tag, out url ) ) {
4244        if( tag == FormatTag.URL ) {
4245          if( pressed ) {
4246            set_cursor( url_cursor );
4247            set_tooltip_markup( url );
4248          } else {
4249            set_cursor( null );
4250            set_tooltip_markup( null );
4251          }
4252        }
4253      }
4254    }
4255  }
4256
4257  /* Returns true if we can perform a node copy operation */
4258  public bool node_copyable() {
4259    return( _selected.current_node() != null );
4260  }
4261
4262  /* Returns true if we can perform a node cut operation */
4263  public bool node_cuttable() {
4264    return( _selected.current_node() != null );
4265  }
4266
4267  /* Returns true if we can perform a node paste operation */
4268  public bool node_pasteable() {
4269    return( MinderClipboard.node_pasteable() );
4270  }
4271
4272  /* Returns true if the currently selected nodes are alignable */
4273  public bool nodes_alignable() {
4274    var nodes = _selected.nodes();
4275    if( nodes.length < 2 ) return( false );
4276    for( int i=0; i<nodes.length; i++ ) {
4277      var node = nodes.index( i );
4278      if( !node.is_root() && (node.layout.name != _( "Manual" )) ) {
4279        return( false );
4280      }
4281    }
4282    return( true );
4283  }
4284
4285  /* Serializes the current node tree */
4286  public string serialize_for_copy( Array<Node> nodes, Connections conns ) {
4287    string    str;
4288    Xml.Doc*  doc  = new Xml.Doc( "1.0" );
4289    Xml.Node* root = new Xml.Node( null, "minder" );
4290    doc->set_root_element( root );
4291    Xml.Node* ns = new Xml.Node( null, "nodes" );
4292    for( int i=0; i<nodes.length; i++ ) {
4293      nodes.index( i ).save( ns );
4294    }
4295    root->add_child( ns );
4296    Xml.Node* cs = new Xml.Node( null, "connections" );
4297    for( int i=0; i<nodes.length; i++ ) {
4298      conns.save_if_in_node( cs, nodes.index( i ) );
4299    }
4300    root->add_child( cs );
4301    doc->dump_memory( out str );
4302    delete doc;
4303    return( str );
4304  }
4305
4306  /* Deserializes the paste string and returns the list of nodes */
4307  public void deserialize_for_paste( string str, Array<Node> nodes, Array<Connection> conns, HashMap<int,int> id_map, Array<NodeLinkInfo?> link_ids ) {
4308    Xml.Doc* doc    = Xml.Parser.parse_doc( str );
4309    if( doc == null ) return;
4310    for( Xml.Node* it = doc->get_root_element()->children; it != null; it = it->next ) {
4311      if( it->type == Xml.ElementType.ELEMENT_NODE ) {
4312        switch( it->name ) {
4313          // case "images"      :  image_manager.load( it );  break;
4314          case "connections" :  _connections.load( this, it, conns, nodes, id_map );  break;
4315          case "nodes"       :
4316            for( Xml.Node* it2 = it->children; it2 != null; it2 = it2->next ) {
4317              if( (it2->type == Xml.ElementType.ELEMENT_NODE) && (it2->name == "node") ) {
4318                var node = new Node.with_name( this, "", null );
4319                node.load( this, it2, true, id_map, link_ids );
4320                nodes.append_val( node );
4321              }
4322            }
4323            break;
4324        }
4325      }
4326    }
4327    for( int i=0; i<link_ids.length; i++ ) {
4328      link_ids.index( i ).node.linked_node = get_node( nodes, int.parse( link_ids.index( i ).id_str ) );
4329    }
4330    delete doc;
4331  }
4332
4333  /* Copies the current node to the node clipboard */
4334  public void get_nodes_for_clipboard( out Array<Node> nodes, out Connections conns ) {
4335
4336    nodes = new Array<Node>();
4337    conns = _connections;
4338
4339    /* Setup the nodes that will be copied */
4340    if( _selected.current_node() != null ) {
4341      nodes.append_val( new Node.copy_tree( this, _selected.current_node(), image_manager ) );
4342    } else {
4343      _selected.get_subtrees( ref nodes, image_manager );
4344    }
4345
4346  }
4347
4348  /* Copies the currently selected text to the clipboard */
4349  public void copy_selected_text() {
4350    string? value        = null;
4351    var     current_node = _selected.current_node();
4352    var     current_conn = _selected.current_connection();
4353    if( current_node != null ) {
4354      value = current_node.name.get_selected_text();
4355    } else if( current_conn != null ) {
4356      value = current_conn.title.get_selected_text();
4357    }
4358    if( value != null ) {
4359      MinderClipboard.copy_text( value );
4360    }
4361  }
4362
4363  /* Copies either the current node or the currently selected text to the clipboard */
4364  public void do_copy() {
4365    var current = _selected.current_node();
4366    if( current != null ) {
4367      switch( current.mode ) {
4368        case NodeMode.CURRENT  :  MinderClipboard.copy_nodes( this );  break;
4369        case NodeMode.EDITABLE :  copy_selected_text();                break;
4370      }
4371    } else if( _selected.nodes().length > 1 ) {
4372      MinderClipboard.copy_nodes( this );
4373    } else if( is_connection_editable() ) {
4374      copy_selected_text();
4375    }
4376  }
4377
4378  /* Cuts the current node from the tree and stores it in the clipboard */
4379  public void cut_node_to_clipboard() {
4380    var current = _selected.current_node();
4381    if( current == null ) return;
4382    var next_node = next_node_to_select();
4383    var conns     = new Array<Connection>();
4384    UndoNodeGroups? undo_groups = null;
4385    _connections.node_deleted( current, conns );
4386    _groups.remove_node( current, ref undo_groups );
4387    MinderClipboard.copy_nodes( this );
4388    if( current.is_root() ) {
4389      for( int i=0; i<_nodes.length; i++ ) {
4390        if( _nodes.index( i ) == current ) {
4391          undo_buffer.add_item( new UndoNodeCut( current, i, conns, undo_groups ) );
4392          _nodes.remove_index( i );
4393          break;
4394        }
4395      }
4396    } else {
4397      undo_buffer.add_item( new UndoNodeCut( current, current.index(), conns, undo_groups ) );
4398      current.delete();
4399    }
4400    _selected.remove_node( current );
4401    select_node( next_node );
4402    queue_draw();
4403    auto_save();
4404  }
4405
4406  public void cut_selected_nodes_to_clipboard() {
4407    if( _selected.num_nodes() == 0 ) return;
4408    var nodes = _selected.ordered_nodes();
4409    var conns = new Array<Connection>();
4410    Array<UndoNodeGroups?> undo_groups = null;
4411    for( int i=0; i<nodes.length; i++ ) {
4412      _connections.node_only_deleted( nodes.index( i ), conns );
4413    }
4414    _groups.remove_nodes( nodes, out undo_groups );
4415    MinderClipboard.copy_nodes( this );
4416    undo_buffer.add_item( new UndoNodesCut( nodes, conns, undo_groups ) );
4417    for( int i=0; i<nodes.length; i++ ) {
4418      nodes.index( i ).delete_only();
4419    }
4420    _selected.clear_nodes();
4421    queue_draw();
4422    auto_save();
4423  }
4424
4425  /* Cuts the current selected text to the clipboard */
4426  public void cut_selected_text() {
4427    copy_selected_text();
4428    var current_node = _selected.current_node();
4429    var current_conn = _selected.current_connection();
4430    if( current_node != null ) {
4431      current_node.name.insert( "", undo_text );
4432    } else if( current_conn != null ) {
4433      current_conn.title.insert( "", undo_text );
4434    }
4435    queue_draw();
4436    auto_save();
4437  }
4438
4439  /* Either cuts the current node or cuts the currently selected text */
4440  public void do_cut() {
4441    var current = _selected.current_node();
4442    if( current != null ) {
4443      switch( current.mode ) {
4444        case NodeMode.CURRENT  :  cut_node_to_clipboard();  break;
4445        case NodeMode.EDITABLE :  cut_selected_text();      break;
4446      }
4447    } else if( _selected.nodes().length > 1 ) {
4448      cut_selected_nodes_to_clipboard();
4449    } else if( is_connection_editable() ) {
4450      cut_selected_text();
4451    }
4452  }
4453
4454  private void replace_node_text( Node node, string text ) {
4455    var orig_text = new CanvasText( this );
4456    orig_text.copy( node.name );
4457    node.name.text.replace_text( 0, node.name.text.text.char_count(), text.strip() );
4458    undo_buffer.add_item( new UndoNodeName( this, node, orig_text ) );
4459    queue_draw();
4460    auto_save();
4461  }
4462
4463  private void replace_connection_text( Connection conn, string text ) {
4464    var orig_title = new CanvasText( this );
4465    orig_title.copy( conn.title );
4466    conn.title.text.replace_text( 0, conn.title.text.text.char_count(), text.strip() );
4467    undo_buffer.add_item( new UndoConnectionTitle( this, conn, orig_title ) );
4468    queue_draw();
4469    current_changed( this );
4470    auto_save();
4471  }
4472
4473  private void replace_node_image( Node node, Pixbuf image ) {
4474    var ni = new NodeImage.from_pixbuf( image_manager, image, node.style.node_width );
4475    if( ni.valid ) {
4476      var orig_image = node.image;
4477      node.set_image( image_manager, ni );
4478      undo_buffer.add_item( new UndoNodeImage( node, orig_image ) );
4479      queue_draw();
4480      current_changed( this );
4481      auto_save();
4482    }
4483  }
4484
4485  private void replace_node_xml( Node node, string text ) {
4486    var nodes    = new Array<Node>();
4487    var conns    = new Array<Connection>();
4488    var id_map   = new HashMap<int,int>();
4489    var link_ids = new Array<NodeLinkInfo?>();
4490    deserialize_for_paste( text, nodes, conns, id_map, link_ids );
4491    if( nodes.length == 0 ) return;
4492    replace_node( node, nodes.index( 0 ) );
4493    for( int i=1; i<nodes.length; i++ ) {
4494      add_root( nodes.index( i ), -1 );
4495    }
4496    undo_buffer.add_item( new UndoNodesReplace( node, nodes ) );
4497    select_node( nodes.index( 0 ) );
4498    queue_draw();
4499    current_changed( this );
4500    auto_save();
4501  }
4502
4503  private void insert_node_text( Node node, string text ) {
4504    node.name.insert( text, undo_text );
4505    queue_draw();
4506  }
4507
4508  private void insert_connection_text( Connection conn, string text ) {
4509    conn.title.insert( text, undo_text );
4510    queue_draw();
4511  }
4512
4513  private void paste_text_as_node( Node? node, string text ) {
4514    var nodes  = new Array<Node>();
4515    var export = (ExportText)win.exports.get_by_name( "text" );
4516    export.import_text( text, 0, this, false, nodes );
4517    undo_buffer.add_item( new UndoNodesInsert( this, nodes ) );
4518    queue_draw();
4519    auto_save();
4520  }
4521
4522  private void paste_image_as_node( Node? node, Pixbuf image ) {
4523    var new_node = (node == null) ? create_root_node() : create_child_node( node );
4524    var ni = new NodeImage.from_pixbuf( image_manager, image, 200 );
4525    if( ni.valid ) {
4526      new_node.set_image( image_manager, ni );
4527    }
4528    undo_buffer.add_item( new UndoNodeInsert( new_node, ((node == null) ? (int)(_nodes.length - 1) : new_node.index()) ) );
4529    select_node( new_node );
4530    queue_draw();
4531    current_changed( this );
4532    auto_save();
4533  }
4534
4535  private void paste_as_nodes( Node? node, string text ) {
4536    var nodes    = new Array<Node>();
4537    var conns    = new Array<Connection>();
4538    var id_map   = new HashMap<int,int>();
4539    var link_ids = new Array<NodeLinkInfo?>();
4540    deserialize_for_paste( text, nodes, conns, id_map, link_ids );
4541    if( nodes.length == 0 ) return;
4542    if( node == null ) {
4543      for( int i=0; i<nodes.length; i++ ) {
4544        _nodes.index( _nodes.length - 1 ).layout.position_root( _nodes.index( _nodes.length - 1 ), nodes.index( i ) );
4545        add_root( nodes.index( i ), -1 );
4546      }
4547    } else if( node.is_root() ) {
4548      uint num_children = node.children().length;
4549      if( num_children > 0 ) {
4550        for( int i=0; i<nodes.length; i++ ) {
4551          nodes.index( i ).side = node.children().index( num_children - 1 ).side;
4552          nodes.index( i ).layout.propagate_side( nodes.index( i ), nodes.index( i ).side );
4553          nodes.index( i ).attach( node, -1, _theme );
4554        }
4555      } else {
4556        for( int i=0; i<nodes.length; i++ ) {
4557          nodes.index( i ).attach( node, -1, _theme );
4558        }
4559      }
4560    } else {
4561      for( int i=0; i<nodes.length; i++ ) {
4562        nodes.index( i ).side = node.side;
4563        nodes.index( i ).layout.propagate_side( nodes.index( i ), nodes.index( i ).side );
4564        nodes.index( i ).attach( node, -1, _theme );
4565      }
4566    }
4567    undo_buffer.add_item( new UndoNodePaste( nodes, conns ) );
4568    select_node( nodes.index( 0 ) );
4569    queue_draw();
4570    current_changed( this );
4571    auto_save();
4572  }
4573
4574  /* Called by the clipboard to paste text */
4575  public void paste_text( string text, bool shift ) {
4576    var node = _selected.current_node();
4577    var conn = _selected.current_connection();
4578    if( shift ) {
4579      if( (node != null) && (node.mode == NodeMode.CURRENT) ) {
4580        replace_node_text( node, text );
4581      } else if( (conn != null) && (conn.mode == ConnMode.SELECTED) ) {
4582        replace_connection_text( conn, text );
4583      }
4584    } else {
4585      if( (node != null) && (node.mode == NodeMode.EDITABLE) ) {
4586        insert_node_text( node, text );
4587      } else if( (conn != null) && (conn.mode == ConnMode.EDITABLE) ) {
4588        insert_connection_text( conn, text );
4589      } else if( conn == null ) {
4590        paste_text_as_node( node, text );
4591      }
4592    }
4593  }
4594
4595  /* Called by the clipboard to paste image */
4596  public void paste_image( Pixbuf image, bool shift ) {
4597    var node = _selected.current_node();
4598    if( shift ) {
4599      if( (node != null) && (node.mode == NodeMode.CURRENT) ) {
4600        replace_node_image( node, image );
4601      }
4602    } else {
4603      paste_image_as_node( node, image );
4604    }
4605  }
4606
4607  /* Called by the clipboard to paste nodes */
4608  public void paste_nodes( string text, bool shift ) {
4609    var node = _selected.current_node();
4610    if( shift ) {
4611      if( (node != null) && (node.mode == NodeMode.CURRENT) ) {
4612        replace_node_xml( node, text );
4613      }
4614    } else {
4615      paste_as_nodes( node, text );
4616    }
4617  }
4618
4619  /* Pastes the contents of the clipboard into the current node */
4620  public void do_paste( bool shift ) {
4621    MinderClipboard.paste( this, shift );
4622  }
4623
4624  /*
4625   Called whenever the user scrolls on the canvas.  We will adjust the
4626   origin to give the canvas the appearance of scrolling.
4627  */
4628  private bool on_scroll( EventScroll e ) {
4629
4630    double delta_x, delta_y;
4631    e.get_scroll_deltas( out delta_x, out delta_y );
4632
4633    bool shift   = (bool)(e.state & ModifierType.SHIFT_MASK);
4634    bool control = (bool)(e.state & ModifierType.CONTROL_MASK);
4635
4636    /* Swap the deltas if the SHIFT key is held down */
4637    if( shift && !control ) {
4638      double tmp = delta_x;
4639      delta_x = delta_y;
4640      delta_y = tmp;
4641    } else if( control ) {
4642      if( e.delta_y < 0 ) {
4643        zoom_in();
4644      } else if( e.delta_y > 0 ) {
4645        zoom_out();
4646      }
4647      return( false );
4648    }
4649
4650    /* Adjust the origin and redraw */
4651    move_origin( (delta_x * 120), (delta_y * 120) );
4652    queue_draw();
4653
4654    /* When the end of the scroll occurs, save the scroll position to the file */
4655    auto_save();
4656
4657    return( false );
4658
4659  }
4660
4661  /* Perform an automatic save for times when changes may be happening rapidly */
4662  public void auto_save() {
4663    if( _auto_save_id != null ) {
4664      Source.remove( _auto_save_id );
4665    }
4666    _auto_save_id = Timeout.add( 200, do_auto_save );
4667  }
4668
4669  /* Allows the document to be auto-saved after a scroll event */
4670  private bool do_auto_save() {
4671    _auto_save_id = null;
4672    is_loaded = true;
4673    changed();
4674    return( false );
4675  }
4676
4677  /* Called whenever we drag something over the canvas */
4678  private bool handle_drag_motion( Gdk.DragContext ctx, int x, int y, uint t ) {
4679
4680    Node       attach_node;
4681    Connection attach_conn;
4682    Sticker    attach_sticker;
4683
4684    var scaled_x = scale_value( x );
4685    var scaled_y = scale_value( y );
4686
4687    get_droppable( scaled_x, scaled_y, out attach_node, out attach_conn, out attach_sticker );
4688
4689    /* Clear the mode of any previous attach node/connection */
4690    if( _attach_node != null ) {
4691      set_node_mode( _attach_node, NodeMode.NONE );
4692    }
4693    if( _attach_conn != null ) {
4694      set_connection_mode( _attach_conn, ConnMode.NONE );
4695    }
4696    if( _attach_sticker != null ) {
4697      _attach_sticker.mode = StickerMode.NONE;
4698    }
4699
4700    if( attach_node != null ) {
4701      set_node_mode( attach_node, NodeMode.DROPPABLE );
4702      _attach_node = attach_node;
4703      queue_draw();
4704    } else if( attach_conn != null ) {
4705      set_connection_mode( attach_conn, ConnMode.DROPPABLE );
4706      _attach_conn = attach_conn;
4707      queue_draw();
4708    } else if( attach_sticker != null ) {
4709      attach_sticker.mode = StickerMode.DROPPABLE;
4710      _attach_sticker = attach_sticker;
4711      queue_draw();
4712    } else if( _attach_node != null ) {
4713      _attach_node = null;
4714      queue_draw();
4715    } else if( _attach_conn != null ) {
4716      _attach_conn = null;
4717      queue_draw();
4718    } else if( _attach_sticker != null ) {
4719      _attach_sticker = null;
4720      queue_draw();
4721    }
4722
4723    return( true );
4724
4725  }
4726
4727  /* Called when something is dropped on the DrawArea */
4728  private void handle_drag_data_received( Gdk.DragContext ctx, int x, int y, Gtk.SelectionData data, uint info, uint t ) {
4729
4730    if( ((_attach_node == null) || (_attach_node.mode != NodeMode.DROPPABLE)) &&
4731        ((_attach_conn == null) || (_attach_conn.mode != ConnMode.DROPPABLE)) ) {
4732
4733      if( info == DragTypes.URI ) {
4734        foreach (var uri in data.get_uris()) {
4735          var image = new NodeImage.from_uri( image_manager, uri, 200 );
4736          if( image.valid ) {
4737            var node = new Node.with_name( this, _( "Another Idea" ), layouts.get_default() );
4738            node.set_image( image_manager, image );
4739            _nodes.index( _nodes.length - 1 ).layout.position_root( _nodes.index( _nodes.length - 1 ), node );
4740            _nodes.append_val( node );
4741            if( select_node( node ) ) {
4742              set_node_mode( node, NodeMode.EDITABLE, false );
4743              queue_draw();
4744            }
4745          }
4746        }
4747      } else if( info == DragTypes.STICKER ) {
4748        if( _attach_sticker != null ) {
4749          var sticker = new Sticker( data.get_text(), _attach_sticker.posx, _attach_sticker.posy, (int)_attach_sticker.width );
4750          _stickers.remove_sticker( _attach_sticker );
4751          _stickers.add_sticker( sticker );
4752          _selected.set_current_sticker( sticker );
4753          _undo_buffer.add_item( new UndoStickerChange( _attach_sticker, sticker ) );
4754          _attach_sticker.mode = StickerMode.NONE;
4755          _attach_sticker = null;
4756        } else {
4757          var double_x = (double)x;
4758          var double_y = (double)y;
4759          var sticker = new Sticker( data.get_text(), double_x, double_y );
4760          _stickers.add_sticker( sticker );
4761          _selected.set_current_sticker( sticker );
4762          _undo_buffer.add_item( new UndoStickerAdd( sticker ) );
4763        }
4764      }
4765
4766      Gtk.drag_finish( ctx, true, false, t );
4767
4768      grab_focus();
4769      see();
4770      queue_draw();
4771      current_changed( this );
4772      auto_save();
4773
4774    } else {
4775
4776      if( info == DragTypes.URI ) {
4777        if( data.get_uris().length == 1 ) {
4778          var image = new NodeImage.from_uri( image_manager, data.get_uris()[0], _attach_node.style.node_width );
4779          if( image.valid ) {
4780            var orig_image = _attach_node.image;
4781            _attach_node.set_image( image_manager, image );
4782            undo_buffer.add_item( new UndoNodeImage( _attach_node, orig_image ) );
4783            set_node_mode( _attach_node, NodeMode.NONE );
4784            _attach_node = null;
4785          }
4786        }
4787      } else if( info == DragTypes.STICKER ) {
4788        var sticker = data.get_text();
4789        if( _attach_node != null ) {
4790          if( _attach_node.sticker == null ) {
4791            undo_buffer.add_item( new UndoNodeStickerAdd( _attach_node, sticker ) );
4792          } else {
4793            undo_buffer.add_item( new UndoNodeStickerChange( _attach_node, _attach_node.sticker ) );
4794          }
4795          _attach_node.sticker = data.get_text();
4796          set_node_mode( _attach_node, NodeMode.NONE );
4797          _attach_node = null;
4798        } else if( _attach_conn != null ) {
4799          if( _attach_conn.sticker == null ) {
4800            undo_buffer.add_item( new UndoConnectionStickerAdd( _attach_conn, sticker ) );
4801          } else {
4802            undo_buffer.add_item( new UndoConnectionStickerChange( _attach_conn, _attach_conn.sticker ) );
4803          }
4804          set_connection_mode( _attach_conn, ConnMode.NONE );
4805          _attach_conn.sticker = data.get_text();
4806          _attach_conn = null;
4807        }
4808      }
4809
4810      Gtk.drag_finish( ctx, true, false, t );
4811      queue_draw();
4812      current_changed( this );
4813      auto_save();
4814
4815    }
4816
4817  }
4818
4819  /* Sets the image of the current node to the given filename */
4820  public bool update_current_image( string uri ) {
4821    var current = _selected.current_node();
4822    var image   = new NodeImage.from_uri( image_manager, uri, current.style.node_width );
4823    if( image.valid ) {
4824      var orig_image = current.image;
4825      current.set_image( image_manager, image );
4826      undo_buffer.add_item( new UndoNodeImage( current, orig_image ) );
4827      queue_draw();
4828      current_changed( this );
4829      auto_save();
4830      return( true );
4831    }
4832    return( false );
4833  }
4834
4835  /* Starts a connection from the current node */
4836  public void start_connection( bool key, bool link ) {
4837    var current_node = _selected.current_node();
4838    if( current_node == null ) return;
4839    var conn = new Connection( this, current_node );
4840    _selected.set_current_connection( conn );
4841    conn.mode = link ? ConnMode.LINKING : ConnMode.CONNECTING;
4842    if( key ) {
4843      double x, y, w, h;
4844      current_node.bbox( out x, out y, out w, out h );
4845      conn.draw_to( (x + (w / 2)), (y + (h / 2)) );
4846      if( _attach_node != null ) {
4847        set_node_mode( _attach_node, NodeMode.NONE );
4848      }
4849      _attach_node = current_node;
4850      set_node_mode( _attach_node, NodeMode.ATTACHABLE );
4851    } else {
4852      conn.draw_to( _press_x, _press_y );
4853    }
4854    _last_node = current_node;
4855    queue_draw();
4856  }
4857
4858  /* Called when a connection is being drawn by moving the mouse */
4859  public void update_connection( double x, double y ) {
4860    var current = _selected.current_connection();
4861    if( current == null ) return;
4862    current.draw_to( scale_value( x ), scale_value( y ) );
4863    queue_draw();
4864  }
4865
4866  /* Called when the connection is being connected via the keyboard */
4867  public void update_connection_by_node( Node? node ) {
4868    if( node == null ) return;
4869    double x, y, w, h;
4870    node.bbox( out x, out y, out w, out h );
4871    _selected.current_connection().draw_to( (x + (w / 2)), (y + (h / 2)) );
4872    if( _attach_node != null ) {
4873      set_node_mode( _attach_node, NodeMode.NONE );
4874    }
4875    _attach_node = node;
4876    set_node_mode( _attach_node, NodeMode.ATTACHABLE );
4877    queue_draw();
4878  }
4879
4880  /* Ends a connection at the given node */
4881  public void end_connection( Node n ) {
4882    var current = _selected.current_connection();
4883    if( current == null ) return;
4884    current.connect_to( n );
4885    _connections.add_connection( current );
4886    undo_buffer.add_item( new UndoConnectionAdd( current ) );
4887    _selected.set_current_connection( current );
4888    handle_connection_edit_on_creation( current );
4889    _last_connection = null;
4890    _last_node       = null;
4891    set_node_mode( _attach_node, NodeMode.NONE );
4892    _attach_node     = null;
4893    auto_save();
4894    queue_draw();
4895  }
4896
4897  /*
4898   If exactly two nodes are currently selected, draws a connection from the first selected node
4899   to the second selected node.
4900  */
4901  public void create_connection() {
4902    if( _selected.num_nodes() != 2 ) return;
4903    double x, y, w, h;
4904    var    nodes = _selected.nodes();
4905    var    conn  = new Connection( this, nodes.index( 0 ) );
4906    conn.connect_to( nodes.index( 1 ) );
4907    nodes.index( 1 ).bbox( out x, out y, out w, out h );
4908    conn.draw_to( (x + (w / 2)), (y + (h / 2)) );
4909    _connections.add_connection( conn );
4910    _selected.set_current_connection( conn );
4911    undo_buffer.add_item( new UndoConnectionAdd( conn ) );
4912    handle_connection_edit_on_creation( conn );
4913    auto_save();
4914    queue_draw();
4915  }
4916
4917  /* Deletes the current connection */
4918  public void delete_connection() {
4919    var current = _selected.current_connection();
4920    if( current == null ) return;
4921    undo_buffer.add_item( new UndoConnectionDelete( current ) );
4922    _connections.remove_connection( current, false );
4923    _selected.remove_connection( current );
4924    _last_connection = null;
4925    auto_save();
4926    queue_draw();
4927  }
4928
4929  /* Deletes the currently selected connections */
4930  public void delete_connections() {
4931    if( _selected.num_connections() == 0 ) return;
4932    var conns = _selected.connections();
4933    undo_buffer.add_item( new UndoConnectionsDelete( conns ) );
4934    for( int i=0; i<conns.length; i++ ) {
4935      _connections.remove_connection( conns.index( i ), false );
4936    }
4937    _selected.clear_connections();
4938    auto_save();
4939    queue_draw();
4940  }
4941
4942  /* Handles the edit on creation of a newly created connection */
4943  private void handle_connection_edit_on_creation( Connection conn ) {
4944    if( (conn.title == null) && _settings.get_boolean( "edit-connection-title-on-creation" ) ) {
4945      conn.change_title( this, "", true );
4946      set_connection_mode( conn, ConnMode.EDITABLE, false );
4947    }
4948  }
4949
4950  /*
4951   Called when the focus button active state changes.  Causes all nodes and connections
4952   to have the alpha state set to almost transparent (when focus mode is enabled) or fully opaque.
4953  */
4954  public void set_focus_mode( bool focus ) {
4955    double alpha = focus ? _focus_alpha : 1.0;
4956    _focus_mode = focus;
4957    update_focus_mode();
4958  }
4959
4960  /* Update the focus mode */
4961  public void update_focus_mode() {
4962    var nodes = _selected.nodes();
4963    var conns = _selected.connections();
4964    var alpha = (_focus_mode && ((nodes.length > 0) || (conns.length > 0))) ? _focus_alpha : 1.0;
4965    for( int i=0; i<_nodes.length; i++ ) {
4966      _nodes.index( i ).alpha = alpha;
4967    }
4968    if( _focus_mode ) {
4969      for( int i=0; i<nodes.length; i++ ) {
4970        var current = nodes.index( i );
4971        current.alpha = 1.0;
4972        var parent = current.parent;
4973        while( parent != null ) {
4974          parent.set_alpha_only( 1.0 );
4975          parent = parent.parent;
4976        }
4977      }
4978      _connections.update_alpha();
4979      for( int i=0; i<conns.length; i++ ) {
4980        conns.index( i ).alpha = 1.0;
4981      }
4982    }
4983    queue_draw();
4984  }
4985
4986  /* Updates the create_new_from_edit variable */
4987  private void update_create_new_from_edit( GLib.Settings settings ) {
4988    _create_new_from_edit = settings.get_boolean( "new-node-from-edit" );
4989  }
4990
4991  /* Updates all alpha values with the given value */
4992  public void update_focus_mode_alpha( GLib.Settings settings ) {
4993    var key   = "focus-mode-alpha";
4994    var alpha = settings.get_double( key );
4995    if( (alpha < 0) || (alpha >= 1.0) ) {
4996      settings.set_double( key, _focus_alpha );
4997    } else if( _focus_alpha != alpha ) {
4998      _focus_alpha = alpha;
4999      for( int i=0; i<_nodes.length; i++ ) {
5000        _nodes.index( i ).update_alpha( alpha );
5001      }
5002      _connections.update_alpha();
5003      queue_draw();
5004    }
5005  }
5006
5007  /* Called by the Tagger class to actually add the tag to the currently selected row */
5008  public void add_tag( string tag ) {
5009    var node = _selected.current_node();
5010    if( node == null ) return;
5011    var name = node.name;
5012    var orig_text = new CanvasText( this );
5013    orig_text.copy( name );
5014    tagger.preedit_load_tags( name.text );
5015    name.text.insert_text( name.text.text.length, (" @" + tag) );
5016    name.text.changed();
5017    tagger.postedit_load_tags( name.text );
5018    undo_buffer.add_item( new UndoNodeName( this, node, orig_text ) );
5019    auto_save();
5020  }
5021
5022  /* Displays the auto-completion widget with the given list of values */
5023  public void show_auto_completion( GLib.List<string> values, int start_pos, int end_pos ) {
5024    var node = _selected.current_node();
5025    if( is_node_editable() ) {
5026      _completion.show( node.name, values, start_pos, end_pos );
5027    } else {
5028      _completion.hide();
5029    }
5030  }
5031
5032  /* Hides the auto-completion widget from view */
5033  public void hide_auto_completion() {
5034    _completion.hide();
5035  }
5036
5037  /* Sorts and re-arranges the children of the given parent using the given array */
5038  private void sort_children( Node parent, CompareFunc<Node> sort_fn ) {
5039    var children = new SList<Node>();
5040    undo_buffer.add_item( new UndoNodeSort( parent ) );
5041    animator.add_nodes( _nodes, "sort nodes" );
5042    for( int i=0; i<parent.children().length; i++ ) {
5043      children.append( parent.children().index( i ) );
5044    }
5045    children.@foreach( (child) => {
5046      child.detach( child.side );
5047    });
5048    children.sort( sort_fn );
5049    children.@foreach( (child) => {
5050      child.attach( parent, -1, null, false );
5051    });
5052    animator.animate();
5053    auto_save();
5054  }
5055
5056  /* Sorts the current node's children alphabetically */
5057  public void sort_alphabetically() {
5058    CompareFunc<Node> sort_fn = (a, b) => {
5059      return( strcmp( a.name.text.text, b.name.text.text ) );
5060    };
5061    sort_children( _selected.current_node(), sort_fn );
5062  }
5063
5064  /* Sorts the current node's children in a random manner */
5065  public void sort_randomly() {
5066    CompareFunc<Node> sort_fn = (a, b) => {
5067      return( (Random.int_range( 0, 2 ) == 0) ? -1 : 1 );
5068    };
5069    sort_children( _selected.current_node(), sort_fn );
5070  }
5071
5072  /* Moves all trees to avoid overlapping */
5073  public void handle_tree_overlap( NodeBounds prev ) {
5074
5075    var current = _selected.current_node();
5076
5077    if( current == null ) return;
5078
5079    var root  = current.get_root();
5080    var curr  = root.tree_bbox;
5081    var ldiff = curr.x - prev.x;
5082    var rdiff = (curr.x + curr.width) - (prev.x + prev.width);
5083    var adiff = curr.y - prev.y;
5084    var bdiff = (curr.y + curr.height) - (prev.y + prev.height);
5085
5086    for( int i=0; i<_nodes.length; i++ ) {
5087      var node = _nodes.index( i );
5088      if( (node != root) && curr.overlaps( node.tree_bbox ) ) {
5089        if( node.is_left_of( prev ) )  node.posx += ldiff;
5090        if( node.is_right_of( prev ) ) node.posx += rdiff;
5091        if( node.is_above( prev ) )    node.posy += adiff;
5092        if( node.is_below( prev ) )    node.posy += bdiff;
5093      }
5094    }
5095
5096  }
5097
5098}
5099