/* logjam - a GTK client for LiveJournal.
 * Copyright (C) 2000-2003 Evan Martin <evan@livejournal.com>
 *
 * vim: tabstop=4 shiftwidth=4 noexpandtab :
 */

#include <glib.h>
#include "gtk-all.h"
#include <stdlib.h> /* atoi */

#include "conf.h"
#include "jam.h"
#include "pollcreator.h"
#include "util.h"

typedef enum {
	PQ_MOVE_UP,
	PQ_MOVE_DOWN
} move_direction;

typedef enum {
	PQ_RADIO = 0, /* first element must be zero */
	PQ_CHECK,
	PQ_COMBO,
	PQ_TEXT,
	PQ_SCALE,

	PQ_UNDEF      /* must be last */
} PQType;

struct pqtypeinfo {
	gchar *textname;
	gchar *genname;
	gchar *classname;
	PQType id;
} static pqtype[] = {
	{ N_("Radio buttons"),  "radio", "PollQMulti", PQ_RADIO },
	{ N_("Check boxes"),    "check", "PollQMulti", PQ_CHECK },
	{ N_("Drop-down menu"), "drop",  "PollQMulti", PQ_COMBO },
	{ N_("Text entry"),     "text",  "PollQText",  PQ_TEXT  },
	{ N_("Scale"),          "scale", "PollQScale", PQ_SCALE },
	
	{ N_("<undef>"),        NULL,    NULL,         PQ_UNDEF }
};

typedef enum {
	PSEC_ALL,
	PSEC_FRIENDS,
	PSEC_NONE
} PSec;

static char* psec_to_string[] = {
	"all",    
	"friends",
	"none"
};

typedef struct _PQInsert PQInsert;

struct _PQInsert {
	GtkHBox    InsertHBox;                 /* Parent widget */

	GtkWidget *InsertLabel, *TypeMenu, *AddB;
};

typedef struct _PollQ PollQ;

struct _PollQ {
	GtkFrame PQFrame;                 /* Parent widget */
	gint pqnumber;
	PQType type;
	gboolean delete;
	gboolean new;

	GtkWidget *PQVBox;
	GtkWidget *UpperVBox, *LowerHBox;
	
	GtkWidget *QuestionHBox, *QuestionLabel, *QuestionEntry, *QuestionErrorLabel;

	GtkWidget *pqmover, *AnswerHBox;

	GtkWidget *AnswerErrorLabel;
};

typedef struct _PollQText PollQText;

struct _PollQText {
	PollQ pollq;                 /* Parent widget */

	GtkWidget *ParamVBox;
	GtkWidget *SizeLabel, *SizeLJEntry, *MaxLabel, *MaxLJEntry;
};

typedef struct _PollQScale PollQScale;

struct _PollQScale {
	PollQ pollq;                 /* Parent widget */

	GtkWidget *ParamHBox;
	GtkWidget *FromLabel, *FromSpin, *ToLabel, *ToSpin, *ByLabel, *BySpin;
};

typedef struct _PQMover PQMover;

struct _PQMover {
	GtkFixed   mover;                 /* Parent widget */
	GtkWidget *MoveUpButton, *CloseButton, *MoveDownButton;
	GtkWidget *ownerpq;
};

typedef struct _Poll Poll;

struct _Poll {
	GtkScrolledWindow  PollScrolledWindow;                 /* Parent widget */
	GtkWidget         *PollLayout;
	GtkWidget         *PollVBox;
	gint               ElementCount;
	gint               ElementWidth;
	gboolean           errors;
};

typedef struct _PollWin PollWin;

struct _PollWin {
	GtkDialog win;                 /* Parent widget */
	GtkAccelGroup *accel_group;
	
	GtkWidget *pollmeta, *poll, *pqinsert, *controlbar;

	GtkWidget *meta_pollnamee, *meta_viewersom, *meta_votersom;

	GString   **polltext;
};

typedef struct _PollQMulti PollQMulti;
struct _PollQMulti {
	PollQ pollq;

	GtkWidget *AnswerVBox;
	GtkWidget *AnswerLabel, *AnswerLJEntryVBox, *LessMoreBAlignment;
	GtkWidget *LessMoreBBox, *LessB, *MoreB;
};

typedef struct {
	Poll *poll;
	gint  pqnumber;
	GtkWidget *ptypeLJEntry;
} PollQRequest;

enum {
	POLL_REFRESH,
	POLL_LAST_SIG
};

static gint poll_signals[POLL_LAST_SIG] = { 0 };

typedef struct _PollClass PollClass;

struct _PollClass {
	GtkVBox parent_class;
	void (*poll_refresh) (Poll *poll);
};

static void poll_class_init(PollClass *class);

#define POLLWIN(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), pollwin_get_type(), PollWin))
#define POLL(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), poll_get_type(), Poll))

#define POLLQ(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), pollq_get_type(), PollQ))

#define POLLQMULTI(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), pollqmulti_get_type(), PollQMulti))
#define POLLQTEXT(obj)  (G_TYPE_CHECK_INSTANCE_CAST((obj), pollqtext_get_type(),  PollQText))
#define POLLQSCALE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), pollqscale_get_type(), PollQScale))

#define PQINSERT(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), pqinsert_get_type(), PQInsert))
#define PQMOVER(obj)  (G_TYPE_CHECK_INSTANCE_CAST((obj), pqmover_get_type(),  PQMover))

#define JAM_TYPE_POLLQ (pollq_get_type())

/* macro to generate prototypes for type and instance init functions, as
 * well as the actual type function, in one swell foop. */
#define MAKETYPE(fname, newtypeclass, classinitf, instanceinitf, parenttype, parentclass) \
static void  instanceinitf(GtkWidget *w);                   \
static GType fname (void) G_GNUC_CONST;                     \
static GType                                                \
fname(void) {                                                   \
	static GType new_type = 0;                              \
	if (!new_type) {                                        \
		const GTypeInfo new_info = {                        \
			sizeof (parentclass),                           \
			NULL,                                           \
			NULL,                                           \
			(GClassInitFunc)classinitf,                     \
			NULL,                                           \
			NULL,                                           \
			sizeof (newtypeclass),                          \
			0,                                              \
			(GInstanceInitFunc) instanceinitf,              \
		};                                                  \
		new_type = g_type_register_static(parenttype,       \
				#newtypeclass, &new_info, 0);               \
	}                                                       \
	return new_type;                                        \
}

#define MENU_ADD(menu, mnemonic)                    \
	gtk_menu_shell_append(GTK_MENU_SHELL(menu),     \
		gtk_menu_item_new_with_mnemonic(mnemonic));

static gboolean polltext_generate(PollWin *pwin);

static GtkWidget* poll_new(void);
//static GtkWidget* pollq_new(gint pqnumber);
static GtkWidget* pollqmulti_new(PQType type);
static GtkWidget* pollqtext_new(void);
static GtkWidget* pollqscale_new(void);
static GtkWidget* pqmover_new(gint yoffset);
static GtkWidget* pqinsert_new(void);

static GtkWidget* make_pollmeta(PollWin *parent);
static GtkWidget* make_controlbar(PollWin *parent);
static GtkWidget* make_pollq(PollWin *pwin);

static void pollq_delete(GtkWidget *button, Poll *poll);

static void poll_generate_cb(PollWin *pwin);
static void poll_cancel_cb(PollWin *pwin);
static void poll_refresh_cb(Poll *poll);
static gboolean pollqmulti_less_cb(PollQMulti *pqm);
static gboolean pollqmulti_more_cb(PollQMulti *pqm);

static void pollq_moveup(GtkWidget *button, Poll *poll);
static void pollq_movedown(GtkWidget *button, Poll *poll);

/* static PQType _get_pqtype_from_name(G_CONST_RETURN gchar *text);*/

static gboolean pollq_validate(PollQ *pq);

static const gchar* _get_pqtype_name(PQType type);
/* autogenerated by MAKETYPE:
static void pollwin_init(PollWin *pwin);
static void poll_init(Poll *poll);
static void pollq_init(PollQ *pollq);
static void pqinsert_init(PQInsert *pqi);
static void pqmover_init(PQMover *pqm);
static void pollqmulti_init(PollQMulti *pollqm);
static void pollqtext_init(PollQText *pollqt);
static void pollqscale_init(PollQScale *pollqs);
*/

/* Poll Creator container */
MAKETYPE(pollwin_get_type, PollWin, NULL, pollwin_init, GTK_TYPE_DIALOG, GtkDialogClass)

/* Poll question container */
MAKETYPE(poll_get_type, Poll, poll_class_init, poll_init, \
		GTK_TYPE_SCROLLED_WINDOW, GtkScrolledWindowClass)

/* Helper widget */
MAKETYPE(pqmover_get_type,  PQMover,  NULL, pqmover_init, \
		GTK_TYPE_FIXED, GtkFixedClass)

/* Insert new question into poll widget */
MAKETYPE(pqinsert_get_type, PQInsert, NULL, pqinsert_init, \
		GTK_TYPE_HBOX,  GtkHBoxClass)

/* Poll questions
 * (Note how we give GtkFrameClass for derived PollQ types.
 * This is hopefully okay since there is no special class data for these types.)
 */
MAKETYPE(pollq_get_type,      PollQ,      NULL, pollq_init,      GTK_TYPE_FRAME, GtkFrameClass)
MAKETYPE(pollqscale_get_type, PollQScale, NULL, pollqscale_init, JAM_TYPE_POLLQ,  GtkFrameClass)
MAKETYPE(pollqtext_get_type,  PollQText,  NULL, pollqtext_init,  JAM_TYPE_POLLQ,  GtkFrameClass)
MAKETYPE(pollqmulti_get_type, PollQMulti, NULL, pollqmulti_init, JAM_TYPE_POLLQ,  GtkFrameClass)

/* begin particularly ugly section */

#define _DelButton(e)   (PQMOVER(POLLQ(e)->pqmover)->CloseButton)
#define _UpButton(e)    (PQMOVER(POLLQ(e)->pqmover)->MoveUpButton)
#define _DownButton(e)  (PQMOVER(POLLQ(e)->pqmover)->MoveDownButton)

/* the PollQ that owns this mover button. clunky, but it works */
#define _ButtonPollQ(b) POLLQ(PQMOVER(b->parent)->ownerpq)

/* do not free() what's returned from this macro */
#define _gEText(w)      (gchar*)gtk_entry_get_text(GTK_ENTRY(w))

/* end particularly ugly section */

static void
pollqmulti_init(GtkWidget *w) {
	PollQMulti *pqm = POLLQMULTI(w);
	gint i = 5;
	GtkWidget *pqmv = pqmover_new(40);
	GtkBox *ab;
	
	pqm->AnswerVBox = gtk_vbox_new(FALSE, 0);
	ab = GTK_BOX(pqm->AnswerVBox);
	gtk_box_pack_start(GTK_BOX(POLLQ(pqm)->AnswerHBox), GTK_WIDGET(ab),
			TRUE, TRUE, 0);

	pqm->AnswerLabel        = gtk_label_new(_("Possible answers:"));
	pqm->AnswerLJEntryVBox    = gtk_vbox_new(TRUE, 0);
	pqm->LessMoreBAlignment = gtk_alignment_new(0.95F, 0.50F, 0.10F, 1.0F);
	pqm->LessMoreBBox       = gtk_hbutton_box_new();

	gtk_box_pack_start(ab, pqm->AnswerLabel,        TRUE, TRUE, 0);
	gtk_box_pack_start(ab, pqm->AnswerLJEntryVBox,    TRUE, TRUE, 0);
	gtk_box_pack_start(ab, pqm->LessMoreBAlignment, TRUE, TRUE, 0);

	gtk_container_add(GTK_CONTAINER(pqm->LessMoreBAlignment),
			pqm->LessMoreBBox);

	while (i--) {
		gtk_box_pack_start(GTK_BOX(pqm->AnswerLJEntryVBox),
				gtk_entry_new(), TRUE, TRUE, 0);
	}

	/* pollq_init has already shown most of the rest of this widget
	 * (excluding the PQMover) */
	gtk_widget_show_all(pqm->AnswerVBox);

	/* not for gtk_widget_show_all() */
	/* for easy access from close button etc. */
	PQMOVER(pqmv)->ownerpq = GTK_WIDGET(pqm);
    POLLQ(pqm)->pqmover = pqmv;
	gtk_box_pack_start(GTK_BOX(POLLQ(pqm)->LowerHBox), pqmv, FALSE, FALSE, 0);

	/* <<Less button is only shown after More>> has been pressed */
	pqm->LessB = gtk_button_new_with_label(_("<< Less"));
	gtk_box_pack_start(GTK_BOX(pqm->LessMoreBBox), pqm->LessB, TRUE, TRUE, 0);

	/* But More>> is shown now. (Not with the show_all above because then
	 * it the order of Less and More is confused. :-( */
	pqm->MoreB = gtk_button_new_with_label(_("More >>"));
	gtk_box_pack_end(GTK_BOX(pqm->LessMoreBBox), pqm->MoreB, TRUE, TRUE, 0);
	gtk_widget_show(pqm->MoreB);
}

static void
pollqtext_init(GtkWidget *w) {
	PollQText *pqt   = POLLQTEXT(w);
	GtkWidget* hbox1 = gtk_hbox_new(FALSE, 5);
	GtkWidget* hbox2 = gtk_hbox_new(FALSE, 5);
	GtkWidget *pqm   = pqmover_new(0);
	pqt->SizeLabel   = gtk_label_new(NULL);
	pqt->MaxLabel    = gtk_label_new(NULL);
	pqt->SizeLJEntry   = gtk_entry_new();
	pqt->MaxLJEntry    = gtk_entry_new();
	pqt->ParamVBox   = gtk_vbox_new(FALSE, 5);
	
	gtk_box_pack_start(GTK_BOX( POLLQ(pqt)->AnswerHBox ), pqt->ParamVBox,
			FALSE, FALSE, 0);

	gtk_box_pack_start(GTK_BOX(pqt->ParamVBox), hbox1, FALSE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(pqt->ParamVBox), hbox2, FALSE, FALSE, 0);

	/* XXX: _does_ it make sense to issue accelerators to poll question
	 * fields? After all, there are going to be lots of identical
	 * accelerators around. For now, I'm leaving it with
	 * gtk_label_set_markup_with_mnemonic() but not connecting
	 * accelerators to the appropriate widgets. */
	gtk_label_set_markup_with_mnemonic(GTK_LABEL(pqt->SizeLabel),
			_("Text field size <i>(optional)</i>: "));
	gtk_label_set_markup_with_mnemonic(GTK_LABEL(pqt->MaxLabel),
			_("Maximum text length <i>(optional)</i>: "));
	gtk_entry_set_width_chars(GTK_ENTRY(pqt->SizeLJEntry), 4);
	gtk_entry_set_width_chars(GTK_ENTRY(pqt->MaxLJEntry), 4);

	gtk_box_pack_start(GTK_BOX(hbox1), pqt->SizeLabel, FALSE, FALSE, 0);
	gtk_box_pack_end(GTK_BOX(hbox1), pqt->SizeLJEntry, FALSE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(hbox2), pqt->MaxLabel, FALSE, FALSE, 0);
	gtk_box_pack_end(GTK_BOX(hbox2), pqt->MaxLJEntry, FALSE, FALSE, 0);

	gtk_widget_show_all(pqt->ParamVBox);

	/* not for gtk_widget_show_all() */
	/* for easy access from close button etc. */
	PQMOVER(pqm)->ownerpq = GTK_WIDGET(pqt);
    POLLQ(pqt)->pqmover = pqm;
	gtk_box_pack_start(GTK_BOX(POLLQ(pqt)->LowerHBox), pqm, FALSE, FALSE, 0);
}

static void
pollqscale_init(GtkWidget *w) {
	PollQScale *pqs = POLLQSCALE(w);
	GtkAdjustment *adj1 = (GtkAdjustment*)gtk_adjustment_new(
			1.0,  -32000.0, 32000.0, 1.0, 5.0, 5.0);
	GtkAdjustment *adj2 = (GtkAdjustment*)gtk_adjustment_new(
			10.0, -32000.0, 32000.0, 1.0, 5.0, 5.0);
	GtkAdjustment *adj3 = (GtkAdjustment*)gtk_adjustment_new(
			1.0,  -32000.0, 32000.0, 1.0, 5.0, 5.0);
	GtkWidget *pqm = pqmover_new(0);
	
	pqs->ParamHBox = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(POLLQ(pqs)->AnswerHBox), pqs->ParamHBox,
			FALSE, FALSE, 0);

	pqs->FromLabel = gtk_label_new(_("From: "));
	pqs->FromSpin  = gtk_spin_button_new(adj1, 1.0, 0);
	pqs->ToLabel   = gtk_label_new(_("  To: "));
	pqs->ToSpin    = gtk_spin_button_new(adj2, 1.0, 0);
	pqs->ByLabel   = gtk_label_new(_("  By: "));
	pqs->BySpin    = gtk_spin_button_new(adj3, 1.0, 0);
	
	gtk_box_pack_start(GTK_BOX(pqs->ParamHBox), pqs->FromLabel, FALSE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(pqs->ParamHBox), pqs->FromSpin, FALSE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(pqs->ParamHBox), pqs->ToLabel, FALSE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(pqs->ParamHBox), pqs->ToSpin, FALSE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(pqs->ParamHBox), pqs->ByLabel, FALSE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(pqs->ParamHBox), pqs->BySpin, FALSE, FALSE, 0);
	
	gtk_widget_show_all(GTK_WIDGET(pqs->ParamHBox));

	/* not for gtk_widget_show_all() */
	/* for easy access from close button etc. */
	PQMOVER(pqm)->ownerpq = GTK_WIDGET(pqs);
    POLLQ(pqs)->pqmover = pqm;
	gtk_box_pack_start(GTK_BOX(POLLQ(pqs)->LowerHBox), pqm, FALSE, FALSE, 0);
}

/* init everything possible for a generic PollQ. Note how we defer
 * setting up the PQMover so that we can do gtk_widget_show_all() and
 * also tell it where to show up according to PollQ type. */
static void
pollq_init(GtkWidget *w) {
	PollQ *pq = POLLQ(w);
	pq->delete = FALSE;
	pq->new    = TRUE;

    pq->PQVBox = gtk_vbox_new(FALSE, 0);
	gtk_container_add(GTK_CONTAINER(pq), pq->PQVBox);
    
	pq->UpperVBox = gtk_vbox_new(FALSE, 0);
	pq->LowerHBox = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_start(GTK_BOX(pq->PQVBox), pq->UpperVBox, FALSE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(pq->PQVBox), pq->LowerHBox, FALSE, FALSE, 0);

    pq->QuestionHBox  = gtk_hbox_new(FALSE, 0);
	pq->QuestionLabel = gtk_label_new(_("Question: "));
	pq->QuestionEntry = gtk_entry_new();
	gtk_entry_set_max_length(GTK_ENTRY(pq->QuestionEntry), 80);
	pq->QuestionErrorLabel = gtk_label_new(NULL);
	gtk_box_pack_start(GTK_BOX(pq->QuestionHBox), pq->QuestionLabel, FALSE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(pq->QuestionHBox), pq->QuestionEntry, FALSE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(pq->UpperVBox), pq->QuestionHBox, FALSE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(pq->UpperVBox), pq->QuestionErrorLabel, FALSE, FALSE, 0);

	pq->AnswerHBox = gtk_hbox_new(FALSE, 0);
	gtk_box_pack_end(GTK_BOX(pq->LowerHBox), pq->AnswerHBox, TRUE, TRUE, 0);

	pq->AnswerErrorLabel = gtk_label_new(NULL);
	gtk_label_set_line_wrap(GTK_LABEL(pq->AnswerErrorLabel), TRUE);
	gtk_widget_set_size_request(GTK_WIDGET(pq->AnswerErrorLabel), 150, -1);
	gtk_box_pack_end(GTK_BOX(pq->AnswerHBox), pq->AnswerErrorLabel,
			FALSE, FALSE, 0);
	
	/* Our children will pack an Answer here too, hopefully */

	gtk_widget_show_all(w);
}

static void
pqinsert_init(GtkWidget *w) {
	PQInsert *pqi = PQINSERT(w);
	GtkWidget *label  = gtk_label_new(NULL);
	GtkWidget *opmenu = gtk_option_menu_new();
	GtkWidget *menu   = gtk_menu_new();
	GtkWidget *button = gtk_button_new_from_stock(GTK_STOCK_ADD);
	gint i;

	pqi->InsertLabel  = label;
	pqi->TypeMenu     = opmenu;
	pqi->AddB         = button;

	gtk_label_set_markup_with_mnemonic(GTK_LABEL(label), _("Add _question: "));
	gtk_label_set_mnemonic_widget(GTK_LABEL(label), opmenu);

	for (i = 0; i < PQ_UNDEF; i++)
		MENU_ADD(menu, _(pqtype[i].textname));

	gtk_option_menu_set_menu(GTK_OPTION_MENU(opmenu), menu);
	gtk_option_menu_set_history(GTK_OPTION_MENU(opmenu), 0);

	gtk_box_pack_start(GTK_BOX(w), label, FALSE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(w), opmenu, FALSE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(w), button, FALSE, FALSE, 0);

	gtk_widget_show_all(w);
}

static void
pqmover_init(GtkWidget *w) {
	PQMover *pqm = PQMOVER(w);
	GtkWidget *closeb = gtk_button_new();
	GtkWidget *upb    = gtk_button_new();
	GtkWidget *downb  = gtk_button_new();

	pqm->CloseButton    = closeb;
	pqm->MoveUpButton   = upb;
	pqm->MoveDownButton = downb;

	gtk_container_add(GTK_CONTAINER(closeb), gtk_image_new_from_stock(
				GTK_STOCK_CLOSE, GTK_ICON_SIZE_SMALL_TOOLBAR));
	gtk_container_add(GTK_CONTAINER(upb), gtk_image_new_from_stock(
				GTK_STOCK_GO_UP, GTK_ICON_SIZE_SMALL_TOOLBAR));
	gtk_container_add(GTK_CONTAINER(downb), gtk_image_new_from_stock(
				GTK_STOCK_GO_DOWN, GTK_ICON_SIZE_SMALL_TOOLBAR));

	gtk_button_set_relief(GTK_BUTTON(closeb), GTK_RELIEF_NONE);
	gtk_button_set_relief(GTK_BUTTON(upb),    GTK_RELIEF_NONE);
	gtk_button_set_relief(GTK_BUTTON(downb),  GTK_RELIEF_NONE);

	gtk_tooltips_set_tip(app.tooltips, closeb, _("Delete question"),
			_("Deletes this question from the poll."));
	gtk_tooltips_set_tip(app.tooltips, upb, _("Move up"),
			_("Move this question up one place in the poll."));
	gtk_tooltips_set_tip(app.tooltips, downb, _("Move down"),
			_("Move this question down one place in the poll."));

	/* positioning and partial showing is done by pqmover_new(), because
	 * it knows about the desired location of the buttons in the
	 * GtkFixed widget */
}

static void
pollwin_init(GtkWidget *w) {
	PollWin *pwin  = POLLWIN(w);
	GtkWidget *vbox;

	pwin->pqinsert = pqinsert_new();
	pwin->poll     = poll_new();

	gtk_dialog_set_has_separator(GTK_DIALOG(w), FALSE);

	gtk_window_set_title(GTK_WINDOW(pwin), _("Poll Creator"));
	pwin->accel_group = gtk_accel_group_new();
	gtk_window_add_accel_group(GTK_WINDOW(pwin), GTK_ACCEL_GROUP(pwin->accel_group));
	gtk_window_set_modal(GTK_WINDOW(pwin), TRUE);

	vbox = gtk_vbox_new(FALSE, 6);

	gtk_box_pack_start(GTK_BOX(vbox), make_pollmeta(pwin),
			FALSE, FALSE, 5);
	gtk_box_pack_start(GTK_BOX(vbox), gtk_hseparator_new(),
			FALSE, FALSE, 0);
	
	gtk_box_pack_start(GTK_BOX(vbox), pwin->poll,
			TRUE, TRUE, 0);
	
	gtk_box_pack_start(GTK_BOX(vbox), gtk_hseparator_new(),
			FALSE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(vbox), pwin->pqinsert,
			FALSE, FALSE, 0);
	gtk_box_pack_start(GTK_BOX(vbox), make_controlbar(pwin),
			FALSE, FALSE, 3);

	g_signal_connect_swapped(G_OBJECT(PQINSERT(pwin->pqinsert)->AddB), "clicked",
			G_CALLBACK(make_pollq), pwin);

	jam_dialog_set_contents(GTK_DIALOG(pwin), vbox);
	gtk_widget_hide(GTK_DIALOG(pwin)->action_area);
}

static GtkWidget*
poll_new(void) {
	Poll *poll = POLL(g_object_new(poll_get_type(), NULL));
	return GTK_WIDGET(poll);
}

static void
poll_class_init(PollClass *class) {
	GtkObjectClass *object_class;
	object_class = (GtkObjectClass*) class;

	poll_signals[POLL_REFRESH] = g_signal_new("poll_refresh",
			G_TYPE_FROM_CLASS (object_class),
			G_SIGNAL_RUN_FIRST,
			0,
			NULL,
			NULL,
			g_cclosure_marshal_VOID__VOID,
			G_TYPE_NONE, 0, NULL);

	class->poll_refresh = NULL;
}

static void
poll_init(GtkWidget *w) {
	Poll *poll = POLL(w);

	poll->ElementCount = 0;
	poll->errors       = TRUE; /* can't create an empty poll */

	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(poll),
			GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

	poll->PollLayout = gtk_layout_new(NULL, NULL);

	/* Since we are a subclass of GtkScrolledWindow, we must set our
	 * construction-time properties with our own defaults. */
	gtk_scrolled_window_set_hadjustment(GTK_SCROLLED_WINDOW(w),
			gtk_layout_get_hadjustment(GTK_LAYOUT(poll->PollLayout)));
	gtk_scrolled_window_set_vadjustment(GTK_SCROLLED_WINDOW(w),
			gtk_layout_get_vadjustment(GTK_LAYOUT(poll->PollLayout)));
	
	gtk_container_add(GTK_CONTAINER(poll), poll->PollLayout);

	poll->PollVBox = gtk_vbox_new(FALSE, 5);
	gtk_layout_put(GTK_LAYOUT(poll->PollLayout), poll->PollVBox, 3, 3);

	g_signal_connect_swapped(G_OBJECT(w), "poll_refresh",
			G_CALLBACK(poll_refresh_cb), poll);

	/* hack: figure out how big pollqs are by instantiating scale
	 * and check question and deleting them. they are never shown. */
	{
		GtkWidget *pqs = pollqscale_new();
		GtkWidget *pqr = pollqmulti_new(PQ_RADIO);
		GtkRequisition rq;
		/* width: scale widget is widest */
		gtk_widget_size_request(pqs, &rq);
		poll->ElementWidth = rq.width;
		gtk_widget_destroy(pqs);

		/* heigth: radio (and other pollqmulti) is highest */
		gtk_widget_size_request(pqr, &rq);
		gtk_widget_set_size_request(GTK_WIDGET(poll),
				poll->ElementWidth + 10,
				rq.height + 32);
		gtk_widget_destroy(pqr);
	}
	
	gtk_widget_show_all(w);
}

static const gchar*
_get_pqtype_name(PQType type) {
	switch (type) {
		case PQ_RADIO:
		case PQ_CHECK:
		case PQ_COMBO:
		case PQ_TEXT:
		case PQ_SCALE:
			return _(pqtype[type].textname);
		default: g_error("unknown type [%d]", type);
	}
	/* not reached */
	return NULL; /* compiler warning */
}

/* construct a Poll Creator dialog. note that we inherit from GtkDialog
 * but suppress its action_area. In fact, the only reason (and not a
 * particularly *bad* reason) we are a GtkDialog and not a GtkWindow is
 * to exploit's GtkDialog's modality and protection from close events. */
static GtkWidget*
pollwin_new(GString **polltext, GtkWidget *parent) {
	PollWin *pwin  = POLLWIN(g_object_new(pollwin_get_type(), NULL));
	pwin->polltext = polltext;
	gtk_window_set_transient_for(GTK_WINDOW(pwin), GTK_WINDOW(parent));
	return GTK_WIDGET(pwin);
}

static GtkWidget*
pollqmulti_new(PQType type) {
	PollQMulti *pollqm;
	if (! ((type == PQ_RADIO) || (type == PQ_CHECK) || (type == PQ_COMBO)))
		g_error("mismatching PQType [%d] in pollq_multi_new()", type);

	pollqm = POLLQMULTI(g_object_new(pollqmulti_get_type(), NULL));
	POLLQ(pollqm)->type = type;

	return GTK_WIDGET(pollqm);
}

static GtkWidget*
pollqtext_new(void) {
	PollQText *pollqt   = POLLQTEXT(g_object_new(pollqtext_get_type(), NULL));
	POLLQ(pollqt)->type = PQ_TEXT;
	return GTK_WIDGET(pollqt);
}

static GtkWidget*
pollqscale_new(void) {
	PollQScale *pollqs  = POLLQSCALE(g_object_new(pollqscale_get_type(), NULL));
	POLLQ(pollqs)->type = PQ_SCALE;
	return GTK_WIDGET(pollqs);
}

static GtkWidget*
pqmover_new(gint yoffset) {
	PQMover *pqm = PQMOVER(g_object_new(pqmover_get_type(), NULL));
	gtk_fixed_put(GTK_FIXED(pqm), pqm->MoveUpButton,   0, yoffset);
	gtk_fixed_put(GTK_FIXED(pqm), pqm->CloseButton,    0, yoffset + 24);
	gtk_fixed_put(GTK_FIXED(pqm), pqm->MoveDownButton, 0, yoffset + 48);

	/* show close button, but not up and down buttons since this may be
	 * the first or the last question's mover. */
	gtk_widget_show_all(pqm->CloseButton);
	gtk_widget_show(GTK_WIDGET(pqm));

	return GTK_WIDGET(pqm);
}

static GtkWidget*
pqinsert_new(void) {
	PQInsert *pqi = PQINSERT(g_object_new(pqinsert_get_type(), NULL));
	return GTK_WIDGET(pqi);
}

static GtkWidget*
make_pollmeta(PollWin *parent) {
	GtkWidget *vbox = gtk_vbox_new(TRUE, 5);

	{ /* Poll name */
		GtkWidget *pnamebox = gtk_hbox_new(FALSE, 3);
		GtkWidget *label    = gtk_label_new(NULL);
		GtkWidget *entry    = gtk_entry_new();
		
		gtk_label_set_markup_with_mnemonic(GTK_LABEL(label),
				_("Poll _name <i>(optional)</i>: "));
		gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);
		gtk_box_pack_start(GTK_BOX(pnamebox), label, FALSE, FALSE, 0);
		gtk_box_pack_start(GTK_BOX(pnamebox), entry, FALSE, FALSE, 0);

		parent->meta_pollnamee = entry;
		gtk_box_pack_start(GTK_BOX(vbox), pnamebox, FALSE, FALSE, 0);
	}

	{ /* Poll visibility */
		GtkWidget *visibilitybox = gtk_hbox_new(FALSE, 3);
		GtkWidget *label1 = gtk_label_new(NULL);
		GtkWidget *label2 = gtk_label_new(NULL);
		GtkWidget *viewers = gtk_menu_new();
		GtkWidget *voters  = gtk_menu_new();
		GtkWidget *viewersopmenu = gtk_option_menu_new();
		GtkWidget *votersopmenu  = gtk_option_menu_new();

		gtk_label_set_markup_with_mnemonic(GTK_LABEL(label1), _("  V_oters: "));
		gtk_label_set_mnemonic_widget(GTK_LABEL(label2), votersopmenu);
		MENU_ADD(voters, _("All users"));
		MENU_ADD(voters, _("Friends"));
		gtk_option_menu_set_menu(GTK_OPTION_MENU(votersopmenu),
				GTK_WIDGET(voters));
		
		gtk_label_set_markup_with_mnemonic(GTK_LABEL(label2),
				_("  _Results visible to: "));
		gtk_label_set_mnemonic_widget(GTK_LABEL(label1), viewersopmenu);
		MENU_ADD(viewers, _("All users"));
		MENU_ADD(viewers, _("Friends"));
		MENU_ADD(viewers, Q_("users|None"));
		gtk_option_menu_set_menu(GTK_OPTION_MENU(viewersopmenu),
				GTK_WIDGET(viewers));


		gtk_box_pack_start(GTK_BOX(visibilitybox), label1, FALSE, FALSE, 0);
		gtk_box_pack_start(GTK_BOX(visibilitybox), votersopmenu, FALSE, FALSE, 0);
		gtk_box_pack_start(GTK_BOX(visibilitybox), label2, FALSE, FALSE, 0);
		gtk_box_pack_start(GTK_BOX(visibilitybox), viewersopmenu, FALSE, FALSE, 0);

		parent->meta_viewersom = viewersopmenu;
		parent->meta_votersom  = votersopmenu;
		gtk_box_pack_start(GTK_BOX(vbox), visibilitybox, FALSE, FALSE, 0);
	}
	gtk_widget_show_all(vbox);
	parent->pollmeta = vbox;
	return vbox;
}

static GtkWidget*
make_controlbar(PollWin *parent) {
	GtkWidget *bbox = gtk_hbutton_box_new();
	GtkWidget *refreshb  = gtk_button_new_from_stock(GTK_STOCK_REFRESH);
	GtkWidget *cancelb   = gtk_button_new_from_stock(GTK_STOCK_CANCEL);
	GtkWidget *generateb = gtk_button_new();

	GtkWidget *generatex = gtk_hbox_new(FALSE, 0);
	GtkWidget *generatel = gtk_label_new_with_mnemonic(_("_Generate"));
	GtkWidget *generatei = gtk_image_new_from_stock(GTK_STOCK_EXECUTE,
			GTK_ICON_SIZE_SMALL_TOOLBAR);

	gtk_button_box_set_layout(GTK_BUTTON_BOX(bbox), GTK_BUTTONBOX_END);
	gtk_box_set_spacing(GTK_BOX(bbox), 5);

	gtk_box_pack_start(GTK_BOX(bbox), refreshb, FALSE, FALSE, 0);
	gtk_widget_show(refreshb);
	g_signal_connect_swapped(G_OBJECT(refreshb), "clicked",
			G_CALLBACK(poll_refresh_cb), parent->poll);
	
	gtk_box_pack_start(GTK_BOX(bbox), cancelb, FALSE, FALSE, 0);
	gtk_widget_show(cancelb);
	g_signal_connect_swapped(G_OBJECT(cancelb), "clicked",
			G_CALLBACK(poll_cancel_cb), parent);

	gtk_container_add(GTK_CONTAINER(generateb), generatex);
	gtk_container_add(GTK_CONTAINER(generatex), generatei);
	gtk_container_add(GTK_CONTAINER(generatex), generatel);
	gtk_box_pack_start(GTK_BOX(bbox), generateb, FALSE, FALSE, 0);
	gtk_widget_show_all(generateb);
	g_signal_connect_swapped(G_OBJECT(generateb), "clicked",
			G_CALLBACK(poll_generate_cb), parent);
	gtk_widget_show_all(bbox);
	parent->controlbar = bbox;
	return bbox;
}

static GtkWidget*
make_pollq(PollWin *pwin) {
	Poll *poll  = POLL(pwin->poll);
	PQType type = gtk_option_menu_get_history(GTK_OPTION_MENU(
			PQINSERT(pwin->pqinsert)->TypeMenu));
	GtkWidget *pvbox  = poll->PollVBox;
	GtkWidget *pollq  = NULL;

	switch (type) {
		case PQ_RADIO:
		case PQ_CHECK:
		case PQ_COMBO:
			pollq = pollqmulti_new(type);
			g_signal_connect_swapped(G_OBJECT(POLLQMULTI(pollq)->MoreB),
					"clicked", G_CALLBACK(pollqmulti_more_cb), pollq);
			g_signal_connect_swapped(G_OBJECT(POLLQMULTI(pollq)->MoreB),
					"clicked", G_CALLBACK(poll_refresh_cb), poll);

			g_signal_connect_swapped(G_OBJECT(POLLQMULTI(pollq)->LessB),
					"clicked", G_CALLBACK(pollqmulti_less_cb), pollq);
			g_signal_connect_swapped(G_OBJECT(POLLQMULTI(pollq)->LessB),
					"clicked", G_CALLBACK(poll_refresh_cb), poll);
			break;

		case PQ_TEXT:
			pollq = pollqtext_new();
			break;

		case PQ_SCALE:
			pollq = pollqscale_new();
			break;

		default:
			g_error("make_pollq sanity check failed: type %d", type);
	}

	g_signal_connect(G_OBJECT(_UpButton(pollq)), "clicked",
			G_CALLBACK(pollq_moveup), poll);

	g_signal_connect(G_OBJECT(_DelButton(pollq)), "clicked",
			G_CALLBACK(pollq_delete), poll);

	g_signal_connect(G_OBJECT(_DownButton(pollq)), "clicked",
			G_CALLBACK(pollq_movedown), poll);

	poll->ElementCount ++;
	POLLQ(pollq)->pqnumber = poll->ElementCount;
	gtk_widget_set_size_request(pollq, poll->ElementWidth, -1);
	gtk_container_add(GTK_CONTAINER(pvbox), pollq);
	gtk_widget_show(pollq);

	poll_refresh_cb(poll);

	return pollq;
}

static void
poll_refresh_cb(Poll *poll) {
	static gint xsize = 0;
	gint ysize = 0;
	GtkBox *pbox    = GTK_BOX(poll->PollVBox);
	GList *elements = g_list_copy(pbox->children);
	GList *child;
	gint i;

	/* can't generate empty polls */
	poll->errors = poll->ElementCount > 0 ? FALSE : TRUE;

	for (i = 0, child = elements;
			child;
			i++, child = child->next) {

		GtkWidget *e     =
			GTK_WIDGET( ((GtkBoxChild*)(child->data))->widget );
		GtkWidget *up    = _UpButton(e);
		GtkWidget *down  = _DownButton(e);
		gchar      label[30];
		gint       num   = POLLQ(e)->pqnumber;
		GtkRequisition req;

		/* Delete question if so flagged */
		if (POLLQ(e)->delete) {
			/* will drop the objects refcourt to zero; it'll be destroyed. */
			gtk_container_remove(GTK_CONTAINER(pbox), e);
			continue;
		}

		/* Update mover buttons */
		if (num == 1) {                       /* first? */
			gtk_widget_hide_all(up);          /* if so, no up button */
		} else {
			gtk_widget_show_all(up);
		}
		if (num == poll->ElementCount) {      /* last? */
			gtk_widget_hide_all(down);        /* if so, no down button */
		} else {
			gtk_widget_show_all(down);
		}

		/* Reset label */
		g_snprintf(label, 30, _(" Question #%d - %s "), num,
				_get_pqtype_name(POLLQ(e)->type));
		gtk_frame_set_label(GTK_FRAME(e), label);

		/* Place question */
		gtk_box_reorder_child(pbox, e, num - 1); /* pqnumber is 1-based */

		/* Validate question */
		if (!pollq_validate(POLLQ(e)))
			poll->errors = TRUE;

		/* Keep track of layout size */
		gtk_widget_size_request(e, &req);
		ysize += req.height + 10;
	}
	/* assumption: xsize never changes; so we only need to check it once */
	if (!xsize)
		gtk_layout_get_size(GTK_LAYOUT(poll->PollLayout), &xsize, NULL);

	gtk_layout_set_size(GTK_LAYOUT(poll->PollLayout), xsize, ysize);
}

/* remove trailing blank optional answers from multiple-choice pollq,
 * leaving at least one entry.
 * 
 * This function is neither efficient nor GUI-smooth, but it is safe.
 * Note that the element list that GtkBoxen expose is read-only,
 * but luckily gtk_container_remove is atomic and keeps things in shape.
 * 
 * A cleaner approach, perhaps, would be to scan the GSlist in reverse,
 * building our own GList of doomed widgets in the meanwhile, then step
 * through that with gtk_container_remove; but since we're dealing with
 * very short lists we can afford finding the edge elements each time. */
static gboolean
pollqmulti_less_cb(PollQMulti *pqm) {
	gint i = 4;
	
	while (i--) {
		GList *first = GTK_BOX(pqm->AnswerLJEntryVBox)->children;
		GList *last = g_list_last(first);
		GtkWidget *cur;
		if (last && last->data && (last != first)) {
			cur = ((GtkBoxChild*)last->data)->widget;
			if (!g_ascii_strcasecmp("", gtk_entry_get_text(GTK_ENTRY(cur)))) {
				gtk_container_remove(GTK_CONTAINER(pqm->AnswerLJEntryVBox), cur);
			} else {
				break;
			}
		}
	}
	return FALSE; /* keep handling signal; this will lead to a refresh */
}

static gboolean
pollqmulti_more_cb(PollQMulti *pqm) {
	gint i = 4;
	while (i--) {
		gtk_box_pack_start(GTK_BOX(pqm->AnswerLJEntryVBox),
				gtk_entry_new(), TRUE, TRUE, 0);
	}
	gtk_widget_show_all(pqm->AnswerLJEntryVBox);
	gtk_widget_show(pqm->LessB);
	return FALSE; /* keep handling signal; this will lead to a refresh */
}

/* move a PollQ either up or down in the poll. this is a utility
 * function for pollq_moveup() and pollq_movedown().
 * The nice thing about these moves is that the whole poll need not be
 * scanned and updated: merely the adjacent question with which we are
 * switching places has to know about the switch. Of course, we must
 * walk over the poll until we find this adjacent question, but we don't
 * touch the other ones. */
static void
_pollq_move(move_direction dir, PollQ *mover, Poll *poll) {
	gint num = mover->pqnumber;
	GList *child;
	gboolean sanity = FALSE; /* this routine must find one adjacent PollQ */
	
	for (child = GTK_BOX(poll->PollVBox)->children; child;
			child = child->next) {
		PollQ *e = POLLQ( ((GtkBoxChild*)child->data)->widget );

		/* in up moves, the PollQ preceding "us" will move */
		if ((dir == PQ_MOVE_UP) && (e->pqnumber == num - 1)) {
			e->pqnumber ++;
			mover->pqnumber --;

			sanity = TRUE;
			break;
		}
		/* in down moves, it's the PollQ that follows "us" that moves. */
		if ((dir == PQ_MOVE_DOWN) && (e->pqnumber == num + 1)) {
			e->pqnumber --;
			mover->pqnumber ++;

			sanity = TRUE;
			break;
		}
	}
	if (!sanity)
		g_error("no poll element to switch with");

	/* the refresh routine will do the actual GtkBox reordering */
	g_signal_emit_by_name(G_OBJECT(poll), "poll_refresh");
}

static void
pollq_moveup(GtkWidget *button, Poll *poll) {
	_pollq_move(PQ_MOVE_UP, _ButtonPollQ(button), poll);
}

static void
pollq_movedown(GtkWidget *button, Poll *poll) {
	_pollq_move(PQ_MOVE_DOWN, _ButtonPollQ(button), poll);
}

/* when deleting a PollQ, all the PollQs following it must have their
 * pqnubers decreased. Also, keep track of the total number of PollQs. */
static void
pollq_delete(GtkWidget *button, Poll *poll) {
	PollQ *doomed = _ButtonPollQ(button);
	gint num = doomed->pqnumber;
	GList *child;

	doomed->delete = TRUE;
	poll->ElementCount --;

	for (child = GTK_BOX(poll->PollVBox)->children; child;
			child = child->next) {
		GtkWidget *e = GTK_WIDGET( ((GtkBoxChild*)child->data)->widget );
		
		if (POLLQ(e)->pqnumber > num)
			POLLQ(e)->pqnumber--;
	}

	/* the refresh routine will do the actual PollQ removal */
	g_signal_emit_by_name(G_OBJECT(poll), "poll_refresh");
}

/* when the user hits "generate", we initiate a refresh, which in turn
 * validates the poll. this way, polltext_generate() is never called on
 * a poll with errors. If all goes well, this closes the dialog after
 * putting the poll text where pwin->polltext points to. */
static void
poll_generate_cb(PollWin *pwin) {
	Poll *poll = POLL(pwin->poll);
	poll_refresh_cb(poll);
	if ((!poll->errors) && polltext_generate(pwin))
		poll_cancel_cb(pwin);
}

/* emulate closing the dialog. lifted from gtk_dialog_close(), an
 * unfortunately static function in gtkdialog.c */
static void
poll_cancel_cb(PollWin *pwin) {
	GdkEventAny event;
	GtkWidget *widget = GTK_WIDGET(pwin);
  
	event.type = GDK_DELETE;
	event.window = widget->window;
	event.send_event = TRUE;

	g_object_ref (G_OBJECT (event.window));

	gtk_main_do_event ((GdkEvent*)&event);

	g_object_unref (G_OBJECT (event.window));
}

/* The only public function in this module.
 *
 * Run the Poll Creator dialog, passing to it the location of a GString*
 * to which it should write a generated poll; if such is made, paste it
 * into the main LogJam entry at the current location. */
void
run_poll_creator_dlg(JamWin *jw) {
	GString       *polltext = NULL;
	PollWin       *pwin     = POLLWIN(pollwin_new(&polltext, GTK_WIDGET(jw)));
	GtkTextBuffer *buffer   = jam_doc_get_text_buffer(jw->doc);
	
	gtk_dialog_run(GTK_DIALOG(pwin));
	
	if (polltext) {
		gtk_text_buffer_delete_selection(buffer, FALSE, FALSE);
		gtk_text_buffer_insert_at_cursor(buffer,
				polltext->str, polltext->len);
		g_string_free(polltext, TRUE);
	}

	gtk_widget_destroy(GTK_WIDGET(pwin));
}

#if 0
static PQType
_get_pqtype_from_name(G_CONST_RETURN gchar *text) {
	struct pqtypeinfo *p = (struct pqtypeinfo *)pqtype;
	while (strcmp(text, p->textname)) {
		if (p->id == PQ_UNDEF)
			g_error("unknown type [%s]", text);

		p++;
	}
	return p->id;
}
#endif

static void
_validate_multi(PollQMulti *pqm, GtkWidget *anserrl, gboolean *rc) {
	gchar *data = NULL;
	data = _gEText(
		(GtkWidget*)(
			(GtkBoxChild*)(
				GTK_BOX( pqm->AnswerLJEntryVBox )
					->children)
				->data)
			->widget);
	/* whew. */
	if (data == NULL || *data == '\0') {
		gtk_label_set_markup(GTK_LABEL(anserrl),
				_("<b>[Fill in at least the first possible answer.]</b>"));
		gtk_widget_show(anserrl);
		*rc = FALSE;
	}
}

static void
_validate_text(PollQText *pqt, GtkWidget *anserrl, gboolean *rc) {
	gchar *data = NULL;
	gchar *tmp  = NULL;

	data = _gEText(pqt->SizeLJEntry);
	if (data && *data != '\0' &&
			g_ascii_strcasecmp(data, tmp = g_strdup_printf("%d", atoi(data)))) {
		gtk_label_set_markup(GTK_LABEL(anserrl),
				_("<b>[Text parameters must be integer.]</b>"));
		*rc = FALSE;
		gtk_widget_show(anserrl);
	}
	g_free(tmp);
	tmp = NULL;

	data = _gEText(pqt->MaxLJEntry);
	if (data && *data != '\0' &&
			g_ascii_strcasecmp(data, tmp = g_strdup_printf("%d", atoi(data)))) {
		gtk_label_set_markup(GTK_LABEL(anserrl),
				_("<b>[Text parameters must be integer.]</b>"));
		*rc = FALSE;
		gtk_widget_show(anserrl);
	}
	g_free(tmp);
}

static void
_validate_scale(PollQScale *pqs, GtkWidget *anserrl, gboolean *rc) {
	gint from, to, by;
	from = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pqs->FromSpin));
	to   = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pqs->ToSpin));
	by   = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pqs->BySpin));

	/* make sure 20 elements cover scale range */
	if (( (by > 0) && ((from + 19*by) < to) ) ||
			( (by < 0) && ((from + 19*by) > to)) ) {
		gtk_label_set_markup(GTK_LABEL(anserrl),
				_("<b>[Too many elements in scale range. Either "
				"move \"From\" and \"To\" values closer or increase "
				"\"By\".]</b>"));
		*rc = FALSE;
		gtk_widget_show(anserrl);
	}

	if (( (by > 0) && (from > to) ) ||
			( (by < 0) && (from < to) )) {
		gtk_label_set_markup(GTK_LABEL(anserrl),
				_("<b>[\"By\" has wrong sign.]</b>"));
		*rc = FALSE;
		gtk_widget_show(anserrl);
	}

	if (by == 0) {
		gtk_label_set_markup(GTK_LABEL(anserrl),
				_("<b>[\"By\" cannot be zero.]</b>"));
		*rc = FALSE;
		gtk_widget_show(anserrl);
	}
}

static gboolean
pollq_validate(PollQ *pq) {
	gboolean rc        = TRUE;
	GtkWidget *qerrl   = pq->QuestionErrorLabel;
	GtkWidget *anserrl = pq->AnswerErrorLabel;
	gchar *data        = NULL;

	/* if this PollQ has just been made, it won't pass validation; but
	 * there's no sense annoying the user about it now. */
	if (pq->new) {
		pq->new = FALSE; /* it won't *stay* new, though */
		return FALSE;
	}
	
	/* clear error labels */
	gtk_label_set_text(GTK_LABEL(qerrl), NULL);
	gtk_label_set_text(GTK_LABEL(anserrl), NULL);
	
	/* common check for all PollQs */
	data = _gEText(pq->QuestionEntry);
	if (data == NULL || *data == '\0' ) {
		gtk_label_set_markup(GTK_LABEL(qerrl),
				_("<b>[Fill in some question text.]</b>"));
		gtk_widget_show(qerrl);
		rc = FALSE;
	}

	/* type-specific checks */
	switch (pq->type) {
		case PQ_RADIO:
		case PQ_CHECK:
		case PQ_COMBO:
			_validate_multi(POLLQMULTI(pq), anserrl, &rc);
			break;

		case PQ_TEXT:
			_validate_text(POLLQTEXT(pq), anserrl, &rc);
			break;

		case PQ_SCALE:
			_validate_scale(POLLQSCALE(pq), anserrl, &rc);
			break;

		default:
			g_error("unknown poll question type in pollq_validate() [#%d]",
					pq->type);
	}
	return rc;
}

static void
_generate_multi(PollQ *e, gchar *genname, GString *ptext) {
	GList     *answer;
	GtkWidget *answerbox;
	gchar *data = NULL;

	g_string_append_printf(ptext, "<lj-pq type=\"%s\">\n", genname);
	g_string_append_printf(ptext, "%s\n", _gEText(e->QuestionEntry));

	answerbox = POLLQMULTI(e)->AnswerLJEntryVBox;
	for (answer = GTK_BOX(answerbox)->children; answer;
			answer = answer->next) {
		data = _gEText( ((GtkBoxChild*)(answer->data))->widget );
		if (data && *data != '\0') /* skips blank lines */
			g_string_append_printf(ptext, "<lj-pi>%s</lj-pi>\n", data);
	}
	g_string_append(ptext, "</lj-pq>\n\n");
}

static void
_generate_text(PollQ *e, gchar *genname, GString *ptext) {
	gchar *data = NULL;

	g_string_append_printf(ptext, "<lj-pq type=\"%s\"", genname);

	data = _gEText(POLLQTEXT(e)->SizeLJEntry);
	if (data && *data != '\0')
		g_string_append_printf(ptext, " size=\"%s\"", data);

	data = _gEText(POLLQTEXT(e)->MaxLJEntry);
	if (data && *data != '\0')
		g_string_append_printf(ptext, " maxlength=\"%s\"", data);

	data = _gEText(e->QuestionEntry);
	g_string_append_printf(ptext, ">\n%s\n</lj-pq>\n\n", data);
}

static void
_generate_scale(PollQ *e, gchar *genname, GString *ptext) {
	g_string_append_printf(ptext,
			"<lj-pq type=\"%s\" from=\"%d\" to=\"%d\" by=\"%d\">"
			"\n%s\n</lj-pq>\n\n",
			genname,
			gtk_spin_button_get_value_as_int(
				GTK_SPIN_BUTTON( POLLQSCALE(e)->FromSpin) ),
			gtk_spin_button_get_value_as_int(
				GTK_SPIN_BUTTON( POLLQSCALE(e)->ToSpin) ),
			gtk_spin_button_get_value_as_int(
				GTK_SPIN_BUTTON( POLLQSCALE(e)->BySpin) ),
			_gEText(e->QuestionEntry));
}

/* generate poll from GUI. this is always called on an errorless poll
 * (see poll_generate_cb()).
 *
 * The "*pwin->polltext = ptext;" line near the end of this function is
 * the whole point of the Poll Creator UI: pwin->polltext is a pointer
 * to a GString* that acts like an out variable of the whole dialog. */
static gboolean
polltext_generate(PollWin *pwin) {
	Poll      *poll  = POLL(pwin->poll);
	GString   *ptext;
	gchar     *data;
	gint       i;
	GList     *child;
	
	if (poll->errors || poll->ElementCount < 1)
		return FALSE;
	
	ptext = g_string_sized_new(2048);
	
	/* poll open tag */
	data = _gEText(pwin->meta_pollnamee);
	if (data) {
		g_string_append_printf(ptext, "<lj-poll name=\"%s\" ", data);
	} else {
		g_string_append(ptext, "<lj-poll ");
	}

	/* ...with metadata */
	i = gtk_option_menu_get_history(GTK_OPTION_MENU(pwin->meta_viewersom));
	g_string_append_printf(ptext, "whoview=\"%s\" ", psec_to_string[i]);
	
	i = gtk_option_menu_get_history(GTK_OPTION_MENU(pwin->meta_votersom));
	g_string_append_printf(ptext, "whovote=\"%s\">\n\n", psec_to_string[i]);
	
	/* poll questions */
	for (child = GTK_BOX(poll->PollVBox)->children; child;
			child = child->next) {
		PollQ *e = POLLQ( ((GtkBoxChild*)child->data)->widget );
		gchar *genname;
		PQType type = e->type;
		genname = pqtype[type].genname;

		switch (type) {
			case PQ_RADIO:
			case PQ_CHECK:
			case PQ_COMBO:
				_generate_multi(e, genname, ptext);
				break;
				
			case PQ_TEXT:
				_generate_text(e, genname, ptext);
				break;
				
			case PQ_SCALE:
				_generate_scale(e, genname, ptext);
				break;

			default:
				g_error("unknown poll question type in polltext_generate()"
						"[#%d]", type);
		}
	}

	/* closing tag (duh) */
	g_string_append(ptext, "</lj-poll>\n");

	/* set the "out variable" of whole poll creator widget set */
	*pwin->polltext = ptext;
	return TRUE;
}
