1
0
Fork 0
mirror of https://gitlab.gnome.org/GNOME/calls.git synced 2024-11-13 03:51:17 +00:00
Purism-Calls/src/calls-main-window.c
Adrien Plazas a07b0b8382 main-window: Drop the new call expander
It is not needed anymore as the call display can send DTMF tones itself,
so instead let the dial pad always be available to send new calls.
2018-08-29 09:05:57 +02:00

659 lines
17 KiB
C

/*
* Copyright (C) 2018 Purism SPC
*
* This file is part of Calls.
*
* Calls is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Calls is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Calls. If not, see <http://www.gnu.org/licenses/>.
*
* Author: Bob Ham <bob.ham@puri.sm>
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
*/
#include "calls-main-window.h"
#include "calls-origin.h"
#include "calls-call-holder.h"
#include "calls-call-selector-item.h"
#include "util.h"
#include <glib/gi18n.h>
#include <glib-object.h>
#define HANDY_USE_UNSTABLE_API
#include <handy.h>
struct _CallsMainWindow
{
GtkApplicationWindow parent_instance;
CallsProvider *provider;
GListStore *call_holders;
CallsCallHolder *focus;
GtkInfoBar *info;
GtkLabel *info_label;
GtkStack *main_stack;
GtkButton *back;
GtkStack *call_stack;
GtkScrolledWindow *call_scroll;
GtkFlowBox *call_selector;
GtkBox *dial_box;
GtkBox *dial_controls;
GtkComboBox *origin;
GtkSearchEntry *search;
HdyDialer *dial_pad;
GtkListStore *origin_store;
};
enum {
ORIGIN_STORE_COLUMN_NAME,
ORIGIN_STORE_COLUMN_ORIGIN
};
G_DEFINE_TYPE (CallsMainWindow, calls_main_window, GTK_TYPE_APPLICATION_WINDOW);
enum {
PROP_0,
PROP_PROVIDER,
PROP_LAST_PROP,
};
static GParamSpec *props[PROP_LAST_PROP];
CallsMainWindow *
calls_main_window_new (GtkApplication *application, CallsProvider *provider)
{
return g_object_new (CALLS_TYPE_MAIN_WINDOW,
"application", application,
"provider", provider,
NULL);
}
static void
show_message (CallsMainWindow *self, const gchar *text, GtkMessageType type)
{
gtk_info_bar_set_message_type (self->info, type);
gtk_label_set_text (self->info_label, text);
gtk_widget_show (GTK_WIDGET (self->info));
gtk_widget_queue_allocate (GTK_WIDGET (self));
}
static void
info_response_cb (GtkInfoBar *infobar,
gint response_id,
CallsMainWindow *self)
{
gtk_widget_hide (GTK_WIDGET (self->info));
gtk_widget_queue_allocate (GTK_WIDGET (self));
}
static GtkWidget *
call_holders_create_widget_cb (CallsCallHolder *holder,
CallsMainWindow *self)
{
return GTK_WIDGET (calls_call_holder_get_selector_item (holder));
}
static void
search_append_symbol (CallsMainWindow *self, gchar symbol)
{
GtkEntryBuffer *buf = gtk_entry_get_buffer (GTK_ENTRY (self->search));
guint len = gtk_entry_buffer_get_length (buf);
gtk_entry_buffer_insert_text (buf, len, &symbol, 1);
}
static void
dial_pad_symbol_clicked_cb (CallsMainWindow *self, gchar symbol, HdyDialer *dialer)
{
search_append_symbol (self, symbol);
}
static void
dial_pad_deleted_cb (CallsMainWindow *self, HdyDialer *dialer)
{
GtkEntryBuffer *buf = gtk_entry_get_buffer (GTK_ENTRY (self->search));
guint len = gtk_entry_buffer_get_length (buf);
gtk_entry_buffer_delete_text (buf, len - 1, 1);
}
static void
dial_pad_submitted_cb (CallsMainWindow *self, const gchar *unused, HdyDialer *dialer)
{
GtkTreeIter iter;
gboolean ok;
CallsOrigin *origin;
const gchar *number;
g_return_if_fail (CALLS_IS_MAIN_WINDOW (self));
ok = gtk_combo_box_get_active_iter (self->origin, &iter);
g_return_if_fail (ok);
gtk_tree_model_get (GTK_TREE_MODEL (self->origin_store), &iter,
ORIGIN_STORE_COLUMN_ORIGIN, &origin,
-1);
g_return_if_fail (CALLS_IS_ORIGIN (origin));
number = gtk_entry_get_text (GTK_ENTRY (self->search));
calls_origin_dial (origin, number);
}
typedef gboolean (*FindCallHolderFunc) (CallsCallHolder *holder,
gpointer user_data);
static gboolean
find_call_holder_by_call (CallsCallHolder *holder,
gpointer user_data)
{
CallsCallData *data = calls_call_holder_get_data (holder);
return calls_call_data_get_call (data) == user_data;
}
/** Search through the list of call holders, returning the total
number of items in the list, the position of the holder within the
list and a pointer to the holder itself. */
static gboolean
find_call_holder (CallsMainWindow *self,
guint *n_itemsp,
guint *positionp,
CallsCallHolder **holderp,
FindCallHolderFunc predicate,
gpointer user_data)
{
GListModel * const model = G_LIST_MODEL (self->call_holders);
const guint n_items = g_list_model_get_n_items (model);
guint position = 0;
CallsCallHolder *holder;
for (position = 0; position < n_items; ++position)
{
holder = CALLS_CALL_HOLDER (g_list_model_get_item (model, position));
if (predicate (holder, user_data))
{
#define out(var) \
if (var##p) \
{ \
*var##p = var ; \
}
out (n_items);
out (position);
out (holder);
#undef out
return TRUE;
}
}
return FALSE;
}
static void
set_focus (CallsMainWindow *self, CallsCallHolder *holder)
{
if (!holder)
{
holder = g_list_model_get_item (G_LIST_MODEL (self->call_holders), 0);
if (!holder)
{
/* No calls */
self->focus = NULL;
return;
}
}
self->focus = holder;
gtk_stack_set_visible_child
(self->call_stack,
GTK_WIDGET (calls_call_holder_get_display (holder)));
}
static void
back_clicked_cb (GtkButton *back,
CallsMainWindow *self)
{
gtk_stack_set_visible_child (self->call_stack, GTK_WIDGET (self->call_scroll));
gtk_stack_set_visible_child (self->main_stack, GTK_WIDGET (self->call_stack));
}
static void
call_selector_child_activated_cb (GtkFlowBox *box,
GtkFlowBoxChild *child,
CallsMainWindow *self)
{
GtkWidget *widget = gtk_bin_get_child (GTK_BIN (child));
CallsCallSelectorItem *item = CALLS_CALL_SELECTOR_ITEM (widget);
CallsCallHolder *holder = calls_call_selector_item_get_holder (item);
set_focus (self, holder);
}
/** Possibly show various call widgets */
static void
show_calls (CallsMainWindow *self, guint old_call_count)
{
if (old_call_count == 0)
{
gtk_stack_add_titled (self->main_stack,
GTK_WIDGET (self->call_stack),
"call", "Call");
}
if (old_call_count > 0)
{
gtk_widget_show (GTK_WIDGET (self->back));
}
}
static void
hide_calls (CallsMainWindow *self, guint call_count)
{
if (call_count == 0)
{
gtk_container_remove (GTK_CONTAINER (self->main_stack),
GTK_WIDGET (self->call_stack));
}
if (call_count <= 1)
{
gtk_widget_hide (GTK_WIDGET (self->back));
}
}
static void
add_call (CallsMainWindow *self, CallsCall *call)
{
CallsCallHolder *holder;
CallsCallDisplay *display;
g_signal_connect_swapped (call, "message",
G_CALLBACK (show_message), self);
show_calls (self, g_list_model_get_n_items (G_LIST_MODEL (self->call_holders)));
holder = calls_call_holder_new (call);
display = calls_call_holder_get_display (holder);
gtk_stack_add_named (self->call_stack, GTK_WIDGET (display),
calls_call_get_number (call));
gtk_stack_set_visible_child (self->main_stack, GTK_WIDGET (self->call_stack));
g_list_store_append (self->call_holders, holder);
set_focus (self, holder);
}
static void
remove_call_holder (CallsMainWindow *self,
guint n_items,
guint position,
CallsCallHolder *holder)
{
g_list_store_remove (self->call_holders, position);
gtk_container_remove (GTK_CONTAINER (self->call_stack),
GTK_WIDGET (calls_call_holder_get_display (holder)));
if (self->focus == holder)
{
set_focus (self, NULL);
}
hide_calls (self, n_items - 1);
}
static void
remove_call (CallsMainWindow *self, CallsCall *call, const gchar *reason)
{
guint n_items, position;
CallsCallHolder *holder;
gboolean found;
g_return_if_fail (CALLS_IS_MAIN_WINDOW (self));
g_return_if_fail (CALLS_IS_CALL (call));
found = find_call_holder (self, &n_items, &position, &holder,
find_call_holder_by_call, call);
g_return_if_fail (found);
remove_call_holder (self, n_items, position, holder);
if (!reason)
{
reason = "Call ended for unknown reason";
}
show_message(self, reason, GTK_MESSAGE_INFO);
}
static void
remove_calls (CallsMainWindow *self)
{
GListModel * model = G_LIST_MODEL (self->call_holders);
guint n_items = g_list_model_get_n_items (model);
gpointer *item;
CallsCallHolder *holder;
while ( (item = g_list_model_get_item (model, 0)) )
{
holder = CALLS_CALL_HOLDER (item);
remove_call_holder (self, n_items--, 0, holder);
}
}
static void
add_origin_calls (CallsMainWindow *self, CallsOrigin *origin)
{
GList *calls, *node;
calls = calls_origin_get_calls (origin);
for (node = calls; node; node = node->next)
{
add_call (self, CALLS_CALL (node->data));
}
g_list_free (calls);
}
static void
add_origin (CallsMainWindow *self, CallsOrigin *origin)
{
const gint n_origins = gtk_tree_model_iter_n_children
(GTK_TREE_MODEL (self->origin_store), NULL);
GtkTreeIter iter;
if (n_origins == 1)
{
/* We have more than one origin now so show the origin combo box */
gtk_widget_show (GTK_WIDGET (self->origin));
}
gtk_list_store_append (self->origin_store, &iter);
gtk_list_store_set (self->origin_store, &iter,
ORIGIN_STORE_COLUMN_NAME, calls_origin_get_name(origin),
ORIGIN_STORE_COLUMN_ORIGIN, G_OBJECT (origin),
-1);
if (gtk_combo_box_get_active (self->origin) == -1)
{
/* We always want an item active */
gtk_combo_box_set_active (self->origin, 0);
}
g_signal_connect_swapped (origin, "message",
G_CALLBACK (show_message), self);
g_signal_connect_swapped (origin, "call-added",
G_CALLBACK (add_call), self);
g_signal_connect_swapped (origin, "call-removed",
G_CALLBACK (remove_call), self);
add_origin_calls(self, origin);
}
static void
dump_list_store (GtkListStore *store)
{
GtkTreeIter iter;
GtkTreeModel *model = GTK_TREE_MODEL (store);
gboolean ok;
ok = gtk_tree_model_get_iter_first (model, &iter);
if (!ok)
{
return;
}
g_debug ("List store:");
do
{
gchararray name;
gtk_tree_model_get (model, &iter,
ORIGIN_STORE_COLUMN_NAME, &name,
-1);
g_debug (" name: `%s'", name);
}
while (gtk_tree_model_iter_next (model, &iter));
}
static void
update_origin (CallsMainWindow *self)
{
if (gtk_tree_model_iter_n_children
(GTK_TREE_MODEL (self->origin_store), NULL) < 2)
{
/* User has only one choice so hide the origin combo box */
gtk_widget_hide (GTK_WIDGET (self->origin));
}
}
static void
remove_origin (CallsMainWindow *self, CallsOrigin *origin)
{
GtkTreeIter iter;
gboolean ok;
ok = calls_list_store_find (self->origin_store, origin,
ORIGIN_STORE_COLUMN_ORIGIN, &iter);
g_return_if_fail (ok);
gtk_list_store_remove (self->origin_store, &iter);
update_origin (self);
}
static void
remove_origins (CallsMainWindow *self)
{
GtkTreeModel *model = GTK_TREE_MODEL (self->origin_store);
GtkTreeIter iter;
while (gtk_tree_model_get_iter_first (model, &iter))
{
gtk_list_store_remove (self->origin_store, &iter);
}
update_origin (self);
}
static void
add_provider_origins (CallsMainWindow *self, CallsProvider *provider)
{
GList *origins, *node;
origins = calls_provider_get_origins (provider);
for (node = origins; node; node = node->next)
{
add_origin (self, CALLS_ORIGIN (node->data));
}
g_list_free (origins);
dump_list_store (self->origin_store);
}
static void
set_provider (CallsMainWindow *self, CallsProvider *provider)
{
g_signal_connect_swapped (provider, "message",
G_CALLBACK (show_message), self);
g_signal_connect_swapped (provider, "origin-added",
G_CALLBACK (add_origin), self);
g_signal_connect_swapped (provider, "origin-removed",
G_CALLBACK (remove_origin), self);
self->provider = provider;
g_object_ref (G_OBJECT (provider));
add_provider_origins (self, provider);
}
static void
set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
CallsMainWindow *self = CALLS_MAIN_WINDOW (object);
GObject *val_obj;
switch (property_id) {
case PROP_PROVIDER:
val_obj = g_value_get_object (value);
if (val_obj == NULL)
{
g_warning("Null provider");
self->provider = NULL;
}
else
{
g_return_if_fail (CALLS_IS_PROVIDER (val_obj));
set_provider (self, CALLS_PROVIDER (val_obj));
}
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
constructed (GObject *object)
{
GObjectClass *parent_class = g_type_class_peek (GTK_TYPE_APPLICATION_WINDOW);
CallsMainWindow *self = CALLS_MAIN_WINDOW (object);
gtk_container_remove (GTK_CONTAINER (self->main_stack),
GTK_WIDGET (self->call_stack));
gtk_flow_box_bind_model (self->call_selector,
G_LIST_MODEL (self->call_holders),
(GtkFlowBoxCreateWidgetFunc) call_holders_create_widget_cb,
NULL, NULL);
parent_class->constructed (object);
}
static void
calls_main_window_init (CallsMainWindow *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
self->call_holders = g_list_store_new (CALLS_TYPE_CALL_HOLDER);
}
static void
dispose (GObject *object)
{
GObjectClass *parent_class = g_type_class_peek (GTK_TYPE_APPLICATION_WINDOW);
CallsMainWindow *self = CALLS_MAIN_WINDOW (object);
if (self->call_holders)
{
remove_calls (self);
}
if (self->origin_store)
{
remove_origins (self);
}
g_clear_object (&self->call_holders);
g_clear_object (&self->provider);
parent_class->dispose (object);
}
static void
calls_main_window_class_init (CallsMainWindowClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->set_property = set_property;
object_class->constructed = constructed;
object_class->dispose = dispose;
props[PROP_PROVIDER] =
g_param_spec_object ("provider",
_("Provider"),
_("An object implementing low-level call-making functionality"),
CALLS_TYPE_PROVIDER,
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
gtk_widget_class_set_template_from_resource (widget_class, "/sm/puri/calls/ui/main-window.ui");
gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, info);
gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, info_label);
gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, main_stack);
gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, back);
gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, call_stack);
gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, call_scroll);
gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, call_selector);
gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, dial_box);
gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, dial_controls);
gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, origin);
gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, search);
gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, dial_pad);
gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, origin_store);
gtk_widget_class_bind_template_callback (widget_class, info_response_cb);
gtk_widget_class_bind_template_callback (widget_class, call_selector_child_activated_cb);
gtk_widget_class_bind_template_callback (widget_class, back_clicked_cb);
gtk_widget_class_bind_template_callback (widget_class, dial_pad_submitted_cb);
gtk_widget_class_bind_template_callback (widget_class, dial_pad_deleted_cb);
gtk_widget_class_bind_template_callback (widget_class, dial_pad_symbol_clicked_cb);
}