blob: c1a298fac8154ca5810f591ab6f7908745511be4 [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
Stepan Salenikovichf8b76572015-07-08 18:15:32 -040071 /* make sure we don't call HOLD more than once on the same index, by
72 * checking which one is currently selected */
73 auto current_selection = CallModel::instance()->selectionModel()->currentIndex();
74 if (current != current_selection) {
75 /* if the call is on hold, we want to put it off hold automatically
76 * when switching to it */
77 auto call = CallModel::instance()->getCall(current);
78 if (call->state() == Call::State::HOLD) {
79 call << Call::Action::HOLD;
80 }
Stepan Salenikovich744238e2015-07-07 12:55:34 -040081
Stepan Salenikovichf8b76572015-07-08 18:15:32 -040082 CallModel::instance()->selectionModel()->setCurrentIndex(current, QItemSelectionModel::ClearAndSelect);
83 }
Stepan Salenikovich744238e2015-07-07 12:55:34 -040084 } else {
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -040085 CallModel::instance()->selectionModel()->clearCurrentIndex();
Stepan Salenikovich744238e2015-07-07 12:55:34 -040086 }
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -040087}
88
89static void
Stepan Salenikovich7dfd07c2015-05-13 14:18:51 -040090render_call_photo(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
91 GtkCellRenderer *cell,
92 GtkTreeModel *tree_model,
93 GtkTreeIter *iter,
94 G_GNUC_UNUSED gpointer data)
95{
96 /* get call */
97 QModelIndex idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(tree_model), iter);
98 if (idx.isValid()) {
99 QVariant var_c = idx.data(static_cast<int>(Call::Role::Object));
100 Call *c = var_c.value<Call *>();
101
102 /* we only want to show the photo once the call is past creation
103 * since before then we likely don't know the contact yet anyways */
104 if (c->lifeCycleState() != Call::LifeCycleState::CREATION) {
105 /* get photo */
106 QVariant var_p = PixbufDelegate::instance()->callPhoto(c, QSize(50, 50), false);
107 std::shared_ptr<GdkPixbuf> photo = var_p.value<std::shared_ptr<GdkPixbuf>>();
108 g_object_set(G_OBJECT(cell), "pixbuf", photo.get(), NULL);
109 return;
110 }
111 }
112
113 /* otherwise, make sure its an empty pixbuf */
114 g_object_set(G_OBJECT(cell), "pixbuf", NULL, NULL);
115}
116
117static void
118render_name_and_contact_method(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
119 GtkCellRenderer *cell,
120 GtkTreeModel *tree_model,
121 GtkTreeIter *iter,
122 G_GNUC_UNUSED gpointer data)
123{
124 gchar *text = NULL;
125 QModelIndex idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(tree_model), iter);
126 if (idx.isValid()) {
127 QVariant state = idx.data(static_cast<int>(Call::Role::LifeCycleState));
128 QVariant name = idx.data(static_cast<int>(Call::Role::Name));
129 QVariant number = idx.data(static_cast<int>(Call::Role::Number));
130
131 /* only show the number being entered while in creation state */
132 if (state.value<Call::LifeCycleState>() == Call::LifeCycleState::CREATION) {
133 text = g_strdup_printf("%s", number.value<QString>().toUtf8().constData());
134 } else {
135 text = g_strdup_printf("%s\n <span fgcolor=\"gray\">%s</span>",
136 name.value<QString>().toUtf8().constData(),
137 number.value<QString>().toUtf8().constData());
138 }
139 }
140
141 g_object_set(G_OBJECT(cell), "markup", text, NULL);
142 g_free(text);
143}
144
145static void
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400146calls_view_init(CallsView *self)
147{
148 CallsViewPrivate *priv = CALLS_VIEW_GET_PRIVATE(self);
149
Stepan Salenikovich7be4f622015-05-13 15:36:19 -0400150 /* hide if there are no calls */
151 gtk_revealer_set_reveal_child(GTK_REVEALER(self),
152 CallModel::instance()->rowCount());
153 priv->calls_added = QObject::connect(
154 CallModel::instance(),
155 &QAbstractItemModel::rowsInserted,
156 [=] (G_GNUC_UNUSED const QModelIndex &parent,
157 G_GNUC_UNUSED int first,
158 G_GNUC_UNUSED int last)
159 {
160 gtk_revealer_set_reveal_child(GTK_REVEALER(self),
161 CallModel::instance()->rowCount());
162 }
163 );
164
165 priv->calls_removed = QObject::connect(
166 CallModel::instance(),
167 &QAbstractItemModel::rowsRemoved,
168 [=] (G_GNUC_UNUSED const QModelIndex &parent,
169 G_GNUC_UNUSED int first,
170 G_GNUC_UNUSED int last)
171 {
172 gtk_revealer_set_reveal_child(GTK_REVEALER(self),
173 CallModel::instance()->rowCount());
174 }
175 );
176
177 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
178 gtk_widget_set_margin_bottom(box, 10);
179 gtk_container_add(GTK_CONTAINER(self), box);
180
181 /* current calls label */
182 GtkWidget *label = gtk_label_new("Current Calls");
183 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 10);
184
185 GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
186 gtk_box_pack_start(GTK_BOX(box), scrolled_window, FALSE, TRUE, 0);
187
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400188 /* disable vertical scroll... we always want all the calls to be visible */
Stepan Salenikovich7be4f622015-05-13 15:36:19 -0400189 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
190 GTK_POLICY_NEVER,
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400191 GTK_POLICY_NEVER);
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400192
193 priv->treeview_calls = gtk_tree_view_new();
194 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(priv->treeview_calls), FALSE);
195 /* disable default search, we will handle it ourselves via LRC;
196 * otherwise the search steals input focus on key presses */
197 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(priv->treeview_calls), FALSE);
Stepan Salenikovich7dfd07c2015-05-13 14:18:51 -0400198 gtk_tree_view_set_show_expanders(GTK_TREE_VIEW(priv->treeview_calls), FALSE);
Stepan Salenikovich7be4f622015-05-13 15:36:19 -0400199 gtk_container_add(GTK_CONTAINER(scrolled_window), priv->treeview_calls);
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400200
201 /* call model */
202 GtkQTreeModel *call_model;
203 GtkCellRenderer *renderer;
204 GtkTreeViewColumn *column;
205
206 call_model = gtk_q_tree_model_new(
207 CallModel::instance(),
208 4,
209 Call::Role::Name, G_TYPE_STRING,
210 Call::Role::Number, G_TYPE_STRING,
211 Call::Role::Length, G_TYPE_STRING,
212 Call::Role::State, G_TYPE_STRING);
213 gtk_tree_view_set_model(GTK_TREE_VIEW(priv->treeview_calls), GTK_TREE_MODEL(call_model));
214
Stepan Salenikovich7dfd07c2015-05-13 14:18:51 -0400215 /* call photo, name/number column */
216 GtkCellArea *area = gtk_cell_area_box_new();
217 column = gtk_tree_view_column_new_with_area(area);
218 gtk_tree_view_column_set_title(column, "Call");
219
220 /* photo renderer */
221 renderer = gtk_cell_renderer_pixbuf_new();
222 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
223
224 /* get the photo */
225 gtk_tree_view_column_set_cell_data_func(
226 column,
227 renderer,
228 (GtkTreeCellDataFunc)render_call_photo,
229 NULL,
230 NULL);
231
232 /* name and contact method renderer */
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400233 renderer = gtk_cell_renderer_text_new();
234 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovich7dfd07c2015-05-13 14:18:51 -0400235 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
236
237 gtk_tree_view_column_set_cell_data_func(
238 column,
239 renderer,
240 (GtkTreeCellDataFunc)render_name_and_contact_method,
241 NULL,
242 NULL);
243
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400244 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_calls), column);
Stepan Salenikovich7dfd07c2015-05-13 14:18:51 -0400245 gtk_tree_view_column_set_resizable(column, TRUE);
Stepan Salenikoviche7c4e282015-06-11 17:22:08 -0400246 gtk_tree_view_column_set_expand(column, TRUE);
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400247
248 renderer = gtk_cell_renderer_text_new();
249 column = gtk_tree_view_column_new_with_attributes("Duration", renderer, "text", 2, NULL);
250 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
251 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_calls), column);
Stepan Salenikoviche7c4e282015-06-11 17:22:08 -0400252 gtk_tree_view_column_set_expand(column, FALSE);
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400253
254 /* connect signals to and from the slection model of the call model */
255 priv->selection_updated = QObject::connect(
256 CallModel::instance()->selectionModel(),
257 &QItemSelectionModel::currentChanged,
Stepan Salenikovich8e882cd2015-05-28 14:18:06 -0400258 [=](const QModelIndex current, const QModelIndex & previous) {
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400259 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_calls));
260
261 /* first unselect the previous */
262 if (previous.isValid()) {
263 GtkTreeIter old_iter;
264 if (gtk_q_tree_model_source_index_to_iter(call_model, previous, &old_iter)) {
265 gtk_tree_selection_unselect_iter(selection, &old_iter);
266 } else {
267 g_warning("Trying to unselect invalid GtkTreeIter");
268 }
269 }
270
271 /* select the current */
272 if (current.isValid()) {
273 GtkTreeIter new_iter;
274 if (gtk_q_tree_model_source_index_to_iter(call_model, current, &new_iter)) {
275 gtk_tree_selection_select_iter(selection, &new_iter);
276 } else {
277 g_warning("SelectionModel of CallModel changed to invalid QModelIndex?");
278 }
279 }
280 }
281 );
282
283 GtkTreeSelection *call_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_calls));
284 g_signal_connect(call_selection, "changed", G_CALLBACK(update_call_model_selection), NULL);
285
286 gtk_widget_show_all(GTK_WIDGET(self));
287}
288
289static void
290calls_view_dispose(GObject *object)
291{
292 CallsView *self = CALLS_VIEW(object);
293 CallsViewPrivate *priv = CALLS_VIEW_GET_PRIVATE(self);
294
295 QObject::disconnect(priv->selection_updated);
Stepan Salenikovich7be4f622015-05-13 15:36:19 -0400296 QObject::disconnect(priv->calls_added);
297 QObject::disconnect(priv->calls_removed);
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400298
299 G_OBJECT_CLASS(calls_view_parent_class)->dispose(object);
300}
301
302static void
303calls_view_finalize(GObject *object)
304{
305 G_OBJECT_CLASS(calls_view_parent_class)->finalize(object);
306}
307
308static void
309calls_view_class_init(CallsViewClass *klass)
310{
311 G_OBJECT_CLASS(klass)->finalize = calls_view_finalize;
312 G_OBJECT_CLASS(klass)->dispose = calls_view_dispose;
313}
314
315GtkWidget *
316calls_view_new()
317{
318 gpointer self = g_object_new(CALLS_VIEW_TYPE, NULL);
319
320 return (GtkWidget *)self;
321}
322
323GtkTreeSelection *
324calls_view_get_selection(CallsView *calls_view)
325{
326 g_return_val_if_fail(IS_CALLS_VIEW(calls_view), NULL);
327 CallsViewPrivate *priv = CALLS_VIEW_GET_PRIVATE(calls_view);
328
329 return gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_calls));
Stepan Salenikovich8e882cd2015-05-28 14:18:06 -0400330}