chatty-message-list.c 27.7 KB
Newer Older
Andrea Schaefer's avatar
Andrea Schaefer committed
1 2 3
/*
 * Copyright (C) 2018 Purism SPC
 *
4
 * SPDX-License-Identifier: GPL-3.0-or-later
Andrea Schaefer's avatar
Andrea Schaefer committed
5 6 7 8
 */

#include <glib.h>
#include <glib/gi18n.h>
9 10
#define HANDY_USE_UNSTABLE_API
#include <handy.h>
Andrea Schaefer's avatar
Andrea Schaefer committed
11 12 13 14
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <cairo.h>
15
#include <purple.h>
Andrea Schaefer's avatar
Andrea Schaefer committed
16 17 18 19 20
#include "chatty-message-list.h"

#define INDICATOR_WIDTH   60
#define INDICATOR_HEIGHT  40
#define INDICATOR_MARGIN   2
21
#define MSG_BUBBLE_MAX_RATIO .3
Andrea Schaefer's avatar
Andrea Schaefer committed
22

23 24
#define LONGPRESS_TIMEOUT 2000

Andrea Schaefer's avatar
Andrea Schaefer committed
25 26 27 28 29 30 31 32 33 34 35 36

enum {
  PROP_0,
  PROP_TYPE,
  PROP_DISCLAIMER,
  PROP_RULER,
  PROP_INDICATOR,
  PROP_LAST_PROP,
};

static GParamSpec *props[PROP_LAST_PROP];

37 38
enum {
  SIGNAL_MESSAGE_ADDED,
39
  SIGNAL_SCROLL_TOP,
40 41 42 43 44
  SIGNAL_LAST_SIGNAL,
};

static guint signals [SIGNAL_LAST_SIGNAL];

Andrea Schaefer's avatar
Andrea Schaefer committed
45 46
typedef struct
{
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
  GtkBox      *disclaimer;
  GtkWidget   *list;
  GtkWidget   *scroll;
  GtkWidget   *button;
  GtkWidget   *indicator_row;
  GtkWidget   *typing_indicator;
  GtkWidget   *label_pressed;
  GtkWidget   *menu_popover;
  gboolean     disclaimer_enable;
  gboolean     ruler_enable;
  gboolean     indicator_enable;
  gboolean     animation_enable;
  guint        message_type;
  guint        width;
  guint        height;
62
  guint        prev_height;
63 64
  guint        longpress_timeout_handle;
  guint32      refresh_timeout_handle;
Esteban's avatar
Esteban committed
65
  gboolean     first_scroll_to_bottom;
Andrea Schaefer's avatar
Andrea Schaefer committed
66 67 68 69 70 71 72
} ChattyMsgListPrivate;


G_DEFINE_TYPE_WITH_PRIVATE (ChattyMsgList, chatty_msg_list, GTK_TYPE_BOX)

header_strings_t header_strings[3] = {
  {
73 74 75
    .str_0 = N_("This is a IM conversation."),
    .str_1 = N_("Your messages are not encrypted,"),
    .str_2 = N_("ask your counterpart to use E2EE."),
Andrea Schaefer's avatar
Andrea Schaefer committed
76 77
  },
  {
78 79 80
    .str_0 = N_("This is a IM conversation."),
    .str_1 = N_("Your messages are secured"),
    .str_2 = N_("by end-to-end encryption."),
Andrea Schaefer's avatar
Andrea Schaefer committed
81 82
  },
  {
83 84 85
    .str_0 = N_("This is a SMS conversation."),
    .str_1 = N_("Your messages are not encrypted,"),
    .str_2 = N_("and carrier rates may apply."),
Andrea Schaefer's avatar
Andrea Schaefer committed
86 87 88 89
  },
};


90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
static void
msg_list_cmd_copy (GSimpleAction *action,
                   GVariant      *parameter,
                   gpointer       user_data)
{
  const char *label_text = NULL;

  GtkClipboard* clipboard;

  ChattyMsgListPrivate *priv = chatty_msg_list_get_instance_private (user_data);

  clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);

  label_text = gtk_label_get_text (GTK_LABEL(priv->label_pressed));

  if (label_text != NULL) {
    gtk_clipboard_set_text (clipboard, label_text, -1);
  }
}


static const GActionEntry msg_list_entries [] =
{
  { "copy-msg-text",  msg_list_cmd_copy }
};


Andrea Schaefer's avatar
Andrea Schaefer committed
117 118 119
static void
cb_list_size_allocate (GtkWidget     *sender,
                       GtkAllocation *allocation,
120
                       gpointer       self)
Andrea Schaefer's avatar
Andrea Schaefer committed
121 122 123 124
{
  GtkAdjustment *adj;
  gdouble       upper;
  gdouble       size;
125
  gdouble       value;
Andrea Schaefer's avatar
Andrea Schaefer committed
126 127 128

  ChattyMsgListPrivate *priv = chatty_msg_list_get_instance_private (self);

Guido Gunther's avatar
Guido Gunther committed
129
  adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (priv->scroll));
Andrea Schaefer's avatar
Andrea Schaefer committed
130 131 132

  size = gtk_adjustment_get_page_size (adj);
  upper = gtk_adjustment_get_upper (adj);
133
  value = gtk_adjustment_get_value (adj);
134

135
  // Only scroll to the bottom if in the proximity of bottom.
Esteban's avatar
Esteban committed
136
  if (!priv->first_scroll_to_bottom || abs (upper - value) < size * 1.5){
137
    gtk_adjustment_set_value (adj, upper - size);
Esteban's avatar
Esteban committed
138
    priv->first_scroll_to_bottom = TRUE;
139
  }
140
}
Esteban's avatar
Esteban committed
141

142 143 144 145 146 147 148 149 150 151 152 153 154

static void
cb_cont_size_allocate (GtkWidget     *sender,
                       GtkAllocation *allocation,
                       gpointer       self)
{
  ChattyMsgListPrivate *priv = chatty_msg_list_get_instance_private (self);
  
  if (priv->prev_height > 0 && priv->prev_height != allocation->height) {
    cb_list_size_allocate (NULL, NULL, self);
  }

  priv->prev_height = allocation->height;
Andrea Schaefer's avatar
Andrea Schaefer committed
155 156 157
}


158 159 160 161 162
static void
cb_scroll_edge_reached (GtkScrolledWindow *scrolled_window,
                        GtkPositionType     pos,
                        gpointer            self)
{
Esteban's avatar
Esteban committed
163 164 165

    if (pos == GTK_POS_TOP)    
      g_signal_emit (self,
166 167
                   signals[SIGNAL_SCROLL_TOP],
                   0);
168 169 170
}


Guido Gunther's avatar
Guido Gunther committed
171
static void
172 173 174
cb_list_focus (GtkWidget *sender,
               int        direction,
               gpointer   self)
Andrea Schaefer's avatar
Andrea Schaefer committed
175 176
{
  GtkAdjustment *adj;
177 178
  gdouble        upper;
  gdouble        size;
Andrea Schaefer's avatar
Andrea Schaefer committed
179 180 181

  ChattyMsgListPrivate *priv = chatty_msg_list_get_instance_private (self);

Guido Gunther's avatar
Guido Gunther committed
182
  adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (priv->scroll));
Andrea Schaefer's avatar
Andrea Schaefer committed
183 184 185 186 187 188 189

  size = gtk_adjustment_get_page_size (adj);
  upper = gtk_adjustment_get_upper (adj);
  gtk_adjustment_set_value (adj, upper - size);
}


190 191 192 193 194 195 196 197 198
static gint
cb_longpress_timeout (gpointer self)
{
  const char *label_text = NULL;

  GtkClipboard* clipboard;

  ChattyMsgListPrivate *priv = chatty_msg_list_get_instance_private (self);

199 200 201 202 203 204
  gtk_popover_set_relative_to (GTK_POPOVER(priv->menu_popover),
                               GTK_WIDGET(priv->label_pressed));

  gtk_popover_popup (GTK_POPOVER(priv->menu_popover));


205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251
  clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);

  label_text = gtk_label_get_text (GTK_LABEL(priv->label_pressed));

  if (label_text != NULL) {
    gtk_clipboard_set_text (clipboard, label_text, -1);
  }

  priv->longpress_timeout_handle = 0;

  return FALSE;
}


static void
chatty_msg_list_longpress_timeout_start (ChattyMsgList *self)
{
  ChattyMsgListPrivate *priv = chatty_msg_list_get_instance_private (self);

  if (!priv->longpress_timeout_handle) {
      priv->longpress_timeout_handle = g_timeout_add (LONGPRESS_TIMEOUT,
                                                      cb_longpress_timeout,
                                                      (gpointer) self);
  }
}


static void
chatty_msg_list_longpress_timeout_stop (ChattyMsgList *self)
{
  ChattyMsgListPrivate *priv = chatty_msg_list_get_instance_private (self);

  if (priv->longpress_timeout_handle) {
      g_source_remove (priv->longpress_timeout_handle);
      priv->longpress_timeout_handle = 0;
  }
}


static void
cb_msg_label_pressed (GtkWidget      *event_box,
                      GdkEventButton *event,
                      gpointer        self)
{
  ChattyMsgListPrivate *priv = chatty_msg_list_get_instance_private (self);

  priv->label_pressed = g_object_get_data (G_OBJECT(event_box), "label");
252 253 254 255 256 257

  if (event->button == 3) {
    cb_longpress_timeout (self);
  } else {
    chatty_msg_list_longpress_timeout_start (self);
  }
258 259 260 261 262 263 264 265 266 267 268 269
}


static void
cb_msg_label_released (GtkWidget      *event_box,
                       GdkEventButton *event,
                       gpointer        self)
{
  chatty_msg_list_longpress_timeout_stop (self);
}


Andrea Schaefer's avatar
Andrea Schaefer committed
270 271 272
static void
chatty_msg_list_add_header (ChattyMsgList *self)
{
Guido Gunther's avatar
Guido Gunther committed
273 274 275 276
  GtkWidget       *label;
  GtkWidget       *label_2;
  GtkWidget       *label_3;
  GtkWidget       *row;
Andrea Schaefer's avatar
Andrea Schaefer committed
277 278 279 280 281
  GtkStyleContext *sc;

  ChattyMsgListPrivate *priv = chatty_msg_list_get_instance_private (self);

  row = gtk_list_box_row_new ();
282

Andrea Schaefer's avatar
Andrea Schaefer committed
283
  g_object_set (G_OBJECT(row),
284 285 286 287
                "selectable", FALSE,
                "activatable", FALSE,
                NULL);

Guido Gunther's avatar
Guido Gunther committed
288
  gtk_widget_set_size_request (row,
Andrea Schaefer's avatar
Andrea Schaefer committed
289
                               1,
290 291 292 293
                               0); // TODO: set priv->height instead);
                                   // TODO: @LELAND: Talk to Andrea about this header:
                                   // Adding messages backward leves this space at the bottom (320 to 0 by now)

Andrea Schaefer's avatar
Andrea Schaefer committed
294

Guido Gunther's avatar
Guido Gunther committed
295
  gtk_container_add (GTK_CONTAINER (priv->list), row);
Andrea Schaefer's avatar
Andrea Schaefer committed
296

297
  if (priv->disclaimer_enable && priv->message_type < CHATTY_MSG_TYPE_LAST) {
Guido Gunther's avatar
Guido Gunther committed
298
    priv->disclaimer = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0));
Andrea Schaefer's avatar
Andrea Schaefer committed
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393

    label = gtk_label_new (_(header_strings[priv->message_type].str_0));
    sc = gtk_widget_get_style_context (GTK_WIDGET(label));
    gtk_style_context_add_class (sc, "label_disclaim");
    gtk_box_pack_start (priv->disclaimer, GTK_WIDGET(label), FALSE, FALSE, 0);

    label_2 = gtk_label_new (_(header_strings[priv->message_type].str_1));
    gtk_box_pack_start (priv->disclaimer, GTK_WIDGET(label_2), FALSE, FALSE, 0);

    label_3 = gtk_label_new (_(header_strings[priv->message_type].str_2));
    gtk_box_pack_start (priv->disclaimer, GTK_WIDGET(label_3), FALSE, FALSE, 0);

    gtk_container_add (GTK_CONTAINER(row), GTK_WIDGET(priv->disclaimer));

    gtk_widget_show_all (GTK_WIDGET(row));
  }
}


static void
chatty_msg_list_set_property (GObject      *object,
                              guint         property_id,
                              const GValue *value,
                              GParamSpec   *pspec)
{
  ChattyMsgList *self = CHATTY_MSG_LIST (object);

  ChattyMsgListPrivate *priv = chatty_msg_list_get_instance_private (self);

  switch (property_id) {
    case PROP_TYPE:
      priv->message_type = g_value_get_int (value);
      break;

    case PROP_DISCLAIMER:
      priv->disclaimer_enable = g_value_get_boolean (value);
      chatty_msg_list_add_header (self);
      break;

    case PROP_RULER:
      priv->ruler_enable = g_value_get_boolean (value);
      break;

    case PROP_INDICATOR:
      priv->indicator_enable = g_value_get_boolean (value);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
  }
}


static void
chatty_msg_list_get_property (GObject    *object,
                              guint       property_id,
                              GValue     *value,
                              GParamSpec *pspec)
{
  ChattyMsgList *self = CHATTY_MSG_LIST (object);

  ChattyMsgListPrivate *priv = chatty_msg_list_get_instance_private (self);

  switch (property_id) {
    case PROP_TYPE:
      g_value_set_int (value, priv->message_type);
      break;

    case PROP_DISCLAIMER:
      g_value_set_boolean (value, priv->disclaimer_enable);
      break;

    case PROP_RULER:
      g_value_set_boolean (value, priv->ruler_enable);
      break;

    case PROP_INDICATOR:
      g_value_set_boolean (value, priv->indicator_enable);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object,
                                         property_id,
                                         pspec);
      break;
  }
}


static void
chatty_msg_list_hide_header (ChattyMsgList *self)
{
  ChattyMsgListPrivate *priv = chatty_msg_list_get_instance_private (self);

394 395 396
  if (GTK_WIDGET(priv->disclaimer) != NULL) {
    gtk_widget_hide (GTK_WIDGET(priv->disclaimer));
  }
Andrea Schaefer's avatar
Andrea Schaefer committed
397 398 399 400 401 402
}


static void
chatty_draw_typing_indicator (cairo_t *cr)
{
403 404 405 406
  double dot_pattern [3][3]= {{0.5, 0.9, 0.9},
                              {0.7, 0.5, 0.9},
                              {0.9, 0.7, 0.5}};
  guint  dot_origins [3] = {15, 30, 45};
Andrea Schaefer's avatar
Andrea Schaefer committed
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475
  double grey_lev,
         x, y,
         width, height,
         rad, deg;

  static guint i;

  deg = M_PI / 180.0;

  rad = INDICATOR_MARGIN * 5;
  x = y = INDICATOR_MARGIN;
  width = INDICATOR_WIDTH - INDICATOR_MARGIN * 2;
  height = INDICATOR_HEIGHT - INDICATOR_MARGIN * 2;

  if (i > 2)
    i = 0;

  cairo_new_sub_path (cr);
  cairo_arc (cr, x + width - rad, y + rad, rad, -90 * deg, 0 * deg);
  cairo_arc (cr, x + width - rad, y + height - rad, rad, 0 * deg, 90 * deg);
  cairo_arc (cr, x + rad, y + height - rad, rad, 90 * deg, 180 * deg);
  cairo_arc (cr, x + rad, y + rad, rad, 180 * deg, 270 * deg);
  cairo_close_path (cr);

  cairo_set_source_rgb (cr, 0.7, 0.7, 0.7);
  cairo_set_line_width (cr, 1.0);
  cairo_stroke (cr);

  for (guint n = 0; n < 3; n++) {
    cairo_arc (cr, dot_origins[n], 20, 5, 0, 2 * M_PI);
    grey_lev = dot_pattern[i][n];
    cairo_set_source_rgb (cr, grey_lev, grey_lev, grey_lev);
    cairo_fill (cr);
  }

  i++;
}


static gboolean
cb_on_draw_event (GtkWidget *widget,
                  cairo_t   *cr,
                  gpointer  self)
{
  ChattyMsgListPrivate *priv = chatty_msg_list_get_instance_private (self);

  if (priv->animation_enable)
    chatty_draw_typing_indicator (cr);

  return TRUE;
}


static gboolean
cb_indicator_refresh (gpointer self)
{
    ChattyMsgListPrivate *priv = chatty_msg_list_get_instance_private (self);

    priv->animation_enable = TRUE;

    gtk_widget_queue_draw (priv->typing_indicator);

    return TRUE;
}


void
chatty_msg_list_show_typing_indicator (ChattyMsgList *self)
{
Guido Gunther's avatar
Guido Gunther committed
476 477
  GtkWidget   *box;
  GtkRevealer *revealer;
Andrea Schaefer's avatar
Andrea Schaefer committed
478 479 480 481 482 483

  ChattyMsgListPrivate *priv = chatty_msg_list_get_instance_private (self);

  priv->animation_enable = FALSE;

  priv->indicator_row = gtk_list_box_row_new ();
Guido Gunther's avatar
Guido Gunther committed
484 485
  gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (priv->indicator_row), FALSE);
  gtk_list_box_row_set_selectable (GTK_LIST_BOX_ROW (priv->indicator_row), FALSE);
Andrea Schaefer's avatar
Andrea Schaefer committed
486 487 488 489 490 491 492

  gtk_container_add (GTK_CONTAINER (priv->list),
                                    GTK_WIDGET (priv->indicator_row));

  box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
  gtk_container_set_border_width (GTK_CONTAINER (box), 4);

Guido Gunther's avatar
Guido Gunther committed
493
  revealer = GTK_REVEALER (gtk_revealer_new ());
Andrea Schaefer's avatar
Andrea Schaefer committed
494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513
  gtk_revealer_set_transition_type (revealer,
                                    GTK_REVEALER_TRANSITION_TYPE_SLIDE_UP);
  gtk_revealer_set_transition_duration (revealer, 350);

  gtk_container_add (GTK_CONTAINER (revealer),
                     GTK_WIDGET (box));
  gtk_container_add (GTK_CONTAINER (priv->indicator_row),
                     GTK_WIDGET (revealer));

  priv->typing_indicator = gtk_drawing_area_new();

  gtk_widget_set_size_request (priv->typing_indicator,
                               INDICATOR_WIDTH,
                               INDICATOR_HEIGHT);

  g_signal_connect (G_OBJECT (priv->typing_indicator),
                    "draw",
                    G_CALLBACK (cb_on_draw_event),
                    (gpointer) self);

Guido Gunther's avatar
Guido Gunther committed
514
  gtk_box_pack_start (GTK_BOX (box), GTK_WIDGET (priv->typing_indicator),
Andrea Schaefer's avatar
Andrea Schaefer committed
515 516 517 518 519 520 521
                      FALSE, TRUE, 8);

  chatty_msg_list_hide_header (self);
  gtk_widget_show_all (GTK_WIDGET(priv->indicator_row));
  gtk_revealer_set_reveal_child (revealer, TRUE);

  priv->refresh_timeout_handle = g_timeout_add (300,
Guido Gunther's avatar
Guido Gunther committed
522
                                                (GSourceFunc)(cb_indicator_refresh),
Andrea Schaefer's avatar
Andrea Schaefer committed
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540
                                                (gpointer) self);
}


void
chatty_msg_list_hide_typing_indicator (ChattyMsgList *self)
{
  ChattyMsgListPrivate *priv = chatty_msg_list_get_instance_private (self);

  if (priv->refresh_timeout_handle) {
      g_source_remove (priv->refresh_timeout_handle);
      priv->refresh_timeout_handle = 0;
  }

  if (priv->typing_indicator != NULL) {
    gtk_widget_hide (GTK_WIDGET(priv->typing_indicator));
    gtk_widget_destroy (GTK_WIDGET(priv->typing_indicator));
    gtk_widget_destroy (GTK_WIDGET(priv->indicator_row));
541
    priv->typing_indicator = NULL;
Andrea Schaefer's avatar
Andrea Schaefer committed
542 543 544 545 546 547 548
  }
}


void
chatty_msg_list_clear (ChattyMsgList *self)
{
Andrea Schaefer's avatar
Andrea Schaefer committed
549 550
  GList *children;
  GList *iter;
Andrea Schaefer's avatar
Andrea Schaefer committed
551 552 553 554 555

  ChattyMsgListPrivate *priv = chatty_msg_list_get_instance_private (self);

  children = gtk_container_get_children (GTK_CONTAINER(priv->list));

Andrea Schaefer's avatar
Andrea Schaefer committed
556
  for (iter = children; iter != NULL; iter = g_list_next (iter)) {
557
    gtk_container_remove (GTK_CONTAINER(priv->list), GTK_WIDGET(iter->data));
Andrea Schaefer's avatar
Andrea Schaefer committed
558
  }
Andrea Schaefer's avatar
Andrea Schaefer committed
559 560

  g_list_free (children);
Andrea Schaefer's avatar
Andrea Schaefer committed
561
  g_list_free (iter);
Andrea Schaefer's avatar
Andrea Schaefer committed
562 563
}

564
 
565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582
/* This removes all markup exept for links */
static gchar *
chatty_msg_list_escape_message (const gchar *message)
{
  g_autofree gchar  *striped;
  g_autofree gchar  *escaped;
  g_autofree gchar  *linkified;
  gchar *result;

  striped = purple_markup_strip_html  (message);
  escaped = purple_markup_escape_text (striped, -1);
  linkified = purple_markup_linkify (escaped);
  // convert all tags to lowercase for GtkLabel markup parser
  purple_markup_html_to_xhtml (linkified, &result, NULL);

  return result;
}

583
GtkWidget *
584 585
chatty_msg_list_add_message_at (ChattyMsgList *self,
                                guint          message_dir,
586
                                const gchar   *html_message,
587 588 589
                                const gchar   *footer,
                                GtkWidget     *icon,
                                guint          position)
Andrea Schaefer's avatar
Andrea Schaefer committed
590
{
591

Andrea Schaefer's avatar
Andrea Schaefer committed
592 593 594
  GtkListBoxRow   *row;
  GtkBox          *box;
  GtkBox          *vbox;
595
  GtkWidget       *ebox;
Andrea Schaefer's avatar
Andrea Schaefer committed
596
  GtkRevealer     *revealer;
597
  GtkLabel        *label_msg;
598
  GtkLabel        *label_footer;
Andrea Schaefer's avatar
Andrea Schaefer committed
599 600 601
  GtkStyleContext *sc;
  gchar           *style;
  gchar           *str;
602
  gint             width, height;
603
  gchar *message;
Andrea Schaefer's avatar
Andrea Schaefer committed
604 605 606

  ChattyMsgListPrivate *priv = chatty_msg_list_get_instance_private (self);

607 608
  message = chatty_msg_list_escape_message (html_message);

Guido Gunther's avatar
Guido Gunther committed
609
  row = GTK_LIST_BOX_ROW (gtk_list_box_row_new ());
Andrea Schaefer's avatar
Andrea Schaefer committed
610

Andrea Schaefer's avatar
Andrea Schaefer committed
611
  g_object_set (G_OBJECT(row),
Andrea Schaefer's avatar
Andrea Schaefer committed
612
                "selectable", FALSE,
613
                "activatable", FALSE,
Andrea Schaefer's avatar
Andrea Schaefer committed
614 615
                NULL);

616 617 618 619 620
  if (position == ADD_MESSAGE_ON_BOTTOM){
    gtk_container_add (GTK_CONTAINER (priv->list), GTK_WIDGET (row));
  } else {
    gtk_list_box_prepend (GTK_LIST_BOX (priv->list), GTK_WIDGET (row));
  }
Andrea Schaefer's avatar
Andrea Schaefer committed
621

Guido Gunther's avatar
Guido Gunther committed
622
  box = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0));
Andrea Schaefer's avatar
Andrea Schaefer committed
623 624
  gtk_container_set_border_width (GTK_CONTAINER (box), 4);

Guido Gunther's avatar
Guido Gunther committed
625
  revealer = GTK_REVEALER (gtk_revealer_new ());
626 627
  gtk_revealer_set_transition_type (revealer,
                                    GTK_REVEALER_TRANSITION_TYPE_SLIDE_UP);
Andrea Schaefer's avatar
Andrea Schaefer committed
628 629 630 631 632
  gtk_revealer_set_transition_duration (revealer, 350);

  gtk_container_add (GTK_CONTAINER (revealer), GTK_WIDGET (box));
  gtk_container_add (GTK_CONTAINER (row), GTK_WIDGET (revealer));

Guido Gunther's avatar
Guido Gunther committed
633
  vbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_VERTICAL, 0));
Andrea Schaefer's avatar
Andrea Schaefer committed
634

635
  label_msg = GTK_LABEL (gtk_label_new (message));
636
  gtk_widget_set_name (GTK_WIDGET(label_msg), "label-msg");
637
  gtk_label_set_xalign (label_msg, 0);
638
  gtk_label_set_line_wrap (label_msg, TRUE);
639
  gtk_label_set_line_wrap_mode (label_msg, PANGO_WRAP_WORD_CHAR);
640
  gtk_label_set_use_markup (GTK_LABEL(label_msg), TRUE);
641

642 643 644 645 646 647 648 649 650 651 652 653 654 655
  gtk_widget_get_size_request (GTK_WIDGET(label_msg), &width, &height);

  ebox = gtk_event_box_new ();
  g_object_set_data (G_OBJECT(ebox), "label", label_msg);
  gtk_widget_set_size_request (GTK_WIDGET(ebox), width, height);
  gtk_event_box_set_visible_window (GTK_EVENT_BOX(ebox), FALSE);
  gtk_widget_set_events (GTK_WIDGET(ebox), GDK_BUTTON_PRESS_MASK);
  g_signal_connect (G_OBJECT(ebox), "button_press_event",
                    G_CALLBACK(cb_msg_label_pressed),
                    (gpointer)self);
  g_signal_connect (G_OBJECT(ebox), "button_release_event",
                    G_CALLBACK(cb_msg_label_released),
                    (gpointer)self);

656
  gtk_container_add (GTK_CONTAINER(ebox), GTK_WIDGET(label_msg));
Andrea Schaefer's avatar
Andrea Schaefer committed
657 658

  if (message_dir == MSG_IS_INCOMING) {
659 660 661 662 663 664 665 666 667
    if (icon != NULL) {
      g_object_set (icon,
                    "valign", GTK_ALIGN_START,
                    "margin", 4,
                    NULL);

      gtk_box_pack_start (box, GTK_WIDGET(icon), FALSE, TRUE, 4);
    }

668 669 670
    // Set name to identify the row in size_allocate
    gtk_widget_set_name (GTK_WIDGET(row), "incoming");

671
    gtk_box_pack_start (box, GTK_WIDGET(vbox), FALSE, TRUE, 8);
Andrea Schaefer's avatar
Andrea Schaefer committed
672 673
    style = "bubble_white";
  } else if (message_dir == MSG_IS_OUTGOING) {
674 675 676
    // Set name to identify the row in size_allocate
    gtk_widget_set_name (GTK_WIDGET(row), "outcoming");

677
    gtk_box_pack_end (box, GTK_WIDGET(vbox), FALSE, FALSE, 8);
Andrea Schaefer's avatar
Andrea Schaefer committed
678
    style = "bubble_blue";
679 680 681
  } else if (message_dir == MSG_IS_SYSTEM) {
    gtk_box_pack_start (box, GTK_WIDGET(vbox), TRUE, TRUE, 8);
    style = "bubble_purple";
Andrea Schaefer's avatar
Andrea Schaefer committed
682 683
  }

684 685


686
  if (message_dir == MSG_IS_OUTGOING && priv->message_type == CHATTY_MSG_TYPE_SMS) {
Andrea Schaefer's avatar
Andrea Schaefer committed
687 688 689
    style = "bubble_green";
  }

690
  sc = gtk_widget_get_style_context (GTK_WIDGET(label_msg));
691
  gtk_style_context_add_class (sc, "message_bubble");
Andrea Schaefer's avatar
Andrea Schaefer committed
692 693
  gtk_style_context_add_class (sc, style);

694
  gtk_box_pack_start (vbox, GTK_WIDGET(ebox), FALSE, FALSE, 0);
Andrea Schaefer's avatar
Andrea Schaefer committed
695 696

  if (footer != NULL) {
697 698
    label_footer = GTK_LABEL(gtk_label_new (NULL));
    gtk_widget_set_name (GTK_WIDGET(label_footer), "label-footer");
Andrea Schaefer's avatar
Andrea Schaefer committed
699 700
    str = g_strconcat ("<small>", footer, "</small>", NULL);

701 702 703 704 705 706
    if (message_dir == MSG_IS_OUTGOING) {
      gtk_label_set_xalign (label_footer, 1);
    } else {
      gtk_label_set_xalign (label_footer, 0);
    }

707 708 709
    gtk_widget_set_sensitive (GTK_WIDGET(label_footer), FALSE);
    gtk_label_set_markup (label_footer, str);
    gtk_box_pack_start (vbox, GTK_WIDGET(label_footer), FALSE, FALSE, 10);
Andrea Schaefer's avatar
Andrea Schaefer committed
710 711 712
    g_free (str);
  }

713 714 715 716 717 718
  if (message_dir == MSG_IS_OUTGOING) {
    g_signal_emit (self,
                   signals[SIGNAL_MESSAGE_ADDED],
                   0,
                   G_OBJECT(vbox));
  }
719

Andrea Schaefer's avatar
Andrea Schaefer committed
720 721 722 723
  chatty_msg_list_hide_header (self);

  gtk_widget_show_all (GTK_WIDGET(row));
  gtk_revealer_set_reveal_child (revealer, TRUE);
724 725

  return GTK_WIDGET(revealer);
Andrea Schaefer's avatar
Andrea Schaefer committed
726 727 728
}


729
GtkWidget *
730 731 732 733 734 735
chatty_msg_list_add_message (ChattyMsgList *self,
                             guint          message_dir,
                             const gchar   *message,
                             const gchar   *footer,
                             GtkWidget     *icon)
{
736
  return chatty_msg_list_add_message_at (self, message_dir, message, footer, icon, ADD_MESSAGE_ON_BOTTOM);
737 738
}

Andrea Schaefer's avatar
Andrea Schaefer committed
739 740 741 742

static void
chatty_msg_list_constructed (GObject *object)
{
743 744
  GtkBuilder           *builder;
  GSimpleActionGroup   *simple_action_group;
745
  GtkStyleContext      *sc;
746
  ChattyMsgList        *self = CHATTY_MSG_LIST (object);
Andrea Schaefer's avatar
Andrea Schaefer committed
747
  ChattyMsgListPrivate *priv = chatty_msg_list_get_instance_private (self);
748
  const gchar          *path;
Andrea Schaefer's avatar
Andrea Schaefer committed
749

750
  gtk_widget_set_valign (GTK_WIDGET(priv->list), GTK_ALIGN_END);
Andrea Schaefer's avatar
Andrea Schaefer committed
751 752 753
  sc = gtk_widget_get_style_context (GTK_WIDGET(priv->list));

  gtk_style_context_add_class (sc, "message_list");
Guido Gunther's avatar
Guido Gunther committed
754
  gtk_list_box_set_selection_mode (GTK_LIST_BOX (priv->list), GTK_SELECTION_NONE);
Andrea Schaefer's avatar
Andrea Schaefer committed
755 756 757 758 759 760 761 762
  g_signal_connect_object (GTK_WIDGET (priv->list),
                           "size-allocate",
                           G_CALLBACK (cb_list_size_allocate),
                           (gpointer) self, 0);
  g_signal_connect_object (GTK_WIDGET (priv->list),
                           "focus",
                           G_CALLBACK (cb_list_focus),
                           (gpointer) self, 0);
763

764 765 766 767 768
  g_signal_connect (GTK_SCROLLED_WINDOW (priv->scroll),
                    "edge-reached",
                    G_CALLBACK(cb_scroll_edge_reached),
                    self);

769 770 771 772 773
  g_signal_connect (GTK_WIDGET (self),
                    "size-allocate",
                    G_CALLBACK(cb_cont_size_allocate),
                    self);

Esteban's avatar
Esteban committed
774 775
  priv->first_scroll_to_bottom = FALSE;

776 777
  priv->typing_indicator = NULL;

778 779
  priv->prev_height = 0;

780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795
  path = "/sm/puri/chatty/ui/chatty-message-list-popover.ui";
  builder = gtk_builder_new_from_resource (path);

  priv->menu_popover =
    GTK_WIDGET(gtk_builder_get_object (builder, "label_msg_popover"));

  simple_action_group = g_simple_action_group_new ();

  g_action_map_add_action_entries (G_ACTION_MAP (simple_action_group),
                                   msg_list_entries,
                                   G_N_ELEMENTS (msg_list_entries),
                                   (gpointer) self);

  gtk_widget_insert_action_group (GTK_WIDGET (self),
                                  "msg_list",
                                  G_ACTION_GROUP (simple_action_group));
Andrea Schaefer's avatar
Andrea Schaefer committed
796 797
}

798

799 800 801
static void 
chatty_msg_list_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
{
802
  GtkScrolledWindow *scroll;
803
  GtkViewport       *view;
804
  HdyColumn         *column;
805 806 807 808
  GtkListBox        *list;
  GtkListBoxRow     *row;
  GtkRevealer       *revealer;
  GtkBox            *hbox, *vbox;
809
  GtkAllocation      hbox_allocation;
810
  GList             *children;
811

812
  children = gtk_container_get_children (GTK_CONTAINER (widget));
813 814 815
  scroll = GTK_SCROLLED_WINDOW (g_list_first(children)->data);

  view = GTK_VIEWPORT(gtk_bin_get_child (GTK_BIN(scroll)));
816 817
  column = HDY_COLUMN(gtk_bin_get_child (GTK_BIN(view)));
  list = GTK_LIST_BOX(gtk_bin_get_child (GTK_BIN(column)));
818 819 820 821 822 823 824 825 826 827

  children = gtk_container_get_children (GTK_CONTAINER (list));

  for (GList *l = children; l != NULL; l = g_list_next (l)){
    row = l->data;
    revealer = GTK_REVEALER (gtk_bin_get_child(GTK_BIN(row)));
    if(revealer != NULL){
      hbox = GTK_BOX(gtk_bin_get_child (GTK_BIN(revealer)));

      children = gtk_container_get_children (GTK_CONTAINER (hbox));
828 829 830 831 832 833 834 835

      if (GTK_IS_IMAGE(g_list_first (children)->data)) {
        vbox = GTK_BOX(g_list_next (children)->data);
      } else if (GTK_IS_DRAWING_AREA(g_list_first (children)->data)) {
        continue;
      } else {
        vbox = GTK_BOX(g_list_first (children)->data);
      }
836 837 838 839

      gtk_widget_get_clip (GTK_WIDGET(hbox), &hbox_allocation);

      if(!strcmp ("incoming", gtk_widget_get_name (GTK_WIDGET(row)))){
840 841 842 843
        gtk_widget_set_margin_end(GTK_WIDGET(vbox), hbox_allocation.width * MSG_BUBBLE_MAX_RATIO);
      }
      else if(!strcmp ("outcoming", gtk_widget_get_name (GTK_WIDGET(row)))){
        gtk_widget_set_margin_start(GTK_WIDGET(vbox), hbox_allocation.width * MSG_BUBBLE_MAX_RATIO);
844 845 846 847
      }
    }
  }

848
  GTK_WIDGET_CLASS (chatty_msg_list_parent_class)->size_allocate (widget, allocation);
849
}
Andrea Schaefer's avatar
Andrea Schaefer committed
850

851

Andrea Schaefer's avatar
Andrea Schaefer committed
852 853 854
static void
chatty_msg_list_class_init (ChattyMsgListClass *klass)
{
855
  GObjectClass   *object_class = G_OBJECT_CLASS (klass);
Andrea Schaefer's avatar
Andrea Schaefer committed
856 857 858 859 860 861 862
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

  object_class->constructed = chatty_msg_list_constructed;

  object_class->set_property = chatty_msg_list_set_property;
  object_class->get_property = chatty_msg_list_get_property;

863
  widget_class->size_allocate = chatty_msg_list_size_allocate;
Andrea Schaefer's avatar
Andrea Schaefer committed
864 865 866

  props[PROP_TYPE] =
    g_param_spec_int ("message_type",
867 868
                      "Message Type",
                      "Select the message type",
869
                      CHATTY_MSG_TYPE_IM, CHATTY_MSG_TYPE_LAST, CHATTY_MSG_TYPE_IM,
Andrea Schaefer's avatar
Andrea Schaefer committed
870 871 872 873
                      G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);

  props[PROP_DISCLAIMER] =
    g_param_spec_boolean ("disclaimer",
874 875
                          "Messagemode Disclaimer",
                          "Enables a disclaimer with privacy advice",
Andrea Schaefer's avatar
Andrea Schaefer committed
876 877 878 879 880
                          TRUE,
                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);

  props[PROP_RULER] =
    g_param_spec_boolean ("ruler",
881 882
                          "Timestamp Ruler",
                          "Enables a ruler that shows a timestamp",
Andrea Schaefer's avatar
Andrea Schaefer committed
883 884 885 886 887
                          FALSE,
                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);

  props[PROP_INDICATOR] =
    g_param_spec_boolean ("indicator",
888 889
                          "Typing Indicator",
                          "Enables the typing indicator",
Andrea Schaefer's avatar
Andrea Schaefer committed
890 891 892 893 894
                          TRUE,
                          G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);

  g_object_class_install_properties (object_class, PROP_LAST_PROP, props);

895 896 897 898 899 900 901 902 903
  signals[SIGNAL_MESSAGE_ADDED] =
    g_signal_new ("message-added",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0,
                  NULL, NULL, NULL,
                  G_TYPE_NONE,
                  1,
                  G_TYPE_OBJECT);
904 905 906 907 908 909 910 911 912
  
  signals[SIGNAL_SCROLL_TOP] =
    g_signal_new ("scroll-top",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_LAST,
                  0,
                  NULL, NULL, NULL,
                  G_TYPE_NONE,
                  0);
913

Andrea Schaefer's avatar
Andrea Schaefer committed
914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929
  gtk_widget_class_set_template_from_resource (widget_class,
    "/sm/puri/chatty/ui/chatty-message-list.ui");

  gtk_widget_class_bind_template_child_private (widget_class,
                                                ChattyMsgList,
                                                scroll);
  gtk_widget_class_bind_template_child_private (widget_class,
                                                ChattyMsgList,
                                                list);
}


void
chatty_msg_list_set_msg_type (ChattyMsgList *self,
                              guint         message_type)
{
930
  g_return_if_fail (CHATTY_IS_MSG_LIST (self));
Andrea Schaefer's avatar
Andrea Schaefer committed
931 932 933 934 935 936 937 938 939 940

  g_object_set (G_OBJECT (self), "message_type", message_type, NULL);
}


guint
chatty_msg_list_get_msg_type (ChattyMsgList *self)
{
  ChattyMsgListPrivate *priv = chatty_msg_list_get_instance_private (self);

941
  g_return_val_if_fail (CHATTY_IS_MSG_LIST (self), CHATTY_MSG_TYPE_UNKNOWN);
Andrea Schaefer's avatar
Andrea Schaefer committed
942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964

  return priv->message_type;
}


static void
chatty_msg_list_init (ChattyMsgList *self)
{
  gtk_widget_init_template (GTK_WIDGET (self));
}


GtkWidget *
chatty_msg_list_new (guint message_type,
                     gboolean disclaimer)
{
  return g_object_new (CHATTY_TYPE_MSG_LIST,
                       "message_type",
                       message_type,
                       "disclaimer",
                       disclaimer,
                       NULL);
}