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

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

	This library 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 
	Lesser General Public License for more details.
*/

using Cairo;
using Math;

namespace BirdFont {

public class Toolbox : GLib.Object  {
	public static ToolCollection current_set; 
	
	public static HiddenTools hidden_tools;
	public static DrawingTools drawing_tools;
	public static KerningTools kerning_tools;
	public static PreviewTools preview_tools;
	public static OverviewTools overview_tools;
	public static BackgroundTools background_tools;	
	public static SpacingTools spacing_tools;
	public static SpacingClassTools spacing_class_tools;
	public static FileTools file_tools;
	public static ThemeTools theme_tools;
	
	public Tool press_tool;
	
	public signal void redraw (int x, int y, int w, int h);
	
	public static int allocation_width = 0;
	public static int allocation_height = 0;
		
	/** Scrolling with scroll wheel */
	bool scrolling_toolbox = false;
	
	/** Scroll with touch pad. */
	bool scrolling_touch = false;
	double scroll_y = 0;

	public Gee.ArrayList<ToolCollection> tool_sets;

	string? tool_tip = null;
	double tool_tip_x = 0;
	double tool_tip_y = 0;
	
	public signal void new_tool_set (Tab? tab);
	bool suppress_event = false;
	
	public Toolbox (GlyphCanvas glyph_canvas, TabBar tab_bar) {
		tool_sets = new Gee.ArrayList<ToolCollection> ();
		press_tool = new Tool (null);

		hidden_tools = new HiddenTools ();
		drawing_tools = new DrawingTools (glyph_canvas); 
		kerning_tools = new KerningTools ();
		preview_tools = new PreviewTools ();
		overview_tools = new OverviewTools ();
		background_tools = new BackgroundTools ();
		spacing_tools = new SpacingTools ();
		spacing_class_tools = new SpacingClassTools  ();
		file_tools = new FileTools ();
		theme_tools = new ThemeTools ();
		
		tool_sets.add (theme_tools);
		tool_sets.add (file_tools);
		tool_sets.add (hidden_tools);
		tool_sets.add (drawing_tools);
		tool_sets.add (kerning_tools);
		tool_sets.add (preview_tools);
		tool_sets.add (overview_tools);
		tool_sets.add (spacing_class_tools);
		tool_sets.add (background_tools);
		// the menu has all the file_tools commands, it will not be added here
		tool_sets.add (hidden_tools); // tools without buttons
		
		current_set = file_tools;
		current_set.selected ();
		
		tab_bar.signal_tab_selected.connect ((tab) => {
			string tab_name = tab.get_display ().get_name ();
			set_toolbox_from_tab (tab_name, tab);
			new_tool_set (tab);
		});
		
		update_expanders ();
	}

	public void set_suppress_event (bool e) {
		suppress_event = e;
	}

	public static DrawingTools get_drawing_tools () {
		return drawing_tools;
	}

	public void update_all_expanders () {
		foreach (ToolCollection tc in tool_sets) {
			tc.redraw ();
		}
	}
	
	public static void cache_all_tools () {
		Toolbox t = MainWindow.get_toolbox ();
		
		foreach (ToolCollection tc in t.tool_sets) {
			tc.cache ();
		}
	}

	public static void set_toolbox_from_tab (string tab_name, Tab? t = null) {		
		if (tab_name == "Spacing") {
			current_set = (ToolCollection) spacing_tools;
		} else if (tab_name == "Kerning") {
			current_set = (ToolCollection) kerning_tools;
		} else if (tab_name == "Preview") {
			current_set = (ToolCollection) preview_tools;
		} else if (tab_name == "Overview") {
			current_set = (ToolCollection) overview_tools;
		} else if (tab_name == "Backgrounds") {
			current_set = (ToolCollection) background_tools;
		} else if (tab_name == "SpacingClasses") {
			current_set = (ToolCollection) spacing_class_tools;
		} else if (tab_name == "Themes") {
			current_set = (ToolCollection) theme_tools;
		} else if (t != null && ((!) t).get_display () is GlyphTab) {
			current_set = (ToolCollection) drawing_tools;
		} else if (t != null && ((!) t).get_display () is Glyph) {
			warning ("Expecting GlyphTab instead of Glyph.");
			current_set = (ToolCollection) drawing_tools;
		} else {
			current_set = (ToolCollection) file_tools;
		}
		
		current_set.selected ();
		MainWindow.get_toolbox ().update_expanders ();
		redraw_tool_box ();
	}

	public static Tool get_move_tool () {
		return DrawingTools.move_tool;
	}
	
	public static void set_object_stroke (double width) {
		DrawingTools.object_stroke.set_value_round (width);
		redraw_tool_box ();
	}

	public static void set_allocation (int width, int height) {
		if (width != allocation_width || allocation_height != height) {
			allocation_width = width;
			allocation_height = height;
			Toolbox.redraw_tool_box ();
		}
	}
	
	public void press (uint button, double x, double y) {
		if (MenuTab.has_suppress_event () || suppress_event) {
			warn_if_test ("Event suppressed");
			return;
		}
		
		y -= current_set.scroll;
		
		foreach (Expander exp in current_set.get_expanders ()) {
			if (exp.visible) {
				foreach (Tool t in exp.tool) {
					if (t.tool_is_visible () && t.is_over (x, y)) {
						t.panel_press_action (t, button, x, y);
						press_tool = t;
					}
				}
			}
		}
		
		scrolling_touch = true;
		scroll_y = y;
	}
	
	public void release (uint button, double x, double y) {
		bool active;
		
		y -= current_set.scroll;
		
		if (MenuTab.has_suppress_event () || suppress_event) {
			warn_if_test ("Event suppressed");
			return;
		}
				
		foreach (Expander exp in current_set.get_expanders ()) {
			if (exp.visible) {	
				foreach (Tool t in exp.tool) {
					if (t.tool_is_visible ()) {
						active = t.is_over (x, y);
						
						if (active) {
							if (press_tool == t) {
								select_tool (t);
							}
						}
						
						t.panel_release_action (t, button, x, y);
					}
				}
			}
		}
		
		scrolling_touch = false;
	}

	public void double_click (uint button, double x, double y) {
		if (MenuTab.has_suppress_event () || suppress_event) {
			warn_if_test ("Event suppressed");
			return;
		}
		
		y -= current_set.scroll;
		
		foreach (Expander e in current_set.get_expanders ()) {
			if (e.visible) {
				foreach (Tool t in e.tool) {
					t.panel_double_click_action (t, button, x, y);
				}
			}
		}
	}

	public void scroll_wheel (double x, double y, double dx, double dy) {
		bool action = false;
		
		y -= current_set.scroll;
		
		if (MenuTab.has_suppress_event () || suppress_event) {
			warn_if_test ("Event suppressed");
			return;
		}
				
		if (!scrolling_toolbox) {	
			foreach (Expander exp in current_set.get_expanders ()) {
				if (exp.visible) {
					foreach (Tool t in exp.tool) {
						if (t.tool_is_visible () && t.is_over (x, y)) {
							if (dy < 0) {
								action = t.scroll_wheel_up_action (t);
							} else {
								action = t.scroll_wheel_down_action (t);
							}
							press_tool = t;
						}
					}
				}
			}
		}
		
		if (!action) {
			scroll_current_set (dy);
		}
		
		redraw_tool_box ();
	}

	void scroll_current_set (double d) {
		current_set.scroll += d;
		
		if (current_set.scroll > 0) {
			current_set.scroll = 0;
		}

		if (current_set.content_height < allocation_height) {
			current_set.scroll = 0;
		} else if (current_set.content_height + current_set.scroll < allocation_height) {
			current_set.scroll = allocation_height - current_set.content_height;
		}
					
		update_expanders ();
		suppress_scroll ();	
	}

	public void scroll_up (double x, double y) {
		scroll_wheel (x, y, 0, 20);
	}
	
	public void scroll_down (double x, double y) {
		scroll_wheel (x, y, 0, -20);
	}
	
	void suppress_scroll () {
		scrolling_toolbox = true;
		
		Timeout.add (2000, () => {
			scrolling_toolbox = false;
			return false;
		});
	}
	
	public void move (double x, double y) {
		bool update;
		bool a;
		bool consumed = false;
		bool active;
		
		y -= current_set.scroll;
		
		MainWindow.set_cursor (NativeWindow.VISIBLE);
			
		foreach (Expander exp in current_set.get_expanders ()) {
			if (exp.visible) {
				a = exp.is_over (x, y);
				update = exp.set_active (a);
				
				if (update) {
					redraw ((int) exp.x - 10, (int) exp.y - 10, (int) (exp.x + exp.w + 20), (int) (exp.y + exp.h + 20));
				}
				
				foreach (Tool t in exp.tool) {
					if (t.tool_is_visible ()) {
						active = t.is_over (x, y);

						if (!active && t.is_active ()) {
							t.move_out_action (t);
						}
						
						update = t.set_active (active);
						
						if (update) {
							redraw (0, 0, allocation_width, allocation_height);
						}
						
						if (t.panel_move_action (t, x, y)) {
							consumed = true;
						}
					}
				}
			}
		}
		
		if (scrolling_touch && !consumed && BirdFont.android) {
			scroll_current_set (y - scroll_y);
			scroll_y = y;
			redraw_tool_box ();
		}
	}

	public static void redraw_tool_box () {
		if (MenuTab.has_suppress_event ()) {
			warn_if_test ("Don't redraw toolbox when background thread is running.");
			return;
		}

		Toolbox t = MainWindow.get_toolbox ();
		if (!is_null (t)) {
			t.redraw (0, 0, allocation_width, allocation_height);
		}
	}
	
	public void reset_active_tool () {
		foreach (Expander exp in current_set.get_expanders ()) {
			foreach (Tool t in exp.tool) {
				t.set_active (false);
			}
		}
	}

	public Tool? get_active_tool () {
		foreach (Expander exp in current_set.get_expanders ()) {
			foreach (Tool t in exp.tool) {
				if (t.is_active ()) {
					return t;
				}
			}
		}
		
		return null;
	}

	public void set_current_tool (Tool tool) {
		if (tool.editor_events) {
			current_set.set_current_tool (tool);
		}
	}

	public Tool get_current_tool () {
		return current_set.get_current_tool ();
	}
	
	public void select_tool (Tool tool) {
		bool update;
		int offset_y;
		foreach (Expander exp in current_set.get_expanders ()) {
			if (exp.visible) {
				foreach (Tool t in exp.tool) {
					if (tool.get_id () == t.get_id ()) {
						if (!t.tool_is_visible ()) {
							warning ("Tool is hidden");
						} else {						
							update = false;
							
							update = tool.set_selected (true);
							if (tool.persistent) {
								update = tool.set_active (true);
							}
							
							tool.select_action (tool);
							
							if (update) {
								offset_y = (int) (exp.y - scroll_y);
								redraw ((int) exp.x - 10, (int) offset_y - 10, allocation_width, (int) (allocation_height - offset_y + 10));
							}
							
							set_current_tool (tool);
						}
					}
				}
			}
		}
	}
	
	public Tool get_tool (string name) {
		foreach (ToolCollection tc in tool_sets) {
			foreach (Expander e in tc.get_expanders ()) {
				foreach (Tool t in e.tool) {
					if (t.get_name () == name) {
						return t;
					}
				}
			}
		}
				
		warning ("No tool found for name \"%s\".\n", name);
		
		return new Tool ("no_icon");
	}
	
	public static void set_tool_visible (string name, bool visible) {
		Toolbox tb = MainWindow.get_toolbox ();
		Tool t = tb.get_tool (name); 
		t.set_tool_visibility (visible);
		tb.update_expanders ();
		Toolbox.redraw_tool_box ();
	}
	
	public static void select_tool_by_name (string name) {
		Toolbox b = MainWindow.get_toolbox ();
		
		if (is_null (b)) {
			return;
		}
				
		b.select_tool (b.get_tool (name));
	}
		
	public static double get_scale () {
		return MainWindow.units;
	}
	
	public void set_default_tool_size () {
		foreach (ToolCollection t in tool_sets) {
			foreach (Expander e in t.get_expanders ()) {
				e.update_tool_position ();
			}
		}
	}
	
	public void update_expanders () {
		double pos;
		
		foreach (Expander e in current_set.get_expanders ()) {
			e.set_scroll (current_set.scroll);
		}
		
		pos = 4 * get_scale ();
		foreach (Expander e in current_set.get_expanders ()) {
			if (e.visible) {
				e.set_offset (pos);
				pos += e.get_content_height () + 4 * get_scale ();
				current_set.content_height = pos;
				
				if (BirdFont.android) {
					current_set.content_height *= 1.15;
				}
			}
		}

		foreach (Expander e in current_set.get_expanders ()) {
			e.set_active (false);
		}
	}
	
	private void draw_expanders (int w, int h, Context cr) {
		foreach (Expander e in current_set.get_expanders ()) {
			if (e.visible) {
				e.draw (cr);
			}
		}
	}
	
	public void draw (int w, int h, Context cr) { 
		cr.save ();
			
		Theme.color (cr, "Default Background");
		
		cr.rectangle (0, 0, w, h);
		cr.set_line_width (0);
		cr.fill ();
				
		draw_expanders (w, h, cr);
	
		cr.restore ();
		
		draw_tool_tip (cr);
	}

	private void draw_tool_tip (Context cr) {
		TextArea t;
		
		if (tool_tip != null && tool_tip != "") {
			t = new TextArea (17 * get_scale ());
   
			t.allocation = new WidgetAllocation.for_area (0, 0, allocation_width, allocation_height);
			t.set_editable (false);
			t.set_text ((!) tool_tip);
			
			t.width = allocation_width - 20 * get_scale ();
			
			t.min_height = 17 * get_scale ();
			t.height = 17 * get_scale ();

			t.layout ();

			t.widget_x = 10 * get_scale ();
			t.widget_y = tool_tip_y - t.height - 5 * get_scale ();		

			if (t.widget_y < 5) {
				t.widget_y = 5;
			}

			t.draw (cr);
		}
	}
	
	public void hide_tooltip () {
		if (tool_tip != null) {
			tool_tip = null;
			redraw_tool_box ();
		}
	}
	
	public void show_tooltip (string tool_tip, double x, double y) {
		if (tool_tip != "") {
			this.tool_tip = tool_tip;
			this.tool_tip_x = x;
			this.tool_tip_y = y;
			
			redraw_tool_box ();
		}
	}
	
	public void set_current_tool_set (ToolCollection ts) {
		current_set = ts;
	}
	
	public class EmptySet : ToolCollection  {
		Gee.ArrayList<Expander> expanders;
		
		public EmptySet () {
			expanders = new Gee.ArrayList<Expander> ();
		}
		
		public override Gee.ArrayList<Expander> get_expanders () {
			return expanders;
		}
	}
}

}

Contact

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