blob: 0d968088a003b375c395951f16c2c5d620ad8366 [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>
44
45struct _HistoryView
46{
47 GtkScrolledWindow parent;
48};
49
50struct _HistoryViewClass
51{
52 GtkScrolledWindowClass parent_class;
53};
54
55typedef struct _HistoryViewPrivate HistoryViewPrivate;
56
57struct _HistoryViewPrivate
58{
59 QSortFilterProxyModel *q_history_model;
60};
61
62G_DEFINE_TYPE_WITH_PRIVATE(HistoryView, history_view, GTK_TYPE_SCROLLED_WINDOW);
63
64#define HISTORY_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), HISTORY_VIEW_TYPE, HistoryViewPrivate))
65
66static void
67render_call_direction(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
68 GtkCellRenderer *cell,
69 GtkTreeModel *tree_model,
70 GtkTreeIter *iter,
71 G_GNUC_UNUSED gpointer data)
72{
73 /* check if this is a top level item (the fuzzy date item),
74 * in this case we don't want to show a call direction */
75 gchar *render_direction = NULL;
76 GtkTreeIter parent;
77 if (gtk_tree_model_iter_parent(tree_model, &parent, iter)) {
78 /* get direction and missed values */
79 GValue value = G_VALUE_INIT;
80 gtk_tree_model_get_value(tree_model, iter, 3, &value);
81 Call::Direction direction = (Call::Direction)g_value_get_int(&value);
82 g_value_unset(&value);
83
84 gtk_tree_model_get_value(tree_model, iter, 4, &value);
85 gboolean missed = g_value_get_boolean(&value);
86 g_value_unset(&value);
87
88 switch (direction) {
89 case Call::Direction::INCOMING:
90 if (missed)
91 render_direction = g_strdup_printf("<span fgcolor=\"red\" font=\"monospace\">&#8601;</span>");
92 else
93 render_direction = g_strdup_printf("<span fgcolor=\"green\" font=\"monospace\">&#8601;</span>");
94 break;
95 case Call::Direction::OUTGOING:
96 if (missed)
97 render_direction = g_strdup_printf("<span fgcolor=\"red\" font=\"monospace\">&#8599;</span>");
98 else
99 render_direction = g_strdup_printf("<span fgcolor=\"green\" font=\"monospace\">&#8599;</span>");
100 break;
101 }
102 }
103 g_object_set(G_OBJECT(cell), "markup", render_direction, NULL);
104 g_free(render_direction);
105}
106
107static void
108activate_history_item(GtkTreeView *tree_view,
109 GtkTreePath *path,
110 G_GNUC_UNUSED GtkTreeViewColumn *column,
111 G_GNUC_UNUSED gpointer user_data)
112{
113 GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
114
115 /* expand / collapse row */
116 if (gtk_tree_view_row_expanded(tree_view, path))
117 gtk_tree_view_collapse_row(tree_view, path);
118 else
119 gtk_tree_view_expand_row(tree_view, path, FALSE);
120
121 /* get iter */
122 GtkTreeIter iter;
123 if (gtk_tree_model_get_iter(model, &iter, path)) {
124 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(model), &iter);
125
126 QVariant contact_method = idx.data(static_cast<int>(Call::Role::ContactMethod));
127 /* create new call */
128 if (contact_method.value<ContactMethod*>()) {
129 place_new_call(contact_method.value<ContactMethod*>());
130 }
131 }
132}
133
134static void
135copy_history_item(G_GNUC_UNUSED GtkWidget *item, GtkTreeView *treeview)
136{
137 GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
138 QModelIndex idx = get_index_from_selection(selection);
139
140 if (idx.isValid()) {
141 GtkClipboard* clip = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
142
143 const gchar* number = idx.data(static_cast<int>(Call::Role::Number)).toString().toUtf8().constData();
144 gtk_clipboard_set_text(clip, number, -1);
145 }
146}
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");
157 * CategorizedHistoryModel::instance()->removeRow(idx.row(), idx.parent());
158 * }
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
166 * user should be able to copy the "number",
167 * 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
175 GtkWidget *menu = gtk_menu_new();
176
177 /* copy */
178 GtkWidget *item = gtk_menu_item_new_with_mnemonic("_Copy");
179 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
180 g_signal_connect(item, "activate", G_CALLBACK(copy_history_item), treeview);
181
182 /* TODO: delete history entry
183 * gtk_menu_shell_append(GTK_MENU_SHELL(menu), gtk_separator_menu_item_new());
184 * item = gtk_menu_item_new_with_mnemonic("_Delete entry");
185 * gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
186 * g_signal_connect(item, "activate", G_CALLBACK(delete_history_item), treeview);
187 */
188
189 /* show menu */
190 gtk_widget_show_all(menu);
191 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);
192
193 return FALSE; /* continue to default handler */
194}
195
196static void
197expand_if_child(G_GNUC_UNUSED GtkTreeModel *tree_model,
198 GtkTreePath *path,
199 G_GNUC_UNUSED GtkTreeIter *iter,
200 GtkTreeView *treeview)
201{
202 if (gtk_tree_path_get_depth(path) == 2)
203 gtk_tree_view_expand_to_path(treeview, path);
204}
205
206static void
207history_view_init(HistoryView *self)
208{
209 HistoryViewPrivate *priv = HISTORY_VIEW_GET_PRIVATE(self);
210
211 /* history view/model */
212 GtkWidget *treeview_history = gtk_tree_view_new();
213 /* make headers visible to allow column resizing */
214 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview_history), TRUE);
215 gtk_container_add(GTK_CONTAINER(self), treeview_history);
216
217 /* sort the history in descending order by date */
218 priv->q_history_model = new QSortFilterProxyModel();
219 priv->q_history_model->setSourceModel(CategorizedHistoryModel::instance());
220 priv->q_history_model->setSortRole(static_cast<int>(Call::Role::Date));
221 priv->q_history_model->sort(0,Qt::DescendingOrder);
222
223 GtkQSortFilterTreeModel *history_model = gtk_q_sort_filter_tree_model_new(
224 priv->q_history_model,
225 5,
226 Qt::DisplayRole, G_TYPE_STRING,
227 Call::Role::Number, G_TYPE_STRING,
228 Call::Role::FormattedDate, G_TYPE_STRING,
229 Call::Role::Direction, G_TYPE_INT,
230 Call::Role::Missed, G_TYPE_BOOLEAN);
231 gtk_tree_view_set_model( GTK_TREE_VIEW(treeview_history), GTK_TREE_MODEL(history_model) );
232
233 /* name column, also used for call direction and fuzzy date for top level items */
234 GtkTreeViewColumn *column = gtk_tree_view_column_new();
235 gtk_tree_view_column_set_title(column, "Name");
236
237 /* call direction */
238 GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
239 gtk_tree_view_column_pack_start(column, renderer, FALSE);
240
241 /* display the call direction with arrows */
242 gtk_tree_view_column_set_cell_data_func(
243 column,
244 renderer,
245 (GtkTreeCellDataFunc)render_call_direction,
246 NULL,
247 NULL);
248
249 /* name or time category column */
250 renderer = gtk_cell_renderer_text_new();
251 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
252 gtk_tree_view_column_pack_start(column, renderer, FALSE);
253 gtk_tree_view_column_set_attributes(column, renderer, "text", 0, NULL);
254 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview_history), column);
255 gtk_tree_view_column_set_resizable(column, TRUE);
256
257 /* "number" column */
258 renderer = gtk_cell_renderer_text_new();
259 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
260 column = gtk_tree_view_column_new_with_attributes("Number", renderer, "text", 1, NULL);
261 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview_history), column);
262 gtk_tree_view_column_set_resizable(column, TRUE);
263
264 /* date column */
265 renderer = gtk_cell_renderer_text_new ();
266 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
267 column = gtk_tree_view_column_new_with_attributes ("Date", renderer, "text", 2, NULL);
268 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview_history), column);
269 gtk_tree_view_column_set_resizable(column, TRUE);
270
271 /* expand the first row, which should be the most recent calls */
272 gtk_tree_view_expand_row(GTK_TREE_VIEW(treeview_history),
273 gtk_tree_path_new_from_string("0"),
274 FALSE);
275
276 g_signal_connect(treeview_history, "row-activated", G_CALLBACK(activate_history_item), NULL);
277 g_signal_connect(treeview_history, "button-press-event", G_CALLBACK(history_popup_menu), treeview_history);
278 g_signal_connect(history_model, "row-inserted", G_CALLBACK(expand_if_child), treeview_history);
279
280 gtk_widget_show_all(GTK_WIDGET(self));
281}
282
283static void
284history_view_dispose(GObject *object)
285{
286 G_OBJECT_CLASS(history_view_parent_class)->dispose(object);
287}
288
289static void
290history_view_finalize(GObject *object)
291{
292 HistoryView *self = HISTORY_VIEW(object);
293 HistoryViewPrivate *priv = HISTORY_VIEW_GET_PRIVATE(self);
294
295 delete priv->q_history_model;
296
297 G_OBJECT_CLASS(history_view_parent_class)->finalize(object);
298}
299
300static void
301history_view_class_init(HistoryViewClass *klass)
302{
303 G_OBJECT_CLASS(klass)->finalize = history_view_finalize;
304 G_OBJECT_CLASS(klass)->dispose = history_view_dispose;
305}
306
307GtkWidget *
308history_view_new()
309{
310 gpointer self = g_object_new(HISTORY_VIEW_TYPE, NULL);
311
312 return (GtkWidget *)self;
313}