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( "<", "<" ) ); 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