blob: 758391eca205e83acbc1059f25e084adb3b00b32 [file] [log] [blame]
Stepan Salenikovich9816a942015-04-22 17:49:16 -04001/*
2 * Copyright (C) 2015 Savoir-Faire Linux Inc.
3 * 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
24 * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
25 * 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>
34#include "models/gtkqsortfiltertreemodel.h"
35#include <categorizedhistorymodel.h>
36#include <QtCore/QSortFilterProxyModel>
37#include <personmodel.h>
38#include "utils/calling.h"
39#include <memory>
40#include "delegates/pixbufdelegate.h"
41#include "defines.h"
42#include "utils/models.h"
43#include <contactmethod.h>
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -040044#include <QtCore/QDateTime> // for date time formatting
Stepan Salenikovich9d294492015-05-14 16:34:24 -040045#include <QtCore/QItemSelectionModel>
Stepan Salenikovichf2d76c52015-07-17 17:54:56 -040046#include "utils/menus.h"
Stepan Salenikovich9816a942015-04-22 17:49:16 -040047
48struct _HistoryView
49{
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -040050 GtkBox parent;
Stepan Salenikovich9816a942015-04-22 17:49:16 -040051};
52
53struct _HistoryViewClass
54{
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -040055 GtkBoxClass parent_class;
Stepan Salenikovich9816a942015-04-22 17:49:16 -040056};
57
58typedef struct _HistoryViewPrivate HistoryViewPrivate;
59
60struct _HistoryViewPrivate
61{
Stepan Salenikovich9d294492015-05-14 16:34:24 -040062 CategorizedHistoryModel::SortedProxy *q_sorted_proxy;
63 QMetaObject::Connection category_changed;
Stepan Salenikovich9816a942015-04-22 17:49:16 -040064};
65
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -040066G_DEFINE_TYPE_WITH_PRIVATE(HistoryView, history_view, GTK_TYPE_BOX);
Stepan Salenikovich9816a942015-04-22 17:49:16 -040067
68#define HISTORY_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), HISTORY_VIEW_TYPE, HistoryViewPrivate))
69
70static void
71render_call_direction(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
72 GtkCellRenderer *cell,
73 GtkTreeModel *tree_model,
74 GtkTreeIter *iter,
75 G_GNUC_UNUSED gpointer data)
76{
77 /* check if this is a top level item (the fuzzy date item),
78 * in this case we don't want to show a call direction */
79 gchar *render_direction = NULL;
80 GtkTreeIter parent;
81 if (gtk_tree_model_iter_parent(tree_model, &parent, iter)) {
82 /* get direction and missed values */
83 GValue value = G_VALUE_INIT;
84 gtk_tree_model_get_value(tree_model, iter, 3, &value);
85 Call::Direction direction = (Call::Direction)g_value_get_int(&value);
86 g_value_unset(&value);
87
88 gtk_tree_model_get_value(tree_model, iter, 4, &value);
89 gboolean missed = g_value_get_boolean(&value);
90 g_value_unset(&value);
91
92 switch (direction) {
93 case Call::Direction::INCOMING:
94 if (missed)
95 render_direction = g_strdup_printf("<span fgcolor=\"red\" font=\"monospace\">&#8601;</span>");
96 else
97 render_direction = g_strdup_printf("<span fgcolor=\"green\" font=\"monospace\">&#8601;</span>");
98 break;
99 case Call::Direction::OUTGOING:
100 if (missed)
101 render_direction = g_strdup_printf("<span fgcolor=\"red\" font=\"monospace\">&#8599;</span>");
102 else
103 render_direction = g_strdup_printf("<span fgcolor=\"green\" font=\"monospace\">&#8599;</span>");
104 break;
105 }
106 }
107 g_object_set(G_OBJECT(cell), "markup", render_direction, NULL);
108 g_free(render_direction);
109}
110
111static void
112activate_history_item(GtkTreeView *tree_view,
113 GtkTreePath *path,
114 G_GNUC_UNUSED GtkTreeViewColumn *column,
115 G_GNUC_UNUSED gpointer user_data)
116{
117 GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
118
119 /* expand / collapse row */
120 if (gtk_tree_view_row_expanded(tree_view, path))
121 gtk_tree_view_collapse_row(tree_view, path);
122 else
123 gtk_tree_view_expand_row(tree_view, path, FALSE);
124
125 /* get iter */
126 GtkTreeIter iter;
127 if (gtk_tree_model_get_iter(model, &iter, path)) {
128 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(model), &iter);
129
130 QVariant contact_method = idx.data(static_cast<int>(Call::Role::ContactMethod));
131 /* create new call */
132 if (contact_method.value<ContactMethod*>()) {
133 place_new_call(contact_method.value<ContactMethod*>());
134 }
135 }
136}
137
138static void
139copy_history_item(G_GNUC_UNUSED GtkWidget *item, GtkTreeView *treeview)
140{
141 GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
142 QModelIndex idx = get_index_from_selection(selection);
143
144 if (idx.isValid()) {
145 GtkClipboard* clip = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
146
147 const gchar* number = idx.data(static_cast<int>(Call::Role::Number)).toString().toUtf8().constData();
148 gtk_clipboard_set_text(clip, number, -1);
149 }
150}
151
152/* TODO: can't seem to delete just one item for now, add when supported in backend
153 * static void
154 * delete_history_item(G_GNUC_UNUSED GtkWidget *item, GtkTreeView *treeview)
155 * {
156 * GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
157 * QModelIndex idx = get_index_from_selection(selection);
158 *
159 * if (idx.isValid()) {
160 * g_debug("deleting history item");
161 * CategorizedHistoryModel::instance()->removeRow(idx.row(), idx.parent());
162 * }
163 * }
164 */
165
166static gboolean
167history_popup_menu(G_GNUC_UNUSED GtkWidget *widget, GdkEventButton *event, GtkTreeView *treeview)
168{
169 /* build popup menu when right clicking on history item
170 * user should be able to copy the "number",
171 * delete history item or all of the history,
172 * and eventualy add the number to a contact
173 */
174
175 /* check for right click */
176 if (event->button != BUTTON_RIGHT_CLICK || event->type != GDK_BUTTON_PRESS)
177 return FALSE;
178
179 GtkWidget *menu = gtk_menu_new();
180
181 /* copy */
182 GtkWidget *item = gtk_menu_item_new_with_mnemonic("_Copy");
183 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
184 g_signal_connect(item, "activate", G_CALLBACK(copy_history_item), treeview);
185
186 /* TODO: delete history entry
187 * gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
188 * item = gtk_menu_item_new_with_mnemonic("_Delete entry");
189 * gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
190 * g_signal_connect(item, "activate", G_CALLBACK(delete_history_item), treeview);
191 */
192
Stepan Salenikovichf2d76c52015-07-17 17:54:56 -0400193 /* check if the selected item is a call, if so get the contact method and
194 * check if it is already linked to a person, if not, then offer to either
195 * add to a new or existing contact */
196 auto selection = gtk_tree_view_get_selection(treeview);
197 const auto& idx = get_index_from_selection(selection);
198 const auto& var_c = idx.data(static_cast<int>(Call::Role::Object));
199 if (idx.isValid() && var_c.isValid()) {
200 if (auto call = var_c.value<Call *>()) {
201 auto contactmethod = call->peerContactMethod();
202 if (!contact_method_has_contact(contactmethod)) {
203 auto add_to = menu_item_contact_add_to(contactmethod, GTK_WIDGET(treeview));
204 gtk_menu_shell_append(GTK_MENU_SHELL(menu), add_to);
205 }
206 }
207 }
208
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400209 /* show menu */
210 gtk_widget_show_all(menu);
211 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);
212
Stepan Salenikovichf2d76c52015-07-17 17:54:56 -0400213 return TRUE;
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400214}
215
216static void
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400217render_call_photo(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
218 GtkCellRenderer *cell,
219 GtkTreeModel *tree_model,
220 GtkTreeIter *iter,
221 G_GNUC_UNUSED gpointer data)
222{
223 /* check if this is a top level item (category),
224 * in this case we don't want to show a photo */
225 GtkTreePath *path = gtk_tree_model_get_path(tree_model, iter);
226 int depth = gtk_tree_path_get_depth(path);
227 gtk_tree_path_free(path);
228 if (depth == 2) {
229 /* get person */
230 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(tree_model), iter);
231 if (idx.isValid()) {
232 QVariant var_c = idx.data(static_cast<int>(Call::Role::Object));
233 Call *c = var_c.value<Call *>();
234 /* get photo */
235 QVariant var_p = PixbufDelegate::instance()->callPhoto(c, QSize(50, 50), false);
236 std::shared_ptr<GdkPixbuf> photo = var_p.value<std::shared_ptr<GdkPixbuf>>();
237 g_object_set(G_OBJECT(cell), "pixbuf", photo.get(), NULL);
238 return;
239 }
240 }
241
242 /* otherwise, make sure its an empty pixbuf */
243 g_object_set(G_OBJECT(cell), "pixbuf", NULL, NULL);
244}
245
246static void
247render_name_and_contact_method(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
248 GtkCellRenderer *cell,
249 GtkTreeModel *tree_model,
250 GtkTreeIter *iter,
251 G_GNUC_UNUSED gpointer data)
252{
253 /**
254 * If call, show the name and the contact method (number) underneath;
255 * otherwise just display the category.
256 */
257 GtkTreePath *path = gtk_tree_model_get_path(tree_model, iter);
258 int depth = gtk_tree_path_get_depth(path);
259 gtk_tree_path_free(path);
260
261 gchar *text = NULL;
262
263 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(tree_model), iter);
264 if (idx.isValid()) {
265 QVariant var = idx.data(Qt::DisplayRole);
266 if (depth == 1) {
267 /* category */
268 text = g_strdup_printf("<b>%s</b>", var.value<QString>().toUtf8().constData());
269 } else if (depth == 2) {
270 /* call item */
271 QVariant var_name = idx.data(static_cast<int>(Call::Role::Name));
272 QVariant var_number = idx.data(static_cast<int>(Call::Role::Number));
273 text = g_strdup_printf("%s\n <span fgcolor=\"gray\">%s</span>",
274 var_name.value<QString>().toUtf8().constData(),
275 var_number.value<QString>().toUtf8().constData());
276 }
277 }
278
279 g_object_set(G_OBJECT(cell), "markup", text, NULL);
280 g_free(text);
281}
282
283static void
284render_time(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
285 GtkCellRenderer *cell,
286 GtkTreeModel *tree_model,
287 GtkTreeIter *iter,
288 G_GNUC_UNUSED gpointer data)
289{
290 /**
291 * If call, show the the time;
292 * if category, don't show anything.
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() && depth == 2) {
302 QVariant var_d = idx.data(static_cast<int>(Call::Role::Date));
303 time_t time = var_d.value<time_t>();
304 QDateTime date_time = QDateTime::fromTime_t(time);
305 text = g_strdup_printf("%s", date_time.time().toString().toUtf8().constData());
306 }
307
308 g_object_set(G_OBJECT(cell), "markup", text, NULL);
309 g_free(text);
310}
311
312static void
313render_date(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
314 GtkCellRenderer *cell,
315 GtkTreeModel *tree_model,
316 GtkTreeIter *iter,
317 G_GNUC_UNUSED gpointer data)
318{
319 /**
320 * If call, show the date;
321 * if category, don't show anything.
322 */
323 GtkTreePath *path = gtk_tree_model_get_path(tree_model, iter);
324 int depth = gtk_tree_path_get_depth(path);
325 gtk_tree_path_free(path);
326
327 gchar *text = NULL;
328
329 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(tree_model), iter);
330 if (idx.isValid() && depth == 2) {
331 QVariant var_d = idx.data(static_cast<int>(Call::Role::Date));
332 time_t time = var_d.value<time_t>();
333 QDateTime date_time = QDateTime::fromTime_t(time);
334 text = g_strdup_printf("%s", date_time.date().toString().toUtf8().constData());
335 }
336
337 g_object_set(G_OBJECT(cell), "markup", text, NULL);
338 g_free(text);
339}
340
341static void
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400342history_view_init(HistoryView *self)
343{
344 HistoryViewPrivate *priv = HISTORY_VIEW_GET_PRIVATE(self);
345
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -0400346 gtk_orientable_set_orientation(GTK_ORIENTABLE(self), GTK_ORIENTATION_VERTICAL);
347 /* need to be able to focus on widget so that we can auto-scroll to it */
348 gtk_widget_set_can_focus(GTK_WIDGET(self), TRUE);
Stepan Salenikovich7be4f622015-05-13 15:36:19 -0400349
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -0400350 GtkWidget *label_history = gtk_label_new("History");
351 gtk_box_pack_start(GTK_BOX(self), label_history, FALSE, TRUE, 10);
352
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400353 GtkWidget *treeview_history = gtk_tree_view_new();
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -0400354 /* set can-focus to false so that the scrollwindow doesn't jump to try to
355 * contain the top of the treeview */
356 gtk_widget_set_can_focus(treeview_history, FALSE);
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400357 /* make headers visible to allow column resizing */
358 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview_history), TRUE);
Stepan Salenikovich7c71bfe2015-05-13 18:08:09 -0400359 gtk_box_pack_start(GTK_BOX(self), treeview_history, TRUE, TRUE, 0);
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400360
Stepan Salenikovichb01d7362015-04-27 23:02:00 -0400361 /* disable default search, we will handle it ourselves via LRC;
362 * otherwise the search steals input focus on key presses */
363 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(treeview_history), FALSE);
364
Stepan Salenikovich9d294492015-05-14 16:34:24 -0400365 /* instantiate history proxy model */
366 priv->q_sorted_proxy = CategorizedHistoryModel::SortedProxy::instance();
367
368 /* for now there is no way in the UI to pick whether sorting is ascending
369 * or descending, so we do it in the code when the category changes */
370 priv->category_changed = QObject::connect(
371 priv->q_sorted_proxy->categorySelectionModel(),
372 &QItemSelectionModel::currentChanged,
373 [=] (const QModelIndex &current, G_GNUC_UNUSED const QModelIndex &previous)
374 {
375 if (current.isValid()) {
376 if (current.row() == 0) {
377 /* sort in descending order for the date */
378 priv->q_sorted_proxy->model()->sort(0, Qt::DescendingOrder);
379 } else {
380 /* ascending order for verything else */
381 priv->q_sorted_proxy->model()->sort(0);
382 }
383 }
384 }
385 );
386
387 /* select default category (the first one, which is by date) */
388 priv->q_sorted_proxy->categorySelectionModel()->setCurrentIndex(
389 priv->q_sorted_proxy->categoryModel()->index(0, 0),
390 QItemSelectionModel::ClearAndSelect);
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400391
392 GtkQSortFilterTreeModel *history_model = gtk_q_sort_filter_tree_model_new(
Stepan Salenikovich9d294492015-05-14 16:34:24 -0400393 priv->q_sorted_proxy->model(),
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400394 5,
395 Qt::DisplayRole, G_TYPE_STRING,
396 Call::Role::Number, G_TYPE_STRING,
397 Call::Role::FormattedDate, G_TYPE_STRING,
398 Call::Role::Direction, G_TYPE_INT,
399 Call::Role::Missed, G_TYPE_BOOLEAN);
400 gtk_tree_view_set_model( GTK_TREE_VIEW(treeview_history), GTK_TREE_MODEL(history_model) );
401
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400402 /* call direction, photo, name/number column */
403 GtkCellArea *area = gtk_cell_area_box_new();
404 GtkTreeViewColumn *column = gtk_tree_view_column_new_with_area(area);
405 gtk_tree_view_column_set_title(column, "Call");
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400406
407 /* call direction */
408 GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400409 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400410
411 /* display the call direction with arrows */
412 gtk_tree_view_column_set_cell_data_func(
413 column,
414 renderer,
415 (GtkTreeCellDataFunc)render_call_direction,
416 NULL,
417 NULL);
418
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400419 /* photo renderer */
420 renderer = gtk_cell_renderer_pixbuf_new();
421 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400422
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400423 /* get the photo */
424 gtk_tree_view_column_set_cell_data_func(
425 column,
426 renderer,
427 (GtkTreeCellDataFunc)render_call_photo,
428 NULL,
429 NULL);
430
431 /* name and contact method renderer */
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400432 renderer = gtk_cell_renderer_text_new();
433 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400434 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
435
436 gtk_tree_view_column_set_cell_data_func(
437 column,
438 renderer,
439 (GtkTreeCellDataFunc)render_name_and_contact_method,
440 NULL,
441 NULL);
442
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400443 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview_history), column);
444 gtk_tree_view_column_set_resizable(column, TRUE);
Stepan Salenikoviche7c4e282015-06-11 17:22:08 -0400445 gtk_tree_view_column_set_expand(column, TRUE);
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400446
447 /* date column */
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400448 area = gtk_cell_area_box_new();
449 column = gtk_tree_view_column_new_with_area(area);
450 gtk_tree_view_column_set_title(column, "Date");
451
452 /* time renderer */
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400453 renderer = gtk_cell_renderer_text_new ();
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400454 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
455 /* format the time*/
456 gtk_tree_view_column_set_cell_data_func(
457 column,
458 renderer,
459 (GtkTreeCellDataFunc)render_time,
460 NULL,
461 NULL);
462
463 /* date renderer */
464 renderer = gtk_cell_renderer_text_new ();
465 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400466 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovich82b1acf2015-05-12 12:33:51 -0400467 /* format the date */
468 gtk_tree_view_column_set_cell_data_func(
469 column,
470 renderer,
471 (GtkTreeCellDataFunc)render_date,
472 NULL,
473 NULL);
474
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400475 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview_history), column);
476 gtk_tree_view_column_set_resizable(column, TRUE);
Stepan Salenikoviche7c4e282015-06-11 17:22:08 -0400477 gtk_tree_view_column_set_expand(column, FALSE);
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400478
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400479 g_signal_connect(treeview_history, "row-activated", G_CALLBACK(activate_history_item), NULL);
480 g_signal_connect(treeview_history, "button-press-event", G_CALLBACK(history_popup_menu), treeview_history);
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400481
482 gtk_widget_show_all(GTK_WIDGET(self));
483}
484
485static void
486history_view_dispose(GObject *object)
487{
Stepan Salenikovich9d294492015-05-14 16:34:24 -0400488 HistoryViewPrivate *priv = HISTORY_VIEW_GET_PRIVATE(object);
489
490 QObject::disconnect(priv->category_changed);
491
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400492 G_OBJECT_CLASS(history_view_parent_class)->dispose(object);
493}
494
495static void
496history_view_finalize(GObject *object)
497{
Stepan Salenikovich9816a942015-04-22 17:49:16 -0400498 G_OBJECT_CLASS(history_view_parent_class)->finalize(object);
499}
500
501static void
502history_view_class_init(HistoryViewClass *klass)
503{
504 G_OBJECT_CLASS(klass)->finalize = history_view_finalize;
505 G_OBJECT_CLASS(klass)->dispose = history_view_dispose;
506}
507
508GtkWidget *
509history_view_new()
510{
511 gpointer self = g_object_new(HISTORY_VIEW_TYPE, NULL);
512
513 return (GtkWidget *)self;
Stepan Salenikovich9d294492015-05-14 16:34:24 -0400514}