/* PSPPIRE - a graphical user interface for PSPP.
Copyright (C) 2011 Free Software Foundation, Inc.
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 .
*/
#include
#include
#include "psppire-scanf.h"
#include
#include
#include
#include
#include "xalloc.h"
static void psppire_scanf_class_init (PsppireScanfClass *class);
static void psppire_scanf_init (PsppireScanf *w);
G_DEFINE_TYPE (PsppireScanf, psppire_scanf, GTK_TYPE_BOX)
/* Properties */
enum
{
PROP_0,
PROP_FORMAT,
PROP_NCONV,
PROP_USE_UNDERLINE,
PROP_MNEMONIC_WIDGET
};
/* Create a GtkLabel and pack it into BOX.
The label is created using part of the string at S, and the directives
at DIRS[DIR_IDX] and subsequent.
After this function returns, *S points to the first unused character.
*/
static void
ship_label (PsppireScanf *box, const char **s,
const char_directives *dirs, size_t dir_idx)
{
GtkWidget *label ;
GString *str = g_string_new (*s);
if (dirs)
{
char_directive dir = dirs->dir[dir_idx];
int n = 0;
while (dir_idx < dirs->count && dir.conversion == '%')
{
g_string_erase (str, dir.dir_start - *s, 1);
dir = dirs->dir[++dir_idx];
n++;
}
g_string_truncate (str, dir.dir_start - *s - n);
if (dir_idx>= dirs->count)
*s = NULL;
else
*s = dir.dir_end;
}
label = gtk_label_new (str->str);
g_string_free (str, TRUE);
gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
gtk_widget_show (label);
}
static void
guts (PsppireScanf *scanf)
{
gint i;
arguments a;
const char *s = scanf->format;
/* Get the number of args into D */
g_return_if_fail (0 == printf_parse (scanf->format, &scanf->d, &a));
if (scanf->d.count> 0)
scanf->widgets = xcalloc (scanf->d.count, sizeof (*scanf->widgets));
/* A is not used, so get rid of it */
if (a.arg != a.direct_alloc_arg)
free (a.arg);
for (i = 0 ; i < scanf->d.count ; ++i)
{
GtkWidget **w;
char_directive dir = scanf->d.dir[i];
int precision = 0;
int width = 0;
if (dir.precision_start && dir.precision_end)
precision = g_ascii_strtoll (dir.precision_start + 1,
(char **) &dir.precision_end, 10);
if (dir.width_start && dir.width_end)
width = g_ascii_strtoll (dir.width_start, (char **) &dir.width_end, 10);
if (dir.dir_start> s)
ship_label (scanf, &s, &scanf->d, i);
if (dir.conversion == '%')
{
if (s) s++;
continue;
}
w = &scanf->widgets [dir.arg_index];
switch (dir.conversion)
{
case 'd':
case 'i':
case 'f':
{
*w = gtk_spin_button_new_with_range (0, 100.0, 1.0);
g_object_set (*w, "digits", precision, NULL);
}
break;
case 's':
*w = gtk_entry_new ();
break;
};
g_object_set (*w, "width-chars", width, NULL);
gtk_box_pack_start (GTK_BOX (scanf), *w, FALSE, FALSE, 0);
gtk_widget_show (*w);
}
if (s && *s)
ship_label (scanf, &s, NULL, 0);
}
static void
set_mnemonic (PsppireScanf *scanf)
{
if (scanf->use_underline || scanf->mnemonic_widget)
{
GList *l = gtk_container_get_children (GTK_CONTAINER (scanf));
while (l)
{
if (GTK_IS_LABEL (l->data))
{
const gchar *t = gtk_label_get_label (l->data);
if (g_strstr_len (t, -1, "_"))
{
g_object_set (l->data,
"use-underline", TRUE,
"mnemonic-widget", scanf->mnemonic_widget,
NULL);
break;
}
}
l = l->next;
}
g_list_free (l);
}
}
static void
psppire_scanf_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
PsppireScanf *scanf = PSPPIRE_SCANF (object);
switch (prop_id)
{
case PROP_FORMAT:
scanf->format = g_value_get_string (value);
guts (scanf);
break;
case PROP_MNEMONIC_WIDGET:
scanf->mnemonic_widget = g_value_get_object (value);
set_mnemonic (scanf);
break;
case PROP_USE_UNDERLINE:
scanf->use_underline = g_value_get_boolean (value);
set_mnemonic (scanf);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
};
}
static void
psppire_scanf_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
PsppireScanf *scanf = PSPPIRE_SCANF (object);
switch (prop_id)
{
case PROP_FORMAT:
g_value_set_string (value, scanf->format);
break;
case PROP_NCONV:
g_value_set_int (value, scanf->d.count);
break;
case PROP_USE_UNDERLINE:
g_value_set_boolean (value, scanf->use_underline);
break;
case PROP_MNEMONIC_WIDGET:
g_value_set_object (value, scanf->mnemonic_widget);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
};
}
static GObjectClass *parent_class = NULL;
static void
psppire_scanf_dispose (GObject *obj)
{
PsppireScanf *w = PSPPIRE_SCANF (obj);
if (w->dispose_has_run)
return;
/* Make sure dispose does not run twice. */
w->dispose_has_run = TRUE;
/* Chain up to the parent class */
G_OBJECT_CLASS (parent_class)->dispose (obj);
}
static void
psppire_scanf_finalize (GObject *obj)
{
PsppireScanf *w = PSPPIRE_SCANF (obj);
free (w->widgets);
if (w->d.dir != w->d.direct_alloc_dir)
free (w->d.dir);
/* Chain up to the parent class */
G_OBJECT_CLASS (parent_class)->finalize (obj);
}
static void
psppire_scanf_class_init (PsppireScanfClass *class)
{
GObjectClass *object_class = G_OBJECT_CLASS (class);
GParamSpec *format_spec =
g_param_spec_string ("format",
"Format",
"A Scanf style format string",
NULL,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
GParamSpec *nconv_spec =
g_param_spec_int ("n-conv",
"Conversions",
"The number of conversions in the format string",
0, G_MAXINT, 0,
G_PARAM_READABLE);
GParamSpec *use_underline_spec =
g_param_spec_boolean ("use-underline",
"Use Underline",
"If set, an underline in the text indicates the next character should be used for the mnemonic accelerator key",
FALSE,
G_PARAM_READWRITE);
GParamSpec *mnemonic_widget_spec =
g_param_spec_object ("mnemonic-widget",
"Mnemonic widget",
"The widget which is to be activated when the Scanf's mnemonic key is pressed. Has no effect if use-underline is false.",
GTK_TYPE_WIDGET,
G_PARAM_READWRITE);
parent_class = g_type_class_peek_parent (class);
object_class->dispose = psppire_scanf_dispose;
object_class->finalize = psppire_scanf_finalize;
object_class->set_property = psppire_scanf_set_property;
object_class->get_property = psppire_scanf_get_property;
g_object_class_install_property (object_class,
PROP_NCONV,
nconv_spec);
g_object_class_install_property (object_class,
PROP_FORMAT,
format_spec);
g_object_class_install_property (object_class,
PROP_USE_UNDERLINE,
use_underline_spec);
g_object_class_install_property (object_class,
PROP_MNEMONIC_WIDGET,
mnemonic_widget_spec);
}
static void
psppire_scanf_init (PsppireScanf *w)
{
w->dispose_has_run = FALSE;
gtk_orientable_set_orientation (GTK_ORIENTABLE (w), GTK_ORIENTATION_HORIZONTAL);
}
static gchar
psppire_get_conversion_char (PsppireScanf *w, gint n)
{
g_return_val_if_fail (n < w->d.count, '0円');
return w->d.dir[n].conversion;
}
GtkWidget *
psppire_scanf_get_child (PsppireScanf *w, gint n)
{
g_return_val_if_fail (n < w->d.count, NULL);
return w->widgets[n];
}
/*
This widget is a horizontal GtkBox populated with GtkLabel and GtkEntry widgets.
Each conversion in FMT will cause a GtkEntry (possibly a GtkSpinButton) to
be created. Any text between conversions produces a GtkLabel.
There should be N arguments following FMT should be of type GtkEntry **,
where N is the number of conversions.
These arguments will be filled with a pointer to the corresponding widgets.
Their properties may be changed, but they should not be unrefed.
*/
GtkWidget *
psppire_scanf_new (const gchar *fmt, ...)
{
gint n, i;
va_list ap;
GtkWidget *w = GTK_WIDGET (g_object_new (psppire_scanf_get_type (),
"format", fmt, NULL));
g_object_get (w, "n-conv", &n, NULL);
va_start (ap, fmt);
for (i = 0 ; i < n ; ++i)
{
GtkWidget **field;
if (psppire_get_conversion_char (PSPPIRE_SCANF (w), i) == '%')
continue;
field = va_arg (ap, GtkWidget **);
*field = psppire_scanf_get_child (PSPPIRE_SCANF (w), i);
}
va_end (ap);
return w;
}