blob: 5ec891da683297c028bfb2a8b0300b954e49117d [file] [log] [blame]
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -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 "callsview.h"
32
33#include <gtk/gtk.h>
34#include "models/gtkqtreemodel.h"
35#include <callmodel.h>
36#include <QtCore/QItemSelectionModel>
37#include "utils/models.h"
Stepan Salenikovich7dfd07c2015-05-13 14:18:51 -040038#include "delegates/pixbufdelegate.h"
39#include <QtCore/QSize>
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -040040
41struct _CallsView
42{
Stepan Salenikovich7be4f622015-05-13 15:36:19 -040043 GtkRevealer parent;
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -040044};
45
46struct _CallsViewClass
47{
Stepan Salenikovich7be4f622015-05-13 15:36:19 -040048 GtkRevealerClass parent_class;
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -040049};
50
51typedef struct _CallsViewPrivate CallsViewPrivate;
52
53struct _CallsViewPrivate
54{
55 GtkWidget *treeview_calls;
56 QMetaObject::Connection selection_updated;
Stepan Salenikovich7be4f622015-05-13 15:36:19 -040057 QMetaObject::Connection calls_added;
58 QMetaObject::Connection calls_removed;
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -040059};
60
Stepan Salenikovich7be4f622015-05-13 15:36:19 -040061G_DEFINE_TYPE_WITH_PRIVATE(CallsView, calls_view, GTK_TYPE_REVEALER);
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -040062
63#define CALLS_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CALLS_VIEW_TYPE, CallsViewPrivate))
64
65static void
66update_call_model_selection(GtkTreeSelection *selection, G_GNUC_UNUSED gpointer user_data)
67{
68 QModelIndex current = get_index_from_selection(selection);
Stepan Salenikovich744238e2015-07-07 12:55:34 -040069 if (current.isValid()) {
70
71 /* if the call is on hold, we want to put it off hold automatically
72 * when switching to it */
73 auto call = CallModel::instance()->getCall(current);
74 if (call->state() == Call::State::HOLD)
75 call << Call::Action::HOLD;
76
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -040077 CallModel::instance()->selectionModel()->setCurrentIndex(current, QItemSelectionModel::ClearAndSelect);
Stepan Salenikovich744238e2015-07-07 12:55:34 -040078 } else {
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -040079 CallModel::instance()->selectionModel()->clearCurrentIndex();
Stepan Salenikovich744238e2015-07-07 12:55:34 -040080 }
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -040081}
82
83static void
Stepan Salenikovich7dfd07c2015-05-13 14:18:51 -040084render_call_photo(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
85 GtkCellRenderer *cell,
86 GtkTreeModel *tree_model,
87 GtkTreeIter *iter,
88 G_GNUC_UNUSED gpointer data)
89{
90 /* get call */
91 QModelIndex idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(tree_model), iter);
92 if (idx.isValid()) {
93 QVariant var_c = idx.data(static_cast<int>(Call::Role::Object));
94 Call *c = var_c.value<Call *>();
95
96 /* we only want to show the photo once the call is past creation
97 * since before then we likely don't know the contact yet anyways */
98 if (c->lifeCycleState() != Call::LifeCycleState::CREATION) {
99 /* get photo */
100 QVariant var_p = PixbufDelegate::instance()->callPhoto(c, QSize(50, 50), false);
101 std::shared_ptr<GdkPixbuf> photo = var_p.value<std::shared_ptr<GdkPixbuf>>();
102 g_object_set(G_OBJECT(cell), "pixbuf", photo.get(), NULL);
103 return;
104 }
105 }
106
107 /* otherwise, make sure its an empty pixbuf */
108 g_object_set(G_OBJECT(cell), "pixbuf", NULL, NULL);
109}
110
111static void
112render_name_and_contact_method(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
113 GtkCellRenderer *cell,
114 GtkTreeModel *tree_model,
115 GtkTreeIter *iter,
116 G_GNUC_UNUSED gpointer data)
117{
118 gchar *text = NULL;
119 QModelIndex idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(tree_model), iter);
120 if (idx.isValid()) {
121 QVariant state = idx.data(static_cast<int>(Call::Role::LifeCycleState));
122 QVariant name = idx.data(static_cast<int>(Call::Role::Name));
123 QVariant number = idx.data(static_cast<int>(Call::Role::Number));
124
125 /* only show the number being entered while in creation state */
126 if (state.value<Call::LifeCycleState>() == Call::LifeCycleState::CREATION) {
127 text = g_strdup_printf("%s", number.value<QString>().toUtf8().constData());
128 } else {
129 text = g_strdup_printf("%s\n <span fgcolor=\"gray\">%s</span>",
130 name.value<QString>().toUtf8().constData(),
131 number.value<QString>().toUtf8().constData());
132 }
133 }
134
135 g_object_set(G_OBJECT(cell), "markup", text, NULL);
136 g_free(text);
137}
138
139static void
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400140calls_view_init(CallsView *self)
141{
142 CallsViewPrivate *priv = CALLS_VIEW_GET_PRIVATE(self);
143
Stepan Salenikovich7be4f622015-05-13 15:36:19 -0400144 /* hide if there are no calls */
145 gtk_revealer_set_reveal_child(GTK_REVEALER(self),
146 CallModel::instance()->rowCount());
147 priv->calls_added = QObject::connect(
148 CallModel::instance(),
149 &QAbstractItemModel::rowsInserted,
150 [=] (G_GNUC_UNUSED const QModelIndex &parent,
151 G_GNUC_UNUSED int first,
152 G_GNUC_UNUSED int last)
153 {
154 gtk_revealer_set_reveal_child(GTK_REVEALER(self),
155 CallModel::instance()->rowCount());
156 }
157 );
158
159 priv->calls_removed = QObject::connect(
160 CallModel::instance(),
161 &QAbstractItemModel::rowsRemoved,
162 [=] (G_GNUC_UNUSED const QModelIndex &parent,
163 G_GNUC_UNUSED int first,
164 G_GNUC_UNUSED int last)
165 {
166 gtk_revealer_set_reveal_child(GTK_REVEALER(self),
167 CallModel::instance()->rowCount());
168 }
169 );
170
171 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
172 gtk_widget_set_margin_bottom(box, 10);
173 gtk_container_add(GTK_CONTAINER(self), box);
174
175 /* current calls label */
176 GtkWidget *label = gtk_label_new("Current Calls");
177 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 10);
178
179 GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
180 gtk_box_pack_start(GTK_BOX(box), scrolled_window, FALSE, TRUE, 0);
181
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400182 /* disable vertical scroll... we always want all the calls to be visible */
Stepan Salenikovich7be4f622015-05-13 15:36:19 -0400183 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
184 GTK_POLICY_NEVER,
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400185 GTK_POLICY_NEVER);
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400186
187 priv->treeview_calls = gtk_tree_view_new();
188 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(priv->treeview_calls), FALSE);
189 /* disable default search, we will handle it ourselves via LRC;
190 * otherwise the search steals input focus on key presses */
191 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(priv->treeview_calls), FALSE);
Stepan Salenikovich7dfd07c2015-05-13 14:18:51 -0400192 gtk_tree_view_set_show_expanders(GTK_TREE_VIEW(priv->treeview_calls), FALSE);
Stepan Salenikovich7be4f622015-05-13 15:36:19 -0400193 gtk_container_add(GTK_CONTAINER(scrolled_window), priv->treeview_calls);
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400194
195 /* call model */
196 GtkQTreeModel *call_model;
197 GtkCellRenderer *renderer;
198 GtkTreeViewColumn *column;
199
200 call_model = gtk_q_tree_model_new(
201 CallModel::instance(),
202 4,
203 Call::Role::Name, G_TYPE_STRING,
204 Call::Role::Number, G_TYPE_STRING,
205 Call::Role::Length, G_TYPE_STRING,
206 Call::Role::State, G_TYPE_STRING);
207 gtk_tree_view_set_model(GTK_TREE_VIEW(priv->treeview_calls), GTK_TREE_MODEL(call_model));
208
Stepan Salenikovich7dfd07c2015-05-13 14:18:51 -0400209 /* call photo, name/number column */
210 GtkCellArea *area = gtk_cell_area_box_new();
211 column = gtk_tree_view_column_new_with_area(area);
212 gtk_tree_view_column_set_title(column, "Call");
213
214 /* photo renderer */
215 renderer = gtk_cell_renderer_pixbuf_new();
216 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
217
218 /* get the photo */
219 gtk_tree_view_column_set_cell_data_func(
220 column,
221 renderer,
222 (GtkTreeCellDataFunc)render_call_photo,
223 NULL,
224 NULL);
225
226 /* name and contact method renderer */
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400227 renderer = gtk_cell_renderer_text_new();
228 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovich7dfd07c2015-05-13 14:18:51 -0400229 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
230
231 gtk_tree_view_column_set_cell_data_func(
232 column,
233 renderer,
234 (GtkTreeCellDataFunc)render_name_and_contact_method,
235 NULL,
236 NULL);
237
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400238 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_calls), column);
Stepan Salenikovich7dfd07c2015-05-13 14:18:51 -0400239 gtk_tree_view_column_set_resizable(column, TRUE);
Stepan Salenikoviche7c4e282015-06-11 17:22:08 -0400240 gtk_tree_view_column_set_expand(column, TRUE);
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400241
242 renderer = gtk_cell_renderer_text_new();
243 column = gtk_tree_view_column_new_with_attributes("Duration", renderer, "text", 2, NULL);
244 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
245 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_calls), column);
Stepan Salenikoviche7c4e282015-06-11 17:22:08 -0400246 gtk_tree_view_column_set_expand(column, FALSE);
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400247
248 /* connect signals to and from the slection model of the call model */
249 priv->selection_updated = QObject::connect(
250 CallModel::instance()->selectionModel(),
251 &QItemSelectionModel::currentChanged,
Stepan Salenikovich8e882cd2015-05-28 14:18:06 -0400252 [=](const QModelIndex current, const QModelIndex & previous) {
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400253 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_calls));
254
255 /* first unselect the previous */
256 if (previous.isValid()) {
257 GtkTreeIter old_iter;
258 if (gtk_q_tree_model_source_index_to_iter(call_model, previous, &old_iter)) {
259 gtk_tree_selection_unselect_iter(selection, &old_iter);
260 } else {
261 g_warning("Trying to unselect invalid GtkTreeIter");
262 }
263 }
264
265 /* select the current */
266 if (current.isValid()) {
267 GtkTreeIter new_iter;
268 if (gtk_q_tree_model_source_index_to_iter(call_model, current, &new_iter)) {
269 gtk_tree_selection_select_iter(selection, &new_iter);
270 } else {
271 g_warning("SelectionModel of CallModel changed to invalid QModelIndex?");
272 }
273 }
274 }
275 );
276
277 GtkTreeSelection *call_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_calls));
278 g_signal_connect(call_selection, "changed", G_CALLBACK(update_call_model_selection), NULL);
279
280 gtk_widget_show_all(GTK_WIDGET(self));
281}
282
283static void
284calls_view_dispose(GObject *object)
285{
286 CallsView *self = CALLS_VIEW(object);
287 CallsViewPrivate *priv = CALLS_VIEW_GET_PRIVATE(self);
288
289 QObject::disconnect(priv->selection_updated);
Stepan Salenikovich7be4f622015-05-13 15:36:19 -0400290 QObject::disconnect(priv->calls_added);
291 QObject::disconnect(priv->calls_removed);
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400292
293 G_OBJECT_CLASS(calls_view_parent_class)->dispose(object);
294}
295
296static void
297calls_view_finalize(GObject *object)
298{
299 G_OBJECT_CLASS(calls_view_parent_class)->finalize(object);
300}
301
302static void
303calls_view_class_init(CallsViewClass *klass)
304{
305 G_OBJECT_CLASS(klass)->finalize = calls_view_finalize;
306 G_OBJECT_CLASS(klass)->dispose = calls_view_dispose;
307}
308
309GtkWidget *
310calls_view_new()
311{
312 gpointer self = g_object_new(CALLS_VIEW_TYPE, NULL);
313
314 return (GtkWidget *)self;
315}
316
317GtkTreeSelection *
318calls_view_get_selection(CallsView *calls_view)
319{
320 g_return_val_if_fail(IS_CALLS_VIEW(calls_view), NULL);
321 CallsViewPrivate *priv = CALLS_VIEW_GET_PRIVATE(calls_view);
322
323 return gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_calls));
Stepan Salenikovich8e882cd2015-05-28 14:18:06 -0400324}