blob: 573ef4b1e28e8a28cf99d38f94aa1b57b27ce896 [file] [log] [blame]
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -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
20#include "recentcontactsview.h"
21
22#include <gtk/gtk.h>
23#include <glib/gi18n.h>
24#include "models/gtkqsortfiltertreemodel.h"
25#include "utils/calling.h"
26#include <memory>
27#include <globalinstances.h>
28#include "native/pixbufmanipulator.h"
29#include <contactmethod.h>
30#include "defines.h"
31#include "utils/models.h"
32#include <recentmodel.h>
33#include <call.h>
34#include "utils/menus.h"
35#include <itemdataroles.h>
36#include <callmodel.h>
37#include <QtCore/QItemSelectionModel>
38#include <historytimecategorymodel.h>
39#include <QtCore/QDateTime>
40
41static constexpr const char* COPY_DATA_KEY = "copy_data";
42
43struct _RecentContactsView
44{
45 GtkTreeView parent;
46};
47
48struct _RecentContactsViewClass
49{
50 GtkTreeViewClass parent_class;
51};
52
53typedef struct _RecentContactsViewPrivate RecentContactsViewPrivate;
54
55struct _RecentContactsViewPrivate
56{
57 GtkWidget *overlay_button;
Stepan Salenikoviche8fa6812015-10-16 15:34:59 -040058
59 QMetaObject::Connection selection_updated;
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -040060};
61
62G_DEFINE_TYPE_WITH_PRIVATE(RecentContactsView, recent_contacts_view, GTK_TYPE_TREE_VIEW);
63
64#define RECENT_CONTACTS_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), RECENT_CONTACTS_VIEW_TYPE, RecentContactsViewPrivate))
65
66static void
67update_call_model_selection(GtkTreeSelection *selection, G_GNUC_UNUSED gpointer user_data)
68{
69 auto current_proxy = get_index_from_selection(selection);
Guillaume Roguez5d1514b2015-10-22 15:55:31 -040070 auto current = RecentModel::instance().peopleProxy()->mapToSource(current_proxy);
71 if (auto call_to_select = RecentModel::instance().getActiveCall(current)) {
72 CallModel::instance().selectCall(call_to_select);
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -040073 } else {
Guillaume Roguez5d1514b2015-10-22 15:55:31 -040074 CallModel::instance().selectionModel()->clearCurrentIndex();
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -040075 }
76}
77
78static void
79copy_contact_info(GtkWidget *item, G_GNUC_UNUSED gpointer user_data)
80{
81 gpointer data = g_object_get_data(G_OBJECT(item), COPY_DATA_KEY);
82 g_return_if_fail(data);
83 gchar* text = (gchar *)data;
84 GtkClipboard* clip = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
85 gtk_clipboard_set_text(clip, text, -1);
86}
87
88static void
89call_contactmethod(G_GNUC_UNUSED GtkWidget *item, ContactMethod *cm)
90{
91 g_return_if_fail(cm);
92 place_new_call(cm);
93}
94
95static void
96render_contact_photo(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
97 GtkCellRenderer *cell,
98 GtkTreeModel *model,
99 GtkTreeIter *iter,
100 G_GNUC_UNUSED gpointer data)
101{
102 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(model), iter);
103
104 std::shared_ptr<GdkPixbuf> photo;
105 /* we only want to render a photo for the top nodes: Person, ContactMethod (, later Conference) */
106 QVariant object = idx.data(static_cast<int>(Ring::Role::Object));
107 if (idx.isValid() && object.isValid()) {
108 QVariant var_photo;
109 if (auto person = object.value<Person *>()) {
110 var_photo = GlobalInstances::pixmapManipulator().contactPhoto(person, QSize(50, 50), false);
111 } else if (auto cm = object.value<ContactMethod *>()) {
112 /* get photo, note that this should in all cases be the fallback avatar, since there
113 * shouldn't be a person associated with this contact method */
114 var_photo = GlobalInstances::pixmapManipulator().callPhoto(cm, QSize(50, 50), false);
115 }
116 if (var_photo.isValid())
117 photo = var_photo.value<std::shared_ptr<GdkPixbuf>>();
118 else {
119 // set the width of the cell rendered to the with of the photo
120 // so that the other renderers are shifted to the right
121 g_object_set(G_OBJECT(cell), "width", 50, NULL);
122 }
123 }
124
125 g_object_set(G_OBJECT(cell), "pixbuf", photo.get(), NULL);
126}
127
128static void
129render_name_and_info(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
130 GtkCellRenderer *cell,
131 GtkTreeModel *model,
132 GtkTreeIter *iter,
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400133 GtkTreeView *treeview)
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400134{
135 gchar *text = NULL;
136
137 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(model), iter);
138
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400139 // check if this iter is selected
140 gboolean is_selected = FALSE;
141 if (GTK_IS_TREE_VIEW(treeview)) {
142 auto selection = gtk_tree_view_get_selection(treeview);
143 is_selected = gtk_tree_selection_iter_is_selected(selection, iter);
144 }
145
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400146 auto type = idx.data(static_cast<int>(Ring::Role::ObjectType));
147 if (idx.isValid() && type.isValid()) {
148 switch (type.value<Ring::ObjectType>()) {
149 case Ring::ObjectType::Person:
150 case Ring::ObjectType::ContactMethod:
151 {
152 auto var_name = idx.data(static_cast<int>(Ring::Role::Name));
153 auto var_lastused = idx.data(static_cast<int>(Ring::Role::LastUsed));
154 auto var_status = idx.data(static_cast<int>(Ring::Role::FormattedState));
155
156 QString name, status;
157
158 if (var_name.isValid())
159 name = var_name.value<QString>();
160
161 // show the status if there is a call, otherwise the last used date/time
162 if (var_status.isValid()) {
163 status += var_status.value<QString>();
164 }else if (var_lastused.isValid()) {
165 auto date_time = var_lastused.value<QDateTime>();
166 auto category = HistoryTimeCategoryModel::timeToHistoryConst(date_time.toTime_t());
167
168 // if it is 'today', then we only want to show the time
169 if (category != HistoryTimeCategoryModel::HistoryConst::Today) {
170 status += HistoryTimeCategoryModel::timeToHistoryCategory(date_time.toTime_t());
171 }
172 // we only want to show the time if it is less than a week ago
173 if (category < HistoryTimeCategoryModel::HistoryConst::A_week_ago) {
174 if (!status.isEmpty())
175 status += ", ";
176 status += QLocale::system().toString(date_time.time(), QLocale::ShortFormat);
177 }
178 }
179
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400180 /* we want the color of the status text to be the default color if this iter is
181 * selected so that the treeview is able to invert it against the selection color */
182 if (is_selected) {
183 text = g_strdup_printf("%s\n<span size=\"smaller\">%s</span>",
184 name.toUtf8().constData(),
185 status.toUtf8().constData());
186 } else {
187 text = g_strdup_printf("%s\n<span size=\"smaller\" color=\"gray\">%s</span>",
188 name.toUtf8().constData(),
189 status.toUtf8().constData());
190 }
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400191 }
192 break;
193 case Ring::ObjectType::Call:
194 {
195 auto var_status = idx.data(static_cast<int>(Ring::Role::FormattedState));
196
197 QString status;
198
199 if (var_status.isValid())
200 status += var_status.value<QString>();
201
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400202 /* we want the color of the status text to be the default color if this iter is
203 * selected so that the treeview is able to invert it against the selection color */
204 if (is_selected)
205 text = g_strdup_printf("<span size=\"smaller\">%s</span>", status.toUtf8().constData());
206 else
207 text = g_strdup_printf("<span size=\"smaller\" color=\"gray\">%s</span>", status.toUtf8().constData());
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400208 }
209 break;
210 case Ring::ObjectType::Media:
211 // nothing to do for now
212 break;
213 }
214 }
215
216 g_object_set(G_OBJECT(cell), "markup", text, NULL);
217 g_free(text);
218}
219
220static void
221render_call_duration(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
222 GtkCellRenderer *cell,
223 GtkTreeModel *model,
224 GtkTreeIter *iter,
225 G_GNUC_UNUSED gpointer data)
226{
227 gchar *text = NULL;
228
229 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(model), iter);
230
231 auto type = idx.data(static_cast<int>(Ring::Role::ObjectType));
232 if (idx.isValid() && type.isValid()) {
233 switch (type.value<Ring::ObjectType>()) {
234 case Ring::ObjectType::Person:
235 case Ring::ObjectType::ContactMethod:
236 {
237 // check if there are any children (calls); we need to convert to source model in
238 // case there is only one
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400239 auto idx_source = RecentModel::instance().peopleProxy()->mapToSource(idx);
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400240 auto duration = idx.data(static_cast<int>(Ring::Role::Length));
241 if (idx_source.isValid()
242 && (idx_source.model()->rowCount(idx_source) == 1)
243 && duration.isValid())
244 {
245 text = g_strdup_printf("%s", duration.value<QString>().toUtf8().constData());
246 }
247 }
248 break;
249 case Ring::ObjectType::Call:
250 {
251 auto duration = idx.data(static_cast<int>(Ring::Role::Length));
252
253 if (duration.isValid())
254 text = g_strdup_printf("%s", duration.value<QString>().toUtf8().constData());
255 }
256 break;
257 case Ring::ObjectType::Media:
258 // nothing to do for now
259 break;
260 }
261 }
262
263 g_object_set(G_OBJECT(cell), "markup", text, NULL);
264 g_free(text);
265}
266
267static void
268activate_item(GtkTreeView *tree_view,
269 GtkTreePath *path,
270 G_GNUC_UNUSED GtkTreeViewColumn *column,
271 G_GNUC_UNUSED gpointer user_data)
272{
273 auto model = gtk_tree_view_get_model(tree_view);
274 GtkTreeIter iter;
275 if (gtk_tree_model_get_iter(model, &iter, path)) {
276 auto idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(model), &iter);
277 auto type = idx.data(static_cast<int>(Ring::Role::ObjectType));
278 if (idx.isValid() && type.isValid()) {
279 switch (type.value<Ring::ObjectType>()) {
280 case Ring::ObjectType::Person:
281 {
282 // call the last used contact method
283 // TODO: if no contact methods have been used, offer a popup to Choose
284 auto p_var = idx.data(static_cast<int>(Ring::Role::Object));
285 if (p_var.isValid()) {
286 auto person = p_var.value<Person *>();
287 auto cms = person->phoneNumbers();
288
289 if (!cms.isEmpty()) {
290 auto last_used_cm = cms.at(0);
291 for (int i = 1; i < cms.size(); ++i) {
292 auto new_cm = cms.at(i);
293 if (difftime(new_cm->lastUsed(), last_used_cm->lastUsed()) > 0)
294 last_used_cm = new_cm;
295 }
296
297 place_new_call(last_used_cm);
298 }
299 }
300 }
301 break;
302 case Ring::ObjectType::ContactMethod:
303 {
304 // call the contact method
305 auto cm = idx.data(static_cast<int>(Ring::Role::Object));
306 if (cm.isValid())
307 place_new_call(cm.value<ContactMethod *>());
308 }
309 break;
310 case Ring::ObjectType::Call:
311 case Ring::ObjectType::Media:
312 // nothing to do for now
313 break;
314 }
315 }
316 }
317}
318
319static gboolean
320create_popup_menu(GtkTreeView *treeview, GdkEventButton *event, G_GNUC_UNUSED gpointer user_data)
321{
322 /* build popup menu when right clicking on contact item
323 * user should be able to copy the contact's name or "number".
324 * or add the "number" to his contact list, if not already so
325 */
326
327 /* check for right click */
328 if (event->button != BUTTON_RIGHT_CLICK || event->type != GDK_BUTTON_PRESS)
329 return FALSE;
330
331 GtkTreeIter iter;
332 GtkTreeModel *model;
333 GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
334 if (!gtk_tree_selection_get_selected(selection, &model, &iter))
335 return FALSE;
336
337 GtkWidget *menu = gtk_menu_new();
338 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(model), &iter);
339
340 /* if Person or CM, give option to call */
341 auto type = idx.data(static_cast<int>(Ring::Role::ObjectType));
342 auto object = idx.data(static_cast<int>(Ring::Role::Object));
343 if (type.isValid() && object.isValid()) {
344 switch (type.value<Ring::ObjectType>()) {
345 case Ring::ObjectType::Person:
346 {
347 /* possiblity for multiple numbers */
348 auto cms = object.value<Person *>()->phoneNumbers();
349 if (cms.size() == 1) {
350 auto item = gtk_menu_item_new_with_mnemonic(_("_Call"));
351 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
352 g_signal_connect(item,
353 "activate",
354 G_CALLBACK(call_contactmethod),
355 cms.at(0));
356 } else if (cms.size() > 1) {
357 auto call_item = gtk_menu_item_new_with_mnemonic(_("_Call"));
358 gtk_menu_shell_append(GTK_MENU_SHELL(menu), call_item);
359 auto call_menu = gtk_menu_new();
360 gtk_menu_item_set_submenu(GTK_MENU_ITEM(call_item), call_menu);
361 for (int i = 0; i < cms.size(); ++i) {
362 auto item = gtk_menu_item_new_with_label(cms.at(i)->uri().toUtf8().constData());
363 gtk_menu_shell_append(GTK_MENU_SHELL(call_menu), item);
364 g_signal_connect(item,
365 "activate",
366 G_CALLBACK(call_contactmethod),
367 cms.at(i));
368 }
369 }
370 }
371 break;
372 case Ring::ObjectType::ContactMethod:
373 {
374 auto cm = object.value<ContactMethod *>();
375 auto item = gtk_menu_item_new_with_mnemonic(_("_Call"));
376 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
377 g_signal_connect(item,
378 "activate",
379 G_CALLBACK(call_contactmethod),
380 cm);
381 }
382 break;
383 case Ring::ObjectType::Call:
384 case Ring::ObjectType::Media:
385 // nothing to do for now
386 break;
387 }
388 }
389
390 /* copy name */
391 QVariant name_var = idx.data(static_cast<int>(Ring::Role::Name));
392 if (name_var.isValid()) {
393 gchar *name = g_strdup_printf("%s", name_var.value<QString>().toUtf8().constData());
394 GtkWidget *item = gtk_menu_item_new_with_mnemonic(_("_Copy name"));
395 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
396 g_object_set_data_full(G_OBJECT(item), COPY_DATA_KEY, name, (GDestroyNotify)g_free);
397 g_signal_connect(item,
398 "activate",
399 G_CALLBACK(copy_contact_info),
400 NULL);
401 }
402
403 /* copy number(s) */
404 if (type.isValid() && object.isValid()) {
405 switch (type.value<Ring::ObjectType>()) {
406 case Ring::ObjectType::Person:
407 {
408 /* possiblity for multiple numbers */
409 auto cms = object.value<Person *>()->phoneNumbers();
410 if (cms.size() == 1) {
411 gchar *number = g_strdup_printf("%s",cms.at(0)->uri().toUtf8().constData());
412 auto item = gtk_menu_item_new_with_mnemonic(_("_Copy number"));
413 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
414 g_object_set_data_full(G_OBJECT(item), COPY_DATA_KEY, number, (GDestroyNotify)g_free);
415 g_signal_connect(item,
416 "activate",
417 G_CALLBACK(copy_contact_info),
418 NULL);
419 } else if (cms.size() > 1) {
420 auto copy_item = gtk_menu_item_new_with_mnemonic(_("_Copy number"));
421 gtk_menu_shell_append(GTK_MENU_SHELL(menu), copy_item);
422 auto copy_menu = gtk_menu_new();
423 gtk_menu_item_set_submenu(GTK_MENU_ITEM(copy_item), copy_menu);
424 for (int i = 0; i < cms.size(); ++i) {
425 gchar *number = g_strdup_printf("%s",cms.at(i)->uri().toUtf8().constData());
426 auto item = gtk_menu_item_new_with_label(cms.at(i)->uri().toUtf8().constData());
427 gtk_menu_shell_append(GTK_MENU_SHELL(copy_menu), item);
428 g_object_set_data_full(G_OBJECT(item), COPY_DATA_KEY, number, (GDestroyNotify)g_free);
429 g_signal_connect(item,
430 "activate",
431 G_CALLBACK(copy_contact_info),
432 NULL);
433 }
434 }
435 }
436 break;
437 case Ring::ObjectType::ContactMethod:
438 case Ring::ObjectType::Call:
439 case Ring::ObjectType::Media:
440 QVariant number_var = idx.data(static_cast<int>(Ring::Role::Number));
441 if (number_var.isValid()) {
442 gchar *number = g_strdup_printf("%s", number_var.value<QString>().toUtf8().constData());
443 GtkWidget *item = gtk_menu_item_new_with_mnemonic(_("_Copy number"));
444 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
445 g_object_set_data_full(G_OBJECT(item), COPY_DATA_KEY, number, (GDestroyNotify)g_free);
446 g_signal_connect(item,
447 "activate",
448 G_CALLBACK(copy_contact_info),
449 NULL);
450 }
451 break;
452 }
453 }
454
455 /* if the object is a CM, then give the option to add to contacts*/
456 if (type.isValid() && object.isValid()) {
457 if (type.value<Ring::ObjectType>() == Ring::ObjectType::ContactMethod) {
458 /* get rectangle */
459 auto path = gtk_tree_model_get_path(model, &iter);
460 auto column = gtk_tree_view_get_column(treeview, 0);
461 GdkRectangle rect;
462 gtk_tree_view_get_cell_area(treeview, path, column, &rect);
463 gtk_tree_view_convert_bin_window_to_widget_coords(treeview, rect.x, rect.y, &rect.x, &rect.y);
464 gtk_tree_path_free(path);
465 auto add_to = menu_item_add_to_contact(object.value<ContactMethod *>(), GTK_WIDGET(treeview), &rect);
466 gtk_menu_shell_append(GTK_MENU_SHELL(menu), add_to);
467 }
468 }
469
470 /* show menu */
471 gtk_widget_show_all(menu);
472 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);
473
474 return TRUE; /* we handled the event */
475}
476
477static void
478expand_if_child(G_GNUC_UNUSED GtkTreeModel *tree_model,
479 GtkTreePath *path,
480 G_GNUC_UNUSED GtkTreeIter *iter,
481 GtkTreeView *treeview)
482{
483 if (gtk_tree_path_get_depth(path) > 1)
484 gtk_tree_view_expand_to_path(treeview, path);
485}
486
487static void
Stepan Salenikovichec0862f2015-10-16 15:49:42 -0400488scroll_to_selection(GtkTreeSelection *selection)
489{
490 GtkTreeModel *model = nullptr;
491 GtkTreeIter iter;
492 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
493 auto path = gtk_tree_model_get_path(model, &iter);
494 auto treeview = gtk_tree_selection_get_tree_view(selection);
495 gtk_tree_view_scroll_to_cell(treeview, path, nullptr, FALSE, 0.0, 0.0);
496 }
497}
498
499static void
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400500recent_contacts_view_init(RecentContactsView *self)
501{
Stepan Salenikoviche8fa6812015-10-16 15:34:59 -0400502 RecentContactsViewPrivate *priv = RECENT_CONTACTS_VIEW_GET_PRIVATE(self);
503
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400504 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(self), FALSE);
505 /* no need to show the expander since it will always be expanded */
506 gtk_tree_view_set_show_expanders(GTK_TREE_VIEW(self), FALSE);
507 /* disable default search, we will handle it ourselves via LRC;
508 * otherwise the search steals input focus on key presses */
509 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(self), FALSE);
510
511 GtkQSortFilterTreeModel *recent_model = gtk_q_sort_filter_tree_model_new(
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400512 RecentModel::instance().peopleProxy(),
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400513 1,
514 Qt::DisplayRole, G_TYPE_STRING);
515
516 gtk_tree_view_set_model(GTK_TREE_VIEW(self),
517 GTK_TREE_MODEL(recent_model));
518
519 /* photo and name/contact method column */
520 GtkCellArea *area = gtk_cell_area_box_new();
521 GtkTreeViewColumn *column = gtk_tree_view_column_new_with_area(area);
522
523 /* photo renderer */
524 GtkCellRenderer *renderer = gtk_cell_renderer_pixbuf_new();
525 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
526
527 /* get the photo */
528 gtk_tree_view_column_set_cell_data_func(
529 column,
530 renderer,
531 (GtkTreeCellDataFunc)render_contact_photo,
532 NULL,
533 NULL);
534
535 /* name/cm and status renderer */
536 renderer = gtk_cell_renderer_text_new();
537 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
538 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
539
540 gtk_tree_view_column_set_cell_data_func(
541 column,
542 renderer,
543 (GtkTreeCellDataFunc)render_name_and_info,
Stepan Salenikoviche4981b22015-10-22 15:22:59 -0400544 self,
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400545 NULL);
546
547 gtk_tree_view_append_column(GTK_TREE_VIEW(self), column);
548 gtk_tree_view_column_set_resizable(column, TRUE);
549 gtk_tree_view_column_set_expand(column, TRUE);
550
551 /* call duration column */
552 renderer = gtk_cell_renderer_text_new();
553 column = gtk_tree_view_column_new_with_attributes("Duration", renderer, NULL);
554 gtk_tree_view_append_column(GTK_TREE_VIEW(self), column);
555 gtk_tree_view_column_set_cell_data_func(
556 column,
557 renderer,
558 (GtkTreeCellDataFunc)render_call_duration,
559 NULL,
560 NULL);
561
562 gtk_tree_view_expand_all(GTK_TREE_VIEW(self));
563
564 g_signal_connect(self, "button-press-event", G_CALLBACK(create_popup_menu), NULL);
565 g_signal_connect(self, "row-activated", G_CALLBACK(activate_item), NULL);
566 g_signal_connect(recent_model, "row-inserted", G_CALLBACK(expand_if_child), self);
567
568 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
569 g_signal_connect(selection, "changed", G_CALLBACK(update_call_model_selection), NULL);
Stepan Salenikovichec0862f2015-10-16 15:49:42 -0400570 g_signal_connect(selection, "changed", G_CALLBACK(scroll_to_selection), NULL);
571 g_signal_connect_swapped(recent_model, "rows-reordered", G_CALLBACK(scroll_to_selection), selection);
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400572
Stepan Salenikoviche8fa6812015-10-16 15:34:59 -0400573 /* try to select the same call as the call model */
574 priv->selection_updated = QObject::connect(
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400575 CallModel::instance().selectionModel(),
Stepan Salenikoviche8fa6812015-10-16 15:34:59 -0400576 &QItemSelectionModel::currentChanged,
577 [self, recent_model](const QModelIndex current, G_GNUC_UNUSED const QModelIndex & previous) {
578 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
579
580 /* select the current */
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400581 auto call = CallModel::instance().getCall(current);
582 auto recent_idx = RecentModel::instance().getIndex(call);
Stepan Salenikoviche8fa6812015-10-16 15:34:59 -0400583 if (recent_idx.isValid()) {
584 QModelIndex proxy_selection; // the index to select in the peopleProxy
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400585 if (RecentModel::instance().rowCount(recent_idx.parent()) > 1) {
586 proxy_selection = RecentModel::instance().peopleProxy()->mapFromSource(recent_idx);
Stepan Salenikoviche8fa6812015-10-16 15:34:59 -0400587 } else {
588 // if single call, select the parent
Guillaume Roguez5d1514b2015-10-22 15:55:31 -0400589 proxy_selection = RecentModel::instance().peopleProxy()->mapFromSource(recent_idx.parent());
Stepan Salenikoviche8fa6812015-10-16 15:34:59 -0400590 }
591
592 GtkTreeIter new_iter;
593 if (gtk_q_sort_filter_tree_model_source_index_to_iter(recent_model, proxy_selection, &new_iter)) {
594 gtk_tree_selection_select_iter(selection, &new_iter);
595 }
596 }
597 }
598 );
599
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400600 gtk_widget_show_all(GTK_WIDGET(self));
601}
602
603static void
604recent_contacts_view_dispose(GObject *object)
605{
Stepan Salenikoviche8fa6812015-10-16 15:34:59 -0400606 RecentContactsView *self = RECENT_CONTACTS_VIEW(object);
607 RecentContactsViewPrivate *priv = RECENT_CONTACTS_VIEW_GET_PRIVATE(self);
608
609 QObject::disconnect(priv->selection_updated);
610
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400611 G_OBJECT_CLASS(recent_contacts_view_parent_class)->dispose(object);
612}
613
614static void
615recent_contacts_view_finalize(GObject *object)
616{
617 G_OBJECT_CLASS(recent_contacts_view_parent_class)->finalize(object);
618}
619
620static void
621recent_contacts_view_class_init(RecentContactsViewClass *klass)
622{
623 G_OBJECT_CLASS(klass)->finalize = recent_contacts_view_finalize;
624 G_OBJECT_CLASS(klass)->dispose = recent_contacts_view_dispose;
625}
626
627GtkWidget *
628recent_contacts_view_new()
629{
630 gpointer self = g_object_new(RECENT_CONTACTS_VIEW_TYPE, NULL);
631
632 return (GtkWidget *)self;
633}