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