// Calterm, the Gnome Terminal frontend for Calculators
// Copyright (C) 2001, Sebastian Ritterbusch (Rascal@Ritterbusch.de)
//                     Davide Angelocola
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
//

// The following code is ugly. Don't read it. Just use calterm.hpp ;)

#include "calterm.hpp"

#include <gtk/gtk.h>
#include <strings.h>

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <pwd.h>
#include <stdlib.h>

#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <gdk/gdkprivate.h>
#include <gdk/gdkkeysyms.h>
#include <libgnome/gnome-url.h>

#include <zvt/zvtterm.h>

MenuEntry::MenuEntry(string aMenu,string aShortcut) 
            : iMenu(aMenu),iShortcut(aShortcut)
{
}

GtkItemFactoryEntry MenuEntry::Entry(void)
{
   GtkItemFactoryEntry a;
   a.path=(gchar *)iMenu.c_str();
   a.accelerator=(gchar *)iShortcut.c_str();
   a.callback=CallBack();
   a.callback_action=CallBackAction();
   a.item_type=ItemType();
   return a;
}

Calterm * CaltermPointer;

vector<string> MenuFileSelectHelperStrings;
vector<void (*)(string,Calterm &)> MenuFileSelectHelperFunctions;
GtkWidget *MenuFileSelectHelperCurrent=NULL;
int        MenuFileSelectHelperCurrentAction=0;

static void MenuFileSelectHelperClose(GtkWidget *w,GtkFileSelection *fs)
{
   gtk_widget_destroy(GTK_WIDGET (w));
   MenuFileSelectHelperCurrent=NULL;
}

static void MenuFileSelectHelperOpen(GtkWidget *w,GtkFileSelection *fs)
{
   MenuFileSelectHelperFunctions[MenuFileSelectHelperCurrentAction]
    (gtk_file_selection_get_filename(GTK_FILE_SELECTION(w)),
     *CaltermPointer);
   gtk_widget_destroy(GTK_WIDGET (w));
}

static void MenuFileSelectHelper(gpointer data, guint action, GtkWidget *widget)
{
   if(action>=0 && action<=MenuFileSelectHelperStrings.size())
   {
      if(MenuFileSelectHelperCurrent)
      {
         gtk_widget_destroy(GTK_WIDGET (MenuFileSelectHelperCurrent));
         MenuFileSelectHelperCurrent=NULL;
      }
      
      GtkWidget *filew;
      filew=gtk_file_selection_new(MenuFileSelectHelperStrings[action].c_str());

      // gtk_file_selection_set_filename(GTK_FILE_SELECTION(filew),current_filename);
      
      gtk_signal_connect_object(GTK_OBJECT (GTK_FILE_SELECTION (filew)->cancel_button),
         "clicked", (GtkSignalFunc) MenuFileSelectHelperClose,
         GTK_OBJECT (filew));
      
      gtk_signal_connect_object(GTK_OBJECT (GTK_FILE_SELECTION (filew)->ok_button),
         "clicked", (GtkSignalFunc) MenuFileSelectHelperOpen,
         GTK_OBJECT (filew));
      gtk_widget_show(filew);  
      
      MenuFileSelectHelperCurrent=filew; 
      MenuFileSelectHelperCurrentAction=action;
   }    
}

MenuFileSelect::MenuFileSelect(string aMenu,string aShortcut,
                               string aTitle,
                               void (*aFunction)(string,Calterm &))
                : MenuEntry(aMenu,aShortcut)
{
   iAction=MenuFileSelectHelperStrings.size();
   MenuFileSelectHelperStrings.push_back(aTitle);
   MenuFileSelectHelperFunctions.push_back(aFunction);
}

GtkItemFactoryCallback MenuFileSelect::CallBack(void)
{
   return (GtkItemFactoryCallback)MenuFileSelectHelper;
}

vector<void (*)(Calterm &)> MenuFunctionHelperFunctions;

static void MenuFunctionHelper(gpointer data, guint action, GtkWidget *widget)
{
   if(action>=0 && action<=MenuFunctionHelperFunctions.size())
   {
      MenuFunctionHelperFunctions[action](*CaltermPointer);
   }    
}

MenuFunction::MenuFunction(string aMenu,string aShortcut,
                               void (*aFunction)(Calterm &))
                : MenuEntry(aMenu,aShortcut)
{
   iAction=MenuFunctionHelperFunctions.size();
   MenuFunctionHelperFunctions.push_back(aFunction);
}

GtkItemFactoryCallback MenuFunction::CallBack(void)
{
   return (GtkItemFactoryCallback)MenuFunctionHelper;
}

vector<string> MenuShowUrlHelperStrings;

static void MenuShowUrlHelper(gpointer data, guint action, GtkWidget *widget)
{
   if(action>=0 && action<=MenuShowUrlHelperStrings.size())
      gnome_url_show(MenuShowUrlHelperStrings[action].c_str());
}

MenuShowUrl::MenuShowUrl(string aMenu,string aShortcut,string aUrl)
                : MenuEntry(aMenu,aShortcut)
{
   iAction=MenuShowUrlHelperStrings.size();
   MenuShowUrlHelperStrings.push_back(aUrl);
}

GtkItemFactoryCallback MenuShowUrl::CallBack(void)
{
   return (GtkItemFactoryCallback)MenuShowUrlHelper;
}

vector<string> MenuSimpleSendHelperStrings;

static void MenuSimpleSendHelper(gpointer data, guint action, GtkWidget *widget)
{
   if(action>=0 && action<=MenuSimpleSendHelperStrings.size())
      CaltermPointer->send_command(MenuSimpleSendHelperStrings[action]);
}

MenuSimpleSend::MenuSimpleSend(string aMenu,string aShortcut,string aSend)
                : MenuEntry(aMenu,aShortcut)
{
   iAction=MenuSimpleSendHelperStrings.size();
   MenuSimpleSendHelperStrings.push_back(aSend);
}

GtkItemFactoryCallback MenuSimpleSend::CallBack(void)
{
   return (GtkItemFactoryCallback)MenuSimpleSendHelper;
}

Calterm::Calterm(void)
            :  iWindowName("calterm"),
               iClientPath(""),
               iScrollPosRight(1),
               iCursorBlink(1)
{
   CaltermPointer=this;
}

Calterm::Calterm(string aClientPath)
            :  iWindowName(aClientPath),
               iClientPath(aClientPath),
               iScrollPosRight(1),
               iCursorBlink(1)
{
   CaltermPointer=this;
}


Calterm::Calterm(string aWindowName,
              string aClientPath,
              int    aScrollBackLines,
              int    aScrollPosRight,
              int    aCursorBlink) 
            :  iWindowName(aWindowName),
               iClientPath(aClientPath),
               iScrollPosRight(aScrollPosRight),
               iCursorBlink(aCursorBlink)
{
   CaltermPointer=this;
}

Calterm::Calterm(string aWindowName,
              string aClientPath,
              string aClientCommandLineOptions,
              int    aScrollBackLines,
              int    aScrollPosRight,
              int    aCursorBlink) 
            :  iWindowName(aWindowName),
               iClientPath(aClientPath),
               iClientCommandLineOptions(aClientCommandLineOptions),
               iScrollPosRight(aScrollPosRight),
               iCursorBlink(aCursorBlink)
{
   CaltermPointer=this;
}
              
Calterm::~Calterm(void)
{
}
     
string Calterm::do_command(string c,int transparent)
{
   ZvtTerm *t=ZVT_TERM (iTerminal);
   char *a=new char[1025];

   string dest;
   
   int pfound=0;
   int l;

   write(t->vx->vt.keyfd, c.c_str(), c.length());

   do {
      int i;

      while((l=read(t->vx->vt.childfd, a,1024))==-1 && errno==EAGAIN)
         sleep(1); // 100 ms would be much better... 
                   // or "waiting until data there"

      a[l]=0;
      
      dest+=a;

      printf("%s",a); // should be sent to ZVT dependent on transparent setting

      for(i=0;i<l && a[i]!='>';i++);
      if(i<l)
         pfound=1;

   } while(!pfound && l!=-1);

   delete [] a;
   
   return dest;
}

void Calterm::send_command(string c)
{
   ZvtTerm *t=ZVT_TERM (iTerminal);
   write(t->vx->vt.keyfd, c.c_str(), c.length() );
}

void get_main_menu( GtkWidget  *window,
                    GtkWidget **menubar,
                    vector<MenuEntry *> iMenuEntries )
{
   GtkItemFactory *item_factory;
   GtkAccelGroup *accel_group;
   gint nmenu_items =iMenuEntries.size();

   GtkItemFactoryEntry * menu_items=new GtkItemFactoryEntry[nmenu_items];
   // When is this going to be "delete []"ed?
      
   int i;
   for(i=0;i<iMenuEntries.size();i++)
   {
      menu_items[i]=iMenuEntries[i]->Entry();   
      delete iMenuEntries[i];
   }

   accel_group = gtk_accel_group_new ();

   /* This function initializes the item factory.
     Param 1: The type of menu - can be GTK_TYPE_MENU_BAR, GTK_TYPE_MENU,
              or GTK_TYPE_OPTION_MENU.
     Param 2: The path of the menu.
     Param 3: A pointer to a gtk_accel_group.  The item factory sets up
              the accelerator table while generating menus.
   */

   item_factory = gtk_item_factory_new (GTK_TYPE_MENU_BAR, "<main>", 
				       accel_group);

   /* This function generates the menu items. Pass the item factory,
     the number of items in the array, the array itself, and any
     callback data for the the menu items. */
  
   gtk_item_factory_create_items (item_factory, nmenu_items, menu_items, NULL);

   /* Attach the new accelerator group to the window. */
   gtk_window_add_accel_group (GTK_WINDOW (window), accel_group);

   if (menubar)
      /* Finally, return the actual menu bar created by the item factory. */ 
      *menubar = gtk_item_factory_get_widget (item_factory, "<main>");
}


#define FONT "-misc-fixed-medium-r-normal--12-200-75-75-c-100-iso8859-1"

extern char      **environ;
static char      **env;
static char      **env_copy;
static int         winid_pos;

/*
   A few globals needed elsewhere. Including the environment setup.
*/

static void
child_died_event (ZvtTerm *iTerminal)
{
   gtk_exit(0);
}

/*
   This is later setup as the child_died signal handler. In the case of a
   iTerminalinal we just iTerminalinate the whole application when this happens.
*/

static void
title_changed_event (ZvtTerm *iTerminal, VTTITLE_TYPE type, char *newtitle)
{
  switch(type)
    {
    case VTTITLE_WINDOW:
    case VTTITLE_WINDOWICON:
//      gtk_window_set_title (GTK_WINDOW (iWindow), newtitle);
      break;
    default:
      break;
    }
}

/*
   Another signal that may be generated by the widget is the
   title_changed signal. This signal is generated when the xiTerminal escape
   sequence to set a title is processed. Here we setup a simple handler
   that will change the iWindow title when this sequence is caught. By
   default no action is taken.
*/

static void
set_hints (GtkWidget *widget)
{
        ZvtTerm *iTerminal;
        GdkGeometry hints;
        GtkWidget *app;

        g_assert (widget != NULL);
        iTerminal = ZVT_TERM (widget);

        app = gtk_widget_get_toplevel(widget);
        g_assert (app != NULL);

#define PADDING 2
        hints.base_width = (GTK_WIDGET (iTerminal)->style->klass->xthickness * 2) + PADDING;
        hints.base_height =  (GTK_WIDGET (iTerminal)->style->klass->ythickness * 2);

        hints.width_inc = iTerminal->charwidth;
        hints.height_inc = iTerminal->charheight;
        hints.min_width = hints.base_width + hints.width_inc;
        hints.min_height = hints.base_height + hints.height_inc;

        gtk_window_set_geometry_hints(GTK_WINDOW(app),
                                      GTK_WIDGET(iTerminal),
                                      &hints,
                                      (GdkWindowHints)(GDK_HINT_RESIZE_INC|GDK_HINT_MIN_SIZE|GDK_HINT_BASE_SIZE));
}

/*
   Here we have the same code as explained in the section called Window
   Hints, for setting appropriate iWindow-resize hints.

   The main program follows:
*/

void  Calterm::start(int *argcP,char ***argvP)
{
  int i, c, cmdindex;
  char buffer[60], **p;
  struct passwd *pw;
  GtkWidget /* *iTerminal now global for first fnct , */ *hbox,*vbox, *scrollbar;

  int login_shell = 0;
  cmdindex = 0;

/*
   The next section sets up the environment as explained elsewhere. We
   setup the TERM environment and drop the iTerminalinal size environment, and
   add the COLORTERM value, to enable colour output for specific
   applications.

   We also setup a placeholder for the WINDOWID environment when it is
   known later on. This is used by some X-aware iTerminalinal applications to
   manipulate the iTerminalinal iWindow.
*/
   
  /* set up iTerminalinal environment */
  env = environ;

  for (p = env; *p; p++);
    i = p - env;
  env_copy = (char **) g_malloc (sizeof (char *) * (i + 3));
  for (i = 0, p = env; *p; p++) {
    if (strncmp (*p, "TERM=", 5) == 0) {
      env_copy [i++] = "TERM=xterm";
    } else if ((strncmp (*p, "COLUMNS=", 8) == 0) ||
               (strncmp (*p, "LINES=", 6) == 0)) {
      continue;
    } else {
      env_copy [i++] = *p;
    }
  }

  env_copy [i++] = "COLORTERM=zvterm";
  winid_pos = i++;
  env_copy [winid_pos] = "TEST";
  env_copy [i] = NULL;

   int x=0;
   char **blub;

  gtk_init(argcP, argvP);

/*
   Process the command line options. Note the saving of the position of
   the '-e' option and the iTerminalination of processing once it has been
   found. This is a compatability with the 'xiTerminal' '-e' command line
   option.
*/

  /* process arguments */
/*  while ( (cmdindex==0) && (c=getopt(argc, argv, "le:s:rh")) != EOF ) {
    switch(c)  {
      case 'e':
      cmdindex = optind-1;      
      // index of argv array to pass to exec 
      break;

      case 's':
      scrollbacklines = atoi(optarg);
      break;

      case 'l':
      login_shell = 1;
      break;

      case 'r':
      scrollpos = RIGHT;
      break;

      case '?':
      case 'h':
      default:
      fprintf(stderr, "Usage: rasiTerminal [-sNN] [-l] [-r] [-e command args]\n");
      exit(1);
      break;
    }
  }
*/
/*
   Now we are ready to create our application widgets ...
*/

  /* Create widgets and set options */
  iWindow = gtk_window_new (GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title (GTK_WINDOW (iWindow), iWindowName.c_str());
  gtk_window_set_wmclass (GTK_WINDOW (iWindow), iWindowName.c_str(), iWindowName.c_str());
  gtk_widget_realize (iWindow);

/* adding vbox (SR) */
  vbox = gtk_vbox_new (FALSE, 0);
  gtk_box_set_spacing (GTK_BOX (vbox), 2);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 2);
  gtk_container_add (GTK_CONTAINER (iWindow), vbox);
  gtk_widget_show (vbox);
/* end adding */

/* Here I added (SR) */
{
  GtkWidget *menubar;
         
    get_main_menu (iWindow, &menubar,iMenuEntries);
    gtk_box_pack_start (GTK_BOX (vbox), menubar, FALSE, TRUE, 0);
    gtk_widget_show (menubar);
}
/*  End Addition (SR) */



  /* create hbox */
  
  hbox = gtk_hbox_new (FALSE, 0);
  gtk_box_set_spacing (GTK_BOX (hbox), 2);
  gtk_container_set_border_width (GTK_CONTAINER (hbox), 2);
  gtk_container_add (GTK_CONTAINER (vbox), hbox); /* changed iWindow to vbox */
  gtk_widget_show (hbox);

/*
           Here we create the iTerminalinal widget, and setup a number of
   options, as covered in the section called Terminal properties in the
   chapter called ZVT Terminal Widget. We setup a basic set of options
   which are much the same as the defaults (but i also like a blinking
   cursor!).
*/
   
  /* create iTerminalinal */
  iTerminal = zvt_term_new_with_size(80,24);
  zvt_term_set_font_name(ZVT_TERM (iTerminal), FONT);
  zvt_term_set_blink (ZVT_TERM (iTerminal), iCursorBlink);
  zvt_term_set_bell (ZVT_TERM (iTerminal), TRUE);
  zvt_term_set_scrollback(ZVT_TERM (iTerminal), iScrollBackLines);
  zvt_term_set_scroll_on_keystroke (ZVT_TERM (iTerminal), TRUE);
  zvt_term_set_scroll_on_output (ZVT_TERM (iTerminal), FALSE);
  zvt_term_set_background (ZVT_TERM (iTerminal), NULL, 0, 0);
  zvt_term_set_wordclass (ZVT_TERM (iTerminal), (unsigned char *)"-A-Za-z0-9/_:.,?+%=");


/*
           Attatch the signal handlers we defined above. We also attach
   the destroy signal to the exit handling event - if the iWindow is
   closed by the iWindow manager then this catches that event. Otherwise
   the close button doesn't work!
*/

  gtk_signal_connect (GTK_OBJECT (iTerminal),
                      "child_died",
                      GTK_SIGNAL_FUNC (child_died_event),
                      NULL);

  gtk_signal_connect (GTK_OBJECT (iTerminal),
                      "destroy",
                      GTK_SIGNAL_FUNC (child_died_event),
                      NULL);

  gtk_signal_connect (GTK_OBJECT (iTerminal),
                      "title_changed",
                      GTK_SIGNAL_FUNC (title_changed_event),
                      NULL);

  gtk_signal_connect_after (GTK_OBJECT (iTerminal),
                            "realize",
                            GTK_SIGNAL_FUNC (set_hints),
                            iTerminal);

  gtk_widget_show (iTerminal);


/*
           And here we show how the scrollbar is attached to the iTerminal
   adjustment designed for this purpose, and how the scrollbar has its
   focus disabled. We also give the user the option for a left or right
   scrollbar.

   Although many perfer the iWindows/motif convention of a right-mounted
   scrollbar, but a left-mounted scrollbar should be an option if
   possible, as it is often a much more practical position to have it in.
   Particularly if there are overlapping iWindows, or iWindows partly
   off-screen.
*/   
   
  /* scrollbar */
  scrollbar =
    gtk_vscrollbar_new (GTK_ADJUSTMENT (ZVT_TERM (iTerminal)->adjustment));
  GTK_WIDGET_UNSET_FLAGS (scrollbar, GTK_CAN_FOCUS);
  if (!iScrollPosRight) {
    gtk_box_pack_start (GTK_BOX (hbox), scrollbar, FALSE, TRUE, 0);
    gtk_box_pack_start (GTK_BOX (hbox), iTerminal, 1, 1, 0);
  } else {
    gtk_box_pack_start (GTK_BOX (hbox), iTerminal, 1, 1, 0);
    gtk_box_pack_start (GTK_BOX (hbox), scrollbar, FALSE, TRUE, 0);
  }
  gtk_widget_show (scrollbar);



  /* show them all! */
  gtk_widget_show (iWindow);

/*
           Here we show how the sub-shell is created using
   zvt_term_forkpty(). We also give the user the option of logging the
   session in utmp/wtmp. This probably isn't that necessary for a
   non-shell application, but might be useful.

   It also shows how either a shell is executed, or the command given on
   the command line is executed. And how the login shell arguments are
   processed. Again this is not normally required for a non-shell
   application.
*/   
   
  /* fork the shell/program */
  switch (zvt_term_forkpty(ZVT_TERM (iTerminal), ZVT_TERM_DO_UTMP_LOG |  ZVT_TERM_DO_WTMP_LOG)) {
    case -1:
    perror("ERROR: unable to fork:");
    exit(1);
    break;

    case 0:
    if (cmdindex) {
      environ = env_copy;
//      execvp(argv[cmdindex], &argv[cmdindex]);
    } else {
      GString *shell, *name;


      shell=g_string_new(iClientPath.c_str());
      name=g_string_new("rascal");

      /* get shell from passwd */
/*
      pw = getpwuid(getuid());
      if (pw) {
        shell = g_string_new(pw->pw_shell);
        if (login_shell)
          name = g_string_new("-");
        else
          name = g_string_new("");

        g_string_append(name, strrchr(pw->pw_shell, '/'));
      } else {
        shell = g_string_new("/bin/sh");
        if (login_shell)
          name = g_string_new("-sh");
        else
          name = g_string_new("sh");
      }
*/
/*      do {
         system(shell->str);
         system("clear");*/

         int i,arguments_in_string=0;
         for(i=0;i<iClientCommandLineOptions.length()
                   && iClientCommandLineOptions[i]==' ';i++);
                   // skipping leading spaces
         if(i<iClientCommandLineOptions.length())
            arguments_in_string=1;
         
         for(;i+1<iClientCommandLineOptions.length();i++)
            if(iClientCommandLineOptions[i]==' ' &&
               iClientCommandLineOptions[i+1]!=' ')
               arguments_in_string++;
         
         string *options=new string[arguments_in_string];
         const char **options_pointers=new const char *[arguments_in_string+2];
         
         for(i=0;i<iClientCommandLineOptions.length()
                   && iClientCommandLineOptions[i]==' ';i++);
                   // skipping leading spaces

         int j;
         options_pointers[0]=name->str;
         for(j=0;j<arguments_in_string;j++)
         {
            for(;i<iClientCommandLineOptions.length()
               && iClientCommandLineOptions[i]!=' ';i++)
               options[j]+=iClientCommandLineOptions[i];
            for(;i<iClientCommandLineOptions.length()
               && iClientCommandLineOptions[i]==' ';i++);
            options_pointers[j+1]=options[j].c_str(); 
         }
         options_pointers[j+1]=NULL;
         
         execve (shell->str, (char *const *)options_pointers, env_copy);
/*      } while(1);
      exit(0);*/
      perror ("Could not exec\n");
      _exit (127);
    }
    perror("ERROR: Cannot exec command:");
    exit(1);

    default:
    break;
  }

/*
   And thats it! We're ready to go into the main processing loop!
*/

  /* main loop */
  gtk_main ();
  gtk_exit(0);
  return;
}


syntax highlighted by Code2HTML, v. 0.9.1