blob: 81ee4c653cc09c7c4f7236973e5f4351b713f3c7 [file] [log] [blame]
Stepan Salenikovich9816a942015-04-22 17:49:16 -04001/*
Stepan Salenikovichbe87d2c2016-01-25 14:14:34 -05002 * Copyright (C) 2015-2016 Savoir-faire Linux Inc.
Stepan Salenikovich9816a942015-04-22 17:49:16 -04003 * Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Stepan Salenikovich9816a942015-04-22 17:49:16 -040018 */
19
20#include "historyview.h"
21
22#include <gtk/gtk.h>
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -040023#include <glib/gi18n.h>
Stepan Salenikovich9816a942015-04-22 17:49:16 -040024#include "models/gtkqsortfiltertreemodel.h"
25#include <categorizedhistorymodel.h>
26#include <QtCore/QSortFilterProxyModel>
27#include <personmodel.h>
28#include "utils/calling.h"
29#include <memory>
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -040030#include <globalinstances.h>
31#include "native/pixbufmanipulator.h"
Stepan Salenikovich9816a942015-04-22 17:49:16 -040032#include "defines.h"
33#include "utils/models.h"
34#include <contactmethod.h>
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -040035#include <QtCore/QDateTime> // for date time formatting
Stepan Salenikovich9d294492015-05-14 16:34:24 -040036#include <QtCore/QItemSelectionModel>
Stepan Salenikovichf2d76c52015-07-17 17:54:56 -040037#include "utils/menus.h"
Stepan Salenikovich9816a942015-04-22 17:49:16 -040038
Julien Baron865676b2016-03-04 20:38:59 +010039static constexpr const char* COPY_DATA_KEY = "copy_data";
40
Stepan Salenikovich9816a942015-04-22 17:49:16 -040041struct _HistoryView
42{
Stepan Salenikovich4a5dd7f2015-10-29 16:35:02 -040043 GtkTreeView parent;
Stepan Salenikovich9816a942015-04-22 17:49:16 -040044};
45
46struct _HistoryViewClass
47{
Stepan Salenikovich4a5dd7f2015-10-29 16:35:02 -040048 GtkTreeViewClass parent_class;
Stepan Salenikovich9816a942015-04-22 17:49:16 -040049};
50
51typedef struct _HistoryViewPrivate HistoryViewPrivate;
52
53struct _HistoryViewPrivate
54{
Stepan Salenikovich9d294492015-05-14 16:34:24 -040055 CategorizedHistoryModel::SortedProxy *q_sorted_proxy;
56 QMetaObject::Connection category_changed;
Stepan Salenikovich9816a942015-04-22 17:49:16 -040057};
58
Stepan Salenikovich4a5dd7f2015-10-29 16:35:02 -040059G_DEFINE_TYPE_WITH_PRIVATE(HistoryView, history_view, GTK_TYPE_TREE_VIEW);
Stepan Salenikovich9816a942015-04-22 17:49:16 -040060
61#define HISTORY_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), HISTORY_VIEW_TYPE, HistoryViewPrivate))
62
63static void
64render_call_direction(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
65 GtkCellRenderer *cell,
66 GtkTreeModel *tree_model,
67 GtkTreeIter *iter,
68 G_GNUC_UNUSED gpointer data)
69{
70 /* check if this is a top level item (the fuzzy date item),
71 * in this case we don't want to show a call direction */
72 gchar *render_direction = NULL;
73 GtkTreeIter parent;
74 if (gtk_tree_model_iter_parent(tree_model, &parent, iter)) {
75 /* get direction and missed values */
76 GValue value = G_VALUE_INIT;
77 gtk_tree_model_get_value(tree_model, iter, 3, &value);
78 Call::Direction direction = (Call::Direction)g_value_get_int(&value);
79 g_value_unset(&value);
80
81 gtk_tree_model_get_value(tree_model, iter, 4, &value);
82 gboolean missed = g_value_get_boolean(&value);
83 g_value_unset(&value);
84
85 switch (direction) {
86 case Call::Direction::INCOMING:
87 if (missed)
88 render_direction = g_strdup_printf("<span fgcolor=\"red\" font=\"monospace\">&#8601;</span>");
89 else
90 render_direction = g_strdup_printf("<span fgcolor=\"green\" font=\"monospace\">&#8601;</span>");
91 break;
92 case Call::Direction::OUTGOING:
93 if (missed)
94 render_direction = g_strdup_printf("<span fgcolor=\"red\" font=\"monospace\">&#8599;</span>");
95 else
96 render_direction = g_strdup_printf("<span fgcolor=\"green\" font=\"monospace\">&#8599;</span>");
97 break;
98 }
99 }
100 g_object_set(G_OBJECT(cell), "markup", render_direction, NULL);
101 g_free(render_direction);
102}
103
104static void
105activate_history_item(GtkTreeView *tree_view,
106 GtkTreePath *path,
107 G_GNUC_UNUSED GtkTreeViewColumn *column,
108 G_GNUC_UNUSED gpointer user_data)
109{
110 GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
111
112 /* expand / collapse row */
113 if (gtk_tree_view_row_expanded(tree_view, path))
114 gtk_tree_view_collapse_row(tree_view, path);
115 else
116 gtk_tree_view_expand_row(tree_view, path, FALSE);
117
118 /* get iter */
119 GtkTreeIter iter;
120 if (gtk_tree_model_get_iter(model, &iter, path)) {
121 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(model), &iter);
122
123 QVariant contact_method = idx.data(static_cast<int>(Call::Role::ContactMethod));
124 /* create new call */
125 if (contact_method.value<ContactMethod*>()) {
126 place_new_call(contact_method.value<ContactMethod*>());
127 }
128 }
129}
130
131static void
Julien Baron865676b2016-03-04 20:38:59 +0100132call_contactmethod(G_GNUC_UNUSED GtkWidget *item, ContactMethod *cm)
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400133{
Julien Baron865676b2016-03-04 20:38:59 +0100134 g_return_if_fail(cm);
135 place_new_call(cm);
136}
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400137
Julien Baron865676b2016-03-04 20:38:59 +0100138static void
139copy_contact_info(GtkWidget *item, G_GNUC_UNUSED gpointer user_data)
140{
141 gpointer data = g_object_get_data(G_OBJECT(item), COPY_DATA_KEY);
142 g_return_if_fail(data);
143 gchar* text = (gchar *)data;
144 GtkClipboard* clip = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
145 gtk_clipboard_set_text(clip, text, -1);
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400146}
147
148/* TODO: can't seem to delete just one item for now, add when supported in backend
149 * static void
150 * delete_history_item(G_GNUC_UNUSED GtkWidget *item, GtkTreeView *treeview)
151 * {
152 * GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
153 * QModelIndex idx = get_index_from_selection(selection);
154 *
155 * if (idx.isValid()) {
156 * g_debug("deleting history item");
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400157 * CategorizedHistoryModel::instance().removeRow(idx.row(), idx.parent());
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400158 * }
159 * }
160 */
161
162static gboolean
163history_popup_menu(G_GNUC_UNUSED GtkWidget *widget, GdkEventButton *event, GtkTreeView *treeview)
164{
165 /* build popup menu when right clicking on history item
Julien Baron865676b2016-03-04 20:38:59 +0100166 * user should be able to call, copy the "name" or the "number",
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400167 * delete history item or all of the history,
168 * and eventualy add the number to a contact
169 */
170
171 /* check for right click */
172 if (event->button != BUTTON_RIGHT_CLICK || event->type != GDK_BUTTON_PRESS)
173 return FALSE;
174
Julien Baron865676b2016-03-04 20:38:59 +0100175 /* check if the selected item is a call */
176 auto selection = gtk_tree_view_get_selection(treeview);
177 const auto& idx = get_index_from_selection(selection);
178 const auto& var_c = idx.data(static_cast<int>(Call::Role::Object));
179 if (!idx.isValid() || !var_c.isValid())
180 return FALSE;
181 auto call = var_c.value<Call *>();
182 if (call == nullptr)
183 return FALSE;
184
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400185 GtkWidget *menu = gtk_menu_new();
186
Julien Baron865676b2016-03-04 20:38:59 +0100187 /* call */
188 auto item = gtk_menu_item_new_with_mnemonic(_("_Call"));
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400189 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
Julien Baron865676b2016-03-04 20:38:59 +0100190 g_signal_connect(item,
191 "activate",
192 G_CALLBACK(call_contactmethod),
193 call->peerContactMethod());
194
195 /* copy name */
196 QVariant name_var = idx.data(static_cast<int>(Ring::Role::Name));
197 if (name_var.isValid()) {
198 gchar *name = g_strdup_printf("%s", name_var.value<QString>().toUtf8().constData());
199 item = gtk_menu_item_new_with_mnemonic(_("_Copy name"));
200 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
201 g_object_set_data_full(G_OBJECT(item), COPY_DATA_KEY, name, (GDestroyNotify)g_free);
202 g_signal_connect(item, "activate", G_CALLBACK(copy_contact_info), NULL);
203 }
204
205 /* copy number */
206 QVariant number_var = idx.data(static_cast<int>(Ring::Role::Number));
207 if (number_var.isValid()) {
208 gchar *number = g_strdup_printf("%s", number_var.value<QString>().toUtf8().constData());
209 item = gtk_menu_item_new_with_mnemonic(_("_Copy number"));
210 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
211 g_object_set_data_full(G_OBJECT(item), COPY_DATA_KEY, number, (GDestroyNotify)g_free);
212 g_signal_connect(item, "activate", G_CALLBACK(copy_contact_info), NULL);
213 }
214
215 /* get the contact method and check if it is already linked to a person,
216 * if not, then offer to either add to a new or existing contact */
217 auto contactmethod = call->peerContactMethod();
218 if (!contact_method_has_contact(contactmethod)) {
219 GtkTreeIter iter;
220 GtkTreeModel *model;
221 gtk_tree_selection_get_selected(selection, &model, &iter);
222 auto path = gtk_tree_model_get_path(model, &iter);
223 auto column = gtk_tree_view_get_column(treeview, 0);
224 GdkRectangle rect;
225 gtk_tree_view_get_cell_area(treeview, path, column, &rect);
226 gtk_tree_view_convert_bin_window_to_widget_coords(treeview, rect.x, rect.y, &rect.x, &rect.y);
227 gtk_tree_path_free(path);
228 auto add_to = menu_item_add_to_contact(contactmethod, GTK_WIDGET(treeview), &rect);
229 gtk_menu_shell_append(GTK_MENU_SHELL(menu), add_to);
230 }
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400231
232 /* TODO: delete history entry
233 * gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
234 * item = gtk_menu_item_new_with_mnemonic("_Delete entry");
235 * gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
236 * g_signal_connect(item, "activate", G_CALLBACK(delete_history_item), treeview);
237 */
238
239 /* show menu */
240 gtk_widget_show_all(menu);
241 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);
242
Stepan Salenikovichf2d76c52015-07-17 17:54:56 -0400243 return TRUE;
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400244}
245
246static void
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400247render_call_photo(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
248 GtkCellRenderer *cell,
249 GtkTreeModel *tree_model,
250 GtkTreeIter *iter,
251 G_GNUC_UNUSED gpointer data)
252{
253 /* check if this is a top level item (category),
254 * in this case we don't want to show a photo */
255 GtkTreePath *path = gtk_tree_model_get_path(tree_model, iter);
256 int depth = gtk_tree_path_get_depth(path);
257 gtk_tree_path_free(path);
258 if (depth == 2) {
259 /* get person */
260 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(tree_model), iter);
261 if (idx.isValid()) {
262 QVariant var_c = idx.data(static_cast<int>(Call::Role::Object));
263 Call *c = var_c.value<Call *>();
264 /* get photo */
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -0400265 QVariant var_p = GlobalInstances::pixmapManipulator().callPhoto(c, QSize(50, 50), false);
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400266 std::shared_ptr<GdkPixbuf> photo = var_p.value<std::shared_ptr<GdkPixbuf>>();
267 g_object_set(G_OBJECT(cell), "pixbuf", photo.get(), NULL);
268 return;
269 }
270 }
271
272 /* otherwise, make sure its an empty pixbuf */
273 g_object_set(G_OBJECT(cell), "pixbuf", NULL, NULL);
274}
275
276static void
277render_name_and_contact_method(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
278 GtkCellRenderer *cell,
279 GtkTreeModel *tree_model,
280 GtkTreeIter *iter,
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400281 GtkTreeView *treeview)
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400282{
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400283 // check if this iter is selected
284 gboolean is_selected = FALSE;
285 if (GTK_IS_TREE_VIEW(treeview)) {
286 auto selection = gtk_tree_view_get_selection(treeview);
287 is_selected = gtk_tree_selection_iter_is_selected(selection, iter);
288 }
289
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400290 /**
291 * If call, show the name and the contact method (number) underneath;
292 * otherwise just display the category.
293 */
294 GtkTreePath *path = gtk_tree_model_get_path(tree_model, iter);
295 int depth = gtk_tree_path_get_depth(path);
296 gtk_tree_path_free(path);
297
298 gchar *text = NULL;
299
300 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(tree_model), iter);
301 if (idx.isValid()) {
302 QVariant var = idx.data(Qt::DisplayRole);
303 if (depth == 1) {
304 /* category */
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400305 text = g_markup_printf_escaped("<b>%s</b>", var.value<QString>().toUtf8().constData());
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400306 } else if (depth == 2) {
307 /* call item */
308 QVariant var_name = idx.data(static_cast<int>(Call::Role::Name));
309 QVariant var_number = idx.data(static_cast<int>(Call::Role::Number));
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400310
311 /* we want the color of the status text to be the default color if this iter is
312 * selected so that the treeview is able to invert it against the selection color */
313 if (is_selected) {
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400314 text = g_markup_printf_escaped("%s\n %s",
315 var_name.value<QString>().toUtf8().constData(),
316 var_number.value<QString>().toUtf8().constData());
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400317 } else {
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400318 text = g_markup_printf_escaped("%s\n <span fgcolor=\"gray\">%s</span>",
319 var_name.value<QString>().toUtf8().constData(),
320 var_number.value<QString>().toUtf8().constData());
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400321 }
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400322 }
323 }
324
325 g_object_set(G_OBJECT(cell), "markup", text, NULL);
326 g_free(text);
327}
328
329static void
Stepan Salenikovich501a7f82015-12-23 11:06:47 -0500330render_date_time(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
331 GtkCellRenderer *cell,
332 GtkTreeModel *tree_model,
333 GtkTreeIter *iter,
334 GtkTreeView *treeview)
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400335{
Stepan Salenikovich501a7f82015-12-23 11:06:47 -0500336 // check if this iter is selected
337 gboolean is_selected = FALSE;
338 if (GTK_IS_TREE_VIEW(treeview)) {
339 auto selection = gtk_tree_view_get_selection(treeview);
340 is_selected = gtk_tree_selection_iter_is_selected(selection, iter);
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400341 }
342
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400343 gchar *text = NULL;
344
345 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(tree_model), iter);
Stepan Salenikovich501a7f82015-12-23 11:06:47 -0500346 QVariant var_d = idx.data(static_cast<int>(Call::Role::DateTime));
347 if (idx.isValid() && var_d.isValid()) {
Stepan Salenikovich803562b2015-12-23 10:51:01 -0500348 QDateTime date_time = var_d.value<QDateTime>();
Stepan Salenikovich501a7f82015-12-23 11:06:47 -0500349
350 /* we want the color of the text to be the default color if this iter is
351 * selected so that the treeview is able to invert it against the selection color */
352 if (is_selected) {
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400353 text = g_markup_printf_escaped("%s\n%s",
354 date_time.time().toString().toUtf8().constData(),
355 date_time.date().toString().toUtf8().constData()
Stepan Salenikovich501a7f82015-12-23 11:06:47 -0500356 );
357 } else {
Stepan Salenikovich0731de02016-05-17 17:47:16 -0400358 text = g_markup_printf_escaped("%s\n<span fgcolor=\"gray\">%s</span>",
359 date_time.time().toString().toUtf8().constData(),
360 date_time.date().toString().toUtf8().constData()
Stepan Salenikovich501a7f82015-12-23 11:06:47 -0500361 );
362 }
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400363 }
364
365 g_object_set(G_OBJECT(cell), "markup", text, NULL);
366 g_free(text);
367}
368
369static void
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400370history_view_init(HistoryView *self)
371{
372 HistoryViewPrivate *priv = HISTORY_VIEW_GET_PRIVATE(self);
373
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400374 /* make headers visible to allow column resizing */
Stepan Salenikovich4a5dd7f2015-10-29 16:35:02 -0400375 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(self), TRUE);
376 /* disable default search, we will handle it ourselves;
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400377 * otherwise the search steals input focus on key presses */
Stepan Salenikovich4a5dd7f2015-10-29 16:35:02 -0400378 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(self), FALSE);
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400379
Stepan Salenikovich9d294492015-05-14 16:34:24 -0400380 /* instantiate history proxy model */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400381 priv->q_sorted_proxy = &CategorizedHistoryModel::SortedProxy::instance();
Stepan Salenikovich9d294492015-05-14 16:34:24 -0400382
Stepan Salenikovich9d294492015-05-14 16:34:24 -0400383 /* select default category (the first one, which is by date) */
384 priv->q_sorted_proxy->categorySelectionModel()->setCurrentIndex(
385 priv->q_sorted_proxy->categoryModel()->index(0, 0),
386 QItemSelectionModel::ClearAndSelect);
Stepan Salenikovich803562b2015-12-23 10:51:01 -0500387 /* make sure it is sorted so that newest calls are at the top */
388 priv->q_sorted_proxy->model()->sort(0, Qt::AscendingOrder);
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400389
390 GtkQSortFilterTreeModel *history_model = gtk_q_sort_filter_tree_model_new(
Stepan Salenikovich9d294492015-05-14 16:34:24 -0400391 priv->q_sorted_proxy->model(),
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400392 5,
aviau271bcc22016-05-27 17:25:19 -0400393 0, Qt::DisplayRole, G_TYPE_STRING,
394 0, Call::Role::Number, G_TYPE_STRING,
395 0, Call::Role::FormattedDate, G_TYPE_STRING,
396 0, Call::Role::Direction, G_TYPE_INT,
397 0, Call::Role::Missed, G_TYPE_BOOLEAN);
Stepan Salenikovich4a5dd7f2015-10-29 16:35:02 -0400398 gtk_tree_view_set_model( GTK_TREE_VIEW(self), GTK_TREE_MODEL(history_model) );
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400399
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400400 /* call direction, photo, name/number column */
401 GtkCellArea *area = gtk_cell_area_box_new();
402 GtkTreeViewColumn *column = gtk_tree_view_column_new_with_area(area);
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -0400403 gtk_tree_view_column_set_title(column, C_("Call history", "Call"));
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400404
405 /* call direction */
406 GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400407 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400408
409 /* display the call direction with arrows */
410 gtk_tree_view_column_set_cell_data_func(
411 column,
412 renderer,
413 (GtkTreeCellDataFunc)render_call_direction,
414 NULL,
415 NULL);
416
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400417 /* photo renderer */
418 renderer = gtk_cell_renderer_pixbuf_new();
419 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400420
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400421 /* get the photo */
422 gtk_tree_view_column_set_cell_data_func(
423 column,
424 renderer,
425 (GtkTreeCellDataFunc)render_call_photo,
426 NULL,
427 NULL);
428
429 /* name and contact method renderer */
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400430 renderer = gtk_cell_renderer_text_new();
431 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400432 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
433
434 gtk_tree_view_column_set_cell_data_func(
435 column,
436 renderer,
437 (GtkTreeCellDataFunc)render_name_and_contact_method,
Stepan Salenikovich4a5dd7f2015-10-29 16:35:02 -0400438 self,
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400439 NULL);
440
Stepan Salenikovich4a5dd7f2015-10-29 16:35:02 -0400441 gtk_tree_view_append_column(GTK_TREE_VIEW(self), column);
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400442 gtk_tree_view_column_set_resizable(column, TRUE);
Stepan Salenikoviche7c4e282015-06-11 17:22:08 -0400443 gtk_tree_view_column_set_expand(column, TRUE);
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400444
Stepan Salenikovich501a7f82015-12-23 11:06:47 -0500445 /* date time column */
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400446 area = gtk_cell_area_box_new();
447 column = gtk_tree_view_column_new_with_area(area);
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -0400448 gtk_tree_view_column_set_title(column, C_("Call history", "Date"));
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400449
Stepan Salenikovich501a7f82015-12-23 11:06:47 -0500450 /* date time renderer */
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400451 renderer = gtk_cell_renderer_text_new ();
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400452 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
453 /* format the time*/
454 gtk_tree_view_column_set_cell_data_func(
455 column,
456 renderer,
Stepan Salenikovich501a7f82015-12-23 11:06:47 -0500457 (GtkTreeCellDataFunc)render_date_time,
458 self,
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400459 NULL);
460
Stepan Salenikovich4a5dd7f2015-10-29 16:35:02 -0400461 gtk_tree_view_append_column(GTK_TREE_VIEW(self), column);
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400462 gtk_tree_view_column_set_resizable(column, TRUE);
Stepan Salenikoviche7c4e282015-06-11 17:22:08 -0400463 gtk_tree_view_column_set_expand(column, FALSE);
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400464
Stepan Salenikovich4a5dd7f2015-10-29 16:35:02 -0400465 g_signal_connect(self, "row-activated", G_CALLBACK(activate_history_item), NULL);
466 g_signal_connect(self, "button-press-event", G_CALLBACK(history_popup_menu), self);
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400467
468 gtk_widget_show_all(GTK_WIDGET(self));
469}
470
471static void
472history_view_dispose(GObject *object)
473{
Stepan Salenikovich9d294492015-05-14 16:34:24 -0400474 HistoryViewPrivate *priv = HISTORY_VIEW_GET_PRIVATE(object);
475
476 QObject::disconnect(priv->category_changed);
477
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400478 G_OBJECT_CLASS(history_view_parent_class)->dispose(object);
479}
480
481static void
482history_view_finalize(GObject *object)
483{
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400484 G_OBJECT_CLASS(history_view_parent_class)->finalize(object);
485}
486
487static void
488history_view_class_init(HistoryViewClass *klass)
489{
490 G_OBJECT_CLASS(klass)->finalize = history_view_finalize;
491 G_OBJECT_CLASS(klass)->dispose = history_view_dispose;
492}
493
494GtkWidget *
495history_view_new()
496{
497 gpointer self = g_object_new(HISTORY_VIEW_TYPE, NULL);
498
499 return (GtkWidget *)self;
Stepan Salenikovich9d294492015-05-14 16:34:24 -0400500}