Explorar el Código

tools: do not indent the entire file

Daniele Bartolini hace 5 años
padre
commit
1362006383
Se han modificado 47 ficheros con 8519 adiciones y 8658 borrados
  1. 308 308
      tools/api/engine_api.vala
  2. 17 16
      tools/config.vala
  3. 96 96
      tools/core/console_client.vala
  4. 1060 1059
      tools/core/database.vala
  5. 74 73
      tools/core/guid.vala
  6. 247 246
      tools/core/json/json.vala
  7. 302 301
      tools/core/json/sjson.vala
  8. 16 15
      tools/core/math/math_utils.vala
  9. 15 14
      tools/core/math/matrix4x4.vala
  10. 91 90
      tools/core/math/quaternion.vala
  11. 32 31
      tools/core/math/vector2.vala
  12. 38 37
      tools/core/math/vector3.vala
  13. 40 39
      tools/core/math/vector4.vala
  14. 35 34
      tools/level_editor/action_type.vala
  15. 33 32
      tools/level_editor/data_compiler.vala
  16. 19 18
      tools/level_editor/dialog_level_changed.vala
  17. 16 15
      tools/level_editor/dialog_open_project.vala
  18. 218 217
      tools/level_editor/editor_view.vala
  19. 655 654
      tools/level_editor/level.vala
  20. 1552 1551
      tools/level_editor/level_editor.vala
  21. 94 93
      tools/level_editor/level_layers_tree_view.vala
  22. 336 335
      tools/level_editor/level_tree_view.vala
  23. 71 70
      tools/level_editor/panel_new_project.vala
  24. 127 126
      tools/level_editor/panel_projects_list.vala
  25. 6 5
      tools/level_editor/panel_welcome.vala
  26. 91 90
      tools/level_editor/preferences_dialog.vala
  27. 586 769
      tools/level_editor/project.vala
  28. 438 437
      tools/level_editor/project_browser.vala
  29. 213 212
      tools/level_editor/project_store.vala
  30. 653 652
      tools/level_editor/properties_view.vala
  31. 266 265
      tools/level_editor/resource_chooser.vala
  32. 38 37
      tools/level_editor/statusbar.vala
  33. 111 110
      tools/level_editor/user.vala
  34. 29 28
      tools/widgets/check_box.vala
  35. 20 19
      tools/widgets/clamp.vala
  36. 36 35
      tools/widgets/color_button_vector3.vala
  37. 35 34
      tools/widgets/combo_box_map.vala
  38. 155 154
      tools/widgets/console_view.vala
  39. 89 88
      tools/widgets/entry_double.vala
  40. 6 5
      tools/widgets/entry_position.vala
  41. 22 21
      tools/widgets/entry_rotation.vala
  42. 6 5
      tools/widgets/entry_scale.vala
  43. 41 40
      tools/widgets/entry_vector2.vala
  44. 61 60
      tools/widgets/entry_vector3.vala
  45. 51 50
      tools/widgets/entry_vector4.vala
  46. 65 64
      tools/widgets/resource_chooser_button.vala
  47. 9 8
      tools/widgets/slide.vala

+ 308 - 308
tools/api/engine_api.vala

@@ -5,378 +5,378 @@
 
 namespace Crown
 {
-	/// Functions to encode Vala types to Lua.
-	namespace Lua
+/// Functions to encode Vala types to Lua.
+namespace Lua
+{
+	public string bool(bool b)
 	{
-		public string bool(bool b)
-		{
-			return b == true ? "true" : "false";
-		}
-
-		public string vector2(Vector2 v)
-		{
-			return "Vector2(%.17g, %.17g)".printf(v.x, v.y);
-		}
-
-		public string vector3(Vector3 v)
-		{
-			return "Vector3(%.17g, %.17g, %.17g)".printf(v.x, v.y, v.z);
-		}
+		return b == true ? "true" : "false";
+	}
 
-		public string quaternion(Quaternion q)
-		{
-			return "Quaternion.from_elements(%.17g, %.17g, %.17g, %.17g)".printf(q.x, q.y, q.z, q.w);
-		}
+	public string vector2(Vector2 v)
+	{
+		return "Vector2(%.17g, %.17g)".printf(v.x, v.y);
+	}
 
+	public string vector3(Vector3 v)
+	{
+		return "Vector3(%.17g, %.17g, %.17g)".printf(v.x, v.y, v.z);
 	}
 
-	namespace DataCompilerApi
+	public string quaternion(Quaternion q)
 	{
-		public string compile(Guid id, string data_dir, string platform)
-		{
-			return "{\"type\":\"compile\",\"id\":\"%s\",\"data_dir\":\"%s\",\"platform\":\"%s\"}".printf(id.to_string()
-				, data_dir.replace("\\", "\\\\").replace("\"", "\\\"")
-				, platform
-				);
-		}
+		return "Quaternion.from_elements(%.17g, %.17g, %.17g, %.17g)".printf(q.x, q.y, q.z, q.w);
+	}
 
-		public string quit()
-		{
-			return "{\"type\":\"quit\"}";
-		}
+}
 
+namespace DataCompilerApi
+{
+	public string compile(Guid id, string data_dir, string platform)
+	{
+		return "{\"type\":\"compile\",\"id\":\"%s\",\"data_dir\":\"%s\",\"platform\":\"%s\"}".printf(id.to_string()
+			, data_dir.replace("\\", "\\\\").replace("\"", "\\\"")
+			, platform
+			);
 	}
 
-	namespace DeviceApi
+	public string quit()
 	{
-		public string command(string[] args)
-		{
-			StringBuilder sb = new StringBuilder();
-			for (int i = 0; i < args.length; ++i)
-			{
-				string arg = args[i].replace("\\", "\\\\").replace("\"", "\\\"");
-				sb.append("\"%s\",".printf(arg));
-			}
-
-			return "{\"type\":\"command\",\"args\":[%s]}".printf(sb.str);
-		}
+		return "{\"type\":\"quit\"}";
+	}
 
-		public string reload(string type, string name)
-		{
-			return command({ "reload", type, name });
-		}
+}
 
-		public string refresh()
+namespace DeviceApi
+{
+	public string command(string[] args)
+	{
+		StringBuilder sb = new StringBuilder();
+		for (int i = 0; i < args.length; ++i)
 		{
-			return command({ "refresh" });
+			string arg = args[i].replace("\\", "\\\\").replace("\"", "\\\"");
+			sb.append("\"%s\",".printf(arg));
 		}
 
-		public string pause()
-		{
-			return command({ "pause" });
-		}
+		return "{\"type\":\"command\",\"args\":[%s]}".printf(sb.str);
+	}
 
-		public string unpause()
-		{
-			return command({ "unpause" });
-		}
+	public string reload(string type, string name)
+	{
+		return command({ "reload", type, name });
+	}
 
-		public string resize(int width, int height)
-		{
-			return "{\"type\":\"resize\",\"width\":%d,\"height\":%d}".printf(width, height);
-		}
+	public string refresh()
+	{
+		return command({ "refresh" });
+	}
 
+	public string pause()
+	{
+		return command({ "pause" });
 	}
 
-	namespace LevelEditorApi
+	public string unpause()
 	{
-		public string reset()
-		{
-			return "LevelEditor:reset()";
-		}
+		return command({ "unpause" });
+	}
 
-		public string set_mouse_state(int x, int y, bool left, bool middle, bool right)
-		{
-			return "LevelEditor:set_mouse_state(%d,%d,%s,%s,%s)".printf(x
-				, y
-				, Lua.bool(left)
-				, Lua.bool(middle)
-				, Lua.bool(right)
-				);
-		}
+	public string resize(int width, int height)
+	{
+		return "{\"type\":\"resize\",\"width\":%d,\"height\":%d}".printf(width, height);
+	}
 
-		public string mouse_wheel(double delta)
-		{
-			return "LevelEditor:mouse_wheel(%.17g)".printf(delta);
-		}
+}
 
-		public string mouse_down(int x, int y)
-		{
-			return "LevelEditor:mouse_down(%d,%d)".printf(x, y);
-		}
+namespace LevelEditorApi
+{
+	public string reset()
+	{
+		return "LevelEditor:reset()";
+	}
 
-		public string mouse_up(int x, int y)
-		{
-			return "LevelEditor:mouse_up(%d,%d)".printf(x, y);
-		}
+	public string set_mouse_state(int x, int y, bool left, bool middle, bool right)
+	{
+		return "LevelEditor:set_mouse_state(%d,%d,%s,%s,%s)".printf(x
+			, y
+			, Lua.bool(left)
+			, Lua.bool(middle)
+			, Lua.bool(right)
+			);
+	}
 
-		public string key_down(string key)
-		{
-			return "LevelEditor:key_down(\"%s\")".printf(key);
-		}
+	public string mouse_wheel(double delta)
+	{
+		return "LevelEditor:mouse_wheel(%.17g)".printf(delta);
+	}
 
-		public string key_up(string key)
-		{
-			return "LevelEditor:key_up(\"%s\")".printf(key);
-		}
+	public string mouse_down(int x, int y)
+	{
+		return "LevelEditor:mouse_down(%d,%d)".printf(x, y);
+	}
 
-		public enum ToolType
-		{
-			PLACE,
-			MOVE,
-			ROTATE,
-			SCALE
-		}
+	public string mouse_up(int x, int y)
+	{
+		return "LevelEditor:mouse_up(%d,%d)".printf(x, y);
+	}
 
-		private const string[] _tools =
-		{
-			"place_tool",
-			"move_tool",
-			"rotate_tool",
-			"scale_tool"
-		};
+	public string key_down(string key)
+	{
+		return "LevelEditor:key_down(\"%s\")".printf(key);
+	}
 
-		public string set_tool_type(ToolType type)
-		{
-			return "LevelEditor:set_tool(LevelEditor.%s)".printf(_tools[(int)type]);
-		}
+	public string key_up(string key)
+	{
+		return "LevelEditor:key_up(\"%s\")".printf(key);
+	}
 
-		public enum SnapMode
-		{
-			RELATIVE,
-			ABSOLUTE
-		}
+	public enum ToolType
+	{
+		PLACE,
+		MOVE,
+		ROTATE,
+		SCALE
+	}
 
-		public string set_snap_mode(SnapMode sm)
-		{
-			return """LevelEditor:set_snap_mode("%s")""".printf(sm == SnapMode.RELATIVE ? "relative" : "absolute");
-		}
+	private const string[] _tools =
+	{
+		"place_tool",
+		"move_tool",
+		"rotate_tool",
+		"scale_tool"
+	};
 
-		public enum ReferenceSystem
-		{
-			LOCAL,
-			WORLD
-		}
+	public string set_tool_type(ToolType type)
+	{
+		return "LevelEditor:set_tool(LevelEditor.%s)".printf(_tools[(int)type]);
+	}
 
-		public string set_reference_system(ReferenceSystem rs)
-		{
-			return """LevelEditor:set_reference_system("%s")""".printf(rs == ReferenceSystem.LOCAL ? "local" : "world");
-		}
+	public enum SnapMode
+	{
+		RELATIVE,
+		ABSOLUTE
+	}
 
-		public string enable_show_grid(bool enabled)
-		{
-			return "LevelEditor:enable_show_grid(%s)".printf(Lua.bool(enabled));
-		}
+	public string set_snap_mode(SnapMode sm)
+	{
+		return """LevelEditor:set_snap_mode("%s")""".printf(sm == SnapMode.RELATIVE ? "relative" : "absolute");
+	}
 
-		public string enable_snap_to_grid(bool enabled)
-		{
-			return "LevelEditor:enable_snap_to_grid(%s)".printf(Lua.bool(enabled));
-		}
+	public enum ReferenceSystem
+	{
+		LOCAL,
+		WORLD
+	}
 
-		public string enable_debug_render_world(bool enabled)
-		{
-			return "LevelEditor:enable_debug_render_world(%s)".printf(Lua.bool(enabled));
-		}
+	public string set_reference_system(ReferenceSystem rs)
+	{
+		return """LevelEditor:set_reference_system("%s")""".printf(rs == ReferenceSystem.LOCAL ? "local" : "world");
+	}
 
-		public string enable_debug_physics_world(bool enabled)
-		{
-			return "LevelEditor:enable_debug_physics_world(%s)".printf(Lua.bool(enabled));
-		}
+	public string enable_show_grid(bool enabled)
+	{
+		return "LevelEditor:enable_show_grid(%s)".printf(Lua.bool(enabled));
+	}
 
-		public string set_grid_size(double size)
-		{
-			return "LevelEditor:set_grid_size(%.17g)".printf(size);
-		}
+	public string enable_snap_to_grid(bool enabled)
+	{
+		return "LevelEditor:enable_snap_to_grid(%s)".printf(Lua.bool(enabled));
+	}
 
-		public string set_rotation_snap(double deg)
-		{
-			return "LevelEditor:set_rotation_snap(%.17g)".printf(MathUtils.rad(deg));
-		}
+	public string enable_debug_render_world(bool enabled)
+	{
+		return "LevelEditor:enable_debug_render_world(%s)".printf(Lua.bool(enabled));
+	}
 
-		public string spawn_unit(Guid id, string name, Vector3 pos, Quaternion rot, Vector3 scl)
-		{
-			return "LevelEditor:spawn_unit(\"%s\", \"%s\", %s, %s, %s)".printf(id.to_string()
-				, name
-				, Lua.vector3(pos)
-				, Lua.quaternion(rot)
-				, Lua.vector3(scl)
-				);
-		}
+	public string enable_debug_physics_world(bool enabled)
+	{
+		return "LevelEditor:enable_debug_physics_world(%s)".printf(Lua.bool(enabled));
+	}
 
-		public string spawn_empty_unit(Guid id)
-		{
-			return "LevelEditor:spawn_empty_unit(\"%s\")".printf(id.to_string());
-		}
+	public string set_grid_size(double size)
+	{
+		return "LevelEditor:set_grid_size(%.17g)".printf(size);
+	}
 
-		public string spawn_sound(Guid id, string name, Vector3 pos, Quaternion rot, double range, double volume, bool loop)
-		{
-			return "LevelEditor:spawn_sound(\"%s\", \"%s\", %s, %s, %.17g, %.17g, %s)".printf(id.to_string()
-				, name
-				, Lua.vector3(pos)
-				, Lua.quaternion(rot)
-				, range
-				, volume
-				, Lua.bool(loop)
-				);
-		}
+	public string set_rotation_snap(double deg)
+	{
+		return "LevelEditor:set_rotation_snap(%.17g)".printf(MathUtils.rad(deg));
+	}
 
-		public string add_tranform_component(Guid id, Guid component_id, Vector3 pos, Quaternion rot, Vector3 scl)
-		{
-			return "LevelEditor:add_transform_component(\"%s\", \"%s\", %s, %s, %s)".printf(id.to_string()
-				, component_id.to_string()
-				, Lua.vector3(pos)
-				, Lua.quaternion(rot)
-				, Lua.vector3(scl)
-				);
-		}
+	public string spawn_unit(Guid id, string name, Vector3 pos, Quaternion rot, Vector3 scl)
+	{
+		return "LevelEditor:spawn_unit(\"%s\", \"%s\", %s, %s, %s)".printf(id.to_string()
+			, name
+			, Lua.vector3(pos)
+			, Lua.quaternion(rot)
+			, Lua.vector3(scl)
+			);
+	}
 
-		public string add_mesh_component(Guid id, Guid component_id, string mesh_resource, string geometry_name, string material_resource, bool visible)
-		{
-			return "LevelEditor:add_mesh_component(\"%s\", \"%s\", \"%s\", \"%s\", \"%s\", %s)".printf(id.to_string()
-				, component_id.to_string()
-				, mesh_resource
-				, geometry_name
-				, material_resource
-				, Lua.bool(visible)
-				);
-		}
+	public string spawn_empty_unit(Guid id)
+	{
+		return "LevelEditor:spawn_empty_unit(\"%s\")".printf(id.to_string());
+	}
 
-		public string add_sprite_component(Guid id, Guid component_id, string sprite_resource, string material_resource, double layer, double depth, bool visible)
-		{
-			return "LevelEditor:add_sprite_component(\"%s\", \"%s\", \"%s\", \"%s\", %.17g, %.17g, %s)".printf(id.to_string()
-				, component_id.to_string()
-				, sprite_resource
-				, material_resource
-				, layer
-				, depth
-				, Lua.bool(visible)
-				);
-		}
+	public string spawn_sound(Guid id, string name, Vector3 pos, Quaternion rot, double range, double volume, bool loop)
+	{
+		return "LevelEditor:spawn_sound(\"%s\", \"%s\", %s, %s, %.17g, %.17g, %s)".printf(id.to_string()
+			, name
+			, Lua.vector3(pos)
+			, Lua.quaternion(rot)
+			, range
+			, volume
+			, Lua.bool(loop)
+			);
+	}
 
-		public string add_camera_component(Guid id, Guid component_id, string projection, double fov, double far_range, double near_range)
-		{
-			return "LevelEditor:add_camera_component(\"%s\", \"%s\", \"%s\", %.17g, %.17g, %.17g)".printf(id.to_string()
-				, component_id.to_string()
-				, projection
-				, fov
-				, far_range
-				, near_range
-				);
-		}
+	public string add_tranform_component(Guid id, Guid component_id, Vector3 pos, Quaternion rot, Vector3 scl)
+	{
+		return "LevelEditor:add_transform_component(\"%s\", \"%s\", %s, %s, %s)".printf(id.to_string()
+			, component_id.to_string()
+			, Lua.vector3(pos)
+			, Lua.quaternion(rot)
+			, Lua.vector3(scl)
+			);
+	}
 
-		public string add_light_component(Guid id, Guid component_id, string type, double range, double intensity, double spot_angle, Vector3 color)
-		{
-			return "LevelEditor:add_light_component(\"%s\", \"%s\", \"%s\", %.17g, %.17g, %.17g, %s)".printf(id.to_string()
-				, component_id.to_string()
-				, type
-				, range
-				, intensity
-				, spot_angle
-				, Lua.vector3(color)
-				);
-		}
+	public string add_mesh_component(Guid id, Guid component_id, string mesh_resource, string geometry_name, string material_resource, bool visible)
+	{
+		return "LevelEditor:add_mesh_component(\"%s\", \"%s\", \"%s\", \"%s\", \"%s\", %s)".printf(id.to_string()
+			, component_id.to_string()
+			, mesh_resource
+			, geometry_name
+			, material_resource
+			, Lua.bool(visible)
+			);
+	}
 
-		public string move_object(Guid id, Vector3 pos, Quaternion rot, Vector3 scl)
-		{
-			return @"LevelEditor:move_object(\"%s\", %s, %s, %s)".printf(id.to_string()
-				, Lua.vector3(pos)
-				, Lua.quaternion(rot)
-				, Lua.vector3(scl)
-				);
-		}
+	public string add_sprite_component(Guid id, Guid component_id, string sprite_resource, string material_resource, double layer, double depth, bool visible)
+	{
+		return "LevelEditor:add_sprite_component(\"%s\", \"%s\", \"%s\", \"%s\", %.17g, %.17g, %s)".printf(id.to_string()
+			, component_id.to_string()
+			, sprite_resource
+			, material_resource
+			, layer
+			, depth
+			, Lua.bool(visible)
+			);
+	}
 
-		public string set_light(Guid id, string type, double range, double intensity, double spot_angle, Vector3 color)
-		{
-			return @"LevelEditor._objects[\"%s\"]:set_light(\"%s\", %.17g, %.17g, %.17g, %s)".printf(id.to_string()
-				, type
-				, range
-				, intensity
-				, spot_angle
-				, Lua.quaternion({color.x, color.y, color.z, 1.0})
-				);
-		}
+	public string add_camera_component(Guid id, Guid component_id, string projection, double fov, double far_range, double near_range)
+	{
+		return "LevelEditor:add_camera_component(\"%s\", \"%s\", \"%s\", %.17g, %.17g, %.17g)".printf(id.to_string()
+			, component_id.to_string()
+			, projection
+			, fov
+			, far_range
+			, near_range
+			);
+	}
 
-		public string set_sound_range(Guid id, double range)
-		{
-			return @"LevelEditor._objects[\"%s\"]:set_range(%.17g)".printf(id.to_string()
-				, range
-				);
-		}
+	public string add_light_component(Guid id, Guid component_id, string type, double range, double intensity, double spot_angle, Vector3 color)
+	{
+		return "LevelEditor:add_light_component(\"%s\", \"%s\", \"%s\", %.17g, %.17g, %.17g, %s)".printf(id.to_string()
+			, component_id.to_string()
+			, type
+			, range
+			, intensity
+			, spot_angle
+			, Lua.vector3(color)
+			);
+	}
 
-		public string set_mesh(Guid id, double instance_id, string material, bool visible)
-		{
-			return @"LevelEditor._objects[\"%s\"]:set_mesh(%.17g, \"%s\", %s)".printf(id.to_string()
-				, instance_id
-				, material
-				, Lua.bool(visible)
-				);
-		}
+	public string move_object(Guid id, Vector3 pos, Quaternion rot, Vector3 scl)
+	{
+		return @"LevelEditor:move_object(\"%s\", %s, %s, %s)".printf(id.to_string()
+			, Lua.vector3(pos)
+			, Lua.quaternion(rot)
+			, Lua.vector3(scl)
+			);
+	}
 
-		public string set_sprite(Guid id, string material, double layer, double depth, bool visible)
-		{
-			return @"LevelEditor._objects[\"%s\"]:set_sprite(\"%s\", %.17g, %.17g, %s)".printf(id.to_string()
-				, material
-				, layer
-				, depth
-				, Lua.bool(visible)
-				);
-		}
+	public string set_light(Guid id, string type, double range, double intensity, double spot_angle, Vector3 color)
+	{
+		return @"LevelEditor._objects[\"%s\"]:set_light(\"%s\", %.17g, %.17g, %.17g, %s)".printf(id.to_string()
+			, type
+			, range
+			, intensity
+			, spot_angle
+			, Lua.quaternion({color.x, color.y, color.z, 1.0})
+			);
+	}
 
-		public string set_camera(Guid id, string projection, double fov, double near_range, double far_range)
-		{
-			return @"LevelEditor._objects[\"%s\"]:set_camera(\"%s\", %.17g, %.17g, %.17g)".printf(id.to_string()
-				, projection
-				, fov
-				, near_range
-				, far_range
-				);
-		}
+	public string set_sound_range(Guid id, double range)
+	{
+		return @"LevelEditor._objects[\"%s\"]:set_range(%.17g)".printf(id.to_string()
+			, range
+			);
+	}
 
-		public string set_placeable(string type, string name)
-		{
-			return "LevelEditor:set_placeable(\"%s\", \"%s\")".printf(type, name);
-		}
+	public string set_mesh(Guid id, double instance_id, string material, bool visible)
+	{
+		return @"LevelEditor._objects[\"%s\"]:set_mesh(%.17g, \"%s\", %s)".printf(id.to_string()
+			, instance_id
+			, material
+			, Lua.bool(visible)
+			);
+	}
 
-		public string selection_set(Guid[] ids)
-		{
-			StringBuilder sb = new StringBuilder();
-			sb.append("LevelEditor._selection:set({");
-			for (int i = 0; i < ids.length; ++i)
-				sb.append("\"%s\",".printf(ids[i].to_string()));
-			sb.append("})");
-			return sb.str;
-		}
+	public string set_sprite(Guid id, string material, double layer, double depth, bool visible)
+	{
+		return @"LevelEditor._objects[\"%s\"]:set_sprite(\"%s\", %.17g, %.17g, %s)".printf(id.to_string()
+			, material
+			, layer
+			, depth
+			, Lua.bool(visible)
+			);
+	}
 
-		public string destroy(Guid id)
-		{
-			return @"LevelEditor:destroy(\"%s\")".printf(id.to_string());
-		}
+	public string set_camera(Guid id, string projection, double fov, double near_range, double far_range)
+	{
+		return @"LevelEditor._objects[\"%s\"]:set_camera(\"%s\", %.17g, %.17g, %.17g)".printf(id.to_string()
+			, projection
+			, fov
+			, near_range
+			, far_range
+			);
+	}
 
-		public string set_color(string name, Vector3 color)
-		{
-			Quaternion c = Quaternion(color.x, color.y, color.z, 1.0);
-			return @"Colors.%s = function() return %s end".printf(name, Lua.quaternion(c));
-		}
+	public string set_placeable(string type, string name)
+	{
+		return "LevelEditor:set_placeable(\"%s\", \"%s\")".printf(type, name);
+	}
 
+	public string selection_set(Guid[] ids)
+	{
+		StringBuilder sb = new StringBuilder();
+		sb.append("LevelEditor._selection:set({");
+		for (int i = 0; i < ids.length; ++i)
+			sb.append("\"%s\",".printf(ids[i].to_string()));
+		sb.append("})");
+		return sb.str;
 	}
 
-	namespace UnitPreviewApi
+	public string destroy(Guid id)
 	{
-		public string set_preview_resource(string placeable_type, string name)
-		{
-			return "UnitPreview:set_preview_resource(\"%s\", \"%s\")".printf(placeable_type, name);
-		}
+		return @"LevelEditor:destroy(\"%s\")".printf(id.to_string());
+	}
+
+	public string set_color(string name, Vector3 color)
+	{
+		Quaternion c = Quaternion(color.x, color.y, color.z, 1.0);
+		return @"Colors.%s = function() return %s end".printf(name, Lua.quaternion(c));
+	}
 
+}
+
+namespace UnitPreviewApi
+{
+	public string set_preview_resource(string placeable_type, string name)
+	{
+		return "UnitPreview:set_preview_resource(\"%s\", \"%s\")".printf(placeable_type, name);
 	}
 
 }
+
+}

+ 17 - 16
tools/config.vala

@@ -5,29 +5,30 @@
 
 namespace Crown
 {
-	const string CROWN_VERSION = "0.39.0";
+const string CROWN_VERSION = "0.39.0";
 
 #if CROWN_PLATFORM_LINUX
-	const string ENGINE_DIR = ".";
-	const string EXE_PREFIX = "./";
-	const string EXE_SUFFIX = "";
+const string ENGINE_DIR = ".";
+const string EXE_PREFIX = "./";
+const string EXE_SUFFIX = "";
 #elif CROWN_PLATFORM_WINDOWS
-	const string ENGINE_DIR = ".";
-	const string EXE_PREFIX = "";
-	const string EXE_SUFFIX = ".exe";
+const string ENGINE_DIR = ".";
+const string EXE_PREFIX = "";
+const string EXE_SUFFIX = ".exe";
 #endif
-	const string ENGINE_EXE = EXE_PREFIX
+const string ENGINE_EXE = EXE_PREFIX
 #if CROWN_DEBUG
-		+ "crown-debug"
+	+ "crown-debug"
 #else
-		+ "crown-development"
+	+ "crown-development"
 #endif
-		+ EXE_SUFFIX;
-	const string DEPLOY_DEFAULT_NAME = "crown-release";
-	const string DEPLOY_EXE = EXE_PREFIX + DEPLOY_DEFAULT_NAME + EXE_SUFFIX;
+	+ EXE_SUFFIX;
+const string DEPLOY_DEFAULT_NAME = "crown-release";
+const string DEPLOY_EXE = EXE_PREFIX + DEPLOY_DEFAULT_NAME + EXE_SUFFIX;
 
-	const uint16 CROWN_DEFAULT_SERVER_PORT = 10618;
+const uint16 CROWN_DEFAULT_SERVER_PORT = 10618;
+
+const string LEVEL_EDITOR_BOOT_DIR = "core/editors/level_editor";
+const string UNIT_PREVIEW_BOOT_DIR = "core/editors/unit_preview";
 
-	const string LEVEL_EDITOR_BOOT_DIR = "core/editors/level_editor";
-	const string UNIT_PREVIEW_BOOT_DIR = "core/editors/unit_preview";
 }

+ 96 - 96
tools/core/console_client.vala

@@ -5,127 +5,127 @@
 
 namespace Crown
 {
-	/// Manages communication with engine executable.
-	public class ConsoleClient : GLib.Object
-	{
-		private SocketConnection _connection;
+/// Manages communication with engine executable.
+public class ConsoleClient : GLib.Object
+{
+	private SocketConnection _connection;
+
+	// Signals
+	public signal void connected(string address, int port);
+	public signal void disconnected();
+	public signal void message_received(ConsoleClient client, uint8[] json);
 
-		// Signals
-		public signal void connected(string address, int port);
-		public signal void disconnected();
-		public signal void message_received(ConsoleClient client, uint8[] json);
+	public ConsoleClient()
+	{
+		_connection = null;
+	}
 
-		public ConsoleClient()
+	public new void connect(string address, int port)
+	{
+		try
 		{
-			_connection = null;
+			GLib.SocketClient client = new GLib.SocketClient();
+			_connection = client.connect(new InetSocketAddress.from_string(address, port), null);
+			if (_connection != null)
+				connected(address, port);
 		}
-
-		public new void connect(string address, int port)
+		catch (Error e)
 		{
-			try
-			{
-				GLib.SocketClient client = new GLib.SocketClient();
-				_connection = client.connect(new InetSocketAddress.from_string(address, port), null);
-				if (_connection != null)
-					connected(address, port);
-			}
-			catch (Error e)
-			{
-				stderr.printf("%s\n", e.message);
-			}
+			stderr.printf("%s\n", e.message);
 		}
+	}
 
-		public void close()
+	public void close()
+	{
+		try
 		{
-			try
+			if (_connection != null)
 			{
-				if (_connection != null)
-				{
-					_connection.close();
-					_connection = null;
-					disconnected();
-				}
-			}
-			catch (Error e)
-			{
-				stderr.printf("%s\n", e.message);
+				_connection.close();
+				_connection = null;
+				disconnected();
 			}
 		}
-
-		public bool is_connected()
+		catch (Error e)
 		{
-			return _connection != null && _connection.is_connected();
+			stderr.printf("%s\n", e.message);
 		}
+	}
 
-		// Sends the JSON-encoded data to the target
-		public void send(string json)
-		{
-			if (_connection == null)
-				return;
+	public bool is_connected()
+	{
+		return _connection != null && _connection.is_connected();
+	}
 
-			try
-			{
-				// FIXME: Add bit conversion utils
-				uint32 len = json.length;
-				uint8* ptr = (uint8*)(&len);
-				var array = new uint8[4];
-				for (var i = 0; i < 4; ++i)
-					array[i] = ptr[i];
-
-				_connection.output_stream.write(array);
-				_connection.output_stream.write(json.data);
-			}
-			catch (Error e)
-			{
-				stderr.printf("%s\n", e.message);
-			}
-		}
+	// Sends the JSON-encoded data to the target
+	public void send(string json)
+	{
+		if (_connection == null)
+			return;
 
-		// Sends the lua script to the target
-		public void send_script(string lua)
+		try
 		{
-			send("{\"type\":\"repl\",\"repl\":\"" + lua.replace("\\", "\\\\").replace("\"", "\\\"") + "\"}");
+			// FIXME: Add bit conversion utils
+			uint32 len = json.length;
+			uint8* ptr = (uint8*)(&len);
+			var array = new uint8[4];
+			for (var i = 0; i < 4; ++i)
+				array[i] = ptr[i];
+
+			_connection.output_stream.write(array);
+			_connection.output_stream.write(json.data);
 		}
-
-		public void receive_async()
+		catch (Error e)
 		{
-			_connection.input_stream.read_bytes_async.begin(4, GLib.Priority.DEFAULT, null, on_read);
+			stderr.printf("%s\n", e.message);
 		}
+	}
+
+	// Sends the lua script to the target
+	public void send_script(string lua)
+	{
+		send("{\"type\":\"repl\",\"repl\":\"" + lua.replace("\\", "\\\\").replace("\"", "\\\"") + "\"}");
+	}
 
-		private void on_read(Object? obj, AsyncResult ar)
+	public void receive_async()
+	{
+		_connection.input_stream.read_bytes_async.begin(4, GLib.Priority.DEFAULT, null, on_read);
+	}
+
+	private void on_read(Object? obj, AsyncResult ar)
+	{
+		try
 		{
-			try
-			{
-				InputStream input_stream = (InputStream)obj;
-				uint8[] header = input_stream.read_bytes_async.end(ar).get_data();
-
-				// Connection closed gracefully
-				if (header.length == 0)
-				{
-					close();
-					return;
-				}
-				assert(header.length > 0);
-
-				// FIXME: Add bit conversion utils
-				uint32 size = 0;
-				size |= header[3] << 24;
-				size |= header[2] << 16;
-				size |= header[1] << 8;
-				size |= header[0] << 0;
-
-				uint8[] data = new uint8[size];
-				size_t bytes_read = 0;
-				if (input_stream.read_all(data, out bytes_read))
-					message_received(this, data);
-			}
-			catch (Error e)
+			InputStream input_stream = (InputStream)obj;
+			uint8[] header = input_stream.read_bytes_async.end(ar).get_data();
+
+			// Connection closed gracefully
+			if (header.length == 0)
 			{
-				stderr.printf("%s\n", e.message);
-				if (e.code == 44) // An existing connection was forcibly closed by the remote host.
-					disconnected();
+				close();
+				return;
 			}
+			assert(header.length > 0);
+
+			// FIXME: Add bit conversion utils
+			uint32 size = 0;
+			size |= header[3] << 24;
+			size |= header[2] << 16;
+			size |= header[1] << 8;
+			size |= header[0] << 0;
+
+			uint8[] data = new uint8[size];
+			size_t bytes_read = 0;
+			if (input_stream.read_all(data, out bytes_read))
+				message_received(this, data);
+		}
+		catch (Error e)
+		{
+			stderr.printf("%s\n", e.message);
+			if (e.code == 44) // An existing connection was forcibly closed by the remote host.
+				disconnected();
 		}
 	}
+}
 
 } // namespace Crown

+ 1060 - 1059
tools/core/database.vala

@@ -7,1307 +7,1308 @@ using Gee;
 
 namespace Crown
 {
-	public class Database
+public class Database
+{
+	private enum Action
+	{
+		CREATE,
+		DESTROY,
+		SET_PROPERTY_NULL,
+		SET_PROPERTY_BOOL,
+		SET_PROPERTY_DOUBLE,
+		SET_PROPERTY_STRING,
+		SET_PROPERTY_GUID,
+		SET_PROPERTY_VECTOR3,
+		SET_PROPERTY_QUATERNION,
+		ADD_TO_SET,
+		REMOVE_FROM_SET,
+		RESTORE_POINT
+	}
+
+	private struct RestorePoint
 	{
-		private enum Action
+		public int id;
+		public uint32 size;
+		public Guid[] data;
+	}
+
+	private class Stack
+	{
+		private uint8[] _data;
+		private uint32 _read;
+
+		public Stack()
 		{
-			CREATE,
-			DESTROY,
-			SET_PROPERTY_NULL,
-			SET_PROPERTY_BOOL,
-			SET_PROPERTY_DOUBLE,
-			SET_PROPERTY_STRING,
-			SET_PROPERTY_GUID,
-			SET_PROPERTY_VECTOR3,
-			SET_PROPERTY_QUATERNION,
-			ADD_TO_SET,
-			REMOVE_FROM_SET,
-			RESTORE_POINT
+			_data = new uint8[1024*1024];
+			_read = 0;
 		}
 
-		private struct RestorePoint
+		public void clear()
 		{
-			public int id;
-			public uint32 size;
-			public Guid[] data;
+			_read = 0;
 		}
 
-		private class Stack
+		public uint32 size()
 		{
-			private uint8[] _data;
-			private uint32 _read;
-
-			public Stack()
-			{
-				_data = new uint8[1024*1024];
-				_read = 0;
-			}
-
-			public void clear()
-			{
-				_read = 0;
-			}
-
-			public uint32 size()
-			{
-				return _read;
-			}
-
-			public void write_data(void* data, ulong len)
-			{
-				uint8* buf = (uint8*)data;
-				for (ulong i = 0; i < len; ++i, ++_read)
-					_data[_read] = buf[i];
-			}
-
-			public void write_bool(bool a)
-			{
-				write_data(&a, sizeof(bool));
-			}
-
-			public void write_int(int a)
-			{
-				write_data(&a, sizeof(int));
-			}
-
-			public void write_uint32(uint32 a)
-			{
-				write_data(&a, sizeof(uint32));
-			}
-
-			public void write_double(double a)
-			{
-				write_data(&a, sizeof(double));
-			}
-
-			public void write_string(string str)
-			{
-				uint32 len = str.length;
-				write_data(&str.data[0], len);
-				write_data(&len, sizeof(uint32));
-			}
-
-			public void write_guid(Guid a)
-			{
-				write_data(&a, sizeof(Guid));
-			}
-
-			public void write_vector3(Vector3 a)
-			{
-				write_data(&a, sizeof(Vector3));
-			}
-
-			public void write_quaternion(Quaternion a)
-			{
-				write_data(&a, sizeof(Quaternion));
-			}
-
-			public void write_action(Action t)
-			{
-				write_uint32((uint32)t);
-			}
-
-			public Action read_action()
-			{
-				_read -= (uint32)sizeof(uint32);
-				uint32 a = *(uint32*)(&_data[_read]);
-				return (Action)a;
-			}
-
-			public bool read_bool()
-			{
-				_read -= (uint32)sizeof(bool);
-				bool a = *(bool*)(&_data[_read]);
-				return a;
-			}
-
-			public int read_int()
-			{
-				_read -= (uint32)sizeof(int);
-				int a = *(int*)(&_data[_read]);
-				return a;
-			}
-
-			public uint32 read_uint32()
-			{
-				_read -= (uint32)sizeof(uint32);
-				uint32 a = *(uint32*)(&_data[_read]);
-				return a;
-			}
-
-			public double read_double()
-			{
-				_read -= (uint32)sizeof(double);
-				double a = *(double*)(&_data[_read]);
-				return a;
-			}
-
-			public Guid read_guid()
-			{
-				_read -= (uint32)sizeof(Guid);
-				Guid a = *(Guid*)(&_data[_read]);
-				return a;
-			}
-
-			public Vector3 read_vector3()
-			{
-				_read -= (uint32)sizeof(Vector3);
-				Vector3 a = *(Vector3*)(&_data[_read]);
-				return a;
-			}
-
-			public Quaternion read_quaternion()
-			{
-				_read -= (uint32)sizeof(Quaternion);
-				Quaternion a = *(Quaternion*)(&_data[_read]);
-				return a;
-			}
+			return _read;
+		}
 
-			public string read_string()
-			{
-				_read -= (uint32)sizeof(uint32);
-				uint32 len = *(uint32*)(&_data[_read]);
-				_read -= len;
-				uint8[] str = new uint8[len + 1];
-				for (uint32 i = 0; i < len; ++i)
-					str[i] = *(uint8*)(&_data[_read + i]);
-				str[len] = '\0';
-				return (string)str;
-			}
+		public void write_data(void* data, ulong len)
+		{
+			uint8* buf = (uint8*)data;
+			for (ulong i = 0; i < len; ++i, ++_read)
+				_data[_read] = buf[i];
+		}
 
-			public void write_create_action(Guid id)
-			{
-				write_guid(id);
-				write_action(Action.CREATE);
-			}
+		public void write_bool(bool a)
+		{
+			write_data(&a, sizeof(bool));
+		}
 
-			public void write_destroy_action(Guid id)
-			{
-				write_guid(id);
-				write_action(Action.DESTROY);
-			}
+		public void write_int(int a)
+		{
+			write_data(&a, sizeof(int));
+		}
 
-			public void write_set_property_null_action(Guid id, string key)
-			{
-				// No value to push
-				write_string(key);
-				write_guid(id);
-				write_action(Action.SET_PROPERTY_NULL);
-			}
+		public void write_uint32(uint32 a)
+		{
+			write_data(&a, sizeof(uint32));
+		}
 
-			public void write_set_property_bool_action(Guid id, string key, bool val)
-			{
-				write_bool(val);
-				write_string(key);
-				write_guid(id);
-				write_action(Action.SET_PROPERTY_BOOL);
-			}
+		public void write_double(double a)
+		{
+			write_data(&a, sizeof(double));
+		}
 
-			public void write_set_property_double_action(Guid id, string key, double val)
-			{
-				write_double(val);
-				write_string(key);
-				write_guid(id);
-				write_action(Action.SET_PROPERTY_DOUBLE);
-			}
+		public void write_string(string str)
+		{
+			uint32 len = str.length;
+			write_data(&str.data[0], len);
+			write_data(&len, sizeof(uint32));
+		}
 
-			public void write_set_property_string_action(Guid id, string key, string val)
-			{
-				write_string(val);
-				write_string(key);
-				write_guid(id);
-				write_action(Action.SET_PROPERTY_STRING);
-			}
+		public void write_guid(Guid a)
+		{
+			write_data(&a, sizeof(Guid));
+		}
 
-			public void write_set_property_guid_action(Guid id, string key, Guid val)
-			{
-				write_guid(val);
-				write_string(key);
-				write_guid(id);
-				write_action(Action.SET_PROPERTY_GUID);
-			}
+		public void write_vector3(Vector3 a)
+		{
+			write_data(&a, sizeof(Vector3));
+		}
 
-			public void write_set_property_vector3_action(Guid id, string key, Vector3 val)
-			{
-				write_vector3(val);
-				write_string(key);
-				write_guid(id);
-				write_action(Action.SET_PROPERTY_VECTOR3);
-			}
+		public void write_quaternion(Quaternion a)
+		{
+			write_data(&a, sizeof(Quaternion));
+		}
 
-			public void write_set_property_quaternion_action(Guid id, string key, Quaternion val)
-			{
-				write_quaternion(val);
-				write_string(key);
-				write_guid(id);
-				write_action(Action.SET_PROPERTY_QUATERNION);
-			}
+		public void write_action(Action t)
+		{
+			write_uint32((uint32)t);
+		}
 
-			public void write_add_to_set_action(Guid id, string key, Guid item_id)
-			{
-				write_guid(item_id);
-				write_string(key);
-				write_guid(id);
-				write_action(Action.ADD_TO_SET);
-			}
+		public Action read_action()
+		{
+			_read -= (uint32)sizeof(uint32);
+			uint32 a = *(uint32*)(&_data[_read]);
+			return (Action)a;
+		}
 
-			public void write_remove_from_set_action(Guid id, string key, Guid item_id)
-			{
-				write_guid(item_id);
-				write_string(key);
-				write_guid(id);
-				write_action(Action.REMOVE_FROM_SET);
-			}
+		public bool read_bool()
+		{
+			_read -= (uint32)sizeof(bool);
+			bool a = *(bool*)(&_data[_read]);
+			return a;
+		}
 
-			public void write_restore_point(int id, uint32 size, Guid[] data)
-			{
-				uint32 num_guids = data.length;
-				for (uint32 i = 0; i < num_guids; ++i)
-					write_guid(data[i]);
-				write_uint32(num_guids);
-				write_uint32(size);
-				write_int(id);
-				write_action(Action.RESTORE_POINT);
-			}
+		public int read_int()
+		{
+			_read -= (uint32)sizeof(int);
+			int a = *(int*)(&_data[_read]);
+			return a;
+		}
 
-			public uint32 peek_type()
-			{
-				return *(uint32*)(&_data[_read - (uint32)sizeof(uint32)]);
-			}
+		public uint32 read_uint32()
+		{
+			_read -= (uint32)sizeof(uint32);
+			uint32 a = *(uint32*)(&_data[_read]);
+			return a;
+		}
 
-			public RestorePoint read_restore_point()
-			{
-				Action t = read_action();
-				assert(t == Action.RESTORE_POINT);
-				int id = read_int();
-				uint32 size = read_uint32();
-				uint32 num_guids = read_uint32();
-				Guid[] ids = new Guid[num_guids];
-				for (uint32 i = 0; i < num_guids; ++i)
-					ids[i] = read_guid();
-				return { id, size, ids };
-			}
+		public double read_double()
+		{
+			_read -= (uint32)sizeof(double);
+			double a = *(double*)(&_data[_read]);
+			return a;
 		}
 
-		// Data
-		private HashMap<string, Value?> _data;
-		private Stack _undo;
-		private Stack _redo;
-		private Stack _undo_points;
-		private Stack _redo_points;
-		private bool _changed;
+		public Guid read_guid()
+		{
+			_read -= (uint32)sizeof(Guid);
+			Guid a = *(Guid*)(&_data[_read]);
+			return a;
+		}
 
-		// Signals
-		public signal void key_changed(Guid id, string key);
-		public signal void undo_redo(bool undo, int id, Guid[] data);
+		public Vector3 read_vector3()
+		{
+			_read -= (uint32)sizeof(Vector3);
+			Vector3 a = *(Vector3*)(&_data[_read]);
+			return a;
+		}
 
-		public Database()
+		public Quaternion read_quaternion()
 		{
-			_data = new HashMap<string, Value?>();
-			_undo = new Stack();
-			_redo = new Stack();
-			_undo_points = new Stack();
-			_redo_points = new Stack();
+			_read -= (uint32)sizeof(Quaternion);
+			Quaternion a = *(Quaternion*)(&_data[_read]);
+			return a;
+		}
 
-			reset();
+		public string read_string()
+		{
+			_read -= (uint32)sizeof(uint32);
+			uint32 len = *(uint32*)(&_data[_read]);
+			_read -= len;
+			uint8[] str = new uint8[len + 1];
+			for (uint32 i = 0; i < len; ++i)
+				str[i] = *(uint8*)(&_data[_read + i]);
+			str[len] = '\0';
+			return (string)str;
 		}
 
-		/// Resets database to clean state.
-		public void reset()
+		public void write_create_action(Guid id)
 		{
-			_data.clear();
-			_undo.clear();
-			_redo.clear();
-			_undo_points.clear();
-			_redo_points.clear();
+			write_guid(id);
+			write_action(Action.CREATE);
+		}
 
-			_changed = false;
+		public void write_destroy_action(Guid id)
+		{
+			write_guid(id);
+			write_action(Action.DESTROY);
+		}
 
-			// This is a special field which stores all objects
-			_data.set("_objects", new HashMap<string, Value?>());
+		public void write_set_property_null_action(Guid id, string key)
+		{
+			// No value to push
+			write_string(key);
+			write_guid(id);
+			write_action(Action.SET_PROPERTY_NULL);
 		}
 
-		/// Returns whether the database has been changed since last call to Save().
-		public bool changed()
+		public void write_set_property_bool_action(Guid id, string key, bool val)
 		{
-			return _changed;
+			write_bool(val);
+			write_string(key);
+			write_guid(id);
+			write_action(Action.SET_PROPERTY_BOOL);
 		}
 
-		/// Saves database to path without marking it as not changed.
-		public void dump(string path)
+		public void write_set_property_double_action(Guid id, string key, double val)
 		{
-			Hashtable json = encode();
-			SJSON.save(json, path);
+			write_double(val);
+			write_string(key);
+			write_guid(id);
+			write_action(Action.SET_PROPERTY_DOUBLE);
 		}
 
-		/// Saves database to path.
-		public void save(string path)
+		public void write_set_property_string_action(Guid id, string key, string val)
 		{
-			dump(path);
-			_changed = false;
+			write_string(val);
+			write_string(key);
+			write_guid(id);
+			write_action(Action.SET_PROPERTY_STRING);
 		}
 
-		/// Loads database from path.
-		public void load(string path)
+		public void write_set_property_guid_action(Guid id, string key, Guid val)
 		{
-			Hashtable json = SJSON.load(path);
-			decode(json);
-			_changed = false;
+			write_guid(val);
+			write_string(key);
+			write_guid(id);
+			write_action(Action.SET_PROPERTY_GUID);
 		}
 
-		private Hashtable encode()
+		public void write_set_property_vector3_action(Guid id, string key, Vector3 val)
 		{
-			return encode_object(GUID_ZERO, _data);
+			write_vector3(val);
+			write_string(key);
+			write_guid(id);
+			write_action(Action.SET_PROPERTY_VECTOR3);
 		}
 
-		private static bool is_valid_value(Value? value)
+		public void write_set_property_quaternion_action(Guid id, string key, Quaternion val)
 		{
-			return value == null
-				|| value.holds(typeof(bool))
-				|| value.holds(typeof(double))
-				|| value.holds(typeof(string))
-				|| value.holds(typeof(Guid))
-				|| value.holds(typeof(Vector3))
-				|| value.holds(typeof(Quaternion))
-				;
+			write_quaternion(val);
+			write_string(key);
+			write_guid(id);
+			write_action(Action.SET_PROPERTY_QUATERNION);
 		}
 
-		private static bool is_valid_key(string key)
+		public void write_add_to_set_action(Guid id, string key, Guid item_id)
 		{
-			return key.length > 0
-				&& key != "_objects"
-				&& !key.has_prefix(".")
-				&& !key.has_suffix(".")
-				;
+			write_guid(item_id);
+			write_string(key);
+			write_guid(id);
+			write_action(Action.ADD_TO_SET);
 		}
 
-#if 0
-		private static string value_to_string(Value? value)
+		public void write_remove_from_set_action(Guid id, string key, Guid item_id)
 		{
-			if (value == null)
-				return "null";
-			if (value.holds(typeof(bool)))
-				return ((bool)value).to_string();
-			if (value.holds(typeof(double)))
-				return ((double)value).to_string();
-			if (value.holds(typeof(string)))
-				return ((string)value).to_string();
-			if (value.holds(typeof(Guid)))
-				return ((Guid)value).to_string();
-			if (value.holds(typeof(Vector3)))
-				return ((Vector3)value).to_string();
-			if (value.holds(typeof(Quaternion)))
-				return ((Quaternion)value).to_string();
-			if (value.holds(typeof(HashSet)))
-				return "Set<Guid>";
+			write_guid(item_id);
+			write_string(key);
+			write_guid(id);
+			write_action(Action.REMOVE_FROM_SET);
+		}
 
-			return "<invalid>";
+		public void write_restore_point(int id, uint32 size, Guid[] data)
+		{
+			uint32 num_guids = data.length;
+			for (uint32 i = 0; i < num_guids; ++i)
+				write_guid(data[i]);
+			write_uint32(num_guids);
+			write_uint32(size);
+			write_int(id);
+			write_action(Action.RESTORE_POINT);
 		}
-#endif // CROWN_DEBUG
 
-		public void decode(Hashtable json)
+		public uint32 peek_type()
 		{
-			reset();
-			decode_object(GUID_ZERO, "", json);
+			return *(uint32*)(&_data[_read - (uint32)sizeof(uint32)]);
 		}
 
-		private void decode_object(Guid id, string db_key, Hashtable json)
+		public RestorePoint read_restore_point()
 		{
-			string old_db = db_key;
-			string k = db_key;
+			Action t = read_action();
+			assert(t == Action.RESTORE_POINT);
+			int id = read_int();
+			uint32 size = read_uint32();
+			uint32 num_guids = read_uint32();
+			Guid[] ids = new Guid[num_guids];
+			for (uint32 i = 0; i < num_guids; ++i)
+				ids[i] = read_guid();
+			return { id, size, ids };
+		}
+	}
 
-			string[] keys = json.keys.to_array();
-			foreach (string key in keys)
-			{
-				assert(key != "_objects");
-				if (key == "id")
-					continue;
+	// Data
+	private HashMap<string, Value?> _data;
+	private Stack _undo;
+	private Stack _redo;
+	private Stack _undo_points;
+	private Stack _redo_points;
+	private bool _changed;
 
-				Value? val = json[key];
+	// Signals
+	public signal void key_changed(Guid id, string key);
+	public signal void undo_redo(bool undo, int id, Guid[] data);
 
-				k += k == "" ? key : ("." + key);
+	public Database()
+	{
+		_data = new HashMap<string, Value?>();
+		_undo = new Stack();
+		_redo = new Stack();
+		_undo_points = new Stack();
+		_redo_points = new Stack();
 
-				if (val.holds(typeof(Hashtable)))
-				{
-					Hashtable ht = (Hashtable)val;
-					decode_object(id, k, ht);
-				}
-				else if (val.holds(typeof(ArrayList)))
-				{
-					ArrayList<Value?> arr = (ArrayList<Value?>)val;
-					if (arr.size > 0 && arr[0].holds(typeof(double)))
-						set_property_internal(id, k, decode_value(val));
-					else
-						decode_set(id, key, arr);
-				}
-				else
-				{
-					set_property_internal(id, k, decode_value(val));
-				}
+		reset();
+	}
 
-				k = old_db;
-			}
-		}
+	/// Resets database to clean state.
+	public void reset()
+	{
+		_data.clear();
+		_undo.clear();
+		_redo.clear();
+		_undo_points.clear();
+		_redo_points.clear();
 
-		private void decode_set(Guid id, string key, ArrayList<Value?> json)
-		{
-			// Set should be created even if it is empty.
-			create_empty_set(id, key);
+		_changed = false;
 
-			for (int i = 0; i < json.size; ++i)
-			{
-				Hashtable obj = (Hashtable)json[i];
-				Guid item_id = Guid.parse((string)obj["id"]);
-				create_internal(item_id);
-				decode_object(item_id, "", obj);
-				add_to_set_internal(id, key, item_id);
-			}
-		}
+		// This is a special field which stores all objects
+		_data.set("_objects", new HashMap<string, Value?>());
+	}
+
+	/// Returns whether the database has been changed since last call to Save().
+	public bool changed()
+	{
+		return _changed;
+	}
+
+	/// Saves database to path without marking it as not changed.
+	public void dump(string path)
+	{
+		Hashtable json = encode();
+		SJSON.save(json, path);
+	}
+
+	/// Saves database to path.
+	public void save(string path)
+	{
+		dump(path);
+		_changed = false;
+	}
+
+	/// Loads database from path.
+	public void load(string path)
+	{
+		Hashtable json = SJSON.load(path);
+		decode(json);
+		_changed = false;
+	}
+
+	private Hashtable encode()
+	{
+		return encode_object(GUID_ZERO, _data);
+	}
+
+	private static bool is_valid_value(Value? value)
+	{
+		return value == null
+			|| value.holds(typeof(bool))
+			|| value.holds(typeof(double))
+			|| value.holds(typeof(string))
+			|| value.holds(typeof(Guid))
+			|| value.holds(typeof(Vector3))
+			|| value.holds(typeof(Quaternion))
+			;
+	}
+
+	private static bool is_valid_key(string key)
+	{
+		return key.length > 0
+			&& key != "_objects"
+			&& !key.has_prefix(".")
+			&& !key.has_suffix(".")
+			;
+	}
+
+#if 0
+	private static string value_to_string(Value? value)
+	{
+		if (value == null)
+			return "null";
+		if (value.holds(typeof(bool)))
+			return ((bool)value).to_string();
+		if (value.holds(typeof(double)))
+			return ((double)value).to_string();
+		if (value.holds(typeof(string)))
+			return ((string)value).to_string();
+		if (value.holds(typeof(Guid)))
+			return ((Guid)value).to_string();
+		if (value.holds(typeof(Vector3)))
+			return ((Vector3)value).to_string();
+		if (value.holds(typeof(Quaternion)))
+			return ((Quaternion)value).to_string();
+		if (value.holds(typeof(HashSet)))
+			return "Set<Guid>";
+
+		return "<invalid>";
+	}
+#endif // CROWN_DEBUG
+
+	public void decode(Hashtable json)
+	{
+		reset();
+		decode_object(GUID_ZERO, "", json);
+	}
+
+	private void decode_object(Guid id, string db_key, Hashtable json)
+	{
+		string old_db = db_key;
+		string k = db_key;
 
-		private Value? decode_value(Value? value)
+		string[] keys = json.keys.to_array();
+		foreach (string key in keys)
 		{
-			if (value.holds(typeof(ArrayList)))
-			{
-				ArrayList<Value?> al = (ArrayList<Value?>)value;
-				if (al.size == 3)
-					return Vector3((double)al[0], (double)al[1], (double)al[2]);
-				else if (al.size == 4)
-					return Quaternion((double)al[0], (double)al[1], (double)al[2], (double)al[3]);
-				else
-					assert(false);
-			}
-			else if (value.holds(typeof(string)))
+			assert(key != "_objects");
+			if (key == "id")
+				continue;
+
+			Value? val = json[key];
+
+			k += k == "" ? key : ("." + key);
+
+			if (val.holds(typeof(Hashtable)))
 			{
-				Guid id;
-				if (Guid.try_parse((string)value, out id))
-					return id;
-				return value;
+				Hashtable ht = (Hashtable)val;
+				decode_object(id, k, ht);
 			}
-			else if (value == null || value.holds(typeof(bool)) || value.holds(typeof(double)))
+			else if (val.holds(typeof(ArrayList)))
 			{
-				return value;
+				ArrayList<Value?> arr = (ArrayList<Value?>)val;
+				if (arr.size > 0 && arr[0].holds(typeof(double)))
+					set_property_internal(id, k, decode_value(val));
+				else
+					decode_set(id, key, arr);
 			}
 			else
 			{
-				assert(false);
+				set_property_internal(id, k, decode_value(val));
 			}
 
-			return null;
+			k = old_db;
 		}
+	}
+
+	private void decode_set(Guid id, string key, ArrayList<Value?> json)
+	{
+		// Set should be created even if it is empty.
+		create_empty_set(id, key);
 
-		private Hashtable encode_object(Guid id, HashMap<string, Value?> db)
+		for (int i = 0; i < json.size; ++i)
 		{
-			Hashtable obj = new Hashtable();
-			if (id != GUID_ZERO)
-				obj["id"] = id.to_string();
+			Hashtable obj = (Hashtable)json[i];
+			Guid item_id = Guid.parse((string)obj["id"]);
+			create_internal(item_id);
+			decode_object(item_id, "", obj);
+			add_to_set_internal(id, key, item_id);
+		}
+	}
 
-			string[] keys = db.keys.to_array();
-			foreach (string key in keys)
-			{
-				if (key == "_objects")
-					continue;
+	private Value? decode_value(Value? value)
+	{
+		if (value.holds(typeof(ArrayList)))
+		{
+			ArrayList<Value?> al = (ArrayList<Value?>)value;
+			if (al.size == 3)
+				return Vector3((double)al[0], (double)al[1], (double)al[2]);
+			else if (al.size == 4)
+				return Quaternion((double)al[0], (double)al[1], (double)al[2], (double)al[3]);
+			else
+				assert(false);
+		}
+		else if (value.holds(typeof(string)))
+		{
+			Guid id;
+			if (Guid.try_parse((string)value, out id))
+				return id;
+			return value;
+		}
+		else if (value == null || value.holds(typeof(bool)) || value.holds(typeof(double)))
+		{
+			return value;
+		}
+		else
+		{
+			assert(false);
+		}
 
-				// Since null-key is equivalent to non-existent key, skip serialization.
-				if (db[key] == null)
-					continue;
+		return null;
+	}
 
-				string[] foo = key.split(".");
-				Hashtable x = obj;
-				if (foo.length > 1)
-				{
-					for (int i = 0; i < foo.length - 1; ++i)
-					{
-						string f = foo[i];
+	private Hashtable encode_object(Guid id, HashMap<string, Value?> db)
+	{
+		Hashtable obj = new Hashtable();
+		if (id != GUID_ZERO)
+			obj["id"] = id.to_string();
+
+		string[] keys = db.keys.to_array();
+		foreach (string key in keys)
+		{
+			if (key == "_objects")
+				continue;
 
-						if (x.has_key(f))
-						{
-							x = (Hashtable)x[f];
-							continue;
-						}
+			// Since null-key is equivalent to non-existent key, skip serialization.
+			if (db[key] == null)
+				continue;
+
+			string[] foo = key.split(".");
+			Hashtable x = obj;
+			if (foo.length > 1)
+			{
+				for (int i = 0; i < foo.length - 1; ++i)
+				{
+					string f = foo[i];
 
-						Hashtable y = new Hashtable();
-						x.set(f, y);
-						x = y;
+					if (x.has_key(f))
+					{
+						x = (Hashtable)x[f];
+						continue;
 					}
+
+					Hashtable y = new Hashtable();
+					x.set(f, y);
+					x = y;
 				}
-				x.set(foo[foo.length-1], encode_value(db[key]));
 			}
-
-			return obj;
+			x.set(foo[foo.length-1], encode_value(db[key]));
 		}
 
-		private Value? encode_value(Value? value)
-		{
-			assert(is_valid_value(value) || value.holds(typeof(HashSet)));
+		return obj;
+	}
 
-			if (value.holds(typeof(Vector3)))
-			{
-				Vector3 v = (Vector3)value;
-				ArrayList<Value?> arr = new Gee.ArrayList<Value?>();
-				arr.add(v.x);
-				arr.add(v.y);
-				arr.add(v.z);
-				return arr;
-			}
-			else if (value.holds(typeof(Quaternion)))
-			{
-				Quaternion q = (Quaternion)value;
-				ArrayList<Value?> arr = new Gee.ArrayList<Value?>();
-				arr.add(q.x);
-				arr.add(q.y);
-				arr.add(q.z);
-				arr.add(q.w);
-				return arr;
-			}
-			else if (value.holds(typeof(Guid)))
-			{
-				Guid id = (Guid)value;
-				return id.to_string();
-			}
-			else if (value.holds(typeof(HashSet)))
-			{
-				HashSet<Guid?> hs = (HashSet<Guid?>)value;
-				ArrayList<Value?> arr = new Gee.ArrayList<Value?>();
-				foreach (Guid id in hs)
-				{
-					HashMap<string, Value?> objs = (HashMap<string, Value?>)_data["_objects"];
-					arr.add(encode_object(id, (HashMap<string, Value?>)objs[id.to_string()]));
-				}
-				return arr;
-			}
-			else
+	private Value? encode_value(Value? value)
+	{
+		assert(is_valid_value(value) || value.holds(typeof(HashSet)));
+
+		if (value.holds(typeof(Vector3)))
+		{
+			Vector3 v = (Vector3)value;
+			ArrayList<Value?> arr = new Gee.ArrayList<Value?>();
+			arr.add(v.x);
+			arr.add(v.y);
+			arr.add(v.z);
+			return arr;
+		}
+		else if (value.holds(typeof(Quaternion)))
+		{
+			Quaternion q = (Quaternion)value;
+			ArrayList<Value?> arr = new Gee.ArrayList<Value?>();
+			arr.add(q.x);
+			arr.add(q.y);
+			arr.add(q.z);
+			arr.add(q.w);
+			return arr;
+		}
+		else if (value.holds(typeof(Guid)))
+		{
+			Guid id = (Guid)value;
+			return id.to_string();
+		}
+		else if (value.holds(typeof(HashSet)))
+		{
+			HashSet<Guid?> hs = (HashSet<Guid?>)value;
+			ArrayList<Value?> arr = new Gee.ArrayList<Value?>();
+			foreach (Guid id in hs)
 			{
-				return value;
+				HashMap<string, Value?> objs = (HashMap<string, Value?>)_data["_objects"];
+				arr.add(encode_object(id, (HashMap<string, Value?>)objs[id.to_string()]));
 			}
+			return arr;
 		}
-
-		private HashMap<string, Value?> get_data(Guid id)
+		else
 		{
-			assert(has_object(id));
-
-			return (HashMap<string, Value?>)(id == GUID_ZERO ? _data : ((HashMap<string, Value?>)_data["_objects"])[id.to_string()]);
+			return value;
 		}
+	}
 
-		private void create_internal(Guid id)
-		{
-			assert(id != GUID_ZERO);
+	private HashMap<string, Value?> get_data(Guid id)
+	{
+		assert(has_object(id));
+
+		return (HashMap<string, Value?>)(id == GUID_ZERO ? _data : ((HashMap<string, Value?>)_data["_objects"])[id.to_string()]);
+	}
+
+	private void create_internal(Guid id)
+	{
+		assert(id != GUID_ZERO);
 #if 0
-			stdout.printf("create %s\n", id.to_string());
+		stdout.printf("create %s\n", id.to_string());
 #endif // CROWN_DEBUG
-			((HashMap<string, Value?>)_data["_objects"]).set(id.to_string(), new HashMap<string, Value?>());
+		((HashMap<string, Value?>)_data["_objects"]).set(id.to_string(), new HashMap<string, Value?>());
 
-			_changed = true;
-			key_changed(id, "_objects");
-		}
+		_changed = true;
+		key_changed(id, "_objects");
+	}
 
-		private void destroy_internal(Guid id)
-		{
-			assert(id != GUID_ZERO);
-			assert(has_object(id));
+	private void destroy_internal(Guid id)
+	{
+		assert(id != GUID_ZERO);
+		assert(has_object(id));
 #if 0
-			stdout.printf("destroy %s\n", id.to_string());
+		stdout.printf("destroy %s\n", id.to_string());
 #endif // CROWN_DEBUG
-			((HashMap<string, Value?>)_data["_objects"]).unset(id.to_string());
+		((HashMap<string, Value?>)_data["_objects"]).unset(id.to_string());
 
-			_changed = true;
-			key_changed(id, "_objects");
-		}
+		_changed = true;
+		key_changed(id, "_objects");
+	}
 
-		private void set_property_internal(Guid id, string key, Value? value)
-		{
-			assert(has_object(id));
-			assert(is_valid_key(key));
-			assert(is_valid_value(value));
+	private void set_property_internal(Guid id, string key, Value? value)
+	{
+		assert(has_object(id));
+		assert(is_valid_key(key));
+		assert(is_valid_value(value));
 #if 0
-			stdout.printf("set_property %s %s %s\n"
-				, id.to_string()
-				, key
-				, (value == null) ? "null" : value_to_string(value)
-				);
+		stdout.printf("set_property %s %s %s\n"
+			, id.to_string()
+			, key
+			, (value == null) ? "null" : value_to_string(value)
+			);
 #endif // CROWN_DEBUG
-			HashMap<string, Value?> ob = get_data(id);
-			ob[key] = value;
+		HashMap<string, Value?> ob = get_data(id);
+		ob[key] = value;
 
-			_changed = true;
-			key_changed(id, key);
-		}
+		_changed = true;
+		key_changed(id, key);
+	}
 
-		private void create_empty_set(Guid id, string key)
-		{
-			assert(has_object(id));
-			assert(is_valid_key(key));
+	private void create_empty_set(Guid id, string key)
+	{
+		assert(has_object(id));
+		assert(is_valid_key(key));
 
-			HashMap<string, Value?> ob = get_data(id);
-			assert(!ob.has_key(key));
+		HashMap<string, Value?> ob = get_data(id);
+		assert(!ob.has_key(key));
 
-			ob[key] = new HashSet<Guid?>(Guid.hash_func, Guid.equal_func);
-		}
+		ob[key] = new HashSet<Guid?>(Guid.hash_func, Guid.equal_func);
+	}
 
-		private void add_to_set_internal(Guid id, string key, Guid item_id)
-		{
-			assert(has_object(id));
-			assert(is_valid_key(key));
-			assert(item_id != GUID_ZERO);
-			assert(has_object(item_id));
+	private void add_to_set_internal(Guid id, string key, Guid item_id)
+	{
+		assert(has_object(id));
+		assert(is_valid_key(key));
+		assert(item_id != GUID_ZERO);
+		assert(has_object(item_id));
 #if 0
-			stdout.printf("add_to_set %s %s %s\n"
-				, id.to_string()
-				, key
-				, item_id.to_string()
-				);
+		stdout.printf("add_to_set %s %s %s\n"
+			, id.to_string()
+			, key
+			, item_id.to_string()
+			);
 #endif // CROWN_DEBUG
-			HashMap<string, Value?> ob = get_data(id);
-
-			if (!ob.has_key(key))
-			{
-				HashSet<Guid?> hs = new HashSet<Guid?>(Guid.hash_func, Guid.equal_func);
-				hs.add(item_id);
-				ob[key] = hs;
-			}
-			else
-			{
-				((HashSet<Guid?>)ob[key]).add(item_id);
-			}
+		HashMap<string, Value?> ob = get_data(id);
 
-			_changed = true;
-			key_changed(id, key);
+		if (!ob.has_key(key))
+		{
+			HashSet<Guid?> hs = new HashSet<Guid?>(Guid.hash_func, Guid.equal_func);
+			hs.add(item_id);
+			ob[key] = hs;
 		}
-
-		private void remove_from_set_internal(Guid id, string key, Guid item_id)
+		else
 		{
-			assert(has_object(id));
-			assert(is_valid_key(key));
-			assert(item_id != GUID_ZERO);
-#if 0
-			stdout.printf("remove_from_set %s %s %s\n"
-				, id.to_string()
-				, key
-				, item_id.to_string()
-				);
-#endif // CROWN_DEBUG
-			HashMap<string, Value?> ob = get_data(id);
-			((HashSet<Guid?>)ob[key]).remove(item_id);
-
-			_changed = true;
-			key_changed(id, key);
+			((HashSet<Guid?>)ob[key]).add(item_id);
 		}
 
-		public void create(Guid id)
-		{
-			assert(id != GUID_ZERO);
-			assert(!has_object(id));
+		_changed = true;
+		key_changed(id, key);
+	}
 
-			_undo.write_destroy_action(id);
-			_redo.clear();
-			_redo_points.clear();
+	private void remove_from_set_internal(Guid id, string key, Guid item_id)
+	{
+		assert(has_object(id));
+		assert(is_valid_key(key));
+		assert(item_id != GUID_ZERO);
+#if 0
+		stdout.printf("remove_from_set %s %s %s\n"
+			, id.to_string()
+			, key
+			, item_id.to_string()
+			);
+#endif // CROWN_DEBUG
+		HashMap<string, Value?> ob = get_data(id);
+		((HashSet<Guid?>)ob[key]).remove(item_id);
 
-			create_internal(id);
-		}
+		_changed = true;
+		key_changed(id, key);
+	}
 
-		public void destroy(Guid id)
-		{
-			assert(id != GUID_ZERO);
-			assert(has_object(id));
+	public void create(Guid id)
+	{
+		assert(id != GUID_ZERO);
+		assert(!has_object(id));
 
+		_undo.write_destroy_action(id);
+		_redo.clear();
+		_redo_points.clear();
 
-			HashMap<string, Value?> o = get_data(id);
-			string[] keys = o.keys.to_array();
+		create_internal(id);
+	}
 
-			foreach (string key in keys)
-			{
-				Value? value = o[key];
-				if (value.holds(typeof(HashSet)))
-				{
-					HashSet<Guid?> hs = (HashSet<Guid?>)value;
-					Guid?[] ids = hs.to_array();
-					foreach (Guid item_id in ids)
-					{
-						remove_from_set(id, key, item_id);
-						destroy(item_id);
-					}
-				}
-				else
-				{
-					set_property_null(id, key);
-				}
-			}
+	public void destroy(Guid id)
+	{
+		assert(id != GUID_ZERO);
+		assert(has_object(id));
 
-			_undo.write_create_action(id);
-			_redo.clear();
-			_redo_points.clear();
 
-			destroy_internal(id);
-		}
+		HashMap<string, Value?> o = get_data(id);
+		string[] keys = o.keys.to_array();
 
-		public void set_property_null(Guid id, string key)
+		foreach (string key in keys)
 		{
-			assert(has_object(id));
-			assert(is_valid_key(key));
-			assert(is_valid_value(null));
-
-			HashMap<string, Value?> ob = get_data(id);
-			if (ob.has_key(key) && ob[key] != null)
+			Value? value = o[key];
+			if (value.holds(typeof(HashSet)))
 			{
-				if (ob[key].holds(typeof(bool)))
-					_undo.write_set_property_bool_action(id, key, (bool)ob[key]);
-				if (ob[key].holds(typeof(double)))
-					_undo.write_set_property_double_action(id, key, (double)ob[key]);
-				if (ob[key].holds(typeof(string)))
-					_undo.write_set_property_string_action(id, key, (string)ob[key]);
-				if (ob[key].holds(typeof(Guid)))
-					_undo.write_set_property_guid_action(id, key, (Guid)ob[key]);
-				if (ob[key].holds(typeof(Vector3)))
-					_undo.write_set_property_vector3_action(id, key, (Vector3)ob[key]);
-				if (ob[key].holds(typeof(Quaternion)))
-					_undo.write_set_property_quaternion_action(id, key, (Quaternion)ob[key]);
+				HashSet<Guid?> hs = (HashSet<Guid?>)value;
+				Guid?[] ids = hs.to_array();
+				foreach (Guid item_id in ids)
+				{
+					remove_from_set(id, key, item_id);
+					destroy(item_id);
+				}
 			}
 			else
 			{
-				_undo.write_set_property_null_action(id, key);
+				set_property_null(id, key);
 			}
+		}
 
-			_redo.clear();
-			_redo_points.clear();
+		_undo.write_create_action(id);
+		_redo.clear();
+		_redo_points.clear();
 
-			set_property_internal(id, key, null);
-		}
+		destroy_internal(id);
+	}
 
-		public void set_property_bool(Guid id, string key, bool val)
-		{
-			assert(has_object(id));
-			assert(is_valid_key(key));
-			assert(is_valid_value(val));
+	public void set_property_null(Guid id, string key)
+	{
+		assert(has_object(id));
+		assert(is_valid_key(key));
+		assert(is_valid_value(null));
 
-			HashMap<string, Value?> ob = get_data(id);
-			if (ob.has_key(key) && ob[key] != null)
+		HashMap<string, Value?> ob = get_data(id);
+		if (ob.has_key(key) && ob[key] != null)
+		{
+			if (ob[key].holds(typeof(bool)))
 				_undo.write_set_property_bool_action(id, key, (bool)ob[key]);
-			else
-				_undo.write_set_property_null_action(id, key);
+			if (ob[key].holds(typeof(double)))
+				_undo.write_set_property_double_action(id, key, (double)ob[key]);
+			if (ob[key].holds(typeof(string)))
+				_undo.write_set_property_string_action(id, key, (string)ob[key]);
+			if (ob[key].holds(typeof(Guid)))
+				_undo.write_set_property_guid_action(id, key, (Guid)ob[key]);
+			if (ob[key].holds(typeof(Vector3)))
+				_undo.write_set_property_vector3_action(id, key, (Vector3)ob[key]);
+			if (ob[key].holds(typeof(Quaternion)))
+				_undo.write_set_property_quaternion_action(id, key, (Quaternion)ob[key]);
+		}
+		else
+		{
+			_undo.write_set_property_null_action(id, key);
+		}
 
-			_redo.clear();
-			_redo_points.clear();
+		_redo.clear();
+		_redo_points.clear();
 
-			set_property_internal(id, key, val);
-		}
+		set_property_internal(id, key, null);
+	}
 
-		public void set_property_double(Guid id, string key, double val)
-		{
-			assert(has_object(id));
-			assert(is_valid_key(key));
-			assert(is_valid_value(val));
+	public void set_property_bool(Guid id, string key, bool val)
+	{
+		assert(has_object(id));
+		assert(is_valid_key(key));
+		assert(is_valid_value(val));
 
-			HashMap<string, Value?> ob = get_data(id);
-			if (ob.has_key(key) && ob[key] != null)
-				_undo.write_set_property_double_action(id, key, (double)ob[key]);
-			else
-				_undo.write_set_property_null_action(id, key);
+		HashMap<string, Value?> ob = get_data(id);
+		if (ob.has_key(key) && ob[key] != null)
+			_undo.write_set_property_bool_action(id, key, (bool)ob[key]);
+		else
+			_undo.write_set_property_null_action(id, key);
 
-			_redo.clear();
-			_redo_points.clear();
+		_redo.clear();
+		_redo_points.clear();
 
-			set_property_internal(id, key, val);
-		}
+		set_property_internal(id, key, val);
+	}
 
-		public void set_property_string(Guid id, string key, string val)
-		{
-			assert(has_object(id));
-			assert(is_valid_key(key));
-			assert(is_valid_value(val));
+	public void set_property_double(Guid id, string key, double val)
+	{
+		assert(has_object(id));
+		assert(is_valid_key(key));
+		assert(is_valid_value(val));
 
-			HashMap<string, Value?> ob = get_data(id);
-			if (ob.has_key(key) && ob[key] != null)
-				_undo.write_set_property_string_action(id, key, (string)ob[key]);
-			else
-				_undo.write_set_property_null_action(id, key);
+		HashMap<string, Value?> ob = get_data(id);
+		if (ob.has_key(key) && ob[key] != null)
+			_undo.write_set_property_double_action(id, key, (double)ob[key]);
+		else
+			_undo.write_set_property_null_action(id, key);
 
-			_redo.clear();
-			_redo_points.clear();
+		_redo.clear();
+		_redo_points.clear();
 
-			set_property_internal(id, key, val);
-		}
+		set_property_internal(id, key, val);
+	}
 
-		public void set_property_guid(Guid id, string key, Guid val)
-		{
-			assert(has_object(id));
-			assert(is_valid_key(key));
-			assert(is_valid_value(val));
+	public void set_property_string(Guid id, string key, string val)
+	{
+		assert(has_object(id));
+		assert(is_valid_key(key));
+		assert(is_valid_value(val));
 
-			HashMap<string, Value?> ob = get_data(id);
-			if (ob.has_key(key) && ob[key] != null)
-				_undo.write_set_property_guid_action(id, key, (Guid)ob[key]);
-			else
-				_undo.write_set_property_null_action(id, key);
+		HashMap<string, Value?> ob = get_data(id);
+		if (ob.has_key(key) && ob[key] != null)
+			_undo.write_set_property_string_action(id, key, (string)ob[key]);
+		else
+			_undo.write_set_property_null_action(id, key);
 
-			_redo.clear();
-			_redo_points.clear();
+		_redo.clear();
+		_redo_points.clear();
 
-			set_property_internal(id, key, val);
-		}
+		set_property_internal(id, key, val);
+	}
 
-		public void set_property_vector3(Guid id, string key, Vector3 val)
-		{
-			assert(has_object(id));
-			assert(is_valid_key(key));
-			assert(is_valid_value(val));
+	public void set_property_guid(Guid id, string key, Guid val)
+	{
+		assert(has_object(id));
+		assert(is_valid_key(key));
+		assert(is_valid_value(val));
 
-			HashMap<string, Value?> ob = get_data(id);
-			if (ob.has_key(key) && ob[key] != null)
-				_undo.write_set_property_vector3_action(id, key, (Vector3)ob[key]);
-			else
-				_undo.write_set_property_null_action(id, key);
+		HashMap<string, Value?> ob = get_data(id);
+		if (ob.has_key(key) && ob[key] != null)
+			_undo.write_set_property_guid_action(id, key, (Guid)ob[key]);
+		else
+			_undo.write_set_property_null_action(id, key);
 
-			_redo.clear();
-			_redo_points.clear();
+		_redo.clear();
+		_redo_points.clear();
 
-			set_property_internal(id, key, val);
-		}
+		set_property_internal(id, key, val);
+	}
 
-		public void set_property_quaternion(Guid id, string key, Quaternion val)
-		{
-			assert(has_object(id));
-			assert(is_valid_key(key));
-			assert(is_valid_value(val));
+	public void set_property_vector3(Guid id, string key, Vector3 val)
+	{
+		assert(has_object(id));
+		assert(is_valid_key(key));
+		assert(is_valid_value(val));
 
-			HashMap<string, Value?> ob = get_data(id);
-			if (ob.has_key(key) && ob[key] != null)
-				_undo.write_set_property_quaternion_action(id, key, (Quaternion)ob[key]);
-			else
-				_undo.write_set_property_null_action(id, key);
+		HashMap<string, Value?> ob = get_data(id);
+		if (ob.has_key(key) && ob[key] != null)
+			_undo.write_set_property_vector3_action(id, key, (Vector3)ob[key]);
+		else
+			_undo.write_set_property_null_action(id, key);
 
-			_redo.clear();
-			_redo_points.clear();
+		_redo.clear();
+		_redo_points.clear();
 
-			set_property_internal(id, key, val);
-		}
+		set_property_internal(id, key, val);
+	}
 
-		public void add_to_set(Guid id, string key, Guid item_id)
-		{
-			assert(has_object(id));
-			assert(is_valid_key(key));
-			assert(item_id != GUID_ZERO);
-			assert(has_object(item_id));
+	public void set_property_quaternion(Guid id, string key, Quaternion val)
+	{
+		assert(has_object(id));
+		assert(is_valid_key(key));
+		assert(is_valid_value(val));
 
-			_undo.write_remove_from_set_action(id, key, item_id);
-			_redo.clear();
-			_redo_points.clear();
+		HashMap<string, Value?> ob = get_data(id);
+		if (ob.has_key(key) && ob[key] != null)
+			_undo.write_set_property_quaternion_action(id, key, (Quaternion)ob[key]);
+		else
+			_undo.write_set_property_null_action(id, key);
 
-			add_to_set_internal(id, key, item_id);
-		}
+		_redo.clear();
+		_redo_points.clear();
 
-		public void remove_from_set(Guid id, string key, Guid item_id)
-		{
-			assert(has_object(id));
-			assert(is_valid_key(key));
-			assert(item_id != GUID_ZERO);
+		set_property_internal(id, key, val);
+	}
 
-			_undo.write_add_to_set_action(id, key, item_id);
-			_redo.clear();
-			_redo_points.clear();
+	public void add_to_set(Guid id, string key, Guid item_id)
+	{
+		assert(has_object(id));
+		assert(is_valid_key(key));
+		assert(item_id != GUID_ZERO);
+		assert(has_object(item_id));
 
-			remove_from_set_internal(id, key, item_id);
-		}
+		_undo.write_remove_from_set_action(id, key, item_id);
+		_redo.clear();
+		_redo_points.clear();
 
-		public bool has_object(Guid id)
-		{
-			return id == GUID_ZERO || ((HashMap<string, Value?>)_data["_objects"]).has_key(id.to_string());
-		}
+		add_to_set_internal(id, key, item_id);
+	}
 
-		public bool has_property(Guid id, string key)
-		{
-			return get_property(id, key) != null;
-		}
+	public void remove_from_set(Guid id, string key, Guid item_id)
+	{
+		assert(has_object(id));
+		assert(is_valid_key(key));
+		assert(item_id != GUID_ZERO);
 
-		public Value? get_property(Guid id, string key)
-		{
-			assert(has_object(id));
-			assert(is_valid_key(key));
+		_undo.write_add_to_set_action(id, key, item_id);
+		_redo.clear();
+		_redo_points.clear();
+
+		remove_from_set_internal(id, key, item_id);
+	}
+
+	public bool has_object(Guid id)
+	{
+		return id == GUID_ZERO || ((HashMap<string, Value?>)_data["_objects"]).has_key(id.to_string());
+	}
+
+	public bool has_property(Guid id, string key)
+	{
+		return get_property(id, key) != null;
+	}
+
+	public Value? get_property(Guid id, string key)
+	{
+		assert(has_object(id));
+		assert(is_valid_key(key));
 
-			HashMap<string, Value?> ob = get_data(id);
-			Value? value = (ob.has_key(key) ? ob[key] : null);
+		HashMap<string, Value?> ob = get_data(id);
+		Value? value = (ob.has_key(key) ? ob[key] : null);
 #if 0
-			stdout.printf("get_property %s %s %s\n"
-				, id.to_string()
-				, key
-				, (value == null) ? "null" : value_to_string(value)
-				);
+		stdout.printf("get_property %s %s %s\n"
+			, id.to_string()
+			, key
+			, (value == null) ? "null" : value_to_string(value)
+			);
 #endif // CROWN_DEBUG
-			return value;
-		}
-
-		public bool get_property_bool(Guid id, string key)
-		{
-			return (bool)get_property(id, key);
-		}
+		return value;
+	}
 
-		public double get_property_double(Guid id, string key)
-		{
-			return (double)get_property(id, key);
-		}
+	public bool get_property_bool(Guid id, string key)
+	{
+		return (bool)get_property(id, key);
+	}
 
-		public string get_property_string(Guid id, string key)
-		{
-			return (string)get_property(id, key);
-		}
+	public double get_property_double(Guid id, string key)
+	{
+		return (double)get_property(id, key);
+	}
 
-		public Guid get_property_guid(Guid id, string key)
-		{
-			return (Guid)get_property(id, key);
-		}
+	public string get_property_string(Guid id, string key)
+	{
+		return (string)get_property(id, key);
+	}
 
-		public Vector3 get_property_vector3(Guid id, string key)
-		{
-			return (Vector3)get_property(id, key);
-		}
+	public Guid get_property_guid(Guid id, string key)
+	{
+		return (Guid)get_property(id, key);
+	}
 
-		public Quaternion get_property_quaternion(Guid id, string key)
-		{
-			return (Quaternion)get_property(id, key);
-		}
+	public Vector3 get_property_vector3(Guid id, string key)
+	{
+		return (Vector3)get_property(id, key);
+	}
 
-		public HashSet<Guid?> get_property_set(Guid id, string key, HashSet<Guid?> deffault)
-		{
-			assert(has_object(id));
-			assert(is_valid_key(key));
+	public Quaternion get_property_quaternion(Guid id, string key)
+	{
+		return (Quaternion)get_property(id, key);
+	}
 
-			HashMap<string, Value?> ob = get_data(id);
-			if (ob.has_key(key))
-				return ob[key] as HashSet<Guid?>;
-			else
-				return deffault;
+	public HashSet<Guid?> get_property_set(Guid id, string key, HashSet<Guid?> deffault)
+	{
+		assert(has_object(id));
+		assert(is_valid_key(key));
+
+		HashMap<string, Value?> ob = get_data(id);
+		if (ob.has_key(key))
+			return ob[key] as HashSet<Guid?>;
+		else
+			return deffault;
 #if 0
-			// stdout.printf("get_property %s %s %s\n"
-			// 	, id.to_string()
-			// 	, key
-			// 	, (value == null) ? "null" : value_to_string(value)
-			// 	);
+		// stdout.printf("get_property %s %s %s\n"
+		// 	, id.to_string()
+		// 	, key
+		// 	, (value == null) ? "null" : value_to_string(value)
+		// 	);
 #endif // CROWN_DEBUG
-		}
+	}
 
-		public HashMap<string, Value?> get_object(Guid id)
-		{
-			return (HashMap<string, Value?>)get_data(GUID_ZERO)[id.to_string()];
-		}
+	public HashMap<string, Value?> get_object(Guid id)
+	{
+		return (HashMap<string, Value?>)get_data(GUID_ZERO)[id.to_string()];
+	}
 
-		public string[] get_keys(Guid id)
-		{
-			HashMap<string, Value?> data = get_data(id);
-			return data.keys.to_array();
-		}
+	public string[] get_keys(Guid id)
+	{
+		HashMap<string, Value?> data = get_data(id);
+		return data.keys.to_array();
+	}
 
-		public void add_restore_point(int id, Guid[] data)
-		{
+	public void add_restore_point(int id, Guid[] data)
+	{
 #if 0
-			stdout.printf("add_restore_point %d, undo size = %u\n", id, _undo.size());
+		stdout.printf("add_restore_point %d, undo size = %u\n", id, _undo.size());
 #endif // CROWN_DEBUG
-			_undo_points.write_restore_point(id, _undo.size(), data);
+		_undo_points.write_restore_point(id, _undo.size(), data);
 
-			_redo.clear();
-			_redo_points.clear();
-		}
+		_redo.clear();
+		_redo_points.clear();
+	}
 
-		/// Duplicates the object specified by id and assign new_id to the duplicated object.
-		public void duplicate(Guid id, Guid new_id)
-		{
-			assert(id != GUID_ZERO);
-			assert(new_id != GUID_ZERO);
-			assert(id != new_id);
-			assert(has_object(id));
+	/// Duplicates the object specified by id and assign new_id to the duplicated object.
+	public void duplicate(Guid id, Guid new_id)
+	{
+		assert(id != GUID_ZERO);
+		assert(new_id != GUID_ZERO);
+		assert(id != new_id);
+		assert(has_object(id));
 
-			create(new_id);
+		create(new_id);
 
-			HashMap<string, Value?> ob = get_data(id);
-			string[] keys = ob.keys.to_array();
-			foreach (string key in keys)
+		HashMap<string, Value?> ob = get_data(id);
+		string[] keys = ob.keys.to_array();
+		foreach (string key in keys)
+		{
+			Value? val = ob[key];
+			if (val.holds(typeof(HashSet)))
 			{
-				Value? val = ob[key];
-				if (val.holds(typeof(HashSet)))
-				{
-					HashSet<Guid?> hs = (HashSet<Guid?>)val;
-					foreach (Guid j in hs)
-					{
-						Guid x = Guid.new_guid();
-						duplicate(j, x);
-						add_to_set(new_id, key, x);
-					}
-				}
-				else
+				HashSet<Guid?> hs = (HashSet<Guid?>)val;
+				foreach (Guid j in hs)
 				{
-					if (ob[key] == null)
-						set_property_null(new_id, key);
-					if (ob[key].holds(typeof(bool)))
-						set_property_bool(new_id, key, (bool)ob[key]);
-					if (ob[key].holds(typeof(double)))
-						set_property_double(new_id, key, (double)ob[key]);
-					if (ob[key].holds(typeof(string)))
-						set_property_string(new_id, key, (string)ob[key]);
-					if (ob[key].holds(typeof(Guid)))
-						set_property_guid(new_id, key, (Guid)ob[key]);
-					if (ob[key].holds(typeof(Vector3)))
-						set_property_vector3(new_id, key, (Vector3)ob[key]);
-					if (ob[key].holds(typeof(Quaternion)))
-						set_property_quaternion(new_id, key, (Quaternion)ob[key]);
+					Guid x = Guid.new_guid();
+					duplicate(j, x);
+					add_to_set(new_id, key, x);
 				}
 			}
+			else
+			{
+				if (ob[key] == null)
+					set_property_null(new_id, key);
+				if (ob[key].holds(typeof(bool)))
+					set_property_bool(new_id, key, (bool)ob[key]);
+				if (ob[key].holds(typeof(double)))
+					set_property_double(new_id, key, (double)ob[key]);
+				if (ob[key].holds(typeof(string)))
+					set_property_string(new_id, key, (string)ob[key]);
+				if (ob[key].holds(typeof(Guid)))
+					set_property_guid(new_id, key, (Guid)ob[key]);
+				if (ob[key].holds(typeof(Vector3)))
+					set_property_vector3(new_id, key, (Vector3)ob[key]);
+				if (ob[key].holds(typeof(Quaternion)))
+					set_property_quaternion(new_id, key, (Quaternion)ob[key]);
+			}
 		}
+	}
 
-		/// Copies the database to db under the given new_key.
-		public void copy_to(Database db, string new_key)
-		{
-			assert(db != null);
-			assert(is_valid_key(new_key));
+	/// Copies the database to db under the given new_key.
+	public void copy_to(Database db, string new_key)
+	{
+		assert(db != null);
+		assert(is_valid_key(new_key));
 
-			copy_deep(db, GUID_ZERO, new_key);
-		}
+		copy_deep(db, GUID_ZERO, new_key);
+	}
 
-		public void copy_deep(Database db, Guid id, string new_key)
+	public void copy_deep(Database db, Guid id, string new_key)
+	{
+		HashMap<string, Value?> ob = get_data(id);
+		string[] keys = ob.keys.to_array();
+		foreach (string key in keys)
 		{
-			HashMap<string, Value?> ob = get_data(id);
-			string[] keys = ob.keys.to_array();
-			foreach (string key in keys)
-			{
-				if (key == "_objects")
-					continue;
+			if (key == "_objects")
+				continue;
 
-				Value? value = ob[key];
-				if (value.holds(typeof(HashSet)))
-				{
-					HashSet<Guid?> hs = (HashSet<Guid?>)value;
-					foreach (Guid j in hs)
-					{
-						db.create(j);
-						copy_deep(db, j, "");
-						db.add_to_set(id, new_key + (new_key == "" ? "" : ".") + key, j);
-					}
-				}
-				else
+			Value? value = ob[key];
+			if (value.holds(typeof(HashSet)))
+			{
+				HashSet<Guid?> hs = (HashSet<Guid?>)value;
+				foreach (Guid j in hs)
 				{
-					string kk = new_key + (new_key == "" ? "" : ".") + key;
-
-					if (ob[key] == null)
-						db.set_property_null(id, kk);
-					if (ob[key].holds(typeof(bool)))
-						db.set_property_bool(id, kk, (bool)ob[key]);
-					if (ob[key].holds(typeof(double)))
-						db.set_property_double(id, kk, (double)ob[key]);
-					if (ob[key].holds(typeof(string)))
-						db.set_property_string(id, kk, (string)ob[key]);
-					if (ob[key].holds(typeof(Guid)))
-						db.set_property_guid(id, kk, (Guid)ob[key]);
-					if (ob[key].holds(typeof(Vector3)))
-						db.set_property_vector3(id, kk, (Vector3)ob[key]);
-					if (ob[key].holds(typeof(Quaternion)))
-						db.set_property_quaternion(id, kk, (Quaternion)ob[key]);
+					db.create(j);
+					copy_deep(db, j, "");
+					db.add_to_set(id, new_key + (new_key == "" ? "" : ".") + key, j);
 				}
 			}
-		}
+			else
+			{
+				string kk = new_key + (new_key == "" ? "" : ".") + key;
 
-		// Un-does the last action and returns its ID, or -1 if there is no
-		// action to undo.
-		public int undo()
-		{
-			if (_undo_points.size() == 0)
-				return -1;
-
-			RestorePoint rp = _undo_points.read_restore_point();
-			_redo_points.write_restore_point(rp.id, _redo.size(), rp.data);
-			undo_until(rp.size);
-			undo_redo(true, rp.id, rp.data);
-			return rp.id;
+				if (ob[key] == null)
+					db.set_property_null(id, kk);
+				if (ob[key].holds(typeof(bool)))
+					db.set_property_bool(id, kk, (bool)ob[key]);
+				if (ob[key].holds(typeof(double)))
+					db.set_property_double(id, kk, (double)ob[key]);
+				if (ob[key].holds(typeof(string)))
+					db.set_property_string(id, kk, (string)ob[key]);
+				if (ob[key].holds(typeof(Guid)))
+					db.set_property_guid(id, kk, (Guid)ob[key]);
+				if (ob[key].holds(typeof(Vector3)))
+					db.set_property_vector3(id, kk, (Vector3)ob[key]);
+				if (ob[key].holds(typeof(Quaternion)))
+					db.set_property_quaternion(id, kk, (Quaternion)ob[key]);
+			}
 		}
+	}
 
-		// Re-does the last action and returns its ID, or -1 if there is no
-		// action to redo.
-		public int redo()
-		{
-			if (_redo_points.size() == 0)
-				return -1;
-
-			RestorePoint rp = _redo_points.read_restore_point();
-			_undo_points.write_restore_point(rp.id, _undo.size(), rp.data);
-			redo_until(rp.size);
-			undo_redo(false, rp.id, rp.data);
-			return rp.id;
-		}
+	// Un-does the last action and returns its ID, or -1 if there is no
+	// action to undo.
+	public int undo()
+	{
+		if (_undo_points.size() == 0)
+			return -1;
+
+		RestorePoint rp = _undo_points.read_restore_point();
+		_redo_points.write_restore_point(rp.id, _redo.size(), rp.data);
+		undo_until(rp.size);
+		undo_redo(true, rp.id, rp.data);
+		return rp.id;
+	}
 
-		private void undo_until(uint32 size)
-		{
-			undo_redo_until(size, _undo, _redo);
-		}
+	// Re-does the last action and returns its ID, or -1 if there is no
+	// action to redo.
+	public int redo()
+	{
+		if (_redo_points.size() == 0)
+			return -1;
+
+		RestorePoint rp = _redo_points.read_restore_point();
+		_undo_points.write_restore_point(rp.id, _undo.size(), rp.data);
+		redo_until(rp.size);
+		undo_redo(false, rp.id, rp.data);
+		return rp.id;
+	}
 
-		private void redo_until(uint32 size)
-		{
-			undo_redo_until(size, _redo, _undo);
-		}
+	private void undo_until(uint32 size)
+	{
+		undo_redo_until(size, _undo, _redo);
+	}
 
-		private void undo_redo_until(uint32 size, Stack undo, Stack redo)
+	private void redo_until(uint32 size)
+	{
+		undo_redo_until(size, _redo, _undo);
+	}
+
+	private void undo_redo_until(uint32 size, Stack undo, Stack redo)
+	{
+		while (undo.size() != size)
 		{
-			while (undo.size() != size)
+			uint32 type = undo.peek_type();
+			if (type == Action.CREATE)
 			{
-				uint32 type = undo.peek_type();
-				if (type == Action.CREATE)
-				{
-					Action t = undo.read_action();
-					assert(t == Action.CREATE);
+				Action t = undo.read_action();
+				assert(t == Action.CREATE);
 
-					Guid id = undo.read_guid();
+				Guid id = undo.read_guid();
 
-					redo.write_destroy_action(id);
-					create_internal(id);
-				}
-				else if (type == Action.DESTROY)
-				{
-					Action t = undo.read_action();
-					assert(t == Action.DESTROY);
+				redo.write_destroy_action(id);
+				create_internal(id);
+			}
+			else if (type == Action.DESTROY)
+			{
+				Action t = undo.read_action();
+				assert(t == Action.DESTROY);
 
-					Guid id = undo.read_guid();
+				Guid id = undo.read_guid();
 
-					redo.write_create_action(id);
-					destroy_internal(id);
-				}
-				else if (type == Action.SET_PROPERTY_NULL)
-				{
-					Action t = undo.read_action();
-					assert(t == Action.SET_PROPERTY_NULL);
+				redo.write_create_action(id);
+				destroy_internal(id);
+			}
+			else if (type == Action.SET_PROPERTY_NULL)
+			{
+				Action t = undo.read_action();
+				assert(t == Action.SET_PROPERTY_NULL);
 
-					Guid id = undo.read_guid();
-					string key = undo.read_string();
+				Guid id = undo.read_guid();
+				string key = undo.read_string();
 
-					if (has_property(id, key))
-					{
-						if (get_data(id)[key].holds(typeof(bool)))
-							redo.write_set_property_bool_action(id, key, get_property_bool(id, key));
-						if (get_data(id)[key].holds(typeof(double)))
-							redo.write_set_property_double_action(id, key, get_property_double(id, key));
-						if (get_data(id)[key].holds(typeof(string)))
-							redo.write_set_property_string_action(id, key, get_property_string(id, key));
-						if (get_data(id)[key].holds(typeof(Guid)))
-							redo.write_set_property_guid_action(id, key, get_property_guid(id, key));
-						if (get_data(id)[key].holds(typeof(Vector3)))
-							redo.write_set_property_vector3_action(id, key, get_property_vector3(id, key));
-						if (get_data(id)[key].holds(typeof(Quaternion)))
-							redo.write_set_property_quaternion_action(id, key, get_property_quaternion(id, key));
-					}
-					else
-					{
-						redo.write_set_property_null_action(id, key);
-					}
-					set_property_internal(id, key, null);
-				}
-				else if (type == Action.SET_PROPERTY_BOOL)
+				if (has_property(id, key))
 				{
-					Action t = undo.read_action();
-					assert(t == Action.SET_PROPERTY_BOOL);
-
-					Guid id = undo.read_guid();
-					string key = undo.read_string();
-					bool val = undo.read_bool();
-
-					if (has_property(id, key))
+					if (get_data(id)[key].holds(typeof(bool)))
 						redo.write_set_property_bool_action(id, key, get_property_bool(id, key));
-					else
-						redo.write_set_property_null_action(id, key);
-					set_property_internal(id, key, val);
+					if (get_data(id)[key].holds(typeof(double)))
+						redo.write_set_property_double_action(id, key, get_property_double(id, key));
+					if (get_data(id)[key].holds(typeof(string)))
+						redo.write_set_property_string_action(id, key, get_property_string(id, key));
+					if (get_data(id)[key].holds(typeof(Guid)))
+						redo.write_set_property_guid_action(id, key, get_property_guid(id, key));
+					if (get_data(id)[key].holds(typeof(Vector3)))
+						redo.write_set_property_vector3_action(id, key, get_property_vector3(id, key));
+					if (get_data(id)[key].holds(typeof(Quaternion)))
+						redo.write_set_property_quaternion_action(id, key, get_property_quaternion(id, key));
 				}
-				else if (type == Action.SET_PROPERTY_DOUBLE)
+				else
 				{
-					Action t = undo.read_action();
-					assert(t == Action.SET_PROPERTY_DOUBLE);
+					redo.write_set_property_null_action(id, key);
+				}
+				set_property_internal(id, key, null);
+			}
+			else if (type == Action.SET_PROPERTY_BOOL)
+			{
+				Action t = undo.read_action();
+				assert(t == Action.SET_PROPERTY_BOOL);
 
-					Guid id = undo.read_guid();
-					string key = undo.read_string();
-					double val = undo.read_double();
+				Guid id = undo.read_guid();
+				string key = undo.read_string();
+				bool val = undo.read_bool();
 
-					if (has_property(id, key))
-						redo.write_set_property_double_action(id, key, get_property_double(id, key));
-					else
-						redo.write_set_property_null_action(id, key);
-					set_property_internal(id, key, val);
-				}
-				else if (type == Action.SET_PROPERTY_STRING)
-				{
-					Action t = undo.read_action();
-					assert(t == Action.SET_PROPERTY_STRING);
+				if (has_property(id, key))
+					redo.write_set_property_bool_action(id, key, get_property_bool(id, key));
+				else
+					redo.write_set_property_null_action(id, key);
+				set_property_internal(id, key, val);
+			}
+			else if (type == Action.SET_PROPERTY_DOUBLE)
+			{
+				Action t = undo.read_action();
+				assert(t == Action.SET_PROPERTY_DOUBLE);
 
-					Guid id = undo.read_guid();
-					string key = undo.read_string();
-					string val = undo.read_string();
+				Guid id = undo.read_guid();
+				string key = undo.read_string();
+				double val = undo.read_double();
 
-					if (has_property(id, key))
-						redo.write_set_property_string_action(id, key, get_property_string(id, key));
-					else
-						redo.write_set_property_null_action(id, key);
-					set_property_internal(id, key, val);
-				}
-				else if (type == Action.SET_PROPERTY_GUID)
-				{
-					Action t = undo.read_action();
-					assert(t == Action.SET_PROPERTY_GUID);
+				if (has_property(id, key))
+					redo.write_set_property_double_action(id, key, get_property_double(id, key));
+				else
+					redo.write_set_property_null_action(id, key);
+				set_property_internal(id, key, val);
+			}
+			else if (type == Action.SET_PROPERTY_STRING)
+			{
+				Action t = undo.read_action();
+				assert(t == Action.SET_PROPERTY_STRING);
 
-					Guid id = undo.read_guid();
-					string key = undo.read_string();
-					Guid val = undo.read_guid();
+				Guid id = undo.read_guid();
+				string key = undo.read_string();
+				string val = undo.read_string();
 
-					if (has_property(id, key))
-						redo.write_set_property_guid_action(id, key, get_property_guid(id, key));
-					else
-						redo.write_set_property_null_action(id, key);
-					set_property_internal(id, key, val);
-				}
-				else if (type == Action.SET_PROPERTY_VECTOR3)
-				{
-					Action t = undo.read_action();
-					assert(t == Action.SET_PROPERTY_VECTOR3);
+				if (has_property(id, key))
+					redo.write_set_property_string_action(id, key, get_property_string(id, key));
+				else
+					redo.write_set_property_null_action(id, key);
+				set_property_internal(id, key, val);
+			}
+			else if (type == Action.SET_PROPERTY_GUID)
+			{
+				Action t = undo.read_action();
+				assert(t == Action.SET_PROPERTY_GUID);
 
-					Guid id = undo.read_guid();
-					string key = undo.read_string();
-					Vector3 val = undo.read_vector3();
+				Guid id = undo.read_guid();
+				string key = undo.read_string();
+				Guid val = undo.read_guid();
 
-					if (has_property(id, key))
-						redo.write_set_property_vector3_action(id, key, get_property_vector3(id, key));
-					else
-						redo.write_set_property_null_action(id, key);
-					set_property_internal(id, key, val);
-				}
-				else if (type == Action.SET_PROPERTY_QUATERNION)
-				{
-					Action t = undo.read_action();
-					assert(t == Action.SET_PROPERTY_QUATERNION);
+				if (has_property(id, key))
+					redo.write_set_property_guid_action(id, key, get_property_guid(id, key));
+				else
+					redo.write_set_property_null_action(id, key);
+				set_property_internal(id, key, val);
+			}
+			else if (type == Action.SET_PROPERTY_VECTOR3)
+			{
+				Action t = undo.read_action();
+				assert(t == Action.SET_PROPERTY_VECTOR3);
 
-					Guid id = undo.read_guid();
-					string key = undo.read_string();
-					Quaternion val = undo.read_quaternion();
+				Guid id = undo.read_guid();
+				string key = undo.read_string();
+				Vector3 val = undo.read_vector3();
 
-					if (has_property(id, key))
-						redo.write_set_property_quaternion_action(id, key, get_property_quaternion(id, key));
-					else
-						redo.write_set_property_null_action(id, key);
-					set_property_internal(id, key, val);
-				}
-				else if (type == Action.ADD_TO_SET)
-				{
-					Action t = undo.read_action();
-					assert(t == Action.ADD_TO_SET);
+				if (has_property(id, key))
+					redo.write_set_property_vector3_action(id, key, get_property_vector3(id, key));
+				else
+					redo.write_set_property_null_action(id, key);
+				set_property_internal(id, key, val);
+			}
+			else if (type == Action.SET_PROPERTY_QUATERNION)
+			{
+				Action t = undo.read_action();
+				assert(t == Action.SET_PROPERTY_QUATERNION);
 
-					Guid id = undo.read_guid();
-					string key = undo.read_string();
-					Guid item_id = undo.read_guid();
+				Guid id = undo.read_guid();
+				string key = undo.read_string();
+				Quaternion val = undo.read_quaternion();
 
-					redo.write_remove_from_set_action(id, key, item_id);
-					add_to_set_internal(id, key, item_id);
-				}
-				else if (type == Action.REMOVE_FROM_SET)
-				{
-					Action t = undo.read_action();
-					assert(t == Action.REMOVE_FROM_SET);
+				if (has_property(id, key))
+					redo.write_set_property_quaternion_action(id, key, get_property_quaternion(id, key));
+				else
+					redo.write_set_property_null_action(id, key);
+				set_property_internal(id, key, val);
+			}
+			else if (type == Action.ADD_TO_SET)
+			{
+				Action t = undo.read_action();
+				assert(t == Action.ADD_TO_SET);
 
-					Guid id = undo.read_guid();
-					string key = undo.read_string();
-					Guid item_id = undo.read_guid();
+				Guid id = undo.read_guid();
+				string key = undo.read_string();
+				Guid item_id = undo.read_guid();
 
-					redo.write_add_to_set_action(id, key, item_id);
-					remove_from_set_internal(id, key, item_id);
-				}
+				redo.write_remove_from_set_action(id, key, item_id);
+				add_to_set_internal(id, key, item_id);
+			}
+			else if (type == Action.REMOVE_FROM_SET)
+			{
+				Action t = undo.read_action();
+				assert(t == Action.REMOVE_FROM_SET);
+
+				Guid id = undo.read_guid();
+				string key = undo.read_string();
+				Guid item_id = undo.read_guid();
+
+				redo.write_add_to_set_action(id, key, item_id);
+				remove_from_set_internal(id, key, item_id);
 			}
 		}
 	}
 }
+
+}

+ 74 - 73
tools/core/guid.vala

@@ -5,87 +5,88 @@
 
 namespace Crown
 {
-	[Compact]
-	public struct Guid
-	{
-		uint32 data1;
-		uint16 data2;
-		uint16 data3;
-		uint64 data4;
+[Compact]
+public struct Guid
+{
+	uint32 data1;
+	uint16 data2;
+	uint16 data3;
+	uint64 data4;
 
-		public static Guid new_guid()
-		{
-			// FIXME: Replace Rand with something better.
-			Rand rnd = new Rand();
-			uint32 a = rnd.next_int();
-			uint32 b = rnd.next_int();
-			uint64 c = rnd.next_int();
-			uint64 d = rnd.next_int();
+	public static Guid new_guid()
+	{
+		// FIXME: Replace Rand with something better.
+		Rand rnd = new Rand();
+		uint32 a = rnd.next_int();
+		uint32 b = rnd.next_int();
+		uint64 c = rnd.next_int();
+		uint64 d = rnd.next_int();
 
-			uint32 d1 = a;
-			uint16 d2 = (uint16)((b & 0xffff0000u) >> 16);
-			uint16 d3 = (uint16)((b & 0x0000ffffu) >>  0);
-			uint64 d4 = (c << 32 | d);
+		uint32 d1 = a;
+		uint16 d2 = (uint16)((b & 0xffff0000u) >> 16);
+		uint16 d3 = (uint16)((b & 0x0000ffffu) >>  0);
+		uint64 d4 = (c << 32 | d);
 
-			d3 = (d3 & 0x4fffu) | 0x4000u;
-			d4 = (d4 & 0x3fffffffffffffffu) | 0x8000000000000000u;
-			return { d1, d2, d3, d4 };
-		}
+		d3 = (d3 & 0x4fffu) | 0x4000u;
+		d4 = (d4 & 0x3fffffffffffffffu) | 0x8000000000000000u;
+		return { d1, d2, d3, d4 };
+	}
 
-		public static Guid parse(string guid)
-		{
-			Guid g;
-			bool success = Guid.try_parse(guid, out g);
-			assert(success);
-			return g;
-		}
+	public static Guid parse(string guid)
+	{
+		Guid g;
+		bool success = Guid.try_parse(guid, out g);
+		assert(success);
+		return g;
+	}
 
-		public static bool try_parse(string str, out Guid guid)
-		{
-			uint32 a = 0;
-			uint32 b = 0;
-			uint32 c = 0;
-			uint32 d = 0;
-			uint32 e = 0;
-			uint32 f = 0;
-			int num = str.scanf("%8x-%4x-%4x-%4x-%4x%8x", &a, &b, &c, &d, &e, &f);
-			uint32 d1 = a;
-			uint16 d2 = (uint16)(b & 0x0000ffffu);
-			uint16 d3 = (uint16)(c & 0x0000ffffu);
-			uint64 d4 = (uint64)(d & 0x0000ffffu) << 48 | (uint64)(e & 0x0000ffffu) << 32 | (uint64)f;
-			guid = { d1, d2, d3, d4 };
-			return num == 6;
-		}
+	public static bool try_parse(string str, out Guid guid)
+	{
+		uint32 a = 0;
+		uint32 b = 0;
+		uint32 c = 0;
+		uint32 d = 0;
+		uint32 e = 0;
+		uint32 f = 0;
+		int num = str.scanf("%8x-%4x-%4x-%4x-%4x%8x", &a, &b, &c, &d, &e, &f);
+		uint32 d1 = a;
+		uint16 d2 = (uint16)(b & 0x0000ffffu);
+		uint16 d3 = (uint16)(c & 0x0000ffffu);
+		uint64 d4 = (uint64)(d & 0x0000ffffu) << 48 | (uint64)(e & 0x0000ffffu) << 32 | (uint64)f;
+		guid = { d1, d2, d3, d4 };
+		return num == 6;
+	}
 
-		public string to_string()
-		{
-			return "%.8x-%.4x-%.4x-%.4x-%.4x%.8x".printf(data1
-				, data2
-				, data3
-				, (uint32)((data4 & 0xffff000000000000u) >> 48)
-				, (uint32)((data4 & 0x0000ffff00000000u) >> 32)
-				, (uint32)((data4 & 0x00000000ffffffffu) >>  0)
-				);
-		}
+	public string to_string()
+	{
+		return "%.8x-%.4x-%.4x-%.4x-%.4x%.8x".printf(data1
+			, data2
+			, data3
+			, (uint32)((data4 & 0xffff000000000000u) >> 48)
+			, (uint32)((data4 & 0x0000ffff00000000u) >> 32)
+			, (uint32)((data4 & 0x00000000ffffffffu) >>  0)
+			);
+	}
 
-		public static uint hash_func(Guid? id)
-		{
-			uint32 d1 = (uint32)(id.data1 & 0xff) << 24;
-			uint32 d2 = (uint32)(id.data2 & 0xff) << 16;
-			uint32 d3 = (uint32)(id.data3 & 0xff) <<  8;
-			uint32 d4 = (uint32)(id.data4 & 0xff) <<  0;
-			return d1 | d2 | d3 | d4;
-		}
+	public static uint hash_func(Guid? id)
+	{
+		uint32 d1 = (uint32)(id.data1 & 0xff) << 24;
+		uint32 d2 = (uint32)(id.data2 & 0xff) << 16;
+		uint32 d3 = (uint32)(id.data3 & 0xff) <<  8;
+		uint32 d4 = (uint32)(id.data4 & 0xff) <<  0;
+		return d1 | d2 | d3 | d4;
+	}
 
-		public static bool equal_func(Guid? a, Guid? b)
-		{
-			return a.data1 == b.data1
-				&& a.data2 == b.data2
-				&& a.data3 == b.data3
-				&& a.data4 == b.data4
-				;
-		}
+	public static bool equal_func(Guid? a, Guid? b)
+	{
+		return a.data1 == b.data1
+			&& a.data2 == b.data2
+			&& a.data3 == b.data3
+			&& a.data4 == b.data4
+			;
 	}
+}
+
+const Guid GUID_ZERO = { 0, 0, 0, 0 };
 
-	const Guid GUID_ZERO = { 0, 0, 0, 0 };
 }

+ 247 - 246
tools/core/json/json.vala

@@ -12,305 +12,306 @@ using Gee;
 
 namespace Crown
 {
-	public class Hashtable : HashMap<string, Value?> { }
+public class Hashtable : HashMap<string, Value?> { }
 
+/// <summary>
+/// Provides functions for encoding and decoding files in the JSON format.
+/// </summary>
+[Compact]
+public class JSON
+{
 	/// <summary>
-	/// Provides functions for encoding and decoding files in the JSON format.
+	///  Encodes the hashtable t in the JSON format. The hash table can
+	///  contain, numbers, bools, strings, ArrayLists and Hashtables.
 	/// </summary>
-	[Compact]
-	public class JSON
+	public static string encode(Value? t)
 	{
-		/// <summary>
-		///  Encodes the hashtable t in the JSON format. The hash table can
-		///  contain, numbers, bools, strings, ArrayLists and Hashtables.
-		/// </summary>
-		public static string encode(Value? t)
-		{
-			StringBuilder sb = new StringBuilder();
-			write(t, sb, 0);
-			sb.append_c('\n');
-			return sb.str;
-		}
+		StringBuilder sb = new StringBuilder();
+		write(t, sb, 0);
+		sb.append_c('\n');
+		return sb.str;
+	}
 
-		/// <summary>
-		/// Decodes a JSON bytestream into a hash table with numbers, bools, strings,
-		/// ArrayLists and Hashtables.
-		/// </summary>
-		public static Value? decode(uint8[] sjson)
-		{
-			int index = 0;
-			return parse(sjson, ref index);
-		}
+	/// <summary>
+	/// Decodes a JSON bytestream into a hash table with numbers, bools, strings,
+	/// ArrayLists and Hashtables.
+	/// </summary>
+	public static Value? decode(uint8[] sjson)
+	{
+		int index = 0;
+		return parse(sjson, ref index);
+	}
 
-		/// <summary>
-		/// Convenience function for loading a file.
-		/// </summary>
-		public static Hashtable load(string path)
-		{
-			FileStream fs = FileStream.open(path, "rb");
-			if (fs == null)
-				return new Hashtable();
+	/// <summary>
+	/// Convenience function for loading a file.
+	/// </summary>
+	public static Hashtable load(string path)
+	{
+		FileStream fs = FileStream.open(path, "rb");
+		if (fs == null)
+			return new Hashtable();
 
-			// Get file size
-			fs.seek(0, FileSeek.END);
-			size_t size = fs.tell();
-			fs.rewind();
-			if (size == 0)
-				return new Hashtable();
+		// Get file size
+		fs.seek(0, FileSeek.END);
+		size_t size = fs.tell();
+		fs.rewind();
+		if (size == 0)
+			return new Hashtable();
 
-			// Read whole file
-			uint8[] bytes = new uint8[size];
-			size_t bytes_read = fs.read(bytes);
-			if (bytes_read != size)
-				return new Hashtable();
+		// Read whole file
+		uint8[] bytes = new uint8[size];
+		size_t bytes_read = fs.read(bytes);
+		if (bytes_read != size)
+			return new Hashtable();
 
-			return decode(bytes) as Hashtable;
-		}
+		return decode(bytes) as Hashtable;
+	}
 
-		/// <summary>
-		/// Convenience function for saving a file.
-		/// </summary>
-		public static void save(Hashtable h, string path)
-		{
-			FileStream fs = FileStream.open(path, "wb");
-			if (fs == null)
-				return;
+	/// <summary>
+	/// Convenience function for saving a file.
+	/// </summary>
+	public static void save(Hashtable h, string path)
+	{
+		FileStream fs = FileStream.open(path, "wb");
+		if (fs == null)
+			return;
 
-			fs.write(encode(h).data);
-		}
+		fs.write(encode(h).data);
+	}
 
-		static void write_new_line(StringBuilder builder, int indentation)
-		{
-			builder.append_c('\n');
-			for (int i = 0; i < indentation; ++i)
-				builder.append_c('\t');
-		}
+	static void write_new_line(StringBuilder builder, int indentation)
+	{
+		builder.append_c('\n');
+		for (int i = 0; i < indentation; ++i)
+			builder.append_c('\t');
+	}
 
-		static void write(Value? o, StringBuilder builder, int indentation)
-		{
-			if (o == null)
-				builder.append("null");
-			else if (o.holds(typeof(bool)) && (bool)o == false)
-				builder.append("false");
-			else if (o.holds(typeof(bool)))
-				builder.append("true");
-			else if (o.holds(typeof(int)))
-				builder.append_printf("%d", (int)o);
-			else if (o.holds(typeof(float)))
-				builder.append_printf("%.9g", (float)o);
-			else if (o.holds(typeof(double)))
-				builder.append_printf("%.17g", (double)o);
-			else if (o.holds(typeof(string)))
-				write_string((string)o, builder);
-			else if (o.holds(typeof(ArrayList)))
-				write_array((ArrayList)o, builder, indentation);
-			else if (o.holds(typeof(Hashtable)))
-				write_object((Hashtable)o, builder, indentation);
-			else
-				assert(false);
-		}
+	static void write(Value? o, StringBuilder builder, int indentation)
+	{
+		if (o == null)
+			builder.append("null");
+		else if (o.holds(typeof(bool)) && (bool)o == false)
+			builder.append("false");
+		else if (o.holds(typeof(bool)))
+			builder.append("true");
+		else if (o.holds(typeof(int)))
+			builder.append_printf("%d", (int)o);
+		else if (o.holds(typeof(float)))
+			builder.append_printf("%.9g", (float)o);
+		else if (o.holds(typeof(double)))
+			builder.append_printf("%.17g", (double)o);
+		else if (o.holds(typeof(string)))
+			write_string((string)o, builder);
+		else if (o.holds(typeof(ArrayList)))
+			write_array((ArrayList)o, builder, indentation);
+		else if (o.holds(typeof(Hashtable)))
+			write_object((Hashtable)o, builder, indentation);
+		else
+			assert(false);
+	}
 
-		static void write_string(string s, StringBuilder builder)
+	static void write_string(string s, StringBuilder builder)
+	{
+		builder.append_c('"');
+		for (int i = 0; i < s.length; ++i)
 		{
-			builder.append_c('"');
-			for (int i = 0; i < s.length; ++i)
+			uint8 c = s[i];
+			if (c == '"' || c == '\\')
 			{
-				uint8 c = s[i];
-				if (c == '"' || c == '\\')
-				{
-					builder.append_c('\\');
-					builder.append_c((char)c);
-				}
-				else if (c == '\n')
-				{
-					builder.append_c('\\');
-					builder.append_c('n');
-				}
-				else
-					builder.append_c((char)c);
+				builder.append_c('\\');
+				builder.append_c((char)c);
 			}
-			builder.append_c('"');
-		}
-
-		static void write_array(ArrayList<Value?> a, StringBuilder builder, int indentation)
-		{
-			bool write_comma = false;
-			builder.append("[ ");
-			foreach (Value? item in a)
+			else if (c == '\n')
 			{
-				if (write_comma)
-					builder.append(", ");
-				write(item, builder, indentation + 1);
-				write_comma = true;
+				builder.append_c('\\');
+				builder.append_c('n');
 			}
-			builder.append("]");
+			else
+				builder.append_c((char)c);
 		}
+		builder.append_c('"');
+	}
 
-		static void write_object(Hashtable t, StringBuilder builder, int indentation)
+	static void write_array(ArrayList<Value?> a, StringBuilder builder, int indentation)
+	{
+		bool write_comma = false;
+		builder.append("[ ");
+		foreach (Value? item in a)
 		{
-			builder.append_c('{');
-			bool write_comma = false;
-			foreach (var de in t.entries)
-			{
-				if (write_comma)
-					builder.append(", ");
-				write_new_line(builder, indentation);
-				write(de.key, builder, indentation);
-				builder.append(" : ");
-				write(de.value, builder, indentation);
-				write_comma = true;
-			}
-			write_new_line(builder, indentation);
-			builder.append_c('}');
+			if (write_comma)
+				builder.append(", ");
+			write(item, builder, indentation + 1);
+			write_comma = true;
 		}
+		builder.append("]");
+	}
 
-		static void skip_whitespace(uint8[] json, ref int index)
+	static void write_object(Hashtable t, StringBuilder builder, int indentation)
+	{
+		builder.append_c('{');
+		bool write_comma = false;
+		foreach (var de in t.entries)
 		{
-			while (index < json.length)
-			{
-				uint8 c = json[index];
-				if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',')
-					++index;
-				else
-					break;
-			}
+			if (write_comma)
+				builder.append(", ");
+			write_new_line(builder, indentation);
+			write(de.key, builder, indentation);
+			builder.append(" : ");
+			write(de.value, builder, indentation);
+			write_comma = true;
 		}
+		write_new_line(builder, indentation);
+		builder.append_c('}');
+	}
 
-		static void consume(uint8[] json, ref int index, string consume)
+	static void skip_whitespace(uint8[] json, ref int index)
+	{
+		while (index < json.length)
 		{
-			skip_whitespace(json, ref index);
-			for (int i = 0; i < consume.length; ++i)
-			{
-				if (json[index] != consume[i])
-					assert(false);
+			uint8 c = json[index];
+			if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',')
 				++index;
-			}
+			else
+				break;
 		}
+	}
 
-		static Value? parse(uint8[] json, ref int index)
+	static void consume(uint8[] json, ref int index, string consume)
+	{
+		skip_whitespace(json, ref index);
+		for (int i = 0; i < consume.length; ++i)
 		{
-			uint8 c = next(json, ref index);
-
-			if (c == '{')
-				return parse_object(json, ref index);
-			else if (c == '[')
-				return parse_array(json, ref index);
-			else if (c == '"')
-				return parse_string(json, ref index);
-			else if (c == '-' || c >= '0' && c <= '9')
-				return parse_number(json, ref index);
-			else if (c == 't')
-			{
-				consume(json, ref index, "true");
-				return true;
-			}
-			else if (c == 'f')
-			{
-				consume(json, ref index, "false");
-				return false;
-			}
-			else if (c == 'n')
-			{
-				consume(json, ref index, "null");
-				return null;
-			}
-			else
-			{
+			if (json[index] != consume[i])
 				assert(false);
-				return null;
-			}
+			++index;
 		}
+	}
 
-		static uint8 next(uint8[] json, ref int index)
+	static Value? parse(uint8[] json, ref int index)
+	{
+		uint8 c = next(json, ref index);
+
+		if (c == '{')
+			return parse_object(json, ref index);
+		else if (c == '[')
+			return parse_array(json, ref index);
+		else if (c == '"')
+			return parse_string(json, ref index);
+		else if (c == '-' || c >= '0' && c <= '9')
+			return parse_number(json, ref index);
+		else if (c == 't')
 		{
-			skip_whitespace(json, ref index);
-			return json[index];
+			consume(json, ref index, "true");
+			return true;
 		}
-
-		static Hashtable parse_object(uint8[] json, ref int index)
+		else if (c == 'f')
 		{
-			Hashtable ht = new Hashtable();
-			consume(json, ref index, "{");
-			skip_whitespace(json, ref index);
-
-			while (next(json, ref index) != '}')
-			{
-				string key = parse_string(json, ref index);
-				consume(json, ref index, ":");
-				if (key.has_suffix("_binary"))
-					ht[key] = (Value?)parse_binary(json, ref index);
-				else
-					ht[key] = parse(json, ref index);
-			}
-			consume(json, ref index, "}");
-			return ht;
+			consume(json, ref index, "false");
+			return false;
 		}
+		else if (c == 'n')
+		{
+			consume(json, ref index, "null");
+			return null;
+		}
+		else
+		{
+			assert(false);
+			return null;
+		}
+	}
 
-		static ArrayList<Value?> parse_array(uint8[] json, ref int index)
+	static uint8 next(uint8[] json, ref int index)
+	{
+		skip_whitespace(json, ref index);
+		return json[index];
+	}
+
+	static Hashtable parse_object(uint8[] json, ref int index)
+	{
+		Hashtable ht = new Hashtable();
+		consume(json, ref index, "{");
+		skip_whitespace(json, ref index);
+
+		while (next(json, ref index) != '}')
 		{
-			ArrayList<Value?> a = new ArrayList<Value?>();
-			consume(json, ref index, "[");
-			while (next(json, ref index) != ']')
-			{
-				Value? value = parse(json, ref index);
-				a.add(value);
-			}
-			consume(json, ref index, "]");
-			return a;
+			string key = parse_string(json, ref index);
+			consume(json, ref index, ":");
+			if (key.has_suffix("_binary"))
+				ht[key] = (Value?)parse_binary(json, ref index);
+			else
+				ht[key] = parse(json, ref index);
 		}
+		consume(json, ref index, "}");
+		return ht;
+	}
 
-		static uint8[] parse_binary(uint8[] json, ref int index)
+	static ArrayList<Value?> parse_array(uint8[] json, ref int index)
+	{
+		ArrayList<Value?> a = new ArrayList<Value?>();
+		consume(json, ref index, "[");
+		while (next(json, ref index) != ']')
 		{
-			ArrayList<uint8> s = new ArrayList<uint8>();
+			Value? value = parse(json, ref index);
+			a.add(value);
+		}
+		consume(json, ref index, "]");
+		return a;
+	}
 
-			consume(json, ref index, "\"");
-			while (true)
+	static uint8[] parse_binary(uint8[] json, ref int index)
+	{
+		ArrayList<uint8> s = new ArrayList<uint8>();
+
+		consume(json, ref index, "\"");
+		while (true)
+		{
+			uint8 c = json[index];
+			++index;
+			if (c == '"')
+				break;
+			else if (c != '\\')
+				s.add(c);
+			else
 			{
-				uint8 c = json[index];
+				uint8 q = json[index];
 				++index;
-				if (c == '"')
-					break;
-				else if (c != '\\')
-					s.add(c);
+				if (q == '"' || q == '\\' || q == '/')
+					s.add(q);
+				else if (q == 'b') s.add('\b');
+				else if (q == 'f') s.add('\f');
+				else if (q == 'n') s.add('\n');
+				else if (q == 'r') s.add('\r');
+				else if (q == 't') s.add('\t');
+				else if (q == 'u')
+				{
+					assert(false);
+				}
 				else
 				{
-					uint8 q = json[index];
-					++index;
-					if (q == '"' || q == '\\' || q == '/')
-						s.add(q);
-					else if (q == 'b') s.add('\b');
-					else if (q == 'f') s.add('\f');
-					else if (q == 'n') s.add('\n');
-					else if (q == 'r') s.add('\r');
-					else if (q == 't') s.add('\t');
-					else if (q == 'u')
-					{
-						assert(false);
-					}
-					else
-					{
-						assert(false);
-					}
+					assert(false);
 				}
 			}
-			s.add('\0');
-			return s.to_array();
 		}
+		s.add('\0');
+		return s.to_array();
+	}
 
-		static string parse_string(uint8[] json, ref int index)
-		{
-			return (string)parse_binary(json, ref index);
-		}
+	static string parse_string(uint8[] json, ref int index)
+	{
+		return (string)parse_binary(json, ref index);
+	}
 
-		static double parse_number(uint8[] json, ref int index)
-		{
-			int end = index;
-			while ("0123456789+-.eE".index_of_char((char)json[end]) != -1)
-				++end;
-			uint8[] num = json[index:end];
-			num += '\0';
-			index = end;
-			return double.parse((string)num);
-		}
+	static double parse_number(uint8[] json, ref int index)
+	{
+		int end = index;
+		while ("0123456789+-.eE".index_of_char((char)json[end]) != -1)
+			++end;
+		uint8[] num = json[index:end];
+		num += '\0';
+		index = end;
+		return double.parse((string)num);
 	}
 }
+
+}

+ 302 - 301
tools/core/json/sjson.vala

@@ -11,356 +11,357 @@
 using Gee;
 
 namespace Crown
+{
+/// <summary>
+/// Provides functions for encoding and decoding files in the simplified JSON format.
+/// </summary>
+[Compact]
+public class SJSON
 {
 	/// <summary>
-	/// Provides functions for encoding and decoding files in the simplified JSON format.
+	///  Encodes the Hashtable t in the simplified JSON format. The Hashtable can
+	///  contain, numbers, bools, strings, ArrayLists and Hashtables.
 	/// </summary>
-	[Compact]
-	public class SJSON
+	public static string encode(Hashtable t)
 	{
-		/// <summary>
-		///  Encodes the Hashtable t in the simplified JSON format. The Hashtable can
-		///  contain, numbers, bools, strings, ArrayLists and Hashtables.
-		/// </summary>
-		public static string encode(Hashtable t)
-		{
-			StringBuilder sb = new StringBuilder();
-			write_root_object(t, sb);
-			sb.append_c('\n');
-			return sb.str;
-		}
-
-		/// <summary>
-		/// Encodes the object o in the simplified JSON format (not as a root object).
-		/// </summary>
-		public static string encode_object(Value? o)
-		{
-			StringBuilder sb = new StringBuilder();
-			write(o, sb, 0);
-			return sb.str;
-		}
+		StringBuilder sb = new StringBuilder();
+		write_root_object(t, sb);
+		sb.append_c('\n');
+		return sb.str;
+	}
 
-		/// <summary>
-		/// Decodes a SJSON bytestream into a Hashtable with numbers, bools, strings,
-		/// ArrayLists and Hashtables.
-		/// </summary>
-		public static Hashtable decode(uint8[] sjson)
-		{
-			int index = 0;
-			return parse_root_object(sjson, ref index);
-		}
+	/// <summary>
+	/// Encodes the object o in the simplified JSON format (not as a root object).
+	/// </summary>
+	public static string encode_object(Value? o)
+	{
+		StringBuilder sb = new StringBuilder();
+		write(o, sb, 0);
+		return sb.str;
+	}
 
-		/// <summary>
-		/// Convenience function for loading a file.
-		/// </summary>
-		public static Hashtable load(string path)
-		{
-			FileStream fs = FileStream.open(path, "rb");
-			if (fs == null)
-				return new Hashtable();
-
-			// Get file size
-			fs.seek(0, FileSeek.END);
-			size_t size = fs.tell();
-			fs.rewind();
-			if (size == 0)
-				return new Hashtable();
-
-			// Read whole file
-			uint8[] bytes = new uint8[size];
-			size_t bytes_read = fs.read(bytes);
-			if (bytes_read != size)
-				return new Hashtable();
-
-			return decode(bytes) as Hashtable;
-		}
+	/// <summary>
+	/// Decodes a SJSON bytestream into a Hashtable with numbers, bools, strings,
+	/// ArrayLists and Hashtables.
+	/// </summary>
+	public static Hashtable decode(uint8[] sjson)
+	{
+		int index = 0;
+		return parse_root_object(sjson, ref index);
+	}
 
-		/// <summary>
-		/// Convenience function for saving a file.
-		/// </summary>
-		public static void save(Hashtable h, string path)
-		{
-			FileStream fs = FileStream.open(path, "wb");
-			if (fs == null)
-				return;
+	/// <summary>
+	/// Convenience function for loading a file.
+	/// </summary>
+	public static Hashtable load(string path)
+	{
+		FileStream fs = FileStream.open(path, "rb");
+		if (fs == null)
+			return new Hashtable();
+
+		// Get file size
+		fs.seek(0, FileSeek.END);
+		size_t size = fs.tell();
+		fs.rewind();
+		if (size == 0)
+			return new Hashtable();
+
+		// Read whole file
+		uint8[] bytes = new uint8[size];
+		size_t bytes_read = fs.read(bytes);
+		if (bytes_read != size)
+			return new Hashtable();
+
+		return decode(bytes) as Hashtable;
+	}
 
-			fs.write(encode(h).data);
-		}
+	/// <summary>
+	/// Convenience function for saving a file.
+	/// </summary>
+	public static void save(Hashtable h, string path)
+	{
+		FileStream fs = FileStream.open(path, "wb");
+		if (fs == null)
+			return;
 
-		static void write_root_object(Hashtable t, StringBuilder builder)
-		{
-		   write_object_fields(t, builder, 0);
-		}
+		fs.write(encode(h).data);
+	}
 
-		static void write_object_fields(Hashtable t, StringBuilder builder, int indentation)
-		{
-			ArrayList<string> keys = new ArrayList<string>.wrap(t.keys.to_array());
-			keys.sort(Gee.Functions.get_compare_func_for(typeof(string)));
-			foreach (string key in keys) {
-				write_new_line(builder, indentation);
-				builder.append(key);
-				builder.append(" = ");
-				write(t[key], builder, indentation);
-			}
-		}
+	static void write_root_object(Hashtable t, StringBuilder builder)
+	{
+	   write_object_fields(t, builder, 0);
+	}
 
-		static void write_new_line(StringBuilder builder, int indentation)
-		{
-			builder.append_c('\n');
-			for (int i = 0; i < indentation; ++i)
-				builder.append_c('\t');
+	static void write_object_fields(Hashtable t, StringBuilder builder, int indentation)
+	{
+		ArrayList<string> keys = new ArrayList<string>.wrap(t.keys.to_array());
+		keys.sort(Gee.Functions.get_compare_func_for(typeof(string)));
+		foreach (string key in keys) {
+			write_new_line(builder, indentation);
+			builder.append(key);
+			builder.append(" = ");
+			write(t[key], builder, indentation);
 		}
+	}
 
-		static void write(Value? o, StringBuilder builder, int indentation)
-		{
-			if (o == null)
-				builder.append("null");
-			else if (o.holds(typeof(bool)) && (bool)o == false)
-				builder.append("false");
-			else if (o.holds(typeof(bool)))
-				builder.append("true");
-			else if (o.holds(typeof(uint8)))
-				builder.append_printf("%u", (uint8)o);
-			else if (o.holds(typeof(int)))
-				builder.append_printf("%d", (int)o);
-			else if (o.holds(typeof(float)))
-				builder.append_printf("%.9g", (float)o);
-			else if (o.holds(typeof(double)))
-				builder.append_printf("%.17g", (double)o);
-			else if (o.holds(typeof(string)))
-				write_string((string)o, builder);
-			else if (o.holds(typeof(ArrayList)))
-				write_array((ArrayList)o, builder, indentation);
-			else if (o.holds(typeof(Hashtable)))
-				write_object((Hashtable)o, builder, indentation);
-			else
-				GLib.assert(false);
-		}
+	static void write_new_line(StringBuilder builder, int indentation)
+	{
+		builder.append_c('\n');
+		for (int i = 0; i < indentation; ++i)
+			builder.append_c('\t');
+	}
 
-		static void write_string(string s, StringBuilder builder)
-		{
-			builder.append_c('"');
-			for (int i=0; i<s.length; ++i) {
-				char c = s[i];
-				if (c == '"' || c == '\\')
-					builder.append_c('\\');
-				builder.append_c(c);
-			}
-			builder.append_c('"');
-		}
+	static void write(Value? o, StringBuilder builder, int indentation)
+	{
+		if (o == null)
+			builder.append("null");
+		else if (o.holds(typeof(bool)) && (bool)o == false)
+			builder.append("false");
+		else if (o.holds(typeof(bool)))
+			builder.append("true");
+		else if (o.holds(typeof(uint8)))
+			builder.append_printf("%u", (uint8)o);
+		else if (o.holds(typeof(int)))
+			builder.append_printf("%d", (int)o);
+		else if (o.holds(typeof(float)))
+			builder.append_printf("%.9g", (float)o);
+		else if (o.holds(typeof(double)))
+			builder.append_printf("%.17g", (double)o);
+		else if (o.holds(typeof(string)))
+			write_string((string)o, builder);
+		else if (o.holds(typeof(ArrayList)))
+			write_array((ArrayList)o, builder, indentation);
+		else if (o.holds(typeof(Hashtable)))
+			write_object((Hashtable)o, builder, indentation);
+		else
+			GLib.assert(false);
+	}
 
-		static void write_array(ArrayList<Value?> a, StringBuilder builder, int indentation)
-		{
-			builder.append_c('[');
-			foreach (Value? item in a) {
-				write_new_line(builder, indentation+1);
-				write(item, builder, indentation+1);
-			}
-			write_new_line(builder, indentation);
-			builder.append_c(']');
+	static void write_string(string s, StringBuilder builder)
+	{
+		builder.append_c('"');
+		for (int i=0; i<s.length; ++i) {
+			char c = s[i];
+			if (c == '"' || c == '\\')
+				builder.append_c('\\');
+			builder.append_c(c);
 		}
+		builder.append_c('"');
+	}
 
-		static void write_object(Hashtable t, StringBuilder builder, int indentation)
-		{
-			builder.append_c('{');
-			write_object_fields(t, builder, indentation+1);
-			write_new_line(builder, indentation);
-			builder.append_c('}');
+	static void write_array(ArrayList<Value?> a, StringBuilder builder, int indentation)
+	{
+		builder.append_c('[');
+		foreach (Value? item in a) {
+			write_new_line(builder, indentation+1);
+			write(item, builder, indentation+1);
 		}
+		write_new_line(builder, indentation);
+		builder.append_c(']');
+	}
 
-		static Hashtable parse_root_object(uint8 [] json, ref int index)
-		{
-			Hashtable ht = new Hashtable();
-			while (!at_end(json, ref index)) {
-				string key = parse_identifier(json, ref index);
-				consume(json, ref index, "=");
-				Value? value = parse_value(json, ref index);
-				ht[key] = value;
-			}
-			return ht;
-		}
+	static void write_object(Hashtable t, StringBuilder builder, int indentation)
+	{
+		builder.append_c('{');
+		write_object_fields(t, builder, indentation+1);
+		write_new_line(builder, indentation);
+		builder.append_c('}');
+	}
 
-		static bool at_end(uint8 [] json, ref int index)
-		{
-			skip_whitespace(json, ref index);
-			return (index >= json.length);
+	static Hashtable parse_root_object(uint8 [] json, ref int index)
+	{
+		Hashtable ht = new Hashtable();
+		while (!at_end(json, ref index)) {
+			string key = parse_identifier(json, ref index);
+			consume(json, ref index, "=");
+			Value? value = parse_value(json, ref index);
+			ht[key] = value;
 		}
+		return ht;
+	}
 
-		static void skip_whitespace(uint8 [] json, ref int index)
-		{
-			while (index < json.length) {
-				uint8 c = json[index];
-				if (c == '/')
-					skip_comment(json, ref index);
-				else if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',')
-					++index;
-				else
-					break;
-			}
-		}
+	static bool at_end(uint8 [] json, ref int index)
+	{
+		skip_whitespace(json, ref index);
+		return (index >= json.length);
+	}
 
-		static void skip_comment(uint8 [] json, ref int index)
-		{
-			uint8 next = json[index + 1];
-			if (next == '/')
-			{
-				while (index + 1 < json.length && json[index] != '\n')
-					++index;
+	static void skip_whitespace(uint8 [] json, ref int index)
+	{
+		while (index < json.length) {
+			uint8 c = json[index];
+			if (c == '/')
+				skip_comment(json, ref index);
+			else if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',')
 				++index;
-			}
-			else if (next == '*')
-			{
-				while (index + 2 < json.length && (json[index] != '*' || json[index + 1] != '/'))
-					++index;
-				index += 2;
-			}
 			else
-				GLib.assert(false);
+				break;
 		}
+	}
 
-		static string parse_identifier(uint8 [] json, ref int index)
+	static void skip_comment(uint8 [] json, ref int index)
+	{
+		uint8 next = json[index + 1];
+		if (next == '/')
 		{
-			skip_whitespace(json, ref index);
-
-			if (json[index] == '"')
-				return parse_string(json, ref index);
-
-			ArrayList<uint8> s = new ArrayList<uint8>();
-			while (true) {
-				uint8 c = json[index];
-				if (c == ' ' || c == '\t' || c == '\n' || c == '=')
-					break;
-				s.add(c);
+			while (index + 1 < json.length && json[index] != '\n')
 				++index;
-			}
-			s.add('\0');
-			return (string)s.to_array();
+			++index;
 		}
-
-		static void consume(uint8 [] json, ref int index, string consume)
+		else if (next == '*')
 		{
-			skip_whitespace(json, ref index);
-			for (int i=0; i<consume.length; ++i) {
-				if (json[index] != consume[i])
-					GLib.assert(false);
+			while (index + 2 < json.length && (json[index] != '*' || json[index + 1] != '/'))
 				++index;
-			}
+			index += 2;
 		}
+		else
+			GLib.assert(false);
+	}
 
-		static Value? parse_value(uint8 [] json, ref int index)
-		{
-			uint8 c = next(json, ref index);
-
-			if (c == '{')
-				return parse_object(json, ref index);
-			else if (c == '[')
-				return parse_array(json, ref index);
-			else if (c == '"')
-				return parse_string(json, ref index);
-			else if (c == '-' || c >= '0' && c <= '9')
-				return parse_number(json, ref index);
-			else if (c == 't')
-			{
-				consume(json, ref index, "true");
-				return true;
-			}
-			else if (c == 'f')
-			{
-				consume(json, ref index, "false");
-				return false;
-			}
-			else if (c == 'n')
-			{
-				consume(json, ref index, "null");
-				return null;
-			}
-			else
-			{
-				GLib.assert(false);
-				return null;
-			}
+	static string parse_identifier(uint8 [] json, ref int index)
+	{
+		skip_whitespace(json, ref index);
+
+		if (json[index] == '"')
+			return parse_string(json, ref index);
+
+		ArrayList<uint8> s = new ArrayList<uint8>();
+		while (true) {
+			uint8 c = json[index];
+			if (c == ' ' || c == '\t' || c == '\n' || c == '=')
+				break;
+			s.add(c);
+			++index;
+		}
+		s.add('\0');
+		return (string)s.to_array();
+	}
 
+	static void consume(uint8 [] json, ref int index, string consume)
+	{
+		skip_whitespace(json, ref index);
+		for (int i=0; i<consume.length; ++i) {
+			if (json[index] != consume[i])
+				GLib.assert(false);
+			++index;
 		}
+	}
 
-		static uint8 next(uint8 [] json, ref int index)
+	static Value? parse_value(uint8 [] json, ref int index)
+	{
+		uint8 c = next(json, ref index);
+
+		if (c == '{')
+			return parse_object(json, ref index);
+		else if (c == '[')
+			return parse_array(json, ref index);
+		else if (c == '"')
+			return parse_string(json, ref index);
+		else if (c == '-' || c >= '0' && c <= '9')
+			return parse_number(json, ref index);
+		else if (c == 't')
 		{
-			skip_whitespace(json, ref index);
-			return json[index];
+			consume(json, ref index, "true");
+			return true;
 		}
-
-		static Hashtable parse_object(uint8 [] json, ref int index)
+		else if (c == 'f')
 		{
-			Hashtable ht = new Hashtable();
-			consume(json, ref index, "{");
-			skip_whitespace(json, ref index);
-
-			while (next(json, ref index) != '}') {
-				string key = parse_identifier(json, ref index);
-				consume(json, ref index, "=");
-				Value? value = parse_value(json, ref index);
-				ht[key] = value;
-			}
-			consume(json, ref index, "}");
-			return ht;
+			consume(json, ref index, "false");
+			return false;
 		}
-
-		static ArrayList<Value?> parse_array(uint8 [] json, ref int index)
+		else if (c == 'n')
 		{
-			ArrayList<Value?> a = new ArrayList<Value?>();
-			consume(json, ref index, "[");
-			while (next(json, ref index) != ']') {
-				Value? value = parse_value(json, ref index);
-				a.add(value);
-			}
-			consume(json, ref index, "]");
-			return a;
+			consume(json, ref index, "null");
+			return null;
 		}
-
-		static string parse_string(uint8[] json, ref int index)
+		else
 		{
-			ArrayList<uint8> s = new ArrayList<uint8>();
+			GLib.assert(false);
+			return null;
+		}
+
+	}
+
+	static uint8 next(uint8 [] json, ref int index)
+	{
+		skip_whitespace(json, ref index);
+		return json[index];
+	}
+
+	static Hashtable parse_object(uint8 [] json, ref int index)
+	{
+		Hashtable ht = new Hashtable();
+		consume(json, ref index, "{");
+		skip_whitespace(json, ref index);
+
+		while (next(json, ref index) != '}') {
+			string key = parse_identifier(json, ref index);
+			consume(json, ref index, "=");
+			Value? value = parse_value(json, ref index);
+			ht[key] = value;
+		}
+		consume(json, ref index, "}");
+		return ht;
+	}
 
-			consume(json, ref index, "\"");
-			while (true) {
-				uint8 c = json[index];
+	static ArrayList<Value?> parse_array(uint8 [] json, ref int index)
+	{
+		ArrayList<Value?> a = new ArrayList<Value?>();
+		consume(json, ref index, "[");
+		while (next(json, ref index) != ']') {
+			Value? value = parse_value(json, ref index);
+			a.add(value);
+		}
+		consume(json, ref index, "]");
+		return a;
+	}
+
+	static string parse_string(uint8[] json, ref int index)
+	{
+		ArrayList<uint8> s = new ArrayList<uint8>();
+
+		consume(json, ref index, "\"");
+		while (true) {
+			uint8 c = json[index];
+			++index;
+			if (c == '"')
+				break;
+			else if (c != '\\')
+				s.add(c);
+			else {
+				uint8 q = json[index];
 				++index;
-				if (c == '"')
-					break;
-				else if (c != '\\')
-					s.add(c);
-				else {
-					uint8 q = json[index];
-					++index;
-					if (q == '"' || q == '\\' || q == '/')
-						s.add(q);
-					else if (q == 'b') s.add('\b');
-					else if (q == 'f') s.add('\f');
-					else if (q == 'n') s.add('\n');
-					else if (q == 'r') s.add('\r');
-					else if (q == 't') s.add('\t');
-					else if (q == 'u')
-					{
-						GLib.assert(false);
-					}
-					else
-					{
-						GLib.assert(false);
-					}
+				if (q == '"' || q == '\\' || q == '/')
+					s.add(q);
+				else if (q == 'b') s.add('\b');
+				else if (q == 'f') s.add('\f');
+				else if (q == 'n') s.add('\n');
+				else if (q == 'r') s.add('\r');
+				else if (q == 't') s.add('\t');
+				else if (q == 'u')
+				{
+					GLib.assert(false);
+				}
+				else
+				{
+					GLib.assert(false);
 				}
 			}
-			s.add('\0');
-			return (string)s.to_array();
 		}
+		s.add('\0');
+		return (string)s.to_array();
+	}
 
-		static double parse_number(uint8[] json, ref int index)
-		{
-			int end = index;
-			while (end < json.length && "0123456789+-.eE".index_of_char((char)json[end]) != -1)
-				++end;
-			uint8[] num = json[index:end];
-			num += '\0';
-			index = end;
-			return double.parse((string)num);
-		}
+	static double parse_number(uint8[] json, ref int index)
+	{
+		int end = index;
+		while (end < json.length && "0123456789+-.eE".index_of_char((char)json[end]) != -1)
+			++end;
+		uint8[] num = json[index:end];
+		num += '\0';
+		index = end;
+		return double.parse((string)num);
 	}
 }
+
+}

+ 16 - 15
tools/core/math/math_utils.vala

@@ -5,23 +5,24 @@
 
 namespace Crown
 {
-	namespace MathUtils
+namespace MathUtils
+{
+	public bool equal(double a, double b, double epsilon = 0.00001f)
 	{
-		public bool equal(double a, double b, double epsilon = 0.00001f)
-		{
-			return b <= (a + epsilon)
-				&& b >= (a - epsilon)
-				;
-		}
+		return b <= (a + epsilon)
+			&& b >= (a - epsilon)
+			;
+	}
 
-		public double rad(double deg)
-		{
-			return deg * Math.PI / 180.0;
-		}
+	public double rad(double deg)
+	{
+		return deg * Math.PI / 180.0;
+	}
 
-		public double deg(double rad)
-		{
-			return rad * 180.0 / Math.PI;
-		}
+	public double deg(double rad)
+	{
+		return rad * 180.0 / Math.PI;
 	}
 }
+
+}

+ 15 - 14
tools/core/math/matrix4x4.vala

@@ -5,20 +5,21 @@
 
 namespace Crown
 {
-	[Compact]
-	public struct Matrix4x4
-	{
-		public Vector4 x;
-		public Vector4 y;
-		public Vector4 z;
-		public Vector4 t;
+[Compact]
+public struct Matrix4x4
+{
+	public Vector4 x;
+	public Vector4 y;
+	public Vector4 z;
+	public Vector4 t;
 
-		public Matrix4x4(Vector4 x, Vector4 y, Vector4 z, Vector4 t)
-		{
-			this.x = x;
-			this.y = y;
-			this.z = z;
-			this.t = t;
-		}
+	public Matrix4x4(Vector4 x, Vector4 y, Vector4 z, Vector4 t)
+	{
+		this.x = x;
+		this.y = y;
+		this.z = z;
+		this.t = t;
 	}
 }
+
+}

+ 91 - 90
tools/core/math/quaternion.vala

@@ -7,108 +7,109 @@ using Gee;
 
 namespace Crown
 {
-	[Compact]
-	public struct Quaternion
+[Compact]
+public struct Quaternion
+{
+	public double x;
+	public double y;
+	public double z;
+	public double w;
+
+	public Quaternion(double x, double y, double z, double w)
 	{
-		public double x;
-		public double y;
-		public double z;
-		public double w;
+		this.x = x;
+		this.y = y;
+		this.z = z;
+		this.w = w;
+	}
 
-		public Quaternion(double x, double y, double z, double w)
-		{
-			this.x = x;
-			this.y = y;
-			this.z = z;
-			this.w = w;
-		}
+	public Quaternion.from_array(ArrayList<Value?> arr)
+	{
+		this.x = (double)arr[0];
+		this.y = (double)arr[1];
+		this.z = (double)arr[2];
+		this.w = (double)arr[3];
+	}
 
-		public Quaternion.from_array(ArrayList<Value?> arr)
-		{
-			this.x = (double)arr[0];
-			this.y = (double)arr[1];
-			this.z = (double)arr[2];
-			this.w = (double)arr[3];
-		}
+	public Quaternion.from_axis_angle(Vector3 axis, float angle)
+	{
+		double ha = angle * 0.5;
+		double sa = Math.sin(ha);
+		double ca = Math.cos(ha);
+		this.x = axis.x * sa;
+		this.y = axis.y * sa;
+		this.z = axis.z * sa;
+		this.w = ca;
+	}
 
-		public Quaternion.from_axis_angle(Vector3 axis, float angle)
-		{
-			double ha = angle * 0.5;
-			double sa = Math.sin(ha);
-			double ca = Math.cos(ha);
-			this.x = axis.x * sa;
-			this.y = axis.y * sa;
-			this.z = axis.z * sa;
-			this.w = ca;
-		}
+	public Quaternion.from_euler(double rx, double ry, double rz)
+	{
+		// http://www.euclideanspace.com/maths/geometry/rotations/conversions/eulerToQuaternion/
+		double c1 = Math.cos(ry*0.5);
+		double s1 = Math.sin(ry*0.5);
+		double c2 = Math.cos(rz*0.5);
+		double s2 = Math.sin(rz*0.5);
+		double c3 = Math.cos(rx*0.5);
+		double s3 = Math.sin(rx*0.5);
+		double c1c2 = c1*c2;
+		double s1s2 = s1*s2;
+
+		double nw = c1c2*c3 - s1s2*s3;
+		double nx = c1c2*s3 + s1s2*c3;
+		double ny = s1*c2*c3 + c1*s2*s3;
+		double nz = c1*s2*c3 - s1*c2*s3;
 
-		public Quaternion.from_euler(double rx, double ry, double rz)
-		{
-			// http://www.euclideanspace.com/maths/geometry/rotations/conversions/eulerToQuaternion/
-			double c1 = Math.cos(ry*0.5);
-			double s1 = Math.sin(ry*0.5);
-			double c2 = Math.cos(rz*0.5);
-			double s2 = Math.sin(rz*0.5);
-			double c3 = Math.cos(rx*0.5);
-			double s3 = Math.sin(rx*0.5);
-			double c1c2 = c1*c2;
-			double s1s2 = s1*s2;
+		this.x = nx;
+		this.y = ny;
+		this.z = nz;
+		this.w = nw;
+	}
 
-			double nw = c1c2*c3 - s1s2*s3;
-			double nx = c1c2*s3 + s1s2*c3;
-			double ny = s1*c2*c3 + c1*s2*s3;
-			double nz = c1*s2*c3 - s1*c2*s3;
+	public ArrayList<Value?> to_array()
+	{
+		ArrayList<Value?> arr = new	ArrayList<Value?>();
+		arr.add(this.x);
+		arr.add(this.y);
+		arr.add(this.z);
+		arr.add(this.w);
+		return arr;
+	}
 
-			this.x = nx;
-			this.y = ny;
-			this.z = nz;
-			this.w = nw;
+	public Vector3 to_euler()
+	{
+		// http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/
+		double test = x*y + z*w;
+		if (test > 0.499)
+		{ // singularity at north pole
+			double rx = 0.0;
+			double ry = 2.0 * Math.atan2(x, w);
+			double rz = Math.PI*0.5;
+			return Vector3(rx, ry, rz);
 		}
-
-		public ArrayList<Value?> to_array()
-		{
-			ArrayList<Value?> arr = new	ArrayList<Value?>();
-			arr.add(this.x);
-			arr.add(this.y);
-			arr.add(this.z);
-			arr.add(this.w);
-			return arr;
+		if (test < -0.499)
+		{ // singularity at south pole
+			double rx = +0.0;
+			double ry = -2.0 * Math.atan2(x, w);
+			double rz = -Math.PI*0.5;
+			return Vector3(rx, ry, rz);
 		}
+		double xx = x*x;
+		double yy = y*y;
+		double zz = z*z;
 
-		public Vector3 to_euler()
-		{
-			// http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/
-			double test = x*y + z*w;
-			if (test > 0.499)
-			{ // singularity at north pole
-				double rx = 0.0;
-				double ry = 2.0 * Math.atan2(x, w);
-				double rz = Math.PI*0.5;
-				return Vector3(rx, ry, rz);
-			}
-			if (test < -0.499)
-			{ // singularity at south pole
-				double rx = +0.0;
-				double ry = -2.0 * Math.atan2(x, w);
-				double rz = -Math.PI*0.5;
-				return Vector3(rx, ry, rz);
-			}
-			double xx = x*x;
-			double yy = y*y;
-			double zz = z*z;
-
-			double rrx = Math.atan2(2.0*x*w - 2.0*y*z, 1.0 - 2.0*xx - 2.0*zz);
-			double rry = Math.atan2(2.0*y*w - 2.0*x*z, 1.0 - 2.0*yy - 2.0*zz);
-			double rrz = Math.asin(2.0*test);
+		double rrx = Math.atan2(2.0*x*w - 2.0*y*z, 1.0 - 2.0*xx - 2.0*zz);
+		double rry = Math.atan2(2.0*y*w - 2.0*x*z, 1.0 - 2.0*yy - 2.0*zz);
+		double rrz = Math.asin(2.0*test);
 
-			return Vector3(rrx, rry, rrz);
-		}
+		return Vector3(rrx, rry, rrz);
+	}
 
-		public string to_string()
-		{
-			return "%f, %f, %f, %f".printf(x, y, z, w);
-		}
+	public string to_string()
+	{
+		return "%f, %f, %f, %f".printf(x, y, z, w);
 	}
+}
+
+public const Quaternion QUATERNION_IDENTITY = { 0.0, 0.0, 0.0, 1.0 };
 
-	public const Quaternion QUATERNION_IDENTITY = { 0.0, 0.0, 0.0, 1.0 };
 }

+ 32 - 31
tools/core/math/vector2.vala

@@ -7,38 +7,39 @@ using Gee;
 
 namespace Crown
 {
-	[Compact]
-	public struct Vector2
+[Compact]
+public struct Vector2
+{
+	public double x;
+	public double y;
+
+	public Vector2(double x, double y)
 	{
-		public double x;
-		public double y;
-
-		public Vector2(double x, double y)
-		{
-			this.x = x;
-			this.y = y;
-		}
-
-		public Vector2.from_array(ArrayList<Value?> arr)
-		{
-			this.x = (double)arr[0];
-			this.y = (double)arr[1];
-		}
-
-		public ArrayList<Value?> to_array()
-		{
-			ArrayList<Value?> arr = new	ArrayList<Value?>();
-			arr.add(this.x);
-			arr.add(this.y);
-			return arr;
-		}
-
-		public string to_string()
-		{
-			return "%f, %f".printf(x, y);
-		}
+		this.x = x;
+		this.y = y;
 	}
 
-	public const Vector2 VECTOR2_ZERO = { 0.0, 0.0 };
-	public const Vector2 VECTOR2_ONE  = { 1.0, 1.0 };
+	public Vector2.from_array(ArrayList<Value?> arr)
+	{
+		this.x = (double)arr[0];
+		this.y = (double)arr[1];
+	}
+
+	public ArrayList<Value?> to_array()
+	{
+		ArrayList<Value?> arr = new	ArrayList<Value?>();
+		arr.add(this.x);
+		arr.add(this.y);
+		return arr;
+	}
+
+	public string to_string()
+	{
+		return "%f, %f".printf(x, y);
+	}
+}
+
+public const Vector2 VECTOR2_ZERO = { 0.0, 0.0 };
+public const Vector2 VECTOR2_ONE  = { 1.0, 1.0 };
+
 }

+ 38 - 37
tools/core/math/vector3.vala

@@ -7,44 +7,45 @@ using Gee;
 
 namespace Crown
 {
-	[Compact]
-	public struct Vector3
+[Compact]
+public struct Vector3
+{
+	public double x;
+	public double y;
+	public double z;
+
+	public Vector3(double x, double y, double z)
 	{
-		public double x;
-		public double y;
-		public double z;
-
-		public Vector3(double x, double y, double z)
-		{
-			this.x = x;
-			this.y = y;
-			this.z = z;
-		}
-
-		public Vector3.from_array(ArrayList<Value?> arr)
-		{
-			this.x = (double)arr[0];
-			this.y = (double)arr[1];
-			this.z = (double)arr[2];
-		}
-
-		public ArrayList<Value?> to_array()
-		{
-			ArrayList<Value?> arr = new	ArrayList<Value?>();
-			arr.add(this.x);
-			arr.add(this.y);
-			arr.add(this.z);
-			return arr;
-		}
-
-		public string to_string()
-		{
-			return "%f, %f, %f".printf(x, y, z);
-		}
+		this.x = x;
+		this.y = y;
+		this.z = z;
 	}
 
-	public const Vector3 VECTOR3_ZERO = { 0.0, 0.0, 0.0 };
-	public const Vector3 VECTOR3_ONE  = { 1.0, 1.0, 1.0 };
-	public const Vector3 VECTOR3_MIN  = {-double.MAX, -double.MAX, -double.MAX };
-	public const Vector3 VECTOR3_MAX  = { double.MAX,  double.MAX,  double.MAX };
+	public Vector3.from_array(ArrayList<Value?> arr)
+	{
+		this.x = (double)arr[0];
+		this.y = (double)arr[1];
+		this.z = (double)arr[2];
+	}
+
+	public ArrayList<Value?> to_array()
+	{
+		ArrayList<Value?> arr = new	ArrayList<Value?>();
+		arr.add(this.x);
+		arr.add(this.y);
+		arr.add(this.z);
+		return arr;
+	}
+
+	public string to_string()
+	{
+		return "%f, %f, %f".printf(x, y, z);
+	}
+}
+
+public const Vector3 VECTOR3_ZERO = { 0.0, 0.0, 0.0 };
+public const Vector3 VECTOR3_ONE  = { 1.0, 1.0, 1.0 };
+public const Vector3 VECTOR3_MIN  = {-double.MAX, -double.MAX, -double.MAX };
+public const Vector3 VECTOR3_MAX  = { double.MAX,  double.MAX,  double.MAX };
+
 }

+ 40 - 39
tools/core/math/vector4.vala

@@ -7,46 +7,47 @@ using Gee;
 
 namespace Crown
 {
-	[Compact]
-	public struct Vector4
+[Compact]
+public struct Vector4
+{
+	public double x;
+	public double y;
+	public double z;
+	public double w;
+
+	public Vector4(double x, double y, double z, double w)
 	{
-		public double x;
-		public double y;
-		public double z;
-		public double w;
-
-		public Vector4(double x, double y, double z, double w)
-		{
-			this.x = x;
-			this.y = y;
-			this.z = z;
-			this.w = w;
-		}
-
-		public Vector4.from_array(ArrayList<Value?> arr)
-		{
-			this.x = (double)arr[0];
-			this.y = (double)arr[1];
-			this.z = (double)arr[2];
-			this.w = (double)arr[3];
-		}
-
-		public ArrayList<Value?> to_array()
-		{
-			ArrayList<Value?> arr = new	ArrayList<Value?>();
-			arr.add(this.x);
-			arr.add(this.y);
-			arr.add(this.z);
-			arr.add(this.w);
-			return arr;
-		}
-
-		public string to_string()
-		{
-			return "%f, %f, %f, %f".printf(x, y, z, w);
-		}
+		this.x = x;
+		this.y = y;
+		this.z = z;
+		this.w = w;
 	}
 
-	public const Vector4 VECTOR4_ZERO = { 0.0, 0.0, 0.0, 0.0 };
-	public const Vector4 VECTOR4_ONE  = { 1.0, 1.0, 1.0, 1.0 };
+	public Vector4.from_array(ArrayList<Value?> arr)
+	{
+		this.x = (double)arr[0];
+		this.y = (double)arr[1];
+		this.z = (double)arr[2];
+		this.w = (double)arr[3];
+	}
+
+	public ArrayList<Value?> to_array()
+	{
+		ArrayList<Value?> arr = new	ArrayList<Value?>();
+		arr.add(this.x);
+		arr.add(this.y);
+		arr.add(this.z);
+		arr.add(this.w);
+		return arr;
+	}
+
+	public string to_string()
+	{
+		return "%f, %f, %f, %f".printf(x, y, z, w);
+	}
+}
+
+public const Vector4 VECTOR4_ZERO = { 0.0, 0.0, 0.0, 0.0 };
+public const Vector4 VECTOR4_ONE  = { 1.0, 1.0, 1.0, 1.0 };
+
 }

+ 35 - 34
tools/level_editor/action_type.vala

@@ -5,39 +5,40 @@
 
 namespace Crown
 {
-	public enum ActionType
-	{
-		SPAWN_UNIT,
-		DESTROY_UNIT,
-		SPAWN_SOUND,
-		DESTROY_SOUND,
-		MOVE_OBJECTS,
-		DUPLICATE_OBJECTS,
-		OBJECT_SET_EDITOR_NAME,
-		SET_LIGHT,
-		SET_MESH,
-		SET_SPRITE,
-		SET_CAMERA,
-		SET_COLLIDER,
-		SET_ACTOR,
-		SET_SOUND
-	}
+public enum ActionType
+{
+	SPAWN_UNIT,
+	DESTROY_UNIT,
+	SPAWN_SOUND,
+	DESTROY_SOUND,
+	MOVE_OBJECTS,
+	DUPLICATE_OBJECTS,
+	OBJECT_SET_EDITOR_NAME,
+	SET_LIGHT,
+	SET_MESH,
+	SET_SPRITE,
+	SET_CAMERA,
+	SET_COLLIDER,
+	SET_ACTOR,
+	SET_SOUND
+}
+
+public const string ActionNames[] =
+{
+	"Spawn Unit",
+	"Destroy Unit",
+	"Spawn Sound",
+	"Destroy Sound",
+	"Move Objects",
+	"Duplicate Objects",
+	"Set Object Name",
+	"Set Light Parameter",
+	"Set Mesh Parameter",
+	"Set Sprite Parameter",
+	"Set Camera Parameter",
+	"Set Collider Parameter",
+	"Set Actor Parameter",
+	"Set Sound Parameter"
+};
 
-	public const string ActionNames[] =
-	{
-		"Spawn Unit",
-		"Destroy Unit",
-		"Spawn Sound",
-		"Destroy Sound",
-		"Move Objects",
-		"Duplicate Objects",
-		"Set Object Name",
-		"Set Light Parameter",
-		"Set Mesh Parameter",
-		"Set Sprite Parameter",
-		"Set Camera Parameter",
-		"Set Collider Parameter",
-		"Set Actor Parameter",
-		"Set Sound Parameter"
-	};
 }

+ 33 - 32
tools/level_editor/data_compiler.vala

@@ -5,42 +5,43 @@
 
 namespace Crown
 {
-	public class DataCompiler
-	{
-		private ConsoleClient _compiler;
-		private Guid _id;
-		private bool _success;
-		private SourceFunc _callback;
+public class DataCompiler
+{
+	private ConsoleClient _compiler;
+	private Guid _id;
+	private bool _success;
+	private SourceFunc _callback;
 
-		public DataCompiler(ConsoleClient client)
-		{
-			_compiler = client;
-			_id = GUID_ZERO;
-			_success = false;
-			_callback = null;
-		}
+	public DataCompiler(ConsoleClient client)
+	{
+		_compiler = client;
+		_id = GUID_ZERO;
+		_success = false;
+		_callback = null;
+	}
 
-		// Returns true if success, false otherwise.
-		public async bool compile(string data_dir, string platform)
-		{
-			if (_callback != null)
-				return false;
+	// Returns true if success, false otherwise.
+	public async bool compile(string data_dir, string platform)
+	{
+		if (_callback != null)
+			return false;
 
-			_id = Guid.new_guid();
-			_success = false;
-			_compiler.send(DataCompilerApi.compile(_id, data_dir, platform));
-			_callback = compile.callback;
-			yield;
+		_id = Guid.new_guid();
+		_success = false;
+		_compiler.send(DataCompilerApi.compile(_id, data_dir, platform));
+		_callback = compile.callback;
+		yield;
 
-			return _success;
-		}
+		return _success;
+	}
 
-		public void finished(bool success)
-		{
-			_success = success;
-			if (_callback != null)
-				_callback();
-			_callback = null;
-		}
+	public void finished(bool success)
+	{
+		_success = success;
+		if (_callback != null)
+			_callback();
+		_callback = null;
 	}
 }
+
+}

+ 19 - 18
tools/level_editor/dialog_level_changed.vala

@@ -7,26 +7,27 @@ using Gtk;
 
 namespace Crown
 {
-	public class DialogLevelChanged : Gtk.MessageDialog
+public class DialogLevelChanged : Gtk.MessageDialog
+{
+	public DialogLevelChanged(Gtk.Window? parent)
 	{
-		public DialogLevelChanged(Gtk.Window? parent)
-		{
-			Object(text: "Save changes to Level before closing?"
-				, message_type: Gtk.MessageType.WARNING
-				, modal: true
-				);
+		Object(text: "Save changes to Level before closing?"
+			, message_type: Gtk.MessageType.WARNING
+			, modal: true
+			);
 
-			add_buttons("Close _without Saving"
-				, ResponseType.NO
-				, "_Cancel"
-				, ResponseType.CANCEL
-				, "_Save"
-				, ResponseType.YES
-				);
-			set_default_response(ResponseType.YES);
+		add_buttons("Close _without Saving"
+			, ResponseType.NO
+			, "_Cancel"
+			, ResponseType.CANCEL
+			, "_Save"
+			, ResponseType.YES
+			);
+		set_default_response(ResponseType.YES);
 
-			if (parent != null)
-				this.set_transient_for(parent);
-		}
+		if (parent != null)
+			this.set_transient_for(parent);
 	}
 }
+
+}

+ 16 - 15
tools/level_editor/dialog_open_project.vala

@@ -7,23 +7,24 @@ using Gtk;
 
 namespace Crown
 {
-	public class DialogOpenProject : Gtk.FileChooserDialog
+public class DialogOpenProject : Gtk.FileChooserDialog
+{
+	public DialogOpenProject(Gtk.Window? parent)
 	{
-		public DialogOpenProject(Gtk.Window? parent)
-		{
-			Object(title: "Open Project..."
-				, modal: true
-				, action: FileChooserAction.SELECT_FOLDER
-				);
+		Object(title: "Open Project..."
+			, modal: true
+			, action: FileChooserAction.SELECT_FOLDER
+			);
 
-			add_buttons("Cancel"
-				, ResponseType.CANCEL
-				, "Open"
-				, ResponseType.ACCEPT
-				);
+		add_buttons("Cancel"
+			, ResponseType.CANCEL
+			, "Open"
+			, ResponseType.ACCEPT
+			);
 
-			if (parent != null)
-				this.set_transient_for(parent);
-		}
+		if (parent != null)
+			this.set_transient_for(parent);
 	}
 }
+
+}

+ 218 - 217
tools/level_editor/editor_view.vala

@@ -13,279 +13,280 @@ extern uint gdk_win32_window_get_handle (Gdk.Window window);
 
 namespace Crown
 {
-	public class EditorView : Gtk.Alignment
-	{
-		// Data
-		private ConsoleClient _client;
+public class EditorView : Gtk.Alignment
+{
+	// Data
+	private ConsoleClient _client;
 
-		private int _mouse_curr_x;
-		private int _mouse_curr_y;
+	private int _mouse_curr_x;
+	private int _mouse_curr_y;
 
-		private bool _mouse_left;
-		private bool _mouse_middle;
-		private bool _mouse_right;
+	private bool _mouse_left;
+	private bool _mouse_middle;
+	private bool _mouse_right;
 
-		private uint _window_id;
+	private uint _window_id;
 
-		public uint window_id
-		{
-			get { return (uint)_window_id; }
-		}
+	public uint window_id
+	{
+		get { return (uint)_window_id; }
+	}
 
-		private HashMap<uint, bool> _keys;
+	private HashMap<uint, bool> _keys;
 
-		// Widgets
+	// Widgets
 #if CROWN_PLATFORM_LINUX
-		private Gtk.Socket _socket;
+	private Gtk.Socket _socket;
 #endif
-		public Gtk.EventBox _event_box;
+	public Gtk.EventBox _event_box;
 
-		// Signals
-		public signal void realized();
+	// Signals
+	public signal void realized();
 
-		private string key_to_string(uint k)
+	private string key_to_string(uint k)
+	{
+		switch (k)
 		{
-			switch (k)
-			{
-			case Gdk.Key.w: return "w";
-			case Gdk.Key.a: return "a";
-			case Gdk.Key.s: return "s";
-			case Gdk.Key.d: return "d";
-			default:        return "<unknown>";
-			}
+		case Gdk.Key.w: return "w";
+		case Gdk.Key.a: return "a";
+		case Gdk.Key.s: return "s";
+		case Gdk.Key.d: return "d";
+		default:        return "<unknown>";
 		}
+	}
 
-		private bool camera_modifier_pressed()
-		{
-			return _keys[Gdk.Key.Alt_L]
-				|| _keys[Gdk.Key.Alt_R]
-				;
-		}
+	private bool camera_modifier_pressed()
+	{
+		return _keys[Gdk.Key.Alt_L]
+			|| _keys[Gdk.Key.Alt_R]
+			;
+	}
 
-		private void camera_modifier_reset()
-		{
-			_keys[Gdk.Key.Alt_L] = false;
-			_keys[Gdk.Key.Alt_R] = false;
-		}
+	private void camera_modifier_reset()
+	{
+		_keys[Gdk.Key.Alt_L] = false;
+		_keys[Gdk.Key.Alt_R] = false;
+	}
 
-		public EditorView(ConsoleClient client, bool input_enabled = true)
-		{
-			this.xalign = 0;
-			this.yalign = 0;
-			this.xscale = 1;
-			this.yscale = 1;
+	public EditorView(ConsoleClient client, bool input_enabled = true)
+	{
+		this.xalign = 0;
+		this.yalign = 0;
+		this.xscale = 1;
+		this.yscale = 1;
 
-			_client = client;
+		_client = client;
 
-			_mouse_curr_x = 0;
-			_mouse_curr_y = 0;
+		_mouse_curr_x = 0;
+		_mouse_curr_y = 0;
 
-			_mouse_left   = false;
-			_mouse_middle = false;
-			_mouse_right  = false;
+		_mouse_left   = false;
+		_mouse_middle = false;
+		_mouse_right  = false;
 
-			_window_id = 0;
+		_window_id = 0;
 
-			_keys = new HashMap<uint, bool>();
-			_keys[Gdk.Key.w] = false;
-			_keys[Gdk.Key.a] = false;
-			_keys[Gdk.Key.s] = false;
-			_keys[Gdk.Key.d] = false;
-			_keys[Gdk.Key.Alt_L] = false;
-			_keys[Gdk.Key.Alt_R] = false;
+		_keys = new HashMap<uint, bool>();
+		_keys[Gdk.Key.w] = false;
+		_keys[Gdk.Key.a] = false;
+		_keys[Gdk.Key.s] = false;
+		_keys[Gdk.Key.d] = false;
+		_keys[Gdk.Key.Alt_L] = false;
+		_keys[Gdk.Key.Alt_R] = false;
 
-			// Widgets
+		// Widgets
 #if CROWN_PLATFORM_LINUX
-			_socket = new Gtk.Socket();
-			_socket.set_visual(Gdk.Screen.get_default().get_system_visual());
-			_socket.realize.connect(on_socket_realized);
-			_socket.plug_removed.connect(on_socket_plug_removed);
-			_socket.set_size_request(128, 128);
+		_socket = new Gtk.Socket();
+		_socket.set_visual(Gdk.Screen.get_default().get_system_visual());
+		_socket.realize.connect(on_socket_realized);
+		_socket.plug_removed.connect(on_socket_plug_removed);
+		_socket.set_size_request(128, 128);
 #endif
-			_event_box = new Gtk.EventBox();
-			_event_box.can_focus = true;
-			_event_box.events |= Gdk.EventMask.POINTER_MOTION_MASK
-				| Gdk.EventMask.KEY_PRESS_MASK
-				| Gdk.EventMask.KEY_RELEASE_MASK
-				| Gdk.EventMask.FOCUS_CHANGE_MASK
-				| Gdk.EventMask.SCROLL_MASK
-				;
-			_event_box.focus_out_event.connect(on_event_box_focus_out_event);
+		_event_box = new Gtk.EventBox();
+		_event_box.can_focus = true;
+		_event_box.events |= Gdk.EventMask.POINTER_MOTION_MASK
+			| Gdk.EventMask.KEY_PRESS_MASK
+			| Gdk.EventMask.KEY_RELEASE_MASK
+			| Gdk.EventMask.FOCUS_CHANGE_MASK
+			| Gdk.EventMask.SCROLL_MASK
+			;
+		_event_box.focus_out_event.connect(on_event_box_focus_out_event);
 #if CROWN_PLATFORM_WINDOWS
-			_event_box.realize.connect(on_event_box_realized);
-			_event_box.size_allocate.connect(on_size_allocate);
+		_event_box.realize.connect(on_event_box_realized);
+		_event_box.size_allocate.connect(on_size_allocate);
 #endif
 
-			if (input_enabled)
-			{
-				_event_box.button_release_event.connect(on_button_release);
-				_event_box.button_press_event.connect(on_button_press);
-				_event_box.key_press_event.connect(on_key_press);
-				_event_box.key_release_event.connect(on_key_release);
-				_event_box.motion_notify_event.connect(on_motion_notify);
-				_event_box.scroll_event.connect(on_scroll);
-			}
+		if (input_enabled)
+		{
+			_event_box.button_release_event.connect(on_button_release);
+			_event_box.button_press_event.connect(on_button_press);
+			_event_box.key_press_event.connect(on_key_press);
+			_event_box.key_release_event.connect(on_key_release);
+			_event_box.motion_notify_event.connect(on_motion_notify);
+			_event_box.scroll_event.connect(on_scroll);
+		}
 
 #if CROWN_PLATFORM_LINUX
-			_event_box.add(_socket);
+		_event_box.add(_socket);
 #endif
-			add(_event_box);
-		}
+		add(_event_box);
+	}
 
-		private bool on_button_release(Gdk.EventButton ev)
+	private bool on_button_release(Gdk.EventButton ev)
+	{
+		_mouse_left   = ev.button == Gdk.BUTTON_PRIMARY   ? false : _mouse_left;
+		_mouse_middle = ev.button == Gdk.BUTTON_MIDDLE    ? false : _mouse_middle;
+		_mouse_right  = ev.button == Gdk.BUTTON_SECONDARY ? false : _mouse_right;
+
+		string s = LevelEditorApi.set_mouse_state(_mouse_curr_x
+			, _mouse_curr_y
+			, _mouse_left
+			, _mouse_middle
+			, _mouse_right
+			);
+
+		if (camera_modifier_pressed())
 		{
-			_mouse_left   = ev.button == Gdk.BUTTON_PRIMARY   ? false : _mouse_left;
-			_mouse_middle = ev.button == Gdk.BUTTON_MIDDLE    ? false : _mouse_middle;
-			_mouse_right  = ev.button == Gdk.BUTTON_SECONDARY ? false : _mouse_right;
-
-			string s = LevelEditorApi.set_mouse_state(_mouse_curr_x
-				, _mouse_curr_y
-				, _mouse_left
-				, _mouse_middle
-				, _mouse_right
-				);
-
-			if (camera_modifier_pressed())
-			{
-				if (!_mouse_left || !_mouse_middle || !_mouse_right)
-					s += "LevelEditor:camera_drag_start('idle')";
-			}
-			else
-			{
-				if (ev.button == Gdk.BUTTON_PRIMARY)
-					s += LevelEditorApi.mouse_up((int)ev.x, (int)ev.y);
-			}
-
-			_client.send_script(s);
-			return Gdk.EVENT_PROPAGATE;
+			if (!_mouse_left || !_mouse_middle || !_mouse_right)
+				s += "LevelEditor:camera_drag_start('idle')";
 		}
-
-		private bool on_button_press(Gdk.EventButton ev)
+		else
 		{
-			// Grab keyboard focus
-			_event_box.grab_focus();
-
-			_mouse_left   = ev.button == Gdk.BUTTON_PRIMARY   ? true : _mouse_left;
-			_mouse_middle = ev.button == Gdk.BUTTON_MIDDLE    ? true : _mouse_middle;
-			_mouse_right  = ev.button == Gdk.BUTTON_SECONDARY ? true : _mouse_right;
-
-			string s = LevelEditorApi.set_mouse_state(_mouse_curr_x
-				, _mouse_curr_y
-				, _mouse_left
-				, _mouse_middle
-				, _mouse_right
-				);
-
-			if (camera_modifier_pressed())
-			{
-				if (_mouse_left)
-					s += "LevelEditor:camera_drag_start('tumble')";
-				if (_mouse_middle)
-					s += "LevelEditor:camera_drag_start('track')";
-				if (_mouse_right)
-					s += "LevelEditor:camera_drag_start('dolly')";
-			}
-			else
-			{
-				if (ev.button == Gdk.BUTTON_PRIMARY)
-					s += LevelEditorApi.mouse_down((int)ev.x, (int)ev.y);
-			}
-
-			_client.send_script(s);
-			return Gdk.EVENT_PROPAGATE;
+			if (ev.button == Gdk.BUTTON_PRIMARY)
+				s += LevelEditorApi.mouse_up((int)ev.x, (int)ev.y);
 		}
 
-		private bool on_key_press(Gdk.EventKey ev)
-		{
-			if (ev.keyval == Gdk.Key.Up)
-				_client.send_script("LevelEditor:key_down(\"move_up\")");
-			if (ev.keyval == Gdk.Key.Down)
-				_client.send_script("LevelEditor:key_down(\"move_down\")");
-			if (ev.keyval == Gdk.Key.Right)
-				_client.send_script("LevelEditor:key_down(\"move_right\")");
-			if (ev.keyval == Gdk.Key.Left)
-				_client.send_script("LevelEditor:key_down(\"move_left\")");
+		_client.send_script(s);
+		return Gdk.EVENT_PROPAGATE;
+	}
 
-			if (!_keys.has_key(ev.keyval))
-				return Gdk.EVENT_STOP;
+	private bool on_button_press(Gdk.EventButton ev)
+	{
+		// Grab keyboard focus
+		_event_box.grab_focus();
 
-			if (!_keys[ev.keyval])
-				_client.send_script(LevelEditorApi.key_down(key_to_string(ev.keyval)));
+		_mouse_left   = ev.button == Gdk.BUTTON_PRIMARY   ? true : _mouse_left;
+		_mouse_middle = ev.button == Gdk.BUTTON_MIDDLE    ? true : _mouse_middle;
+		_mouse_right  = ev.button == Gdk.BUTTON_SECONDARY ? true : _mouse_right;
 
-			_keys[ev.keyval] = true;
+		string s = LevelEditorApi.set_mouse_state(_mouse_curr_x
+			, _mouse_curr_y
+			, _mouse_left
+			, _mouse_middle
+			, _mouse_right
+			);
 
-			return Gdk.EVENT_PROPAGATE;
+		if (camera_modifier_pressed())
+		{
+			if (_mouse_left)
+				s += "LevelEditor:camera_drag_start('tumble')";
+			if (_mouse_middle)
+				s += "LevelEditor:camera_drag_start('track')";
+			if (_mouse_right)
+				s += "LevelEditor:camera_drag_start('dolly')";
 		}
-
-		private bool on_key_release(Gdk.EventKey ev)
+		else
 		{
-			if ((ev.keyval == Gdk.Key.Alt_L || ev.keyval == Gdk.Key.Alt_R))
-				_client.send_script("LevelEditor:camera_drag_start('idle')");
+			if (ev.button == Gdk.BUTTON_PRIMARY)
+				s += LevelEditorApi.mouse_down((int)ev.x, (int)ev.y);
+		}
 
-			if (!_keys.has_key(ev.keyval))
-				return Gdk.EVENT_PROPAGATE;
+		_client.send_script(s);
+		return Gdk.EVENT_PROPAGATE;
+	}
 
-			if (_keys[ev.keyval])
-				_client.send_script(LevelEditorApi.key_up(key_to_string(ev.keyval)));
+	private bool on_key_press(Gdk.EventKey ev)
+	{
+		if (ev.keyval == Gdk.Key.Up)
+			_client.send_script("LevelEditor:key_down(\"move_up\")");
+		if (ev.keyval == Gdk.Key.Down)
+			_client.send_script("LevelEditor:key_down(\"move_down\")");
+		if (ev.keyval == Gdk.Key.Right)
+			_client.send_script("LevelEditor:key_down(\"move_right\")");
+		if (ev.keyval == Gdk.Key.Left)
+			_client.send_script("LevelEditor:key_down(\"move_left\")");
+
+		if (!_keys.has_key(ev.keyval))
+			return Gdk.EVENT_STOP;
 
-			_keys[ev.keyval] = false;
+		if (!_keys[ev.keyval])
+			_client.send_script(LevelEditorApi.key_down(key_to_string(ev.keyval)));
 
-			return Gdk.EVENT_PROPAGATE;
-		}
+		_keys[ev.keyval] = true;
 
-		private bool on_motion_notify(Gdk.EventMotion ev)
-		{
-			_mouse_curr_x = (int)ev.x;
-			_mouse_curr_y = (int)ev.y;
+		return Gdk.EVENT_PROPAGATE;
+	}
 
-			_client.send_script(LevelEditorApi.set_mouse_state(_mouse_curr_x
-				, _mouse_curr_y
-				, _mouse_left
-				, _mouse_middle
-				, _mouse_right
-				));
+	private bool on_key_release(Gdk.EventKey ev)
+	{
+		if ((ev.keyval == Gdk.Key.Alt_L || ev.keyval == Gdk.Key.Alt_R))
+			_client.send_script("LevelEditor:camera_drag_start('idle')");
 
+		if (!_keys.has_key(ev.keyval))
 			return Gdk.EVENT_PROPAGATE;
-		}
 
-		private bool on_scroll(Gdk.EventScroll ev)
-		{
-			_client.send_script(LevelEditorApi.mouse_wheel(ev.direction == Gdk.ScrollDirection.UP ? 1.0 : -1.0));
-			return Gdk.EVENT_PROPAGATE;
-		}
+		if (_keys[ev.keyval])
+			_client.send_script(LevelEditorApi.key_up(key_to_string(ev.keyval)));
 
-		private bool on_event_box_focus_out_event(Gdk.EventFocus ev)
-		{
-			camera_modifier_reset();
-			return Gdk.EVENT_PROPAGATE;
-		}
+		_keys[ev.keyval] = false;
+
+		return Gdk.EVENT_PROPAGATE;
+	}
+
+	private bool on_motion_notify(Gdk.EventMotion ev)
+	{
+		_mouse_curr_x = (int)ev.x;
+		_mouse_curr_y = (int)ev.y;
+
+		_client.send_script(LevelEditorApi.set_mouse_state(_mouse_curr_x
+			, _mouse_curr_y
+			, _mouse_left
+			, _mouse_middle
+			, _mouse_right
+			));
+
+		return Gdk.EVENT_PROPAGATE;
+	}
+
+	private bool on_scroll(Gdk.EventScroll ev)
+	{
+		_client.send_script(LevelEditorApi.mouse_wheel(ev.direction == Gdk.ScrollDirection.UP ? 1.0 : -1.0));
+		return Gdk.EVENT_PROPAGATE;
+	}
+
+	private bool on_event_box_focus_out_event(Gdk.EventFocus ev)
+	{
+		camera_modifier_reset();
+		return Gdk.EVENT_PROPAGATE;
+	}
 
 #if CROWN_PLATFORM_LINUX
-		private void on_socket_realized()
-		{
-			// We do not have window XID until socket is realized...
-			_window_id = (uint)_socket.get_id();
-			realized();
-		}
+	private void on_socket_realized()
+	{
+		// We do not have window XID until socket is realized...
+		_window_id = (uint)_socket.get_id();
+		realized();
+	}
 
-		private bool on_socket_plug_removed()
-		{
-			// Prevent the default handler from destroying the Socket.
-			return Gdk.EVENT_STOP;
-		}
+	private bool on_socket_plug_removed()
+	{
+		// Prevent the default handler from destroying the Socket.
+		return Gdk.EVENT_STOP;
+	}
 
 #elif CROWN_PLATFORM_WINDOWS
-		private void on_event_box_realized()
-		{
-			_event_box.get_window().ensure_native();
-			_window_id = gdk_win32_window_get_handle(_event_box.get_window());
-			realized();
-		}
+	private void on_event_box_realized()
+	{
+		_event_box.get_window().ensure_native();
+		_window_id = gdk_win32_window_get_handle(_event_box.get_window());
+		realized();
+	}
 
-		private void on_size_allocate(Gtk.Allocation ev)
-		{
-			_client.send(DeviceApi.resize(ev.width, ev.height));
-		}
-#endif
+	private void on_size_allocate(Gtk.Allocation ev)
+	{
+		_client.send(DeviceApi.resize(ev.width, ev.height));
 	}
+#endif
+}
+
 }

+ 655 - 654
tools/level_editor/level.vala

@@ -7,161 +7,143 @@ using Gee;
 
 namespace Crown
 {
-	/// Manages objects in a level.
-	public class Level
-	{
-		public Project _project;
+/// Manages objects in a level.
+public class Level
+{
+	public Project _project;
 
-		// Engine connections
-		public ConsoleClient _client;
+	// Engine connections
+	public ConsoleClient _client;
 
-		// Data
-		public Database _db;
-		public Database _prefabs;
-		public Gee.HashSet<string> _loaded_prefabs;
-		public Gee.ArrayList<Guid?> _selection;
+	// Data
+	public Database _db;
+	public Database _prefabs;
+	public Gee.HashSet<string> _loaded_prefabs;
+	public Gee.ArrayList<Guid?> _selection;
 
-		public uint _num_units;
-		public uint _num_sounds;
+	public uint _num_units;
+	public uint _num_sounds;
 
-		public string _filename;
+	public string _filename;
 
-		// Signals
-		public signal void selection_changed(Gee.ArrayList<Guid?> selection);
-		public signal void object_editor_name_changed(Guid object_id, string name);
+	// Signals
+	public signal void selection_changed(Gee.ArrayList<Guid?> selection);
+	public signal void object_editor_name_changed(Guid object_id, string name);
 
-		public Level(Database db, ConsoleClient client, Project project)
-		{
-			_project = project;
+	public Level(Database db, ConsoleClient client, Project project)
+	{
+		_project = project;
 
-			// Engine connections
-			_client = client;
+		// Engine connections
+		_client = client;
 
-			// Data
-			_db = db;
-			_db.undo_redo.connect(undo_redo_action);
+		// Data
+		_db = db;
+		_db.undo_redo.connect(undo_redo_action);
 
-			_prefabs = new Database();
-			_loaded_prefabs = new Gee.HashSet<string>();
-			_selection = new Gee.ArrayList<Guid?>();
+		_prefabs = new Database();
+		_loaded_prefabs = new Gee.HashSet<string>();
+		_selection = new Gee.ArrayList<Guid?>();
 
-			reset();
-		}
+		reset();
+	}
 
-		/// Resets the level
-		public void reset()
-		{
-			_db.reset();
-			_prefabs.reset();
-			_loaded_prefabs.clear();
+	/// Resets the level
+	public void reset()
+	{
+		_db.reset();
+		_prefabs.reset();
+		_loaded_prefabs.clear();
 
-			_selection.clear();
-			selection_changed(_selection);
+		_selection.clear();
+		selection_changed(_selection);
 
-			_num_units = 0;
-			_num_sounds = 0;
+		_num_units = 0;
+		_num_sounds = 0;
 
-			_filename = null;
-		}
+		_filename = null;
+	}
 
-		/// Loads the level from @a path.
-		public void load(string path)
-		{
-			reset();
-			_db.load(path);
+	/// Loads the level from @a path.
+	public void load(string path)
+	{
+		reset();
+		_db.load(path);
 
-			_filename = path;
-		}
+		_filename = path;
+	}
 
-		/// Saves the level to @a path.
-		public void save(string path)
-		{
-			_db.save(path);
+	/// Saves the level to @a path.
+	public void save(string path)
+	{
+		_db.save(path);
 
-			_filename = path;
-		}
+		_filename = path;
+	}
 
-		/// Loads the empty level template.
-		public void load_empty_level()
-		{
-			load(Path.build_filename(_project.toolchain_dir(), "core/editors/levels/empty.level"));
+	/// Loads the empty level template.
+	public void load_empty_level()
+	{
+		load(Path.build_filename(_project.toolchain_dir(), "core/editors/levels/empty.level"));
 
-			_filename = null;
-		}
+		_filename = null;
+	}
+
+	public void spawn_unit(Guid id, string name, Vector3 pos, Quaternion rot, Vector3 scl)
+	{
+		on_unit_spawned(id, name, pos, rot, scl);
+		send_spawn_units(new Guid[] { id });
+	}
+
+	public void destroy_objects(Guid[] ids)
+	{
+		Guid[] units = {};
+		Guid[] sounds = {};
 
-		public void spawn_unit(Guid id, string name, Vector3 pos, Quaternion rot, Vector3 scl)
+		foreach (Guid id in ids)
 		{
-			on_unit_spawned(id, name, pos, rot, scl);
-			send_spawn_units(new Guid[] { id });
+			if (is_unit(id))
+				units += id;
+			else if (is_sound(id))
+				sounds += id;
 		}
 
-		public void destroy_objects(Guid[] ids)
+		if (units.length > 0)
 		{
-			Guid[] units = {};
-			Guid[] sounds = {};
-
-			foreach (Guid id in ids)
-			{
-				if (is_unit(id))
-					units += id;
-				else if (is_sound(id))
-					sounds += id;
-			}
-
-			if (units.length > 0)
-			{
-				_db.add_restore_point((int)ActionType.DESTROY_UNIT, units);
-				foreach (Guid id in units)
-				{
-					_db.remove_from_set(GUID_ZERO, "units", id);
-					_db.destroy(id);
-				}
-			}
-
-			if (sounds.length > 0)
+			_db.add_restore_point((int)ActionType.DESTROY_UNIT, units);
+			foreach (Guid id in units)
 			{
-				_db.add_restore_point((int)ActionType.DESTROY_SOUND, sounds);
-				foreach (Guid id in sounds)
-				{
-					_db.remove_from_set(GUID_ZERO, "sounds", id);
-					_db.destroy(id);
-				}
+				_db.remove_from_set(GUID_ZERO, "units", id);
+				_db.destroy(id);
 			}
-
-			send_destroy_objects(ids);
 		}
 
-		public void move_selected_objects(Vector3 pos, Quaternion rot, Vector3 scl)
+		if (sounds.length > 0)
 		{
-			if (_selection.size == 0)
-				return;
-
-			Guid id = _selection.last();
-			on_move_objects(new Guid[] { id }, new Vector3[] { pos }, new Quaternion[] { rot }, new Vector3[] { scl });
-			send_move_objects(new Guid[] { id }, new Vector3[] { pos }, new Quaternion[] { rot }, new Vector3[] { scl });
+			_db.add_restore_point((int)ActionType.DESTROY_SOUND, sounds);
+			foreach (Guid id in sounds)
+			{
+				_db.remove_from_set(GUID_ZERO, "sounds", id);
+				_db.destroy(id);
+			}
 		}
 
-		public void duplicate_selected_objects()
-		{
-			if (_selection.size > 0)
-			{
-				Guid[] ids = new Guid[_selection.size];
-				// FIXME
-				{
-					Guid?[] tmp = _selection.to_array();
-					for (int i = 0; i < tmp.length; ++i)
-						ids[i] = tmp[i];
-				}
-				Guid[] new_ids = new Guid[ids.length];
+		send_destroy_objects(ids);
+	}
 
-				for (int i = 0; i < new_ids.length; ++i)
-					new_ids[i] = Guid.new_guid();
+	public void move_selected_objects(Vector3 pos, Quaternion rot, Vector3 scl)
+	{
+		if (_selection.size == 0)
+			return;
 
-				duplicate_objects(ids, new_ids);
-			}
-		}
+		Guid id = _selection.last();
+		on_move_objects(new Guid[] { id }, new Vector3[] { pos }, new Quaternion[] { rot }, new Vector3[] { scl });
+		send_move_objects(new Guid[] { id }, new Vector3[] { pos }, new Quaternion[] { rot }, new Vector3[] { scl });
+	}
 
-		public void destroy_selected_objects()
+	public void duplicate_selected_objects()
+	{
+		if (_selection.size > 0)
 		{
 			Guid[] ids = new Guid[_selection.size];
 			// FIXME
@@ -170,659 +152,678 @@ namespace Crown
 				for (int i = 0; i < tmp.length; ++i)
 					ids[i] = tmp[i];
 			}
-			_selection.clear();
+			Guid[] new_ids = new Guid[ids.length];
+
+			for (int i = 0; i < new_ids.length; ++i)
+				new_ids[i] = Guid.new_guid();
 
-			destroy_objects(ids);
+			duplicate_objects(ids, new_ids);
 		}
+	}
 
-		public void duplicate_objects(Guid[] ids, Guid[] new_ids)
+	public void destroy_selected_objects()
+	{
+		Guid[] ids = new Guid[_selection.size];
+		// FIXME
 		{
-			_db.add_restore_point((int)ActionType.DUPLICATE_OBJECTS, new_ids);
-			for (int i = 0; i < ids.length; ++i)
-			{
-				_db.duplicate(ids[i], new_ids[i]);
-
-				if (is_unit(ids[i]))
-				{
-					_db.add_to_set(GUID_ZERO, "units", new_ids[i]);
-				}
-				else if (is_sound(ids[i]))
-				{
-					_db.add_to_set(GUID_ZERO, "sounds", new_ids[i]);
-				}
-			}
-			send_spawn_objects(new_ids);
+			Guid?[] tmp = _selection.to_array();
+			for (int i = 0; i < tmp.length; ++i)
+				ids[i] = tmp[i];
 		}
+		_selection.clear();
 
-		public void on_unit_spawned(Guid id, string name, Vector3 pos, Quaternion rot, Vector3 scl)
-		{
-			load_prefab(name);
+		destroy_objects(ids);
+	}
 
-			_db.add_restore_point((int)ActionType.SPAWN_UNIT, new Guid[] { id });
-			_db.create(id);
-			_db.set_property_string(id, "editor.name", "unit_%04u".printf(_num_units++));
-			_db.set_property_string(id, "prefab", name);
+	public void duplicate_objects(Guid[] ids, Guid[] new_ids)
+	{
+		_db.add_restore_point((int)ActionType.DUPLICATE_OBJECTS, new_ids);
+		for (int i = 0; i < ids.length; ++i)
+		{
+			_db.duplicate(ids[i], new_ids[i]);
 
-			Guid transform_id = GUID_ZERO;
-			Unit unit = new Unit(_db, id, _prefabs);
-			if (unit.has_component("transform", ref transform_id))
+			if (is_unit(ids[i]))
 			{
-				unit.set_component_property_vector3   (transform_id, "data.position", pos);
-				unit.set_component_property_quaternion(transform_id, "data.rotation", rot);
-				unit.set_component_property_vector3   (transform_id, "data.scale", scl);
-				unit.set_component_property_string    (transform_id, "type", "transform");
+				_db.add_to_set(GUID_ZERO, "units", new_ids[i]);
 			}
-			else
+			else if (is_sound(ids[i]))
 			{
-				_db.set_property_vector3   (id, "position", pos);
-				_db.set_property_quaternion(id, "rotation", rot);
-				_db.set_property_vector3   (id, "scale", scl);
+				_db.add_to_set(GUID_ZERO, "sounds", new_ids[i]);
 			}
-			_db.add_to_set(GUID_ZERO, "units", id);
 		}
+		send_spawn_objects(new_ids);
+	}
+
+	public void on_unit_spawned(Guid id, string name, Vector3 pos, Quaternion rot, Vector3 scl)
+	{
+		load_prefab(name);
 
-		public void on_sound_spawned(Guid id, string name, Vector3 pos, Quaternion rot, Vector3 scl, double range, double volume, bool loop)
+		_db.add_restore_point((int)ActionType.SPAWN_UNIT, new Guid[] { id });
+		_db.create(id);
+		_db.set_property_string(id, "editor.name", "unit_%04u".printf(_num_units++));
+		_db.set_property_string(id, "prefab", name);
+
+		Guid transform_id = GUID_ZERO;
+		Unit unit = new Unit(_db, id, _prefabs);
+		if (unit.has_component("transform", ref transform_id))
+		{
+			unit.set_component_property_vector3   (transform_id, "data.position", pos);
+			unit.set_component_property_quaternion(transform_id, "data.rotation", rot);
+			unit.set_component_property_vector3   (transform_id, "data.scale", scl);
+			unit.set_component_property_string    (transform_id, "type", "transform");
+		}
+		else
 		{
-			_db.add_restore_point((int)ActionType.SPAWN_SOUND, new Guid[] { id });
-			_db.create(id);
-			_db.set_property_string    (id, "editor.name", "sound_%04u".printf(_num_sounds++));
 			_db.set_property_vector3   (id, "position", pos);
 			_db.set_property_quaternion(id, "rotation", rot);
-			_db.set_property_string    (id, "name", name);
-			_db.set_property_double    (id, "range", range);
-			_db.set_property_double    (id, "volume", volume);
-			_db.set_property_bool      (id, "loop", loop);
-			_db.add_to_set(GUID_ZERO, "sounds", id);
+			_db.set_property_vector3   (id, "scale", scl);
 		}
+		_db.add_to_set(GUID_ZERO, "units", id);
+	}
 
-		public void on_move_objects(Guid[] ids, Vector3[] positions, Quaternion[] rotations, Vector3[] scales)
+	public void on_sound_spawned(Guid id, string name, Vector3 pos, Quaternion rot, Vector3 scl, double range, double volume, bool loop)
+	{
+		_db.add_restore_point((int)ActionType.SPAWN_SOUND, new Guid[] { id });
+		_db.create(id);
+		_db.set_property_string    (id, "editor.name", "sound_%04u".printf(_num_sounds++));
+		_db.set_property_vector3   (id, "position", pos);
+		_db.set_property_quaternion(id, "rotation", rot);
+		_db.set_property_string    (id, "name", name);
+		_db.set_property_double    (id, "range", range);
+		_db.set_property_double    (id, "volume", volume);
+		_db.set_property_bool      (id, "loop", loop);
+		_db.add_to_set(GUID_ZERO, "sounds", id);
+	}
+
+	public void on_move_objects(Guid[] ids, Vector3[] positions, Quaternion[] rotations, Vector3[] scales)
+	{
+		_db.add_restore_point((int)ActionType.MOVE_OBJECTS, ids);
+
+		for (int i = 0; i < ids.length; ++i)
 		{
-			_db.add_restore_point((int)ActionType.MOVE_OBJECTS, ids);
+			Guid id = ids[i];
+			Vector3 pos = positions[i];
+			Quaternion rot = rotations[i];
+			Vector3 scl = scales[i];
 
-			for (int i = 0; i < ids.length; ++i)
+			if (is_unit(id))
 			{
-				Guid id = ids[i];
-				Vector3 pos = positions[i];
-				Quaternion rot = rotations[i];
-				Vector3 scl = scales[i];
+				Guid transform_id = GUID_ZERO;
+				Unit unit = new Unit(_db, id, _prefabs);
 
-				if (is_unit(id))
+				if (unit.has_component("transform", ref transform_id))
 				{
-					Guid transform_id = GUID_ZERO;
-					Unit unit = new Unit(_db, id, _prefabs);
-
-					if (unit.has_component("transform", ref transform_id))
-					{
-						unit.set_component_property_vector3   (transform_id, "data.position", pos);
-						unit.set_component_property_quaternion(transform_id, "data.rotation", rot);
-						unit.set_component_property_vector3   (transform_id, "data.scale", scl);
-					}
-					else
-					{
-						_db.set_property_vector3   (id, "position", pos);
-						_db.set_property_quaternion(id, "rotation", rot);
-						_db.set_property_vector3   (id, "scale", scl);
-					}
+					unit.set_component_property_vector3   (transform_id, "data.position", pos);
+					unit.set_component_property_quaternion(transform_id, "data.rotation", rot);
+					unit.set_component_property_vector3   (transform_id, "data.scale", scl);
 				}
-				else if (is_sound(id))
+				else
 				{
 					_db.set_property_vector3   (id, "position", pos);
 					_db.set_property_quaternion(id, "rotation", rot);
+					_db.set_property_vector3   (id, "scale", scl);
 				}
 			}
-			// FIXME: Hack to force update the properties view
-			selection_changed(_selection);
+			else if (is_sound(id))
+			{
+				_db.set_property_vector3   (id, "position", pos);
+				_db.set_property_quaternion(id, "rotation", rot);
+			}
 		}
+		// FIXME: Hack to force update the properties view
+		selection_changed(_selection);
+	}
 
-		public void on_selection(Guid[] ids)
-		{
-			_selection.clear();
-			foreach (Guid id in ids)
-				_selection.add(id);
+	public void on_selection(Guid[] ids)
+	{
+		_selection.clear();
+		foreach (Guid id in ids)
+			_selection.add(id);
 
-			selection_changed(_selection);
-		}
+		selection_changed(_selection);
+	}
 
-		public void selection_set(Guid[] ids)
-		{
-			_selection.clear();
-			for (int i = 0; i < ids.length; ++i)
-				_selection.add(ids[i]);
-			_client.send_script(LevelEditorApi.selection_set(ids));
+	public void selection_set(Guid[] ids)
+	{
+		_selection.clear();
+		for (int i = 0; i < ids.length; ++i)
+			_selection.add(ids[i]);
+		_client.send_script(LevelEditorApi.selection_set(ids));
 
-			selection_changed(_selection);
-		}
+		selection_changed(_selection);
+	}
 
-		public void set_light(Guid unit_id, Guid component_id, string type, double range, double intensity, double spot_angle, Vector3 color)
-		{
-			_db.add_restore_point((int)ActionType.SET_LIGHT, new Guid[] { unit_id });
+	public void set_light(Guid unit_id, Guid component_id, string type, double range, double intensity, double spot_angle, Vector3 color)
+	{
+		_db.add_restore_point((int)ActionType.SET_LIGHT, new Guid[] { unit_id });
 
-			Unit unit = new Unit(_db, unit_id, _prefabs);
-			unit.set_component_property_string (component_id, "data.type",       type);
-			unit.set_component_property_double (component_id, "data.range",      range);
-			unit.set_component_property_double (component_id, "data.intensity",  intensity);
-			unit.set_component_property_double (component_id, "data.spot_angle", spot_angle);
-			unit.set_component_property_vector3(component_id, "data.color",      color);
-			unit.set_component_property_string (component_id, "type", "light");
-
-			_client.send_script(LevelEditorApi.set_light(unit_id, type, range, intensity, spot_angle, color));
-		}
+		Unit unit = new Unit(_db, unit_id, _prefabs);
+		unit.set_component_property_string (component_id, "data.type",       type);
+		unit.set_component_property_double (component_id, "data.range",      range);
+		unit.set_component_property_double (component_id, "data.intensity",  intensity);
+		unit.set_component_property_double (component_id, "data.spot_angle", spot_angle);
+		unit.set_component_property_vector3(component_id, "data.color",      color);
+		unit.set_component_property_string (component_id, "type", "light");
 
-		public void set_mesh(Guid unit_id, Guid component_id, string mesh_resource, string geometry, string material, bool visible)
-		{
-			_db.add_restore_point((int)ActionType.SET_MESH, new Guid[] { unit_id });
+		_client.send_script(LevelEditorApi.set_light(unit_id, type, range, intensity, spot_angle, color));
+	}
 
-			Unit unit = new Unit(_db, unit_id, _prefabs);
-			unit.set_component_property_string(component_id, "data.mesh_resource", mesh_resource);
-			unit.set_component_property_string(component_id, "data.geometry_name", geometry);
-			unit.set_component_property_string(component_id, "data.material", material);
-			unit.set_component_property_bool  (component_id, "data.visible", visible);
-			unit.set_component_property_string(component_id, "type", "mesh_renderer");
+	public void set_mesh(Guid unit_id, Guid component_id, string mesh_resource, string geometry, string material, bool visible)
+	{
+		_db.add_restore_point((int)ActionType.SET_MESH, new Guid[] { unit_id });
 
-			_client.send_script(LevelEditorApi.set_mesh(unit_id, 0 /*instance_id*/, material, visible));
-		}
+		Unit unit = new Unit(_db, unit_id, _prefabs);
+		unit.set_component_property_string(component_id, "data.mesh_resource", mesh_resource);
+		unit.set_component_property_string(component_id, "data.geometry_name", geometry);
+		unit.set_component_property_string(component_id, "data.material", material);
+		unit.set_component_property_bool  (component_id, "data.visible", visible);
+		unit.set_component_property_string(component_id, "type", "mesh_renderer");
 
-		public void set_sprite(Guid unit_id, Guid component_id, double layer, double depth, string material, string sprite_resource, bool visible)
-		{
-			_db.add_restore_point((int)ActionType.SET_SPRITE, new Guid[] { unit_id });
+		_client.send_script(LevelEditorApi.set_mesh(unit_id, 0 /*instance_id*/, material, visible));
+	}
 
-			Unit unit = new Unit(_db, unit_id, _prefabs);
-			unit.set_component_property_double(component_id, "data.layer", layer);
-			unit.set_component_property_double(component_id, "data.depth", depth);
-			unit.set_component_property_string(component_id, "data.material", material);
-			unit.set_component_property_string(component_id, "data.sprite_resource", sprite_resource);
-			unit.set_component_property_bool  (component_id, "data.visible", visible);
-			unit.set_component_property_string(component_id, "type", "sprite_renderer");
-
-			_client.send_script(LevelEditorApi.set_sprite(unit_id, material, layer, depth, visible));
-		}
+	public void set_sprite(Guid unit_id, Guid component_id, double layer, double depth, string material, string sprite_resource, bool visible)
+	{
+		_db.add_restore_point((int)ActionType.SET_SPRITE, new Guid[] { unit_id });
 
-		public void set_camera(Guid unit_id, Guid component_id, string projection, double fov, double near_range, double far_range)
-		{
-			_db.add_restore_point((int)ActionType.SET_CAMERA, new Guid[] { unit_id });
+		Unit unit = new Unit(_db, unit_id, _prefabs);
+		unit.set_component_property_double(component_id, "data.layer", layer);
+		unit.set_component_property_double(component_id, "data.depth", depth);
+		unit.set_component_property_string(component_id, "data.material", material);
+		unit.set_component_property_string(component_id, "data.sprite_resource", sprite_resource);
+		unit.set_component_property_bool  (component_id, "data.visible", visible);
+		unit.set_component_property_string(component_id, "type", "sprite_renderer");
 
-			Unit unit = new Unit(_db, unit_id, _prefabs);
-			unit.set_component_property_string(component_id, "data.projection", projection);
-			unit.set_component_property_double(component_id, "data.fov", fov);
-			unit.set_component_property_double(component_id, "data.near_range", near_range);
-			unit.set_component_property_double(component_id, "data.far_range", far_range);
-			unit.set_component_property_string(component_id, "type", "camera");
+		_client.send_script(LevelEditorApi.set_sprite(unit_id, material, layer, depth, visible));
+	}
 
-			_client.send_script(LevelEditorApi.set_camera(unit_id, projection, fov, near_range, far_range));
-		}
+	public void set_camera(Guid unit_id, Guid component_id, string projection, double fov, double near_range, double far_range)
+	{
+		_db.add_restore_point((int)ActionType.SET_CAMERA, new Guid[] { unit_id });
 
-		public void set_collider(Guid unit_id, Guid component_id, string shape, string scene, string name)
-		{
-			_db.add_restore_point((int)ActionType.SET_COLLIDER, new Guid[] { unit_id });
+		Unit unit = new Unit(_db, unit_id, _prefabs);
+		unit.set_component_property_string(component_id, "data.projection", projection);
+		unit.set_component_property_double(component_id, "data.fov", fov);
+		unit.set_component_property_double(component_id, "data.near_range", near_range);
+		unit.set_component_property_double(component_id, "data.far_range", far_range);
+		unit.set_component_property_string(component_id, "type", "camera");
 
-			Unit unit = new Unit(_db, unit_id, _prefabs);
-			unit.set_component_property_string(component_id, "data.shape", shape);
-			unit.set_component_property_string(component_id, "data.scene", scene);
-			unit.set_component_property_string(component_id, "data.name", name);
-			unit.set_component_property_string(component_id, "type", "collider");
+		_client.send_script(LevelEditorApi.set_camera(unit_id, projection, fov, near_range, far_range));
+	}
 
-			// No synchronization
-		}
+	public void set_collider(Guid unit_id, Guid component_id, string shape, string scene, string name)
+	{
+		_db.add_restore_point((int)ActionType.SET_COLLIDER, new Guid[] { unit_id });
 
-		public void set_actor(Guid unit_id, Guid component_id, string class, string collision_filter, string material, double mass)
-		{
-			_db.add_restore_point((int)ActionType.SET_ACTOR, new Guid[] { unit_id });
+		Unit unit = new Unit(_db, unit_id, _prefabs);
+		unit.set_component_property_string(component_id, "data.shape", shape);
+		unit.set_component_property_string(component_id, "data.scene", scene);
+		unit.set_component_property_string(component_id, "data.name", name);
+		unit.set_component_property_string(component_id, "type", "collider");
 
-			Unit unit = new Unit(_db, unit_id, _prefabs);
-			unit.set_component_property_string(component_id, "data.class", class);
-			unit.set_component_property_string(component_id, "data.collision_filter", collision_filter);
-			unit.set_component_property_string(component_id, "data.material", material);
-			unit.set_component_property_double(component_id, "data.mass", mass);
-			unit.set_component_property_bool  (component_id, "data.lock_rotation_x", (bool)unit.get_component_property_bool(component_id, "data.lock_rotation_x"));
-			unit.set_component_property_bool  (component_id, "data.lock_rotation_y", (bool)unit.get_component_property_bool(component_id, "data.lock_rotation_y"));
-			unit.set_component_property_bool  (component_id, "data.lock_rotation_z", (bool)unit.get_component_property_bool(component_id, "data.lock_rotation_z"));
-			unit.set_component_property_bool  (component_id, "data.lock_translation_x", (bool)unit.get_component_property_bool(component_id, "data.lock_translation_x"));
-			unit.set_component_property_bool  (component_id, "data.lock_translation_y", (bool)unit.get_component_property_bool(component_id, "data.lock_translation_y"));
-			unit.set_component_property_bool  (component_id, "data.lock_translation_z", (bool)unit.get_component_property_bool(component_id, "data.lock_translation_z"));
-			unit.set_component_property_string(component_id, "type", "actor");
-
-			// No synchronization
-		}
+		// No synchronization
+	}
 
-		public void set_sound(Guid sound_id, string name, double range, double volume, bool loop)
-		{
-			_db.add_restore_point((int)ActionType.SET_SOUND, new Guid[] { sound_id });
+	public void set_actor(Guid unit_id, Guid component_id, string class, string collision_filter, string material, double mass)
+	{
+		_db.add_restore_point((int)ActionType.SET_ACTOR, new Guid[] { unit_id });
+
+		Unit unit = new Unit(_db, unit_id, _prefabs);
+		unit.set_component_property_string(component_id, "data.class", class);
+		unit.set_component_property_string(component_id, "data.collision_filter", collision_filter);
+		unit.set_component_property_string(component_id, "data.material", material);
+		unit.set_component_property_double(component_id, "data.mass", mass);
+		unit.set_component_property_bool  (component_id, "data.lock_rotation_x", (bool)unit.get_component_property_bool(component_id, "data.lock_rotation_x"));
+		unit.set_component_property_bool  (component_id, "data.lock_rotation_y", (bool)unit.get_component_property_bool(component_id, "data.lock_rotation_y"));
+		unit.set_component_property_bool  (component_id, "data.lock_rotation_z", (bool)unit.get_component_property_bool(component_id, "data.lock_rotation_z"));
+		unit.set_component_property_bool  (component_id, "data.lock_translation_x", (bool)unit.get_component_property_bool(component_id, "data.lock_translation_x"));
+		unit.set_component_property_bool  (component_id, "data.lock_translation_y", (bool)unit.get_component_property_bool(component_id, "data.lock_translation_y"));
+		unit.set_component_property_bool  (component_id, "data.lock_translation_z", (bool)unit.get_component_property_bool(component_id, "data.lock_translation_z"));
+		unit.set_component_property_string(component_id, "type", "actor");
+
+		// No synchronization
+	}
 
-			_db.set_property_string(sound_id, "name", name);
-			_db.set_property_double(sound_id, "range", range);
-			_db.set_property_double(sound_id, "volume", volume);
-			_db.set_property_bool  (sound_id, "loop", loop);
+	public void set_sound(Guid sound_id, string name, double range, double volume, bool loop)
+	{
+		_db.add_restore_point((int)ActionType.SET_SOUND, new Guid[] { sound_id });
 
-			_client.send_script(LevelEditorApi.set_sound_range(sound_id, range));
-		}
+		_db.set_property_string(sound_id, "name", name);
+		_db.set_property_double(sound_id, "range", range);
+		_db.set_property_double(sound_id, "volume", volume);
+		_db.set_property_bool  (sound_id, "loop", loop);
 
-		public string object_editor_name(Guid object_id)
-		{
-			if (_db.has_property(object_id, "editor.name"))
-				return _db.get_property_string(object_id, "editor.name");
-			else
-				return "<unnamed>";
-		}
+		_client.send_script(LevelEditorApi.set_sound_range(sound_id, range));
+	}
 
-		public void object_set_editor_name(Guid object_id, string name)
-		{
-			_db.add_restore_point((int)ActionType.OBJECT_SET_EDITOR_NAME, new Guid[] { object_id });
-			_db.set_property_string(object_id, "editor.name", name);
+	public string object_editor_name(Guid object_id)
+	{
+		if (_db.has_property(object_id, "editor.name"))
+			return _db.get_property_string(object_id, "editor.name");
+		else
+			return "<unnamed>";
+	}
 
-			object_editor_name_changed(object_id, name);
-		}
+	public void object_set_editor_name(Guid object_id, string name)
+	{
+		_db.add_restore_point((int)ActionType.OBJECT_SET_EDITOR_NAME, new Guid[] { object_id });
+		_db.set_property_string(object_id, "editor.name", name);
 
-		private void send_spawn_units(Guid[] ids)
-		{
-			StringBuilder sb = new StringBuilder();
-			generate_spawn_unit_commands(ids, sb);
-			_client.send_script(sb.str);
-		}
+		object_editor_name_changed(object_id, name);
+	}
 
-		private void send_spawn_sounds(Guid[] ids)
-		{
-			StringBuilder sb = new StringBuilder();
-			generate_spawn_sound_commands(ids, sb);
-			_client.send_script(sb.str);
-		}
+	private void send_spawn_units(Guid[] ids)
+	{
+		StringBuilder sb = new StringBuilder();
+		generate_spawn_unit_commands(ids, sb);
+		_client.send_script(sb.str);
+	}
 
-		private void send_spawn_objects(Guid[] ids)
+	private void send_spawn_sounds(Guid[] ids)
+	{
+		StringBuilder sb = new StringBuilder();
+		generate_spawn_sound_commands(ids, sb);
+		_client.send_script(sb.str);
+	}
+
+	private void send_spawn_objects(Guid[] ids)
+	{
+		StringBuilder sb = new StringBuilder();
+		for (int i = 0; i < ids.length; ++i)
 		{
-			StringBuilder sb = new StringBuilder();
-			for (int i = 0; i < ids.length; ++i)
+			if (is_unit(ids[i]))
 			{
-				if (is_unit(ids[i]))
-				{
-					generate_spawn_unit_commands(new Guid[] { ids[i] }, sb);
-				}
-				else if (is_sound(ids[i]))
-				{
-					generate_spawn_sound_commands(new Guid[] { ids[i] }, sb);
-				}
+				generate_spawn_unit_commands(new Guid[] { ids[i] }, sb);
+			}
+			else if (is_sound(ids[i]))
+			{
+				generate_spawn_sound_commands(new Guid[] { ids[i] }, sb);
 			}
-			_client.send_script(sb.str);
 		}
+		_client.send_script(sb.str);
+	}
 
-		private void send_destroy_objects(Guid[] ids)
-		{
-			StringBuilder sb = new StringBuilder();
-			foreach (Guid id in ids)
-				sb.append(LevelEditorApi.destroy(id));
-
-			_client.send_script(sb.str);
-		}
+	private void send_destroy_objects(Guid[] ids)
+	{
+		StringBuilder sb = new StringBuilder();
+		foreach (Guid id in ids)
+			sb.append(LevelEditorApi.destroy(id));
 
-		private void send_move_objects(Guid[] ids, Vector3[] positions, Quaternion[] rotations, Vector3[] scales)
-		{
-			StringBuilder sb = new StringBuilder();
-			for (int i = 0; i < ids.length; ++i)
-				sb.append(LevelEditorApi.move_object(ids[i], positions[i], rotations[i], scales[i]));
+		_client.send_script(sb.str);
+	}
 
-			_client.send_script(sb.str);
-		}
+	private void send_move_objects(Guid[] ids, Vector3[] positions, Quaternion[] rotations, Vector3[] scales)
+	{
+		StringBuilder sb = new StringBuilder();
+		for (int i = 0; i < ids.length; ++i)
+			sb.append(LevelEditorApi.move_object(ids[i], positions[i], rotations[i], scales[i]));
 
-		public void send_level()
-		{
-			HashSet<Guid?> units  = _db.get_property_set(GUID_ZERO, "units", new HashSet<Guid?>());
-			HashSet<Guid?> sounds = _db.get_property_set(GUID_ZERO, "sounds", new HashSet<Guid?>());
+		_client.send_script(sb.str);
+	}
 
-			Guid[] unit_ids = new Guid[units.size];
-			Guid[] sound_ids = new Guid[sounds.size];
+	public void send_level()
+	{
+		HashSet<Guid?> units  = _db.get_property_set(GUID_ZERO, "units", new HashSet<Guid?>());
+		HashSet<Guid?> sounds = _db.get_property_set(GUID_ZERO, "sounds", new HashSet<Guid?>());
 
-			// FIXME
-			{
-				Guid?[] tmp = units.to_array();
-				for (int i = 0; i < tmp.length; ++i)
-					unit_ids[i] = tmp[i];
-			}
-			// FIXME
-			{
-				Guid?[] tmp = sounds.to_array();
-				for (int i = 0; i < tmp.length; ++i)
-					sound_ids[i] = tmp[i];
-			}
+		Guid[] unit_ids = new Guid[units.size];
+		Guid[] sound_ids = new Guid[sounds.size];
 
-			StringBuilder sb = new StringBuilder();
-			sb.append(LevelEditorApi.reset());
-			generate_spawn_unit_commands(unit_ids, sb);
-			generate_spawn_sound_commands(sound_ids, sb);
-			_client.send_script(sb.str);
+		// FIXME
+		{
+			Guid?[] tmp = units.to_array();
+			for (int i = 0; i < tmp.length; ++i)
+				unit_ids[i] = tmp[i];
 		}
-
-		/// <summary>
-		/// Loads the prefab name into the database of prefabs.
-		/// </summary>
-		private void load_prefab(string name)
+		// FIXME
 		{
-			if (_loaded_prefabs.contains(name))
-				return;
+			Guid?[] tmp = sounds.to_array();
+			for (int i = 0; i < tmp.length; ++i)
+				sound_ids[i] = tmp[i];
+		}
 
-			Database prefab_db = new Database();
+		StringBuilder sb = new StringBuilder();
+		sb.append(LevelEditorApi.reset());
+		generate_spawn_unit_commands(unit_ids, sb);
+		generate_spawn_sound_commands(sound_ids, sb);
+		_client.send_script(sb.str);
+	}
 
-			// Try to load from toolchain directory first
-			File file = File.new_for_path(Path.build_filename(_project.toolchain_dir(), name + ".unit"));
-			if (file.query_exists())
-				prefab_db.load(file.get_path());
-			else
-				prefab_db.load(Path.build_filename(_project.source_dir(), name + ".unit"));
+	/// <summary>
+	/// Loads the prefab name into the database of prefabs.
+	/// </summary>
+	private void load_prefab(string name)
+	{
+		if (_loaded_prefabs.contains(name))
+			return;
 
-			// Recursively load all sub-prefabs
-			Value? prefab = prefab_db.get_property(GUID_ZERO, "prefab");
-			if (prefab != null)
-				load_prefab((string)prefab);
+		Database prefab_db = new Database();
 
-			prefab_db.copy_to(_prefabs, name);
-			_loaded_prefabs.add(name);
-		}
+		// Try to load from toolchain directory first
+		File file = File.new_for_path(Path.build_filename(_project.toolchain_dir(), name + ".unit"));
+		if (file.query_exists())
+			prefab_db.load(file.get_path());
+		else
+			prefab_db.load(Path.build_filename(_project.source_dir(), name + ".unit"));
+
+		// Recursively load all sub-prefabs
+		Value? prefab = prefab_db.get_property(GUID_ZERO, "prefab");
+		if (prefab != null)
+			load_prefab((string)prefab);
+
+		prefab_db.copy_to(_prefabs, name);
+		_loaded_prefabs.add(name);
+	}
 
-		private void generate_spawn_unit_commands(Guid[] unit_ids, StringBuilder sb)
+	private void generate_spawn_unit_commands(Guid[] unit_ids, StringBuilder sb)
+	{
+		foreach (Guid unit_id in unit_ids)
 		{
-			foreach (Guid unit_id in unit_ids)
-			{
-				Unit unit = new Unit(_db, unit_id, _prefabs);
+			Unit unit = new Unit(_db, unit_id, _prefabs);
 
-				if (unit.has_prefab())
-					load_prefab(_db.get_property_string(unit_id, "prefab"));
+			if (unit.has_prefab())
+				load_prefab(_db.get_property_string(unit_id, "prefab"));
 
-				sb.append(LevelEditorApi.spawn_empty_unit(unit_id));
+			sb.append(LevelEditorApi.spawn_empty_unit(unit_id));
 
-				Guid component_id = GUID_ZERO;
+			Guid component_id = GUID_ZERO;
 
-				if (unit.has_component("transform", ref component_id))
-				{
-					string s = LevelEditorApi.add_tranform_component(unit_id
-						, component_id
-						, unit.get_component_property_vector3   (component_id, "data.position")
-						, unit.get_component_property_quaternion(component_id, "data.rotation")
-						, unit.get_component_property_vector3   (component_id, "data.scale")
-						);
-					sb.append(s);
-				}
-				if (unit.has_component("mesh_renderer", ref component_id))
-				{
-					string s = LevelEditorApi.add_mesh_component(unit_id
-						, component_id
-						, unit.get_component_property_string(component_id, "data.mesh_resource")
-						, unit.get_component_property_string(component_id, "data.geometry_name")
-						, unit.get_component_property_string(component_id, "data.material")
-						, unit.get_component_property_bool  (component_id, "data.visible")
-						);
-					sb.append(s);
-				}
-				if (unit.has_component("sprite_renderer", ref component_id))
-				{
-					string s = LevelEditorApi.add_sprite_component(unit_id
-						, component_id
-						, unit.get_component_property_string(component_id, "data.sprite_resource")
-						, unit.get_component_property_string(component_id, "data.material")
-						, unit.get_component_property_double(component_id, "data.layer")
-						, unit.get_component_property_double(component_id, "data.depth")
-						, unit.get_component_property_bool  (component_id, "data.visible")
-						);
-					sb.append(s);
-				}
-				if (unit.has_component("light", ref component_id))
-				{
-					string s = LevelEditorApi.add_light_component(unit_id
-						, component_id
-						, unit.get_component_property_string (component_id, "data.type")
-						, unit.get_component_property_double (component_id, "data.range")
-						, unit.get_component_property_double (component_id, "data.intensity")
-						, unit.get_component_property_double (component_id, "data.spot_angle")
-						, unit.get_component_property_vector3(component_id, "data.color")
-						);
-					sb.append(s);
-				}
-				if (unit.has_component("camera", ref component_id))
-				{
-					string s = LevelEditorApi.add_camera_component(unit_id
-						, component_id
-						, unit.get_component_property_string(component_id, "data.projection")
-						, unit.get_component_property_double(component_id, "data.fov")
-						, unit.get_component_property_double(component_id, "data.far_range")
-						, unit.get_component_property_double(component_id, "data.near_range")
-						);
-					sb.append(s);
-				}
+			if (unit.has_component("transform", ref component_id))
+			{
+				string s = LevelEditorApi.add_tranform_component(unit_id
+					, component_id
+					, unit.get_component_property_vector3   (component_id, "data.position")
+					, unit.get_component_property_quaternion(component_id, "data.rotation")
+					, unit.get_component_property_vector3   (component_id, "data.scale")
+					);
+				sb.append(s);
 			}
-		}
-
-		private void generate_spawn_sound_commands(Guid[] sound_ids, StringBuilder sb)
-		{
-			foreach (Guid sound_id in sound_ids)
+			if (unit.has_component("mesh_renderer", ref component_id))
+			{
+				string s = LevelEditorApi.add_mesh_component(unit_id
+					, component_id
+					, unit.get_component_property_string(component_id, "data.mesh_resource")
+					, unit.get_component_property_string(component_id, "data.geometry_name")
+					, unit.get_component_property_string(component_id, "data.material")
+					, unit.get_component_property_bool  (component_id, "data.visible")
+					);
+				sb.append(s);
+			}
+			if (unit.has_component("sprite_renderer", ref component_id))
+			{
+				string s = LevelEditorApi.add_sprite_component(unit_id
+					, component_id
+					, unit.get_component_property_string(component_id, "data.sprite_resource")
+					, unit.get_component_property_string(component_id, "data.material")
+					, unit.get_component_property_double(component_id, "data.layer")
+					, unit.get_component_property_double(component_id, "data.depth")
+					, unit.get_component_property_bool  (component_id, "data.visible")
+					);
+				sb.append(s);
+			}
+			if (unit.has_component("light", ref component_id))
 			{
-				string s = LevelEditorApi.spawn_sound(sound_id
-					, _db.get_property_string    (sound_id, "name")
-					, _db.get_property_vector3   (sound_id, "position")
-					, _db.get_property_quaternion(sound_id, "rotation")
-					, _db.get_property_double    (sound_id, "range")
-					, _db.get_property_double    (sound_id, "volume")
-					, _db.get_property_bool      (sound_id, "loop")
+				string s = LevelEditorApi.add_light_component(unit_id
+					, component_id
+					, unit.get_component_property_string (component_id, "data.type")
+					, unit.get_component_property_double (component_id, "data.range")
+					, unit.get_component_property_double (component_id, "data.intensity")
+					, unit.get_component_property_double (component_id, "data.spot_angle")
+					, unit.get_component_property_vector3(component_id, "data.color")
 					);
 				sb.append(s);
 			}
+			if (unit.has_component("camera", ref component_id))
+			{
+				string s = LevelEditorApi.add_camera_component(unit_id
+					, component_id
+					, unit.get_component_property_string(component_id, "data.projection")
+					, unit.get_component_property_double(component_id, "data.fov")
+					, unit.get_component_property_double(component_id, "data.far_range")
+					, unit.get_component_property_double(component_id, "data.near_range")
+					);
+				sb.append(s);
+			}
+		}
+	}
+
+	private void generate_spawn_sound_commands(Guid[] sound_ids, StringBuilder sb)
+	{
+		foreach (Guid sound_id in sound_ids)
+		{
+			string s = LevelEditorApi.spawn_sound(sound_id
+				, _db.get_property_string    (sound_id, "name")
+				, _db.get_property_vector3   (sound_id, "position")
+				, _db.get_property_quaternion(sound_id, "rotation")
+				, _db.get_property_double    (sound_id, "range")
+				, _db.get_property_double    (sound_id, "volume")
+				, _db.get_property_bool      (sound_id, "loop")
+				);
+			sb.append(s);
 		}
+	}
 
-		private void undo_redo_action(bool undo, int id, Guid[] data)
+	private void undo_redo_action(bool undo, int id, Guid[] data)
+	{
+		switch (id)
 		{
-			switch (id)
+		case (int)ActionType.SPAWN_UNIT:
 			{
-			case (int)ActionType.SPAWN_UNIT:
-				{
-					if (undo)
-						send_destroy_objects(data);
-					else
-						send_spawn_units(data);
-				}
-				break;
+				if (undo)
+					send_destroy_objects(data);
+				else
+					send_spawn_units(data);
+			}
+			break;
 
-			case (int)ActionType.DESTROY_UNIT:
-				{
-					if (undo)
-						send_spawn_units(data);
-					else
-						send_destroy_objects(data);
-				}
-				break;
+		case (int)ActionType.DESTROY_UNIT:
+			{
+				if (undo)
+					send_spawn_units(data);
+				else
+					send_destroy_objects(data);
+			}
+			break;
 
-			case (int)ActionType.SPAWN_SOUND:
-				{
-					if (undo)
-						send_destroy_objects(data);
-					else
-						send_spawn_sounds(data);
-				}
-				break;
+		case (int)ActionType.SPAWN_SOUND:
+			{
+				if (undo)
+					send_destroy_objects(data);
+				else
+					send_spawn_sounds(data);
+			}
+			break;
 
-			case (int)ActionType.DESTROY_SOUND:
-				{
-					if (undo)
-						send_spawn_sounds(data);
-					else
-						send_destroy_objects(data);
-				}
-				break;
+		case (int)ActionType.DESTROY_SOUND:
+			{
+				if (undo)
+					send_spawn_sounds(data);
+				else
+					send_destroy_objects(data);
+			}
+			break;
 
-			case (int)ActionType.MOVE_OBJECTS:
-				{
-					Guid[] ids = data;
+		case (int)ActionType.MOVE_OBJECTS:
+			{
+				Guid[] ids = data;
 
-					Vector3[] positions = new Vector3[ids.length];
-					Quaternion[] rotations = new Quaternion[ids.length];
-					Vector3[] scales = new Vector3[ids.length];
+				Vector3[] positions = new Vector3[ids.length];
+				Quaternion[] rotations = new Quaternion[ids.length];
+				Vector3[] scales = new Vector3[ids.length];
 
-					for (int i = 0; i < ids.length; ++i)
+				for (int i = 0; i < ids.length; ++i)
+				{
+					if (is_unit(ids[i]))
 					{
-						if (is_unit(ids[i]))
-						{
-							Guid unit_id = ids[i];
-							Guid transform_id = GUID_ZERO;
-
-							Unit unit = new Unit(_db, unit_id, _prefabs);
-
-							if (unit.has_component("transform", ref transform_id))
-							{
-								positions[i] = unit.get_component_property_vector3   (transform_id, "data.position");
-								rotations[i] = unit.get_component_property_quaternion(transform_id, "data.rotation");
-								scales[i]    = unit.get_component_property_vector3   (transform_id, "data.scale");
-							}
-							else
-							{
-								positions[i] = _db.get_property_vector3   (unit_id, "position");
-								rotations[i] = _db.get_property_quaternion(unit_id, "rotation");
-								scales[i]    = _db.get_property_vector3   (unit_id, "scale");
-							}
-						}
-						else if (is_sound(ids[i]))
+						Guid unit_id = ids[i];
+						Guid transform_id = GUID_ZERO;
+
+						Unit unit = new Unit(_db, unit_id, _prefabs);
+
+						if (unit.has_component("transform", ref transform_id))
 						{
-							Guid sound_id = ids[i];
-							positions[i] = _db.get_property_vector3   (sound_id, "position");
-							rotations[i] = _db.get_property_quaternion(sound_id, "rotation");
-							scales[i]    = Vector3(1.0, 1.0, 1.0);
+							positions[i] = unit.get_component_property_vector3   (transform_id, "data.position");
+							rotations[i] = unit.get_component_property_quaternion(transform_id, "data.rotation");
+							scales[i]    = unit.get_component_property_vector3   (transform_id, "data.scale");
 						}
 						else
 						{
-							assert(false);
+							positions[i] = _db.get_property_vector3   (unit_id, "position");
+							rotations[i] = _db.get_property_quaternion(unit_id, "rotation");
+							scales[i]    = _db.get_property_vector3   (unit_id, "scale");
 						}
 					}
-
-					send_move_objects(ids, positions, rotations, scales);
-					// FIXME: Hack to force update the properties view
-					selection_changed(_selection);
-				}
-				break;
-
-			case (int)ActionType.DUPLICATE_OBJECTS:
-				{
-					Guid[] new_ids = data;
-					if (undo)
-						send_destroy_objects(new_ids);
+					else if (is_sound(ids[i]))
+					{
+						Guid sound_id = ids[i];
+						positions[i] = _db.get_property_vector3   (sound_id, "position");
+						rotations[i] = _db.get_property_quaternion(sound_id, "rotation");
+						scales[i]    = Vector3(1.0, 1.0, 1.0);
+					}
 					else
-						send_spawn_objects(new_ids);
+					{
+						assert(false);
+					}
 				}
-				break;
 
-			case (int)ActionType.OBJECT_SET_EDITOR_NAME:
-				object_editor_name_changed(data[0], object_editor_name(data[0]));
-				break;
+				send_move_objects(ids, positions, rotations, scales);
+				// FIXME: Hack to force update the properties view
+				selection_changed(_selection);
+			}
+			break;
 
-			case (int)ActionType.SET_LIGHT:
-				{
-					Guid unit_id = data[0];
-					Guid component_id = GUID_ZERO;
-
-					Unit unit = new Unit(_db, unit_id, _prefabs);
-					unit.has_component("light", ref component_id);
-
-					_client.send_script(LevelEditorApi.set_light(unit_id
-						, unit.get_component_property_string (component_id, "data.type")
-						, unit.get_component_property_double (component_id, "data.range")
-						, unit.get_component_property_double (component_id, "data.intensity")
-						, unit.get_component_property_double (component_id, "data.spot_angle")
-						, unit.get_component_property_vector3(component_id, "data.color")
-						));
-					// FIXME: Hack to force update the properties view
-					selection_changed(_selection);
-				}
-				break;
+		case (int)ActionType.DUPLICATE_OBJECTS:
+			{
+				Guid[] new_ids = data;
+				if (undo)
+					send_destroy_objects(new_ids);
+				else
+					send_spawn_objects(new_ids);
+			}
+			break;
 
-			case (int)ActionType.SET_MESH:
-				{
-					Guid unit_id = data[0];
-					Guid component_id = GUID_ZERO;
-
-					Unit unit = new Unit(_db, unit_id, _prefabs);
-					unit.has_component("mesh_renderer", ref component_id);
-
-					_client.send_script(LevelEditorApi.set_mesh(unit_id
-						, 0/*instance_id*/
-						, unit.get_component_property_string(component_id, "data.material")
-						, unit.get_component_property_bool  (component_id, "data.visible")
-						));
-					// FIXME: Hack to force update the properties view
-					selection_changed(_selection);
-				}
-				break;
+		case (int)ActionType.OBJECT_SET_EDITOR_NAME:
+			object_editor_name_changed(data[0], object_editor_name(data[0]));
+			break;
 
-			case (int)ActionType.SET_SPRITE:
-				{
-					Guid unit_id = data[0];
-					Guid component_id = GUID_ZERO;
-
-					Unit unit = new Unit(_db, unit_id, _prefabs);
-					unit.has_component("sprite_renderer", ref component_id);
-
-					_client.send_script(LevelEditorApi.set_sprite(unit_id
-						, unit.get_component_property_string(component_id, "data.material")
-						, unit.get_component_property_double(component_id, "data.layer")
-						, unit.get_component_property_double(component_id, "data.depth")
-						, unit.get_component_property_bool  (component_id, "data.visible")
-						));
-					// FIXME: Hack to force update the properties view
-					selection_changed(_selection);
-				}
-				break;
+		case (int)ActionType.SET_LIGHT:
+			{
+				Guid unit_id = data[0];
+				Guid component_id = GUID_ZERO;
 
-			case (int)ActionType.SET_CAMERA:
-				{
-					Guid unit_id = data[0];
-					Guid component_id = GUID_ZERO;
-
-					Unit unit = new Unit(_db, unit_id, _prefabs);
-					unit.has_component("camera", ref component_id);
-
-					_client.send_script(LevelEditorApi.set_camera(unit_id
-						, unit.get_component_property_string(component_id, "data.projection")
-						, unit.get_component_property_double(component_id, "data.fov")
-						, unit.get_component_property_double(component_id, "data.near_range")
-						, unit.get_component_property_double(component_id, "data.far_range")
-						));
-					// FIXME: Hack to force update the properties view
-					selection_changed(_selection);
-				}
-				break;
+				Unit unit = new Unit(_db, unit_id, _prefabs);
+				unit.has_component("light", ref component_id);
+
+				_client.send_script(LevelEditorApi.set_light(unit_id
+					, unit.get_component_property_string (component_id, "data.type")
+					, unit.get_component_property_double (component_id, "data.range")
+					, unit.get_component_property_double (component_id, "data.intensity")
+					, unit.get_component_property_double (component_id, "data.spot_angle")
+					, unit.get_component_property_vector3(component_id, "data.color")
+					));
+				// FIXME: Hack to force update the properties view
+				selection_changed(_selection);
+			}
+			break;
 
-			case (int)ActionType.SET_COLLIDER:
-				{
-					// FIXME: Hack to force update the properties view
-					selection_changed(_selection);
-				}
-				break;
+		case (int)ActionType.SET_MESH:
+			{
+				Guid unit_id = data[0];
+				Guid component_id = GUID_ZERO;
 
-			case (int)ActionType.SET_ACTOR:
-				{
-					// FIXME: Hack to force update the properties view
-					selection_changed(_selection);
-				}
-				break;
+				Unit unit = new Unit(_db, unit_id, _prefabs);
+				unit.has_component("mesh_renderer", ref component_id);
+
+				_client.send_script(LevelEditorApi.set_mesh(unit_id
+					, 0/*instance_id*/
+					, unit.get_component_property_string(component_id, "data.material")
+					, unit.get_component_property_bool  (component_id, "data.visible")
+					));
+				// FIXME: Hack to force update the properties view
+				selection_changed(_selection);
+			}
+			break;
 
-			case (int)ActionType.SET_SOUND:
-				{
-					Guid sound_id = data[0];
+		case (int)ActionType.SET_SPRITE:
+			{
+				Guid unit_id = data[0];
+				Guid component_id = GUID_ZERO;
 
-					_client.send_script(LevelEditorApi.set_sound_range(sound_id
-						, _db.get_property_double(sound_id, "range")
-						));
-					// FIXME: Hack to force update the properties view
-					selection_changed(_selection);
-				}
-				break;
+				Unit unit = new Unit(_db, unit_id, _prefabs);
+				unit.has_component("sprite_renderer", ref component_id);
+
+				_client.send_script(LevelEditorApi.set_sprite(unit_id
+					, unit.get_component_property_string(component_id, "data.material")
+					, unit.get_component_property_double(component_id, "data.layer")
+					, unit.get_component_property_double(component_id, "data.depth")
+					, unit.get_component_property_bool  (component_id, "data.visible")
+					));
+				// FIXME: Hack to force update the properties view
+				selection_changed(_selection);
+			}
+			break;
+
+		case (int)ActionType.SET_CAMERA:
+			{
+				Guid unit_id = data[0];
+				Guid component_id = GUID_ZERO;
 
-			default:
-				stdout.printf("Unknown undo/redo action: %d\n", id);
-				assert(false);
-				break;
+				Unit unit = new Unit(_db, unit_id, _prefabs);
+				unit.has_component("camera", ref component_id);
+
+				_client.send_script(LevelEditorApi.set_camera(unit_id
+					, unit.get_component_property_string(component_id, "data.projection")
+					, unit.get_component_property_double(component_id, "data.fov")
+					, unit.get_component_property_double(component_id, "data.near_range")
+					, unit.get_component_property_double(component_id, "data.far_range")
+					));
+				// FIXME: Hack to force update the properties view
+				selection_changed(_selection);
 			}
-		}
+			break;
 
-		public bool is_unit(Guid id)
-		{
-			return _db.get_property_set(GUID_ZERO, "units", new HashSet<Guid?>()).contains(id);
-		}
+		case (int)ActionType.SET_COLLIDER:
+			{
+				// FIXME: Hack to force update the properties view
+				selection_changed(_selection);
+			}
+			break;
 
-		public bool is_sound(Guid id)
-		{
-			return _db.get_property_set(GUID_ZERO, "sounds", new HashSet<Guid?>()).contains(id);
+		case (int)ActionType.SET_ACTOR:
+			{
+				// FIXME: Hack to force update the properties view
+				selection_changed(_selection);
+			}
+			break;
+
+		case (int)ActionType.SET_SOUND:
+			{
+				Guid sound_id = data[0];
+
+				_client.send_script(LevelEditorApi.set_sound_range(sound_id
+					, _db.get_property_double(sound_id, "range")
+					));
+				// FIXME: Hack to force update the properties view
+				selection_changed(_selection);
+			}
+			break;
+
+		default:
+			stdout.printf("Unknown undo/redo action: %d\n", id);
+			assert(false);
+			break;
 		}
 	}
+
+	public bool is_unit(Guid id)
+	{
+		return _db.get_property_set(GUID_ZERO, "units", new HashSet<Guid?>()).contains(id);
+	}
+
+	public bool is_sound(Guid id)
+	{
+		return _db.get_property_set(GUID_ZERO, "sounds", new HashSet<Guid?>()).contains(id);
+	}
+}
+
 }

+ 1552 - 1551
tools/level_editor/level_editor.vala

@@ -9,1969 +9,1970 @@ using Gtk;
 
 namespace Crown
 {
-	const int WINDOW_DEFAULT_WIDTH = 1280;
-	const int WINDOW_DEFAULT_HEIGHT = 720;
+const int WINDOW_DEFAULT_WIDTH = 1280;
+const int WINDOW_DEFAULT_HEIGHT = 720;
 
-	public class LevelEditorWindow : Gtk.ApplicationWindow
+public class LevelEditorWindow : Gtk.ApplicationWindow
+{
+	private const GLib.ActionEntry[] action_entries =
 	{
-		private const GLib.ActionEntry[] action_entries =
-		{
-			{ "fullscreen", on_fullscreen, null, null }
-		};
-
-		public bool _fullscreen;
-
-		public LevelEditorWindow(Gtk.Application app)
-		{
-			Object(application: app);
-
-			this.add_action_entries(action_entries, this);
-
-			this.title = "Level Editor";
-			this.key_press_event.connect(this.on_key_press);
-			this.key_release_event.connect(this.on_key_release);
-			this.window_state_event.connect(this.on_window_state_event);
-			this.delete_event.connect(this.on_delete_event);
-			this.focus_out_event.connect(this.on_focus_out);
+		{ "fullscreen", on_fullscreen, null, null }
+	};
 
-			_fullscreen = false;
-		}
-
-		private void on_fullscreen(GLib.SimpleAction action, GLib.Variant? param)
-		{
-			if (_fullscreen)
-				unfullscreen();
-			else
-				fullscreen();
-		}
-
-		private bool on_key_press(Gdk.EventKey ev)
-		{
-			LevelEditorApplication app = (LevelEditorApplication)application;
+	public bool _fullscreen;
 
-			if (ev.keyval == Gdk.Key.Control_L)
-				app._editor.send_script(LevelEditorApi.key_down("ctrl_left"));
-			else if (ev.keyval == Gdk.Key.Shift_L)
-				app._editor.send_script(LevelEditorApi.key_down("shift_left"));
-			else if (ev.keyval == Gdk.Key.Alt_L)
-				app._editor.send_script(LevelEditorApi.key_down("alt_left"));
+	public LevelEditorWindow(Gtk.Application app)
+	{
+		Object(application: app);
 
-			return Gdk.EVENT_PROPAGATE;
-		}
+		this.add_action_entries(action_entries, this);
 
-		private bool on_key_release(Gdk.EventKey ev)
-		{
-			LevelEditorApplication app = (LevelEditorApplication)application;
+		this.title = "Level Editor";
+		this.key_press_event.connect(this.on_key_press);
+		this.key_release_event.connect(this.on_key_release);
+		this.window_state_event.connect(this.on_window_state_event);
+		this.delete_event.connect(this.on_delete_event);
+		this.focus_out_event.connect(this.on_focus_out);
 
-			if (ev.keyval == Gdk.Key.Control_L)
-				app._editor.send_script(LevelEditorApi.key_up("ctrl_left"));
-			else if (ev.keyval == Gdk.Key.Shift_L)
-				app._editor.send_script(LevelEditorApi.key_up("shift_left"));
-			else if (ev.keyval == Gdk.Key.Alt_L)
-				app._editor.send_script(LevelEditorApi.key_up("alt_left"));
+		_fullscreen = false;
+	}
 
-			return Gdk.EVENT_PROPAGATE;
-		}
+	private void on_fullscreen(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		if (_fullscreen)
+			unfullscreen();
+		else
+			fullscreen();
+	}
 
-		private bool on_window_state_event(Gdk.EventWindowState ev)
-		{
-			_fullscreen = (ev.new_window_state & Gdk.WindowState.FULLSCREEN) != 0;
-			return Gdk.EVENT_STOP;
-		}
+	private bool on_key_press(Gdk.EventKey ev)
+	{
+		LevelEditorApplication app = (LevelEditorApplication)application;
 
-		private bool on_delete_event()
-		{
-			LevelEditorApplication app = (LevelEditorApplication)application;
-			if (app.should_quit())
-			{
-				app.close_all();
-				return Gdk.EVENT_PROPAGATE; // Quit application
-			}
+		if (ev.keyval == Gdk.Key.Control_L)
+			app._editor.send_script(LevelEditorApi.key_down("ctrl_left"));
+		else if (ev.keyval == Gdk.Key.Shift_L)
+			app._editor.send_script(LevelEditorApi.key_down("shift_left"));
+		else if (ev.keyval == Gdk.Key.Alt_L)
+			app._editor.send_script(LevelEditorApi.key_down("alt_left"));
 
-			return Gdk.EVENT_STOP; // Keep alive
-		}
+		return Gdk.EVENT_PROPAGATE;
+	}
 
-		private bool on_focus_out(Gdk.EventFocus ev)
-		{
-			LevelEditorApplication app = (LevelEditorApplication)application;
+	private bool on_key_release(Gdk.EventKey ev)
+	{
+		LevelEditorApplication app = (LevelEditorApplication)application;
 
+		if (ev.keyval == Gdk.Key.Control_L)
 			app._editor.send_script(LevelEditorApi.key_up("ctrl_left"));
+		else if (ev.keyval == Gdk.Key.Shift_L)
 			app._editor.send_script(LevelEditorApi.key_up("shift_left"));
+		else if (ev.keyval == Gdk.Key.Alt_L)
 			app._editor.send_script(LevelEditorApi.key_up("alt_left"));
-			return Gdk.EVENT_PROPAGATE;
-		}
+
+		return Gdk.EVENT_PROPAGATE;
 	}
 
-	public enum StartGame
+	private bool on_window_state_event(Gdk.EventWindowState ev)
 	{
-		NORMAL,
-		TEST
+		_fullscreen = (ev.new_window_state & Gdk.WindowState.FULLSCREEN) != 0;
+		return Gdk.EVENT_STOP;
 	}
 
-	public class LevelEditorApplication : Gtk.Application
+	private bool on_delete_event()
 	{
-		// Constants
-		private const GLib.ActionEntry[] action_entries_file =
+		LevelEditorApplication app = (LevelEditorApplication)application;
+		if (app.should_quit())
 		{
-			//                                 parameter type
-			// name           activate()       |     state
-			// |              |                |     |
-			{ "menu-file",    null,            null, null },
-			{ "new-level",    on_new_level,    null, null },
-			{ "open-level",   on_open_level,   "s",  null },
-			{ "new-project",  on_new_project,  null, null },
-			{ "open-project", on_open_project, null, null },
-			{ "save",         on_save,         null, null },
-			{ "save-as",      on_save_as,      null, null },
-			{ "import",       on_import,       null, null },
-			{ "preferences",  on_preferences,  null, null },
-			{ "deploy",       on_deploy,       null, null },
-			{ "close",        on_close,        null, null },
-			{ "quit",         on_quit,         null, null }
-		};
+			app.close_all();
+			return Gdk.EVENT_PROPAGATE; // Quit application
+		}
 
-		private const GLib.ActionEntry[] action_entries_edit =
-		{
-			{ "menu-edit",            null,                        null, null         },
-			{ "undo",                 on_undo,                     null, null         },
-			{ "redo",                 on_redo,                     null, null         },
-			{ "duplicate",            on_duplicate,                null, null         },
-			{ "delete",               on_delete,                   null, null         },
-			{ "tool",                 on_tool_changed,             "s",  "'move'"     },
-			{ "snap",                 on_snap_mode_changed,        "s",  "'relative'" },
-			{ "reference-system",     on_reference_system_changed, "s",  "'local'"    },
-			{ "snap-to-grid",         on_snap_to_grid,             null, "true"       },
-			{ "menu-grid",            null,                        null, null         },
-			{ "grid-show",            on_show_grid,                null, "true"       },
-			{ "grid-custom",          on_custom_grid,              null, null         },
-			{ "grid-preset",          on_grid_changed,             "s",  "'1'"        },
-			{ "menu-rotation-snap",   null,                        null, null         },
-			{ "rotation-snap-custom", on_rotation_snap,            null, null         },
-			{ "rotation-snap-preset", on_rotation_snap_changed,    "s",  "'15'"       }
-		};
+		return Gdk.EVENT_STOP; // Keep alive
+	}
 
-		private const GLib.ActionEntry[] action_entries_create =
-		{
-			{ "menu-create",        null,                null, null },
-			{ "menu-primitives",    null,                null, null },
-			{ "primitive-cube",     on_create_primitive, null, null },
-			{ "primitive-sphere",   on_create_primitive, null, null },
-			{ "primitive-cone",     on_create_primitive, null, null },
-			{ "primitive-cylinder", on_create_primitive, null, null },
-			{ "primitive-plane",    on_create_primitive, null, null },
-			{ "camera",             on_create_primitive, null, null },
-			{ "light",              on_create_primitive, null, null },
-			{ "sound-source",       on_create_primitive, null, null }
-		};
+	private bool on_focus_out(Gdk.EventFocus ev)
+	{
+		LevelEditorApplication app = (LevelEditorApplication)application;
 
-		private const GLib.ActionEntry[] action_entries_camera =
-		{
-			{ "menu-camera", null,           null, null            },
-			{ "camera-view", on_camera_view, "s",  "'perspective'" }
-		};
+		app._editor.send_script(LevelEditorApi.key_up("ctrl_left"));
+		app._editor.send_script(LevelEditorApi.key_up("shift_left"));
+		app._editor.send_script(LevelEditorApi.key_up("alt_left"));
+		return Gdk.EVENT_PROPAGATE;
+	}
+}
 
-		private const GLib.ActionEntry[] action_entries_view =
-		{
-			{ "menu-view",           null,                   null, null    },
-			{ "resource-chooser",    on_resource_chooser,    null, null    },
-			{ "project-browser",     on_project_browser,     null, null    },
-			{ "console",             on_console,             null, null    },
-			{ "statusbar",           on_statusbar,           null, null    },
-			{ "inspector",           on_inspector,           null, null    },
-			{ "debug-render-world",  on_debug_render_world,  null, "false" },
-			{ "debug-physics-world", on_debug_physics_world, null, "false" }
-		};
+public enum StartGame
+{
+	NORMAL,
+	TEST
+}
 
-		private const GLib.ActionEntry[] action_entries_debug =
-		{
-			{ "menu-debug", null,              null, null },
-			{ "test-level", on_run_game,       null, null },
-			{ "run-game",   on_run_game,       null, null },
-			{ "build-data", on_build_data,     null, null },
-			{ "reload-lua", on_refresh_lua,    null, null },
-			{ "restart",    on_editor_restart, null, null }
-		};
+public class LevelEditorApplication : Gtk.Application
+{
+	// Constants
+	private const GLib.ActionEntry[] action_entries_file =
+	{
+		//                                 parameter type
+		// name           activate()       |     state
+		// |              |                |     |
+		{ "menu-file",    null,            null, null },
+		{ "new-level",    on_new_level,    null, null },
+		{ "open-level",   on_open_level,   "s",  null },
+		{ "new-project",  on_new_project,  null, null },
+		{ "open-project", on_open_project, null, null },
+		{ "save",         on_save,         null, null },
+		{ "save-as",      on_save_as,      null, null },
+		{ "import",       on_import,       null, null },
+		{ "preferences",  on_preferences,  null, null },
+		{ "deploy",       on_deploy,       null, null },
+		{ "close",        on_close,        null, null },
+		{ "quit",         on_quit,         null, null }
+	};
+
+	private const GLib.ActionEntry[] action_entries_edit =
+	{
+		{ "menu-edit",            null,                        null, null         },
+		{ "undo",                 on_undo,                     null, null         },
+		{ "redo",                 on_redo,                     null, null         },
+		{ "duplicate",            on_duplicate,                null, null         },
+		{ "delete",               on_delete,                   null, null         },
+		{ "tool",                 on_tool_changed,             "s",  "'move'"     },
+		{ "snap",                 on_snap_mode_changed,        "s",  "'relative'" },
+		{ "reference-system",     on_reference_system_changed, "s",  "'local'"    },
+		{ "snap-to-grid",         on_snap_to_grid,             null, "true"       },
+		{ "menu-grid",            null,                        null, null         },
+		{ "grid-show",            on_show_grid,                null, "true"       },
+		{ "grid-custom",          on_custom_grid,              null, null         },
+		{ "grid-preset",          on_grid_changed,             "s",  "'1'"        },
+		{ "menu-rotation-snap",   null,                        null, null         },
+		{ "rotation-snap-custom", on_rotation_snap,            null, null         },
+		{ "rotation-snap-preset", on_rotation_snap_changed,    "s",  "'15'"       }
+	};
+
+	private const GLib.ActionEntry[] action_entries_create =
+	{
+		{ "menu-create",        null,                null, null },
+		{ "menu-primitives",    null,                null, null },
+		{ "primitive-cube",     on_create_primitive, null, null },
+		{ "primitive-sphere",   on_create_primitive, null, null },
+		{ "primitive-cone",     on_create_primitive, null, null },
+		{ "primitive-cylinder", on_create_primitive, null, null },
+		{ "primitive-plane",    on_create_primitive, null, null },
+		{ "camera",             on_create_primitive, null, null },
+		{ "light",              on_create_primitive, null, null },
+		{ "sound-source",       on_create_primitive, null, null }
+	};
+
+	private const GLib.ActionEntry[] action_entries_camera =
+	{
+		{ "menu-camera", null,           null, null            },
+		{ "camera-view", on_camera_view, "s",  "'perspective'" }
+	};
 
-		private const GLib.ActionEntry[] action_entries_help =
-		{
-			{ "menu-help",    null,            null, null },
-			{ "manual",       on_manual,       null, null },
-			{ "report-issue", on_report_issue, null, null },
-			{ "browse-logs",  on_browse_logs,  null, null },
-			{ "changelog",    on_changelog,    null, null },
-			{ "about",        on_about,        null, null }
-		};
+	private const GLib.ActionEntry[] action_entries_view =
+	{
+		{ "menu-view",           null,                   null, null    },
+		{ "resource-chooser",    on_resource_chooser,    null, null    },
+		{ "project-browser",     on_project_browser,     null, null    },
+		{ "console",             on_console,             null, null    },
+		{ "statusbar",           on_statusbar,           null, null    },
+		{ "inspector",           on_inspector,           null, null    },
+		{ "debug-render-world",  on_debug_render_world,  null, "false" },
+		{ "debug-physics-world", on_debug_physics_world, null, "false" }
+	};
+
+	private const GLib.ActionEntry[] action_entries_debug =
+	{
+		{ "menu-debug", null,              null, null },
+		{ "test-level", on_run_game,       null, null },
+		{ "run-game",   on_run_game,       null, null },
+		{ "build-data", on_build_data,     null, null },
+		{ "reload-lua", on_refresh_lua,    null, null },
+		{ "restart",    on_editor_restart, null, null }
+	};
+
+	private const GLib.ActionEntry[] action_entries_help =
+	{
+		{ "menu-help",    null,            null, null },
+		{ "manual",       on_manual,       null, null },
+		{ "report-issue", on_report_issue, null, null },
+		{ "browse-logs",  on_browse_logs,  null, null },
+		{ "changelog",    on_changelog,    null, null },
+		{ "about",        on_about,        null, null }
+	};
+
+	// Command line options
+	private string? _source_dir = null;
+	private string _toolchain_dir = "";
+	private string? _level_resource = null;
+	private User _user;
+
+	// Editor state
+	private double _grid_size;
+	private double _rotation_snap;
+	private bool _show_grid;
+	private bool _snap_to_grid;
+	private bool _debug_render_world;
+	private bool _debug_physics_world;
+	private LevelEditorApi.ToolType _tool_type;
+	private LevelEditorApi.SnapMode _snap_mode;
+	private LevelEditorApi.ReferenceSystem _reference_system;
+
+	// Engine connections
+	private GLib.Subprocess _compiler_process;
+	private GLib.Subprocess _editor_process;
+	private GLib.Subprocess _game_process;
+	private ConsoleClient _compiler;
+	public ConsoleClient _editor;
+	private ConsoleClient _game;
+
+	// Level data
+	private Database _database;
+	private Project _project;
+	private ProjectStore _project_store;
+	private Level _level;
+	private DataCompiler _data_compiler;
+
+	// Widgets
+	private ProjectBrowser _project_browser;
+	private EditorView _editor_view;
+	private LevelTreeView _level_treeview;
+	private LevelLayersTreeView _level_layers_treeview;
+	private PropertiesView _properties_view;
+	private PreferencesDialog _preferences_dialog;
+	private ResourceChooser _resource_chooser;
+	private Gtk.Popover _resource_popover;
+	private Gtk.Overlay _editor_view_overlay;
+	private Slide _project_slide;
+	private Slide _editor_slide;
+	private Slide _inspector_slide;
+
+	private Gtk.Toolbar _toolbar;
+	private Gtk.ToolButton _toolbar_run;
+	private Gtk.Notebook _level_tree_view_notebook;
+	private Gtk.Paned _editor_pane;
+	private Gtk.Paned _content_pane;
+	private Gtk.Paned _inspector_pane;
+	private Gtk.Paned _main_pane;
+	private Statusbar _statusbar;
+	private Gtk.Box _main_vbox;
+	private Gtk.FileFilter _file_filter;
+	private Gtk.ComboBoxText _combo;
+	private PanelNewProject _panel_new_project;
+	private PanelProjectsList _panel_projects_list;
+	private PanelWelcome _panel_welcome;
+	private Gtk.Stack _main_stack;
+
+	private uint _save_timer_id;
+
+	public LevelEditorApplication()
+	{
+		Object(application_id: "org.crown.level_editor"
+			, flags: GLib.ApplicationFlags.FLAGS_NONE
+			);
+	}
+
+	protected override void startup()
+	{
+		base.startup();
 
-		// Command line options
-		private string? _source_dir = null;
-		private string _toolchain_dir = "";
-		private string? _level_resource = null;
-		private User _user;
+		Intl.setlocale(LocaleCategory.ALL, "C");
+		Gtk.Settings.get_default().gtk_theme_name = "Adwaita";
+		Gtk.Settings.get_default().gtk_application_prefer_dark_theme = true;
 
-		// Editor state
-		private double _grid_size;
-		private double _rotation_snap;
-		private bool _show_grid;
-		private bool _snap_to_grid;
-		private bool _debug_render_world;
-		private bool _debug_physics_world;
-		private LevelEditorApi.ToolType _tool_type;
-		private LevelEditorApi.SnapMode _snap_mode;
-		private LevelEditorApi.ReferenceSystem _reference_system;
+		Gtk.CssProvider provider = new Gtk.CssProvider();
+		Gdk.Screen screen = Gdk.Display.get_default().get_default_screen();
+		Gtk.StyleContext.add_provider_for_screen(screen, provider, STYLE_PROVIDER_PRIORITY_APPLICATION);
+		provider.load_from_resource("/org/crown/level_editor/css/style.css");
 
-		// Engine connections
-		private GLib.Subprocess _compiler_process;
-		private GLib.Subprocess _editor_process;
-		private GLib.Subprocess _game_process;
-		private ConsoleClient _compiler;
-		public ConsoleClient _editor;
-		private ConsoleClient _game;
-
-		// Level data
-		private Database _database;
-		private Project _project;
-		private ProjectStore _project_store;
-		private Level _level;
-		private DataCompiler _data_compiler;
+		// HACK: register CrownClamp type within GObject's type system to
+		// make GtkBuilder able to find it when creating the widget from
+		// .ui files.
+		// https://stackoverflow.com/questions/24235937/custom-gtk-widget-with-template-ui
+		new Clamp().get_type().ensure();
 
-		// Widgets
-		private ProjectBrowser _project_browser;
-		private EditorView _editor_view;
-		private LevelTreeView _level_treeview;
-		private LevelLayersTreeView _level_layers_treeview;
-		private PropertiesView _properties_view;
-		private PreferencesDialog _preferences_dialog;
-		private ResourceChooser _resource_chooser;
-		private Gtk.Popover _resource_popover;
-		private Gtk.Overlay _editor_view_overlay;
-		private Slide _project_slide;
-		private Slide _editor_slide;
-		private Slide _inspector_slide;
-
-		private Gtk.Toolbar _toolbar;
-		private Gtk.ToolButton _toolbar_run;
-		private Gtk.Notebook _level_tree_view_notebook;
-		private Gtk.Paned _editor_pane;
-		private Gtk.Paned _content_pane;
-		private Gtk.Paned _inspector_pane;
-		private Gtk.Paned _main_pane;
-		private Statusbar _statusbar;
-		private Gtk.Box _main_vbox;
-		private Gtk.FileFilter _file_filter;
-		private Gtk.ComboBoxText _combo;
-		private PanelNewProject _panel_new_project;
-		private PanelProjectsList _panel_projects_list;
-		private PanelWelcome _panel_welcome;
-		private Gtk.Stack _main_stack;
-
-		private uint _save_timer_id;
-
-		public LevelEditorApplication()
-		{
-			Object(application_id: "org.crown.level_editor"
-				, flags: GLib.ApplicationFlags.FLAGS_NONE
-				);
-		}
+		this.add_action_entries(action_entries_file, this);
+		this.add_action_entries(action_entries_edit, this);
+		this.add_action_entries(action_entries_create, this);
+		this.add_action_entries(action_entries_camera, this);
+		this.add_action_entries(action_entries_view, this);
+		this.add_action_entries(action_entries_debug, this);
+		this.add_action_entries(action_entries_help, this);
 
-		protected override void startup()
-		{
-			base.startup();
+		_compiler = new ConsoleClient();
+		_compiler.connected.connect(on_compiler_connected);
+		_compiler.disconnected.connect(on_compiler_disconnected_unexpected);
+		_compiler.message_received.connect(on_message_received);
+
+		_data_compiler = new DataCompiler(_compiler);
 
-			Intl.setlocale(LocaleCategory.ALL, "C");
-			Gtk.Settings.get_default().gtk_theme_name = "Adwaita";
-			Gtk.Settings.get_default().gtk_application_prefer_dark_theme = true;
+		_project = new Project(_data_compiler);
+		_project.set_toolchain_dir(_toolchain_dir);
 
-			Gtk.CssProvider provider = new Gtk.CssProvider();
-			Gdk.Screen screen = Gdk.Display.get_default().get_default_screen();
-			Gtk.StyleContext.add_provider_for_screen(screen, provider, STYLE_PROVIDER_PRIORITY_APPLICATION);
-			provider.load_from_resource("/org/crown/level_editor/css/style.css");
+		_database = new Database();
 
-			// HACK: register CrownClamp type within GObject's type system to
-			// make GtkBuilder able to find it when creating the widget from
-			// .ui files.
-			// https://stackoverflow.com/questions/24235937/custom-gtk-widget-with-template-ui
-			new Clamp().get_type().ensure();
+		_editor = new ConsoleClient();
+		_editor.connected.connect(on_editor_connected);
+		_editor.disconnected.connect(on_editor_disconnected_unexpected);
+		_editor.message_received.connect(on_message_received);
 
-			this.add_action_entries(action_entries_file, this);
-			this.add_action_entries(action_entries_edit, this);
-			this.add_action_entries(action_entries_create, this);
-			this.add_action_entries(action_entries_camera, this);
-			this.add_action_entries(action_entries_view, this);
-			this.add_action_entries(action_entries_debug, this);
-			this.add_action_entries(action_entries_help, this);
+		_game = new ConsoleClient();
+		_game.connected.connect(on_game_connected);
+		_game.disconnected.connect(on_game_disconnected);
+		_game.message_received.connect(on_message_received);
 
-			_compiler = new ConsoleClient();
-			_compiler.connected.connect(on_compiler_connected);
-			_compiler.disconnected.connect(on_compiler_disconnected_unexpected);
-			_compiler.message_received.connect(on_message_received);
+		_level = new Level(_database, _editor, _project);
 
-			_data_compiler = new DataCompiler(_compiler);
+		// Editor state
+		_grid_size = 1.0;
+		_rotation_snap = 15.0;
+		_show_grid = true;
+		_snap_to_grid = true;
+		_debug_render_world = false;
+		_debug_physics_world = false;
+		_tool_type = LevelEditorApi.ToolType.MOVE;
+		_snap_mode = LevelEditorApi.SnapMode.RELATIVE;
+		_reference_system = LevelEditorApi.ReferenceSystem.LOCAL;
 
-			_project = new Project(_data_compiler);
-			_project.set_toolchain_dir(_toolchain_dir);
+		// Engine connections
+		_compiler_process = null;
+		_editor_process = null;
+		_game_process = null;
 
-			_database = new Database();
+		_project_store = new ProjectStore(_project);
 
-			_editor = new ConsoleClient();
-			_editor.connected.connect(on_editor_connected);
-			_editor.disconnected.connect(on_editor_disconnected_unexpected);
-			_editor.message_received.connect(on_message_received);
+		// Widgets
+		_combo = new Gtk.ComboBoxText();
+		_combo.append("editor", "Editor");
+		_combo.append("game", "Game");
+		_combo.set_active_id("editor");
 
-			_game = new ConsoleClient();
-			_game.connected.connect(on_game_connected);
-			_game.disconnected.connect(on_game_disconnected);
-			_game.message_received.connect(on_message_received);
+		_console_view = new ConsoleView(_project, _combo);
+		_project_browser = new ProjectBrowser(_project, _project_store);
+		_level_treeview = new LevelTreeView(_database, _level);
+		_level_layers_treeview = new LevelLayersTreeView(_database, _level);
+		_properties_view = new PropertiesView(_level, _project_store);
 
-			_level = new Level(_database, _editor, _project);
+		_project_slide = new Slide();
+		_editor_slide = new Slide();
+		_inspector_slide = new Slide();
 
-			// Editor state
-			_grid_size = 1.0;
-			_rotation_snap = 15.0;
-			_show_grid = true;
-			_snap_to_grid = true;
-			_debug_render_world = false;
-			_debug_physics_world = false;
-			_tool_type = LevelEditorApi.ToolType.MOVE;
-			_snap_mode = LevelEditorApi.SnapMode.RELATIVE;
-			_reference_system = LevelEditorApi.ReferenceSystem.LOCAL;
+		Gtk.Builder builder = new Gtk.Builder.from_resource("/org/crown/level_editor/ui/toolbar.ui");
+		_toolbar = builder.get_object("toolbar") as Gtk.Toolbar;
+		_toolbar_run = builder.get_object("run") as Gtk.ToolButton;
 
-			// Engine connections
-			_compiler_process = null;
-			_editor_process = null;
-			_game_process = null;
+		_editor_view_overlay = new Gtk.Overlay();
+		_editor_view_overlay.add_overlay(_toolbar);
 
-			_project_store = new ProjectStore(_project);
+		_resource_popover = new Gtk.Popover(_toolbar);
+		_resource_popover.delete_event.connect(() => { _resource_popover.hide(); return Gdk.EVENT_STOP; });
+		_resource_popover.modal = true;
 
-			// Widgets
-			_combo = new Gtk.ComboBoxText();
-			_combo.append("editor", "Editor");
-			_combo.append("game", "Game");
-			_combo.set_active_id("editor");
+		_preferences_dialog = new PreferencesDialog(this);
+		_preferences_dialog.set_transient_for(this.active_window);
+		_preferences_dialog.delete_event.connect(() => { _preferences_dialog.hide(); return Gdk.EVENT_STOP; });
 
-			_console_view = new ConsoleView(_project, _combo);
-			_project_browser = new ProjectBrowser(_project, _project_store);
-			_level_treeview = new LevelTreeView(_database, _level);
-			_level_layers_treeview = new LevelLayersTreeView(_database, _level);
-			_properties_view = new PropertiesView(_level, _project_store);
+		_resource_chooser = new ResourceChooser(_project, _project_store, true);
+		_resource_chooser.resource_selected.connect(on_resource_browser_resource_selected);
+		_resource_chooser.resource_selected.connect(() => { _resource_popover.hide(); });
+		_resource_popover.add(_resource_chooser);
 
-			_project_slide = new Slide();
-			_editor_slide = new Slide();
-			_inspector_slide = new Slide();
+		_level_tree_view_notebook = new Notebook();
+		_level_tree_view_notebook.show_border = false;
+		_level_tree_view_notebook.append_page(_level_treeview, new Gtk.Image.from_icon_name("level-tree", IconSize.SMALL_TOOLBAR));
+		_level_tree_view_notebook.append_page(_level_layers_treeview, new Gtk.Image.from_icon_name("level-layers", IconSize.SMALL_TOOLBAR));
 
-			Gtk.Builder builder = new Gtk.Builder.from_resource("/org/crown/level_editor/ui/toolbar.ui");
-			_toolbar = builder.get_object("toolbar") as Gtk.Toolbar;
-			_toolbar_run = builder.get_object("run") as Gtk.ToolButton;
+		_editor_pane = new Gtk.Paned(Gtk.Orientation.HORIZONTAL);
+		_editor_pane.pack1(_project_slide, false, false);
+		_editor_pane.pack2(_editor_slide, true, false);
 
-			_editor_view_overlay = new Gtk.Overlay();
-			_editor_view_overlay.add_overlay(_toolbar);
+		_content_pane = new Gtk.Paned(Gtk.Orientation.VERTICAL);
+		_content_pane.pack1(_editor_pane, true, false);
+		_content_pane.pack2(_console_view, false, false);
 
-			_resource_popover = new Gtk.Popover(_toolbar);
-			_resource_popover.delete_event.connect(() => { _resource_popover.hide(); return Gdk.EVENT_STOP; });
-			_resource_popover.modal = true;
+		_inspector_pane = new Gtk.Paned(Gtk.Orientation.VERTICAL);
+		_inspector_pane.pack1(_level_tree_view_notebook, true, false);
+		_inspector_pane.pack2(_properties_view, false, false);
 
-			_preferences_dialog = new PreferencesDialog(this);
-			_preferences_dialog.set_transient_for(this.active_window);
-			_preferences_dialog.delete_event.connect(() => { _preferences_dialog.hide(); return Gdk.EVENT_STOP; });
+		_main_pane = new Gtk.Paned(Gtk.Orientation.HORIZONTAL);
+		_main_pane.pack1(_content_pane, true, false);
+		_main_pane.pack2(_inspector_slide, false, false);
 
-			_resource_chooser = new ResourceChooser(_project, _project_store, true);
-			_resource_chooser.resource_selected.connect(on_resource_browser_resource_selected);
-			_resource_chooser.resource_selected.connect(() => { _resource_popover.hide(); });
-			_resource_popover.add(_resource_chooser);
+		_statusbar = new Statusbar();
 
-			_level_tree_view_notebook = new Notebook();
-			_level_tree_view_notebook.show_border = false;
-			_level_tree_view_notebook.append_page(_level_treeview, new Gtk.Image.from_icon_name("level-tree", IconSize.SMALL_TOOLBAR));
-			_level_tree_view_notebook.append_page(_level_layers_treeview, new Gtk.Image.from_icon_name("level-layers", IconSize.SMALL_TOOLBAR));
+		_main_vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
+		_main_vbox.pack_start(_main_pane, true, true, 0);
+		_main_vbox.pack_start(_statusbar, false, false, 0);
+		_main_vbox.set_visible(true);
 
-			_editor_pane = new Gtk.Paned(Gtk.Orientation.HORIZONTAL);
-			_editor_pane.pack1(_project_slide, false, false);
-			_editor_pane.pack2(_editor_slide, true, false);
+		_file_filter = new Gtk.FileFilter();
+		_file_filter.set_filter_name("Level (*.level)");
+		_file_filter.add_pattern("*.level");
 
-			_content_pane = new Gtk.Paned(Gtk.Orientation.VERTICAL);
-			_content_pane.pack1(_editor_pane, true, false);
-			_content_pane.pack2(_console_view, false, false);
+		_user = new User();
+		_panel_new_project = new PanelNewProject(this, _user, _project);
 
-			_inspector_pane = new Gtk.Paned(Gtk.Orientation.VERTICAL);
-			_inspector_pane.pack1(_level_tree_view_notebook, true, false);
-			_inspector_pane.pack2(_properties_view, false, false);
+		_panel_welcome = new PanelWelcome();
+		_panel_projects_list = new PanelProjectsList(this, _user);
+		_panel_welcome.pack_start(_panel_projects_list);
+		_panel_welcome.set_visible(true); // To make Gtk.Stack work...
 
-			_main_pane = new Gtk.Paned(Gtk.Orientation.HORIZONTAL);
-			_main_pane.pack1(_content_pane, true, false);
-			_main_pane.pack2(_inspector_slide, false, false);
+		_main_stack = new Gtk.Stack();
+		_main_stack.add_named(_panel_welcome, "panel_welcome");
+		_main_stack.add_named(_panel_new_project, "panel_new_project");
+		_main_stack.add_named(_main_vbox, "main_vbox");
 
-			_statusbar = new Statusbar();
+		load_settings();
+		_user.load(_user_file.get_path());
+
+		if (_source_dir == null)
+		{
+			show_panel("panel_welcome");
+		}
+		else
+		{
+			show_panel("main_vbox");
+			restart_compiler(_source_dir, _level_resource);
+		}
+	}
 
-			_main_vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
-			_main_vbox.pack_start(_main_pane, true, true, 0);
-			_main_vbox.pack_start(_statusbar, false, false, 0);
-			_main_vbox.set_visible(true);
+	public void load_settings()
+	{
+		Hashtable settings = SJSON.load(_settings_file.get_path());
 
-			_file_filter = new Gtk.FileFilter();
-			_file_filter.set_filter_name("Level (*.level)");
-			_file_filter.add_pattern("*.level");
+		_preferences_dialog.load(settings.has_key("preferences") ? (Hashtable)settings["preferences"] : new Hashtable());
+	}
 
-			_user = new User();
-			_panel_new_project = new PanelNewProject(this, _user, _project);
+	public void save_settings()
+	{
+		Hashtable preferences = new Hashtable();
+		_preferences_dialog.save(preferences);
 
-			_panel_welcome = new PanelWelcome();
-			_panel_projects_list = new PanelProjectsList(this, _user);
-			_panel_welcome.pack_start(_panel_projects_list);
-			_panel_welcome.set_visible(true); // To make Gtk.Stack work...
+		Hashtable settings = new Hashtable();
+		settings["preferences"] = preferences;
 
-			_main_stack = new Gtk.Stack();
-			_main_stack.add_named(_panel_welcome, "panel_welcome");
-			_main_stack.add_named(_panel_new_project, "panel_new_project");
-			_main_stack.add_named(_main_vbox, "main_vbox");
+		SJSON.save(settings, _settings_file.get_path());
+	}
 
-			load_settings();
-			_user.load(_user_file.get_path());
+	protected override void activate()
+	{
+		if (this.active_window == null)
+		{
+			LevelEditorWindow win = new LevelEditorWindow(this);
+			win.set_default_size(WINDOW_DEFAULT_WIDTH, WINDOW_DEFAULT_HEIGHT);
+			win.add(_main_stack);
 
-			if (_source_dir == null)
+			try
 			{
-				show_panel("panel_welcome");
+				win.icon = IconTheme.get_default().load_icon("pepper", 48, 0);
 			}
-			else
+			catch (Error e)
 			{
-				show_panel("main_vbox");
-				restart_compiler(_source_dir, _level_resource);
+				loge(e.message);
 			}
 		}
 
-		public void load_settings()
+		this.active_window.show_all();
+		this.active_window.maximize();
+	}
+
+	protected override bool local_command_line(ref unowned string[] args, out int exit_status)
+	{
+		if (args.length > 1)
 		{
-			Hashtable settings = SJSON.load(_settings_file.get_path());
+			if (!GLib.FileUtils.test(args[1], FileTest.EXISTS) || !GLib.FileUtils.test(args[1], FileTest.IS_DIR))
+			{
+				loge("Source directory does not exist or it is not a directory");
+				exit_status = 1;
+				return true;
+			}
 
-			_preferences_dialog.load(settings.has_key("preferences") ? (Hashtable)settings["preferences"] : new Hashtable());
+			_source_dir = args[1];
 		}
 
-		public void save_settings()
+		if (args.length > 2)
 		{
-			Hashtable preferences = new Hashtable();
-			_preferences_dialog.save(preferences);
-
-			Hashtable settings = new Hashtable();
-			settings["preferences"] = preferences;
-
-			SJSON.save(settings, _settings_file.get_path());
+			// Validation is done below after the Project object instantiation
+			_level_resource = args[2];
 		}
 
-		protected override void activate()
+		if (args.length > 3)
 		{
-			if (this.active_window == null)
+			if (!GLib.FileUtils.test(args[3], FileTest.EXISTS) || !GLib.FileUtils.test(args[3], FileTest.IS_DIR))
 			{
-				LevelEditorWindow win = new LevelEditorWindow(this);
-				win.set_default_size(WINDOW_DEFAULT_WIDTH, WINDOW_DEFAULT_HEIGHT);
-				win.add(_main_stack);
-
-				try
-				{
-					win.icon = IconTheme.get_default().load_icon("pepper", 48, 0);
-				}
-				catch (Error e)
-				{
-					loge(e.message);
-				}
+				loge("Toolchain directory does not exist or it is not a directory");
+				exit_status = 1;
+				return true;
 			}
 
-			this.active_window.show_all();
-			this.active_window.maximize();
+			_toolchain_dir = args[3];
 		}
-
-		protected override bool local_command_line(ref unowned string[] args, out int exit_status)
+		else
 		{
-			if (args.length > 1)
+			bool found = false;
+
+			/// More desirable paths come first
+			string toolchain_paths[] =
+			{
+				".",
+				"../..",
+				"../../../samples"
+			};
+
+			for (int i = 0; i < toolchain_paths.length; ++i)
 			{
-				if (!GLib.FileUtils.test(args[1], FileTest.EXISTS) || !GLib.FileUtils.test(args[1], FileTest.IS_DIR))
+				string path = Path.build_filename(toolchain_paths[i], "core");
+
+				// Try to locate the toolchain directory
+				if (GLib.FileUtils.test(path, FileTest.EXISTS) && GLib.FileUtils.test(path, FileTest.IS_DIR))
 				{
-					loge("Source directory does not exist or it is not a directory");
-					exit_status = 1;
-					return true;
+					_toolchain_dir = toolchain_paths[i];
+					found = true;
+					break;
 				}
-
-				_source_dir = args[1];
 			}
 
-			if (args.length > 2)
+			if (!found)
 			{
-				// Validation is done below after the Project object instantiation
-				_level_resource = args[2];
+				loge("Unable to find the toolchain directory");
+				exit_status = 1;
+				return true;
 			}
+		}
 
-			if (args.length > 3)
-			{
-				if (!GLib.FileUtils.test(args[3], FileTest.EXISTS) || !GLib.FileUtils.test(args[3], FileTest.IS_DIR))
-				{
-					loge("Toolchain directory does not exist or it is not a directory");
-					exit_status = 1;
-					return true;
-				}
+		exit_status = 0;
+		return false;
+	}
 
-				_toolchain_dir = args[3];
-			}
-			else
-			{
-				bool found = false;
+	protected override int command_line(ApplicationCommandLine command_line)
+	{
+		this.activate();
+		return 0;
+	}
 
-				/// More desirable paths come first
-				string toolchain_paths[] =
-				{
-					".",
-					"../..",
-					"../../../samples"
-				};
+	public ConsoleClient? current_selected_client()
+	{
+		if (_combo.get_active_id() == "editor")
+			return _editor;
+		else if (_combo.get_active_id() == "game")
+			return _game;
+		else
+			return null;
+	}
 
-				for (int i = 0; i < toolchain_paths.length; ++i)
-				{
-					string path = Path.build_filename(toolchain_paths[i], "core");
+	private void on_resource_browser_resource_selected(string type, string name)
+	{
+		_editor.send_script(LevelEditorApi.set_placeable(type, name));
+		activate_action("tool", new GLib.Variant.string("place"));
+	}
 
-					// Try to locate the toolchain directory
-					if (GLib.FileUtils.test(path, FileTest.EXISTS) && GLib.FileUtils.test(path, FileTest.IS_DIR))
-					{
-						_toolchain_dir = toolchain_paths[i];
-						found = true;
-						break;
-					}
-				}
+	private void on_compiler_connected(string address, int port)
+	{
+		logi("Connected to data_compiler@%s:%d".printf(address, port));
+		_compiler.receive_async();
+	}
 
-				if (!found)
-				{
-					loge("Unable to find the toolchain directory");
-					exit_status = 1;
-					return true;
-				}
-			}
+	private void on_compiler_disconnected()
+	{
+		logi("Disconnected from data_compiler");
+	}
 
-			exit_status = 0;
-			return false;
-		}
+	private void on_compiler_disconnected_unexpected()
+	{
+		on_compiler_disconnected();
 
-		protected override int command_line(ApplicationCommandLine command_line)
-		{
-			this.activate();
-			return 0;
-		}
+		stop_game();
+		stop_editor();
 
-		public ConsoleClient? current_selected_client()
-		{
-			if (_combo.get_active_id() == "editor")
-				return _editor;
-			else if (_combo.get_active_id() == "game")
-				return _game;
-			else
-				return null;
-		}
+		// Reset the callback
+		_data_compiler.finished(false);
 
-		private void on_resource_browser_resource_selected(string type, string name)
-		{
-			_editor.send_script(LevelEditorApi.set_placeable(type, name));
-			activate_action("tool", new GLib.Variant.string("place"));
-		}
+		_project_slide.show_widget(compiler_crashed_label());
+		_editor_slide.show_widget(compiler_crashed_label());
+		_inspector_slide.show_widget(compiler_crashed_label());
+	}
 
-		private void on_compiler_connected(string address, int port)
-		{
-			logi("Connected to data_compiler@%s:%d".printf(address, port));
-			_compiler.receive_async();
-		}
+	private void on_editor_connected(string address, int port)
+	{
+		logi("Connected to level_editor@%s:%d".printf(address, port));
+		_editor.receive_async();
+	}
 
-		private void on_compiler_disconnected()
-		{
-			logi("Disconnected from data_compiler");
-		}
+	private void on_editor_disconnected()
+	{
+		logi("Disconnected from editor");
+	}
 
-		private void on_compiler_disconnected_unexpected()
-		{
-			on_compiler_disconnected();
+	private void on_editor_disconnected_unexpected()
+	{
+		on_editor_disconnected();
 
-			stop_game();
-			stop_editor();
+		Gtk.Label label = new Gtk.Label(null);
+		label.set_markup("Something went wrong.\rTry to <a href=\"restart\">restart</a> this view.");
+		label.activate_link.connect(() => {
+			activate_action("restart", null);
+			return true;
+		});
+		_editor_slide.show_widget(label);
+	}
 
-			// Reset the callback
-			_data_compiler.finished(false);
+	private void on_game_connected(string address, int port)
+	{
+		logi("Connected to game@%s:%d".printf(address, port));
+		_game.receive_async();
+		_combo.set_active_id("game");
+	}
 
-			_project_slide.show_widget(compiler_crashed_label());
-			_editor_slide.show_widget(compiler_crashed_label());
-			_inspector_slide.show_widget(compiler_crashed_label());
-		}
+	private void on_game_disconnected()
+	{
+		logi("Disconnected from game");
+		_project.delete_garbage();
+		_combo.set_active_id("editor");
+		_toolbar_run.icon_name = "game-run";
+	}
 
-		private void on_editor_connected(string address, int port)
-		{
-			logi("Connected to level_editor@%s:%d".printf(address, port));
-			_editor.receive_async();
-		}
+	private void on_message_received(ConsoleClient client, uint8[] json)
+	{
+		Hashtable msg = JSON.decode(json) as Hashtable;
+		string msg_type = msg["type"] as string;
 
-		private void on_editor_disconnected()
+		if (msg_type == "message")
 		{
-			logi("Disconnected from editor");
+			log((string)msg["system"], (string)msg["severity"], (string)msg["message"]);
 		}
-
-		private void on_editor_disconnected_unexpected()
+		else if (msg_type == "add_file")
 		{
-			on_editor_disconnected();
+			string path = (string)msg["path"];
 
-			Gtk.Label label = new Gtk.Label(null);
-			label.set_markup("Something went wrong.\rTry to <a href=\"restart\">restart</a> this view.");
-			label.activate_link.connect(() => {
-				activate_action("restart", null);
-				return true;
-			});
-			_editor_slide.show_widget(label);
+			_project.add_file(path);
 		}
-
-		private void on_game_connected(string address, int port)
+		else if (msg_type == "remove_file")
 		{
-			logi("Connected to game@%s:%d".printf(address, port));
-			_game.receive_async();
-			_combo.set_active_id("game");
-		}
+			string path = (string)msg["path"];
 
-		private void on_game_disconnected()
+			_project.remove_file(path);
+		}
+		else if (msg_type == "add_tree")
 		{
-			logi("Disconnected from game");
-			_project.delete_garbage();
-			_combo.set_active_id("editor");
-			_toolbar_run.icon_name = "game-run";
+			string path = (string)msg["path"];
+
+			_project.add_tree(path);
 		}
+		else if (msg_type == "remove_tree")
+		{
+			string path = (string)msg["path"];
 
-		private void on_message_received(ConsoleClient client, uint8[] json)
+			_project.remove_tree(path);
+		}
+		else if (msg_type == "compile")
 		{
-			Hashtable msg = JSON.decode(json) as Hashtable;
-			string msg_type = msg["type"] as string;
+			// Guid id = Guid.parse((string)msg["id"]);
 
-			if (msg_type == "message")
+			if (msg.has_key("start"))
 			{
-				log((string)msg["system"], (string)msg["severity"], (string)msg["message"]);
+				// FIXME
 			}
-			else if (msg_type == "add_file")
+			else if (msg.has_key("success"))
 			{
-				string path = (string)msg["path"];
-
-				_project.add_file(path);
+				_data_compiler.finished((bool)msg["success"]);
 			}
-			else if (msg_type == "remove_file")
-			{
-				string path = (string)msg["path"];
+		}
+		else if (msg_type == "unit_spawned")
+		{
+			string id             = (string)           msg["id"];
+			string name           = (string)           msg["name"];
+			ArrayList<Value?> pos = (ArrayList<Value?>)msg["position"];
+			ArrayList<Value?> rot = (ArrayList<Value?>)msg["rotation"];
+			ArrayList<Value?> scl = (ArrayList<Value?>)msg["scale"];
 
-				_project.remove_file(path);
-			}
-			else if (msg_type == "add_tree")
-			{
-				string path = (string)msg["path"];
+			_level.on_unit_spawned(Guid.parse(id)
+				, name
+				, Vector3.from_array(pos)
+				, Quaternion.from_array(rot)
+				, Vector3.from_array(scl)
+				);
+		}
+		else if (msg_type == "sound_spawned")
+		{
+			string id             = (string)           msg["id"];
+			string name           = (string)           msg["name"];
+			ArrayList<Value?> pos = (ArrayList<Value?>)msg["position"];
+			ArrayList<Value?> rot = (ArrayList<Value?>)msg["rotation"];
+			ArrayList<Value?> scl = (ArrayList<Value?>)msg["scale"];
+			double range          = (double)           msg["range"];
+			double volume         = (double)           msg["volume"];
+			bool loop             = (bool)             msg["loop"];
+
+			_level.on_sound_spawned(Guid.parse(id)
+				, name
+				, Vector3.from_array(pos)
+				, Quaternion.from_array(rot)
+				, Vector3.from_array(scl)
+				, range
+				, volume
+				, loop
+				);
+		}
+		else if (msg_type == "move_objects")
+		{
+			Hashtable ids           = (Hashtable)msg["ids"];
+			Hashtable new_positions = (Hashtable)msg["new_positions"];
+			Hashtable new_rotations = (Hashtable)msg["new_rotations"];
+			Hashtable new_scales    = (Hashtable)msg["new_scales"];
 
-				_project.add_tree(path);
-			}
-			else if (msg_type == "remove_tree")
-			{
-				string path = (string)msg["path"];
+			ArrayList<string> keys = new ArrayList<string>.wrap(ids.keys.to_array());
+			keys.sort(Gee.Functions.get_compare_func_for(typeof(string)));
 
-				_project.remove_tree(path);
-			}
-			else if (msg_type == "compile")
-			{
-				// Guid id = Guid.parse((string)msg["id"]);
+			Guid[] n_ids             = new Guid[keys.size];
+			Vector3[] n_positions    = new Vector3[keys.size];
+			Quaternion[] n_rotations = new Quaternion[keys.size];
+			Vector3[] n_scales       = new Vector3[keys.size];
 
-				if (msg.has_key("start"))
-				{
-					// FIXME
-				}
-				else if (msg.has_key("success"))
-				{
-					_data_compiler.finished((bool)msg["success"]);
-				}
-			}
-			else if (msg_type == "unit_spawned")
-			{
-				string id             = (string)           msg["id"];
-				string name           = (string)           msg["name"];
-				ArrayList<Value?> pos = (ArrayList<Value?>)msg["position"];
-				ArrayList<Value?> rot = (ArrayList<Value?>)msg["rotation"];
-				ArrayList<Value?> scl = (ArrayList<Value?>)msg["scale"];
-
-				_level.on_unit_spawned(Guid.parse(id)
-					, name
-					, Vector3.from_array(pos)
-					, Quaternion.from_array(rot)
-					, Vector3.from_array(scl)
-					);
-			}
-			else if (msg_type == "sound_spawned")
+			for (int i = 0; i < keys.size; ++i)
 			{
-				string id             = (string)           msg["id"];
-				string name           = (string)           msg["name"];
-				ArrayList<Value?> pos = (ArrayList<Value?>)msg["position"];
-				ArrayList<Value?> rot = (ArrayList<Value?>)msg["rotation"];
-				ArrayList<Value?> scl = (ArrayList<Value?>)msg["scale"];
-				double range          = (double)           msg["range"];
-				double volume         = (double)           msg["volume"];
-				bool loop             = (bool)             msg["loop"];
-
-				_level.on_sound_spawned(Guid.parse(id)
-					, name
-					, Vector3.from_array(pos)
-					, Quaternion.from_array(rot)
-					, Vector3.from_array(scl)
-					, range
-					, volume
-					, loop
-					);
-			}
-			else if (msg_type == "move_objects")
-			{
-				Hashtable ids           = (Hashtable)msg["ids"];
-				Hashtable new_positions = (Hashtable)msg["new_positions"];
-				Hashtable new_rotations = (Hashtable)msg["new_rotations"];
-				Hashtable new_scales    = (Hashtable)msg["new_scales"];
-
-				ArrayList<string> keys = new ArrayList<string>.wrap(ids.keys.to_array());
-				keys.sort(Gee.Functions.get_compare_func_for(typeof(string)));
-
-				Guid[] n_ids             = new Guid[keys.size];
-				Vector3[] n_positions    = new Vector3[keys.size];
-				Quaternion[] n_rotations = new Quaternion[keys.size];
-				Vector3[] n_scales       = new Vector3[keys.size];
-
-				for (int i = 0; i < keys.size; ++i)
-				{
-					string k = keys[i];
-
-					n_ids[i]       = Guid.parse((string)ids[k]);
-					n_positions[i] = Vector3.from_array((ArrayList<Value?>)(new_positions[k]));
-					n_rotations[i] = Quaternion.from_array((ArrayList<Value?>)new_rotations[k]);
-					n_scales[i]    = Vector3.from_array((ArrayList<Value?>)new_scales[k]);
-				}
+				string k = keys[i];
 
-				_level.on_move_objects(n_ids, n_positions, n_rotations, n_scales);
+				n_ids[i]       = Guid.parse((string)ids[k]);
+				n_positions[i] = Vector3.from_array((ArrayList<Value?>)(new_positions[k]));
+				n_rotations[i] = Quaternion.from_array((ArrayList<Value?>)new_rotations[k]);
+				n_scales[i]    = Vector3.from_array((ArrayList<Value?>)new_scales[k]);
 			}
-			else if (msg_type == "selection")
-			{
-				Hashtable objects = (Hashtable)msg["objects"];
 
-				ArrayList<string> keys = new ArrayList<string>.wrap(objects.keys.to_array());
-				keys.sort(Gee.Functions.get_compare_func_for(typeof(string)));
+			_level.on_move_objects(n_ids, n_positions, n_rotations, n_scales);
+		}
+		else if (msg_type == "selection")
+		{
+			Hashtable objects = (Hashtable)msg["objects"];
 
-				Guid[] ids = new Guid[keys.size];
+			ArrayList<string> keys = new ArrayList<string>.wrap(objects.keys.to_array());
+			keys.sort(Gee.Functions.get_compare_func_for(typeof(string)));
 
-				for (int i = 0; i < keys.size; ++i)
-				{
-					string k = keys[i];
-					ids[i] = Guid.parse((string)objects[k]);
-				}
+			Guid[] ids = new Guid[keys.size];
 
-				_level.on_selection(ids);
-			}
-			else if (msg_type == "error")
-			{
-				loge((string)msg["message"]);
-			}
-			else
+			for (int i = 0; i < keys.size; ++i)
 			{
-				loge("Unknown message type: " + msg_type);
+				string k = keys[i];
+				ids[i] = Guid.parse((string)objects[k]);
 			}
 
-			// Receive next message
-			client.receive_async();
+			_level.on_selection(ids);
 		}
-
-		private void send_state()
+		else if (msg_type == "error")
 		{
-			StringBuilder sb = new StringBuilder();
-			sb.append(LevelEditorApi.set_grid_size(_grid_size));
-			sb.append(LevelEditorApi.set_rotation_snap(_rotation_snap));
-			sb.append(LevelEditorApi.enable_show_grid(_show_grid));
-			sb.append(LevelEditorApi.enable_snap_to_grid(_snap_to_grid));
-			sb.append(LevelEditorApi.enable_debug_render_world(_debug_render_world));
-			sb.append(LevelEditorApi.enable_debug_physics_world(_debug_physics_world));
-			sb.append(LevelEditorApi.set_tool_type(_tool_type));
-			sb.append(LevelEditorApi.set_snap_mode(_snap_mode));
-			sb.append(LevelEditorApi.set_reference_system(_reference_system));
-			_editor.send_script(sb.str);
+			loge((string)msg["message"]);
 		}
-
-		private bool on_button_press(Gdk.EventButton ev)
+		else
 		{
-			return Gdk.EVENT_STOP;
+			loge("Unknown message type: " + msg_type);
 		}
 
-		private bool on_button_release(Gdk.EventButton ev)
-		{
-			return Gdk.EVENT_STOP;
-		}
+		// Receive next message
+		client.receive_async();
+	}
 
-		Gtk.Widget starting_compiler_label()
-		{
-			return new Gtk.Label("Compiling resources, please wait...");
-		}
+	private void send_state()
+	{
+		StringBuilder sb = new StringBuilder();
+		sb.append(LevelEditorApi.set_grid_size(_grid_size));
+		sb.append(LevelEditorApi.set_rotation_snap(_rotation_snap));
+		sb.append(LevelEditorApi.enable_show_grid(_show_grid));
+		sb.append(LevelEditorApi.enable_snap_to_grid(_snap_to_grid));
+		sb.append(LevelEditorApi.enable_debug_render_world(_debug_render_world));
+		sb.append(LevelEditorApi.enable_debug_physics_world(_debug_physics_world));
+		sb.append(LevelEditorApi.set_tool_type(_tool_type));
+		sb.append(LevelEditorApi.set_snap_mode(_snap_mode));
+		sb.append(LevelEditorApi.set_reference_system(_reference_system));
+		_editor.send_script(sb.str);
+	}
 
-		Gtk.Widget compiler_crashed_label()
-		{
-			Gtk.Label label = new Gtk.Label(null);
-			label.set_markup("Data Compiler disconnected.\rTry to <a href=\"restart\">restart</a> compiler to continue.");
-			label.activate_link.connect(() => {
-				restart_compiler(_project.source_dir(), _level._filename);
-				return true;
-			});
+	private bool on_button_press(Gdk.EventButton ev)
+	{
+		return Gdk.EVENT_STOP;
+	}
 
-			return label;
-		}
+	private bool on_button_release(Gdk.EventButton ev)
+	{
+		return Gdk.EVENT_STOP;
+	}
 
-		Gtk.Widget compiler_failed_compilation_label()
-		{
-			Gtk.Label label = new Gtk.Label(null);
-			label.set_markup("Data compilation failed.\rFix errors and <a href=\"restart\">restart</a> compiler to continue.");
-			label.activate_link.connect(() => {
-				restart_compiler(_project.source_dir(), _level._filename);
-				return true;
-			});
+	Gtk.Widget starting_compiler_label()
+	{
+		return new Gtk.Label("Compiling resources, please wait...");
+	}
 
-			return label;
-		}
+	Gtk.Widget compiler_crashed_label()
+	{
+		Gtk.Label label = new Gtk.Label(null);
+		label.set_markup("Data Compiler disconnected.\rTry to <a href=\"restart\">restart</a> compiler to continue.");
+		label.activate_link.connect(() => {
+			restart_compiler(_project.source_dir(), _level._filename);
+			return true;
+		});
 
-		public void restart_compiler(string source_dir, string? level_resource)
-		{
-			stop_compiler();
+		return label;
+	}
 
-			_project.load(source_dir);
-			if (level_resource != null)
-				_level.load(Path.build_filename(_project.source_dir(), level_resource + ".level"));
-			else
-				_level.load_empty_level();
+	Gtk.Widget compiler_failed_compilation_label()
+	{
+		Gtk.Label label = new Gtk.Label(null);
+		label.set_markup("Data compilation failed.\rFix errors and <a href=\"restart\">restart</a> compiler to continue.");
+		label.activate_link.connect(() => {
+			restart_compiler(_project.source_dir(), _level._filename);
+			return true;
+		});
 
-			_project_slide.show_widget(starting_compiler_label());
-			_editor_slide.show_widget(starting_compiler_label());
-			_inspector_slide.show_widget(starting_compiler_label());
+		return label;
+	}
 
-			string args[] =
-			{
-				ENGINE_EXE,
-				"--source-dir", _project.source_dir(),
-				"--data-dir", _project.data_dir(),
-				"--map-source-dir", "core", _project.toolchain_dir(),
-				"--server",
-				"--wait-console",
-				null
-			};
-
-			GLib.SubprocessLauncher sl = new GLib.SubprocessLauncher(subprocess_flags());
-			sl.set_cwd(ENGINE_DIR);
-			try
-			{
-				_compiler_process = sl.spawnv(args);
-			}
-			catch (Error e)
-			{
-				loge(e.message);
-			}
+	public void restart_compiler(string source_dir, string? level_resource)
+	{
+		stop_compiler();
 
-			// It is an error if compiler disconnects after here.
-			_compiler.disconnected.disconnect(on_compiler_disconnected);
-			_compiler.disconnected.connect(on_compiler_disconnected_unexpected);
+		_project.load(source_dir);
+		if (level_resource != null)
+			_level.load(Path.build_filename(_project.source_dir(), level_resource + ".level"));
+		else
+			_level.load_empty_level();
 
-			for (int tries = 0; !_compiler.is_connected() && tries < 5; ++tries)
-			{
-				_compiler.connect("127.0.0.1", CROWN_DEFAULT_SERVER_PORT);
-				GLib.Thread.usleep(250*1000);
-			}
+		_project_slide.show_widget(starting_compiler_label());
+		_editor_slide.show_widget(starting_compiler_label());
+		_inspector_slide.show_widget(starting_compiler_label());
 
-			_data_compiler.compile.begin(_project.data_dir(), _project.platform(), (obj, res) => {
-				if (_data_compiler.compile.end(res))
-				{
-					restart_editor();
+		string args[] =
+		{
+			ENGINE_EXE,
+			"--source-dir", _project.source_dir(),
+			"--data-dir", _project.data_dir(),
+			"--map-source-dir", "core", _project.toolchain_dir(),
+			"--server",
+			"--wait-console",
+			null
+		};
 
-					_project_slide.show_widget(_project_browser);
-					_inspector_slide.show_widget(_inspector_pane);
-				}
-				else
-				{
-					_project_slide.show_widget(compiler_failed_compilation_label());
-					_editor_slide.show_widget(compiler_failed_compilation_label());
-					_inspector_slide.show_widget(compiler_failed_compilation_label());
-				}
-			});
+		GLib.SubprocessLauncher sl = new GLib.SubprocessLauncher(subprocess_flags());
+		sl.set_cwd(ENGINE_DIR);
+		try
+		{
+			_compiler_process = sl.spawnv(args);
 		}
-
-		private void stop_compiler()
+		catch (Error e)
 		{
-			_level.reset();
-			_project.reset();
+			loge(e.message);
+		}
 
-			stop_game();
-			stop_editor();
+		// It is an error if compiler disconnects after here.
+		_compiler.disconnected.disconnect(on_compiler_disconnected);
+		_compiler.disconnected.connect(on_compiler_disconnected_unexpected);
+
+		for (int tries = 0; !_compiler.is_connected() && tries < 5; ++tries)
+		{
+			_compiler.connect("127.0.0.1", CROWN_DEFAULT_SERVER_PORT);
+			GLib.Thread.usleep(250*1000);
+		}
 
-			if (_compiler != null)
+		_data_compiler.compile.begin(_project.data_dir(), _project.platform(), (obj, res) => {
+			if (_data_compiler.compile.end(res))
 			{
-				// Explicit call to this function should not produce error messages.
-				_compiler.disconnected.disconnect(on_compiler_disconnected_unexpected);
-				_compiler.disconnected.connect(on_compiler_disconnected);
+				restart_editor();
 
-				_compiler.send(DataCompilerApi.quit());
-				_compiler.close();
+				_project_slide.show_widget(_project_browser);
+				_inspector_slide.show_widget(_inspector_pane);
 			}
-
-			if (_compiler_process != null)
+			else
 			{
-				try
-				{
-					_compiler_process.wait();
-				}
-				catch (Error e)
-				{
-					loge(e.message);
-				}
+				_project_slide.show_widget(compiler_failed_compilation_label());
+				_editor_slide.show_widget(compiler_failed_compilation_label());
+				_inspector_slide.show_widget(compiler_failed_compilation_label());
 			}
-		}
+		});
+	}
+
+	private void stop_compiler()
+	{
+		_level.reset();
+		_project.reset();
 
-		private void start_editor(uint window_xid)
+		stop_game();
+		stop_editor();
+
+		if (_compiler != null)
 		{
-			if (window_xid == 0)
-				return;
+			// Explicit call to this function should not produce error messages.
+			_compiler.disconnected.disconnect(on_compiler_disconnected_unexpected);
+			_compiler.disconnected.connect(on_compiler_disconnected);
 
-			string args[] =
-			{
-				ENGINE_EXE,
-				"--data-dir", _project.data_dir(),
-				"--boot-dir", LEVEL_EDITOR_BOOT_DIR,
-				"--parent-window", window_xid.to_string(),
-				"--wait-console",
-				null
-			};
+			_compiler.send(DataCompilerApi.quit());
+			_compiler.close();
+		}
 
-			GLib.SubprocessLauncher sl = new GLib.SubprocessLauncher(subprocess_flags());
-			sl.set_cwd(ENGINE_DIR);
+		if (_compiler_process != null)
+		{
 			try
 			{
-				_editor_process = sl.spawnv(args);
+				_compiler_process.wait();
 			}
 			catch (Error e)
 			{
 				loge(e.message);
 			}
-
-			// It is an error if editor disconnects after here.
-			_editor.disconnected.disconnect(on_editor_disconnected);
-			_editor.disconnected.connect(on_editor_disconnected_unexpected);
-
-			for (int tries = 0; !_editor.is_connected() && tries < 10; ++tries)
-			{
-				_editor.connect("127.0.0.1", 10001);
-				GLib.Thread.usleep(500*1000);
-			}
-
-			_level.send_level();
-			send_state();
-			_preferences_dialog.apply();
 		}
+	}
 
-		private void stop_editor()
-		{
-			_resource_chooser.stop_editor();
-
-			if (_editor != null)
-			{
-				// Explicit call to this function should not produce error messages.
-				_editor.disconnected.disconnect(on_editor_disconnected_unexpected);
-				_editor.disconnected.connect(on_editor_disconnected);
-
-				_editor.send_script("Device.quit()");
-				_editor.close();
-			}
+	private void start_editor(uint window_xid)
+	{
+		if (window_xid == 0)
+			return;
 
-			if (_editor_process != null)
-			{
-				try
-				{
-					_editor_process.wait();
-				}
-				catch (Error e)
-				{
-					loge(e.message);
-				}
-			}
+		string args[] =
+		{
+			ENGINE_EXE,
+			"--data-dir", _project.data_dir(),
+			"--boot-dir", LEVEL_EDITOR_BOOT_DIR,
+			"--parent-window", window_xid.to_string(),
+			"--wait-console",
+			null
+		};
 
-			_editor_slide.show_widget(new Gtk.Label("Disconnected."));
+		GLib.SubprocessLauncher sl = new GLib.SubprocessLauncher(subprocess_flags());
+		sl.set_cwd(ENGINE_DIR);
+		try
+		{
+			_editor_process = sl.spawnv(args);
 		}
-
-		private void restart_editor()
+		catch (Error e)
 		{
-			stop_editor();
+			loge(e.message);
+		}
 
-			if (_editor_view != null)
-			{
-				_editor_view_overlay.remove(_editor_view);
-				_editor_view = null;
-			}
+		// It is an error if editor disconnects after here.
+		_editor.disconnected.disconnect(on_editor_disconnected);
+		_editor.disconnected.connect(on_editor_disconnected_unexpected);
 
-			_editor_view = new EditorView(_editor);
-			_editor_view.realized.connect(on_editor_view_realized);
-			_editor_view.button_press_event.connect(on_button_press);
-			_editor_view.button_release_event.connect(on_button_release);
+		for (int tries = 0; !_editor.is_connected() && tries < 10; ++tries)
+		{
+			_editor.connect("127.0.0.1", 10001);
+			GLib.Thread.usleep(500*1000);
+		}
 
-			_editor_view_overlay.add(_editor_view);
-			_editor_slide.show_widget(_editor_view_overlay);
+		_level.send_level();
+		send_state();
+		_preferences_dialog.apply();
+	}
 
-			_resource_chooser.restart_editor();
-		}
+	private void stop_editor()
+	{
+		_resource_chooser.stop_editor();
 
-		private void start_game(StartGame sg)
+		if (_editor != null)
 		{
-			_project.dump_test_level(_database);
+			// Explicit call to this function should not produce error messages.
+			_editor.disconnected.disconnect(on_editor_disconnected_unexpected);
+			_editor.disconnected.connect(on_editor_disconnected);
 
-			_data_compiler.compile.begin(_project.data_dir(), _project.platform(), (obj, res) => {
-				if (_data_compiler.compile.end(res))
-				{
-					string args[] =
-					{
-						ENGINE_EXE,
-						"--data-dir", _project.data_dir(),
-						"--console-port", "12345",
-						"--wait-console",
-						"--lua-string", sg == StartGame.TEST ? "TEST=true" : "",
-						null
-					};
-
-					GLib.SubprocessLauncher sl = new GLib.SubprocessLauncher(subprocess_flags());
-					sl.set_cwd(ENGINE_DIR);
-
-					try
-					{
-						_game_process = sl.spawnv(args);
-					}
-					catch (Error e)
-					{
-						loge(e.message);
-					}
-
-					for (int tries = 0; !_game.is_connected() && tries < 10; ++tries)
-					{
-						_game.connect("127.0.0.1", 12345);
-						GLib.Thread.usleep(500*1000);
-					}
-				}
-				else
-				{
-					_toolbar_run.icon_name = "game-run";
-				}
-			});
+			_editor.send_script("Device.quit()");
+			_editor.close();
 		}
 
-		private void stop_game()
+		if (_editor_process != null)
 		{
-			if (_game != null)
+			try
 			{
-				_game.send_script("Device.quit()");
-				_game.close();
+				_editor_process.wait();
 			}
-
-			if (_game_process != null)
+			catch (Error e)
 			{
-				try
-				{
-					_game_process.wait();
-				}
-				catch (Error e)
-				{
-					loge(e.message);
-				}
+				loge(e.message);
 			}
 		}
 
-		private void deploy_game()
+		_editor_slide.show_widget(new Gtk.Label("Disconnected."));
+	}
+
+	private void restart_editor()
+	{
+		stop_editor();
+
+		if (_editor_view != null)
 		{
-			Gtk.FileChooserDialog fcd = new Gtk.FileChooserDialog("Select destination directory..."
-				, this.active_window
-				, FileChooserAction.SELECT_FOLDER
-				, "Cancel"
-				, ResponseType.CANCEL
-				, "Open"
-				, ResponseType.ACCEPT
-				);
+			_editor_view_overlay.remove(_editor_view);
+			_editor_view = null;
+		}
 
-			if (fcd.run() == ResponseType.ACCEPT)
-			{
-				GLib.File data_dir = File.new_for_path(fcd.get_filename());
+		_editor_view = new EditorView(_editor);
+		_editor_view.realized.connect(on_editor_view_realized);
+		_editor_view.button_press_event.connect(on_button_press);
+		_editor_view.button_release_event.connect(on_button_release);
+
+		_editor_view_overlay.add(_editor_view);
+		_editor_slide.show_widget(_editor_view_overlay);
+
+		_resource_chooser.restart_editor();
+	}
+
+	private void start_game(StartGame sg)
+	{
+		_project.dump_test_level(_database);
 
+		_data_compiler.compile.begin(_project.data_dir(), _project.platform(), (obj, res) => {
+			if (_data_compiler.compile.end(res))
+			{
 				string args[] =
 				{
 					ENGINE_EXE,
-					"--source-dir", _project.source_dir(),
-					"--map-source-dir", "core", _project.toolchain_dir(),
-					"--data-dir", data_dir.get_path(),
-					"--compile",
+					"--data-dir", _project.data_dir(),
+					"--console-port", "12345",
+					"--wait-console",
+					"--lua-string", sg == StartGame.TEST ? "TEST=true" : "",
 					null
 				};
 
 				GLib.SubprocessLauncher sl = new GLib.SubprocessLauncher(subprocess_flags());
 				sl.set_cwd(ENGINE_DIR);
+
 				try
 				{
-					GLib.Subprocess compiler = sl.spawnv(args);
-					compiler.wait();
-					if (compiler.get_exit_status() == 0)
-					{
-						string game_name = DEPLOY_DEFAULT_NAME;
-						GLib.File engine_exe_src = File.new_for_path(DEPLOY_EXE);
-						GLib.File engine_exe_dst = File.new_for_path(Path.build_filename(data_dir.get_path(), game_name + EXE_SUFFIX));
-						engine_exe_src.copy(engine_exe_dst, FileCopyFlags.OVERWRITE);
-
-#if CROWN_PLATFORM_WINDOWS
-						string lua51_name = "lua51.dll";
-						GLib.File lua51_dll_src = File.new_for_path(lua51_name);
-						GLib.File lua51_dll_dst = File.new_for_path(Path.build_filename(data_dir.get_path(), lua51_name));
-						lua51_dll_src.copy(lua51_dll_dst, FileCopyFlags.OVERWRITE);
-
-						string openal_name = "openal-release.dll";
-						GLib.File openal_dll_src = File.new_for_path(openal_name);
-						GLib.File openal_dll_dst = File.new_for_path(Path.build_filename(data_dir.get_path(), openal_name));
-						openal_dll_src.copy(openal_dll_dst, FileCopyFlags.OVERWRITE);
-#endif // CROWN_PLATFORM_WINDOWS
-
-						logi("Project deployed to `%s`".printf(data_dir.get_path()));
-					}
+					_game_process = sl.spawnv(args);
 				}
 				catch (Error e)
 				{
-					logi("%s".printf(e.message));
-					logi("Failed to deploy project");
+					loge(e.message);
 				}
-			}
-
-			fcd.destroy();
-		}
-
-		private void on_editor_view_realized()
-		{
-			start_editor(_editor_view.window_id);
-		}
-
-		private void on_tool_changed(GLib.SimpleAction action, GLib.Variant? param)
-		{
-			string name = param.get_string();
-			if (name == "place")
-				_tool_type = LevelEditorApi.ToolType.PLACE;
-			else if (name == "move")
-				_tool_type = LevelEditorApi.ToolType.MOVE;
-			else if (name == "rotate")
-				_tool_type = LevelEditorApi.ToolType.ROTATE;
-			else if (name == "scale")
-				_tool_type = LevelEditorApi.ToolType.SCALE;
-
-			send_state();
-			action.set_state(param);
-		}
-
-		private void on_snap_mode_changed(GLib.SimpleAction action, GLib.Variant? param)
-		{
-			string name = param.get_string();
-			if (name == "relative")
-				_snap_mode = LevelEditorApi.SnapMode.RELATIVE;
-			else if (name == "absolute")
-				_snap_mode = LevelEditorApi.SnapMode.ABSOLUTE;
-
-			send_state();
-			action.set_state(param);
-		}
-
-		private void on_reference_system_changed(GLib.SimpleAction action, GLib.Variant? param)
-		{
-			string name = param.get_string();
-			if (name == "local")
-				_reference_system = LevelEditorApi.ReferenceSystem.LOCAL;
-			else if (name == "world")
-				_reference_system = LevelEditorApi.ReferenceSystem.WORLD;
-
-			send_state();
-			action.set_state(param);
-		}
-
-		private void on_grid_changed(GLib.SimpleAction action, GLib.Variant? param)
-		{
-			_grid_size = float.parse(param.get_string());
-			send_state();
-			action.set_state(param);
-		}
-
-		private void on_rotation_snap_changed(GLib.SimpleAction action, GLib.Variant? param)
-		{
-			_rotation_snap = float.parse(param.get_string());
-			send_state();
-			action.set_state(param);
-		}
-
-		private void new_level()
-		{
-			_level.load_empty_level();
-			_level.send_level();
-		}
-
-		private void load_level(string level)
-		{
-			string filename = level;
-
-			if (filename == "")
-			{
-				Gtk.FileChooserDialog fcd = new Gtk.FileChooserDialog("Open Level..."
-					, this.active_window
-					, FileChooserAction.OPEN
-					, "Cancel"
-					, ResponseType.CANCEL
-					, "Open"
-					, ResponseType.ACCEPT
-					);
-				fcd.add_filter(_file_filter);
-				fcd.set_current_folder(_project.source_dir());
-
-				if (fcd.run() == ResponseType.ACCEPT)
-					filename = fcd.get_filename();
 
-				fcd.destroy();
+				for (int tries = 0; !_game.is_connected() && tries < 10; ++tries)
+				{
+					_game.connect("127.0.0.1", 12345);
+					GLib.Thread.usleep(500*1000);
+				}
 			}
-
-			if (filename == "")
-				return;
-
-			if (!_project.path_is_within_dir(filename, _project.source_dir()))
+			else
 			{
-				loge("File must be within `%s`".printf(_project.source_dir()));
-				return;
+				_toolbar_run.icon_name = "game-run";
 			}
+		});
+	}
 
-			if (filename.has_suffix(".level") && filename != _level._filename)
-			{
-				_level.load(filename);
-				_level.send_level();
-				send_state();
-			}
+	private void stop_game()
+	{
+		if (_game != null)
+		{
+			_game.send_script("Device.quit()");
+			_game.close();
 		}
 
-		private bool save_as(string? filename)
+		if (_game_process != null)
 		{
-			string path;
-
-			if (filename != null)
-			{
-				path = filename;
-			}
-			else
+			try
 			{
-				Gtk.FileChooserDialog fcd = new Gtk.FileChooserDialog("Save As..."
-					, this.active_window
-					, FileChooserAction.SAVE
-					, "Cancel"
-					, ResponseType.CANCEL
-					, "Save"
-					, ResponseType.ACCEPT
-					);
-				fcd.add_filter(_file_filter);
-				fcd.set_current_folder(_project.source_dir());
-				int rt = fcd.run();
-
-				if (rt != ResponseType.ACCEPT)
-				{
-					fcd.destroy();
-					return false;
-				}
-
-				path = fcd.get_filename();
-				fcd.destroy();
+				_game_process.wait();
 			}
-
-			if (!_project.path_is_within_dir(path, _project.source_dir()))
+			catch (Error e)
 			{
-				loge("File must be within `%s`".printf(_project.source_dir()));
-				return false;
+				loge(e.message);
 			}
-
-			_level.save(path.has_suffix(".level") ? path : path + ".level");
-			_statusbar.set_temporary_message("Saved %s".printf(_level._filename));
-			return true;
-		}
-
-		private bool save()
-		{
-			return save_as(_level._filename);
-		}
-
-		private bool save_timeout()
-		{
-			if (_level._filename != null)
-				save();
-
-			return true;
-		}
-
-		public void close_all()
-		{
-			_user.save(_user_file.get_path());
-			save_settings();
-
-			if (_save_timer_id > 0)
-				GLib.Source.remove(_save_timer_id);
-
-			if (_resource_chooser != null)
-				_resource_chooser.destroy();
-
-			if (_preferences_dialog != null)
-				_preferences_dialog.destroy();
-
-			stop_compiler();
 		}
+	}
 
-		protected override void shutdown()
-		{
-			base.shutdown();
-		}
+	private void deploy_game()
+	{
+		Gtk.FileChooserDialog fcd = new Gtk.FileChooserDialog("Select destination directory..."
+			, this.active_window
+			, FileChooserAction.SELECT_FOLDER
+			, "Cancel"
+			, ResponseType.CANCEL
+			, "Open"
+			, ResponseType.ACCEPT
+			);
 
-		// Returns true if the level has been saved or the user decided it
-		// should be discarded.
-		public bool should_quit()
+		if (fcd.run() == ResponseType.ACCEPT)
 		{
-			int rt = ResponseType.YES;
+			GLib.File data_dir = File.new_for_path(fcd.get_filename());
 
-			if (_database.changed())
+			string args[] =
 			{
-				DialogLevelChanged lc = new DialogLevelChanged(this.active_window);
-				rt = lc.run();
-				lc.destroy();
-			}
-
-			if (!_database.changed() || rt == ResponseType.YES && save() || rt == ResponseType.NO)
-				return true;
+				ENGINE_EXE,
+				"--source-dir", _project.source_dir(),
+				"--map-source-dir", "core", _project.toolchain_dir(),
+				"--data-dir", data_dir.get_path(),
+				"--compile",
+				null
+			};
 
-			return false;
-		}
+			GLib.SubprocessLauncher sl = new GLib.SubprocessLauncher(subprocess_flags());
+			sl.set_cwd(ENGINE_DIR);
+			try
+			{
+				GLib.Subprocess compiler = sl.spawnv(args);
+				compiler.wait();
+				if (compiler.get_exit_status() == 0)
+				{
+					string game_name = DEPLOY_DEFAULT_NAME;
+					GLib.File engine_exe_src = File.new_for_path(DEPLOY_EXE);
+					GLib.File engine_exe_dst = File.new_for_path(Path.build_filename(data_dir.get_path(), game_name + EXE_SUFFIX));
+					engine_exe_src.copy(engine_exe_dst, FileCopyFlags.OVERWRITE);
 
-		private void on_new_level(GLib.SimpleAction action, GLib.Variant? param)
-		{
-			int rt = ResponseType.YES;
+#if CROWN_PLATFORM_WINDOWS
+					string lua51_name = "lua51.dll";
+					GLib.File lua51_dll_src = File.new_for_path(lua51_name);
+					GLib.File lua51_dll_dst = File.new_for_path(Path.build_filename(data_dir.get_path(), lua51_name));
+					lua51_dll_src.copy(lua51_dll_dst, FileCopyFlags.OVERWRITE);
+
+					string openal_name = "openal-release.dll";
+					GLib.File openal_dll_src = File.new_for_path(openal_name);
+					GLib.File openal_dll_dst = File.new_for_path(Path.build_filename(data_dir.get_path(), openal_name));
+					openal_dll_src.copy(openal_dll_dst, FileCopyFlags.OVERWRITE);
+#endif // CROWN_PLATFORM_WINDOWS
 
-			if (_database.changed())
-			{
-				DialogLevelChanged lc = new DialogLevelChanged(this.active_window);
-				rt = lc.run();
-				lc.destroy();
+					logi("Project deployed to `%s`".printf(data_dir.get_path()));
+				}
 			}
-
-			if (!_database.changed() || rt == ResponseType.YES && save() || rt == ResponseType.NO)
+			catch (Error e)
 			{
-				new_level();
-				send_state();
+				logi("%s".printf(e.message));
+				logi("Failed to deploy project");
 			}
 		}
 
-		private void on_open_level(GLib.SimpleAction action, GLib.Variant? param)
-		{
-			int rt = ResponseType.YES;
+		fcd.destroy();
+	}
 
-			if (_level._filename == param.get_string())
-				return;
+	private void on_editor_view_realized()
+	{
+		start_editor(_editor_view.window_id);
+	}
 
-			if (_database.changed())
-			{
-				DialogLevelChanged lc = new DialogLevelChanged(this.active_window);
-				rt = lc.run();
-				lc.destroy();
-			}
+	private void on_tool_changed(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		string name = param.get_string();
+		if (name == "place")
+			_tool_type = LevelEditorApi.ToolType.PLACE;
+		else if (name == "move")
+			_tool_type = LevelEditorApi.ToolType.MOVE;
+		else if (name == "rotate")
+			_tool_type = LevelEditorApi.ToolType.ROTATE;
+		else if (name == "scale")
+			_tool_type = LevelEditorApi.ToolType.SCALE;
 
-			if (!_database.changed() || rt == ResponseType.YES && save() || rt == ResponseType.NO)
-				load_level(param.get_string());
-		}
+		send_state();
+		action.set_state(param);
+	}
 
-		private void on_open_project(GLib.SimpleAction action, GLib.Variant? param)
-		{
-			int rt = ResponseType.YES;
+	private void on_snap_mode_changed(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		string name = param.get_string();
+		if (name == "relative")
+			_snap_mode = LevelEditorApi.SnapMode.RELATIVE;
+		else if (name == "absolute")
+			_snap_mode = LevelEditorApi.SnapMode.ABSOLUTE;
 
-			if (_database.changed())
-			{
-				DialogLevelChanged lc = new DialogLevelChanged(this.active_window);
-				rt = lc.run();
-				lc.destroy();
-			}
+		send_state();
+		action.set_state(param);
+	}
 
-			if (!_database.changed() || rt == ResponseType.YES && save() || rt == ResponseType.NO)
-			{
-				DialogOpenProject op = new DialogOpenProject(this.active_window);
-				rt = op.run();
-				if (rt != ResponseType.ACCEPT)
-				{
-					op.destroy();
-					return;
-				}
+	private void on_reference_system_changed(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		string name = param.get_string();
+		if (name == "local")
+			_reference_system = LevelEditorApi.ReferenceSystem.LOCAL;
+		else if (name == "world")
+			_reference_system = LevelEditorApi.ReferenceSystem.WORLD;
 
-				string source_dir = op.get_filename();
-				op.destroy();
+		send_state();
+		action.set_state(param);
+	}
 
-				if (_project.source_dir() == source_dir)
-					return;
+	private void on_grid_changed(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		_grid_size = float.parse(param.get_string());
+		send_state();
+		action.set_state(param);
+	}
 
-				logi("Loading project: `%s`...".printf(source_dir));
-				restart_compiler(source_dir, null);
-			}
-		}
+	private void on_rotation_snap_changed(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		_rotation_snap = float.parse(param.get_string());
+		send_state();
+		action.set_state(param);
+	}
 
-		private void on_new_project(GLib.SimpleAction action, GLib.Variant? param)
+	private void new_level()
+	{
+		_level.load_empty_level();
+		_level.send_level();
+	}
+
+	private void load_level(string level)
+	{
+		string filename = level;
+
+		if (filename == "")
 		{
-			int rt = ResponseType.YES;
+			Gtk.FileChooserDialog fcd = new Gtk.FileChooserDialog("Open Level..."
+				, this.active_window
+				, FileChooserAction.OPEN
+				, "Cancel"
+				, ResponseType.CANCEL
+				, "Open"
+				, ResponseType.ACCEPT
+				);
+			fcd.add_filter(_file_filter);
+			fcd.set_current_folder(_project.source_dir());
 
-			if (_database.changed())
-			{
-				DialogLevelChanged lc = new DialogLevelChanged(this.active_window);
-				rt = lc.run();
-				lc.destroy();
-			}
+			if (fcd.run() == ResponseType.ACCEPT)
+				filename = fcd.get_filename();
 
-			if (!_database.changed() || rt == ResponseType.YES && save() || rt == ResponseType.NO)
-			{
-				stop_compiler();
-				show_panel("panel_new_project");
-			}
+			fcd.destroy();
 		}
 
-		private void on_save(GLib.SimpleAction action, GLib.Variant? param)
-		{
-			save();
-		}
+		if (filename == "")
+			return;
 
-		private void on_save_as(GLib.SimpleAction action, GLib.Variant? param)
+		if (!_project.path_is_within_dir(filename, _project.source_dir()))
 		{
-			save_as(null);
+			loge("File must be within `%s`".printf(_project.source_dir()));
+			return;
 		}
 
-		private void on_import(GLib.SimpleAction action, GLib.Variant? param)
+		if (filename.has_suffix(".level") && filename != _level._filename)
 		{
-			_project.import(null, this.active_window);
+			_level.load(filename);
+			_level.send_level();
+			send_state();
 		}
+	}
 
-		private void on_preferences(GLib.SimpleAction action, GLib.Variant? param)
-		{
-			_preferences_dialog.show_all();
-		}
+	private bool save_as(string? filename)
+	{
+		string path;
 
-		private void on_deploy(GLib.SimpleAction action, GLib.Variant? param)
+		if (filename != null)
 		{
-			deploy_game();
+			path = filename;
 		}
-
-		private void on_close(GLib.SimpleAction action, GLib.Variant? param)
+		else
 		{
-			int rt = ResponseType.YES;
+			Gtk.FileChooserDialog fcd = new Gtk.FileChooserDialog("Save As..."
+				, this.active_window
+				, FileChooserAction.SAVE
+				, "Cancel"
+				, ResponseType.CANCEL
+				, "Save"
+				, ResponseType.ACCEPT
+				);
+			fcd.add_filter(_file_filter);
+			fcd.set_current_folder(_project.source_dir());
+			int rt = fcd.run();
 
-			if (_database.changed())
+			if (rt != ResponseType.ACCEPT)
 			{
-				DialogLevelChanged lc = new DialogLevelChanged(this.active_window);
-				rt = lc.run();
-				lc.destroy();
+				fcd.destroy();
+				return false;
 			}
 
-			if (!_database.changed() || rt == ResponseType.YES && save() || rt == ResponseType.NO)
-			{
-				stop_compiler();
-				show_panel("panel_welcome");
-			}
+			path = fcd.get_filename();
+			fcd.destroy();
 		}
 
-		private void on_quit(GLib.SimpleAction action, GLib.Variant? param)
+		if (!_project.path_is_within_dir(path, _project.source_dir()))
 		{
-			this.active_window.close();
+			loge("File must be within `%s`".printf(_project.source_dir()));
+			return false;
 		}
 
-		private void on_show_grid(GLib.SimpleAction action, GLib.Variant? param)
-		{
-			_show_grid = !action.get_state().get_boolean();
-			send_state();
-			action.set_state(new GLib.Variant.boolean(_show_grid));
-		}
+		_level.save(path.has_suffix(".level") ? path : path + ".level");
+		_statusbar.set_temporary_message("Saved %s".printf(_level._filename));
+		return true;
+	}
 
-		private void on_custom_grid()
-		{
-			Gtk.Dialog dg = new Gtk.Dialog.with_buttons("Grid size"
-				, this.active_window
-				, DialogFlags.MODAL
-				, "Cancel"
-				, ResponseType.CANCEL
-				, "Ok"
-				, ResponseType.OK
-				, null
-				);
+	private bool save()
+	{
+		return save_as(_level._filename);
+	}
 
-			EntryDouble sb = new EntryDouble(_grid_size, 0.1, 1000);
-			sb.activate.connect(() => { dg.response(ResponseType.OK); });
-			dg.get_content_area().add(sb);
-			dg.skip_taskbar_hint = true;
-			dg.show_all();
+	private bool save_timeout()
+	{
+		if (_level._filename != null)
+			save();
 
-			if (dg.run() == ResponseType.OK)
-			{
-				_grid_size = sb.value;
-				send_state();
-			}
+		return true;
+	}
 
-			dg.destroy();
-		}
+	public void close_all()
+	{
+		_user.save(_user_file.get_path());
+		save_settings();
 
-		private void on_rotation_snap(GLib.SimpleAction action, GLib.Variant? param)
-		{
-			Gtk.Dialog dg = new Gtk.Dialog.with_buttons("Rotation snap"
-				, this.active_window
-				, DialogFlags.MODAL
-				, "Cancel"
-				, ResponseType.CANCEL
-				, "Ok"
-				, ResponseType.OK
-				, null
-				);
+		if (_save_timer_id > 0)
+			GLib.Source.remove(_save_timer_id);
 
-			EntryDouble sb = new EntryDouble(_rotation_snap, 1.0, 180.0);
-			sb.activate.connect(() => { dg.response(ResponseType.OK); });
-			dg.get_content_area().add(sb);
-			dg.skip_taskbar_hint = true;
-			dg.show_all();
+		if (_resource_chooser != null)
+			_resource_chooser.destroy();
 
-			if (dg.run() == ResponseType.OK)
-			{
-				_rotation_snap = sb.value;
-				send_state();
-			}
+		if (_preferences_dialog != null)
+			_preferences_dialog.destroy();
+
+		stop_compiler();
+	}
 
-			dg.destroy();
-		}
+	protected override void shutdown()
+	{
+		base.shutdown();
+	}
 
-		private void on_create_primitive(GLib.SimpleAction action, GLib.Variant? param)
-		{
-			if (action.name == "primitive-cube")
-				_editor.send_script(LevelEditorApi.set_placeable("unit", "core/units/primitives/cube"));
-			else if (action.name == "primitive-sphere")
-				_editor.send_script(LevelEditorApi.set_placeable("unit", "core/units/primitives/sphere"));
-			else if (action.name == "primitive-cone")
-				_editor.send_script(LevelEditorApi.set_placeable("unit", "core/units/primitives/cone"));
-			else if (action.name == "primitive-cylinder")
-				_editor.send_script(LevelEditorApi.set_placeable("unit", "core/units/primitives/cylinder"));
-			else if (action.name == "primitive-plane")
-				_editor.send_script(LevelEditorApi.set_placeable("unit", "core/units/primitives/plane"));
-			else if (action.name == "camera")
-				_editor.send_script(LevelEditorApi.set_placeable("unit", "core/units/camera"));
-			else if (action.name == "light")
-				_editor.send_script(LevelEditorApi.set_placeable("unit", "core/units/light"));
-			else if (action.name == "sound-source")
-				_editor.send_script(LevelEditorApi.set_placeable("sound", ""));
+	// Returns true if the level has been saved or the user decided it
+	// should be discarded.
+	public bool should_quit()
+	{
+		int rt = ResponseType.YES;
 
-			activate_action("tool", new GLib.Variant.string("place"));
+		if (_database.changed())
+		{
+			DialogLevelChanged lc = new DialogLevelChanged(this.active_window);
+			rt = lc.run();
+			lc.destroy();
 		}
 
-		private void on_camera_view(GLib.SimpleAction action, GLib.Variant? param)
-		{
-			string name = param.get_string();
+		if (!_database.changed() || rt == ResponseType.YES && save() || rt == ResponseType.NO)
+			return true;
 
-			if (name == "perspective")
-				_editor.send_script("LevelEditor:camera_view_perspective()");
-			else if (name == "front")
-				_editor.send_script("LevelEditor:camera_view_front()");
-			else if (name == "back")
-				_editor.send_script("LevelEditor:camera_view_back()");
-			else if (name == "right")
-				_editor.send_script("LevelEditor:camera_view_right()");
-			else if (name == "left")
-				_editor.send_script("LevelEditor:camera_view_left()");
-			else if (name == "top")
-				_editor.send_script("LevelEditor:camera_view_top()");
-			else if (name == "bottom")
-				_editor.send_script("LevelEditor:camera_view_bottom()");
+		return false;
+	}
 
-			action.set_state(param);
-		}
+	private void on_new_level(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		int rt = ResponseType.YES;
 
-		private void on_resource_chooser(GLib.SimpleAction action, GLib.Variant? param)
+		if (_database.changed())
 		{
-			_resource_popover.show_all();
+			DialogLevelChanged lc = new DialogLevelChanged(this.active_window);
+			rt = lc.run();
+			lc.destroy();
 		}
 
-		private void on_project_browser(GLib.SimpleAction action, GLib.Variant? param)
+		if (!_database.changed() || rt == ResponseType.YES && save() || rt == ResponseType.NO)
 		{
-			if (_project_slide.is_visible())
-			{
-				_project_slide.hide();
-			}
-			else
-			{
-				_project_slide.show_all();
-			}
+			new_level();
+			send_state();
 		}
+	}
+
+	private void on_open_level(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		int rt = ResponseType.YES;
+
+		if (_level._filename == param.get_string())
+			return;
 
-		private void on_console(GLib.SimpleAction action, GLib.Variant? param)
+		if (_database.changed())
 		{
-			if (_console_view.is_visible())
-			{
-				if (_console_view._entry.has_focus)
-					_console_view.hide();
-				else
-					_console_view._entry.grab_focus();
-			}
-			else
-			{
-				_console_view.show_all();
-			}
+			DialogLevelChanged lc = new DialogLevelChanged(this.active_window);
+			rt = lc.run();
+			lc.destroy();
 		}
 
-		private void on_statusbar(GLib.SimpleAction action, GLib.Variant? param)
+		if (!_database.changed() || rt == ResponseType.YES && save() || rt == ResponseType.NO)
+			load_level(param.get_string());
+	}
+
+	private void on_open_project(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		int rt = ResponseType.YES;
+
+		if (_database.changed())
 		{
-			if (_statusbar.is_visible())
-			{
-				_statusbar.hide();
-			}
-			else
-			{
-				_statusbar.show_all();
-			}
+			DialogLevelChanged lc = new DialogLevelChanged(this.active_window);
+			rt = lc.run();
+			lc.destroy();
 		}
 
-		private void on_inspector(GLib.SimpleAction action, GLib.Variant? param)
+		if (!_database.changed() || rt == ResponseType.YES && save() || rt == ResponseType.NO)
 		{
-			if (_inspector_slide.is_visible())
+			DialogOpenProject op = new DialogOpenProject(this.active_window);
+			rt = op.run();
+			if (rt != ResponseType.ACCEPT)
 			{
-				_inspector_slide.hide();
-			}
-			else
-			{
-				_inspector_slide.show_all();
+				op.destroy();
+				return;
 			}
+
+			string source_dir = op.get_filename();
+			op.destroy();
+
+			if (_project.source_dir() == source_dir)
+				return;
+
+			logi("Loading project: `%s`...".printf(source_dir));
+			restart_compiler(source_dir, null);
 		}
+	}
+
+	private void on_new_project(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		int rt = ResponseType.YES;
 
-		private void on_editor_restart(GLib.SimpleAction action, GLib.Variant? param)
+		if (_database.changed())
 		{
-			restart_editor();
+			DialogLevelChanged lc = new DialogLevelChanged(this.active_window);
+			rt = lc.run();
+			lc.destroy();
 		}
 
-		private void on_build_data(GLib.SimpleAction action, GLib.Variant? param)
+		if (!_database.changed() || rt == ResponseType.YES && save() || rt == ResponseType.NO)
 		{
-			_data_compiler.compile.begin(_project.data_dir(), _project.platform(), (obj, res) => {
-				_data_compiler.compile.end(res);
-			});
+			stop_compiler();
+			show_panel("panel_new_project");
 		}
+	}
+
+	private void on_save(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		save();
+	}
+
+	private void on_save_as(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		save_as(null);
+	}
+
+	private void on_import(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		_project.import(null, this.active_window);
+	}
+
+	private void on_preferences(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		_preferences_dialog.show_all();
+	}
+
+	private void on_deploy(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		deploy_game();
+	}
+
+	private void on_close(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		int rt = ResponseType.YES;
 
-		private void on_refresh_lua(GLib.SimpleAction action, GLib.Variant? param)
+		if (_database.changed())
 		{
-			_data_compiler.compile.begin(_project.data_dir(), _project.platform(), (obj, res) => {
-				if (_data_compiler.compile.end(res))
-				{
-					_editor.send(DeviceApi.refresh());
-					_game.send(DeviceApi.refresh());
-				}
-			});
+			DialogLevelChanged lc = new DialogLevelChanged(this.active_window);
+			rt = lc.run();
+			lc.destroy();
 		}
 
-		private void on_snap_to_grid(GLib.SimpleAction action, GLib.Variant? param)
+		if (!_database.changed() || rt == ResponseType.YES && save() || rt == ResponseType.NO)
 		{
-			_snap_to_grid = !action.get_state().get_boolean();
-			send_state();
-			action.set_state(new GLib.Variant.boolean(_snap_to_grid));
+			stop_compiler();
+			show_panel("panel_welcome");
 		}
+	}
+
+	private void on_quit(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		this.active_window.close();
+	}
+
+	private void on_show_grid(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		_show_grid = !action.get_state().get_boolean();
+		send_state();
+		action.set_state(new GLib.Variant.boolean(_show_grid));
+	}
+
+	private void on_custom_grid()
+	{
+		Gtk.Dialog dg = new Gtk.Dialog.with_buttons("Grid size"
+			, this.active_window
+			, DialogFlags.MODAL
+			, "Cancel"
+			, ResponseType.CANCEL
+			, "Ok"
+			, ResponseType.OK
+			, null
+			);
+
+		EntryDouble sb = new EntryDouble(_grid_size, 0.1, 1000);
+		sb.activate.connect(() => { dg.response(ResponseType.OK); });
+		dg.get_content_area().add(sb);
+		dg.skip_taskbar_hint = true;
+		dg.show_all();
 
-		private void on_debug_render_world(GLib.SimpleAction action, GLib.Variant? param)
+		if (dg.run() == ResponseType.OK)
 		{
-			_debug_render_world = !action.get_state().get_boolean();
+			_grid_size = sb.value;
 			send_state();
-			action.set_state(new GLib.Variant.boolean(_debug_render_world));
 		}
 
-		private void on_debug_physics_world(GLib.SimpleAction action, GLib.Variant? param)
+		dg.destroy();
+	}
+
+	private void on_rotation_snap(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		Gtk.Dialog dg = new Gtk.Dialog.with_buttons("Rotation snap"
+			, this.active_window
+			, DialogFlags.MODAL
+			, "Cancel"
+			, ResponseType.CANCEL
+			, "Ok"
+			, ResponseType.OK
+			, null
+			);
+
+		EntryDouble sb = new EntryDouble(_rotation_snap, 1.0, 180.0);
+		sb.activate.connect(() => { dg.response(ResponseType.OK); });
+		dg.get_content_area().add(sb);
+		dg.skip_taskbar_hint = true;
+		dg.show_all();
+
+		if (dg.run() == ResponseType.OK)
 		{
-			_debug_physics_world = !action.get_state().get_boolean();
+			_rotation_snap = sb.value;
 			send_state();
-			action.set_state(new GLib.Variant.boolean(_debug_physics_world));
 		}
 
-		private void on_run_game(GLib.SimpleAction action, GLib.Variant? param)
-		{
-			if (_game.is_connected())
-			{
-				stop_game();
-			}
-			else
-			{
-				// Always change icon state regardless of failures
-				_toolbar_run.icon_name = "game-stop";
-				start_game(action.name == "test-level" ? StartGame.TEST : StartGame.NORMAL);
-			}
-		}
+		dg.destroy();
+	}
 
-		private void on_undo(GLib.SimpleAction action, GLib.Variant? param)
-		{
-			int id = _database.undo();
-			if (id != -1)
-				_statusbar.set_temporary_message("Undo: " + ActionNames[id]);
-		}
+	private void on_create_primitive(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		if (action.name == "primitive-cube")
+			_editor.send_script(LevelEditorApi.set_placeable("unit", "core/units/primitives/cube"));
+		else if (action.name == "primitive-sphere")
+			_editor.send_script(LevelEditorApi.set_placeable("unit", "core/units/primitives/sphere"));
+		else if (action.name == "primitive-cone")
+			_editor.send_script(LevelEditorApi.set_placeable("unit", "core/units/primitives/cone"));
+		else if (action.name == "primitive-cylinder")
+			_editor.send_script(LevelEditorApi.set_placeable("unit", "core/units/primitives/cylinder"));
+		else if (action.name == "primitive-plane")
+			_editor.send_script(LevelEditorApi.set_placeable("unit", "core/units/primitives/plane"));
+		else if (action.name == "camera")
+			_editor.send_script(LevelEditorApi.set_placeable("unit", "core/units/camera"));
+		else if (action.name == "light")
+			_editor.send_script(LevelEditorApi.set_placeable("unit", "core/units/light"));
+		else if (action.name == "sound-source")
+			_editor.send_script(LevelEditorApi.set_placeable("sound", ""));
+
+		activate_action("tool", new GLib.Variant.string("place"));
+	}
+
+	private void on_camera_view(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		string name = param.get_string();
+
+		if (name == "perspective")
+			_editor.send_script("LevelEditor:camera_view_perspective()");
+		else if (name == "front")
+			_editor.send_script("LevelEditor:camera_view_front()");
+		else if (name == "back")
+			_editor.send_script("LevelEditor:camera_view_back()");
+		else if (name == "right")
+			_editor.send_script("LevelEditor:camera_view_right()");
+		else if (name == "left")
+			_editor.send_script("LevelEditor:camera_view_left()");
+		else if (name == "top")
+			_editor.send_script("LevelEditor:camera_view_top()");
+		else if (name == "bottom")
+			_editor.send_script("LevelEditor:camera_view_bottom()");
+
+		action.set_state(param);
+	}
+
+	private void on_resource_chooser(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		_resource_popover.show_all();
+	}
 
-		private void on_redo(GLib.SimpleAction action, GLib.Variant? param)
+	private void on_project_browser(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		if (_project_slide.is_visible())
 		{
-			int id = _database.redo();
-			if (id != -1)
-				_statusbar.set_temporary_message("Redo: " + ActionNames[id]);
+			_project_slide.hide();
 		}
-
-		private void on_duplicate(GLib.SimpleAction action, GLib.Variant? param)
+		else
 		{
-			_level.duplicate_selected_objects();
+			_project_slide.show_all();
 		}
+	}
 
-		private void on_delete(GLib.SimpleAction action, GLib.Variant? param)
+	private void on_console(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		if (_console_view.is_visible())
 		{
-			_level.destroy_selected_objects();
+			if (_console_view._entry.has_focus)
+				_console_view.hide();
+			else
+				_console_view._entry.grab_focus();
 		}
-
-		private void on_manual(GLib.SimpleAction action, GLib.Variant? param)
+		else
 		{
-			try
-			{
-				AppInfo.launch_default_for_uri("https://dbartolini.github.io/crown/html/v" + CROWN_VERSION, null);
-			}
-			catch (Error e)
-			{
-				loge(e.message);
-			}
+			_console_view.show_all();
 		}
+	}
 
-		private void on_report_issue(GLib.SimpleAction action, GLib.Variant? param)
+	private void on_statusbar(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		if (_statusbar.is_visible())
 		{
-			try
-			{
-				AppInfo.launch_default_for_uri("https://github.com/dbartolini/crown/issues", null);
-			}
-			catch (Error e)
-			{
-				loge(e.message);
-			}
+			_statusbar.hide();
 		}
-
-		private void on_browse_logs(GLib.SimpleAction action, GLib.Variant? param)
+		else
 		{
-			open_directory(_logs_dir.get_path());
+			_statusbar.show_all();
 		}
+	}
 
-		private void on_changelog(GLib.SimpleAction action, GLib.Variant? param)
+	private void on_inspector(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		if (_inspector_slide.is_visible())
 		{
-			try
-			{
-				AppInfo.launch_default_for_uri("https://dbartolini.github.io/crown/html/v" + CROWN_VERSION + "/changelog.html", null);
-			}
-			catch (Error e)
-			{
-				loge(e.message);
-			}
+			_inspector_slide.hide();
 		}
-
-		private void on_about(GLib.SimpleAction action, GLib.Variant? param)
+		else
 		{
-			Gtk.AboutDialog dlg = new Gtk.AboutDialog();
-			dlg.set_destroy_with_parent(true);
-			dlg.set_transient_for(this.active_window);
-			dlg.set_modal(true);
-			dlg.set_logo_icon_name("pepper");
-
-			dlg.program_name = "Crown Game Engine";
-			dlg.version = CROWN_VERSION;
-			dlg.website = "https://github.com/dbartolini/crown";
-			dlg.copyright = "Copyright (c) 2012-2020 Daniele Bartolini and individual contributors.";
-			dlg.license = "Crown Game Engine.\n"
-				+ "Copyright (c) 2012-2020 Daniele Bartolini and individual contributors.\n"
-				+ "\n"
-				+ "This program is free software; you can redistribute it and/or\n"
-				+ "modify it under the terms of the GNU General Public License\n"
-				+ "as published by the Free Software Foundation; either version 2\n"
-				+ "of the License, or (at your option) any later version.\n"
-				+ "\n"
-				+ "This program is distributed in the hope that it will be useful,\n"
-				+ "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
-				+ "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
-				+ "GNU General Public License for more details.\n"
-				+ "\n"
-				+ "You should have received a copy of the GNU General Public License\n"
-				+ "along with this program; if not, write to the Free Software\n"
-				+ "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n"
-				;
-			dlg.run();
-			dlg.destroy();
+			_inspector_slide.show_all();
 		}
+	}
 
-		public void set_autosave_timer(uint minutes)
-		{
-			if (_save_timer_id > 0)
-				GLib.Source.remove(_save_timer_id);
+	private void on_editor_restart(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		restart_editor();
+	}
 
-			_save_timer_id = GLib.Timeout.add_seconds(minutes*60, save_timeout);
-		}
+	private void on_build_data(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		_data_compiler.compile.begin(_project.data_dir(), _project.platform(), (obj, res) => {
+			_data_compiler.compile.end(res);
+		});
+	}
 
-		public void menu_set_enabled(bool enabled, GLib.ActionEntry[] entries, string[]? whitelist = null)
-		{
-			for (int ii = 0; ii < entries.length; ++ii)
+	private void on_refresh_lua(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		_data_compiler.compile.begin(_project.data_dir(), _project.platform(), (obj, res) => {
+			if (_data_compiler.compile.end(res))
 			{
-				string action_name = entries[ii].name;
-				int jj = 0;
-				if (whitelist != null)
-				{
-					for (; jj < whitelist.length; ++jj)
-					{
-						if (action_name == whitelist[jj])
-							break;
-					}
-				}
-				if (whitelist == null || whitelist != null && jj == whitelist.length)
-				{
-					GLib.SimpleAction sa = this.lookup_action(action_name) as GLib.SimpleAction;
-					if (sa != null)
-						sa.set_enabled(enabled);
-				}
+				_editor.send(DeviceApi.refresh());
+				_game.send(DeviceApi.refresh());
 			}
-		}
+		});
+	}
 
-		public void show_panel(string name, Gtk.StackTransitionType stt = Gtk.StackTransitionType.NONE)
-		{
-			_main_stack.set_visible_child_full(name, stt);
+	private void on_snap_to_grid(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		_snap_to_grid = !action.get_state().get_boolean();
+		send_state();
+		action.set_state(new GLib.Variant.boolean(_snap_to_grid));
+	}
 
-			if (name == "main_vbox")
-			{
-				// FIXME: save/restore last known window state
-				int win_w;
-				int win_h;
-				this.active_window.get_size(out win_w, out win_h);
-				_editor_pane.set_position(210);
-				_content_pane.set_position(win_h - 250);
-				_inspector_pane.set_position(win_h - 600);
-				_main_pane.set_position(win_w - 375);
-
-				menu_set_enabled(true, action_entries_file);
-				menu_set_enabled(true, action_entries_edit);
-				menu_set_enabled(true, action_entries_create);
-				menu_set_enabled(true, action_entries_camera);
-				menu_set_enabled(true, action_entries_view);
-				menu_set_enabled(true, action_entries_debug);
-				menu_set_enabled(true, action_entries_help);
-			}
-			else if (name == "panel_welcome"
-				|| name == "panel_new_project"
-				|| name == "panel_projects_list"
-				)
-			{
-				menu_set_enabled(false, action_entries_file, {"new-project", "open-project", "quit"});
-				menu_set_enabled(false, action_entries_edit);
-				menu_set_enabled(false, action_entries_create);
-				menu_set_enabled(false, action_entries_camera);
-				menu_set_enabled(false, action_entries_view);
-				menu_set_enabled(false, action_entries_debug);
-				menu_set_enabled( true, action_entries_help);
-			}
-		}
+	private void on_debug_render_world(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		_debug_render_world = !action.get_state().get_boolean();
+		send_state();
+		action.set_state(new GLib.Variant.boolean(_debug_render_world));
 	}
 
-	// Global paths
-	public static GLib.File _config_dir;
-	public static GLib.File _logs_dir;
-	public static GLib.File _documents_dir;
-	public static GLib.File _log_file;
-	public static GLib.File _settings_file;
-	public static GLib.File _user_file;
-
-	public static GLib.FileStream _log_stream;
-	public static ConsoleView _console_view;
-
-	public static void log(string system, string severity, string message)
-	{
-		GLib.DateTime now = new GLib.DateTime.now_utc();
-		string line = "%s.%06d  %.4s %s: %s\n".printf(now.format("%H:%M:%S")
-			, now.get_microsecond()
-			, severity.ascii_up()
-			, system
-			, message
-			);
+	private void on_debug_physics_world(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		_debug_physics_world = !action.get_state().get_boolean();
+		send_state();
+		action.set_state(new GLib.Variant.boolean(_debug_physics_world));
+	}
 
-		if (_log_stream != null)
+	private void on_run_game(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		if (_game.is_connected())
+		{
+			stop_game();
+		}
+		else
 		{
-			_log_stream.puts(line);
-			_log_stream.flush();
+			// Always change icon state regardless of failures
+			_toolbar_run.icon_name = "game-stop";
+			start_game(action.name == "test-level" ? StartGame.TEST : StartGame.NORMAL);
 		}
+	}
 
-		if (_console_view != null)
-			_console_view.log(severity, line);
+	private void on_undo(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		int id = _database.undo();
+		if (id != -1)
+			_statusbar.set_temporary_message("Undo: " + ActionNames[id]);
 	}
 
-	public static void logi(string message)
+	private void on_redo(GLib.SimpleAction action, GLib.Variant? param)
 	{
-		log("editor", "info", message);
+		int id = _database.redo();
+		if (id != -1)
+			_statusbar.set_temporary_message("Redo: " + ActionNames[id]);
 	}
 
-	public static void logw(string message)
+	private void on_duplicate(GLib.SimpleAction action, GLib.Variant? param)
 	{
-		log("editor", "warning", message);
+		_level.duplicate_selected_objects();
 	}
 
-	public static void loge(string message)
+	private void on_delete(GLib.SimpleAction action, GLib.Variant? param)
 	{
-		log("editor", "error", message);
+		_level.destroy_selected_objects();
 	}
 
-	public void open_directory(string directory)
+	private void on_manual(GLib.SimpleAction action, GLib.Variant? param)
 	{
-#if CROWN_PLATFORM_LINUX
 		try
 		{
-			GLib.AppInfo.launch_default_for_uri("file://" + directory, null);
+			AppInfo.launch_default_for_uri("https://dbartolini.github.io/crown/html/v" + CROWN_VERSION, null);
 		}
 		catch (Error e)
 		{
 			loge(e.message);
 		}
-#else
-		GLib.SubprocessLauncher sl = new GLib.SubprocessLauncher(subprocess_flags());
+	}
+
+	private void on_report_issue(GLib.SimpleAction action, GLib.Variant? param)
+	{
 		try
 		{
-			sl.spawnv({ "explorer.exe", directory, null });
+			AppInfo.launch_default_for_uri("https://github.com/dbartolini/crown/issues", null);
 		}
 		catch (Error e)
 		{
 			loge(e.message);
 		}
-#endif
 	}
 
-	public static GLib.SubprocessFlags subprocess_flags()
+	private void on_browse_logs(GLib.SimpleAction action, GLib.Variant? param)
 	{
-		GLib.SubprocessFlags flags = SubprocessFlags.NONE;
-#if !CROWN_DEBUG
-		flags |= SubprocessFlags.STDOUT_SILENCE | SubprocessFlags.STDERR_SILENCE;
-#endif
-		return flags;
+		open_directory(_logs_dir.get_path());
 	}
 
-	public static bool is_directory_empty(string path)
+	private void on_changelog(GLib.SimpleAction action, GLib.Variant? param)
 	{
-		GLib.File file = GLib.File.new_for_path(path);
 		try
 		{
-			FileEnumerator enumerator = file.enumerate_children("standard::*"
-				, FileQueryInfoFlags.NOFOLLOW_SYMLINKS
-				);
-			return enumerator.next_file() == null;
+			AppInfo.launch_default_for_uri("https://dbartolini.github.io/crown/html/v" + CROWN_VERSION + "/changelog.html", null);
 		}
-		catch (GLib.Error e)
+		catch (Error e)
 		{
 			loge(e.message);
 		}
+	}
 
-		return false;
+	private void on_about(GLib.SimpleAction action, GLib.Variant? param)
+	{
+		Gtk.AboutDialog dlg = new Gtk.AboutDialog();
+		dlg.set_destroy_with_parent(true);
+		dlg.set_transient_for(this.active_window);
+		dlg.set_modal(true);
+		dlg.set_logo_icon_name("pepper");
+
+		dlg.program_name = "Crown Game Engine";
+		dlg.version = CROWN_VERSION;
+		dlg.website = "https://github.com/dbartolini/crown";
+		dlg.copyright = "Copyright (c) 2012-2020 Daniele Bartolini and individual contributors.";
+		dlg.license = "Crown Game Engine.\n"
+			+ "Copyright (c) 2012-2020 Daniele Bartolini and individual contributors.\n"
+			+ "\n"
+			+ "This program is free software; you can redistribute it and/or\n"
+			+ "modify it under the terms of the GNU General Public License\n"
+			+ "as published by the Free Software Foundation; either version 2\n"
+			+ "of the License, or (at your option) any later version.\n"
+			+ "\n"
+			+ "This program is distributed in the hope that it will be useful,\n"
+			+ "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
+			+ "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
+			+ "GNU General Public License for more details.\n"
+			+ "\n"
+			+ "You should have received a copy of the GNU General Public License\n"
+			+ "along with this program; if not, write to the Free Software\n"
+			+ "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.\n"
+			;
+		dlg.run();
+		dlg.destroy();
+	}
+
+	public void set_autosave_timer(uint minutes)
+	{
+		if (_save_timer_id > 0)
+			GLib.Source.remove(_save_timer_id);
+
+		_save_timer_id = GLib.Timeout.add_seconds(minutes*60, save_timeout);
 	}
 
-	public static int main(string[] args)
+	public void menu_set_enabled(bool enabled, GLib.ActionEntry[] entries, string[]? whitelist = null)
 	{
-		// Global paths
-		_config_dir = GLib.File.new_for_path(GLib.Path.build_filename(GLib.Environment.get_user_config_dir(), "crown"));
-		try { _config_dir.make_directory(); } catch (Error e) { /* Nobody cares */ }
-		_logs_dir = GLib.File.new_for_path(GLib.Path.build_filename(_config_dir.get_path(), "logs"));
-		try { _logs_dir.make_directory(); } catch (Error e) { /* Nobody cares */ }
-		_documents_dir = GLib.File.new_for_path(GLib.Environment.get_user_special_dir(GLib.UserDirectory.DOCUMENTS));
+		for (int ii = 0; ii < entries.length; ++ii)
+		{
+			string action_name = entries[ii].name;
+			int jj = 0;
+			if (whitelist != null)
+			{
+				for (; jj < whitelist.length; ++jj)
+				{
+					if (action_name == whitelist[jj])
+						break;
+				}
+			}
+			if (whitelist == null || whitelist != null && jj == whitelist.length)
+			{
+				GLib.SimpleAction sa = this.lookup_action(action_name) as GLib.SimpleAction;
+				if (sa != null)
+					sa.set_enabled(enabled);
+			}
+		}
+	}
+
+	public void show_panel(string name, Gtk.StackTransitionType stt = Gtk.StackTransitionType.NONE)
+	{
+		_main_stack.set_visible_child_full(name, stt);
+
+		if (name == "main_vbox")
+		{
+			// FIXME: save/restore last known window state
+			int win_w;
+			int win_h;
+			this.active_window.get_size(out win_w, out win_h);
+			_editor_pane.set_position(210);
+			_content_pane.set_position(win_h - 250);
+			_inspector_pane.set_position(win_h - 600);
+			_main_pane.set_position(win_w - 375);
+
+			menu_set_enabled(true, action_entries_file);
+			menu_set_enabled(true, action_entries_edit);
+			menu_set_enabled(true, action_entries_create);
+			menu_set_enabled(true, action_entries_camera);
+			menu_set_enabled(true, action_entries_view);
+			menu_set_enabled(true, action_entries_debug);
+			menu_set_enabled(true, action_entries_help);
+		}
+		else if (name == "panel_welcome"
+			|| name == "panel_new_project"
+			|| name == "panel_projects_list"
+			)
+		{
+			menu_set_enabled(false, action_entries_file, {"new-project", "open-project", "quit"});
+			menu_set_enabled(false, action_entries_edit);
+			menu_set_enabled(false, action_entries_create);
+			menu_set_enabled(false, action_entries_camera);
+			menu_set_enabled(false, action_entries_view);
+			menu_set_enabled(false, action_entries_debug);
+			menu_set_enabled( true, action_entries_help);
+		}
+	}
+}
+
+// Global paths
+public static GLib.File _config_dir;
+public static GLib.File _logs_dir;
+public static GLib.File _documents_dir;
+public static GLib.File _log_file;
+public static GLib.File _settings_file;
+public static GLib.File _user_file;
+
+public static GLib.FileStream _log_stream;
+public static ConsoleView _console_view;
+
+public static void log(string system, string severity, string message)
+{
+	GLib.DateTime now = new GLib.DateTime.now_utc();
+	string line = "%s.%06d  %.4s %s: %s\n".printf(now.format("%H:%M:%S")
+		, now.get_microsecond()
+		, severity.ascii_up()
+		, system
+		, message
+		);
+
+	if (_log_stream != null)
+	{
+		_log_stream.puts(line);
+		_log_stream.flush();
+	}
+
+	if (_console_view != null)
+		_console_view.log(severity, line);
+}
+
+public static void logi(string message)
+{
+	log("editor", "info", message);
+}
+
+public static void logw(string message)
+{
+	log("editor", "warning", message);
+}
+
+public static void loge(string message)
+{
+	log("editor", "error", message);
+}
 
-		_log_file = GLib.File.new_for_path(GLib.Path.build_filename(_logs_dir.get_path(), new GLib.DateTime.now_utc().format("%Y-%m-%d") + ".log"));
-		_settings_file = GLib.File.new_for_path(GLib.Path.build_filename(_config_dir.get_path(), "settings.sjson"));
-		_user_file = GLib.File.new_for_path(GLib.Path.build_filename(_config_dir.get_path(), "user.sjson"));
+public void open_directory(string directory)
+{
+#if CROWN_PLATFORM_LINUX
+	try
+	{
+		GLib.AppInfo.launch_default_for_uri("file://" + directory, null);
+	}
+	catch (Error e)
+	{
+		loge(e.message);
+	}
+#else
+	GLib.SubprocessLauncher sl = new GLib.SubprocessLauncher(subprocess_flags());
+	try
+	{
+		sl.spawnv({ "explorer.exe", directory, null });
+	}
+	catch (Error e)
+	{
+		loge(e.message);
+	}
+#endif
+}
 
-		_log_stream = GLib.FileStream.open(_log_file.get_path(), "a");
+public static GLib.SubprocessFlags subprocess_flags()
+{
+	GLib.SubprocessFlags flags = SubprocessFlags.NONE;
+#if !CROWN_DEBUG
+	flags |= SubprocessFlags.STDOUT_SILENCE | SubprocessFlags.STDERR_SILENCE;
+#endif
+	return flags;
+}
 
-		LevelEditorApplication app = new LevelEditorApplication();
-		return app.run(args);
+public static bool is_directory_empty(string path)
+{
+	GLib.File file = GLib.File.new_for_path(path);
+	try
+	{
+		FileEnumerator enumerator = file.enumerate_children("standard::*"
+			, FileQueryInfoFlags.NOFOLLOW_SYMLINKS
+			);
+		return enumerator.next_file() == null;
 	}
+	catch (GLib.Error e)
+	{
+		loge(e.message);
+	}
+
+	return false;
+}
+
+public static int main(string[] args)
+{
+	// Global paths
+	_config_dir = GLib.File.new_for_path(GLib.Path.build_filename(GLib.Environment.get_user_config_dir(), "crown"));
+	try { _config_dir.make_directory(); } catch (Error e) { /* Nobody cares */ }
+	_logs_dir = GLib.File.new_for_path(GLib.Path.build_filename(_config_dir.get_path(), "logs"));
+	try { _logs_dir.make_directory(); } catch (Error e) { /* Nobody cares */ }
+	_documents_dir = GLib.File.new_for_path(GLib.Environment.get_user_special_dir(GLib.UserDirectory.DOCUMENTS));
+
+	_log_file = GLib.File.new_for_path(GLib.Path.build_filename(_logs_dir.get_path(), new GLib.DateTime.now_utc().format("%Y-%m-%d") + ".log"));
+	_settings_file = GLib.File.new_for_path(GLib.Path.build_filename(_config_dir.get_path(), "settings.sjson"));
+	_user_file = GLib.File.new_for_path(GLib.Path.build_filename(_config_dir.get_path(), "user.sjson"));
+
+	_log_stream = GLib.FileStream.open(_log_file.get_path(), "a");
+
+	LevelEditorApplication app = new LevelEditorApplication();
+	return app.run(args);
+}
+
 }

+ 94 - 93
tools/level_editor/level_layers_tree_view.vala

@@ -8,103 +8,104 @@ using Gtk;
 
 namespace Crown
 {
-	public class LevelLayersTreeView : Gtk.Bin
+public class LevelLayersTreeView : Gtk.Bin
+{
+	private enum ItemFlags
 	{
-		private enum ItemFlags
-		{
-			VISIBLE = 1,
-			LOCKED = 2
-		}
+		VISIBLE = 1,
+		LOCKED = 2
+	}
+
+	// Data
+	private Level _level;
+	private Database _db;
 
+	// Widgets
+	private Gtk.Entry _filter_entry;
+	private Gtk.ListStore _list_store;
+	private Gtk.TreeModelFilter _tree_filter;
+	private Gtk.TreeView _tree_view;
+	private Gtk.TreeSelection _tree_selection;
+	private Gtk.ScrolledWindow _scrolled_window;
+	private Gtk.Box _vbox;
+
+	public LevelLayersTreeView(Database db, Level level)
+	{
 		// Data
-		private Level _level;
-		private Database _db;
+		_level = level;
+		_db = db;
 
 		// Widgets
-		private Gtk.Entry _filter_entry;
-		private Gtk.ListStore _list_store;
-		private Gtk.TreeModelFilter _tree_filter;
-		private Gtk.TreeView _tree_view;
-		private Gtk.TreeSelection _tree_selection;
-		private Gtk.ScrolledWindow _scrolled_window;
-		private Gtk.Box _vbox;
-
-		public LevelLayersTreeView(Database db, Level level)
-		{
-			// Data
-			_level = level;
-			_db = db;
-
-			// Widgets
-			_filter_entry = new Gtk.SearchEntry();
-			_filter_entry.set_placeholder_text("Search...");
-			_filter_entry.changed.connect(on_filter_entry_text_changed);
-
-			_list_store = new Gtk.ListStore(3, typeof(string), typeof(string), typeof(string));
-			_list_store.insert_with_values(null, -1
-				, 0, "layer-visible"
-				, 1, "layer-locked"
-				, 2, "Background"
-				, -1
-				);
-			_list_store.insert_with_values(null, -1
-				, 0, "layer-visible"
-				, 1, "layer-locked"
-				, 2, "Default"
-				, -1
-				);
-
-			_tree_filter = new Gtk.TreeModelFilter(_list_store, null);
-			_tree_filter.set_visible_func(filter_tree);
-
-			_tree_view = new Gtk.TreeView();
-			_tree_view.insert_column_with_attributes(-1, "Visible", new Gtk.CellRendererPixbuf(), "icon-name", 0, null);
-			_tree_view.insert_column_with_attributes(-1, "Locked",  new Gtk.CellRendererPixbuf(), "icon-name", 1, null);
-			_tree_view.insert_column_with_attributes(-1, "Name",    new Gtk.CellRendererText(),   "text",      2, null);
-
-			_tree_view.headers_clickable = false;
-			_tree_view.headers_visible = false;
-			_tree_view.model = _tree_filter;
-			_tree_view.button_press_event.connect(on_button_pressed);
-
-			_tree_selection = _tree_view.get_selection();
-			_tree_selection.set_mode(Gtk.SelectionMode.MULTIPLE);
-
-			_scrolled_window = new Gtk.ScrolledWindow(null, null);
-			_scrolled_window.add(_tree_view);
-
-			_vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
-			_vbox.pack_start(_filter_entry, false, true, 0);
-			_vbox.pack_start(_scrolled_window, true, true, 0);
-
-			this.add(_vbox);
-
-			this.get_style_context().add_class("level-layers-view");
-		}
-
-		private bool on_button_pressed(Gdk.EventButton ev)
-		{
-			return Gdk.EVENT_PROPAGATE;
-		}
-
-		private bool filter_tree(Gtk.TreeModel model, Gtk.TreeIter iter)
-		{
-			Value val;
-			model.get_value(iter, 2, out val);
-
-			_tree_view.expand_all();
-
-			string layer_name = ((string)val).down();
-			string filter_text = _filter_entry.text.down();
-			if (filter_text == "" || layer_name.index_of(filter_text) > -1)
-				return true;
-
-			return false;
-		}
-
-		private void on_filter_entry_text_changed()
-		{
-			_tree_filter.refilter();
-		}
+		_filter_entry = new Gtk.SearchEntry();
+		_filter_entry.set_placeholder_text("Search...");
+		_filter_entry.changed.connect(on_filter_entry_text_changed);
+
+		_list_store = new Gtk.ListStore(3, typeof(string), typeof(string), typeof(string));
+		_list_store.insert_with_values(null, -1
+			, 0, "layer-visible"
+			, 1, "layer-locked"
+			, 2, "Background"
+			, -1
+			);
+		_list_store.insert_with_values(null, -1
+			, 0, "layer-visible"
+			, 1, "layer-locked"
+			, 2, "Default"
+			, -1
+			);
+
+		_tree_filter = new Gtk.TreeModelFilter(_list_store, null);
+		_tree_filter.set_visible_func(filter_tree);
+
+		_tree_view = new Gtk.TreeView();
+		_tree_view.insert_column_with_attributes(-1, "Visible", new Gtk.CellRendererPixbuf(), "icon-name", 0, null);
+		_tree_view.insert_column_with_attributes(-1, "Locked",  new Gtk.CellRendererPixbuf(), "icon-name", 1, null);
+		_tree_view.insert_column_with_attributes(-1, "Name",    new Gtk.CellRendererText(),   "text",      2, null);
+
+		_tree_view.headers_clickable = false;
+		_tree_view.headers_visible = false;
+		_tree_view.model = _tree_filter;
+		_tree_view.button_press_event.connect(on_button_pressed);
+
+		_tree_selection = _tree_view.get_selection();
+		_tree_selection.set_mode(Gtk.SelectionMode.MULTIPLE);
+
+		_scrolled_window = new Gtk.ScrolledWindow(null, null);
+		_scrolled_window.add(_tree_view);
+
+		_vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
+		_vbox.pack_start(_filter_entry, false, true, 0);
+		_vbox.pack_start(_scrolled_window, true, true, 0);
+
+		this.add(_vbox);
+
+		this.get_style_context().add_class("level-layers-view");
 	}
+
+	private bool on_button_pressed(Gdk.EventButton ev)
+	{
+		return Gdk.EVENT_PROPAGATE;
+	}
+
+	private bool filter_tree(Gtk.TreeModel model, Gtk.TreeIter iter)
+	{
+		Value val;
+		model.get_value(iter, 2, out val);
+
+		_tree_view.expand_all();
+
+		string layer_name = ((string)val).down();
+		string filter_text = _filter_entry.text.down();
+		if (filter_text == "" || layer_name.index_of(filter_text) > -1)
+			return true;
+
+		return false;
+	}
+
+	private void on_filter_entry_text_changed()
+	{
+		_tree_filter.refilter();
+	}
+}
+
 }

+ 336 - 335
tools/level_editor/level_tree_view.vala

@@ -8,413 +8,414 @@ using Gtk;
 
 namespace Crown
 {
-	public class LevelTreeView : Gtk.Box
+public class LevelTreeView : Gtk.Box
+{
+	private enum ItemType
 	{
-		private enum ItemType
-		{
-			FOLDER,
-			UNIT,
-			SOUND
-		}
-
-		private enum Column
-		{
-			TYPE,
-			GUID,
-			NAME,
-
-			COUNT
-		}
-
-		// Data
-		private Level _level;
-		private Database _db;
-
-		// Widgets
-		private Gtk.Entry _filter_entry;
-		private Gtk.TreeStore _tree_store;
-		private Gtk.TreeModelFilter _tree_filter;
-		private Gtk.TreeModelSort _tree_sort;
-		private Gtk.TreeView _tree_view;
-		private Gtk.TreeSelection _tree_selection;
-		private Gtk.ScrolledWindow _scrolled_window;
-
-		public LevelTreeView(Database db, Level level)
-		{
-			Object(orientation: Gtk.Orientation.VERTICAL, spacing: 0);
-
-			// Data
-			_level = level;
-			_level.selection_changed.connect(on_level_selection_changed);
-			_level.object_editor_name_changed.connect(on_object_editor_name_changed);
-
-			_db = db;
-			_db.key_changed.connect(on_database_key_changed);
+		FOLDER,
+		UNIT,
+		SOUND
+	}
 
-			// Widgets
-			_filter_entry = new Gtk.SearchEntry();
-			_filter_entry.set_placeholder_text("Search...");
-			_filter_entry.changed.connect(on_filter_entry_text_changed);
+	private enum Column
+	{
+		TYPE,
+		GUID,
+		NAME,
 
-			_tree_store = new Gtk.TreeStore(Column.COUNT
-				, typeof(int)    // Column.TYPE
-				, typeof(Guid)   // Column.GUID
-				, typeof(string) // Column.NAME
-				);
+		COUNT
+	}
 
-			_tree_filter = new Gtk.TreeModelFilter(_tree_store, null);
-			_tree_filter.set_visible_func((model, iter) => {
-				_tree_view.expand_all();
+	// Data
+	private Level _level;
+	private Database _db;
 
-				Value type;
-				Value name;
-				model.get_value(iter, Column.TYPE, out type);
-				model.get_value(iter, Column.NAME, out name);
+	// Widgets
+	private Gtk.Entry _filter_entry;
+	private Gtk.TreeStore _tree_store;
+	private Gtk.TreeModelFilter _tree_filter;
+	private Gtk.TreeModelSort _tree_sort;
+	private Gtk.TreeView _tree_view;
+	private Gtk.TreeSelection _tree_selection;
+	private Gtk.ScrolledWindow _scrolled_window;
 
-				if ((int)type == ItemType.FOLDER)
-					return true;
+	public LevelTreeView(Database db, Level level)
+	{
+		Object(orientation: Gtk.Orientation.VERTICAL, spacing: 0);
 
-				string name_str = (string)name;
-				string filter_text = _filter_entry.text.down();
+		// Data
+		_level = level;
+		_level.selection_changed.connect(on_level_selection_changed);
+		_level.object_editor_name_changed.connect(on_object_editor_name_changed);
 
-				return name_str != null
-					&& (filter_text == "" || name_str.down().index_of(filter_text) > -1)
-					;
-			});
+		_db = db;
+		_db.key_changed.connect(on_database_key_changed);
 
-			_tree_sort = new Gtk.TreeModelSort.with_model(_tree_filter);
-			_tree_sort.set_default_sort_func((model, iter_a, iter_b) => {
-				Value type_a;
-				Value type_b;
-				model.get_value(iter_a, Column.TYPE, out type_a);
-				model.get_value(iter_b, Column.TYPE, out type_b);
-				if ((int)type_a == ItemType.FOLDER || (int)type_b == ItemType.FOLDER)
-					return -1;
-
-				Value id_a;
-				Value id_b;
-				model.get_value(iter_a, Column.NAME, out id_a);
-				model.get_value(iter_b, Column.NAME, out id_b);
-				return strcmp((string)id_a, (string)id_b);
-			});
+		// Widgets
+		_filter_entry = new Gtk.SearchEntry();
+		_filter_entry.set_placeholder_text("Search...");
+		_filter_entry.changed.connect(on_filter_entry_text_changed);
+
+		_tree_store = new Gtk.TreeStore(Column.COUNT
+			, typeof(int)    // Column.TYPE
+			, typeof(Guid)   // Column.GUID
+			, typeof(string) // Column.NAME
+			);
+
+		_tree_filter = new Gtk.TreeModelFilter(_tree_store, null);
+		_tree_filter.set_visible_func((model, iter) => {
+			_tree_view.expand_all();
 
-			_tree_view = new Gtk.TreeView();
-			_tree_view.insert_column_with_attributes(-1
-				, "Names"
-				, new Gtk.CellRendererText()
-				, "text"
-				, Column.NAME
-				, null
-				);
+			Value type;
+			Value name;
+			model.get_value(iter, Column.TYPE, out type);
+			model.get_value(iter, Column.NAME, out name);
+
+			if ((int)type == ItemType.FOLDER)
+				return true;
+
+			string name_str = (string)name;
+			string filter_text = _filter_entry.text.down();
+
+			return name_str != null
+				&& (filter_text == "" || name_str.down().index_of(filter_text) > -1)
+				;
+		});
+
+		_tree_sort = new Gtk.TreeModelSort.with_model(_tree_filter);
+		_tree_sort.set_default_sort_func((model, iter_a, iter_b) => {
+			Value type_a;
+			Value type_b;
+			model.get_value(iter_a, Column.TYPE, out type_a);
+			model.get_value(iter_b, Column.TYPE, out type_b);
+			if ((int)type_a == ItemType.FOLDER || (int)type_b == ItemType.FOLDER)
+				return -1;
+
+			Value id_a;
+			Value id_b;
+			model.get_value(iter_a, Column.NAME, out id_a);
+			model.get_value(iter_b, Column.NAME, out id_b);
+			return strcmp((string)id_a, (string)id_b);
+		});
+
+		_tree_view = new Gtk.TreeView();
+		_tree_view.insert_column_with_attributes(-1
+			, "Names"
+			, new Gtk.CellRendererText()
+			, "text"
+			, Column.NAME
+			, null
+			);
 /*
-			// Debug
-			_tree_view.insert_column_with_attributes(-1
-				, "Guids"
-				, new gtk.CellRendererText()
-				, "text"
-				, Column.GUID
-				, null
-				);
+		// Debug
+		_tree_view.insert_column_with_attributes(-1
+			, "Guids"
+			, new gtk.CellRendererText()
+			, "text"
+			, Column.GUID
+			, null
+			);
 */
-			_tree_view.headers_clickable = false;
-			_tree_view.headers_visible = false;
-			_tree_view.model = _tree_sort;
-			_tree_view.button_press_event.connect(on_button_pressed);
-			_tree_view.button_release_event.connect(on_button_released);
+		_tree_view.headers_clickable = false;
+		_tree_view.headers_visible = false;
+		_tree_view.model = _tree_sort;
+		_tree_view.button_press_event.connect(on_button_pressed);
+		_tree_view.button_release_event.connect(on_button_released);
 
-			_tree_selection = _tree_view.get_selection();
-			_tree_selection.set_mode(Gtk.SelectionMode.MULTIPLE);
-			_tree_selection.changed.connect(on_tree_selection_changed);
+		_tree_selection = _tree_view.get_selection();
+		_tree_selection.set_mode(Gtk.SelectionMode.MULTIPLE);
+		_tree_selection.changed.connect(on_tree_selection_changed);
 
-			_scrolled_window = new Gtk.ScrolledWindow(null, null);
-			_scrolled_window.add(_tree_view);
+		_scrolled_window = new Gtk.ScrolledWindow(null, null);
+		_scrolled_window.add(_tree_view);
 
-			this.pack_start(_filter_entry, false, true, 0);
-			this.pack_start(_scrolled_window, true, true, 0);
+		this.pack_start(_filter_entry, false, true, 0);
+		this.pack_start(_scrolled_window, true, true, 0);
 
-			this.get_style_context().add_class("level-tree-view");
-		}
+		this.get_style_context().add_class("level-tree-view");
+	}
 
-		private bool on_button_pressed(Gdk.EventButton ev)
+	private bool on_button_pressed(Gdk.EventButton ev)
+	{
+		if (ev.button == Gdk.BUTTON_SECONDARY)
 		{
-			if (ev.button == Gdk.BUTTON_SECONDARY)
-			{
-				Gtk.Menu menu = new Gtk.Menu();
-				Gtk.MenuItem mi;
-
-				mi = new Gtk.MenuItem.with_label("Rename...");
-				mi.activate.connect(() => {
-					Gtk.Dialog dg = new Gtk.Dialog.with_buttons("New Name"
-						, (Gtk.Window)this.get_toplevel()
-						, DialogFlags.MODAL
-						, "Cancel"
-						, ResponseType.CANCEL
-						, "Ok"
-						, ResponseType.OK
-						, null
-						);
-
-					Gtk.Entry sb = new Gtk.Entry();
-					sb.activate.connect(() => { dg.response(ResponseType.OK); });
-					dg.get_content_area().add(sb);
-					dg.skip_taskbar_hint = true;
-					dg.show_all();
-
-					if (dg.run() == (int)ResponseType.OK)
-					{
-						_tree_selection.selected_foreach((model, path, iter) => {
-							Value type;
-							model.get_value(iter, Column.TYPE, out type);
-							if ((int)type == ItemType.FOLDER)
-								return;
-
-							string new_name = sb.text.strip();
-
-							Value name;
-							model.get_value(iter, Column.NAME, out name);
-							if (new_name == "" || new_name == (string)name)
-								return;
-
-							Value guid;
-							model.get_value(iter, Column.GUID, out guid);
-							_level.object_set_editor_name((Guid)guid, new_name);
-						});
-					}
-
-					dg.destroy();
-				});
-				menu.add(mi);
+			Gtk.Menu menu = new Gtk.Menu();
+			Gtk.MenuItem mi;
+
+			mi = new Gtk.MenuItem.with_label("Rename...");
+			mi.activate.connect(() => {
+				Gtk.Dialog dg = new Gtk.Dialog.with_buttons("New Name"
+					, (Gtk.Window)this.get_toplevel()
+					, DialogFlags.MODAL
+					, "Cancel"
+					, ResponseType.CANCEL
+					, "Ok"
+					, ResponseType.OK
+					, null
+					);
+
+				Gtk.Entry sb = new Gtk.Entry();
+				sb.activate.connect(() => { dg.response(ResponseType.OK); });
+				dg.get_content_area().add(sb);
+				dg.skip_taskbar_hint = true;
+				dg.show_all();
 
-				mi = new Gtk.MenuItem.with_label("Delete");
-				mi.activate.connect(() => {
-					Guid[] ids = {};
+				if (dg.run() == (int)ResponseType.OK)
+				{
 					_tree_selection.selected_foreach((model, path, iter) => {
 						Value type;
 						model.get_value(iter, Column.TYPE, out type);
 						if ((int)type == ItemType.FOLDER)
 							return;
 
-						Value id;
-						model.get_value(iter, Column.GUID, out id);
-						ids += (Guid)id;
-					});
+						string new_name = sb.text.strip();
 
-					_level.destroy_objects(ids);
-				});
-				menu.add(mi);
-
-				menu.show_all();
-				menu.popup(null, null, null, ev.button, ev.time);
-			}
+						Value name;
+						model.get_value(iter, Column.NAME, out name);
+						if (new_name == "" || new_name == (string)name)
+							return;
 
-			return Gdk.EVENT_PROPAGATE;
-		}
+						Value guid;
+						model.get_value(iter, Column.GUID, out guid);
+						_level.object_set_editor_name((Guid)guid, new_name);
+					});
+				}
 
-		private bool on_button_released(Gdk.EventButton ev)
-		{
-			if (ev.button == Gdk.BUTTON_PRIMARY)
-			{
-				Gtk.TreePath path;
-				int cell_x;
-				int cell_y;
-				if (_tree_view.get_path_at_pos((int)ev.x, (int)ev.y, out path, null, out cell_x, out cell_y))
-				{
-					Gtk.TreeIter iter;
-					_tree_view.model.get_iter(out iter, path);
+				dg.destroy();
+			});
+			menu.add(mi);
 
+			mi = new Gtk.MenuItem.with_label("Delete");
+			mi.activate.connect(() => {
+				Guid[] ids = {};
+				_tree_selection.selected_foreach((model, path, iter) => {
 					Value type;
-					_tree_view.model.get_value(iter, Column.TYPE, out type);
-					if ((int)type != ItemType.FOLDER)
-						return Gdk.EVENT_PROPAGATE;
+					model.get_value(iter, Column.TYPE, out type);
+					if ((int)type == ItemType.FOLDER)
+						return;
 
-					if (_tree_view.is_row_expanded(path))
-						_tree_view.collapse_row(path);
-					else
-						_tree_view.expand_row(path, /*open_all = */false);
+					Value id;
+					model.get_value(iter, Column.GUID, out id);
+					ids += (Guid)id;
+				});
 
-					return Gdk.EVENT_STOP;
-				}
-			}
+				_level.destroy_objects(ids);
+			});
+			menu.add(mi);
 
-			return Gdk.EVENT_PROPAGATE;
+			menu.show_all();
+			menu.popup(null, null, null, ev.button, ev.time);
 		}
 
-		private void on_tree_selection_changed()
+		return Gdk.EVENT_PROPAGATE;
+	}
+
+	private bool on_button_released(Gdk.EventButton ev)
+	{
+		if (ev.button == Gdk.BUTTON_PRIMARY)
 		{
-			_level.selection_changed.disconnect(on_level_selection_changed);
+			Gtk.TreePath path;
+			int cell_x;
+			int cell_y;
+			if (_tree_view.get_path_at_pos((int)ev.x, (int)ev.y, out path, null, out cell_x, out cell_y))
+			{
+				Gtk.TreeIter iter;
+				_tree_view.model.get_iter(out iter, path);
 
-			Guid[] ids = {};
-			_tree_selection.selected_foreach((model, path, iter) => {
 				Value type;
-				model.get_value(iter, Column.TYPE, out type);
-				if ((int)type == ItemType.FOLDER)
-					return;
+				_tree_view.model.get_value(iter, Column.TYPE, out type);
+				if ((int)type != ItemType.FOLDER)
+					return Gdk.EVENT_PROPAGATE;
 
-				Value id;
-				model.get_value(iter, Column.GUID, out id);
-				ids += (Guid)id;
-			});
+				if (_tree_view.is_row_expanded(path))
+					_tree_view.collapse_row(path);
+				else
+					_tree_view.expand_row(path, /*open_all = */false);
 
-			_level.selection_set(ids);
-			_level.selection_changed.connect(on_level_selection_changed);
+				return Gdk.EVENT_STOP;
+			}
 		}
 
-		private void on_level_selection_changed(Gee.ArrayList<Guid?> selection)
-		{
-			_tree_selection.changed.disconnect(on_tree_selection_changed);
-			_tree_selection.unselect_all();
+		return Gdk.EVENT_PROPAGATE;
+	}
 
-			_tree_sort.foreach ((model, path, iter) => {
-				Value type;
-				model.get_value(iter, Column.TYPE, out type);
-				if ((int)type == ItemType.FOLDER)
-					return false;
+	private void on_tree_selection_changed()
+	{
+		_level.selection_changed.disconnect(on_level_selection_changed);
+
+		Guid[] ids = {};
+		_tree_selection.selected_foreach((model, path, iter) => {
+			Value type;
+			model.get_value(iter, Column.TYPE, out type);
+			if ((int)type == ItemType.FOLDER)
+				return;
 
-				Value id;
-				model.get_value(iter, Column.GUID, out id);
+			Value id;
+			model.get_value(iter, Column.GUID, out id);
+			ids += (Guid)id;
+		});
 
-				foreach (Guid? guid in selection)
-				{
-					if ((Guid)id == guid)
-					{
-						_tree_selection.select_iter(iter);
-						return false;
-					}
-				}
+		_level.selection_set(ids);
+		_level.selection_changed.connect(on_level_selection_changed);
+	}
 
+	private void on_level_selection_changed(Gee.ArrayList<Guid?> selection)
+	{
+		_tree_selection.changed.disconnect(on_tree_selection_changed);
+		_tree_selection.unselect_all();
+
+		_tree_sort.foreach ((model, path, iter) => {
+			Value type;
+			model.get_value(iter, Column.TYPE, out type);
+			if ((int)type == ItemType.FOLDER)
 				return false;
-			});
 
-			_tree_selection.changed.connect(on_tree_selection_changed);
-		}
+			Value id;
+			model.get_value(iter, Column.GUID, out id);
 
-		private void on_object_editor_name_changed(Guid object_id, string name)
-		{
-			_tree_sort.foreach ((model, path, iter) => {
-				Value type;
-				model.get_value(iter, Column.TYPE, out type);
-				if ((int)type == ItemType.FOLDER)
+			foreach (Guid? guid in selection)
+			{
+				if ((Guid)id == guid)
+				{
+					_tree_selection.select_iter(iter);
 					return false;
+				}
+			}
 
-				Value guid;
-				model.get_value(iter, Column.GUID, out guid);
-				Guid guid_model = (Guid)guid;
+			return false;
+		});
 
-				if (guid_model == object_id)
-				{
-					Gtk.TreeIter iter_filter;
-					Gtk.TreeIter iter_model;
-					_tree_sort.convert_iter_to_child_iter(out iter_filter, iter);
-					_tree_filter.convert_iter_to_child_iter(out iter_model, iter_filter);
-
-					_tree_store.set(iter_model
-						, Column.NAME
-						, name
-						, -1
-						);
-
-					return true;
-				}
+		_tree_selection.changed.connect(on_tree_selection_changed);
+	}
 
+	private void on_object_editor_name_changed(Guid object_id, string name)
+	{
+		_tree_sort.foreach ((model, path, iter) => {
+			Value type;
+			model.get_value(iter, Column.TYPE, out type);
+			if ((int)type == ItemType.FOLDER)
 				return false;
-			});
-		}
 
-		private void on_database_key_changed(Guid id, string key)
-		{
-			if (id != GUID_ZERO)
-				return;
+			Value guid;
+			model.get_value(iter, Column.GUID, out guid);
+			Guid guid_model = (Guid)guid;
 
-			if (key != "units" && key != "sounds")
-				return;
+			if (guid_model == object_id)
+			{
+				Gtk.TreeIter iter_filter;
+				Gtk.TreeIter iter_model;
+				_tree_sort.convert_iter_to_child_iter(out iter_filter, iter);
+				_tree_filter.convert_iter_to_child_iter(out iter_model, iter_filter);
 
-			_tree_selection.changed.disconnect(on_tree_selection_changed);
-			_tree_view.model = null;
-			_tree_store.clear();
+				_tree_store.set(iter_model
+					, Column.NAME
+					, name
+					, -1
+					);
 
-			Gtk.TreeIter unit_iter;
-			Gtk.TreeIter light_iter;
-			Gtk.TreeIter sound_iter;
-			_tree_store.insert_with_values(out unit_iter
-				, null
-				, -1
-				, Column.TYPE
-				, ItemType.FOLDER
-				, Column.GUID
-				, GUID_ZERO
-				, Column.NAME
-				, "Units"
-				, -1
-				);
-			_tree_store.insert_with_values(out light_iter
-				, null
+				return true;
+			}
+
+			return false;
+		});
+	}
+
+	private void on_database_key_changed(Guid id, string key)
+	{
+		if (id != GUID_ZERO)
+			return;
+
+		if (key != "units" && key != "sounds")
+			return;
+
+		_tree_selection.changed.disconnect(on_tree_selection_changed);
+		_tree_view.model = null;
+		_tree_store.clear();
+
+		Gtk.TreeIter unit_iter;
+		Gtk.TreeIter light_iter;
+		Gtk.TreeIter sound_iter;
+		_tree_store.insert_with_values(out unit_iter
+			, null
+			, -1
+			, Column.TYPE
+			, ItemType.FOLDER
+			, Column.GUID
+			, GUID_ZERO
+			, Column.NAME
+			, "Units"
+			, -1
+			);
+		_tree_store.insert_with_values(out light_iter
+			, null
+			, -1
+			, Column.TYPE
+			, ItemType.FOLDER
+			, Column.GUID
+			, GUID_ZERO
+			, Column.NAME
+			, "Lights"
+			, -1
+			);
+		_tree_store.insert_with_values(out sound_iter
+			, null
+			, -1
+			, Column.TYPE
+			, ItemType.FOLDER
+			, Column.GUID
+			, GUID_ZERO
+			, Column.NAME
+			, "Sounds"
+			, -1
+			);
+
+		HashSet<Guid?> units  = _db.get_property_set(GUID_ZERO, "units", new HashSet<Guid?>());
+		HashSet<Guid?> sounds = _db.get_property_set(GUID_ZERO, "sounds", new HashSet<Guid?>());
+
+		foreach (Guid unit in units)
+		{
+			Unit u = new Unit(_level._db, unit, _level._prefabs);
+			Gtk.TreeIter iter;
+			_tree_store.insert_with_values(out iter
+				, u.is_light() ? light_iter : unit_iter
 				, -1
 				, Column.TYPE
-				, ItemType.FOLDER
+				, ItemType.UNIT
 				, Column.GUID
-				, GUID_ZERO
+				, unit
 				, Column.NAME
-				, "Lights"
+				, _level.object_editor_name(unit)
 				, -1
 				);
-			_tree_store.insert_with_values(out sound_iter
-				, null
+		}
+		foreach (Guid sound in sounds)
+		{
+			Gtk.TreeIter iter;
+			_tree_store.insert_with_values(out iter
+				, sound_iter
 				, -1
 				, Column.TYPE
-				, ItemType.FOLDER
+				, ItemType.SOUND
 				, Column.GUID
-				, GUID_ZERO
+				, sound
 				, Column.NAME
-				, "Sounds"
+				, _level.object_editor_name(sound)
 				, -1
 				);
+		}
 
-			HashSet<Guid?> units  = _db.get_property_set(GUID_ZERO, "units", new HashSet<Guid?>());
-			HashSet<Guid?> sounds = _db.get_property_set(GUID_ZERO, "sounds", new HashSet<Guid?>());
-
-			foreach (Guid unit in units)
-			{
-				Unit u = new Unit(_level._db, unit, _level._prefabs);
-				Gtk.TreeIter iter;
-				_tree_store.insert_with_values(out iter
-					, u.is_light() ? light_iter : unit_iter
-					, -1
-					, Column.TYPE
-					, ItemType.UNIT
-					, Column.GUID
-					, unit
-					, Column.NAME
-					, _level.object_editor_name(unit)
-					, -1
-					);
-			}
-			foreach (Guid sound in sounds)
-			{
-				Gtk.TreeIter iter;
-				_tree_store.insert_with_values(out iter
-					, sound_iter
-					, -1
-					, Column.TYPE
-					, ItemType.SOUND
-					, Column.GUID
-					, sound
-					, Column.NAME
-					, _level.object_editor_name(sound)
-					, -1
-					);
-			}
-
-			_tree_view.model = _tree_sort;
-			_tree_view.expand_all();
+		_tree_view.model = _tree_sort;
+		_tree_view.expand_all();
 
-			_tree_selection.changed.connect(on_tree_selection_changed);
-		}
+		_tree_selection.changed.connect(on_tree_selection_changed);
+	}
 
-		private void on_filter_entry_text_changed()
-		{
-			_tree_selection.changed.disconnect(on_tree_selection_changed);
-			_tree_filter.refilter();
-			_tree_selection.changed.connect(on_tree_selection_changed);
-		}
+	private void on_filter_entry_text_changed()
+	{
+		_tree_selection.changed.disconnect(on_tree_selection_changed);
+		_tree_filter.refilter();
+		_tree_selection.changed.connect(on_tree_selection_changed);
 	}
 }
+
+}

+ 71 - 70
tools/level_editor/panel_new_project.vala

@@ -8,77 +8,78 @@ using Gee;
 
 namespace Crown
 {
-	[GtkTemplate (ui = "/org/crown/level_editor/ui/panel_new_project.ui")]
-	public class PanelNewProject : Gtk.Viewport
+[GtkTemplate (ui = "/org/crown/level_editor/ui/panel_new_project.ui")]
+public class PanelNewProject : Gtk.Viewport
+{
+	// Data
+	LevelEditorApplication _application;
+	User _user;
+	Project _project;
+
+	// Widgets
+	[GtkChild]
+	Gtk.Button _button_back;
+
+	[GtkChild]
+	Gtk.Button _button_create;
+
+	[GtkChild]
+	Gtk.Entry _entry_name;
+
+	[GtkChild]
+	Gtk.FileChooserButton _file_chooser_button_location;
+
+	[GtkChild]
+	Gtk.Label _label_message;
+
+	public PanelNewProject(LevelEditorApplication app, User user, Project project)
 	{
 		// Data
-		LevelEditorApplication _application;
-		User _user;
-		Project _project;
-
-		// Widgets
-		[GtkChild]
-		Gtk.Button _button_back;
-
-		[GtkChild]
-		Gtk.Button _button_create;
-
-		[GtkChild]
-		Gtk.Entry _entry_name;
-
-		[GtkChild]
-		Gtk.FileChooserButton _file_chooser_button_location;
-
-		[GtkChild]
-		Gtk.Label _label_message;
-
-		public PanelNewProject(LevelEditorApplication app, User user, Project project)
-		{
-			// Data
-			_application = app;
-			_user = user;
-			_project = project;
-
-			_file_chooser_button_location.set_current_folder(_documents_dir.get_path());
-
-			_button_back.clicked.connect(() => {
-				_application.show_panel("panel_welcome", StackTransitionType.SLIDE_UP);
-			});
-
-			_button_create.clicked.connect(() => {
-				if (_entry_name.text == "")
-				{
-					_label_message.label = "Choose project name";
-					return;
-				}
-
-				GLib.File location = _file_chooser_button_location.get_file();
-				string? source_dir = location.get_path();
-
-				if (source_dir == null)
-				{
-					_label_message.label = "Location is not valid";
-					return;
-				}
-				if (GLib.FileUtils.test(source_dir, FileTest.IS_REGULAR))
-				{
-					_label_message.label = "Location must be an empty directory";
-					return;
-				}
-
-				if (!is_directory_empty(source_dir))
-				{
-					_label_message.label = "Location must be an empty directory";
-					return;
-				}
-
-				_label_message.label = "";
-
-				_user.add_recent_project(source_dir, _entry_name.text);
-				_application.show_panel("main_vbox");
-				_project.create_initial_files(source_dir);
-				_application.restart_compiler(source_dir, null);
-			});
-		}
+		_application = app;
+		_user = user;
+		_project = project;
+
+		_file_chooser_button_location.set_current_folder(_documents_dir.get_path());
+
+		_button_back.clicked.connect(() => {
+			_application.show_panel("panel_welcome", StackTransitionType.SLIDE_UP);
+		});
+
+		_button_create.clicked.connect(() => {
+			if (_entry_name.text == "")
+			{
+				_label_message.label = "Choose project name";
+				return;
+			}
+
+			GLib.File location = _file_chooser_button_location.get_file();
+			string? source_dir = location.get_path();
+
+			if (source_dir == null)
+			{
+				_label_message.label = "Location is not valid";
+				return;
+			}
+			if (GLib.FileUtils.test(source_dir, FileTest.IS_REGULAR))
+			{
+				_label_message.label = "Location must be an empty directory";
+				return;
+			}
+
+			if (!is_directory_empty(source_dir))
+			{
+				_label_message.label = "Location must be an empty directory";
+				return;
+			}
+
+			_label_message.label = "";
+
+			_user.add_recent_project(source_dir, _entry_name.text);
+			_application.show_panel("main_vbox");
+			_project.create_initial_files(source_dir);
+			_application.restart_compiler(source_dir, null);
+		});
 	}
 }
+
+}

+ 127 - 126
tools/level_editor/panel_projects_list.vala

@@ -8,135 +8,136 @@ using Gee;
 
 namespace Crown
 {
-	[GtkTemplate (ui = "/org/crown/level_editor/ui/panel_projects_list.ui")]
-	public class PanelProjectsList : Gtk.ScrolledWindow
+[GtkTemplate (ui = "/org/crown/level_editor/ui/panel_projects_list.ui")]
+public class PanelProjectsList : Gtk.ScrolledWindow
+{
+	// Data
+	LevelEditorApplication _application;
+	User _user;
+
+	// Widgets
+	[GtkChild]
+	Gtk.ListBox _list_projects;
+
+	[GtkChild]
+	Gtk.Button _button_new_project;
+
+	[GtkChild]
+	Gtk.Button _button_import_project;
+
+	public PanelProjectsList(LevelEditorApplication app, User user)
 	{
 		// Data
-		LevelEditorApplication _application;
-		User _user;
-
-		// Widgets
-		[GtkChild]
-		Gtk.ListBox _list_projects;
-
-		[GtkChild]
-		Gtk.Button _button_new_project;
-
-		[GtkChild]
-		Gtk.Button _button_import_project;
-
-		public PanelProjectsList(LevelEditorApplication app, User user)
-		{
-			// Data
-			_application = app;
-			_user = user;
-
-			_list_projects.set_sort_func((row1, row2) => {
-				int64 mtime1 = int64.parse(row1.get_data("mtime"));
-				int64 mtime2 = int64.parse(row2.get_data("mtime"));
-				return mtime1 > mtime2 ? -1 : 1; // LRU
-			});
-
-			_button_new_project.clicked.connect(() => {
-				_application.show_panel("panel_new_project", Gtk.StackTransitionType.SLIDE_DOWN);
-			});
-
-			_button_import_project.clicked.connect(() => {
-				DialogOpenProject op = new DialogOpenProject((Gtk.Window)this.get_toplevel());
-				if (op.run() != ResponseType.ACCEPT)
-				{
-					op.destroy();
-					return;
-				}
-
-				string source_dir = op.get_filename();
+		_application = app;
+		_user = user;
+
+		_list_projects.set_sort_func((row1, row2) => {
+			int64 mtime1 = int64.parse(row1.get_data("mtime"));
+			int64 mtime2 = int64.parse(row2.get_data("mtime"));
+			return mtime1 > mtime2 ? -1 : 1; // LRU
+		});
+
+		_button_new_project.clicked.connect(() => {
+			_application.show_panel("panel_new_project", Gtk.StackTransitionType.SLIDE_DOWN);
+		});
+
+		_button_import_project.clicked.connect(() => {
+			DialogOpenProject op = new DialogOpenProject((Gtk.Window)this.get_toplevel());
+			if (op.run() != ResponseType.ACCEPT)
+			{
 				op.destroy();
+				return;
+			}
+
+			string source_dir = op.get_filename();
+			op.destroy();
 
-				_user.add_recent_project(source_dir, source_dir);
-			});
-
-			_user.recent_project_added.connect(on_recent_project_added);
-			// _user.recent_project_removed.connect(on_recent_project_remove);
-		}
-
-		public void on_recent_project_added(string source_dir, string name, string time)
-		{
-			Gtk.Widget widget;
-
-			// Add row
-			Gtk.ListBoxRow row = new Gtk.ListBoxRow();
-			row.set_data("source_dir", source_dir);
-			row.set_data("mtime", time);
-			Gtk.Box vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
-
-			widget = new Gtk.Label(null);
-			widget.set_margin_start(12);
-			widget.set_margin_end(12);
-			widget.set_margin_top(8);
-			widget.set_margin_bottom(8);
-			((Gtk.Label)widget).set_markup("<b>%s</b>".printf(name));
-			((Gtk.Label)widget).set_xalign(0.0f);
-			vbox.pack_start(widget);
-
-			widget = new Gtk.Label(null);
-			widget.set_margin_start(12);
-			widget.set_margin_end(12);
-			widget.set_margin_bottom(8);
-			((Gtk.Label)widget).set_markup("<small>%s</small>".printf(source_dir));
-			((Gtk.Label)widget).set_xalign(0.0f);
-			vbox.pack_start(widget);
-
-			Gtk.Box hbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6);
-			hbox.pack_start(vbox);
-
-			Gtk.Button remove_button = new Gtk.Button.from_icon_name("list-remove-symbolic");
-			remove_button.get_style_context().add_class("flat");
-			remove_button.get_style_context().add_class("destructive-action");
-			remove_button.set_halign(Gtk.Align.CENTER);
-			remove_button.set_valign(Gtk.Align.CENTER);
-			remove_button.set_margin_end(12);
-			remove_button.clicked.connect(() => {
-				Gtk.MessageDialog md = new Gtk.MessageDialog((Gtk.Window)this.get_toplevel()
-					, Gtk.DialogFlags.MODAL
-					, Gtk.MessageType.WARNING
-					, Gtk.ButtonsType.NONE
-					, "Remove \"%s\" from the list?\n\nThis action removes the project from the list only, files on disk will not be deleted.".printf(source_dir)
-					);
-				md.add_button("_Cancel", ResponseType.CANCEL);
-				md.add_button("_Remove", ResponseType.YES);
-				md.set_default_response(ResponseType.CANCEL);
-				int rt = md.run();
-				md.destroy();
-
-				if (rt == ResponseType.CANCEL)
-					return;
-
-				_user.remove_recent_project(row.get_data("source_dir"));
-				_list_projects.remove(row);
-			});
-			hbox.pack_end(remove_button, false, false, 0);
-
-			Gtk.Button button_open = new Gtk.Button.with_label("Open");
-			button_open.get_style_context().add_class("flat");
-			button_open.set_halign(Gtk.Align.CENTER);
-			button_open.set_valign(Gtk.Align.CENTER);
-			// button_open.set_margin_end(12);
-			button_open.clicked.connect(() => {
-				string mtime = new GLib.DateTime.now_utc().to_unix().to_string();
-				row.set_data("mtime", mtime);
-				_list_projects.invalidate_sort();
-				_user.touch_recent_project(row.get_data("source_dir"), mtime);
-				_application.show_panel("main_vbox", Gtk.StackTransitionType.NONE);
-				_application.restart_compiler(source_dir, null);
-			});
-			hbox.pack_end(button_open, false, false, 0);
-
-			row.add(hbox);
-			_list_projects.add(row);
-			_list_projects.show_all(); // Otherwise the list is not always updated...
-
-			if (!GLib.FileUtils.test(source_dir, FileTest.EXISTS))
-				button_open.sensitive = false;
-		}
+			_user.add_recent_project(source_dir, source_dir);
+		});
+
+		_user.recent_project_added.connect(on_recent_project_added);
+		// _user.recent_project_removed.connect(on_recent_project_remove);
+	}
+
+	public void on_recent_project_added(string source_dir, string name, string time)
+	{
+		Gtk.Widget widget;
+
+		// Add row
+		Gtk.ListBoxRow row = new Gtk.ListBoxRow();
+		row.set_data("source_dir", source_dir);
+		row.set_data("mtime", time);
+		Gtk.Box vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
+
+		widget = new Gtk.Label(null);
+		widget.set_margin_start(12);
+		widget.set_margin_end(12);
+		widget.set_margin_top(8);
+		widget.set_margin_bottom(8);
+		((Gtk.Label)widget).set_markup("<b>%s</b>".printf(name));
+		((Gtk.Label)widget).set_xalign(0.0f);
+		vbox.pack_start(widget);
+
+		widget = new Gtk.Label(null);
+		widget.set_margin_start(12);
+		widget.set_margin_end(12);
+		widget.set_margin_bottom(8);
+		((Gtk.Label)widget).set_markup("<small>%s</small>".printf(source_dir));
+		((Gtk.Label)widget).set_xalign(0.0f);
+		vbox.pack_start(widget);
+
+		Gtk.Box hbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6);
+		hbox.pack_start(vbox);
+
+		Gtk.Button remove_button = new Gtk.Button.from_icon_name("list-remove-symbolic");
+		remove_button.get_style_context().add_class("flat");
+		remove_button.get_style_context().add_class("destructive-action");
+		remove_button.set_halign(Gtk.Align.CENTER);
+		remove_button.set_valign(Gtk.Align.CENTER);
+		remove_button.set_margin_end(12);
+		remove_button.clicked.connect(() => {
+			Gtk.MessageDialog md = new Gtk.MessageDialog((Gtk.Window)this.get_toplevel()
+				, Gtk.DialogFlags.MODAL
+				, Gtk.MessageType.WARNING
+				, Gtk.ButtonsType.NONE
+				, "Remove \"%s\" from the list?\n\nThis action removes the project from the list only, files on disk will not be deleted.".printf(source_dir)
+				);
+			md.add_button("_Cancel", ResponseType.CANCEL);
+			md.add_button("_Remove", ResponseType.YES);
+			md.set_default_response(ResponseType.CANCEL);
+			int rt = md.run();
+			md.destroy();
+
+			if (rt == ResponseType.CANCEL)
+				return;
+
+			_user.remove_recent_project(row.get_data("source_dir"));
+			_list_projects.remove(row);
+		});
+		hbox.pack_end(remove_button, false, false, 0);
+
+		Gtk.Button button_open = new Gtk.Button.with_label("Open");
+		button_open.get_style_context().add_class("flat");
+		button_open.set_halign(Gtk.Align.CENTER);
+		button_open.set_valign(Gtk.Align.CENTER);
+		// button_open.set_margin_end(12);
+		button_open.clicked.connect(() => {
+			string mtime = new GLib.DateTime.now_utc().to_unix().to_string();
+			row.set_data("mtime", mtime);
+			_list_projects.invalidate_sort();
+			_user.touch_recent_project(row.get_data("source_dir"), mtime);
+			_application.show_panel("main_vbox", Gtk.StackTransitionType.NONE);
+			_application.restart_compiler(source_dir, null);
+		});
+		hbox.pack_end(button_open, false, false, 0);
+
+		row.add(hbox);
+		_list_projects.add(row);
+		_list_projects.show_all(); // Otherwise the list is not always updated...
+
+		if (!GLib.FileUtils.test(source_dir, FileTest.EXISTS))
+			button_open.sensitive = false;
 	}
 }
+
+}

+ 6 - 5
tools/level_editor/panel_welcome.vala

@@ -7,11 +7,12 @@ using Gtk;
 
 namespace Crown
 {
-	public class PanelWelcome : Gtk.Box
+public class PanelWelcome : Gtk.Box
+{
+	public PanelWelcome()
 	{
-		public PanelWelcome()
-		{
-			Object(orientation: Gtk.Orientation.HORIZONTAL, spacing: 0);
-		}
+		Object(orientation: Gtk.Orientation.HORIZONTAL, spacing: 0);
 	}
 }
+
+}

+ 91 - 90
tools/level_editor/preferences_dialog.vala

@@ -8,97 +8,98 @@ using Gee;
 
 namespace Crown
 {
-	[GtkTemplate (ui = "/org/crown/level_editor/ui/preferences_dialog.ui")]
-	public class PreferencesDialog : Gtk.Dialog
+[GtkTemplate (ui = "/org/crown/level_editor/ui/preferences_dialog.ui")]
+public class PreferencesDialog : Gtk.Dialog
+{
+	// Data
+	LevelEditorApplication _application;
+
+	// Widgets
+	[GtkChild]
+	ColorButtonVector3 _grid_color_button;
+
+	[GtkChild]
+	ColorButtonVector3 _grid_disabled_color_button;
+
+	[GtkChild]
+	ColorButtonVector3 _axis_x_color_button;
+
+	[GtkChild]
+	ColorButtonVector3 _axis_y_color_button;
+
+	[GtkChild]
+	ColorButtonVector3 _axis_z_color_button;
+
+	[GtkChild]
+	ColorButtonVector3 _axis_selected_color_button;
+
+	[GtkChild]
+	Gtk.SpinButton _gizmo_size_spin_button;
+
+	[GtkChild]
+	Gtk.SpinButton _level_autosave_spin_button;
+
+	public PreferencesDialog(LevelEditorApplication app)
 	{
 		// Data
-		LevelEditorApplication _application;
-
-		// Widgets
-		[GtkChild]
-		ColorButtonVector3 _grid_color_button;
-
-		[GtkChild]
-		ColorButtonVector3 _grid_disabled_color_button;
-
-		[GtkChild]
-		ColorButtonVector3 _axis_x_color_button;
-
-		[GtkChild]
-		ColorButtonVector3 _axis_y_color_button;
-
-		[GtkChild]
-		ColorButtonVector3 _axis_z_color_button;
-
-		[GtkChild]
-		ColorButtonVector3 _axis_selected_color_button;
-
-		[GtkChild]
-		Gtk.SpinButton _gizmo_size_spin_button;
-
-		[GtkChild]
-		Gtk.SpinButton _level_autosave_spin_button;
-
-		public PreferencesDialog(LevelEditorApplication app)
-		{
-			// Data
-			_application = app;
-
-			this.title = "Preferences";
-		}
-
-		[GtkCallback]
-		private void on_color_set()
-		{
-			_application._editor.send_script(LevelEditorApi.set_color("grid", _grid_color_button.value));
-			_application._editor.send_script(LevelEditorApi.set_color("grid_disabled", _grid_disabled_color_button.value));
-			_application._editor.send_script(LevelEditorApi.set_color("axis_x", _axis_x_color_button.value));
-			_application._editor.send_script(LevelEditorApi.set_color("axis_y", _axis_y_color_button.value));
-			_application._editor.send_script(LevelEditorApi.set_color("axis_z", _axis_z_color_button.value));
-			_application._editor.send_script(LevelEditorApi.set_color("axis_selected", _axis_selected_color_button.value));
-		}
-
-		[GtkCallback]
-		private void on_gizmo_size_value_changed()
-		{
-			_application._editor.send_script("Gizmo.size = %f".printf(_gizmo_size_spin_button.value));
-		}
-
-		[GtkCallback]
-		private void on_level_autosave_value_changed()
-		{
-			_application.set_autosave_timer((uint)_level_autosave_spin_button.value);
-		}
-
-		public void load(Hashtable preferences)
-		{
-			_grid_color_button.value          = Vector3.from_array(preferences.has_key("grid") ? (Gee.ArrayList<GLib.Value?>)preferences["grid"] : _grid_color_button.value.to_array());
-			_grid_disabled_color_button.value = Vector3.from_array(preferences.has_key("grid_disabled") ? (Gee.ArrayList<GLib.Value?>)preferences["grid_disabled"] : _grid_disabled_color_button.value.to_array());
-			_axis_x_color_button.value        = Vector3.from_array(preferences.has_key("axis_x") ? (Gee.ArrayList<GLib.Value?>)preferences["axis_x"] : _axis_x_color_button.value.to_array());
-			_axis_y_color_button.value        = Vector3.from_array(preferences.has_key("axis_y") ? (Gee.ArrayList<GLib.Value?>)preferences["axis_y"] : _axis_y_color_button.value.to_array());
-			_axis_z_color_button.value        = Vector3.from_array(preferences.has_key("axis_z") ? (Gee.ArrayList<GLib.Value?>)preferences["axis_z"] : _axis_z_color_button.value.to_array());
-			_axis_selected_color_button.value = Vector3.from_array(preferences.has_key("axis_selected") ? (Gee.ArrayList<GLib.Value?>)preferences["axis_selected"] : _axis_selected_color_button.value.to_array());
-			_gizmo_size_spin_button.value     = preferences.has_key("gizmo_size") ? (double)preferences["gizmo_size"] : _gizmo_size_spin_button.value;
-			_level_autosave_spin_button.value = preferences.has_key("autosave_timer") ? (double)preferences["autosave_timer"] : _level_autosave_spin_button.value;
-		}
-
-		public void save(Hashtable preferences)
-		{
-			preferences["grid"]           = _grid_color_button.value.to_array();
-			preferences["grid_disabled"]  = _grid_disabled_color_button.value.to_array();
-			preferences["axis_x"]         = _axis_x_color_button.value.to_array();
-			preferences["axis_y"]         = _axis_y_color_button.value.to_array();
-			preferences["axis_z"]         = _axis_z_color_button.value.to_array();
-			preferences["axis_selected"]  = _axis_selected_color_button.value.to_array();
-			preferences["gizmo_size"]     = _gizmo_size_spin_button.value;
-			preferences["autosave_timer"] = _level_autosave_spin_button.value;
-		}
-
-		public void apply()
-		{
-			GLib.Signal.emit_by_name(_grid_color_button, "color-set");
-			GLib.Signal.emit_by_name(_gizmo_size_spin_button, "value-changed");
-			GLib.Signal.emit_by_name(_level_autosave_spin_button, "value-changed");
-		}
+		_application = app;
+
+		this.title = "Preferences";
+	}
+
+	[GtkCallback]
+	private void on_color_set()
+	{
+		_application._editor.send_script(LevelEditorApi.set_color("grid", _grid_color_button.value));
+		_application._editor.send_script(LevelEditorApi.set_color("grid_disabled", _grid_disabled_color_button.value));
+		_application._editor.send_script(LevelEditorApi.set_color("axis_x", _axis_x_color_button.value));
+		_application._editor.send_script(LevelEditorApi.set_color("axis_y", _axis_y_color_button.value));
+		_application._editor.send_script(LevelEditorApi.set_color("axis_z", _axis_z_color_button.value));
+		_application._editor.send_script(LevelEditorApi.set_color("axis_selected", _axis_selected_color_button.value));
 	}
+
+	[GtkCallback]
+	private void on_gizmo_size_value_changed()
+	{
+		_application._editor.send_script("Gizmo.size = %f".printf(_gizmo_size_spin_button.value));
+	}
+
+	[GtkCallback]
+	private void on_level_autosave_value_changed()
+	{
+		_application.set_autosave_timer((uint)_level_autosave_spin_button.value);
+	}
+
+	public void load(Hashtable preferences)
+	{
+		_grid_color_button.value          = Vector3.from_array(preferences.has_key("grid") ? (Gee.ArrayList<GLib.Value?>)preferences["grid"] : _grid_color_button.value.to_array());
+		_grid_disabled_color_button.value = Vector3.from_array(preferences.has_key("grid_disabled") ? (Gee.ArrayList<GLib.Value?>)preferences["grid_disabled"] : _grid_disabled_color_button.value.to_array());
+		_axis_x_color_button.value        = Vector3.from_array(preferences.has_key("axis_x") ? (Gee.ArrayList<GLib.Value?>)preferences["axis_x"] : _axis_x_color_button.value.to_array());
+		_axis_y_color_button.value        = Vector3.from_array(preferences.has_key("axis_y") ? (Gee.ArrayList<GLib.Value?>)preferences["axis_y"] : _axis_y_color_button.value.to_array());
+		_axis_z_color_button.value        = Vector3.from_array(preferences.has_key("axis_z") ? (Gee.ArrayList<GLib.Value?>)preferences["axis_z"] : _axis_z_color_button.value.to_array());
+		_axis_selected_color_button.value = Vector3.from_array(preferences.has_key("axis_selected") ? (Gee.ArrayList<GLib.Value?>)preferences["axis_selected"] : _axis_selected_color_button.value.to_array());
+		_gizmo_size_spin_button.value     = preferences.has_key("gizmo_size") ? (double)preferences["gizmo_size"] : _gizmo_size_spin_button.value;
+		_level_autosave_spin_button.value = preferences.has_key("autosave_timer") ? (double)preferences["autosave_timer"] : _level_autosave_spin_button.value;
+	}
+
+	public void save(Hashtable preferences)
+	{
+		preferences["grid"]           = _grid_color_button.value.to_array();
+		preferences["grid_disabled"]  = _grid_disabled_color_button.value.to_array();
+		preferences["axis_x"]         = _axis_x_color_button.value.to_array();
+		preferences["axis_y"]         = _axis_y_color_button.value.to_array();
+		preferences["axis_z"]         = _axis_z_color_button.value.to_array();
+		preferences["axis_selected"]  = _axis_selected_color_button.value.to_array();
+		preferences["gizmo_size"]     = _gizmo_size_spin_button.value;
+		preferences["autosave_timer"] = _level_autosave_spin_button.value;
+	}
+
+	public void apply()
+	{
+		GLib.Signal.emit_by_name(_grid_color_button, "color-set");
+		GLib.Signal.emit_by_name(_gizmo_size_spin_button, "value-changed");
+		GLib.Signal.emit_by_name(_level_autosave_spin_button, "value-changed");
+	}
+}
+
 }

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 586 - 769
tools/level_editor/project.vala


+ 438 - 437
tools/level_editor/project_browser.vala

@@ -8,496 +8,448 @@ using Gee;
 
 namespace Crown
 {
-	// Returns true if the row should be hidden
-	private bool row_should_be_hidden(string type, string name)
-	{
-		return type == "<folder>" && name == "core"
-			|| type == "dds"
-			|| type == "importer_settings"
-			|| type == "ktx"
-			|| type == "ogg"
-			|| type == "png"
-			|| type == "pvr"
-			|| type == "wav"
-			|| name == Project.LEVEL_EDITOR_TEST_NAME
-			;
-	}
-
-	public class ProjectBrowser : Gtk.Box
-	{
-		// Data
-		public Project _project;
-		public Gtk.TreeStore _tree_store;
-
-		// Widgets
-		public Gtk.TreeModelFilter _tree_filter;
-		public Gtk.TreeModelSort _tree_sort;
-		public Gtk.TreeView _tree_view;
-		public Gtk.TreeSelection _tree_selection;
-		public Gtk.ScrolledWindow _scrolled_window;
+// Returns true if the row should be hidden
+private bool row_should_be_hidden(string type, string name)
+{
+	return type == "<folder>" && name == "core"
+		|| type == "dds"
+		|| type == "importer_settings"
+		|| type == "ktx"
+		|| type == "ogg"
+		|| type == "png"
+		|| type == "pvr"
+		|| type == "wav"
+		|| name == Project.LEVEL_EDITOR_TEST_NAME
+		;
+}
 
-		// Signals
-		public signal void resource_selected(string type, string name);
+public class ProjectBrowser : Gtk.Box
+{
+	// Data
+	public Project _project;
+	public Gtk.TreeStore _tree_store;
 
-		public static string basename(string type, string name)
-		{
-			return type == "" ? name : name + "." + type;
-		}
+	// Widgets
+	public Gtk.TreeModelFilter _tree_filter;
+	public Gtk.TreeModelSort _tree_sort;
+	public Gtk.TreeView _tree_view;
+	public Gtk.TreeSelection _tree_selection;
+	public Gtk.ScrolledWindow _scrolled_window;
 
-		public static string filename(Project project, string type, string name)
-		{
-			string bn = ProjectBrowser.basename(type, name);
-			return Path.build_filename(project.source_dir(), bn);
-		}
+	// Signals
+	public signal void resource_selected(string type, string name);
 
-		public ProjectBrowser(Project? project, ProjectStore project_store)
-		{
-			Object(orientation: Gtk.Orientation.VERTICAL, spacing: 0);
+	public static string basename(string type, string name)
+	{
+		return type == "" ? name : name + "." + type;
+	}
 
-			// Data
-			_project = project;
-			_tree_store = project_store._tree_store;
+	public static string filename(Project project, string type, string name)
+	{
+		string bn = ProjectBrowser.basename(type, name);
+		return Path.build_filename(project.source_dir(), bn);
+	}
 
-			// Widgets
-			_tree_filter = new Gtk.TreeModelFilter(_tree_store, null);
-			_tree_filter.set_visible_func((model, iter) => {
-				_tree_view.expand_row(new Gtk.TreePath.first(), false);
+	public ProjectBrowser(Project? project, ProjectStore project_store)
+	{
+		Object(orientation: Gtk.Orientation.VERTICAL, spacing: 0);
 
-				Value type;
-				Value name;
-				model.get_value(iter, ProjectStore.Column.TYPE, out type);
-				model.get_value(iter, ProjectStore.Column.NAME, out name);
-
-				return (string)type != null
-					&& (string)name != null
-					&& !row_should_be_hidden((string)type, (string)name)
-					;
-			});
-
-			_tree_sort = new Gtk.TreeModelSort.with_model(_tree_filter);
-			_tree_sort.set_default_sort_func((model, iter_a, iter_b) => {
-				Value type_a;
-				Value type_b;
-				model.get_value(iter_a, ProjectStore.Column.TYPE, out type_a);
-				model.get_value(iter_b, ProjectStore.Column.TYPE, out type_b);
-
-				if ((string)type_a == "<folder>")
-				{
-					if ((string)type_b != "<folder>")
-						return -1;
-				}
-				else if ((string)type_b == "<folder>")
-				{
-					if ((string)type_a != "<folder>")
-						return 1;
-				}
+		// Data
+		_project = project;
+		_tree_store = project_store._tree_store;
 
-				Value id_a;
-				Value id_b;
-				model.get_value(iter_a, ProjectStore.Column.SEGMENT, out id_a);
-				model.get_value(iter_b, ProjectStore.Column.SEGMENT, out id_b);
-				return strcmp((string)id_a, (string)id_b);
-			});
-
-			Gtk.TreeViewColumn column = new Gtk.TreeViewColumn();
-			Gtk.CellRendererPixbuf cell_pixbuf = new Gtk.CellRendererPixbuf();
-			Gtk.CellRendererText cell_text = new Gtk.CellRendererText();
-			column.pack_start(cell_pixbuf, false);
-			column.pack_start(cell_text, true);
-			column.set_cell_data_func(cell_pixbuf, (cell_layout, cell, model, iter) => {
-				Value type;
-				model.get_value(iter, ProjectStore.Column.TYPE, out type);
-
-				// https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
-				if ((string)type == "<folder>")
-					cell.set_property("icon-name", "folder-symbolic");
-				else if ((string)type == "state_machine")
-					cell.set_property("icon-name", "text-x-generic-symbolic");
-				else if ((string)type == "config")
-					cell.set_property("icon-name", "text-x-generic-symbolic");
-				else if ((string)type == "font")
-					cell.set_property("icon-name", "font-x-generic-symbolic");
-				else if ((string)type == "unit")
-					cell.set_property("icon-name", "text-x-generic-symbolic");
-				else if ((string)type == "level")
-					cell.set_property("icon-name", "text-x-generic-symbolic");
-				else if ((string)type == "material")
-					cell.set_property("icon-name", "text-x-generic-symbolic");
-				else if ((string)type == "mesh")
-					cell.set_property("icon-name", "text-x-generic-symbolic");
-				else if ((string)type == "package")
-					cell.set_property("icon-name", "package-x-generic-symbolic");
-				else if ((string)type == "physics_config")
-					cell.set_property("icon-name", "text-x-generic-symbolic");
-				else if ((string)type == "lua")
-					cell.set_property("icon-name", "x-office-document-symbolic");
-				else if ((string)type == "shader")
-					cell.set_property("icon-name", "text-x-script-symbolic");
-				else if ((string)type == "sound")
-					cell.set_property("icon-name", "audio-x-generic-symbolic");
-				else if ((string)type == "sprite_animation")
-					cell.set_property("icon-name", "text-x-generic-symbolic");
-				else if ((string)type == "sprite")
-					cell.set_property("icon-name", "text-x-generic-symbolic");
-				else if ((string)type == "texture")
-					cell.set_property("icon-name", "image-x-generic-symbolic");
-				else
-					cell.set_property("icon-name", "text-x-generic-symbolic");
-			});
-			column.set_cell_data_func(cell_text, (cell_layout, cell, model, iter) => {
-				Value segment;
-				Value type;
-				model.get_value(iter, ProjectStore.Column.SEGMENT, out segment);
-				model.get_value(iter, ProjectStore.Column.TYPE, out type);
+		// Widgets
+		_tree_filter = new Gtk.TreeModelFilter(_tree_store, null);
+		_tree_filter.set_visible_func((model, iter) => {
+			_tree_view.expand_row(new Gtk.TreePath.first(), false);
+
+			Value type;
+			Value name;
+			model.get_value(iter, ProjectStore.Column.TYPE, out type);
+			model.get_value(iter, ProjectStore.Column.NAME, out name);
+
+			return (string)type != null
+				&& (string)name != null
+				&& !row_should_be_hidden((string)type, (string)name)
+				;
+		});
+
+		_tree_sort = new Gtk.TreeModelSort.with_model(_tree_filter);
+		_tree_sort.set_default_sort_func((model, iter_a, iter_b) => {
+			Value type_a;
+			Value type_b;
+			model.get_value(iter_a, ProjectStore.Column.TYPE, out type_a);
+			model.get_value(iter_b, ProjectStore.Column.TYPE, out type_b);
+
+			if ((string)type_a == "<folder>")
+			{
+				if ((string)type_b != "<folder>")
+					return -1;
+			}
+			else if ((string)type_b == "<folder>")
+			{
+				if ((string)type_a != "<folder>")
+					return 1;
+			}
 
-				if ((string)type == "<folder>")
-					cell.set_property("text", (string)segment);
-				else
-					cell.set_property("text", ProjectBrowser.basename((string)type, (string)segment));
-			});
-			_tree_view = new Gtk.TreeView();
-			_tree_view.append_column(column);
+			Value id_a;
+			Value id_b;
+			model.get_value(iter_a, ProjectStore.Column.SEGMENT, out id_a);
+			model.get_value(iter_b, ProjectStore.Column.SEGMENT, out id_b);
+			return strcmp((string)id_a, (string)id_b);
+		});
+
+		Gtk.TreeViewColumn column = new Gtk.TreeViewColumn();
+		Gtk.CellRendererPixbuf cell_pixbuf = new Gtk.CellRendererPixbuf();
+		Gtk.CellRendererText cell_text = new Gtk.CellRendererText();
+		column.pack_start(cell_pixbuf, false);
+		column.pack_start(cell_text, true);
+		column.set_cell_data_func(cell_pixbuf, (cell_layout, cell, model, iter) => {
+			Value type;
+			model.get_value(iter, ProjectStore.Column.TYPE, out type);
+
+			// https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html
+			if ((string)type == "<folder>")
+				cell.set_property("icon-name", "folder-symbolic");
+			else if ((string)type == "state_machine")
+				cell.set_property("icon-name", "text-x-generic-symbolic");
+			else if ((string)type == "config")
+				cell.set_property("icon-name", "text-x-generic-symbolic");
+			else if ((string)type == "font")
+				cell.set_property("icon-name", "font-x-generic-symbolic");
+			else if ((string)type == "unit")
+				cell.set_property("icon-name", "text-x-generic-symbolic");
+			else if ((string)type == "level")
+				cell.set_property("icon-name", "text-x-generic-symbolic");
+			else if ((string)type == "material")
+				cell.set_property("icon-name", "text-x-generic-symbolic");
+			else if ((string)type == "mesh")
+				cell.set_property("icon-name", "text-x-generic-symbolic");
+			else if ((string)type == "package")
+				cell.set_property("icon-name", "package-x-generic-symbolic");
+			else if ((string)type == "physics_config")
+				cell.set_property("icon-name", "text-x-generic-symbolic");
+			else if ((string)type == "lua")
+				cell.set_property("icon-name", "x-office-document-symbolic");
+			else if ((string)type == "shader")
+				cell.set_property("icon-name", "text-x-script-symbolic");
+			else if ((string)type == "sound")
+				cell.set_property("icon-name", "audio-x-generic-symbolic");
+			else if ((string)type == "sprite_animation")
+				cell.set_property("icon-name", "text-x-generic-symbolic");
+			else if ((string)type == "sprite")
+				cell.set_property("icon-name", "text-x-generic-symbolic");
+			else if ((string)type == "texture")
+				cell.set_property("icon-name", "image-x-generic-symbolic");
+			else
+				cell.set_property("icon-name", "text-x-generic-symbolic");
+		});
+		column.set_cell_data_func(cell_text, (cell_layout, cell, model, iter) => {
+			Value segment;
+			Value type;
+			model.get_value(iter, ProjectStore.Column.SEGMENT, out segment);
+			model.get_value(iter, ProjectStore.Column.TYPE, out type);
+
+			if ((string)type == "<folder>")
+				cell.set_property("text", (string)segment);
+			else
+				cell.set_property("text", ProjectBrowser.basename((string)type, (string)segment));
+		});
+		_tree_view = new Gtk.TreeView();
+		_tree_view.append_column(column);
 /*
-			// This is for debugging only
-			_tree_view.insert_column_with_attributes(-1
-				, "Segment"
-				, new Gtk.CellRendererText()
-				, "text"
-				, ProjectStore.Column.SEGMENT
-				, null
-				);
-			_tree_view.insert_column_with_attributes(-1
-				, "Name"
-				, new Gtk.CellRendererText()
-				, "text"
-				, ProjectStore.Column.NAME
-				, null
-				);
-			_tree_view.insert_column_with_attributes(-1
-				, "Type"
-				, new Gtk.CellRendererText()
-				, "text"
-				, ProjectStore.Column.TYPE
-				, null
-				);
+		// This is for debugging only
+		_tree_view.insert_column_with_attributes(-1
+			, "Segment"
+			, new Gtk.CellRendererText()
+			, "text"
+			, ProjectStore.Column.SEGMENT
+			, null
+			);
+		_tree_view.insert_column_with_attributes(-1
+			, "Name"
+			, new Gtk.CellRendererText()
+			, "text"
+			, ProjectStore.Column.NAME
+			, null
+			);
+		_tree_view.insert_column_with_attributes(-1
+			, "Type"
+			, new Gtk.CellRendererText()
+			, "text"
+			, ProjectStore.Column.TYPE
+			, null
+			);
 */
-			_tree_view.model = _tree_sort;
-			_tree_view.headers_visible = false;
-			_tree_view.can_focus = false;
-			_tree_view.button_press_event.connect(on_button_pressed);
-			_tree_view.button_release_event.connect(on_button_released);
+		_tree_view.model = _tree_sort;
+		_tree_view.headers_visible = false;
+		_tree_view.can_focus = false;
+		_tree_view.button_press_event.connect(on_button_pressed);
+		_tree_view.button_release_event.connect(on_button_released);
 
-			_tree_selection = _tree_view.get_selection();
-			_tree_selection.set_mode(Gtk.SelectionMode.BROWSE);
+		_tree_selection = _tree_view.get_selection();
+		_tree_selection.set_mode(Gtk.SelectionMode.BROWSE);
 
-			_scrolled_window = new Gtk.ScrolledWindow(null, null);
-			_scrolled_window.add(_tree_view);
+		_scrolled_window = new Gtk.ScrolledWindow(null, null);
+		_scrolled_window.add(_tree_view);
 
-			this.pack_start(_scrolled_window, true, true, 0);
-		}
+		this.pack_start(_scrolled_window, true, true, 0);
+	}
 
-		private bool on_button_pressed(Gdk.EventButton ev)
+	private bool on_button_pressed(Gdk.EventButton ev)
+	{
+		if (ev.button == Gdk.BUTTON_SECONDARY)
 		{
-			if (ev.button == Gdk.BUTTON_SECONDARY)
+			Gtk.TreePath path;
+			int cell_x;
+			int cell_y;
+			if (_tree_view.get_path_at_pos((int)ev.x, (int)ev.y, out path, null, out cell_x, out cell_y))
 			{
-				Gtk.TreePath path;
-				int cell_x;
-				int cell_y;
-				if (_tree_view.get_path_at_pos((int)ev.x, (int)ev.y, out path, null, out cell_x, out cell_y))
-				{
-					Gtk.TreeIter iter;
-					_tree_view.model.get_iter(out iter, path);
-
-					Value type;
-					Value name;
-					_tree_view.model.get_value(iter, ProjectStore.Column.TYPE, out type);
-					_tree_view.model.get_value(iter, ProjectStore.Column.NAME, out name);
-
-					if (type == "<folder>")
-					{
-						Gtk.Menu menu = new Gtk.Menu();
-						Gtk.MenuItem mi;
-
-						mi = new Gtk.MenuItem.with_label("Import...");
-						mi.activate.connect(() => {
-							GLib.File file = GLib.File.new_for_path(GLib.Path.build_filename(_project.source_dir(), (string)name));
-							_project.import(file.get_path(), (Gtk.Window)this.get_toplevel());
-						});
-						menu.add(mi);
+				Gtk.TreeIter iter;
+				_tree_view.model.get_iter(out iter, path);
 
-						mi = new Gtk.SeparatorMenuItem();
-						menu.add(mi);
-
-						mi = new Gtk.MenuItem.with_label("New Script...");
-						mi.activate.connect(() => {
-							Gtk.Dialog dg = new Gtk.Dialog.with_buttons("Script Name"
-								, (Gtk.Window)this.get_toplevel()
-								, DialogFlags.MODAL
-								, "Cancel"
-								, ResponseType.CANCEL
-								, "Ok"
-								, ResponseType.OK
-								, null
-								);
-
-							Gtk.Entry sb = new Gtk.Entry();
-							sb.activate.connect(() => { dg.response(ResponseType.OK); });
-							dg.get_content_area().add(sb);
-							dg.skip_taskbar_hint = true;
-							dg.show_all();
+				Value type;
+				Value name;
+				_tree_view.model.get_value(iter, ProjectStore.Column.TYPE, out type);
+				_tree_view.model.get_value(iter, ProjectStore.Column.NAME, out name);
 
-							if (dg.run() == (int)ResponseType.OK)
+				if (type == "<folder>")
+				{
+					Gtk.Menu menu = new Gtk.Menu();
+					Gtk.MenuItem mi;
+
+					mi = new Gtk.MenuItem.with_label("Import...");
+					mi.activate.connect(() => {
+						GLib.File file = GLib.File.new_for_path(GLib.Path.build_filename(_project.source_dir(), (string)name));
+						_project.import(file.get_path(), (Gtk.Window)this.get_toplevel());
+					});
+					menu.add(mi);
+
+					mi = new Gtk.SeparatorMenuItem();
+					menu.add(mi);
+
+					mi = new Gtk.MenuItem.with_label("New Script...");
+					mi.activate.connect(() => {
+						Gtk.Dialog dg = new Gtk.Dialog.with_buttons("Script Name"
+							, (Gtk.Window)this.get_toplevel()
+							, DialogFlags.MODAL
+							, "Cancel"
+							, ResponseType.CANCEL
+							, "Ok"
+							, ResponseType.OK
+							, null
+							);
+
+						Gtk.Entry sb = new Gtk.Entry();
+						sb.activate.connect(() => { dg.response(ResponseType.OK); });
+						dg.get_content_area().add(sb);
+						dg.skip_taskbar_hint = true;
+						dg.show_all();
+
+						if (dg.run() == (int)ResponseType.OK)
+						{
+							if (sb.text.strip() == "")
 							{
-								if (sb.text.strip() == "")
-								{
-									dg.destroy();
-									return;
-								}
-
-								_project.create_script((string)name, sb.text, true);
+								dg.destroy();
+								return;
 							}
 
-							dg.destroy();
-						});
-						menu.add(mi);
-
-						mi = new Gtk.MenuItem.with_label("New Script (Unit)...");
-						mi.activate.connect(() => {
-							Gtk.Dialog dg = new Gtk.Dialog.with_buttons("Script Name"
-								, (Gtk.Window)this.get_toplevel()
-								, DialogFlags.MODAL
-								, "Cancel"
-								, ResponseType.CANCEL
-								, "Ok"
-								, ResponseType.OK
-								, null
-								);
-
-							Gtk.Entry sb = new Gtk.Entry();
-							sb.activate.connect(() => { dg.response(ResponseType.OK); });
-							dg.get_content_area().add(sb);
-							dg.skip_taskbar_hint = true;
-							dg.show_all();
+							_project.create_script((string)name, sb.text, true);
+						}
 
-							if (dg.run() == (int)ResponseType.OK)
+						dg.destroy();
+					});
+					menu.add(mi);
+
+					mi = new Gtk.MenuItem.with_label("New Script (Unit)...");
+					mi.activate.connect(() => {
+						Gtk.Dialog dg = new Gtk.Dialog.with_buttons("Script Name"
+							, (Gtk.Window)this.get_toplevel()
+							, DialogFlags.MODAL
+							, "Cancel"
+							, ResponseType.CANCEL
+							, "Ok"
+							, ResponseType.OK
+							, null
+							);
+
+						Gtk.Entry sb = new Gtk.Entry();
+						sb.activate.connect(() => { dg.response(ResponseType.OK); });
+						dg.get_content_area().add(sb);
+						dg.skip_taskbar_hint = true;
+						dg.show_all();
+
+						if (dg.run() == (int)ResponseType.OK)
+						{
+							if (sb.text.strip() == "")
 							{
-								if (sb.text.strip() == "")
-								{
-									dg.destroy();
-									return;
-								}
-
-								_project.create_script((string)name, sb.text, false);
+								dg.destroy();
+								return;
 							}
 
-							dg.destroy();
-						});
-						menu.add(mi);
-
-						mi = new Gtk.SeparatorMenuItem();
-						menu.add(mi);
-
-						mi = new Gtk.MenuItem.with_label("New Unit...");
-						mi.activate.connect(() => {
-							Gtk.Dialog dg = new Gtk.Dialog.with_buttons("Unit Name"
-								, (Gtk.Window)this.get_toplevel()
-								, DialogFlags.MODAL
-								, "Cancel"
-								, ResponseType.CANCEL
-								, "Ok"
-								, ResponseType.OK
-								, null
-								);
-
-							Gtk.Entry sb = new Gtk.Entry();
-							sb.activate.connect(() => { dg.response(ResponseType.OK); });
-							dg.get_content_area().add(sb);
-							dg.skip_taskbar_hint = true;
-							dg.show_all();
+							_project.create_script((string)name, sb.text, false);
+						}
 
-							if (dg.run() == (int)ResponseType.OK)
+						dg.destroy();
+					});
+					menu.add(mi);
+
+					mi = new Gtk.SeparatorMenuItem();
+					menu.add(mi);
+
+					mi = new Gtk.MenuItem.with_label("New Unit...");
+					mi.activate.connect(() => {
+						Gtk.Dialog dg = new Gtk.Dialog.with_buttons("Unit Name"
+							, (Gtk.Window)this.get_toplevel()
+							, DialogFlags.MODAL
+							, "Cancel"
+							, ResponseType.CANCEL
+							, "Ok"
+							, ResponseType.OK
+							, null
+							);
+
+						Gtk.Entry sb = new Gtk.Entry();
+						sb.activate.connect(() => { dg.response(ResponseType.OK); });
+						dg.get_content_area().add(sb);
+						dg.skip_taskbar_hint = true;
+						dg.show_all();
+
+						if (dg.run() == (int)ResponseType.OK)
+						{
+							if (sb.text.strip() == "")
 							{
-								if (sb.text.strip() == "")
-								{
-									dg.destroy();
-									return;
-								}
-
-								_project.create_unit((string)name, sb.text);
+								dg.destroy();
+								return;
 							}
 
-							dg.destroy();
-						});
-						menu.add(mi);
-
-						mi = new Gtk.SeparatorMenuItem();
-						menu.add(mi);
-
-						mi = new Gtk.MenuItem.with_label("New Folder...");
-						mi.activate.connect(() => {
-							Gtk.Dialog dg = new Gtk.Dialog.with_buttons("Folder Name"
-								, (Gtk.Window)this.get_toplevel()
-								, DialogFlags.MODAL
-								, "Cancel"
-								, ResponseType.CANCEL
-								, "Ok"
-								, ResponseType.OK
-								, null
-								);
-
-							Gtk.Entry sb = new Gtk.Entry();
-							sb.activate.connect(() => { dg.response(ResponseType.OK); });
-							dg.get_content_area().add(sb);
-							dg.skip_taskbar_hint = true;
-							dg.show_all();
+							_project.create_unit((string)name, sb.text);
+						}
 
-							if (dg.run() == (int)ResponseType.OK)
+						dg.destroy();
+					});
+					menu.add(mi);
+
+					mi = new Gtk.SeparatorMenuItem();
+					menu.add(mi);
+
+					mi = new Gtk.MenuItem.with_label("New Folder...");
+					mi.activate.connect(() => {
+						Gtk.Dialog dg = new Gtk.Dialog.with_buttons("Folder Name"
+							, (Gtk.Window)this.get_toplevel()
+							, DialogFlags.MODAL
+							, "Cancel"
+							, ResponseType.CANCEL
+							, "Ok"
+							, ResponseType.OK
+							, null
+							);
+
+						Gtk.Entry sb = new Gtk.Entry();
+						sb.activate.connect(() => { dg.response(ResponseType.OK); });
+						dg.get_content_area().add(sb);
+						dg.skip_taskbar_hint = true;
+						dg.show_all();
+
+						if (dg.run() == (int)ResponseType.OK)
+						{
+							if (sb.text.strip() == "")
 							{
-								if (sb.text.strip() == "")
-								{
-									dg.destroy();
-									return;
-								}
-
-								GLib.File file = GLib.File.new_for_path(GLib.Path.build_filename(_project.source_dir(), (string)name, sb.text));
-								try
-								{
-									file.make_directory();
-								}
-								catch (Error e)
-								{
-									loge(e.message);
-								}
+								dg.destroy();
+								return;
 							}
 
-							dg.destroy();
-						});
-						menu.add(mi);
-
-						if ((string)name != ProjectStore.ROOT_FOLDER)
-						{
-							mi = new Gtk.MenuItem.with_label("Delete Folder");
-							mi.activate.connect(() => {
-								Gtk.MessageDialog md = new Gtk.MessageDialog((Gtk.Window)this.get_toplevel()
-									, Gtk.DialogFlags.MODAL
-									, Gtk.MessageType.WARNING
-									, Gtk.ButtonsType.NONE
-									, "Delete Folder " + (string)name + "?"
-									);
-								md.add_button("_Cancel", ResponseType.CANCEL);
-								md.add_button("_Delete", ResponseType.YES);
-								md.set_default_response(ResponseType.CANCEL);
-								int rt = md.run();
-								md.destroy();
-
-								if (rt == (int)ResponseType.CANCEL)
-									return;
-
-								GLib.File file = GLib.File.new_for_path(GLib.Path.build_filename(_project.source_dir(), (string)name));
-								try
-								{
-									_project.delete_tree(file);
-								}
-								catch (Error e)
-								{
-									loge(e.message);
-								}
-							});
-							menu.add(mi);
-						}
-
-						menu.show_all();
-						menu.popup(null, null, null, ev.button, ev.time);
-					}
-					else // If file
-					{
-						Gtk.Menu menu = new Gtk.Menu();
-						Gtk.MenuItem mi;
-
-						mi = new Gtk.MenuItem.with_label("Delete File");
-						mi.activate.connect(() => {
-							GLib.File file = GLib.File.new_for_path(ProjectBrowser.filename(_project, (string)type, (string)name));
+							GLib.File file = GLib.File.new_for_path(GLib.Path.build_filename(_project.source_dir(), (string)name, sb.text));
 							try
 							{
-								file.delete();
+								file.make_directory();
 							}
 							catch (Error e)
 							{
 								loge(e.message);
 							}
-						});
-						menu.add(mi);
+						}
+
+						dg.destroy();
+					});
+					menu.add(mi);
 
-						mi = new Gtk.MenuItem.with_label("Open Containing Folder...");
+					if ((string)name != ProjectStore.ROOT_FOLDER)
+					{
+						mi = new Gtk.MenuItem.with_label("Delete Folder");
 						mi.activate.connect(() => {
-							Gtk.TreeIter parent;
-							if (_tree_view.model.iter_parent(out parent, iter))
-							{
-								Value parent_name;
-								_tree_view.model.get_value(parent, ProjectStore.Column.NAME, out parent_name);
+							Gtk.MessageDialog md = new Gtk.MessageDialog((Gtk.Window)this.get_toplevel()
+								, Gtk.DialogFlags.MODAL
+								, Gtk.MessageType.WARNING
+								, Gtk.ButtonsType.NONE
+								, "Delete Folder " + (string)name + "?"
+								);
+							md.add_button("_Cancel", ResponseType.CANCEL);
+							md.add_button("_Delete", ResponseType.YES);
+							md.set_default_response(ResponseType.CANCEL);
+							int rt = md.run();
+							md.destroy();
 
-								GLib.File file = GLib.File.new_for_path(GLib.Path.build_filename(_project.source_dir(), (string)parent_name));
-								open_directory(file.get_path());
+							if (rt == (int)ResponseType.CANCEL)
+								return;
+
+							GLib.File file = GLib.File.new_for_path(GLib.Path.build_filename(_project.source_dir(), (string)name));
+							try
+							{
+								_project.delete_tree(file);
+							}
+							catch (Error e)
+							{
+								loge(e.message);
 							}
 						});
 						menu.add(mi);
-
-						menu.show_all();
-						menu.popup(null, null, null, ev.button, ev.time);
 					}
+
+					menu.show_all();
+					menu.popup(null, null, null, ev.button, ev.time);
 				}
-			}
-			else if (ev.button == Gdk.BUTTON_PRIMARY)
-			{
-				if (ev.type == Gdk.EventType.2BUTTON_PRESS)
+				else // If file
 				{
-					Gtk.TreePath path;
-					int cell_x;
-					int cell_y;
-					if (_tree_view.get_path_at_pos((int)ev.x, (int)ev.y, out path, null, out cell_x, out cell_y))
-					{
-						Gtk.TreeIter iter;
-						_tree_view.model.get_iter(out iter, path);
-
-						Value type;
-						_tree_view.model.get_value(iter, ProjectStore.Column.TYPE, out type);
-						if ((string)type == "<folder>")
-							return Gdk.EVENT_PROPAGATE;
-
-						Value name;
-						_tree_view.model.get_value(iter, ProjectStore.Column.NAME, out name);
+					Gtk.Menu menu = new Gtk.Menu();
+					Gtk.MenuItem mi;
 
+					mi = new Gtk.MenuItem.with_label("Delete File");
+					mi.activate.connect(() => {
 						GLib.File file = GLib.File.new_for_path(ProjectBrowser.filename(_project, (string)type, (string)name));
-
-						if (type == "level")
+						try
 						{
-							Gtk.Application app = ((Gtk.Window)this.get_toplevel()).application;
-							app.activate_action("open-level", file.get_path());
+							file.delete();
 						}
-						else
+						catch (Error e)
 						{
-							try
-							{
-								GLib.AppInfo? app = file.query_default_handler();
-								GLib.List<GLib.File> files = new GLib.List<GLib.File>();
-								files.append(file);
-								app.launch(files, null);
-							}
-							catch (Error e)
-							{
-								loge(e.message);
-							}
+							loge(e.message);
 						}
-					}
+					});
+					menu.add(mi);
+
+					mi = new Gtk.MenuItem.with_label("Open Containing Folder...");
+					mi.activate.connect(() => {
+						Gtk.TreeIter parent;
+						if (_tree_view.model.iter_parent(out parent, iter))
+						{
+							Value parent_name;
+							_tree_view.model.get_value(parent, ProjectStore.Column.NAME, out parent_name);
+
+							GLib.File file = GLib.File.new_for_path(GLib.Path.build_filename(_project.source_dir(), (string)parent_name));
+							open_directory(file.get_path());
+						}
+					});
+					menu.add(mi);
+
+					menu.show_all();
+					menu.popup(null, null, null, ev.button, ev.time);
 				}
 			}
-
-			return Gdk.EVENT_PROPAGATE;
 		}
-
-		private bool on_button_released(Gdk.EventButton ev)
+		else if (ev.button == Gdk.BUTTON_PRIMARY)
 		{
-			if (ev.button == Gdk.BUTTON_PRIMARY)
+			if (ev.type == Gdk.EventType.2BUTTON_PRESS)
 			{
 				Gtk.TreePath path;
 				int cell_x;
@@ -509,19 +461,68 @@ namespace Crown
 
 					Value type;
 					_tree_view.model.get_value(iter, ProjectStore.Column.TYPE, out type);
-					if ((string)type != "<folder>")
+					if ((string)type == "<folder>")
 						return Gdk.EVENT_PROPAGATE;
 
-					if (_tree_view.is_row_expanded(path))
-						_tree_view.collapse_row(path);
-					else
-						_tree_view.expand_row(path, /*open_all = */false);
+					Value name;
+					_tree_view.model.get_value(iter, ProjectStore.Column.NAME, out name);
+
+					GLib.File file = GLib.File.new_for_path(ProjectBrowser.filename(_project, (string)type, (string)name));
 
-					return Gdk.EVENT_STOP;
+					if (type == "level")
+					{
+						Gtk.Application app = ((Gtk.Window)this.get_toplevel()).application;
+						app.activate_action("open-level", file.get_path());
+					}
+					else
+					{
+						try
+						{
+							GLib.AppInfo? app = file.query_default_handler();
+							GLib.List<GLib.File> files = new GLib.List<GLib.File>();
+							files.append(file);
+							app.launch(files, null);
+						}
+						catch (Error e)
+						{
+							loge(e.message);
+						}
+					}
 				}
 			}
+		}
+
+		return Gdk.EVENT_PROPAGATE;
+	}
 
-			return Gdk.EVENT_PROPAGATE;
+	private bool on_button_released(Gdk.EventButton ev)
+	{
+		if (ev.button == Gdk.BUTTON_PRIMARY)
+		{
+			Gtk.TreePath path;
+			int cell_x;
+			int cell_y;
+			if (_tree_view.get_path_at_pos((int)ev.x, (int)ev.y, out path, null, out cell_x, out cell_y))
+			{
+				Gtk.TreeIter iter;
+				_tree_view.model.get_iter(out iter, path);
+
+				Value type;
+				_tree_view.model.get_value(iter, ProjectStore.Column.TYPE, out type);
+				if ((string)type != "<folder>")
+					return Gdk.EVENT_PROPAGATE;
+
+				if (_tree_view.is_row_expanded(path))
+					_tree_view.collapse_row(path);
+				else
+					_tree_view.expand_row(path, /*open_all = */false);
+
+				return Gdk.EVENT_STOP;
+			}
 		}
+
+		return Gdk.EVENT_PROPAGATE;
 	}
 }
+
+}

+ 213 - 212
tools/level_editor/project_store.vala

@@ -8,82 +8,108 @@ using Gee;
 
 namespace Crown
 {
-	public class ProjectStore
+public class ProjectStore
+{
+	public const string ROOT_FOLDER = "";
+
+	public enum Column
 	{
-		public const string ROOT_FOLDER = "";
+		NAME,
+		TYPE,
+		SEGMENT,
 
-		public enum Column
-		{
-			NAME,
-			TYPE,
-			SEGMENT,
+		COUNT
+	}
 
-			COUNT
-		}
+	// Data
+	public Project _project;
+	public Gtk.TreeStore _tree_store;
+	public Gtk.ListStore _list_store;
+	public Gee.HashMap<string, Gtk.TreeRowReference> _folders;
 
+	public ProjectStore(Project project)
+	{
 		// Data
-		public Project _project;
-		public Gtk.TreeStore _tree_store;
-		public Gtk.ListStore _list_store;
-		public Gee.HashMap<string, Gtk.TreeRowReference> _folders;
-
-		public ProjectStore(Project project)
-		{
-			// Data
-			_project = project;
-			_project.file_added.connect(on_project_file_added);
-			_project.file_removed.connect(on_project_file_removed);
-			_project.tree_added.connect(on_project_tree_added);
-			_project.tree_removed.connect(on_project_tree_removed);
-			_project.project_reset.connect(on_project_reset);
-			_project.project_loaded.connect(on_project_loaded);
-
-			_tree_store = new Gtk.TreeStore(Column.COUNT
-				, typeof(string) // resource name
-				, typeof(string) // resource type
-				, typeof(string) // resource segment
-				);
-			_list_store = new Gtk.ListStore(2
-				, typeof(string) // resource name
-				, typeof(string) // resource type
-				);
+		_project = project;
+		_project.file_added.connect(on_project_file_added);
+		_project.file_removed.connect(on_project_file_removed);
+		_project.tree_added.connect(on_project_tree_added);
+		_project.tree_removed.connect(on_project_tree_removed);
+		_project.project_reset.connect(on_project_reset);
+		_project.project_loaded.connect(on_project_loaded);
+
+		_tree_store = new Gtk.TreeStore(Column.COUNT
+			, typeof(string) // resource name
+			, typeof(string) // resource type
+			, typeof(string) // resource segment
+			);
+		_list_store = new Gtk.ListStore(2
+			, typeof(string) // resource name
+			, typeof(string) // resource type
+			);
+
+		_folders = new Gee.HashMap<string, Gtk.TreeRowReference>();
+
+		reset();
+	}
 
-			_folders = new Gee.HashMap<string, Gtk.TreeRowReference>();
+	public void reset()
+	{
+		_folders.clear();
+		_tree_store.clear();
+		_list_store.clear();
+	}
 
-			reset();
-		}
+	private string folder(string name)
+	{
+		int last_slash = name.last_index_of_char('/');
+		if (last_slash == -1)
+			return ROOT_FOLDER;
+		return name.substring(0, last_slash);
+	}
 
-		public void reset()
-		{
-			_folders.clear();
-			_tree_store.clear();
-			_list_store.clear();
-		}
+	private string segment(string name)
+	{
+		int last_slash = name.last_index_of_char('/');
+		if (last_slash == -1)
+			return name;
+		return name.substring(last_slash + 1);
+	}
 
-		private string folder(string name)
+	private Gtk.TreeIter make_tree_internal(string folder, int start_index, Gtk.TreeRowReference parent)
+	{
+		// Folder can be:
+		// "", root folder
+		// "folder", one word
+		// "folder/another_folder", any number of words concatenated by '/'
+		int first_slash = folder.index_of_char('/', start_index);
+		if (first_slash == -1)
 		{
-			int last_slash = name.last_index_of_char('/');
-			if (last_slash == -1)
-				return ROOT_FOLDER;
-			return name.substring(0, last_slash);
-		}
+			Gtk.TreeIter parent_iter;
+			_tree_store.get_iter(out parent_iter, parent.get_path());
+			Gtk.TreeIter iter;
+			_tree_store.insert_with_values(out iter
+				, parent_iter
+				, -1
+				, Column.NAME
+				, folder
+				, Column.TYPE
+				, "<folder>"
+				, Column.SEGMENT
+				, folder.substring(start_index)
+				, -1
+				);
 
-		private string segment(string name)
-		{
-			int last_slash = name.last_index_of_char('/');
-			if (last_slash == -1)
-				return name;
-			return name.substring(last_slash + 1);
+			_folders[folder] = new Gtk.TreeRowReference(_tree_store, _tree_store.get_path(iter));
+			return iter;
 		}
-
-		private Gtk.TreeIter make_tree_internal(string folder, int start_index, Gtk.TreeRowReference parent)
+		else
 		{
-			// Folder can be:
-			// "", root folder
-			// "folder", one word
-			// "folder/another_folder", any number of words concatenated by '/'
-			int first_slash = folder.index_of_char('/', start_index);
-			if (first_slash == -1)
+			if (_folders.has_key(folder.substring(0, first_slash)))
+			{
+				return make_tree_internal(folder, first_slash + 1, _folders[folder.substring(0, first_slash)]);
+			}
+			else
 			{
 				Gtk.TreeIter parent_iter;
 				_tree_store.get_iter(out parent_iter, parent.get_path());
@@ -92,189 +118,164 @@ namespace Crown
 					, parent_iter
 					, -1
 					, Column.NAME
-					, folder
+					, folder.substring(0, first_slash)
 					, Column.TYPE
 					, "<folder>"
 					, Column.SEGMENT
-					, folder.substring(start_index)
+					, folder.substring(start_index, first_slash - start_index)
 					, -1
 					);
 
-				_folders[folder] = new Gtk.TreeRowReference(_tree_store, _tree_store.get_path(iter));
-				return iter;
-			}
-			else
-			{
-				if (_folders.has_key(folder.substring(0, first_slash)))
-				{
-					return make_tree_internal(folder, first_slash + 1, _folders[folder.substring(0, first_slash)]);
-				}
-				else
-				{
-					Gtk.TreeIter parent_iter;
-					_tree_store.get_iter(out parent_iter, parent.get_path());
-					Gtk.TreeIter iter;
-					_tree_store.insert_with_values(out iter
-						, parent_iter
-						, -1
-						, Column.NAME
-						, folder.substring(0, first_slash)
-						, Column.TYPE
-						, "<folder>"
-						, Column.SEGMENT
-						, folder.substring(start_index, first_slash - start_index)
-						, -1
-						);
-
-					Gtk.TreeRowReference trr = new Gtk.TreeRowReference(_tree_store, _tree_store.get_path(iter));
-					_folders[folder.substring(0, first_slash)] = trr;
-
-					return make_tree_internal(folder, first_slash + 1, trr);
-				}
-			}
-		}
+				Gtk.TreeRowReference trr = new Gtk.TreeRowReference(_tree_store, _tree_store.get_path(iter));
+				_folders[folder.substring(0, first_slash)] = trr;
 
-		private Gtk.TreeIter make_tree(string folder)
-		{
-			if (_folders.has_key(folder))
-			{
-				Gtk.TreeIter iter;
-				_tree_store.get_iter(out iter, _folders[folder].get_path());
-				return iter;
+				return make_tree_internal(folder, first_slash + 1, trr);
 			}
-
-			return make_tree_internal(folder, 0, _folders[ROOT_FOLDER]);
 		}
+	}
 
-		private void on_project_file_added(string type, string name)
+	private Gtk.TreeIter make_tree(string folder)
+	{
+		if (_folders.has_key(folder))
 		{
-			string f = folder(name);
-			string s = segment(name);
-			Gtk.TreeIter parent = make_tree(f);
 			Gtk.TreeIter iter;
-			_tree_store.insert_with_values(out iter
-				, parent
-				, -1
-				, Column.NAME
-				, name
-				, Column.TYPE
-				, type
-				, Column.SEGMENT
-				, s
-				, -1
-				);
-			_list_store.insert_with_values(out iter
-				, -1
-				, Column.NAME
-				, name
-				, Column.TYPE
-				, type
-				, -1
-				);
+			_tree_store.get_iter(out iter, _folders[folder].get_path());
+			return iter;
 		}
 
-		private void on_project_file_removed(string type, string name)
+		return make_tree_internal(folder, 0, _folders[ROOT_FOLDER]);
+	}
+
+	private void on_project_file_added(string type, string name)
+	{
+		string f = folder(name);
+		string s = segment(name);
+		Gtk.TreeIter parent = make_tree(f);
+		Gtk.TreeIter iter;
+		_tree_store.insert_with_values(out iter
+			, parent
+			, -1
+			, Column.NAME
+			, name
+			, Column.TYPE
+			, type
+			, Column.SEGMENT
+			, s
+			, -1
+			);
+		_list_store.insert_with_values(out iter
+			, -1
+			, Column.NAME
+			, name
+			, Column.TYPE
+			, type
+			, -1
+			);
+	}
+
+	private void on_project_file_removed(string type, string name)
+	{
+		string f = folder(name);
+		if (!_folders.has_key(f))
+			return;
+
+		// Remove from tree store
+		Gtk.TreeIter parent_iter;
+		_tree_store.get_iter(out parent_iter, _folders[f].get_path());
+		Gtk.TreeIter child;
+		if (_tree_store.iter_children(out child, parent_iter))
 		{
-			string f = folder(name);
-			if (!_folders.has_key(f))
-				return;
+			Value iter_name;
+			Value iter_type;
 
-			// Remove from tree store
-			Gtk.TreeIter parent_iter;
-			_tree_store.get_iter(out parent_iter, _folders[f].get_path());
-			Gtk.TreeIter child;
-			if (_tree_store.iter_children(out child, parent_iter))
+			while (true)
 			{
-				Value iter_name;
-				Value iter_type;
-
-				while (true)
+				_tree_store.get_value(child, Column.NAME, out iter_name);
+				_tree_store.get_value(child, Column.TYPE, out iter_type);
+				if ((string)iter_name == name && (string)iter_type == type)
 				{
-					_tree_store.get_value(child, Column.NAME, out iter_name);
-					_tree_store.get_value(child, Column.TYPE, out iter_type);
-					if ((string)iter_name == name && (string)iter_type == type)
-					{
-						_tree_store.remove(ref child);
-						return;
-					}
-
-					if (!_tree_store.iter_next(ref child))
-						break;
+					_tree_store.remove(ref child);
+					return;
 				}
+
+				if (!_tree_store.iter_next(ref child))
+					break;
 			}
+		}
 
-			// Remove from list store
-			if (_list_store.iter_children(out child, null))
-			{
-				Value iter_name;
-				Value iter_type;
+		// Remove from list store
+		if (_list_store.iter_children(out child, null))
+		{
+			Value iter_name;
+			Value iter_type;
 
-				while (true)
+			while (true)
+			{
+				_list_store.get_value(child, Column.NAME, out iter_name);
+				_list_store.get_value(child, Column.TYPE, out iter_type);
+				if ((string)iter_name == name && (string)iter_type == type)
 				{
-					_list_store.get_value(child, Column.NAME, out iter_name);
-					_list_store.get_value(child, Column.TYPE, out iter_type);
-					if ((string)iter_name == name && (string)iter_type == type)
-					{
-						_list_store.remove(ref child);
-						return;
-					}
-
-					if (!_list_store.iter_next(ref child))
-						break;
+					_list_store.remove(ref child);
+					return;
 				}
-			}
-		}
 
-		private void on_project_tree_added(string name)
-		{
-			make_tree(name);
+				if (!_list_store.iter_next(ref child))
+					break;
+			}
 		}
+	}
 
-		private void on_project_tree_removed(string name)
-		{
-			if (name == ROOT_FOLDER)
-				return;
+	private void on_project_tree_added(string name)
+	{
+		make_tree(name);
+	}
 
-			if (!_folders.has_key(name))
-				return;
+	private void on_project_tree_removed(string name)
+	{
+		if (name == ROOT_FOLDER)
+			return;
 
-			// Remove the tree
-			Gtk.TreeIter iter;
-			_tree_store.get_iter(out iter, _folders[name].get_path());
-			_tree_store.remove(ref iter);
+		if (!_folders.has_key(name))
+			return;
 
-			// Remove any stale TreeRowRerefence
-			var it = _folders.map_iterator();
-			for (var has_next = it.next(); has_next; has_next = it.next())
-			{
-			    string ff = it.get_key();
-				if (ff.has_prefix(name + "/"))
-					it.unset();
-			}
-			_folders.unset(name);
-		}
+		// Remove the tree
+		Gtk.TreeIter iter;
+		_tree_store.get_iter(out iter, _folders[name].get_path());
+		_tree_store.remove(ref iter);
 
-		private void on_project_reset()
+		// Remove any stale TreeRowRerefence
+		var it = _folders.map_iterator();
+		for (var has_next = it.next(); has_next; has_next = it.next())
 		{
-			reset();
+		    string ff = it.get_key();
+			if (ff.has_prefix(name + "/"))
+				it.unset();
 		}
+		_folders.unset(name);
+	}
 
-		private void on_project_loaded()
-		{
-			Gtk.TreeIter iter;
-			_tree_store.insert_with_values(out iter
-				, null
-				, -1
-				, Column.NAME
-				, ROOT_FOLDER
-				, Column.TYPE
-				, "<folder>"
-				, Column.SEGMENT
-				, _project.name()
-				, -1
-				);
+	private void on_project_reset()
+	{
+		reset();
+	}
 
-			_folders[ROOT_FOLDER] = new Gtk.TreeRowReference(_tree_store, _tree_store.get_path(iter));
-		}
+	private void on_project_loaded()
+	{
+		Gtk.TreeIter iter;
+		_tree_store.insert_with_values(out iter
+			, null
+			, -1
+			, Column.NAME
+			, ROOT_FOLDER
+			, Column.TYPE
+			, "<folder>"
+			, Column.SEGMENT
+			, _project.name()
+			, -1
+			);
+
+		_folders[ROOT_FOLDER] = new Gtk.TreeRowReference(_tree_store, _tree_store.get_path(iter));
 	}
 }
+
+}

+ 653 - 652
tools/level_editor/properties_view.vala

@@ -8,402 +8,394 @@ using Gee;
 
 namespace Crown
 {
-	public class PropertyRow : Gtk.Bin
+public class PropertyRow : Gtk.Bin
+{
+	public PropertyRow(Gtk.Widget child)
 	{
-		public PropertyRow(Gtk.Widget child)
-		{
-			this.hexpand = true;
-			add(child);
-		}
+		this.hexpand = true;
+		add(child);
 	}
+}
 
-	public class ComponentView : Gtk.Grid
+public class ComponentView : Gtk.Grid
+{
+	// Data
+	public Guid _id;
+	public Guid _component_id;
+	public int _rows;
+
+	public ComponentView()
 	{
 		// Data
-		public Guid _id;
-		public Guid _component_id;
-		public int _rows;
-
-		public ComponentView()
-		{
-			// Data
-			_id = GUID_ZERO;
-			_component_id = GUID_ZERO;
-			_rows = 0;
-		}
+		_id = GUID_ZERO;
+		_component_id = GUID_ZERO;
+		_rows = 0;
+	}
 
-		public void add_row(string label, Gtk.Widget w)
-		{
-			this.row_spacing = 6;
-			this.column_spacing = 12;
+	public void add_row(string label, Gtk.Widget w)
+	{
+		this.row_spacing = 6;
+		this.column_spacing = 12;
 
-			Gtk.Label l = new Label(label);
-			l.width_chars = 13;
-			l.set_alignment(1.0f, 0.5f);
+		Gtk.Label l = new Label(label);
+		l.width_chars = 13;
+		l.set_alignment(1.0f, 0.5f);
 
-			PropertyRow r = new PropertyRow(w);
+		PropertyRow r = new PropertyRow(w);
 
-			this.attach(l, 0, (int)_rows);
-			this.attach(r, 1, (int)_rows);
-			++_rows;
-		}
+		this.attach(l, 0, (int)_rows);
+		this.attach(r, 1, (int)_rows);
+		++_rows;
+	}
 
-		public virtual void update()
-		{
-		}
+	public virtual void update()
+	{
 	}
+}
+
+public class TransformComponentView : ComponentView
+{
+	// Data
+	Level _level;
+
+	// Widgets
+	private EntryPosition _position;
+	private EntryRotation _rotation;
+	private EntryScale _scale;
 
-	public class TransformComponentView : ComponentView
+	public TransformComponentView(Level level)
 	{
 		// Data
-		Level _level;
+		_level = level;
+		_id = GUID_ZERO;
+		_component_id = GUID_ZERO;
 
 		// Widgets
-		private EntryPosition _position;
-		private EntryRotation _rotation;
-		private EntryScale _scale;
-
-		public TransformComponentView(Level level)
-		{
-			// Data
-			_level = level;
-			_id = GUID_ZERO;
-			_component_id = GUID_ZERO;
-
-			// Widgets
-			_position = new EntryPosition();
-			_position.value_changed.connect(on_value_changed);
-			_rotation = new EntryRotation();
-			_rotation.value_changed.connect(on_value_changed);
-			_scale = new EntryScale();
-			_scale.value_changed.connect(on_value_changed);
-
-			add_row("Position", _position);
-			add_row("Rotation", _rotation);
-			add_row("Scale", _scale);
-		}
+		_position = new EntryPosition();
+		_position.value_changed.connect(on_value_changed);
+		_rotation = new EntryRotation();
+		_rotation.value_changed.connect(on_value_changed);
+		_scale = new EntryScale();
+		_scale.value_changed.connect(on_value_changed);
+
+		add_row("Position", _position);
+		add_row("Rotation", _rotation);
+		add_row("Scale", _scale);
+	}
 
-		private void on_value_changed()
-		{
-			_level.move_selected_objects(_position.value
-				, _rotation.value
-				, _scale.value
-				);
-		}
+	private void on_value_changed()
+	{
+		_level.move_selected_objects(_position.value
+			, _rotation.value
+			, _scale.value
+			);
+	}
 
-		public override void update()
-		{
-			Unit unit = new Unit(_level._db, _id, _level._prefabs);
-			_position.value = unit.get_component_property_vector3   (_component_id, "data.position");
-			_rotation.value = unit.get_component_property_quaternion(_component_id, "data.rotation");
-			_scale.value    = unit.get_component_property_vector3   (_component_id, "data.scale");
-		}
+	public override void update()
+	{
+		Unit unit = new Unit(_level._db, _id, _level._prefabs);
+		_position.value = unit.get_component_property_vector3   (_component_id, "data.position");
+		_rotation.value = unit.get_component_property_quaternion(_component_id, "data.rotation");
+		_scale.value    = unit.get_component_property_vector3   (_component_id, "data.scale");
 	}
+}
+
+public class MeshRendererComponentView : ComponentView
+{
+	// Data
+	Level _level;
+
+	// Widgets
+	private ResourceChooserButton _mesh_resource;
+	private Gtk.Entry _geometry;
+	private ResourceChooserButton _material;
+	private CheckBox _visible;
 
-	public class MeshRendererComponentView : ComponentView
+	public MeshRendererComponentView(Level level, ProjectStore store)
 	{
 		// Data
-		Level _level;
+		_level = level;
 
 		// Widgets
-		private ResourceChooserButton _mesh_resource;
-		private Gtk.Entry _geometry;
-		private ResourceChooserButton _material;
-		private CheckBox _visible;
-
-		public MeshRendererComponentView(Level level, ProjectStore store)
-		{
-			// Data
-			_level = level;
-
-			// Widgets
-			_mesh_resource = new ResourceChooserButton(store, "mesh");
-			_mesh_resource.value_changed.connect(on_value_changed);
-			_geometry = new Gtk.Entry();
-			_geometry.sensitive = false;
-			_material = new ResourceChooserButton(store, "material");
-			_material.value_changed.connect(on_value_changed);
-			_visible = new CheckBox();
-			_visible.value_changed.connect(on_value_changed);
-
-			add_row("Mesh", _mesh_resource);
-			add_row("Geometry", _geometry);
-			add_row("Material", _material);
-			add_row("Visible", _visible);
-		}
+		_mesh_resource = new ResourceChooserButton(store, "mesh");
+		_mesh_resource.value_changed.connect(on_value_changed);
+		_geometry = new Gtk.Entry();
+		_geometry.sensitive = false;
+		_material = new ResourceChooserButton(store, "material");
+		_material.value_changed.connect(on_value_changed);
+		_visible = new CheckBox();
+		_visible.value_changed.connect(on_value_changed);
+
+		add_row("Mesh", _mesh_resource);
+		add_row("Geometry", _geometry);
+		add_row("Material", _material);
+		add_row("Visible", _visible);
+	}
 
-		private void on_value_changed()
-		{
-			_level.set_mesh(_id
-				, _component_id
-				, _mesh_resource.value
-				, _geometry.text
-				, _material.value
-				, _visible.value
-				);
-		}
+	private void on_value_changed()
+	{
+		_level.set_mesh(_id
+			, _component_id
+			, _mesh_resource.value
+			, _geometry.text
+			, _material.value
+			, _visible.value
+			);
+	}
 
-		public override void update()
-		{
-			Unit unit = new Unit(_level._db, _id, _level._prefabs);
-			_mesh_resource.value = unit.get_component_property_string(_component_id, "data.mesh_resource");
-			_geometry.text       = unit.get_component_property_string(_component_id, "data.geometry_name");
-			_material.value      = unit.get_component_property_string(_component_id, "data.material");
-			_visible.value       = unit.get_component_property_bool  (_component_id, "data.visible");
-		}
+	public override void update()
+	{
+		Unit unit = new Unit(_level._db, _id, _level._prefabs);
+		_mesh_resource.value = unit.get_component_property_string(_component_id, "data.mesh_resource");
+		_geometry.text       = unit.get_component_property_string(_component_id, "data.geometry_name");
+		_material.value      = unit.get_component_property_string(_component_id, "data.material");
+		_visible.value       = unit.get_component_property_bool  (_component_id, "data.visible");
 	}
+}
+
+public class SpriteRendererComponentView : ComponentView
+{
+	// Data
+	Level _level;
 
-	public class SpriteRendererComponentView : ComponentView
+	// Widgets
+	private ResourceChooserButton _sprite_resource;
+	private ResourceChooserButton _material;
+	private EntryDouble _layer;
+	private EntryDouble _depth;
+	private CheckBox _visible;
+
+	public SpriteRendererComponentView(Level level, ProjectStore store)
 	{
 		// Data
-		Level _level;
+		_level = level;
 
 		// Widgets
-		private ResourceChooserButton _sprite_resource;
-		private ResourceChooserButton _material;
-		private EntryDouble _layer;
-		private EntryDouble _depth;
-		private CheckBox _visible;
-
-		public SpriteRendererComponentView(Level level, ProjectStore store)
-		{
-			// Data
-			_level = level;
-
-			// Widgets
-			_sprite_resource = new ResourceChooserButton(store, "sprite");
-			_sprite_resource.value_changed.connect(on_value_changed);
-			_material = new ResourceChooserButton(store, "material");
-			_material.value_changed.connect(on_value_changed);
-			_layer = new EntryDouble(0.0, 0.0, 7.0);
-			_layer.value_changed.connect(on_value_changed);
-			_depth = new EntryDouble(0.0, 0.0, (double)uint32.MAX);
-			_depth.value_changed.connect(on_value_changed);
-			_visible = new CheckBox();
-			_visible.value_changed.connect(on_value_changed);
-
-			add_row("Sprite", _sprite_resource);
-			add_row("Material", _material);
-			add_row("Layer", _layer);
-			add_row("Depth", _depth);
-			add_row("Visible", _visible);
-		}
+		_sprite_resource = new ResourceChooserButton(store, "sprite");
+		_sprite_resource.value_changed.connect(on_value_changed);
+		_material = new ResourceChooserButton(store, "material");
+		_material.value_changed.connect(on_value_changed);
+		_layer = new EntryDouble(0.0, 0.0, 7.0);
+		_layer.value_changed.connect(on_value_changed);
+		_depth = new EntryDouble(0.0, 0.0, (double)uint32.MAX);
+		_depth.value_changed.connect(on_value_changed);
+		_visible = new CheckBox();
+		_visible.value_changed.connect(on_value_changed);
+
+		add_row("Sprite", _sprite_resource);
+		add_row("Material", _material);
+		add_row("Layer", _layer);
+		add_row("Depth", _depth);
+		add_row("Visible", _visible);
+	}
 
-		private void on_value_changed()
-		{
-			_level.set_sprite(_id
-				, _component_id
-				, _layer.value
-				, _depth.value
-				, _material.value
-				, _sprite_resource.value
-				, _visible.value
-				);
-		}
+	private void on_value_changed()
+	{
+		_level.set_sprite(_id
+			, _component_id
+			, _layer.value
+			, _depth.value
+			, _material.value
+			, _sprite_resource.value
+			, _visible.value
+			);
+	}
 
-		public override void update()
-		{
-			Unit unit = new Unit(_level._db, _id, _level._prefabs);
-			_sprite_resource.value = unit.get_component_property_string(_component_id, "data.sprite_resource");
-			_material.value        = unit.get_component_property_string(_component_id, "data.material");
-			_layer.value           = unit.get_component_property_double(_component_id, "data.layer");
-			_depth.value           = unit.get_component_property_double(_component_id, "data.depth");
-			_visible.value         = unit.get_component_property_bool  (_component_id, "data.visible");
-		}
+	public override void update()
+	{
+		Unit unit = new Unit(_level._db, _id, _level._prefabs);
+		_sprite_resource.value = unit.get_component_property_string(_component_id, "data.sprite_resource");
+		_material.value        = unit.get_component_property_string(_component_id, "data.material");
+		_layer.value           = unit.get_component_property_double(_component_id, "data.layer");
+		_depth.value           = unit.get_component_property_double(_component_id, "data.depth");
+		_visible.value         = unit.get_component_property_bool  (_component_id, "data.visible");
 	}
+}
+
+public class LightComponentView : ComponentView
+{
+	// Data
+	Level _level;
+
+	// Widgets
+	private ComboBoxMap _type;
+	private EntryDouble _range;
+	private EntryDouble _intensity;
+	private EntryDouble _spot_angle;
+	private ColorButtonVector3 _color;
 
-	public class LightComponentView : ComponentView
+	public LightComponentView(Level level)
 	{
 		// Data
-		Level _level;
+		_level = level;
 
 		// Widgets
-		private ComboBoxMap _type;
-		private EntryDouble _range;
-		private EntryDouble _intensity;
-		private EntryDouble _spot_angle;
-		private ColorButtonVector3 _color;
-
-		public LightComponentView(Level level)
-		{
-			// Data
-			_level = level;
-
-			// Widgets
-			_type = new ComboBoxMap();
-			_type.value_changed.connect(on_value_changed);
-			_type.append("directional", "Directional");
-			_type.append("omni", "Omni");
-			_type.append("spot", "Spot");
-			_range = new EntryDouble(0.0, 0.0, double.MAX);
-			_range.value_changed.connect(on_value_changed);
-			_intensity = new EntryDouble(0.0, 0.0,  double.MAX);
-			_intensity.value_changed.connect(on_value_changed);
-			_spot_angle = new EntryDouble(0.0, 0.0,  90.0);
-			_spot_angle.value_changed.connect(on_value_changed);
-			_color = new ColorButtonVector3();
-			_color.value_changed.connect(on_value_changed);
-
-			add_row("Type", _type);
-			add_row("Range", _range);
-			add_row("Intensity", _intensity);
-			add_row("Spot Angle", _spot_angle);
-			add_row("Color", _color);
-		}
+		_type = new ComboBoxMap();
+		_type.value_changed.connect(on_value_changed);
+		_type.append("directional", "Directional");
+		_type.append("omni", "Omni");
+		_type.append("spot", "Spot");
+		_range = new EntryDouble(0.0, 0.0, double.MAX);
+		_range.value_changed.connect(on_value_changed);
+		_intensity = new EntryDouble(0.0, 0.0,  double.MAX);
+		_intensity.value_changed.connect(on_value_changed);
+		_spot_angle = new EntryDouble(0.0, 0.0,  90.0);
+		_spot_angle.value_changed.connect(on_value_changed);
+		_color = new ColorButtonVector3();
+		_color.value_changed.connect(on_value_changed);
+
+		add_row("Type", _type);
+		add_row("Range", _range);
+		add_row("Intensity", _intensity);
+		add_row("Spot Angle", _spot_angle);
+		add_row("Color", _color);
+	}
 
-		private void on_value_changed()
-		{
-			_level.set_light(_id
-				, _component_id
-				, _type.value
-				, _range.value
-				, _intensity.value
-				, _spot_angle.value*(Math.PI/180.0)
-				, _color.value
-				);
-		}
+	private void on_value_changed()
+	{
+		_level.set_light(_id
+			, _component_id
+			, _type.value
+			, _range.value
+			, _intensity.value
+			, _spot_angle.value*(Math.PI/180.0)
+			, _color.value
+			);
+	}
 
-		public override void update()
-		{
-			Unit unit = new Unit(_level._db, _id, _level._prefabs);
-			string type       = unit.get_component_property_string (_component_id, "data.type");
-			double range      = unit.get_component_property_double (_component_id, "data.range");
-			double intensity  = unit.get_component_property_double (_component_id, "data.intensity");
-			double spot_angle = unit.get_component_property_double (_component_id, "data.spot_angle");
-			Vector3 color     = unit.get_component_property_vector3(_component_id, "data.color");
-
-			_type.value       = type;
-			_range.value      = range;
-			_intensity.value  = intensity;
-			_spot_angle.value = spot_angle*(180.0/Math.PI);
-			_color.value      = color;
-		}
+	public override void update()
+	{
+		Unit unit = new Unit(_level._db, _id, _level._prefabs);
+		string type       = unit.get_component_property_string (_component_id, "data.type");
+		double range      = unit.get_component_property_double (_component_id, "data.range");
+		double intensity  = unit.get_component_property_double (_component_id, "data.intensity");
+		double spot_angle = unit.get_component_property_double (_component_id, "data.spot_angle");
+		Vector3 color     = unit.get_component_property_vector3(_component_id, "data.color");
+
+		_type.value       = type;
+		_range.value      = range;
+		_intensity.value  = intensity;
+		_spot_angle.value = spot_angle*(180.0/Math.PI);
+		_color.value      = color;
 	}
+}
 
-	public class CameraComponentView : ComponentView
+public class CameraComponentView : ComponentView
+{
+	// Data
+	Level _level;
+
+	// Widgets
+	private ComboBoxMap _projection;
+	private EntryDouble _fov;
+	private EntryDouble _near_range;
+	private EntryDouble _far_range;
+
+	public CameraComponentView(Level level)
 	{
 		// Data
-		Level _level;
+		_level = level;
 
 		// Widgets
-		private ComboBoxMap _projection;
-		private EntryDouble _fov;
-		private EntryDouble _near_range;
-		private EntryDouble _far_range;
-
-		public CameraComponentView(Level level)
-		{
-			// Data
-			_level = level;
-
-			// Widgets
-			_projection = new ComboBoxMap();
-			_projection.append("orthographic", "Orthographic");
-			_projection.append("perspective", "Perspective");
-			_projection.value_changed.connect(on_value_changed);
-			_fov = new EntryDouble(0.0, 1.0,   90.0);
-			_fov.value_changed.connect(on_value_changed);
-			_near_range = new EntryDouble(0.001, double.MIN, double.MAX);
-			_near_range.value_changed.connect(on_value_changed);
-			_far_range  = new EntryDouble(1000.000, double.MIN, double.MAX);
-			_far_range.value_changed.connect(on_value_changed);
-
-			add_row("Projection", _projection);
-			add_row("FOV", _fov);
-			add_row("Near Range", _near_range);
-			add_row("Far Range", _far_range);
-		}
+		_projection = new ComboBoxMap();
+		_projection.append("orthographic", "Orthographic");
+		_projection.append("perspective", "Perspective");
+		_projection.value_changed.connect(on_value_changed);
+		_fov = new EntryDouble(0.0, 1.0,   90.0);
+		_fov.value_changed.connect(on_value_changed);
+		_near_range = new EntryDouble(0.001, double.MIN, double.MAX);
+		_near_range.value_changed.connect(on_value_changed);
+		_far_range  = new EntryDouble(1000.000, double.MIN, double.MAX);
+		_far_range.value_changed.connect(on_value_changed);
+
+		add_row("Projection", _projection);
+		add_row("FOV", _fov);
+		add_row("Near Range", _near_range);
+		add_row("Far Range", _far_range);
+	}
 
-		private void on_value_changed()
-		{
-			_level.set_camera(_id
-				, _component_id
-				, _projection.value
-				, _fov.value*(Math.PI/180.0)
-				, _near_range.value
-				, _far_range.value
-				);
-		}
+	private void on_value_changed()
+	{
+		_level.set_camera(_id
+			, _component_id
+			, _projection.value
+			, _fov.value*(Math.PI/180.0)
+			, _near_range.value
+			, _far_range.value
+			);
+	}
 
-		public override void update()
-		{
-			Unit unit = new Unit(_level._db, _id, _level._prefabs);
+	public override void update()
+	{
+		Unit unit = new Unit(_level._db, _id, _level._prefabs);
 
-			_projection.value = unit.get_component_property_string(_component_id, "data.projection");
-			_fov.value        = unit.get_component_property_double(_component_id, "data.fov") * (180.0/Math.PI);
-			_near_range.value = unit.get_component_property_double(_component_id, "data.near_range");
-			_far_range.value  = unit.get_component_property_double(_component_id, "data.far_range");
-		}
+		_projection.value = unit.get_component_property_string(_component_id, "data.projection");
+		_fov.value        = unit.get_component_property_double(_component_id, "data.fov") * (180.0/Math.PI);
+		_near_range.value = unit.get_component_property_double(_component_id, "data.near_range");
+		_far_range.value  = unit.get_component_property_double(_component_id, "data.far_range");
 	}
+}
+
+public class ColliderComponentView : ComponentView
+{
+	// Data
+	Level _level;
+
+	// Widgets
+	private Gtk.Entry _source;
+	private Gtk.Entry _shape;
+	private ResourceChooserButton _scene;
+	private Gtk.Entry _name;
 
-	public class ColliderComponentView : ComponentView
+	public ColliderComponentView(Level level, ProjectStore store)
 	{
 		// Data
-		Level _level;
+		_level = level;
 
 		// Widgets
-		private Gtk.Entry _source;
-		private Gtk.Entry _shape;
-		private ResourceChooserButton _scene;
-		private Gtk.Entry _name;
+		_source = new Gtk.Entry();
+		_source.sensitive = false;
+		_shape = new Gtk.Entry();
+		_shape.sensitive = false;
+		_scene = new ResourceChooserButton(store, "mesh");
+		_scene.sensitive = false;
+		_scene.value_changed.connect(on_value_changed);
+		_scene.sensitive = false;
+		_name = new Gtk.Entry();
+		_name.sensitive = false;
+
+		add_row("Source", _source);
+		add_row("Shape", _shape);
+		add_row("Scene", _scene);
+		add_row("Name", _name);
+	}
 
-		public ColliderComponentView(Level level, ProjectStore store)
+	private void on_value_changed()
+	{
+		if (_source.text == "mesh")
 		{
-			// Data
-			_level = level;
-
-			// Widgets
-			_source = new Gtk.Entry();
-			_source.sensitive = false;
-			_shape = new Gtk.Entry();
-			_shape.sensitive = false;
-			_scene = new ResourceChooserButton(store, "mesh");
-			_scene.sensitive = false;
-			_scene.value_changed.connect(on_value_changed);
-			_scene.sensitive = false;
-			_name = new Gtk.Entry();
-			_name.sensitive = false;
-
-			add_row("Source", _source);
-			add_row("Shape", _shape);
-			add_row("Scene", _scene);
-			add_row("Name", _name);
+			_level.set_collider(_id
+				, _component_id
+				, _shape.text
+				, _scene.value
+				, _name.text
+				);
 		}
+	}
 
-		private void on_value_changed()
-		{
-			if (_source.text == "mesh")
-			{
-				_level.set_collider(_id
-					, _component_id
-					, _shape.text
-					, _scene.value
-					, _name.text
-					);
-			}
-		}
+	public override void update()
+	{
+		Unit unit = new Unit(_level._db, _id, _level._prefabs);
 
-		public override void update()
+		Value? source = unit.get_component_property(_component_id, "data.source");
+		if (source != null)
 		{
-			Unit unit = new Unit(_level._db, _id, _level._prefabs);
-
-			Value? source = unit.get_component_property(_component_id, "data.source");
-			if (source != null)
+			if ((string)source == "inline")
 			{
-				if ((string)source == "inline")
-				{
-					_source.text = "inline";
-					_shape.text  = "";
-					_scene.value = "";
-					_name.text   = "";
-				}
-				else
-				{
-					_source.text = "mesh";
-					_shape.text  = unit.get_component_property_string(_component_id, "data.shape");
-					_scene.value = unit.get_component_property_string(_component_id, "data.scene");
-					_name.text   = unit.get_component_property_string(_component_id, "data.name");
-				}
+				_source.text = "inline";
+				_shape.text  = "";
+				_scene.value = "";
+				_name.text   = "";
 			}
 			else
 			{
@@ -413,419 +405,428 @@ namespace Crown
 				_name.text   = unit.get_component_property_string(_component_id, "data.name");
 			}
 		}
+		else
+		{
+			_source.text = "mesh";
+			_shape.text  = unit.get_component_property_string(_component_id, "data.shape");
+			_scene.value = unit.get_component_property_string(_component_id, "data.scene");
+			_name.text   = unit.get_component_property_string(_component_id, "data.name");
+		}
 	}
+}
+
+public class ActorComponentView : ComponentView
+{
+	// Data
+	Level _level;
+
+	// Widgets
+	private ComboBoxMap _class;
+	private Gtk.Entry _collision_filter;
+	private EntryDouble _mass;
+	private Gtk.Entry _material;
 
-	public class ActorComponentView : ComponentView
+	public ActorComponentView(Level level)
 	{
 		// Data
-		Level _level;
+		_level = level;
 
 		// Widgets
-		private ComboBoxMap _class;
-		private Gtk.Entry _collision_filter;
-		private EntryDouble _mass;
-		private Gtk.Entry _material;
-
-		public ActorComponentView(Level level)
-		{
-			// Data
-			_level = level;
-
-			// Widgets
-			_class = new ComboBoxMap();
-			_class.append("static", "static");
-			_class.append("dynamic", "dynamic");
-			_class.append("keyframed", "keyframed");
-			_class.append("trigger", "trigger");
-			_class.value_changed.connect(on_value_changed);
-			_collision_filter = new Gtk.Entry();
-			_collision_filter.sensitive = false;
-			_material = new Gtk.Entry();
-			_material.sensitive = false;
-			_mass = new EntryDouble(1.0, 0.0, double.MAX);
-			_mass.value_changed.connect(on_value_changed);
-
-			add_row("Class", _class);
-			add_row("Collision Filter", _collision_filter);
-			add_row("Material", _material);
-			add_row("Mass", _mass);
-		}
+		_class = new ComboBoxMap();
+		_class.append("static", "static");
+		_class.append("dynamic", "dynamic");
+		_class.append("keyframed", "keyframed");
+		_class.append("trigger", "trigger");
+		_class.value_changed.connect(on_value_changed);
+		_collision_filter = new Gtk.Entry();
+		_collision_filter.sensitive = false;
+		_material = new Gtk.Entry();
+		_material.sensitive = false;
+		_mass = new EntryDouble(1.0, 0.0, double.MAX);
+		_mass.value_changed.connect(on_value_changed);
+
+		add_row("Class", _class);
+		add_row("Collision Filter", _collision_filter);
+		add_row("Material", _material);
+		add_row("Mass", _mass);
+	}
 
-		private void on_value_changed()
-		{
-			_level.set_actor(_id
-				, _component_id
-				, _class.value
-				, _collision_filter.text
-				, _material.text
-				, _mass.value
-				);
-		}
+	private void on_value_changed()
+	{
+		_level.set_actor(_id
+			, _component_id
+			, _class.value
+			, _collision_filter.text
+			, _material.text
+			, _mass.value
+			);
+	}
 
-		public override void update()
-		{
-			Unit unit = new Unit(_level._db, _id, _level._prefabs);
-			_class.value           = unit.get_component_property_string(_component_id, "data.class");
-			_collision_filter.text = unit.get_component_property_string(_component_id, "data.collision_filter");
-			_material.text         = unit.get_component_property_string(_component_id, "data.material");
-			_mass.value            = unit.get_component_property_double(_component_id, "data.mass");
-		}
+	public override void update()
+	{
+		Unit unit = new Unit(_level._db, _id, _level._prefabs);
+		_class.value           = unit.get_component_property_string(_component_id, "data.class");
+		_collision_filter.text = unit.get_component_property_string(_component_id, "data.collision_filter");
+		_material.text         = unit.get_component_property_string(_component_id, "data.material");
+		_mass.value            = unit.get_component_property_double(_component_id, "data.mass");
 	}
+}
+
+public class ScriptComponentView : ComponentView
+{
+	// Data
+	Level _level;
+
+	// Widgets
+	private ResourceChooserButton _script_resource;
 
-	public class ScriptComponentView : ComponentView
+	public ScriptComponentView(Level level, ProjectStore store)
 	{
 		// Data
-		Level _level;
+		_level = level;
 
 		// Widgets
-		private ResourceChooserButton _script_resource;
+		_script_resource = new ResourceChooserButton(store, "lua");
+		_script_resource.value_changed.connect(on_value_changed);
 
-		public ScriptComponentView(Level level, ProjectStore store)
-		{
-			// Data
-			_level = level;
+		add_row("Script", _script_resource);
+	}
 
-			// Widgets
-			_script_resource = new ResourceChooserButton(store, "lua");
-			_script_resource.value_changed.connect(on_value_changed);
+	private void on_value_changed()
+	{
+		Unit unit = new Unit(_level._db, _id, _level._prefabs);
+		unit.set_component_property_string(_component_id, "data.script_resource", _script_resource.value);
+		unit.set_component_property_string(_component_id, "type",                 "script");
+	}
 
-			add_row("Script", _script_resource);
-		}
+	public override void update()
+	{
+		Unit unit = new Unit(_level._db, _id, _level._prefabs);
+		_script_resource.value = unit.get_component_property_string(_component_id, "data.script_resource");
+	}
+}
 
-		private void on_value_changed()
-		{
-			Unit unit = new Unit(_level._db, _id, _level._prefabs);
-			unit.set_component_property_string(_component_id, "data.script_resource", _script_resource.value);
-			unit.set_component_property_string(_component_id, "type",                 "script");
-		}
+public class AnimationStateMachine : ComponentView
+{
+	// Data
+	Level _level;
 
-		public override void update()
-		{
-			Unit unit = new Unit(_level._db, _id, _level._prefabs);
-			_script_resource.value = unit.get_component_property_string(_component_id, "data.script_resource");
-		}
-	}
+	// Widgets
+	private ResourceChooserButton _state_machine_resource;
 
-	public class AnimationStateMachine : ComponentView
+	public AnimationStateMachine(Level level, ProjectStore store)
 	{
 		// Data
-		Level _level;
+		_level = level;
 
 		// Widgets
-		private ResourceChooserButton _state_machine_resource;
+		_state_machine_resource = new ResourceChooserButton(store, "state_machine");
+		_state_machine_resource.value_changed.connect(on_value_changed);
 
-		public AnimationStateMachine(Level level, ProjectStore store)
-		{
-			// Data
-			_level = level;
+		add_row("State Machine", _state_machine_resource);
+	}
 
-			// Widgets
-			_state_machine_resource = new ResourceChooserButton(store, "state_machine");
-			_state_machine_resource.value_changed.connect(on_value_changed);
+	private void on_value_changed()
+	{
+		Unit unit = new Unit(_level._db, _id, _level._prefabs);
+		unit.set_component_property_string(_component_id, "data.state_machine_resource", _state_machine_resource.value);
+		unit.set_component_property_string(_component_id, "type",                        "animation_state_machine");
+	}
 
-			add_row("State Machine", _state_machine_resource);
-		}
+	public override void update()
+	{
+		Unit unit = new Unit(_level._db, _id, _level._prefabs);
+		_state_machine_resource.value = unit.get_component_property_string(_component_id, "data.state_machine_resource");
+	}
+}
 
-		private void on_value_changed()
-		{
-			Unit unit = new Unit(_level._db, _id, _level._prefabs);
-			unit.set_component_property_string(_component_id, "data.state_machine_resource", _state_machine_resource.value);
-			unit.set_component_property_string(_component_id, "type",                        "animation_state_machine");
-		}
+public class UnitView : ComponentView
+{
+	// Data
+	Level _level;
 
-		public override void update()
-		{
-			Unit unit = new Unit(_level._db, _id, _level._prefabs);
-			_state_machine_resource.value = unit.get_component_property_string(_component_id, "data.state_machine_resource");
-		}
-	}
+	// Widgets
+	private Gtk.Entry _unit_name;
 
-	public class UnitView : ComponentView
+	public UnitView(Level level)
 	{
 		// Data
-		Level _level;
+		_level = level;
 
 		// Widgets
-		private Gtk.Entry _unit_name;
+		_unit_name = new Gtk.Entry();
+		_unit_name.sensitive = false;
 
-		public UnitView(Level level)
-		{
-			// Data
-			_level = level;
+		add_row("Name", _unit_name);
+	}
 
-			// Widgets
-			_unit_name = new Gtk.Entry();
-			_unit_name.sensitive = false;
+	public override void update()
+	{
+		if (_level._db.has_property(_id, "prefab"))
+			_unit_name.text = _level._db.get_property_string(_id, "prefab");
+		else
+			_unit_name.text = "<none>";
+	}
+}
 
-			add_row("Name", _unit_name);
-		}
+public class SoundTransformView : ComponentView
+{
+	// Data
+	Level _level;
 
-		public override void update()
-		{
-			if (_level._db.has_property(_id, "prefab"))
-				_unit_name.text = _level._db.get_property_string(_id, "prefab");
-			else
-				_unit_name.text = "<none>";
-		}
-	}
+	// Widgets
+	private EntryVector3 _position;
+	private EntryRotation _rotation;
 
-	public class SoundTransformView : ComponentView
+	public SoundTransformView(Level level)
 	{
 		// Data
-		Level _level;
+		_level = level;
+		_id = GUID_ZERO;
 
 		// Widgets
-		private EntryVector3 _position;
-		private EntryRotation _rotation;
+		_position = new EntryPosition();
+		_rotation = new EntryRotation();
 
-		public SoundTransformView(Level level)
-		{
-			// Data
-			_level = level;
-			_id = GUID_ZERO;
+		_position.value_changed.connect(on_value_changed);
+		_rotation.value_changed.connect(on_value_changed);
 
-			// Widgets
-			_position = new EntryPosition();
-			_rotation = new EntryRotation();
+		add_row("Position", _position);
+		add_row("Rotation", _rotation);
+	}
 
-			_position.value_changed.connect(on_value_changed);
-			_rotation.value_changed.connect(on_value_changed);
+	private void on_value_changed()
+	{
+		Vector3 pos    = _position.value;
+		Quaternion rot = _rotation.value;
 
-			add_row("Position", _position);
-			add_row("Rotation", _rotation);
-		}
+		_level.move_selected_objects(pos, rot, Vector3(1.0, 1.0, 1.0));
+	}
 
-		private void on_value_changed()
-		{
-			Vector3 pos    = _position.value;
-			Quaternion rot = _rotation.value;
+	public override void update()
+	{
+		Vector3 pos    = _level._db.get_property_vector3   (_id, "position");
+		Quaternion rot = _level._db.get_property_quaternion(_id, "rotation");
 
-			_level.move_selected_objects(pos, rot, Vector3(1.0, 1.0, 1.0));
-		}
+		_position.value = pos;
+		_rotation.value = rot;
+	}
+}
 
-		public override void update()
-		{
-			Vector3 pos    = _level._db.get_property_vector3   (_id, "position");
-			Quaternion rot = _level._db.get_property_quaternion(_id, "rotation");
+public class SoundView : ComponentView
+{
+	// Data
+	Level _level;
 
-			_position.value = pos;
-			_rotation.value = rot;
-		}
-	}
+	// Widgets
+	private ResourceChooserButton _name;
+	private EntryDouble _range;
+	private EntryDouble _volume;
+	private CheckBox _loop;
 
-	public class SoundView : ComponentView
+	public SoundView(Level level, ProjectStore store)
 	{
 		// Data
-		Level _level;
+		_level = level;
 
 		// Widgets
-		private ResourceChooserButton _name;
-		private EntryDouble _range;
-		private EntryDouble _volume;
-		private CheckBox _loop;
-
-		public SoundView(Level level, ProjectStore store)
-		{
-			// Data
-			_level = level;
-
-			// Widgets
-			_name   = new ResourceChooserButton(store, "sound");
-			_name.value_changed.connect(on_value_changed);
-			_range  = new EntryDouble(1.0, 0.0, double.MAX);
-			_range.value_changed.connect(on_value_changed);
-			_volume = new EntryDouble(1.0, 0.0, 1.0);
-			_volume.value_changed.connect(on_value_changed);
-			_loop   = new CheckBox();
-			_loop.value_changed.connect(on_value_changed);
-
-			add_row("Name", _name);
-			add_row("Range", _range);
-			add_row("Volume", _volume);
-			add_row("Loop", _loop);
-		}
+		_name   = new ResourceChooserButton(store, "sound");
+		_name.value_changed.connect(on_value_changed);
+		_range  = new EntryDouble(1.0, 0.0, double.MAX);
+		_range.value_changed.connect(on_value_changed);
+		_volume = new EntryDouble(1.0, 0.0, 1.0);
+		_volume.value_changed.connect(on_value_changed);
+		_loop   = new CheckBox();
+		_loop.value_changed.connect(on_value_changed);
+
+		add_row("Name", _name);
+		add_row("Range", _range);
+		add_row("Volume", _volume);
+		add_row("Loop", _loop);
+	}
 
-		private void on_value_changed()
-		{
-			_level.set_sound(_id
-				, _name.value
-				, _range.value
-				, _volume.value
-				, _loop.value
-				);
-		}
+	private void on_value_changed()
+	{
+		_level.set_sound(_id
+			, _name.value
+			, _range.value
+			, _volume.value
+			, _loop.value
+			);
+	}
 
-		public override void update()
-		{
-			_name.value   = _level._db.get_property_string(_id, "name");
-			_range.value  = _level._db.get_property_double(_id, "range");
-			_volume.value = _level._db.get_property_double(_id, "volume");
-			_loop.value   = _level._db.get_property_bool  (_id, "loop");
-		}
+	public override void update()
+	{
+		_name.value   = _level._db.get_property_string(_id, "name");
+		_range.value  = _level._db.get_property_double(_id, "range");
+		_volume.value = _level._db.get_property_double(_id, "volume");
+		_loop.value   = _level._db.get_property_bool  (_id, "loop");
 	}
+}
 
-	public class PropertiesView : Gtk.Bin
+public class PropertiesView : Gtk.Bin
+{
+	public struct ComponentEntry
 	{
-		public struct ComponentEntry
-		{
-			string type;
-			int position;
-		}
+		string type;
+		int position;
+	}
 
+	// Data
+	private Level _level;
+	private HashMap<string, Gtk.Expander> _expanders;
+	private HashMap<string, ComponentView> _components;
+	private ArrayList<ComponentEntry?> _entries;
+
+	// Widgets
+	private Gtk.Label _nothing_to_show;
+	private Gtk.Viewport _viewport;
+	private Gtk.ScrolledWindow _scrolled_window;
+	private Gtk.Box _components_vbox;
+	private Gtk.Widget _current_widget;
+
+	public PropertiesView(Level level, ProjectStore store)
+	{
 		// Data
-		private Level _level;
-		private HashMap<string, Gtk.Expander> _expanders;
-		private HashMap<string, ComponentView> _components;
-		private ArrayList<ComponentEntry?> _entries;
+		_level = level;
+		_level.selection_changed.connect(on_selection_changed);
+
+		_expanders = new HashMap<string, Gtk.Expander>();
+		_components = new HashMap<string, ComponentView>();
+		_entries = new ArrayList<ComponentEntry?>();
 
 		// Widgets
-		private Gtk.Label _nothing_to_show;
-		private Gtk.Viewport _viewport;
-		private Gtk.ScrolledWindow _scrolled_window;
-		private Gtk.Box _components_vbox;
-		private Gtk.Widget _current_widget;
+		_components_vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
+		_components_vbox.margin_bottom = 18;
 
-		public PropertiesView(Level level, ProjectStore store)
-		{
-			// Data
-			_level = level;
-			_level.selection_changed.connect(on_selection_changed);
-
-			_expanders = new HashMap<string, Gtk.Expander>();
-			_components = new HashMap<string, ComponentView>();
-			_entries = new ArrayList<ComponentEntry?>();
-
-			// Widgets
-			_components_vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
-			_components_vbox.margin_bottom = 18;
-
-			// Unit
-			add_component_view("Unit",                    "name",                    0, new UnitView(_level));
-			add_component_view("Transform",               "transform",               0, new TransformComponentView(_level));
-			add_component_view("Light",                   "light",                   1, new LightComponentView(_level));
-			add_component_view("Camera",                  "camera",                  2, new CameraComponentView(_level));
-			add_component_view("Mesh Renderer",           "mesh_renderer",           3, new MeshRendererComponentView(_level, store));
-			add_component_view("Sprite Renderer",         "sprite_renderer",         3, new SpriteRendererComponentView(_level, store));
-			add_component_view("Collider",                "collider",                3, new ColliderComponentView(_level, store));
-			add_component_view("Actor",                   "actor",                   3, new ActorComponentView(_level));
-			add_component_view("Script",                  "script",                  3, new ScriptComponentView(_level, store));
-			add_component_view("Animation State Machine", "animation_state_machine", 3, new AnimationStateMachine(_level, store));
-
-			// Sound
-			add_component_view("Transform", "sound_transform",  0, new SoundTransformView(_level));
-			add_component_view("Sound",     "sound_properties", 1, new SoundView(_level, store));
-
-			_entries.sort((a, b) => { return (a.position < b.position ? -1 : 1); });
-			foreach (var entry in _entries)
-				_components_vbox.pack_start(_expanders[entry.type], false, true, 0);
+		// Unit
+		add_component_view("Unit",                    "name",                    0, new UnitView(_level));
+		add_component_view("Transform",               "transform",               0, new TransformComponentView(_level));
+		add_component_view("Light",                   "light",                   1, new LightComponentView(_level));
+		add_component_view("Camera",                  "camera",                  2, new CameraComponentView(_level));
+		add_component_view("Mesh Renderer",           "mesh_renderer",           3, new MeshRendererComponentView(_level, store));
+		add_component_view("Sprite Renderer",         "sprite_renderer",         3, new SpriteRendererComponentView(_level, store));
+		add_component_view("Collider",                "collider",                3, new ColliderComponentView(_level, store));
+		add_component_view("Actor",                   "actor",                   3, new ActorComponentView(_level));
+		add_component_view("Script",                  "script",                  3, new ScriptComponentView(_level, store));
+		add_component_view("Animation State Machine", "animation_state_machine", 3, new AnimationStateMachine(_level, store));
 
-			_nothing_to_show = new Gtk.Label("Nothing to show");
+		// Sound
+		add_component_view("Transform", "sound_transform",  0, new SoundTransformView(_level));
+		add_component_view("Sound",     "sound_properties", 1, new SoundView(_level, store));
 
-			_viewport = new Gtk.Viewport(null, null);
-			_viewport.add(_components_vbox);
+		_entries.sort((a, b) => { return (a.position < b.position ? -1 : 1); });
+		foreach (var entry in _entries)
+			_components_vbox.pack_start(_expanders[entry.type], false, true, 0);
 
-			_scrolled_window = new Gtk.ScrolledWindow(null, null);
-			_scrolled_window.add(_viewport);
+		_nothing_to_show = new Gtk.Label("Nothing to show");
 
-			_current_widget = null;
+		_viewport = new Gtk.Viewport(null, null);
+		_viewport.add(_components_vbox);
 
-			this.get_style_context().add_class("properties-view");
-			this.set_current_widget(_nothing_to_show);
-		}
+		_scrolled_window = new Gtk.ScrolledWindow(null, null);
+		_scrolled_window.add(_viewport);
 
-		private void add_component_view(string label, string component_type, int position, ComponentView cv)
-		{
-			Gtk.Label lb = new Gtk.Label(null);
-			lb.set_markup("<b>%s</b>".printf(label));
-			lb.set_alignment(0.0f, 0.5f);
+		_current_widget = null;
 
-			Gtk.Expander expander = new Gtk.Expander("");
-			expander.label_widget = lb;
-			expander.child = cv;
-			expander.expanded = true;
-			expander.margin_end = 12;
+		this.get_style_context().add_class("properties-view");
+		this.set_current_widget(_nothing_to_show);
+	}
 
-			_components[component_type] = cv;
-			_expanders[component_type] = expander;
+	private void add_component_view(string label, string component_type, int position, ComponentView cv)
+	{
+		Gtk.Label lb = new Gtk.Label(null);
+		lb.set_markup("<b>%s</b>".printf(label));
+		lb.set_alignment(0.0f, 0.5f);
 
-			_entries.add({ component_type, position });
-		}
+		Gtk.Expander expander = new Gtk.Expander("");
+		expander.label_widget = lb;
+		expander.child = cv;
+		expander.expanded = true;
+		expander.margin_end = 12;
 
-		private void set_current_widget(Gtk.Widget w)
-		{
-			if (_current_widget == w)
-				return;
+		_components[component_type] = cv;
+		_expanders[component_type] = expander;
 
-			if (_current_widget != null)
-			{
-				_current_widget.hide();
-				remove(_current_widget);
-			}
+		_entries.add({ component_type, position });
+	}
+
+	private void set_current_widget(Gtk.Widget w)
+	{
+		if (_current_widget == w)
+			return;
 
-			_current_widget = w;
-			_current_widget.show_all();
-			add(_current_widget);
+		if (_current_widget != null)
+		{
+			_current_widget.hide();
+			remove(_current_widget);
 		}
 
-		private void on_selection_changed(Gee.ArrayList<Guid?> selection)
+		_current_widget = w;
+		_current_widget.show_all();
+		add(_current_widget);
+	}
+
+	private void on_selection_changed(Gee.ArrayList<Guid?> selection)
+	{
+		if (selection.size != 1)
 		{
-			if (selection.size != 1)
-			{
-				set_current_widget(_nothing_to_show);
-				return;
-			}
+			set_current_widget(_nothing_to_show);
+			return;
+		}
 
-			Guid id = selection[selection.size - 1];
+		Guid id = selection[selection.size - 1];
 
-			if (_level.is_unit(id))
+		if (_level.is_unit(id))
+		{
+			set_current_widget(_scrolled_window);
+
+			foreach (var entry in _entries)
 			{
-				set_current_widget(_scrolled_window);
+				Gtk.Expander expander = _expanders[entry.type];
 
-				foreach (var entry in _entries)
+				Guid component_id = GUID_ZERO;
+				Unit unit = new Unit(_level._db, id, _level._prefabs);
+				if (unit.has_component(entry.type, ref component_id) || entry.type == "name")
+				{
+					ComponentView cv = _components[entry.type];
+					cv._id = id;
+					cv._component_id = component_id;
+					cv.update();
+					expander.show_all();
+				}
+				else
 				{
-					Gtk.Expander expander = _expanders[entry.type];
-
-					Guid component_id = GUID_ZERO;
-					Unit unit = new Unit(_level._db, id, _level._prefabs);
-					if (unit.has_component(entry.type, ref component_id) || entry.type == "name")
-					{
-						ComponentView cv = _components[entry.type];
-						cv._id = id;
-						cv._component_id = component_id;
-						cv.update();
-						expander.show_all();
-					}
-					else
-					{
-						expander.hide();
-					}
+					expander.hide();
 				}
 			}
-			else if (_level.is_sound(id))
+		}
+		else if (_level.is_sound(id))
+		{
+			set_current_widget(_scrolled_window);
+
+			foreach (var entry in _entries)
 			{
-				set_current_widget(_scrolled_window);
+				Gtk.Expander expander = _expanders[entry.type];
 
-				foreach (var entry in _entries)
+				if (entry.type == "sound_transform" || entry.type == "sound_properties")
 				{
-					Gtk.Expander expander = _expanders[entry.type];
-
-					if (entry.type == "sound_transform" || entry.type == "sound_properties")
-					{
-						ComponentView cv = _components[entry.type];
-						cv._id = id;
-						cv.update();
-						expander.show_all();
-					}
-					else
-					{
-						expander.hide();
-					}
+					ComponentView cv = _components[entry.type];
+					cv._id = id;
+					cv.update();
+					expander.show_all();
+				}
+				else
+				{
+					expander.hide();
 				}
-			}
-			else
-			{
-				set_current_widget(_nothing_to_show);
 			}
 		}
+		else
+		{
+			set_current_widget(_nothing_to_show);
+		}
 	}
 }
+
+}

+ 266 - 265
tools/level_editor/resource_chooser.vala

@@ -8,338 +8,339 @@ using Gee;
 
 namespace Crown
 {
-	// Returns true if the item should be filtered out
-	private bool user_filter(string type, string name)
-	{
-		return (type == "unit" || type == "sound") && !name.has_prefix("core/");
-	}
+// Returns true if the item should be filtered out
+private bool user_filter(string type, string name)
+{
+	return (type == "unit" || type == "sound") && !name.has_prefix("core/");
+}
 
-	public delegate bool UserFilter(string type, string name);
+public delegate bool UserFilter(string type, string name);
 
-	public class ResourceChooser : Gtk.Box
+public class ResourceChooser : Gtk.Box
+{
+	// Data
+	public Project _project;
+	public GLib.Subprocess _editor_process;
+	public ConsoleClient _console_client;
+	public Gtk.ListStore _list_store;
+	public bool _preview;
+	public unowned UserFilter _user_filter;
+	public string _name;
+
+	// Widgets
+	public Gtk.Entry _filter_entry;
+	public Gtk.TreeModelFilter _tree_filter;
+	public Gtk.TreeModelSort _tree_sort;
+	public Gtk.TreeView _tree_view;
+	public Gtk.TreeSelection _tree_selection;
+	public Gtk.ScrolledWindow _scrolled_window;
+
+	public Slide _editor_slide;
+	public EditorView _editor_view;
+
+	// Signals
+	public signal void resource_selected(string type, string name);
+
+	public ResourceChooser(Project? project, ProjectStore project_store, bool preview)
 	{
-		// Data
-		public Project _project;
-		public GLib.Subprocess _editor_process;
-		public ConsoleClient _console_client;
-		public Gtk.ListStore _list_store;
-		public bool _preview;
-		public unowned UserFilter _user_filter;
-		public string _name;
-
-		// Widgets
-		public Gtk.Entry _filter_entry;
-		public Gtk.TreeModelFilter _tree_filter;
-		public Gtk.TreeModelSort _tree_sort;
-		public Gtk.TreeView _tree_view;
-		public Gtk.TreeSelection _tree_selection;
-		public Gtk.ScrolledWindow _scrolled_window;
+		Object(orientation: Gtk.Orientation.VERTICAL, spacing: 0);
 
-		public Slide _editor_slide;
-		public EditorView _editor_view;
-
-		// Signals
-		public signal void resource_selected(string type, string name);
-
-		public ResourceChooser(Project? project, ProjectStore project_store, bool preview)
-		{
-			Object(orientation: Gtk.Orientation.VERTICAL, spacing: 0);
-
-			// Data
-			_project = project;
-
-			_console_client = new ConsoleClient();
-			_list_store = project_store._list_store;
-			_preview = preview;
-			_user_filter = user_filter;
-
-			// Widgets
-			_filter_entry = new Gtk.SearchEntry();
-			_filter_entry.set_placeholder_text("Search...");
-			_filter_entry.changed.connect(on_filter_entry_text_changed);
-			_filter_entry.key_press_event.connect(on_filter_entry_key_pressed);
+		// Data
+		_project = project;
 
-			_tree_filter = new Gtk.TreeModelFilter(_list_store, null);
-			_tree_filter.set_visible_func((model, iter) => {
-				Value type;
-				Value name;
-				model.get_value(iter, ProjectStore.Column.TYPE, out type);
-				model.get_value(iter, ProjectStore.Column.NAME, out name);
+		_console_client = new ConsoleClient();
+		_list_store = project_store._list_store;
+		_preview = preview;
+		_user_filter = user_filter;
 
-				string type_str = (string)type;
-				string name_str = (string)name;
-
-				return type_str != null
-					&& name_str != null
-					&& _user_filter(type_str, name_str)
-					&& (_filter_entry.text.length == 0 || name_str.index_of(_filter_entry.text) > -1)
-					;
-			});
-
-			_tree_sort = new Gtk.TreeModelSort.with_model(_tree_filter);
-			_tree_sort.set_default_sort_func((model, iter_a, iter_b) => {
-				Value id_a;
-				Value id_b;
-				model.get_value(iter_a, ProjectStore.Column.NAME, out id_a);
-				model.get_value(iter_b, ProjectStore.Column.NAME, out id_b);
-				return strcmp((string)id_a, (string)id_b);
-			});
-
-			_tree_view = new Gtk.TreeView();
-			_tree_view.insert_column_with_attributes(-1
-				, "Name"
-				, new Gtk.CellRendererText()
-				, "text"
-				, ProjectStore.Column.NAME
-				, null
-				);
+		// Widgets
+		_filter_entry = new Gtk.SearchEntry();
+		_filter_entry.set_placeholder_text("Search...");
+		_filter_entry.changed.connect(on_filter_entry_text_changed);
+		_filter_entry.key_press_event.connect(on_filter_entry_key_pressed);
+
+		_tree_filter = new Gtk.TreeModelFilter(_list_store, null);
+		_tree_filter.set_visible_func((model, iter) => {
+			Value type;
+			Value name;
+			model.get_value(iter, ProjectStore.Column.TYPE, out type);
+			model.get_value(iter, ProjectStore.Column.NAME, out name);
+
+			string type_str = (string)type;
+			string name_str = (string)name;
+
+			return type_str != null
+				&& name_str != null
+				&& _user_filter(type_str, name_str)
+				&& (_filter_entry.text.length == 0 || name_str.index_of(_filter_entry.text) > -1)
+				;
+		});
+
+		_tree_sort = new Gtk.TreeModelSort.with_model(_tree_filter);
+		_tree_sort.set_default_sort_func((model, iter_a, iter_b) => {
+			Value id_a;
+			Value id_b;
+			model.get_value(iter_a, ProjectStore.Column.NAME, out id_a);
+			model.get_value(iter_b, ProjectStore.Column.NAME, out id_b);
+			return strcmp((string)id_a, (string)id_b);
+		});
+
+		_tree_view = new Gtk.TreeView();
+		_tree_view.insert_column_with_attributes(-1
+			, "Name"
+			, new Gtk.CellRendererText()
+			, "text"
+			, ProjectStore.Column.NAME
+			, null
+			);
 /*
-			// Debug
-			_tree_view.insert_column_with_attributes(-1
-				, "Type"
-				, new Gtk.CellRendererText()
-				, "text"
-				, ProjectStore.Column.TYPE
-				, null
-				);
+		// Debug
+		_tree_view.insert_column_with_attributes(-1
+			, "Type"
+			, new Gtk.CellRendererText()
+			, "text"
+			, ProjectStore.Column.TYPE
+			, null
+			);
 */
-			_tree_view.model = _tree_sort;
-			_tree_view.headers_visible = false;
-			_tree_view.can_focus = false;
-			_tree_view.row_activated.connect(on_row_activated);
-			_tree_view.button_release_event.connect(on_button_released);
+		_tree_view.model = _tree_sort;
+		_tree_view.headers_visible = false;
+		_tree_view.can_focus = false;
+		_tree_view.row_activated.connect(on_row_activated);
+		_tree_view.button_release_event.connect(on_button_released);
 
-			_tree_selection = _tree_view.get_selection();
-			_tree_selection.set_mode(Gtk.SelectionMode.BROWSE);
-			_tree_selection.changed.connect(on_tree_selection_changed);
+		_tree_selection = _tree_view.get_selection();
+		_tree_selection.set_mode(Gtk.SelectionMode.BROWSE);
+		_tree_selection.changed.connect(on_tree_selection_changed);
 
-			_scrolled_window = new Gtk.ScrolledWindow(null, null);
-			_scrolled_window.add(_tree_view);
-			_scrolled_window.set_size_request(300, 400);
+		_scrolled_window = new Gtk.ScrolledWindow(null, null);
+		_scrolled_window.add(_tree_view);
+		_scrolled_window.set_size_request(300, 400);
 
-			_editor_slide = new Slide();
+		_editor_slide = new Slide();
 
-			this.pack_start(_filter_entry, false, true, 0);
-			this.pack_start(_editor_slide, true, true, 0);
-			this.pack_start(_scrolled_window, true, true, 0);
+		this.pack_start(_filter_entry, false, true, 0);
+		this.pack_start(_editor_slide, true, true, 0);
+		this.pack_start(_scrolled_window, true, true, 0);
 
-			Gtk.Label label = new Gtk.Label("No Preview");
-			label.set_size_request(300, 300);
-			_editor_slide.show_widget(label);
+		Gtk.Label label = new Gtk.Label("No Preview");
+		label.set_size_request(300, 300);
+		_editor_slide.show_widget(label);
 
-			this.destroy.connect(on_destroy);
+		this.destroy.connect(on_destroy);
 
-			restart_editor();
-		}
+		restart_editor();
+	}
 
-		private void on_row_activated(Gtk.TreePath path, TreeViewColumn column)
+	private void on_row_activated(Gtk.TreePath path, TreeViewColumn column)
+	{
+		Gtk.TreePath filter_path = _tree_sort.convert_path_to_child_path(path);
+		Gtk.TreePath child_path = _tree_filter.convert_path_to_child_path(filter_path);
+		Gtk.TreeIter iter;
+		if (_list_store.get_iter(out iter, child_path))
 		{
-			Gtk.TreePath filter_path = _tree_sort.convert_path_to_child_path(path);
-			Gtk.TreePath child_path = _tree_filter.convert_path_to_child_path(filter_path);
-			Gtk.TreeIter iter;
-			if (_list_store.get_iter(out iter, child_path))
-			{
-				Value name;
-				Value type;
-				_list_store.get_value(iter, ProjectStore.Column.NAME, out name);
-				_list_store.get_value(iter, ProjectStore.Column.TYPE, out type);
-				_name = (string)name;
-				resource_selected((string)type, (string)name);
-			}
+			Value name;
+			Value type;
+			_list_store.get_value(iter, ProjectStore.Column.NAME, out name);
+			_list_store.get_value(iter, ProjectStore.Column.TYPE, out type);
+			_name = (string)name;
+			resource_selected((string)type, (string)name);
 		}
+	}
 
-		private bool on_button_released(Gdk.EventButton ev)
+	private bool on_button_released(Gdk.EventButton ev)
+	{
+		if (ev.button == Gdk.BUTTON_PRIMARY)
 		{
-			if (ev.button == Gdk.BUTTON_PRIMARY)
+			Gtk.TreePath path;
+			int cell_x;
+			int cell_y;
+			if (_tree_view.get_path_at_pos((int)ev.x, (int)ev.y, out path, null, out cell_x, out cell_y))
 			{
-				Gtk.TreePath path;
-				int cell_x;
-				int cell_y;
-				if (_tree_view.get_path_at_pos((int)ev.x, (int)ev.y, out path, null, out cell_x, out cell_y))
+				if (_tree_view.get_selection().path_is_selected(path))
 				{
-					if (_tree_view.get_selection().path_is_selected(path))
+					Gtk.TreePath filter_path = _tree_sort.convert_path_to_child_path(path);
+					Gtk.TreePath child_path = _tree_filter.convert_path_to_child_path(filter_path);
+					Gtk.TreeIter iter;
+					if (_list_store.get_iter(out iter, child_path))
 					{
-						Gtk.TreePath filter_path = _tree_sort.convert_path_to_child_path(path);
-						Gtk.TreePath child_path = _tree_filter.convert_path_to_child_path(filter_path);
-						Gtk.TreeIter iter;
-						if (_list_store.get_iter(out iter, child_path))
-						{
-							Value name;
-							Value type;
-							_list_store.get_value(iter, ProjectStore.Column.NAME, out name);
-							_list_store.get_value(iter, ProjectStore.Column.TYPE, out type);
-							_name = (string)name;
-							resource_selected((string)type, (string)name);
-						}
+						Value name;
+						Value type;
+						_list_store.get_value(iter, ProjectStore.Column.NAME, out name);
+						_list_store.get_value(iter, ProjectStore.Column.TYPE, out type);
+						_name = (string)name;
+						resource_selected((string)type, (string)name);
 					}
 				}
 			}
+		}
+
+		return Gdk.EVENT_PROPAGATE;
+	}
+
+	private void on_destroy()
+	{
+		stop_editor();
+	}
 
-			return Gdk.EVENT_PROPAGATE;
+	private void start_editor(uint window_xid)
+	{
+		if (window_xid == 0)
+			return;
+
+		string args[] =
+		{
+			ENGINE_EXE,
+			"--data-dir", _project.data_dir(),
+			"--boot-dir", UNIT_PREVIEW_BOOT_DIR,
+			"--parent-window", window_xid.to_string(),
+			"--console-port", "10002",
+			"--wait-console",
+			null
+		};
+
+		GLib.SubprocessLauncher sl = new GLib.SubprocessLauncher(SubprocessFlags.NONE);
+		sl.set_cwd(ENGINE_DIR);
+		try
+		{
+			_editor_process = sl.spawnv(args);
+		}
+		catch (Error e)
+		{
+			GLib.stderr.printf("%s\n", e.message);
 		}
 
-		private void on_destroy()
+		while (!_console_client.is_connected())
 		{
-			stop_editor();
+			_console_client.connect("127.0.0.1", 10002);
+			GLib.Thread.usleep(100*1000);
 		}
 
-		private void start_editor(uint window_xid)
+		_tree_view.set_cursor(new Gtk.TreePath.first(), null, false);
+	}
+
+	public void stop_editor()
+	{
+		if (!_preview)
+			return;
+
+		if (_console_client != null)
 		{
-			if (window_xid == 0)
-				return;
+			_console_client.send_script("Device.quit()");
+			_console_client.close();
+		}
 
-			string args[] =
-			{
-				ENGINE_EXE,
-				"--data-dir", _project.data_dir(),
-				"--boot-dir", UNIT_PREVIEW_BOOT_DIR,
-				"--parent-window", window_xid.to_string(),
-				"--console-port", "10002",
-				"--wait-console",
-				null
-			};
-
-			GLib.SubprocessLauncher sl = new GLib.SubprocessLauncher(SubprocessFlags.NONE);
-			sl.set_cwd(ENGINE_DIR);
+		if (_editor_process != null)
+		{
 			try
 			{
-				_editor_process = sl.spawnv(args);
+				_editor_process.wait();
 			}
 			catch (Error e)
 			{
-				GLib.stderr.printf("%s\n", e.message);
+				stderr.printf("Error: %s\n", e.message);
 			}
-
-			while (!_console_client.is_connected())
-			{
-				_console_client.connect("127.0.0.1", 10002);
-				GLib.Thread.usleep(100*1000);
-			}
-
-			_tree_view.set_cursor(new Gtk.TreePath.first(), null, false);
 		}
+	}
 
-		public void stop_editor()
-		{
-			if (!_preview)
-				return;
+	public void restart_editor()
+	{
+		if (!_preview)
+			return;
 
-			if (_console_client != null)
-			{
-				_console_client.send_script("Device.quit()");
-				_console_client.close();
-			}
+		stop_editor();
 
-			if (_editor_process != null)
-			{
-				try
-				{
-					_editor_process.wait();
-				}
-				catch (Error e)
-				{
-					stderr.printf("Error: %s\n", e.message);
-				}
-			}
+		if (_editor_view != null)
+		{
+			_editor_view = null;
 		}
 
-		public void restart_editor()
-		{
-			if (!_preview)
-				return;
+		_editor_view = new EditorView(_console_client, false);
+		_editor_view.set_size_request(300, 300);
+		_editor_view.realized.connect(on_editor_view_realized);
 
-			stop_editor();
+		_editor_slide.show_widget(_editor_view);
+	}
 
-			if (_editor_view != null)
-			{
-				_editor_view = null;
-			}
+	private void on_editor_view_realized()
+	{
+		start_editor(((EditorView)_editor_view).window_id);
+	}
 
-			_editor_view = new EditorView(_console_client, false);
-			_editor_view.set_size_request(300, 300);
-			_editor_view.realized.connect(on_editor_view_realized);
+	private void on_filter_entry_text_changed()
+	{
+		_tree_selection.changed.disconnect(on_tree_selection_changed);
+		_tree_filter.refilter();
+		_tree_selection.changed.connect(on_tree_selection_changed);
+		_tree_view.set_cursor(new Gtk.TreePath.first(), null, false);
+	}
 
-			_editor_slide.show_widget(_editor_view);
-		}
+	private bool on_filter_entry_key_pressed(Gdk.EventKey ev)
+	{
+		Gtk.TreeModel model;
+		Gtk.TreeIter iter;
+		bool selected = _tree_selection.get_selected(out model, out iter);
 
-		private void on_editor_view_realized()
+		if (ev.keyval == Gdk.Key.Down)
 		{
-			start_editor(((EditorView)_editor_view).window_id);
-		}
+			if (selected && model.iter_next(ref iter))
+			{
+				_tree_selection.select_iter(iter);
+				_tree_view.scroll_to_cell(model.get_path(iter), null, true, 1.0f, 0.0f);
+			}
 
-		private void on_filter_entry_text_changed()
-		{
-			_tree_selection.changed.disconnect(on_tree_selection_changed);
-			_tree_filter.refilter();
-			_tree_selection.changed.connect(on_tree_selection_changed);
-			_tree_view.set_cursor(new Gtk.TreePath.first(), null, false);
+			return Gdk.EVENT_STOP;
 		}
-
-		private bool on_filter_entry_key_pressed(Gdk.EventKey ev)
+		else if (ev.keyval == Gdk.Key.Up)
 		{
-			Gtk.TreeModel model;
-			Gtk.TreeIter iter;
-			bool selected = _tree_selection.get_selected(out model, out iter);
-
-			if (ev.keyval == Gdk.Key.Down)
+			if (selected && model.iter_previous(ref iter))
 			{
-				if (selected && model.iter_next(ref iter))
-				{
-					_tree_selection.select_iter(iter);
-					_tree_view.scroll_to_cell(model.get_path(iter), null, true, 1.0f, 0.0f);
-				}
-
-				return Gdk.EVENT_STOP;
+				_tree_selection.select_iter(iter);
+				_tree_view.scroll_to_cell(model.get_path(iter), null, true, 1.0f, 0.0f);
 			}
-			else if (ev.keyval == Gdk.Key.Up)
-			{
-				if (selected && model.iter_previous(ref iter))
-				{
-					_tree_selection.select_iter(iter);
-					_tree_view.scroll_to_cell(model.get_path(iter), null, true, 1.0f, 0.0f);
-				}
 
-				return Gdk.EVENT_STOP;
-			}
-			else if (ev.keyval == 65293) // Enter
+			return Gdk.EVENT_STOP;
+		}
+		else if (ev.keyval == 65293) // Enter
+		{
+			if (selected)
 			{
-				if (selected)
-				{
-					Value name;
-					Value type;
-					model.get_value(iter, ProjectStore.Column.NAME, out name);
-					model.get_value(iter, ProjectStore.Column.TYPE, out type);
-					_name = (string)name;
-					resource_selected((string)type, (string)name);
-				}
-
-				return Gdk.EVENT_STOP;
+				Value name;
+				Value type;
+				model.get_value(iter, ProjectStore.Column.NAME, out name);
+				model.get_value(iter, ProjectStore.Column.TYPE, out type);
+				_name = (string)name;
+				resource_selected((string)type, (string)name);
 			}
 
-			return Gdk.EVENT_PROPAGATE;
+			return Gdk.EVENT_STOP;
 		}
 
-		private void on_tree_selection_changed()
+		return Gdk.EVENT_PROPAGATE;
+	}
+
+	private void on_tree_selection_changed()
+	{
+		if (_preview)
 		{
-			if (_preview)
+			Gtk.TreeModel model;
+			Gtk.TreeIter iter;
+			if (_tree_selection.get_selected(out model, out iter))
 			{
-				Gtk.TreeModel model;
-				Gtk.TreeIter iter;
-				if (_tree_selection.get_selected(out model, out iter))
-				{
-					Value name;
-					Value type;
-					model.get_value(iter, ProjectStore.Column.NAME, out name);
-					model.get_value(iter, ProjectStore.Column.TYPE, out type);
-					_console_client.send_script(UnitPreviewApi.set_preview_resource((string)type, (string)name));
-				}
+				Value name;
+				Value type;
+				model.get_value(iter, ProjectStore.Column.NAME, out name);
+				model.get_value(iter, ProjectStore.Column.TYPE, out type);
+				_console_client.send_script(UnitPreviewApi.set_preview_resource((string)type, (string)name));
 			}
 		}
+	}
 
-		public void set_type_filter(owned UserFilter filter)
-		{
-			_user_filter = filter;
-			_tree_filter.refilter();
-		}
+	public void set_type_filter(owned UserFilter filter)
+	{
+		_user_filter = filter;
+		_tree_filter.refilter();
 	}
 }
+
+}

+ 38 - 37
tools/level_editor/statusbar.vala

@@ -7,53 +7,54 @@ using Gtk;
 
 namespace Crown
 {
-	public class Statusbar : Gtk.Box
+public class Statusbar : Gtk.Box
+{
+	// Data
+	public uint _timer_id;
+
+	// Widgets
+	public Gtk.Label _status;
+	public Gtk.Label _temporary_message;
+
+	public Statusbar()
 	{
+		Object(orientation: Gtk.Orientation.HORIZONTAL, spacing: 0);
+
 		// Data
-		public uint _timer_id;
+		_timer_id = 0;
 
 		// Widgets
-		public Gtk.Label _status;
-		public Gtk.Label _temporary_message;
+		_status = new Gtk.Label("Done");
+		_temporary_message = new Gtk.Label("");
 
-		public Statusbar()
-		{
-			Object(orientation: Gtk.Orientation.HORIZONTAL, spacing: 0);
-
-			// Data
-			_timer_id = 0;
+		this.pack_start(_status, false, false, 0);
+		this.pack_start(_temporary_message, false, false, 0);
+		this.get_style_context().add_class("statusbar");
+	}
 
-			// Widgets
-			_status = new Gtk.Label("Done");
-			_temporary_message = new Gtk.Label("");
+	~Statusbar()
+	{
+		if (_timer_id > 0)
+			GLib.Source.remove(_timer_id);
+	}
 
-			this.pack_start(_status, false, false, 0);
-			this.pack_start(_temporary_message, false, false, 0);
-			this.get_style_context().add_class("statusbar");
-		}
+	/// Shows a message on the statusbar and removes it after 4 seconds.
+	public void set_temporary_message(string message)
+	{
+		_temporary_message.set_label("; " + message);
 
-		~Statusbar()
+		if (_timer_id > 0)
 		{
-			if (_timer_id > 0)
-				GLib.Source.remove(_timer_id);
+			GLib.Source.remove(_timer_id);
+			_timer_id = 0;
 		}
 
-		/// Shows a message on the statusbar and removes it after 4 seconds.
-		public void set_temporary_message(string message)
-		{
-			_temporary_message.set_label("; " + message);
-
-			if (_timer_id > 0)
-			{
-				GLib.Source.remove(_timer_id);
-				_timer_id = 0;
-			}
-
-			_timer_id = GLib.Timeout.add_seconds(4, () => {
-				_temporary_message.set_label("");
-				_timer_id = 0;
-				return false; // Remove the source
-			});
-		}
+		_timer_id = GLib.Timeout.add_seconds(4, () => {
+			_temporary_message.set_label("");
+			_timer_id = 0;
+			return false; // Remove the source
+		});
 	}
 }
+
+}

+ 111 - 110
tools/level_editor/user.vala

@@ -7,148 +7,149 @@ using Gee;
 
 namespace Crown
 {
-	public class User
-	{
-		// Data
-		public Hashtable _data;
+public class User
+{
+	// Data
+	public Hashtable _data;
 
-		// Signals
-		public signal void recent_project_added(string source_dir, string name, string time);
-		public signal void recent_project_removed(string source_dir);
+	// Signals
+	public signal void recent_project_added(string source_dir, string name, string time);
+	public signal void recent_project_removed(string source_dir);
 
-		public User()
-		{
-		}
+	public User()
+	{
+	}
 
-		public void decode(Hashtable sjson)
-		{
-			_data = sjson;
+	public void decode(Hashtable sjson)
+	{
+		_data = sjson;
 
-			_data.foreach((ee) => {
-				if (ee.key == "recent_projects")
+		_data.foreach((ee) => {
+			if (ee.key == "recent_projects")
+			{
+				var recent_projects = ee.value as ArrayList<Value?>;
+				for (int ii = 0; ii < recent_projects.size; ++ii)
 				{
-					var recent_projects = ee.value as ArrayList<Value?>;
-					for (int ii = 0; ii < recent_projects.size; ++ii)
-					{
-						Hashtable rp = recent_projects[ii] as Hashtable;
+					Hashtable rp = recent_projects[ii] as Hashtable;
 
-						recent_project_added((string)rp["source_dir"]
-							, (string)rp["name"]
-							, (string)rp["mtime"]
-							);
-					}
-				}
-				else
-				{
-					logw("Unknown key: `%s`".printf(ee.key));
+					recent_project_added((string)rp["source_dir"]
+						, (string)rp["name"]
+						, (string)rp["mtime"]
+						);
 				}
+			}
+			else
+			{
+				logw("Unknown key: `%s`".printf(ee.key));
+			}
 
-				return true;
-			});
-		}
+			return true;
+		});
+	}
 
-		public Hashtable encode()
-		{
-			return _data;
-		}
+	public Hashtable encode()
+	{
+		return _data;
+	}
 
-		public void load(string path)
-		{
-			Hashtable sjson = SJSON.load(path);
-			decode(sjson);
-		}
+	public void load(string path)
+	{
+		Hashtable sjson = SJSON.load(path);
+		decode(sjson);
+	}
 
-		public void save(string path)
-		{
-			SJSON.save(encode(), path);
-		}
+	public void save(string path)
+	{
+		SJSON.save(encode(), path);
+	}
 
-		public void add_recent_project(string source_dir, string name)
-		{
-			Gee.ArrayList<Value?> recent_projects = null;
-			bool duplicate = false;
+	public void add_recent_project(string source_dir, string name)
+	{
+		Gee.ArrayList<Value?> recent_projects = null;
+		bool duplicate = false;
 
-			_data.foreach ((ee) => {
-				if (ee.key == "recent_projects")
+		_data.foreach ((ee) => {
+			if (ee.key == "recent_projects")
+			{
+				recent_projects = ee.value as ArrayList<Value?>;
+				for (int ii = 0; ii < recent_projects.size; ++ii)
 				{
-					recent_projects = ee.value as ArrayList<Value?>;
-					for (int ii = 0; ii < recent_projects.size; ++ii)
-					{
-						Hashtable rp = recent_projects[ii] as Hashtable;
+					Hashtable rp = recent_projects[ii] as Hashtable;
 
-						if ((string)rp["source_dir"] == source_dir)
-						{
-							duplicate = true;
-							return false; // break
-						}
+					if ((string)rp["source_dir"] == source_dir)
+					{
+						duplicate = true;
+						return false; // break
 					}
 				}
-
-				return true;
-			});
-
-			if (recent_projects == null)
-			{
-				recent_projects = new ArrayList<Value?>();
-				_data["recent_projects"] = recent_projects;
 			}
 
-			if (!duplicate)
-			{
-				string mtime = new GLib.DateTime.now_utc().to_unix().to_string();
-				var new_rp = new Hashtable();
-				new_rp["name"]       = source_dir; // FIXME: store project name somewhere inside project directory
-				new_rp["source_dir"] = source_dir;
-				new_rp["mtime"]      = mtime;
-				recent_projects.add(new_rp);
-
-				recent_project_added(source_dir, source_dir, mtime);
-			}
+			return true;
+		});
+
+		if (recent_projects == null)
+		{
+			recent_projects = new ArrayList<Value?>();
+			_data["recent_projects"] = recent_projects;
 		}
 
-		public void remove_recent_project(string source_dir)
+		if (!duplicate)
 		{
-			_data.foreach((ee) => {
-				if (ee.key == "recent_projects")
+			string mtime = new GLib.DateTime.now_utc().to_unix().to_string();
+			var new_rp = new Hashtable();
+			new_rp["name"]       = source_dir; // FIXME: store project name somewhere inside project directory
+			new_rp["source_dir"] = source_dir;
+			new_rp["mtime"]      = mtime;
+			recent_projects.add(new_rp);
+
+			recent_project_added(source_dir, source_dir, mtime);
+		}
+	}
+
+	public void remove_recent_project(string source_dir)
+	{
+		_data.foreach((ee) => {
+			if (ee.key == "recent_projects")
+			{
+				var recent_projects = ee.value as ArrayList<Value?>;
+				var it = recent_projects.iterator();
+				for (var has_next = it.next(); has_next; has_next = it.next())
 				{
-					var recent_projects = ee.value as ArrayList<Value?>;
-					var it = recent_projects.iterator();
-					for (var has_next = it.next(); has_next; has_next = it.next())
+					Hashtable rp = it.get() as Hashtable;
+					if ((string)rp["source_dir"] == source_dir)
 					{
-						Hashtable rp = it.get() as Hashtable;
-						if ((string)rp["source_dir"] == source_dir)
-						{
-							it.remove();
-							recent_project_removed(source_dir);
-							return false; // break
-						}
+						it.remove();
+						recent_project_removed(source_dir);
+						return false; // break
 					}
 				}
+			}
 
-				return true;
-			});
-		}
+			return true;
+		});
+	}
 
-		public void touch_recent_project(string source_dir, string time)
-		{
-			_data.foreach((ee) => {
-				if (ee.key == "recent_projects")
+	public void touch_recent_project(string source_dir, string time)
+	{
+		_data.foreach((ee) => {
+			if (ee.key == "recent_projects")
+			{
+				var recent_projects = ee.value as ArrayList<Value?>;
+				var it = recent_projects.iterator();
+				for (var has_next = it.next(); has_next; has_next = it.next())
 				{
-					var recent_projects = ee.value as ArrayList<Value?>;
-					var it = recent_projects.iterator();
-					for (var has_next = it.next(); has_next; has_next = it.next())
+					Hashtable rp = it.get() as Hashtable;
+					if ((string)rp["source_dir"] == source_dir)
 					{
-						Hashtable rp = it.get() as Hashtable;
-						if ((string)rp["source_dir"] == source_dir)
-						{
-							rp["mtime"] = time;
-							return false; // break
-						}
+						rp["mtime"] = time;
+						return false; // break
 					}
 				}
+			}
 
-				return true;
-			});
-		}
+			return true;
+		});
 	}
 }
+
+}

+ 29 - 28
tools/widgets/check_box.vala

@@ -7,40 +7,41 @@ using Gtk;
 
 namespace Crown
 {
-	public class CheckBox : Gtk.CheckButton
-	{
-		// Data
-		public bool _stop_emit;
+public class CheckBox : Gtk.CheckButton
+{
+	// Data
+	public bool _stop_emit;
 
-		public bool value
+	public bool value
+	{
+		get
 		{
-			get
-			{
-				return this.active;
-			}
-			set
-			{
-				_stop_emit = true;
-				this.active = value;
-				_stop_emit = false;
-			}
+			return this.active;
 		}
-
-		// Signals
-		public signal void value_changed();
-
-		public CheckBox()
+		set
 		{
-			// Data
+			_stop_emit = true;
+			this.active = value;
 			_stop_emit = false;
-
-			this.toggled.connect(on_value_changed);
 		}
+	}
 
-		private void on_value_changed()
-		{
-			if (!_stop_emit)
-				value_changed();
-		}
+	// Signals
+	public signal void value_changed();
+
+	public CheckBox()
+	{
+		// Data
+		_stop_emit = false;
+
+		this.toggled.connect(on_value_changed);
 	}
+
+	private void on_value_changed()
+	{
+		if (!_stop_emit)
+			value_changed();
+	}
+}
+
 }

+ 20 - 19
tools/widgets/clamp.vala

@@ -7,29 +7,30 @@ using Gtk;
 
 namespace Crown
 {
-	public class Clamp : Gtk.Bin
+public class Clamp : Gtk.Bin
+{
+	// Drop-in replacement (sort-of) for HdyClamp from libhandy1.
+	public Clamp()
 	{
-		// Drop-in replacement (sort-of) for HdyClamp from libhandy1.
-		public Clamp()
-		{
-		}
+	}
 
-		protected override void size_allocate(Gtk.Allocation alloc)
-		{
-			Gtk.Widget? child = this.get_child();
-			if (child == null || !child.is_visible())
-				return;
+	protected override void size_allocate(Gtk.Allocation alloc)
+	{
+		Gtk.Widget? child = this.get_child();
+		if (child == null || !child.is_visible())
+			return;
 
-			int child_min_width;
-			child.get_preferred_width(out child_min_width, null);
+		int child_min_width;
+		child.get_preferred_width(out child_min_width, null);
 
-			Gtk.Allocation child_alloc = {};
-			child_alloc.width = 600;
-			child_alloc.height = alloc.height;
-			child_alloc.x = alloc.x + (alloc.width - child_alloc.width) / 2;
-			child_alloc.y = alloc.y;
+		Gtk.Allocation child_alloc = {};
+		child_alloc.width = 600;
+		child_alloc.height = alloc.height;
+		child_alloc.x = alloc.x + (alloc.width - child_alloc.width) / 2;
+		child_alloc.y = alloc.y;
 
-			child.size_allocate_with_baseline(child_alloc, this.get_allocated_baseline());
-		}
+		child.size_allocate_with_baseline(child_alloc, this.get_allocated_baseline());
 	}
 }
+
+}

+ 36 - 35
tools/widgets/color_button_vector3.vala

@@ -7,48 +7,49 @@ using Gtk;
 
 namespace Crown
 {
-	public class ColorButtonVector3 : Gtk.ColorButton
-	{
-		// Data
-		public bool _stop_emit;
+public class ColorButtonVector3 : Gtk.ColorButton
+{
+	// Data
+	public bool _stop_emit;
 
-		// Signals
-		public signal void value_changed();
+	// Signals
+	public signal void value_changed();
 
-		public Vector3 value
+	public Vector3 value
+	{
+		get
 		{
-			get
-			{
-				Gdk.RGBA rgba = this.get_rgba();
-				double r = rgba.red;
-				double g = rgba.green;
-				double b = rgba.blue;
-				return Vector3(r, g, b);
-			}
-			set
-			{
-				_stop_emit = true;
-				Vector3 val = (Vector3)value;
-				double r = val.x;
-				double g = val.y;
-				double b = val.z;
-				double a = 1.0;
-				this.set_rgba({ r, g, b, a });
-				_stop_emit = false;
-			}
+			Gdk.RGBA rgba = this.get_rgba();
+			double r = rgba.red;
+			double g = rgba.green;
+			double b = rgba.blue;
+			return Vector3(r, g, b);
 		}
-
-		public ColorButtonVector3()
+		set
 		{
+			_stop_emit = true;
+			Vector3 val = (Vector3)value;
+			double r = val.x;
+			double g = val.y;
+			double b = val.z;
+			double a = 1.0;
+			this.set_rgba({ r, g, b, a });
 			_stop_emit = false;
-
-			this.color_set.connect(on_color_set);
 		}
+	}
 
-		private void on_color_set()
-		{
-			if (!_stop_emit)
-				value_changed();
-		}
+	public ColorButtonVector3()
+	{
+		_stop_emit = false;
+
+		this.color_set.connect(on_color_set);
 	}
+
+	private void on_color_set()
+	{
+		if (!_stop_emit)
+			value_changed();
+	}
+}
+
 }

+ 35 - 34
tools/widgets/combo_box_map.vala

@@ -7,48 +7,49 @@ using Gtk;
 
 namespace Crown
 {
-	public class ComboBoxMap : Gtk.ComboBoxText
-	{
-		// Data
-		public bool _stop_emit;
+public class ComboBoxMap : Gtk.ComboBoxText
+{
+	// Data
+	public bool _stop_emit;
 
-		// Signals
-		public signal void value_changed();
+	// Signals
+	public signal void value_changed();
 
-		public string value
+	public string value
+	{
+		get
 		{
-			get
-			{
-				return this.get_active_id();
-			}
-			set
-			{
-				_stop_emit = true;
-				this.set_active_id((string)value);
-				_stop_emit = false;
-			}
+			return this.get_active_id();
 		}
-
-		public ComboBoxMap()
+		set
 		{
-			// Data
 			_stop_emit = true;
-
-			// Widgets
-			this.changed.connect(on_changed);
-			this.scroll_event.connect(on_scroll);
+			this.set_active_id((string)value);
+			_stop_emit = false;
 		}
+	}
 
-		private void on_changed()
-		{
-			if (!_stop_emit)
-				value_changed();
-		}
+	public ComboBoxMap()
+	{
+		// Data
+		_stop_emit = true;
 
-		private bool on_scroll(Gdk.EventScroll ev)
-		{
-			GLib.Signal.stop_emission_by_name(this, "scroll-event");
-			return Gdk.EVENT_PROPAGATE;
-		}
+		// Widgets
+		this.changed.connect(on_changed);
+		this.scroll_event.connect(on_scroll);
+	}
+
+	private void on_changed()
+	{
+		if (!_stop_emit)
+			value_changed();
 	}
+
+	private bool on_scroll(Gdk.EventScroll ev)
+	{
+		GLib.Signal.stop_emission_by_name(this, "scroll-event");
+		return Gdk.EVENT_PROPAGATE;
+	}
+}
+
 }

+ 155 - 154
tools/widgets/console_view.vala

@@ -7,203 +7,204 @@ using Gtk;
 
 namespace Crown
 {
-	public class EntryHistory
-	{
-		public uint _capacity;
-		public uint _size;
-		public uint _index;
-		public string[] _data;
+public class EntryHistory
+{
+	public uint _capacity;
+	public uint _size;
+	public uint _index;
+	public string[] _data;
 
-		// Creates a new history with room for capacity records.
-		public EntryHistory(uint capacity)
-		{
-			_capacity = capacity;
-			_size = 0;
-			_index = 0;
-			_data = new string[capacity];
-		}
+	// Creates a new history with room for capacity records.
+	public EntryHistory(uint capacity)
+	{
+		_capacity = capacity;
+		_size = 0;
+		_index = 0;
+		_data = new string[capacity];
+	}
 
-		// Push a new string into the history.
-		public void push(string text)
-		{
-			// Add command to history
-			_data[_index] = text;
-			_index = (_index + 1) % _capacity;
+	// Push a new string into the history.
+	public void push(string text)
+	{
+		// Add command to history
+		_data[_index] = text;
+		_index = (_index + 1) % _capacity;
 
-			if (_size < _capacity)
-				++_size;
-		}
+		if (_size < _capacity)
+			++_size;
+	}
 
-		public void clear()
-		{
-			_size = 0;
-			_index = 0;
-		}
+	public void clear()
+	{
+		_size = 0;
+		_index = 0;
+	}
 
-		// Returns the element at @a distance slots from the current index.
-		// Distance must be in the [1; _size] range.
-		public string element(uint distance)
-		{
-			if (distance < 1 || distance > _size)
-				return "ERROR";
+	// Returns the element at @a distance slots from the current index.
+	// Distance must be in the [1; _size] range.
+	public string element(uint distance)
+	{
+		if (distance < 1 || distance > _size)
+			return "ERROR";
 
-			if (_index >= distance)
-				return _data[_index - distance];
-			else
-				return _data[_capacity - (distance - _index)];
-		}
+		if (_index >= distance)
+			return _data[_index - distance];
+		else
+			return _data[_capacity - (distance - _index)];
 	}
+}
 
-	public class ConsoleView : Gtk.Box
+public class ConsoleView : Gtk.Box
+{
+	// Data
+	public EntryHistory _entry_history;
+	public uint _distance;
+	public Project _project;
+
+	// Widgets
+	public Gtk.ScrolledWindow _scrolled_window;
+	public Gtk.TextView _text_view;
+	public Gtk.Entry _entry;
+	public Gtk.Box _entry_hbox;
+
+	public ConsoleView(Project project, Gtk.ComboBoxText combo)
 	{
+		Object(orientation: Gtk.Orientation.VERTICAL, spacing: 0);
+
 		// Data
-		public EntryHistory _entry_history;
-		public uint _distance;
-		public Project _project;
+		_entry_history = new EntryHistory(256);
+		_distance = 0;
+		_project = project;
 
 		// Widgets
-		public Gtk.ScrolledWindow _scrolled_window;
-		public Gtk.TextView _text_view;
-		public Gtk.Entry _entry;
-		public Gtk.Box _entry_hbox;
-
-		public ConsoleView(Project project, Gtk.ComboBoxText combo)
-		{
-			Object(orientation: Gtk.Orientation.VERTICAL, spacing: 0);
-
-			// Data
-			_entry_history = new EntryHistory(256);
-			_distance = 0;
-			_project = project;
+		_text_view = new Gtk.TextView();
+		_text_view.editable = false;
+		_text_view.can_focus = false;
 
-			// Widgets
-			_text_view = new Gtk.TextView();
-			_text_view.editable = false;
-			_text_view.can_focus = false;
+		// // Create tags for color-formatted text
+		Gtk.TextTag tag_info = new Gtk.TextTag("info");
+		tag_info.foreground_rgba = { 0.7, 0.7, 0.7, 1.0 };
+		Gtk.TextTag tag_warning = new Gtk.TextTag("warning");
+		tag_warning.foreground_rgba = { 1.0, 1.0, 0.4, 1.0 };
+		Gtk.TextTag tag_error = new Gtk.TextTag("error");
+		tag_error.foreground_rgba = { 1.0, 0.4, 0.4, 1.0 };
 
-			// // Create tags for color-formatted text
-			Gtk.TextTag tag_info = new Gtk.TextTag("info");
-			tag_info.foreground_rgba = { 0.7, 0.7, 0.7, 1.0 };
-			Gtk.TextTag tag_warning = new Gtk.TextTag("warning");
-			tag_warning.foreground_rgba = { 1.0, 1.0, 0.4, 1.0 };
-			Gtk.TextTag tag_error = new Gtk.TextTag("error");
-			tag_error.foreground_rgba = { 1.0, 0.4, 0.4, 1.0 };
+		Gtk.TextBuffer tb = _text_view.buffer;
+		tb.tag_table.add(tag_info);
+		tb.tag_table.add(tag_warning);
+		tb.tag_table.add(tag_error);
 
-			Gtk.TextBuffer tb = _text_view.buffer;
-			tb.tag_table.add(tag_info);
-			tb.tag_table.add(tag_warning);
-			tb.tag_table.add(tag_error);
+		_scrolled_window = new Gtk.ScrolledWindow(null, null);
+		_scrolled_window.add(_text_view);
 
-			_scrolled_window = new Gtk.ScrolledWindow(null, null);
-			_scrolled_window.add(_text_view);
+		_entry = new Gtk.Entry();
+		_entry.key_press_event.connect(on_entry_key_pressed);
+		_entry.activate.connect(on_entry_activated);
 
-			_entry = new Gtk.Entry();
-			_entry.key_press_event.connect(on_entry_key_pressed);
-			_entry.activate.connect(on_entry_activated);
+		_entry_hbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
+		_entry_hbox.pack_start(_entry, true, true);
+		_entry_hbox.pack_end(combo, false, false);
 
-			_entry_hbox = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
-			_entry_hbox.pack_start(_entry, true, true);
-			_entry_hbox.pack_end(combo, false, false);
+		this.pack_start(_scrolled_window, true, true, 0);
+		this.pack_start(_entry_hbox, false, true, 0);
 
-			this.pack_start(_scrolled_window, true, true, 0);
-			this.pack_start(_entry_hbox, false, true, 0);
+		this.show.connect(on_show);
 
-			this.show.connect(on_show);
+		this.get_style_context().add_class("console-view");
+	}
 
-			this.get_style_context().add_class("console-view");
-		}
+	private void on_entry_activated()
+	{
+		string text = _entry.text;
+		text = text.strip();
 
-		private void on_entry_activated()
+		if (text.length > 0)
 		{
-			string text = _entry.text;
-			text = text.strip();
-
-			if (text.length > 0)
-			{
-				_entry_history.push(text);
-				_distance = 0;
+			_entry_history.push(text);
+			_distance = 0;
 
-				Gtk.Application app = ((Gtk.Window)this.get_toplevel()).application;
-				ConsoleClient? client = ((LevelEditorApplication)app).current_selected_client();
+			Gtk.Application app = ((Gtk.Window)this.get_toplevel()).application;
+			ConsoleClient? client = ((LevelEditorApplication)app).current_selected_client();
 
-				if (text[0] == ':')
-				{
-					string[] args = text[1:text.length].split(" ");
-					if (args.length > 0)
-					{
-						if (client != null)
-							client.send(DeviceApi.command(args));
-					}
-				}
-				else
+			if (text[0] == ':')
+			{
+				string[] args = text[1:text.length].split(" ");
+				if (args.length > 0)
 				{
 					if (client != null)
-						client.send_script(text);
+						client.send(DeviceApi.command(args));
 				}
 			}
-
-			_entry.text = "";
+			else
+			{
+				if (client != null)
+					client.send_script(text);
+			}
 		}
 
-		private bool on_entry_key_pressed(Gdk.EventKey ev)
+		_entry.text = "";
+	}
+
+	private bool on_entry_key_pressed(Gdk.EventKey ev)
+	{
+		if (ev.keyval == Gdk.Key.Down)
 		{
-			if (ev.keyval == Gdk.Key.Down)
+			if (_distance > 1)
 			{
-				if (_distance > 1)
-				{
-					--_distance;
-					_entry.text = _entry_history.element(_distance);
-				}
-				else
-				{
-					_entry.text = "";
-				}
-
-				_entry.set_position(_entry.text.length);
-				return Gdk.EVENT_STOP;
+				--_distance;
+				_entry.text = _entry_history.element(_distance);
 			}
-			else if (ev.keyval == Gdk.Key.Up)
+			else
 			{
-				if (_distance < _entry_history._size)
-				{
-					++_distance;
-					_entry.text = _entry_history.element(_distance);
-				}
-
-				_entry.set_position(_entry.text.length);
-				return Gdk.EVENT_STOP;
+				_entry.text = "";
 			}
 
-			return Gdk.EVENT_PROPAGATE;
+			_entry.set_position(_entry.text.length);
+			return Gdk.EVENT_STOP;
 		}
-
-		private void on_show()
+		else if (ev.keyval == Gdk.Key.Up)
 		{
-			_entry.grab_focus_without_selecting();
+			if (_distance < _entry_history._size)
+			{
+				++_distance;
+				_entry.text = _entry_history.element(_distance);
+			}
+
+			_entry.set_position(_entry.text.length);
+			return Gdk.EVENT_STOP;
 		}
 
-		public void log(string severity, string message)
-		{
-			string line = message;
+		return Gdk.EVENT_PROPAGATE;
+	}
 
-			// Replace IDs with human-readable names
-			int id_index = message.index_of("#ID(");
-			if (id_index != -1)
-			{
-				string id = message.substring(id_index + 4, 16);
-				string name = _project.id_to_name(id);
-				line = message.replace("#ID(%s)".printf(id), "'%s'".printf(name));
-			}
+	private void on_show()
+	{
+		_entry.grab_focus_without_selecting();
+	}
+
+	public void log(string severity, string message)
+	{
+		string line = message;
 
-			Gtk.TextBuffer buffer = _text_view.buffer;
-			Gtk.TextIter end_iter;
-			buffer.get_end_iter(out end_iter);
-			buffer.insert(ref end_iter, line, line.length);
-			end_iter.backward_chars(line.length);
-			Gtk.TextIter start_iter = end_iter;
-			buffer.get_end_iter(out end_iter);
-			buffer.apply_tag(buffer.tag_table.lookup(severity), start_iter, end_iter);
-			_text_view.scroll_to_mark(buffer.create_mark("bottom", end_iter, false), 0, true, 0.0, 1.0);
+		// Replace IDs with human-readable names
+		int id_index = message.index_of("#ID(");
+		if (id_index != -1)
+		{
+			string id = message.substring(id_index + 4, 16);
+			string name = _project.id_to_name(id);
+			line = message.replace("#ID(%s)".printf(id), "'%s'".printf(name));
 		}
+
+		Gtk.TextBuffer buffer = _text_view.buffer;
+		Gtk.TextIter end_iter;
+		buffer.get_end_iter(out end_iter);
+		buffer.insert(ref end_iter, line, line.length);
+		end_iter.backward_chars(line.length);
+		Gtk.TextIter start_iter = end_iter;
+		buffer.get_end_iter(out end_iter);
+		buffer.apply_tag(buffer.tag_table.lookup(severity), start_iter, end_iter);
+		_text_view.scroll_to_mark(buffer.create_mark("bottom", end_iter, false), 0, true, 0.0, 1.0);
 	}
 }
+
+}

+ 89 - 88
tools/widgets/entry_double.vala

@@ -8,114 +8,115 @@ using Gdk;
 
 namespace Crown
 {
-	public class EntryDouble : Gtk.Entry
+public class EntryDouble : Gtk.Entry
+{
+	public double _min;
+	public double _max;
+	public double _value;
+	public bool _stop_emit;
+	public string _preview_fmt;
+	public string _edit_fmt;
+
+	public double value
 	{
-		public double _min;
-		public double _max;
-		public double _value;
-		public bool _stop_emit;
-		public string _preview_fmt;
-		public string _edit_fmt;
-
-		public double value
+		get
 		{
-			get
-			{
-				return _value;
-			}
-			set
-			{
-				_stop_emit = true;
-				set_value_safe(value);
-				_stop_emit = false;
-			}
+			return _value;
 		}
-
-		// Signals
-		public signal void value_changed();
-
-		public EntryDouble(double val, double min, double max, string preview_fmt = "%.6g", string edit_fmt = "%.17g")
+		set
 		{
-			this.input_purpose = Gtk.InputPurpose.DIGITS;
-			this.set_width_chars(1);
-
-			this.scroll_event.connect(on_scroll);
-			this.button_release_event.connect(on_button_release);
-			this.activate.connect(on_activate);
-			this.focus_in_event.connect(on_focus_in);
-			this.focus_out_event.connect(on_focus_out);
-
-			this._min = min;
-			this._max = max;
-			this._preview_fmt = preview_fmt;
-			this._edit_fmt = edit_fmt;
-
 			_stop_emit = true;
-			set_value_safe(val);
+			set_value_safe(value);
 			_stop_emit = false;
 		}
+	}
 
-		private bool on_scroll(Gdk.EventScroll ev)
-		{
-			GLib.Signal.stop_emission_by_name(this, "scroll-event");
-			return Gdk.EVENT_PROPAGATE;
-		}
+	// Signals
+	public signal void value_changed();
 
-		private bool on_button_release(Gdk.EventButton ev)
-		{
-			if (ev.button == Gdk.BUTTON_PRIMARY && this.has_focus)
-			{
-				this.text = _edit_fmt.printf(_value);
-				this.set_position(-1);
-				this.select_region(0, -1);
-				return Gdk.EVENT_STOP;
-			}
-
-			return Gdk.EVENT_PROPAGATE;
-		}
+	public EntryDouble(double val, double min, double max, string preview_fmt = "%.6g", string edit_fmt = "%.17g")
+	{
+		this.input_purpose = Gtk.InputPurpose.DIGITS;
+		this.set_width_chars(1);
+
+		this.scroll_event.connect(on_scroll);
+		this.button_release_event.connect(on_button_release);
+		this.activate.connect(on_activate);
+		this.focus_in_event.connect(on_focus_in);
+		this.focus_out_event.connect(on_focus_out);
+
+		this._min = min;
+		this._max = max;
+		this._preview_fmt = preview_fmt;
+		this._edit_fmt = edit_fmt;
+
+		_stop_emit = true;
+		set_value_safe(val);
+		_stop_emit = false;
+	}
 
-		private void on_activate()
-		{
-			this.select_region(0, 0);
-			this.set_position(-1);
-			set_value_safe(string_to_double(this.text, _value));
-		}
+	private bool on_scroll(Gdk.EventScroll ev)
+	{
+		GLib.Signal.stop_emission_by_name(this, "scroll-event");
+		return Gdk.EVENT_PROPAGATE;
+	}
 
-		private bool on_focus_in(Gdk.EventFocus ev)
+	private bool on_button_release(Gdk.EventButton ev)
+	{
+		if (ev.button == Gdk.BUTTON_PRIMARY && this.has_focus)
 		{
 			this.text = _edit_fmt.printf(_value);
 			this.set_position(-1);
 			this.select_region(0, -1);
-			return Gdk.EVENT_PROPAGATE;
+			return Gdk.EVENT_STOP;
 		}
 
-		private bool on_focus_out(Gdk.EventFocus ef)
-		{
-			set_value_safe(string_to_double(this.text, _value));
-			return Gdk.EVENT_PROPAGATE;
-		}
+		return Gdk.EVENT_PROPAGATE;
+	}
 
-		private void set_value_safe(double val)
-		{
-			double clamped = val.clamp(_min, _max);
-
-			// Convert to text for displaying
-			this.text = _preview_fmt.printf(clamped);
-
-			// Notify value changed
-			if (_value != clamped)
-			{
-				_value = clamped;
-				if (!_stop_emit)
-					value_changed();
-			}
-		}
+	private void on_activate()
+	{
+		this.select_region(0, 0);
+		this.set_position(-1);
+		set_value_safe(string_to_double(this.text, _value));
+	}
+
+	private bool on_focus_in(Gdk.EventFocus ev)
+	{
+		this.text = _edit_fmt.printf(_value);
+		this.set_position(-1);
+		this.select_region(0, -1);
+		return Gdk.EVENT_PROPAGATE;
+	}
+
+	private bool on_focus_out(Gdk.EventFocus ef)
+	{
+		set_value_safe(string_to_double(this.text, _value));
+		return Gdk.EVENT_PROPAGATE;
+	}
+
+	private void set_value_safe(double val)
+	{
+		double clamped = val.clamp(_min, _max);
+
+		// Convert to text for displaying
+		this.text = _preview_fmt.printf(clamped);
 
-		/// Returns @a str as double or @a deffault if conversion fails.
-		private double string_to_double(string str, double deffault)
+		// Notify value changed
+		if (_value != clamped)
 		{
-			double result;
-			return double.try_parse(str, out result) ? result : deffault;
+			_value = clamped;
+			if (!_stop_emit)
+				value_changed();
 		}
 	}
+
+	/// Returns @a str as double or @a deffault if conversion fails.
+	private double string_to_double(string str, double deffault)
+	{
+		double result;
+		return double.try_parse(str, out result) ? result : deffault;
+	}
+}
+
 }

+ 6 - 5
tools/widgets/entry_position.vala

@@ -7,11 +7,12 @@ using Gtk;
 
 namespace Crown
 {
-	public class EntryPosition : EntryVector3
+public class EntryPosition : EntryVector3
+{
+	public EntryPosition(Vector3 xyz = VECTOR3_ZERO, Vector3 min = VECTOR3_MIN, Vector3 max = VECTOR3_MAX)
 	{
-		public EntryPosition(Vector3 xyz = VECTOR3_ZERO, Vector3 min = VECTOR3_MIN, Vector3 max = VECTOR3_MAX)
-		{
-			base(xyz, min, max, "%.5g");
-		}
+		base(xyz, min, max, "%.5g");
 	}
 }
+
+}

+ 22 - 21
tools/widgets/entry_rotation.vala

@@ -7,31 +7,32 @@ using Gtk;
 
 namespace Crown
 {
-	public class EntryRotation : EntryVector3
+public class EntryRotation : EntryVector3
+{
+	public new Quaternion value
 	{
-		public new Quaternion value
+		get
 		{
-			get
-			{
-				double x = MathUtils.rad(_x.value);
-				double y = MathUtils.rad(_y.value);
-				double z = MathUtils.rad(_z.value);
-				return Quaternion.from_euler(x, y, z);
-			}
-			set
-			{
-				_stop_emit = true;
-				Vector3 euler = ((Quaternion)value).to_euler();
-				_x.value = MathUtils.deg(euler.x);
-				_y.value = MathUtils.deg(euler.y);
-				_z.value = MathUtils.deg(euler.z);
-				_stop_emit = false;
-			}
+			double x = MathUtils.rad(_x.value);
+			double y = MathUtils.rad(_y.value);
+			double z = MathUtils.rad(_z.value);
+			return Quaternion.from_euler(x, y, z);
 		}
-
-		public EntryRotation(Vector3 xyz = VECTOR3_ZERO)
+		set
 		{
-			base(xyz, Vector3(-180.0, -180.0, -180.0), Vector3(180.0, 180.0, 180.0), "%.4g");
+			_stop_emit = true;
+			Vector3 euler = ((Quaternion)value).to_euler();
+			_x.value = MathUtils.deg(euler.x);
+			_y.value = MathUtils.deg(euler.y);
+			_z.value = MathUtils.deg(euler.z);
+			_stop_emit = false;
 		}
 	}
+
+	public EntryRotation(Vector3 xyz = VECTOR3_ZERO)
+	{
+		base(xyz, Vector3(-180.0, -180.0, -180.0), Vector3(180.0, 180.0, 180.0), "%.4g");
+	}
+}
+
 }

+ 6 - 5
tools/widgets/entry_scale.vala

@@ -7,11 +7,12 @@ using Gtk;
 
 namespace Crown
 {
-	public class EntryScale : EntryVector3
+public class EntryScale : EntryVector3
+{
+	public EntryScale(Vector3 xyz = VECTOR3_ONE, Vector3 min = VECTOR3_ZERO, Vector3 max = VECTOR3_MAX)
 	{
-		public EntryScale(Vector3 xyz = VECTOR3_ONE, Vector3 min = VECTOR3_ZERO, Vector3 max = VECTOR3_MAX)
-		{
-			base(xyz, min, max, "%.4g");
-		}
+		base(xyz, min, max, "%.4g");
 	}
 }
+
+}

+ 41 - 40
tools/widgets/entry_vector2.vala

@@ -7,56 +7,57 @@ using Gtk;
 
 namespace Crown
 {
-	public class EntryVector2 : Gtk.Box
-	{
-		// Data
-		public bool _stop_emit;
+public class EntryVector2 : Gtk.Box
+{
+	// Data
+	public bool _stop_emit;
 
-		// Widgets
-		public EntryDouble _x;
-		public EntryDouble _y;
+	// Widgets
+	public EntryDouble _x;
+	public EntryDouble _y;
 
-		public Vector2 value
+	public Vector2 value
+	{
+		get
 		{
-			get
-			{
-				return Vector2(_x.value, _y.value);
-			}
-			set
-			{
-				_stop_emit = true;
-				Vector2 val = (Vector2)value;
-				_x.value = val.x;
-				_y.value = val.y;
-				_stop_emit = false;
-			}
+			return Vector2(_x.value, _y.value);
 		}
+		set
+		{
+			_stop_emit = true;
+			Vector2 val = (Vector2)value;
+			_x.value = val.x;
+			_y.value = val.y;
+			_stop_emit = false;
+		}
+	}
 
-		// Signals
-		public signal void value_changed();
+	// Signals
+	public signal void value_changed();
 
-		public EntryVector2(Vector2 xyz, Vector2 min, Vector2 max)
-		{
-			Object(orientation: Gtk.Orientation.HORIZONTAL, spacing: 0);
+	public EntryVector2(Vector2 xyz, Vector2 min, Vector2 max)
+	{
+		Object(orientation: Gtk.Orientation.HORIZONTAL, spacing: 0);
 
-			// Data
-			_stop_emit = false;
+		// Data
+		_stop_emit = false;
 
-			// Widgets
-			_x = new EntryDouble(xyz.x, min.x, max.x);
-			_y = new EntryDouble(xyz.y, min.y, max.y);
+		// Widgets
+		_x = new EntryDouble(xyz.x, min.x, max.x);
+		_y = new EntryDouble(xyz.y, min.y, max.y);
 
-			_x.value_changed.connect(on_value_changed);
-			_y.value_changed.connect(on_value_changed);
+		_x.value_changed.connect(on_value_changed);
+		_y.value_changed.connect(on_value_changed);
 
-			this.pack_start(_x, true, true);
-			this.pack_start(_y, true, true);
-		}
+		this.pack_start(_x, true, true);
+		this.pack_start(_y, true, true);
+	}
 
-		private void on_value_changed()
-		{
-			if (!_stop_emit)
-				value_changed();
-		}
+	private void on_value_changed()
+	{
+		if (!_stop_emit)
+			value_changed();
 	}
 }
+
+}

+ 61 - 60
tools/widgets/entry_vector3.vala

@@ -7,77 +7,78 @@ using Gtk;
 
 namespace Crown
 {
-	public class EntryVector3 : Gtk.Box
-	{
-		// Data
-		public bool _stop_emit;
+public class EntryVector3 : Gtk.Box
+{
+	// Data
+	public bool _stop_emit;
 
-		// Widgets
-		public EntryDouble _x;
-		public EntryDouble _y;
-		public EntryDouble _z;
-		public Gtk.Label _x_label;
-		public Gtk.Label _y_label;
-		public Gtk.Label _z_label;
+	// Widgets
+	public EntryDouble _x;
+	public EntryDouble _y;
+	public EntryDouble _z;
+	public Gtk.Label _x_label;
+	public Gtk.Label _y_label;
+	public Gtk.Label _z_label;
 
-		public Vector3 value
+	public Vector3 value
+	{
+		get
 		{
-			get
-			{
-				return Vector3(_x.value, _y.value, _z.value);
-			}
-			set
-			{
-				_stop_emit = true;
-				Vector3 val = (Vector3)value;
-				_x.value = val.x;
-				_y.value = val.y;
-				_z.value = val.z;
-				_stop_emit = false;
-			}
+			return Vector3(_x.value, _y.value, _z.value);
 		}
+		set
+		{
+			_stop_emit = true;
+			Vector3 val = (Vector3)value;
+			_x.value = val.x;
+			_y.value = val.y;
+			_z.value = val.z;
+			_stop_emit = false;
+		}
+	}
 
-		// Signals
-		public signal void value_changed();
+	// Signals
+	public signal void value_changed();
 
-		public EntryVector3(Vector3 xyz, Vector3 min, Vector3 max, string fmt)
-		{
-			Object(orientation: Gtk.Orientation.HORIZONTAL, spacing: 0);
+	public EntryVector3(Vector3 xyz, Vector3 min, Vector3 max, string fmt)
+	{
+		Object(orientation: Gtk.Orientation.HORIZONTAL, spacing: 0);
 
-			// Data
-			_stop_emit = false;
+		// Data
+		_stop_emit = false;
 
-			// Widgets
-			_x = new EntryDouble(xyz.x, min.x, max.x, fmt);
-			_y = new EntryDouble(xyz.y, min.y, max.y, fmt);
-			_z = new EntryDouble(xyz.z, min.z, max.z, fmt);
+		// Widgets
+		_x = new EntryDouble(xyz.x, min.x, max.x, fmt);
+		_y = new EntryDouble(xyz.y, min.y, max.y, fmt);
+		_z = new EntryDouble(xyz.z, min.z, max.z, fmt);
 
-			_x.value_changed.connect(on_value_changed);
-			_y.value_changed.connect(on_value_changed);
-			_z.value_changed.connect(on_value_changed);
+		_x.value_changed.connect(on_value_changed);
+		_y.value_changed.connect(on_value_changed);
+		_z.value_changed.connect(on_value_changed);
 
-			_x_label = new Gtk.Label("X");
-			_x_label.get_style_context().add_class("axis");
-			_x_label.get_style_context().add_class("x");
-			_y_label = new Gtk.Label("Y");
-			_y_label.get_style_context().add_class("axis");
-			_y_label.get_style_context().add_class("y");
-			_z_label = new Gtk.Label("Z");
-			_z_label.get_style_context().add_class("axis");
-			_z_label.get_style_context().add_class("z");
+		_x_label = new Gtk.Label("X");
+		_x_label.get_style_context().add_class("axis");
+		_x_label.get_style_context().add_class("x");
+		_y_label = new Gtk.Label("Y");
+		_y_label.get_style_context().add_class("axis");
+		_y_label.get_style_context().add_class("y");
+		_z_label = new Gtk.Label("Z");
+		_z_label.get_style_context().add_class("axis");
+		_z_label.get_style_context().add_class("z");
 
-			this.pack_start(_x_label, false);
-			this.pack_start(_x, true, true);
-			this.pack_start(_y_label, false);
-			this.pack_start(_y, true, true);
-			this.pack_start(_z_label, false);
-			this.pack_start(_z, true, true);
-		}
+		this.pack_start(_x_label, false);
+		this.pack_start(_x, true, true);
+		this.pack_start(_y_label, false);
+		this.pack_start(_y, true, true);
+		this.pack_start(_z_label, false);
+		this.pack_start(_z, true, true);
+	}
 
-		private void on_value_changed()
-		{
-			if (!_stop_emit)
-				value_changed();
-		}
+	private void on_value_changed()
+	{
+		if (!_stop_emit)
+			value_changed();
 	}
 }
+
+}

+ 51 - 50
tools/widgets/entry_vector4.vala

@@ -7,66 +7,67 @@ using Gtk;
 
 namespace Crown
 {
-	public class EntryVector4 : Gtk.Box
-	{
-		// Data
-		public bool _stop_emit;
+public class EntryVector4 : Gtk.Box
+{
+	// Data
+	public bool _stop_emit;
 
-		// Widgets
-		public EntryDouble _x;
-		public EntryDouble _y;
-		public EntryDouble _z;
-		public EntryDouble _w;
+	// Widgets
+	public EntryDouble _x;
+	public EntryDouble _y;
+	public EntryDouble _z;
+	public EntryDouble _w;
 
-		public Vector4 value
+	public Vector4 value
+	{
+		get
 		{
-			get
-			{
-				return Vector4(_x.value, _y.value, _z.value, _w.value);
-			}
-			set
-			{
-				_stop_emit = true;
-				Vector4 val = (Vector4)value;
-				_x.value = val.x;
-				_y.value = val.y;
-				_z.value = val.z;
-				_w.value = val.w;
-				_stop_emit = false;
-			}
+			return Vector4(_x.value, _y.value, _z.value, _w.value);
 		}
+		set
+		{
+			_stop_emit = true;
+			Vector4 val = (Vector4)value;
+			_x.value = val.x;
+			_y.value = val.y;
+			_z.value = val.z;
+			_w.value = val.w;
+			_stop_emit = false;
+		}
+	}
 
-		// Signals
-		public signal void value_changed();
+	// Signals
+	public signal void value_changed();
 
-		public EntryVector4(Vector4 xyz, Vector4 min, Vector4 max)
-		{
-			Object(orientation: Gtk.Orientation.HORIZONTAL, spacing: 0);
+	public EntryVector4(Vector4 xyz, Vector4 min, Vector4 max)
+	{
+		Object(orientation: Gtk.Orientation.HORIZONTAL, spacing: 0);
 
-			// Data
-			_stop_emit = false;
+		// Data
+		_stop_emit = false;
 
-			// Widgets
-			_x = new EntryDouble(xyz.x, min.x, max.x);
-			_y = new EntryDouble(xyz.y, min.y, max.y);
-			_z = new EntryDouble(xyz.z, min.z, max.z);
-			_w = new EntryDouble(xyz.w, min.w, max.w);
+		// Widgets
+		_x = new EntryDouble(xyz.x, min.x, max.x);
+		_y = new EntryDouble(xyz.y, min.y, max.y);
+		_z = new EntryDouble(xyz.z, min.z, max.z);
+		_w = new EntryDouble(xyz.w, min.w, max.w);
 
-			_x.value_changed.connect(on_value_changed);
-			_y.value_changed.connect(on_value_changed);
-			_z.value_changed.connect(on_value_changed);
-			_w.value_changed.connect(on_value_changed);
+		_x.value_changed.connect(on_value_changed);
+		_y.value_changed.connect(on_value_changed);
+		_z.value_changed.connect(on_value_changed);
+		_w.value_changed.connect(on_value_changed);
 
-			this.pack_start(_x, true, true);
-			this.pack_start(_y, true, true);
-			this.pack_start(_z, true, true);
-			this.pack_start(_w, true, true);
-		}
+		this.pack_start(_x, true, true);
+		this.pack_start(_y, true, true);
+		this.pack_start(_z, true, true);
+		this.pack_start(_w, true, true);
+	}
 
-		private void on_value_changed()
-		{
-			if (!_stop_emit)
-				value_changed();
-		}
+	private void on_value_changed()
+	{
+		if (!_stop_emit)
+			value_changed();
 	}
 }
+
+}

+ 65 - 64
tools/widgets/resource_chooser_button.vala

@@ -7,83 +7,84 @@ using Gtk;
 
 namespace Crown
 {
-	public class ResourceChooserButton : Gtk.Box
-	{
-		// Data
-		private bool _stop_emit;
-		private string _type;
+public class ResourceChooserButton : Gtk.Box
+{
+	// Data
+	private bool _stop_emit;
+	private string _type;
 
-		// Widgets
-		private Gtk.Entry _name;
-		private Gtk.Button _selector;
-		private ProjectStore _project_store;
+	// Widgets
+	private Gtk.Entry _name;
+	private Gtk.Button _selector;
+	private ProjectStore _project_store;
 
-		public string value
+	public string value
+	{
+		get
+		{
+			return _name.text;
+		}
+		set
 		{
-			get
-			{
-				return _name.text;
-			}
-			set
-			{
-				_stop_emit = true;
-				_name.text = value;
-				_stop_emit = false;
-			}
+			_stop_emit = true;
+			_name.text = value;
+			_stop_emit = false;
 		}
+	}
 
-		// Signals
-		public signal void value_changed();
+	// Signals
+	public signal void value_changed();
 
-		public ResourceChooserButton(ProjectStore store, string type)
-		{
-			Object(orientation: Gtk.Orientation.HORIZONTAL, spacing: 0);
+	public ResourceChooserButton(ProjectStore store, string type)
+	{
+		Object(orientation: Gtk.Orientation.HORIZONTAL, spacing: 0);
 
-			// Data
-			_stop_emit = false;
-			_type = type;
+		// Data
+		_stop_emit = false;
+		_type = type;
 
-			// Widgets
-			_name = new Gtk.Entry();
-			_name.sensitive = false;
-			_name.hexpand = true;
-			_name.changed.connect(on_value_changed);
-			_selector = new Gtk.Button.from_icon_name("document-open-symbolic");
-			_selector.clicked.connect(on_selector_clicked);
-			_project_store = store;
+		// Widgets
+		_name = new Gtk.Entry();
+		_name.sensitive = false;
+		_name.hexpand = true;
+		_name.changed.connect(on_value_changed);
+		_selector = new Gtk.Button.from_icon_name("document-open-symbolic");
+		_selector.clicked.connect(on_selector_clicked);
+		_project_store = store;
 
-			this.pack_start(_name, true, true);
-			this.pack_end(_selector, false);
-		}
+		this.pack_start(_name, true, true);
+		this.pack_end(_selector, false);
+	}
 
-		private void on_value_changed()
-		{
-			if (!_stop_emit)
-				value_changed();
-		}
+	private void on_value_changed()
+	{
+		if (!_stop_emit)
+			value_changed();
+	}
 
-		private void on_selector_clicked()
-		{
-			Gtk.Dialog dg = new Gtk.Dialog.with_buttons("Select Resource"
-				, (Gtk.Window)this.get_toplevel()
-				, DialogFlags.MODAL
-				, null
-				);
+	private void on_selector_clicked()
+	{
+		Gtk.Dialog dg = new Gtk.Dialog.with_buttons("Select Resource"
+			, (Gtk.Window)this.get_toplevel()
+			, DialogFlags.MODAL
+			, null
+			);
 
-			var rb = new ResourceChooser(null, _project_store, false);
-			rb.set_type_filter(type_filter);
-			rb.resource_selected.connect(() => { _name.text = rb._name; dg.response(ResponseType.OK); });
+		var rb = new ResourceChooser(null, _project_store, false);
+		rb.set_type_filter(type_filter);
+		rb.resource_selected.connect(() => { _name.text = rb._name; dg.response(ResponseType.OK); });
 
-			dg.skip_taskbar_hint = true;
-			dg.get_content_area().pack_start(rb, true, true, 0);
-			dg.show_all();
-			dg.run();
-			dg.destroy();
-		}
+		dg.skip_taskbar_hint = true;
+		dg.get_content_area().pack_start(rb, true, true, 0);
+		dg.show_all();
+		dg.run();
+		dg.destroy();
+	}
 
-		private bool type_filter(string type, string name)
-		{
-			return _type == type;
-		}
+	private bool type_filter(string type, string name)
+	{
+		return _type == type;
 	}
 }
+
+}

+ 9 - 8
tools/widgets/slide.vala

@@ -7,14 +7,15 @@ using Gtk;
 
 namespace Crown
 {
-	public class Slide : Gtk.Bin
+public class Slide : Gtk.Bin
+{
+	public void show_widget(Gtk.Widget slide)
 	{
-		public void show_widget(Gtk.Widget slide)
-		{
-			if (this.get_child() != null)
-				this.remove(this.get_child());
-			this.add(slide);
-			this.show_all();
-		}
+		if (this.get_child() != null)
+			this.remove(this.get_child());
+		this.add(slide);
+		this.show_all();
 	}
 }
+
+}

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio