diff --git a/src/chatview.cpp b/src/chatview.cpp
index 87b6c13e7e6b3973240347769d1d2c437f833480..75a1ccc31a66d2f5ffa2b266e8bd66e07bfd4cc0 100644
--- a/src/chatview.cpp
+++ b/src/chatview.cpp
@@ -478,3 +478,30 @@ chat_view_new_person(Person *p)
 
     return (GtkWidget *)self;
 }
+
+Call*
+chat_view_get_call(ChatView *self)
+{
+    g_return_val_if_fail(IS_CHAT_VIEW(self), nullptr);
+    auto priv = CHAT_VIEW_GET_PRIVATE(self);
+
+    return priv->call;
+}
+
+ContactMethod*
+chat_view_get_cm(ChatView *self)
+{
+    g_return_val_if_fail(IS_CHAT_VIEW(self), nullptr);
+    auto priv = CHAT_VIEW_GET_PRIVATE(self);
+
+    return priv->cm;
+}
+
+Person*
+chat_view_get_person(ChatView *self)
+{
+    g_return_val_if_fail(IS_CHAT_VIEW(self), nullptr);
+    auto priv = CHAT_VIEW_GET_PRIVATE(self);
+
+    return priv->person;
+}
diff --git a/src/chatview.h b/src/chatview.h
index 0710601df6bd80083f5861a5837accea117bb379..f7685cc74c2180481dd736a65e5e19ae2fca08ac 100644
--- a/src/chatview.h
+++ b/src/chatview.h
@@ -38,10 +38,13 @@ typedef struct _ChatView      ChatView;
 typedef struct _ChatViewClass ChatViewClass;
 
 
-GType      chat_view_get_type   (void) G_GNUC_CONST;
-GtkWidget *chat_view_new_call   (Call* call);
-GtkWidget *chat_view_new_cm     (ContactMethod* cm);
-GtkWidget *chat_view_new_person (Person* p);
+GType          chat_view_get_type   (void) G_GNUC_CONST;
+GtkWidget     *chat_view_new_call   (Call*);
+GtkWidget     *chat_view_new_cm     (ContactMethod*);
+GtkWidget     *chat_view_new_person (Person* p);
+Call          *chat_view_get_call   (ChatView*);
+ContactMethod *chat_view_get_cm     (ChatView*);
+Person        *chat_view_get_person (ChatView*);
 
 G_END_DECLS
 
diff --git a/src/currentcallview.cpp b/src/currentcallview.cpp
index 7a613936acd619ebcde0ee2e5a30b82871154ae1..e928027788bd9264f4af594d98b986721798bde6 100644
--- a/src/currentcallview.cpp
+++ b/src/currentcallview.cpp
@@ -525,12 +525,6 @@ current_call_view_class_init(CurrentCallViewClass *klass)
         G_TYPE_NONE, 0);
 }
 
-GtkWidget *
-current_call_view_new(void)
-{
-    return (GtkWidget *)g_object_new(CURRENT_CALL_VIEW_TYPE, NULL);
-}
-
 static void
 update_state(CurrentCallView *view, Call *call)
 {
@@ -639,11 +633,11 @@ toggle_smartinfo(GSimpleAction* action, G_GNUC_UNUSED GVariant* state, GtkWidget
     }
 }
 
-void
-current_call_view_set_call_info(CurrentCallView *view, const QModelIndex& idx) {
+static void
+set_call_info(CurrentCallView *view, Call *call) {
     CurrentCallViewPrivate *priv = CURRENT_CALL_VIEW_GET_PRIVATE(view);
 
-    priv->call = CallModel::instance().getCall(idx);
+    priv->call = call;
 
     /* get call image */
     QVariant var_i = GlobalInstances::pixmapManipulator().callPhoto(priv->call, QSize(60, 60), false);
@@ -651,15 +645,15 @@ current_call_view_set_call_info(CurrentCallView *view, const QModelIndex& idx) {
     gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_peer), image.get());
 
     /* get name */
-    auto name = idx.model()->data(idx, static_cast<int>(Ring::Role::Name));
-    gtk_label_set_text(GTK_LABEL(priv->label_name), name.toString().toUtf8().constData());
+    auto name = call->formattedName();
+    gtk_label_set_text(GTK_LABEL(priv->label_name), name.toUtf8().constData());
 
     /* get uri, if different from name */
-    auto uri = idx.model()->data(idx, static_cast<int>(Ring::Role::Number));
-    if (name.toString() != uri.toString()) {
+    auto uri = call->peerContactMethod()->uri();
+    if (name != uri) {
         auto cat_uri = g_strdup_printf("(%s) %s"
                                        ,priv->call->peerContactMethod()->category()->name().toUtf8().constData()
-                                       ,uri.toString().toUtf8().constData());
+                                       ,uri.toUtf8().constData());
         gtk_label_set_text(GTK_LABEL(priv->label_uri), cat_uri);
         g_free(cat_uri);
         gtk_widget_show(priv->label_uri);
@@ -765,3 +759,21 @@ current_call_view_set_call_info(CurrentCallView *view, const QModelIndex& idx) {
     /* show chat view on any new incoming messages */
     g_signal_connect_swapped(chat_view, "new-messages-displayed", G_CALLBACK(show_chat_view), view);
 }
+
+GtkWidget *
+current_call_view_new(Call *call)
+{
+    auto self = g_object_new(CURRENT_CALL_VIEW_TYPE, NULL);
+    set_call_info(CURRENT_CALL_VIEW(self), call);
+
+    return GTK_WIDGET(self);
+}
+
+Call*
+current_call_view_get_call(CurrentCallView *self)
+{
+    g_return_val_if_fail(IS_CURRENT_CALL_VIEW(self), nullptr);
+    auto priv = CURRENT_CALL_VIEW_GET_PRIVATE(self);
+
+    return priv->call;
+}
diff --git a/src/currentcallview.h b/src/currentcallview.h
index 686da4359815a3d581eae3c8df01b42bfa3ed4ce..d3c041704f880ff6f84d9540fe12ea384d36be94 100644
--- a/src/currentcallview.h
+++ b/src/currentcallview.h
@@ -22,7 +22,7 @@
 
 #include <gtk/gtk.h>
 
-class QModelIndex;
+class Call;
 
 G_BEGIN_DECLS
 
@@ -37,8 +37,8 @@ typedef struct _CurrentCallViewClass CurrentCallViewClass;
 
 
 GType      current_call_view_get_type      (void) G_GNUC_CONST;
-GtkWidget *current_call_view_new           (void);
-void       current_call_view_set_call_info (CurrentCallView *view, const QModelIndex& idx);
+GtkWidget *current_call_view_new           (Call*);
+Call      *current_call_view_get_call      (CurrentCallView*);
 
 G_END_DECLS
 
diff --git a/src/incomingcallview.cpp b/src/incomingcallview.cpp
index b7e0812e4b9191f0707886a801847956738f17ed..42913e735394b160dcda1f2056015c94ca6d8b18 100644
--- a/src/incomingcallview.cpp
+++ b/src/incomingcallview.cpp
@@ -54,6 +54,8 @@ struct _IncomingCallViewPrivate
     GtkWidget *button_reject_incoming;
     GtkWidget *button_end_call;
 
+    Call *call;
+
     QMetaObject::Connection state_change_connection;
 };
 
@@ -101,12 +103,6 @@ incoming_call_view_class_init(IncomingCallViewClass *klass)
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), IncomingCallView, button_end_call);
 }
 
-GtkWidget *
-incoming_call_view_new(void)
-{
-    return (GtkWidget *)g_object_new(INCOMING_CALL_VIEW_TYPE, NULL);
-}
-
 static void
 update_state(IncomingCallView *view, Call *call)
 {
@@ -160,11 +156,11 @@ update_state(IncomingCallView *view, Call *call)
     }
 }
 
-void
-incoming_call_view_set_call_info(IncomingCallView *view, const QModelIndex& idx) {
+static void
+set_call_info(IncomingCallView *view, Call *call) {
     IncomingCallViewPrivate *priv = INCOMING_CALL_VIEW_GET_PRIVATE(view);
 
-    Call *call = CallModel::instance().getCall(idx);
+    priv->call = call;
 
     /* get call image */
     QVariant var_i = GlobalInstances::pixmapManipulator().callPhoto(call, QSize(110, 110), false);
@@ -172,15 +168,15 @@ incoming_call_view_set_call_info(IncomingCallView *view, const QModelIndex& idx)
     gtk_image_set_from_pixbuf(GTK_IMAGE(priv->image_incoming), image.get());
 
     /* get name */
-    auto name = idx.model()->data(idx, static_cast<int>(Ring::Role::Name));
-    gtk_label_set_text(GTK_LABEL(priv->label_name), name.toString().toUtf8().constData());
+    auto name = call->formattedName();
+    gtk_label_set_text(GTK_LABEL(priv->label_name), name.toUtf8().constData());
 
     /* get uri, if different from name */
-    auto uri = idx.model()->data(idx, static_cast<int>(Ring::Role::Number));
-    if (name.toString() != uri.toString()) {
+    auto uri = call->peerContactMethod()->uri();
+    if (name != uri) {
         auto cat_uri = g_strdup_printf("(%s) %s"
                                        ,call->peerContactMethod()->category()->name().toUtf8().constData()
-                                       ,uri.toString().toUtf8().constData());
+                                       ,uri.toUtf8().constData());
         gtk_label_set_text(GTK_LABEL(priv->label_uri), cat_uri);
         g_free(cat_uri);
         gtk_widget_show(priv->label_uri);
@@ -195,3 +191,21 @@ incoming_call_view_set_call_info(IncomingCallView *view, const QModelIndex& idx)
         [=]() { update_state(view, call); }
     );
 }
+
+GtkWidget *
+incoming_call_view_new(Call *call)
+{
+    auto self = g_object_new(INCOMING_CALL_VIEW_TYPE, NULL);
+    set_call_info(INCOMING_CALL_VIEW(self), call);
+
+    return GTK_WIDGET(self);
+}
+
+Call*
+incoming_call_view_get_call(IncomingCallView *self)
+{
+    g_return_val_if_fail(IS_INCOMING_CALL_VIEW(self), nullptr);
+    auto priv = INCOMING_CALL_VIEW_GET_PRIVATE(self);
+
+    return priv->call;
+}
diff --git a/src/incomingcallview.h b/src/incomingcallview.h
index ad09d7acc5721e2257db184a380be9d3fdb30900..0a0ba5c1982a55b76e25a5c2686307ac78b9d9b5 100644
--- a/src/incomingcallview.h
+++ b/src/incomingcallview.h
@@ -22,7 +22,7 @@
 
 #include <gtk/gtk.h>
 
-class QModelIndex;
+class Call;
 
 G_BEGIN_DECLS
 
@@ -37,8 +37,8 @@ typedef struct _IncomingCallViewClass IncomingCallViewClass;
 
 
 GType      incoming_call_view_get_type      (void) G_GNUC_CONST;
-GtkWidget *incoming_call_view_new           (void);
-void       incoming_call_view_set_call_info (IncomingCallView *view, const QModelIndex& idx);
+GtkWidget *incoming_call_view_new           (Call*);
+Call      *incoming_call_view_get_call      (IncomingCallView*);
 
 G_END_DECLS
 
diff --git a/src/ringmainwindow.cpp b/src/ringmainwindow.cpp
index 41a6629fea8c932054fbd12b76441d4b74465753..3b979ceaa7ef7ef56f8e657cad919b63a9137361 100644
--- a/src/ringmainwindow.cpp
+++ b/src/ringmainwindow.cpp
@@ -98,8 +98,11 @@ struct _RingMainWindowPrivate
     GtkWidget *image_settings;
     GtkWidget *hbox_settings;
     GtkWidget *scrolled_window_smartview;
+    GtkWidget *treeview_conversations;
     GtkWidget *scrolled_window_contacts;
+    GtkWidget *treeview_contacts;
     GtkWidget *scrolled_window_history;
+    GtkWidget *treeview_history;
     GtkWidget *vbox_left_pane;
     GtkWidget *search_entry;
     GtkWidget *stack_main_view;
@@ -115,7 +118,8 @@ struct _RingMainWindowPrivate
     GtkWidget *radiobutton_media_settings;
     GtkWidget *radiobutton_account_settings;
 
-    QMetaObject::Connection selection_updated;
+    QMetaObject::Connection selected_item_changed;
+    QMetaObject::Connection selected_call_over;
 
     gboolean   show_settings;
 
@@ -187,167 +191,253 @@ video_double_clicked(G_GNUC_UNUSED CurrentCallView *view, RingMainWindow *self)
 static void
 hide_view_clicked(G_GNUC_UNUSED GtkWidget *view, RingMainWindow *self)
 {
-    g_return_if_fail(IS_RING_MAIN_WINDOW(self));
+    auto priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self));
 
     /* clear selection */
-    RecentModel::instance().selectionModel()->clear();
+    auto selection_conversations = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_conversations));
+    gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_conversations));
+    auto selection_contacts = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_contacts));
+    gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_contacts));
+    auto selection_history = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_history));
+    gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_history));
 }
 
 /**
- * This takes the RecentModel index as the argument and displays the corresponding view:
+ * This function determines which view to display in the right panel of the main window based on
+ * which item is selected in one of the 3 contact list views (Conversations, Contacts, History). The
+ * possible views ares:
  * - incoming call view
  * - current call view
  * - chat view
- * - welcome view (if no index is selected)
+ * - welcome view (if no valid item is selected)
+ *
+ * There should never be a conflict of which item should be displayed (ie: which item is selected),
+ * as there should only ever be one selection at a time in the 3 views except in the case that the
+ * same item is selected in more than one view (see the compare_treeview_selection() function).
+ *
+ * This function could be called from a g_idle source, so it returns a boolean to remove itself from
+ * being called again. The boolean doesn't reflect success nor failure.
  */
-static void
-selection_changed(const QModelIndex& recent_idx, RingMainWindow *win)
+static gboolean
+selection_changed(RingMainWindow *win)
 {
     // g_debug("selection changed");
-    g_return_if_fail(IS_RING_MAIN_WINDOW(win));
-    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
 
-    /* if we're showing the settings, then nothing needs to be done as the call
-       view is not shown */
-    if (priv->show_settings) return;
+    g_return_val_if_fail(IS_RING_MAIN_WINDOW(win), G_SOURCE_REMOVE);
+    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
 
-    /* get the current visible stack child */
-    GtkWidget *old_call_view = gtk_bin_get_child(GTK_BIN(priv->frame_call));
+    auto old_view = gtk_bin_get_child(GTK_BIN(priv->frame_call));
+    GtkWidget *new_view = nullptr;
+
+    /* if we're showing the settings, then we need to make sure to remove any possible ongoing call
+     * view so that we don't have more than one VideoWidget at a time */
+    if (priv->show_settings) {
+        if (old_view != priv->welcome_view) {
+            leave_full_screen(win);
+            gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_view);
+            gtk_container_add(GTK_CONTAINER(priv->frame_call), priv->welcome_view);
+            gtk_widget_show(priv->welcome_view);
+        }
+        return G_SOURCE_REMOVE;
+    }
 
-    /* make sure we leave full screen, since the call selection is changing */
-    leave_full_screen(win);
+    /* there should only be one item selected at a time, get which one is selected */
+    auto selection_conversations = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_conversations));
+    auto selection_contacts = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_contacts));
+    auto selection_history = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_history));
+
+    GtkTreeModel *model = nullptr;
+    GtkTreeIter iter;
+    QModelIndex idx;
+
+    if (gtk_tree_selection_get_selected(selection_conversations, &model, &iter)) {
+        idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(model), &iter);
+    } else if (gtk_tree_selection_get_selected(selection_contacts, &model, &iter)) {
+        idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(model), &iter);
+    } else if (gtk_tree_selection_get_selected(selection_history, &model, &iter)) {
+        idx = gtk_q_sort_filter_tree_model_get_source_idx(GTK_Q_SORT_FILTER_TREE_MODEL(model), &iter);
+    }
 
     /* check which object type is selected */
-    auto type = recent_idx.data(static_cast<int>(Ring::Role::ObjectType)).value<Ring::ObjectType>();
-    auto object = recent_idx.data(static_cast<int>(Ring::Role::Object));
-    /* try to get the call model index, in case its a call, since we're still using the CallModel as well */
-    auto call_idx = CallModel::instance().getIndex(RecentModel::instance().getActiveCall(recent_idx));
-
-    /* we prioritize showing the call view */
-    if (call_idx.isValid()) {
-        /* show the call view */
-        QVariant state =  call_idx.data(static_cast<int>(Call::Role::LifeCycleState));
-        GtkWidget *new_call_view = NULL;
-
-        switch(state.value<Call::LifeCycleState>()) {
-            case Call::LifeCycleState::CREATION:
-            case Call::LifeCycleState::INITIALIZATION:
-            case Call::LifeCycleState::FINISHED:
-                new_call_view = incoming_call_view_new();
-                incoming_call_view_set_call_info(INCOMING_CALL_VIEW(new_call_view), call_idx);
-                break;
-            case Call::LifeCycleState::PROGRESS:
-                new_call_view = current_call_view_new();
-                g_signal_connect(new_call_view, "video-double-clicked", G_CALLBACK(video_double_clicked), win);
-                current_call_view_set_call_info(CURRENT_CALL_VIEW(new_call_view), call_idx);
-                break;
-            case Call::LifeCycleState::COUNT__:
-                g_warning("LifeCycleState should never be COUNT");
-                break;
-        }
+    auto type = idx.data(static_cast<int>(Ring::Role::ObjectType));
+    auto object = idx.data(static_cast<int>(Ring::Role::Object));
 
-        gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_call_view);
-        gtk_container_add(GTK_CONTAINER(priv->frame_call), new_call_view);
-        gtk_widget_show(new_call_view);
-    } else if (type == Ring::ObjectType::Person && object.isValid()) {
-        /* show chat view constructed from Person object */
-        auto new_chat_view = chat_view_new_person(object.value<Person *>());
-        g_signal_connect(new_chat_view, "hide-view-clicked", G_CALLBACK(hide_view_clicked), win);
-        gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_call_view);
-        gtk_container_add(GTK_CONTAINER(priv->frame_call), new_chat_view);
-        gtk_widget_show(new_chat_view);
-    } else if (type == Ring::ObjectType::ContactMethod && object.isValid()) {
-        /* show chat view constructed from CM */
-        auto new_chat_view = chat_view_new_cm(object.value<ContactMethod *>());
-        g_signal_connect(new_chat_view, "hide-view-clicked", G_CALLBACK(hide_view_clicked), win);
-        gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_call_view);
-        gtk_container_add(GTK_CONTAINER(priv->frame_call), new_chat_view);
-        gtk_widget_show(new_chat_view);
-    } else {
-        /* nothing selected that we can display, show the welcome view */
-        gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_call_view);
-        gtk_container_add(GTK_CONTAINER(priv->frame_call), priv->welcome_view);
-    }
-}
+    if (idx.isValid() && type.isValid() && object.isValid()) {
 
-static gboolean
-selected_item_changed(RingMainWindow *win)
-{
-    // g_debug("item changed");
-    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
+        /* we prioritize the views in the following order:
+         *  - call view (if there is an ongoing call associated with the item)
+         *  - chat view built from Person
+         *  - chat view built from ContactMethod
+         */
 
-    /* if we're showing the settings, then nothing needs to be done as the call
-       view is not shown */
-    if (priv->show_settings) return G_SOURCE_REMOVE;
-
-    auto idx_selected = RecentModel::instance().selectionModel()->currentIndex();
-
-    /* we prioritize showing the call view; but if the call is over we go back to showing the chat view */
-    if(auto call = RecentModel::instance().getActiveCall(idx_selected)) {
-        /* check if we need to change the view */
-        auto current_view = gtk_bin_get_child(GTK_BIN(priv->frame_call));
-        auto state = call->lifeCycleState();
-
-        /* check what the current state is vs what is displayed */
-        switch(state) {
-            case Call::LifeCycleState::CREATION:
-            case Call::LifeCycleState::FINISHED:
-            /* go back to incoming call view;
-             * it will show that the call failed and offer to hang it up */
-            case Call::LifeCycleState::INITIALIZATION:
-                {
-                    /* show the incoming call view */
-                    if (!IS_INCOMING_CALL_VIEW(current_view)) {
-                        auto new_view = incoming_call_view_new();
-                        incoming_call_view_set_call_info(INCOMING_CALL_VIEW(new_view), CallModel::instance().getIndex(call));
-                        gtk_container_remove(GTK_CONTAINER(priv->frame_call), current_view);
-                        gtk_container_add(GTK_CONTAINER(priv->frame_call), new_view);
-                        gtk_widget_show(new_view);
-                    }
+        Person *person = nullptr;
+        ContactMethod *cm = nullptr;
+        Call *call = nullptr;
+
+        /* use the RecentModel to see if there are any ongoing calls associated with the selected item */
+        switch(type.value<Ring::ObjectType>()) {
+            case Ring::ObjectType::Person:
+            {
+                person = object.value<Person *>();
+                auto recent_idx = RecentModel::instance().getIndex(person);
+                if (recent_idx.isValid()) {
+                    call = RecentModel::instance().getActiveCall(recent_idx);
                 }
-                break;
-            case Call::LifeCycleState::PROGRESS:
-                {
-                    /* show the current call view */
-                    if (!IS_CURRENT_CALL_VIEW(current_view)) {
-                        auto new_view = current_call_view_new();
-                        g_signal_connect(new_view, "video-double-clicked", G_CALLBACK(video_double_clicked), win);
-                        current_call_view_set_call_info(CURRENT_CALL_VIEW(new_view), CallModel::instance().getIndex(call));
-                        gtk_container_remove(GTK_CONTAINER(priv->frame_call), current_view);
-                        gtk_container_add(GTK_CONTAINER(priv->frame_call), new_view);
-                        gtk_widget_show(new_view);
+            }
+            break;
+            case Ring::ObjectType::ContactMethod:
+            {
+                cm = object.value<ContactMethod *>();
+                auto recent_idx = RecentModel::instance().getIndex(cm);
+                if (recent_idx.isValid()) {
+                    call = RecentModel::instance().getActiveCall(recent_idx);
+                }
+            }
+            break;
+            case Ring::ObjectType::Call:
+            {
+                /* if the call is over (eg: if it comes from the history model) then we first check
+                 * if there is an ongoing call with the same ContactMethod, otherwise we need to get
+                 * the associated Person or ContactMethod */
+                call = object.value<Call *>();
+                if (call->isHistory()) {
+                    cm = call->peerContactMethod();
+                    call = nullptr;
+                    if (cm) {
+                        auto recent_idx = RecentModel::instance().getIndex(cm);
+                        if (recent_idx.isValid()) {
+                            call = RecentModel::instance().getActiveCall(recent_idx);
+                        }
+                        person = cm->contact();
                     }
                 }
-                break;
-            case Call::LifeCycleState::COUNT__:
-                g_warning("LifeCycleState should never be COUNT");
-                break;
+            }
+            break;
+            case Ring::ObjectType::Media:
+            case Ring::ObjectType::Certificate:
+            case Ring::ObjectType::COUNT__:
+            // nothing to do
+            break;
         }
-    } else if (idx_selected.isValid()) {
-        /* otherwise, the call is over and is already removed from the RecentModel */
-        auto current_view = gtk_bin_get_child(GTK_BIN(priv->frame_call));
-        leave_full_screen(win);
 
-        /* show the chat view */
-        if (!IS_CHAT_VIEW(current_view)) {
-            auto type = idx_selected.data(static_cast<int>(Ring::Role::ObjectType)).value<Ring::ObjectType>();
-            auto object = idx_selected.data(static_cast<int>(Ring::Role::Object));
-            if (type == Ring::ObjectType::Person && object.isValid()) {
-                /* show chat view constructed from Person object */
-                auto new_view = chat_view_new_person(object.value<Person *>());
+        /* we prioritize showing the call view */
+        if (call) {
+            auto state = call->lifeCycleState();
+
+            Call *current_call = nullptr;
+
+            switch(state) {
+                case Call::LifeCycleState::CREATION:
+                case Call::LifeCycleState::INITIALIZATION:
+                case Call::LifeCycleState::FINISHED:
+                    // check if we're already displaying this call
+                    if (IS_INCOMING_CALL_VIEW(old_view))
+                        current_call = incoming_call_view_get_call(INCOMING_CALL_VIEW(old_view));
+                    if (current_call != call)
+                        new_view = incoming_call_view_new(call);
+                    break;
+                case Call::LifeCycleState::PROGRESS:
+                    // check if we're already displaying this call
+                    if (IS_CURRENT_CALL_VIEW(old_view))
+                        current_call = current_call_view_get_call(CURRENT_CALL_VIEW(old_view));
+                    if (current_call != call) {
+                        new_view = current_call_view_new(call);
+                        g_signal_connect(new_view, "video-double-clicked", G_CALLBACK(video_double_clicked), win);
+                    }
+                    break;
+                case Call::LifeCycleState::COUNT__:
+                    g_warning("LifeCycleState should never be COUNT");
+                    break;
+            }
+
+            if (new_view) {
+                /* connect to the call's lifeCycleStateChanged signal to know when to change the view */
+                QObject::disconnect(priv->selected_item_changed);
+                priv->selected_item_changed = QObject::connect(
+                    call,
+                    &Call::lifeCycleStateChanged,
+                    [win] (Call::LifeCycleState, Call::LifeCycleState)
+                    {g_idle_add((GSourceFunc)selection_changed, win);}
+                );
+                /* we want to also change the view when the call is over */
+                QObject::disconnect(priv->selected_call_over);
+                priv->selected_call_over = QObject::connect(
+                    call,
+                    &Call::isOver,
+                    [win] ()
+                    {g_idle_add((GSourceFunc)selection_changed, win);}
+                );
+            }
+
+        } else if (person) {
+            /* show chat view constructed from Person object */
+
+            // check if we're already displaying the chat view for this person
+            Person *current_person = nullptr;
+            if (IS_CHAT_VIEW(old_view))
+                current_person = chat_view_get_person(CHAT_VIEW(old_view));
+
+            if (current_person != person) {
+                new_view = chat_view_new_person(person);
                 g_signal_connect(new_view, "hide-view-clicked", G_CALLBACK(hide_view_clicked), win);
-                gtk_container_remove(GTK_CONTAINER(priv->frame_call), current_view);
-                gtk_container_add(GTK_CONTAINER(priv->frame_call), new_view);
-                gtk_widget_show(new_view);
-            } else if (type == Ring::ObjectType::ContactMethod && object.isValid()) {
-                /* show chat view constructed from CM */
-                auto new_view = chat_view_new_cm(object.value<ContactMethod *>());
+
+                /* connect to the Person's callAdded signal, because we want to switch to the call view
+                 * in this case */
+                QObject::disconnect(priv->selected_item_changed);
+                QObject::disconnect(priv->selected_call_over);
+                priv->selected_item_changed = QObject::connect(
+                    person,
+                    &Person::callAdded,
+                    [win] (Call*)
+                    {g_idle_add((GSourceFunc)selection_changed, win);}
+                );
+            }
+        } else if (cm) {
+            /* show chat view constructed from CM */
+
+            // check if we're already displaying the chat view for this cm
+            ContactMethod *current_cm = nullptr;
+            if (IS_CHAT_VIEW(old_view))
+                current_cm = chat_view_get_cm(CHAT_VIEW(old_view));
+
+            if (current_cm != cm) {
+                new_view = chat_view_new_cm(cm);
                 g_signal_connect(new_view, "hide-view-clicked", G_CALLBACK(hide_view_clicked), win);
-                gtk_container_remove(GTK_CONTAINER(priv->frame_call), current_view);
-                gtk_container_add(GTK_CONTAINER(priv->frame_call), new_view);
-                gtk_widget_show(new_view);
+
+                /* connect to the ContactMethod's callAdded signal, because we want to switch to the
+                 *call view in this case */
+                QObject::disconnect(priv->selected_item_changed);
+                QObject::disconnect(priv->selected_call_over);
+                priv->selected_item_changed = QObject::connect(
+                    cm,
+                    &ContactMethod::callAdded,
+                    [win] (Call*)
+                    {g_idle_add((GSourceFunc)selection_changed, win);}
+                );
+            }
+        } else {
+            /* not a supported object type, display the welcome view */
+            if (old_view != priv->welcome_view) {
+                QObject::disconnect(priv->selected_item_changed);
+                QObject::disconnect(priv->selected_call_over);
+                new_view = priv->welcome_view;
             }
         }
+    } else {
+        /* selection isn't valid, display the welcome view */
+        if (old_view != priv->welcome_view) {
+            QObject::disconnect(priv->selected_item_changed);
+            QObject::disconnect(priv->selected_call_over);
+            new_view = priv->welcome_view;
+        }
+    }
+
+    if (new_view) {
+        /* make sure we leave full screen, since the view is changing */
+        leave_full_screen(win);
+        gtk_container_remove(GTK_CONTAINER(priv->frame_call), old_view);
+        gtk_container_add(GTK_CONTAINER(priv->frame_call), new_view);
+        gtk_widget_show(new_view);
     }
 
     return G_SOURCE_REMOVE;
@@ -395,9 +485,10 @@ settings_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
     /* check which view to show */
     if (!priv->show_settings) {
         /* show the settings */
+        priv->show_settings = TRUE;
 
         /* make sure we are not showing a call view so we don't have more than one clutter stage at a time */
-        selection_changed(QModelIndex(), win);
+        selection_changed(win);
 
         /* show settings */
         gtk_image_set_from_icon_name(GTK_IMAGE(priv->image_settings), "emblem-ok-symbolic", GTK_ICON_SIZE_SMALL_TOOLBAR);
@@ -414,8 +505,6 @@ settings_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
 
         gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_UP);
         gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), priv->last_settings_view);
-
-        priv->show_settings = TRUE;
     } else {
         /* hide the settings */
         priv->show_settings = FALSE;
@@ -443,7 +532,7 @@ settings_clicked(G_GNUC_UNUSED GtkButton *button, RingMainWindow *win)
         gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), CALL_VIEW_NAME);
 
         /* show the view which was selected previously */
-        selection_changed(RecentModel::instance().selectionModel()->currentIndex(), win);
+        selection_changed(win);
     }
 }
 
@@ -838,6 +927,157 @@ dtmf_pressed(RingMainWindow *win,
     return GDK_EVENT_PROPAGATE;
 }
 
+/**
+ * This function determines if two different contact list views (Conversations, Contacts, History)
+ * have the same item selected. Note that the same item does not necessarily mean the same object.
+ * For example, if the history view has a Call selected and the Conversations view has a Person
+ * selected which is associated with the ContactMethod to which that Call was made, then this will
+ * evaluate to TRUE.
+ */
+static gboolean
+compare_treeview_selection(GtkTreeSelection *selection1, GtkTreeSelection *selection2)
+{
+    g_return_val_if_fail(selection1 && selection2, FALSE);
+
+    if (selection1 == selection2) return TRUE;
+
+    auto idx1 = get_index_from_selection(selection1);
+    auto type1 = idx1.data(static_cast<int>(Ring::Role::ObjectType));
+    auto object1 = idx1.data(static_cast<int>(Ring::Role::Object));
+
+    auto idx2 = get_index_from_selection(selection2);
+    auto type2 = idx2.data(static_cast<int>(Ring::Role::ObjectType));
+    auto object2 = idx2.data(static_cast<int>(Ring::Role::Object));
+
+    if (idx1.isValid() && type1.isValid() && object1.isValid() && idx2.isValid() && type2.isValid() && object2.isValid()) {
+        Call *call1 = nullptr;
+        ContactMethod *cm1 = nullptr;
+        Person *person1 = nullptr;
+
+        Call *call2 = nullptr;
+        ContactMethod *cm2 = nullptr;
+        Person *person2 = nullptr;
+
+        switch(type1.value<Ring::ObjectType>()) {
+            case Ring::ObjectType::Person:
+                person1 = object1.value<Person *>();
+                break;
+            case Ring::ObjectType::ContactMethod:
+                cm1 = object1.value<ContactMethod *>();
+                person1 = cm1->contact();
+                break;
+            case Ring::ObjectType::Call:
+                call1 = object1.value<Call *>();
+                cm1 = call1->peerContactMethod();
+                person1 = cm1->contact();
+                break;
+            case Ring::ObjectType::Media:
+            case Ring::ObjectType::Certificate:
+            case Ring::ObjectType::COUNT__:
+            // nothing to do
+            break;
+        }
+
+        switch(type2.value<Ring::ObjectType>()) {
+            case Ring::ObjectType::Person:
+                person2 = object2.value<Person *>();
+                break;
+            case Ring::ObjectType::ContactMethod:
+                cm2 = object2.value<ContactMethod *>();
+                person2 = cm2->contact();
+                break;
+            case Ring::ObjectType::Call:
+                call2 = object2.value<Call *>();
+                cm2 = call2->peerContactMethod();
+                person2 = cm2->contact();
+                break;
+            case Ring::ObjectType::Media:
+            case Ring::ObjectType::Certificate:
+            case Ring::ObjectType::COUNT__:
+            // nothing to do
+            break;
+        }
+
+        if (person1 != nullptr && person1 == person2)
+            return TRUE;
+        if (cm1 != nullptr && cm1 == cm2)
+            return TRUE;
+        if (call1 != nullptr && call1 == call2)
+            return TRUE;
+    }
+
+    return FALSE;
+}
+
+static void
+conversation_selection_changed(GtkTreeSelection *selection, RingMainWindow *self)
+{
+    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
+    GtkTreeIter iter;
+    GtkTreeModel *model = nullptr;
+    if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
+        /* something is selected in the conversations view, clear the selection in the other views;
+         * unless the current selection is of the same item; we don't try to match the selection in
+         * all views since not all items exist in all 3 contact list views
+         */
+
+        auto selection_contacts = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_contacts));
+        if (!compare_treeview_selection(selection, selection_contacts))
+            gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_contacts));
+
+        auto selection_history = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_history));
+        if (!compare_treeview_selection(selection, selection_history))
+            gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_history));
+    }
+
+    selection_changed(self);
+}
+
+static void
+contact_selection_changed(GtkTreeSelection *selection, RingMainWindow *self)
+{
+    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
+    GtkTreeIter iter;
+    if (gtk_tree_selection_get_selected(selection, nullptr, &iter)) {
+        /* something is selected in the contacts view, clear the selection in the other views;
+         * unless the current selection is of the same item; we don't try to match the selection in
+         * all views since not all items exist in all 3 contact list views
+         */
+        auto selection_conversations = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_conversations));
+        if (!compare_treeview_selection(selection, selection_conversations))
+            gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_conversations));
+
+        auto selection_history = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_history));
+        if (!compare_treeview_selection(selection, selection_history))
+            gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_history));
+    }
+
+    selection_changed(self);
+}
+
+static void
+history_selection_changed(GtkTreeSelection *selection, RingMainWindow *self)
+{
+    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
+    GtkTreeIter iter;
+    if (gtk_tree_selection_get_selected(selection, nullptr, &iter)) {
+        /* something is selected in the history view, clear the selection in the other views;
+         * unless the current selection is of the same item; we don't try to match the selection in
+         * all views since not all items exist in all 3 contact list views
+         */
+
+        auto selection_conversations = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_conversations));
+        if (!compare_treeview_selection(selection, selection_conversations))
+            gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_conversations));
+
+        auto selection_contacts = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_contacts));
+        if (!compare_treeview_selection(selection, selection_contacts))
+            gtk_tree_selection_unselect_all(GTK_TREE_SELECTION(selection_contacts));
+    }
+
+    selection_changed(self);
+}
+
 static void
 ring_main_window_init(RingMainWindow *win)
 {
@@ -907,51 +1147,29 @@ ring_main_window_init(RingMainWindow *win)
     g_signal_connect(priv->radiobutton_general_settings, "toggled", G_CALLBACK(show_general_settings), win);
 
     /* populate the notebook */
-    auto smart_view = recent_contacts_view_new();
-    gtk_container_add(GTK_CONTAINER(priv->scrolled_window_smartview), smart_view);
+    priv->treeview_conversations = recent_contacts_view_new();
+    gtk_container_add(GTK_CONTAINER(priv->scrolled_window_smartview), priv->treeview_conversations);
 
-    auto contacts_view = contacts_view_new();
-    gtk_container_add(GTK_CONTAINER(priv->scrolled_window_contacts), contacts_view);
+    priv->treeview_contacts = contacts_view_new();
+    gtk_container_add(GTK_CONTAINER(priv->scrolled_window_contacts), priv->treeview_contacts);
 
-    auto history_view = history_view_new();
-    gtk_container_add(GTK_CONTAINER(priv->scrolled_window_history), history_view);
+    priv->treeview_history = history_view_new();
+    gtk_container_add(GTK_CONTAINER(priv->scrolled_window_history), priv->treeview_history);
 
     /* welcome/default view */
     priv->welcome_view = ring_welcome_view_new();
-    g_object_ref(priv->welcome_view);
-    // gtk_stack_add_named(GTK_STACK(priv->stack_call_view), welcome_view, DEFAULT_VIEW_NAME);
+    g_object_ref(priv->welcome_view); // increase ref because don't want it to be destroyed when not displayed
     gtk_container_add(GTK_CONTAINER(priv->frame_call), priv->welcome_view);
     gtk_widget_show(priv->welcome_view);
 
-    /* call/chat selection */
-    QObject::connect(
-       RecentModel::instance().selectionModel(),
-       &QItemSelectionModel::currentChanged,
-       [win](const QModelIndex current, G_GNUC_UNUSED const QModelIndex & previous) {
-            if (auto call = RecentModel::instance().getActiveCall(current)) {
-                /* if the call is on hold, we want to put it off hold automatically
-                 * when switching to it */
-                if (call->state() == Call::State::HOLD)
-                    call << Call::Action::HOLD;
-            }
-            selection_changed(current, win);
-        }
-    );
+    auto selection_conversations = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_conversations));
+    g_signal_connect(selection_conversations, "changed", G_CALLBACK(conversation_selection_changed), win);
 
-    /* connect to dataChanged of the RecentModel to see if we need to change the view */
-    QObject::connect(
-        &RecentModel::instance(),
-        &RecentModel::dataChanged,
-        [win](const QModelIndex & topLeft, G_GNUC_UNUSED const QModelIndex & bottomRight, G_GNUC_UNUSED const QVector<int> & roles) {
-            /* it is possible for dataChanged to be emitted inside of a dataChanged handler or
-             * some other signal; since the connection is via a lambda, Qt would cause the
-             * handler to be called directly. This is not behaviour we usually want, so we call our
-             * function via g_idle so that it gets called after the initial handler is done.
-             */
-            if (topLeft == RecentModel::instance().selectionModel()->currentIndex())
-                g_idle_add((GSourceFunc)selected_item_changed, win);
-        }
-    );
+    auto selection_contacts = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_contacts));
+    g_signal_connect(selection_contacts, "changed", G_CALLBACK(contact_selection_changed), win);
+
+    auto selection_history = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_history));
+    g_signal_connect(selection_history, "changed", G_CALLBACK(history_selection_changed), win);
 
     g_signal_connect(priv->button_placecall, "clicked", G_CALLBACK(search_entry_placecall), win);
     g_signal_connect(priv->search_entry, "activate", G_CALLBACK(search_entry_placecall), win);
@@ -1036,13 +1254,12 @@ ring_main_window_init(RingMainWindow *win)
     g_signal_connect(priv->search_entry, "changed", G_CALLBACK(search_entry_text_changed), win);
     g_signal_connect(entry_completion, "match-selected", G_CALLBACK(select_autocompletion), win);
 
-    /* connect to incoming call and focus */
+    /* make sure the incoming call is the selected call in the CallModel */
     QObject::connect(
         &CallModel::instance(),
         &CallModel::incomingCall,
-        [=](Call* call) {
-            CallModel::instance().selectionModel()->setCurrentIndex(
-                CallModel::instance().getIndex(call), QItemSelectionModel::ClearAndSelect);
+        [](Call* call) {
+            CallModel::instance().selectCall(call);
         }
     );
 
@@ -1060,7 +1277,9 @@ ring_main_window_dispose(GObject *object)
     RingMainWindow *self = RING_MAIN_WINDOW(object);
     RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
 
-    QObject::disconnect(priv->selection_updated);
+    QObject::disconnect(priv->selected_item_changed);
+    QObject::disconnect(priv->selected_call_over);
+    g_object_unref(priv->welcome_view); // can now be destroyed
 
     G_OBJECT_CLASS(ring_main_window_parent_class)->dispose(object);
 }
@@ -1068,9 +1287,6 @@ ring_main_window_dispose(GObject *object)
 static void
 ring_main_window_finalize(GObject *object)
 {
-    RingMainWindow *self = RING_MAIN_WINDOW(object);
-    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
-
     G_OBJECT_CLASS(ring_main_window_parent_class)->finalize(object);
 }
 
diff --git a/ui/ringmainwindow.ui b/ui/ringmainwindow.ui
index 7559a55c4651a9da0be37a774f483073581fa606..5e9f716ac335b84aa5d6e02aa9e95f81177f876a 100644
--- a/ui/ringmainwindow.ui
+++ b/ui/ringmainwindow.ui
@@ -205,7 +205,7 @@
                     <property name="visible">True</property>
                   </object>
                   <packing>
-                    <property name="position">2</property>
+                    <property name="position">1</property>
                   </packing>
                 </child>
                 <child type="tab">
@@ -214,7 +214,7 @@
                     <property name="hexpand">True</property>
                   </object>
                   <packing>
-                    <property name="position">2</property>
+                    <property name="position">1</property>
                   </packing>
                 </child>
                 <!-- end contacts list -->
@@ -224,7 +224,7 @@
                     <property name="visible">True</property>
                   </object>
                   <packing>
-                    <property name="position">3</property>
+                    <property name="position">2</property>
                   </packing>
                 </child>
                 <child type="tab">
@@ -233,7 +233,7 @@
                     <property name="hexpand">True</property>
                   </object>
                   <packing>
-                    <property name="position">3</property>
+                    <property name="position">2</property>
                   </packing>
                 </child>
                 <!-- end history -->