blob: c2808bbd82ec78f9b58089b8a927b33442a11b33 [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);
70 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);
73 } else {
74 CallModel::instance()->selectionModel()->clearCurrentIndex();
75 }
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,
133 G_GNUC_UNUSED gpointer data)
134{
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
139 auto type = idx.data(static_cast<int>(Ring::Role::ObjectType));
140 if (idx.isValid() && type.isValid()) {
141 switch (type.value<Ring::ObjectType>()) {
142 case Ring::ObjectType::Person:
143 case Ring::ObjectType::ContactMethod:
144 {
145 auto var_name = idx.data(static_cast<int>(Ring::Role::Name));
146 auto var_lastused = idx.data(static_cast<int>(Ring::Role::LastUsed));
147 auto var_status = idx.data(static_cast<int>(Ring::Role::FormattedState));
148
149 QString name, status;
150
151 if (var_name.isValid())
152 name = var_name.value<QString>();
153
154 // show the status if there is a call, otherwise the last used date/time
155 if (var_status.isValid()) {
156 status += var_status.value<QString>();
157 }else if (var_lastused.isValid()) {
158 auto date_time = var_lastused.value<QDateTime>();
159 auto category = HistoryTimeCategoryModel::timeToHistoryConst(date_time.toTime_t());
160
161 // if it is 'today', then we only want to show the time
162 if (category != HistoryTimeCategoryModel::HistoryConst::Today) {
163 status += HistoryTimeCategoryModel::timeToHistoryCategory(date_time.toTime_t());
164 }
165 // we only want to show the time if it is less than a week ago
166 if (category < HistoryTimeCategoryModel::HistoryConst::A_week_ago) {
167 if (!status.isEmpty())
168 status += ", ";
169 status += QLocale::system().toString(date_time.time(), QLocale::ShortFormat);
170 }
171 }
172
173 text = g_strdup_printf("%s\n<span size=\"smaller\" color=\"gray\">%s</span>",
174 name.toUtf8().constData(),
175 status.toUtf8().constData());
176 }
177 break;
178 case Ring::ObjectType::Call:
179 {
180 auto var_status = idx.data(static_cast<int>(Ring::Role::FormattedState));
181
182 QString status;
183
184 if (var_status.isValid())
185 status += var_status.value<QString>();
186
187 text = g_strdup_printf("<span size=\"smaller\" color=\"gray\">%s</span>", status.toUtf8().constData());
188 }
189 break;
190 case Ring::ObjectType::Media:
191 // nothing to do for now
192 break;
193 }
194 }
195
196 g_object_set(G_OBJECT(cell), "markup", text, NULL);
197 g_free(text);
198}
199
200static void
201render_call_duration(G_GNUC_UNUSED GtkTreeViewColumn *tree_column,
202 GtkCellRenderer *cell,
203 GtkTreeModel *model,
204 GtkTreeIter *iter,
205 G_GNUC_UNUSED gpointer data)
206{
207 gchar *text = NULL;
208
209 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(model), iter);
210
211 auto type = idx.data(static_cast<int>(Ring::Role::ObjectType));
212 if (idx.isValid() && type.isValid()) {
213 switch (type.value<Ring::ObjectType>()) {
214 case Ring::ObjectType::Person:
215 case Ring::ObjectType::ContactMethod:
216 {
217 // check if there are any children (calls); we need to convert to source model in
218 // case there is only one
219 auto idx_source = RecentModel::instance()->peopleProxy()->mapToSource(idx);
220 auto duration = idx.data(static_cast<int>(Ring::Role::Length));
221 if (idx_source.isValid()
222 && (idx_source.model()->rowCount(idx_source) == 1)
223 && duration.isValid())
224 {
225 text = g_strdup_printf("%s", duration.value<QString>().toUtf8().constData());
226 }
227 }
228 break;
229 case Ring::ObjectType::Call:
230 {
231 auto duration = idx.data(static_cast<int>(Ring::Role::Length));
232
233 if (duration.isValid())
234 text = g_strdup_printf("%s", duration.value<QString>().toUtf8().constData());
235 }
236 break;
237 case Ring::ObjectType::Media:
238 // nothing to do for now
239 break;
240 }
241 }
242
243 g_object_set(G_OBJECT(cell), "markup", text, NULL);
244 g_free(text);
245}
246
247static void
248activate_item(GtkTreeView *tree_view,
249 GtkTreePath *path,
250 G_GNUC_UNUSED GtkTreeViewColumn *column,
251 G_GNUC_UNUSED gpointer user_data)
252{
253 auto model = gtk_tree_view_get_model(tree_view);
254 GtkTreeIter iter;
255 if (gtk_tree_model_get_iter(model, &iter, path)) {
256 auto idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(model), &iter);
257 auto type = idx.data(static_cast<int>(Ring::Role::ObjectType));
258 if (idx.isValid() && type.isValid()) {
259 switch (type.value<Ring::ObjectType>()) {
260 case Ring::ObjectType::Person:
261 {
262 // call the last used contact method
263 // TODO: if no contact methods have been used, offer a popup to Choose
264 auto p_var = idx.data(static_cast<int>(Ring::Role::Object));
265 if (p_var.isValid()) {
266 auto person = p_var.value<Person *>();
267 auto cms = person->phoneNumbers();
268
269 if (!cms.isEmpty()) {
270 auto last_used_cm = cms.at(0);
271 for (int i = 1; i < cms.size(); ++i) {
272 auto new_cm = cms.at(i);
273 if (difftime(new_cm->lastUsed(), last_used_cm->lastUsed()) > 0)
274 last_used_cm = new_cm;
275 }
276
277 place_new_call(last_used_cm);
278 }
279 }
280 }
281 break;
282 case Ring::ObjectType::ContactMethod:
283 {
284 // call the contact method
285 auto cm = idx.data(static_cast<int>(Ring::Role::Object));
286 if (cm.isValid())
287 place_new_call(cm.value<ContactMethod *>());
288 }
289 break;
290 case Ring::ObjectType::Call:
291 case Ring::ObjectType::Media:
292 // nothing to do for now
293 break;
294 }
295 }
296 }
297}
298
299static gboolean
300create_popup_menu(GtkTreeView *treeview, GdkEventButton *event, G_GNUC_UNUSED gpointer user_data)
301{
302 /* build popup menu when right clicking on contact item
303 * user should be able to copy the contact's name or "number".
304 * or add the "number" to his contact list, if not already so
305 */
306
307 /* check for right click */
308 if (event->button != BUTTON_RIGHT_CLICK || event->type != GDK_BUTTON_PRESS)
309 return FALSE;
310
311 GtkTreeIter iter;
312 GtkTreeModel *model;
313 GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview);
314 if (!gtk_tree_selection_get_selected(selection, &model, &iter))
315 return FALSE;
316
317 GtkWidget *menu = gtk_menu_new();
318 QModelIndex idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(model), &iter);
319
320 /* if Person or CM, give option to call */
321 auto type = idx.data(static_cast<int>(Ring::Role::ObjectType));
322 auto object = idx.data(static_cast<int>(Ring::Role::Object));
323 if (type.isValid() && object.isValid()) {
324 switch (type.value<Ring::ObjectType>()) {
325 case Ring::ObjectType::Person:
326 {
327 /* possiblity for multiple numbers */
328 auto cms = object.value<Person *>()->phoneNumbers();
329 if (cms.size() == 1) {
330 auto item = gtk_menu_item_new_with_mnemonic(_("_Call"));
331 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
332 g_signal_connect(item,
333 "activate",
334 G_CALLBACK(call_contactmethod),
335 cms.at(0));
336 } else if (cms.size() > 1) {
337 auto call_item = gtk_menu_item_new_with_mnemonic(_("_Call"));
338 gtk_menu_shell_append(GTK_MENU_SHELL(menu), call_item);
339 auto call_menu = gtk_menu_new();
340 gtk_menu_item_set_submenu(GTK_MENU_ITEM(call_item), call_menu);
341 for (int i = 0; i < cms.size(); ++i) {
342 auto item = gtk_menu_item_new_with_label(cms.at(i)->uri().toUtf8().constData());
343 gtk_menu_shell_append(GTK_MENU_SHELL(call_menu), item);
344 g_signal_connect(item,
345 "activate",
346 G_CALLBACK(call_contactmethod),
347 cms.at(i));
348 }
349 }
350 }
351 break;
352 case Ring::ObjectType::ContactMethod:
353 {
354 auto cm = object.value<ContactMethod *>();
355 auto item = gtk_menu_item_new_with_mnemonic(_("_Call"));
356 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
357 g_signal_connect(item,
358 "activate",
359 G_CALLBACK(call_contactmethod),
360 cm);
361 }
362 break;
363 case Ring::ObjectType::Call:
364 case Ring::ObjectType::Media:
365 // nothing to do for now
366 break;
367 }
368 }
369
370 /* copy name */
371 QVariant name_var = idx.data(static_cast<int>(Ring::Role::Name));
372 if (name_var.isValid()) {
373 gchar *name = g_strdup_printf("%s", name_var.value<QString>().toUtf8().constData());
374 GtkWidget *item = gtk_menu_item_new_with_mnemonic(_("_Copy name"));
375 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
376 g_object_set_data_full(G_OBJECT(item), COPY_DATA_KEY, name, (GDestroyNotify)g_free);
377 g_signal_connect(item,
378 "activate",
379 G_CALLBACK(copy_contact_info),
380 NULL);
381 }
382
383 /* copy number(s) */
384 if (type.isValid() && object.isValid()) {
385 switch (type.value<Ring::ObjectType>()) {
386 case Ring::ObjectType::Person:
387 {
388 /* possiblity for multiple numbers */
389 auto cms = object.value<Person *>()->phoneNumbers();
390 if (cms.size() == 1) {
391 gchar *number = g_strdup_printf("%s",cms.at(0)->uri().toUtf8().constData());
392 auto item = gtk_menu_item_new_with_mnemonic(_("_Copy number"));
393 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
394 g_object_set_data_full(G_OBJECT(item), COPY_DATA_KEY, number, (GDestroyNotify)g_free);
395 g_signal_connect(item,
396 "activate",
397 G_CALLBACK(copy_contact_info),
398 NULL);
399 } else if (cms.size() > 1) {
400 auto copy_item = gtk_menu_item_new_with_mnemonic(_("_Copy number"));
401 gtk_menu_shell_append(GTK_MENU_SHELL(menu), copy_item);
402 auto copy_menu = gtk_menu_new();
403 gtk_menu_item_set_submenu(GTK_MENU_ITEM(copy_item), copy_menu);
404 for (int i = 0; i < cms.size(); ++i) {
405 gchar *number = g_strdup_printf("%s",cms.at(i)->uri().toUtf8().constData());
406 auto item = gtk_menu_item_new_with_label(cms.at(i)->uri().toUtf8().constData());
407 gtk_menu_shell_append(GTK_MENU_SHELL(copy_menu), item);
408 g_object_set_data_full(G_OBJECT(item), COPY_DATA_KEY, number, (GDestroyNotify)g_free);
409 g_signal_connect(item,
410 "activate",
411 G_CALLBACK(copy_contact_info),
412 NULL);
413 }
414 }
415 }
416 break;
417 case Ring::ObjectType::ContactMethod:
418 case Ring::ObjectType::Call:
419 case Ring::ObjectType::Media:
420 QVariant number_var = idx.data(static_cast<int>(Ring::Role::Number));
421 if (number_var.isValid()) {
422 gchar *number = g_strdup_printf("%s", number_var.value<QString>().toUtf8().constData());
423 GtkWidget *item = gtk_menu_item_new_with_mnemonic(_("_Copy number"));
424 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
425 g_object_set_data_full(G_OBJECT(item), COPY_DATA_KEY, number, (GDestroyNotify)g_free);
426 g_signal_connect(item,
427 "activate",
428 G_CALLBACK(copy_contact_info),
429 NULL);
430 }
431 break;
432 }
433 }
434
435 /* if the object is a CM, then give the option to add to contacts*/
436 if (type.isValid() && object.isValid()) {
437 if (type.value<Ring::ObjectType>() == Ring::ObjectType::ContactMethod) {
438 /* get rectangle */
439 auto path = gtk_tree_model_get_path(model, &iter);
440 auto column = gtk_tree_view_get_column(treeview, 0);
441 GdkRectangle rect;
442 gtk_tree_view_get_cell_area(treeview, path, column, &rect);
443 gtk_tree_view_convert_bin_window_to_widget_coords(treeview, rect.x, rect.y, &rect.x, &rect.y);
444 gtk_tree_path_free(path);
445 auto add_to = menu_item_add_to_contact(object.value<ContactMethod *>(), GTK_WIDGET(treeview), &rect);
446 gtk_menu_shell_append(GTK_MENU_SHELL(menu), add_to);
447 }
448 }
449
450 /* show menu */
451 gtk_widget_show_all(menu);
452 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, NULL, NULL, event->button, event->time);
453
454 return TRUE; /* we handled the event */
455}
456
457static void
458expand_if_child(G_GNUC_UNUSED GtkTreeModel *tree_model,
459 GtkTreePath *path,
460 G_GNUC_UNUSED GtkTreeIter *iter,
461 GtkTreeView *treeview)
462{
463 if (gtk_tree_path_get_depth(path) > 1)
464 gtk_tree_view_expand_to_path(treeview, path);
465}
466
467static void
Stepan Salenikovichec0862f2015-10-16 15:49:42 -0400468scroll_to_selection(GtkTreeSelection *selection)
469{
470 GtkTreeModel *model = nullptr;
471 GtkTreeIter iter;
472 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
473 auto path = gtk_tree_model_get_path(model, &iter);
474 auto treeview = gtk_tree_selection_get_tree_view(selection);
475 gtk_tree_view_scroll_to_cell(treeview, path, nullptr, FALSE, 0.0, 0.0);
476 }
477}
478
479static void
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400480recent_contacts_view_init(RecentContactsView *self)
481{
Stepan Salenikoviche8fa6812015-10-16 15:34:59 -0400482 RecentContactsViewPrivate *priv = RECENT_CONTACTS_VIEW_GET_PRIVATE(self);
483
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400484 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(self), FALSE);
485 /* no need to show the expander since it will always be expanded */
486 gtk_tree_view_set_show_expanders(GTK_TREE_VIEW(self), FALSE);
487 /* disable default search, we will handle it ourselves via LRC;
488 * otherwise the search steals input focus on key presses */
489 gtk_tree_view_set_enable_search(GTK_TREE_VIEW(self), FALSE);
490
491 GtkQSortFilterTreeModel *recent_model = gtk_q_sort_filter_tree_model_new(
492 RecentModel::instance()->peopleProxy(),
493 1,
494 Qt::DisplayRole, G_TYPE_STRING);
495
496 gtk_tree_view_set_model(GTK_TREE_VIEW(self),
497 GTK_TREE_MODEL(recent_model));
498
499 /* photo and name/contact method column */
500 GtkCellArea *area = gtk_cell_area_box_new();
501 GtkTreeViewColumn *column = gtk_tree_view_column_new_with_area(area);
502
503 /* photo renderer */
504 GtkCellRenderer *renderer = gtk_cell_renderer_pixbuf_new();
505 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
506
507 /* get the photo */
508 gtk_tree_view_column_set_cell_data_func(
509 column,
510 renderer,
511 (GtkTreeCellDataFunc)render_contact_photo,
512 NULL,
513 NULL);
514
515 /* name/cm and status renderer */
516 renderer = gtk_cell_renderer_text_new();
517 g_object_set(G_OBJECT(renderer), "ellipsize", PANGO_ELLIPSIZE_END, NULL);
518 gtk_cell_area_box_pack_start(GTK_CELL_AREA_BOX(area), renderer, FALSE, FALSE, FALSE);
519
520 gtk_tree_view_column_set_cell_data_func(
521 column,
522 renderer,
523 (GtkTreeCellDataFunc)render_name_and_info,
524 NULL,
525 NULL);
526
527 gtk_tree_view_append_column(GTK_TREE_VIEW(self), column);
528 gtk_tree_view_column_set_resizable(column, TRUE);
529 gtk_tree_view_column_set_expand(column, TRUE);
530
531 /* call duration column */
532 renderer = gtk_cell_renderer_text_new();
533 column = gtk_tree_view_column_new_with_attributes("Duration", renderer, NULL);
534 gtk_tree_view_append_column(GTK_TREE_VIEW(self), column);
535 gtk_tree_view_column_set_cell_data_func(
536 column,
537 renderer,
538 (GtkTreeCellDataFunc)render_call_duration,
539 NULL,
540 NULL);
541
542 gtk_tree_view_expand_all(GTK_TREE_VIEW(self));
543
544 g_signal_connect(self, "button-press-event", G_CALLBACK(create_popup_menu), NULL);
545 g_signal_connect(self, "row-activated", G_CALLBACK(activate_item), NULL);
546 g_signal_connect(recent_model, "row-inserted", G_CALLBACK(expand_if_child), self);
547
548 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
549 g_signal_connect(selection, "changed", G_CALLBACK(update_call_model_selection), NULL);
Stepan Salenikovichec0862f2015-10-16 15:49:42 -0400550 g_signal_connect(selection, "changed", G_CALLBACK(scroll_to_selection), NULL);
551 g_signal_connect_swapped(recent_model, "rows-reordered", G_CALLBACK(scroll_to_selection), selection);
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400552
Stepan Salenikoviche8fa6812015-10-16 15:34:59 -0400553 /* try to select the same call as the call model */
554 priv->selection_updated = QObject::connect(
555 CallModel::instance()->selectionModel(),
556 &QItemSelectionModel::currentChanged,
557 [self, recent_model](const QModelIndex current, G_GNUC_UNUSED const QModelIndex & previous) {
558 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self));
559
560 /* select the current */
561 auto call = CallModel::instance()->getCall(current);
562 auto recent_idx = RecentModel::instance()->getIndex(call);
563 if (recent_idx.isValid()) {
564 QModelIndex proxy_selection; // the index to select in the peopleProxy
565 if (RecentModel::instance()->rowCount(recent_idx.parent()) > 1) {
566 proxy_selection = RecentModel::instance()->peopleProxy()->mapFromSource(recent_idx);
567 } else {
568 // if single call, select the parent
569 proxy_selection = RecentModel::instance()->peopleProxy()->mapFromSource(recent_idx.parent());
570 }
571
572 GtkTreeIter new_iter;
573 if (gtk_q_sort_filter_tree_model_source_index_to_iter(recent_model, proxy_selection, &new_iter)) {
574 gtk_tree_selection_select_iter(selection, &new_iter);
575 }
576 }
577 }
578 );
579
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400580 gtk_widget_show_all(GTK_WIDGET(self));
581}
582
583static void
584recent_contacts_view_dispose(GObject *object)
585{
Stepan Salenikoviche8fa6812015-10-16 15:34:59 -0400586 RecentContactsView *self = RECENT_CONTACTS_VIEW(object);
587 RecentContactsViewPrivate *priv = RECENT_CONTACTS_VIEW_GET_PRIVATE(self);
588
589 QObject::disconnect(priv->selection_updated);
590
Stepan Salenikovich2f8b4492015-09-21 17:10:36 -0400591 G_OBJECT_CLASS(recent_contacts_view_parent_class)->dispose(object);
592}
593
594static void
595recent_contacts_view_finalize(GObject *object)
596{
597 G_OBJECT_CLASS(recent_contacts_view_parent_class)->finalize(object);
598}
599
600static void
601recent_contacts_view_class_init(RecentContactsViewClass *klass)
602{
603 G_OBJECT_CLASS(klass)->finalize = recent_contacts_view_finalize;
604 G_OBJECT_CLASS(klass)->dispose = recent_contacts_view_dispose;
605}
606
607GtkWidget *
608recent_contacts_view_new()
609{
610 gpointer self = g_object_new(RECENT_CONTACTS_VIEW_TYPE, NULL);
611
612 return (GtkWidget *)self;
613}