Clone in a terminal:
Tool.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;

namespace Supplement {

public class Tool : GLib.Object {
	
	public double x = 0;
	public double y = 0;
	public double w = 0;
	public double h = 0;

	protected bool active = false;
	protected bool selected = false;
	
	double r_default_selected = 131/255.0;
	double g_default_selected = 179/255.0;
	double b_default_selected = 231/255.0;
	double a_default_selected = 1;
	
	double r_default = 185/255.0;
	double g_default = 207/255.0;
	double b_default = 231/255.0;
	double a_default = 1;
	
	double r;
	double g;
	double b;
	double a;
	
	ImageSurface? icon = null;
		
	public signal void select_action (Tool selected);
	public signal void deselect_action (Tool selected);
	
	public signal void press_action (Tool selected, int button, int x, int y);
	public signal void double_click_action (Tool selected, int button, int x, int y);
	public signal void move_action (Tool selected, int x, int y);
	public signal void release_action (Tool selected, int button, int x, int y);
	
	public signal void key_press_action (Tool selected, uint32 keyval);
	public signal void key_release_action (Tool selected, uint32 keyval);
	
	public signal void panel_press_action (Tool selected, uint button, double x, double y);
	public signal void panel_release_action (Tool selected, uint button, double x, double y);
	public signal void panel_move_action (Tool selected, double x, double y);
	
	public signal void draw_action (Tool selected, Context cr, Glyph glyph);
	
	protected string name = "";
	
	static int next_id = 1;
	
	int id;
	
	public bool new_selection = false;
	
	bool show_bg = true;
	
	string tip = "";

	// keyboard bindings
	public uint modifier_flag;
	public unichar key;
	
	private static ImageSurface? selected_button = null;
	private static ImageSurface? active_selected_button = null;
	private static ImageSurface? deselected_button = null;
	private static ImageSurface? active_deselected_button = null;
	
	/** Create tool with a certain name and load icon "name".png */
	public Tool (string? name = null, string tip = "", unichar key = '\0', uint modifier_flag = 0) {
		this.tip = tip;
		
		if (selected_button == null) {
			selected_button = Icons.get_icon ("tool_button_selected.png");
			active_selected_button = Icons.get_icon ("tool_button_selected_active.png");
			deselected_button = Icons.get_icon ("tool_button_deselected.png");
			active_deselected_button = Icons.get_icon ("tool_button_deselected_active.png");
		}
		
		if (name != null) {
			set_icon ((!) name);
			this.name = (!) name;
		}
		
		this.key = key;
		this.modifier_flag = modifier_flag;
				
		id = next_id;
		next_id++;
		
		panel_press_action.connect ((self, button, x, y) => {
			MainWindow.get_tool_tip ().set_text_from_tool ();
		});
		
		r = r_default;
		g = g_default;
		b = b_default;
		a = a_default;
	}
	
	public void set_icon (string name) {
		StringBuilder n = new StringBuilder ();
		n.append ((!) name);
		n.append (".png");
		icon = Icons.get_icon (n.str);
	}
	
	public bool is_active () {
		return active;
	}
	
	public void set_show_background (bool bg) {
		show_bg = bg;
	}
	
	public int get_id () {
		return id;
	}

	public string get_name () {
		return name;
	}

	public bool is_selected () {
			return selected;
	}
		
	public virtual void set_properties (double tx, double ty, double tw, double th) {
		x = tx;
		y = ty;
		w = tw;
		h = th;
	}

	public string get_tip () {
		return tip;
	}

	public bool is_over (double xp, double yp) {	
		return (x <= xp <= x + w  + 12 + 15 && y + 15 <= yp <= y + w + 12 + 30);  
	}
	
	public bool set_selected (bool a) {
		TooltipArea? tpa = null;					
		tpa = MainWindow.get_tool_tip ();
							
		new_selection = true;
		selected = a;
		set_active (a);
		
		if (!a) {
			deselect_action (this);
		} else {
			((!)tpa).update_text ();
		}
		
		return true;
	}
	
	public bool set_active (bool ac) {
		bool ret = (active != ac);

		if (selected) {
			if (ac) {
				r = r_default_selected;
				g = g_default_selected;
				b = b_default_selected;
				a = a_default_selected * 0.5;
			} else {
				r = r_default_selected;
				g = g_default_selected;
				b = b_default_selected;
				a = a_default_selected;
			}			
		} else {
			if (ac) {
				r = r_default;
				g = g_default;
				b = b_default;
				a = a_default * 0.2;
			} else {
				r = r_default;
				g = g_default;
				b = b_default;
				a = a_default;
			}
		}

		active = ac;
		return ret;
	}
		
	public virtual void draw (Context cr) {
		double xt = x + 3 + w;
		double yt = y + h + 17;

		double bgx, bgy;
		
		bgx = xt - 6;
		bgy = yt - 7;

		cr.save ();

		// Button in four states
		if (selected && selected_button != null) {
			cr.set_source_surface ((!) selected_button, bgx, bgy);
			cr.paint ();
		}

		if (selected && active && active_selected_button != null) {
			cr.set_source_surface ((!) active_selected_button, bgx, bgy);
			cr.paint ();
		}

		if (!selected && deselected_button != null) {
			cr.set_source_surface ((!) deselected_button, bgx, bgy);
			cr.paint ();
		}

		if (!selected && active && active_deselected_button != null) {
			cr.set_source_surface ((!) active_deselected_button, bgx, bgy);
			cr.paint ();
		}
				
		if (icon != null) {
			ImageSurface i = (!) icon;
			
			if (likely (i.status () == Cairo.Status.SUCCESS)) {
				cr.set_source_surface (i, xt + (15 - i.get_width () / 2) - 5.7, yt + (15 - i.get_height ()) / 2.0);
				cr.paint ();
			} else {
				warning (@"Falied to load icon for $name");
			}
		}
		
		cr.restore ();
	}

	/** Run a test case for this tool. */
	public virtual bool test () {
		stderr.printf (@"$(get_name ()) does not have a test case, implement one by overriding test method in Tool base class.");
		return false;
	}
	
	/** Help function to test button press actions. */
	public void test_click_action (int b, int x, int y) {
		Tool.yield ();
		press_action (this, b, x, y);
		
		Tool.yield ();
		release_action (this, b, x, y);
	}

	/** Help function to test select action for this tool. */
	public void test_select_action () {
		Tool.yield ();
		MainWindow.get_toolbox ().select_tool (this);
	}

	public void test_move_action (int x, int y) {
		Tool.yield ();
		move_action (this, x, y);
	}

	public void test_press_action (int b, int x, int y) {
		Tool.yield ();
		press_action (this, b, x, y);
	}

	public void test_release_action (int b, int x, int y) {
		Tool.yield ();
		release_action (this, b, x, y);
	}

	public static void test_open_next_glyph () {
		Tool.yield ();
		OverView o = MainWindow.get_overview ();
		
		MainWindow.get_tab_bar ().select_overview ();
		MainWindow.get_toolbox ().select_tool_by_name ("utf_8");
		
		o.select_next_glyph ();
		Tool.yield ();
		
		o.open_current_glyph ();
		Tool.yield ();
	}
	
	/** Run pending events in main loop before continue. */
	public static void @yield () {
		int t = 0;
		var time = new TimeoutSource(500);
		bool timeout;

		if (TestSupplement.is_slow_test ()) {
			timeout = false;
			
			time.set_callback(() => {
				timeout = true;
				return false;
			});

			time.attach(null);		
		} else {
			timeout = true;
		}
    
		unowned MainContext c = MainContext.default ();
		bool a = c.acquire ();
		
		while (unlikely (!a)) {
			warning ("Failed to acquire main loop.\n");
			return;
		}

		while (c.pending () || TestSupplement.is_slow_test ()) {
			c.iteration (true);
			t++;

			if (!c.pending () && TestSupplement.is_slow_test ()) {
				if (timeout) break;
			}
						
			if (unlikely (t > 100000)) {
				assert (false);
				break;
			}
		}
		
		c.release ();
	}
	
}

}

Contact

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