1 /* Copyright (C) 2016-2017 Shengyu Zhang <i@silverrainz.me>
2  *
3  * This file is part of Srain.
4  *
5  * Srain is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 /**
20  * @file sui_window.c
21  * @brief Sui window class
22  * @author Shengyu Zhang <i@silverrainz.me>
23  * @version 0.06.2
24  * @date 2016-03-01
25  */
26 
27 #include <gtk/gtk.h>
28 #include <string.h>
29 #include <assert.h>
30 
31 #include "core/core.h"
32 #include "sui/sui.h"
33 #include "meta.h"
34 #include "log.h"
35 #include "i18n.h"
36 
37 #include "sui_common.h"
38 #include "sui_event_hdr.h"
39 #include "sui_window.h"
40 #include "sui_connect_panel.h"
41 #include "sui_join_panel.h"
42 #include "sui_side_bar.h"
43 #include "sui_server_buffer.h"
44 
45 #define SEND_MESSAGE_INTERVAL       100
46 
47 #define WINDOW_STACK_PAGE_WELCOME   "welcome"
48 #define WINDOW_STACK_PAGE_MAIN      "main"
49 
50 struct _SuiWindow {
51     GtkApplicationWindow parent;
52 
53     SuiWindowEvents *events;
54     SuiWindowConfig *cfg;
55 
56     /* Top level container */
57     GtkPaned *title_paned;
58     GtkBox *window_box;
59     GtkSeparator *header_separator;
60     GtkBox *header_box;
61     GtkPaned *header_paned;
62     GtkStack *window_stack;
63 
64     /* Side header */
65     GtkHeaderBar *side_header_bar;
66     GtkBox *side_header_box;
67     GtkBox *side_left_header_box;
68     GtkBox *side_right_header_box;
69     GtkImage *start_image;
70     GtkMenuButton *start_menu_button;
71     GtkButton *connect_button;
72     GtkButton *join_button;
73 
74     /* Buffer header */
75     GtkHeaderBar *buffer_header_bar;
76     GtkBox *buffer_header_box;
77     GtkBox *buffer_title_box;
78     GtkLabel *buffer_title_label;
79     GtkLabel *buffer_subtitle_label;
80     GtkMenuButton *buffer_menu_button;
81 
82     /* Welcome page */
83     GtkBox *welcome_connect_box;
84 
85     /* Main page */
86     GtkPaned *main_paned;
87     GtkBox *side_box;
88     SuiSideBar *side_bar;
89     GtkStack *buffer_stack;
90     GtkButton *plugin_button;
91     GtkTextView *input_text_view;
92     GtkButton *send_button;
93     int send_timer;
94 
95     /* Panels */
96     SuiConnectPanel *connect_panel;
97 };
98 
99 struct _SuiWindowClass {
100     GtkApplicationWindowClass parent_class;
101 };
102 
103 static void sui_window_set_events(SuiWindow *self, SuiWindowEvents *events);
104 
105 static void update_header(SuiWindow *self);
106 static void update_title(SuiWindow *self);
107 static void update_focus(SuiWindow *self);
108 static int get_buffer_count(SuiWindow *self);
109 static void send_message_cancel(SuiWindow *self);
110 static void send_message(SuiWindow *self);
111 
112 static void on_destroy(SuiWindow *self);
113 static void on_notify_is_active(GObject *object, GParamSpec *pspec,
114         gpointer data);
115 static gboolean on_delete_event(GtkWidget *widget, GdkEvent *event,
116             gpointer user_data);
117 
118 static void window_stack_on_child_changed(GtkWidget *widget, GParamSpec *pspec,
119         gpointer user_data);
120 static void buffer_stack_on_child_changed(GtkWidget *widget, GParamSpec *pspec,
121         gpointer user_data);
122 static void popover_button_on_click(GtkButton *button, gpointer user_data);
123 static void join_button_on_click(GtkButton *button, gpointer user_data);
124 static gboolean CTRL_J_K_on_press(GtkAccelGroup *group, GObject *obj,
125         guint keyval, GdkModifierType mod, gpointer user_data);
126 static gboolean input_text_view_on_key_press(GtkTextView *text_view,
127         GdkEventKey *event, gpointer user_data);
128 static void send_button_on_clicked(GtkWidget *widget, gpointer user_data);
129 static gboolean send_message_timeout(gpointer user_data);
130 
131 /*****************************************************************************
132  * GObject functions
133  *****************************************************************************/
134 
135 enum
136 {
137   // 0 for PROP_NOME
138   PROP_EVENTS = 1,
139   PROP_CONFIG,
140   N_PROPERTIES
141 };
142 
143 static GParamSpec *obj_properties[N_PROPERTIES] = { NULL, };
144 
145 G_DEFINE_TYPE(SuiWindow, sui_window, GTK_TYPE_APPLICATION_WINDOW);
146 
sui_window_set_property(GObject * object,guint property_id,const GValue * value,GParamSpec * pspec)147 static void sui_window_set_property(GObject *object, guint property_id,
148         const GValue *value, GParamSpec *pspec){
149   SuiWindow *self;
150 
151   self = SUI_WINDOW(object);
152 
153   switch (property_id){
154     case PROP_EVENTS:
155       sui_window_set_events(self, g_value_get_pointer(value));
156       break;
157     case PROP_CONFIG:
158       sui_window_set_config(self, g_value_get_pointer(value));
159       break;
160     default:
161       /* We don't have any other property... */
162       G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
163       break;
164     }
165 }
166 
sui_window_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)167 static void sui_window_get_property(GObject *object, guint property_id,
168         GValue *value, GParamSpec *pspec){
169   SuiWindow *self;
170 
171   self = SUI_WINDOW(object);
172 
173   switch (property_id){
174     case PROP_EVENTS:
175       g_value_set_pointer(value, sui_window_get_events(self));
176       break;
177     case PROP_CONFIG:
178       g_value_set_pointer(value, sui_window_get_config(self));
179       break;
180     default:
181       /* We don't have any other property... */
182       G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
183       break;
184     }
185 }
186 
sui_window_init(SuiWindow * self)187 static void sui_window_init(SuiWindow *self){
188     GClosure *closure_j;
189     GClosure *closure_k;
190     GtkAccelGroup *accel;
191 
192     gtk_widget_init_template(GTK_WIDGET(self));
193 
194     /* Bind title_paned, header_paned and main_paned */
195     g_object_bind_property(
196             self->title_paned, "position",
197             self->main_paned, "position",
198             G_BINDING_BIDIRECTIONAL);
199     g_object_bind_property(
200             self->header_paned, "position",
201             self->main_paned, "position",
202             G_BINDING_BIDIRECTIONAL);
203 
204     self->send_timer = 0;
205 
206     /* Stack side bar init */
207     self->side_bar = sui_side_bar_new();
208     gtk_box_pack_start(self->side_box, GTK_WIDGET(self->side_bar),
209             TRUE, TRUE, 0);
210     sui_side_bar_set_stack(self->side_bar, self->buffer_stack);
211     gtk_widget_show(GTK_WIDGET(self->side_bar));
212 
213     // Setup menu button
214     gtk_menu_button_set_popover(
215             self->start_menu_button,
216             GTK_WIDGET(sui_application_get_popover_menu(
217                     sui_application_get_instance())));
218 
219     /* Popover init */
220     self->connect_panel = g_object_ref(sui_connect_panel_new());
221 
222     g_signal_connect(self, "destroy",
223             G_CALLBACK(on_destroy), NULL);
224     g_signal_connect(self, "notify::is-active",
225             G_CALLBACK(on_notify_is_active), NULL);
226     g_signal_connect(self, "delete-event",
227             G_CALLBACK(on_delete_event), NULL);
228 
229     // Click to show/hide GtkPopover
230     g_signal_connect(self->connect_button, "clicked",
231             G_CALLBACK(popover_button_on_click), self->connect_panel);
232     g_signal_connect(self->join_button, "clicked",
233             G_CALLBACK(join_button_on_click), self);
234 
235     g_signal_connect(self->window_stack, "notify::visible-child",
236             G_CALLBACK(window_stack_on_child_changed), self);
237     g_signal_connect(self->buffer_stack, "notify::visible-child",
238             G_CALLBACK(buffer_stack_on_child_changed), self);
239 
240     g_signal_connect(self->input_text_view, "key-press-event",
241             G_CALLBACK(input_text_view_on_key_press), self);
242     g_signal_connect(self->send_button, "clicked",
243             G_CALLBACK(send_button_on_clicked), self);
244 
245     /* shortcut <C-j> and <C-k> */
246     accel = gtk_accel_group_new();
247 
248     closure_j = g_cclosure_new(G_CALLBACK(CTRL_J_K_on_press),
249             self->side_bar, NULL);
250     closure_k = g_cclosure_new(G_CALLBACK(CTRL_J_K_on_press),
251             self->side_bar, NULL);
252 
253     gtk_accel_group_connect(accel, GDK_KEY_j, GDK_CONTROL_MASK,
254             GTK_ACCEL_VISIBLE, closure_j);
255     gtk_accel_group_connect(accel, GDK_KEY_k, GDK_CONTROL_MASK,
256             GTK_ACCEL_VISIBLE, closure_k);
257 
258     gtk_window_add_accel_group(GTK_WINDOW(self), accel);
259 
260     g_closure_unref(closure_j);
261     g_closure_unref(closure_k);
262 
263 }
264 
sui_window_constructed(GObject * object)265 static void sui_window_constructed(GObject *object){
266     SuiWindow *self;
267 
268     self = SUI_WINDOW(object);
269     if (!self->cfg->csd){
270         gtk_widget_show(GTK_WIDGET(self->header_box));
271 
272         /* Move side header widgets from side_header_bar to side_header_box */
273         gtk_container_remove(GTK_CONTAINER(self->side_header_bar),
274                 GTK_WIDGET(self->side_left_header_box));
275         gtk_container_remove(GTK_CONTAINER(self->side_header_bar),
276                 GTK_WIDGET(self->side_right_header_box));
277         gtk_box_pack_start(self->side_header_box,
278                 GTK_WIDGET(self->side_left_header_box), TRUE, TRUE, 0);
279         gtk_box_pack_end(self->side_header_box,
280                 GTK_WIDGET(self->side_right_header_box), TRUE, TRUE, 0);
281         gtk_container_child_set(GTK_CONTAINER(self->side_header_box),
282                 GTK_WIDGET(self->side_right_header_box), "expand", FALSE, NULL);
283 
284         /* Move buffer header widgets from buffer_header_bar to buffer_header_box */
285         gtk_header_bar_set_custom_title(self->buffer_header_bar, NULL);
286         gtk_container_remove(GTK_CONTAINER(self->buffer_header_bar),
287                 GTK_WIDGET(self->buffer_menu_button));
288         gtk_box_set_center_widget(self->buffer_header_box,
289                 GTK_WIDGET(self->buffer_title_box));
290         gtk_box_pack_end(self->buffer_header_box,
291                 GTK_WIDGET(self->buffer_menu_button), TRUE, TRUE, 0);
292         gtk_container_child_set(GTK_CONTAINER(self->buffer_header_box),
293                 GTK_WIDGET(self->buffer_menu_button), "expand", FALSE, NULL);
294 
295         // Hide the titlebar node
296         gtk_window_set_titlebar(GTK_WINDOW(self), NULL);
297         // Show the seperator
298         gtk_widget_show(GTK_WIDGET(self->header_separator));
299     } else {
300         gtk_widget_hide(GTK_WIDGET(self->header_box));
301 
302         // Use appliaction icon instead of standard icon when CSD enabled
303         gtk_image_set_from_icon_name(self->start_image, PACKAGE,
304                 GTK_ICON_SIZE_BUTTON);
305     }
306     update_header(self);
307     update_title(self);
308 
309     G_OBJECT_CLASS(sui_window_parent_class)->constructed(object);
310 }
311 
sui_window_class_init(SuiWindowClass * class)312 static void sui_window_class_init(SuiWindowClass *class){
313     GObjectClass *object_class;
314     GtkWidgetClass *widget_class;
315 
316     /* Overwrite callbacks */
317     object_class = G_OBJECT_CLASS(class);
318     object_class->constructed = sui_window_constructed;
319     object_class->set_property = sui_window_set_property;
320     object_class->get_property = sui_window_get_property;
321 
322     /* Install properties */
323     obj_properties[PROP_EVENTS] =
324         g_param_spec_pointer("events",
325                 "Events",
326                 "Event callbacks of window.",
327                 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
328 
329     obj_properties[PROP_CONFIG] =
330         g_param_spec_pointer("config",
331                 "Config",
332                 "Configuration of window.",
333                 G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
334 
335     g_object_class_install_properties(object_class, N_PROPERTIES,
336             obj_properties);
337 
338     widget_class = GTK_WIDGET_CLASS(class);
339     gtk_widget_class_set_template_from_resource(widget_class, "/im/srain/Srain/window.glade");
340 
341     gtk_widget_class_bind_template_child(widget_class, SuiWindow, title_paned);
342     gtk_widget_class_bind_template_child(widget_class, SuiWindow, window_box);
343     gtk_widget_class_bind_template_child(widget_class, SuiWindow, header_separator);
344     gtk_widget_class_bind_template_child(widget_class, SuiWindow, header_box);
345     gtk_widget_class_bind_template_child(widget_class, SuiWindow, header_paned);
346     gtk_widget_class_bind_template_child(widget_class, SuiWindow, window_stack);
347 
348     gtk_widget_class_bind_template_child(widget_class, SuiWindow, side_header_bar);
349     gtk_widget_class_bind_template_child(widget_class, SuiWindow, side_header_box);
350     gtk_widget_class_bind_template_child(widget_class, SuiWindow, side_left_header_box);
351     gtk_widget_class_bind_template_child(widget_class, SuiWindow, side_right_header_box);
352     gtk_widget_class_bind_template_child(widget_class, SuiWindow, start_image);
353     gtk_widget_class_bind_template_child(widget_class, SuiWindow, start_menu_button);
354     gtk_widget_class_bind_template_child(widget_class, SuiWindow, connect_button);
355     gtk_widget_class_bind_template_child(widget_class, SuiWindow, join_button);
356 
357     gtk_widget_class_bind_template_child(widget_class, SuiWindow, buffer_header_bar);
358     gtk_widget_class_bind_template_child(widget_class, SuiWindow, buffer_header_box);
359     gtk_widget_class_bind_template_child(widget_class, SuiWindow, buffer_title_box);
360     gtk_widget_class_bind_template_child(widget_class, SuiWindow, buffer_title_label);
361     gtk_widget_class_bind_template_child(widget_class, SuiWindow, buffer_subtitle_label);
362     gtk_widget_class_bind_template_child(widget_class, SuiWindow, buffer_menu_button);
363 
364     gtk_widget_class_bind_template_child(widget_class, SuiWindow, welcome_connect_box);
365 
366     gtk_widget_class_bind_template_child(widget_class, SuiWindow, main_paned);
367     gtk_widget_class_bind_template_child(widget_class, SuiWindow, side_box);
368     gtk_widget_class_bind_template_child(widget_class, SuiWindow, buffer_stack);
369     gtk_widget_class_bind_template_child(widget_class, SuiWindow, plugin_button);
370     gtk_widget_class_bind_template_child(widget_class, SuiWindow, input_text_view);
371     gtk_widget_class_bind_template_child(widget_class, SuiWindow, send_button);
372 }
373 
374 /*****************************************************************************
375  * Exported functions
376  *****************************************************************************/
377 
sui_window_new(SuiApplication * app,SuiWindowEvents * events,SuiWindowConfig * cfg)378 SuiWindow* sui_window_new(SuiApplication *app, SuiWindowEvents *events,
379         SuiWindowConfig *cfg){
380     SuiWindow *self;
381 
382     self = g_object_new(SUI_TYPE_WINDOW,
383             "application", app,
384             "events", events,
385             "config", cfg,
386             NULL);
387 
388     return self;
389 }
390 
sui_window_add_buffer(SuiWindow * self,SuiBuffer * buf)391 void sui_window_add_buffer(SuiWindow *self, SuiBuffer *buf){
392     GString *gstr;
393 
394     if (get_buffer_count(self) == 0){
395         gtk_stack_set_visible_child_name(
396                 self->window_stack, WINDOW_STACK_PAGE_MAIN);
397     }
398 
399     gstr = g_string_new("");
400     g_string_printf(gstr, "%s %s",
401             sui_buffer_get_remark(buf),
402             sui_buffer_get_name(buf));
403     gtk_stack_add_named(self->buffer_stack, GTK_WIDGET(buf), gstr->str);
404     g_string_free(gstr, TRUE);
405 
406     gtk_stack_set_visible_child(self->buffer_stack, GTK_WIDGET(buf));
407 }
408 
sui_window_rm_buffer(SuiWindow * self,SuiBuffer * buf)409 void sui_window_rm_buffer(SuiWindow *self, SuiBuffer *buf){
410     gtk_container_remove(GTK_CONTAINER(self->buffer_stack), GTK_WIDGET(buf));
411 
412     if (get_buffer_count(self) == 0){
413         gtk_stack_set_visible_child_name(
414                 self->window_stack, WINDOW_STACK_PAGE_WELCOME);
415     }
416 }
417 
sui_window_get_cur_buffer(SuiWindow * self)418 SuiBuffer* sui_window_get_cur_buffer(SuiWindow *self){
419     SuiBuffer *buf;
420 
421     buf = SUI_BUFFER(gtk_stack_get_visible_child(self->buffer_stack));
422 
423     return buf;
424 }
425 
sui_window_set_cur_buffer(SuiWindow * self,SuiBuffer * buf)426 void sui_window_set_cur_buffer(SuiWindow *self, SuiBuffer *buf){
427     gtk_stack_set_visible_child(self->buffer_stack, GTK_WIDGET(buf));
428 }
429 
sui_window_get_buffer(SuiWindow * self,const char * name,const char * remark)430 SuiBuffer* sui_window_get_buffer(SuiWindow *self,
431         const char *name, const char *remark){
432     SuiBuffer *buf;
433 
434     GString *fullname = g_string_new("");
435     g_string_printf(fullname, "%s %s", remark, name);
436     buf = SUI_BUFFER(gtk_stack_get_child_by_name(self->buffer_stack, fullname->str));
437     g_string_free(fullname, TRUE);
438 
439     return buf;
440 }
441 
sui_window_get_side_bar(SuiWindow * self)442 SuiSideBar* sui_window_get_side_bar(SuiWindow *self){
443     return self->side_bar;
444 }
445 
sui_window_is_active(SuiWindow * self)446 int sui_window_is_active(SuiWindow *self){
447     int active;
448 
449     g_object_get(G_OBJECT(self), "is-active", &active, NULL);
450 
451     return active;
452 }
453 
sui_window_get_events(SuiWindow * self)454 SuiWindowEvents* sui_window_get_events(SuiWindow *self) {
455     return self->events;
456 }
457 
sui_window_set_config(SuiWindow * self,SuiWindowConfig * cfg)458 void sui_window_set_config(SuiWindow *self, SuiWindowConfig *cfg) {
459     self->cfg = cfg;
460 }
461 
sui_window_get_config(SuiWindow * self)462 SuiWindowConfig* sui_window_get_config(SuiWindow *self) {
463     return self->cfg;
464 }
465 
sui_window_set_title(SuiWindow * self,const char * title)466 void sui_window_set_title(SuiWindow *self, const char *title){
467     gtk_label_set_text(self->buffer_title_label, title);
468 }
469 
sui_window_set_subtitle(SuiWindow * self,const char * subtitle)470 void sui_window_set_subtitle(SuiWindow *self, const char *subtitle){
471     gtk_label_set_text(self->buffer_subtitle_label, subtitle);
472 }
473 
474 
475 /*****************************************************************************
476  * Static functions
477  *****************************************************************************/
478 
sui_window_set_events(SuiWindow * self,SuiWindowEvents * events)479 static void sui_window_set_events(SuiWindow *self, SuiWindowEvents *events) {
480     self->events = events;
481 }
482 
update_header(SuiWindow * self)483 static void update_header(SuiWindow *self){
484     const char *page;
485 
486     page = gtk_stack_get_visible_child_name(self->window_stack);
487     if (g_strcmp0(page, WINDOW_STACK_PAGE_WELCOME) == 0){
488         gtk_widget_set_visible(GTK_WIDGET(self->connect_button), FALSE);
489         gtk_widget_set_visible(GTK_WIDGET(self->join_button), FALSE);
490         if (self->cfg->csd){
491             gtk_widget_set_visible(GTK_WIDGET(self->buffer_header_bar), FALSE);
492             gtk_header_bar_set_show_close_button(self->side_header_bar, TRUE);
493         } else {
494             gtk_widget_set_visible(GTK_WIDGET(self->buffer_header_box), FALSE);
495         }
496 
497         // Add connect panel to welcome page
498         gtk_box_pack_start(self->welcome_connect_box,
499                 GTK_WIDGET(self->connect_panel), TRUE, TRUE, 0);
500     } else if (g_strcmp0(page, WINDOW_STACK_PAGE_MAIN) == 0){
501         gtk_widget_set_visible(GTK_WIDGET(self->connect_button), TRUE);
502         gtk_widget_set_visible(GTK_WIDGET(self->join_button), TRUE);
503         if (self->cfg->csd){
504             gtk_header_bar_set_show_close_button(self->side_header_bar, FALSE);
505             gtk_widget_set_visible(GTK_WIDGET(self->buffer_header_bar), TRUE);
506         } else {
507             gtk_widget_set_visible(GTK_WIDGET(self->buffer_header_box), TRUE);
508         }
509 
510         // Remove connect panel to welcome page
511         gtk_container_remove(GTK_CONTAINER(self->welcome_connect_box),
512                 GTK_WIDGET(self->connect_panel));
513     } else {
514         g_warn_if_reached();
515     }
516 }
517 
update_title(SuiWindow * self)518 static void update_title(SuiWindow *self){
519     const char *page;
520 
521     page = gtk_stack_get_visible_child_name(self->window_stack);
522     if (g_strcmp0(page, WINDOW_STACK_PAGE_WELCOME) == 0){
523         gtk_window_set_title(GTK_WINDOW(self), PACKAGE_NAME);
524         if (self->cfg->csd){
525             gtk_header_bar_set_title(self->side_header_bar, PACKAGE_NAME);
526         }
527     } else if (g_strcmp0(page, WINDOW_STACK_PAGE_MAIN) == 0){
528         char *title;
529 
530         title = g_strdup_printf("%s @ %s - %s",
531                 gtk_label_get_text(self->buffer_title_label),
532                 gtk_label_get_text(self->buffer_subtitle_label),
533                 PACKAGE_NAME);
534         gtk_window_set_title(GTK_WINDOW(self), title);
535         if (self->cfg->csd){
536             gtk_header_bar_set_title(self->side_header_bar, NULL);
537             gtk_header_bar_set_title(self->buffer_header_bar, title);
538         }
539         g_free(title);
540     } else {
541         g_warn_if_reached();
542     }
543 }
544 
update_focus(SuiWindow * self)545 static void update_focus(SuiWindow *self){
546     const char *page;
547 
548     page = gtk_stack_get_visible_child_name(self->window_stack);
549     if (g_strcmp0(page, WINDOW_STACK_PAGE_WELCOME) == 0){
550         // No need to grap focus of self->connect_panel because it isn't focusable.
551     } else if (g_strcmp0(page, WINDOW_STACK_PAGE_MAIN) == 0){
552         gtk_widget_grab_focus(GTK_WIDGET(self->input_text_view));
553     } else {
554         g_warn_if_reached();
555     }
556 }
557 
get_buffer_count(SuiWindow * self)558 static int get_buffer_count(SuiWindow *self){
559     return g_list_length(
560             gtk_container_get_children(GTK_CONTAINER(self->buffer_stack)));
561 }
562 
send_message(SuiWindow * self)563 static void send_message(SuiWindow *self){
564     g_return_if_fail(!self->send_timer);
565 
566     gtk_text_view_set_editable(self->input_text_view, FALSE); // Lock text view
567     self->send_timer = g_timeout_add(
568             SEND_MESSAGE_INTERVAL, send_message_timeout, self);
569 
570     gtk_image_set_from_icon_name(
571             GTK_IMAGE(gtk_button_get_image(self->send_button)),
572             "document-revert-symbolic", GTK_ICON_SIZE_BUTTON);
573 }
574 
send_message_cancel(SuiWindow * self)575 static void send_message_cancel(SuiWindow *self){
576     g_return_if_fail(self->send_timer);
577 
578     g_source_remove(self->send_timer);
579     self->send_timer = 0;
580     gtk_text_view_set_editable(self->input_text_view, TRUE); // Unlock text view
581 
582     gtk_image_set_from_icon_name(
583             GTK_IMAGE(gtk_button_get_image(self->send_button)),
584             "document-send-symbolic", GTK_ICON_SIZE_BUTTON);
585 }
586 
on_destroy(SuiWindow * self)587 static void on_destroy(SuiWindow *self){
588     // Nothing to do for now
589 }
590 
on_notify_is_active(GObject * object,GParamSpec * pspec,gpointer data)591 static void on_notify_is_active(GObject *object, GParamSpec *pspec,
592         gpointer data){
593     if (sui_window_is_active(SUI_WINDOW(object))){
594         /* Stop stress the icon */
595         sui_application_highlight_tray_icon(
596                 sui_application_get_instance(), FALSE);
597     }
598 }
599 
on_delete_event(GtkWidget * widget,GdkEvent * event,gpointer user_data)600 static gboolean on_delete_event(GtkWidget *widget, GdkEvent *event,
601             gpointer user_data){
602     SuiWindow *self;
603 
604     self = SUI_WINDOW(widget);
605 
606     if (self->cfg->exit_on_close) {
607         return FALSE;
608     } else {
609         gtk_widget_set_visible(widget, FALSE);
610         return TRUE;
611     }
612 }
613 
popover_button_on_click(GtkButton * button,gpointer user_data)614 static void popover_button_on_click(GtkButton *button, gpointer user_data){
615     GtkWidget *panel;
616 
617     panel = GTK_WIDGET(user_data);
618     sui_common_popup_panel(GTK_WIDGET(button), panel);
619 }
620 
join_button_on_click(GtkButton * button,gpointer user_data)621 static void join_button_on_click(GtkButton *button, gpointer user_data){
622     SuiServerBuffer *buf;
623     SuiJoinPanel *panel;
624 
625     buf = sui_common_get_cur_server_buffer();
626     g_return_if_fail(buf);
627 
628     panel = sui_server_buffer_get_join_panel(buf);
629     sui_common_popup_panel(GTK_WIDGET(button), GTK_WIDGET(panel));
630 }
631 
CTRL_J_K_on_press(GtkAccelGroup * group,GObject * obj,guint keyval,GdkModifierType mod,gpointer user_data)632 static gboolean CTRL_J_K_on_press(GtkAccelGroup *group, GObject *obj,
633         guint keyval, GdkModifierType mod, gpointer user_data){
634     SuiSideBar *side_bar;
635 
636     if (mod != GDK_CONTROL_MASK) return FALSE;
637 
638     side_bar = user_data;
639     switch (keyval){
640         case GDK_KEY_k:
641             sui_side_bar_prev(side_bar);
642             break;
643         case GDK_KEY_j:
644             sui_side_bar_next(side_bar);
645             break;
646         default:
647             ERR_FR("unknown keyval %d", keyval);
648             return FALSE;
649     }
650 
651     return TRUE;
652 }
653 
input_text_view_on_key_press(GtkTextView * text_view,GdkEventKey * event,gpointer user_data)654 static gboolean input_text_view_on_key_press(GtkTextView *text_view,
655         GdkEventKey *event, gpointer user_data){
656     SuiWindow *self;
657 
658     self = SUI_WINDOW(user_data);
659 
660     switch (event->keyval) {
661         case GDK_KEY_Tab:
662             {
663                 SuiBuffer *buf;
664 
665                 if (!gtk_text_view_get_editable(self->input_text_view)){
666                     return FALSE;
667                 }
668 
669                 buf = sui_window_get_cur_buffer(self);
670                 g_return_val_if_fail(buf, FALSE);
671                 sui_buffer_complete(buf);
672                 break;
673             }
674         case GDK_KEY_Return:
675             {
676                 if ((self->cfg->send_on_ctrl_enter)
677                         ^ (event->state & GDK_CONTROL_MASK )){
678                     // TODO: filter SHIFT, ALT and META?
679                     return FALSE;
680                 }
681                 gtk_button_clicked(self->send_button);
682                 break;
683             }
684         case GDK_KEY_Up:
685         case GDK_KEY_Down:
686             {
687                 int cursor_pos;
688                 int nline;
689                 int cursor_line;
690                 GtkTextBuffer *text_buf;
691                 GtkTextIter cursor;
692                 SuiBuffer *buf;
693 
694                 if (!gtk_text_view_get_editable(self->input_text_view)){
695                     return FALSE;
696                 }
697 
698                 buf = sui_window_get_cur_buffer(self);
699                 g_return_val_if_fail(buf, FALSE);
700                 text_buf = gtk_text_view_get_buffer(self->input_text_view);
701 
702                 g_object_get(text_buf, "cursor-position", &cursor_pos, NULL);
703                 gtk_text_buffer_get_iter_at_offset(text_buf, &cursor, cursor_pos);
704                 cursor_line = gtk_text_iter_get_line(&cursor);
705                 nline = gtk_text_buffer_get_line_count(text_buf);
706 
707                 if (event->keyval == GDK_KEY_Up){
708                     if (cursor_line != 0) {
709                         return FALSE;
710                     }
711                     sui_buffer_browse_prev_input(buf);
712                 } else {
713                     if (cursor_line != nline - 1) {
714                         return FALSE;
715                     }
716                     sui_buffer_browse_next_input(buf);
717                 }
718             }
719             break;
720         default:
721             return FALSE;
722     }
723 
724     return TRUE;
725 }
726 
send_button_on_clicked(GtkWidget * widget,gpointer user_data)727 static void send_button_on_clicked(GtkWidget *widget, gpointer user_data){
728     SuiWindow *self;
729 
730     self = SUI_WINDOW(user_data);
731 
732     if (self->send_timer == 0) {
733         send_message(self);
734     } else {
735         send_message_cancel(self);
736     }
737 }
738 
send_message_timeout(gpointer user_data)739 static gboolean send_message_timeout(gpointer user_data){
740     SuiWindow *self;
741     SuiBuffer *buf;
742 
743     self = SUI_WINDOW(user_data);
744     buf = sui_window_get_cur_buffer(self);
745 
746     if (!sui_buffer_send_input(buf)){
747         send_message_cancel(self);
748         return G_SOURCE_REMOVE;
749     }
750 
751     return G_SOURCE_CONTINUE;
752 }
753 
window_stack_on_child_changed(GtkWidget * widget,GParamSpec * pspec,gpointer user_data)754 static void window_stack_on_child_changed(GtkWidget *widget, GParamSpec *pspec,
755         gpointer user_data){
756     SuiWindow *self;
757 
758     self = user_data;
759     update_header(self);
760     update_title(self);
761     update_focus(self);
762 }
763 
buffer_stack_on_child_changed(GtkWidget * widget,GParamSpec * pspec,gpointer user_data)764 static void buffer_stack_on_child_changed(GtkWidget *widget, GParamSpec *pspec,
765         gpointer user_data){
766     SuiWindow *self;
767     SuiBuffer *buf;
768 
769     self = user_data;
770 
771     // Cancel sending message when current buffer is replaced
772     if (self->send_timer){
773         send_message_cancel(self);
774     }
775 
776     buf = sui_window_get_cur_buffer(self);
777     if (!SUI_IS_BUFFER(buf)){
778         return;
779     }
780 
781     sui_window_set_title(self, sui_buffer_get_name(buf));
782     sui_window_set_subtitle(self, sui_buffer_get_remark(buf));
783     update_title(self);
784     gtk_text_view_set_buffer(self->input_text_view,
785             sui_buffer_get_input_text_buffer(buf));
786     gtk_menu_button_set_popup(self->buffer_menu_button,
787             GTK_WIDGET(sui_buffer_get_menu(buf)));
788 }
789