search entry: open chat view instead of call

This changes the default behaviour of the search entry, the button
next to it, and the autocompletion drop down to opening the chat view
with the selected CM/Person instead of placing a new call. This allows
people who wish to simply chat with a new RingID to do so, instead of
first having to call that RingID or add it to a Contact in their
addressbook.

Additionally, a setting has been added to change this behaviour, so
that (typically) SIP users can easily place new calls by entering phone
numbers in the search entry.

Also, a "Place call" button has been added to the chat view (when not
in a call), so that users can easily call someone they are chatting with
without having to double click on that contact in the contacts list.

Change-Id: Ia833fb36620fd34afdbb3c3a4357c212a87f8796
Tuleap: #953
diff --git a/src/chatview.cpp b/src/chatview.cpp
index 75a1ccc..ef8f7fc 100644
--- a/src/chatview.cpp
+++ b/src/chatview.cpp
@@ -30,6 +30,7 @@
 #include "ringnotify.h"
 #include "numbercategory.h"
 #include <QtCore/QDateTime>
+#include "utils/calling.h"
 
 static constexpr GdkRGBA RING_BLUE  = {0.0508, 0.594, 0.676, 1.0}; // outgoing msg color: (13, 152, 173)
 
@@ -55,6 +56,7 @@
     GtkWidget *label_peer;
     GtkWidget *combobox_cm;
     GtkWidget *button_close_chatview;
+    GtkWidget *button_placecall;
 
     /* only one of the three following pointers should be non void;
      * either this is an in-call chat (and so the in-call chat APIs will be used)
@@ -117,7 +119,7 @@
                 if (!cm->sendOfflineTextMessage(messages))
                     g_warning("message failed to send"); // TODO: warn the user about this in the UI
             } else {
-                g_warning("no ContactMethod chosen; message not esnt");
+                g_warning("no ContactMethod chosen; message not sent");
             }
         } else if (priv->cm) {
             if (!priv->cm->sendOfflineTextMessage(messages))
@@ -145,6 +147,27 @@
 }
 
 static void
+placecall_clicked(ChatView *self)
+{
+    auto priv = CHAT_VIEW_GET_PRIVATE(self);
+
+    if (priv->person) {
+        // get the chosen cm
+        auto active = gtk_combo_box_get_active(GTK_COMBO_BOX(priv->combobox_cm));
+        if (active >= 0) {
+            auto cm = priv->person->phoneNumbers().at(active);
+            place_new_call(cm);
+        } else {
+            g_warning("no ContactMethod chosen; cannot place call");
+        }
+    } else if (priv->cm) {
+        place_new_call(priv->cm);
+    } else {
+        g_warning("no Person or ContactMethod set; cannot place call");
+    }
+}
+
+static void
 chat_view_init(ChatView *view)
 {
     gtk_widget_init_template(GTK_WIDGET(view));
@@ -161,6 +184,8 @@
     g_signal_connect(adjustment, "changed", G_CALLBACK(scroll_to_bottom), NULL);
 
     g_signal_connect(priv->button_close_chatview, "clicked", G_CALLBACK(hide_chat_view), view);
+
+    g_signal_connect_swapped(priv->button_placecall, "clicked", G_CALLBACK(placecall_clicked), view);
 }
 
 static void
@@ -179,6 +204,7 @@
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, label_peer);
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, combobox_cm);
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, button_close_chatview);
+    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), ChatView, button_placecall);
 
     chat_view_signals[NEW_MESSAGES_DISPLAYED] = g_signal_new (
         "new-messages-displayed",
@@ -406,6 +432,9 @@
     /* if there is only one CM, make the combo box insensitive */
     if (cms.size() < 2)
         gtk_widget_set_sensitive(priv->combobox_cm, FALSE);
+
+    /* if no CMs make the call button insensitive */
+    gtk_widget_set_sensitive(priv->button_placecall, !cms.isEmpty());
 }
 
 static void
diff --git a/src/generalsettingsview.cpp b/src/generalsettingsview.cpp
index c32dbc2..294b3d3 100644
--- a/src/generalsettingsview.cpp
+++ b/src/generalsettingsview.cpp
@@ -55,6 +55,7 @@
     GtkWidget *checkbutton_bringtofront;
     GtkWidget *checkbutton_callnotifications;
     GtkWidget *checkbutton_chatnotifications;
+    GtkWidget *checkbutton_searchentryplacescall;
     GtkWidget *radiobutton_chatright;
     GtkWidget *radiobutton_chatbottom;
     GtkWidget *box_profil_settings;
@@ -156,6 +157,9 @@
     g_settings_bind(priv->settings, "enable-chat-notifications",
                     priv->checkbutton_chatnotifications, "active",
                     G_SETTINGS_BIND_DEFAULT);
+    g_settings_bind(priv->settings, "search-entry-places-call",
+                    priv->checkbutton_searchentryplacescall, "active",
+                    G_SETTINGS_BIND_DEFAULT);
     g_settings_bind(priv->settings, "chat-pane-horizontal",
                     priv->radiobutton_chatright, "active",
                     G_SETTINGS_BIND_DEFAULT);
@@ -186,6 +190,7 @@
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), GeneralSettingsView, checkbutton_bringtofront);
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), GeneralSettingsView, checkbutton_callnotifications);
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), GeneralSettingsView, checkbutton_chatnotifications);
+    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), GeneralSettingsView, checkbutton_searchentryplacescall);
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), GeneralSettingsView, radiobutton_chatright);
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), GeneralSettingsView, radiobutton_chatbottom);
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), GeneralSettingsView, adjustment_history_duration);
diff --git a/src/ringmainwindow.cpp b/src/ringmainwindow.cpp
index caed435..20ea2bf 100644
--- a/src/ringmainwindow.cpp
+++ b/src/ringmainwindow.cpp
@@ -29,6 +29,7 @@
 // Qt
 #include <QtCore/QItemSelectionModel>
 #include <QtCore/QSortFilterProxyModel>
+#include <QtCore/QDateTime>
 
 // LRC
 #include <callmodel.h>
@@ -110,7 +111,7 @@
     GtkWidget *vbox_call_view;
     GtkWidget *frame_call;
     GtkWidget *welcome_view;
-    GtkWidget *button_placecall;
+    GtkWidget *button_new_conversation  ;
     GtkWidget *account_settings_view;
     GtkWidget *media_settings_view;
     GtkWidget *general_settings_view;
@@ -447,23 +448,29 @@
 }
 
 static void
-search_entry_placecall(G_GNUC_UNUSED GtkWidget *entry, gpointer win)
+search_entry_activated(GtkWidget *entry, RingMainWindow *self)
 {
-    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
+    RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
 
-    const gchar *number_entered = gtk_entry_get_text(GTK_ENTRY(priv->search_entry));
+    const auto *number_entered = gtk_entry_get_text(GTK_ENTRY(entry));
 
     if (number_entered && strlen(number_entered) > 0) {
         auto cm = PhoneDirectoryModel::instance().getNumber(number_entered);
 
-        g_debug("dialing to number: %s", cm->uri().toUtf8().constData());
+        if (g_settings_get_boolean(priv->settings, "search-entry-places-call")) {
+            place_new_call(cm);
 
-        place_new_call(cm);
+            /* move focus away from entry so that DTMF tones can be entered via the keyboard */
+            gtk_widget_child_focus(GTK_WIDGET(self), GTK_DIR_TAB_FORWARD);
+        } else {
+            // if its a new CM, bring it to the top
+            if (cm->lastUsed() == 0)
+                cm->setLastUsed(QDateTime::currentDateTime().toTime_t());
 
-        /* move focus away from entry so that DTMF tones can be entered via the keyboard */
-        gtk_widget_child_focus(GTK_WIDGET(win), GTK_DIR_TAB_FORWARD);
-        /* clear the entry */
-        gtk_entry_set_text(GTK_ENTRY(priv->search_entry), "");
+            // select cm
+            RecentModel::instance().selectionModel()->setCurrentIndex(RecentModel::instance().getIndex(cm), QItemSelectionModel::ClearAndSelect);
+        }
+        gtk_entry_set_text(GTK_ENTRY(entry), "");
     }
 }
 
@@ -873,10 +880,19 @@
     if (idx.isValid()) {
         auto cm = priv->q_completion_model->number(idx);
 
-        place_new_call(cm);
+        if (g_settings_get_boolean(priv->settings, "search-entry-places-call")) {
+            place_new_call(cm);
 
-        /* move focus away from entry so that DTMF tones can be entered via the keyboard */
-        gtk_widget_child_focus(GTK_WIDGET(win), GTK_DIR_TAB_FORWARD);
+            /* move focus away from entry so that DTMF tones can be entered via the keyboard */
+            gtk_widget_child_focus(GTK_WIDGET(win), GTK_DIR_TAB_FORWARD);
+        } else {
+            // if its a new CM, bring it to the top
+            if (cm->lastUsed() == 0)
+                cm->setLastUsed(QDateTime::currentDateTime().toTime_t());
+
+            // select cm
+            RecentModel::instance().selectionModel()->setCurrentIndex(RecentModel::instance().getIndex(cm), QItemSelectionModel::ClearAndSelect);
+        }
 
         /* clear the entry */
         gtk_entry_set_text(GTK_ENTRY(priv->search_entry), "");
@@ -1093,6 +1109,18 @@
 }
 
 static void
+search_entry_places_call_changed(GSettings *settings, const gchar *key, RingMainWindow *self)
+{
+    auto priv = RING_MAIN_WINDOW_GET_PRIVATE(self);
+
+    if (g_settings_get_boolean(settings, key)) {
+        gtk_widget_set_tooltip_text(priv->button_new_conversation, C_("button next to search entry will place a new call", "place call"));
+    } else {
+        gtk_widget_set_tooltip_text(priv->button_new_conversation, C_("button next to search entry will open chat", "open chat"));
+    }
+}
+
+static void
 ring_main_window_init(RingMainWindow *win)
 {
     RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
@@ -1105,6 +1133,10 @@
     gtk_window_set_default_size(GTK_WINDOW(win), width, height);
     g_signal_connect(win, "configure-event", G_CALLBACK(window_size_changed), nullptr);
 
+    /* search-entry-places-call setting */
+    search_entry_places_call_changed(priv->settings, "search-entry-places-call", win);
+    g_signal_connect(priv->settings, "changed::search-entry-places-call", G_CALLBACK(search_entry_places_call_changed), win);
+
      /* set window icon */
     GError *error = NULL;
     GdkPixbuf* icon = gdk_pixbuf_new_from_resource("/cx/ring/RingGnome/ring-symbol-blue", &error);
@@ -1192,8 +1224,8 @@
     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);
+    g_signal_connect(priv->button_new_conversation, "clicked", G_CALLBACK(search_entry_activated), win);
+    g_signal_connect(priv->search_entry, "activate", G_CALLBACK(search_entry_activated), win);
 
     /* autocompletion */
     priv->q_completion_model = new NumberCompletionModel();
@@ -1338,7 +1370,7 @@
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, stack_main_view);
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, vbox_call_view);
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, frame_call);
-    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_placecall);
+    gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_new_conversation  );
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_general_settings);
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_media_settings);
     gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, radiobutton_account_settings);