pidgin/pidginconversationwindow.c

Mon, 21 Mar 2022 20:25:43 -0500

author
Gary Kramlich <grim@reaperworld.com>
date
Mon, 21 Mar 2022 20:25:43 -0500
changeset 41306
8befd71928c2
parent 41254
ef50e0dc74b0
child 41441
e114ed471a1e
permissions
-rw-r--r--

Create a new status manager that's built in glade

Behavorial Changes:

* Confirmation dialog for remove has been removed.
* Remove button is disabled if the currently selected status is the active
status. Previously this scenario was silently ignored after confirmation.

Errata:

It is possible to open multiple modify windows for a status. Previously the
editor was reaching into the manager and controlling this pointer. I was going
to fix this, but the editor isn't a widget yet and I don't like doing duplicate
work.

Testing Done:
Basically tried everything I could think of with editing and selecting and so on.

Bugs closed: PIDGIN-17590

Reviewed at https://reviews.imfreedom.org/r/1322/

/*
 * 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);
}

mercurial