summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGünther Wagner <info@gunibert.de>2022-02-01 22:05:39 +0100
committerGünther Wagner <info@gunibert.de>2022-02-01 22:18:07 +0100
commit110734aaee4675535f4f28bd8fc60d7aba2e5645 (patch)
tree1c2326c9487198b85ab5d83e82c1a21322943621
parent51b5ed4003c8d074e6edbbd946524d30e1b84ad5 (diff)
downloadlibrest-110734aaee4675535f4f28bd8fc60d7aba2e5645.tar.gz
demo: add authentication methods
-rw-r--r--examples/demo/demo-main.c2
-rw-r--r--examples/demo/demo-rest-page.c437
-rw-r--r--examples/demo/demo-rest-page.ui333
-rw-r--r--examples/demo/demo-table-row.c149
-rw-r--r--examples/demo/demo-table-row.h35
-rw-r--r--examples/demo/demo-table-row.ui36
-rw-r--r--examples/demo/demo-table.c210
-rw-r--r--examples/demo/demo-table.h34
-rw-r--r--examples/demo/demo-window.c10
-rw-r--r--examples/demo/meson.build2
-rw-r--r--examples/demo/org.gnome.RestDemo.gresource.xml2
-rw-r--r--examples/demo/org.gnome.RestDemo.json14
-rw-r--r--examples/demo/style.css3
13 files changed, 1255 insertions, 12 deletions
diff --git a/examples/demo/demo-main.c b/examples/demo/demo-main.c
index 8f12404..f4f6029 100644
--- a/examples/demo/demo-main.c
+++ b/examples/demo/demo-main.c
@@ -23,6 +23,7 @@
#include <adwaita.h>
#include <gtksourceview/gtksource.h>
#include "demo-window.h"
+#include "demo-table.h"
static void
on_activate (GApplication *app, gpointer user_data)
@@ -76,6 +77,7 @@ main (gint argc,
{
adw_init ();
gtk_source_init ();
+ g_type_ensure (DEMO_TYPE_TABLE);
g_set_prgname ("librest-demo");
g_set_application_name ("librest-demo");
diff --git a/examples/demo/demo-rest-page.c b/examples/demo/demo-rest-page.c
index 05290c0..46ebfec 100644
--- a/examples/demo/demo-rest-page.c
+++ b/examples/demo/demo-rest-page.c
@@ -20,8 +20,11 @@
#include "demo-rest-page.h"
#include <rest/rest.h>
+#include <libsoup/soup.h>
#include <gtksourceview/gtksource.h>
#include <json-glib/json-glib.h>
+#include "demo-table.h"
+#include "demo-table-row.h"
struct _DemoRestPage
{
@@ -33,8 +36,45 @@ struct _DemoRestPage
GtkWidget *sourceview;
GtkWidget *body;
GtkWidget *notebook;
+ GtkWidget *authentication;
+ GtkWidget *authmethods;
+ GtkWidget *authentication_stack;
+ GtkWidget *headers;
+ GtkWidget *headerslb;
+ GtkWidget *params;
+ GtkWidget *paramslb;
+
+ /* basic auth */
+ GtkWidget *basic_username;
+ GtkWidget *basic_password;
+
+ /* digest auth */
+ GtkWidget *digest_username;
+ GtkWidget *digest_password;
+
+ /* oauth 1 auth */
+ GtkWidget *oauth1_client_identifier;
+ GtkWidget *oauth1_client_secret;
+ RestProxy *oauth1_proxy;
+
+ /* oauth 2 auth */
+ GtkWidget *oauth2_client_identifier;
+ GtkWidget *oauth2_client_secret;
+ GtkWidget *oauth2_auth_url;
+ GtkWidget *oauth2_token_url;
+ GtkWidget *oauth2_redirect_url;
+ RestProxy *oauth2_proxy;
+ RestPkceCodeChallenge *pkce;
};
+typedef enum {
+ AUTHMODE_NO,
+ AUTHMODE_BASIC,
+ AUTHMODE_DIGEST,
+ AUTHMODE_OAUTH1,
+ AUTHMODE_OAUTH2
+} AuthMode;
+
G_DEFINE_FINAL_TYPE (DemoRestPage, demo_rest_page, GTK_TYPE_BOX)
DemoRestPage *
@@ -48,9 +88,35 @@ demo_rest_page_finalize (GObject *object)
{
DemoRestPage *self = (DemoRestPage *)object;
+ g_clear_object (&self->oauth1_proxy);
+ g_clear_object (&self->oauth2_proxy);
+ g_clear_pointer (&self->pkce, rest_pkce_code_challenge_free);
+
G_OBJECT_CLASS (demo_rest_page_parent_class)->finalize (object);
}
+static AuthMode
+get_current_auth_mode (DemoRestPage *self)
+{
+ const gchar *stack_name;
+
+ g_return_val_if_fail (DEMO_IS_REST_PAGE (self), 0);
+
+ stack_name = gtk_stack_get_visible_child_name (GTK_STACK (self->authentication_stack));
+ if (g_strcmp0 (stack_name, "no_auth") == 0)
+ return AUTHMODE_NO;
+ else if (g_strcmp0 (stack_name, "basic") == 0)
+ return AUTHMODE_BASIC;
+ else if (g_strcmp0 (stack_name, "digest") == 0)
+ return AUTHMODE_DIGEST;
+ else if (g_strcmp0 (stack_name, "oauth1") == 0)
+ return AUTHMODE_OAUTH1;
+ else if (g_strcmp0 (stack_name, "oauth2") == 0)
+ return AUTHMODE_OAUTH2;
+
+ return AUTHMODE_NO;
+}
+
static void
populate_http_method (DemoRestPage *self)
{
@@ -82,6 +148,265 @@ set_json_response (DemoRestPage *self,
GtkSourceLanguageManager *manager = gtk_source_language_manager_get_default ();
GtkSourceLanguage *lang = gtk_source_language_manager_guess_language (manager, NULL, "application/json");
gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (buffer), lang);
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (self->notebook), 0);
+}
+
+static void
+set_text_response (DemoRestPage *self,
+ RestProxyCall *call)
+{
+ g_autoptr(GHashTable) response_headers = NULL;
+
+ const gchar *payload = rest_proxy_call_get_payload (call);
+ goffset payload_length = rest_proxy_call_get_payload_length (call);
+ response_headers = rest_proxy_call_get_response_headers (call);
+
+ GtkTextBuffer *buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (self->sourceview));
+ gtk_text_buffer_set_text (buffer, payload, payload_length);
+
+ GtkSourceLanguageManager *manager = gtk_source_language_manager_get_default ();
+ GtkSourceLanguage *lang = gtk_source_language_manager_guess_language (manager, NULL, g_hash_table_lookup (response_headers, "Content-Type"));
+ gtk_source_buffer_set_language (GTK_SOURCE_BUFFER (buffer), lang);
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (self->notebook), 0);
+}
+
+static void
+demo_rest_page_fetched_oauth1_access_token (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ RestProxy *proxy = (RestProxy *)object;
+ g_autoptr(GError) error = NULL;
+
+ g_assert (G_IS_OBJECT (object));
+ g_assert (G_IS_ASYNC_RESULT (result));
+
+ oauth_proxy_access_token_finish (OAUTH_PROXY (proxy), result, &error);
+}
+
+static void
+demo_rest_page_fetched_oauth2_access_token (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ RestProxy *proxy = (RestProxy *)object;
+ g_autoptr(GError) error = NULL;
+
+ g_assert (G_IS_OBJECT (object));
+ g_assert (G_IS_ASYNC_RESULT (result));
+
+ rest_oauth2_proxy_fetch_access_token_finish (REST_OAUTH2_PROXY (proxy), result, &error);
+}
+
+static void
+oauth1_dialog_response (GtkDialog *dialog,
+ gint response_id,
+ DemoRestPage *self)
+{
+ switch (response_id)
+ {
+ case GTK_RESPONSE_OK:
+ {
+ const gchar *verifier = NULL;
+ GtkWidget *content_area = gtk_dialog_get_content_area (dialog);
+ GtkWidget *box = gtk_widget_get_first_child (content_area);
+ GtkWidget *entry = gtk_widget_get_last_child (box);
+
+ verifier = gtk_editable_get_text (GTK_EDITABLE (entry));
+ oauth_proxy_access_token_async (OAUTH_PROXY (self->oauth1_proxy),
+ "access_token",
+ verifier,
+ NULL,
+ demo_rest_page_fetched_oauth1_access_token,
+ self);
+ break;
+ }
+ case GTK_RESPONSE_CANCEL:
+ g_clear_object (&self->oauth1_proxy);
+ break;
+ }
+}
+
+static void
+oauth2_dialog_response (GtkDialog *dialog,
+ gint response_id,
+ DemoRestPage *self)
+{
+ switch (response_id)
+ {
+ case GTK_RESPONSE_OK:
+ {
+ const gchar *code = NULL;
+ GtkWidget *content_area = gtk_dialog_get_content_area (dialog);
+ GtkWidget *box = gtk_widget_get_first_child (content_area);
+ GtkWidget *entry = gtk_widget_get_last_child (box);
+
+ code = gtk_editable_get_text (GTK_EDITABLE (entry));
+ rest_oauth2_proxy_fetch_access_token_async (REST_OAUTH2_PROXY (self->oauth2_proxy),
+ code,
+ rest_pkce_code_challenge_get_verifier (self->pkce),
+ NULL,
+ demo_rest_page_fetched_oauth2_access_token,
+ self);
+ break;
+ }
+ case GTK_RESPONSE_CANCEL:
+ g_clear_object (&self->oauth2_proxy);
+ break;
+ }
+}
+
+static GtkWidget *
+demo_rest_page_create_oauth1_dialog (DemoRestPage *self,
+ RestProxy *proxy)
+{
+ GtkWidget *dialog = NULL;
+ GtkWidget *content_area;
+ GtkWidget *box, *lbl, *token_lbl, *verifier_entry;
+ g_autofree char *token_str = NULL;
+
+ dialog = gtk_dialog_new_with_buttons ("Get Verifier...",
+ GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (self))),
+ GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_USE_HEADER_BAR,
+ "Ok",
+ GTK_RESPONSE_OK,
+ "Cancel",
+ GTK_RESPONSE_CANCEL,
+ NULL);
+
+ content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+ gtk_widget_set_margin_top (content_area, 6);
+ gtk_widget_set_margin_start (content_area, 6);
+ gtk_widget_set_margin_bottom (content_area, 6);
+ gtk_widget_set_margin_end (content_area, 6);
+
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ lbl = gtk_label_new ("Open a browser and authorize this application...");
+ gtk_box_append (GTK_BOX (box), lbl);
+ token_str = g_strdup_printf ("Use this token: %s", oauth_proxy_get_token (OAUTH_PROXY (proxy)));
+ token_lbl = gtk_label_new (token_str);
+ gtk_label_set_selectable (GTK_LABEL (token_lbl), TRUE);
+ gtk_box_append (GTK_BOX (box), token_lbl);
+ verifier_entry = gtk_entry_new ();
+ gtk_box_append (GTK_BOX (box), verifier_entry);
+
+ gtk_box_append (GTK_BOX (content_area), box);
+
+ g_signal_connect (dialog, "response", G_CALLBACK (oauth1_dialog_response), self);
+ g_signal_connect_swapped (dialog, "response", G_CALLBACK (gtk_window_destroy), dialog);
+
+ return dialog;
+}
+
+static GtkWidget *
+demo_rest_page_create_oauth2_dialog (DemoRestPage *self,
+ RestProxy *proxy)
+{
+ GtkWidget *dialog = NULL;
+ GtkWidget *content_area;
+ GtkWidget *box, *lbl, *token_lbl, *verifier_entry;
+ g_autofree char *token_str = NULL;
+
+ dialog = gtk_dialog_new_with_buttons ("Get Code...",
+ GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (self))),
+ GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_USE_HEADER_BAR,
+ "Ok",
+ GTK_RESPONSE_OK,
+ "Cancel",
+ GTK_RESPONSE_CANCEL,
+ NULL);
+
+ content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+ gtk_widget_set_margin_top (content_area, 6);
+ gtk_widget_set_margin_start (content_area, 6);
+ gtk_widget_set_margin_bottom (content_area, 6);
+ gtk_widget_set_margin_end (content_area, 6);
+
+ box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ lbl = gtk_label_new ("Open a browser and authorize this application:");
+ gtk_box_append (GTK_BOX (box), lbl);
+ token_str = g_markup_printf_escaped ("Use this <a href=\"%s\">link</a>", rest_oauth2_proxy_build_authorization_url (REST_OAUTH2_PROXY (self->oauth2_proxy),
+ rest_pkce_code_challenge_get_challenge (self->pkce),
+ NULL,
+ NULL));
+ token_lbl = gtk_label_new (NULL);
+ gtk_label_set_markup (GTK_LABEL (token_lbl), token_str);
+ gtk_label_set_selectable (GTK_LABEL (token_lbl), TRUE);
+ gtk_box_append (GTK_BOX (box), token_lbl);
+ verifier_entry = gtk_entry_new ();
+ gtk_box_append (GTK_BOX (box), verifier_entry);
+
+ gtk_box_append (GTK_BOX (content_area), box);
+
+ g_signal_connect (dialog, "response", G_CALLBACK (oauth2_dialog_response), self);
+ g_signal_connect_swapped (dialog, "response", G_CALLBACK (gtk_window_destroy), dialog);
+
+ return dialog;
+}
+
+static void
+demo_rest_page_fetched_oauth1_request_token (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ DemoRestPage *self = (DemoRestPage *)user_data;
+ RestProxy *proxy = (RestProxy *)object;
+ GtkWidget *dialog = NULL;
+ g_autoptr(GError) error = NULL;
+
+ g_assert (G_IS_OBJECT (object));
+ g_assert (G_IS_ASYNC_RESULT (result));
+
+ oauth_proxy_request_token_finish (OAUTH_PROXY (proxy), result, &error);
+
+ /* here we show a dialog requesting the user to a browser for authentication */
+ /* g_print ("%s\n", oauth_proxy_get_token (proxy)); */
+ dialog = demo_rest_page_create_oauth1_dialog (self, proxy);
+
+ gtk_widget_show (dialog);
+}
+
+static void
+on_oauth1_get_access_token_clicked (GtkButton *btn,
+ gpointer user_data)
+{
+ DemoRestPage *self = (DemoRestPage *)user_data;
+ const char *url = NULL;
+ const char *consumer_key = NULL, *consumer_secret = NULL;
+ const char *function = NULL;
+
+ url = gtk_editable_get_text (GTK_EDITABLE (self->host));
+ consumer_key = gtk_editable_get_text (GTK_EDITABLE (self->oauth1_client_identifier));
+ consumer_secret = gtk_editable_get_text (GTK_EDITABLE (self->oauth1_client_secret));
+ function = gtk_editable_get_text (GTK_EDITABLE (self->function));
+
+ self->oauth1_proxy = oauth_proxy_new (consumer_key, consumer_secret, url, FALSE);
+ oauth_proxy_request_token_async (OAUTH_PROXY (self->oauth1_proxy), function, "https://www.gnome.org", NULL, demo_rest_page_fetched_oauth1_request_token, self);
+}
+
+static void
+on_oauth2_get_access_token_clicked (GtkButton *btn,
+ gpointer user_data)
+{
+ DemoRestPage *self = (DemoRestPage *)user_data;
+ GtkWidget *dialog = NULL;
+ const char *url = NULL;
+ const char *client_id = NULL, *client_secret = NULL;
+ const char *authurl = NULL, *tokenurl = NULL, *redirecturl = NULL;
+
+ url = gtk_editable_get_text (GTK_EDITABLE (self->host));
+ client_id = gtk_editable_get_text (GTK_EDITABLE (self->oauth2_client_identifier));
+ client_secret = gtk_editable_get_text (GTK_EDITABLE (self->oauth2_client_secret));
+ authurl = gtk_editable_get_text (GTK_EDITABLE (self->oauth2_auth_url));
+ tokenurl = gtk_editable_get_text (GTK_EDITABLE (self->oauth2_token_url));
+ redirecturl = gtk_editable_get_text (GTK_EDITABLE (self->oauth2_redirect_url));
+
+ self->oauth2_proxy = REST_PROXY (rest_oauth2_proxy_new (authurl, tokenurl, redirecturl, client_id, client_secret, url));
+ self->pkce = rest_pkce_code_challenge_new_random ();
+
+ dialog = demo_rest_page_create_oauth2_dialog (self, self->oauth2_proxy);
+
+ gtk_widget_show (dialog);
}
static void
@@ -103,6 +428,8 @@ call_returned (GObject *object,
{
if (g_strcmp0 (content_type, "application/json") == 0)
set_json_response (self, call);
+ else
+ set_text_response (self, call);
}
}
@@ -116,24 +443,101 @@ on_send_clicked (GtkButton *btn,
const gchar *function;
guint selected_method;
GListModel *model;
+ AuthMode auth_mode;
+ RestProxy *proxy;
g_return_if_fail (DEMO_IS_REST_PAGE (self));
selected_method = gtk_drop_down_get_selected (GTK_DROP_DOWN (self->httpmethod));
model = gtk_drop_down_get_model (GTK_DROP_DOWN (self->httpmethod));
http = gtk_string_list_get_string (GTK_STRING_LIST (model), selected_method);
+ auth_mode = get_current_auth_mode (self);
url = gtk_editable_get_text (GTK_EDITABLE (self->host));
function = gtk_editable_get_text (GTK_EDITABLE (self->function));
- RestProxy *proxy = rest_proxy_new (url, FALSE);
+ switch (auth_mode)
+ {
+ case AUTHMODE_NO:
+ proxy = rest_proxy_new (url, FALSE);
+ break;
+ // TODO: provide a direct auth possibility in librest
+ case AUTHMODE_BASIC:
+ {
+ const gchar *username, *password;
+
+ username = gtk_editable_get_text (GTK_EDITABLE (self->basic_username));
+ password = gtk_editable_get_text (GTK_EDITABLE (self->basic_password));
+
+ proxy = rest_proxy_new_with_authentication (url, FALSE, username, password);
+ break;
+ }
+ // thanks to libsoup there is no difference as the server defines the used authentication mechanism
+ case AUTHMODE_DIGEST:
+ {
+ const gchar *username, *password;
+
+ username = gtk_editable_get_text (GTK_EDITABLE (self->basic_username));
+ password = gtk_editable_get_text (GTK_EDITABLE (self->basic_password));
+
+ proxy = rest_proxy_new_with_authentication (url, FALSE, username, password);
+ break;
+ }
+ case AUTHMODE_OAUTH1:
+ {
+ g_object_set (self->oauth1_proxy, "url-format", url, NULL);
+ proxy = self->oauth1_proxy;
+
+ break;
+ }
+ case AUTHMODE_OAUTH2:
+ {
+ proxy = rest_proxy_new (url, FALSE);
+ break;
+ }
+ }
+ SoupLogger *logger = soup_logger_new (SOUP_LOGGER_LOG_BODY);
+ rest_proxy_add_soup_feature (proxy, SOUP_SESSION_FEATURE (logger));
RestProxyCall *call = rest_proxy_new_call (proxy);
rest_proxy_call_set_method (call, http);
rest_proxy_call_set_function (call, function);
+
+ /* get params */
+ model = demo_table_get_model (DEMO_TABLE (self->paramslb));
+ for (guint i = 0; i < g_list_model_get_n_items (model); i++)
+ {
+ DemoTableRow *h = (DemoTableRow *)g_list_model_get_item (model, i);
+ rest_proxy_call_add_param (call, demo_table_row_get_key (h), demo_table_row_get_value (h));
+ }
+
+ /* get headers */
+ model = demo_table_get_model (DEMO_TABLE (self->headerslb));
+ for (guint i = 0; i < g_list_model_get_n_items (model); i++)
+ {
+ DemoTableRow *h = (DemoTableRow *)g_list_model_get_item (model, i);
+ rest_proxy_call_add_header (call, demo_table_row_get_key (h), demo_table_row_get_value (h));
+ }
+
rest_proxy_call_invoke_async (call, NULL, call_returned, self);
}
static void
+on_auth_method_activated (GtkDropDown *dropdown,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ DemoRestPage *self = (DemoRestPage *)user_data;
+ g_autofree gchar *page_name = NULL;
+ GtkStringObject *obj = NULL;
+
+ g_return_if_fail (DEMO_IS_REST_PAGE (self));
+
+ obj = GTK_STRING_OBJECT (gtk_drop_down_get_selected_item (dropdown));
+ page_name = g_utf8_strdown (gtk_string_object_get_string (obj), -1);
+ gtk_stack_set_visible_child_name (GTK_STACK (self->authentication_stack), g_strdelimit (page_name, " ", '_'));
+}
+
+static void
demo_rest_page_class_init (DemoRestPageClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
@@ -147,7 +551,35 @@ demo_rest_page_class_init (DemoRestPageClass *klass)
gtk_widget_class_bind_template_child (widget_class, DemoRestPage, sourceview);
gtk_widget_class_bind_template_child (widget_class, DemoRestPage, body);
gtk_widget_class_bind_template_child (widget_class, DemoRestPage, notebook);
+ gtk_widget_class_bind_template_child (widget_class, DemoRestPage, authentication);
+ gtk_widget_class_bind_template_child (widget_class, DemoRestPage, authmethods);
+ gtk_widget_class_bind_template_child (widget_class, DemoRestPage, authentication_stack);
+ gtk_widget_class_bind_template_child (widget_class, DemoRestPage, headers);
+ gtk_widget_class_bind_template_child (widget_class, DemoRestPage, params);
+ gtk_widget_class_bind_template_child (widget_class, DemoRestPage, paramslb);
+ gtk_widget_class_bind_template_child (widget_class, DemoRestPage, headerslb);
+
+ /* basic auth */
+ gtk_widget_class_bind_template_child (widget_class, DemoRestPage, basic_username);
+ gtk_widget_class_bind_template_child (widget_class, DemoRestPage, basic_password);
+ /* digest auth */
+ gtk_widget_class_bind_template_child (widget_class, DemoRestPage, digest_username);
+ gtk_widget_class_bind_template_child (widget_class, DemoRestPage, digest_password);
+ /* oauth 1 auth */
+ gtk_widget_class_bind_template_child (widget_class, DemoRestPage, oauth1_client_identifier);
+ gtk_widget_class_bind_template_child (widget_class, DemoRestPage, oauth1_client_secret);
+ /* oauth 2 auth */
+ gtk_widget_class_bind_template_child (widget_class, DemoRestPage, oauth2_client_identifier);
+ gtk_widget_class_bind_template_child (widget_class, DemoRestPage, oauth2_client_secret);
+ gtk_widget_class_bind_template_child (widget_class, DemoRestPage, oauth2_auth_url);
+ gtk_widget_class_bind_template_child (widget_class, DemoRestPage, oauth2_token_url);
+ gtk_widget_class_bind_template_child (widget_class, DemoRestPage, oauth2_redirect_url);
+
+ /* callbacks */
gtk_widget_class_bind_template_callback (widget_class, on_send_clicked);
+ gtk_widget_class_bind_template_callback (widget_class, on_auth_method_activated);
+ gtk_widget_class_bind_template_callback (widget_class, on_oauth1_get_access_token_clicked);
+ gtk_widget_class_bind_template_callback (widget_class, on_oauth2_get_access_token_clicked);
}
static void
@@ -162,6 +594,9 @@ demo_rest_page_init (DemoRestPage *self)
gtk_source_buffer_set_style_scheme (GTK_SOURCE_BUFFER (buffer), style);
gtk_notebook_set_tab_label_text (GTK_NOTEBOOK (self->notebook), self->body, "Body");
+ gtk_notebook_set_tab_label_text (GTK_NOTEBOOK (self->notebook), self->params, "Params");
+ gtk_notebook_set_tab_label_text (GTK_NOTEBOOK (self->notebook), self->authentication, "Authentication");
+ gtk_notebook_set_tab_label_text (GTK_NOTEBOOK (self->notebook), self->headers, "Headers");
}
GtkWidget *
diff --git a/examples/demo/demo-rest-page.ui b/examples/demo/demo-rest-page.ui
index 417a96c..09d7c06 100644
--- a/examples/demo/demo-rest-page.ui
+++ b/examples/demo/demo-rest-page.ui
@@ -53,6 +53,339 @@
</child>
</object>
</child>
+ <child>
+ <object class="GtkScrolledWindow" id="params">
+ <child>
+ <object class="DemoTable" id="paramslb">
+ <property name="title">New Param...</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="authentication">
+ <property name="orientation">horizontal</property>
+ <property name="margin-start">6</property>
+ <property name="margin-end">6</property>
+ <property name="margin-top">6</property>
+ <property name="margin-bottom">6</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkDropDown" id="authmethods">
+ <property name="valign">start</property>
+ <property name="width-request">100</property>
+ <signal name="notify::selected-item" handler="on_auth_method_activated" swapped="no" object="DemoRestPage"/>
+ <property name="model">
+ <object class="GtkStringList" id="auth_method_options">
+ <items>
+ <item>No Auth</item>
+ <item>Basic</item>
+ <item>Digest</item>
+ <item>OAuth1</item>
+ <item>OAuth2</item>
+ </items>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStack" id="authentication_stack">
+ <property name="hexpand">true</property>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">no_auth</property>
+ <property name="child">
+ <object class="GtkBox"/>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">basic</property>
+ <property name="child">
+ <object class="GtkGrid">
+ <property name="column-spacing">8</property>
+ <property name="column-homogeneous">true</property>
+ <property name="row-spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Username:</property>
+ <property name="xalign">1.0</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="basic_username">
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Password:</property>
+ <property name="xalign">1.0</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="basic_password">
+ <property name="visibility">false</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">digest</property>
+ <property name="child">
+ <object class="GtkGrid">
+ <property name="column-spacing">8</property>
+ <property name="column-homogeneous">true</property>
+ <property name="row-spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Username:</property>
+ <property name="xalign">1.0</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="digest_username">
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Password:</property>
+ <property name="xalign">1.0</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="digest_password">
+ <property name="visibility">false</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">oauth1</property>
+ <property name="child">
+ <object class="GtkGrid">
+ <property name="column-spacing">8</property>
+ <property name="column-homogeneous">true</property>
+ <property name="row-spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Client Identifier:</property>
+ <property name="xalign">1.0</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="oauth1_client_identifier">
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Client Secret:</property>
+ <property name="xalign">1.0</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="oauth1_client_secret">
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="oauth1_get_access_token">
+ <property name="label">Get access token...</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">2</property>
+ </layout>
+ <signal name="clicked" handler="on_oauth1_get_access_token_clicked" swapped="no" object="DemoRestPage"/>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStackPage">
+ <property name="name">oauth2</property>
+ <property name="child">
+ <object class="GtkGrid">
+ <property name="column-spacing">8</property>
+ <property name="column-homogeneous">true</property>
+ <property name="row-spacing">6</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Client Identifier:</property>
+ <property name="xalign">1.0</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="oauth2_client_identifier">
+ <layout>
+ <property name="column">1</property>
+ <property name="row">0</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Client Secret:</property>
+ <property name="xalign">1.0</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="oauth2_client_secret">
+ <layout>
+ <property name="column">1</property>
+ <property name="row">1</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Authorization URL:</property>
+ <property name="xalign">1.0</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="oauth2_auth_url">
+ <layout>
+ <property name="column">1</property>
+ <property name="row">2</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Token URL:</property>
+ <property name="xalign">1.0</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="oauth2_token_url">
+ <layout>
+ <property name="column">1</property>
+ <property name="row">3</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="label">Redirect URL:</property>
+ <property name="xalign">1.0</property>
+ <layout>
+ <property name="column">0</property>
+ <property name="row">4</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="oauth2_redirect_url">
+ <layout>
+ <property name="column">1</property>
+ <property name="row">4</property>
+ </layout>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="oauth2_get_access_token">
+ <property name="label">Get access token...</property>
+ <layout>
+ <property name="column">1</property>
+ <property name="row">5</property>
+ </layout>
+ <signal name="clicked" handler="on_oauth2_get_access_token_clicked" swapped="no" object="DemoRestPage"/>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="headers">
+ <child>
+ <object class="DemoTable" id="headerslb">
+ <property name="title">New Header...</property>
+ </object>
+ </child>
+ </object>
+ </child>
</object>
</child>
</template>
diff --git a/examples/demo/demo-table-row.c b/examples/demo/demo-table-row.c
new file mode 100644
index 0000000..1070a2a
--- /dev/null
+++ b/examples/demo/demo-table-row.c
@@ -0,0 +1,149 @@
+/* demo-table-row.c
+ *
+ * Copyright 2022 Günther Wagner <info@gunibert.de>
+ *
+ * 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 3 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "demo-table-row.h"
+
+struct _DemoTableRow
+{
+ GObject parent_instance;
+
+ gchar *key;
+ gchar *value;
+};
+
+G_DEFINE_FINAL_TYPE (DemoTableRow, demo_table_row, G_TYPE_OBJECT)
+
+enum {
+ PROP_0,
+ PROP_KEY,
+ PROP_VALUE,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+DemoTableRow *
+demo_table_row_new (void)
+{
+ return g_object_new (DEMO_TYPE_TABLE_ROW, NULL);
+}
+
+static void
+demo_table_row_finalize (GObject *object)
+{
+ DemoTableRow *self = (DemoTableRow *)object;
+
+ g_clear_pointer (&self->key, g_free);
+ g_clear_pointer (&self->value, g_free);
+
+ G_OBJECT_CLASS (demo_table_row_parent_class)->finalize (object);
+}
+
+static void
+demo_table_row_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ DemoTableRow *self = DEMO_TABLE_ROW (object);
+
+ switch (prop_id)
+ {
+ case PROP_KEY:
+ g_value_set_string (value, self->key);
+ break;
+ case PROP_VALUE:
+ g_value_set_string (value, self->value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+demo_table_row_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ DemoTableRow *self = DEMO_TABLE_ROW (object);
+
+ switch (prop_id)
+ {
+ case PROP_KEY:
+ self->key = g_value_dup_string (value);
+ break;
+ case PROP_VALUE:
+ self->value = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+
+static void
+demo_table_row_class_init (DemoTableRowClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = demo_table_row_finalize;
+ object_class->get_property = demo_table_row_get_property;
+ object_class->set_property = demo_table_row_set_property;
+
+ properties [PROP_KEY] =
+ g_param_spec_string ("key",
+ "Key",
+ "Key",
+ "",
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ properties [PROP_VALUE] =
+ g_param_spec_string ("value",
+ "Value",
+ "Value",
+ "",
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+demo_table_row_init (DemoTableRow *self)
+{
+}
+
+gchar *
+demo_table_row_get_key (DemoTableRow *self)
+{
+ g_return_val_if_fail (DEMO_IS_TABLE_ROW (self), NULL);
+
+ return self->key;
+}
+
+gchar *
+demo_table_row_get_value (DemoTableRow *self)
+{
+ g_return_val_if_fail (DEMO_IS_TABLE_ROW (self), NULL);
+
+ return self->value;
+}
diff --git a/examples/demo/demo-table-row.h b/examples/demo/demo-table-row.h
new file mode 100644
index 0000000..ba93656
--- /dev/null
+++ b/examples/demo/demo-table-row.h
@@ -0,0 +1,35 @@
+/* demo-table-row.h
+ *
+ * Copyright 2022 Günther Wagner <info@gunibert.de>
+ *
+ * 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 3 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define DEMO_TYPE_TABLE_ROW (demo_table_row_get_type())
+
+G_DECLARE_FINAL_TYPE (DemoTableRow, demo_table_row, DEMO, TABLE_ROW, GObject)
+
+DemoTableRow *demo_table_row_new (void);
+gchar *demo_table_row_get_key (DemoTableRow *self);
+gchar *demo_table_row_get_value (DemoTableRow *self);
+
+G_END_DECLS
diff --git a/examples/demo/demo-table-row.ui b/examples/demo/demo-table-row.ui
new file mode 100644
index 0000000..7f71888
--- /dev/null
+++ b/examples/demo/demo-table-row.ui
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk" version="4.0"/>
+ <object class="GtkListBoxRow" id="row">
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <property name="valign">start</property>
+ <child>
+ <object class="GtkEntry" id="key">
+ <property name="hexpand">true</property>
+ <style>
+ <class name="flat"/>
+ <class name="frame"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkEntry" id="value">
+ <property name="hexpand">true</property>
+ <style>
+ <class name="flat"/>
+ <class name="frame"/>
+ </style>
+ </object>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+ </child>
+ <style>
+ <class name="headerrow"/>
+ </style>
+ </object>
+</interface>
diff --git a/examples/demo/demo-table.c b/examples/demo/demo-table.c
new file mode 100644
index 0000000..3591c5c
--- /dev/null
+++ b/examples/demo/demo-table.c
@@ -0,0 +1,210 @@
+/* demo-table.c
+ *
+ * Copyright 2022 Günther Wagner <info@gunibert.de>
+ *
+ * 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 3 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include "demo-table.h"
+#include "demo-table-row.h"
+
+struct _DemoTable
+{
+ GtkWidget parent_instance;
+ char *title;
+
+ GtkListBox *lb;
+ GListModel *model;
+
+ GtkWidget *dummy_row;
+};
+
+G_DEFINE_FINAL_TYPE (DemoTable, demo_table, GTK_TYPE_WIDGET)
+
+enum {
+ PROP_0,
+ PROP_TITLE,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+GtkWidget *
+demo_table_new (char *title)
+{
+ return g_object_new (DEMO_TYPE_TABLE, "title", title, NULL);
+}
+
+static void
+demo_table_finalize (GObject *object)
+{
+ DemoTable *self = (DemoTable *)object;
+
+ g_clear_pointer (&self->title, g_free);
+
+ G_OBJECT_CLASS (demo_table_parent_class)->finalize (object);
+}
+
+static void
+demo_table_dispose (GObject *object)
+{
+ DemoTable *self = (DemoTable *)object;
+
+ gtk_widget_unparent (GTK_WIDGET (self->lb));
+
+ G_OBJECT_CLASS (demo_table_parent_class)->dispose (object);
+}
+
+static void
+demo_table_activated (GtkListBox *box,
+ GtkListBoxRow *row,
+ gpointer user_data)
+{
+ DemoTable *self = (DemoTable *)user_data;
+
+ g_return_if_fail (DEMO_IS_TABLE (self));
+
+ if (GTK_WIDGET (row) == self->dummy_row)
+ {
+ GListStore *s = G_LIST_STORE (self->model);
+ g_list_store_append (s, demo_table_row_new ());
+ }
+}
+
+static GtkWidget *
+create_row (gpointer item,
+ gpointer user_data)
+{
+ GtkBuilder *builder;
+
+ builder = gtk_builder_new_from_resource ("/org/gnome/RestDemo/demo-table-row.ui");
+
+ GObject *key_entry = gtk_builder_get_object (builder, "key");
+ g_object_bind_property (key_entry, "text", item, "key", G_BINDING_DEFAULT);
+
+ GObject *value_entry = gtk_builder_get_object (builder, "value");
+ g_object_bind_property (value_entry, "text", item, "value", G_BINDING_DEFAULT);
+ return GTK_WIDGET (gtk_builder_get_object (builder, "row"));
+}
+
+static GtkWidget *
+create_dummy_row (DemoTable *self)
+{
+ GtkWidget *row;
+ GtkWidget *label;
+
+ g_return_val_if_fail (DEMO_IS_TABLE (self), NULL);
+
+ label = g_object_new (GTK_TYPE_LABEL,
+ "label", self->title,
+ "visible", TRUE,
+ "xalign", 0.0f,
+ NULL);
+ g_object_bind_property (self, "title", label, "label", G_BINDING_DEFAULT);
+ gtk_style_context_add_class (gtk_widget_get_style_context (label), "dim-label");
+
+ row = g_object_new (GTK_TYPE_LIST_BOX_ROW,
+ "child", label,
+ "visible", TRUE,
+ NULL);
+
+ return row;
+}
+
+static void
+demo_table_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ DemoTable *self = DEMO_TABLE (object);
+
+ switch (prop_id)
+ {
+ case PROP_TITLE:
+ g_value_set_string (value, self->title);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+demo_table_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ DemoTable *self = DEMO_TABLE (object);
+
+ switch (prop_id)
+ {
+ case PROP_TITLE:
+ g_clear_pointer (&self->title, g_free);
+ self->title = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+demo_table_class_init (DemoTableClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = demo_table_finalize;
+ object_class->dispose = demo_table_dispose;
+ object_class->set_property = demo_table_set_property;
+ object_class->get_property = demo_table_get_property;
+
+ properties [PROP_TITLE] =
+ g_param_spec_string ("title",
+ "Title",
+ "Title",
+ "",
+ (G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS));
+ g_object_class_install_property (object_class, PROP_TITLE,
+ properties [PROP_TITLE]);
+
+ gtk_widget_class_set_layout_manager_type (widget_class, GTK_TYPE_BOX_LAYOUT);
+}
+
+static void
+demo_table_init (DemoTable *self)
+{
+ self->lb = GTK_LIST_BOX (gtk_list_box_new ());
+ gtk_widget_set_hexpand (GTK_WIDGET (self->lb), TRUE);
+ gtk_list_box_set_selection_mode (GTK_LIST_BOX (self->lb), GTK_SELECTION_NONE);
+ gtk_widget_set_parent (GTK_WIDGET (self->lb), GTK_WIDGET (self));
+ g_signal_connect (self->lb, "row-activated", G_CALLBACK (demo_table_activated), self);
+
+ self->model = G_LIST_MODEL (g_list_store_new (DEMO_TYPE_TABLE_ROW));
+ gtk_list_box_bind_model (self->lb, self->model, create_row, self, NULL);
+
+ self->dummy_row = create_dummy_row (self);
+ gtk_list_box_append (self->lb, self->dummy_row);
+}
+
+GListModel *
+demo_table_get_model (DemoTable *self)
+{
+ g_return_val_if_fail (DEMO_IS_TABLE (self), NULL);
+
+ return self->model;
+}
diff --git a/examples/demo/demo-table.h b/examples/demo/demo-table.h
new file mode 100644
index 0000000..0b570db
--- /dev/null
+++ b/examples/demo/demo-table.h
@@ -0,0 +1,34 @@
+/* demo-table.h
+ *
+ * Copyright 2022 Günther Wagner <info@gunibert.de>
+ *
+ * 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 3 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define DEMO_TYPE_TABLE (demo_table_get_type())
+
+G_DECLARE_FINAL_TYPE (DemoTable, demo_table, DEMO, TABLE, GtkWidget)
+
+GtkWidget *demo_table_new (char *title);
+GListModel *demo_table_get_model (DemoTable *self);
+
+G_END_DECLS
diff --git a/examples/demo/demo-window.c b/examples/demo/demo-window.c
index 32681d5..63c48d8 100644
--- a/examples/demo/demo-window.c
+++ b/examples/demo/demo-window.c
@@ -38,14 +38,6 @@ demo_window_new (GtkApplication *app)
}
static void
-demo_window_finalize (GObject *object)
-{
- DemoWindow *self = (DemoWindow *)object;
-
- G_OBJECT_CLASS (demo_window_parent_class)->finalize (object);
-}
-
-static void
on_add_tab_clicked (GtkButton *btn,
gpointer user_data)
{
@@ -66,10 +58,8 @@ on_add_tab_clicked (GtkButton *btn,
static void
demo_window_class_init (DemoWindowClass *klass)
{
- GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
- object_class->finalize = demo_window_finalize;
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/RestDemo/demo-window.ui");
gtk_widget_class_bind_template_child (widget_class, DemoWindow, tabview);
gtk_widget_class_bind_template_callback (widget_class, on_add_tab_clicked);
diff --git a/examples/demo/meson.build b/examples/demo/meson.build
index dae36b3..43d77e9 100644
--- a/examples/demo/meson.build
+++ b/examples/demo/meson.build
@@ -2,6 +2,8 @@ demo_sources = [
'demo-main.c',
'demo-window.c',
'demo-rest-page.c',
+ 'demo-table.c',
+ 'demo-table-row.c',
]
demo_deps = [
diff --git a/examples/demo/org.gnome.RestDemo.gresource.xml b/examples/demo/org.gnome.RestDemo.gresource.xml
index ecde4f3..e58e441 100644
--- a/examples/demo/org.gnome.RestDemo.gresource.xml
+++ b/examples/demo/org.gnome.RestDemo.gresource.xml
@@ -3,5 +3,7 @@
<gresource prefix="/org/gnome/RestDemo">
<file>demo-window.ui</file>
<file>demo-rest-page.ui</file>
+ <file>demo-table-row.ui</file>
+ <file>style.css</file>
</gresource>
</gresources>
diff --git a/examples/demo/org.gnome.RestDemo.json b/examples/demo/org.gnome.RestDemo.json
index 1ce49cf..bd0fde4 100644
--- a/examples/demo/org.gnome.RestDemo.json
+++ b/examples/demo/org.gnome.RestDemo.json
@@ -24,6 +24,17 @@
],
"modules" : [
{
+ "name" : "libsoup",
+ "builddir" : true,
+ "buildsystem" : "meson",
+ "sources" : [
+ {
+ "type" : "git",
+ "url" : "https://gitlab.gnome.org/GNOME/libsoup.git"
+ }
+ ]
+ },
+ {
"name" : "librest-demo",
"builddir" : true,
"buildsystem" : "meson",
@@ -34,7 +45,8 @@
}
],
"config-opts" : [
- "-Dgtk_doc=false"
+ "-Dgtk_doc=false",
+ "-Dsoup2=false"
]
}
],
diff --git a/examples/demo/style.css b/examples/demo/style.css
new file mode 100644
index 0000000..266daf3
--- /dev/null
+++ b/examples/demo/style.css
@@ -0,0 +1,3 @@
+.headerrow {
+ padding: 0px;
+}