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