blob: 43a8633e395e6eaceebc263b7f81fbc9c2ba926b [file] [log] [blame]
Stepan Salenikovich9816a942015-04-22 17:49:16 -04001/*
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -04002 * Copyright (C) 2015 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.
18 *
19 * Additional permission under GNU GPL version 3 section 7:
20 *
21 * If you modify this program, or any covered work, by linking or
22 * combining it with the OpenSSL project's OpenSSL library (or a
23 * modified version of that library), containing parts covered by the
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -040024 * terms of the OpenSSL or SSLeay licenses, Savoir-faire Linux Inc.
Stepan Salenikovich9816a942015-04-22 17:49:16 -040025 * grants you additional permission to convey the resulting work.
26 * Corresponding Source for a non-source form of such a combination
27 * shall include the source code for the parts of OpenSSL used as well
28 * as that of the covered work.
29 */
30
31#include "historyview.h"
32
33#include <gtk/gtk.h>
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -040034#include <glib/gi18n.h>
Stepan Salenikovich9816a942015-04-22 17:49:16 -040035#include "models/gtkqsortfiltertreemodel.h"
36#include <categorizedhistorymodel.h>
37#include <QtCore/QSortFilterProxyModel>
38#include <personmodel.h>
39#include "utils/calling.h"
40#include <memory>
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -040041#include <globalinstances.h>
42#include "native/pixbufmanipulator.h"
Stepan Salenikovich9816a942015-04-22 17:49:16 -040043#include "defines.h"
44#include "utils/models.h"
45#include <contactmethod.h>
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -040046#include <QtCore/QDateTime> // for date time formatting
Stepan Salenikovich9d294492015-05-14 16:34:24 -040047#include <QtCore/QItemSelectionModel>
Stepan Salenikovichf2d76c52015-07-17 17:54:56 -040048#include "utils/menus.h"
Stepan Salenikovich9816a942015-04-22 17:49:16 -040049
50struct _HistoryView
51{
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -040052 GtkBox parent;
Stepan Salenikovich9816a942015-04-22 17:49:16 -040053};
54
55struct _HistoryViewClass
56{
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -040057 GtkBoxClass parent_class;
Stepan Salenikovich9816a942015-04-22 17:49:16 -040058};
59
60typedef struct _HistoryViewPrivate HistoryViewPrivate;
61
62struct _HistoryViewPrivate
63{
Stepan Salenikovich9d294492015-05-14 16:34:24 -040064 CategorizedHistoryModel::SortedProxy *q_sorted_proxy;
65 QMetaObject::Connection category_changed;
Stepan Salenikovich9816a942015-04-22 17:49:16 -040066};
67
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -040068G_DEFINE_TYPE_WITH_PRIVATE(HistoryView, history_view, GTK_TYPE_BOX);
Stepan Salenikovich9816a942015-04-22 17:49:16 -040069
70#define HISTORY_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), HISTORY_VIEW_TYPE, HistoryViewPrivate))
71
72static void
73render_call_direction(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
74 GtkCellRenderer *cell,
75 GtkTreeModel *tree_model,
76 GtkTreeIter *iter,
77 G_GNUC_UNUSED gpointer data)
78{
79 /* check if this is a top level item (the fuzzy date item),
80 * in this case we don't want to show a call direction */
81 gchar *render_direction = NULL;
82 GtkTreeIter parent;
83 if (gtk_tree_model_iter_parent(tree_model, &parent, iter)) {
84 /* get direction and missed values */
85 GValue value = G_VALUE_INIT;
86 gtk_tree_model_get_value(tree_model, iter, 3, &value);
87 Call::Direction direction = (Call::Direction)g_value_get_int(&value);
88 g_value_unset(&value);
89
90 gtk_tree_model_get_value(tree_model, iter, 4, &value);
91 gboolean missed = g_value_get_boolean(&value);
92 g_value_unset(&value);
93
94 switch (direction) {
95 case Call::Direction::INCOMING:
96 if (missed)
97 render_direction = g_strdup_printf("<span fgcolor=\"red\" font=\"monospace\">&#8601;</span>");
98 else
99 render_direction = g_strdup_printf("<span fgcolor=\"green\" font=\"monospace\">&#8601;</span>");
100 break;
101 case Call::Direction::OUTGOING:
102 if (missed)
103 render_direction = g_strdup_printf("<span fgcolor=\"red\" font=\"monospace\">&#8599;</span>");
104 else
105 render_direction = g_strdup_printf("<span fgcolor=\"green\" font=\"monospace\">&#8599;</span>");
106 break;
107 }
108 }
109 g_object_set(G_OBJECT(cell), "markup", render_direction, NULL);
110 g_free(render_direction);
111}
112
113static void
114activate_history_item(GtkTreeView *tree_view,
115 GtkTreePath *path,
116 G_GNUC_UNUSED GtkTreeViewColumn *column,
117 G_GNUC_UNUSED gpointer user_data)
118{
119 GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
120
121 /* expand / collapse row */
122 if (gtk_tree_view_row_expanded(tree_view, path))
123 gtk_tree_view_collapse_row(tree_view, path);
124 else
125 gtk_tree_view_expand_row(tree_view, path, FALSE);
126
127 /* get iter */
128 GtkTreeIter iter;
129 if (gtk_tree_model_get_iter(model, &iter, path)) {
130 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(model), &iter);
131
132 QVariant contact_method = idx.data(static_cast<int>(Call::Role::ContactMethod));
133 /* create new call */
134 if (contact_method.value<ContactMethod*>()) {
135 place_new_call(contact_method.value<ContactMethod*>());
136 }
137 }
138}
139
140static void
141copy_history_item(G_GNUC_UNUSED GtkWidget *item, GtkTreeView *treeview)
142{
143 GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
144 QModelIndex idx = get_index_from_selection(selection);
145
146 if (idx.isValid()) {
147 GtkClipboard* clip = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
148
149 const gchar* number = idx.data(static_cast<int>(Call::Role::Number)).toString().toUtf8().constData();
150 gtk_clipboard_set_text(clip, number, -1);
151 }
152}
153
154/* TODO: can't seem to delete just one item for now, add when supported in backend
155 * static void
156 * delete_history_item(G_GNUC_UNUSED GtkWidget *item, GtkTreeView *treeview)
157 * {
158 * GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
159 * QModelIndex idx = get_index_from_selection(selection);
160 *
161 * if (idx.isValid()) {
162 * g_debug("deleting history item");
163 * CategorizedHistoryModel::instance()->removeRow(idx.row(), idx.parent());
164 * }
165 * }
166 */
167
168static gboolean
169history_popup_menu(G_GNUC_UNUSED GtkWidget *widget, GdkEventButton *event, GtkTreeView *treeview)
170{
171 /* build popup menu when right clicking on history item
172 * user should be able to copy the "number",
173 * delete history item or all of the history,
174 * and eventualy add the number to a contact
175 */
176
177 /* check for right click */
178 if (event->button != BUTTON_RIGHT_CLICK || event->type != GDK_BUTTON_PRESS)
179 return FALSE;
180
181 GtkWidget *menu = gtk_menu_new();
182
183 /* copy */
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -0400184 GtkWidget *item = gtk_menu_item_new_with_mnemonic(_("_Copy"));
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400185 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
186 g_signal_connect(item, "activate", G_CALLBACK(copy_history_item), treeview);
187
188 /* TODO: delete history entry
189 * gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
190 * item = gtk_menu_item_new_with_mnemonic("_Delete entry");
191 * gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
192 * g_signal_connect(item, "activate", G_CALLBACK(delete_history_item), treeview);
193 */
194
Stepan Salenikovichf2d76c52015-07-17 17:54:56 -0400195 /* check if the selected item is a call, if so get the contact method and
196 * check if it is already linked to a person, if not, then offer to either
197 * add to a new or existing contact */
198 auto selection = gtk_tree_view_get_selection(treeview);
199 const auto& idx = get_index_from_selection(selection);
200 const auto& var_c = idx.data(static_cast<int>(Call::Role::Object));
201 if (idx.isValid() && var_c.isValid()) {
202 if (auto call = var_c.value<Call *>()) {
203 auto contactmethod = call->peerContactMethod();
204 if (!contact_method_has_contact(contactmethod)) {
Stepan Salenikovich0cf247d2015-07-24 17:36:32 -0400205 GtkTreeIter iter;
206 GtkTreeModel *model;
207 gtk_tree_selection_get_selected(selection, &model, &iter);
208 auto path = gtk_tree_model_get_path(model, &iter);
209 auto column = gtk_tree_view_get_column(treeview, 0);
210 GdkRectangle rect;
211 gtk_tree_view_get_cell_area(treeview, path, column, &rect);
212 gtk_tree_view_convert_bin_window_to_widget_coords(treeview, rect.x, rect.y, &rect.x, &rect.y);
213 gtk_tree_path_free(path);
214 auto add_to = menu_item_add_to_contact(contactmethod, GTK_WIDGET(treeview), &rect);
Stepan Salenikovichf2d76c52015-07-17 17:54:56 -0400215 gtk_menu_shell_append(GTK_MENU_SHELL(menu), add_to);
216 }
217 }
218 }
219
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400220 /* show menu */
221 gtk_widget_show_all(menu);
222 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);
223
Stepan Salenikovichf2d76c52015-07-17 17:54:56 -0400224 return TRUE;
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400225}
226
227static void
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400228render_call_photo(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
229 GtkCellRenderer *cell,
230 GtkTreeModel *tree_model,
231 GtkTreeIter *iter,
232 G_GNUC_UNUSED gpointer data)
233{
234 /* check if this is a top level item (category),
235 * in this case we don't want to show a photo */
236 GtkTreePath *path = gtk_tree_model_get_path(tree_model, iter);
237 int depth = gtk_tree_path_get_depth(path);
238 gtk_tree_path_free(path);
239 if (depth == 2) {
240 /* get person */
241 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(tree_model), iter);
242 if (idx.isValid()) {
243 QVariant var_c = idx.data(static_cast<int>(Call::Role::Object));
244 Call *c = var_c.value<Call *>();
245 /* get photo */
Stepan Salenikovichbbd6c132015-08-20 15:21:48 -0400246 QVariant var_p = GlobalInstances::pixmapManipulator().callPhoto(c, QSize(50, 50), false);
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400247 std::shared_ptr<GdkPixbuf> photo = var_p.value<std::shared_ptr<GdkPixbuf>>();
248 g_object_set(G_OBJECT(cell), "pixbuf", photo.get(), NULL);
249 return;
250 }
251 }
252
253 /* otherwise, make sure its an empty pixbuf */
254 g_object_set(G_OBJECT(cell), "pixbuf", NULL, NULL);
255}
256
257static void
258render_name_and_contact_method(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
259 GtkCellRenderer *cell,
260 GtkTreeModel *tree_model,
261 GtkTreeIter *iter,
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400262 GtkTreeView *treeview)
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400263{
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400264 // check if this iter is selected
265 gboolean is_selected = FALSE;
266 if (GTK_IS_TREE_VIEW(treeview)) {
267 auto selection = gtk_tree_view_get_selection(treeview);
268 is_selected = gtk_tree_selection_iter_is_selected(selection, iter);
269 }
270
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400271 /**
272 * If call, show the name and the contact method (number) underneath;
273 * otherwise just display the category.
274 */
275 GtkTreePath *path = gtk_tree_model_get_path(tree_model, iter);
276 int depth = gtk_tree_path_get_depth(path);
277 gtk_tree_path_free(path);
278
279 gchar *text = NULL;
280
281 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(tree_model), iter);
282 if (idx.isValid()) {
283 QVariant var = idx.data(Qt::DisplayRole);
284 if (depth == 1) {
285 /* category */
286 text = g_strdup_printf("<b>%s</b>", var.value<QString>().toUtf8().constData());
287 } else if (depth == 2) {
288 /* call item */
289 QVariant var_name = idx.data(static_cast<int>(Call::Role::Name));
290 QVariant var_number = idx.data(static_cast<int>(Call::Role::Number));
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400291
292 /* we want the color of the status text to be the default color if this iter is
293 * selected so that the treeview is able to invert it against the selection color */
294 if (is_selected) {
295 text = g_strdup_printf("%s\n %s",
296 var_name.value<QString>().toUtf8().constData(),
297 var_number.value<QString>().toUtf8().constData());
298 } else {
299 text = g_strdup_printf("%s\n <span fgcolor=\"gray\">%s</span>",
300 var_name.value<QString>().toUtf8().constData(),
301 var_number.value<QString>().toUtf8().constData());
302 }
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400303 }
304 }
305
306 g_object_set(G_OBJECT(cell), "markup", text, NULL);
307 g_free(text);
308}
309
310static void
311render_time(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
312 GtkCellRenderer *cell,
313 GtkTreeModel *tree_model,
314 GtkTreeIter *iter,
315 G_GNUC_UNUSED gpointer data)
316{
317 /**
318 * If call, show the the time;
319 * if category, don't show anything.
320 */
321 GtkTreePath *path = gtk_tree_model_get_path(tree_model, iter);
322 int depth = gtk_tree_path_get_depth(path);
323 gtk_tree_path_free(path);
324
325 gchar *text = NULL;
326
327 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(tree_model), iter);
328 if (idx.isValid() && depth == 2) {
329 QVariant var_d = idx.data(static_cast<int>(Call::Role::Date));
330 time_t time = var_d.value<time_t>();
331 QDateTime date_time = QDateTime::fromTime_t(time);
332 text = g_strdup_printf("%s", date_time.time().toString().toUtf8().constData());
333 }
334
335 g_object_set(G_OBJECT(cell), "markup", text, NULL);
336 g_free(text);
337}
338
339static void
340render_date(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
341 GtkCellRenderer *cell,
342 GtkTreeModel *tree_model,
343 GtkTreeIter *iter,
344 G_GNUC_UNUSED gpointer data)
345{
346 /**
347 * If call, show the date;
348 * if category, don't show anything.
349 */
350 GtkTreePath *path = gtk_tree_model_get_path(tree_model, iter);
351 int depth = gtk_tree_path_get_depth(path);
352 gtk_tree_path_free(path);
353
354 gchar *text = NULL;
355
356 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(tree_model), iter);
357 if (idx.isValid() && depth == 2) {
358 QVariant var_d = idx.data(static_cast<int>(Call::Role::Date));
359 time_t time = var_d.value<time_t>();
360 QDateTime date_time = QDateTime::fromTime_t(time);
361 text = g_strdup_printf("%s", date_time.date().toString().toUtf8().constData());
362 }
363
364 g_object_set(G_OBJECT(cell), "markup", text, NULL);
365 g_free(text);
366}
367
368static void
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400369history_view_init(HistoryView *self)
370{
371 HistoryViewPrivate *priv = HISTORY_VIEW_GET_PRIVATE(self);
372
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -0400373 gtk_orientable_set_orientation(GTK_ORIENTABLE(self), GTK_ORIENTATION_VERTICAL);
374 /* need to be able to focus on widget so that we can auto-scroll to it */
375 gtk_widget_set_can_focus(GTK_WIDGET(self), TRUE);
Stepan Salenikovich7be4f622015-05-13 15:36:19 -0400376
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400377 GtkWidget *treeview_history = gtk_tree_view_new();
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -0400378 /* set can-focus to false so that the scrollwindow doesn't jump to try to
379 * contain the top of the treeview */
380 gtk_widget_set_can_focus(treeview_history, FALSE);
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400381 /* make headers visible to allow column resizing */
382 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview_history), TRUE);
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -0400383 gtk_box_pack_start(GTK_BOX(self), treeview_history, TRUE, TRUE, 0);
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400384
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400385 /* disable default search, we will handle it ourselves via LRC;
386 * otherwise the search steals input focus on key presses */
387 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(treeview_history), FALSE);
388
Stepan Salenikovich9d294492015-05-14 16:34:24 -0400389 /* instantiate history proxy model */
390 priv->q_sorted_proxy = CategorizedHistoryModel::SortedProxy::instance();
391
392 /* for now there is no way in the UI to pick whether sorting is ascending
393 * or descending, so we do it in the code when the category changes */
394 priv->category_changed = QObject::connect(
395 priv->q_sorted_proxy->categorySelectionModel(),
396 &QItemSelectionModel::currentChanged,
397 [=] (const QModelIndex &current, G_GNUC_UNUSED const QModelIndex &previous)
398 {
399 if (current.isValid()) {
400 if (current.row() == 0) {
401 /* sort in descending order for the date */
402 priv->q_sorted_proxy->model()->sort(0, Qt::DescendingOrder);
403 } else {
404 /* ascending order for verything else */
405 priv->q_sorted_proxy->model()->sort(0);
406 }
407 }
408 }
409 );
410
411 /* select default category (the first one, which is by date) */
412 priv->q_sorted_proxy->categorySelectionModel()->setCurrentIndex(
413 priv->q_sorted_proxy->categoryModel()->index(0, 0),
414 QItemSelectionModel::ClearAndSelect);
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400415
416 GtkQSortFilterTreeModel *history_model = gtk_q_sort_filter_tree_model_new(
Stepan Salenikovich9d294492015-05-14 16:34:24 -0400417 priv->q_sorted_proxy->model(),
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400418 5,
419 Qt::DisplayRole, G_TYPE_STRING,
420 Call::Role::Number, G_TYPE_STRING,
421 Call::Role::FormattedDate, G_TYPE_STRING,
422 Call::Role::Direction, G_TYPE_INT,
423 Call::Role::Missed, G_TYPE_BOOLEAN);
424 gtk_tree_view_set_model( GTK_TREE_VIEW(treeview_history), GTK_TREE_MODEL(history_model) );
425
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400426 /* call direction, photo, name/number column */
427 GtkCellArea *area = gtk_cell_area_box_new();
428 GtkTreeViewColumn *column = gtk_tree_view_column_new_with_area(area);
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -0400429 gtk_tree_view_column_set_title(column, C_("Call history", "Call"));
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400430
431 /* call direction */
432 GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400433 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400434
435 /* display the call direction with arrows */
436 gtk_tree_view_column_set_cell_data_func(
437 column,
438 renderer,
439 (GtkTreeCellDataFunc)render_call_direction,
440 NULL,
441 NULL);
442
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400443 /* photo renderer */
444 renderer = gtk_cell_renderer_pixbuf_new();
445 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400446
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400447 /* get the photo */
448 gtk_tree_view_column_set_cell_data_func(
449 column,
450 renderer,
451 (GtkTreeCellDataFunc)render_call_photo,
452 NULL,
453 NULL);
454
455 /* name and contact method renderer */
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400456 renderer = gtk_cell_renderer_text_new();
457 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400458 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
459
460 gtk_tree_view_column_set_cell_data_func(
461 column,
462 renderer,
463 (GtkTreeCellDataFunc)render_name_and_contact_method,
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400464 treeview_history,
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400465 NULL);
466
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400467 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview_history), column);
468 gtk_tree_view_column_set_resizable(column, TRUE);
Stepan Salenikoviche7c4e282015-06-11 17:22:08 -0400469 gtk_tree_view_column_set_expand(column, TRUE);
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400470
471 /* date column */
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400472 area = gtk_cell_area_box_new();
473 column = gtk_tree_view_column_new_with_area(area);
Stepan Salenikovicha1b8cb32015-09-11 14:58:35 -0400474 gtk_tree_view_column_set_title(column, C_("Call history", "Date"));
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400475
476 /* time renderer */
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400477 renderer = gtk_cell_renderer_text_new ();
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400478 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
479 /* format the time*/
480 gtk_tree_view_column_set_cell_data_func(
481 column,
482 renderer,
483 (GtkTreeCellDataFunc)render_time,
484 NULL,
485 NULL);
486
487 /* date renderer */
488 renderer = gtk_cell_renderer_text_new ();
489 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400490 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400491 /* format the date */
492 gtk_tree_view_column_set_cell_data_func(
493 column,
494 renderer,
495 (GtkTreeCellDataFunc)render_date,
496 NULL,
497 NULL);
498
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400499 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview_history), column);
500 gtk_tree_view_column_set_resizable(column, TRUE);
Stepan Salenikoviche7c4e282015-06-11 17:22:08 -0400501 gtk_tree_view_column_set_expand(column, FALSE);
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400502
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400503 g_signal_connect(treeview_history, "row-activated", G_CALLBACK(activate_history_item), NULL);
504 g_signal_connect(treeview_history, "button-press-event", G_CALLBACK(history_popup_menu), treeview_history);
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400505
506 gtk_widget_show_all(GTK_WIDGET(self));
507}
508
509static void
510history_view_dispose(GObject *object)
511{
Stepan Salenikovich9d294492015-05-14 16:34:24 -0400512 HistoryViewPrivate *priv = HISTORY_VIEW_GET_PRIVATE(object);
513
514 QObject::disconnect(priv->category_changed);
515
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400516 G_OBJECT_CLASS(history_view_parent_class)->dispose(object);
517}
518
519static void
520history_view_finalize(GObject *object)
521{
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400522 G_OBJECT_CLASS(history_view_parent_class)->finalize(object);
523}
524
525static void
526history_view_class_init(HistoryViewClass *klass)
527{
528 G_OBJECT_CLASS(klass)->finalize = history_view_finalize;
529 G_OBJECT_CLASS(klass)->dispose = history_view_dispose;
530}
531
532GtkWidget *
533history_view_new()
534{
535 gpointer self = g_object_new(HISTORY_VIEW_TYPE, NULL);
536
537 return (GtkWidget *)self;
Stepan Salenikovich9d294492015-05-14 16:34:24 -0400538}