Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clickable links in event descriptions #656

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions src/EventEdition/InfoPanel.vala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
public class Maya.View.EventEdition.InfoPanel : Gtk.Grid {
private Gtk.Entry title_entry;
private Gtk.TextView comment_textview;
private Gtk.TextTag? comment_link_tag;
private Granite.Widgets.DatePicker from_date_picker;
private Granite.Widgets.DatePicker to_date_picker;
private Gtk.Switch allday_switch;
Expand Down Expand Up @@ -145,6 +146,26 @@ public class Maya.View.EventEdition.InfoPanel : Gtk.Grid {
comment_textview.set_border_window_size (Gtk.TextWindowType.TOP, 2);
comment_textview.set_border_window_size (Gtk.TextWindowType.BOTTOM, 2);

// Change cursor to hand when pointing a link when CTRL is pressed
comment_textview.motion_notify_event.connect ((event) => {
if (comment_link_tag != null) {
Gtk.TextIter pointed_at;
int buffer_x, buffer_y;
comment_textview.window_to_buffer_coords (Gtk.TextWindowType.WIDGET, (int) event.x, (int) event.y, out buffer_x, out buffer_y);
comment_textview.get_iter_at_location (out pointed_at, buffer_x, buffer_y);

var control = event.state & Gdk.ModifierType.CONTROL_MASK;
if (pointed_at.has_tag (comment_link_tag) && control != 0) {
var hand = new Gdk.Cursor.from_name (comment_textview.get_display (), "hand");
event.window.set_cursor (hand);
} else {
var hand = new Gdk.Cursor.from_name (comment_textview.get_display (), "text");
event.window.set_cursor (hand);
}
}
return false;
});

var scrolled = new Gtk.ScrolledWindow (null, null);
scrolled.hscrollbar_policy = Gtk.PolicyType.NEVER;
scrolled.add (comment_textview);
Expand Down Expand Up @@ -264,6 +285,31 @@ public class Maya.View.EventEdition.InfoPanel : Gtk.Grid {
Gtk.TextBuffer buffer = new Gtk.TextBuffer (null);
buffer.text = property.get_comment ();
comment_textview.set_buffer (buffer);

// Identify links in the description and apply a tag to style them
var link_regex = new GLib.Regex ("https?://[\\w.-]*(\\/[\\w-\\d]*)*");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't account for some types of URLs, for example ones that include ? parameters. This is common in things like Zoom links.

Here's a blog post that goes over some example patterns. I'm not sure if there's any licensing issues involved with copying these, however.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The compiler also gives a warning of an unhandled GLib.RegexError on this line.

comment_link_tag = buffer.create_tag ("link");
comment_link_tag.event.connect (on_link_clicked);
comment_link_tag.set_property ("foreground", "#0000FF");
comment_link_tag.underline = Pango.Underline.SINGLE;
GLib.MatchInfo mi;

Gtk.TextIter search_iter;
buffer.get_start_iter (out search_iter);
if (link_regex.match (buffer.text, 0, out mi)) {
do {
int start, end;
mi.fetch_pos (0, out start, out end);
var link_text = buffer.text.substring (start, end - start);

Gtk.TextIter starti, endi;
search_iter.forward_search (link_text, Gtk.TextSearchFlags.TEXT_ONLY, out starti, out endi, null);

buffer.apply_tag (comment_link_tag, starti, endi);
search_iter.forward_chars (link_text.length);
} while (mi.next ());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The compiler gives a warning of an unhandled GLib.RegexError here.

}
Comment on lines +290 to +311
Copy link
Member

@pongloongyeat pongloongyeat Mar 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with this so please don't take my comment too seriously. Would it be possible to do the regex search on TextBuffer.changed? I tried your PR and when I type links in, it only adds the tag after I've saved the event and reopened it. Note however that doing a regex search on each TextBuffer.changed would probably be incredibly inefficient but I am of the opinion that event descriptions are typically short and at most contain 100 words or so, so performance shouldn't be too big of a hit.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should assume that descriptions are going to be short, especially the type that have links. Auto-generated Zoom or Webex events have very long descriptions. That said, I'm not sure what the performance hit of regex on change would be.

Two potential compromises I can think of:

  1. Run the regex when the description field loses focus (so the focus goes somewhere else, like the title). This still wouldn't parse immediately, but it's possible that people could change the description and then go somewhere else, and then they'd see the benefit while they're still editing.
  2. Use a timeout to wait a second or two after editing stops, then parse. I've seen some work over in elementary/code that worked with this.


}

// Load the source
Expand Down Expand Up @@ -299,6 +345,27 @@ public class Maya.View.EventEdition.InfoPanel : Gtk.Grid {
}
}

bool on_link_clicked (Gtk.TextTag tag, GLib.Object unused, Gdk.Event event, Gtk.TextIter iter) {
Gdk.ModifierType state;
event.get_state (out state);
var control = (state & Gdk.ModifierType.CONTROL_MASK) != 0;
if (event.type == Gdk.EventType.BUTTON_RELEASE &&
control) {
var button_event = (Gdk.EventButton) event;
if (button_event.button == Gdk.BUTTON_PRIMARY) {
var start = iter.copy ();
start.backward_to_tag_toggle (tag);
iter.forward_to_tag_toggle (tag);

var link_dst = comment_textview.buffer.get_text (start, iter, false);
Gtk.show_uri_on_window (null, link_dst, Gdk.CURRENT_TIME);
Copy link
Collaborator

@mcclurgm mcclurgm Jun 2, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also has an unhandled error of some sort (the compiler isn't very specific).

return true;
}
}

return false;
}

Granite.Widgets.DatePicker make_date_picker () {
var format = Granite.DateTime.get_default_date_format (false, true, true);
var date_picker = new Granite.Widgets.DatePicker.with_format (format);
Expand Down