ソースを参照

tools: add hyperlink to resource paths

Daniele Bartolini 4 年 前
コミット
e6ed6d3244
3 ファイル変更165 行追加17 行削除
  1. 1 0
      docs/changelog.rst
  2. 13 3
      tools/level_editor/project.vala
  3. 151 14
      tools/widgets/console_view.vala

+ 1 - 0
docs/changelog.rst

@@ -13,6 +13,7 @@ Changelog
 
 * Windows: fixed wrong Editor View window size.
 * Added a setting to limit the number of lines displayed by the Console.
+* Added hyperlinks to resource paths in the Console.
 
 0.42.0
 ------

+ 13 - 3
tools/level_editor/project.vala

@@ -381,11 +381,21 @@ public class Project
 		}
 	}
 
-	public string id_to_name(string id)
+	/// Converts the @a resource_id to its corresponding human-readable @a
+	/// resource_name. It returns true if the conversion is successful, otherwise
+	/// it returns false and sets @a resource_name to the value of @a resource_id.
+	public bool resource_id_to_name(out string resource_name, string resource_id)
 	{
 		Hashtable index = SJSON.load(Path.build_filename(_data_dir.get_path(), "data_index.sjson"));
-		Value? name = index[id];
-		return name != null ? (string)name : id;
+		Value? name = index[resource_id];
+		if (name != null)
+		{
+			resource_name = (string)name;
+			return true;
+		}
+
+		resource_name = resource_id;
+		return false;
 	}
 
 	public Database files()

+ 151 - 14
tools/widgets/console_view.vala

@@ -85,8 +85,11 @@ public class ConsoleView : Gtk.Box
 	public PreferencesDialog _preferences_dialog;
 
 	// Widgets
-	public Gtk.ScrolledWindow _scrolled_window;
+	public Gdk.Cursor _text_cursor;
+	public Gdk.Cursor _pointer_cursor;
+	public bool _cursor_is_hovering_link;
 	public Gtk.TextView _text_view;
+	public Gtk.ScrolledWindow _scrolled_window;
 	public EntryText _entry;
 	public Gtk.Box _entry_hbox;
 
@@ -101,6 +104,10 @@ public class ConsoleView : Gtk.Box
 		_preferences_dialog = preferences_dialog;
 
 		// Widgets
+		_text_cursor = new Gdk.Cursor.from_name(this.get_display(), "text");
+		_pointer_cursor = new Gdk.Cursor.from_name(this.get_display(), "pointer");
+		_cursor_is_hovering_link = false;
+
 		_text_view = new Gtk.TextView();
 		_text_view.editable = false;
 		_text_view.can_focus = false;
@@ -134,6 +141,8 @@ public class ConsoleView : Gtk.Box
 
 		this.show.connect(on_show);
 		this.destroy.connect(on_destroy);
+		this.button_release_event.connect(on_button_released);
+		this.motion_notify_event.connect(on_motion_notify);
 
 		this.get_style_context().add_class("console-view");
 
@@ -214,19 +223,87 @@ public class ConsoleView : Gtk.Box
 		_console_view_valid = false;
 	}
 
-	public void log(string severity, string message)
+	private bool on_button_released(Gdk.EventButton ev)
+	{
+		if (ev.button == Gdk.BUTTON_PRIMARY)
+		{
+			// Do not handle click if some text is selected.
+			Gtk.TextIter dummy_iter;
+			if (_text_view.buffer.get_selection_bounds(out dummy_iter, out dummy_iter))
+				return Gdk.EVENT_PROPAGATE;
+
+			int buffer_x;
+			int buffer_y;
+			_text_view.window_to_buffer_coords(Gtk.TextWindowType.WIDGET
+				, (int)ev.x
+				, (int)ev.y
+				, out buffer_x
+				, out buffer_y
+				);
+
+			Gtk.TextIter iter;
+			if (_text_view.get_iter_at_location(out iter, buffer_x, buffer_y))
+			{
+				// Check whether the text under the mouse pointer has a link tag.
+				GLib.SList<unowned TextTag> tags = iter.get_tags();
+				foreach (var item in tags)
+				{
+					string resource_name = item.get_data<string>("resource_name");
+					if (resource_name != null)
+					{
+						Gtk.Application app = ((Gtk.Window)this.get_toplevel()).application;
+						app.activate_action("open-resource", new GLib.Variant.string(resource_name));
+					}
+				}
+			}
+		}
+
+		return Gdk.EVENT_PROPAGATE;
+	}
+
+	private bool on_motion_notify(Gdk.EventMotion ev)
 	{
-		string line = message;
+		bool hovering = false;
+
+		int buffer_x;
+		int buffer_y;
+		_text_view.window_to_buffer_coords(TextWindowType.WIDGET
+			, (int)ev.x
+			, (int)ev.y
+			, out buffer_x
+			, out buffer_y
+			);
+
+		Gtk.TextIter iter;
+		if (_text_view.get_iter_at_location(out iter, buffer_x, buffer_y))
+		{
+			// Check whether the text under the mouse pointer has a link tag.
+			GLib.SList<unowned TextTag> tags = iter.get_tags();
+			foreach (var item in tags)
+			{
+				string resource_name = item.get_data<string>("resource_name");
+				if (resource_name != null)
+				{
+					hovering = true;
+				}
+			}
+		}
 
-		// Replace IDs with human-readable names.
-		int id_index = message.index_of("#ID(");
-		if (id_index != -1)
+		if (_cursor_is_hovering_link != hovering)
 		{
-			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));
+			_cursor_is_hovering_link = hovering;
+
+			if (_cursor_is_hovering_link)
+				_text_view.get_window(Gtk.TextWindowType.TEXT).set_cursor(_pointer_cursor);
+			else
+				_text_view.get_window(Gtk.TextWindowType.TEXT).set_cursor(_text_cursor);
 		}
 
+		return Gdk.EVENT_PROPAGATE;
+	}
+
+	public void log(string severity, string message)
+	{
 		Gtk.TextBuffer buffer = _text_view.buffer;
 
 		// Limit number of lines recorded.
@@ -240,14 +317,74 @@ public class ConsoleView : Gtk.Box
 			buffer.delete(ref start_of_first_line, ref end_of_first_line);
 		}
 
-		// Append line.
 		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;
+
+		// Replace all IDs with corresponding human-readable names.
+		int id_index = 0;
+		do
+		{
+			// Search for occurrences of the ID string.
+			int id_index_orig = id_index;
+			id_index = message.index_of("#ID(", id_index);
+			if (id_index != -1)
+			{
+				// If an occurrenct is found, insert the preceding text as usual.
+				string line_chunk = message.substring(id_index_orig, id_index-id_index_orig);
+				buffer.insert_with_tags(ref end_iter
+					, line_chunk
+					, line_chunk.length
+					, buffer.tag_table.lookup(severity)
+					, null
+					);
+
+				// Try to extract the ID from the ID string.
+				int id_closing_parentheses = message.index_of(")", id_index+4);
+				if (id_closing_parentheses == -1)
+				{
+					// If the ID is malformed, insert the whole line as-is.
+					buffer.insert_with_tags(ref end_iter
+						, message.substring(id_index)
+						, -1
+						, buffer.tag_table.lookup(severity)
+						, null
+						);
+					break;
+				}
+
+				// Convert the resource ID to human-readable resource name.
+				string resource_name;
+				string resource_id = message.substring(id_index+4, id_closing_parentheses-(id_index+4));
+				_project.resource_id_to_name(out resource_name, resource_id);
+				// Create a tag for hyperlink.
+				Gtk.TextTag hyperlink = null;
+				hyperlink = buffer.create_tag(null, "underline", Pango.Underline.SINGLE, null);
+				hyperlink.set_data("resource_name", resource_name);
+
+				buffer.insert_with_tags(ref end_iter
+					, resource_name
+					, -1
+					, buffer.tag_table.lookup(severity)
+					, hyperlink
+					, null
+					);
+				id_index += 4 + resource_id.length;
+				continue;
+			}
+			else
+			{
+				buffer.insert_with_tags(ref end_iter
+					, message.substring(id_index_orig)
+					, -1
+					, buffer.tag_table.lookup(severity)
+					, null
+					);
+			}
+		}
+		while (id_index++ >= 0);
+
+		// Scroll to bottom.
 		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);
 	}
 }