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