Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-3.0-or-later
2 : : // SPDX-FileCopyrightText: Andy Holmes <andrew.g.r.holmes@gmail.com>
3 : :
4 : : #define G_LOG_DOMAIN "valent-messages-window"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <inttypes.h>
9 : :
10 : : #include <adwaita.h>
11 : : #include <glib/gi18n.h>
12 : : #include <gtk/gtk.h>
13 : : #include <libebook-contacts/libebook-contacts.h>
14 : : #include <valent.h>
15 : :
16 : : #include "valent-contact-page.h"
17 : : #include "valent-contact-row.h"
18 : : #include "valent-conversation-page.h"
19 : : #include "valent-ui-utils-private.h"
20 : : #include "valent-message-row.h"
21 : :
22 : : #include "valent-messages-window.h"
23 : :
24 : :
25 : : struct _ValentMessagesWindow
26 : : {
27 : : AdwApplicationWindow parent_instance;
28 : :
29 : : GListModel *contacts;
30 : : ValentContactsAdapter *contacts_adapter;
31 : : GListModel *messages;
32 : : ValentMessagesAdapter *messages_adapter;
33 : : GCancellable *search;
34 : :
35 : : /* template */
36 : : AdwNavigationSplitView *main_view;
37 : : AdwHeaderBar *sidebar_header;
38 : : GtkListBox *sidebar_list;
39 : : AdwNavigationView *content_view;
40 : : AdwNavigationPage *search_page;
41 : : GtkWidget *search_entry;
42 : : GtkListBox *search_list;
43 : : AdwNavigationPage *contact_page;
44 : : AdwDialog *details_dialog;
45 : : GtkListBox *medium_list;
46 : : };
47 : :
48 : : void valent_messages_window_set_active_message (ValentMessagesWindow *window,
49 : : ValentMessage *message);
50 : : void valent_messages_window_set_active_thread (ValentMessagesWindow *window,
51 : : const char *iri);
52 : :
53 [ + + + - ]: 5 : G_DEFINE_FINAL_TYPE (ValentMessagesWindow, valent_messages_window, ADW_TYPE_APPLICATION_WINDOW)
54 : :
55 : : typedef enum {
56 : : PROP_MESSAGES = 1,
57 : : } ValentMessagesWindowProperty;
58 : :
59 : : static GParamSpec *properties[PROP_MESSAGES + 1] = { NULL, };
60 : :
61 : :
62 : : /*
63 : : * Contact Lookup
64 : : */
65 : : static void
66 : 0 : lookup_contact_cb (ValentContactsAdapter *adapter,
67 : : GAsyncResult *result,
68 : : ValentMessageRow *row)
69 : : {
70 : 0 : g_autoptr (EContact) contact = NULL;
71 : 0 : g_autoptr (GError) error = NULL;
72 : :
73 : 0 : contact = valent_contacts_adapter_reverse_lookup_finish (adapter, result, &error);
74 [ # # ]: 0 : if (contact == NULL)
75 : : {
76 [ # # ]: 0 : if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
77 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
78 : :
79 [ # # ]: 0 : return;
80 : : }
81 : :
82 [ # # ]: 0 : valent_message_row_set_contact (row, contact);
83 : : }
84 : :
85 : : static void
86 : 0 : search_contacts_cb (ValentContactsAdapter *adapter,
87 : : GAsyncResult *result,
88 : : ValentMessagesWindow *self)
89 : : {
90 : 0 : g_autoptr (GListModel) contacts = NULL;
91 : 0 : unsigned int n_contacts;
92 [ # # # # ]: 0 : g_autoptr (GError) error = NULL;
93 : :
94 : 0 : contacts = valent_contacts_adapter_search_finish (adapter, result, &error);
95 [ # # ]: 0 : if (error != NULL)
96 : : {
97 [ # # ]: 0 : if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
98 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
99 : :
100 [ # # ]: 0 : return;
101 : : }
102 : :
103 : 0 : n_contacts = g_list_model_get_n_items (contacts);
104 [ # # ]: 0 : for (unsigned int i = 0; i < n_contacts; i++)
105 : : {
106 : 0 : g_autoptr (EContact) contact = g_list_model_get_item (contacts, i);
107 : 0 : GtkWidget *row;
108 [ # # # # ]: 0 : g_autolist (EVCardAttribute) attrs = NULL;
109 : 0 : g_autofree char *number = NULL;
110 : 0 : unsigned int n_attrs;
111 : :
112 : : #if EDS_CHECK_VERSION (3, 59, 0)
113 : : attrs = e_vcard_get_attributes_by_name (E_VCARD (contact), EVC_TEL);
114 : : #else
115 : 0 : attrs = e_contact_get_attributes (contact, E_CONTACT_TEL);
116 : : #endif
117 : 0 : n_attrs = g_list_length (attrs);
118 [ # # ]: 0 : if (n_attrs == 0)
119 : 0 : continue;
120 : :
121 : 0 : g_object_get (contact, "primary-phone", &number, NULL);
122 [ # # # # ]: 0 : if (number == NULL || *number == '\0')
123 : : {
124 : 0 : g_free (number);
125 : 0 : number = e_vcard_attribute_get_value ((EVCardAttribute *)attrs->data);
126 : : }
127 : :
128 [ # # ]: 0 : if (n_attrs > 1)
129 : : {
130 : 0 : g_autofree char *tmp = g_steal_pointer (&number);
131 : :
132 : 0 : number = g_strdup_printf (ngettext ("%s and %u more…",
133 : : "%s and %u more…",
134 : : n_attrs - 1),
135 : : tmp, n_attrs - 1);
136 : : }
137 : :
138 : 0 : row = g_object_new (VALENT_TYPE_CONTACT_ROW,
139 : : "contact", contact,
140 : : "contact-medium", number,
141 : : NULL);
142 : :
143 [ # # ]: 0 : if (n_attrs > 1)
144 : : {
145 : 0 : gtk_accessible_update_state (GTK_ACCESSIBLE (row),
146 : : GTK_ACCESSIBLE_STATE_EXPANDED, FALSE,
147 : : -1);
148 : : }
149 : :
150 : 0 : gtk_list_box_insert (self->search_list, row, -1);
151 : : }
152 : : }
153 : :
154 : : static void
155 : 0 : search_messages_cb (ValentMessagesAdapter *adapter,
156 : : GAsyncResult *result,
157 : : ValentMessagesWindow *self)
158 : : {
159 : 0 : g_autoptr (GListModel) messages = NULL;
160 : 0 : unsigned int n_messages;
161 : 0 : g_autoptr (GError) error = NULL;
162 : :
163 : 0 : messages = valent_messages_adapter_search_finish (adapter, result, &error);
164 [ # # ]: 0 : if (messages == NULL)
165 : : {
166 [ # # ]: 0 : if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
167 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
168 : :
169 [ # # ]: 0 : return;
170 : : }
171 : :
172 : 0 : n_messages = g_list_model_get_n_items (messages);
173 [ # # ]: 0 : for (unsigned int i = 0; i < n_messages; i++)
174 : : {
175 : 0 : g_autoptr (ValentMessage) message = g_list_model_get_item (messages, i);
176 : 0 : GtkWidget *row;
177 : 0 : const char *medium;
178 : :
179 : 0 : row = g_object_new (VALENT_TYPE_MESSAGE_ROW,
180 : : "message", message,
181 : : NULL);
182 : 0 : gtk_list_box_insert (self->search_list, row, -1);
183 : :
184 : 0 : medium = valent_message_get_sender (message);
185 [ # # # # ]: 0 : if (medium == NULL || *medium == '\0')
186 : : {
187 : 0 : const char * const *recipients = NULL;
188 : :
189 : 0 : recipients = valent_message_get_recipients (message);
190 [ # # ]: 0 : if (recipients != NULL)
191 : 0 : medium = recipients[0];
192 : : }
193 : :
194 [ # # # # ]: 0 : if (medium != NULL && *medium != '\0')
195 : : {
196 [ # # ]: 0 : g_autoptr (GCancellable) cancellable = NULL;
197 : :
198 : 0 : cancellable = g_cancellable_new ();
199 : 0 : g_signal_connect_object (row,
200 : : "destroy",
201 : : G_CALLBACK (g_cancellable_cancel),
202 : : cancellable,
203 : : G_CONNECT_SWAPPED);
204 [ # # ]: 0 : valent_contacts_adapter_reverse_lookup (self->contacts_adapter,
205 : : medium,
206 : : cancellable,
207 : : (GAsyncReadyCallback) lookup_contact_cb,
208 : : row);
209 : : }
210 : : }
211 : : }
212 : :
213 : : /*
214 : : * Message Search
215 : : */
216 : : static void
217 : 0 : search_header_func (GtkListBoxRow *row,
218 : : GtkListBoxRow *before,
219 : : gpointer user_data)
220 : : {
221 [ # # ]: 0 : if (VALENT_IS_MESSAGE_ROW (row))
222 : : {
223 [ # # # # ]: 0 : if (before == NULL || !VALENT_IS_MESSAGE_ROW (before))
224 : : {
225 : 0 : GtkWidget *label;
226 : :
227 : 0 : label = g_object_new (GTK_TYPE_LABEL,
228 : : "label", _("Conversations"),
229 : : "halign", GTK_ALIGN_START,
230 : : "margin-bottom", 6,
231 : : "margin-end", 6,
232 : : "margin-start", 6,
233 : : "margin-top", 6,
234 : : NULL);
235 : 0 : gtk_widget_add_css_class (label, "dim-label");
236 : 0 : gtk_widget_add_css_class (label, "caption-heading");
237 : 0 : gtk_list_box_row_set_header (row, label);
238 : : }
239 : : }
240 [ # # ]: 0 : else if (!VALENT_IS_CONTACT_ROW (before))
241 : : {
242 : 0 : GtkWidget *label;
243 : :
244 : 0 : label = g_object_new (GTK_TYPE_LABEL,
245 : : "label", _("Contacts"),
246 : : "halign", GTK_ALIGN_START,
247 : : "margin-bottom", 6,
248 : : "margin-end", 6,
249 : : "margin-start", 6,
250 : : "margin-top", 12, // +6 for section spacing
251 : : NULL);
252 : 0 : gtk_widget_add_css_class (label, "dim-label");
253 : 0 : gtk_widget_add_css_class (label, "caption-heading");
254 : 0 : gtk_list_box_row_set_header (row, label);
255 : : }
256 : 0 : }
257 : :
258 : : static void
259 : 0 : on_search_changed (GtkSearchEntry *entry,
260 : : ValentMessagesWindow *self)
261 : : {
262 : 0 : GtkWidget *child;
263 : 0 : const char *search_query;
264 : :
265 : : /* Clear previous results
266 : : */
267 : 0 : g_cancellable_cancel (self->search);
268 [ # # ]: 0 : g_clear_object (&self->search);
269 : :
270 [ # # ]: 0 : while ((child = gtk_widget_get_first_child (GTK_WIDGET (self->search_list))))
271 : 0 : gtk_list_box_remove (self->search_list, child);
272 : :
273 : 0 : search_query = gtk_editable_get_text (GTK_EDITABLE (entry));
274 [ # # # # ]: 0 : if (search_query == NULL || *search_query == '\0')
275 : : return;
276 : :
277 : : /* Search messages
278 : : */
279 : 0 : self->search = g_cancellable_new ();
280 : 0 : valent_messages_adapter_search (self->messages_adapter,
281 : : search_query,
282 : : self->search,
283 : : (GAsyncReadyCallback)search_messages_cb,
284 : : self);
285 : 0 : valent_contacts_adapter_search (self->contacts_adapter,
286 : : search_query,
287 : : self->search,
288 : : (GAsyncReadyCallback)search_contacts_cb,
289 : : self);
290 : : }
291 : :
292 : : static void
293 : 0 : lookup_thread_cb (ValentMessagesAdapter *adapter,
294 : : GAsyncResult *result,
295 : : ValentMessagesWindow *self)
296 : : {
297 : 0 : g_autofree char *iri = NULL;
298 : 0 : g_autoptr (GError) error = NULL;
299 : :
300 : 0 : iri = g_task_propagate_pointer (G_TASK (result), &error);
301 [ # # ]: 0 : if (iri == NULL)
302 : : {
303 [ # # ]: 0 : if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
304 : 0 : g_warning ("%s(): %s", G_STRFUNC, error->message);
305 : : else
306 : 0 : g_debug ("%s(): no thread found for contact", G_STRFUNC);
307 : :
308 [ # # ]: 0 : return;
309 : : }
310 : :
311 : 0 : adw_navigation_view_pop (self->content_view);
312 [ # # ]: 0 : valent_messages_window_set_active_thread (self, iri);
313 : : }
314 : :
315 : : static void
316 : 0 : on_contact_selected (ValentContactPage *page,
317 : : EContact *contact,
318 : : const char *target,
319 : : ValentMessagesWindow *self)
320 : : {
321 : 0 : g_autoptr (GCancellable) cancellable = NULL;
322 : :
323 : 0 : cancellable = g_cancellable_new ();
324 : 0 : g_signal_connect_object (self,
325 : : "destroy",
326 : : G_CALLBACK (g_cancellable_cancel),
327 : : cancellable,
328 : : G_CONNECT_SWAPPED);
329 : :
330 [ # # ]: 0 : valent_messages_adapter_lookup_thread (self->messages_adapter,
331 : 0 : ((const char * const []){ target, NULL }),
332 : : cancellable,
333 : : (GAsyncReadyCallback) lookup_thread_cb,
334 : : self);
335 : :
336 : : // FIXME: loading indicator
337 : 0 : }
338 : :
339 : : static void
340 : 0 : on_contact_medium_selected (GtkListBoxRow *row,
341 : : ValentMessagesWindow *self)
342 : : {
343 : 0 : g_autoptr (GCancellable) cancellable = NULL;
344 [ # # ]: 0 : g_autofree char *medium = NULL;
345 : :
346 [ # # ]: 0 : g_assert (VALENT_IS_CONTACT_ROW (row));
347 : :
348 : 0 : cancellable = g_cancellable_new ();
349 : 0 : g_signal_connect_object (self,
350 : : "destroy",
351 : : G_CALLBACK (g_cancellable_cancel),
352 : : cancellable,
353 : : G_CONNECT_SWAPPED);
354 : :
355 : 0 : g_object_get (row, "contact-medium", &medium, NULL);
356 : 0 : valent_messages_adapter_lookup_thread (self->messages_adapter,
357 : 0 : ((const char * const []){ medium, NULL }),
358 : : cancellable,
359 : : (GAsyncReadyCallback) lookup_thread_cb,
360 : : self);
361 : :
362 : 0 : adw_dialog_close (self->details_dialog);
363 : 0 : }
364 : :
365 : : static void
366 : 0 : on_contact_row_collapsed (AdwDialog *dialog,
367 : : GtkWidget *row)
368 : : {
369 : 0 : gtk_accessible_reset_relation (GTK_ACCESSIBLE (row),
370 : : GTK_ACCESSIBLE_RELATION_CONTROLS);
371 : 0 : gtk_accessible_update_state (GTK_ACCESSIBLE (row),
372 : : GTK_ACCESSIBLE_STATE_EXPANDED, FALSE,
373 : : -1);
374 : 0 : g_signal_handlers_disconnect_by_func (dialog, on_contact_row_collapsed, row);
375 : 0 : }
376 : :
377 : : static void
378 : 0 : on_search_selected (GtkListBox *box,
379 : : GtkListBoxRow *row,
380 : : ValentMessagesWindow *self)
381 : : {
382 [ # # ]: 0 : g_assert (VALENT_IS_MESSAGES_WINDOW (self));
383 : :
384 [ # # ]: 0 : if (VALENT_IS_MESSAGE_ROW (row))
385 : : {
386 : 0 : ValentMessage *message;
387 : :
388 : 0 : message = valent_message_row_get_message (VALENT_MESSAGE_ROW (row));
389 : 0 : valent_messages_window_set_active_message (self, message);
390 : :
391 : : /* Reset the search
392 : : */
393 : 0 : adw_navigation_view_pop (self->content_view);
394 : 0 : gtk_editable_set_text (GTK_EDITABLE (self->search_entry), "");
395 : : }
396 [ # # ]: 0 : else if (VALENT_IS_CONTACT_ROW (row))
397 : : {
398 : 0 : g_autolist (EVCardAttribute) attrs = NULL;
399 : 0 : EContact *contact;
400 : :
401 : 0 : contact = valent_contact_row_get_contact (VALENT_CONTACT_ROW (row));
402 : : #if EDS_CHECK_VERSION (3, 59, 0)
403 : : attrs = e_vcard_get_attributes_by_name (E_VCARD (contact), EVC_TEL);
404 : : #else
405 : 0 : attrs = e_contact_get_attributes (contact, E_CONTACT_TEL);
406 : : #endif
407 [ # # ]: 0 : if (g_list_length (attrs) == 1)
408 : : {
409 : 0 : on_contact_medium_selected (row, self);
410 : :
411 : : /* Reset the search
412 : : */
413 : 0 : adw_navigation_view_pop (self->content_view);
414 : 0 : gtk_editable_set_text (GTK_EDITABLE (self->search_entry), "");
415 : : }
416 : : else
417 : : {
418 : 0 : gtk_list_box_remove_all (GTK_LIST_BOX (self->medium_list));
419 [ # # ]: 0 : for (const GList *iter = attrs; iter; iter = iter->next)
420 : : {
421 : 0 : EVCardAttribute *attr = iter->data;
422 : 0 : GtkWidget *medium_row;
423 : 0 : g_autofree char *number = NULL;
424 : 0 : const char *type_ = NULL;
425 : :
426 [ # # ]: 0 : if (e_vcard_attribute_has_type (attr, "WORK"))
427 : 0 : type_ = _("Work");
428 [ # # ]: 0 : else if (e_vcard_attribute_has_type (attr, "CELL"))
429 : 0 : type_ = _("Mobile");
430 [ # # ]: 0 : else if (e_vcard_attribute_has_type (attr, "HOME"))
431 : 0 : type_ = _("Home");
432 : : else
433 : 0 : type_ = _("Other");
434 : :
435 : 0 : number = e_vcard_attribute_get_value (attr);
436 : 0 : medium_row = g_object_new (ADW_TYPE_ACTION_ROW,
437 : : "activatable", TRUE,
438 : : "title", number,
439 : : "subtitle", type_,
440 : : NULL);
441 : 0 : g_object_set_data_full (G_OBJECT (medium_row),
442 : : "contact",
443 : : g_object_ref (contact),
444 : : g_object_unref);
445 : 0 : g_signal_connect_object (medium_row,
446 : : "activated",
447 : : G_CALLBACK (on_contact_medium_selected),
448 : : self,
449 : : G_CONNECT_DEFAULT);
450 : :
451 : 0 : gtk_list_box_insert (self->medium_list, medium_row, -1);
452 : : }
453 : :
454 : : /* Present the dialog and match the expanded state
455 : : */
456 : 0 : gtk_accessible_update_state (GTK_ACCESSIBLE (row),
457 : : GTK_ACCESSIBLE_STATE_EXPANDED, TRUE,
458 : : -1);
459 : 0 : gtk_accessible_update_relation (GTK_ACCESSIBLE (row),
460 : : GTK_ACCESSIBLE_RELATION_CONTROLS, self->details_dialog, NULL,
461 : : -1);
462 : 0 : g_signal_connect_object (self->details_dialog,
463 : : "closed",
464 : : G_CALLBACK (on_contact_row_collapsed),
465 : : row,
466 : : G_CONNECT_DEFAULT);
467 : 0 : adw_dialog_present (self->details_dialog, GTK_WIDGET (self));
468 : : }
469 : : }
470 : 0 : }
471 : :
472 : : /*
473 : : * Sidebar
474 : : */
475 : : static GtkWidget *
476 : 1 : sidebar_list_create (gpointer item,
477 : : gpointer user_data)
478 : : {
479 : 1 : ValentMessagesWindow *window = VALENT_MESSAGES_WINDOW (user_data);
480 : 1 : GListModel *thread = G_LIST_MODEL (item);
481 : 2 : g_autoptr (ValentMessage) message = NULL;
482 : 1 : ValentMessageBox box = VALENT_MESSAGE_BOX_ALL;
483 : 1 : const char * const *recipients = NULL;
484 : 1 : const char *medium = NULL;
485 : 1 : GtkWidget *row;
486 : :
487 : 1 : g_object_get (thread, "latest-message", &message, NULL);
488 : 1 : row = g_object_new (VALENT_TYPE_MESSAGE_ROW,
489 : : "message", message,
490 : : NULL);
491 : 1 : g_object_bind_property (thread, "latest-message",
492 : : row, "message",
493 : : G_BINDING_DEFAULT);
494 : 1 : g_object_set_data_full (G_OBJECT (row),
495 : : "valent-message-thread",
496 : : g_object_ref (thread),
497 : : g_object_unref);
498 : :
499 : : // TODO: participant-based avatar for sidebar rows
500 [ + - ]: 1 : if (message != NULL)
501 : 1 : box = valent_message_get_box (message);
502 : :
503 [ - + ]: 1 : if (box == VALENT_MESSAGE_BOX_INBOX)
504 : : {
505 : 0 : medium = valent_message_get_sender (message);
506 : : }
507 [ - + ]: 1 : else if (box == VALENT_MESSAGE_BOX_SENT)
508 : : {
509 : 1 : recipients = valent_message_get_recipients (message);
510 [ - + ]: 1 : if (recipients != NULL)
511 : 1 : medium = recipients[0];
512 : : }
513 : :
514 [ - + - - ]: 1 : if (window->contacts_adapter != NULL &&
515 [ # # ]: 0 : medium != NULL && *medium != '\0')
516 : : {
517 [ + - ]: 1 : g_autoptr (GCancellable) cancellable = NULL;
518 : :
519 : 0 : cancellable = g_cancellable_new ();
520 : 0 : g_signal_connect_object (row,
521 : : "destroy",
522 : : G_CALLBACK (g_cancellable_cancel),
523 : : cancellable,
524 : : G_CONNECT_SWAPPED);
525 [ # # ]: 0 : valent_contacts_adapter_reverse_lookup (window->contacts_adapter,
526 : : medium,
527 : : cancellable,
528 : : (GAsyncReadyCallback) lookup_contact_cb,
529 : : row);
530 : : }
531 : : else
532 : : {
533 : 2 : g_autoptr (EContact) contact = NULL;
534 : :
535 : 1 : contact = e_contact_new ();
536 : 1 : e_contact_set (contact, E_CONTACT_FULL_NAME, _("Unknown"));
537 : 1 : e_contact_set (contact, E_CONTACT_PHONE_OTHER, _("Unknown sender"));
538 [ + - ]: 1 : valent_message_row_set_contact (VALENT_MESSAGE_ROW (row), contact);
539 : : }
540 : :
541 [ + - ]: 1 : return row;
542 : : }
543 : :
544 : : static GtkWidget *
545 : 0 : valent_messages_window_ensure_conversation (ValentMessagesWindow *window,
546 : : const char *thread_iri)
547 : : {
548 : 0 : AdwNavigationPage *conversation;
549 : :
550 : 0 : conversation = adw_navigation_view_find_page (window->content_view, thread_iri);
551 [ # # ]: 0 : if (conversation == NULL)
552 : : {
553 : 0 : conversation = g_object_new (VALENT_TYPE_CONVERSATION_PAGE,
554 : : "tag", thread_iri,
555 : : "contacts", window->contacts_adapter,
556 : : "messages", window->messages_adapter,
557 : : "iri", thread_iri,
558 : : NULL);
559 : 0 : adw_navigation_view_push (window->content_view, conversation);
560 : : }
561 : : else
562 : : {
563 : 0 : adw_navigation_view_pop_to_page (window->content_view, conversation);
564 : : }
565 : :
566 : 0 : return GTK_WIDGET (conversation);
567 : : }
568 : :
569 : : static void
570 : 0 : on_conversation_activated (GtkListBox *box,
571 : : GtkListBoxRow *row,
572 : : ValentMessagesWindow *self)
573 : : {
574 : 0 : GtkWidget *page;
575 : 0 : EContact *contact;
576 : 0 : ValentMessage *message;
577 : 0 : const char *sender;
578 : 0 : int64_t thread_id;
579 : 0 : const char *adapter_urn = NULL;
580 : 0 : g_autofree char *thread_urn = NULL;
581 : :
582 [ # # ]: 0 : g_assert (VALENT_IS_MESSAGES_WINDOW (self));
583 [ # # ]: 0 : g_assert (VALENT_IS_MESSAGE_ROW (row));
584 : :
585 [ # # ]: 0 : if (row == NULL)
586 : 0 : return;
587 : :
588 : : // TODO: use IRI
589 : 0 : contact = valent_message_row_get_contact (VALENT_MESSAGE_ROW (row));
590 : 0 : message = valent_message_row_get_message (VALENT_MESSAGE_ROW (row));
591 : 0 : thread_id = valent_message_get_thread_id (message);
592 : 0 : adapter_urn = valent_resource_get_iri (VALENT_RESOURCE (self->messages_adapter));
593 : 0 : thread_urn = g_strdup_printf ("%s:%"PRId64, adapter_urn, thread_id);
594 : :
595 : 0 : page = valent_messages_window_ensure_conversation (self, thread_urn);
596 : 0 : sender = valent_message_get_sender (message);
597 [ # # # # ]: 0 : if (sender != NULL && *sender != '\0')
598 : : {
599 : 0 : valent_conversation_page_add_participant (VALENT_CONVERSATION_PAGE (page),
600 : : contact,
601 : : sender);
602 : : }
603 : :
604 : 0 : adw_navigation_split_view_set_show_content (self->main_view, TRUE);
605 : : }
606 : :
607 : : /*
608 : : * Message Source
609 : : */
610 : : static void
611 : 1 : on_selected_item (GObject *object,
612 : : GParamSpec *pspec,
613 : : ValentMessagesWindow *self)
614 : : {
615 : 1 : ValentMessagesAdapter *adapter = NULL;
616 : 1 : GObject *owner = NULL;
617 : 1 : unsigned int n_items = 0;
618 : :
619 [ - + ]: 1 : g_assert (VALENT_IS_MESSAGES_WINDOW (self));
620 : :
621 : 1 : adapter = gtk_drop_down_get_selected_item (GTK_DROP_DOWN (object));
622 [ + - ]: 1 : if (!g_set_object (&self->messages_adapter, adapter))
623 : : return;
624 : :
625 : : // HACK: try to find a matching contacts adapter
626 : 1 : owner = valent_resource_get_source (VALENT_RESOURCE (adapter));
627 : 1 : n_items = g_list_model_get_n_items (self->contacts);
628 [ + + ]: 2 : for (unsigned int i = 0; i < n_items; i++)
629 : : {
630 : 1 : g_autoptr (ValentContactsAdapter) item = NULL;
631 : 1 : GObject *item_owner = NULL;
632 : :
633 : 1 : item = g_list_model_get_item (self->contacts, i);
634 : 1 : item_owner = valent_resource_get_source (VALENT_RESOURCE (item));
635 [ - + ]: 1 : if (item_owner == owner)
636 : : {
637 : 0 : g_set_object (&self->contacts_adapter, item);
638 [ # # ]: 0 : break;
639 : : }
640 : : }
641 : :
642 : 1 : gtk_list_box_bind_model (self->sidebar_list,
643 : : G_LIST_MODEL (adapter),
644 : : sidebar_list_create,
645 : : self,
646 : : NULL);
647 : : }
648 : :
649 : : /*
650 : : * GActions
651 : : */
652 : : static void
653 : 0 : sms_new_action (GtkWidget *widget,
654 : : const char *action_name,
655 : : GVariant *parameter)
656 : : {
657 : 0 : ValentMessagesWindow *self = VALENT_MESSAGES_WINDOW (widget);
658 : :
659 [ # # ]: 0 : g_assert (VALENT_IS_MESSAGES_WINDOW (self));
660 : :
661 : 0 : gtk_list_box_select_row (self->sidebar_list, NULL);
662 : :
663 [ # # ]: 0 : if (self->contact_page == NULL)
664 : : {
665 : 0 : self->contact_page = g_object_new (VALENT_TYPE_CONTACT_PAGE,
666 : : "tag", "contacts",
667 : : "contacts", self->contacts_adapter,
668 : : NULL);
669 : 0 : g_signal_connect_object (self->contact_page,
670 : : "selected",
671 : : G_CALLBACK (on_contact_selected),
672 : : self,
673 : : G_CONNECT_DEFAULT);
674 : 0 : adw_navigation_view_push (self->content_view, self->contact_page);
675 : : }
676 : : else
677 : : {
678 : 0 : adw_navigation_view_pop_to_page (self->content_view, self->contact_page);
679 : : }
680 : :
681 : 0 : adw_navigation_split_view_set_show_content (self->main_view, TRUE);
682 : 0 : }
683 : :
684 : : static void
685 : 0 : sms_search_action (GtkWidget *widget,
686 : : const char *action_name,
687 : : GVariant *parameter)
688 : : {
689 : 0 : ValentMessagesWindow *self = VALENT_MESSAGES_WINDOW (widget);
690 : :
691 [ # # ]: 0 : g_assert (VALENT_IS_MESSAGES_WINDOW (self));
692 : :
693 [ # # ]: 0 : if (self->search_page == NULL)
694 : 0 : adw_navigation_view_push_by_tag (self->content_view, "search");
695 : : else
696 : 0 : adw_navigation_view_pop_to_page (self->content_view, self->search_page);
697 : :
698 : 0 : gtk_widget_grab_focus (self->search_entry);
699 : 0 : adw_navigation_split_view_set_show_content (self->main_view, TRUE);
700 : 0 : }
701 : :
702 : : /*
703 : : * AdwNavigationPage
704 : : */
705 : : static void
706 : 0 : on_page_popped (AdwNavigationView *view,
707 : : AdwNavigationPage *page,
708 : : ValentMessagesWindow *self)
709 : : {
710 [ # # ]: 0 : if (self->contact_page == page)
711 : 0 : self->contact_page = NULL;
712 [ # # ]: 0 : else if (self->search_page == page)
713 : 0 : self->search_page = NULL;
714 : 0 : }
715 : :
716 : : static void
717 : 0 : on_page_pushed (AdwNavigationView *view,
718 : : ValentMessagesWindow *self)
719 : : {
720 : 0 : AdwNavigationPage *page = adw_navigation_view_get_visible_page (view);
721 : 0 : const char *tag = adw_navigation_page_get_tag (page);
722 : :
723 [ # # ]: 0 : if (g_strcmp0 (tag, "contacts") == 0)
724 : 0 : self->contact_page = page;
725 [ # # ]: 0 : else if (g_strcmp0 (tag, "search") == 0)
726 : 0 : self->search_page = page;
727 : 0 : }
728 : :
729 : : /*
730 : : * GObject
731 : : */
732 : : static void
733 : 1 : valent_messages_window_dispose (GObject *object)
734 : : {
735 : 1 : GtkWidget *widget = GTK_WIDGET (object);
736 : :
737 : 1 : gtk_widget_dispose_template (widget, VALENT_TYPE_MESSAGES_WINDOW);
738 : :
739 : 1 : G_OBJECT_CLASS (valent_messages_window_parent_class)->dispose (object);
740 : 1 : }
741 : :
742 : : static void
743 : 1 : valent_messages_window_finalize (GObject *object)
744 : : {
745 : 1 : ValentMessagesWindow *self = VALENT_MESSAGES_WINDOW (object);
746 : :
747 [ + - ]: 1 : g_clear_object (&self->contacts);
748 [ - + ]: 1 : g_clear_object (&self->contacts_adapter);
749 [ + - ]: 1 : g_clear_object (&self->messages);
750 [ + - ]: 1 : g_clear_object (&self->messages_adapter);
751 : :
752 : 1 : G_OBJECT_CLASS (valent_messages_window_parent_class)->finalize (object);
753 : 1 : }
754 : :
755 : : static void
756 : 3 : valent_messages_window_get_property (GObject *object,
757 : : guint prop_id,
758 : : GValue *value,
759 : : GParamSpec *pspec)
760 : : {
761 : 3 : ValentMessagesWindow *self = VALENT_MESSAGES_WINDOW (object);
762 : :
763 [ + - ]: 3 : switch (prop_id)
764 : : {
765 : 3 : case PROP_MESSAGES:
766 : 3 : g_value_set_object (value, self->messages);
767 : 3 : break;
768 : :
769 : 0 : default:
770 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
771 : : }
772 : 3 : }
773 : :
774 : : static void
775 : 1 : valent_messages_window_class_init (ValentMessagesWindowClass *klass)
776 : : {
777 : 1 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
778 : 1 : GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
779 : :
780 : 1 : object_class->dispose = valent_messages_window_dispose;
781 : 1 : object_class->finalize = valent_messages_window_finalize;
782 : 1 : object_class->get_property = valent_messages_window_get_property;
783 : :
784 : 1 : gtk_widget_class_set_template_from_resource (widget_class, "/plugins/gnome/valent-messages-window.ui");
785 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentMessagesWindow, main_view);
786 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentMessagesWindow, sidebar_header);
787 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentMessagesWindow, sidebar_list);
788 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentMessagesWindow, content_view);
789 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentMessagesWindow, search_entry);
790 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentMessagesWindow, search_list);
791 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentMessagesWindow, details_dialog);
792 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentMessagesWindow, medium_list);
793 : 1 : gtk_widget_class_bind_template_callback (widget_class, on_conversation_activated);
794 : 1 : gtk_widget_class_bind_template_callback (widget_class, on_page_popped);
795 : 1 : gtk_widget_class_bind_template_callback (widget_class, on_page_pushed);
796 : 1 : gtk_widget_class_bind_template_callback (widget_class, on_search_changed);
797 : 1 : gtk_widget_class_bind_template_callback (widget_class, on_contact_selected);
798 : 1 : gtk_widget_class_bind_template_callback (widget_class, on_search_selected);
799 : 1 : gtk_widget_class_bind_template_callback (widget_class, on_selected_item);
800 : :
801 : 1 : gtk_widget_class_install_action (widget_class, "sms.new", NULL, sms_new_action);
802 : 1 : gtk_widget_class_install_action (widget_class, "sms.search", NULL, sms_search_action);
803 : :
804 : 2 : properties [PROP_MESSAGES] =
805 : 1 : g_param_spec_object ("messages", NULL, NULL,
806 : : VALENT_TYPE_MESSAGES,
807 : : (G_PARAM_READABLE |
808 : : G_PARAM_STATIC_STRINGS));
809 : :
810 : 1 : g_object_class_install_properties (object_class, G_N_ELEMENTS (properties), properties);
811 : :
812 : 1 : g_type_ensure (VALENT_TYPE_CONTACT_PAGE);
813 : 1 : }
814 : :
815 : : static void
816 : 1 : valent_messages_window_init (ValentMessagesWindow *self)
817 : : {
818 : 1 : gtk_widget_init_template (GTK_WIDGET (self));
819 : :
820 : 1 : g_set_object (&self->contacts, G_LIST_MODEL (valent_contacts_get_default ()));
821 : 1 : g_set_object (&self->messages, G_LIST_MODEL (valent_messages_get_default ()));
822 : 1 : g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_MESSAGES]);
823 : :
824 : 1 : gtk_list_box_set_header_func (self->search_list,
825 : : search_header_func,
826 : : self, NULL);
827 : 1 : }
828 : :
829 : : /*< private >
830 : : * valent_messages_window_set_active_message:
831 : : * @window: a `ValentMessagesWindow`
832 : : * @message: a `ValentMessage`
833 : : *
834 : : * Set the active conversation to the thread of @message scroll to @message.
835 : : */
836 : : void
837 : 0 : valent_messages_window_set_active_message (ValentMessagesWindow *window,
838 : : ValentMessage *message)
839 : : {
840 : 0 : GtkWidget *widget;
841 : 0 : ValentConversationPage *conversation;
842 : 0 : int64_t thread_id;
843 : 0 : const char *adapter_urn = NULL;
844 : 0 : g_autofree char *thread_urn = NULL;
845 : :
846 [ # # ]: 0 : g_return_if_fail (VALENT_IS_MESSAGES_WINDOW (window));
847 : :
848 : 0 : thread_id = valent_message_get_thread_id (message);
849 : 0 : adapter_urn = valent_resource_get_iri (VALENT_RESOURCE (window->messages_adapter));
850 : 0 : thread_urn = g_strdup_printf ("%s:%"PRId64, adapter_urn, thread_id);
851 : :
852 : 0 : widget = valent_messages_window_ensure_conversation (window, thread_urn);
853 : 0 : conversation = VALENT_CONVERSATION_PAGE (widget);
854 : 0 : valent_conversation_page_scroll_to_message (conversation, message);
855 : : }
856 : :
857 : : /**
858 : : * valent_messages_window_set_active_thread:
859 : : * @window: a `ValentMessagesWindow`
860 : : * @iri: a thread IRI
861 : : *
862 : : * Set the active conversation
863 : : */
864 : : void
865 : 0 : valent_messages_window_set_active_thread (ValentMessagesWindow *window,
866 : : const char *iri)
867 : : {
868 [ # # ]: 0 : g_return_if_fail (VALENT_IS_MESSAGES_WINDOW (window));
869 [ # # ]: 0 : g_return_if_fail (iri != NULL);
870 : :
871 : 0 : valent_messages_window_ensure_conversation (window, iri);
872 : : }
873 : :
|