pidgin/pidgin

Add a protocol actions interface

23 months ago, Elliott Sales de Andrade
05ac40f0e0c4
Add a protocol actions interface

Testing Done:
Compile only, for this part by itself.

Reviewed at https://reviews.imfreedom.org/r/1511/
/*
* Pidgin - Internet Messenger
* Copyright (C) Pidgin Developers <devel@pidgin.im>
*
* Pidgin is the legal property of its developers, whose names are too numerous
* to list here. Please refer to the COPYRIGHT file distributed with this
* source distribution.
*
* This program 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 2 of the License, or
* (at your option) any later version.
*
* This program 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 this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <glib/gi18n-lib.h>
#include "pidginconversationwindow.h"
#include "gtkconv.h"
enum {
PIDGIN_CONVERSATION_WINDOW_COLUMN_OBJECT,
PIDGIN_CONVERSATION_WINDOW_COLUMN_ICON,
PIDGIN_CONVERSATION_WINDOW_COLUMN_MARKUP,
};
enum {
SIG_CONVERSATION_SWITCHED,
N_SIGNALS,
};
static guint signals[N_SIGNALS] = {0, };
struct _PidginConversationWindow {
GtkApplicationWindow parent;
GtkWidget *vbox;
GtkWidget *view;
GtkTreeStore *model;
GtkWidget *stack;
GtkTreePath *conversation_path;
};
G_DEFINE_TYPE(PidginConversationWindow, pidgin_conversation_window,
GTK_TYPE_APPLICATION_WINDOW)
static GtkWidget *default_window = NULL;
/******************************************************************************
* Callbacks
*****************************************************************************/
static void
pidgin_conversation_window_selection_changed(GtkTreeSelection *selection,
gpointer data)
{
PidginConversationWindow *window = PIDGIN_CONVERSATION_WINDOW(data);
GtkTreeModel *model = NULL;
GtkTreeIter iter;
gboolean changed = FALSE;
if(gtk_tree_selection_get_selected(selection, &model, &iter)) {
gchar *markup = NULL;
gtk_tree_model_get(model, &iter,
PIDGIN_CONVERSATION_WINDOW_COLUMN_MARKUP, &markup,
-1);
gtk_stack_set_visible_child_name(GTK_STACK(window->stack), markup);
g_free(markup);
changed = TRUE;
}
if(!changed) {
gtk_stack_set_visible_child_name(GTK_STACK(window->stack), "__empty__");
}
}
static gboolean
pidgin_conversation_window_key_pressed_cb(GtkEventControllerKey *controller,
guint keyval,
G_GNUC_UNUSED guint keycode,
GdkModifierType state,
gpointer data)
{
PidginConversationWindow *window = data;
/* If CTRL was held down... */
if (state & GDK_CONTROL_MASK) {
switch (keyval) {
case GDK_KEY_Page_Down:
case GDK_KEY_KP_Page_Down:
case ']':
pidgin_conversation_window_select_next(window);
return TRUE;
break;
case GDK_KEY_Page_Up:
case GDK_KEY_KP_Page_Up:
case '[':
pidgin_conversation_window_select_previous(window);
return TRUE;
break;
} /* End of switch */
}
/* If ALT (or whatever) was held down... */
else if (state & GDK_MOD1_MASK) {
if ('1' <= keyval && keyval <= '9') {
guint switchto = keyval - '1';
pidgin_conversation_window_select_nth(window, switchto);
return TRUE;
}
}
return FALSE;
}
/******************************************************************************
* GObjectImplementation
*****************************************************************************/
static void
pidgin_conversation_window_dispose(GObject *obj) {
PidginConversationWindow *window = PIDGIN_CONVERSATION_WINDOW(obj);
if(GTK_IS_TREE_MODEL(window->model)) {
GtkTreeModel *model = GTK_TREE_MODEL(window->model);
GtkTreeIter parent, iter;
gtk_tree_model_get_iter(model, &parent, window->conversation_path);
if(gtk_tree_model_iter_children(model, &iter, &parent)) {
gboolean valid = FALSE;
/* gtk_tree_store_remove moves the iter to the next item at the
* same level, so we abuse that to do our iteration.
*/
do {
PurpleConversation *conversation = NULL;
gtk_tree_model_get(model, &iter,
PIDGIN_CONVERSATION_WINDOW_COLUMN_OBJECT, &conversation,
-1);
if(PURPLE_IS_CONVERSATION(conversation)) {
pidgin_conversation_detach(conversation);
valid = gtk_tree_store_remove(window->model, &iter);
}
} while(valid);
}
g_clear_pointer(&window->conversation_path, gtk_tree_path_free);
}
G_OBJECT_CLASS(pidgin_conversation_window_parent_class)->dispose(obj);
}
static void
pidgin_conversation_window_init(PidginConversationWindow *window) {
GtkEventController *key = NULL;
GtkTreeIter iter;
gtk_widget_init_template(GTK_WIDGET(window));
gtk_window_set_application(GTK_WINDOW(window),
GTK_APPLICATION(g_application_get_default()));
key = gtk_event_controller_key_new(GTK_WIDGET(window));
gtk_event_controller_set_propagation_phase(key, GTK_PHASE_CAPTURE);
g_signal_connect(G_OBJECT(key), "key-pressed",
G_CALLBACK(pidgin_conversation_window_key_pressed_cb),
window);
g_object_set_data_full(G_OBJECT(window), "key-press-controller", key,
g_object_unref);
/* Add our toplevels to the tree store. */
gtk_tree_store_append(window->model, &iter, NULL);
gtk_tree_store_set(window->model, &iter,
PIDGIN_CONVERSATION_WINDOW_COLUMN_MARKUP, _("Conversations"),
-1);
window->conversation_path = gtk_tree_model_get_path(GTK_TREE_MODEL(window->model),
&iter);
}
static void
pidgin_conversation_window_class_init(PidginConversationWindowClass *klass) {
GObjectClass *obj_class = G_OBJECT_CLASS(klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
obj_class->dispose = pidgin_conversation_window_dispose;
signals[SIG_CONVERSATION_SWITCHED] = g_signal_new_class_handler(
"conversation-switched",
G_OBJECT_CLASS_TYPE(obj_class),
G_SIGNAL_RUN_LAST,
NULL,
NULL,
NULL,
NULL,
G_TYPE_NONE,
1,
PURPLE_TYPE_CONVERSATION
);
gtk_widget_class_set_template_from_resource(
widget_class,
"/im/pidgin/Pidgin3/Conversations/window.ui"
);
gtk_widget_class_bind_template_child(widget_class, PidginConversationWindow,
vbox);
gtk_widget_class_bind_template_child(widget_class, PidginConversationWindow,
model);
gtk_widget_class_bind_template_child(widget_class, PidginConversationWindow,
view);
gtk_widget_class_bind_template_child(widget_class, PidginConversationWindow,
stack);
gtk_widget_class_bind_template_callback(widget_class,
pidgin_conversation_window_selection_changed);
}
/******************************************************************************
* API
*****************************************************************************/
GtkWidget *
pidgin_conversation_window_get_default(void) {
if(!GTK_IS_WIDGET(default_window)) {
default_window = pidgin_conversation_window_new();
g_object_add_weak_pointer(G_OBJECT(default_window),
(gpointer)&default_window);
}
return default_window;
}
GtkWidget *
pidgin_conversation_window_new(void) {
return GTK_WIDGET(g_object_new(PIDGIN_TYPE_CONVERSATION_WINDOW, NULL));
}
void
pidgin_conversation_window_add(PidginConversationWindow *window,
PurpleConversation *conversation)
{
PidginConversation *gtkconv = NULL;
GtkTreeIter parent, iter;
GtkTreeModel *model = NULL;
const gchar *markup = NULL;
gboolean expand = FALSE;
g_return_if_fail(PIDGIN_IS_CONVERSATION_WINDOW(window));
g_return_if_fail(PURPLE_IS_CONVERSATION(conversation));
model = GTK_TREE_MODEL(window->model);
if(!gtk_tree_model_get_iter(model, &parent, window->conversation_path)) {
/* If we can't find the conversation_path we have to bail. */
g_warning("couldn't get an iterator to conversation_path");
return;
}
if(!gtk_tree_model_iter_has_child(model, &parent)) {
expand = TRUE;
}
markup = purple_conversation_get_name(conversation);
gtkconv = PIDGIN_CONVERSATION(conversation);
if(gtkconv != NULL) {
GtkWidget *parent = gtk_widget_get_parent(gtkconv->tab_cont);
if(GTK_IS_WIDGET(parent)) {
g_object_ref(gtkconv->tab_cont);
gtk_container_remove(GTK_CONTAINER(parent), gtkconv->tab_cont);
}
gtk_stack_add_named(GTK_STACK(window->stack), gtkconv->tab_cont, markup);
gtk_widget_show_all(gtkconv->tab_cont);
if(GTK_IS_WIDGET(parent)) {
g_object_unref(gtkconv->tab_cont);
}
}
gtk_tree_store_prepend(window->model, &iter, &parent);
gtk_tree_store_set(window->model, &iter,
PIDGIN_CONVERSATION_WINDOW_COLUMN_OBJECT, conversation,
PIDGIN_CONVERSATION_WINDOW_COLUMN_MARKUP, markup,
-1);
/* If we just added the first child, expand the parent. */
if(expand) {
gtk_tree_view_expand_row(GTK_TREE_VIEW(window->view),
window->conversation_path, FALSE);
}
if(!gtk_widget_is_visible(GTK_WIDGET(window))) {
gtk_widget_show_all(GTK_WIDGET(window));
}
}
void
pidgin_conversation_window_remove(PidginConversationWindow *window,
PurpleConversation *conversation)
{
GtkTreeIter parent, iter;
GtkTreeModel *model = NULL;
GObject *obj = NULL;
g_return_if_fail(PIDGIN_IS_CONVERSATION_WINDOW(window));
g_return_if_fail(PURPLE_IS_CONVERSATION(conversation));
model = GTK_TREE_MODEL(window->model);
if(!gtk_tree_model_get_iter(model, &parent, window->conversation_path)) {
/* The path is somehow invalid, so bail... */
return;
}
if(!gtk_tree_model_iter_children(model, &iter, &parent)) {
/* The conversations iter has no children. */
return;
}
do {
gtk_tree_model_get(model, &iter,
PIDGIN_CONVERSATION_WINDOW_COLUMN_OBJECT, &obj,
-1);
if(PURPLE_CONVERSATION(obj) == conversation) {
gtk_tree_store_remove(window->model, &iter);
g_clear_object(&obj);
break;
}
g_clear_object(&obj);
} while(gtk_tree_model_iter_next(model, &iter));
}
guint
pidgin_conversation_window_get_count(PidginConversationWindow *window) {
GList *children = NULL;
guint count = 0;
g_return_val_if_fail(PIDGIN_IS_CONVERSATION_WINDOW(window), 0);
children = gtk_container_get_children(GTK_CONTAINER(window));
while(children != NULL) {
children = g_list_delete_link(children, children);
count++;
}
return count;
}
PurpleConversation *
pidgin_conversation_window_get_selected(PidginConversationWindow *window) {
PurpleConversation *conversation = NULL;
GtkTreeSelection *selection = NULL;
GtkTreeIter iter;
g_return_val_if_fail(PIDGIN_IS_CONVERSATION_WINDOW(window), NULL);
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(window->view));
if(gtk_tree_selection_get_selected(selection, NULL, &iter)) {
gtk_tree_model_get(GTK_TREE_MODEL(window->model), &iter,
PIDGIN_CONVERSATION_WINDOW_COLUMN_OBJECT, &conversation,
-1);
}
return conversation;
}
void
pidgin_conversation_window_select(PidginConversationWindow *window,
PurpleConversation *conversation)
{
const gchar *name = NULL;
g_return_if_fail(PIDGIN_IS_CONVERSATION_WINDOW(window));
g_return_if_fail(PURPLE_IS_CONVERSATION(conversation));
name = purple_conversation_get_name(conversation);
gtk_stack_set_visible_child_name(GTK_STACK(window->stack), name);
}
void
pidgin_conversation_window_select_previous(PidginConversationWindow *window) {
GtkTreeIter iter;
GtkTreeModel *model = NULL;
GtkTreeSelection *selection = NULL;
gboolean set = FALSE;
g_return_if_fail(PIDGIN_IS_CONVERSATION_WINDOW(window));
model = GTK_TREE_MODEL(window->model);
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(window->view));
if(gtk_tree_selection_get_selected(selection, NULL, &iter)) {
if(gtk_tree_model_iter_previous(model, &iter)) {
gtk_tree_selection_select_iter(selection, &iter);
set = TRUE;
}
}
if(!set) {
pidgin_conversation_window_select_last(window);
}
}
void
pidgin_conversation_window_select_next(PidginConversationWindow *window) {
GtkTreeIter iter;
GtkTreeModel *model = NULL;
GtkTreeSelection *selection = NULL;
gboolean set = FALSE;
g_return_if_fail(PIDGIN_IS_CONVERSATION_WINDOW(window));
model = GTK_TREE_MODEL(window->model);
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(window->view));
if(gtk_tree_selection_get_selected(selection, NULL, &iter)) {
if(gtk_tree_model_iter_next(model, &iter)) {
gtk_tree_selection_select_iter(selection, &iter);
set = TRUE;
}
}
if(!set) {
pidgin_conversation_window_select_first(window);
}
}
void
pidgin_conversation_window_select_first(PidginConversationWindow *window) {
GtkTreeIter iter;
GtkTreeModel *model = NULL;
g_return_if_fail(PIDGIN_IS_CONVERSATION_WINDOW(window));
model = GTK_TREE_MODEL(window->model);
if(gtk_tree_model_get_iter_first(model, &iter)) {
GtkTreeSelection *selection = NULL;
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(window->view));
gtk_tree_selection_select_iter(selection, &iter);
}
}
void
pidgin_conversation_window_select_last(PidginConversationWindow *window) {
GtkTreeIter iter;
GtkTreeModel *model = NULL;
gint count = 0;
g_return_if_fail(PIDGIN_IS_CONVERSATION_WINDOW(window));
model = GTK_TREE_MODEL(window->model);
count = gtk_tree_model_iter_n_children(model, NULL);
if(gtk_tree_model_iter_nth_child(model, &iter, NULL, count - 1)) {
GtkTreeSelection *selection = NULL;
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(window->view));
gtk_tree_selection_select_iter(selection, &iter);
}
}
void
pidgin_conversation_window_select_nth(PidginConversationWindow *window,
guint nth)
{
GtkTreeIter iter;
GtkTreeModel *model = NULL;
g_return_if_fail(PIDGIN_IS_CONVERSATION_WINDOW(window));
model = GTK_TREE_MODEL(window->model);
if(gtk_tree_model_iter_nth_child(model, &iter, NULL, nth)) {
GtkTreeSelection *selection = NULL;
selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(window->view));
gtk_tree_selection_select_iter(selection, &iter);
}
}
gboolean
pidgin_conversation_window_conversation_is_selected(PidginConversationWindow *window,
PurpleConversation *conversation)
{
const gchar *name = NULL, *visible = NULL;
g_return_val_if_fail(PIDGIN_IS_CONVERSATION_WINDOW(window), FALSE);
g_return_val_if_fail(PURPLE_IS_CONVERSATION(conversation), FALSE);
name = purple_conversation_get_name(conversation);
visible = gtk_stack_get_visible_child_name(GTK_STACK(window->stack));
return purple_strequal(name, visible);
}