Clone in a terminal:
GtkWindow.vala
/*
    Copyright (C) 2012 Johan Mattsson

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

using Cairo;
using Gtk;
using Gdk;
using Supplement;
using WebKit;

namespace Supplement {

public class GtkWindow : Gtk.Window, NativeWindow {

	HBox list_box;
	HBox canvas_box;
	
	WebView html_canvas;
	ScrolledWindow html_box;

	VBox tab_box;

	static DrawingArea margin_bottom;
	static DrawingArea margin_right;
	
	GlyphCanvasArea glyph_canvas_area;
	
	static List<uint> key_pressed = new List<uint> ();
	
	public GtkWindow (string title) {
		// set_title (title);
	}
	
	public void init () {
		margin_bottom = new DrawingArea ();
		margin_right = new DrawingArea ();
	
		margin_bottom.set_size_request (0, 0);
		margin_right.set_size_request (0, 0);
		
		delete_event.connect (quit);
		
		set_size_and_position ();
		
		html_canvas = new WebView ();
		WebKit.set_cache_model (CacheModel.DOCUMENT_VIEWER);
		html_canvas.get_settings ().enable_default_context_menu = false;
				
		html_canvas.title_changed.connect ((p, s) => {
			webkit_callback (s);
		});
		
		html_box = new ScrolledWindow (null, null);
		html_box.set_policy (PolicyType.NEVER, PolicyType.AUTOMATIC);
		html_box.add (html_canvas);
		html_canvas.set_editable (true);
		
		MainWindow.get_tab_bar ().signal_tab_selected.connect ((f, tab) => {
			bool n;
			File layout_dir;
			File layout_uri;
			string uri = "";
			FontDisplay fd = tab.get_display ();
			
			MainWindow.glyph_canvas.set_current_glyph (fd);
			n = fd.is_html_canvas ();
			
			if (n) {
				layout_dir = FontDisplay.find_layout_dir ();
				uri = fd.get_uri ();
				
				if (uri == "") {
					layout_uri = layout_dir.get_child (fd.get_html_file ());
					uri = fd.path_to_uri ((!) layout_uri.get_path ());
				}
		
				if (fd.get_html () == "") {
					
					if (fd.get_name () == "Preview") {
						// hack: force webkit to ignore cache in preview					
						html_box.set_visible (false);
						glyph_canvas_area.set_visible (true);
												
						try {
							Preview preview = (Preview) fd;
							DataInputStream dis = new DataInputStream (preview.get_html_file ().read ());
							string? line;
							StringBuilder sb = new StringBuilder ();
							uint rid = Random.next_int ();
							Font font = Supplement.get_current_font ();
							
							File preview_directory = Supplement.get_preview_directory ();
							
							File f_ttf = font.get_folder ().get_child (@"$(font.get_name ()).ttf");
							File f_eot = font.get_folder ().get_child (@"$(font.get_name ()).eot");
							File f_svg = font.get_folder ().get_child (@"$(font.get_name ()).svg");

							if (f_ttf.query_exists ()) {
								f_ttf.delete ();
							}
								
							if (f_eot.query_exists ()) {
								f_eot.delete ();
							}
							
							if (f_svg.query_exists ()) {
								f_svg.delete ();
							}
							
							ExportTool.export_ttf_font ();							
							ExportTool.export_svg_font ();
							
							File r_ttf = preview_directory.get_child (@"$(font.get_name ())$rid.ttf");
							File r_svg = preview_directory.get_child (@"$(font.get_name ())$rid.svg");
							
							if (Supplement.win32) {
								f_ttf.copy (r_ttf, FileCopyFlags.NONE);
							}
							
							f_svg.copy (r_svg, FileCopyFlags.NONE);

							while ((line = dis.read_line (null)) != null) {
								line = ((!) line).replace (@"$(font.get_name ()).ttf", @"$(FontDisplay.path_to_uri ((!) f_ttf.get_path ()))?$rid");
								line = ((!) line).replace (@"$(font.get_name ()).eot", @"$(FontDisplay.path_to_uri ((!) f_eot.get_path ()))?$rid");
								line = ((!) line).replace (@"$(font.get_name ()).svg", @"$(FontDisplay.path_to_uri ((!) f_svg.get_path ()))?$rid");
								sb.append ((!) line);
							}
					
							html_canvas.load_html_string (sb.str, uri);							
						} catch (Error e) {
							warning (e.message);
							warning ("Failed to load html into canvas.");
						}
						
					} else {
						// normal way to load a uri for all other pages
						html_canvas.load_uri (uri);
						html_canvas.reload_bypass_cache ();			
					}
						
				} else {
					html_canvas.load_html_string (fd.get_html (), uri);
				}
				
				html_box.set_visible (n);
				glyph_canvas_area.set_visible (!n);
			} else {
				html_box.set_visible (false);
				glyph_canvas_area.set_visible (true);
			}
		});

		// Hide this canvas when window is realized and flip canvas 
		// visibility in tab selection signal.
		html_canvas.expose_event.connect ((t, e) => {
			glyph_canvas_area.set_visible (false);
			return false;
		});
				
		MainWindow.tabs.add_unique_tab (MainWindow.menu_tab, 60, true);
		
		MainWindow.tabs.select_tab_name ("Menu");

		glyph_canvas_area = new GlyphCanvasArea (MainWindow.glyph_canvas);

		canvas_box = new HBox (false, 0);
		canvas_box.pack_start (glyph_canvas_area, true, true, 0);
		canvas_box.pack_start (html_box, true, true, 0);
		
		tab_box = new VBox (false, 0);
		tab_box.pack_start (new TabbarCanvas (MainWindow.tabs), false, false, 0);	
		
		tab_box.pack_start (canvas_box, true, true, 0);

		tab_box.pack_start (new TooltipCanvas (MainWindow.tool_tip), false, false, 0);
		tab_box.pack_start (margin_bottom, false, false, 0);
		
		list_box = new HBox (false, 0);
		list_box.pack_start (tab_box, true, true, 0);
		list_box.pack_start (new ToolboxCanvas (MainWindow.tools), false, false, 0);
		list_box.pack_start (margin_right, false, false, 0);

		VBox vbox = new VBox (false, 0);
		
		vbox.pack_start(create_menu (), false, false, 0);
		vbox.pack_start(list_box, true, true, 0);

		add (vbox);
		
		try {
			set_icon_from_file ((!) Icons.find_icon ("window_icon.png").get_path ());
		} catch (GLib.Error e) {
			warning (e.message);
		}

		key_press_event.connect ((t, event) => {
			MainWindow window = MainWindow.get_singleton ();
			FontDisplay fd = window.glyph_canvas.current_display;
			
			if (fd is Glyph) {
				window.tools.key_press (event.keyval);
			}
			
			window.glyph_canvas.key_press (event.keyval);
			return false;
		});
		
		key_release_event.connect ((t, event) => {
			MainWindow window = MainWindow.get_singleton ();
			if (window.glyph_canvas is Glyph) {
				window.glyph_canvas.key_release (event.keyval);
			}
			return false;
		});
		
		show_all ();
	}

	MenuBar create_menu () {
		MenuBar menubar = new MenuBar ();
		Gtk.Menu file_menu = new Gtk.Menu ();
		Gtk.Menu edit_menu = new Gtk.Menu ();
		Gtk.Menu tab_menu = new Gtk.Menu ();
		Gtk.Menu tool_menu = new Gtk.Menu ();

		AccelGroup accel_group = new Gtk.AccelGroup();
		add_accel_group (accel_group);
		
		// File
		Gtk.MenuItem new_item = new Gtk.MenuItem.with_mnemonic (_("_New"));
		file_menu.append (new_item);
		new_item.activate.connect (() => { MenuTab.new_file (); });
		new_item.add_accelerator ("activate", accel_group, 'N', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);

		Gtk.MenuItem load_item = new Gtk.MenuItem.with_mnemonic (_("_Open"));
		file_menu.append (load_item);
		load_item.activate.connect (() => { MenuTab.load (); });
		load_item.add_accelerator ("activate", accel_group, 'O', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);

		Gtk.MenuItem save_item = new Gtk.MenuItem.with_mnemonic (_("_Save"));
		file_menu.append (save_item);
		save_item.activate.connect (() => { MenuTab.save (); });
		save_item.add_accelerator ("activate", accel_group, 'S', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);

		Gtk.MenuItem save_as_item = new Gtk.MenuItem.with_mnemonic (_("Save _as"));
		file_menu.append (save_as_item);
		save_as_item.activate.connect (() => { MenuTab.save_as (); });
				
		Gtk.MenuItem export_item = new Gtk.MenuItem.with_mnemonic (_("_Export"));
		file_menu.append (export_item);
		export_item.activate.connect (() => { ExportTool.export_all (); });
		export_item.add_accelerator ("activate", accel_group, 'E', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);

		Gtk.MenuItem preview_item = new Gtk.MenuItem.with_mnemonic(_("_Preview"));
		file_menu.append (preview_item);
		preview_item.activate.connect (() => { MenuTab.preview (); });
		preview_item.add_accelerator ("activate", accel_group, 'P', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);

		Gtk.MenuItem kerning_item = new Gtk.MenuItem.with_mnemonic (_("_Kerning"));
		file_menu.append (kerning_item);
		kerning_item.activate.connect (() => { MenuTab.show_kerning_context (); });
		kerning_item.add_accelerator ("activate", accel_group, 'K', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);

		Gtk.MenuItem quit_item = new Gtk.MenuItem.with_mnemonic (_("_Quit"));
		file_menu.append (quit_item);
		quit_item.activate.connect (() => { quit(); });

		// Edit
		Gtk.MenuItem undo_item = new Gtk.MenuItem.with_mnemonic (_("_Undo"));
		edit_menu.append (undo_item);
		undo_item.activate.connect (() => { MainWindow.get_current_display ().undo (); });	
		undo_item.add_accelerator ("activate", accel_group, 'Z', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);
		
		Gtk.MenuItem copy_item = new Gtk.MenuItem.with_mnemonic (_("_Copy"));
		edit_menu.append (copy_item);
		copy_item.activate.connect (() => { ClipTool.copy (); });		
		copy_item.add_accelerator ("activate", accel_group, 'C', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);

		Gtk.MenuItem paste_item = new Gtk.MenuItem.with_mnemonic (_("_Paste"));
		edit_menu.append (paste_item);
		paste_item.activate.connect (() => { ClipTool.paste (); });	
		paste_item.add_accelerator ("activate", accel_group, 'V', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);
		
		// Tab
		Gtk.MenuItem next_tab_item = new Gtk.MenuItem.with_mnemonic (_("_Next tab"));
		tab_menu.append (next_tab_item);
		next_tab_item.activate.connect (() => { 
			TabBar tb = MainWindow.get_tab_bar ();
			int n = tb.get_selected () + 1;
			
			if (!(0 <= n < tb.get_length ())) {
				return;
			}
			
			tb.select_tab (n);
		});	

		Gtk.MenuItem prevoius_tab_item = new Gtk.MenuItem.with_mnemonic (_("_Previous tab"));
		tab_menu.append (prevoius_tab_item);
		prevoius_tab_item.activate.connect (() => { 
			TabBar tb = MainWindow.get_tab_bar ();
			int n = tb.get_selected () - 1;

			if (!(0 <= n < tb.get_length ())) {
				return;
			}
			
			tb.select_tab (n);
		});					

		Gtk.MenuItem close_tab_item = new Gtk.MenuItem.with_mnemonic (_("_Close tab"));
		tab_menu.append (close_tab_item);
		close_tab_item.activate.connect (() => { 
			TabBar tb = MainWindow.get_tab_bar ();
			int n = tb.get_selected ();

			if (!(0 <= n < tb.get_length ())) {
				return;
			}
			
			tb.close_tab (n);
		});	
		close_tab_item.add_accelerator ("activate", accel_group, 'W', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);

		// Tool
		Gtk.MenuItem pen_item = new Gtk.MenuItem.with_mnemonic (_("_Create path"));
		tool_menu.append (pen_item);
		pen_item.activate.connect (() => { 
			Toolbox.select_tool_by_name ("pen_tool");
		});
		pen_item.add_accelerator ("activate", accel_group, ',', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);

		Gtk.MenuItem zoom_item = new Gtk.MenuItem.with_mnemonic (_("_Zoom"));
		tool_menu.append (zoom_item);
		zoom_item.activate.connect (() => { 
			Toolbox.select_tool_by_name ("zoom_tool");
		});

		Gtk.MenuItem counter_item = new Gtk.MenuItem.with_mnemonic (_("_Create counter path"));
		tool_menu.append (counter_item);
		counter_item.activate.connect (() => { 
			Toolbox.select_tool_by_name ("cut");
		});
		counter_item.add_accelerator ("activate", accel_group, 'U', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);

		Gtk.MenuItem move_item = new Gtk.MenuItem.with_mnemonic (_("_Move"));
		tool_menu.append (move_item);
		move_item.activate.connect (() => { 
			Toolbox.select_tool_by_name ("move");
		});
		move_item.add_accelerator ("activate", accel_group, 'M', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);

		Gtk.MenuItem full_unicode_item = new Gtk.MenuItem.with_mnemonic (_("Show _full unicode characters set"));
		tool_menu.append (full_unicode_item);
		full_unicode_item.activate.connect (() => { 
			Toolbox.select_tool_by_name ("utf_8");
		});
		full_unicode_item.add_accelerator ("activate", accel_group, 'F', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);

		Gtk.MenuItem default_charset_item = new Gtk.MenuItem.with_mnemonic (_("Show de_fault characters set"));
		tool_menu.append (default_charset_item);
		default_charset_item.activate.connect (() => { 
			Toolbox.select_tool_by_name ("custom_character_set");
		});
		default_charset_item.add_accelerator ("activate", accel_group, 'R', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);

		Gtk.MenuItem avalilable_characters_item = new Gtk.MenuItem.with_mnemonic (_("Show characters in font"));
		tool_menu.append (avalilable_characters_item);
		avalilable_characters_item.activate.connect (() => { 
			Toolbox.select_tool_by_name ("available_characters");
		});
		avalilable_characters_item.add_accelerator ("activate", accel_group, 'D', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);

		Gtk.MenuItem add_grid_item = new Gtk.MenuItem.with_mnemonic (_("Add new _grid item"));
		tool_menu.append (add_grid_item);
		add_grid_item.activate.connect (() => { 
			MainWindow.get_toolbox ().add_new_grid ();
		});

		Gtk.MenuItem remove_grid_item = new Gtk.MenuItem.with_mnemonic (_("Remove gr_id item"));
		tool_menu.append (remove_grid_item);
		remove_grid_item.activate.connect (() => { 
			MainWindow.get_toolbox ().remove_current_grid ();
		});
		
		Gtk.MenuItem zoom_in_item = new Gtk.MenuItem.with_mnemonic (_("_Zoom in"));
		tool_menu.append (zoom_in_item);
		zoom_in_item.activate.connect (() => { 
			Toolbox.select_tool_by_name ("zoom_in");
		});
		zoom_in_item.add_accelerator ("activate", accel_group, '+', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);

		Gtk.MenuItem zoom_out_item = new Gtk.MenuItem.with_mnemonic (_("Zoom _out"));
		tool_menu.append (zoom_out_item);
		zoom_out_item.activate.connect (() => { 
			Toolbox.select_tool_by_name ("zoom_out");
		});
		zoom_out_item.add_accelerator ("activate", accel_group, '-', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);

		Gtk.MenuItem zoom_1_1_item = new Gtk.MenuItem.with_mnemonic (_("Zoom to _scale 1:1"));
		tool_menu.append (zoom_1_1_item);
		zoom_1_1_item.activate.connect (() => { 
			Toolbox.select_tool_by_name ("zoom_1_1");
		});
		zoom_1_1_item.add_accelerator ("activate", accel_group, '0', Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);

		// Add menus
		Gtk.MenuItem file_launcher = new Gtk.MenuItem.with_mnemonic (_("_File"));
		file_launcher.set_submenu (file_menu);

		Gtk.MenuItem edit_launcher = new Gtk.MenuItem.with_mnemonic (_("_Edit"));
		edit_launcher.set_submenu (edit_menu);

		Gtk.MenuItem tab_launcher = new Gtk.MenuItem.with_mnemonic (_("_Tab"));
		tab_launcher.set_submenu (tab_menu);

		Gtk.MenuItem tool_launcher = new Gtk.MenuItem.with_mnemonic (_("T_ool"));
		tool_launcher.set_submenu (tool_menu);
						
		menubar.append (file_launcher);
		menubar.append (edit_launcher);
		menubar.append (tab_launcher);
		menubar.append (tool_launcher);
		
		return menubar;	
	}

	internal void toggle_expanded_margin_bottom () {
		int w, h;
		margin_bottom.get_size_request (out w, out h);
		
		if (h == 1) h = 2; 
		else h = 1;
		
		margin_bottom.set_size_request (w, h);
	}
	
	internal void toggle_expanded_margin_right () {	
		int w, h;
		margin_right.get_size_request (out w, out h);

		if (w == 1) w = 2; 
		else w = 1;

		margin_right.set_size_request (w, h);
	}

	public static void hide_cursor () {
		/*
		Pixmap pixmap = new Pixmap (null, 1, 1, 1);
		Color color = { 0, 0, 0, 0 };
		Cursor cursor = new Cursor.from_pixmap (pixmap, pixmap, color, color, 0, 0);
		*/
		
		// Fixa: But why?
		// (Supplement.exe:1300): Gdk-CRITICAL **: gdk_window_set_cursor: assertion `GDK_IS_WINDOW (window)' failed		
		// singleton.frame.set_cursor (cursor);
	}

	public void update_window_size () {
		int w, h;
		get_size (out w, out h);
		
		Preferences.set ("window_width", @"$w");
		Preferences.set ("window_height", @"$h");
	}
		
	private void set_size_and_position () {
		int w = Preferences.get_window_width ();
		int h = Preferences.get_window_height ();
		
		set_default_size (w, h);
		// move (10, 240);
	}
	
	public bool quit () {
		bool added;
		SaveDialog s = new SaveDialog ();
		
		if (Supplement.get_current_font ().is_modified ()) {
			added = MainWindow.get_tab_bar ().add_unique_tab (s, 50);
		} else {
			added = false;
		}
		
		if (!added) {
			Supplement.get_current_font ().save_backup ();
			Gtk.main_quit ();
		}
		
		s.finished.connect (() => {
			Supplement.get_current_font ().delete_backup ();
			Gtk.main_quit ();
		});
		
		return true;
	}
	
	public string? file_chooser (string title) {
		string? fn = null;
		FileChooserDialog file_chooser = new FileChooserDialog (title, this, FileChooserAction.SAVE, Stock.CANCEL, ResponseType.CANCEL, Stock.SAVE, ResponseType.ACCEPT);
		Font font = Supplement.get_current_font ();
		
		try {
			file_chooser.set_current_folder_file (font.get_folder ());
		} catch (GLib.Error e) {
			stderr.printf (e.message);
		}
		
		if (file_chooser.run () == ResponseType.ACCEPT) {	
			MainWindow.get_glyph_canvas ().redraw ();
			fn = file_chooser.get_filename ();
		}

		file_chooser.destroy ();
		
		return fn;
	}	
}

class TabbarCanvas : DrawingArea {
	TabBar tabbar;
	
	public TabbarCanvas (TabBar tb) {		
		tabbar = tb;

		set_extension_events (ExtensionMode.CURSOR | EventMask.POINTER_MOTION_MASK);
		add_events (EventMask.BUTTON_PRESS_MASK | EventMask.POINTER_MOTION_MASK | EventMask.LEAVE_NOTIFY_MASK);
	  
		motion_notify_event.connect ((t, e)=> {
			Gtk.Allocation alloc;
			tabbar.motion (e.x, e.y);
			get_allocation (out alloc);
			queue_draw_area (0, 0, alloc.width, alloc.height);
			return true;
		});	
				
		button_press_event.connect ((t, e)=> {
			Gtk.Allocation alloc;
			get_allocation (out alloc);
			tabbar.select_tab_click (e.x, e.y, alloc.width, alloc.height);
			queue_draw_area (0, 0, alloc.width, alloc.height);
			return true;
		});

		expose_event.connect ((t, e)=> {
			Context cr = cairo_create (get_window ());

			Gtk.Allocation alloc;
			get_allocation (out alloc);

			tabbar.draw (cr, alloc.width, alloc.height);
			return true;
		});
	
		tabbar.signal_tab_selected.connect ((t) => {
			Gtk.Allocation alloc;
			get_allocation (out alloc);
			queue_draw_area (0, 0, alloc.width, alloc.height);	
		});
		
		set_size_request (20, 25);
	}
	
}

class ToolboxCanvas : DrawingArea {
	Toolbox tb;
	
	public ToolboxCanvas (Toolbox toolbox) {
		tb = toolbox;
		
		tb.redraw.connect ((x, y, w, h) => {
			queue_draw_area (x, y, w, h);
		});
		
		button_press_event.connect ((se, e)=> {
			tb.press (e.button, e.x, e.y);
			return true;
		});	
				
		button_release_event.connect ((se, e)=> {
			tb.release (e.button, e.x, e.y);
			return true;
		});

		motion_notify_event.connect ((sen, e)=> {
			tb.move (e.x, e.y);
			return true;
		});
		
		expose_event.connect ((t, e)=> {
			Gtk.Allocation allocation;
			get_allocation (out allocation);
			
			Context cw = cairo_create(get_window());
			tb.allocation_width = allocation.width;
			tb.allocation_height = allocation.height;
			tb.draw (allocation.width, allocation.height, cw);
			
			return true;
		});
		
		add_events (EventMask.BUTTON_PRESS_MASK | EventMask.BUTTON_RELEASE_MASK | EventMask.POINTER_MOTION_MASK | EventMask.LEAVE_NOTIFY_MASK);

		set_size_request (160, 100);

		leave_notify_event.connect ((t, e)=> {
			tb.reset_active_tool ();
			return true;
		});
		
	}
}

public class GlyphCanvasArea : DrawingArea  {
	GlyphCanvas glyph_canvas;
	Gtk.Allocation alloc;

	/* State on linux */
	public static const int L_SHIFT = 17;
	public static const int L_CTRL = 20;
	public static const int L_SHIFT_CTRL = 21;

	/* State on windows */
	public static const int W_SHIFT = 1;
	public static const int W_CTRL = 4;
	public static const int W_SHIFT_CTRL = 5;
		
	public GlyphCanvasArea (GlyphCanvas gc) {
		int event_flags;
		
		glyph_canvas = gc;
		
		event_flags = EventMask.BUTTON_PRESS_MASK;
		event_flags |= EventMask.BUTTON_RELEASE_MASK;
		event_flags |= EventMask.POINTER_MOTION_MASK;
		event_flags |= EventMask.LEAVE_NOTIFY_MASK;
		event_flags |= EventMask.SCROLL_MASK;
		
		add_events (event_flags);

		glyph_canvas.signal_redraw_area.connect ((x, y, w, h) => {
			queue_draw_area ((int)x, (int)y, (int)w, (int)h);
		});

		expose_event.connect ((t, e)=> {		
			Gtk.Allocation allocation;
			get_allocation (out allocation);
			
			glyph_canvas.allocation.width = allocation.width;
			glyph_canvas.allocation.height = allocation.height;
			glyph_canvas.allocation.y = allocation.x;
			glyph_canvas.allocation.y = allocation.y;
				
			if (unlikely (allocation != alloc && alloc.width != 0)) {
				// Set size of glyph widget to an even number and notify 
				// set new allocation for glyph
				bool ug = false;
				
				if (allocation.height % 2 != 0) {
					MainWindow.native_window.toggle_expanded_margin_bottom ();
					ug = true;
				}
				
				if (allocation.width % 2 != 0) {
					MainWindow.native_window.toggle_expanded_margin_right ();
					ug = true;
				}					
				
				if (ug) {
					//redraw_area (1, 1, 2, 2);
				} else if (unlikely (allocation.width % 2 != 0 || allocation.height % 2 != 0)) {
					warning (@"\nGlyph canvas is not divisible by two.\nWidth: $(allocation.width)\nHeight: $(allocation.height)");
				}
				
				Supplement.current_glyph.resized ();
			}
			
			alloc = allocation;
			
			Context cw = cairo_create (get_window());
			
			Surface s = new Surface.similar (cw.get_target (), Cairo.Content.COLOR_ALPHA, allocation.width, allocation.height);
			Context c = new Context (s); 

			glyph_canvas.current_display.draw (glyph_canvas.allocation, c);

			cw.save ();
			cw.set_source_surface (c.get_target (), 0, 0);
			cw.paint ();
			cw.restore ();
			
			return true;
		});

		button_press_event.connect ((t, e)=> {
			set_modifier ((int) e.state);
			
			if (e.type == EventType.BUTTON_PRESS) {
				glyph_canvas.current_display.button_press (e.button, e.x, e.y);	
			} else if (e.type == EventType.2BUTTON_PRESS) {
				glyph_canvas.current_display.double_click (e.button, e.x, e.y);
			}
				
			return true;
		});
		
		button_release_event.connect ((t, e)=> {
			set_modifier ((int) e.state);
			glyph_canvas.current_display.button_release ((int) e.button, e.x, e.y);
			return true;
		});
		
		motion_notify_event.connect ((t, e)=> {
			glyph_canvas.current_display.motion_notify (e.x, e.y);		
			return true;
		});
		
		scroll_event.connect ((t, e)=> {
			set_modifier ((int) e.state);
			
			if (e.direction == Gdk.ScrollDirection.UP) {
				glyph_canvas.current_display.scroll_wheel_up (e.x, e.y);
			} else if (e.direction == Gdk.ScrollDirection.DOWN) {
				glyph_canvas.current_display.scroll_wheel_down (e.x, e.y);
			}
			
			glyph_canvas.current_display.button_release (2, e.x, e.y);
			return true;
		});		
	}

	static void set_modifier (int k) {
		switch (k) {
			case L_SHIFT:
				KeyBindings.set_modifier (SHIFT);
				break;
			case L_CTRL:
				KeyBindings.set_modifier (CTRL);
				break;
			case L_SHIFT_CTRL:
				KeyBindings.set_modifier (SHIFT | CTRL);
				break;
			case W_SHIFT:
				KeyBindings.set_modifier (SHIFT);
				break;
			case W_CTRL:
				KeyBindings.set_modifier (CTRL);
				break;
			case W_SHIFT_CTRL:
				KeyBindings.set_modifier (SHIFT | CTRL);
				break;

			default:
				KeyBindings.set_modifier (NONE);
				break;
		}		
	}
}

public class TooltipCanvas : DrawingArea {
	TooltipArea tooltip_area;
	
	public TooltipCanvas (TooltipArea ta) {
		tooltip_area = ta;

		expose_event.connect ((t, e)=> {
				Allocation allocation = {0, 0, 0, 0};
				Gtk.Allocation alloc;
				Context cr = cairo_create (get_window ());
				
				get_allocation (out alloc);

				allocation.width = alloc.width;
				allocation.height = alloc.height;
				allocation.y = alloc.x;
				allocation.y = alloc.y;
				
				tooltip_area.draw (cr, allocation);

				return true;
		});
		
		tooltip_area.redraw.connect (() => {
			Gtk.Allocation alloc;
			get_allocation (out alloc);
			queue_draw_area (0, 0, alloc.width, alloc.height);
		});
		
		set_size_request (10, 20);
	}
}

}

Contact

Email: johan.mattsson.m@gmail.com
Mastodon: typo.social/@birdfont
GitHub: johanmattssonm
LinkedIn: johan-mattsson-2ab324112