/*
   Copyright (C) 1998,1999,2000,2001
   T. Scott Dattalo and Ralf Forsberg

This file is part of gpsim.

gpsim 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, or (at your option)
any later version.

gpsim 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 gpsim; see the file COPYING.  If not, write to
the Free Software Foundation, 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.  */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#include "../config.h"
#ifdef HAVE_GUI

#include <unistd.h>
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include <gdk/gdkkeysyms.h>
#include <glib.h>
#include <string.h>


//#define DEBUG

#include "gui.h"
#include "gui_statusbar.h"
#include "../src/processor.h"


//========================================================================

class StatusBarXREF : public CrossReferenceToGUI
{
public:

  void Update(int new_value)
  {

    StatusBar_Window *sbw;

    sbw  = (StatusBar_Window *) (parent_window);
    sbw->Update();

  }
};


//------------------------------------------------------------------------
static void LabeledEntry_callback(GtkWidget *entry, LabeledEntry *le)
{
  const char *text;
  unsigned int value;
  char *bad_position;

  if(!gpGuiProcessor || !gpGuiProcessor->cpu || !le || !le->entry)
    return;

  text=gtk_entry_get_text (GTK_ENTRY (le->entry));
    
  value = strtoul(text, &bad_position, 16);
  if( strlen(bad_position) )
    return;  /* string contains an invalid number */

  le->put_value(value);

  if(le->parent)
    le->parent->Update();

  return;
}

//========================================================================
EntryWidget::EntryWidget()
{
  entry = 0;
  parent = 0;
}

void EntryWidget::SetEntryWidth(int string_width)
{
  if(entry)
    gtk_entry_set_width_chars(GTK_ENTRY (entry), string_width);
}

void EntryWidget::AssignParent(GUI_Object *new_parent)
{
  parent = new_parent;
}


void EntryWidget::Create(bool isEditable)
{
  entry = gtk_entry_new ();
  if(!isEditable)
    gtk_entry_set_editable(GTK_ENTRY(entry),0);
  gtk_widget_show (entry);
}

//========================================================================


LabeledEntry::LabeledEntry(void)
{
  label = 0;
}
#if GTK_MAJOR_VERSION < 2
#define gtk_style_get_font(X) X->font
#endif

void LabeledEntry::Create(GtkWidget *box,
			  char *clabel, 
			  int string_width,
			  bool isEditable=true)
{

  label = (GtkWidget *)gtk_label_new (clabel);
    
  gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5);
  gtk_widget_set_usize (label, 0, 15);
  gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
  gtk_widget_show (label);

  EntryWidget::Create(isEditable);

  gtk_entry_set_text (GTK_ENTRY (entry), "----");

  SetEntryWidth(string_width);

  gtk_box_pack_start (GTK_BOX (box), entry, FALSE, FALSE, 0);



}

void LabeledEntry::Update(void)
{
}

void LabeledEntry::put_value(unsigned int new_value)
{

}

void LabeledEntry::NewLabel(char *clabel)
{
  if(label)
    gtk_label_set_text(GTK_LABEL(label),clabel);

}
//------------------------------------------------------------------------
RegisterLabeledEntry::RegisterLabeledEntry(GtkWidget *box,
					   Register *new_reg,
					   bool isEditable=true) 
 : LabeledEntry()
{
  reg = new_reg;

  if(reg) {
    pCellFormat = new char[10];
    sprintf(pCellFormat,"0x%%0%dx",reg->register_size()*2);

    label = (GtkWidget *)gtk_label_new ((char *) reg->name().c_str());
    
    gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5);
    gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
    gtk_widget_show (label);

    entry = gtk_entry_new ();
    SetEntryWidth(2 + reg->register_size()*2);
    Update();

    gtk_box_pack_start (GTK_BOX (box), entry, FALSE, FALSE, 0);

    gtk_widget_show (entry);

    if(!isEditable)
      gtk_entry_set_editable(GTK_ENTRY(entry),0);

    gtk_signal_connect(GTK_OBJECT(entry), "activate",
		       GTK_SIGNAL_FUNC(LabeledEntry_callback),
		       this);


  } else
    pCellFormat=0;

}

void RegisterLabeledEntry::put_value(unsigned int new_value)
{
  if(reg)
    reg->put_value(new_value);
}

void RegisterLabeledEntry::Update(void)
{
  char buffer[32];

  if(reg && pCellFormat) {

    unsigned int value = reg->get_value();

    sprintf(buffer,pCellFormat,value);

    gtk_entry_set_text (GTK_ENTRY (entry), buffer);

  }
}
void RegisterLabeledEntry::AssignRegister(Register *new_reg)
{
  reg = new_reg;

  if(pCellFormat)
    delete pCellFormat;

  if(reg) {
    pCellFormat = new char[10];
    sprintf(pCellFormat,"0x%%0%dx",reg->register_size()*2);

    NewLabel((char *) reg->name().c_str());
    SetEntryWidth(2 + reg->register_size()*2);
  }
}

//------------------------------------------------------------------------
void StatusBar_Window::Update(void)
{

  if (!created)
      return;

  //update the displayed values

  if(!gp || !gp->cpu)
    return;


  list<RegisterLabeledEntry *>::iterator iRLE;

  for(iRLE = entries.begin();
      iRLE != entries.end();
      ++iRLE)
    (*iRLE)->Update();
}


/*
 * CreateStatusBar
 *
 * Create the status bar at the bottom of the window
 * 
 *
 * vbox_main - The box to which we will append.
 *
 */ 



void StatusBar_Window::Create(GtkWidget *vbox_main)
{

  if(created)
    return;
  Dprintf((" %s\n",__FUNCTION__));

  /* --- Put up h-box --- */
  gtk_box_pack_end (GTK_BOX (vbox_main), hbox, FALSE, FALSE, 0);
  gtk_widget_show (hbox);

  created=1;


}

/*  NewProcessor
 *
 */

void StatusBar_Window::NewProcessor(GUI_Processor *_gp, MemoryAccess *_ma)
{


  if(!_gp  || !_gp->cpu || !_ma)
    return;

  if(ma)
    return;

  gp = _gp;
  ma = _ma;
  Dprintf((" %s\n",__FUNCTION__));

  list<Register *>::iterator iReg;

  for(iReg = ma->SpecialRegisters.begin();
      iReg != ma->SpecialRegisters.end();
      ++iReg)
    entries.push_back(new RegisterLabeledEntry(hbox, *iReg));


  /* Now create a cross-reference link that the simulator can use to
   * send information back to the gui
   */

  Program_Counter * pPC;
  ProgramMemoryAccess* pPMA;
  pPMA = dynamic_cast<ProgramMemoryAccess*>(ma);

  if(gp->cpu && gp->cpu->pc) {
    pPC = pPMA == NULL ? gp->cpu->pc : pPMA->GetProgramCounter();
    StatusBarXREF *cross_reference;

    cross_reference = new StatusBarXREF();
    cross_reference->parent_window_type =   WT_status_bar;
    cross_reference->parent_window = (gpointer) this;
    cross_reference->data = (gpointer) this;
  
    pPC->add_xref((gpointer) cross_reference);

  }

  Update();

}

StatusBar_Window::StatusBar_Window(void)
{
  gp = 0;
  ma = 0;

  cpu_cycles = 0;
  time = 0;
  created = false;

  /* --- Create h-box for holding the status line --- */
  hbox = gtk_hbox_new (FALSE, 0);

}












#if 0 
class CyclesLabeledEntry : public LabeledEntry {
public:

  CyclesLabeledEntry();
  virtual void Update(void);
};


typedef enum {
  MENU_TIME_USECONDS,
  MENU_TIME_MSECONDS,
  MENU_TIME_SECONDS,
  MENU_TIME_HHMMSS
} menu_id;

typedef struct _menu_item {
  char *name;
  menu_id id;
} menu_item;

class TimeLabeledEntry : public LabeledEntry 
{
public:
  TimeLabeledEntry();
  virtual void Update(void);
  GtkWidget *build_menu();

  void set_time_format(menu_id id)
  {
    time_format = id;
  }

  GtkWidget *menu;
  menu_id time_format;

};

struct popup_data {
  TimeLabeledEntry *tle;
  menu_id id;
};


//========================================================================

class StatusBarXREF : public CrossReferenceToGUI
{
public:

  void Update(int new_value)
  {

    StatusBar_Window *sbw;

    sbw  = (StatusBar_Window *) (parent_window);
    sbw->Update();

  }

  /**
    * Remove()
    * Override to circumvent the default behavior
    */
  void Remove(void) {}
};


//------------------------------------------------------------------------
static void LabeledEntry_callback(GtkWidget *entry, LabeledEntry *le)
{
  const char *text;
  unsigned int value;
  char *bad_position;

  if(!gp || !gp->cpu || !le || !le->entry)
    return;

  text=gtk_entry_get_text (GTK_ENTRY (le->entry));
    
  value = strtoul(text, &bad_position, 16);
  if( strlen(bad_position) )
    return;  /* string contains an invalid number */

  le->put_value(value);

  if(le->sbw)
    le->sbw->Update();

  return;
}

//========================================================================


LabeledEntry::LabeledEntry(void)
{
  label = 0;
  entry = 0;
  sbw = 0;
}

LabeledEntry::~LabeledEntry() {
  gtk_widget_destroy(entry);
  gtk_widget_destroy(label);
}

#if GTK_MAJOR_VERSION < 2
#define gtk_style_get_font(X) X->font
#endif

void LabeledEntry::Create(GtkWidget *box,
			  char *clabel, 
			  int string_width,
			  bool isEditable=true)
{
  // Entry to the right
  entry = gtk_entry_new ();
  gtk_entry_set_text (GTK_ENTRY (entry), "----");
  SetEntryWidth(string_width);
  gtk_box_pack_end (GTK_BOX (box), entry, FALSE, FALSE, 0);
  gtk_widget_show (entry);

  if(!isEditable)
    gtk_entry_set_editable(GTK_ENTRY(entry),0);
  // Lable to the left of the entry
  label = (GtkWidget *)gtk_label_new (clabel);
    
  gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5);
  gtk_widget_set_usize (label, 0, 15);
  gtk_box_pack_end (GTK_BOX (box), label, FALSE, FALSE, 0);
  gtk_widget_show (label);
}

void LabeledEntry::SetEntryWidth(int string_width)
{
  if(entry)
    gtk_widget_set_usize (entry,
			  string_width * 
			  gdk_string_width (gtk_style_get_font(entry->style), "9") + 6,
			  -1);
}

void LabeledEntry::AssignParent(StatusBar_Window *new_sbw)
{
  sbw = new_sbw;
}

void LabeledEntry::Update(void)
{
  //  if(sbw)
  //    sbw->Update();

}

void LabeledEntry::put_value(unsigned int new_value)
{

}

void LabeledEntry::NewLabel(char *clabel)
{
  if(label)
    gtk_label_set_text(GTK_LABEL(label),clabel);

}
//------------------------------------------------------------------------
RegisterLabeledEntry::RegisterLabeledEntry(GtkWidget *box,
					   Register *new_reg,
					   bool isEditable) 
 : LabeledEntry()
{
  reg = new_reg;

  if(reg) {

    pCellFormat = new char[10];
    sprintf(pCellFormat,"0x%%0%dx",reg->register_size()*2);

    label = (GtkWidget *)gtk_label_new ((char *) reg->name().c_str());
    
    gtk_misc_set_alignment (GTK_MISC (label), 1.0, 0.5);
    //    gtk_widget_set_usize (label, 0, 15);
    gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
    gtk_widget_show (label);

    entry = gtk_entry_new ();

    // gtk_entry_set_text (GTK_ENTRY (entry), "----");

    SetEntryWidth(2 + reg->register_size()*2);
    Update();

    //SetEntryWidth(string_width);

    gtk_box_pack_start (GTK_BOX (box), entry, FALSE, FALSE, 0);

    gtk_widget_show (entry);

    if(!isEditable)
      gtk_entry_set_editable(GTK_ENTRY(entry),0);

    gtk_signal_connect(GTK_OBJECT(entry), "activate",
		       GTK_SIGNAL_FUNC(LabeledEntry_callback),
		       this);


  } else
    pCellFormat=0;

}

RegisterLabeledEntry::~RegisterLabeledEntry() {
  delete pCellFormat;
}

void RegisterLabeledEntry::put_value(unsigned int new_value)
{
  if(reg)
    reg->put_value(new_value);
}

void RegisterLabeledEntry::Update(void)
{
  char buffer[32];

  if(reg && pCellFormat) {

    unsigned int value = reg->get_value();

    sprintf(buffer,pCellFormat,value);

    gtk_entry_set_text (GTK_ENTRY (entry), buffer);

  }
}
void RegisterLabeledEntry::AssignRegister(Register *new_reg)
{
  reg = new_reg;

  if(pCellFormat)
    delete pCellFormat;

  if(reg) {
    pCellFormat = new char[10];
    sprintf(pCellFormat,"0x%%0%dx",reg->register_size()*2);

    NewLabel((char *) reg->name().c_str());
    SetEntryWidth(2 + reg->register_size()*2);
  }
}

//------------------------------------------------------------------------
CyclesLabeledEntry::CyclesLabeledEntry()
{
}

void CyclesLabeledEntry::Update(void)
{
  char buffer[32];
  sprintf(buffer,"0x%016" PRINTF_INT64_MODIFIER "x",get_cycles().value);
  gtk_entry_set_text (GTK_ENTRY (entry), buffer);
}

TimeLabeledEntry::TimeLabeledEntry()
{
  time_format = MENU_TIME_USECONDS;
  menu = 0;
}

void TimeLabeledEntry::Update()
{
  char buffer[32];

  double time_db = gp->cpu->get_InstPeriod() * get_cycles().value;


  switch(time_format) {
  case MENU_TIME_USECONDS:
    time_db *= 1e6;
    sprintf(buffer,"%19.2f us",time_db);
    break;
  
  case MENU_TIME_MSECONDS:
    time_db *= 1e3;
    sprintf(buffer,"%19.3f ms",time_db);
    break;

  case MENU_TIME_HHMMSS:
    {
      double v=time_db;
      int hh=(int)(v/3600),mm,ss,cc;
      v-=hh*3600.0;
      mm=(int)(v/60);
      v-=mm*60.0;
      ss=(int)v;
      cc=(int)(v*100.0+0.5);
      sprintf(buffer,"    %02d:%02d:%02d.%02d",hh,mm,ss,cc);
    }
    break;

  default:
    sprintf(buffer,"%19.3f s",time_db);
  }

  gtk_entry_set_text (GTK_ENTRY (entry), buffer);

}

//----------------------------------------
// called when user has selected a menu item
static void
popup_activated(GtkWidget *widget, gpointer data)
{
  if(!widget || !data)
    return;
    
  popup_data *pd = (popup_data *)data;
  if(pd->tle) {
    pd->tle->set_time_format(pd->id);
    pd->tle->Update();
  }
}

GtkWidget * TimeLabeledEntry::build_menu(void)
{
  static menu_item menu_items[] = {
    {"Micro seconds", MENU_TIME_USECONDS},
    {"Mili seconds", MENU_TIME_MSECONDS},
    {"Seconds", MENU_TIME_SECONDS},
    {"HH:MM:SS.CC", MENU_TIME_HHMMSS}
  };


  GtkWidget *item;
  unsigned int i;

  menu=gtk_menu_new();

  item = gtk_tearoff_menu_item_new ();
  gtk_menu_append (GTK_MENU (menu), item);
  gtk_widget_show (item);
  
  
  for (i=0; i < (sizeof(menu_items)/sizeof(menu_items[0])) ; i++){
    item=gtk_menu_item_new_with_label(menu_items[i].name);

    popup_data *pd = new popup_data;
    pd->tle = this;
    pd->id = menu_items[i].id;

    gtk_signal_connect(GTK_OBJECT(item),"activate",
		       (GtkSignalFunc) popup_activated,
		       pd);

    gtk_widget_show(item);
    gtk_menu_append(GTK_MENU(menu),item);
  }
  
  return menu;
}

//------------------------------------------------------------------------
void StatusBar_Window::Update(void)
{

  if( !created)
      return;

  //update the displayed values

  if(!gp || !gp->cpu)
    return;


  RegisterLabeledList::iterator iRLE;

  for(iRLE = entries.begin();
      iRLE != entries.end();
      ++iRLE)

    (*iRLE)->Update();

  cpu_cycles->Update();
  time->Update();
}


// button press handler
static gint
do_popup(GtkWidget *widget, GdkEventButton *event, TimeLabeledEntry *tle)
{
    if(!widget || !event || !tle)
      return 0;
  
    if( (event->type == GDK_BUTTON_PRESS) &&  (event->button == 3) )
    {
	gtk_menu_popup(GTK_MENU(tle->menu), 0, 0, 0, 0,
			   3, event->time);
	// It looks like we need it to avoid a selection in the entry.
	// For this we tell the entry to stop reporting this event.
	gtk_signal_emit_stop_by_name(GTK_OBJECT(tle->entry),"button_press_event");
    }
    return FALSE;
}

/*
 * CreateStatusBar
 *
 * Create the status bar at the bottom of the window
 * 
 *
 * vbox_main - The box to which we will append.
 *
 */ 



void StatusBar_Window::Create(GtkWidget *vbox_main)
{
  if(created)
    return;

  Dprintf((" %s",__FUNCTION__));

  /* --- Put up h-box --- */
  gtk_box_pack_end (GTK_BOX (vbox_main), hbox, FALSE, FALSE, 0);
  gtk_widget_show (hbox);

  created=1;
}

/*  NewProcessor
 *
 */

void StatusBar_Window::NewProcessor(GUI_Processor *_gp, MemoryAccess *_ma)
{


  if(!_gp  || !_gp->cpu || !_ma || !hbox)
    return;

  gp = _gp;
  ma = _ma;
  Dprintf((" %s",__FUNCTION__));

  if(cpu_cycles == NULL) {
    cpu_cycles = new CyclesLabeledEntry();
    cpu_cycles->Create(hbox,"Cycles:", 18,false);
  }
  if(time == NULL) {
    TimeLabeledEntry *tle = new TimeLabeledEntry();
    time = tle;
    time->Create(hbox,"Time:", 22,false);
    /* create popupmenu */
    tle->build_menu();
    gtk_signal_connect(GTK_OBJECT(time->entry),
		      "button_press_event",
		      (GtkSignalFunc) do_popup,
		      tle);

    /* Now create a cross-reference link that the simulator can use to
    * send information back to the gui
    */

    if(gp->cpu && gp->cpu->pc) {
      StatusBarXREF *cross_reference;

      cross_reference = new StatusBarXREF();
      cross_reference->parent_window_type =   WT_status_bar;
      cross_reference->parent_window = (gpointer) this;
      cross_reference->data = (gpointer) this;
    
      gp->cpu->pc->add_xref((gpointer) cross_reference);

    }
  }

  list<Register *>::iterator iReg;
  entries.clear();

  for(iReg = ma->SpecialRegisters.begin();
      iReg != ma->SpecialRegisters.end();
      ++iReg) {

    //cout << " Adding " << ((*iReg)->showType()) << " to status bar\n";
    entries.push_back(hbox, *iReg);
  }


  Update();

}

StatusBar_Window::StatusBar_Window(void)
{
  gp = 0;
  ma = 0;

  cpu_cycles = 0;
  time = 0;
  created = false;

  time = NULL;
  cpu_cycles = NULL;

  /* --- Create h-box for holding the status line --- */
  hbox = gtk_hbox_new (FALSE, 0);

}
#endif

#endif // HAVE_GUI


syntax highlighted by Code2HTML, v. 0.9.1