commit 85b243c149d96ac88c5afd20993bbe18c50e16ca
parent 4065995bbc9f0c605b63e57574753e7c7ee9985c
Author: TheJackiMonster <thejackimonster@gmail.com>
Date: Sat, 12 Mar 2022 22:59:40 +0100
Implemented QR generation and usage of lobbies
Signed-off-by: TheJackiMonster <thejackimonster@gmail.com>
Diffstat:
11 files changed, 981 insertions(+), 30 deletions(-)
diff --git a/Makefile b/Makefile
@@ -27,6 +27,7 @@ SOURCES = messenger_gtk.c\
ui/new_account.c\
ui/new_contact.c\
ui/new_group.c\
+ ui/new_lobby.c\
ui/new_platform.c\
ui/picker.c\
ui/send_file.c\
diff --git a/resources/ui.gresource.xml b/resources/ui.gresource.xml
@@ -16,6 +16,7 @@
<file compressed="true">ui/messenger.ui</file>
<file compressed="true">ui/new_contact.ui</file>
<file compressed="true">ui/new_group.ui</file>
+ <file compressed="true">ui/new_lobby.ui</file>
<file compressed="true">ui/new_platform.ui</file>
<file compressed="true">ui/new_account.ui</file>
<file compressed="true">ui/picker.ui</file>
diff --git a/resources/ui/messenger.ui b/resources/ui/messenger.ui
@@ -246,7 +246,7 @@ Author: Tobias Frisch
</packing>
</child>
<child>
- <object class="GtkButton" id="favourites_button">
+ <object class="GtkButton" id="lobby_button">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
@@ -257,7 +257,7 @@ Author: Tobias Frisch
<object class="GtkImage">
<property name="visible">True</property>
<property name="can-focus">False</property>
- <property name="icon-name">user-bookmarks-symbolic</property>
+ <property name="icon-name">dialog-password-symbolic</property>
</object>
</child>
</object>
diff --git a/resources/ui/new_contact.ui b/resources/ui/new_contact.ui
@@ -27,6 +27,8 @@ Author: Tobias Frisch
<property name="title" translatable="yes">New Contact</property>
<property name="modal">True</property>
<property name="window-position">center-on-parent</property>
+ <property name="default-width">400</property>
+ <property name="default-height">350</property>
<property name="type-hint">dialog</property>
<child internal-child="vbox">
<object class="GtkBox">
diff --git a/resources/ui/new_lobby.ui b/resources/ui/new_lobby.ui
@@ -0,0 +1,403 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.38.2
+
+Copyright (C) 2022 GNUnet e.V.
+
+GNUnet is free software: you can redistribute it and/or modify it
+under the terms of the GNU Affero General Public License as published
+by the Free Software Foundation, either version 3 of the License,
+or (at your option) any later version.
+
+GNUnet 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
+Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+SPDX-License-Identifier: AGPL3.0-or-later
+Author: Tobias Frisch
+
+-->
+<interface>
+ <requires lib="gtk+" version="3.24"/>
+ <requires lib="libhandy" version="1.2"/>
+ <object class="GtkListStore" id="delay_store">
+ <columns>
+ <!-- column-name name -->
+ <column type="gchararray"/>
+ <!-- column-name value -->
+ <column type="gulong"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Off</col>
+ <col id="1">0</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">4 weeks</col>
+ <col id="1">2419200</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">1 week</col>
+ <col id="1">604800</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">1 day</col>
+ <col id="1">86400</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">8 hours</col>
+ <col id="1">28800</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">1 hour</col>
+ <col id="1">3600</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">5 minutes</col>
+ <col id="1">300</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">30 seconds</col>
+ <col id="1">30</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkDialog" id="new_lobby_dialog">
+ <property name="can-focus">False</property>
+ <property name="title" translatable="yes">New Lobby</property>
+ <property name="modal">True</property>
+ <property name="window-position">center-on-parent</property>
+ <property name="default-height">450</property>
+ <property name="type-hint">dialog</property>
+ <child internal-child="vbox">
+ <object class="GtkBox">
+ <property name="can-focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox">
+ <property name="can-focus">False</property>
+ <property name="layout-style">end</property>
+ <child>
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">Cancel</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="generate_button">
+ <property name="label" translatable="yes">Generate</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="copy_button">
+ <property name="label" translatable="yes">Copy</property>
+ <property name="sensitive">False</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkInfoBar" id="warning_info_bar">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="message-type">warning</property>
+ <property name="show-close-button">True</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox">
+ <property name="can-focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="layout-style">expand</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child internal-child="content_area">
+ <object class="GtkBox">
+ <property name="can-focus">False</property>
+ <property name="border-width">8</property>
+ <property name="spacing">16</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes">Beware that you don't reuse lobbies to establish private chats with only one of your contacts!</property>
+ <property name="wrap">True</property>
+ <property name="wrap-mode">word-char</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="height-request">200</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="hscrollbar-policy">never</property>
+ <property name="shadow-type">in</property>
+ <child>
+ <object class="GtkViewport">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <child>
+ <object class="GtkStack" id="new_lobby_stack">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <child>
+ <object class="GtkBox" id="generate_box">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="border-width">8</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">4</property>
+ <child>
+ <object class="HdyAvatar">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">center</property>
+ <property name="margin-top">8</property>
+ <property name="margin-bottom">8</property>
+ <property name="icon-name">dialog-password-symbolic</property>
+ <property name="size">128</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes">Delay until new lobby expires:</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="expiration_combo_box">
+ <property name="width-request">250</property>
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="model">delay_store</property>
+ <property name="active">0</property>
+ <child>
+ <object class="GtkCellRendererText"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="name">page_generate</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="copy_box">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkStack" id="preview_stack">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <child>
+ <object class="GtkBox" id="fail_box">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="valign">center</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">8</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="pixel-size">64</property>
+ <property name="icon-name">action-unavailable-symbolic</property>
+ <property name="icon_size">3</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes">Generation of new lobby failed!</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="name">page_fail</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinner" id="loading_spinner">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="active">True</property>
+ </object>
+ <packing>
+ <property name="name">page_loading</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkDrawingArea" id="id_drawing_area">
+ <property name="height-request">250</property>
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">8</property>
+ <property name="margin-end">8</property>
+ <property name="margin-top">8</property>
+ <property name="margin-bottom">8</property>
+ </object>
+ <packing>
+ <property name="name">page_drawing</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes">ID:</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="id_entry">
+ <property name="width-request">250</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="editable">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="name">page_copy</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/src/application.h b/src/application.h
@@ -38,6 +38,7 @@
#include "ui/new_account.h"
#include "ui/new_contact.h"
#include "ui/new_group.h"
+#include "ui/new_lobby.h"
#include "ui/new_platform.h"
#include "ui/send_file.h"
#include "ui/settings.h"
@@ -87,6 +88,7 @@ typedef struct MESSENGER_Application
UI_NEW_CONTACT_Handle new_contact;
UI_NEW_GROUP_Handle new_group;
+ UI_NEW_LOBBY_Handle new_lobby;
UI_NEW_PLATFORM_Handle new_platform;
UI_NEW_ACCOUNT_Handle new_account;
diff --git a/src/ui/messenger.c b/src/ui/messenger.c
@@ -31,6 +31,8 @@
#include "contacts.h"
#include "message.h"
#include "new_contact.h"
+#include "new_group.h"
+#include "new_lobby.h"
#include "new_platform.h"
#include "settings.h"
@@ -58,6 +60,19 @@ handle_flap_via_button_click(UNUSED GtkButton* button,
}
static void
+handle_lobby_button_click(UNUSED GtkButton* button,
+ gpointer user_data)
+{
+ MESSENGER_Application *app = (MESSENGER_Application*) user_data;
+
+ hdy_flap_set_reveal_flap(HDY_FLAP(app->ui.messenger.flap_user_details), FALSE);
+
+ ui_new_lobby_dialog_init(app, &(app->ui.new_lobby));
+
+ gtk_widget_show(GTK_WIDGET(app->ui.new_lobby.dialog));
+}
+
+static void
_switch_details_revealer_visibility(UI_MESSENGER_Handle *handle,
gboolean state)
{
@@ -264,6 +279,25 @@ handle_main_window_destroy(UNUSED GtkWidget *window,
application_exit(app, MESSENGER_QUIT);
}
+static void
+_switch_accounts_listbox_connection(MESSENGER_Application *app,
+ UI_MESSENGER_Handle *handle,
+ gboolean enabled)
+{
+ if (enabled)
+ handle->accounts_signal = g_signal_connect(
+ handle->accounts_listbox,
+ "row-activated",
+ G_CALLBACK(handle_accounts_listbox_row_activated),
+ app
+ );
+ else
+ g_signal_handler_disconnect(
+ handle->accounts_listbox,
+ handle->accounts_signal
+ );
+}
+
void
ui_messenger_init(MESSENGER_Application *app,
UI_MESSENGER_Handle *handle)
@@ -342,8 +376,15 @@ ui_messenger_init(MESSENGER_Application *app,
handle->flap_user_details
);
- handle->favourites_button = GTK_BUTTON(
- gtk_builder_get_object(handle->builder, "favourites_button")
+ handle->lobby_button = GTK_BUTTON(
+ gtk_builder_get_object(handle->builder, "lobby_button")
+ );
+
+ g_signal_connect(
+ handle->lobby_button,
+ "clicked",
+ G_CALLBACK(handle_lobby_button_click),
+ app
);
handle->account_details_button = GTK_BUTTON(
@@ -373,12 +414,7 @@ ui_messenger_init(MESSENGER_Application *app,
gtk_builder_get_object(handle->builder, "add_account_listbox_row")
);
- g_signal_connect(
- handle->accounts_listbox,
- "row-activated",
- G_CALLBACK(handle_accounts_listbox_row_activated),
- app
- );
+ _switch_accounts_listbox_connection(app, handle, TRUE);
handle->new_contact_button = GTK_BUTTON(
gtk_builder_get_object(handle->builder, "new_contact_button")
@@ -522,6 +558,22 @@ _messenger_iterate_accounts(void *cls,
return GNUNET_YES;
}
+static void
+_clear_accounts_listbox(GtkWidget *widget,
+ gpointer data)
+{
+ GtkListBoxRow *row = GTK_LIST_BOX_ROW(widget);
+ GtkListBox *listbox = GTK_LIST_BOX(data);
+
+ if ((!row) || (!listbox) || (!gtk_list_box_row_get_selectable(row)))
+ return;
+
+ gtk_container_remove(
+ GTK_CONTAINER(listbox),
+ widget
+ );
+}
+
void
ui_messenger_refresh(MESSENGER_Application *app,
UI_MESSENGER_Handle *handle)
@@ -529,31 +581,21 @@ ui_messenger_refresh(MESSENGER_Application *app,
if (!(handle->accounts_listbox))
return;
- GList *list = gtk_container_get_children(
- GTK_CONTAINER(handle->accounts_listbox)
- );
-
- while (list)
- {
- GtkListBoxRow *row = GTK_LIST_BOX_ROW(list->data);
-
- if ((!row) || (!gtk_list_box_row_get_selectable(row)))
- goto skip_row;
+ _switch_accounts_listbox_connection(app, handle, FALSE);
- gtk_container_remove(
- GTK_CONTAINER(handle->accounts_listbox),
- GTK_WIDGET(row)
- );
-
- skip_row:
- list = list->next;
- }
+ gtk_container_foreach(
+ GTK_CONTAINER(handle->accounts_listbox),
+ _clear_accounts_listbox,
+ handle->accounts_listbox
+ );
GNUNET_CHAT_iterate_accounts(
app->chat.messenger.handle,
_messenger_iterate_accounts,
app
);
+
+ _switch_accounts_listbox_connection(app, handle, TRUE);
}
gboolean
diff --git a/src/ui/messenger.h b/src/ui/messenger.h
@@ -52,7 +52,7 @@ typedef struct UI_MESSENGER_Handle
GtkLabel *profile_key_label;
GtkButton *hide_user_details_button;
- GtkButton *favourites_button;
+ GtkButton *lobby_button;
GtkButton *account_details_button;
GtkImage *account_details_symbol;
@@ -72,6 +72,8 @@ typedef struct UI_MESSENGER_Handle
GtkStack *chats_stack;
GtkWidget *no_chat_box;
+
+ gulong accounts_signal;
} UI_MESSENGER_Handle;
void
diff --git a/src/ui/new_contact.c b/src/ui/new_contact.c
@@ -40,8 +40,28 @@ handle_confirm_button_click(UNUSED GtkButton *button,
{
MESSENGER_Application *app = (MESSENGER_Application*) user_data;
- // TODO: Add new contact
+ const gint id_length = gtk_entry_get_text_length(app->ui.new_contact.id_entry);
+ const gchar *id_text = gtk_entry_get_text(app->ui.new_contact.id_entry);
+ if (id_length <= 0)
+ goto close_dialog;
+
+ gchar *emsg = NULL;
+ struct GNUNET_CHAT_Uri *uri = GNUNET_CHAT_uri_parse(id_text, &emsg);
+
+ if (uri)
+ {
+ GNUNET_CHAT_lobby_join(app->chat.messenger.handle, uri);
+ GNUNET_CHAT_uri_destroy(uri);
+ }
+
+ if (emsg)
+ {
+ printf("ERROR: %s\n", emsg);
+ GNUNET_free(emsg);
+ }
+
+close_dialog:
gtk_window_close(GTK_WINDOW(app->ui.new_contact.dialog));
}
@@ -61,6 +81,9 @@ handle_id_drawing_area_draw(GtkWidget* drawing_area,
GtkStyleContext* context = gtk_widget_get_style_context(drawing_area);
+ if (!context)
+ return FALSE;
+
const guint width = gtk_widget_get_allocated_width(drawing_area);
const guint height = gtk_widget_get_allocated_height(drawing_area);
diff --git a/src/ui/new_lobby.c b/src/ui/new_lobby.c
@@ -0,0 +1,404 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2022 GNUnet e.V.
+
+ GNUnet is free software: you can redistribute it and/or modify it
+ under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ GNUnet 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
+ Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ SPDX-License-Identifier: AGPL3.0-or-later
+ */
+/*
+ * @author Tobias Frisch
+ * @file ui/new_lobby.c
+ */
+
+#include "new_lobby.h"
+
+#include "../application.h"
+
+static void
+handle_warning_info_bar_close(GtkInfoBar *info_bar,
+ UNUSED gpointer user_data)
+{
+ gtk_info_bar_set_revealed(info_bar, FALSE);
+}
+
+static void
+handle_warning_info_bar_response(GtkInfoBar *info_bar,
+ UNUSED int response_id,
+ UNUSED gpointer user_data)
+{
+ gtk_info_bar_set_revealed(info_bar, FALSE);
+}
+
+static void
+handle_cancel_button_click(UNUSED GtkButton *button,
+ gpointer user_data)
+{
+ GtkDialog *dialog = GTK_DIALOG(user_data);
+ gtk_window_close(GTK_WINDOW(dialog));
+}
+
+void
+handle_lobby_opened_and_uri_generated(void *cls,
+ const struct GNUNET_CHAT_Uri *uri)
+{
+ MESSENGER_Application *app = (MESSENGER_Application*) cls;
+
+ if (app->ui.new_lobby.qr)
+ QRcode_free(app->ui.new_lobby.qr);
+
+ if (!uri)
+ {
+ if (app->ui.new_lobby.preview_stack)
+ gtk_stack_set_visible_child(
+ app->ui.new_lobby.preview_stack,
+ app->ui.new_lobby.fail_box
+ );
+
+ app->ui.new_lobby.qr = NULL;
+ return;
+ }
+
+ gchar *uri_string = GNUNET_CHAT_uri_to_string(uri);
+
+ app->ui.new_lobby.qr = QRcode_encodeString(
+ uri_string,
+ 0,
+ QR_ECLEVEL_L,
+ QR_MODE_8,
+ 0
+ );
+
+ if (app->ui.new_lobby.id_drawing_area)
+ gtk_widget_queue_draw(GTK_WIDGET(app->ui.new_lobby.id_drawing_area));
+
+ if (app->ui.new_lobby.preview_stack)
+ gtk_stack_set_visible_child(
+ app->ui.new_lobby.preview_stack,
+ GTK_WIDGET(app->ui.new_lobby.id_drawing_area)
+ );
+
+ if (app->ui.new_lobby.id_entry)
+ gtk_entry_set_text(app->ui.new_lobby.id_entry, uri_string);
+
+ GNUNET_free(uri_string);
+
+ if (!(app->ui.new_lobby.id_entry))
+ return;
+
+ const gint id_length = gtk_entry_get_text_length(app->ui.new_lobby.id_entry);
+
+ gtk_widget_set_sensitive(
+ GTK_WIDGET(app->ui.new_lobby.copy_button),
+ id_length > 0? TRUE : FALSE
+ );
+}
+
+static void
+handle_generate_button_click(UNUSED GtkButton *button,
+ gpointer user_data)
+{
+ MESSENGER_Application *app = (MESSENGER_Application*) user_data;
+
+ GtkTreeModel *model = gtk_combo_box_get_model(
+ app->ui.new_lobby.expiration_combo_box
+ );
+
+ gulong delay = 0;
+
+ GtkTreeIter iter;
+ if (gtk_combo_box_get_active_iter(app->ui.new_lobby.expiration_combo_box,
+ &iter))
+ gtk_tree_model_get(model, &iter, 1, &delay, -1);
+
+ struct GNUNET_TIME_Relative expiration = GNUNET_TIME_relative_multiply(
+ GNUNET_TIME_relative_get_second_(),
+ delay
+ );
+
+ gtk_stack_set_visible_child(
+ app->ui.new_lobby.preview_stack,
+ GTK_WIDGET(app->ui.new_lobby.loading_spinner)
+ );
+
+ gtk_stack_set_visible_child(
+ app->ui.new_lobby.stack,
+ app->ui.new_lobby.copy_box
+ );
+
+ gtk_widget_set_sensitive(GTK_WIDGET(app->ui.new_lobby.copy_button), FALSE);
+
+ gtk_widget_set_visible(GTK_WIDGET(app->ui.new_lobby.generate_button), FALSE);
+ gtk_widget_set_visible(GTK_WIDGET(app->ui.new_lobby.copy_button), TRUE);
+
+ GNUNET_CHAT_lobby_open(
+ app->chat.messenger.handle,
+ expiration,
+ handle_lobby_opened_and_uri_generated,
+ app
+ );
+}
+
+static void
+handle_copy_button_click(UNUSED GtkButton *button,
+ gpointer user_data)
+{
+ MESSENGER_Application *app = (MESSENGER_Application*) user_data;
+
+ const gint id_length = gtk_entry_get_text_length(app->ui.new_lobby.id_entry);
+ const gchar *id_text = gtk_entry_get_text(app->ui.new_lobby.id_entry);
+
+ GtkClipboard *clipboard = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
+
+ if ((clipboard) && (id_length > 0))
+ gtk_clipboard_set_text(clipboard, id_text, id_length);
+
+ gtk_window_close(GTK_WINDOW(app->ui.new_lobby.dialog));
+}
+
+static void
+handle_dialog_destroy(UNUSED GtkWidget *window,
+ gpointer user_data)
+{
+ ui_new_lobby_dialog_cleanup((UI_NEW_LOBBY_Handle*) user_data);
+}
+
+static gboolean
+handle_id_drawing_area_draw(GtkWidget* drawing_area,
+ cairo_t* cairo,
+ gpointer user_data)
+{
+ UI_NEW_LOBBY_Handle *handle = (UI_NEW_LOBBY_Handle*) user_data;
+
+ GtkStyleContext* context = gtk_widget_get_style_context(drawing_area);
+
+ if (!context)
+ return FALSE;
+
+ const guint width = gtk_widget_get_allocated_width(drawing_area);
+ const guint height = gtk_widget_get_allocated_height(drawing_area);
+
+ gtk_render_background(context, cairo, 0, 0, width, height);
+
+ if ((!(handle->qr)) || (handle->qr->width <= 0))
+ return FALSE;
+
+ const guint m = 3;
+ const guint w = handle->qr->width;
+ const guint w2 = w + m * 2;
+
+ guchar pixels [w2 * w2 * 3];
+
+ guint x, y, z;
+ for (y = 0; y < w2; y++)
+ for (x = 0; x < w2; x++)
+ {
+ guchar value;
+
+ if ((x >= m) && (y >= m) && (x - m < w) && (y - m < w))
+ value = ((handle->qr->data[(y - m) * w + x - m]) & 1);
+ else
+ value = 0;
+
+ for (z = 0; z < 3; z++)
+ pixels[(y * w2 + x) * 3 + z] = value? 0x00 : 0xff;
+ }
+
+ GdkPixbuf *image = gdk_pixbuf_new_from_data(
+ pixels,
+ GDK_COLORSPACE_RGB,
+ FALSE,
+ 8,
+ w2,
+ w2,
+ w2 * 3,
+ NULL,
+ NULL
+ );
+
+ if (!image)
+ return FALSE;
+
+ int dwidth = gdk_pixbuf_get_width(image);
+ int dheight = gdk_pixbuf_get_height(image);
+
+ double ratio_width = 1.0 * width / dwidth;
+ double ratio_height = 1.0 * height / dheight;
+
+ const double ratio = ratio_width < ratio_height? ratio_width : ratio_height;
+
+ dwidth = (int) (dwidth * ratio);
+ dheight = (int) (dheight * ratio);
+
+ double dx = (width - dwidth) * 0.5;
+ double dy = (height - dheight) * 0.5;
+
+ const int interp_type = (ratio >= 1.0?
+ GDK_INTERP_NEAREST :
+ GDK_INTERP_BILINEAR
+ );
+
+ GdkPixbuf* scaled = gdk_pixbuf_scale_simple(
+ image,
+ dwidth,
+ dheight,
+ interp_type
+ );
+
+ gtk_render_icon(context, cairo, scaled, dx, dy);
+
+ cairo_fill(cairo);
+
+ g_object_unref(scaled);
+ g_object_unref(image);
+
+ return FALSE;
+}
+
+
+void
+ui_new_lobby_dialog_init(MESSENGER_Application *app,
+ UI_NEW_LOBBY_Handle *handle)
+{
+ handle->builder = gtk_builder_new_from_resource(
+ application_get_resource_path(app, "ui/new_lobby.ui")
+ );
+
+ handle->dialog = GTK_DIALOG(
+ gtk_builder_get_object(handle->builder, "new_lobby_dialog")
+ );
+
+ gtk_window_set_transient_for(
+ GTK_WINDOW(handle->dialog),
+ GTK_WINDOW(app->ui.messenger.main_window)
+ );
+
+ handle->warning_info_bar = GTK_INFO_BAR(
+ gtk_builder_get_object(handle->builder, "warning_info_bar")
+ );
+
+ g_signal_connect(
+ handle->warning_info_bar,
+ "close",
+ G_CALLBACK(handle_warning_info_bar_close),
+ NULL
+ );
+
+ g_signal_connect(
+ handle->warning_info_bar,
+ "response",
+ G_CALLBACK(handle_warning_info_bar_response),
+ NULL
+ );
+
+ handle->stack = GTK_STACK(
+ gtk_builder_get_object(handle->builder, "new_lobby_stack")
+ );
+
+ handle->generate_box = GTK_WIDGET(
+ gtk_builder_get_object(handle->builder, "generate_box")
+ );
+
+ handle->copy_box = GTK_WIDGET(
+ gtk_builder_get_object(handle->builder, "copy_box")
+ );
+
+ handle->expiration_combo_box = GTK_COMBO_BOX(
+ gtk_builder_get_object(handle->builder, "expiration_combo_box")
+ );
+
+ handle->preview_stack = GTK_STACK(
+ gtk_builder_get_object(handle->builder, "preview_stack")
+ );
+
+ handle->fail_box = GTK_WIDGET(
+ gtk_builder_get_object(handle->builder, "fail_box")
+ );
+
+ handle->loading_spinner = GTK_SPINNER(
+ gtk_builder_get_object(handle->builder, "loading_spinner")
+ );
+
+ handle->id_drawing_area = GTK_DRAWING_AREA(
+ gtk_builder_get_object(handle->builder, "id_drawing_area")
+ );
+
+ handle->id_draw_signal = g_signal_connect(
+ handle->id_drawing_area,
+ "draw",
+ G_CALLBACK(handle_id_drawing_area_draw),
+ handle
+ );
+
+ handle->id_entry = GTK_ENTRY(
+ gtk_builder_get_object(handle->builder, "id_entry")
+ );
+
+ handle->cancel_button = GTK_BUTTON(
+ gtk_builder_get_object(handle->builder, "cancel_button")
+ );
+
+ g_signal_connect(
+ handle->cancel_button,
+ "clicked",
+ G_CALLBACK(handle_cancel_button_click),
+ handle->dialog
+ );
+
+ handle->generate_button = GTK_BUTTON(
+ gtk_builder_get_object(handle->builder, "generate_button")
+ );
+
+ g_signal_connect(
+ handle->generate_button,
+ "clicked",
+ G_CALLBACK(handle_generate_button_click),
+ app
+ );
+
+ handle->copy_button = GTK_BUTTON(
+ gtk_builder_get_object(handle->builder, "copy_button")
+ );
+
+ g_signal_connect(
+ handle->copy_button,
+ "clicked",
+ G_CALLBACK(handle_copy_button_click),
+ app
+ );
+
+ g_signal_connect(
+ handle->dialog,
+ "destroy",
+ G_CALLBACK(handle_dialog_destroy),
+ handle
+ );
+}
+
+
+void
+ui_new_lobby_dialog_cleanup(UI_NEW_LOBBY_Handle *handle)
+{
+ g_signal_handler_disconnect(
+ handle->id_drawing_area,
+ handle->id_draw_signal
+ );
+
+ g_object_unref(handle->builder);
+
+ if (handle->qr)
+ QRcode_free(handle->qr);
+
+ memset(handle, 0, sizeof(*handle));
+}
diff --git a/src/ui/new_lobby.h b/src/ui/new_lobby.h
@@ -0,0 +1,71 @@
+/*
+ This file is part of GNUnet.
+ Copyright (C) 2022 GNUnet e.V.
+
+ GNUnet is free software: you can redistribute it and/or modify it
+ under the terms of the GNU Affero General Public License as published
+ by the Free Software Foundation, either version 3 of the License,
+ or (at your option) any later version.
+
+ GNUnet 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
+ Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ SPDX-License-Identifier: AGPL3.0-or-later
+ */
+/*
+ * @author Tobias Frisch
+ * @file ui/new_lobby.h
+ */
+
+#ifndef UI_NEW_LOBBY_H_
+#define UI_NEW_LOBBY_H_
+
+#include "messenger.h"
+
+#include <cairo/cairo.h>
+#include <gdk/gdkpixbuf.h>
+#include <qrencode.h>
+
+typedef struct UI_NEW_LOBBY_Handle
+{
+ GtkBuilder *builder;
+ GtkDialog *dialog;
+
+ GtkInfoBar *warning_info_bar;
+
+ GtkStack *stack;
+ GtkWidget *generate_box;
+ GtkWidget *copy_box;
+
+ GtkComboBox *expiration_combo_box;
+
+ GtkStack *preview_stack;
+ GtkWidget *fail_box;
+
+ GtkSpinner *loading_spinner;
+
+ GtkDrawingArea *id_drawing_area;
+ GtkEntry *id_entry;
+
+ gulong id_draw_signal;
+
+ GtkButton *cancel_button;
+ GtkButton *generate_button;
+ GtkButton *copy_button;
+
+ QRcode *qr;
+} UI_NEW_LOBBY_Handle;
+
+void
+ui_new_lobby_dialog_init(MESSENGER_Application *app,
+ UI_NEW_LOBBY_Handle *handle);
+
+void
+ui_new_lobby_dialog_cleanup(UI_NEW_LOBBY_Handle *handle);
+
+#endif /* UI_NEW_LOBBY_H_ */