//
// "$Id: fluid.cxx 6783 2009-04-27 15:50:29Z AlbrechtS $"
//
// FLUID main entry for the Fast Light Tool Kit (FLTK).
//
// Copyright 1998-2007 by Bill Spitzak and others.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library 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
// Library General Public License for more details.
//
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA.
//
// Please report all bugs and problems on the following page:
//
//     http://www.fltk.org/str.php
//

#include <FL/Fl.H>
#include <FL/Fl_Double_Window.H>
#include <FL/Fl_Box.H>
#include <FL/Fl_Button.H>
#include <FL/Fl_File_Icon.H>
#include <FL/Fl_Help_Dialog.H>
#include <FL/Fl_Hold_Browser.H>
#include <FL/Fl_Menu_Bar.H>
#include <FL/Fl_Input.H>
#include <FL/fl_ask.H>
#include <FL/fl_draw.H>
#include <FL/Fl_File_Chooser.H>
#include <FL/fl_message.H>
#include <FL/filename.H>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/stat.h>
#include <time.h> // time(), localtime(), etc.

#include "../src/flstring.h"
#include "alignment_panel.h"
#include "function_panel.h"
#include "template_panel.h"
#if !defined(WIN32) || defined(__CYGWIN__)
#  include "print_panel.cxx"
#endif // !WIN32 || __CYGWIN__

#if defined(WIN32) && !defined(__CYGWIN__)
#  include <direct.h>
#  include <windows.h>
#  include <io.h>
#  include <commdlg.h>
#  include <FL/x.H>
#  ifndef __WATCOMC__
// Visual C++ 2005 incorrectly displays a warning about the use of POSIX APIs
// on Windows, which is supposed to be POSIX compliant...
#    define access _access
#    define chdir _chdir
#    define getcwd _getcwd
#  endif // !__WATCOMC__
#else
#  include <unistd.h>
#endif
#ifdef __EMX__
#  include <X11/Xlibint.h>
#endif

#include "about_panel.h"
#include "undo.h"

#include "Fl_Type.h"

extern "C"
{
#if defined(HAVE_LIBPNG) && defined(HAVE_LIBZ)
#  include <zlib.h>
#  ifdef HAVE_PNG_H
#    include <png.h>
#  else
#    include <libpng/png.h>
#  endif // HAVE_PNG_H
#endif // HAVE_LIBPNG && HAVE_LIBZ
}

static Fl_Help_Dialog *help_dialog = 0;

Fl_Preferences	fluid_prefs(Fl_Preferences::USER, "fltk.org", "fluid");
int gridx = 5;
int gridy = 5;
int snap = 1;
int show_guides = 1;

// File history info...
char	absolute_history[10][1024];
char	relative_history[10][1024];

void	load_history();
void	update_history(const char *);

// Shell command support...
void	show_shell_window();

////////////////////////////////////////////////////////////////

static const char *filename;
void set_filename(const char *c);
void set_modflag(int mf);
int modflag;

static char* pwd;
static char in_source_dir;
void goto_source_dir() {
  if (in_source_dir) return;
  if (!filename || !*filename) return;
  const char *p = fl_filename_name(filename);
  if (p <= filename) return; // it is in the current directory
  char buffer[1024];
  strlcpy(buffer, filename, sizeof(buffer));
  int n = p-filename; if (n>1) n--; buffer[n] = 0;
  if (!pwd) {
    pwd = getcwd(0,1024);
    if (!pwd) {fprintf(stderr,"getwd : %s\n",strerror(errno)); return;}
  }
  if (chdir(buffer)<0) {fprintf(stderr, "Can't chdir to %s : %s\n",
				buffer, strerror(errno)); return;}
  in_source_dir = 1;
}

void leave_source_dir() {
  if (!in_source_dir) return;
  if (chdir(pwd)<0) {fprintf(stderr, "Can't chdir to %s : %s\n",
			     pwd, strerror(errno));}
  in_source_dir = 0;
}
  
char position_window(Fl_Window *w, const char *prefsName, int Visible, int X, int Y, int W=0, int H=0 ) {
  Fl_Preferences pos(fluid_prefs, prefsName);
  if (prevpos_button->value()) {
    pos.get("x", X, X);
    pos.get("y", Y, Y);
    if ( W!=0 ) {
      pos.get("w", W, W);
      pos.get("h", H, H);
      w->resize( X, Y, W, H );
    }
    else
      w->position( X, Y );
  }
  pos.get("visible", Visible, Visible);
  return Visible;
}

void save_position(Fl_Window *w, const char *prefsName) {
  Fl_Preferences pos(fluid_prefs, prefsName);
  pos.set("x", w->x());
  pos.set("y", w->y());
  pos.set("w", w->w());
  pos.set("h", w->h());
  pos.set("visible", (int)(w->shown() && w->visible()));
}

Fl_Window *main_window;
Fl_Menu_Bar *main_menubar;

static char* cutfname(int which = 0) {
  static char name[2][1024];
  static char beenhere = 0;

  if (!beenhere) {
    beenhere = 1;
    fluid_prefs.getUserdataPath(name[0], sizeof(name[0]));
    strlcat(name[0], "cut_buffer", sizeof(name[0]));
    fluid_prefs.getUserdataPath(name[1], sizeof(name[1]));
    strlcat(name[1], "dup_buffer", sizeof(name[1]));
  }

  return name[which];
}

void save_cb(Fl_Widget *, void *v) {
  const char *c = filename;
  if (v || !c || !*c) {
    fl_file_chooser_ok_label("Save");
    c=fl_file_chooser("Save To:", "FLUID Files (*.f[ld])", c);
    fl_file_chooser_ok_label(NULL);
    if (!c) return;

    if (!access(c, 0)) {
      const char *basename;
      if ((basename = strrchr(c, '/')) != NULL)
        basename ++;
#if defined(WIN32) || defined(__EMX__)
      if ((basename = strrchr(c, '\\')) != NULL)
        basename ++;
#endif // WIN32 || __EMX__
      else
        basename = c;

      if (fl_choice("The file \"%s\" already exists.\n"
                    "Do you want to replace it?", "Cancel",
		    "Replace", NULL, basename) == 0) return;
    }

    if (v != (void *)2) set_filename(c);
  }
  if (!write_file(c)) {
    fl_alert("Error writing %s: %s", c, strerror(errno));
    return;
  }

  if (v != (void *)2) {
    set_modflag(0);
    undo_save = undo_current;
  }
}

void save_template_cb(Fl_Widget *, void *) {
  // Setup the template panel...
  if (!template_panel) make_template_panel();

  template_clear();
  template_browser->add("New Template");
  template_load();

  template_name->show();
  template_name->value("");

  template_instance->hide();

  template_delete->show();
  template_delete->deactivate();

  template_submit->label("Save");
  template_submit->deactivate();

  template_panel->label("Save Template");

  // Show the panel and wait for the user to do something...
  template_panel->show();
  while (template_panel->shown()) Fl::wait();

  // Get the template name, return if it is empty...
  const char *c = template_name->value();
  if (!c || !*c) return;

  // Convert template name to filename_with_underscores
  char safename[1024], *safeptr;
  strlcpy(safename, c, sizeof(safename));
  for (safeptr = safename; *safeptr; safeptr ++) {
    if (isspace(*safeptr)) *safeptr = '_';
  }

  // Find the templates directory...
  char filename[1024];
  fluid_prefs.getUserdataPath(filename, sizeof(filename));

  strlcat(filename, "templates", sizeof(filename));
#if defined(WIN32) && !defined(__CYGWIN__)
  if (access(filename, 0)) mkdir(filename);
#else
  if (access(filename, 0)) mkdir(filename, 0777);
#endif // WIN32 && !__CYGWIN__

  strlcat(filename, "/", sizeof(filename));
  strlcat(filename, safename, sizeof(filename));

  char *ext = filename + strlen(filename);
  if (ext >= (filename + sizeof(filename) - 5)) {
    fl_alert("The template name \"%s\" is too long!", c);
    return;
  }

  // Save the .fl file...
  strcpy(ext, ".fl");

  if (!access(filename, 0)) {
    if (fl_choice("The template \"%s\" already exists.\n"
                  "Do you want to replace it?", "Cancel",
		  "Replace", NULL, c) == 0) return;
  }

  if (!write_file(filename)) {
    fl_alert("Error writing %s: %s", filename, strerror(errno));
    return;
  }

#if defined(HAVE_LIBPNG) && defined(HAVE_LIBZ)
  // Get the screenshot, if any...
  Fl_Type *t;

  for (t = Fl_Type::first; t; t = t->next) {
    // Find the first window...
    if (t->is_window()) break;
  }

  if (!t) return;

  // Grab a screenshot...
  Fl_Window_Type *wt = (Fl_Window_Type *)t;
  uchar *pixels;
  int w, h;

  if ((pixels = wt->read_image(w, h)) == NULL) return;

  // Save to a PNG file...
  strcpy(ext, ".png");

  FILE *fp;

  if ((fp = fopen(filename, "wb")) == NULL) {
    delete[] pixels;
    fl_alert("Error writing %s: %s", filename, strerror(errno));
    return;
  }

  png_structp pptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
  png_infop iptr = png_create_info_struct(pptr);
  png_bytep ptr = (png_bytep)pixels;

  png_init_io(pptr, fp);
  png_set_IHDR(pptr, iptr, w, h, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
               PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
  png_set_sRGB(pptr, iptr, PNG_sRGB_INTENT_PERCEPTUAL);

  png_write_info(pptr, iptr);

  for (int i = h; i > 0; i --, ptr += w * 3) {
    png_write_row(pptr, ptr);
  }

  png_write_end(pptr, iptr);
  png_destroy_write_struct(&pptr, &iptr);

  fclose(fp);

#  if 0 // The original PPM output code...
  strcpy(ext, ".ppm");
  fp = fopen(filename, "wb");
  fprintf(fp, "P6\n%d %d 255\n", w, h);
  fwrite(pixels, w * h, 3, fp);
  fclose(fp);
#  endif // 0

  delete[] pixels;
#endif // HAVE_LIBPNG && HAVE_LIBZ
}

void revert_cb(Fl_Widget *,void *) {
  if (modflag) {
    if (!fl_choice("This user interface has been changed. Really revert?",
                   "Cancel", "Revert", NULL)) return;
  }
  undo_suspend();
  if (!read_file(filename, 0)) {
    undo_resume();
    fl_message("Can't read %s: %s", filename, strerror(errno));
    return;
  }
  undo_resume();
  set_modflag(0);
  undo_clear();
}

void exit_cb(Fl_Widget *,void *) {
  if (modflag)
    switch (fl_choice("Do you want to save changes to this user\n"
                      "interface before exiting?", "Cancel",
                      "Save", "Don't Save"))
    {
      case 0 : /* Cancel */
          return;
      case 1 : /* Save */
          save_cb(NULL, NULL);
	  if (modflag) return;	// Didn't save!
    }

  save_position(main_window,"main_window_pos");

  if (widgetbin_panel) {
    save_position(widgetbin_panel,"widgetbin_pos");
    delete widgetbin_panel;
  }
  if (sourceview_panel) {
    Fl_Preferences svp(fluid_prefs, "sourceview");
    svp.set("autorefresh", sv_autorefresh->value());
    svp.set("autoposition", sv_autoposition->value());
    svp.set("tab", sv_tab->find(sv_tab->value()));
    save_position(sourceview_panel,"sourceview_pos");
    delete sourceview_panel;
  }
  if (about_panel)
    delete about_panel;
  if (help_dialog)
    delete help_dialog;

  undo_clear();

  exit(0);
}

#ifdef __APPLE__
#  include <FL/x.H>

void
apple_open_cb(const char *c) {
  if (modflag) {
    switch (fl_choice("Do you want to save changes to this user\n"
                      "interface before opening another one?", "Don't Save",
                      "Save", "Cancel"))
    {
      case 0 : /* Cancel */
          return;
      case 1 : /* Save */
          save_cb(NULL, NULL);
	  if (modflag) return;	// Didn't save!
    }
  }
  const char *oldfilename;
  oldfilename = filename;
  filename    = NULL;
  set_filename(c);
  undo_suspend();
  if (!read_file(c, 0)) {
    undo_resume();
    fl_message("Can't read %s: %s", c, strerror(errno));
    free((void *)filename);
    filename = oldfilename;
    if (main_window) main_window->label(filename);
    return;
  }

  // Loaded a file; free the old filename...
  set_modflag(0);
  undo_resume();
  undo_clear();
  if (oldfilename) free((void *)oldfilename);
}
#endif // __APPLE__

void open_cb(Fl_Widget *, void *v) {
  if (!v && modflag) {
    switch (fl_choice("Do you want to save changes to this user\n"
                      "interface before opening another one?", "Cancel",
                      "Save", "Don't Save"))
    {
      case 0 : /* Cancel */
          return;
      case 1 : /* Save */
          save_cb(NULL, NULL);
	  if (modflag) return;	// Didn't save!
    }
  }
  const char *c;
  const char *oldfilename;
  fl_file_chooser_ok_label("Open");
  c = fl_file_chooser("Open:", "FLUID Files (*.f[ld])", filename);
  fl_file_chooser_ok_label(NULL);
  if (!c) return;
  oldfilename = filename;
  filename    = NULL;
  set_filename(c);
  if (v != 0) undo_checkpoint();
  undo_suspend();
  if (!read_file(c, v!=0)) {
    undo_resume();
    fl_message("Can't read %s: %s", c, strerror(errno));
    free((void *)filename);
    filename = oldfilename;
    if (main_window) set_modflag(modflag);
    return;
  }
  undo_resume();
  if (v) {
    // Inserting a file; restore the original filename...
    free((void *)filename);
    filename = oldfilename;
    set_modflag(1);
  } else {
    // Loaded a file; free the old filename...
    set_modflag(0);
    undo_clear();
    if (oldfilename) free((void *)oldfilename);
  }
}

void open_history_cb(Fl_Widget *, void *v) {
  if (modflag) {
    switch (fl_choice("Do you want to save changes to this user\n"
                      "interface before opening another one?", "Cancel",
                      "Save", "Don't Save"))
    {
      case 0 : /* Cancel */
          return;
      case 1 : /* Save */
          save_cb(NULL, NULL);
	  if (modflag) return;	// Didn't save!
    }
  }
  const char *oldfilename = filename;
  filename = NULL;
  set_filename((char *)v);
  undo_suspend();
  if (!read_file(filename, 0)) {
    undo_resume();
    undo_clear();
    fl_message("Can't read %s: %s", filename, strerror(errno));
    free((void *)filename);
    filename = oldfilename;
    if (main_window) main_window->label(filename);
    return;
  }
  set_modflag(0);
  undo_resume();
  undo_clear();
  if (oldfilename) free((void *)oldfilename);
}

void new_cb(Fl_Widget *, void *v) {
  // Check if the current file has been modified...
  if (!v && modflag) {
    // Yes, ask the user what to do...
    switch (fl_choice("Do you want to save changes to this user\n"
                      "interface before creating a new one?", "Cancel",
                      "Save", "Don't Save"))
    {
      case 0 : /* Cancel */
          return;
      case 1 : /* Save */
          save_cb(NULL, NULL);
	  if (modflag) return;	// Didn't save!
    }
  }

  // Setup the template panel...
  if (!template_panel) make_template_panel();

  template_clear();
  template_browser->add("Blank");
  template_load();

  template_name->hide();
  template_name->value("");

  template_instance->show();
  template_instance->deactivate();
  template_instance->value("");

  template_delete->hide();

  template_submit->label("New");
  template_submit->deactivate();

  template_panel->label("New");

  // Show the panel and wait for the user to do something...
  template_panel->show();
  while (template_panel->shown()) Fl::wait();

  // See if the user chose anything...
  int item = template_browser->value();
  if (item < 1) return;

  // Clear the current data...
  delete_all();
  set_filename(NULL);

  // Load the template, if any...
  const char *tname = (const char *)template_browser->data(item);

  if (tname) {
    // Grab the instance name...
    const char *iname = template_instance->value();

    if (iname && *iname) {
      // Copy the template to a temp file, then read it in...
      char line[1024], *ptr, *next;
      FILE *infile, *outfile;

      if ((infile = fopen(tname, "r")) == NULL) {
	fl_alert("Error reading template file \"%s\":\n%s", tname,
        	 strerror(errno));
	set_modflag(0);
	undo_clear();
	return;
      }

      if ((outfile = fopen(cutfname(1), "w")) == NULL) {
	fl_alert("Error writing buffer file \"%s\":\n%s", cutfname(1),
        	 strerror(errno));
	fclose(infile);
	set_modflag(0);
	undo_clear();
	return;
      }

      while (fgets(line, sizeof(line), infile)) {
	// Replace @INSTANCE@ with the instance name...
	for (ptr = line; (next = strstr(ptr, "@INSTANCE@")) != NULL; ptr = next + 10) {
	  fwrite(ptr, next - ptr, 1, outfile);
	  fputs(iname, outfile);
	}

	fputs(ptr, outfile);
      }

      fclose(infile);
      fclose(outfile);

      undo_suspend();
      read_file(cutfname(1), 0);
      unlink(cutfname(1));
      undo_resume();
    } else {
      // No instance name, so read the template without replacements...
      undo_suspend();
      read_file(tname, 0);
      undo_resume();
    }
  }

  set_modflag(0);
  undo_clear();
}

int compile_only = 0;
int compile_strings = 0;
int header_file_set = 0;
int code_file_set = 0;
const char* header_file_name = ".h";
const char* code_file_name = ".cxx";
int i18n_type = 0;
const char* i18n_include = "";
const char* i18n_function = "";
const char* i18n_file = "";
const char* i18n_set = "";
char i18n_program[1024] = "";

void write_cb(Fl_Widget *, void *) {
  if (!filename) {
    save_cb(0,0);
    if (!filename) return;
  }
  char cname[1024];
  char hname[1024];
  strlcpy(i18n_program, fl_filename_name(filename), sizeof(i18n_program));
  fl_filename_setext(i18n_program, sizeof(i18n_program), "");
  if (*code_file_name == '.' && strchr(code_file_name, '/') == NULL) {
    strlcpy(cname, fl_filename_name(filename), sizeof(cname));
    fl_filename_setext(cname, sizeof(cname), code_file_name);
  } else {
    strlcpy(cname, code_file_name, sizeof(hname));
  }
  if (*header_file_name == '.' && strchr(header_file_name, '/') == NULL) {
    strlcpy(hname, fl_filename_name(filename), sizeof(hname));
    fl_filename_setext(hname, sizeof(hname), header_file_name);
  } else {
    strlcpy(hname, header_file_name, sizeof(hname));
  }
  if (!compile_only) goto_source_dir();
  int x = write_code(cname,hname);
  if (!compile_only) leave_source_dir();
  strlcat(cname, " and ", sizeof(cname));
  strlcat(cname, hname, sizeof(cname));
  if (compile_only) {
    if (!x) {fprintf(stderr,"%s : %s\n",cname,strerror(errno)); exit(1);}
  } else {
    if (!x) {
      fl_message("Can't write %s: %s", cname, strerror(errno));
    } else if (completion_button->value()) {
      fl_message("Wrote %s", cname);
    }
  }
}

void write_strings_cb(Fl_Widget *, void *) {
  static const char *exts[] = { ".txt", ".po", ".msg" };
  if (!filename) {
    save_cb(0,0);
    if (!filename) return;
  }
  char sname[1024];
  strlcpy(sname, fl_filename_name(filename), sizeof(sname));
  fl_filename_setext(sname, sizeof(sname), exts[i18n_type]);
  if (!compile_only) goto_source_dir();
  int x = write_strings(sname);
  if (!compile_only) leave_source_dir();
  if (compile_only) {
    if (x) {fprintf(stderr,"%s : %s\n",sname,strerror(errno)); exit(1);}
  } else {
    if (x) {
      fl_message("Can't write %s: %s", sname, strerror(errno));
    } else if (completion_button->value()) {
      fl_message("Wrote %s", sname);
    }
  }
}

void openwidget_cb(Fl_Widget *, void *) {
  if (!Fl_Type::current) {
    fl_message("Please select a widget");
    return;
  }
  Fl_Type::current->open();
}

void toggle_overlays(Fl_Widget *,void *);

void select_all_cb(Fl_Widget *,void *);
void select_none_cb(Fl_Widget *,void *);

void group_cb(Fl_Widget *, void *);

void ungroup_cb(Fl_Widget *, void *);

extern int pasteoffset;
static int ipasteoffset;

void copy_cb(Fl_Widget*, void*) {
  if (!Fl_Type::current) {
    fl_beep();
    return;
  }
  ipasteoffset = 10;
  if (!write_file(cutfname(),1)) {
    fl_message("Can't write %s: %s", cutfname(), strerror(errno));
    return;
  }
}

extern void select_only(Fl_Type *);
void cut_cb(Fl_Widget *, void *) {
  if (!Fl_Type::current) {
    fl_beep();
    return;
  }
  if (!write_file(cutfname(),1)) {
    fl_message("Can't write %s: %s", cutfname(), strerror(errno));
    return;
  }
  undo_checkpoint();
  set_modflag(1);
  ipasteoffset = 0;
  Fl_Type *p = Fl_Type::current->parent;
  while (p && p->selected) p = p->parent;
  delete_all(1);
  if (p) select_only(p);
}

void delete_cb(Fl_Widget *, void *) {
  if (!Fl_Type::current) {
    fl_beep();
    return;
  }
  undo_checkpoint();
  set_modflag(1);
  ipasteoffset = 0;
  Fl_Type *p = Fl_Type::current->parent;
  while (p && p->selected) p = p->parent;
  delete_all(1);
  if (p) select_only(p);
}

extern int force_parent;

void paste_cb(Fl_Widget*, void*) {
  //if (ipasteoffset) force_parent = 1;
  pasteoffset = ipasteoffset;
  if (gridx>1) pasteoffset = ((pasteoffset-1)/gridx+1)*gridx;
  if (gridy>1) pasteoffset = ((pasteoffset-1)/gridy+1)*gridy;
  undo_checkpoint();
  undo_suspend();
  if (!read_file(cutfname(), 1)) {
    fl_message("Can't read %s: %s", cutfname(), strerror(errno));
  }
  undo_resume();
  pasteoffset = 0;
  ipasteoffset += 10;
  force_parent = 0;
}

// Duplicate the selected widgets...
void duplicate_cb(Fl_Widget*, void*) {
  if (!Fl_Type::current) {
    fl_beep();
    return;
  }

  if (!write_file(cutfname(1),1)) {
    fl_message("Can't write %s: %s", cutfname(1), strerror(errno));
    return;
  }

  pasteoffset  = 0;
  force_parent = 1;

  undo_checkpoint();
  undo_suspend();
  if (!read_file(cutfname(1), 1)) {
    fl_message("Can't read %s: %s", cutfname(1), strerror(errno));
  }
  unlink(cutfname(1));
  undo_resume();

  force_parent = 0;
}

void earlier_cb(Fl_Widget*,void*);

void later_cb(Fl_Widget*,void*);

Fl_Type *sort(Fl_Type *parent);

static void sort_cb(Fl_Widget *,void *) {
  sort((Fl_Type*)0);
}

void show_project_cb(Fl_Widget *, void *);
void show_grid_cb(Fl_Widget *, void *);
void show_settings_cb(Fl_Widget *, void *);

void align_widget_cb(Fl_Widget *, long);
void widget_size_cb(Fl_Widget *, long);

void about_cb(Fl_Widget *, void *) {
  if (!about_panel) make_about_panel();
  about_panel->show();
}

void show_help(const char *name) {
  const char	*docdir;
  char		helpname[1024];
  
  if (!help_dialog) help_dialog = new Fl_Help_Dialog();

  if ((docdir = getenv("FLTK_DOCDIR")) == NULL) {
#ifdef __EMX__
    // Doesn't make sense to have a hardcoded fallback
    static char fltk_docdir[1024];

    strlcpy(fltk_docdir, __XOS2RedirRoot("/XFree86/lib/X11/fltk/doc"),
            sizeof(fltk_docdir));

    docdir = fltk_docdir;
#else
    docdir = FLTK_DOCDIR;
#endif // __EMX__
  }
  snprintf(helpname, sizeof(helpname), "%s/%s", docdir, name);  

  help_dialog->load(helpname);
  help_dialog->show();
}

void help_cb(Fl_Widget *, void *) {
  show_help("fluid.html");
}

void manual_cb(Fl_Widget *, void *) {
  show_help("index.html");
}


////////////////////////////////////////////////////////////////

#if defined(WIN32) && !defined(__CYGWIN__)
// Draw a shaded box...
static void win_box(int x, int y, int w, int h) {
  fl_color(0xc0, 0xc0, 0xc0);
  fl_rectf(x, y, w, h);
  fl_color(0, 0, 0);
  fl_rect(x, y, w, h);
  fl_color(0xf0, 0xf0, 0xf0);
  fl_rectf(x + 1, y + 1, 4, h - 2);
  fl_rectf(x + 1, y + 1, w - 2, 4);
  fl_color(0x90, 0x90, 0x90);
  fl_rectf(x + w - 5, y + 1, 4, h - 2);
  fl_rectf(x + 1, y + h - 5, w - 2, 4);
}

// Load and show the print dialog...
void print_menu_cb(Fl_Widget *, void *) {
  PRINTDLG	dialog;			// Print dialog
  DOCINFO	docinfo;		// Document info
  int		first, last;		// First and last page
  int		page;			// Current page
  int		winpage;		// Current window page
  int		num_pages;		// Number of pages
  Fl_Type	*t;			// Current widget
  int		num_windows;		// Number of windows
  Fl_Window_Type *windows[1000];	// Windows to print


  // Show print dialog...
  for (t = Fl_Type::first, num_pages = 0; t; t = t->next) {
    if (t->is_window()) num_pages ++;
  }

  memset(&dialog, 0, sizeof(dialog));
  dialog.lStructSize = sizeof(dialog);
  dialog.hwndOwner   = fl_xid(main_window);
  dialog.Flags       = PD_ALLPAGES |
                       PD_RETURNDC;
  dialog.nFromPage   = 1;
  dialog.nToPage     = num_pages;
  dialog.nMinPage    = 1;
  dialog.nMaxPage    = num_pages;
  dialog.nCopies     = 1;

  if (!PrintDlg(&dialog)) return;

  // Get the base filename...
  const char *basename = strrchr(filename, '/');
  if (basename) basename ++;
  else basename = filename;

  // Do the print job...
  memset(&docinfo, 0, sizeof(docinfo));
  docinfo.cbSize      = sizeof(docinfo);
  docinfo.lpszDocName = basename;

  StartDoc(dialog.hDC, &docinfo);

  // Figure out how many pages we'll have to print...
  if (dialog.Flags & PD_PAGENUMS) {
    // Get from and to page numbers...
    first = dialog.nFromPage;
    last  = dialog.nToPage;

    if (first > last) {
      // Swap first/last page
      page  = first;
      first = last;
      last  = page;
    }
  } else {
    // Print everything...
    first = 1;
    last  = dialog.nMaxPage;
  }

  for (t = Fl_Type::first, num_windows = 0, winpage = 0; t; t = t->next) {
    if (t->is_window()) {
      winpage ++;
      windows[num_windows] = (Fl_Window_Type *)t;
      num_windows ++;
#if 0
      if (dialog.Flags & PD_ALLPAGES) num_windows ++;
      else if ((dialog.Flags & PD_PAGENUMS) && winpage >= first &&
               winpage <= last) num_windows ++;
      else if ((dialog.Flags & PD_SELECTION) && t->selected) num_windows ++;
#endif // 0
    }
  }

  num_pages = num_windows;

  // Figure out the page size and margins...
  int	width, length;			// Size of page
  int   xdpi, ydpi;			// Output resolution
  char	buffer[1024];

  width  = GetDeviceCaps(dialog.hDC, HORZRES);
  length = GetDeviceCaps(dialog.hDC, VERTRES);
  xdpi   = GetDeviceCaps(dialog.hDC, LOGPIXELSX);
  ydpi   = GetDeviceCaps(dialog.hDC, LOGPIXELSY);

//  fl_message("width=%d, length=%d, xdpi=%d, ydpi=%d, num_windows=%d\n",
//             width, length, xdpi, ydpi, num_windows);

  HDC	save_dc = fl_gc;
  HWND	save_win = fl_window;
  int	fontsize = 14 * ydpi / 72;

  fl_gc = dialog.hDC;
  fl_window = (HWND)dialog.hDC;
  fl_push_no_clip();

  // Get the time and date...
  time_t curtime = time(NULL);
  struct tm *curdate = localtime(&curtime);
  char date[1024];

  strftime(date, sizeof(date), "%c", curdate);
    
  // Print each of the windows...
  for (winpage = 0; winpage < num_windows; winpage ++) {
    // Draw header...
    StartPage(dialog.hDC);

    fl_font(FL_HELVETICA_BOLD, fontsize);
    fl_color(0, 0, 0);

    fl_draw(basename, 0, fontsize);

    fl_draw(date, (width - (int)fl_width(date)) / 2, fontsize);

    sprintf(buffer, "%d/%d", winpage + 1, num_windows);
    fl_draw(buffer, width - (int)fl_width(buffer), fontsize);

    // Get window image...
    uchar	*pixels;		// Window image data
    int		w, h;			// Window image dimensions
    int		ww, hh;			// Scaled size
    int		ulx, uly;		// Upper-lefthand corner
    Fl_Window	*win;			// Window widget
    BITMAPINFO	info;			// Bitmap information

    win    = (Fl_Window *)(windows[winpage]->o);
    pixels = windows[winpage]->read_image(w, h);

    // Swap colors: FLTK uses R-G-B --> Windows GDI uses B-G-R

    { uchar *p = pixels;
      for (int i=0; i<w*h; i++, p+=3) {
	uchar temp = p[0]; p[0] = p[2]; p[2] = temp;
      }
    }

    // Figure out the window size, first at 100 PPI and then scaled
    // down if that is too big...
    ww = w * xdpi / 100;
    hh = h * ydpi / 100;

    if (ww > width) {
      ww = width;
      hh = h * ww * ydpi / xdpi / w;
    }

    if (hh > (length - ydpi / 2)) {
      hh = length - ydpi / 2;
      ww = w * hh / h;
    }

    // Position the window in the center...
    ulx = (width - ww) / 2;
    uly = (length - hh) / 2;

//    fl_message("winpage=%d, ulx=%d, uly=%d, ww=%d, hh=%d",
//               winpage, ulx, uly, ww, hh);

    // Draw a simulated window border...
    int xborder = 4 * ww / w;
    int yborder = 4 * hh / h;

    win_box(ulx - xborder, uly - 5 * yborder,
            ww + 2 * xborder, hh + 6 * yborder);

    fl_color(0, 0, 255);
    fl_rectf(ulx, uly - 4 * yborder, ww, 4 * yborder);

    fl_font(FL_HELVETICA_BOLD, 2 * yborder);
    fl_color(255, 255, 255);
    fl_draw(win->label() ? win->label() : "Window",
            ulx + xborder, uly - 3 * yborder);

    int x = ulx + ww - 4 * xborder;

    win_box(x, uly - 4 * yborder, 4 * xborder, 4 * yborder);
    fl_color(0, 0, 0);
    fl_line(x + xborder, uly - yborder,
            x + 3 * xborder, uly - 3 * yborder);
    fl_line(x + xborder, uly - 3 * yborder,
            x + 3 * xborder, uly - yborder);
    x -= 4 * xborder;

    if (win->resizable()) {
      win_box(x, uly - 4 * yborder, 4 * xborder, 4 * yborder);
      fl_color(0, 0, 0);
      fl_rect(x + xborder, uly - 3 * yborder, 2 * xborder, 2 * yborder);
      x -= 4 * xborder;
    }

    if (!win->modal()) {
      win_box(x, uly - 4 * yborder, 4 * xborder, 4 * yborder);
      fl_color(0, 0, 0);
      fl_line(x + xborder, uly - yborder, x + 3 * xborder, uly - yborder);
      x -= 4 * xborder;
    }

    // Color image...
    memset(&info, 0, sizeof(info));
    info.bmiHeader.biSize        = sizeof(info);
    info.bmiHeader.biWidth       = w;
    info.bmiHeader.biHeight      = 1;
    info.bmiHeader.biPlanes      = 1;
    info.bmiHeader.biBitCount    = 24;
    info.bmiHeader.biCompression = BI_RGB;

    for (int y = 0; y < h; y ++) {
      StretchDIBits(dialog.hDC, ulx, uly + y * hh / h, ww, (hh + h - 1) / h, 0, 0, w, 1,
                    pixels + y * w * 3, &info, DIB_RGB_COLORS, SRCCOPY);
    }

    delete[] pixels;

    // Show the page...
    EndPage(dialog.hDC);
  }

  // Finish up...
  EndDoc(dialog.hDC);

  fl_gc = save_dc;
  fl_window = save_win;
  fl_pop_clip();

  // Free the print DC and return...
  DeleteDC(dialog.hDC);
}
#else
// Load and show the print dialog...
void print_menu_cb(Fl_Widget *, void *) {
  if (!print_panel) make_print_panel();

  print_load();

  print_selection->deactivate();

  for (Fl_Type *t = Fl_Type::first; t; t = t->next) {
    if (t->selected && t->is_window()) {
      print_selection->activate();
      break;
    }
  }

  print_all->setonly();
  print_all->do_callback();

  print_panel->show();
}

// Quote a string for PostScript printing
static const char *ps_string(const char *s) {
  char *bufptr;
  static char buffer[2048];


  if (!s) {
    buffer[0] = '\0';
  } else {
    for (bufptr = buffer; bufptr < (buffer + sizeof(buffer) - 3) && *s;) {
      if (*s == '(' || *s == ')' || *s == '\\') *bufptr++ = '\\';
      *bufptr++ = *s++;
    }

    *bufptr = '\0';
  }

  return (buffer);
}

// Actually print...
void print_cb(Fl_Return_Button *, void *) {
  FILE		*outfile;		// Output file or pipe to print command
  char		command[1024];		// Print command
  int		copies;			// Collated copies
  int		first, last;		// First and last page
  int		page;			// Current page
  int		winpage;		// Current window page
  int		num_pages;		// Number of pages
  Fl_Type	*t;			// Current widget
  int		num_windows;		// Number of windows
  Fl_Window_Type *windows[1000];	// Windows to print

  // Show progress, deactivate controls...
  print_panel_controls->deactivate();
  print_progress->show();

  // Figure out how many pages we'll have to print...
  if (print_collate_button->value()) copies = (int)print_copies->value();
  else copies = 1;

  if (print_pages->value()) {
    // Get from and to page numbers...
    if ((first = atoi(print_from->value())) < 1) first = 1;
    if ((last = atoi(print_to->value())) < 1) last = 1000;

    if (first > last) {
      // Swap first/last page
      page  = first;
      first = last;
      last  = page;
    }
  } else {
    // Print everything...
    first = 1;
    last  = 1000;
  }

  for (t = Fl_Type::first, num_windows = 0, winpage = 0; t; t = t->next) {
    if (t->is_window()) {
      winpage ++;
      windows[num_windows] = (Fl_Window_Type *)t;

      if (print_all->value()) num_windows ++;
      else if (print_pages->value() && winpage >= first &&
               winpage <= last) num_windows ++;
      else if (print_selection->value() && t->selected) num_windows ++;
    }
  }

  num_pages = num_windows * copies;

  print_progress->minimum(0);
  print_progress->maximum(num_pages);
  print_progress->value(0);
  Fl::check();

  // Get the base filename...
  const char *basename = strrchr(filename, '/');
  if (basename) basename ++;
  else basename = filename;

  // Open the print stream...
  if (print_choice->value()) {
    // Pipe the output into the lp command...
    const char *printer = (const char *)print_choice->menu()[print_choice->value()].user_data();

    snprintf(command, sizeof(command), "lp -s -d %s -n %.0f -t '%s' -o media=%s",
             printer, print_collate_button->value() ? 1.0 : print_copies->value(),
	     basename, print_page_size->text(print_page_size->value()));
    outfile = popen(command, "w");
  } else {
    // Print to file...
    fl_file_chooser_ok_label("Print");
    const char *outname = fl_file_chooser("Print To", "PostScript (*.ps)", NULL, 1);
    fl_file_chooser_ok_label(NULL);

    if (outname && !access(outname, 0)) {
      if (fl_choice("The file \"%s\" already exists.\n"
                    "Do you want to replace it?", "Cancel",
		    "Replace", NULL, outname) == 0) outname = NULL;
    }

    if (outname) outfile = fopen(outname, "w");
    else outfile = NULL;
  }

  if (outfile) {
    // Figure out the page size and margins...
    int	width, length;			// Size of page
    int	left, bottom,			// Bottom lefthand corner
	right, top;			// Top righthand corner

    if (print_page_size->value()) {
      // A4
      width  = 595;
      length = 842;
    } else {
      // Letter
      width  = 612;
      length = 792;
    }

    int output_mode;
    for (output_mode = 0; output_mode < 4; output_mode ++) {
      if (print_output_mode[output_mode]->value()) break;
    }

    if (output_mode & 1) {
      // Landscape
      left   = 36;
      bottom = 18;
      right  = length - 36;
      top    = width - 18;
    } else {
      // Portrait
      left   = 18;
      bottom = 36;
      right  = width - 18;
      top    = length - 36;
    }

    // Get the time and date...
    time_t curtime = time(NULL);
    struct tm *curdate = localtime(&curtime);
    char date[1024];

    strftime(date, sizeof(date), "%c", curdate);
    
    // Write the prolog...
    fprintf(outfile,
            "%%!PS-Adobe-3.0\n"
	    "%%%%BoundingBox: 18 36 %d %d\n"
	    "%%%%Pages: %d\n"
	    "%%%%LanguageLevel: 1\n"
	    "%%%%DocumentData: Clean7Bit\n"
	    "%%%%DocumentNeededResources: font Helvetica-Bold\n"
	    "%%%%Creator: FLUID %.4f\n"
	    "%%%%CreationDate: %s\n"
	    "%%%%Title: (%s)\n"
	    "%%%%EndComments\n"
	    "%%%%BeginProlog\n"
	    "%%languagelevel 1 eq {\n"
	    "  /rectfill {\n"
	    "    newpath 4 2 roll moveto dup 0 exch rlineto exch 0 rlineto\n"
	    "    neg 0 exch rlineto closepath fill\n"
	    "  } bind def\n"
	    "  /rectstroke {\n"
	    "    newpath 4 2 roll moveto dup 0 exch rlineto exch 0 rlineto\n"
	    "    neg 0 exch rlineto closepath stroke\n"
	    "  } bind def\n"
	    "%%} if\n"
	    "%%%%EndProlog\n"
	    "%%%%BeginSetup\n"
	    "%%%%BeginFeature: *PageSize %s\n"
	    "languagelevel 1 ne {\n"
	    "  <</PageSize[%d %d]/ImagingBBox null>>setpagedevice\n"
	    "} {\n"
	    "  %s\n"
	    "} ifelse\n"
	    "%%%%EndFeature\n"
	    "%%%%EndSetup\n",
	    width - 18, length - 36,
	    num_pages,
	    FL_VERSION,
	    date,
	    basename,
	    print_page_size->text(print_page_size->value()),
	    width, length,
	    print_page_size->value() ? "a4tray" : "lettertray");

    // Print each of the windows...
    char	progress[255];		// Progress text
    int		copy;			// Current copy

    for (copy = 0, page = 0; copy < copies; copy ++) {
      for (winpage = 0; winpage < num_pages; winpage ++) {
        // Start next page...
        page ++;
	sprintf(progress, "Printing page %d/%d...", page, num_pages);
	print_progress->value(page);
	print_progress->label(progress);
	Fl::check();

        // Add common page stuff...
	fprintf(outfile,
	        "%%%%Page: %d %d\n"
		"gsave\n",
		page, page);

        if (output_mode & 1) {
	  // Landscape...
	  fprintf(outfile, "%d 0 translate 90 rotate\n", width);
	}

        // Draw header...
	fprintf(outfile,
		"0 setgray\n"
		"/Helvetica-Bold findfont 14 scalefont setfont\n"
		"%d %d moveto (%s) show\n"
		"%.1f %d moveto (%s) dup stringwidth pop -0.5 mul 0 rmoveto show\n"
		"%d %d moveto (%d/%d) dup stringwidth pop neg 0 rmoveto show\n",
	        left, top - 15, ps_string(basename),
		0.5 * (left + right), top - 15, date,
		right, top - 15, winpage + 1, num_windows);

        // Get window image...
	uchar	*pixels;		// Window image data
        int	w, h;			// Window image dimensions
	float	ww, hh;			// Scaled size
	float	border;			// Width of 1 pixel
        float	llx, lly,		// Lower-lefthand corner
		urx, ury;		// Upper-righthand corner
	Fl_Window *win;			// Window widget

        win    = (Fl_Window *)(windows[winpage]->o);
	pixels = windows[winpage]->read_image(w, h);

        // Figure out the window size, first at 100 PPI and then scaled
	// down if that is too big...
        ww = w * 72.0 / 100.0;
	hh = h * 72.0 / 100.0;

        if (ww > (right - left)) {
	  ww = right - left;
	  hh = h * ww / w;
	}

        if (hh > (top - bottom - 36)) {
	  hh = top - bottom;
	  ww = w * hh / h;
	}

        border = ww / w;

	// Position the window in the center...
	llx = 0.5 * (right - left - ww);
	lly = 0.5 * (top - bottom - hh);
	urx = 0.5 * (right - left + ww);
	ury = 0.5 * (top - bottom + hh);

        // Draw a simulated window border...
        fprintf(outfile,
	        "0.75 setgray\n"			// Gray background
	        "newpath %.2f %.2f %.2f 180 90 arcn\n"	// Top left 
		"%.2f %.2f %.2f 90 0 arcn\n"		// Top right
		"%.2f %.2f %.2f 0 -90 arcn\n"		// Bottom right
		"%.2f %.2f %.2f -90 -180 arcn\n"	// Bottom left
		"closepath gsave fill grestore\n"	// Fill
		"0 setlinewidth 0 setgray stroke\n",	// Outline
		llx, ury + 12 * border, 4 * border,
		urx, ury + 12 * border, 4 * border,
		urx, lly, 4 * border,
		llx, lly, 4 * border);

        // Title bar...
	if (output_mode & 2) {
	  fputs("0.25 setgray\n", outfile);
	} else {
	  fputs("0.1 0.2 0.6 setrgbcolor\n", outfile);
	}

        fprintf(outfile, "%.2f %.2f %.2f %.2f rectfill\n",
	        llx + 12 * border, ury,
		ww - (24 + 16 * (!win->modal() || win->resizable()) +
		      16 * (!win->modal() && win->resizable())) * border,
		16 * border);

        if (win->resizable()) {
	  fprintf(outfile,
		  "%.2f %.2f %.2f -90 -180 arcn\n"	// Bottom left
	          "0 %.2f rlineto %.2f 0 rlineto 0 -%.2f rlineto closepath fill\n"
		  "%.2f %.2f %.2f 0 -90 arcn\n"	// Bottom right
	          "-%.2f 0 rlineto 0 %.2f rlineto %.2f 0 rlineto closepath fill\n",
		  llx, lly, 4 * border,
		  12 * border, 16 * border, 16 * border,
		  urx, lly, 4 * border,
		  12 * border, 16 * border, 16 * border);
	}

        // Inside outline and button shading...
        fprintf(outfile,
	        "%.2f setlinewidth 0.5 setgray\n"
		"%.2f %.2f %.2f %.2f rectstroke\n"
		"%.2f %.2f moveto 0 %.2f rlineto\n"
		"%.2f %.2f moveto 0 %.2f rlineto\n",
		border,
		llx - 0.5 * border, lly - 0.5 * border, ww + border, hh + border,
		llx + 12 * border, ury, 16 * border,
		urx - 12 * border, ury, 16 * border);

        if (!win->modal() || win->resizable()) {
	  fprintf(outfile, "%.2f %.2f moveto 0 %.2f rlineto\n",
	          urx - 28 * border, ury, 16 * border);
	}

        if (!win->modal() && win->resizable()) {
	  fprintf(outfile, "%.2f %.2f moveto 0 %.2f rlineto\n",
	          urx - 44 * border, ury, 16 * border);
	}

        fprintf(outfile, "%.2f %.2f moveto %.2f 0 rlineto stroke\n",
		llx - 3.5 * border, ury + 0.5 * border, ww + 7 * border);

        // Button icons...
        fprintf(outfile,
	        "%.2f setlinewidth 0 setgray\n"
		"%.2f %.2f moveto %.2f -%.2f rlineto %.2f %.2f rlineto\n"
		"%.2f %.2f moveto -%.2f -%.2f rlineto 0 %.2f rmoveto %.2f -%.2f rlineto\n",
		2 * border,
		llx, ury + 10 * border, 4 * border, 4 * border, 4 * border, 4 * border,
		urx, ury + 12 * border, 8 * border, 8 * border, 8 * border, 8 * border, 8 * border);

        float x = urx - 16 * border;

        if (win->resizable()) {
	  // Maximize button
	  fprintf(outfile,
	          "%.2f %.2f moveto -%.2f 0 rlineto 0 -%.2f rlineto "
		  "%.2f 0 rlineto 0 %.2f rlineto\n",
		  x, ury + 12 * border, 8 * border, 8 * border,
		  8 * border, 8 * border);

          x -= 16 * border;
        }

        if (!win->modal()) {
	  // Minimize button
	  fprintf(outfile,
	          "%.2f %.2f moveto -%.2f 0 rlineto\n",
		  x, ury + 4 * border, 8 * border);
        }

        fputs("stroke\n", outfile);

        if (win->label()) {
	  // Add window title...
	  fprintf(outfile,
		  "1 setgray\n"
		  "/Helvetica-Bold findfont %.2f scalefont setfont\n"
		  "(%s) %.2f %.2f moveto show\n",
		  12 * border,
		  ps_string(win->label()), llx + 16 * border, ury + 4 * border);
	}

        fprintf(outfile,
	        "gsave\n"
		"%.2f %.2f translate %.2f %.2f scale\n",
		llx, ury - border, border, border);

        if (output_mode & 2) {
	  // Grayscale image...
	  fprintf(outfile,
	          "/imgdata %d string def\n"
		  "%d %d 8[1 0 0 -1 0 1] "
		  "{currentfile imgdata readhexstring pop} image\n",
		  w,
		  w, h);

          uchar *ptr = pixels;
	  int i, count = w * h;

          for (i = 0; i < count; i ++, ptr += 3) {
	    fprintf(outfile, "%02X",
	            (31 * ptr[0] + 61 * ptr[1] + 8 * ptr[2]) / 100);
	    if (!(i % 40)) putc('\n', outfile);
	  }
	} else {
	  // Color image...
	  fprintf(outfile,
	          "/imgdata %d string def\n"
		  "%d %d 8[1 0 0 -1 0 1] "
		  "{currentfile imgdata readhexstring pop} false 3 colorimage\n",
		  w * 3,
		  w, h);

          uchar *ptr = pixels;
	  int i, count = w * h;

          for (i = 0; i < count; i ++, ptr += 3) {
	    fprintf(outfile, "%02X%02X%02X", ptr[0], ptr[1], ptr[2]);
	    if (!(i % 13)) putc('\n', outfile);
	  }
	}

        fputs("\ngrestore\n", outfile);

        delete[] pixels;

        // Show the page...
	fputs("grestore showpage\n", outfile);
      }
    }

    // Finish up...
    fputs("%%EOF\n", outfile);

    if (print_choice->value()) pclose(outfile);
    else fclose(outfile);
  } else {
    // Unable to print...
    fl_alert("Error printing: %s", strerror(errno));
  }

  // Hide progress, activate controls, hide print panel...
  print_panel_controls->activate();
  print_progress->hide();
  print_panel->hide();
}
#endif // WIN32 && !__CYGWIN__

////////////////////////////////////////////////////////////////

extern Fl_Menu_Item New_Menu[];

void toggle_widgetbin_cb(Fl_Widget *, void *);
void toggle_sourceview_cb(Fl_Double_Window *, void *);

Fl_Menu_Item Main_Menu[] = {
{"&File",0,0,0,FL_SUBMENU},
  {"&New...", FL_COMMAND+'n', new_cb, 0},
  {"&Open...", FL_COMMAND+'o', open_cb, 0},
  {"&Insert...", FL_COMMAND+'i', open_cb, (void*)1, FL_MENU_DIVIDER},
#define SAVE_ITEM 4
  {"&Save", FL_COMMAND+'s', save_cb, 0},
  {"Save &As...", FL_COMMAND+FL_SHIFT+'s', save_cb, (void*)1},
  {"Sa&ve A Copy...", 0, save_cb, (void*)2},
  {"Save &Template...", 0, save_template_cb},
  {"&Revert...", 0, revert_cb, 0, FL_MENU_DIVIDER},
  {"&Print...", FL_COMMAND+'p', print_menu_cb},
  {"Write &Code...", FL_COMMAND+FL_SHIFT+'c', write_cb, 0},
  {"&Write Strings...", FL_COMMAND+FL_SHIFT+'w', write_strings_cb, 0, FL_MENU_DIVIDER},
#define HISTORY_ITEM 12
  {relative_history[0], FL_COMMAND+'0', open_history_cb, absolute_history[0]},
  {relative_history[1], FL_COMMAND+'1', open_history_cb, absolute_history[1]},
  {relative_history[2], FL_COMMAND+'2', open_history_cb, absolute_history[2]},
  {relative_history[3], FL_COMMAND+'3', open_history_cb, absolute_history[3]},
  {relative_history[4], FL_COMMAND+'4', open_history_cb, absolute_history[4]},
  {relative_history[5], FL_COMMAND+'5', open_history_cb, absolute_history[5]},
  {relative_history[6], FL_COMMAND+'6', open_history_cb, absolute_history[6]},
  {relative_history[7], FL_COMMAND+'7', open_history_cb, absolute_history[7]},
  {relative_history[8], FL_COMMAND+'8', open_history_cb, absolute_history[8]},
  {relative_history[9], FL_COMMAND+'9', open_history_cb, absolute_history[9], FL_MENU_DIVIDER},
  {"&Quit", FL_COMMAND+'q', exit_cb},
  {0},
{"&Edit",0,0,0,FL_SUBMENU},
  {"&Undo", FL_COMMAND+'z', undo_cb},
  {"&Redo", FL_COMMAND+FL_SHIFT+'z', redo_cb, 0, FL_MENU_DIVIDER},
  {"C&ut", FL_COMMAND+'x', cut_cb},
  {"&Copy", FL_COMMAND+'c', copy_cb},
  {"&Paste", FL_COMMAND+'v', paste_cb},
  {"Dup&licate", FL_COMMAND+'u', duplicate_cb},
  {"&Delete", FL_Delete, delete_cb, 0, FL_MENU_DIVIDER},
  {"Select &All", FL_COMMAND+'a', select_all_cb},
  {"Select &None", FL_COMMAND+FL_SHIFT+'a', select_none_cb, 0, FL_MENU_DIVIDER},
  {"Pr&operties...", FL_F+1, openwidget_cb},
  {"&Sort",0,sort_cb},
  {"&Earlier", FL_F+2, earlier_cb},
  {"&Later", FL_F+3, later_cb},
  {"&Group", FL_F+7, group_cb},
  {"Ung&roup", FL_F+8, ungroup_cb,0, FL_MENU_DIVIDER},
  {"Hide O&verlays",FL_COMMAND+FL_SHIFT+'o',toggle_overlays},
#define WIDGETBIN_ITEM 41
  {"Show Widget &Bin...",FL_ALT+'b',toggle_widgetbin_cb},
#define SOURCEVIEW_ITEM 42
  {"Show Source Code...",FL_ALT+FL_SHIFT+'s', (Fl_Callback*)toggle_sourceview_cb, 0, FL_MENU_DIVIDER},
  {"Pro&ject Settings...",FL_ALT+'p',show_project_cb},
  {"GU&I Settings...",FL_ALT+FL_SHIFT+'p',show_settings_cb},
  {0},
{"&New", 0, 0, (void *)New_Menu, FL_SUBMENU_POINTER},
{"&Layout",0,0,0,FL_SUBMENU},
  {"&Align",0,0,0,FL_SUBMENU},
    {"&Left",0,(Fl_Callback *)align_widget_cb,(void*)10},
    {"&Center",0,(Fl_Callback *)align_widget_cb,(void*)11},
    {"&Right",0,(Fl_Callback *)align_widget_cb,(void*)12},
    {"&Top",0,(Fl_Callback *)align_widget_cb,(void*)13},
    {"&Middle",0,(Fl_Callback *)align_widget_cb,(void*)14},
    {"&Bottom",0,(Fl_Callback *)align_widget_cb,(void*)15},
    {0},
  {"&Space Evenly",0,0,0,FL_SUBMENU},
    {"&Across",0,(Fl_Callback *)align_widget_cb,(void*)20},
    {"&Down",0,(Fl_Callback *)align_widget_cb,(void*)21},
    {0},
  {"&Make Same Size",0,0,0,FL_SUBMENU},
    {"&Width",0,(Fl_Callback *)align_widget_cb,(void*)30},
    {"&Height",0,(Fl_Callback *)align_widget_cb,(void*)31},
    {"&Both",0,(Fl_Callback *)align_widget_cb,(void*)32},
    {0},
  {"&Center In Group",0,0,0,FL_SUBMENU},
    {"&Horizontal",0,(Fl_Callback *)align_widget_cb,(void*)40},
    {"&Vertical",0,(Fl_Callback *)align_widget_cb,(void*)41},
    {0},
  {"Set &Widget Size",0,0,0,FL_SUBMENU|FL_MENU_DIVIDER},
    {"&Tiny",FL_ALT+'1',(Fl_Callback *)widget_size_cb,(void*)8,0,FL_NORMAL_LABEL,FL_HELVETICA,8},
    {"&Small",FL_ALT+'2',(Fl_Callback *)widget_size_cb,(void*)11,0,FL_NORMAL_LABEL,FL_HELVETICA,11},
    {"&Normal",FL_ALT+'3',(Fl_Callback *)widget_size_cb,(void*)14,0,FL_NORMAL_LABEL,FL_HELVETICA,14},
    {"&Medium",FL_ALT+'4',(Fl_Callback *)widget_size_cb,(void*)18,0,FL_NORMAL_LABEL,FL_HELVETICA,18},
    {"&Large",FL_ALT+'5',(Fl_Callback *)widget_size_cb,(void*)24,0,FL_NORMAL_LABEL,FL_HELVETICA,24},
    {"&Huge",FL_ALT+'6',(Fl_Callback *)widget_size_cb,(void*)32,0,FL_NORMAL_LABEL,FL_HELVETICA,32},
    {0},
  {"&Grid and Size Settings...",FL_COMMAND+'g',show_grid_cb},
  {0},
{"&Shell",0,0,0,FL_SUBMENU},
  {"Execute &Command...",FL_ALT+'x',(Fl_Callback *)show_shell_window},
  {"Execute &Again...",FL_ALT+'g',(Fl_Callback *)do_shell_command},
  {0},
{"&Help",0,0,0,FL_SUBMENU},
  {"&About FLUID...",0,about_cb},
  {"&On FLUID...",0,help_cb},
  {"&Manual...",0,manual_cb},
  {0},
{0}};

#define BROWSERWIDTH 300
#define BROWSERHEIGHT 500
#define WINWIDTH 300
#define MENUHEIGHT 25
#define WINHEIGHT (BROWSERHEIGHT+MENUHEIGHT)

extern void fill_in_New_Menu();

void scheme_cb(Fl_Choice *, void *) {
  if (compile_only)
    return;

  switch (scheme_choice->value()) {
    case 0 : // Default
      Fl::scheme(NULL);
      break;
    case 1 : // None
      Fl::scheme("none");
      break;
    case 2 : // Plastic
      Fl::scheme("plastic");
      break;
    case 3 : // GTK+
      Fl::scheme("gtk+");
      break;
  }

  fluid_prefs.set("scheme", scheme_choice->value());
}

void toggle_widgetbin_cb(Fl_Widget *, void *) {
  if (!widgetbin_panel) {
    make_widgetbin();
    widgetbin_panel->callback(toggle_widgetbin_cb);
    if (!position_window(widgetbin_panel,"widgetbin_pos", 1, 320, 30)) return;
  }

  if (widgetbin_panel->visible()) {
    widgetbin_panel->hide();
    Main_Menu[WIDGETBIN_ITEM].label("Show Widget &Bin...");
  } else {
    widgetbin_panel->show();
    Main_Menu[WIDGETBIN_ITEM].label("Hide Widget &Bin");
  }
}


void toggle_sourceview_cb(Fl_Double_Window *, void *) {
  if (!sourceview_panel) {
    make_sourceview();
    sourceview_panel->callback((Fl_Callback*)toggle_sourceview_cb);
    Fl_Preferences svp(fluid_prefs, "sourceview");
    int autorefresh;
    svp.get("autorefresh", autorefresh, 1);
    sv_autorefresh->value(autorefresh);
    int autoposition;
    svp.get("autoposition", autoposition, 1);
    sv_autoposition->value(autoposition);
    int tab;
    svp.get("tab", tab, 0);
    if (tab>=0 && tab<sv_tab->children()) sv_tab->value(sv_tab->child(tab));
    if (!position_window(sourceview_panel,"sourceview_pos", 0, 320, 120, 550, 500)) return;
  }

  if (sourceview_panel->visible()) {
    sourceview_panel->hide();
    Main_Menu[SOURCEVIEW_ITEM].label("Show Source Code...");
  } else {
    sourceview_panel->show();
    Main_Menu[SOURCEVIEW_ITEM].label("Hide Source Code...");
    update_sourceview_cb(0,0);
  }
}

void toggle_sourceview_b_cb(Fl_Button*, void *) {
  toggle_sourceview_cb(0,0);
}

void make_main_window() {
  fluid_prefs.get("snap", snap, 1);
  fluid_prefs.get("gridx", gridx, 5);
  fluid_prefs.get("gridy", gridy, 5);
  fluid_prefs.get("show_guides", show_guides, 0);
  fluid_prefs.get("widget_size", Fl_Widget_Type::default_size, 14);

  load_history();

  make_layout_window();
  make_settings_window();
  make_shell_window();

  if (!main_window) {
    Fl_Widget *o;
    main_window = new Fl_Double_Window(WINWIDTH,WINHEIGHT,"fluid");
    main_window->box(FL_NO_BOX);
    o = make_widget_browser(0,MENUHEIGHT,BROWSERWIDTH,BROWSERHEIGHT);
    o->box(FL_FLAT_BOX);
    o->tooltip("Double-click to view or change an item.");
    main_window->resizable(o);
    main_menubar = new Fl_Menu_Bar(0,0,BROWSERWIDTH,MENUHEIGHT);
    main_menubar->menu(Main_Menu);
    main_menubar->global();
    fill_in_New_Menu();
    main_window->end();
  }
}

// Load file history from preferences...
void load_history() {
  int	i;		// Looping var
  int	max_files;


  fluid_prefs.get("recent_files", max_files, 5);
  if (max_files > 10) max_files = 10;

  for (i = 0; i < max_files; i ++) {
    fluid_prefs.get( Fl_Preferences::Name("file%d", i), absolute_history[i], "", sizeof(absolute_history[i]));
    if (absolute_history[i][0]) {
      // Make a relative version of the filename for the menu...
      fl_filename_relative(relative_history[i], sizeof(relative_history[i]),
                           absolute_history[i]);

      if (i == 9) Main_Menu[i + HISTORY_ITEM].flags = FL_MENU_DIVIDER;
      else Main_Menu[i + HISTORY_ITEM].flags = 0;
    } else break;
  }

  for (; i < 10; i ++) {
    if (i) Main_Menu[i + HISTORY_ITEM - 1].flags |= FL_MENU_DIVIDER;
    Main_Menu[i + HISTORY_ITEM].hide();
  }
}

// Update file history from preferences...
void update_history(const char *flname) {
  int	i;		// Looping var
  char	absolute[1024];
  int	max_files;


  fluid_prefs.get("recent_files", max_files, 5);
  if (max_files > 10) max_files = 10;

  fl_filename_absolute(absolute, sizeof(absolute), flname);

  for (i = 0; i < max_files; i ++)
#if defined(WIN32) || defined(__APPLE__)
    if (!strcasecmp(absolute, absolute_history[i])) break;
#else
    if (!strcmp(absolute, absolute_history[i])) break;
#endif // WIN32 || __APPLE__

  if (i == 0) return;

  if (i >= max_files) i = max_files - 1;

  // Move the other flnames down in the list...
  memmove(absolute_history + 1, absolute_history,
          i * sizeof(absolute_history[0]));
  memmove(relative_history + 1, relative_history,
          i * sizeof(relative_history[0]));

  // Put the new file at the top...
  strlcpy(absolute_history[0], absolute, sizeof(absolute_history[0]));

  fl_filename_relative(relative_history[0], sizeof(relative_history[0]),
                       absolute_history[0]);

  // Update the menu items as needed...
  for (i = 0; i < max_files; i ++) {
    fluid_prefs.set( Fl_Preferences::Name("file%d", i), absolute_history[i]);
    if (absolute_history[i][0]) {
      if (i == 9) Main_Menu[i + HISTORY_ITEM].flags = FL_MENU_DIVIDER;
      else Main_Menu[i + HISTORY_ITEM].flags = 0;
    } else break;
  }

  for (; i < 10; i ++) {
    fluid_prefs.set( Fl_Preferences::Name("file%d", i), "");
    if (i) Main_Menu[i + HISTORY_ITEM - 1].flags |= FL_MENU_DIVIDER;
    Main_Menu[i + HISTORY_ITEM].hide();
  }
}

// Shell command support...
#if (!defined(WIN32) || defined(__CYGWIN__)) && !defined(__MWERKS__)
// Support the full piped shell command...
static FILE *shell_pipe = 0;

void
shell_pipe_cb(int, void*) {
  char	line[1024];		// Line from command output...

  if (fgets(line, sizeof(line), shell_pipe) != NULL) {
    // Add the line to the output list...
    shell_run_buffer->append(line);
  } else {
    // End of file; tell the parent...
    Fl::remove_fd(fileno(shell_pipe));

    pclose(shell_pipe);
    shell_pipe = NULL;
    shell_run_buffer->append("... END SHELL COMMAND ...\n");
  }

  shell_run_display->scroll(shell_run_display->count_lines(0,
                            shell_run_buffer->length(), 1), 0);
}

void
do_shell_command(Fl_Return_Button*, void*) {
  const char	*command;	// Command to run


  shell_window->hide();

  if (shell_pipe) {
    fl_alert("Previous shell command still running!");
    return;
  }

  if ((command = shell_command_input->value()) == NULL || !*command) {
    fl_alert("No shell command entered!");
    return;
  }

  if (shell_savefl_button->value()) {
    save_cb(0, 0);
  }

  if (shell_writecode_button->value()) {
    compile_only = 1;
    write_cb(0, 0);
    compile_only = 0;
  }

  if (shell_writemsgs_button->value()) {
    compile_only = 1;
    write_strings_cb(0, 0);
    compile_only = 0;
  }

  // Show the output window and clear things...
  shell_run_buffer->text("");
  shell_run_buffer->append(command);
  shell_run_buffer->append("\n");
  shell_run_window->label("Shell Command Running...");

  if ((shell_pipe = popen((char *)command, "r")) == NULL) {
    fl_alert("Unable to run shell command: %s", strerror(errno));
    return;
  }

  shell_run_button->deactivate();
  shell_run_window->hotspot(shell_run_display);
  shell_run_window->show();

  Fl::add_fd(fileno(shell_pipe), shell_pipe_cb);

  while (shell_pipe) Fl::wait();

  shell_run_button->activate();
  shell_run_window->label("Shell Command Complete");
  fl_beep();

  while (shell_run_window->shown()) Fl::wait();
}
#else
// Just do basic shell command stuff, no status window...
void
do_shell_command(Fl_Return_Button*, void*) {
  const char	*command;	// Command to run
  int		status;		// Status from command...


  shell_window->hide();

  if ((command = shell_command_input->value()) == NULL || !*command) {
    fl_alert("No shell command entered!");
    return;
  }

  if (shell_savefl_button->value()) {
    save_cb(0, 0);
  }

  if (shell_writecode_button->value()) {
    compile_only = 1;
    write_cb(0, 0);
    compile_only = 0;
  }

  if (shell_writemsgs_button->value()) {
    compile_only = 1;
    write_strings_cb(0, 0);
    compile_only = 0;
  }

  if ((status = system(command)) != 0) {
    fl_alert("Shell command returned status %d!", status);
  } else if (completion_button->value()) {
    fl_message("Shell command completed successfully!");
  }
}
#endif // (!WIN32 || __CYGWIN__) && !__MWERKS__


void
show_shell_window() {
  shell_window->hotspot(shell_command_input);
  shell_window->show();
}

void set_filename(const char *c) {
  if (filename) free((void *)filename);
  filename = c ? strdup(c) : NULL;

  if (filename) update_history(filename);

  set_modflag(modflag);
}

//
// The Source View system offers an immediate preview of the code 
// files that will be generated by FLUID. It also marks the code
// generated for the last selected item in the header and the source
// file.
//
// Can we patent this?  ;-)  - Matt, mm@matthiasm.com
//

//
// Update the header and source code highlighting depending on the
// currently selected object
//
void update_sourceview_position()
{
  if (!sourceview_panel || !sourceview_panel->visible()) 
    return;
  if (sv_autoposition->value()==0) 
    return;
  if (sourceview_panel && sourceview_panel->visible() && Fl_Type::current) {
    int pos0, pos1;
    if (sv_source->visible_r()) {
      pos0 = Fl_Type::current->code_line;
      pos1 = Fl_Type::current->code_line_end;
      if (pos0>=0) {
        if (pos1<pos0)
          pos1 = pos0;
        sv_source->buffer()->highlight(pos0, pos1);
        int line = sv_source->buffer()->count_lines(0, pos0);
        sv_source->scroll(line, 0);
      }
    }
    if (sv_header->visible_r()) {
      pos0 = Fl_Type::current->header_line;
      pos1 = Fl_Type::current->header_line_end;
      if (pos0>=0) {
        if (pos1<pos0)
          pos1 = pos0;
        sv_header->buffer()->highlight(pos0, pos1);
        int line = sv_header->buffer()->count_lines(0, pos0);
        sv_header->scroll(line, 0);
      }
    }
  }
}

void update_sourceview_position_cb(Fl_Tabs*, void*) 
{
  update_sourceview_position();
}

static char *sv_source_filename = 0;
static char *sv_header_filename = 0;

//
// Generate a header and source file in a temporary directory and
// load those into the Code Viewer widgets.
//
void update_sourceview_cb(Fl_Button*, void*) 
{
  if (!sourceview_panel || !sourceview_panel->visible()) 
    return;
  // generate space for the source and header file filenames
  if (!sv_source_filename) {
    sv_source_filename = (char*)malloc(FL_PATH_MAX);
    fluid_prefs.getUserdataPath(sv_source_filename, FL_PATH_MAX);
    strlcat(sv_source_filename, "source_view_tmp.cxx", FL_PATH_MAX);
  }
  if (!sv_header_filename) {
    sv_header_filename = (char*)malloc(FL_PATH_MAX);
    fluid_prefs.getUserdataPath(sv_header_filename, FL_PATH_MAX);
    strlcat(sv_header_filename, "source_view_tmp.h", FL_PATH_MAX);
  }

  strlcpy(i18n_program, fl_filename_name(sv_source_filename), sizeof(i18n_program));
  fl_filename_setext(i18n_program, sizeof(i18n_program), "");
  const char *code_file_name_bak = code_file_name;
  code_file_name = sv_source_filename;
  const char *header_file_name_bak = header_file_name;
  header_file_name = sv_header_filename;

  // generate the code and load the files
  write_sourceview = 1;
  // generate files
  if (write_code(sv_source_filename, sv_header_filename)) 
  {
    // load file into source editor
    int pos = sv_source->top_line();
    sv_source->buffer()->loadfile(sv_source_filename);
    sv_source->scroll(pos, 0);
    // load file into header editor
    pos = sv_header->top_line();
    sv_header->buffer()->loadfile(sv_header_filename);
    sv_header->scroll(pos, 0);
    // update the source code highlighting
    update_sourceview_position();
  }
  write_sourceview = 0;

  code_file_name = code_file_name_bak;
  header_file_name = header_file_name_bak;
}

void update_sourceview_timer(void*) 
{
  update_sourceview_cb(0,0);
}

// Set the "modified" flag and update the title of the main window...
void set_modflag(int mf) {
  const char	*basename;
  static char	title[1024];

  modflag = mf;

  if (main_window) {
    if (!filename) basename = "Untitled.fl";
    else if ((basename = strrchr(filename, '/')) != NULL) basename ++;
#if defined(WIN32) || defined(__EMX__) 
    else if ((basename = strrchr(filename, '\\')) != NULL) basename ++;
#endif // WIN32 || __EMX__
    else basename = filename;

    if (modflag) {
      snprintf(title, sizeof(title), "%s (modified)", basename);
      main_window->label(title);
    } else main_window->label(basename);
  }
  // if the UI was modified in any way, update the Source View panel
  if (sourceview_panel && sourceview_panel->visible() && sv_autorefresh->value())
  {
    // we will only update ealiest 0.5 seconds after the last change, and only
    // if no other change was made, so dragging a widget will not generate any
    // CPU load
    Fl::remove_timeout(update_sourceview_timer, 0);
    Fl::add_timeout(0.5, update_sourceview_timer, 0);
  }

  // Enable/disable the Save menu item...
  if (modflag) Main_Menu[SAVE_ITEM].activate();
  else Main_Menu[SAVE_ITEM].deactivate();
}

////////////////////////////////////////////////////////////////

static int arg(int argc, char** argv, int& i) {
  if (argv[i][1] == 'c' && !argv[i][2]) {compile_only = 1; i++; return 1;}
  if (argv[i][1] == 'c' && argv[i][2] == 's' && !argv[i][3]) {compile_only = 1; compile_strings = 1; i++; return 1;}
  if (argv[i][1] == 'o' && !argv[i][2] && i+1 < argc) {
    code_file_name = argv[i+1];
    code_file_set  = 1;
    i += 2;
    return 2;
  }
  if (argv[i][1] == 'h' && !argv[i][2]) {
    header_file_name = argv[i+1];
    header_file_set  = 1;
    i += 2;
    return 2;
  }
  return 0;
}

#if ! (defined(WIN32) && !defined (__CYGWIN__))

int quit_flag = 0;
#include <signal.h>
#ifdef _sigargs
#define SIGARG _sigargs
#else
#ifdef __sigargs
#define SIGARG __sigargs
#else
#define SIGARG int // you may need to fix this for older systems
#endif
#endif

extern "C" {
static void sigint(SIGARG) {
  signal(SIGINT,sigint);
  quit_flag = 1;
}
}
#endif

int main(int argc,char **argv) {
  int i = 1;
  if (!Fl::args(argc,argv,i,arg) || i < argc-1) {
    fprintf(stderr,"usage: %s <switches> name.fl\n"
" -c : write .cxx and .h and exit\n"
" -cs : write .cxx and .h and strings and exit\n"
" -o <name> : .cxx output filename, or extension if <name> starts with '.'\n"
" -h <name> : .h output filename, or extension if <name> starts with '.'\n"
"%s\n", argv[0], Fl::help);
    return 1;
  }
  const char *c = argv[i];

  fl_register_images();

  make_main_window();

#ifdef __APPLE__
  fl_open_callback(apple_open_cb);
#endif // __APPLE__

  if (c) set_filename(c);
  if (!compile_only) {
    Fl::visual((Fl_Mode)(FL_DOUBLE|FL_INDEX));
    Fl_File_Icon::load_system_icons();
    main_window->callback(exit_cb);
    position_window(main_window,"main_window_pos", 1, 10, 30, WINWIDTH, WINHEIGHT );
    main_window->show(argc,argv);
    toggle_widgetbin_cb(0,0);
    toggle_sourceview_cb(0,0);
    if (!c && openlast_button->value() && absolute_history[0][0]) {
      // Open previous file when no file specified...
      open_history_cb(0, absolute_history[0]);
    }
  }
  undo_suspend();
  if (c && !read_file(c,0)) {
    if (compile_only) {
      fprintf(stderr,"%s : %s\n", c, strerror(errno));
      exit(1);
    }
    fl_message("Can't read %s: %s", c, strerror(errno));
  }
  undo_resume();
  if (compile_only) {
    if (compile_strings) write_strings_cb(0,0);
    write_cb(0,0);
    exit(0);
  }
  set_modflag(0);
  undo_clear();
#ifndef WIN32
  signal(SIGINT,sigint);
#endif

  grid_cb(horizontal_input, 0); // Makes sure that windows get snap params...

#ifdef WIN32
  Fl::run();
#else
  while (!quit_flag) Fl::wait();

  if (quit_flag) exit_cb(0,0);
#endif // WIN32

  undo_clear();

  return (0);
}

//
// End of "$Id: fluid.cxx 6783 2009-04-27 15:50:29Z AlbrechtS $".
//
