The Birdfont Source Code


All Repositories / birdfont.git / blob – RSS feed

GtkWindow.vala in birdfont

This file is a part of the Birdfont project.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git

Revisions

View the latest version of birdfont/GtkWindow.vala.
Merge ../birdfont-2.x
1 /* 2 Copyright (C) 2012 2013 2014 2015 Johan Mattsson 3 4 This program is free software: you can redistribute it and/or modify 5 it under the terms of the GNU General Public License as published by 6 the Free Software Foundation, either version 3 of the License, or 7 (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 12 GNU General Public License for more details. 13 14 You should have received a copy of the GNU General Public License 15 along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 using Cairo; 19 using Gtk; 20 using Gdk; 21 using BirdFont; 22 using WebKit; 23 using Gdk; 24 using Notify; 25 26 namespace BirdFont { 27 28 public class GtkWindow : Gtk.Window, NativeWindow { 29 30 Box list_box; 31 Box canvas_box; 32 33 WebView html_canvas; 34 ScrolledWindow html_box; 35 36 Box tab_box; 37 38 GlyphCanvasArea glyph_canvas_area; 39 40 Clipboard clipboard; 41 string clipboard_svg = ""; 42 string inkscape_clipboard = ""; 43 44 Scrollbar scrollbar; 45 bool scrollbar_supress_signal = false; 46 47 ToolboxCanvas toolbox; 48 49 Task background_task = new Task(idle); 50 51 public GtkWindow (string title) { 52 scrollbar = new Scrollbar (Orientation.VERTICAL, new Adjustment (0, 0, 1, 1, 0.01, 0.1)); 53 ((Gtk.Window)this).set_title ("BirdFont"); 54 } 55 56 public static void idle () { 57 } 58 59 public void init () { 60 Signal.connect(this, "notify::is-active", (GLib.Callback) window_focus, null); 61 62 scrollbar.value_changed.connect (() => { 63 double p; 64 65 if (!scrollbar_supress_signal) { 66 p = scrollbar.get_value () / (1 - scrollbar.adjustment.page_size); 67 FontDisplay display = MainWindow.get_current_display (); 68 display.scroll_to (p); 69 } 70 }); 71 72 delete_event.connect (() => { 73 MenuTab.quit (); 74 return true; 75 }); 76 77 set_size_and_position (); 78 79 glyph_canvas_area = new GlyphCanvasArea (MainWindow.glyph_canvas); 80 81 html_canvas = new WebView (); 82 html_box = new ScrolledWindow (null, null); 83 html_box.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC); 84 html_box.add (html_canvas); 85 86 MainWindow.get_tab_bar ().signal_tab_selected.connect ((f, tab) => { 87 string uri = ""; 88 string html = ""; 89 FontDisplay fd = tab.get_display (); 90 91 scrollbar.set_visible (fd.has_scrollbar ()); 92 93 if (fd.get_name () == "Preview") { 94 uri = Preview.get_uri (); 95 html = Preview.get_html_with_absolute_paths (); 96 97 try { 98 html_canvas.load_html (html, uri); 99 } catch (Error e) { 100 warning (e.message); 101 warning ("Failed to load html into canvas."); 102 } 103 104 // show the webview when loading has finished 105 html_box.set_visible (true); 106 glyph_canvas_area.set_visible (false); 107 } else { 108 html_box.set_visible (false); 109 glyph_canvas_area.set_visible (true); 110 } 111 }); 112 113 // Hide this canvas when window is realized and flip canvas 114 // visibility in tab selection signal. 115 html_canvas.draw.connect ((t, e) => { 116 glyph_canvas_area.set_visible (false); 117 return false; 118 }); 119 120 canvas_box = new Box (Orientation.HORIZONTAL, 0); 121 canvas_box.pack_start (glyph_canvas_area, true, true, 0); 122 canvas_box.pack_start (html_box, true, true, 0); 123 canvas_box.pack_start (scrollbar, false, true, 0); 124 tab_box = new Box (Orientation.VERTICAL, 0); 125 126 tab_box.pack_start (new TabbarCanvas (MainWindow.get_tab_bar ()), false, false, 0); 127 tab_box.pack_start (canvas_box, true, true, 0); 128 129 toolbox = new ToolboxCanvas (MainWindow.get_toolbox ()); 130 list_box = new Box (Orientation.HORIZONTAL, 0); 131 list_box.pack_start (toolbox, false, false, 0); 132 list_box.pack_start (tab_box, true, true, 0); 133 134 Box vbox = new Box (Orientation.VERTICAL, 0); 135 vbox.pack_start(list_box, true, true, 0); 136 add (vbox); 137 138 try { 139 set_icon_from_file ((!) SearchPaths.find_file (null, "birdfont_window_icon.png").get_path ()); 140 } catch (GLib.Error e) { 141 warning (e.message); 142 } 143 144 key_press_event.connect ((t, event) => { 145 TabContent.key_press (event.keyval); 146 147 return false; 148 }); 149 150 key_release_event.connect ((t, event) => { 151 TabContent.key_release (event.keyval); 152 153 return false; 154 }); 155 156 size_allocate.connect(() => { 157 GlyphCanvas.redraw (); 158 }); 159 160 scrollbar.set_visible (false); 161 162 show_all (); 163 MainWindow.open_recent_files_tab (); 164 } 165 166 public void window_focus (void* data) { 167 TabContent.reset_modifier (); 168 } 169 170 public static void reset_modifier (ModifierType flags) { 171 if ((flags & ModifierType.CONTROL_MASK) == 0) { 172 TabContent.key_release (Key.CTRL_RIGHT); 173 TabContent.key_release (Key.CTRL_LEFT); 174 } 175 176 if ((flags & ModifierType.SHIFT_MASK) == 0) { 177 TabContent.key_release (Key.SHIFT_LEFT); 178 TabContent.key_release (Key.SHIFT_RIGHT); 179 } 180 181 if ((flags & ModifierType.MOD1_MASK) == 0) { 182 TabContent.key_release (Key.ALT_LEFT); 183 TabContent.key_release (Key.ALT_RIGHT); 184 } 185 186 if ((flags & ModifierType.MOD5_MASK) == 0) { 187 TabContent.key_release (Key.LOGO_LEFT); 188 TabContent.key_release (Key.LOGO_RIGHT); 189 } 190 } 191 192 public void font_loaded () { 193 Font f = BirdFont.get_current_font (); 194 set_title (@"$(f.full_name)"); 195 } 196 197 public void set_scrollbar_size (double size) { 198 scrollbar.adjustment.page_size = size; 199 scrollbar.set_visible (size != 0); 200 } 201 202 public void set_scrollbar_position (double position) { 203 scrollbar_supress_signal = true; 204 scrollbar.adjustment.value = position * (1 - scrollbar.adjustment.page_size); 205 scrollbar_supress_signal = false; 206 } 207 208 public void dump_clipboard_content (Clipboard clipboard, SelectionData selection_data) { 209 string d; 210 return_if_fail (!is_null (selection_data)); 211 d = (string) ((!) selection_data); 212 stdout.printf (d); 213 } 214 215 public void dump_clipboard_target (Clipboard clipboard, Atom[] atoms) { 216 foreach (Atom target in atoms) { 217 print ("Target: " + target.name () + "\n"); 218 clipboard.request_contents (target, dump_clipboard_content); 219 } 220 } 221 222 public void dump_clipboard () { 223 clipboard.request_targets (dump_clipboard_target); 224 } 225 226 public string get_clipboard_data () { 227 SelectionData? selection_data; 228 Atom target; 229 string? t; 230 231 target = Atom.intern_static_string ("image/x-inkscape-svg"); 232 selection_data = clipboard.wait_for_contents (target); 233 234 if (!is_null (selection_data)) { 235 return (string) (((!) selection_data).get_data ()); 236 } 237 238 t = clipboard.wait_for_text (); 239 if (t != null) { 240 return (!) t; 241 } 242 243 return ""; 244 } 245 246 public void set_inkscape_clipboard (string inkscape_clipboard_data) { 247 if (BirdFont.mac) { 248 clipboard.set_text (inkscape_clipboard_data, -1); 249 } else { 250 TargetEntry t = { "image/x-inkscape-svg", 0, 0 }; 251 TargetEntry[] targets = { t }; 252 inkscape_clipboard = inkscape_clipboard_data; 253 254 // we can not add data to this closure because the third argument 255 // is owner and not private data. 256 clipboard.set_with_owner (targets, 257 258 // obtain clipboard data 259 (clipboard, selection_data, info, owner) => { 260 Atom type; 261 uchar[] data = (uchar[])(!)((GtkWindow*)owner)->inkscape_clipboard.to_utf8 (); 262 type = Atom.intern_static_string ("image/x-inkscape-svg"); 263 selection_data.set (type, 8, data); 264 }, 265 266 // clear clipboard data 267 (clipboard, user_data) => { 268 }, 269 270 this); 271 } 272 } 273 274 public void set_clipboard_text (string text) { 275 clipboard.set_text (text, -1); 276 } 277 278 public string get_clipboard_text () { 279 string? t; 280 281 t = clipboard.wait_for_text (); 282 if (t != null) { 283 return ((!) t).dup (); 284 } 285 286 return "".dup (); 287 } 288 289 public void set_clipboard (string svg) { 290 TargetEntry t = { "image/svg+xml", 0, 0 }; 291 TargetEntry[] targets = { t }; 292 clipboard_svg = svg; 293 clipboard.set_with_owner (targets, 294 295 // obtain clipboard data 296 (clipboard, selection_data, info, owner) => { 297 Atom type; 298 uchar[] data = (uchar[])(!)((GtkWindow*)owner)->clipboard_svg.to_utf8 (); 299 type = Atom.intern_static_string ("image/svg+xml"); 300 selection_data.set (type, 0, data); 301 }, 302 303 // clear clipboard data 304 (clipboard, user_data) => { 305 }, 306 307 this); 308 } 309 310 public void update_window_size () { 311 int w, h; 312 get_size (out w, out h); 313 314 Preferences.set ("window_width", @"$w"); 315 Preferences.set ("window_height", @"$h"); 316 } 317 318 private void set_size_and_position () { 319 int w = Preferences.get_window_width (); 320 int h = Preferences.get_window_height (); 321 set_default_size (w, h); 322 } 323 324 public void quit () { 325 Gtk.main_quit (); 326 } 327 328 public void file_chooser (string title, FileChooser fc, uint flags) { 329 string? fn = null; 330 bool folder; 331 if (BirdFont.get_arguments () .has_argument ("--windows")) { 332 folder = (flags & FileChooser.DIRECTORY) > 0; 333 MenuTab.show_file_dialog_tab (title, fc, folder); 334 } else { 335 if ((flags & FileChooser.DIRECTORY) > 0) { 336 if ((flags & FileChooser.LOAD) > 0) { 337 fn = show_file_chooser (title, FileChooserAction.SELECT_FOLDER, Stock.OPEN); 338 } else if ((flags & FileChooser.SAVE) > 0) { 339 fn = show_file_chooser (title, FileChooserAction.SELECT_FOLDER, Stock.SAVE); 340 } else { 341 warning ("Open or save is not set."); 342 } 343 } else if ((flags & FileChooser.LOAD) > 0) { 344 fn = show_file_chooser (title, FileChooserAction.OPEN, Stock.OPEN); 345 } else if ((flags & FileChooser.SAVE) > 0) { 346 fn = show_file_chooser (title, FileChooserAction.SAVE, Stock.SAVE); 347 } else { 348 warning ("Unknown type"); 349 } 350 } 351 352 fc.selected (fn); 353 } 354 355 public string? show_file_chooser (string title, FileChooserAction action, string label) { 356 string? fn = null; 357 FileChooserDialog file_chooser = new FileChooserDialog (title, this, action, Stock.CANCEL, ResponseType.CANCEL, label, ResponseType.ACCEPT); 358 Font font = BirdFont.get_current_font (); 359 int i; 360 string last_folder; 361 362 last_folder = Preferences.get ("last_folder"); 363 364 try { 365 if (last_folder == "") { 366 file_chooser.set_current_folder_file (font.get_folder ()); 367 } else { 368 file_chooser.set_current_folder_file (File.new_for_path (last_folder)); 369 } 370 } catch (GLib.Error e) { 371 stderr.printf (e.message); 372 } 373 374 if (file_chooser.run () == ResponseType.ACCEPT) { 375 GlyphCanvas.redraw (); 376 fn = file_chooser.get_filename (); 377 } 378 379 file_chooser.destroy (); 380 381 if (fn != null) { 382 i = ((!) fn).last_index_of ("/"); 383 if (i > -1) { 384 last_folder = ((!) fn).substring (0, i); 385 Preferences.set ("last_folder", @"$last_folder"); 386 } 387 } 388 389 return fn; 390 } 391 392 public bool convert_to_png (string from, string to) { 393 Pixbuf pixbuf; 394 string folder; 395 int i; 396 397 try { 398 i = to.last_index_of ("/"); 399 if (i != -1) { 400 folder = to.substring (0, i); 401 DirUtils.create (folder, 0xFFFFFF); 402 } 403 404 pixbuf = new Pixbuf.from_file (from); 405 pixbuf.save (to, "png"); 406 } catch (GLib.Error e) { 407 warning (e.message); 408 return false; 409 } 410 411 return true; 412 } 413 414 public void run_background_thread (Task t) { 415 unowned Thread<void*> bg; 416 417 MenuTab.start_background_thread (); 418 background_task = t; 419 420 try { 421 bg = Thread.create<void*> (this.background_thread, true); 422 } catch (GLib.Error e) { 423 warning (e.message); 424 } 425 } 426 427 public void run_non_blocking_background_thread (Task t) { 428 unowned Thread<void*> bg; 429 430 try { 431 bg = Thread.create<void*> (t.perform_task, true); 432 } catch (GLib.Error e) { 433 warning (e.message); 434 } 435 } 436 437 public void* background_thread () { 438 background_task.run (); 439 MenuTab.stop_background_thread (); 440 return null; 441 } 442 443 /** Run export in a background thread. */ 444 public void export_font () { 445 unowned Thread<void*> export_thread; 446 447 MenuTab.start_background_thread (); 448 449 try { 450 export_thread = Thread.create<void*> (this.export_thread, true); 451 } catch (GLib.Error e) { 452 warning (e.message); 453 } 454 } 455 456 public void* export_thread () { 457 IdleSource idle = new IdleSource (); 458 459 ExportCallback.export_fonts (); 460 MenuTab.stop_background_thread (); 461 MenuTab.signal_file_exported (); 462 463 idle.set_callback (() => { 464 Notify.Notification export_notification; 465 export_notification = new Notify.Notification ("BirdFont", t_("Your fonts have been exported."), null); 466 export_notification.show (); 467 return false; 468 }); 469 idle.attach (null); 470 471 return null; 472 } 473 474 /** Load font in a background thread. */ 475 public void load () { 476 unowned Thread<void*> thread; 477 478 MenuTab.start_background_thread (); 479 480 try { 481 thread = Thread.create<void*> (this.loading_thread, true); 482 } catch (GLib.Error e) { 483 warning (e.message); 484 } 485 } 486 487 public void* loading_thread () { 488 BirdFont.get_current_font ().load (); 489 MenuTab.stop_background_thread (); 490 MenuTab.signal_file_loaded (); 491 return null; 492 } 493 494 /** Save font in a background thread. */ 495 public void save () { 496 unowned Thread<void*> thread; 497 498 MenuTab.start_background_thread (); 499 500 try { 501 thread = Thread.create<void*> (this.saving_thread, true); 502 } catch (GLib.Error e) { 503 warning (e.message); 504 } 505 } 506 507 public void* saving_thread () { 508 BirdFont.get_current_font ().save (); 509 MenuTab.stop_background_thread (); 510 MenuTab.signal_file_saved (); 511 return null; 512 } 513 514 public void load_background_image () { 515 unowned Thread<void*> thread; 516 517 MenuTab.start_background_thread (); 518 519 try { 520 thread = Thread.create<void*> (this.background_image_thread, true); 521 } catch (GLib.Error e) { 522 warning (e.message); 523 } 524 } 525 526 public void* background_image_thread () { 527 BackgroundTool.load_background_image (); 528 MenuTab.stop_background_thread (); 529 return null; 530 } 531 532 public bool can_export () { 533 return true; 534 } 535 536 public void set_cursor (int visible) { 537 if (visible != NativeWindow.VISIBLE) { 538 get_window ().set_cursor (new Cursor (CursorType.BLANK_CURSOR)); 539 } else { 540 get_window ().set_cursor (new Cursor (CursorType.ARROW)); 541 } 542 } 543 544 public double get_screen_scale () { 545 string? scale = Environment.get_variable ("GDK_SCALE"); 546 double factor; 547 548 if (scale == null) { 549 return 1; 550 } 551 552 if (double.try_parse ((!) scale, out factor)) { 553 return factor; 554 } 555 556 return 1; 557 } 558 } 559 560 class TabbarCanvas : DrawingArea { 561 TabBar tabbar; 562 563 public TabbarCanvas (TabBar tb) { 564 tabbar = tb; 565 566 add_events (EventMask.BUTTON_PRESS_MASK | EventMask.POINTER_MOTION_MASK | EventMask.LEAVE_NOTIFY_MASK); 567 568 motion_notify_event.connect ((t, e)=> { 569 Gtk.Allocation alloc; 570 tabbar.motion (e.x, e.y); 571 get_allocation (out alloc); 572 queue_draw_area (0, 0, alloc.width, alloc.height); 573 return true; 574 }); 575 576 button_press_event.connect ((t, e)=> { 577 Gtk.Allocation alloc; 578 get_allocation (out alloc); 579 GtkWindow.reset_modifier (e.state); 580 tabbar.select_tab_click (e.x, e.y, alloc.width, alloc.height); 581 queue_draw_area (0, 0, alloc.width, alloc.height); 582 return true; 583 }); 584 585 draw.connect ((t, e)=> { 586 Gtk.Allocation alloc; 587 Context cr; 588 589 cr = cairo_create (get_window ()); 590 get_allocation (out alloc); 591 592 tabbar.draw (cr, alloc.width, alloc.height); 593 return true; 594 }); 595 596 tabbar.signal_tab_selected.connect ((t) => { 597 Gtk.Allocation alloc; 598 get_allocation (out alloc); 599 queue_draw_area (0, 0, alloc.width, alloc.height); 600 }); 601 602 tabbar.redraw_tab_bar.connect ((x, y, w, h) => { 603 queue_draw_area (x, y, w, h); 604 }); 605 606 set_size_request (20, 38); 607 } 608 609 } 610 611 class ToolboxCanvas : DrawingArea { 612 Toolbox tb; 613 614 public ToolboxCanvas (Toolbox toolbox) { 615 tb = toolbox; 616 617 realize.connect (() => { 618 Gtk.Allocation allocation; 619 get_allocation (out allocation); 620 Toolbox.allocation_width = allocation.width; 621 Toolbox.allocation_height = allocation.height; 622 Toolbox.redraw_tool_box (); 623 }); 624 625 tb.redraw.connect ((x, y, w, h) => { 626 queue_draw_area (x, y, w, h); 627 }); 628 629 button_press_event.connect ((se, e)=> { 630 if (e.type == EventType.2BUTTON_PRESS) { 631 tb.double_click (e.button, e.x, e.y); 632 } else { 633 tb.press (e.button, e.x, e.y); 634 } 635 return true; 636 }); 637 638 button_release_event.connect ((se, e)=> { 639 tb.release (e.button, e.x, e.y); 640 return true; 641 }); 642 643 motion_notify_event.connect ((sen, e)=> { 644 // FIXME: e.y is two pixels off in GTK under Gnome 645 tb.move (e.x, e.y); 646 return true; 647 }); 648 649 draw.connect ((t, e)=> { 650 Gtk.Allocation allocation; 651 get_allocation (out allocation); 652 653 Context cw = cairo_create(get_window()); 654 Toolbox.allocation_width = allocation.width; 655 Toolbox.allocation_height = allocation.height; 656 tb.draw (allocation.width, allocation.height, cw); 657 658 return true; 659 }); 660 661 scroll_event.connect ((t, e)=> { 662 if (e.direction == Gdk.ScrollDirection.UP) { 663 tb.scroll_up (e.x, e.y); 664 } else if (e.direction == Gdk.ScrollDirection.DOWN) { 665 tb.scroll_down (e.x, e.y); 666 } 667 return true; 668 }); 669 670 add_events (EventMask.BUTTON_PRESS_MASK | EventMask.BUTTON_RELEASE_MASK | EventMask.POINTER_MOTION_MASK | EventMask.LEAVE_NOTIFY_MASK | EventMask.SCROLL_MASK); 671 672 set_size_request (212, 100); 673 674 leave_notify_event.connect ((t, e)=> { 675 tb.reset_active_tool (); 676 return true; 677 }); 678 679 } 680 } 681 682 public class GlyphCanvasArea : DrawingArea { 683 GlyphCanvas glyph_canvas; 684 WidgetAllocation alloc = new WidgetAllocation (); 685 uint32 last_release = 0; 686 uint32 last_press = 0; 687 688 public GlyphCanvasArea (GlyphCanvas gc) { 689 int event_flags; 690 691 glyph_canvas = gc; 692 693 event_flags = EventMask.BUTTON_PRESS_MASK; 694 event_flags |= EventMask.BUTTON_RELEASE_MASK; 695 event_flags |= EventMask.POINTER_MOTION_MASK; 696 event_flags |= EventMask.LEAVE_NOTIFY_MASK; 697 event_flags |= EventMask.SCROLL_MASK; 698 699 add_events (event_flags); 700 701 bool button_down = false; 702 703 glyph_canvas.signal_redraw_area.connect ((x, y, w, h) => { 704 queue_draw_area ((int)x, (int)y, (int)w, (int)h); 705 }); 706 707 draw.connect ((t, e)=> { 708 Gtk.Allocation allocation; 709 get_allocation (out allocation); 710 711 alloc = new WidgetAllocation (); 712 713 alloc.width = allocation.width; 714 alloc.height = allocation.height; 715 alloc.x = allocation.x; 716 alloc.y = allocation.y; 717 718 Context cw = cairo_create (get_window()); 719 720 Surface s = new Surface.similar (cw.get_target (), Cairo.Content.COLOR_ALPHA, alloc.width, alloc.height); 721 Context c = new Context (s); 722 723 TabContent.draw (alloc, c); 724 725 cw.save (); 726 cw.set_source_surface (c.get_target (), 0, 0); 727 cw.paint (); 728 cw.restore (); 729 730 return true; 731 }); 732 733 button_press_event.connect ((t, e)=> { 734 GtkWindow.reset_modifier (e.state); 735 736 if (button_down) { 737 warning (@"Button already is down. $(e.button)"); 738 } 739 740 if (e.time < last_press) { 741 warning ("Discarding event."); 742 return true; 743 } 744 745 last_press = e.time; 746 747 if (e.type == EventType.BUTTON_PRESS) { 748 TabContent.button_press (e.button, e.x, e.y); 749 button_down = true; 750 } else if (e.type == EventType.2BUTTON_PRESS) { 751 TabContent.double_click (e.button, e.x, e.y); 752 } 753 754 return true; 755 }); 756 757 button_release_event.connect ((t, e)=> { 758 if (!button_down) { 759 warning (@"Button is not down $(e.button)"); 760 } 761 762 if (e.time < last_release) { 763 warning ("Discarding event."); 764 return true; 765 } 766 767 if (e.type == EventType.BUTTON_RELEASE) { 768 TabContent.button_release ((int) e.button, e.x, e.y); 769 last_release = e.time; 770 button_down = false; 771 } 772 773 return true; 774 }); 775 776 motion_notify_event.connect ((t, e)=> { 777 TabContent.motion_notify (e.x, e.y); 778 return true; 779 }); 780 781 scroll_event.connect ((t, e)=> { 782 if (e.direction == Gdk.ScrollDirection.UP) { 783 TabContent.scroll_wheel_up (e.x, e.y); 784 } else if (e.direction == Gdk.ScrollDirection.DOWN) { 785 TabContent.scroll_wheel_down (e.x, e.y) ; 786 } 787 788 TabContent.button_release (2, e.x, e.y); 789 return true; 790 }); 791 792 can_focus = true; 793 } 794 } 795 796 } 797