blob: 0f00fcdf4ca6964fa4b0c577f198b9df2d071236 [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 "contactsview.h"
32
33#include <gtk/gtk.h>
34#include "models/gtkqsortfiltertreemodel.h"
35#include "models/activeitemproxymodel.h"
36#include <categorizedcontactmodel.h>
37#include <personmodel.h>
38#include "utils/calling.h"
39#include <memory>
40#include "delegates/pixbufdelegate.h"
41#include <contactmethod.h>
42
43struct _ContactsView
44{
45 GtkScrolledWindow parent;
46};
47
48struct _ContactsViewClass
49{
50 GtkScrolledWindowClass parent_class;
51};
52
53typedef struct _ContactsViewPrivate ContactsViewPrivate;
54
55struct _ContactsViewPrivate
56{
57 ActiveItemProxyModel *q_contact_model;
58};
59
60G_DEFINE_TYPE_WITH_PRIVATE(ContactsView, contacts_view, GTK_TYPE_SCROLLED_WINDOW);
61
62#define CONTACTS_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CONTACTS_VIEW_TYPE, ContactsViewPrivate))
63
64static void
65render_contact_photo(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
66 GtkCellRenderer *cell,
67 GtkTreeModel *tree_model,
68 GtkTreeIter *iter,
69 G_GNUC_UNUSED gpointer data)
70{
71 /* check if this is a top level item (category),
72 * or a bottom level item (contact method)gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), &iter);
73 * in this case we don't want to show a photo */
74 GtkTreePath *path = gtk_tree_model_get_path(tree_model, iter);
75 int depth = gtk_tree_path_get_depth(path);
76 gtk_tree_path_free(path);
77 if (depth == 2) {
78 /* get person */
79 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(tree_model), iter);
80 if (idx.isValid()) {
81 QVariant var_c = idx.data(static_cast<int>(Person::Role::Object));
82 Person *c = var_c.value<Person *>();
83 /* get photo */
84 QVariant var_p = PixbufDelegate::instance()->contactPhoto(c, QSize(50, 50), false);
85 std::shared_ptr<GdkPixbuf> photo = var_p.value<std::shared_ptr<GdkPixbuf>>();
86 g_object_set(G_OBJECT(cell), "pixbuf", photo.get(), NULL);
87 return;
88 }
89 }
90
91 /* otherwise, make sure its an empty pixbuf */
92 g_object_set(G_OBJECT(cell), "pixbuf", NULL, NULL);
93}
94
95static void
96render_name_and_contact_method(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
97 GtkCellRenderer *cell,
98 GtkTreeModel *tree_model,
99 GtkTreeIter *iter,
100 G_GNUC_UNUSED gpointer data)
101{
102 /**
103 * If contact (person), show the name and the contact method (number)
104 * underneath; if multiple contact methods, then indicate as such
105 *
106 * Otherwise just display the category or contact method
107 */
108 GtkTreePath *path = gtk_tree_model_get_path(tree_model, iter);
109 int depth = gtk_tree_path_get_depth(path);
110 gtk_tree_path_free(path);
111
112 gchar *text = NULL;
113
114 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(tree_model), iter);
115 if (idx.isValid()) {
116 QVariant var = idx.data(Qt::DisplayRole);
117 if (depth == 1) {
118 /* category */
119 text = g_strdup_printf("<b>%s</b>", var.value<QString>().toUtf8().constData());
120 } else if (depth == 2) {
121 /* contact, check for contact methods */
122 QVariant var_c = idx.data(static_cast<int>(Person::Role::Object));
123 if (var_c.isValid()) {
124 Person *c = var_c.value<Person *>();
125 switch (c->phoneNumbers().size()) {
126 case 0:
127 text = g_strdup_printf("%s\n", c->formattedName().toUtf8().constData());
128 break;
129 case 1:
130 {
131 QString number;
132 QVariant var_n = c->phoneNumbers().first()->roleData(Qt::DisplayRole);
133 if (var_n.isValid())
134 number = var_n.value<QString>();
135
136 text = g_strdup_printf("%s\n <span fgcolor=\"gray\">%s</span>",
137 c->formattedName().toUtf8().constData(),
138 number.toUtf8().constData());
139 break;
140 }
141 default:
142 /* more than one, for now don't show any of the contact methods */
143 text = g_strdup_printf("%s\n", c->formattedName().toUtf8().constData());
144 break;
145 }
146 } else {
147 /* should never happen since depth 2 should always be a contact (person) */
148 text = g_strdup_printf("%s", var.value<QString>().toUtf8().constData());
149 }
150 } else {
151 /* contact method (or deeper??) */
152 text = g_strdup_printf("%s", var.value<QString>().toUtf8().constData());
153 }
154 }
155
156 g_object_set(G_OBJECT(cell), "markup", text, NULL);
157 g_free(text);
158
159 /* set the colour */
160 if ( depth == 1) {
161 /* nice blue taken from the ring logo */
162 GdkRGBA rgba = {0.2, 0.75294117647, 0.82745098039, 0.1};
163 g_object_set(G_OBJECT(cell), "cell-background-rgba", &rgba, NULL);
164 } else {
165 g_object_set(G_OBJECT(cell), "cell-background", NULL, NULL);
166 }
167}
168
169static void
170expand_if_child(G_GNUC_UNUSED GtkTreeModel *tree_model,
171 GtkTreePath *path,
172 G_GNUC_UNUSED GtkTreeIter *iter,
173 GtkTreeView *treeview)
174{
175 if (gtk_tree_path_get_depth(path) == 2)
176 gtk_tree_view_expand_to_path(treeview, path);
177}
178
179static void
180activate_contact_item(GtkTreeView *tree_view,
181 GtkTreePath *path,
182 G_GNUC_UNUSED GtkTreeViewColumn *column,
183 G_GNUC_UNUSED gpointer user_data)
184{
185 /* expand / contract row */
186 if (gtk_tree_view_row_expanded(tree_view, path))
187 gtk_tree_view_collapse_row(tree_view, path);
188 else
189 gtk_tree_view_expand_row(tree_view, path, FALSE);
190
191 GtkTreeModel *model = gtk_tree_view_get_model(tree_view);
192
193 /* get iter */
194 GtkTreeIter iter;
195 if (gtk_tree_model_get_iter(model, &iter, path)) {
196 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(model), &iter);
197 if (idx.isValid()) {
198 int depth = gtk_tree_path_get_depth(path);
199 switch (depth) {
200 case 0:
201 case 1:
202 /* category, nothing to do */
203 break;
204 case 2:
205 {
206 /* contact (person), use contact method if there is only one */
207 QVariant var_c = idx.data(static_cast<int>(Person::Role::Object));
208 if (var_c.isValid()) {
209 Person *c = var_c.value<Person *>();
210 if (c->phoneNumbers().size() == 1) {
211 /* call with contact method */
212 place_new_call(c->phoneNumbers().first());
213 }
214 }
215 break;
216 }
217 default:
218 {
219 /* contact method (or deeper) */
220 QVariant var_n = idx.data(static_cast<int>(ContactMethod::Role::Object));
221 if (var_n.isValid()) {
222 /* call with contat method */
223 place_new_call(var_n.value<ContactMethod *>());
224 }
225 break;
226 }
227 }
228 }
229 }
230}
231
232static void
233contacts_view_init(ContactsView *self)
234{
235 ContactsViewPrivate *priv = CONTACTS_VIEW_GET_PRIVATE(self);
236
237 /* contacts view/model */
238 GtkWidget *treeview_contacts = gtk_tree_view_new();
239 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview_contacts), FALSE);
240 gtk_container_add(GTK_CONTAINER(self), treeview_contacts);
241
242 CategorizedContactModel::instance()->setUnreachableHidden(true);
243 priv->q_contact_model = new ActiveItemProxyModel(CategorizedContactModel::instance());
244 priv->q_contact_model->setSortRole(Qt::DisplayRole);
245 priv->q_contact_model->setSortLocaleAware(true);
246 priv->q_contact_model->setSortCaseSensitivity(Qt::CaseInsensitive);
247 priv->q_contact_model->sort(0);
248
249 GtkQSortFilterTreeModel *contact_model = gtk_q_sort_filter_tree_model_new(
250 (QSortFilterProxyModel *)priv->q_contact_model,
251 1,
252 Qt::DisplayRole, G_TYPE_STRING);
253 gtk_tree_view_set_model(GTK_TREE_VIEW(treeview_contacts), GTK_TREE_MODEL(contact_model));
254
255 /* photo and name/contact method column */
256 GtkCellArea *area = gtk_cell_area_box_new();
257 GtkTreeViewColumn *column = gtk_tree_view_column_new_with_area(area);
258 gtk_tree_view_column_set_title(column, "Name");
259
260 /* photo renderer */
261 GtkCellRenderer *renderer = gtk_cell_renderer_pixbuf_new();
262 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
263
264 /* get the photo */
265 gtk_tree_view_column_set_cell_data_func(
266 column,
267 renderer,
268 (GtkTreeCellDataFunc)render_contact_photo,
269 NULL,
270 NULL);
271
272 /* name and contact method renderer */
273 renderer = gtk_cell_renderer_text_new();
274 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
275
276 gtk_tree_view_column_set_cell_data_func(
277 column,
278 renderer,
279 (GtkTreeCellDataFunc)render_name_and_contact_method,
280 NULL,
281 NULL);
282
283 gtk_tree_view_append_column(GTK_TREE_VIEW(treeview_contacts), column);
284 gtk_tree_view_column_set_resizable(column, TRUE);
285
286 gtk_tree_view_expand_all(GTK_TREE_VIEW(treeview_contacts));
287 g_signal_connect(contact_model, "row-inserted", G_CALLBACK(expand_if_child), treeview_contacts);
288 g_signal_connect(treeview_contacts, "row-activated", G_CALLBACK(activate_contact_item), NULL);
289
290 gtk_widget_show_all(GTK_WIDGET(self));
291}
292
293static void
294contacts_view_dispose(GObject *object)
295{
296 G_OBJECT_CLASS(contacts_view_parent_class)->dispose(object);
297}
298
299static void
300contacts_view_finalize(GObject *object)
301{
302 ContactsView *self = CONTACTS_VIEW(object);
303 ContactsViewPrivate *priv = CONTACTS_VIEW_GET_PRIVATE(self);
304
305 delete priv->q_contact_model;
306
307 G_OBJECT_CLASS(contacts_view_parent_class)->finalize(object);
308}
309
310static void
311contacts_view_class_init(ContactsViewClass *klass)
312{
313 G_OBJECT_CLASS(klass)->finalize = contacts_view_finalize;
314 G_OBJECT_CLASS(klass)->dispose = contacts_view_dispose;
315}
316
317GtkWidget *
318contacts_view_new()
319{
320 gpointer self = g_object_new(CONTACTS_VIEW_TYPE, NULL);
321
322 return (GtkWidget *)self;
323}