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-contact-row"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <adwaita.h>
9 : : #include <glib/gi18n.h>
10 : : #include <gtk/gtk.h>
11 : : #include <pango/pango.h>
12 : : #include <valent.h>
13 : :
14 : : #include "valent-contact-row.h"
15 : : #include "valent-sms-utils.h"
16 : :
17 : :
18 : : struct _ValentContactRow
19 : : {
20 : : GtkListBoxRow parent_instance;
21 : :
22 : : EContact *contact;
23 : :
24 : : GtkWidget *grid;
25 : : GtkWidget *avatar;
26 : : GtkWidget *name_label;
27 : : GtkWidget *address_label;
28 : : GtkWidget *address_type_label;
29 : : };
30 : :
31 [ + + + - ]: 229 : G_DEFINE_FINAL_TYPE (ValentContactRow, valent_contact_row, GTK_TYPE_LIST_BOX_ROW)
32 : :
33 : : enum {
34 : : PROP_0,
35 : : PROP_CONTACT,
36 : : PROP_CONTACT_ADDRESS,
37 : : PROP_CONTACT_NAME,
38 : : N_PROPERTIES
39 : : };
40 : :
41 : : static GParamSpec *properties[N_PROPERTIES] = { NULL, };
42 : :
43 : :
44 : : /*
45 : : * GObject
46 : : */
47 : : static void
48 : 9 : valent_contact_row_finalize (GObject *object)
49 : : {
50 : 9 : ValentContactRow *self = VALENT_CONTACT_ROW (object);
51 : :
52 [ + - ]: 9 : g_clear_object (&self->contact);
53 : :
54 : 9 : G_OBJECT_CLASS (valent_contact_row_parent_class)->finalize (object);
55 : 9 : }
56 : :
57 : : static void
58 : 3 : valent_contact_row_get_property (GObject *object,
59 : : guint prop_id,
60 : : GValue *value,
61 : : GParamSpec *pspec)
62 : : {
63 : 3 : ValentContactRow *self = VALENT_CONTACT_ROW (object);
64 : :
65 [ + + + - ]: 3 : switch (prop_id)
66 : : {
67 : 1 : case PROP_CONTACT:
68 : 1 : g_value_set_object (value, valent_contact_row_get_contact (self));
69 : 1 : break;
70 : :
71 : 1 : case PROP_CONTACT_ADDRESS:
72 : 1 : g_value_set_string (value, valent_contact_row_get_contact_address (self));
73 : 1 : break;
74 : :
75 : 1 : case PROP_CONTACT_NAME:
76 : 1 : g_value_set_string (value, valent_contact_row_get_contact_name (self));
77 : 1 : break;
78 : :
79 : 0 : default:
80 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
81 : : }
82 : 3 : }
83 : :
84 : : static void
85 : 11 : valent_contact_row_set_property (GObject *object,
86 : : guint prop_id,
87 : : const GValue *value,
88 : : GParamSpec *pspec)
89 : : {
90 : 11 : ValentContactRow *self = VALENT_CONTACT_ROW (object);
91 : :
92 [ + + + - ]: 11 : switch (prop_id)
93 : : {
94 : 9 : case PROP_CONTACT:
95 : 9 : valent_contact_row_set_contact (self, g_value_get_object (value));
96 : 9 : break;
97 : :
98 : 1 : case PROP_CONTACT_ADDRESS:
99 : 1 : valent_contact_row_set_contact_address (self, g_value_get_string (value));
100 : 1 : break;
101 : :
102 : 1 : case PROP_CONTACT_NAME:
103 : 1 : valent_contact_row_set_contact_name (self, g_value_get_string (value));
104 : 1 : break;
105 : :
106 : 0 : default:
107 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
108 : : }
109 : 11 : }
110 : :
111 : : static void
112 : 2 : valent_contact_row_class_init (ValentContactRowClass *klass)
113 : : {
114 : 2 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
115 : :
116 : 2 : object_class->finalize = valent_contact_row_finalize;
117 : 2 : object_class->get_property = valent_contact_row_get_property;
118 : 2 : object_class->set_property = valent_contact_row_set_property;
119 : :
120 : : /**
121 : : * ValentContactRow:contact
122 : : *
123 : : * The `EContact` for this row.
124 : : */
125 : 4 : properties [PROP_CONTACT] =
126 : 2 : g_param_spec_object ("contact", NULL, NULL,
127 : : E_TYPE_CONTACT,
128 : : (G_PARAM_READWRITE |
129 : : G_PARAM_EXPLICIT_NOTIFY |
130 : : G_PARAM_STATIC_STRINGS));
131 : :
132 : : /**
133 : : * ValentContactRow:contact-address
134 : : *
135 : : * The phone number, e-mail or other address format for the contact.
136 : : *
137 : : * Usually this will be a phone number, however SMS messages may originate
138 : : * from an SMS gateway service. In this case the address may be in another
139 : : * format.
140 : : */
141 : 4 : properties [PROP_CONTACT_ADDRESS] =
142 : 2 : g_param_spec_string ("contact-address", NULL, NULL,
143 : : NULL,
144 : : (G_PARAM_READWRITE |
145 : : G_PARAM_EXPLICIT_NOTIFY |
146 : : G_PARAM_STATIC_STRINGS));
147 : :
148 : : /**
149 : : * ValentContactRow:contact-name
150 : : *
151 : : * The contact name displayed in the row, by default the full name of
152 : : * `ValentContactRow`:contact.
153 : : */
154 : 4 : properties [PROP_CONTACT_NAME] =
155 : 2 : g_param_spec_string ("contact-name", NULL, NULL,
156 : : NULL,
157 : : (G_PARAM_READWRITE |
158 : : G_PARAM_EXPLICIT_NOTIFY |
159 : : G_PARAM_STATIC_STRINGS));
160 : :
161 : 2 : g_object_class_install_properties (object_class, N_PROPERTIES, properties);
162 : 2 : }
163 : :
164 : : static void
165 : 9 : valent_contact_row_init (ValentContactRow *self)
166 : : {
167 : 9 : gtk_widget_add_css_class (GTK_WIDGET (self), "valent-contact-row");
168 : :
169 : 9 : self->grid = g_object_new (GTK_TYPE_GRID,
170 : : "column-spacing", 8,
171 : : "margin-start", 8,
172 : : "margin-end", 8,
173 : : "margin-top", 6,
174 : : "margin-bottom", 6,
175 : : NULL);
176 : 9 : gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (self), self->grid);
177 : :
178 : 9 : self->avatar = g_object_new (ADW_TYPE_AVATAR,
179 : : "size", 32,
180 : : "halign", GTK_ALIGN_START,
181 : : "valign", GTK_ALIGN_CENTER,
182 : : "vexpand", TRUE,
183 : : NULL);
184 : 9 : gtk_grid_attach (GTK_GRID (self->grid), self->avatar, 0, 0, 1, 2);
185 : :
186 : 9 : self->name_label = g_object_new (GTK_TYPE_LABEL,
187 : : "halign", GTK_ALIGN_START,
188 : : "hexpand", TRUE,
189 : : "valign", GTK_ALIGN_CENTER,
190 : : "vexpand", TRUE,
191 : : "xalign", 0.0,
192 : : NULL);
193 : 9 : gtk_grid_attach (GTK_GRID (self->grid), self->name_label, 1, 0, 2, 1);
194 : :
195 : 9 : self->address_label = g_object_new (GTK_TYPE_LABEL,
196 : : "ellipsize", PANGO_ELLIPSIZE_END,
197 : : "halign", GTK_ALIGN_START,
198 : : "hexpand", TRUE,
199 : : "valign", GTK_ALIGN_CENTER,
200 : : "vexpand", TRUE,
201 : : "xalign", 0.0,
202 : : NULL);
203 : 9 : gtk_widget_add_css_class (self->address_label, "dim-label");
204 : 9 : gtk_grid_attach (GTK_GRID (self->grid), self->address_label, 1, 1, 1, 1);
205 : :
206 : 9 : self->address_type_label = g_object_new (GTK_TYPE_LABEL,
207 : : "label", _("Other"),
208 : : "ellipsize", PANGO_ELLIPSIZE_END,
209 : : "halign", GTK_ALIGN_END,
210 : : "hexpand", FALSE,
211 : : "valign", GTK_ALIGN_CENTER,
212 : : "vexpand", TRUE,
213 : : "xalign", 0.0,
214 : : NULL);
215 : 9 : gtk_widget_add_css_class (self->address_type_label, "dim-label");
216 : 9 : gtk_grid_attach (GTK_GRID (self->grid), self->address_type_label, 2, 1, 1, 1);
217 : 9 : }
218 : :
219 : : /**
220 : : * valent_contact_row_header_func:
221 : : * @row: a `GtkListBoxRow`
222 : : * @before: (nullable): a `GtkListBoxRow`
223 : : * @user_data: (closure): user supplied data
224 : : *
225 : : * A `GtkListBoxHeaderFunc` for `ValentContactRow` widgets that takes care of
226 : : * hiding or showing the avatar and name depending on whether the row is
227 : : * grouped with other rows for the same contact.
228 : : *
229 : : * For example, if @before is not a `ValentContactRow` or for a different `EContact`
230 : : * the avatar and name will be shown, otherwise it's considered a secondary row.
231 : : */
232 : : void
233 : 36 : valent_contact_row_header_func (GtkListBoxRow *row,
234 : : GtkListBoxRow *before,
235 : : gpointer user_data)
236 : : {
237 : 36 : ValentContactRow *contact_row;
238 : :
239 [ + - ]: 36 : if G_UNLIKELY (!VALENT_IS_CONTACT_ROW (row))
240 : : return;
241 : :
242 : 36 : contact_row = VALENT_CONTACT_ROW (row);
243 : :
244 [ + + ]: 36 : if (before == NULL)
245 : : {
246 : 12 : valent_contact_row_set_compact (contact_row, FALSE);
247 : : }
248 [ - + ]: 24 : else if (!VALENT_IS_CONTACT_ROW (before))
249 : : {
250 : 0 : GtkWidget *label;
251 : :
252 : 0 : label = g_object_new (GTK_TYPE_LABEL,
253 : : "label", _("Contacts"),
254 : : "halign", GTK_ALIGN_START,
255 : : "margin-end", 6,
256 : : "margin-start", 6,
257 : : "margin-top", 6,
258 : : NULL);
259 : 0 : gtk_widget_add_css_class (label, "dim-label");
260 : 0 : gtk_widget_add_css_class (label, "list-header-title");
261 : 0 : gtk_list_box_row_set_header (row, label);
262 : :
263 : 0 : valent_contact_row_set_compact (contact_row, FALSE);
264 : : }
265 : : else
266 : : {
267 : 24 : EContact *row_contact;
268 : 24 : EContact *before_contact;
269 : :
270 : 24 : row_contact = valent_contact_row_get_contact (contact_row);
271 : 24 : before_contact = valent_contact_row_get_contact (VALENT_CONTACT_ROW (before));
272 : :
273 [ + + ]: 24 : if (g_strcmp0 (e_contact_get_const (row_contact, E_CONTACT_UID),
274 : 24 : e_contact_get_const (before_contact, E_CONTACT_UID)) == 0)
275 : 13 : valent_contact_row_set_compact (contact_row, TRUE);
276 : : else
277 : 11 : valent_contact_row_set_compact (contact_row, FALSE);
278 : : }
279 : : }
280 : :
281 : : /**
282 : : * valent_list_add_contact:
283 : : * @list: a `GtkListBox`
284 : : * @contact: an `EContact`
285 : : *
286 : : * A convenience for adding a `ValentContactRow` to @list for each @contact
287 : : * number.
288 : : */
289 : : void
290 : 4 : valent_list_add_contact (GtkListBox *list,
291 : : EContact *contact)
292 : : {
293 : 4 : g_autolist (EVCardAttribute) attrs = NULL;
294 : :
295 [ + - + - : 4 : g_return_if_fail (GTK_IS_LIST_BOX (list));
- + - - ]
296 [ + - + - : 4 : g_return_if_fail (E_IS_CONTACT (contact));
- + - - ]
297 : :
298 : 4 : attrs = e_contact_get_attributes (contact, E_CONTACT_TEL);
299 : :
300 [ + + ]: 12 : for (const GList *iter = attrs; iter; iter = iter->next)
301 : : {
302 : 8 : EVCardAttribute *attr = iter->data;
303 : 8 : ValentContactRow *row;
304 : 8 : g_autofree char *number = NULL;
305 : 8 : const char *type = NULL;
306 : :
307 : 8 : number = e_vcard_attribute_get_value (attr);
308 : :
309 : 8 : row = g_object_new (VALENT_TYPE_CONTACT_ROW,
310 : : "contact", contact,
311 : : NULL);
312 : :
313 : : /* NOTE: the ordering below results in a preference of Work, Mobile, Home.
314 : : * Justification being that Work is more important context than mobility,
315 : : * while mobility is more relevant than Home if the number is personal. */
316 [ + + ]: 8 : if (e_vcard_attribute_has_type (attr, "WORK"))
317 : 4 : type = _("Work");
318 : :
319 [ + - ]: 4 : else if (e_vcard_attribute_has_type (attr, "CELL"))
320 : 4 : type = _("Mobile");
321 : :
322 [ # # ]: 0 : else if (e_vcard_attribute_has_type (attr, "HOME"))
323 : 0 : type = _("Home");
324 : :
325 : : else
326 : 0 : type = _("Other");
327 : :
328 : 8 : gtk_label_set_label (GTK_LABEL (row->address_label), number);
329 : 8 : gtk_label_set_label (GTK_LABEL (row->address_type_label), type);
330 : :
331 : 8 : gtk_list_box_insert (list, GTK_WIDGET (row), -1);
332 : : }
333 : : }
334 : :
335 : : /**
336 : : * valent_contact_row_new:
337 : : * @contact: an `EContact`
338 : : *
339 : : * Create a new `ValentContactRow` for @contact.
340 : : *
341 : : * Returns: (transfer full): a new `ValentContactRow`
342 : : */
343 : : GtkWidget *
344 : 1 : valent_contact_row_new (EContact *contact)
345 : : {
346 : 1 : return g_object_new (VALENT_TYPE_CONTACT_ROW,
347 : : "contact", contact,
348 : : NULL);
349 : : }
350 : :
351 : : /**
352 : : * valent_contact_row_set_compact:
353 : : * @row: a `ValentContactRow`
354 : : * @compact: %TRUE or %FALSE
355 : : *
356 : : * Set whether @row should display the name and avatar (%FALSE) or not (%TRUE).
357 : : */
358 : : void
359 : 45 : valent_contact_row_set_compact (ValentContactRow *row,
360 : : gboolean compact)
361 : : {
362 [ + - ]: 45 : g_return_if_fail (VALENT_IS_CONTACT_ROW (row));
363 : :
364 [ + + ]: 45 : if (compact)
365 : : {
366 : 13 : gtk_widget_set_visible (row->name_label, FALSE);
367 : 13 : gtk_widget_set_visible (row->avatar, FALSE);
368 : : // avatar (32px) + column spacing (8px)
369 : 13 : gtk_widget_set_margin_start (row->grid, 48);
370 : : }
371 : : else
372 : : {
373 : 32 : gtk_widget_set_visible (row->name_label, TRUE);
374 : 32 : gtk_widget_set_visible (row->avatar, TRUE);
375 : 32 : gtk_widget_set_margin_start (row->grid, 8);
376 : : }
377 : : }
378 : :
379 : : /**
380 : : * valent_contact_row_get_contact:
381 : : * @row: a `ValentContactRow`
382 : : *
383 : : * Get the `EContact` for @row.
384 : : *
385 : : * Returns: (transfer none): a `EContact`
386 : : */
387 : : EContact *
388 : 49 : valent_contact_row_get_contact (ValentContactRow *row)
389 : : {
390 [ + - ]: 49 : g_return_val_if_fail (VALENT_IS_CONTACT_ROW (row), NULL);
391 : :
392 : 49 : return row->contact;
393 : : }
394 : :
395 : : /**
396 : : * valent_contact_row_set_contact:
397 : : * @row: a `ValentContactRow`
398 : : * @contact: a `ValentContact`
399 : : *
400 : : * Set the `ValentContact` for @row.
401 : : */
402 : : void
403 : 9 : valent_contact_row_set_contact (ValentContactRow *row,
404 : : EContact *contact)
405 : : {
406 : 9 : const char *name = NULL;
407 : :
408 [ + - ]: 9 : g_return_if_fail (VALENT_IS_CONTACT_ROW (row));
409 [ + - + - : 9 : g_return_if_fail (contact == NULL || E_IS_CONTACT (contact));
- + - - ]
410 : :
411 [ + - ]: 9 : if (!g_set_object (&row->contact, contact))
412 : : return;
413 : :
414 [ + - ]: 9 : if (row->contact != NULL)
415 : : {
416 : 9 : name = e_contact_get_const (contact, E_CONTACT_FULL_NAME);
417 : 9 : valent_sms_avatar_from_contact (ADW_AVATAR (row->avatar), contact);
418 : : }
419 : :
420 : 9 : valent_contact_row_set_compact (row, FALSE);
421 : 9 : valent_contact_row_set_contact_name (row, name);
422 : 9 : g_object_notify_by_pspec (G_OBJECT (row), properties [PROP_CONTACT]);
423 : : }
424 : :
425 : : /**
426 : : * valent_contact_row_get_contact_address:
427 : : * @row: a `ValentContactRow`
428 : : *
429 : : * Get the contact address displayed in @row.
430 : : *
431 : : * Returns: (transfer none): a phone number string
432 : : */
433 : : const char *
434 : 1 : valent_contact_row_get_contact_address (ValentContactRow *row)
435 : : {
436 [ + - ]: 1 : g_return_val_if_fail (VALENT_IS_CONTACT_ROW (row), NULL);
437 : :
438 : 1 : return gtk_label_get_text (GTK_LABEL (row->address_label));
439 : : }
440 : :
441 : : /**
442 : : * valent_contact_row_set_contact_address:
443 : : * @row: a `ValentContactRow`
444 : : * @address: a phone number or other address
445 : : *
446 : : * Set the contact address displayed in @row.
447 : : */
448 : : void
449 : 1 : valent_contact_row_set_contact_address (ValentContactRow *row,
450 : : const char *address)
451 : : {
452 [ + - ]: 1 : g_return_if_fail (VALENT_IS_CONTACT_ROW (row));
453 : :
454 : 1 : gtk_label_set_text (GTK_LABEL (row->address_label), address);
455 : 1 : gtk_label_set_text (GTK_LABEL (row->address_type_label), _("Other"));
456 : : }
457 : :
458 : : /**
459 : : * valent_contact_row_get_contact_name:
460 : : * @row: a `ValentContactRow`
461 : : *
462 : : * Get the contact name displayed in @row.
463 : : *
464 : : * Returns: (transfer none): a contact name
465 : : */
466 : : const char *
467 : 41 : valent_contact_row_get_contact_name (ValentContactRow *row)
468 : : {
469 [ + - ]: 41 : g_return_val_if_fail (VALENT_IS_CONTACT_ROW (row), NULL);
470 : :
471 : 41 : return gtk_label_get_text (GTK_LABEL (row->name_label));
472 : : }
473 : :
474 : : /**
475 : : * valent_contact_row_set_contact_name:
476 : : * @row: a `ValentContactRow`
477 : : * @name: a contact name
478 : : *
479 : : * Set the contact name displayed in @row.
480 : : */
481 : : void
482 : 10 : valent_contact_row_set_contact_name (ValentContactRow *row,
483 : : const char *name)
484 : : {
485 [ + - ]: 10 : g_return_if_fail (VALENT_IS_CONTACT_ROW (row));
486 : :
487 [ - + - - : 10 : if (name == NULL && E_IS_CONTACT (row->contact))
- - - - -
- ]
488 : 0 : name = e_contact_get_const (row->contact, E_CONTACT_FULL_NAME);
489 : :
490 : 10 : gtk_label_set_text (GTK_LABEL (row->name_label), name);
491 : : }
492 : :
|