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