Clone in a terminal:
Commit 51be82a
Johan Mattsson - 2015-06-08T14:21:18+00:00
51be82ac9145992f5133e05f0f74f4347527f402johan.mattsson.m@gmail.com

Abstract menu class

Changed files (7)

Diff

diff --git a/birdfont/Main.vala b/birdfont/Main.valaindex 165ee90..eefb4b2 100644--- a/birdfont/Main.vala+++ b/birdfont/Main.vala@@ -24,7 +24,7 @@ public static int main (string[] arg) { 	BirdFont.BirdFont birdfont;  	birdfont = new BirdFont.BirdFont ();-	birdfont.init (arg, null);+	birdfont.init (arg, null, "birdfont"); 	Gtk.init (ref arg); 	 	window = new MainWindow ();diff --git a/libbirdfont/AbstractMenu.vala b/libbirdfont/AbstractMenu.valanew file mode 100644index 0000000..d56d441--- /dev/null+++ b/libbirdfont/AbstractMenu.vala@@ -0,0 +1,367 @@+/*+    Copyright (C) 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 B;++namespace BirdFont {++/** Interface for events from native window to the current tab. */+public class AbstractMenu : GLib.Object {++	public bool show_menu {+		get  {+			return menu_visibility;+		}+		+		set {+			string tab_name;+			+			menu_visibility = value;+			current_menu = top_menu;+			+			if (menu_visibility) {+				tab_name = MainWindow.get_tab_bar ().get_selected_tab ().get_display ().get_name ();+				if (tab_name == "Preview") {+					MenuTab.select_overview ();+				}+			}+		}+	}+	+	public bool menu_visibility = false;+	public SubMenu top_menu;++	SubMenu current_menu;+	WidgetAllocation allocation = new WidgetAllocation ();++	double width = 250 * MainWindow.units;+	double height = 25 * MainWindow.units;+	+	public Gee.HashMap<string, MenuItem> menu_items = new Gee.HashMap<string, MenuItem> ();+	public Gee.ArrayList<MenuItem> sorted_menu_items = new Gee.ArrayList<MenuItem> ();++	public AbstractMenu () {+	}++	public void process_key_binding_events (uint keyval) {+		string display;+		FontDisplay current_display = MainWindow.get_current_display ();+		ToolItem tm;+		+		foreach (MenuItem item in sorted_menu_items) {		+			if (item.key == (unichar) keyval && item.modifiers == KeyBindings.modifier) {+				+				display = current_display.get_name ();++				if (current_display is Glyph) {+					display = "Glyph";+				}++				if (!current_display.needs_modifier () || item.modifiers != NONE) {+					if (!SettingsDisplay.update_key_bindings +						&& item.in_display (display)+						&& !(item is ToolItem)) {+						item.action ();+						return;+					}+					+					if (item is ToolItem) {+						tm  = (ToolItem) item;+						+						if (tm.in_display (display)) {+							if (tm.tool.editor_events) {+								MainWindow.get_toolbox ().set_current_tool (tm.tool);+								tm.tool.select_action (tm.tool);+								return;+							} else {+								tm.tool.select_action (tm.tool);								+								return;+							}+						}+					}+				}+			}+		}+	}++	public void load_key_bindings () {+		File default_key_bindings = SearchPaths.find_file (null, "key_bindings.xml");+		File user_key_bindings = get_child (BirdFont.get_settings_directory (), "key_bindings.xml");+		+		if (default_key_bindings.query_exists ()) {+			parse_key_bindings (default_key_bindings);+		}++		if (user_key_bindings.query_exists ()) {+			parse_key_bindings (user_key_bindings);+		}+	}++	public void parse_key_bindings (File f) {+		string xml_data;+		XmlParser parser;+		+		try {+			FileUtils.get_contents((!) f.get_path (), out xml_data);+			parser = new XmlParser (xml_data);+			parse_bindings (parser.get_root_tag ());+		} catch (GLib.Error e) {+			warning (e.message);+		}+	}++	public void parse_bindings (Tag tag) {+		foreach (Tag t in tag) {+			if (t.get_name () == "action") {+				parse_binding (t.get_attributes ());+			}+		}+	}++	public void parse_binding (Attributes attr) {+		uint modifier = NONE;+		unichar key = '\0';+		string action = "";+		MenuItem menu_action;+		MenuItem? ma;+		+		foreach (Attribute a in attr) {+			if (a.get_name () == "key") {+				key = a.get_content ().get_char (0);+			}+			+			if (a.get_name () == "ctrl" && a.get_content () == "true") {+				modifier |= CTRL;+			}++			if (a.get_name () == "alt" && a.get_content () == "true") {+				modifier |= ALT;+			}++			if (a.get_name () == "command" && a.get_content () == "true") {+				modifier |= LOGO;+			}+			+			if (a.get_name () == "shift" && a.get_content () == "true") {+				modifier |= SHIFT;+			}+			+			if (a.get_name () == "action") {+				action = a.get_content ();+			}+		}+		+		ma = menu_items.get (action);+		if (ma != null) {+			menu_action = (!) ma;+			menu_action.modifiers = modifier;+			menu_action.key = key;+		}+	}+	+	public MenuItem add_menu_item (string label, string description = "", string display = "") {+		MenuItem i = new MenuItem (label, description);+		+		if (description != "") {+			menu_items.set (description, i);+			sorted_menu_items.add (i);+		}+		+		if (display != "") {+			i.add_display (display);+		}+								+		return i;+	}++	public void button_release (int button, double ex, double ey) {+		double y = 0;+		double x = allocation.width - width;+		+		if (button == 1) {+			foreach (MenuItem item in current_menu.items) {+				if (x <= ex < allocation.width && y <= ey <= y + height) {+					item.action ();+					GlyphCanvas.redraw ();+					return;+				}+				+				y += height;+			}+			+			menu_visibility = false;+			current_menu = (!) top_menu;+			GlyphCanvas.redraw ();+		}+	}++	public void add_tool_key_bindings () {+		ToolItem tool_item;+		foreach (ToolCollection tool_set in MainWindow.get_toolbox ().tool_sets) {+			foreach (Expander e in tool_set.get_expanders ()) {+				foreach (Tool t in e.tool) {+					tool_item = new ToolItem (t);+					if (tool_item.identifier != "" && !has_menu_item (tool_item.identifier)) {+						menu_items.set (tool_item.identifier, tool_item);+						sorted_menu_items.add (tool_item);+					}+					+					foreach (string d in tool_set.get_displays ()) {+						tool_item.add_display (d);+					}+				}+			}+		}+	}++	public bool has_menu_item (string identifier) {+		foreach (MenuItem mi in sorted_menu_items) {+			if (mi.identifier == identifier) {+				return true;+			}+		}+		+		return false;+	}++	public void set_menu (SubMenu m) {+		current_menu = m;+		GlyphCanvas.redraw ();+	}+	+	public double layout_width () {+		Text key_binding = new Text ();+		double font_size = 17 * MainWindow.units;;+		double w;+		+		width = 0;+		foreach (MenuItem item in current_menu.items) {+			key_binding.set_text (item.get_key_bindings ());+			+			item.label.set_font_size (font_size);+			key_binding.set_font_size (font_size);+			+			w = item.label.get_extent ();+			w += key_binding.get_extent ();+			w += 3 * height * MainWindow.units;+			+			if (w > width) {+				width = w;+			}+		}+		+		return width;+	}+	+	public void draw (WidgetAllocation allocation, Context cr) {+		double y;+		double x;+		double label_x;+		double label_y;+		double font_size;+		Text key_binding;+		double binding_extent;+		+		width = layout_width ();+		+		key_binding = new Text ();+		+		x = allocation.width - width;+		y = 0;+		font_size = 17 * MainWindow.units;+		this.allocation = allocation;+		+		foreach (MenuItem item in current_menu.items) {+			cr.save ();+			Theme.color (cr, "Menu Background");+			cr.rectangle (x, y, width, height);+			cr.fill ();+			cr.restore ();+			+			cr.save ();+			label_x = allocation.width - width + 0.7 * height * MainWindow.units;+			label_y = y + font_size - 1 * MainWindow.units;+			Theme.text_color (item.label, "Menu Foreground");+			item.label.draw_at_baseline (cr, label_x, label_y);+			+			key_binding.set_text (item.get_key_bindings ());+			key_binding.set_font_size (font_size);+			binding_extent = key_binding.get_extent ();+			label_x = x + width - binding_extent - 0.6 * height * MainWindow.units;+			key_binding.set_font_size (font_size);+			Theme.text_color (key_binding, "Menu Foreground");+			key_binding.draw_at_baseline (cr, label_x, label_y);+			+			y += height;+		}+	}++	public void write_key_bindings () {+		DataOutputStream os;+		File file;+		bool has_ctrl, has_alt, has_command, has_shift;+		+		file = get_child (BirdFont.get_settings_directory (), "key_bindings.xml");+		+		try {+			if (file.query_exists ()) {+				file.delete ();+			}+		} catch (GLib.Error e) {+			warning (e.message);+		}+		+		try {+			os = new DataOutputStream (file.create (FileCreateFlags.REPLACE_DESTINATION));+			os.put_string ("""<?xml version="1.0" encoding="utf-8" standalone="yes"?>""");+			os.put_string ("\n");+			+			os.put_string ("<bindings>\n");+			+			foreach (MenuItem item in sorted_menu_items) {+				os.put_string ("\t<action ");+				+				os.put_string (@"key=\"$((!)item.key.to_string ())\" ");+				+				has_ctrl = (item.modifiers & CTRL) > 0;+				os.put_string (@"ctrl=\"$(has_ctrl.to_string ())\" ");++				has_alt = (item.modifiers & ALT) > 0;+				os.put_string (@"alt=\"$(has_alt.to_string ())\" ");		++				has_command = (item.modifiers & LOGO) > 0;+				os.put_string (@"command=\"$(has_command.to_string ())\" ");+					+				has_shift = (item.modifiers & SHIFT) > 0;+				os.put_string (@"shift=\"$(has_shift.to_string ())\" ");			+				+				os.put_string (@"action=\"$(item.identifier)\" ");+				+				os.put_string ("/>\n");+			}+			os.put_string ("</bindings>\n");+			+			os.close ();+		} catch (GLib.Error e) {+			warning (e.message);+		}+	}+	+	public void set_current_menu (SubMenu menu) {+		current_menu = menu;	+	}+}++}diff --git a/libbirdfont/BirdFont.vala b/libbirdfont/BirdFont.valaindex ca87c6a..9299d2a 100644--- a/libbirdfont/BirdFont.vala+++ b/libbirdfont/BirdFont.vala@@ -376,6 +376,8 @@ public class BirdFont { 	 	public static Drawing? drawing = null; 	+	public static string? settings_directory = null;+	 	public BirdFont () { 		set_defaul_drawing_callbacks (); 	}@@ -389,8 +391,9 @@ public class BirdFont { 	/** 	 * @param arg command line arguments 	 * @param program path+	 * @param setting subdirectory 	 */-	public void init (string[] arg, string? program_path) {+	public void init (string[] arg, string? program_path, string? settings_subdir) { 		int err_arg; 		int i; 		File font_file;@@ -400,6 +403,8 @@ public class BirdFont { 		string theme_version; 		CharDatabaseParser parser; 		CodePageBits codepage_bits;+		+		set_settings_subdir (settings_subdir);  		args = new Argument.command_line (arg); 		Font.empty = new Font ();@@ -619,6 +624,10 @@ public class BirdFont { 		return backup; 	} +	public static void set_settings_subdir (string? subdir) {+		settings_directory = subdir;+	}+ 	internal static File get_settings_directory () { 		string home_path; 		File home;@@ -643,7 +652,12 @@ public class BirdFont { 		 		home = File.new_for_path (home_path); #endif-		settings = get_child(home, "birdfont");+		+		if (settings_directory != null) {+			settings = get_child(home, (!) settings_directory);+		} else {+			settings = get_child(home, "birdfont");+		} 				 		if (!settings.query_exists ()) { 			DirUtils.create ((!) settings.get_path (), 0755);diff --git a/libbirdfont/MainWindow.vala b/libbirdfont/MainWindow.valaindex 6bf4f8a..90fef8b 100644--- a/libbirdfont/MainWindow.vala+++ b/libbirdfont/MainWindow.vala@@ -28,7 +28,7 @@ public class MainWindow : GLib.Object { 	public static CharDatabase character_database;
 	public static LigatureList ligature_display;
 	public static SpacingClassTab spacing_class_tab;
-	public static Menu menu;
+	public static AbstractMenu menu;
 	public static Dialog dialog;
 	public static SpacingTab spacing_tab;
 	
@@ -99,7 +99,11 @@ public class MainWindow : GLib.Object { 		GlyphCanvas.redraw ();
 	}
 
-	public static Menu get_menu () {
+	public static void set_menu (AbstractMenu m) {
+		menu = m;
+	}
+	
+	public static AbstractMenu get_menu () {
 		return menu;
 	}
 
diff --git a/libbirdfont/Menu.vala b/libbirdfont/Menu.valaindex 4d536df..4f5bf4e 100644--- a/libbirdfont/Menu.vala+++ b/libbirdfont/Menu.vala@@ -18,41 +18,11 @@ using B; namespace BirdFont {  /** Interface for events from native window to the current tab. */-public class Menu : GLib.Object {--	public bool show_menu {-		get  {-			return menu_visibility;-		}-		-		set {-			string tab_name;-			-			menu_visibility = value;-			current_menu = top_menu;-			-			if (menu_visibility) {-				tab_name = MainWindow.get_tab_bar ().get_selected_tab ().get_display ().get_name ();-				if (tab_name == "Preview") {-					MenuTab.select_overview ();-				}-			}-		}-	}+public class Menu : AbstractMenu { 	-	public bool menu_visibility = false;-	public SubMenu top_menu;--	SubMenu current_menu;-	WidgetAllocation allocation;--	double width = 250 * MainWindow.units;-	double height = 25 * MainWindow.units;-	-	public Gee.HashMap<string, MenuItem> menu_items = new Gee.HashMap<string, MenuItem> ();-	public Gee.ArrayList<MenuItem> sorted_menu_items = new Gee.ArrayList<MenuItem> ();- 	public Menu () {+		base ();+		 		SubMenu menu = new SubMenu (); 		SubMenu file_menu = new SubMenu (); 		SubMenu edit_menu = new SubMenu ();@@ -506,327 +476,13 @@ public class Menu : GLib.Object { 			show_menu = false; 		}); 		menu.items.add (version);--			-		current_menu = menu;+	+		set_current_menu (menu); 		top_menu = menu;-		allocation = new WidgetAllocation ();-+		 		add_tool_key_bindings (); 		load_key_bindings (); 	}--	public void process_key_binding_events (uint keyval) {-		string display;-		FontDisplay current_display = MainWindow.get_current_display ();-		ToolItem tm;-		-		foreach (MenuItem item in sorted_menu_items) {		-			if (item.key == (unichar) keyval && item.modifiers == KeyBindings.modifier) {-				-				display = current_display.get_name ();--				if (current_display is Glyph) {-					display = "Glyph";-				}--				if (!current_display.needs_modifier () || item.modifiers != NONE) {-					if (!SettingsDisplay.update_key_bindings -						&& item.in_display (display)-						&& !(item is ToolItem)) {-						item.action ();-						return;-					}-					-					if (item is ToolItem) {-						tm  = (ToolItem) item;-						-						if (tm.in_display (display)) {-							if (tm.tool.editor_events) {-								MainWindow.get_toolbox ().set_current_tool (tm.tool);-								tm.tool.select_action (tm.tool);-								return;-							} else {-								tm.tool.select_action (tm.tool);								-								return;-							}-						}-					}-				}-			}-		}-	}--	void load_key_bindings () {-		File default_key_bindings = SearchPaths.find_file (null, "key_bindings.xml");-		File user_key_bindings = get_child (BirdFont.get_settings_directory (), "key_bindings.xml");-		-		if (default_key_bindings.query_exists ()) {-			parse_key_bindings (default_key_bindings);-		}--		if (user_key_bindings.query_exists ()) {-			parse_key_bindings (user_key_bindings);-		}-	}--	void parse_key_bindings (File f) {-		string xml_data;-		XmlParser parser;-		-		try {-			FileUtils.get_contents((!) f.get_path (), out xml_data);-			parser = new XmlParser (xml_data);-			parse_bindings (parser.get_root_tag ());-		} catch (GLib.Error e) {-			warning (e.message);-		}-	}--	void parse_bindings (Tag tag) {-		foreach (Tag t in tag) {-			if (t.get_name () == "action") {-				parse_binding (t.get_attributes ());-			}-		}-	}--	void parse_binding (Attributes attr) {-		uint modifier = NONE;-		unichar key = '\0';-		string action = "";-		MenuItem menu_action;-		MenuItem? ma;-		-		foreach (Attribute a in attr) {-			if (a.get_name () == "key") {-				key = a.get_content ().get_char (0);-			}-			-			if (a.get_name () == "ctrl" && a.get_content () == "true") {-				modifier |= CTRL;-			}--			if (a.get_name () == "alt" && a.get_content () == "true") {-				modifier |= ALT;-			}--			if (a.get_name () == "command" && a.get_content () == "true") {-				modifier |= LOGO;-			}-			-			if (a.get_name () == "shift" && a.get_content () == "true") {-				modifier |= SHIFT;-			}-			-			if (a.get_name () == "action") {-				action = a.get_content ();-			}-		}-		-		ma = menu_items.get (action);-		if (ma != null) {-			menu_action = (!) ma;-			menu_action.modifiers = modifier;-			menu_action.key = key;-		}-	}-	-	MenuItem add_menu_item (string label, string description = "", string display = "") {-		MenuItem i = new MenuItem (label, description);-		-		if (description != "") {-			menu_items.set (description, i);-			sorted_menu_items.add (i);-		}-		-		if (display != "") {-			i.add_display (display);-		}-								-		return i;-	}--	public void button_release (int button, double ex, double ey) {-		double y = 0;-		double x = allocation.width - width;-		-		if (button == 1) {-			foreach (MenuItem item in current_menu.items) {-				if (x <= ex < allocation.width && y <= ey <= y + height) {-					item.action ();-					GlyphCanvas.redraw ();-					return;-				}-				-				y += height;-			}-			-			menu_visibility = false;-			current_menu = (!) top_menu;-			GlyphCanvas.redraw ();-		}-	}--	void add_tool_key_bindings () {-		ToolItem tool_item;-		foreach (ToolCollection tool_set in MainWindow.get_toolbox ().tool_sets) {-			foreach (Expander e in tool_set.get_expanders ()) {-				foreach (Tool t in e.tool) {-					tool_item = new ToolItem (t);-					if (tool_item.identifier != "" && !has_menu_item (tool_item.identifier)) {-						menu_items.set (tool_item.identifier, tool_item);-						sorted_menu_items.add (tool_item);-					}-					-					foreach (string d in tool_set.get_displays ()) {-						tool_item.add_display (d);-					}-				}-			}-		}-	}--	public bool has_menu_item (string identifier) {-		foreach (MenuItem mi in sorted_menu_items) {-			if (mi.identifier == identifier) {-				return true;-			}-		}-		-		return false;-	}--	public void set_menu (SubMenu m) {-		current_menu = m;-		GlyphCanvas.redraw ();-	}-	-	public double layout_width () {-		Text key_binding = new Text ();-		double font_size = 17 * MainWindow.units;;-		double w;-		-		width = 0;-		foreach (MenuItem item in current_menu.items) {-			key_binding.set_text (item.get_key_bindings ());-			-			item.label.set_font_size (font_size);-			key_binding.set_font_size (font_size);-			-			w = item.label.get_extent ();-			w += key_binding.get_extent ();-			w += 3 * height * MainWindow.units;-			-			if (w > width) {-				width = w;-			}-		}-		-		return width;-	}-	-	public void draw (WidgetAllocation allocation, Context cr) {-		double y;-		double x;-		double label_x;-		double label_y;-		double font_size;-		Text key_binding;-		double binding_extent;-		-		width = layout_width ();-		-		key_binding = new Text ();-		-		x = allocation.width - width;-		y = 0;-		font_size = 17 * MainWindow.units;-		this.allocation = allocation;-		-		foreach (MenuItem item in current_menu.items) {-			cr.save ();-			Theme.color (cr, "Menu Background");-			cr.rectangle (x, y, width, height);-			cr.fill ();-			cr.restore ();-			-			cr.save ();-			label_x = allocation.width - width + 0.7 * height * MainWindow.units;-			label_y = y + font_size - 1 * MainWindow.units;-			Theme.text_color (item.label, "Menu Foreground");-			item.label.draw_at_baseline (cr, label_x, label_y);-			-			key_binding.set_text (item.get_key_bindings ());-			key_binding.set_font_size (font_size);-			binding_extent = key_binding.get_extent ();-			label_x = x + width - binding_extent - 0.6 * height * MainWindow.units;-			key_binding.set_font_size (font_size);-			Theme.text_color (key_binding, "Menu Foreground");-			key_binding.draw_at_baseline (cr, label_x, label_y);-			-			y += height;-		}-	}--	public void write_key_bindings () {-		DataOutputStream os;-		File file;-		bool has_ctrl, has_alt, has_command, has_shift;-		-		file = get_child (BirdFont.get_settings_directory (), "key_bindings.xml");-		-		try {-			if (file.query_exists ()) {-				file.delete ();-			}-		} catch (GLib.Error e) {-			warning (e.message);-		}-		-		try {-			os = new DataOutputStream (file.create (FileCreateFlags.REPLACE_DESTINATION));-			os.put_string ("""<?xml version="1.0" encoding="utf-8" standalone="yes"?>""");-			os.put_string ("\n");-			-			os.put_string ("<bindings>\n");-			-			foreach (MenuItem item in sorted_menu_items) {-				os.put_string ("\t<action ");-				-				os.put_string (@"key=\"$((!)item.key.to_string ())\" ");-				-				has_ctrl = (item.modifiers & CTRL) > 0;-				os.put_string (@"ctrl=\"$(has_ctrl.to_string ())\" ");--				has_alt = (item.modifiers & ALT) > 0;-				os.put_string (@"alt=\"$(has_alt.to_string ())\" ");		--				has_command = (item.modifiers & LOGO) > 0;-				os.put_string (@"command=\"$(has_command.to_string ())\" ");-					-				has_shift = (item.modifiers & SHIFT) > 0;-				os.put_string (@"shift=\"$(has_shift.to_string ())\" ");			-				-				os.put_string (@"action=\"$(item.identifier)\" ");-				-				os.put_string ("/>\n");-			}-			os.put_string ("</bindings>\n");-			-			os.close ();-		} catch (GLib.Error e) {-			warning (e.message);-		}-	}--	public class SubMenu : GLib.Object { -		public Gee.ArrayList<MenuItem> items;-		-		public SubMenu () {-			items = new Gee.ArrayList<MenuItem> ();-		}-	} }  }diff --git a/libbirdfont/SubMenu.vala b/libbirdfont/SubMenu.valanew file mode 100644index 0000000..b66a981--- /dev/null+++ b/libbirdfont/SubMenu.vala@@ -0,0 +1,25 @@+/*+    Copyright (C) 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.+*/++namespace BirdFont {++public class SubMenu : GLib.Object { +	public Gee.ArrayList<MenuItem> items;+	+	public SubMenu () {+		items = new Gee.ArrayList<MenuItem> ();+	}+}++}diff --git a/libbirdfont/TabContent.vala b/libbirdfont/TabContent.valaindex 49a9a7d..c045da0 100644--- a/libbirdfont/TabContent.vala+++ b/libbirdfont/TabContent.vala@@ -69,7 +69,7 @@ public class TabContent : GLib.Object { 	} 	 	public static void draw (WidgetAllocation allocation, Context cr) {-		Menu menu;+		AbstractMenu menu; 		Dialog dialog; 		 		if (unlikely (MenuTab.suppress_event)) {

Contact

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