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