/* xdaliclock - a melting digital clock
 * Copyright (c) 1991-2010 Jamie Zawinski <jwz@jwz.org>
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.  No representations are made about the suitability of this
 * software for any purpose.  It is provided "as is" without express or
 * implied warranty.
 *
 * Version 1 of this program used only Xlib, not Xt.  The dynamically linked
 * sparc executable was 81k, and geometry and resource handling were kludgy
 * and broken, because I had to duplicate the obscure, undocumented things
 * that Xt does.  Version 2 uses Xt, and geometry and resource handling work
 * correctly - but the dynamically linked executable has more than tripled in
 * size, swelling to 270k.  This is what is commonly refered to as "progress."
 * (Bear in mind that the first (or second) program ever to implement this
 * algorithm ran perfectly well on a Macintosh with a 128k address space.)
 */

#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <time.h>

#include <gtk/gtk.h>

#include "version.h"
#include "xdaliclock.h"
#include "interface.h"
#include "support.h"
#include "hsv.h"

#include <gdk/gdkx.h>

#ifdef HAVE_GTK2
# include <glade/glade-xml.h>
# include <gmodule.h>
#else  /* !HAVE_GTK2 */
# define G_MODULE_EXPORT /**/
#endif /* !HAVE_GTK2 */

char *progname;

typedef struct {
  GtkWidget *toplevel;
  GtkWidget *draw;     /* the GtkDrawingArea */
  dali_config edit;
  dali_config config;

  gboolean update_allowed_p;

  GdkGC *gc;
  gint window_depth;
  guchar *rgb_buf;
  GdkColor fg, bg;
  gint hue_tick;

} clock_data;


static void usage (void);

static void
wm_toplevel_close_cb (GtkWidget *widget, GdkEvent *event, gpointer data)
{
  gtk_main_quit ();
}


static void
render_reset (clock_data *cd)
{
  GtkWidget *widget = cd->draw;
  int ow = cd->config.width;
  int oh = cd->config.height;

  if (cd->config.width  <= 0) cd->config.width  = 1;
  if (cd->config.height <= 0) cd->config.height = 1;

  cd->config.width  = widget->allocation.width;
  cd->config.height = widget->allocation.height;
  render_bitmap_size (&cd->config,
                      &cd->config.width, &cd->config.height,
                      &cd->config.width2, &cd->config.height2);

  if (cd->config.width != ow ||
      cd->config.height != oh ||
      !cd->config.bitmap ||
      !cd->config.render_state)
    {
      if (cd->rgb_buf)
        free (cd->rgb_buf);
      cd->rgb_buf = calloc (1, cd->config.height * cd->config.width * 3);
      if (! cd->rgb_buf) abort();

      if (cd->config.bitmap)
        free (cd->config.bitmap);
      cd->config.bitmap = 0;

      if (cd->config.render_state)
        render_free (&cd->config);

      render_init (&cd->config);
      if (! cd->config.bitmap) abort();
    }
}


static void
set_colors (clock_data *cd, gboolean init_p)
{
  GdkWindow *win = cd->toplevel->window;
  GdkColormap *cmap = gdk_window_get_colormap (win);
  gboolean changed_p = FALSE;

  if (init_p && !cd->config.cycle_p)
    {
      cd->fg.red = cd->fg.green = cd->fg.blue = 0xFFFF;
      cd->bg.red = cd->bg.green = cd->bg.blue = 0x0000;

      if (!gdk_colormap_alloc_color (cmap, &cd->fg, FALSE, TRUE))
        {
          fprintf (stderr, "%s: unable to allocate #%02x%02x%02x\n",
                   progname, cd->fg.red>>8, cd->fg.green>>8, cd->fg.blue>>8);
          exit (1);
        }
      if (!gdk_colormap_alloc_color (cmap, &cd->bg, FALSE, TRUE))
        {
          fprintf (stderr, "%s: unable to allocate #%02x%02x%02x\n",
                   progname, cd->bg.red>>8, cd->bg.green>>8, cd->bg.blue>>8);
          exit (1);
        }
      changed_p = TRUE;
    }

  if (cd->config.cycle_p)
    {
      float scale = 4.7;
      int tick;
      if (! init_p)
        {
          gdk_colormap_free_colors (cmap, &cd->fg, 1);
          gdk_colormap_free_colors (cmap, &cd->bg, 1);
        }

      if (cd->hue_tick >= 360 * scale)
        cd->hue_tick = 0;
      tick = cd->hue_tick / scale;

      hsv_to_rgb (tick,
                  1.0, 1.0,
                  &cd->fg.red, &cd->fg.green, &cd->fg.blue);
      hsv_to_rgb ((tick + 180) % 360,
                  1.0, 1.0,
                  &cd->bg.red, &cd->bg.green, &cd->bg.blue);

      gdk_colormap_alloc_color (cmap, &cd->fg, FALSE, TRUE);
      gdk_colormap_alloc_color (cmap, &cd->bg, FALSE, TRUE);
      changed_p = TRUE;
    }

  if (changed_p && !cd->config.transparent_p)
    {
      GtkWidget *widget = cd->draw;
      gtk_widget_modify_bg (cd->toplevel, GTK_STATE_NORMAL, &cd->bg);
      gtk_widget_modify_bg (widget, GTK_STATE_NORMAL, &cd->bg);
    }
}


static void
draw_image (clock_data *cd, gboolean shape_p)
{
  GdkPixmap *bm;
  GtkWidget *widget = cd->draw;
  int x, y, win_w, win_h;

  if (!cd->config.bitmap) return;  /* clock hasn't started yet */
  if (!widget->window) return;     /* not realized yet */

  win_w = widget->allocation.width;
  win_h = widget->allocation.height;
  x = (win_w - (int) cd->config.width - cd->config.left_offset) / 2;
  y = (win_h - (int) cd->config.height) / 2;

  if (x < 0) x = win_w - cd->config.width;   /* align to right */
  if (y < 0) y = win_h - cd->config.height;  /* align to bottom */

  if (shape_p && cd->config.transparent_p)
    {
      bm = gdk_bitmap_create_from_data (GDK_DRAWABLE (cd->toplevel->window),
                                        (const gchar *) cd->config.bitmap,
                                        (gint) cd->config.width2,
                                        (gint) cd->config.height2);
      gtk_widget_shape_combine_mask (cd->toplevel, bm, x, y);
      gdk_bitmap_unref (bm);
    }

#if 1   /*  7% CPU  */
  bm = gdk_pixmap_create_from_data (GDK_DRAWABLE (widget->window),
                                    (const gchar *) cd->config.bitmap,
                                    (gint) cd->config.width2,
                                    (gint) cd->config.height2,
                                    cd->window_depth,
                                    &cd->fg, &cd->bg);
  gdk_draw_pixmap (GDK_DRAWABLE (widget->window), cd->gc, bm,
                   0, 0, x, y, cd->config.width2, cd->config.height2);
  gdk_bitmap_unref (bm);

#else   /*  12% CPU  */
  {
    int xx, yy, i;
    i = 0;
    for (yy = 0; yy < cd->config.height; yy++)
      {
        const unsigned char *scanline = (cd->config.bitmap +
                                         (yy * (cd->config.width2 >> 3)));
        for (xx = 0; xx < cd->config.width2; xx++)
          {
            GdkColor *c = ((scanline[xx>>3] & (1 << (xx & 7)))
                           ? &cd->fg : &cd->bg);
            cd->rgb_buf[i++] = c->red   >> 8;
            cd->rgb_buf[i++] = c->green >> 8;
            cd->rgb_buf[i++] = c->blue  >> 8;
          }
      }

    gdk_draw_rgb_image (widget->window, cd->gc,
                        x, y,
                        (gint) cd->config.width,
                        (gint) cd->config.height,
                        GDK_RGB_DITHER_NORMAL,
                        cd->rgb_buf, cd->config.width * 3);
  }
#endif

  /* try gdk_draw_segments next...
     http://developer.gnome.org/doc/API/2.0/gdk/gdk-Drawing-Primitives.html
   */

  /* Alternately, a better approach might be to have the GTK window
     just embed an OpenGL view and render the clock as a texture.  On
     most modern machines, that may be the most efficient route to the
     frame buffer.  That's how the OSX and iPhone versions work.
   */
}


gboolean
on_drawingarea_expose_event (GtkWidget *widget, GdkEventExpose *event,
                             gpointer user_data)
{
  GtkWidget *top = GTK_WIDGET (lookup_widget (widget, "daliclock"));
  clock_data *cd = (clock_data *)
    g_object_get_data (G_OBJECT (top), "clock_data");
  if (!cd) abort();

  draw_image (cd, FALSE);
  return FALSE;
}


static int
display_timer (gpointer data)
{
  static int last_w = 0;
  static int last_h = 0;
  clock_data *cd = (clock_data *) data;
  GtkWidget *widget = cd->draw;

  if (!cd->update_allowed_p)
    {
      /* We're not ready to run.  Sleep for a while, else we'll just
         be called again right away, and madly chew CPU. */
      struct timeval tv;
      tv.tv_sec  = 0L;
      tv.tv_usec = 1000000L / cd->config.max_fps;
      select (0, 0, 0, 0, &tv);

      goto END;
    }

  cd->update_allowed_p = FALSE;
  cd->hue_tick++;
  set_colors (cd, FALSE);

  if (widget->allocation.width != last_w ||
      widget->allocation.height != last_h)
    {
      last_w = widget->allocation.width;
      last_h = widget->allocation.height;
      render_reset (cd);
    }

  render_once (&cd->config);
  draw_image (cd, TRUE);

 END:
  return TRUE;  /* re-execute timer */
}


static int
display_tick_timer (gpointer data)
{
  clock_data *cd = (clock_data *) data;
  cd->update_allowed_p = TRUE;
  return TRUE;  /* re-execute timer */
}


static void
popup_config_dialog (GtkWidget *widget)
{
  GtkWidget *top = GTK_WIDGET (lookup_widget (widget, "daliclock"));
  GtkWidget *config;
  clock_data *cd = (clock_data *)
    g_object_get_data (G_OBJECT (top), "clock_data");
  if (!cd) abort();

  cd->edit = cd->config;

  config = create_dali_config();
  g_object_set_data (G_OBJECT (config), "clock_data", (gpointer *) cd);

  gtk_option_menu_set_history (GTK_OPTION_MENU
                               (lookup_widget (config, "hour_optionmenu")),
                               cd->edit.twelve_hour_p + 1);
  gtk_option_menu_set_history (GTK_OPTION_MENU
                               (lookup_widget (config, "seconds_optionmenu")),
                               ((int) cd->edit.time_mode));
  gtk_option_menu_set_history (GTK_OPTION_MENU
                               (lookup_widget (config, "date_optionmenu")),
                               ((int) cd->edit.date_mode));

  gtk_toggle_button_set_active
    (GTK_TOGGLE_BUTTON (lookup_widget (config, "cycle_checkbutton")),
     cd->edit.cycle_p);
  gtk_toggle_button_set_active
    (GTK_TOGGLE_BUTTON (lookup_widget (config, "transparent_checkbutton")),
     cd->edit.transparent_p);

  gtk_widget_show (config);
}

static int
date_off_timer (gpointer data)
{
  clock_data *cd = (clock_data *) data;
  cd->config.display_date_p = 0;
  return FALSE;  /* do not re-execute timer */
}



gboolean
on_drawingarea_button_press_event (GtkWidget *widget,
                                   GdkEventButton *event,
                                   gpointer user_data)
{
  GtkWidget *top = GTK_WIDGET (lookup_widget (widget, "daliclock"));
  clock_data *cd = (clock_data *)
    g_object_get_data (G_OBJECT (top), "clock_data");
  if (!cd) abort();

  switch (event->button)
    {
      case 1:
        cd->config.display_date_p = 1;
        break;
    }
  return FALSE;
}

gboolean
on_drawingarea_button_release_event (GtkWidget *widget,
                                     GdkEventButton *event,
                                     gpointer user_data)
{
  GtkWidget *top = GTK_WIDGET (lookup_widget (widget, "daliclock"));
  clock_data *cd = (clock_data *)
    g_object_get_data (G_OBJECT (top), "clock_data");
  if (!cd) abort();

  switch (event->button)
    {
    case 1:
      /* Switch from date back to time 2 secs after mouse released */
      gtk_timeout_add (2 * 1000, date_off_timer, (gpointer) cd);
      break;
    case 3:
      popup_config_dialog (widget);
      break;
    }
  return FALSE;
}

gboolean
on_drawingarea_key_press_event (GtkWidget *widget,
                                GdkEventKey *event,
                                gpointer user_data)
{
  GtkWidget *top = GTK_WIDGET (lookup_widget (widget, "daliclock"));
  clock_data *cd = (clock_data *)
    g_object_get_data (G_OBJECT (top), "clock_data");
  if (!cd) abort();

  switch (event->string ? *event->string : 0)
    {
    case 'q': case 'Q': case 003: case 004:
      gtk_main_quit ();
      break;
    case ' ':
      cd->config.twelve_hour_p = !cd->config.twelve_hour_p;
      break;
    case '0': case '1': case '2': case '3': case '4': case '5':
    case '6': case '7': case '8': case '9': case '-':
      cd->config.test_hack = event->keyval;
      break;
    case 0:
      break;
    default:
      gdk_beep ();
      break;
    }
  return FALSE;
}


void
on_12_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  GtkWidget *top = GTK_WIDGET (lookup_widget (GTK_WIDGET (menuitem),
                                              "dali_config"));
  clock_data *cd = (clock_data *)
    g_object_get_data (G_OBJECT (top), "clock_data");
  if (!cd) abort();

  cd->edit.twelve_hour_p = 1;
}


void
on_24_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  GtkWidget *top = GTK_WIDGET (lookup_widget (GTK_WIDGET (menuitem),
                                              "dali_config"));
  clock_data *cd = (clock_data *)
    g_object_get_data (G_OBJECT (top), "clock_data");
  if (!cd) abort();

  cd->edit.twelve_hour_p = 0;
}


void
on_hm_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  GtkWidget *top = GTK_WIDGET (lookup_widget (GTK_WIDGET (menuitem),
                                              "dali_config"));
  clock_data *cd = (clock_data *)
    g_object_get_data (G_OBJECT (top), "clock_data");
  if (!cd) abort();

  cd->edit.time_mode = HHMM;
}


void
on_hms_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  GtkWidget *top = GTK_WIDGET (lookup_widget (GTK_WIDGET (menuitem),
                                              "dali_config"));
  clock_data *cd = (clock_data *)
    g_object_get_data (G_OBJECT (top), "clock_data");
  if (!cd) abort();

  cd->edit.time_mode = HHMMSS;
}


void
on_s_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  GtkWidget *top = GTK_WIDGET (lookup_widget (GTK_WIDGET (menuitem),
                                              "dali_config"));
  clock_data *cd = (clock_data *)
    g_object_get_data (G_OBJECT (top), "clock_data");
  if (!cd) abort();

  cd->edit.time_mode = SS;
}


void
on_mmddyy_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  GtkWidget *top = GTK_WIDGET (lookup_widget (GTK_WIDGET (menuitem),
                                              "dali_config"));
  clock_data *cd = (clock_data *)
    g_object_get_data (G_OBJECT (top), "clock_data");
  if (!cd) abort();

  cd->edit.date_mode = MMDDYY;
}


void
on_ddmmyy_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  GtkWidget *top = GTK_WIDGET (lookup_widget (GTK_WIDGET (menuitem),
                                              "dali_config"));
  clock_data *cd = (clock_data *)
    g_object_get_data (G_OBJECT (top), "clock_data");
  if (!cd) abort();

  cd->edit.date_mode = DDMMYY;
}


void
on_yymmdd_activate (GtkMenuItem *menuitem, gpointer user_data)
{
  GtkWidget *top = GTK_WIDGET (lookup_widget (GTK_WIDGET (menuitem),
                                              "dali_config"));
  clock_data *cd = (clock_data *)
    g_object_get_data (G_OBJECT (top), "clock_data");
  if (!cd) abort();

  cd->edit.date_mode = YYMMDD;
}


void
on_cycle_checkbutton_toggled (GtkToggleButton *togglebutton,
                              gpointer user_data)
{
  GtkWidget *top = GTK_WIDGET (lookup_widget (GTK_WIDGET (togglebutton),
                                              "dali_config"));
  clock_data *cd = (clock_data *)
    g_object_get_data (G_OBJECT (top), "clock_data");
  if (!cd) abort();

  cd->edit.cycle_p = gtk_toggle_button_get_active (togglebutton);
}


void
on_transparent_checkbutton_toggled (GtkToggleButton *togglebutton,
                                    gpointer user_data)
{
  GtkWidget *top = GTK_WIDGET (lookup_widget (GTK_WIDGET (togglebutton),
                                              "dali_config"));
  clock_data *cd = (clock_data *)
    g_object_get_data (G_OBJECT (top), "clock_data");
  if (!cd) abort();

  cd->edit.transparent_p = gtk_toggle_button_get_active (togglebutton);
}


void
on_dali_config_response (GtkDialog *dialog, gint response_id,
                         gpointer user_data)
{
  GtkWidget *top = GTK_WIDGET (lookup_widget (GTK_WIDGET (dialog),
                                              "dali_config"));
  clock_data *cd = (clock_data *)
    g_object_get_data (G_OBJECT (top), "clock_data");
  gboolean cycle_changed_p = FALSE;
  gboolean trans_changed_p = FALSE;
  if (!cd) abort();

  switch (response_id)
    {
    case GTK_RESPONSE_OK:
      cycle_changed_p = (cd->config.cycle_p != cd->edit.cycle_p);
      trans_changed_p = (cd->config.transparent_p != cd->edit.transparent_p);
      cd->config = cd->edit;

      /* #### save prefs somewhere */

      fprintf(stderr, "tm:   %d\n", cd->config.time_mode);
      fprintf(stderr, "dm:   %d\n", cd->config.date_mode);
      fprintf(stderr, "date: %d\n", cd->config.display_date_p);
      fprintf(stderr, "12p:  %d\n", cd->config.twelve_hour_p);
      fprintf(stderr, "cyc:  %d\n", cd->config.cycle_p);
      fprintf(stderr, "tran: %d\n", cd->config.transparent_p);
      fprintf(stderr, "\n");

      if (cycle_changed_p)
        {
          GdkWindow *win = cd->toplevel->window;
          GdkColormap *cmap = gdk_window_get_colormap (win);
          gdk_colormap_free_colors (cmap, &cd->fg, 1);
          gdk_colormap_free_colors (cmap, &cd->bg, 1);
          set_colors (cd, TRUE);
        }

      if (trans_changed_p && !cd->config.transparent_p)
        /* turn off the existing shape mask */
        gtk_widget_shape_combine_mask (cd->toplevel, NULL, 0, 0);

      render_reset (cd);
      break;
    case GTK_RESPONSE_CANCEL:
    case GTK_RESPONSE_DELETE_EVENT:
      break;
    default:
      abort();
      break;
    }

  gtk_widget_destroy (GTK_WIDGET (dialog));
}


static void
set_defaults (clock_data *cd)
{
  /* #### save prefs somewhere */

  cd->config.time_mode      = HHMMSS;
  cd->config.date_mode      = MMDDYY;
  cd->config.display_date_p = 0;
  cd->config.twelve_hour_p  = 1;
  cd->config.cycle_p        = 1;
  cd->config.transparent_p  = 0;
  cd->config.max_fps        = 15;
  cd->config.max_fps        = 15;
}

static void
parse_countdown (clock_data *cd, const char *s)
{
  time_t now = time((time_t *) 0);
  int h100 = (99*60*60) + (59*60) + 59;
  unsigned long l = 0;
  char c = 0;
  char mon[255];
  int day = 0;
  int hour = 0;
  int min = 0;
  int sec = 0;
  int year = 0;
  *mon = 0;
  if (1 == sscanf (s, " %lu %c", &l, &c))
    cd->config.countdown = l;
  else if (6 == sscanf (s, " %s %d %d:%d:%d %d %c",
                        mon, &day, &hour, &min, &sec, &year, &c))
    {
      struct tm tm;
      int imonth = -1;
      const char *months[] = {"jan", "feb", "mar", "apr", "may", "jun",
                              "jul", "aug", "sep", "oct", "nov", "dec" };

      for (l = 0; mon[l]; l++)
        if (mon[l] >= 'A' && mon[l] <= 'Z')
          mon[l] = mon[l] + ('a'-'A');

      for (l = 0; l < 12; l++)
        if (!strcmp(months[l], mon))
          {
            imonth = l;
            break;
          }
      if (imonth < 0)
        {
          fprintf (stderr, "%s: unparsable month: \"%s\"\n",
                   progname, mon);
          goto LOSE2;
        }
      if (year > 1900) year -= 1900;
      tm.tm_sec = sec;
      tm.tm_min = min;
      tm.tm_hour = hour;
      tm.tm_mday = day;
      tm.tm_mon = imonth;
      tm.tm_year = year;
      tm.tm_wday = 0;     /* ignored */
      tm.tm_yday = 0;     /* ignored */
      tm.tm_isdst = -1;
      cd->config.countdown = mktime (&tm);
      if ((long) cd->config.countdown < 0L)
        goto LOSE1;
    }
  else
    {
    LOSE1:
      fprintf (stderr, "%s: unparsable date: \"%s\"\n", progname, s);
    LOSE2:
      fprintf (stderr,
               "%s: argument to -countdown should be a time_t integer\n"
               "\t (number of seconds past \"Jan 1 00:00:00 GMT 1970\");\n"
               "\t Or, a string of the form \"Mmm DD HH:MM:SS YYYY\",\n"
               "\t for example, \"Jan 1 00:00:00 2000\".\n"
               "\t This string is interpreted in the local time zone.\n",
               progname);
      exit (1);
    }

  if (cd->config.countdown > now + h100 ||
      cd->config.countdown < now - h100)
    {
      char *s = ctime ((time_t *) &cd->config.countdown);
      char *r = strchr (s, '\n');
      if (r) *r = 0;
      fprintf (stderr,
               "%s: countdown too distant: '%s' is more\n"
               "            than 100 hours away (can't "
               "display it on the clock face.)\n",
               progname, s);
      exit (1);
    }
}


static void
warning_dialog_dismiss_cb (GtkWidget *widget, gpointer user_data)
{
  GtkWidget *shell = GTK_WIDGET (user_data);
  while (shell->parent)
    shell = shell->parent;
  gtk_widget_destroy (GTK_WIDGET (shell));
}


static void
warning_dialog (GtkWidget *parent, const char *message)
{
  char *msg = strdup (message);
  char *head;

  GtkWidget *dialog = gtk_dialog_new ();
  GtkWidget *label = 0;
  GtkWidget *ok = 0;
  int i = 0;

  while (parent && !parent->window)
    parent = parent->parent;

  if (!parent ||
      !GTK_WIDGET (parent)->window) /* too early to pop up transient dialogs */
    {
      fprintf (stderr, "%s: too early for dialog?\n", progname);
      return;
    }

  head = msg;
  while (head)
    {
      char name[20];
      char *s = strchr (head, '\n');
      if (s) *s = 0;

      sprintf (name, "label%d", i++);

      {
        label = gtk_label_new (head);
#ifdef HAVE_GTK2
	gtk_label_set_selectable (GTK_LABEL (label), TRUE);
#endif /* HAVE_GTK2 */

        gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5);
        gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
                            label, TRUE, TRUE, 0);
        gtk_widget_show (label);
      }

      if (s)
	head = s+1;
      else
	head = 0;
    }

  label = gtk_label_new ("");
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
                      label, TRUE, TRUE, 0);
  gtk_widget_show (label);

  label = gtk_hbutton_box_new ();
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
                      label, TRUE, TRUE, 0);

#ifdef HAVE_GTK2
  ok = gtk_button_new_from_stock (GTK_STOCK_OK);
  gtk_container_add (GTK_CONTAINER (label), ok);
#else /* !HAVE_GTK2 */
  ok = gtk_button_new_with_label ("OK");
  gtk_container_add (GTK_CONTAINER (label), ok);
#endif /* !HAVE_GTK2 */

  gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
  gtk_container_set_border_width (GTK_CONTAINER (dialog), 10);
  gtk_window_set_title (GTK_WINDOW (dialog), "Dali Clock");
  GTK_WIDGET_SET_FLAGS (ok, GTK_CAN_DEFAULT);
  gtk_widget_show (ok);
  gtk_widget_show (label);
  gtk_widget_show (dialog);

  gtk_signal_connect_object (GTK_OBJECT (ok), "clicked",
                             GTK_SIGNAL_FUNC (warning_dialog_dismiss_cb),
                             (gpointer) dialog);

  gdk_window_set_transient_for (GTK_WIDGET (dialog)->window,
                                GTK_WIDGET (parent)->window);

#ifdef HAVE_GTK2
  gtk_window_present (GTK_WINDOW (dialog));
#else  /* !HAVE_GTK2 */
  gdk_window_show (GTK_WIDGET (dialog)->window);
  gdk_window_raise (GTK_WIDGET (dialog)->window);
#endif /* !HAVE_GTK2 */

  free (msg);
}




int
main (int argc, char **argv)
{
  clock_data CD;
  clock_data *cd = &CD;
  char *s;
  char *geom = 0;
  char *countdown = 0;
  int i;
  progname = argv[0];
  s = strrchr (progname, '/');
  if (s) progname = s+1;

  gtk_set_locale ();
  gtk_init (&argc, &argv);

  for (i = 1; i < argc; i++)
    {
      if (argv[i][0] == '-' && argv[i][1] == '-')
        argv[i]++;
      if (i < argc-1 &&
          (!strcmp (argv[i], "-geometry") ||
           !strcmp (argv[i], "-geom") ||
           !strcmp (argv[i], "-geo") ||
           !strcmp (argv[i], "-g")))
        geom = argv[++i];
      else if (i < argc-1 &&
               (!strcmp (argv[i], "-countdown") ||
                !strcmp (argv[i], "-c")))
        countdown = argv[++i];
      else
        {
          fprintf (stderr, "%s: unrecognised option \"%s\"\n",
                   progname, argv[i]);
          usage ();
          exit (1);
        }
    }

  memset (cd, 0, sizeof(*cd));
  set_defaults (cd);
  cd->toplevel = create_daliclock();
  cd->draw = GTK_WIDGET (lookup_widget (cd->toplevel, "drawingarea"));

  /* Pick a reasonable default size for the window */
  {
    unsigned int w = 500;
    unsigned int h = w / 4;
    render_bitmap_size (&cd->config, &w, &h, NULL, NULL);
    w *= 1.2;
    h *= 1.6;
    gtk_window_set_default_size (GTK_WINDOW (cd->toplevel), w, h);
  }

  /* After picking the default size, allow --geometry to override it.
   */
  if (geom)
    gtk_window_parse_geometry (GTK_WINDOW (cd->toplevel), geom);

  if (countdown)
    parse_countdown (cd, countdown);

  g_object_set_data (G_OBJECT (cd->toplevel), "clock_data", (gpointer *) cd);

  gtk_signal_connect (GTK_OBJECT (cd->toplevel), "delete_event",
                      GTK_SIGNAL_FUNC (wm_toplevel_close_cb),
                      0);
  gtk_widget_show (cd->toplevel);

  gtk_idle_add (display_timer, (gpointer) cd);
  gtk_timeout_add (1000 / cd->config.max_fps,
                   display_tick_timer, (gpointer) cd);

  {
    gint wx, wy, ww, wh;
    GdkWindow *win = cd->toplevel->window;
    cd->gc = gdk_gc_new (win);
    set_colors (cd, TRUE);
    gdk_window_get_geometry (win, &wx, &wy, &ww, &wh, &cd->window_depth);

    warning_dialog (cd->toplevel,
                    ("WARNING!\n"
                     "\n"
                     "This is the half-finished GTK version\n"
                     "of Dali Clock.\n"
                     "\n"
                     "This version is very incomplete:\n"
                     "Many of the preferences are not hooked\n"
                     "up, and it is very slow.\n"
                     "\n"
                     "Do not ship this.  In fact, do not run it\n"
                     "unless you plan on hacking on it.\n"
                     "\n"
                     "Use the raw Xlib version of Dali Clock\n"
                     "instead: https://www.jwz.org/xdaliclock/\n"
                     "\n"));
  }

  gtk_main ();
  return 0;
}


static void
usage (void)
{
  fprintf (stderr, "\n%s\n\
                  https://www.jwz.org/xdaliclock/\n\n\
usage: %s [ options ]\n\
where options include\n\
\n\
  --display <host:dpy>	  The display to run on.\n\
  --geometry <geometry>   Size and position of window.\n\
  --countdown <date>      Display a countdown instead of a clock.\n\
                          Run `-countdown foo' to see date syntax.\n\
  --root                  Draw on the root window instead.\n",
           version+4, progname);
}
