chatty-window.c 29.3 KB
Newer Older
Andrea Schaefer's avatar
Andrea Schaefer committed
1
2
3
/*
 * Copyright (C) 2018 Purism SPC
 *
4
 * SPDX-License-Identifier: GPL-3.0-or-later
Andrea Schaefer's avatar
Andrea Schaefer committed
5
6
 */

7
#define G_LOG_DOMAIN "chatty-window"
Andrea Schaefer's avatar
Andrea Schaefer committed
8

9
10
11
12
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

13
14
#define _GNU_SOURCE
#include <string.h>
Andrea Schaefer's avatar
Andrea Schaefer committed
15
16
#include <glib.h>
#include <glib/gi18n.h>
Andrea Schaefer's avatar
Andrea Schaefer committed
17
#include <glib-object.h>
Mohammed Sadiq's avatar
Mohammed Sadiq committed
18
#include "contrib/gtk.h"
19

Andrea Schaefer's avatar
Andrea Schaefer committed
20
#include "chatty-window.h"
21
#include "chatty-history.h"
22
#include "chatty-avatar.h"
23
#include "chatty-manager.h"
Mohammed Sadiq's avatar
Mohammed Sadiq committed
24
#include "chatty-list-row.h"
Andrea Schaefer's avatar
Andrea Schaefer committed
25
#include "chatty-settings.h"
26
#include "chatty-mm-chat.h"
Mohammed Sadiq's avatar
Mohammed Sadiq committed
27
#include "chatty-pp-chat.h"
Mohammed Sadiq's avatar
Mohammed Sadiq committed
28
#include "chatty-chat-view.h"
29
#include "chatty-manager.h"
30
#include "chatty-utils.h"
31
32
#include "matrix/chatty-ma-account.h"
#include "matrix/chatty-ma-chat.h"
33
#include "dialogs/chatty-info-dialog.h"
34
35
36
#include "dialogs/chatty-settings-dialog.h"
#include "dialogs/chatty-new-chat-dialog.h"
#include "dialogs/chatty-new-muc-dialog.h"
37
#include "chatty-log.h"
Andrea Schaefer's avatar
Andrea Schaefer committed
38

39
40
41
struct _ChattyWindow
{
  GtkApplicationWindow parent_instance;
Andrea Schaefer's avatar
Andrea Schaefer committed
42

43
  ChattySettings *settings;
Andrea Schaefer's avatar
Andrea Schaefer committed
44

45
46
47
  GtkWidget *sidebar_stack;
  GtkWidget *empty_view;
  GtkWidget *chat_list_view;
48
49
50
51
52
  GtkWidget *chats_listbox;

  GtkWidget *content_box;
  GtkWidget *header_box;
  GtkWidget *header_group;
53

54
55
  GtkWidget *sub_header_icon;
  GtkWidget *sub_header_label;
56

57
  GtkWidget *new_chat_dialog;
58
  GtkWidget *chat_info_dialog;
59

60
  GtkWidget *search_button;
61
62
  GtkWidget *chats_search_bar;
  GtkWidget *chats_search_entry;
63

64
65
  GtkWidget *header_chat_list_new_msg_popover;

66
  GtkWidget *menu_add_contact_button;
67
68
  GtkWidget *menu_new_message_button;
  GtkWidget *menu_new_group_message_button;
69
  GtkWidget *header_add_chat_button;
70
  GtkWidget *call_button;
71
  GtkWidget *header_sub_menu_button;
72
  GtkWidget *leave_button;
73
  GtkWidget *delete_button;
74

75
  GtkWidget *chat_view;
Mohammed Sadiq's avatar
Mohammed Sadiq committed
76

Mohammed Sadiq's avatar
Mohammed Sadiq committed
77
  ChattyItem    *selected_item;
78
  ChattyManager *manager;
Mohammed Sadiq's avatar
Mohammed Sadiq committed
79
80
81
82

  char          *chat_needle;
  GtkFilter     *chat_filter;
  GtkFilterListModel *filter_model;
Andrea Schaefer's avatar
Andrea Schaefer committed
83
84
85
};


86
87
G_DEFINE_TYPE (ChattyWindow, chatty_window, GTK_TYPE_APPLICATION_WINDOW)

88
89
static void chatty_window_chat_list_select_first (ChattyWindow *self);

Mohammed Sadiq's avatar
Mohammed Sadiq committed
90
91
92
93
94
95
96
97
98
99
100
101
102
103
static void
window_set_item (ChattyWindow *self,
                 ChattyItem   *item)
{
  const char *header_label = "";

  g_assert (CHATTY_IS_WINDOW (self));

  if (CHATTY_IS_ITEM (item))
    header_label = chatty_item_get_name (item);

  self->selected_item = item;
  chatty_avatar_set_item (CHATTY_AVATAR (self->sub_header_icon), item);
  gtk_label_set_label (GTK_LABEL (self->sub_header_label), header_label);
104
105
106

  if (!item)
    hdy_leaflet_set_visible_child_name (HDY_LEAFLET (self->content_box), "sidebar");
Mohammed Sadiq's avatar
Mohammed Sadiq committed
107
108
}

109
110
111
112
113
114
115
116
117
static void
chatty_window_update_sidebar_view (ChattyWindow *self)
{
  GtkWidget *current_view;
  GListModel *model;
  gboolean has_child;

  g_assert (CHATTY_IS_WINDOW (self));

Mohammed Sadiq's avatar
Mohammed Sadiq committed
118
  model = G_LIST_MODEL (self->filter_model);
119
120
121
122
123
124
125
126
127
128
129
130
131
132
  has_child = g_list_model_get_n_items (model) > 0;

  if (has_child)
    current_view = self->chat_list_view;
  else
    current_view = self->empty_view;

  gtk_widget_set_sensitive (self->search_button, has_child);
  gtk_stack_set_visible_child (GTK_STACK (self->sidebar_stack), current_view);

  if (!has_child)
    hdy_search_bar_set_search_mode (HDY_SEARCH_BAR (self->chats_search_bar), FALSE);
}

133
static void
Mohammed Sadiq's avatar
Mohammed Sadiq committed
134
window_chat_changed_cb (ChattyWindow *self)
135
{
Mohammed Sadiq's avatar
Mohammed Sadiq committed
136
137
  GListModel *model;
  gboolean has_child;
138

Mohammed Sadiq's avatar
Mohammed Sadiq committed
139
140
  g_assert (CHATTY_IS_WINDOW (self));

Mohammed Sadiq's avatar
Mohammed Sadiq committed
141
  model = G_LIST_MODEL (self->filter_model);
Mohammed Sadiq's avatar
Mohammed Sadiq committed
142
  has_child = g_list_model_get_n_items (model) > 0;
143

144
  gtk_widget_set_sensitive (self->header_sub_menu_button, !!self->selected_item);
145

146
147
148
149
150
  /* Select the first item if none selected and we are in expanded mode */
  if (!self->selected_item &&
      gtk_list_box_get_selection_mode (GTK_LIST_BOX (self->chats_listbox)) == GTK_SELECTION_SINGLE)
    chatty_window_chat_list_select_first (self);

Mohammed Sadiq's avatar
Mohammed Sadiq committed
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
  /*
   * When the items are re-arranged, the selection will be lost.
   * Re-select it.  In GTK4, A #GtkListView with #GtkSingleSelection
   * would suite here better.
   */
  if (self->selected_item && has_child) {
    guint position;

    if (chatty_utils_get_item_position (G_LIST_MODEL (self->filter_model), self->selected_item, &position)) {
      GtkListBoxRow *row;

      row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (self->chats_listbox), position);
      gtk_list_box_select_row (GTK_LIST_BOX (self->chats_listbox), row);
    }
  }
166

167
168
  chatty_window_update_sidebar_view (self);

Mohammed Sadiq's avatar
Mohammed Sadiq committed
169
  if (has_child)
170
171
    return;

172
173
174
175
176
177
178
179
  if (chatty_manager_get_active_protocols (self->manager))
    hdy_status_page_set_description (HDY_STATUS_PAGE (self->empty_view),
                                     _("Select a contact with the "
                                       "<b>“+”</b> button in the titlebar."));
  else
    hdy_status_page_set_description (HDY_STATUS_PAGE (self->empty_view),
                                     _("For <b>Instant Messaging</b> add or activate "
                                       "an account in <i>\"preferences\"</i>."));
Mohammed Sadiq's avatar
Mohammed Sadiq committed
180
}
181

Mohammed Sadiq's avatar
Mohammed Sadiq committed
182
183
184
static gboolean
window_chat_name_matches (ChattyItem   *item,
                          ChattyWindow *self)
185
{
186
  ChattyProtocol protocol;
187

Mohammed Sadiq's avatar
Mohammed Sadiq committed
188
189
190
  g_assert (CHATTY_IS_CHAT (item));
  g_assert (CHATTY_IS_WINDOW (self));

Mohammed Sadiq's avatar
Mohammed Sadiq committed
191
  protocol = chatty_item_get_protocols (item);
192

193
194
  if ((!self->chat_needle || !*self->chat_needle) &&
      CHATTY_IS_MM_CHAT (item))
Mohammed Sadiq's avatar
Mohammed Sadiq committed
195
196
    return TRUE;

Mohammed Sadiq's avatar
Mohammed Sadiq committed
197
  /* FIXME: Not a good idea */
198
  if (chatty_item_get_protocols (item) != CHATTY_PROTOCOL_MMS_SMS) {
199
    ChattyAccount *account;
200

Mohammed Sadiq's avatar
Mohammed Sadiq committed
201
202
    if (CHATTY_IS_PP_CHAT (item) &&
        !chatty_pp_chat_get_auto_join (CHATTY_PP_CHAT (item)))
203
204
      return FALSE;

205
    account = chatty_chat_get_account (CHATTY_CHAT (item));
Mohammed Sadiq's avatar
Mohammed Sadiq committed
206

207
    if (!account || chatty_account_get_status (account) != CHATTY_CONNECTED)
Mohammed Sadiq's avatar
Mohammed Sadiq committed
208
209
210
      return FALSE;
  }

Mohammed Sadiq's avatar
Mohammed Sadiq committed
211
212
  if (protocol != CHATTY_PROTOCOL_MATRIX &&
      hdy_leaflet_get_folded (HDY_LEAFLET (self->header_box))) {
213
214
215
216
217
218
219
220
221
222
223
    GListModel *message_list;
    guint n_items;

    message_list = chatty_chat_get_messages (CHATTY_CHAT (item));
    n_items = g_list_model_get_n_items (message_list);

    if (n_items == 0)
      return FALSE;
  }


Mohammed Sadiq's avatar
Mohammed Sadiq committed
224
225
226
227
228
229
  if (!self->chat_needle || !*self->chat_needle)
    return TRUE;

  return chatty_item_matches (item, self->chat_needle,
                              CHATTY_PROTOCOL_ANY, TRUE);
}
230
231


Mohammed Sadiq's avatar
Mohammed Sadiq committed
232
233
234
235
static void
chatty_window_open_item (ChattyWindow *self,
                         ChattyItem   *item)
{
236
  ChattyChat *chat = NULL;
Mohammed Sadiq's avatar
Mohammed Sadiq committed
237
238
239

  g_assert (CHATTY_IS_WINDOW (self));
  g_assert (CHATTY_IS_ITEM (item));
240
241
242
  g_debug ("opening item of type: %s, protocol: %d",
           G_OBJECT_TYPE_NAME (item),
           chatty_item_get_protocols (item));
Mohammed Sadiq's avatar
Mohammed Sadiq committed
243
244
245
246

  if (CHATTY_IS_CONTACT (item)) {
    const char *number;

247
    number = chatty_item_get_username (item);
248
    chatty_window_set_uri (self, number);
249
250
251
252

    return;
  }

253
254
255
256
257
258
259
260
261
  if (CHATTY_IS_PP_BUDDY (item)) {
    chat = g_object_get_data (G_OBJECT (item), "chat");

    if (!chat) {
      gboolean has_encryption;

      has_encryption = chatty_manager_lurch_plugin_is_loaded (self->manager);
      chat = (ChattyChat *)chatty_pp_chat_new_buddy_chat (CHATTY_PP_BUDDY (item),
                                                          has_encryption);
262
      chatty_manager_add_chat (self->manager, chat);
263
264
    }
  }
265

Mohammed Sadiq's avatar
Mohammed Sadiq committed
266
  if (CHATTY_IS_PP_CHAT (item))
267
    chat = CHATTY_CHAT (item);
268

269
270
  if (CHATTY_IS_PP_CHAT (chat)) {
    chatty_pp_chat_join (CHATTY_PP_CHAT (chat));
271

Mohammed Sadiq's avatar
Mohammed Sadiq committed
272
273
    gtk_filter_changed (self->chat_filter, GTK_FILTER_CHANGE_DIFFERENT);
    window_chat_changed_cb (self);
274
  }
275
276
277
278

  if (CHATTY_IS_MM_CHAT (item)) {
    chatty_window_open_chat (CHATTY_WINDOW (self), CHATTY_CHAT (item));
  }
279
280
281
}

static void
Mohammed Sadiq's avatar
Mohammed Sadiq committed
282
283
284
window_chat_row_activated_cb (GtkListBox    *box,
                              GtkListBoxRow *row,
                              ChattyWindow  *self)
285
{
Mohammed Sadiq's avatar
Mohammed Sadiq committed
286
  g_assert (CHATTY_WINDOW (self));
287

Mohammed Sadiq's avatar
Mohammed Sadiq committed
288
  window_set_item (self, chatty_list_row_get_item (CHATTY_LIST_ROW (row)));
289

Mohammed Sadiq's avatar
Mohammed Sadiq committed
290
  g_return_if_fail (CHATTY_IS_CHAT (self->selected_item));
291

292
293
294
295
  if (CHATTY_IS_PP_CHAT (self->selected_item))
    chatty_window_open_item (self, self->selected_item);
  else
    chatty_window_open_chat (self, CHATTY_CHAT (self->selected_item));
296
}
297

298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
static void
window_call_button_clicked_cb (ChattyWindow *self)
{
  g_autoptr(GError) error = NULL;
  g_autofree char *uri = NULL;

  g_assert (CHATTY_IS_WINDOW (self));
  g_return_if_fail (CHATTY_IS_MM_CHAT (self->selected_item));

  uri = g_strconcat ("tel://",
                     chatty_chat_get_chat_name (CHATTY_CHAT (self->selected_item)),
                     NULL);

  if (!gtk_show_uri_on_window (NULL, uri, GDK_CURRENT_TIME, &error))
    g_warning ("Failed to launch call: %s", error->message);
}

Mohammed Sadiq's avatar
Mohammed Sadiq committed
315
316
317
318
319
320
321
322
323
324
325
326
static void
window_search_changed_cb (ChattyWindow *self,
                          GtkEntry     *entry)
{
  g_assert (CHATTY_IS_WINDOW (self));

  g_free (self->chat_needle);
  self->chat_needle = g_strdup (gtk_entry_get_text (entry));

  gtk_filter_changed (self->chat_filter, GTK_FILTER_CHANGE_DIFFERENT);
}

327
static void
328
notify_fold_cb (ChattyWindow *self)
329
{
Mohammed Sadiq's avatar
Mohammed Sadiq committed
330
  gboolean folded = hdy_leaflet_get_folded (HDY_LEAFLET (self->header_box));
331

Mohammed Sadiq's avatar
Mohammed Sadiq committed
332
  if (folded)
333
334
335
336
    gtk_list_box_set_selection_mode (GTK_LIST_BOX (self->chats_listbox), GTK_SELECTION_NONE);
  else
    gtk_list_box_set_selection_mode (GTK_LIST_BOX (self->chats_listbox), GTK_SELECTION_SINGLE);

Mohammed Sadiq's avatar
Mohammed Sadiq committed
337
  if (folded) {
Mohammed Sadiq's avatar
Mohammed Sadiq committed
338
    window_set_item (self, NULL);
339
340
  } else if (self->selected_item) {
    window_chat_changed_cb (self);
341
  } else {
Andrea Schaefer's avatar
Andrea Schaefer committed
342
    chatty_window_chat_list_select_first (self);
343
  }
344

345
  gtk_filter_changed (self->chat_filter, GTK_FILTER_CHANGE_DIFFERENT);
346
347
348
}


349
static void
350
window_new_message_clicked_cb (ChattyWindow *self)
351
{
Mohammed Sadiq's avatar
Mohammed Sadiq committed
352
353
  ChattyNewChatDialog *dialog;
  ChattyItem *item;
354
  const char *phone_number = NULL;
Mohammed Sadiq's avatar
Mohammed Sadiq committed
355
356
  gint response;

357
358
  g_assert (CHATTY_IS_WINDOW (self));

Mohammed Sadiq's avatar
Mohammed Sadiq committed
359
360
361
362
363
364
  response = gtk_dialog_run (GTK_DIALOG (self->new_chat_dialog));
  gtk_widget_hide (self->new_chat_dialog);

  if (response != GTK_RESPONSE_OK)
    return;

365

Mohammed Sadiq's avatar
Mohammed Sadiq committed
366
367
368
  dialog = CHATTY_NEW_CHAT_DIALOG (self->new_chat_dialog);
  item = chatty_new_chat_dialog_get_selected_item (dialog);

Mohammed Sadiq's avatar
Mohammed Sadiq committed
369
370
  if (CHATTY_IS_CHAT (item))
    window_set_item (self, item);
371

372
373
  if (CHATTY_IS_CONTACT (item) &&
      chatty_contact_is_dummy (CHATTY_CONTACT (item)))
374
    phone_number = chatty_item_get_username (item);
375
376

  if (phone_number)
Mohammed Sadiq's avatar
Mohammed Sadiq committed
377
    chatty_window_set_uri (self, phone_number);
378
379
380
381
  else if (item)
    chatty_window_open_item (self, item);
  else
    g_return_if_reached ();
382
383
384
}


385
386
387
static void
window_new_muc_clicked_cb (ChattyWindow *self)
{
Mohammed Sadiq's avatar
Mohammed Sadiq committed
388
389
  GtkWidget *dialog;

390
391
  g_assert (CHATTY_IS_WINDOW (self));

Mohammed Sadiq's avatar
Mohammed Sadiq committed
392
393
394
395
  dialog = chatty_new_muc_dialog_new (GTK_WINDOW (self));
  gtk_dialog_run (GTK_DIALOG (dialog));

  gtk_widget_destroy (dialog);
396
397
398
399
400
401
402
403
}


static void
window_add_chat_button_clicked_cb (ChattyWindow *self)
{
  g_assert (CHATTY_IS_WINDOW (self));

404
  if (chatty_manager_get_active_protocols (self->manager) == CHATTY_PROTOCOL_MMS_SMS)
405
    window_new_message_clicked_cb (self);
406
407
  else
    gtk_popover_popup (GTK_POPOVER (self->header_chat_list_new_msg_popover));
408
409
410
}


411
412
413
414
415
static void
window_back_clicked_cb (ChattyWindow *self)
{
  g_assert (CHATTY_IS_WINDOW (self));

Mohammed Sadiq's avatar
Mohammed Sadiq committed
416
  window_set_item (self, NULL);
417

418
419
420
421
422
423
424
425
  /*
   * Clears 'selected_node' which is evaluated to
   * block the counting of pending messages
   * while chatting with this node
   */
  gtk_list_box_unselect_all (GTK_LIST_BOX (self->chats_listbox));
}

426
static void
Andrea Schaefer's avatar
Andrea Schaefer committed
427
428
429
430
431
432
433
434
435
436
chatty_window_chat_list_select_first (ChattyWindow *self)
{
  GtkListBoxRow *row;

  row = gtk_list_box_get_row_at_index (GTK_LIST_BOX(self->chats_listbox), 0);

  if (row != NULL) {
    gtk_list_box_select_row (GTK_LIST_BOX(self->chats_listbox), row);
    window_chat_row_activated_cb (GTK_LIST_BOX(self->chats_listbox), row, self);
  } else {
437
    window_set_item (self, NULL);
Andrea Schaefer's avatar
Andrea Schaefer committed
438
439
440
  }
}

441
static void
442
window_delete_buddy_clicked_cb (ChattyWindow *self)
443
{
444
445
446
447
448
  GtkWidget *dialog;
  const char *name;
  const char *text;
  const char *sub_text;
  int response;
449

450
  g_assert (CHATTY_IS_WINDOW (self));
451
  g_return_if_fail (self->selected_item);
452

453
  name = chatty_item_get_name (CHATTY_ITEM (self->selected_item));
454

455
  if (chatty_chat_is_im (CHATTY_CHAT (self->selected_item))) {
456
457
    text = _("Delete chat with");
    sub_text = _("This deletes the conversation history");
458
459
460
  } else {
    text = _("Disconnect group chat");
    sub_text = _("This removes chat from chats list");
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
  }

  dialog = gtk_message_dialog_new (GTK_WINDOW (self),
                                   GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
                                   GTK_MESSAGE_QUESTION,
                                   GTK_BUTTONS_NONE,
                                   "%s %s",
                                   text, name);

  gtk_dialog_add_buttons (GTK_DIALOG (dialog),
                          _("Cancel"),
                          GTK_RESPONSE_CANCEL,
                          _("Delete"),
                          GTK_RESPONSE_OK,
                          NULL);

  gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
                                            "%s",
                                            sub_text);

  gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL);
  gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER_ON_PARENT);

  response = gtk_dialog_run (GTK_DIALOG (dialog));

  if (response == GTK_RESPONSE_OK) {
Mohammed Sadiq's avatar
Mohammed Sadiq committed
487
488
    chatty_history_delete_chat (chatty_manager_get_history (self->manager),
                                CHATTY_CHAT (self->selected_item));
489
490
    if (CHATTY_IS_PP_CHAT (self->selected_item)) {
      chatty_pp_chat_delete (CHATTY_PP_CHAT (self->selected_item));
491
492
    } else if (CHATTY_IS_MM_CHAT (self->selected_item)) {
      chatty_mm_chat_delete (CHATTY_MM_CHAT (self->selected_item));
493
    } else {
494
      g_return_if_reached ();
495
    }
496

497
    window_set_item (self, NULL);
498
499
    if (!hdy_leaflet_get_folded (HDY_LEAFLET (self->content_box)))
      chatty_window_chat_list_select_first (self);
500
501
502
  }

  gtk_widget_destroy (dialog);
503
504
505
506
}


static void
507
window_leave_chat_clicked_cb (ChattyWindow *self)
508
{
509
  g_assert (CHATTY_IS_WINDOW (self));
510

511
  if (self->selected_item) {
512
513
514
    ChattyAccount *account;

    account = chatty_chat_get_account (CHATTY_CHAT (self->selected_item));
515
516
517
    chatty_account_leave_chat_async (account,
                                     CHATTY_CHAT (self->selected_item),
                                     NULL, NULL);
518
519
  }

Mohammed Sadiq's avatar
Mohammed Sadiq committed
520
  window_set_item (self, NULL);
521
  chatty_window_chat_list_select_first (self);
522
523
}

524
525
526
527
528
529
static void
write_contact_cb (GObject      *object,
                  GAsyncResult *result,
                  gpointer      user_data)
{
  g_autoptr(ChattyWindow) self = user_data;
530
  ChattyPpChat *chat = CHATTY_PP_CHAT (object);
531
532
533
534
  g_autoptr(GError) error = NULL;
  GtkWidget *dialog;

  g_assert (CHATTY_IS_WINDOW (self));
535
  g_assert (CHATTY_IS_PP_CHAT (chat));
536

537
  if (chatty_pp_chat_save_to_contacts_finish (chat, result, &error))
538
539
540
541
542
543
544
545
546
547
    return;

  dialog = gtk_message_dialog_new (GTK_WINDOW (self),
                                   GTK_DIALOG_MODAL,
                                   GTK_MESSAGE_WARNING,
                                   GTK_BUTTONS_CLOSE,
                                   _("Error saving contact: %s"), error->message);
  gtk_dialog_run (GTK_DIALOG (dialog));
  gtk_widget_destroy (dialog);
}
548

549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
static void
write_eds_contact_cb (GObject      *object,
                      GAsyncResult *result,
                      gpointer      user_data)
{
  g_autoptr(ChattyWindow) self = user_data;
  g_autoptr(GError) error = NULL;
  GtkWidget *dialog;

  g_assert (CHATTY_IS_WINDOW (self));

  if (chatty_eds_write_contact_finish (result, &error))
    return;

  dialog = gtk_message_dialog_new (GTK_WINDOW (self),
                                   GTK_DIALOG_MODAL,
                                   GTK_MESSAGE_WARNING,
                                   GTK_BUTTONS_CLOSE,
                                   _("Error saving contact: %s"), error->message);
  gtk_dialog_run (GTK_DIALOG (dialog));
  gtk_widget_destroy (dialog);
}

572
static void
573
window_add_contact_clicked_cb (ChattyWindow *self)
574
{
575
  g_assert (CHATTY_IS_WINDOW (self));
Mohammed Sadiq's avatar
Mohammed Sadiq committed
576
  g_return_if_fail (self->selected_item);
577

578
579
580
581
582
  if (CHATTY_IS_PP_CHAT (self->selected_item)) {
    chatty_pp_chat_save_to_contacts_async (CHATTY_PP_CHAT (self->selected_item),
                                           write_contact_cb, g_object_ref (self));
  } else if (CHATTY_IS_MM_CHAT (self->selected_item)) {
    const char *phone;
583

584
585
586
587
588
    phone = chatty_chat_get_chat_name (CHATTY_CHAT (self->selected_item));
    chatty_eds_write_contact_async ("", phone,
                                    write_eds_contact_cb,
                                    g_object_ref (self));
  }
589
590

  gtk_widget_hide (self->menu_add_contact_button);
591
592
}

Andrea Schaefer's avatar
Andrea Schaefer committed
593
static void
594
window_show_chat_info_clicked_cb (ChattyWindow *self)
Andrea Schaefer's avatar
Andrea Schaefer committed
595
{
596
  ChattyInfoDialog *dialog;
Andrea Schaefer's avatar
Andrea Schaefer committed
597

598
  g_assert (CHATTY_IS_WINDOW (self));
599
  g_return_if_fail (CHATTY_IS_CHAT (self->selected_item));
Andrea Schaefer's avatar
Andrea Schaefer committed
600

601
  dialog = CHATTY_INFO_DIALOG (self->chat_info_dialog);
Andrea Schaefer's avatar
Andrea Schaefer committed
602

603
  chatty_info_dialog_set_chat (dialog, CHATTY_CHAT (self->selected_item));
604
605
  gtk_dialog_run (GTK_DIALOG (dialog));
}
Andrea Schaefer's avatar
Andrea Schaefer committed
606

607
static void
Andrea Schaefer's avatar
Andrea Schaefer committed
608
chatty_window_show_settings_dialog (ChattyWindow *self)
609
{
Mohammed Sadiq's avatar
Mohammed Sadiq committed
610
  GtkWidget *dialog;
611

Andrea Schaefer's avatar
Andrea Schaefer committed
612
  g_assert (CHATTY_IS_WINDOW (self));
Andrea Schaefer's avatar
Andrea Schaefer committed
613

Andrea Schaefer's avatar
Andrea Schaefer committed
614
615
  dialog = chatty_settings_dialog_new (GTK_WINDOW (self));
  gtk_dialog_run (GTK_DIALOG (dialog));
616

Mohammed Sadiq's avatar
Mohammed Sadiq committed
617
  gtk_widget_destroy (dialog);
618
}
Andrea Schaefer's avatar
Andrea Schaefer committed
619

Mohammed Sadiq's avatar
Mohammed Sadiq committed
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
/* Copied from chatty-dialogs.c written by Andrea Schäfer <mosibasu@me.com> */
static void
chatty_window_show_about_dialog (ChattyWindow *self)
{
  static const gchar *authors[] = {
    "Adrien Plazas <kekun.plazas@laposte.net>",
    "Andrea Schäfer <mosibasu@me.com>",
    "Benedikt Wildenhain <benedikt.wildenhain@hs-bochum.de>",
    "Guido Günther <agx@sigxcpu.org>",
    "Julian Sparber <jsparber@gnome.org>",
    "Leland Carlye <leland.carlye@protonmail.com>",
    "Mohammed Sadiq https://www.sadiqpk.org/",
    "Richard Bayerle (OMEMO Plugin) https://github.com/gkdr/lurch",
    "Ruslan Marchenko <me@ruff.mobi>",
    "and more...",
    NULL
  };

  static const gchar *artists[] = {
    "Tobias Bernard <tbernard@gnome.org>",
    NULL
  };

  static const gchar *documenters[] = {
    "Heather Ellsworth <heather.ellsworth@puri.sm>",
    NULL
  };

648
649
650
651
652
  /*
   * “program-name” defaults to g_get_application_name().
   * Don’t set it explicitly so that there is one less
   * string to translate.
   */
Mohammed Sadiq's avatar
Mohammed Sadiq committed
653
654
655
656
657
  gtk_show_about_dialog (GTK_WINDOW (self),
                         "logo-icon-name", CHATTY_APP_ID,
                         "version", GIT_VERSION,
                         "comments", _("An SMS and XMPP messaging client"),
                         "website", "https://source.puri.sm/Librem5/chatty",
Mohammed Sadiq's avatar
Mohammed Sadiq committed
658
                         "copyright", "© 2018–2021 Purism SPC",
Mohammed Sadiq's avatar
Mohammed Sadiq committed
659
660
661
662
663
664
665
666
                         "license-type", GTK_LICENSE_GPL_3_0,
                         "authors", authors,
                         "artists", artists,
                         "documenters", documenters,
                         "translator-credits", _("translator-credits"),
                         NULL);
}

667
668
669
670
671
672
673
674
675
static void
window_active_protocols_changed_cb (ChattyWindow *self)
{
  ChattyProtocol protocols;
  gboolean has_sms, has_im;

  g_assert (CHATTY_IS_WINDOW (self));

  protocols = chatty_manager_get_active_protocols (self->manager);
676
677
  has_sms = !!(protocols & CHATTY_PROTOCOL_MMS_SMS);
  has_im  = !!(protocols & ~CHATTY_PROTOCOL_MMS_SMS);
678
679

  gtk_widget_set_sensitive (self->header_add_chat_button, has_sms || has_im);
680
  gtk_widget_set_sensitive (self->menu_new_group_message_button, has_im);
Mohammed Sadiq's avatar
Mohammed Sadiq committed
681

Mohammed Sadiq's avatar
Mohammed Sadiq committed
682
  gtk_filter_changed (self->chat_filter, GTK_FILTER_CHANGE_DIFFERENT);
683
  window_chat_changed_cb (self);
684
685
}

686
687
688
689
690
691
692
static void
window_chat_deleted_cb (ChattyWindow *self,
                        ChattyChat   *chat)
{
  g_assert (CHATTY_IS_WINDOW (self));
  g_assert (CHATTY_IS_CHAT (chat));

693
694
  if (self->selected_item != (gpointer)chat)
    return;
695

696
697
  window_set_item (self, NULL);
  chatty_chat_view_set_chat (CHATTY_CHAT_VIEW (self->chat_view), NULL);
698
}
699

700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
static void
chatty_window_unmap (GtkWidget *widget)
{
  ChattyWindow *self = (ChattyWindow *)widget;
  GtkWindow    *window = (GtkWindow *)widget;
  GdkRectangle  geometry;
  gboolean      is_maximized;

  is_maximized = gtk_window_is_maximized (window);

  chatty_settings_set_window_maximized (self->settings, is_maximized);

  if (!is_maximized) {
    gtk_window_get_size (window, &geometry.width, &geometry.height);
    chatty_settings_set_window_geometry (self->settings, &geometry);
  }

  GTK_WIDGET_CLASS (chatty_window_parent_class)->unmap (widget);
}


Andrea Schaefer's avatar
Andrea Schaefer committed
721
722
static void
chatty_window_constructed (GObject *object)
Andrea Schaefer's avatar
Andrea Schaefer committed
723
{
Andrea Schaefer's avatar
Andrea Schaefer committed
724
725
  ChattyWindow *self = (ChattyWindow *)object;
  GtkWindow    *window = (GtkWindow *)object;
726
  GdkRectangle  geometry;
Andrea Schaefer's avatar
Andrea Schaefer committed
727

Mohammed Sadiq's avatar
Mohammed Sadiq committed
728
  self->settings = g_object_ref (chatty_settings_get_default ());
729
730
731
732
733
  chatty_settings_get_window_geometry (self->settings, &geometry);
  gtk_window_set_default_size (window, geometry.width, geometry.height);

  if (chatty_settings_get_window_maximized (self->settings))
    gtk_window_maximize (window);
734

735
  self->new_chat_dialog = chatty_new_chat_dialog_new (GTK_WINDOW (self));
736
  self->chat_info_dialog = chatty_info_dialog_new (GTK_WINDOW (self));
737

Mohammed Sadiq's avatar
Mohammed Sadiq committed
738
739
740
741
742
743
744
  self->chat_filter = gtk_custom_filter_new ((GtkCustomFilterFunc)window_chat_name_matches,
                                             g_object_ref (self),
                                             g_object_unref);
  self->filter_model = gtk_filter_list_model_new (chatty_manager_get_chat_list (self->manager),
                                                  self->chat_filter);
  gtk_list_box_bind_model (GTK_LIST_BOX (self->chats_listbox),
                           G_LIST_MODEL (self->filter_model),
745
                           (GtkListBoxCreateWidgetFunc)chatty_list_row_new,
746
                           g_object_ref(self), g_object_unref);
Mohammed Sadiq's avatar
Mohammed Sadiq committed
747

748
  g_signal_connect_object (gtk_filter_list_model_get_model (self->filter_model),
Mohammed Sadiq's avatar
Mohammed Sadiq committed
749
750
751
                           "items-changed",
                           G_CALLBACK (window_chat_changed_cb), self,
                           G_CONNECT_SWAPPED);
752
753
  window_chat_changed_cb (self);

Andrea Schaefer's avatar
Andrea Schaefer committed
754
  G_OBJECT_CLASS (chatty_window_parent_class)->constructed (object);
Andrea Schaefer's avatar
Andrea Schaefer committed
755
756
757
758
759
760
761
762
}


static void
chatty_window_finalize (GObject *object)
{
  ChattyWindow *self = (ChattyWindow *)object;

Mohammed Sadiq's avatar
Mohammed Sadiq committed
763
  g_clear_object (&self->settings);
Andrea Schaefer's avatar
Andrea Schaefer committed
764

Andrea Schaefer's avatar
Andrea Schaefer committed
765
  G_OBJECT_CLASS (chatty_window_parent_class)->finalize (object);
Andrea Schaefer's avatar
Andrea Schaefer committed
766
767
}

Mohammed Sadiq's avatar
Mohammed Sadiq committed
768
769
770
771
772
773
774
775
776
777
778
779
780
static void
chatty_window_dispose (GObject *object)
{
  ChattyWindow *self = (ChattyWindow *)object;

  g_clear_object (&self->filter_model);
  g_clear_object (&self->chat_filter);
  g_clear_object (&self->manager);
  g_clear_pointer (&self->chat_needle, g_free);

  G_OBJECT_CLASS (chatty_window_parent_class)->dispose (object);
}

Andrea Schaefer's avatar
Andrea Schaefer committed
781
782
783
784

static void
chatty_window_class_init (ChattyWindowClass *klass)
{
Andrea Schaefer's avatar
Andrea Schaefer committed
785
786
  GObjectClass   *object_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
Andrea Schaefer's avatar
Andrea Schaefer committed
787
788
789

  object_class->constructed  = chatty_window_constructed;
  object_class->finalize     = chatty_window_finalize;
Mohammed Sadiq's avatar
Mohammed Sadiq committed
790
  object_class->dispose      = chatty_window_dispose;
Andrea Schaefer's avatar
Andrea Schaefer committed
791

792
793
  widget_class->unmap = chatty_window_unmap;

Andrea Schaefer's avatar
Andrea Schaefer committed
794
  gtk_widget_class_set_template_from_resource (widget_class,
795
                                               "/sm/puri/Chatty/"
Andrea Schaefer's avatar
Andrea Schaefer committed
796
797
798
799
                                               "ui/chatty-window.ui");

  gtk_widget_class_bind_template_child (widget_class, ChattyWindow, sub_header_label);
  gtk_widget_class_bind_template_child (widget_class, ChattyWindow, sub_header_icon);
Andrea Schaefer's avatar
Andrea Schaefer committed
800
  gtk_widget_class_bind_template_child (widget_class, ChattyWindow, menu_add_contact_button);
801
802
  gtk_widget_class_bind_template_child (widget_class, ChattyWindow, menu_new_message_button);
  gtk_widget_class_bind_template_child (widget_class, ChattyWindow, menu_new_group_message_button);
Andrea Schaefer's avatar
Andrea Schaefer committed
803
  gtk_widget_class_bind_template_child (widget_class, ChattyWindow, header_add_chat_button);
804
  gtk_widget_class_bind_template_child (widget_class, ChattyWindow, call_button);
Andrea Schaefer's avatar
Andrea Schaefer committed
805
  gtk_widget_class_bind_template_child (widget_class, ChattyWindow, header_sub_menu_button);
806
  gtk_widget_class_bind_template_child (widget_class, ChattyWindow, leave_button);
807
  gtk_widget_class_bind_template_child (widget_class, ChattyWindow, delete_button);
808
809

  gtk_widget_class_bind_template_child (widget_class, ChattyWindow, search_button);
Andrea Schaefer's avatar
Andrea Schaefer committed
810
811
  gtk_widget_class_bind_template_child (widget_class, ChattyWindow, chats_search_bar);
  gtk_widget_class_bind_template_child (widget_class, ChattyWindow, chats_search_entry);
812

Andrea Schaefer's avatar
Andrea Schaefer committed
813
814
815
  gtk_widget_class_bind_template_child (widget_class, ChattyWindow, content_box);
  gtk_widget_class_bind_template_child (widget_class, ChattyWindow, header_box);
  gtk_widget_class_bind_template_child (widget_class, ChattyWindow, header_group);
816
817
818
819

  gtk_widget_class_bind_template_child (widget_class, ChattyWindow, sidebar_stack);
  gtk_widget_class_bind_template_child (widget_class, ChattyWindow, empty_view);
  gtk_widget_class_bind_template_child (widget_class, ChattyWindow, chat_list_view);
Andrea Schaefer's avatar
Andrea Schaefer committed
820
  gtk_widget_class_bind_template_child (widget_class, ChattyWindow, chats_listbox);
821

822
  gtk_widget_class_bind_template_child (widget_class, ChattyWindow, chat_view);
823
  gtk_widget_class_bind_template_child (widget_class, ChattyWindow, header_chat_list_new_msg_popover);
Andrea Schaefer's avatar
Andrea Schaefer committed
824
825

  gtk_widget_class_bind_template_callback (widget_class, notify_fold_cb);
826
827
828
  gtk_widget_class_bind_template_callback (widget_class, window_new_message_clicked_cb);
  gtk_widget_class_bind_template_callback (widget_class, window_new_muc_clicked_cb);
  gtk_widget_class_bind_template_callback (widget_class, window_add_chat_button_clicked_cb);
829
830
831
832
833
  gtk_widget_class_bind_template_callback (widget_class, window_back_clicked_cb);
  gtk_widget_class_bind_template_callback (widget_class, window_show_chat_info_clicked_cb);
  gtk_widget_class_bind_template_callback (widget_class, window_add_contact_clicked_cb);
  gtk_widget_class_bind_template_callback (widget_class, window_leave_chat_clicked_cb);
  gtk_widget_class_bind_template_callback (widget_class, window_delete_buddy_clicked_cb);
834
  gtk_widget_class_bind_template_callback (widget_class, window_call_button_clicked_cb);
Mohammed Sadiq's avatar
Mohammed Sadiq committed
835
  gtk_widget_class_bind_template_callback (widget_class, window_search_changed_cb);
836
  gtk_widget_class_bind_template_callback (widget_class, window_chat_row_activated_cb);
837
838
  gtk_widget_class_bind_template_callback (widget_class, chatty_window_show_settings_dialog);
  gtk_widget_class_bind_template_callback (widget_class, chatty_window_show_about_dialog);
Andrea Schaefer's avatar
Andrea Schaefer committed
839
840
841
842
843
844
}


static void
chatty_window_init (ChattyWindow *self)
{
Andrea Schaefer's avatar
Andrea Schaefer committed
845
  gtk_widget_init_template (GTK_WIDGET (self));
846

847
848
849
850
  self->manager = g_object_ref (chatty_manager_get_default ());
  g_signal_connect_object (self->manager, "notify::active-protocols",
                           G_CALLBACK (window_active_protocols_changed_cb), self,
                           G_CONNECT_SWAPPED);
851
852
853
  g_signal_connect_object (self->manager, "chat-deleted",
                           G_CALLBACK (window_chat_deleted_cb), self,
                           G_CONNECT_SWAPPED);
Andrea Schaefer's avatar
Andrea Schaefer committed
854
855
856
857
}


GtkWidget *
Mohammed Sadiq's avatar
Mohammed Sadiq committed
858
chatty_window_new (GtkApplication *application)
Andrea Schaefer's avatar
Andrea Schaefer committed
859
{
Andrea Schaefer's avatar
Andrea Schaefer committed
860
  g_assert (GTK_IS_APPLICATION (application));
Andrea Schaefer's avatar
Andrea Schaefer committed
861
862
863
864
865
866
867

  return g_object_new (CHATTY_TYPE_WINDOW,
                       "application", application,
                       NULL);
}


868
869
870
871
void
chatty_window_set_uri (ChattyWindow *self,
                       const char   *uri)
{
872
  g_autofree char *who = NULL;
873

874
  who = chatty_utils_check_phonenumber (uri, chatty_settings_get_country_iso_code (self->settings));
875

876
877
878
879
880
881
882
883
884
885
886
887
888
  if (!who) {
    GtkWidget *dialog;

    dialog = gtk_message_dialog_new (GTK_WINDOW (self),
                                     GTK_DIALOG_MODAL,
                                     GTK_MESSAGE_WARNING,
                                     GTK_BUTTONS_CLOSE,
                                     _("“%s” is not a valid phone number"), uri);
    gtk_dialog_run (GTK_DIALOG (dialog));
    gtk_widget_destroy (dialog);

    return;
  }
889

Mohammed Sadiq's avatar
Mohammed Sadiq committed
890
891
  if (!chatty_manager_set_uri (self->manager, uri))
    return;
892
893

  gtk_widget_hide (self->new_chat_dialog);
894
895
}

896
897
898
ChattyChat *
chatty_window_get_active_chat (ChattyWindow *self)
{
899
900
  GtkWidget *widget;

901
902
  g_return_val_if_fail (CHATTY_IS_WINDOW (self), NULL);

903
904
905
906
907
908
  widget = gtk_window_get_focus (GTK_WINDOW (self));

  if (widget && gtk_widget_has_focus (widget))
    return chatty_chat_view_get_chat (CHATTY_CHAT_VIEW (self->chat_view));

  return NULL;
909
}
910
911
912
913
914

void
chatty_window_open_chat (ChattyWindow *self,
                         ChattyChat   *chat)
{
915
916
  gboolean can_delete;

917
918
  g_return_if_fail (CHATTY_IS_WINDOW (self));
  g_return_if_fail (CHATTY_IS_CHAT (chat));
919
920
921
  g_debug ("opening item of type: %s, protocol: %d",
           G_OBJECT_TYPE_NAME (chat),
           chatty_item_get_protocols (CHATTY_ITEM (chat)));
922

923
924
925
  chatty_chat_view_set_chat (CHATTY_CHAT_VIEW (self->chat_view), chat);
  window_set_item (self, CHATTY_ITEM (chat));
  window_chat_changed_cb (self);
926

927
  gtk_widget_set_visible (self->leave_button, !CHATTY_IS_MM_CHAT (chat));
928
929
  can_delete = CHATTY_IS_PP_CHAT (chat) || CHATTY_IS_MM_CHAT (chat);
  gtk_widget_set_visible (self->delete_button, can_delete);
930
  hdy_leaflet_set_visible_child (HDY_LEAFLET (self->content_box), self->chat_view);
931
  gtk_widget_hide (self->menu_add_contact_button);
932
  gtk_widget_hide (self->call_button);
933

934
935
  if (chatty_window_get_active_chat (self))
    chatty_chat_set_unread_count (chat, 0);
936
937
938
939
940
941
942
943
944
945
946

  if (CHATTY_IS_MM_CHAT (chat)) {
    GListModel *users;
    const char *name;

    users = chatty_chat_get_users (chat);
    name = chatty_chat_get_chat_name (chat);

    if (g_list_model_get_n_items (users) == 1 &&
        chatty_utils_username_is_valid (name, CHATTY_PROTOCOL_MMS_SMS)) {
      g_autoptr(ChattyMmBuddy) buddy = NULL;
947
      g_autoptr(GAppInfo) app_info = NULL;
948

949
      app_info = g_app_info_get_default_for_uri_scheme ("tel");
950
951
      buddy = g_list_model_get_item (users, 0);

952
953
954
      if (app_info)
        gtk_widget_show (self->call_button);

955
956
957
958
      if (!chatty_mm_buddy_get_contact (buddy))
        gtk_widget_show (self->menu_add_contact_button);
    }
  }
959
}