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-conversation-row"
5 : :
6 : : #include "config.h"
7 : :
8 : : #include <adwaita.h>
9 : : #include <glib/gi18n.h>
10 : : #include <gtk/gtk.h>
11 : : #include <libebook-contacts/libebook-contacts.h>
12 : : #include <valent.h>
13 : :
14 : : #include "valent-date-label.h"
15 : : #include "valent-ui-utils-private.h"
16 : :
17 : : #include "valent-conversation-row.h"
18 : :
19 : :
20 : : struct _ValentConversationRow
21 : : {
22 : : GtkListBoxRow parent_instance;
23 : :
24 : : ValentMessage *message;
25 : : EContact *contact;
26 : : unsigned int incoming : 1;
27 : :
28 : : /* template */
29 : : GtkWidget *layout;
30 : : GtkWidget *avatar;
31 : : GtkWidget *sender_label;
32 : : GtkWidget *attachment_list;
33 : : GtkWidget *message_bubble;
34 : : GtkWidget *summary_label;
35 : : GtkWidget *body_label;
36 : : GtkWidget *date_label;
37 : : GtkWidget *context_menu;
38 : : };
39 : :
40 [ + + + - ]: 14 : G_DEFINE_FINAL_TYPE (ValentConversationRow, valent_conversation_row, GTK_TYPE_LIST_BOX_ROW)
41 : :
42 : :
43 : : typedef enum {
44 : : PROP_CONTACT = 1,
45 : : PROP_DATE,
46 : : PROP_MESSAGE,
47 : : } ValentConversationRowProperty;
48 : :
49 : : static GParamSpec *properties[PROP_MESSAGE + 1] = { NULL, };
50 : :
51 : :
52 : : /* LCOV_EXCL_START */
53 : : static gboolean
54 : : on_activate_link (GtkLabel *label,
55 : : const char *uri,
56 : : gpointer user_data)
57 : : {
58 : : GtkWidget *widget = GTK_WIDGET (label);
59 : : GtkWindow *toplevel = GTK_WINDOW (gtk_widget_get_root (widget));
60 : : g_autoptr (GtkUriLauncher) launcher = NULL;
61 : : g_autofree char *url = NULL;
62 : :
63 : : /* Only handle links that need to be amended with a scheme */
64 : : if (g_uri_peek_scheme (uri) != NULL)
65 : : return FALSE;
66 : :
67 : : if (!GTK_IS_WINDOW (toplevel))
68 : : return FALSE;
69 : :
70 : : url = g_strdup_printf ("https://%s", uri);
71 : : launcher = gtk_uri_launcher_new (url);
72 : : gtk_uri_launcher_launch (launcher, toplevel, NULL, NULL, NULL);
73 : :
74 : : return TRUE;
75 : : }
76 : :
77 : : static void
78 : : on_menu_popup (GtkGestureClick *gesture,
79 : : unsigned int n_press,
80 : : double x,
81 : : double y,
82 : : ValentConversationRow *self)
83 : : {
84 : : gtk_popover_set_pointing_to (GTK_POPOVER (self->context_menu),
85 : : &(GdkRectangle){ (int)x, (int)y });
86 : : gtk_popover_popup (GTK_POPOVER (self->context_menu));
87 : : }
88 : : /* LCOV_EXCL_STOP */
89 : :
90 : : static void
91 : 0 : clipboard_copy_action (GtkWidget *widget,
92 : : const char *action_name,
93 : : GVariant *parameter)
94 : : {
95 : 0 : ValentConversationRow *self = VALENT_CONVERSATION_ROW (widget);
96 : 0 : const char *text;
97 : :
98 [ # # ]: 0 : g_assert (VALENT_IS_CONVERSATION_ROW (self));
99 : :
100 : 0 : text = gtk_label_get_text (GTK_LABEL (self->body_label));
101 [ # # # # ]: 0 : if (text != NULL && *text != '\0')
102 : 0 : gdk_clipboard_set_text (gtk_widget_get_clipboard (widget), text);
103 : 0 : }
104 : :
105 : : static void
106 : 0 : menu_popup_action (GtkWidget *widget,
107 : : const char *action_name,
108 : : GVariant *parameter)
109 : : {
110 : 0 : ValentConversationRow *self = VALENT_CONVERSATION_ROW (widget);
111 : :
112 [ # # ]: 0 : g_assert (VALENT_IS_CONVERSATION_ROW (self));
113 : :
114 : 0 : gtk_popover_popup (GTK_POPOVER (self->context_menu));
115 : 0 : }
116 : :
117 : : /*
118 : : * ValentConversationRow
119 : : */
120 : : static GtkWidget *
121 : 0 : attachment_list_create (gpointer item,
122 : : gpointer user_data)
123 : : {
124 : 0 : ValentMessageAttachment *attachment = VALENT_MESSAGE_ATTACHMENT (item);
125 : 0 : GtkWidget *row;
126 : 0 : GtkWidget *image;
127 : 0 : GIcon *preview;
128 : 0 : GFile *file;
129 : 0 : g_autofree char *filename = NULL;
130 : :
131 : 0 : row = g_object_new (GTK_TYPE_LIST_BOX_ROW,
132 : : "activatable", TRUE,
133 : : "selectable", FALSE,
134 : : NULL);
135 : 0 : gtk_widget_add_css_class (row, "card");
136 : :
137 : 0 : preview = valent_message_attachment_get_preview (attachment);
138 : 0 : file = valent_message_attachment_get_file (attachment);
139 [ # # ]: 0 : if (file != NULL)
140 : 0 : filename = g_file_get_basename (file);
141 : :
142 : 0 : image = g_object_new (GTK_TYPE_IMAGE,
143 : : "gicon", preview,
144 : : "pixel-size", 100,
145 : : "overflow", GTK_OVERFLOW_HIDDEN,
146 : : "tooltip-text", filename,
147 : : NULL);
148 : 0 : gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), image);
149 : :
150 : 0 : return row;
151 : : }
152 : :
153 : : static void
154 : 2 : valent_conversation_row_sync (ValentConversationRow *self)
155 : : {
156 : 2 : ValentMessageBox box;
157 : 2 : const char *sender = NULL;
158 : 2 : const char *body = NULL;
159 : 2 : GListModel *attachments;
160 : :
161 [ + - ]: 2 : g_return_if_fail (VALENT_IS_CONVERSATION_ROW (self));
162 : :
163 : : /* Reset the row
164 : : */
165 : 2 : gtk_widget_set_visible (self->avatar, FALSE);
166 : 2 : gtk_widget_set_visible (self->sender_label, FALSE);
167 : 2 : gtk_widget_set_visible (self->attachment_list, FALSE);
168 : 2 : gtk_widget_set_visible (self->summary_label, FALSE);
169 : 2 : gtk_widget_set_visible (self->body_label, FALSE);
170 : 2 : gtk_list_box_bind_model (GTK_LIST_BOX (self->attachment_list),
171 : : NULL, NULL, NULL, NULL);
172 : 2 : self->incoming = FALSE;
173 : :
174 [ + + ]: 2 : if (self->message == NULL)
175 : : return;
176 : :
177 : : /* Sent/Received style
178 : : */
179 : 1 : box = valent_message_get_box (self->message);
180 [ - + ]: 1 : if (box == VALENT_MESSAGE_BOX_INBOX)
181 : : {
182 : 0 : gtk_widget_add_css_class (GTK_WIDGET (self), "valent-message-inbox");
183 : 0 : gtk_widget_remove_css_class (GTK_WIDGET (self), "valent-message-outbox");
184 : 0 : gtk_widget_remove_css_class (GTK_WIDGET (self), "valent-message-sent");
185 : : }
186 [ - + ]: 1 : else if (box == VALENT_MESSAGE_BOX_SENT)
187 : : {
188 : 0 : gtk_widget_add_css_class (GTK_WIDGET (self), "valent-message-sent");
189 : 0 : gtk_widget_remove_css_class (GTK_WIDGET (self), "valent-message-inbox");
190 : 0 : gtk_widget_remove_css_class (GTK_WIDGET (self), "valent-message-outbox");
191 : : }
192 [ + - ]: 1 : else if (box == VALENT_MESSAGE_BOX_OUTBOX)
193 : : {
194 : 1 : gtk_widget_add_css_class (GTK_WIDGET (self), "valent-message-outbox");
195 : 1 : gtk_widget_remove_css_class (GTK_WIDGET (self), "valent-message-inbox");
196 : 1 : gtk_widget_remove_css_class (GTK_WIDGET (self), "valent-message-sent");
197 : : }
198 : :
199 : 1 : self->incoming = (box == VALENT_MESSAGE_BOX_INBOX);
200 [ - + ]: 1 : if (self->incoming)
201 : : {
202 : 0 : gtk_widget_set_halign (GTK_WIDGET (self), GTK_ALIGN_START);
203 : 0 : gtk_widget_set_visible (self->avatar, TRUE);
204 : 0 : g_object_set (self->date_label, "xalign", 0.0, NULL);
205 : : }
206 : : else
207 : : {
208 : 1 : gtk_widget_set_halign (GTK_WIDGET (self), GTK_ALIGN_END);
209 : 1 : g_object_set (self->date_label, "xalign", 1.0, NULL);
210 : : }
211 : :
212 : : /* Attachments
213 : : */
214 : 1 : attachments = valent_message_get_attachments (self->message);
215 : 1 : gtk_list_box_bind_model (GTK_LIST_BOX (self->attachment_list),
216 : : attachments,
217 : : attachment_list_create,
218 : : NULL, NULL);
219 : :
220 [ + - ]: 1 : if (attachments != NULL)
221 : : {
222 : 1 : gtk_widget_set_visible (self->attachment_list,
223 : 1 : g_list_model_get_n_items (attachments) > 0);
224 : : }
225 : :
226 : : /* Sender
227 : : */
228 [ - + ]: 1 : if (self->incoming)
229 : : {
230 : 0 : sender = valent_message_get_sender (self->message);
231 [ # # ]: 0 : if (self->contact != NULL)
232 : 0 : sender = e_contact_get_const (self->contact, E_CONTACT_FULL_NAME);
233 : : }
234 : :
235 : 1 : gtk_widget_set_visible (self->sender_label, FALSE /* self->incoming */);
236 : 1 : gtk_label_set_label (GTK_LABEL (self->sender_label), sender);
237 : :
238 : : /* Summary & Body (Message Bubble)
239 : : */
240 : 1 : body = valent_message_get_text (self->message);
241 [ + - + - ]: 1 : if (body != NULL && *body != '\0')
242 : : {
243 : 1 : g_autofree char *markup = NULL;
244 : :
245 : 1 : markup = valent_string_to_markup (body);
246 : 1 : gtk_label_set_label (GTK_LABEL (self->body_label), markup);
247 : 1 : gtk_widget_set_visible (self->body_label, TRUE);
248 : : }
249 : :
250 [ + - + - ]: 2 : if (gtk_widget_get_visible (self->summary_label) ||
251 : 1 : gtk_widget_get_visible (self->body_label))
252 : 1 : gtk_widget_set_visible (self->message_bubble, TRUE);
253 : : else
254 : 0 : gtk_widget_set_visible (self->message_bubble, FALSE);
255 : : }
256 : :
257 : : /*
258 : : * GObject
259 : : */
260 : : static void
261 : 1 : valent_conversation_row_finalize (GObject *object)
262 : : {
263 : 1 : ValentConversationRow *self = VALENT_CONVERSATION_ROW (object);
264 : :
265 [ + - ]: 1 : g_clear_object (&self->contact);
266 : :
267 [ + - ]: 1 : if (self->message)
268 : : {
269 : 1 : g_signal_handlers_disconnect_by_data (self->message, self);
270 [ + - ]: 1 : g_clear_object (&self->message);
271 : : }
272 : :
273 : 1 : G_OBJECT_CLASS (valent_conversation_row_parent_class)->finalize (object);
274 : 1 : }
275 : :
276 : : static void
277 : 13 : valent_conversation_row_get_property (GObject *object,
278 : : guint prop_id,
279 : : GValue *value,
280 : : GParamSpec *pspec)
281 : : {
282 : 13 : ValentConversationRow *self = VALENT_CONVERSATION_ROW (object);
283 : :
284 [ + + + - ]: 13 : switch ((ValentConversationRowProperty)prop_id)
285 : : {
286 : 7 : case PROP_CONTACT:
287 : 7 : g_value_set_object (value, self->contact);
288 : 7 : break;
289 : :
290 : 1 : case PROP_DATE:
291 : 1 : g_value_set_int64 (value, valent_conversation_row_get_date (self));
292 : 1 : break;
293 : :
294 : 5 : case PROP_MESSAGE:
295 : 5 : g_value_set_object (value, self->message);
296 : 5 : break;
297 : :
298 : 0 : default:
299 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
300 : : }
301 : 13 : }
302 : :
303 : : static void
304 : 2 : valent_conversation_row_set_property (GObject *object,
305 : : guint prop_id,
306 : : const GValue *value,
307 : : GParamSpec *pspec)
308 : : {
309 : 2 : ValentConversationRow *self = VALENT_CONVERSATION_ROW (object);
310 : :
311 [ + + - ]: 2 : switch ((ValentConversationRowProperty)prop_id)
312 : : {
313 : 1 : case PROP_CONTACT:
314 : 1 : valent_conversation_row_set_contact (self, g_value_get_object (value));
315 : 1 : break;
316 : :
317 : 1 : case PROP_MESSAGE:
318 : 1 : valent_conversation_row_set_message (self, g_value_get_object (value));
319 : 1 : break;
320 : :
321 : 0 : case PROP_DATE:
322 : : default:
323 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
324 : : }
325 : 2 : }
326 : :
327 : : static void
328 : 1 : valent_conversation_row_class_init (ValentConversationRowClass *klass)
329 : : {
330 : 1 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
331 : 1 : GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
332 : :
333 : 1 : object_class->finalize = valent_conversation_row_finalize;
334 : 1 : object_class->get_property = valent_conversation_row_get_property;
335 : 1 : object_class->set_property = valent_conversation_row_set_property;
336 : :
337 : 1 : gtk_widget_class_set_template_from_resource (widget_class, "/plugins/gnome/valent-conversation-row.ui");
338 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentConversationRow, layout);
339 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentConversationRow, avatar);
340 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentConversationRow, sender_label);
341 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentConversationRow, attachment_list);
342 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentConversationRow, message_bubble);
343 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentConversationRow, summary_label);
344 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentConversationRow, body_label);
345 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentConversationRow, date_label);
346 : 1 : gtk_widget_class_bind_template_child (widget_class, ValentConversationRow, context_menu);
347 : 1 : gtk_widget_class_bind_template_callback (widget_class, on_activate_link);
348 : 1 : gtk_widget_class_bind_template_callback (widget_class, on_menu_popup);
349 : 1 : gtk_widget_class_bind_template_callback (widget_class, valent_contact_to_paintable);
350 : :
351 : : /**
352 : : * ValentConversationRow|clipboard.copy:
353 : : *
354 : : * Copies the message text to the clipboard.
355 : : */
356 : 1 : gtk_widget_class_install_action (widget_class, "clipboard.copy", NULL, clipboard_copy_action);
357 : :
358 : : /**
359 : : * ValentConversationRow|menu.popup:
360 : : *
361 : : * Opens the context menu.
362 : : */
363 : 1 : gtk_widget_class_install_action (widget_class, "menu.popup", NULL, menu_popup_action);
364 : 1 : gtk_widget_class_add_binding_action (widget_class,
365 : : GDK_KEY_F10, GDK_SHIFT_MASK,
366 : : "menu.popup",
367 : : NULL);
368 : 1 : gtk_widget_class_add_binding_action (widget_class,
369 : : GDK_KEY_Menu, 0,
370 : : "menu.popup",
371 : : NULL);
372 : :
373 : : /**
374 : : * ValentConversationRow:contact:
375 : : *
376 : : * The `EContact` that sent this message.
377 : : */
378 : 2 : properties [PROP_CONTACT] =
379 : 1 : g_param_spec_object ("contact", NULL, NULL,
380 : : E_TYPE_CONTACT,
381 : : (G_PARAM_READWRITE |
382 : : G_PARAM_CONSTRUCT |
383 : : G_PARAM_EXPLICIT_NOTIFY |
384 : : G_PARAM_STATIC_STRINGS));
385 : :
386 : : /**
387 : : * ValentConversationRow:date:
388 : : *
389 : : * The timestamp of the message.
390 : : */
391 : 2 : properties [PROP_DATE] =
392 : 1 : g_param_spec_int64 ("date", NULL, NULL,
393 : : 0, G_MAXINT64,
394 : : 0,
395 : : (G_PARAM_READABLE |
396 : : G_PARAM_EXPLICIT_NOTIFY |
397 : : G_PARAM_STATIC_STRINGS));
398 : :
399 : : /**
400 : : * ValentConversationRow:message:
401 : : *
402 : : * The message this row displays.
403 : : */
404 : 2 : properties [PROP_MESSAGE] =
405 : 1 : g_param_spec_object ("message", NULL, NULL,
406 : : VALENT_TYPE_MESSAGE,
407 : : (G_PARAM_READWRITE |
408 : : G_PARAM_CONSTRUCT |
409 : : G_PARAM_EXPLICIT_NOTIFY |
410 : : G_PARAM_STATIC_STRINGS));
411 : :
412 : 1 : g_object_class_install_properties (object_class, G_N_ELEMENTS (properties), properties);
413 : :
414 : 1 : g_type_ensure (VALENT_TYPE_DATE_LABEL);
415 : 1 : }
416 : :
417 : : static void
418 : 1 : valent_conversation_row_init (ValentConversationRow *self)
419 : : {
420 : 1 : gtk_widget_init_template (GTK_WIDGET (self));
421 : 1 : }
422 : :
423 : : /**
424 : : * valent_conversation_row_new:
425 : : * @message: a `ValentMessage`
426 : : * @contact: a `EContact`
427 : : *
428 : : * Create a new conversation message for @contact and @message.
429 : : *
430 : : * Returns: (transfer full): a `ValentConversationRow`
431 : : */
432 : : GtkWidget *
433 : 1 : valent_conversation_row_new (ValentMessage *message,
434 : : EContact *contact)
435 : : {
436 : 1 : return g_object_new (VALENT_TYPE_CONVERSATION_ROW,
437 : : "contact", contact,
438 : : "message", message,
439 : : NULL);
440 : : }
441 : :
442 : : /**
443 : : * valent_conversation_row_get_contact:
444 : : * @row: a `ValentConversationRow`
445 : : *
446 : : * Get the contact.
447 : : *
448 : : * Returns: (transfer none) (nullable): a `ValentContact`
449 : : */
450 : : EContact *
451 : 1 : valent_conversation_row_get_contact (ValentConversationRow *row)
452 : : {
453 [ + - ]: 1 : g_return_val_if_fail (VALENT_IS_CONVERSATION_ROW (row), NULL);
454 : :
455 : 1 : return row->contact;
456 : : }
457 : :
458 : : /**
459 : : * valent_conversation_row_set_contact:
460 : : * @row: a `ValentConversationRow`
461 : : * @contact: a `ValentContact`
462 : : *
463 : : * Set or update the contact.
464 : : */
465 : : void
466 : 1 : valent_conversation_row_set_contact (ValentConversationRow *row,
467 : : EContact *contact)
468 : : {
469 [ + - ]: 1 : g_return_if_fail (VALENT_IS_CONVERSATION_ROW (row));
470 [ + - + - : 1 : g_return_if_fail (contact == NULL || E_IS_CONTACT (contact));
- + - - ]
471 : :
472 [ + - ]: 1 : if (g_set_object (&row->contact, contact))
473 : : {
474 : 1 : valent_conversation_row_sync (row);
475 : 1 : g_object_notify_by_pspec (G_OBJECT (row), properties [PROP_CONTACT]);
476 : : }
477 : : }
478 : :
479 : : /**
480 : : * valent_conversation_row_get_date:
481 : : * @row: a `ValentConversationRow`
482 : : *
483 : : * Get the timestamp of the message.
484 : : *
485 : : * Returns: a UNIX epoch timestamp
486 : : */
487 : : int64_t
488 : 2 : valent_conversation_row_get_date (ValentConversationRow *row)
489 : : {
490 [ + - ]: 2 : g_return_val_if_fail (VALENT_IS_CONVERSATION_ROW (row), 0);
491 : :
492 [ - + ]: 2 : if G_UNLIKELY (row->message == NULL)
493 : : return 0;
494 : :
495 : 2 : return valent_message_get_date (row->message);
496 : : }
497 : :
498 : : /**
499 : : * valent_conversation_row_get_message:
500 : : * @row: a `ValentConversationRow`
501 : : *
502 : : * Get the message.
503 : : *
504 : : * Returns: (transfer none): a `ValentMessage`
505 : : */
506 : : ValentMessage *
507 : 1 : valent_conversation_row_get_message (ValentConversationRow *row)
508 : : {
509 [ + - ]: 1 : g_return_val_if_fail (VALENT_IS_CONVERSATION_ROW (row), NULL);
510 : :
511 : 1 : return row->message;
512 : : }
513 : :
514 : : /**
515 : : * valent_conversation_row_set_message:
516 : : * @row: a `ValentConversationRow`
517 : : * @message: a `ValentMessage`
518 : : *
519 : : * Set or update the message.
520 : : */
521 : : void
522 : 1 : valent_conversation_row_set_message (ValentConversationRow *row,
523 : : ValentMessage *message)
524 : : {
525 [ + - ]: 1 : g_return_if_fail (VALENT_IS_CONVERSATION_ROW (row));
526 [ + - - + ]: 1 : g_return_if_fail (message == NULL || VALENT_IS_MESSAGE (message));
527 : :
528 [ + - ]: 1 : if (row->message == message)
529 : : return;
530 : :
531 [ - + ]: 1 : if (row->message != NULL)
532 : : {
533 : 0 : g_signal_handlers_disconnect_by_data (row->message, row);
534 [ # # ]: 0 : g_clear_object (&row->message);
535 : : }
536 : :
537 [ + - ]: 1 : if (message != NULL)
538 : : {
539 : 1 : row->message = g_object_ref (message);
540 : 1 : g_signal_connect_swapped (row->message,
541 : : "notify",
542 : : G_CALLBACK (valent_conversation_row_sync),
543 : : row);
544 : : }
545 : :
546 : 1 : valent_conversation_row_sync (row);
547 : 1 : g_object_notify_by_pspec (G_OBJECT (row), properties [PROP_MESSAGE]);
548 : : }
549 : :
550 : : /**
551 : : * valent_conversation_row_is_incoming:
552 : : * @row: a `ValentConversationRow`
553 : : *
554 : : * Update @row based on the current values of `ValentConversation`:message.
555 : : */
556 : : gboolean
557 : 1 : valent_conversation_row_is_incoming (ValentConversationRow *row)
558 : : {
559 [ + - ]: 1 : g_return_val_if_fail (VALENT_IS_CONVERSATION_ROW (row), FALSE);
560 : :
561 : 1 : return row->incoming;
562 : : }
563 : :
564 : : /**
565 : : * valent_conversation_row_show_avatar:
566 : : * @row: a `ValentConversationRow`
567 : : * @visible: Whether to show the avatar
568 : : *
569 : : * Show or hide the contact avatar for @row.
570 : : */
571 : : void
572 : 2 : valent_conversation_row_show_avatar (ValentConversationRow *row,
573 : : gboolean visible)
574 : : {
575 [ + - ]: 2 : g_return_if_fail (VALENT_IS_CONVERSATION_ROW (row));
576 : :
577 [ + - ]: 2 : if G_LIKELY (gtk_widget_get_visible (row->avatar) == visible)
578 : : return;
579 : :
580 : 2 : gtk_widget_set_visible (row->avatar, visible);
581 : : }
582 : :
|