# HG changeset patch
# Node ID 01761f22665e
# Parent  2dce52f93848
diff --git a/pidgin/gtkconv.c b/pidgin/gtkconv.c
--- a/pidgin/gtkconv.c
+++ b/pidgin/gtkconv.c
@@ -35,6 +35,7 @@
 #include <purple.h>
 
 #include "gtkconv.h"
+#include "pidginconversation.h"
 #include "pidgincore.h"
 #include "pidgindisplaywindow.h"
 #include "pidgininfopane.h"
@@ -392,6 +393,25 @@
 }
 
 static void
+conversation_create(PurpleConversation *conv) {
+	GtkWidget *window = NULL;
+
+	window = pidgin_display_window_get_default();
+	pidgin_display_window_add(PIDGIN_DISPLAY_WINDOW(window), conv);
+}
+
+static void
+conversation_write(PurpleConversation *conv, PurpleMessage *pmsg) {
+	GtkWidget *conversation = NULL;
+
+	conversation = pidgin_conversation_from_purple_conversation(conv);
+	if(PIDGIN_IS_CONVERSATION(conversation)) {
+		pidgin_conversation_write_message(PIDGIN_CONVERSATION(conversation),
+		                                  pmsg);
+	}
+}
+
+static void
 pidgin_conv_new(PurpleConversation *conv)
 {
 	private_gtkconv_new(conv, FALSE);
@@ -548,9 +568,10 @@
 
 static PurpleConversationUiOps conversation_ui_ops =
 {
-	.create_conversation = pidgin_conv_new,
+	.create_conversation = conversation_create,
+	.write_conv = conversation_write,
+
 	.destroy_conversation = pidgin_conv_destroy,
-	.write_conv = pidgin_conv_write_conv,
 	.has_focus = pidgin_conv_has_focus,
 };
 
diff --git a/pidgin/meson.build b/pidgin/meson.build
--- a/pidgin/meson.build
+++ b/pidgin/meson.build
@@ -36,6 +36,7 @@
 	'pidgincommands.c',
 	'pidgincontactlist.c',
 	'pidgincontactlistwindow.c',
+	'pidginconversation.c',
 	'pidgindebug.c',
 	'pidgindisplayitem.c',
 	'pidgindisplaywindow.c',
@@ -102,6 +103,7 @@
 	'pidgincolor.h',
 	'pidgincontactlist.h',
 	'pidgincontactlistwindow.h',
+	'pidginconversation.h',
 	'pidgincore.h',
 	'pidgindisplayitem.h',
 	'pidgindisplaywindow.h',
diff --git a/pidgin/pidginconversation.h b/pidgin/pidginconversation.h
new file mode 100644
--- /dev/null
+++ b/pidgin/pidginconversation.h
@@ -0,0 +1,115 @@
+/*
+ * 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/>.
+ */
+
+#if !defined(PIDGIN_GLOBAL_HEADER_INSIDE) && !defined(PIDGIN_COMPILATION)
+# error "only <pidgin.h> may be included directly"
+#endif
+
+#ifndef PIDGIN_CONVERSATION_H
+#define PIDGIN_CONVERSATION_H
+
+#include <glib.h>
+
+#include <adwaita.h>
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+/**
+ * PidginConversation:
+ *
+ * A [class@Gtk.Widget] for displaying a [class@Purple.Conversation].
+ */
+
+#define PIDGIN_TYPE_CONVERSATION (pidgin_conversation_get_type())
+G_DECLARE_FINAL_TYPE(PidginConversation, pidgin_conversation, PIDGIN,
+                     CONVERSATION, GtkBox)
+
+/**
+ * pidgin_conversation_new:
+ * @conversation: The [class@Purple.Conversation] to display.
+ *
+ * Creates the user interface for @conversation and returns it.
+ *
+ * Returns: (transfer full): The new widget.
+ *
+ * Since: 3.0.0
+ */
+GtkWidget *pidgin_conversation_new(PurpleConversation *conversation);
+
+/**
+ * pidgin_conversation_from_purple_conversation:
+ * @conversation: A [class@Purple.Conversation].
+ *
+ * Gets the [class@Pidgin.Conversation] that is displaying @conversation.
+ *
+ * Returns: (nullable): The [class@Pidgin.Conversation] displaying
+ *          @conversation if one exists, otherwise %NULL.
+ *
+ * Since: 3.0.0
+ */
+GtkWidget *pidgin_conversation_from_purple_conversation(PurpleConversation *conversation);
+
+/**
+ * pidgin_conversation_get_conversation:
+ * @conversation: The instance.
+ *
+ * Gets the [class@Purple.Conversation] that @conversation is displaying.
+ *
+ * Returns: (transfer none): The [class@Purple.Conversation] that @conversation
+ *          is displaying.
+ *
+ * Since: 3.0.0
+ */
+PurpleConversation *pidgin_conversation_get_conversation(PidginConversation *conversation);
+
+/**
+ * pidgin_conversation_write_message:
+ * @conversation: The instance.
+ * @message: The message to write.
+ *
+ * This is temporary until [class@Purple.Conversation] keeps track of its own
+ * messages.
+ *
+ * Write @message to @conversation. This is only for display; if you need to
+ * send a message you should use [method@Purple.Conversation.send_message].
+ *
+ * Since: 3.0.0
+ */
+void pidgin_conversation_write_message(PidginConversation *conversation, PurpleMessage *message);
+
+/**
+ * pidgin_conversation_close:
+ * @conversation: The instance.
+ *
+ * Close @conversation. This only closes the user interface portion of the
+ * conversation and the user may remain in the conversation if the protocol
+ * supports it.
+ *
+ * Since: 3.0.0
+ */
+void pidgin_conversation_close(PidginConversation *conversation);
+
+G_END_DECLS
+
+#endif /* PIDGIN_CONVERSATION_H */
diff --git a/pidgin/pidginconversation.c b/pidgin/pidginconversation.c
new file mode 100644
--- /dev/null
+++ b/pidgin/pidginconversation.c
@@ -0,0 +1,247 @@
+/*
+ * 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 <purple.h>
+
+#include "pidgin/pidginconversation.h"
+#include "pidgin/pidgininfopane.h"
+#include "pidgin/pidginmessage.h"
+
+#define PIDGIN_CONVERSATION_DATA ("pidgin-conversation")
+
+enum {
+	PROP_0,
+	PROP_CONVERSATION,
+	N_PROPERTIES,
+};
+static GParamSpec *properties[N_PROPERTIES] = {NULL,};
+
+struct _PidginConversation {
+	GtkBox parent;
+
+	PurpleConversation *conversation;
+
+	GtkWidget *info_pane;
+	GtkWidget *editor;
+	GtkWidget *history;
+};
+
+G_DEFINE_TYPE(PidginConversation, pidgin_conversation, GTK_TYPE_BOX)
+
+/******************************************************************************
+ * Helpers
+ *****************************************************************************/
+static void
+pidgin_conversation_set_conversation(PidginConversation *conversation,
+                                     PurpleConversation *purple_conversation)
+{
+	if(g_set_object(&conversation->conversation, purple_conversation)) {
+		if(PURPLE_IS_CONVERSATION(purple_conversation)) {
+			g_object_set_data(G_OBJECT(purple_conversation),
+			                  PIDGIN_CONVERSATION_DATA, conversation);
+		}
+
+		pidgin_info_pane_set_conversation(PIDGIN_INFO_PANE(conversation->info_pane),
+		                                  purple_conversation);
+
+		g_object_notify_by_pspec(G_OBJECT(conversation),
+		                         properties[PROP_CONVERSATION]);
+	}
+}
+
+/******************************************************************************
+ * Callbacks
+ *****************************************************************************/
+static void
+pidgin_conversation_send_message_cb(TalkatuInput *input, gpointer data) {
+	PidginConversation *conversation = data;
+	const char *contents = NULL;
+
+	contents = talkatu_message_get_contents(TALKATU_MESSAGE(input));
+
+	purple_conversation_send(conversation->conversation, contents);
+
+	talkatu_message_set_contents(TALKATU_MESSAGE(input), "");
+}
+
+static void
+pidgin_conversation_detach(PidginConversation *conversation) {
+	if(PURPLE_IS_CONVERSATION(conversation->conversation)) {
+		gpointer us = NULL;
+
+		us = g_object_get_data(G_OBJECT(conversation->conversation),
+		                       PIDGIN_CONVERSATION_DATA);
+
+		if(conversation == us) {
+			g_object_set_data(G_OBJECT(conversation->conversation),
+			                  PIDGIN_CONVERSATION_DATA, NULL);
+		}
+	}
+}
+
+/******************************************************************************
+ * GObject Implementation
+ *****************************************************************************/
+static void
+pidgin_conversation_dispose(GObject *obj) {
+	PidginConversation *conversation = PIDGIN_CONVERSATION(obj);
+
+	pidgin_conversation_detach(conversation);
+
+	g_clear_object(&conversation->conversation);
+
+	G_OBJECT_CLASS(pidgin_conversation_parent_class)->dispose(obj);
+}
+
+static void
+pidgin_conversation_get_property(GObject *obj, guint param_id, GValue *value,
+                                 GParamSpec *pspec)
+{
+	PidginConversation *conversation = PIDGIN_CONVERSATION(obj);
+
+	switch(param_id) {
+	case PROP_CONVERSATION:
+		g_value_set_object(value,
+		                   pidgin_conversation_get_conversation(conversation));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+		break;
+	}
+}
+
+static void
+pidgin_conversation_set_property(GObject *obj, guint param_id,
+                                 const GValue *value, GParamSpec *pspec)
+{
+	PidginConversation *conversation = PIDGIN_CONVERSATION(obj);
+
+	switch(param_id) {
+	case PROP_CONVERSATION:
+		pidgin_conversation_set_conversation(conversation,
+		                                     g_value_get_object(value));
+		break;
+	default:
+		G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, param_id, pspec);
+		break;
+	}
+}
+
+static void
+pidgin_conversation_init(PidginConversation *conversation) {
+	gtk_widget_init_template(GTK_WIDGET(conversation));
+}
+
+static void
+pidgin_conversation_class_init(PidginConversationClass *klass) {
+	GObjectClass *obj_class = G_OBJECT_CLASS(klass);
+	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+
+	obj_class->dispose = pidgin_conversation_dispose;
+	obj_class->get_property = pidgin_conversation_get_property;
+	obj_class->set_property = pidgin_conversation_set_property;
+
+	/**
+	 * PidginConversation:conversation:
+	 *
+	 * The [class@Purple.Conversation] that this conversation is displaying.
+	 *
+	 * Since: 3.0.0
+	 */
+	properties[PROP_CONVERSATION] = g_param_spec_object(
+		"conversation", "conversation",
+		"The purple conversation this widget is for.",
+		PURPLE_TYPE_CONVERSATION,
+		G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+	g_object_class_install_properties(obj_class, N_PROPERTIES, properties);
+
+	/* Template stuff. */
+	gtk_widget_class_set_template_from_resource(
+	    widget_class,
+	    "/im/pidgin/Pidgin3/Conversations/conversation.ui"
+	);
+
+	gtk_widget_class_bind_template_child(widget_class, PidginConversation,
+	                                     info_pane);
+	gtk_widget_class_bind_template_child(widget_class, PidginConversation,
+	                                     editor);
+	gtk_widget_class_bind_template_child(widget_class, PidginConversation,
+	                                     history);
+
+	gtk_widget_class_bind_template_callback(widget_class,
+	                                        pidgin_conversation_send_message_cb);
+}
+
+/******************************************************************************
+ * API
+ *****************************************************************************/
+GtkWidget *
+pidgin_conversation_new(PurpleConversation *conversation) {
+	g_return_val_if_fail(PURPLE_IS_CONVERSATION(conversation), NULL);
+
+	return g_object_new(
+		PIDGIN_TYPE_CONVERSATION,
+		"conversation", conversation,
+		NULL);
+}
+
+GtkWidget *
+pidgin_conversation_from_purple_conversation(PurpleConversation *conversation)
+{
+	g_return_val_if_fail(PURPLE_IS_CONVERSATION(conversation), NULL);
+
+	return g_object_get_data(G_OBJECT(conversation), PIDGIN_CONVERSATION_DATA);
+}
+
+PurpleConversation *
+pidgin_conversation_get_conversation(PidginConversation *conversation) {
+	g_return_val_if_fail(PIDGIN_IS_CONVERSATION(conversation), NULL);
+
+	return conversation->conversation;
+}
+
+void
+pidgin_conversation_write_message(PidginConversation *conversation,
+                                  PurpleMessage *purple_message)
+{
+	PidginMessage *message = NULL;
+
+	g_return_if_fail(PIDGIN_IS_CONVERSATION(conversation));
+	g_return_if_fail(PURPLE_IS_MESSAGE(purple_message));
+
+	message = pidgin_message_new(purple_message);
+
+	talkatu_history_write_message(TALKATU_HISTORY(conversation->history),
+	                              TALKATU_MESSAGE(message));
+
+	g_clear_object(&message);
+}
+
+void
+pidgin_conversation_close(PidginConversation *conversation) {
+	g_return_if_fail(PIDGIN_IS_CONVERSATION(conversation));
+
+	pidgin_conversation_detach(conversation);
+}
diff --git a/pidgin/pidgindisplaywindow.c b/pidgin/pidgindisplaywindow.c
--- a/pidgin/pidgindisplaywindow.c
+++ b/pidgin/pidgindisplaywindow.c
@@ -29,6 +29,7 @@
 #include "gtkconv.h"
 #include "gtkdialogs.h"
 #include "gtkutils.h"
+#include "pidginconversation.h"
 #include "pidgindisplayitem.h"
 #include "pidgininvitedialog.h"
 
@@ -179,8 +180,15 @@
 
 	selected = pidgin_display_window_get_selected(window);
 	if(PURPLE_IS_CONVERSATION(selected)) {
+		GtkWidget *conversation = NULL;
+
+		conversation = pidgin_conversation_from_purple_conversation(selected);
+		if(PIDGIN_IS_CONVERSATION(conversation)) {
+			pidgin_conversation_close(PIDGIN_CONVERSATION(conversation));
+			g_clear_object(&conversation);
+		}
+
 		pidgin_display_window_remove(window, selected);
-		pidgin_conversation_detach(selected);
 	}
 }
 
@@ -512,30 +520,35 @@
 
 void
 pidgin_display_window_add(PidginDisplayWindow *window,
-                          PurpleConversation *conversation)
+                          PurpleConversation *purple_conversation)
 {
-	PidginConversationOld *gtkconv = NULL;
+	GtkWidget *pidgin_conversation = NULL;
 
 	g_return_if_fail(PIDGIN_IS_DISPLAY_WINDOW(window));
-	g_return_if_fail(PURPLE_IS_CONVERSATION(conversation));
+	g_return_if_fail(PURPLE_IS_CONVERSATION(purple_conversation));
+
+	pidgin_conversation = pidgin_conversation_from_purple_conversation(purple_conversation);
 
-	gtkconv = PIDGIN_CONVERSATION_OLD(conversation);
-	if(gtkconv != NULL) {
+	if(!PIDGIN_IS_CONVERSATION(pidgin_conversation)) {
+		pidgin_conversation = pidgin_conversation_new(purple_conversation);
+	}
+
+	if(PIDGIN_IS_CONVERSATION(pidgin_conversation)) {
 		PidginDisplayItem *item = NULL;
-		const char *value = NULL;
+		const char *id = NULL;
 
-		GtkWidget *parent = gtk_widget_get_parent(gtkconv->tab_cont);
+		GtkWidget *parent = gtk_widget_get_parent(pidgin_conversation);
 
 		if(GTK_IS_WIDGET(parent)) {
-			g_object_ref(gtkconv->tab_cont);
-			gtk_widget_unparent(gtkconv->tab_cont);
+			g_object_ref(pidgin_conversation);
+			gtk_widget_unparent(pidgin_conversation);
 		}
 
-		value = purple_conversation_get_name(conversation);
-		item = pidgin_display_item_new(gtkconv->tab_cont, value);
-		g_object_set_data(G_OBJECT(item), "conversation", conversation);
+		id = purple_conversation_get_name(purple_conversation);
+		item = pidgin_display_item_new(pidgin_conversation, id);
+		g_object_set_data(G_OBJECT(item), "conversation", purple_conversation);
 
-		g_object_bind_property(conversation, "title",
+		g_object_bind_property(purple_conversation, "title",
 		                       item, "title",
 		                       G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
 
@@ -543,7 +556,7 @@
 		g_clear_object(&item);
 
 		if(GTK_IS_WIDGET(parent)) {
-			g_object_unref(gtkconv->tab_cont);
+			g_object_unref(pidgin_conversation);
 		}
 	}
 }
diff --git a/pidgin/resources/Conversations/conversation.ui b/pidgin/resources/Conversations/conversation.ui
new file mode 100644
--- /dev/null
+++ b/pidgin/resources/Conversations/conversation.ui
@@ -0,0 +1,87 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Pidgin - Internet Messenger
+Copyright (C) Pidgin Developers <devel@pidgin.im>
+
+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 library; if not, see <https://www.gnu.org/licenses/>.
+-->
+<interface>
+  <requires lib="gtk" version="4.0"/>
+  <!-- interface-license-type gplv2 -->
+  <!-- interface-name Pidgin -->
+  <!-- interface-description Internet Messenger -->
+  <!-- interface-copyright Pidgin Developers <devel@pidgin.im> -->
+  <template class="PidginConversation" parent="GtkBox">
+    <property name="orientation">vertical</property>
+    <child>
+      <object class="GtkBox">
+        <property name="orientation">horizontal</property>
+        <property name="vexpand">0</property>
+        <child>
+          <object class="PidginInfoPane" id="info_pane">
+            <property name="hexpand">1</property>
+          </object>
+        </child>
+        <child>
+          <object class="GtkToggleButton" id="toggle_flap">
+            <property name="icon-name">view-dual-symbolic</property>
+            <property name="valign">center</property>
+          </object>
+        </child>
+      </object>
+    </child>
+    <child>
+      <object class="AdwFlap">
+        <property name="flap-position">end</property>
+        <property name="fold-policy">auto</property>
+        <property name="swipe-to-close">1</property>
+        <property name="swipe-to-open">1</property>
+        <binding name="reveal-flap">
+          <lookup name="active">toggle_flap</lookup>
+        </binding>
+        <child type="content">
+          <object class="GtkScrolledWindow">
+            <property name="hexpand">1</property>
+            <property name="vadjustment">
+              <object class="TalkatuAutoScroller"/>
+            </property>
+            <property name="vexpand">1</property>
+            <child>
+              <object class="TalkatuHistory" id="history"/>
+            </child>
+          </object>
+        </child>
+        <child type="flap">
+          <object class="AdwStatusPage">
+            <property name="description">This is a placeholder for now</property>
+            <property name="title">Placeholder</property>
+            <property name="icon-name">starred-symbolic</property>
+          </object>
+        </child>
+      </object>
+    </child>
+    <child>
+      <object class="TalkatuEditor" id="editor">
+        <property name="vexpand">0</property>
+        <property name="orientation">vertical</property>
+        <child internal-child="input">
+          <object class="TalkatuInput" id="input">
+            <property name="send-binding">return|kp-enter</property>
+            <signal name="send-message" handler="pidgin_conversation_send_message_cb"/>
+          </object>
+        </child>
+      </object>
+    </child>
+  </template>
+</interface>
diff --git a/pidgin/resources/pidgin.gresource.xml b/pidgin/resources/pidgin.gresource.xml
--- a/pidgin/resources/pidgin.gresource.xml
+++ b/pidgin/resources/pidgin.gresource.xml
@@ -12,6 +12,7 @@
     <file compressed="true" preprocess="xml-stripblanks">Avatar/avatar.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">Avatar/menu.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">BuddyList/window.ui</file>
+    <file compressed="true" preprocess="xml-stripblanks">Conversations/conversation.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">Conversations/infopane.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">Conversations/invite_dialog.ui</file>
     <file compressed="true" preprocess="xml-stripblanks">ContactList/widget.ui</file>
