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