Skip to content

Commit 68aee5f

Browse files
authored
feat(chatview-ux): multiple UX improvements (#138)
* Notifications when a contact goes `online` and `offline`. * Keyboard shortcut to select a messages selection: * `Ctrl+C`: Copy the messages selected into Clipboard. * `Ctrl+Shift+Q`: Quote the selection. * Keyboard shortcut for insering a new line: * `Shift+Return`: Insert a newline at the cursor position. * Some bug fixes.
1 parent 6c87496 commit 68aee5f

8 files changed

Lines changed: 131 additions & 52 deletions

File tree

res/ui/chat-view.ui

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,6 +1446,7 @@
14461446
<property name="visible">True</property>
14471447
<property name="sensitive">False</property>
14481448
<property name="can_focus">True</property>
1449+
<property name="events">GDK_KEY_PRESS_MASK | GDK_STRUCTURE_MASK</property>
14491450
<property name="max_width_chars">5</property>
14501451
<signal name="activate" handler="send_message" swapped="no"/>
14511452
<style>

src/ChatView.vala

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class Ricin.ChatView : Gtk.Box {
5050

5151
[Signal (action = true)] private signal void copy_messages_selection ();
5252
[Signal (action = true)] private signal void quote_messages_selection ();
53+
[Signal (action = true)] private signal void entry_insert_newline ();
5354

5455
public Tox.Friend fr;
5556
private weak Tox.Tox handle;
@@ -59,7 +60,7 @@ class Ricin.ChatView : Gtk.Box {
5960
private Settings settings;
6061

6162
private Tox.UserStatus last_status;
62-
private string last_message_sender;
63+
private string last_message_sender { get; set; default = "ricin"; }
6364
private string last_message = null;
6465
private bool is_bottom = true;
6566

@@ -401,21 +402,21 @@ class Ricin.ChatView : Gtk.Box {
401402
string txt = "";
402403

403404
if (item is MessageListRow) {
404-
name = ((MessageListRow) item).author;
405+
name = "[" + ((MessageListRow) item).author + "]";
405406
txt = ((MessageListRow) item).label_message.get_text ();
406407
} else if (item is SystemMessageListRow) {
407408
name = "* ";
408409
txt = ((SystemMessageListRow) item).label_message.get_text ();
409410
} else if (item is QuoteMessageListRow) {
410-
name = ((QuoteMessageListRow) item).author;
411+
name = "[" + ((QuoteMessageListRow) item).author + "]";
411412
txt = ((QuoteMessageListRow) item).get_quote ();
412413
}
413414

414415
if (as_quote) {
415416
sb.append_c ('>');
416417
}
417418
if (include_names) {
418-
sb.append (@"[$name] ");
419+
sb.append (@"$name ");
419420
}
420421

421422
sb.append (txt);
@@ -442,6 +443,8 @@ class Ricin.ChatView : Gtk.Box {
442443
string selection = this.get_selected_messages (false, true);
443444
Gtk.Clipboard.get (Gdk.SELECTION_CLIPBOARD).set_text (selection, -1);
444445
this.messages_list.unselect_all ();
446+
this.entry.grab_focus_without_selecting ();
447+
this.entry.set_position (-1);
445448
});
446449
menu_copy_quote.activate.connect (() => {
447450
if (this.messages_selected () == false) {
@@ -451,6 +454,8 @@ class Ricin.ChatView : Gtk.Box {
451454
string quote = this.get_selected_messages (true, true);
452455
Gtk.Clipboard.get (Gdk.SELECTION_CLIPBOARD).set_text (quote, -1);
453456
this.messages_list.unselect_all ();
457+
this.entry.grab_focus_without_selecting ();
458+
this.entry.set_position (-1);
454459
});
455460
menu_quote_selection.activate.connect (() => {
456461
if (this.messages_selected () == false) {
@@ -461,6 +466,7 @@ class Ricin.ChatView : Gtk.Box {
461466
this.entry.set_text (quote);
462467
this.messages_list.unselect_all ();
463468
this.entry.grab_focus_without_selecting ();
469+
this.entry.set_position (-1);
464470
});
465471

466472
menu.append (menu_copy_selection);
@@ -484,13 +490,21 @@ class Ricin.ChatView : Gtk.Box {
484490
});
485491
}
486492

493+
/*private MainWindow get_top () {
494+
495+
}*/
496+
487497
private void init_messages_shortcuts () {
488-
var main_window = ((MainWindow) this.get_toplevel ());
489-
Gtk.AccelGroup accel_group = new Gtk.AccelGroup ();
490-
main_window.add_accel_group (accel_group);
498+
//var main_window = ((MainWindow) this.get_toplevel ().get_toplevel ());
499+
491500
/**
492-
* Keyboard shortcut for copying or quoting selected messages.
501+
* Shortcut for Ctrl+C: Copy selected messages if selection > 0
493502
**/
503+
this.add_accelerator (
504+
"copy-messages-selection", MainWindow.accel_group, Gdk.keyval_from_name("C"),
505+
Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE
506+
);
507+
494508
this.copy_messages_selection.connect (() => {
495509
if (this.messages_selected () == false) {
496510
return;
@@ -499,8 +513,18 @@ class Ricin.ChatView : Gtk.Box {
499513
string selection = this.get_selected_messages (false, true);
500514
Gtk.Clipboard.get (Gdk.SELECTION_CLIPBOARD).set_text (selection, -1);
501515
this.messages_list.unselect_all ();
516+
this.entry.grab_focus_without_selecting ();
517+
this.entry.set_position (-1);
502518
});
503519

520+
/**
521+
* Shortcut for Ctrl+Shift+Q: Quote selected messages if selection > 0
522+
**/
523+
this.add_accelerator (
524+
"quote-messages-selection", MainWindow.accel_group, Gdk.keyval_from_name("Q"),
525+
Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK, Gtk.AccelFlags.VISIBLE
526+
);
527+
504528
this.quote_messages_selection.connect (() => {
505529
if (this.messages_selected () == false) {
506530
return;
@@ -510,23 +534,29 @@ class Ricin.ChatView : Gtk.Box {
510534
this.entry.set_text (quote);
511535
this.messages_list.unselect_all ();
512536
this.entry.grab_focus_without_selecting ();
537+
this.entry.set_position (-1);
513538
});
514539

515540
/**
516-
* Shortcut for Ctrl+C: Copy selected messages if selection > 0
541+
* Shortcut for Shift+Enter in this.entry: Add a newline (\n).
517542
**/
518543
this.add_accelerator (
519-
"copy-messages-selection", accel_group, Gdk.keyval_from_name("C"),
520-
Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE
544+
"entry-insert-newline", MainWindow.accel_group, Gdk.Key.Return,
545+
Gdk.ModifierType.SHIFT_MASK, Gtk.AccelFlags.VISIBLE
521546
);
522547

523-
/**
524-
* Shortcut for Ctrl+Shift+Q: Quote selected messages if selection > 0
525-
**/
526-
this.add_accelerator (
527-
"quote-messages-selection", accel_group, Gdk.keyval_from_name("Q"),
528-
Gdk.ModifierType.CONTROL_MASK | Gdk.ModifierType.SHIFT_MASK, Gtk.AccelFlags.VISIBLE
529-
);
548+
this.entry_insert_newline.connect (() => {
549+
debug ("entry_insert_newline: Called.");
550+
551+
int cursor_position = this.entry.get_position ();
552+
string text = this.entry.get_text ();
553+
string newline = "\n";
554+
555+
this.entry.insert_at_cursor (newline);
556+
this.entry.grab_focus_without_selecting ();
557+
this.entry.set_position (cursor_position + newline.length);
558+
559+
});
530560
}
531561

532562
public void show_notice (string text, string icon_name = "help-info-symbolic") {

src/FriendListRow.vala

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,15 @@ class Ricin.FriendListRow : Gtk.ListBoxRow {
8181
});
8282

8383
fr.notify["status"].connect ((obj, prop) => {
84+
if (fr.status == Tox.UserStatus.ONLINE || fr.status == Tox.UserStatus.OFFLINE) {
85+
string status_str = Util.status_to_string (this.fr.status);
86+
Notification.notify (
87+
this.fr.name + _(" is now ") + status_str,
88+
this.fr.status_message,
89+
3000
90+
);
91+
}
92+
8493
string icon = Util.status_to_icon (this.fr.status, 0);
8594
this.userstatus.set_from_resource (@"/chat/tox/ricin/images/status/$icon.png");
8695
this.changed (); // we sort by user status
@@ -99,11 +108,13 @@ class Ricin.FriendListRow : Gtk.ListBoxRow {
99108
fr.action.connect (this.notify_new_messages);
100109

101110
this.activate.connect (() => {
111+
var main_window = ((MainWindow) this.get_toplevel ());
112+
main_window.global_unread_counter -= this.unreadCount;
113+
102114
this.unreadCount = 0;
103115
this.update_icon ();
104116
this.changed ();
105117

106-
var main_window = ((MainWindow) this.get_toplevel ());
107118
main_window.friendlist.invalidate_filter ();
108119
});
109120
}
@@ -115,7 +126,9 @@ class Ricin.FriendListRow : Gtk.ListBoxRow {
115126
if (this.unreadCount == 0) {
116127
this.label_unread_count.visible = false;
117128
} else {
118-
this.label_unread_count.set_text (@"$(this.unreadCount)");
129+
string count_str = this.unreadCount > 10 ? "<b>10+</b>" : @"$(this.unreadCount)";
130+
131+
this.label_unread_count.set_markup (count_str);
119132
this.label_unread_count.visible = true;
120133
}
121134
}
@@ -127,6 +140,7 @@ class Ricin.FriendListRow : Gtk.ListBoxRow {
127140
}
128141

129142
this.unreadCount++;
143+
main_window.global_unread_counter += this.unreadCount;
130144
this.update_icon ();
131145
}
132146

src/MainWindow.vala

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -51,18 +51,20 @@ public class Ricin.MainWindow : Gtk.ApplicationWindow {
5151

5252
public Tox.Tox tox;
5353
public string focused_view;
54+
public int global_unread_counter = 0;
55+
public static Gtk.AccelGroup accel_group;
56+
5457
private Gtk.ListBoxRow selected_row;
5558
private Gtk.Menu menu_statusicon_main;
5659
private Gtk.StatusIcon statusicon_main;
5760
private Settings settings;
5861
private string window_title;
5962
private string profile;
6063

64+
public signal void notify_message (string message, int timeout = 5000);
6165
[Signal (action = true)] private signal void change_chat_up ();
6266
[Signal (action = true)] private signal void change_chat_down ();
6367

64-
public signal void notify_message (string message, int timeout = 5000);
65-
6668
public MainWindow (Gtk.Application app, string profile, string? password = null, bool is_new = false) {
6769
Object (application: app);
6870
this.settings = Settings.instance;
@@ -81,6 +83,13 @@ public class Ricin.MainWindow : Gtk.ApplicationWindow {
8183
this.set_icon (app_icon);
8284

8385
this.init_keyboard_shortcuts ();
86+
this.notify["global-unread-counter"].connect ((obj, prop) => {
87+
if (this.global_unread_counter == 0) {
88+
this.set_urgency_hint (false);
89+
} else {
90+
this.set_urgency_hint (true);
91+
}
92+
});
8493

8594
var opts = Tox.Options.create ();
8695
opts.ipv6_enabled = this.settings.network_ipv6;
@@ -329,45 +338,51 @@ public class Ricin.MainWindow : Gtk.ApplicationWindow {
329338
}
330339

331340
private void init_keyboard_shortcuts () {
332-
Gtk.AccelGroup accel_group = new Gtk.AccelGroup ();
333-
this.add_accel_group (accel_group);
341+
this.accel_group = new Gtk.AccelGroup ();
342+
this.add_accel_group (this.accel_group);
343+
334344
/**
335-
* Keyboard shortcut for switching to previous/next contact's chatview.
336-
* FIXME: Ctrl+Up | Ctrl+Down doesn't call these signals.
345+
* Shortcut for Ctrl+Up: Change the chat view to the previous one.
337346
**/
338-
this.change_chat_up.connect (() => {
339-
var index = this.selected_row.get_index ();
340-
if (index == 0) {
341-
return;
342-
}
343-
var prev_row = this.friendlist.get_row_at_index (index - 1);
344-
this.selected_row = prev_row;
345-
this.selected_row.activate ();
346-
this.friendlist.select_row (prev_row);
347-
});
347+
this.add_accelerator (
348+
"change-chat-up", accel_group, Gdk.keyval_from_name("Up"),
349+
Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE
350+
);
351+
348352
this.change_chat_down.connect (() => {
349-
var index = this.selected_row.get_index ();
350-
var max = this.friendlist.get_children ().length ();
353+
int index = this.selected_row.get_index ();
354+
uint max = this.friendlist.get_children ().length ();
355+
351356
if (index == max) {
352357
return;
353358
}
359+
354360
var next_row = this.friendlist.get_row_at_index (index + 1);
355361
this.selected_row = next_row;
356362
this.selected_row.activate ();
357363
this.friendlist.select_row (next_row);
358364
});
359365

360-
/**
361-
* Shortcut for Ctrl+Up: Change the chat view to the previous one.
362-
**/
363-
this.add_accelerator ("change-chat-up", accel_group, Gdk.keyval_from_name("Up"),
364-
Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);
365-
366366
/**
367367
* Shortcut for Ctrl+Down: Change the chat view to the next one.
368368
**/
369-
this.add_accelerator ("change-chat-down", accel_group, Gdk.keyval_from_name("Down"),
370-
Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE);
369+
this.add_accelerator (
370+
"change-chat-down", accel_group, Gdk.keyval_from_name("Down"),
371+
Gdk.ModifierType.CONTROL_MASK, Gtk.AccelFlags.VISIBLE
372+
);
373+
374+
this.change_chat_up.connect (() => {
375+
int index = this.selected_row.get_index ();
376+
377+
if (index == 0) {
378+
return;
379+
}
380+
381+
var prev_row = this.friendlist.get_row_at_index (index - 1);
382+
this.selected_row = prev_row;
383+
this.selected_row.activate ();
384+
this.friendlist.select_row (prev_row);
385+
});
371386
}
372387

373388
private async void append_friends () {

src/Notification.vala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ class Ricin.Notification : Object {
99
notif = new Notify.Notification (sender, message, null);
1010
notif.set_image_from_pixbuf (icon);
1111
}
12+
1213
notif.set_category ("im.received");
1314
notif.set_hint ("sound-name", new Variant.string ("message-new-instant"));
1415
notif.set_timeout (timeout);

src/ProfileChooser.vala

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ class Ricin.ProfileChooser : Gtk.ApplicationWindow {
2828
this.set_resizable (false);
2929

3030
this.populate_profiles ();
31+
32+
this.entry_login_password.activate.connect (this.login);
33+
3134
this.show_all ();
3235
}
3336

src/QuoteMessageListRow.vala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,16 @@ class Ricin.QuoteMessageListRow : Gtk.ListBoxRow {
7070
var txt = "";
7171

7272
if (item is QuoteLabel) {
73-
txt = ">" + ((QuoteLabel) item).label_quote.get_text ();
73+
txt = ((QuoteLabel) item).label_quote.get_text ();
7474
} else if (item is PlainLabel) {
7575
txt = ((PlainLabel) item).label_text.get_text ();
7676
}
7777

78+
string[] lines = txt.split ("\n");
79+
foreach (string line in lines) {
80+
txt = ">" + line;
81+
}
82+
7883
sb.append (txt);
7984
sb.append_c ('\n');
8085
}

0 commit comments

Comments
 (0)