blob: ff7184b6bde909fafa7c3bc0ad4938a35a2d0622 [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);
69 if (current.isValid())
70 CallModel::instance()->selectionModel()->setCurrentIndex(current, QItemSelectionModel::ClearAndSelect);
71 else
72 CallModel::instance()->selectionModel()->clearCurrentIndex();
73}
74
75static void
Stepan Salenikovich7dfd07c2015-05-13 14:18:51 -040076render_call_photo(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
77 GtkCellRenderer *cell,
78 GtkTreeModel *tree_model,
79 GtkTreeIter *iter,
80 G_GNUC_UNUSED gpointer data)
81{
82 /* get call */
83 QModelIndex idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(tree_model), iter);
84 if (idx.isValid()) {
85 QVariant var_c = idx.data(static_cast<int>(Call::Role::Object));
86 Call *c = var_c.value<Call *>();
87
88 /* we only want to show the photo once the call is past creation
89 * since before then we likely don't know the contact yet anyways */
90 if (c->lifeCycleState() != Call::LifeCycleState::CREATION) {
91 /* get photo */
92 QVariant var_p = PixbufDelegate::instance()->callPhoto(c, QSize(50, 50), false);
93 std::shared_ptr<GdkPixbuf> photo = var_p.value<std::shared_ptr<GdkPixbuf>>();
94 g_object_set(G_OBJECT(cell), "pixbuf", photo.get(), NULL);
95 return;
96 }
97 }
98
99 /* otherwise, make sure its an empty pixbuf */
100 g_object_set(G_OBJECT(cell), "pixbuf", NULL, NULL);
101}
102
103static void
104render_name_and_contact_method(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
105 GtkCellRenderer *cell,
106 GtkTreeModel *tree_model,
107 GtkTreeIter *iter,
108 G_GNUC_UNUSED gpointer data)
109{
110 gchar *text = NULL;
111 QModelIndex idx = gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(tree_model), iter);
112 if (idx.isValid()) {
113 QVariant state = idx.data(static_cast<int>(Call::Role::LifeCycleState));
114 QVariant name = idx.data(static_cast<int>(Call::Role::Name));
115 QVariant number = idx.data(static_cast<int>(Call::Role::Number));
116
117 /* only show the number being entered while in creation state */
118 if (state.value<Call::LifeCycleState>() == Call::LifeCycleState::CREATION) {
119 text = g_strdup_printf("%s", number.value<QString>().toUtf8().constData());
120 } else {
121 text = g_strdup_printf("%s\n <span fgcolor=\"gray\">%s</span>",
122 name.value<QString>().toUtf8().constData(),
123 number.value<QString>().toUtf8().constData());
124 }
125 }
126
127 g_object_set(G_OBJECT(cell), "markup", text, NULL);
128 g_free(text);
129}
130
131static void
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400132calls_view_init(CallsView *self)
133{
134 CallsViewPrivate *priv = CALLS_VIEW_GET_PRIVATE(self);
135
Stepan Salenikovich7be4f622015-05-13 15:36:19 -0400136 /* hide if there are no calls */
137 gtk_revealer_set_reveal_child(GTK_REVEALER(self),
138 CallModel::instance()->rowCount());
139 priv->calls_added = QObject::connect(
140 CallModel::instance(),
141 &QAbstractItemModel::rowsInserted,
142 [=] (G_GNUC_UNUSED const QModelIndex &parent,
143 G_GNUC_UNUSED int first,
144 G_GNUC_UNUSED int last)
145 {
146 gtk_revealer_set_reveal_child(GTK_REVEALER(self),
147 CallModel::instance()->rowCount());
148 }
149 );
150
151 priv->calls_removed = QObject::connect(
152 CallModel::instance(),
153 &QAbstractItemModel::rowsRemoved,
154 [=] (G_GNUC_UNUSED const QModelIndex &parent,
155 G_GNUC_UNUSED int first,
156 G_GNUC_UNUSED int last)
157 {
158 gtk_revealer_set_reveal_child(GTK_REVEALER(self),
159 CallModel::instance()->rowCount());
160 }
161 );
162
163 GtkWidget *box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
164 gtk_widget_set_margin_bottom(box, 10);
165 gtk_container_add(GTK_CONTAINER(self), box);
166
167 /* current calls label */
168 GtkWidget *label = gtk_label_new("Current Calls");
169 gtk_box_pack_start(GTK_BOX(box), label, FALSE, TRUE, 10);
170
171 GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
172 gtk_box_pack_start(GTK_BOX(box), scrolled_window, FALSE, TRUE, 0);
173
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400174 /* disable vertical scroll... we always want all the calls to be visible */
Stepan Salenikovich7be4f622015-05-13 15:36:19 -0400175 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
176 GTK_POLICY_NEVER,
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400177 GTK_POLICY_NEVER);
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400178
179 priv->treeview_calls = gtk_tree_view_new();
180 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(priv->treeview_calls), FALSE);
181 /* disable default search, we will handle it ourselves via LRC;
182 * otherwise the search steals input focus on key presses */
183 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(priv->treeview_calls), FALSE);
Stepan Salenikovich7dfd07c2015-05-13 14:18:51 -0400184 gtk_tree_view_set_show_expanders(GTK_TREE_VIEW(priv->treeview_calls), FALSE);
Stepan Salenikovich7be4f622015-05-13 15:36:19 -0400185 gtk_container_add(GTK_CONTAINER(scrolled_window), priv->treeview_calls);
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400186
187 /* call model */
188 GtkQTreeModel *call_model;
189 GtkCellRenderer *renderer;
190 GtkTreeViewColumn *column;
191
192 call_model = gtk_q_tree_model_new(
193 CallModel::instance(),
194 4,
195 Call::Role::Name, G_TYPE_STRING,
196 Call::Role::Number, G_TYPE_STRING,
197 Call::Role::Length, G_TYPE_STRING,
198 Call::Role::State, G_TYPE_STRING);
199 gtk_tree_view_set_model(GTK_TREE_VIEW(priv->treeview_calls), GTK_TREE_MODEL(call_model));
200
Stepan Salenikovich7dfd07c2015-05-13 14:18:51 -0400201 /* call photo, name/number column */
202 GtkCellArea *area = gtk_cell_area_box_new();
203 column = gtk_tree_view_column_new_with_area(area);
204 gtk_tree_view_column_set_title(column, "Call");
205
206 /* photo renderer */
207 renderer = gtk_cell_renderer_pixbuf_new();
208 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
209
210 /* get the photo */
211 gtk_tree_view_column_set_cell_data_func(
212 column,
213 renderer,
214 (GtkTreeCellDataFunc)render_call_photo,
215 NULL,
216 NULL);
217
218 /* name and contact method renderer */
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400219 renderer = gtk_cell_renderer_text_new();
220 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
Stepan Salenikovich7dfd07c2015-05-13 14:18:51 -0400221 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
222
223 gtk_tree_view_column_set_cell_data_func(
224 column,
225 renderer,
226 (GtkTreeCellDataFunc)render_name_and_contact_method,
227 NULL,
228 NULL);
229
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400230 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_calls), column);
Stepan Salenikovich7dfd07c2015-05-13 14:18:51 -0400231 gtk_tree_view_column_set_resizable(column, TRUE);
Stepan Salenikoviche7c4e282015-06-11 17:22:08 -0400232 gtk_tree_view_column_set_expand(column, TRUE);
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400233
234 renderer = gtk_cell_renderer_text_new();
235 column = gtk_tree_view_column_new_with_attributes("Duration", renderer, "text", 2, NULL);
236 gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
237 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_calls), column);
Stepan Salenikoviche7c4e282015-06-11 17:22:08 -0400238 gtk_tree_view_column_set_expand(column, FALSE);
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400239
240 /* connect signals to and from the slection model of the call model */
241 priv->selection_updated = QObject::connect(
242 CallModel::instance()->selectionModel(),
243 &QItemSelectionModel::currentChanged,
Stepan Salenikovich8e882cd2015-05-28 14:18:06 -0400244 [=](const QModelIndex current, const QModelIndex & previous) {
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400245 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_calls));
246
247 /* first unselect the previous */
248 if (previous.isValid()) {
249 GtkTreeIter old_iter;
250 if (gtk_q_tree_model_source_index_to_iter(call_model, previous, &old_iter)) {
251 gtk_tree_selection_unselect_iter(selection, &old_iter);
252 } else {
253 g_warning("Trying to unselect invalid GtkTreeIter");
254 }
255 }
256
257 /* select the current */
258 if (current.isValid()) {
259 GtkTreeIter new_iter;
260 if (gtk_q_tree_model_source_index_to_iter(call_model, current, &new_iter)) {
261 gtk_tree_selection_select_iter(selection, &new_iter);
262 } else {
263 g_warning("SelectionModel of CallModel changed to invalid QModelIndex?");
264 }
265 }
Stepan Salenikovich14674952015-06-15 18:43:13 -0400266
267 /* if the call is on hold, we want to put it off hold automatically
268 * when switching to it */
269 if (current.isValid()) {
270 auto call = CallModel::instance()->getCall(current);
271 if (call->state() == Call::State::HOLD)
272 call << Call::Action::HOLD;
273 }
Stepan Salenikovichbbb10d82015-05-13 12:26:44 -0400274 }
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}