/*
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>
#include <assert.h>
#include "../src/interface.h"
#include "../src/cmd_gpsim.h"
#include "gui.h"
#include "preferences.h"
#include "gui_register.h"
#include "gui_regwin.h"
#include "gui_watch.h"
#define NAMECOL 0
#define DECIMALCOL 2
#define HEXCOL 3
#define ASCIICOL 4
#define BITCOL 5
#define LASTCOL BITCOL
static char *watch_titles[]={"name","address","dec","hex","ascii","bits"};
#define COLUMNS sizeof(watch_titles)/sizeof(char*)
class ColumnData
{
public:
int column;
int isVisible;
bool bIsValid;
Watch_Window *ww;
void SetVisibility(bool bVisibility);
void SetValidity(bool );
bool isValid();
void Show();
ColumnData();
} coldata[COLUMNS];
static void select_columns(Watch_Window *ww, GtkWidget *clist);
typedef enum {
MENU_REMOVE,
MENU_SET_VALUE,
MENU_BREAK_CLEAR,
MENU_BREAK_READ,
MENU_BREAK_WRITE,
MENU_BREAK_READ_VALUE,
MENU_BREAK_WRITE_VALUE,
MENU_COLUMNS,
} menu_id;
typedef struct _menu_item {
char *name;
menu_id id;
GtkWidget *item;
} menu_item;
static menu_item menu_items[] = {
{"Remove watch", MENU_REMOVE},
{"Set value...", MENU_SET_VALUE},
{"Clear breakpoints", MENU_BREAK_CLEAR},
{"Set break on read", MENU_BREAK_READ},
{"Set break on write", MENU_BREAK_WRITE},
{"Set break on read value...", MENU_BREAK_READ_VALUE},
{"Set break on write value...", MENU_BREAK_WRITE_VALUE},
{"Columns...", MENU_COLUMNS},
};
// Used only in popup menus
Watch_Window *popup_ww;
//========================================================================
class WatchWindowXREF : public CrossReferenceToGUI
{
public:
void Update(int new_value)
{
Watch_Window *ww = (Watch_Window *) (parent_window);
if (ww)
ww->UpdateWatch((WatchEntry *)data);
}
};
//========================================================================
WatchEntry::WatchEntry()
: cpu(0)
{
}
//========================================================================
ColumnData::ColumnData()
: isVisible(1), bIsValid(true), ww(0)
{
}
void ColumnData::SetVisibility(bool bVisibility)
{
isVisible = bVisibility ? 1 : 0;
Show();
}
void ColumnData::Show()
{
if (ww) {
int show = isVisible & (bIsValid ? 1 : 0);
gtk_clist_set_column_visibility(GTK_CLIST(ww->watch_clist),column,show);
config_set_variable(ww->name(), watch_titles[column],show);
}
}
bool ColumnData::isValid()
{
return bIsValid;
}
void ColumnData::SetValidity(bool newValid)
{
bIsValid = newValid;
}
//========================================================================
void Watch_Window::ClearWatch(WatchEntry *entry)
{
gtk_clist_remove(GTK_CLIST(watch_clist),current_row);
watches=g_list_remove(watches,entry);
entry->Clear_xref();
free(entry);
}
void Watch_Window::UpdateMenus(void)
{
GtkWidget *item;
WatchEntry *entry;
unsigned int i;
for (i=0; i < (sizeof(menu_items)/sizeof(menu_items[0])) ; i++) {
item=menu_items[i].item;
if(menu_items[i].id!=MENU_COLUMNS) {
entry = (WatchEntry*) gtk_clist_get_row_data(GTK_CLIST(watch_clist),current_row);
if(menu_items[i].id!=MENU_COLUMNS &&
(entry==0 ||
(entry->type==REGISTER_EEPROM && menu_items[i].id==MENU_BREAK_CLEAR)||
(entry->type==REGISTER_EEPROM && menu_items[i].id==MENU_BREAK_READ)||
(entry->type==REGISTER_EEPROM && menu_items[i].id==MENU_BREAK_WRITE)||
(entry->type==REGISTER_EEPROM && menu_items[i].id==MENU_BREAK_READ_VALUE)||
(entry->type==REGISTER_EEPROM && menu_items[i].id==MENU_BREAK_WRITE_VALUE)
))
gtk_widget_set_sensitive (item, FALSE);
else
gtk_widget_set_sensitive (item, TRUE);
}
}
}
int Watch_Window::set_config(void) {
int iRet = GUI_Object::set_config();
WriteSymbolList();
return iRet;
}
void Watch_Window::WriteSymbolList()
{
// delete previous list
DeleteSymbolList();
// write the current list
WatchEntry *entry;
guint uSize = g_list_length(watches);
char cwv[100];
char *vname;
for (guint i = 0; i<uSize; i++) {
snprintf(cwv, sizeof(cwv), "WV%d",i);
vname = 0;
entry = (WatchEntry*) g_list_nth_data(watches, i);
if (entry && entry->pRegSymbol)
config_set_string(name(), cwv, entry->pRegSymbol->name().c_str());
}
}
void Watch_Window::DeleteSymbolList() {
int i=0;
char cwv[100];
while (i<1000) {
snprintf(cwv, sizeof(cwv), "WV%d",i++);
if (config_remove(name(), cwv) == 0 ) {
break;
}
}
}
void Watch_Window::ReadSymbolList()
{
// now read symbols watched from a prior simulation session
int i=0;
char cwv[100];
char *vname;
while (i<1000) {
snprintf(cwv, sizeof(cwv), "WV%d",i++);
vname = 0;
if (config_get_string(name(), cwv, &vname) ) {
Value *val = get_symbol_table().find(vname);
if(val != 0) {
Add(val);
}
}
else
break;
}
}
static void unselect_row(GtkCList *clist,
gint row,
gint column,
GdkEvent *event,
Watch_Window *ww)
{
ww->UpdateMenus();
}
// called when user has selected a menu item
static void
popup_activated(GtkWidget *widget, gpointer data)
{
menu_item *item;
WatchEntry *entry;
int value;
if(widget==0 || data==0)
{
printf("Warning popup_activated(%p,%p)\n",widget,data);
return;
}
item = (menu_item *)data;
entry = (WatchEntry*) gtk_clist_get_row_data(GTK_CLIST(popup_ww->watch_clist),popup_ww->current_row);
if(entry==0 && item->id!=MENU_COLUMNS)
return;
if(!entry || !entry->cpu)
return;
switch(item->id)
{
case MENU_REMOVE:
popup_ww->ClearWatch(entry);
//remove_entry(popup_ww,entry);
break;
case MENU_SET_VALUE:
value = gui_get_value("value:");
if(value<0)
break; // Cancel
entry->put_value(value);
break;
case MENU_BREAK_READ:
get_bp().set_read_break(entry->cpu,entry->address);
break;
case MENU_BREAK_WRITE:
get_bp().set_write_break(entry->cpu,entry->address);
break;
case MENU_BREAK_READ_VALUE:
value = gui_get_value("value to read for breakpoint:");
if(value<0)
break; // Cancel
get_bp().set_read_value_break(entry->cpu,entry->address,value);
break;
case MENU_BREAK_WRITE_VALUE:
value = gui_get_value("value to write for breakpoint:");
if(value<0)
break; // Cancel
get_bp().set_write_value_break(entry->cpu,entry->address,value);
break;
case MENU_BREAK_CLEAR:
get_bp().clear_all_register(entry->cpu,entry->address);
break;
case MENU_COLUMNS:
select_columns(popup_ww, popup_ww->watch_clist);
break;
default:
puts("Unhandled menuitem?");
break;
}
}
//------------------------------------------------------------
// call back function to toggle column visibility in the configuration popup
static void set_column(GtkCheckButton *button, ColumnData *coldata)
{
if (coldata)
coldata->SetVisibility(button->toggle_button.active != 0);
}
static void select_columns(Watch_Window *ww, GtkWidget *clist)
{
GtkWidget *dialog=0;
GtkWidget *button;
int i;
dialog = gtk_dialog_new();
gtk_container_set_border_width(GTK_CONTAINER(dialog),30);
gtk_signal_connect_object(GTK_OBJECT(dialog),
"delete_event",GTK_SIGNAL_FUNC(gtk_widget_destroy),GTK_OBJECT(dialog));
for(i=0;i<COLUMNS;i++)
if (coldata[i].isValid()) {
button=gtk_check_button_new_with_label(watch_titles[i]);
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button),coldata[i].isVisible);
gtk_widget_show(button);
gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox),button,FALSE,FALSE,0);
gtk_signal_connect(GTK_OBJECT(button),"clicked",
GTK_SIGNAL_FUNC(set_column),(gpointer)&coldata[i]);
}
button = gtk_button_new_with_label("OK");
gtk_widget_show(button);
gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->action_area), button,
FALSE,FALSE,10);
gtk_signal_connect_object(GTK_OBJECT(button),"clicked",
GTK_SIGNAL_FUNC(gtk_widget_destroy),GTK_OBJECT(dialog));
GTK_WIDGET_SET_FLAGS(button,GTK_CAN_DEFAULT);
gtk_widget_grab_default(button);
gtk_widget_show(dialog);
return;
}
// helper function, called from do_popup
static GtkWidget *
build_menu(GtkWidget *sheet, Watch_Window *ww)
{
GtkWidget *menu;
GtkWidget *item;
unsigned int i;
if(sheet==0 || ww==0)
{
printf("Warning build_menu(%p,%p)\n",sheet,ww);
return 0;
}
popup_ww = ww;
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++){
menu_items[i].item=item=gtk_menu_item_new_with_label(menu_items[i].name);
gtk_signal_connect(GTK_OBJECT(item),"activate",
(GtkSignalFunc) popup_activated,
&menu_items[i]);
gtk_widget_show(item);
gtk_menu_append(GTK_MENU(menu),item);
}
ww->UpdateMenus();
return menu;
}
// button press handler
static gint
do_popup(GtkWidget *widget, GdkEventButton *event, Watch_Window *ww)
{
GtkWidget *popup;
if(widget==0 || event==0 || ww==0)
{
printf("Warning do_popup(%p,%p,%p)\n",widget,event,ww);
return 0;
}
popup=ww->popup_menu;
if( (event->type == GDK_BUTTON_PRESS) && (event->button == 3) )
gtk_menu_popup(GTK_MENU(popup), 0, 0, 0, 0,
3, event->time);
/*
WatchEntry *entry;
if(event->type==GDK_2BUTTON_PRESS &&
event->button==1)
{
int column=ww->current_column;
int row=ww->current_row;
entry = (WatchEntry*) gtk_clist_get_row_data(GTK_CLIST(ww->watch_clist), row);
if(column>=MSBCOL && column<=LSBCOL) {
int value; // , bit;
printf("column %d\n",column);
// Toggle the bit.
value = entry->get_value();
value ^= (1<< (15-(column-MSBCOL)));
entry->put_value(value);
}
}
*/
return 0;
}
static gint
key_press(GtkWidget *widget,
GdkEventKey *key,
gpointer data)
{
WatchEntry *entry;
Watch_Window *ww = (Watch_Window *) data;
if(!ww) return(FALSE);
if(!ww->gp) return(FALSE);
if(!ww->gp->cpu) return(FALSE);
switch(key->keyval) {
case GDK_Delete:
entry = (WatchEntry*) gtk_clist_get_row_data(GTK_CLIST(ww->watch_clist),ww->current_row);
if(entry!=0)
ww->ClearWatch(entry);
break;
}
return TRUE;
}
static gint watch_list_row_selected(GtkCList *watchlist,gint row, gint column,GdkEvent *event, Watch_Window *ww)
{
WatchEntry *entry;
GUI_Processor *gp;
ww->current_row=row;
ww->current_column=column;
gp=ww->gp;
entry = (WatchEntry*) gtk_clist_get_row_data(GTK_CLIST(ww->watch_clist), row);
if(!entry)
return TRUE;
if(entry->type==REGISTER_RAM)
gp->regwin_ram->SelectRegister(entry->address);
else if(entry->type==REGISTER_EEPROM)
gp->regwin_eeprom->SelectRegister(entry->address);
ww->UpdateMenus();
return 0;
}
static void watch_click_column(GtkCList *clist, int column)
{
static int last_col=-1;
static GtkSortType last_sort_type=GTK_SORT_DESCENDING;
if(last_col==-1)
last_col=column;
if(last_col == column)
{
if(last_sort_type==GTK_SORT_DESCENDING)
{
gtk_clist_set_sort_type(clist,GTK_SORT_ASCENDING);
last_sort_type=GTK_SORT_ASCENDING;
}
else
{
gtk_clist_set_sort_type(clist,GTK_SORT_DESCENDING);
last_sort_type=GTK_SORT_DESCENDING;
}
}
gtk_clist_set_sort_column(clist,column);
gtk_clist_sort(clist);
last_col=column;
}
static int delete_event(GtkWidget *widget,
GdkEvent *event,
Watch_Window *ww)
{
ww->ChangeView(VIEW_HIDE);
return TRUE;
}
//========================================================================
// UpdateWatch
// A single watch entry is updated here. Here's what's done:
//
// If the value has not changed since the last update, then the
// foreground and background colors are refreshed and we return.
//
// If the value has changed, then each of the value fields are refreshed.
// Then the foreground and background are refreshed.
void Watch_Window::UpdateWatch(WatchEntry *entry)
{
int row;
row=gtk_clist_find_row_from_data(GTK_CLIST(watch_clist),entry);
if(row==-1)
return;
RegisterValue rvNewValue = entry->getRV();
// If the value has not changed, then simply update the foreground and background
// colors and return.
if (entry->get_shadow() == rvNewValue) {
gtk_clist_set_foreground(GTK_CLIST(watch_clist), row, gColors.normal_fg());
gtk_clist_set_background(GTK_CLIST(watch_clist),
row,
(entry->hasBreak() ? gColors.breakpoint() : gColors.normal_bg()));
return;
}
RegisterValue rvMaskedNewValue;
unsigned int uBitmask;
entry->put_shadow(rvNewValue);
if(entry->pRegSymbol) {
rvMaskedNewValue = *(entry->pRegSymbol);
uBitmask = entry->pRegSymbol->getBitmask();
}
else {
rvMaskedNewValue = entry->getRV();
uBitmask = entry->cpu->register_mask();
}
char str[80];
if(rvNewValue.init & uBitmask) {
strcpy(str, "?");
}
else
sprintf(str,"%d", rvNewValue.data);
gtk_clist_set_text(GTK_CLIST(watch_clist), row, DECIMALCOL, str);
// Hexadecimal representation:
rvMaskedNewValue.toString(str, 80);
gtk_clist_set_text(GTK_CLIST(watch_clist), row, HEXCOL, str);
// ASCII representation
str[0] = (rvNewValue.data>'0' && rvNewValue.data<='z') ? rvNewValue.data : 0;
str[1] =0;
gtk_clist_set_text(GTK_CLIST(watch_clist), row, ASCIICOL, str);
// Bit representation
char sBits[25];
rvNewValue.toBitStr(sBits, 25, entry->cpu->register_mask(),
NULL);
gtk_clist_set_text(GTK_CLIST(watch_clist), row, BITCOL, sBits);
// Set foreground and background colors
gtk_clist_set_foreground(GTK_CLIST(watch_clist), row, gColors.item_has_changed());
gtk_clist_set_background(GTK_CLIST(watch_clist), row,
(entry->hasBreak() ? gColors.breakpoint() : gColors.normal_bg()));
}
//------------------------------------------------------------------------
// Update
//
//
void Watch_Window::Update()
{
GList *iter;
WatchEntry *entry;
int clist_frozen=0;
iter=watches;
while(iter) {
entry=(WatchEntry*)iter->data;
if (entry)
UpdateWatch(entry);
iter=iter->next;
}
if(clist_frozen)
gtk_clist_thaw(GTK_CLIST(watch_clist));
}
//------------------------------------------------------------------------
void Watch_Window::Add( REGISTER_TYPE type, GUIRegister *reg)
{
if(!gp || !gp->cpu || !reg || !reg->bIsValid())
return;
Register *cpu_reg = reg->get_register();
register_symbol * pRegSym = get_symbol_table().findRegisterSymbol(
cpu_reg->address);
Add(type, reg, pRegSym);
}
void Watch_Window::Add( REGISTER_TYPE type, GUIRegister *reg, register_symbol * pRegSym)
{
char vname[50], addressstring[50];
char *entry[COLUMNS]={vname, addressstring, "", "","",""};
int row;
WatchWindowXREF *cross_reference;
WatchEntry *watch_entry;
if(!gp || !gp->cpu || !reg || !reg->bIsValid())
return;
if(!enabled)
Build();
Register *cpu_reg;
if(pRegSym == 0) {
cpu_reg = reg->get_register();
strncpy(vname,cpu_reg->name().c_str(),sizeof(vname));
}
else {
cpu_reg = pRegSym->getReg();
strncpy(vname,pRegSym->name().c_str(),sizeof(vname));
}
unsigned int uAddrMask = 0;
unsigned int uLastAddr = gp->cpu->register_memory_size() - 1;
while(uLastAddr) {
uLastAddr>>=4;
uAddrMask<<=4;
uAddrMask |= 0xf;
}
strcpy(addressstring, GetUserInterface().FormatProgramAddress(
cpu_reg->address, uAddrMask, IUserInterface::eHex));
gtk_clist_freeze(GTK_CLIST(watch_clist));
row=gtk_clist_append(GTK_CLIST(watch_clist), entry);
watch_entry = new WatchEntry();
watch_entry->address=reg->address;
watch_entry->pRegSymbol = pRegSym;
watch_entry->cpu = gp->cpu;
watch_entry->type=type;
watch_entry->rma = reg->rma;
gtk_clist_set_row_data(GTK_CLIST(watch_clist), row, (gpointer)watch_entry);
watches = g_list_append(watches, (gpointer)watch_entry);
UpdateWatch(watch_entry);
cross_reference = new WatchWindowXREF();
cross_reference->parent_window_type = WT_watch_window;
cross_reference->parent_window = (gpointer) this;
cross_reference->data = (gpointer) watch_entry;
watch_entry->Assign_xref(cross_reference);
gtk_clist_thaw(GTK_CLIST(watch_clist));
UpdateMenus();
}
//---
// Add - given a symbol, verify that it is a RAM or EEPROM register
// symbol. If it is then extract the register and use it's address
// to get the GUI representation of the register.
void Watch_Window::Add( Value *regSym)
{
if(regSym && gp) {
register_symbol *rs = dynamic_cast<register_symbol *>(regSym);
if(rs != 0) {
Register *reg = rs->getReg();
if(reg) {
GUIRegister *greg = gp->m_pGUIRamRegisters->Get(reg->address);
Add(REGISTER_RAM, greg, rs);
}
}
}
}
//------------------------------------------------------------------------
// NewSymbols
//
void Watch_Window::NewProcessor(GUI_Processor *_gp)
{
if (!gp || !gp->cpu)
return;
ReadSymbolList();
}
//------------------------------------------------------------------------
// ClearWatches
//
//
void Watch_Window::ClearWatches(void)
{
GList *iter;
WatchEntry *entry;
int row;
iter=watches;
while(iter) {
entry=(WatchEntry*)iter->data;
row=gtk_clist_find_row_from_data(GTK_CLIST(watch_clist),entry);
gtk_clist_remove(GTK_CLIST(watch_clist),row);
entry->Clear_xref();
free(entry);
iter=iter->next;
}
while( (watches=g_list_remove_link(watches,watches))!=0)
;
}
//------------------------------------------------------------------------
// Build
//
//
void Watch_Window::Build(void)
{
if(bIsBuilt)
return;
GtkWidget *vbox;
GtkWidget *scrolled_window;
int i;
window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_window_set_title(GTK_WINDOW(window), "Watch Viewer");
gtk_window_set_default_size(GTK_WINDOW(window), width,height);
gtk_widget_set_uposition(GTK_WIDGET(window),x,y);
gtk_window_set_wmclass(GTK_WINDOW(window),name(),"Gpsim");
gtk_signal_connect (GTK_OBJECT (window), "delete_event",
GTK_SIGNAL_FUNC(delete_event), (gpointer)this);
gtk_signal_connect_after(GTK_OBJECT(window), "configure_event",
GTK_SIGNAL_FUNC(gui_object_configure_event),this);
watch_clist = gtk_clist_new_with_titles(COLUMNS,watch_titles);
gtk_widget_show(watch_clist);
for(i=0;i<LASTCOL;i++) {
//gtk_clist_set_column_auto_resize(GTK_CLIST(watch_clist),i,TRUE);
gtk_clist_set_column_resizeable(GTK_CLIST(watch_clist),i,TRUE);
coldata[i].ww = this;
coldata[i].column = i;
coldata[i].Show();
}
gtk_clist_set_selection_mode (GTK_CLIST(watch_clist), GTK_SELECTION_BROWSE);
gtk_signal_connect(GTK_OBJECT(watch_clist),"click_column",
(GtkSignalFunc)watch_click_column,0);
gtk_signal_connect(GTK_OBJECT(watch_clist),"select_row",
(GtkSignalFunc)watch_list_row_selected,this);
gtk_signal_connect(GTK_OBJECT(watch_clist),"unselect_row",
(GtkSignalFunc)unselect_row,this);
gtk_signal_connect(GTK_OBJECT(watch_clist),
"button_press_event",
(GtkSignalFunc) do_popup,
this);
gtk_signal_connect(GTK_OBJECT(window),"key_press_event",
(GtkSignalFunc) key_press,
(gpointer) this);
scrolled_window=gtk_scrolled_window_new(0, 0);
gtk_widget_show(scrolled_window);
vbox = gtk_vbox_new(FALSE,1);
gtk_widget_show(vbox);
gtk_container_add(GTK_CONTAINER(scrolled_window), watch_clist);
gtk_container_add(GTK_CONTAINER(window),vbox);
gtk_box_pack_start_defaults(GTK_BOX(vbox),scrolled_window);
popup_menu=build_menu(window,this);
gtk_widget_show (window);
enabled=1;
bIsBuilt = true;
UpdateMenuItem();
}
Watch_Window::Watch_Window(GUI_Processor *_gp)
{
int i;
#define MAXROWS (MAX_REGISTERS/REGISTERS_PER_ROW)
#define MAXCOLS (REGISTERS_PER_ROW+1)
menu = "<main>/Windows/Watch";
set_name("watch_viewer");
wc = WC_data;
wt = WT_watch_window;
window = 0;
watches=0;
current_row=0;
gp = _gp;
get_config();
for(i=0;i<COLUMNS;i++) {
if (!config_get_variable(name(),watch_titles[i],&coldata[i].isVisible))
config_set_variable(name(),watch_titles[i],1);
}
{
// Fix a db error in previous versions of gpsim
int j;
while (config_get_variable(name(),"hex",&j))
config_remove(name(), "hex");
const int hexIndex=3;
config_set_variable(name(),watch_titles[hexIndex],coldata[hexIndex].isVisible);
}
if(enabled)
Build();
}
#endif // HAVE_GUI
syntax highlighted by Code2HTML, v. 0.9.1