/*-
 * Copyright (c) 2003-2004 Seth Kingsley.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgment:
 *    This product includes software developed by Seth Kingsley and
 *    contributors.
 * 4. Neither the author's name, nor any of the contributors names may be
 *    used to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include    <sys/cdefs.h>
#include    <sys/types.h>
#include    <sys/errno.h>
#include    <sys/signal.h>

#include    <glib.h>

#include    <libintl.h>

#include    <gtk/gtkmain.h>
#include    <gtk/gtkmenu.h>
#include    <gtk/gtkstock.h>
#include    <gtk/gtkmessagedialog.h>
#include    <gtk/gtktoolitem.h>
#include    <gtk/gtktoolbutton.h>
#include    <gtk/gtkarrow.h>
#include    <gtk/gtklabel.h>

#include    <xmk-gtk.h>

#include    <assert.h>
#include    <sysexits.h>
#ifdef DEBUG
#include    <err.h>
#endif /* DEBUG */

__RCSID("$Id: xmk-gtk.c,v 1.20 2004/07/22 11:16:20 sethk Exp $");

#define BUSY_KEY    "xmk-busy"

#ifdef GLADE2
#include    "support.h"
#endif /* GLADE2 */

struct xmk_ctool_data
{
    GtkButton	*xct_self;
    GtkButton	*xct_tool;
    GtkMenu	*xct_menu;
    gboolean	xct_popped;
};

static int  _status = EX_OK;

static void
_xmk_combo_tool_destroy(GtkToolButton *button, struct xmk_ctool_data *xctp)
{
    g_free(xctp);
}

static void
_xmk_combo_tool_enter(GtkButton *button, struct xmk_ctool_data *xctp)
{
    if (button == xctp->xct_self)
	gtk_button_set_relief(GTK_BUTTON(xctp->xct_tool), GTK_RELIEF_NORMAL);
    else
	gtk_button_set_relief(xctp->xct_self, GTK_RELIEF_NORMAL);
}

static void
_xmk_combo_tool_leave(GtkButton *button, struct xmk_ctool_data *xctp)
{
    if (button == xctp->xct_self)
    {
	if (xctp->xct_popped == TRUE)
	    return;

	gtk_button_set_relief(GTK_BUTTON(xctp->xct_tool), GTK_RELIEF_NONE);
    }
    else
	gtk_button_set_relief(xctp->xct_self, GTK_RELIEF_NONE);
}

static void
_xmk_combo_tool_deactivate(GtkMenu *menu, struct xmk_ctool_data *xctp)
{
    gtk_button_released(xctp->xct_self);
    gtk_button_set_relief(GTK_BUTTON(xctp->xct_tool), GTK_RELIEF_NONE);
    xctp->xct_popped = FALSE;
}

static void
_xmk_combo_tool_menu_pos(GtkMenu *menu,
	gint *xp, gint *yp,
	gboolean *push_inp,
	struct xmk_ctool_data *xctp)
{
#ifdef DEBUG_COMBO_TOOL
    warnx("window %p\n", GTK_WIDGET(xctp->xct_tool)->window);
#endif /* DEBUG_COMBO_TOOL */

    gdk_window_get_origin(GTK_WIDGET(xctp->xct_tool)->window, xp, yp);
    *xp+= GTK_WIDGET(xctp->xct_tool)->allocation.x;
    *yp+= GTK_WIDGET(xctp->xct_tool)->allocation.y +
	GTK_WIDGET(xctp->xct_tool)->allocation.height;
}

static void
_xmk_combo_tool_pressed(GtkButton *button, struct xmk_ctool_data *xctp)
{
    gtk_menu_popup(xctp->xct_menu,
	    NULL,
	    NULL,
	    (GtkMenuPositionFunc)_xmk_combo_tool_menu_pos,
	    xctp,
	    3,
	    gtk_get_current_event_time());
    xctp->xct_popped = TRUE;
}

static gchar *
_xmk_message(gboolean sys, const gchar *fmt, va_list ap)
{
    static gchar    msg[512 + 1];
    gint	    len;

    len = g_vsnprintf(msg, sizeof(msg), fmt, ap);
    if (sys)
	g_snprintf(msg + len, sizeof(msg) - len, ": %s", g_strerror(errno));
    return msg;
}

static void
_xmk_dialog_set_pos(GtkMessageDialog *dialog, GtkWindow *parent)
{
    if (parent)
	gtk_window_set_position(GTK_WINDOW(dialog),
		GTK_WIN_POS_CENTER_ON_PARENT);
    else
    {
	gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
	g_signal_connect((gpointer)dialog,
		"show",
		G_CALLBACK(xmk_window_focus),
		NULL);
    }
}

static GtkMessageDialog *
_xmk_warning_vcreate(GtkWindow *parent,
	gboolean sys,
	const gchar *fmt,
	va_list ap)
{
    GtkMessageDialog *dialog;

    dialog = GTK_MESSAGE_DIALOG(gtk_message_dialog_new(parent,
		(gtk_window_get_modal(parent) == TRUE) ?
		GTK_DIALOG_MODAL : GTK_DIALOG_DESTROY_WITH_PARENT,
		GTK_MESSAGE_WARNING,
		GTK_BUTTONS_OK,
		_xmk_message(sys, fmt, ap)));

    _xmk_dialog_set_pos(dialog, parent);
    gtk_window_set_title(GTK_WINDOW(dialog), "Warning");

    g_signal_connect_swapped((gpointer)dialog,
	    "response",
	    G_CALLBACK(gtk_widget_destroy),
	    GTK_OBJECT(dialog));

    if (gtk_window_get_modal(parent))
	gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);

    return dialog;
}

#ifdef DEBUG
static void
_xmk_trap_ttyin(int sig __unused)
{
    assert(!"background terminal read attempted");
}
#endif /* DEBUG */

void
xmk_window_focus(GtkWidget *widget, void *_udata __unused)
{
    gdk_window_focus(widget->window, GDK_CURRENT_TIME);
}

void
xmk_widget_hide_parent_window(GtkWidget *widget)
{
    gtk_widget_hide(gtk_widget_get_ancestor(widget, GTK_TYPE_WINDOW));
}

void
xmk_gtk_init(int *acp, char **avp[])
{
    mk_malloc = (void *(*)(size_t))g_malloc;
    mk_free = g_free;

#ifdef DEBUG
    signal(SIGTTIN, _xmk_trap_ttyin);
#endif /* DEBUG */

#ifdef ENABLE_NLS
    bindtextdomain(PACKAGE, PREFIX "/share/locale");
    bind_textdomain_codeset(PACKAGE, "UTF-8");
    textdomain(PACKAGE);
#endif /* ENABLE_NLS */

    gtk_init(acp, avp);
#ifdef GLADE2
#ifdef DEBUG
    add_pixmap_directory("../../pixmaps");
#endif /* DEBUG */
    add_pixmap_directory(SHARE_DIR "/pixmaps");
#endif /* GLADE2 */
}

int
xmk_gtk_exit(void)
{
#ifdef DEBUG
    warnx("%s: status %d", __func__, _status);
#endif /* DEBUG */

    return _status;
}

void
xmk_error(GtkWindow *parent, gint eval, gboolean sys, const gchar *fmt, ...)
{
    GtkMessageDialog	*dialog;
    va_list		ap;

    va_start(ap, fmt);
    dialog = GTK_MESSAGE_DIALOG(gtk_message_dialog_new(parent,
		GTK_DIALOG_MODAL,
		GTK_MESSAGE_ERROR,
		GTK_BUTTONS_NONE,
		_xmk_message(sys, fmt, ap)));
    _xmk_dialog_set_pos(dialog, parent);
    gtk_window_set_title(GTK_WINDOW(dialog), "Error");
    gtk_dialog_add_button(GTK_DIALOG(dialog),
	    GTK_STOCK_QUIT,
	    GTK_RESPONSE_OK);
    gtk_dialog_run(GTK_DIALOG(dialog));
    _status = eval;
    gtk_main_quit();
}

GtkMessageDialog *
xmk_warning_create(GtkWindow *parent, gboolean sys, const gchar *fmt, ...)
{
    va_list ap;

    va_start(ap, fmt);
    return _xmk_warning_vcreate(parent, sys, fmt, ap);
}

void
xmk_warning(GtkWindow *parent, gboolean sys, const gchar *fmt, ...)
{
    va_list ap;
    GtkMessageDialog *dialog;

    va_start(ap, fmt);
    dialog = _xmk_warning_vcreate(parent, sys, fmt, ap);
    gtk_window_present(GTK_WINDOW(dialog));
}

void
xmk_notify(GtkWindow *parent, const char *fmt, ...)
{
    GtkMessageDialog	*dialog;
    va_list		ap;

    va_start(ap, fmt);
    dialog = GTK_MESSAGE_DIALOG(gtk_message_dialog_new(parent,
		(gtk_window_get_modal(parent) == TRUE) ?
		GTK_DIALOG_MODAL : GTK_DIALOG_DESTROY_WITH_PARENT,
		GTK_MESSAGE_INFO,
		GTK_BUTTONS_OK,
		_xmk_message(FALSE, fmt, ap)));
    _xmk_dialog_set_pos(dialog, parent);
    gtk_window_set_title(GTK_WINDOW(dialog), "Information");
    g_signal_connect_swapped((gpointer)dialog,
	    "response",
	    G_CALLBACK(gtk_widget_destroy),
	    GTK_OBJECT(dialog));
    if (gtk_window_get_modal(parent))
	gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
    gtk_window_present(GTK_WINDOW(dialog));
}

void
xmk_combo_tool_bind(GtkButton *tool, GtkButton *ctool, GtkMenu *menu)
{
    struct xmk_ctool_data   *xctp;

    xctp = g_malloc(sizeof(*xctp));
    xctp->xct_self = ctool;
    xctp->xct_tool = tool;
    xctp->xct_menu = menu;
    xctp->xct_popped = FALSE;

    g_signal_connect((gpointer)xctp->xct_self,
	    "destroy",
	    G_CALLBACK(_xmk_combo_tool_destroy),
	    xctp);
    g_signal_connect((gpointer)xctp->xct_self,
	    "enter",
	    G_CALLBACK(_xmk_combo_tool_enter),
	    xctp);
    g_signal_connect((gpointer)xctp->xct_self,
	    "leave",
	    G_CALLBACK(_xmk_combo_tool_leave),
	    xctp);
    g_signal_connect((gpointer)tool,
	    "enter",
	    G_CALLBACK(_xmk_combo_tool_enter),
	    xctp);
    g_signal_connect((gpointer)tool,
	    "leave",
	    G_CALLBACK(_xmk_combo_tool_leave),
	    xctp);
    g_signal_connect((gpointer)xctp->xct_self,
	    "pressed",
	    G_CALLBACK(_xmk_combo_tool_pressed),
	    xctp);
    g_signal_connect((gpointer)xctp->xct_menu,
	    "deactivate",
	    G_CALLBACK(_xmk_combo_tool_deactivate),
	    xctp);
}

void
xmk_busy_push(GdkWindow *win)
{
    guint   busy = (guint)g_object_get_data((gpointer)win, BUSY_KEY);

    if (!busy)
    {
	static GdkCursor	*cursor = NULL;

	if (!cursor)
	    cursor = gdk_cursor_new(GDK_WATCH);
	gdk_window_set_cursor(win, cursor);
	gdk_flush();
    }
    ++busy;
    g_object_set_data((gpointer)win, BUSY_KEY, (gpointer)busy);
}

void
xmk_busy_pop(GdkWindow *win)
{
    guint   busy = (guint)g_object_get_data((gpointer)win, BUSY_KEY);

    assert(busy);
    if (!--busy)
    {
	static GdkCursor    *cursor;

	if (!cursor)
	    cursor = gdk_cursor_new(GDK_LEFT_PTR);
	gdk_window_set_cursor(win, cursor);
    }
    g_object_set_data((gpointer)win, BUSY_KEY, (gpointer)busy);
}

void
xmk_clist_scroll_select_bydata(GtkCList *clist, gint col, gpointer data)
{
    gint    row;

    row = gtk_clist_find_row_from_data(clist, data);
    assert(row != -1);

    gtk_clist_freeze(clist);
    gtk_clist_select_row(clist, row, col);
    gtk_clist_moveto(clist, row, col, 0, 0);
    gtk_clist_thaw(clist);
}

void
xmk_tree_view_scroll_select_iter(GtkTreeModel *model,
	GtkTreeView *view,
	GtkTreeSelection *select,
	GtkTreeIter *iter)
{
    GtkTreePath	*path;

    path = gtk_tree_model_get_path(model, iter);
    gtk_tree_selection_select_path(select, path);
    gtk_tree_view_scroll_to_cell(view,
	    path,
	    NULL,
	    FALSE,
	    0,
	    0);
    gtk_tree_path_free(path);
}

