/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ /* * 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 . * * Copyright 2013 Red Hat, Inc. */ /** * SECTION:nmt-newt-form * @short_description: The top-level NmtNewt widget * * #NmtNewtForm is the top-level widget that contains and presents a * "form" (aka dialog) to the user. */ #include "nm-default.h" #include #include #include #include "nmt-newt-form.h" #include "nmt-newt-button.h" #include "nmt-newt-grid.h" #include "nmt-newt-utils.h" G_DEFINE_TYPE (NmtNewtForm, nmt_newt_form, NMT_TYPE_NEWT_CONTAINER) #define NMT_NEWT_FORM_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NMT_TYPE_NEWT_FORM, NmtNewtFormPrivate)) typedef struct { newtComponent form; NmtNewtWidget *content; guint x, y, width, height; guint padding; gboolean fixed_x, fixed_y; gboolean fixed_width, fixed_height; char *title_lc; gboolean dirty, escape_exits; NmtNewtWidget *focus; #ifdef HAVE_NEWTFORMGETSCROLLPOSITION int scroll_position = 0; #endif } NmtNewtFormPrivate; enum { PROP_0, PROP_TITLE, PROP_FULLSCREEN, PROP_FULLSCREEN_VERTICAL, PROP_FULLSCREEN_HORIZONTAL, PROP_X, PROP_Y, PROP_WIDTH, PROP_HEIGHT, PROP_PADDING, PROP_ESCAPE_EXITS, LAST_PROP }; enum { QUIT, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; static void nmt_newt_form_redraw (NmtNewtForm *form); /** * nmt_newt_form_new: * @title: (allow-none): the form title * * Creates a new form, which will be shown centered on the screen. * Compare nmt_newt_form_new_fullscreen(). You can also position a * form manually by setting its #NmtNewtForm:x and #NmtNewtForm:y * properties at construct time, and/or by setting * #NmtNewtForm:fullscreen, #NmtNewtform:fullscreen-horizontal, or * #NmtNewtForm:fullscreen-vertical. * * If @title is NULL, the form will have no title. * * Returns: a new #NmtNewtForm */ NmtNewtForm * nmt_newt_form_new (const char *title) { return g_object_new (NMT_TYPE_NEWT_FORM, "title", title, NULL); } /** * nmt_newt_form_new_fullscreen: * @title: (allow-none): the form title * * Creates a new fullscreen form. Compare nmt_newt_form_new(). * * If @title is NULL, the form will have no title. * * Returns: a new #NmtNewtForm */ NmtNewtForm * nmt_newt_form_new_fullscreen (const char *title) { return g_object_new (NMT_TYPE_NEWT_FORM, "title", title, "fullscreen", TRUE, NULL); } static void nmt_newt_form_init (NmtNewtForm *form) { g_object_ref_sink (form); } static void nmt_newt_form_finalize (GObject *object) { NmtNewtFormPrivate *priv = NMT_NEWT_FORM_GET_PRIVATE (object); g_free (priv->title_lc); g_clear_object (&priv->focus); G_OBJECT_CLASS (nmt_newt_form_parent_class)->finalize (object); } static void nmt_newt_form_needs_rebuild (NmtNewtWidget *widget) { NmtNewtFormPrivate *priv = NMT_NEWT_FORM_GET_PRIVATE (widget); if (!priv->dirty) { priv->dirty = TRUE; nmt_newt_form_redraw (NMT_NEWT_FORM (widget)); } } static void nmt_newt_form_remove (NmtNewtContainer *container, NmtNewtWidget *widget) { NmtNewtFormPrivate *priv = NMT_NEWT_FORM_GET_PRIVATE (container); NmtNewtContainerClass *parent_class = NMT_NEWT_CONTAINER_CLASS (nmt_newt_form_parent_class); g_return_if_fail (widget == priv->content); parent_class->remove (container, widget); priv->content = NULL; } /** * nmt_newt_form_set_content: * @form: the #NmtNewtForm * @content: the form's content * * Sets @form's content to be @content. */ void nmt_newt_form_set_content (NmtNewtForm *form, NmtNewtWidget *content) { NmtNewtFormPrivate *priv = NMT_NEWT_FORM_GET_PRIVATE (form); NmtNewtContainerClass *parent_class = NMT_NEWT_CONTAINER_CLASS (nmt_newt_form_parent_class); if (priv->content) nmt_newt_form_remove (NMT_NEWT_CONTAINER (form), priv->content); priv->content = content; if (priv->content) parent_class->add (NMT_NEWT_CONTAINER (form), content); } static void nmt_newt_form_build (NmtNewtForm *form) { NmtNewtFormPrivate *priv = NMT_NEWT_FORM_GET_PRIVATE (form); int screen_height, screen_width, form_height, form_width; newtComponent *cos; int i; priv->dirty = FALSE; nmt_newt_widget_realize (NMT_NEWT_WIDGET (form)); nmt_newt_widget_size_request (priv->content, &form_width, &form_height); newtGetScreenSize (&screen_width, &screen_height); if (!priv->fixed_width) priv->width = MIN (form_width + 2 * priv->padding, screen_width - 2); if (!priv->fixed_height) priv->height = MIN (form_height + 2 * priv->padding, screen_height - 2); if (!priv->fixed_x) priv->x = (screen_width - form_width) / 2; if (!priv->fixed_y) priv->y = (screen_height - form_height) / 2; nmt_newt_widget_size_allocate (priv->content, priv->padding, priv->padding, priv->width - 2 * priv->padding, priv->height - 2 * priv->padding); if (priv->height - 2 * priv->padding < form_height) { newtComponent scroll_bar = newtVerticalScrollbar (priv->width - 1, 0, priv->height, NEWT_COLORSET_WINDOW, NEWT_COLORSET_ACTCHECKBOX); priv->form = newtForm (scroll_bar, NULL, NEWT_FLAG_NOF12); newtFormAddComponent (priv->form, scroll_bar); newtFormSetHeight (priv->form, priv->height - 2); } else priv->form = newtForm (NULL, NULL, NEWT_FLAG_NOF12); if (priv->escape_exits) newtFormAddHotKey (priv->form, NEWT_KEY_ESCAPE); cos = nmt_newt_widget_get_components (priv->content); for (i = 0; cos[i]; i++) newtFormAddComponent (priv->form, cos[i]); g_free (cos); if (priv->focus) { newtComponent fco; fco = nmt_newt_widget_get_focus_component (priv->focus); if (fco) newtFormSetCurrent (priv->form, fco); } #ifdef HAVE_NEWTFORMGETSCROLLPOSITION if (priv->scroll_position) newtFormSetScrollPosition (priv->form, priv->scroll_position); #endif newtOpenWindow (priv->x, priv->y, priv->width, priv->height, priv->title_lc); } static void nmt_newt_form_destroy (NmtNewtForm *form) { NmtNewtFormPrivate *priv = NMT_NEWT_FORM_GET_PRIVATE (form); #ifdef HAVE_NEWTFORMGETSCROLLPOSITION priv->scroll_position = newtFormGetScrollPosition (priv->form); #endif newtFormDestroy (priv->form); priv->form = NULL; newtPopWindowNoRefresh (); nmt_newt_widget_unrealize (NMT_NEWT_WIDGET (form)); } /* A "normal" newt program would call newtFormRun() to run newt's main loop * and process events. But we want to let GLib's main loop control the program * (eg, so libnm can process D-Bus notifications). So we call this function * to run a single iteration of newt's main loop (or rather, to run newt's * main loop for 1ms) whenever there are events for newt to process (redrawing * or keypresses). */ static void nmt_newt_form_iterate (NmtNewtForm *form) { NmtNewtFormPrivate *priv = NMT_NEWT_FORM_GET_PRIVATE (form); NmtNewtWidget *focus; struct newtExitStruct es; if (priv->dirty) { nmt_newt_form_destroy (form); nmt_newt_form_build (form); } newtFormSetTimer (priv->form, 1); newtFormRun (priv->form, &es); if ( es.reason == NEWT_EXIT_HOTKEY || es.reason == NEWT_EXIT_ERROR) { /* The user hit Esc or there was an error. */ g_clear_object (&priv->focus); nmt_newt_form_quit (form); return; } if (es.reason == NEWT_EXIT_COMPONENT) { /* The user hit Return/Space on a component; update the form focus * to point that that component, and activate it. */ focus = nmt_newt_widget_find_component (priv->content, es.u.co); if (focus) { nmt_newt_form_set_focus (form, focus); nmt_newt_widget_activated (focus); } } else { /* The 1ms timer ran out. Update focus but don't do anything else. */ focus = nmt_newt_widget_find_component (priv->content, newtFormGetCurrent (priv->form)); if (focus) nmt_newt_form_set_focus (form, focus); } } /* @form_stack keeps track of all currently-displayed forms, from top to bottom. * @keypress_source is the global stdin-monitoring GSource. When it triggers, * nmt_newt_form_keypress_callback() iterates the top-most form, so it can * process the keypress. */ static GSList *form_stack; static GSource *keypress_source; static gboolean nmt_newt_form_keypress_callback (int fd, GIOCondition condition, gpointer user_data) { g_return_val_if_fail (form_stack != NULL, FALSE); nmt_newt_form_iterate (form_stack->data); return TRUE; } static gboolean nmt_newt_form_timeout_callback (gpointer user_data) { if (form_stack) nmt_newt_form_iterate (form_stack->data); return FALSE; } static void nmt_newt_form_redraw (NmtNewtForm *form) { g_timeout_add (0, nmt_newt_form_timeout_callback, NULL); } static void nmt_newt_form_real_show (NmtNewtForm *form) { if (!keypress_source) { GIOChannel *io; io = g_io_channel_unix_new (STDIN_FILENO); keypress_source = g_io_create_watch (io, G_IO_IN); g_source_set_can_recurse (keypress_source, TRUE); g_source_set_callback (keypress_source, (GSourceFunc) nmt_newt_form_keypress_callback, NULL, NULL); g_source_attach (keypress_source, NULL); g_io_channel_unref (io); } nmt_newt_form_build (form); form_stack = g_slist_prepend (form_stack, g_object_ref (form)); nmt_newt_form_redraw (form); } /** * nmt_newt_form_show: * @form: an #NmtNewtForm * * Displays @form and begins running it asynchronously in the default * #GMainContext. If another form is currently running, it will remain * visible in the background, but will not be able to receive keyboard * input until @form exits. * * Call nmt_newt_form_quit() to quit the form. */ void nmt_newt_form_show (NmtNewtForm *form) { NMT_NEWT_FORM_GET_CLASS (form)->show (form); } /** * nmt_newt_form_run_sync: * @form: an #NmtNewtForm * * Displays @form as with nmt_newt_form_show(), but then iterates the * #GMainContext internally until @form exits. * * Returns: the widget whose activation caused @form to exit, or * %NULL if it was not caused by a widget. FIXME: this exit value is * sort of weird and may not be 100% accurate anyway. */ NmtNewtWidget * nmt_newt_form_run_sync (NmtNewtForm *form) { NmtNewtFormPrivate *priv = NMT_NEWT_FORM_GET_PRIVATE (form); nmt_newt_form_show (form); while (priv->form) g_main_context_iteration (NULL, TRUE); return priv->focus; } /** * nmt_newt_form_quit: * @form: an #NmtNewtForm * * Causes @form to exit. */ void nmt_newt_form_quit (NmtNewtForm *form) { NmtNewtFormPrivate *priv = NMT_NEWT_FORM_GET_PRIVATE (form); g_return_if_fail (priv->form != NULL); nmt_newt_form_destroy (form); form_stack = g_slist_remove (form_stack, form); if (form_stack) nmt_newt_form_iterate (form_stack->data); else if (keypress_source) { g_source_destroy (keypress_source); g_clear_pointer (&keypress_source, g_source_unref); } g_signal_emit (form, signals[QUIT], 0); g_object_unref (form); } /** * nmt_newt_form_set_focus: * @form: an #NmtNewtForm * @widget: the widget to focus * * Focuses @widget in @form. */ void nmt_newt_form_set_focus (NmtNewtForm *form, NmtNewtWidget *widget) { NmtNewtFormPrivate *priv = NMT_NEWT_FORM_GET_PRIVATE (form); g_return_if_fail (priv->form != NULL); if (priv->focus == widget) return; if (priv->focus) g_object_unref (priv->focus); priv->focus = widget; if (priv->focus) g_object_ref (priv->focus); } static void nmt_newt_form_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec) { NmtNewtFormPrivate *priv = NMT_NEWT_FORM_GET_PRIVATE (object); int screen_width, screen_height; switch (prop_id) { case PROP_TITLE: if (g_value_get_string (value)) { priv->title_lc = nmt_newt_locale_from_utf8 (g_value_get_string (value)); } else priv->title_lc = NULL; break; case PROP_FULLSCREEN: if (g_value_get_boolean (value)) { newtGetScreenSize (&screen_width, &screen_height); priv->x = priv->y = 2; priv->fixed_x = priv->fixed_y = TRUE; priv->width = screen_width - 4; priv->height = screen_height - 4; priv->fixed_width = priv->fixed_height = TRUE; } break; case PROP_FULLSCREEN_VERTICAL: if (g_value_get_boolean (value)) { newtGetScreenSize (&screen_width, &screen_height); priv->y = 2; priv->fixed_y = TRUE; priv->height = screen_height - 4; priv->fixed_height = TRUE; } break; case PROP_FULLSCREEN_HORIZONTAL: if (g_value_get_boolean (value)) { newtGetScreenSize (&screen_width, &screen_height); priv->x = 2; priv->fixed_x = TRUE; priv->width = screen_width - 4; priv->fixed_width = TRUE; } break; case PROP_X: if (g_value_get_uint (value)) { priv->x = g_value_get_uint (value); priv->fixed_x = TRUE; } break; case PROP_Y: if (g_value_get_uint (value)) { priv->y = g_value_get_uint (value); priv->fixed_y = TRUE; } break; case PROP_WIDTH: if (g_value_get_uint (value)) { priv->width = g_value_get_uint (value); priv->fixed_width = TRUE; } break; case PROP_HEIGHT: if (g_value_get_uint (value)) { priv->height = g_value_get_uint (value); priv->fixed_height = TRUE; } break; case PROP_PADDING: priv->padding = g_value_get_uint (value); break; case PROP_ESCAPE_EXITS: priv->escape_exits = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void nmt_newt_form_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec) { NmtNewtFormPrivate *priv = NMT_NEWT_FORM_GET_PRIVATE (object); switch (prop_id) { case PROP_TITLE: if (priv->title_lc) { g_value_take_string (value, nmt_newt_locale_to_utf8 (priv->title_lc)); } else g_value_set_string (value, NULL); break; case PROP_X: g_value_set_uint (value, priv->x); break; case PROP_Y: g_value_set_uint (value, priv->y); break; case PROP_WIDTH: g_value_set_uint (value, priv->width); break; case PROP_HEIGHT: g_value_set_uint (value, priv->height); break; case PROP_PADDING: g_value_set_uint (value, priv->padding); break; case PROP_ESCAPE_EXITS: g_value_set_boolean (value, priv->escape_exits); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; } } static void nmt_newt_form_class_init (NmtNewtFormClass *form_class) { GObjectClass *object_class = G_OBJECT_CLASS (form_class); NmtNewtContainerClass *container_class = NMT_NEWT_CONTAINER_CLASS (form_class); NmtNewtWidgetClass *widget_class = NMT_NEWT_WIDGET_CLASS (form_class); g_type_class_add_private (form_class, sizeof (NmtNewtFormPrivate)); /* virtual methods */ object_class->set_property = nmt_newt_form_set_property; object_class->get_property = nmt_newt_form_get_property; object_class->finalize = nmt_newt_form_finalize; widget_class->needs_rebuild = nmt_newt_form_needs_rebuild; container_class->remove = nmt_newt_form_remove; form_class->show = nmt_newt_form_real_show; /* signals */ /** * NmtNewtForm::quit: * @form: the #NmtNewtForm * * Emitted when the form quits. */ signals[QUIT] = g_signal_new ("quit", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (NmtNewtFormClass, quit), NULL, NULL, NULL, G_TYPE_NONE, 0); /** * NmtNewtForm:title: * * The form's title. If non-%NULL, this will be displayed above * the form in its border. */ g_object_class_install_property (object_class, PROP_TITLE, g_param_spec_string ("title", "", "", NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY)); /** * NmtNewtForm:fullscreen: * * If %TRUE, the form will fill the entire "screen" (ie, terminal * window). */ g_object_class_install_property (object_class, PROP_FULLSCREEN, g_param_spec_boolean ("fullscreen", "", "", FALSE, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY)); /** * NmtNewtForm:fullscreen-vertical: * * If %TRUE, the form will fill the entire "screen" (ie, terminal * window) vertically, but not necessarily horizontally. */ g_object_class_install_property (object_class, PROP_FULLSCREEN_VERTICAL, g_param_spec_boolean ("fullscreen-vertical", "", "", FALSE, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY)); /** * NmtNewtForm:fullscreen-horizontal: * * If %TRUE, the form will fill the entire "screen" (ie, terminal * window) horizontally, but not necessarily vertically. */ g_object_class_install_property (object_class, PROP_FULLSCREEN_HORIZONTAL, g_param_spec_boolean ("fullscreen-horizontal", "", "", FALSE, G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY)); /** * NmtNewtForm:x: * * The form's x coordinate. By default, the form will be centered * on the screen. */ g_object_class_install_property (object_class, PROP_X, g_param_spec_uint ("x", "", "", 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY)); /** * NmtNewtForm:y: * * The form's y coordinate. By default, the form will be centered * on the screen. */ g_object_class_install_property (object_class, PROP_Y, g_param_spec_uint ("y", "", "", 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY)); /** * NmtNewtForm:width: * * The form's width. By default, this will be determined by the * width of the form's content. */ g_object_class_install_property (object_class, PROP_WIDTH, g_param_spec_uint ("width", "", "", 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY)); /** * NmtNewtForm:height: * * The form's height. By default, this will be determined by the * height of the form's content. */ g_object_class_install_property (object_class, PROP_HEIGHT, g_param_spec_uint ("height", "", "", 0, G_MAXUINT, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY)); /** * NmtNewtForm:padding: * * The padding between the form's content and its border. */ g_object_class_install_property (object_class, PROP_PADDING, g_param_spec_uint ("padding", "", "", 0, G_MAXUINT, 1, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY)); /** * NmtNewtForm:escape-exits: * * If %TRUE, then hitting the Escape key will cause the form to * exit. */ g_object_class_install_property (object_class, PROP_ESCAPE_EXITS, g_param_spec_boolean ("escape-exits", "", "", FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_CONSTRUCT_ONLY)); }