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