// $Id$
// Data Display

// Copyright (C) 1995-1999 Technische Universitaet Braunschweig, Germany.
// Copyright (C) 2000 Universitaet Passau, Germany.
// Copyright (C) 2001, 2003, 2004 Free Software Foundation, Inc.
// Written by Dorothea Luetkehaus <luetke@ips.cs.tu-bs.de>
// and Andreas Zeller <zeller@gnu.org>.
// 
// This file is part of DDD.
// 
// DDD 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 3 of the License, or (at your option) any later version.
// 
// DDD 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 DDD -- see the file COPYING.
// If not, see <http://www.gnu.org/licenses/>.
// 
// DDD is the data display debugger.
// For details, see the DDD World-Wide-Web page, 
// `http://www.gnu.org/software/ddd/',
// or send a mail to the DDD developers <ddd@gnu.org>.

char DataDisp_rcsid[] =
    "$Id$";

// An interactive debugger is an outstanding example of what's NOT
// needed--it encourages trial-and-error hacking rather than
// systematic design, and also hides marginal people barely qualified
// for precision programming.
// 					-- HARLAN MILLS
//
// The debugger isn't a substitute for good thinking.  But, in some
// cases, thinking isn't a substitute for a good debugger either.  The
// most effective combination is good thinking and a good debugger.
//
//					-- STEVE McCONNELL, Code Complete


#ifndef LOG_DISPLAYS
#define LOG_DISPLAYS 0
#endif

#ifndef LOG_COMPARE
#define LOG_COMPARE  0
#endif

//-----------------------------------------------------------------------------
// Data Display Implementation
//-----------------------------------------------------------------------------

#include "DataDisp.h"

// Misc includes
#include "AliasGE.h"
#include "AppData.h"		// Constructors
#include "ArgField.h"
#include "ComboBox.h"
#include "Command.h"
#include "CompositeB.h"
#include "DestroyCB.h"
#include "DispGraph.h"
#include "DispNode.h"
#include "DispBox.h"
#include "GraphEdit.h"
#include "Graph.h"
#include "HistoryD.h"
#include "IntIntAA.h"
#include "LessTifH.h"
#include "MString.h"
#include "MakeMenu.h"
#include "Map.h"
#include "PannedGE.h"
#include "PosBuffer.h"
#include "ProgressM.h"
#include "ScrolledGE.h"
#include "SmartC.h"
#include "StringBox.h"		// StringBox::fontTable
#include "StringMap.h"
#include "TagBox.h"
#include "TextSetS.h"
#include "TimeOut.h"
#include "UndoBuffer.h"
#include "VSEFlags.h"
#include "VSLLib.h"
#include "VoidArray.h"
#include "assert.h"
#include "bool.h"
#include "buttons.h"
#include "charsets.h"
#include "cmdtty.h"
#include "comm-manag.h"
#include "converters.h"
#include "cook.h"
#include "ddd.h"
#include "deref.h"
#include "disp-read.h"
#include "history.h"
#include "logo.h"
#include "mydialogs.h"
#include "post.h"
#include "regexps.h"
#include "resolveP.h"
#include "session.h"
#include "settings.h"
#include "status.h"
#include "string-fun.h"
#include "toolbar.h"
#include "value-read.h"
#include "verify.h"
#include "version.h"
#include "vsldoc.h"
#include "windows.h"
#include "wm.h"

// Motif includes
#include <Xm/List.h>
#include <Xm/MessageB.h>
#include <Xm/ToggleB.h>
#include <Xm/RowColumn.h>	// XmMenuPosition()
#include <Xm/SelectioB.h>	// XmCreatePromptDialog()
#include <Xm/TextF.h>		// XmTextFieldGetString()
#include <Xm/Label.h>
#include <Xm/PushB.h>
#include <X11/StringDefs.h>

// System includes
#include <iostream>
#include <fstream>		// ofstream
#include <ctype.h>


//-----------------------------------------------------------------------
// Xt Stuff
//-----------------------------------------------------------------------
XtActionsRec DataDisp::actions [] = {
    {XTARECSTR("graph-select"),         DataDisp::graph_selectAct},
    {XTARECSTR("graph-select-or-move"), DataDisp::graph_select_or_moveAct},
    {XTARECSTR("graph-extend"),         DataDisp::graph_extendAct},
    {XTARECSTR("graph-extend-or-move"), DataDisp::graph_extend_or_moveAct},
    {XTARECSTR("graph-toggle"),         DataDisp::graph_toggleAct},
    {XTARECSTR("graph-toggle-or-move"), DataDisp::graph_toggle_or_moveAct},
    {XTARECSTR("graph-popup-menu"),     DataDisp::graph_popupAct},
    {XTARECSTR("graph-dereference"),    DataDisp::graph_dereferenceAct},
    {XTARECSTR("graph-detail"),         DataDisp::graph_detailAct},
    {XTARECSTR("graph-rotate"),         DataDisp::graph_rotateAct},
    {XTARECSTR("graph-dependent"),      DataDisp::graph_dependentAct}
};



// Popup Menu
struct GraphItms { enum Itms {SelectAll, Refresh, NewArg, New}; };
MMDesc DataDisp::graph_popup[] =
{
    {"selectAll", MMPush,               
     {DataDisp::selectAllCB, 0}, 0, 0, 0, 0},
    {"refresh",   MMPush,               
     {DataDisp::refreshCB, 0}, 0, 0, 0, 0},
    {"new_arg",   MMPush | MMUnmanaged, 
     {DataDisp::popup_new_argCB, 0}, 0, 0, 0, 0},
    {"new",       MMPush,               
     {DataDisp::popup_newCB, 0}, 0, 0, 0, 0},
    MMEnd
};

// Number of shortcut items
const int DataDisp::shortcut_items = 20;

#define SHORTCUT_MENU \
    {"s1",  MMPush | MMUnmanaged, \
        { DataDisp::shortcutCB, XtPointer(1)  }, 0, 0, 0, 0 }, \
    {"s2",  MMPush | MMUnmanaged, \
        { DataDisp::shortcutCB, XtPointer(2)  }, 0, 0, 0, 0 }, \
    {"s3",  MMPush | MMUnmanaged, \
        { DataDisp::shortcutCB, XtPointer(3)  }, 0, 0, 0, 0 }, \
    {"s4",  MMPush | MMUnmanaged, \
        { DataDisp::shortcutCB, XtPointer(4)  }, 0, 0, 0, 0 }, \
    {"s5",  MMPush | MMUnmanaged, \
        { DataDisp::shortcutCB, XtPointer(5)  }, 0, 0, 0, 0 }, \
    {"s6",  MMPush | MMUnmanaged, \
        { DataDisp::shortcutCB, XtPointer(6)  }, 0, 0, 0, 0 }, \
    {"s7",  MMPush | MMUnmanaged, \
        { DataDisp::shortcutCB, XtPointer(7)  }, 0, 0, 0, 0 }, \
    {"s8",  MMPush | MMUnmanaged, \
        { DataDisp::shortcutCB, XtPointer(8)  }, 0, 0, 0, 0 }, \
    {"s9",  MMPush | MMUnmanaged, \
        { DataDisp::shortcutCB, XtPointer(9)  }, 0, 0, 0, 0 }, \
    {"s10", MMPush | MMUnmanaged, \
        { DataDisp::shortcutCB, XtPointer(10) }, 0, 0, 0, 0 }, \
    {"s11", MMPush | MMUnmanaged, \
        { DataDisp::shortcutCB, XtPointer(11) }, 0, 0, 0, 0 }, \
    {"s12", MMPush | MMUnmanaged, \
        { DataDisp::shortcutCB, XtPointer(12) }, 0, 0, 0, 0 }, \
    {"s13", MMPush | MMUnmanaged, \
        { DataDisp::shortcutCB, XtPointer(13) }, 0, 0, 0, 0 }, \
    {"s14", MMPush | MMUnmanaged, \
        { DataDisp::shortcutCB, XtPointer(14) }, 0, 0, 0, 0 }, \
    {"s15", MMPush | MMUnmanaged, \
        { DataDisp::shortcutCB, XtPointer(15) }, 0, 0, 0, 0 }, \
    {"s16", MMPush | MMUnmanaged, \
        { DataDisp::shortcutCB, XtPointer(16) }, 0, 0, 0, 0 }, \
    {"s17", MMPush | MMUnmanaged, \
        { DataDisp::shortcutCB, XtPointer(17) }, 0, 0, 0, 0 }, \
    {"s18", MMPush | MMUnmanaged, \
        { DataDisp::shortcutCB, XtPointer(18) }, 0, 0, 0, 0 }, \
    {"s19", MMPush | MMUnmanaged, \
        { DataDisp::shortcutCB, XtPointer(19) }, 0, 0, 0, 0 }, \
    {"s20", MMPush | MMUnmanaged, \
        { DataDisp::shortcutCB, XtPointer(20) }, 0, 0, 0, 0 }, \
    {"other", MMPush, { DataDisp::dependentCB, 0 }, 0, 0, 0, 0}, \
    MMSep, \
    {"edit",  MMPush, { dddEditShortcutsCB, 0 }, 0, 0, 0, 0}

// The menu used in the `New Display' button.


struct ShortcutItms { enum Itms {S1, S2, S3, S4, S5,
				 S6, S7, S8, S9, S10,
				 S11, S12, S13, S14, S15,
				 S16, S17, S18, S19, S20,
				 Other, Sep1, Edit,
				 Sep2, New2, Dereference2 }; };

MMDesc DataDisp::shortcut_menu[]   = 
{
    SHORTCUT_MENU,
    MMSep,
    {"new2", MMPush, {DataDisp::displayArgCB, XtPointer(false)}, 0, 0, 0, 0 },
    {"dereference2", MMPush, {DataDisp::dereferenceArgCB, 0}, 0, 0, 0, 0 },
    MMEnd
};

// A stand-alone popup menu.
MMDesc DataDisp::shortcut_popup1[] = { SHORTCUT_MENU, MMEnd };

// The sub-menu in the `New Display' item.
MMDesc DataDisp::shortcut_popup2[] = { SHORTCUT_MENU, MMEnd };

struct RotateItms { enum Itms {RotateAll}; };

MMDesc DataDisp::rotate_menu[] =
{
    {"rotateAll",     MMPush | MMInsensitive, 
     {DataDisp::rotateCB, XtPointer(true)}, 0, 0, 0, 0},
    MMEnd
};

// Number of theme items
const int DataDisp::theme_items = 20;

MMDesc DataDisp::theme_menu[] =
{
    {"t1",  MMToggle | MMUnmanaged,  
        { DataDisp::toggleThemeCB, XtPointer(1)  }, 0, 0, 0, 0 },  
    {"t2",  MMToggle | MMUnmanaged,  
        { DataDisp::toggleThemeCB, XtPointer(2)  }, 0, 0, 0, 0 },  
    {"t3",  MMToggle | MMUnmanaged,  
        { DataDisp::toggleThemeCB, XtPointer(3)  }, 0, 0, 0, 0 },  
    {"t4",  MMToggle | MMUnmanaged,  
        { DataDisp::toggleThemeCB, XtPointer(4)  }, 0, 0, 0, 0 },  
    {"t5",  MMToggle | MMUnmanaged,  
        { DataDisp::toggleThemeCB, XtPointer(5)  }, 0, 0, 0, 0 },  
    {"t6",  MMToggle | MMUnmanaged,  
        { DataDisp::toggleThemeCB, XtPointer(6)  }, 0, 0, 0, 0 },  
    {"t7",  MMToggle | MMUnmanaged,  
        { DataDisp::toggleThemeCB, XtPointer(7)  }, 0, 0, 0, 0 },  
    {"t8",  MMToggle | MMUnmanaged,  
        { DataDisp::toggleThemeCB, XtPointer(8)  }, 0, 0, 0, 0 },  
    {"t9",  MMToggle | MMUnmanaged,  
        { DataDisp::toggleThemeCB, XtPointer(9)  }, 0, 0, 0, 0 },  
    {"t10", MMToggle | MMUnmanaged,  
        { DataDisp::toggleThemeCB, XtPointer(10) }, 0, 0, 0, 0 },  
    {"t11", MMToggle | MMUnmanaged,  
        { DataDisp::toggleThemeCB, XtPointer(11) }, 0, 0, 0, 0 },  
    {"t12", MMToggle | MMUnmanaged,  
        { DataDisp::toggleThemeCB, XtPointer(12) }, 0, 0, 0, 0 },  
    {"t13", MMToggle | MMUnmanaged,  
        { DataDisp::toggleThemeCB, XtPointer(13) }, 0, 0, 0, 0 },  
    {"t14", MMToggle | MMUnmanaged,  
        { DataDisp::toggleThemeCB, XtPointer(14) }, 0, 0, 0, 0 },  
    {"t15", MMToggle | MMUnmanaged,  
        { DataDisp::toggleThemeCB, XtPointer(15) }, 0, 0, 0, 0 },  
    {"t16", MMToggle | MMUnmanaged,  
        { DataDisp::toggleThemeCB, XtPointer(16) }, 0, 0, 0, 0 },  
    {"t17", MMToggle | MMUnmanaged,  
        { DataDisp::toggleThemeCB, XtPointer(17) }, 0, 0, 0, 0 },  
    {"t18", MMToggle | MMUnmanaged,  
        { DataDisp::toggleThemeCB, XtPointer(18) }, 0, 0, 0, 0 },  
    {"t19", MMToggle | MMUnmanaged,  
        { DataDisp::toggleThemeCB, XtPointer(19) }, 0, 0, 0, 0 },  
    {"t20", MMToggle | MMUnmanaged,  
        { DataDisp::toggleThemeCB, XtPointer(20) }, 0, 0, 0, 0 },  
    MMSep, 
    {"edit",  MMPush, { dddPopupThemesCB, 0 }, 0, 0, 0, 0},
    MMEnd
};

struct NodeItms { enum Itms {Dereference, New, Theme, Sep1, 
			     Detail, Rotate, Set, Sep2, 
			     Delete }; };

MMDesc DataDisp::node_popup[] =
{
    {"dereference",   MMPush,   {DataDisp::dereferenceCB, 0}, 0, 0, 0, 0},
    {"new",           MMMenu,   MMNoCB, DataDisp::shortcut_popup2, 0, 0, 0},
    {"theme",         MMMenu,   MMNoCB, DataDisp::theme_menu, 0, 0, 0},
    MMSep,
    {"detail",        MMPush,   
     {DataDisp::toggleDetailCB, XtPointer(-1)}, 0, 0, 0, 0},
    {"rotate",        MMPush,   
     {DataDisp::rotateCB, XtPointer(false) }, 0, 0, 0, 0},
    {"set",           MMPush,   {DataDisp::setCB, 0}, 0, 0, 0, 0},
    MMSep,
    {"delete",        MMPush,   
     {DataDisp::deleteCB, 0}, 0, 0, 0, 0},
    MMEnd
};


struct DeleteItms { enum Itms {Cluster}; };

MMDesc DataDisp::delete_menu[] =
{
    {"cluster",     MMPush, {DataDisp::toggleClusterSelectedCB, 0}, 
     0, 0, 0, 0},
    MMEnd
};

struct PlotItms { enum Itms { History }; };

MMDesc DataDisp::plot_menu[] =
{
    {"history",     MMPush, {DataDisp::plotHistoryCB, 0}, 
     0, 0, 0, 0},
    MMEnd
};


struct CmdItms { enum Itms {New, Dereference, Plot, 
			    Detail, Rotate, Set, Delete }; };

MMDesc DataDisp::graph_cmd_area[] =
{
    {"new",           MMPush,                 
     {DataDisp::displayArgCB, XtPointer(true)}, 
     DataDisp::shortcut_menu, 0, 0, 0 },
    {"dereference",   MMPush | MMInsensitive | MMUnmanaged, 
     {DataDisp::dereferenceArgCB, 0}, 0, 0, 0, 0},
    {"plot",          MMPush | MMInsensitive,
     {DataDisp::plotArgCB, 0}, 
     DataDisp::plot_menu, 0, 0, 0},
    {"detail",        MMPush | MMInsensitive, 
     {DataDisp::toggleDetailCB, XtPointer(-1)}, 
     DataDisp::detail_menu, 0, 0, 0 },
    {"rotate",        MMPush | MMInsensitive, 
     {DataDisp::rotateCB, XtPointer(false)}, DataDisp::rotate_menu, 0, 0, 0 },
    {"set",           MMPush | MMInsensitive, 
     {DataDisp::setCB, 0}, 0, 0, 0, 0 },
    {"delete",        MMPush | MMInsensitive, 
     {DataDisp::deleteArgCB, XtPointer(true)},
     DataDisp::delete_menu, 0, 0, 0 },
    MMEnd
};


struct DetailItms { enum Itms { ShowMore, ShowJust, 
				ShowDetail, HideDetail }; };

MMDesc DataDisp::detail_menu[] =
{
    {"show_more",    MMPush, 
     {DataDisp::showMoreDetailCB, XtPointer(1) }, 0, 0, 0, 0},
    {"show_just",    MMPush, 
     {DataDisp::showDetailCB, XtPointer(1) }, 0, 0, 0, 0},
    {"show_detail",  MMPush, 
     {DataDisp::showDetailCB, XtPointer(-1) }, 0, 0, 0, 0},
    {"hide_detail",  MMPush, 
     {DataDisp::hideDetailCB, XtPointer(-1) }, 0, 0, 0, 0},
    MMEnd
};

struct DisplayItms { enum Itms {New, Dereference, 
				ShowDetail, HideDetail, Set, 
				Cluster, Uncluster, Delete}; };

MMDesc DataDisp::display_area[] =
{
    {"new",          MMPush,   {DataDisp::dependentCB, 0 }, 0, 0, 0, 0},
    {"dereference",  MMPush,   {DataDisp::dereferenceCB, 0 }, 0, 0, 0, 0},
    {"show_detail",  MMPush,   
     {DataDisp::showDetailCB, XtPointer(-1) }, 0, 0, 0, 0},
    {"hide_detail",  MMPush,   
     {DataDisp::hideDetailCB, XtPointer(-1) }, 0, 0, 0, 0},
    {"set",          MMPush, {DataDisp::setCB, 0}, 0, 0, 0, 0},
    {"cluster",      MMPush, {DataDisp::clusterSelectedCB, 0}, 0, 0, 0, 0},
    {"uncluster",    MMPush, {DataDisp::unclusterSelectedCB, 0}, 0, 0, 0, 0},
    {"delete",       MMPush | MMHelp, 
     {DataDisp::deleteCB, 0}, 0, 0, 0, 0},
    MMEnd
};

DispGraph *DataDisp::disp_graph             = 0;
Widget     DataDisp::graph_edit             = 0;
Widget     DataDisp::graph_form_w           = 0;
Widget     DataDisp::last_origin            = 0;
ArgField  *DataDisp::graph_arg              = 0;
Widget     DataDisp::graph_cmd_w            = 0;
Widget     DataDisp::graph_selection_w      = 0;
Widget     DataDisp::edit_displays_dialog_w = 0;
Widget     DataDisp::display_list_w         = 0;
Widget     DataDisp::graph_popup_w          = 0;
Widget     DataDisp::node_popup_w           = 0;
Widget     DataDisp::shortcut_popup_w       = 0;

bool DataDisp::detect_aliases   = false;
bool DataDisp::cluster_displays = false;
bool DataDisp::arg_needs_update = false;

int DataDisp::next_ddd_display_number = 1;
int DataDisp::next_gdb_display_number = 1;

XtIntervalId DataDisp::refresh_args_timer       = 0;
XtIntervalId DataDisp::refresh_addr_timer       = 0;
XtIntervalId DataDisp::refresh_graph_edit_timer = 0;

// Array of shortcut expressions and their labels
StringArray DataDisp::shortcut_exprs;
StringArray DataDisp::shortcut_labels;

//----------------------------------------------------------------------------
// Helpers
//----------------------------------------------------------------------------

// Return A <= B
inline bool default_le(int a, int b) { return a <= b; }

// If A and B are both negative, reverse order.  This way, we'll get
// -1, -2, -3, -4, ..., 0, 1, 2, 3, 4 when sorting.
inline bool absolute_le(int a, int b)
{
    if (a < 0 && b < 0)
	return b <= a;
    else
	return a <= b;
}


// Sort A
static void sort(IntArray& a, bool (*le)(int, int) = default_le)
{
    // Shell sort -- simple and fast
    int h = 1;
    do {
	h = h * 3 + 1;
    } while (h <= a.size());
    do {
	h /= 3;
	for (int i = h; i < a.size(); i++)
	{
	    int v = a[i];
	    int j;
	    for (j = i; j >= h && !le(a[j - h], v); j -= h)
		a[j] = a[j - h];
	    if (i != j)
		a[j] = v;
	}
    } while (h != 1);
}


//----------------------------------------------------------------------------
// Origin
//-----------------------------------------------------------------------------

void DataDisp::ClearOriginCB(Widget w, XtPointer, XtPointer)
{
    if (last_origin == w)
    {
	last_origin = 0;
    }
}

void DataDisp::set_last_origin(Widget w)
{
    if (last_origin != 0)
    {
	XtRemoveCallback(last_origin, XtNdestroyCallback, ClearOriginCB, 0);
    }

    last_origin = find_shell(w);

    if (last_origin != 0)
    {
	XtAddCallback(last_origin, XtNdestroyCallback, ClearOriginCB, 0);
    }
}



//----------------------------------------------------------------------------
// DispNode functions
//-----------------------------------------------------------------------------

bool DataDisp::selected(DispNode *dn)
{
    // Don't treat a cluster as selected if only a member is selected
    if (is_cluster(dn) && dn->selected() && dn->selected_value() != 0)
	return false;
    else
	return dn->selected();
}

bool DataDisp::needs_refresh(DispNode *cluster)
{
    if (!is_cluster(cluster))
	return true;

    if (cluster->last_refresh() == 0)
	return true;

    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref); 
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	if (dn->clustered() == cluster->disp_nr() &&
	    dn->last_refresh() > cluster->last_refresh())
	    return true;
    }

    return false;
}


//----------------------------------------------------------------------------
// Counters
//-----------------------------------------------------------------------------

// Count the number of data displays
int DataDisp::count_data_displays()
{
    int count = 0;

    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref); 
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	if (!dn->is_user_command() && !dn->deferred())
	    count++;
    }

    return count;
}

// Get all display numbers
void DataDisp::get_all_display_numbers(IntArray& numbers)
{
    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref); 
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	if (!dn->deferred())
	    numbers += dn->disp_nr();
    }
}

// Get all clusters
void DataDisp::get_all_clusters(IntArray& numbers)
{
    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref); 
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	if (is_cluster(dn))
	    numbers += dn->disp_nr();
    }
}



//-----------------------------------------------------------------------------
// Apply themes to expressions
//-----------------------------------------------------------------------------

string DataDisp::selected_pattern()
{
    return pattern(source_arg->get_string());
}

#if RUNTIME_REGEX
static regex rxindex("[[]-?[0-9][0-9]*].*");
#endif

string DataDisp::pattern(const string& expr, bool shorten)
{
    string pattern = expr;
    pattern.gsub("\\", "\\\\");
    pattern.gsub("?", "\\?");
    pattern.gsub("*", "\\*");

    while (shorten && pattern.contains(rxindex))
    {
	int opening_bracket = pattern.index(rxindex);
	int closing_bracket = pattern.index("]", opening_bracket);
	pattern = pattern.before(opening_bracket) + "[*]" + 
	    pattern.after(closing_bracket);
    }

    pattern.gsub("[", "\\[");
    pattern.gsub("]", "\\]");

    if (shorten)
    {
	if (pattern.contains("->"))
	    pattern = "*" + pattern.from("->", -1);
	if (pattern.contains("."))
	    pattern = "*" + pattern.from(".", -1);
    }

    return pattern;
}

// Apply the theme in CLIENT_DATA to the selected item.
void DataDisp::applyThemeCB (Widget w, XtPointer client_data, 
			     XtPointer call_data)
{
    set_last_origin(w);

    string theme = String(client_data);

    if (gdb->recording())
    {
	toggle_theme(theme, quote(source_arg->get_string()));
	return;
    }

    string p = selected_pattern();
    if (p.empty())
	return;

    DispValue *dv = selected_value();
    if (dv == 0)
	return;

    string doc = vsldoc(theme, DispBox::vsllib_path);
    if (doc.contains("."))
	doc = doc.before(".");
    else if (doc.empty())
	doc = theme;

    defineConversionMacro("THEME", theme.chars());
    defineConversionMacro("THEME_DOC", doc.chars());
    defineConversionMacro("PATTERN", p.chars());
    const string s1 = dv->full_name();
    defineConversionMacro("EXPR", s1.chars());

    bool select = 
	(pattern(dv->full_name(), true) != pattern(dv->full_name(), false));

    if (!select && theme != app_data.suppress_theme)
    {
	// Don't ask for confirmation -- just apply.  We can undo anyway.
	applyThemeOnThisCB(w, client_data, call_data);
	return;
    }

    Arg args[10];
    Cardinal arg = 0;

    XtSetArg(args[arg], XmNdeleteResponse, XmDESTROY); arg++;
    XtSetArg(args[arg], XmNautoUnmanage,   False);     arg++;
    string name = 
	(select ? "select_apply_theme_dialog" : "confirm_apply_theme_dialog");
    Widget dialog = 
	verify(XmCreateQuestionDialog(find_shell(w), XMST(name.chars()), args, arg));

    if (select)
    {
	arg = 0;
	Widget apply = XmCreatePushButton(dialog, XMST("apply"), args, arg);
	XtManageChild(apply);
	XtAddCallback(apply, XmNactivateCallback, 
		      applyThemeOnThisCB, client_data);
	XtAddCallback(apply, XmNactivateCallback, DestroyShellCB, 0);
    }

    Delay::register_shell(dialog);
    XtAddCallback(dialog, XmNokCallback,      applyThemeOnAllCB, client_data);
    XtAddCallback(dialog, XmNokCallback,      DestroyShellCB, 0);
    XtAddCallback(dialog, XmNcancelCallback,  DestroyShellCB, 0);
    XtAddCallback(dialog, XmNhelpCallback,    ImmediateHelpCB, 0);

    manage_and_raise(dialog);
}

// Unapply the theme in CLIENT_DATA from the selected item.
void DataDisp::unapplyThemeCB (Widget w, XtPointer client_data, XtPointer)
{
    set_last_origin(w);

    string theme = String(client_data);

    if (gdb->recording())
    {
	toggle_theme(theme, quote(source_arg->get_string()));
	return;
    }

    if (selected_pattern().empty())
	return;

    DispValue *dv = selected_value();
    if (dv == 0)
	return;

    string expr = dv->full_name();

    ThemePattern tp = DispBox::theme_manager.pattern(theme);
    if (!tp.active() || !tp.matches(expr))
	return;			// Nothing to unapply

    // 1. Try to remove expression
    tp.remove(pattern(expr, false));
    if (!tp.matches(expr))
    {
	unapply_theme(theme, pattern(expr, false), w);
	return;
    }

    // 2. Try to remove expression pattern
    tp.remove(pattern(expr));
    if (!tp.matches(expr))
    {
	unapply_theme(theme, pattern(expr), w);
	return;
    }

    // 3. Remove first matching pattern
    StringArray patterns = tp.patterns();
    for (int i = 0; i < patterns.size(); i++)
    {
	tp.remove(patterns[i]);
	if (!tp.matches(expr))
	{
	    unapply_theme(theme, patterns[i], w);
	    return;
	}
    }
}

void DataDisp::toggleThemeCB(Widget button, XtPointer, XtPointer call_data)
{
    String theme;
    XtVaGetValues(button, XmNuserData, &theme, XtPointer(0));

    if (XmToggleButtonGetState(button))
    {
	// Now activated
	applyThemeCB(button, XtPointer(theme), call_data);
    }
    else
    {
	// Now deactivated
	unapplyThemeCB(button, XtPointer(theme), call_data);
    }
}


// Apply the theme in CLIENT_DATA to the selected item.
void DataDisp::applyThemeOnAllCB(Widget, XtPointer client_data, XtPointer)
{
    string pattern = selected_pattern();
    if (pattern.empty())
	return;

    string theme = String(client_data);
    apply_theme(theme, pattern);
}

// Apply the theme in CLIENT_DATA to the selected item.
void DataDisp::applyThemeOnThisCB(Widget, XtPointer client_data, XtPointer)
{
    string theme = String(client_data);
    apply_theme(theme, quote(source_arg->get_string()));
}

string DataDisp::apply_theme_cmd(const string& theme, const string& pattern)
{
    return "graph apply theme " + theme + " " + pattern;
}

string DataDisp::unapply_theme_cmd(const string& theme, const string& pattern)
{
    return "graph unapply theme " + theme + " " + pattern;
}

string DataDisp::toggle_theme_cmd(const string& theme, const string& pattern)
{
    return "graph toggle theme " + theme + " " + pattern;
}

void DataDisp::apply_themeSQ(const string& theme, const string& pattern,
			     bool /* verbose */, bool do_prompt)
{
    string t = theme;
    strip_space(t);

    string p = pattern;
    strip_space(p);

    ThemePattern& tp = DispBox::theme_manager.pattern(t);

    if (!tp.active())
    {
	// Make sure that `old' settings are no longer conserved
	tp = ThemePattern();
    }

    tp.add(p);
    tp.active() = true;

    update_themes();
    set_theme_manager(DispBox::theme_manager);

    if (do_prompt)
	prompt();
}

void DataDisp::unapply_themeSQ(const string& theme, const string& pattern,
			       bool /* verbose */, bool do_prompt)
{
    string t = theme;
    strip_space(t);

    string p = pattern;
    strip_space(p);

    ThemePattern& tp = DispBox::theme_manager.pattern(t);
    tp.remove(p);

    update_themes();
    set_theme_manager(DispBox::theme_manager);

    if (do_prompt)
	prompt();
}

void DataDisp::toggle_themeSQ(const string& theme, const string& pattern,
			      bool verbose, bool do_prompt)
{
    string t = theme;
    strip_space(t);

    string p = pattern;
    strip_space(p);

    const StringArray& patterns = DispBox::theme_manager.pattern(t).patterns();
    for (int i = 0; i < patterns.size(); i++)
	if (patterns[i] == p)
	{
	    // Pattern already applied
	    unapply_themeSQ(theme, pattern, verbose, do_prompt);
	    return;
	}

    apply_themeSQ(theme, pattern, verbose, do_prompt);
}

//-----------------------------------------------------------------------------
// Button Callbacks
//-----------------------------------------------------------------------------

void DataDisp::dereferenceCB(Widget w, XtPointer client_data, 
			     XtPointer call_data)
{
    set_last_origin(w);

    DispNode *disp_node_arg   = selected_node();
    DispValue *disp_value_arg = selected_value();
    if (disp_node_arg == 0 || disp_value_arg == 0)
    {
	newCB(w, client_data, call_data);
	return;
    }

    string display_expression = disp_value_arg->dereferenced_name();
    disp_value_arg->dereference();
    disp_node_arg->refresh();

    string depends_on;
    if (gdb->recording())
	depends_on = disp_node_arg->name();
    else
	depends_on = itostring(disp_node_arg->disp_nr());

    new_display(display_expression, 0, depends_on, false, false, w);
}

// Replace node by its dereferenced variant
void DataDisp::dereferenceInPlaceCB(Widget w, XtPointer, XtPointer)
{
    DispNode *disp_node_arg   = selected_node();
    DispValue *disp_value_arg = selected_value();
    if (disp_node_arg == 0 || disp_value_arg == 0)
	return;

    string display_expression = disp_value_arg->dereferenced_name();

    static BoxPoint p;
    p = disp_node_arg->pos();
    new_display(display_expression, &p, "", false, false, w);

    IntArray nrs;
    nrs += disp_node_arg->disp_nr();
    delete_display(nrs, w);
}

void DataDisp::dereferenceArgCB(Widget w, XtPointer client_data, 
				XtPointer call_data)
{
    if (selected_value() != 0)
    {
	dereferenceCB(w, client_data, call_data);
	return;
    }

    new_display(deref(source_arg->get_string()), 0, "", false, false, w);
}

void DataDisp::toggleDetailCB(Widget dialog,
			      XtPointer client_data,
			      XtPointer call_data)
{
    if (gdb->recording())
    {
	showDetailCB(dialog, client_data, call_data);
	return;
    }

    int depth = (int)(long)client_data;

    set_last_origin(dialog);

    IntArray disable_nrs;
    IntArray enable_nrs;

    bool changed = false;
    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref); 
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	if (selected(dn))
	{
	    DispValue *dv = dn->selected_value();
	    if (dv == 0)
		dv = dn->value();

	    if (dv == 0 || dn->disabled() || dv->collapsedAll() > 0)
	    {
		if (dv != 0)
		{
		    // Expand this value
		    dv->collapseAll();
		    dv->expandAll(depth);
		}

		if (dn->disabled())
		{
		    // Enable display
		    enable_nrs += dn->disp_nr();
		}
		else
		{
		    dn->refresh();
		    changed = true;
		}
	    }
	    else
	    {
		// Collapse this value
		dv->collapse();

		if (dv == dn->value() && dn->enabled())
		{
		    // Disable display
		    disable_nrs += dn->disp_nr();
		}
		else
		{
		    dn->refresh();
		    changed = true;
		}
	    }
	}
    }

    if (enable_nrs.size() > 0)
	enable_display(enable_nrs, dialog);
    else if (disable_nrs.size() > 0)
	disable_display(disable_nrs, dialog);

    if (changed)
	refresh_graph_edit();
}

void DataDisp::showDetailCB (Widget dialog, XtPointer client_data, XtPointer)
{
    int depth = (int)(long)client_data;
    show(dialog, depth, 0);
}

void DataDisp::showMoreDetailCB(Widget dialog, XtPointer client_data, 
				XtPointer)
{
    int more = (int)(long)client_data;
    show(dialog, 0, more);
}

void DataDisp::show(Widget dialog, int depth, int more)
{
    set_last_origin(dialog);

    if (gdb->recording())
    {
	gdb_command("graph enable display " + source_arg->get_string());
	return;
    }

    IntArray disp_nrs;

    bool changed = false;
    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref); 
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	if (selected(dn))
	{
	    if (dn->disabled())
	    {
		// Enable display
		disp_nrs += dn->disp_nr();
	    }
		
	    DispValue *dv = dn->selected_value();
	    if (dv == 0)
		dv = dn->value();
	    if (dv == 0)
		continue;

	    if (more != 0)
		depth = dv->heightExpanded() + more;

	    if (depth > 0 || dv->collapsedAll() > 0)
	    {
		dv->collapseAll();
		dv->expandAll(depth);
		dn->refresh();

		// Mark as enabled right now; this way, the
		// `enable display' command won't re-expand it.
		dn->enable();
		changed = true;
	    }
	}
    }

    enable_display(disp_nrs, dialog);

    if (changed)
	refresh_graph_edit();
}



void DataDisp::hideDetailCB (Widget dialog, XtPointer, XtPointer)
{
    set_last_origin(dialog);

    if (gdb->recording())
    {
	gdb_command("graph disable display " + source_arg->get_string());
	return;
    }

    IntArray disp_nrs;

    bool changed = false;
    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref); 
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	if (selected(dn))
	{
	    DispValue *dv = dn->selected_value();
	    if (dv == 0)
		dv = dn->value();

	    if ((dv == 0 || dv == dn->value()) && dn->enabled())
	    {
		// Disable display
		disp_nrs += dn->disp_nr();
	    }

	    if (dv != 0 && dv->expanded())
	    {
		dv->collapse();
		dn->refresh();
		changed = true;
	    }
	}
    }

    disable_display(disp_nrs, dialog);

    if (changed)
	refresh_graph_edit();
}


void DataDisp::rotate_value(DispValue *dv, bool all)
{
    if (dv == 0)
	return;

    switch (dv->orientation())
    {
    case Horizontal:
	dv->set_orientation(Vertical);
	dv->set_member_names(true);
	break;
    
    case Vertical:
	dv->set_orientation(Horizontal);
	dv->set_member_names(false);
	break;
    }

    if (all)
	for (int i = 0; i < dv->nchildren(); i++)
	    rotate_value(dv->child(i), all);
}

void DataDisp::rotate_node(DispNode *dn, bool all)
{
    DispValue *dv = dn->selected_value();
    if (dv == 0)
	dv = dn->value();
    if (dv == 0)
	return;

    rotate_value(dv, all);

    if (dv->type() == Simple)
    {
	// We have rotated a scalar value in a plot.  Replot.
	if (dn->clustered())
	{
	    DispNode *cluster = disp_graph->get(dn->clustered());
	    if (cluster != 0 && cluster->value() != 0)
		cluster->value()->replot();
	}
	else if (dn->value() != 0)
	{
	    dn->value()->replot();
	}
    }

    dn->refresh();
}

void DataDisp::rotateCB(Widget w, XtPointer client_data, XtPointer)
{
    bool rotate_all = bool(client_data);

    set_last_origin(w);

    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref); 
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	if (selected(dn))
	    rotate_node(dn, rotate_all);
    }

    refresh_graph_edit();
}

void DataDisp::toggleDisableCB (Widget dialog, XtPointer, XtPointer)
{
    set_last_origin(dialog);
    IntArray disp_nrs;

    bool do_enable  = true;
    bool do_disable = true;

    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref); 
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	if (selected(dn))
	{
	    disp_nrs += dn->disp_nr();
	    if (dn->enabled())
		do_enable = false;
	    if (dn->disabled())
		do_disable = false;
	}
    }

    if (do_enable)
	enable_display(disp_nrs, dialog);
    else if (do_disable)
	disable_display(disp_nrs, dialog);
}

void DataDisp::select_with_all_descendants(GraphNode *node)
{
    bool selected = node->selected();

    DispNode *dn = ptr_cast(DispNode, node);
    if (dn != 0)
	dn->select(0);

    if (!selected)
    {
	node->selected() = true;
	
	for (GraphEdge *edge = node->firstFrom();
	     edge != 0; edge = node->nextFrom(edge))
	    select_with_all_descendants(edge->to());
    }
}

void DataDisp::select_with_all_ancestors(GraphNode *node)
{
    bool selected = node->selected();

    DispNode *dn = ptr_cast(DispNode, node);
    if (dn != 0)
	dn->select(0);

    if (!selected)
    {
	node->selected() = true;
	
	for (GraphEdge *edge = node->firstTo();
	     edge != 0; edge = node->nextTo(edge))
	    select_with_all_ancestors(edge->from());
    }
}

// Upon deletion, select the ancestor and all siblings
void DataDisp::deleteCB (Widget dialog, XtPointer /* client_data */,
			 XtPointer call_data)
{
    set_last_origin(dialog);

    DispValue *dv = selected_value();
    DispNode  *dn = selected_node();

    if (dn != 0 && dv != dn->value())
    {
	// Display part to be suppressed
	applyThemeCB(dialog, XtPointer(app_data.suppress_theme), call_data);
	return;
    }

    IntArray disp_nrs;
    VarArray<GraphNode *> ancestors;
    VarArray<GraphNode *> descendants;

    MapRef ref;
    for (dn = disp_graph->first(ref); 
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	dv = dn->selected_value();
	if (selected(dn) && (dv == 0 || dv == dn->value()))
	{
	    disp_nrs += dn->disp_nr();

	    // Select all ancestors
	    GraphEdge *edge;
	    for (edge = dn->firstTo(); edge != 0; edge = dn->nextTo(edge))
	    {
		GraphNode *ancestor = edge->from();
		while (ancestor->isHint())
		    ancestor = ancestor->firstTo()->from();

		ancestors += ancestor;
	    }

	    // Select all descendants
	    for (edge = dn->firstFrom(); edge != 0; edge = dn->nextFrom(edge))
	    {
		GraphNode *descendant = edge->to();
		while (descendant->isHint())
		    descendant = descendant->firstFrom()->to();

		descendants += descendant;
	    }
	}
    }

    int i;
    for (i = 0; i < ancestors.size(); i++)
	select_with_all_descendants(ancestors[i]);
    for (i = 0; i < descendants.size(); i++)
	select_with_all_ancestors(descendants[i]);

    delete_display(disp_nrs, dialog);
}

void DataDisp::refreshCB(Widget w, XtPointer, XtPointer)
{
    // Unmerge all displays
    MapRef ref;
    for (int k = disp_graph->first_nr(ref); 
	 k != 0;
	 k = disp_graph->next_nr(ref))
    {
	unmerge_display(k);
    }

    // Refresh them
    refresh_display(w);
}

void DataDisp::selectAllCB(Widget w, XtPointer, XtPointer)
{
    // StatusDelay d("Selecting all displays");

    set_last_origin(w);
    XtCallActionProc(graph_edit, 
		     "select-all", (XEvent *)0, (String *)0, 0);
    refresh_graph_edit();
}

void DataDisp::unselectAllCB(Widget w, XtPointer, XtPointer)
{
    // StatusDelay d("Unselecting all displays");

    set_last_origin(w);
    XtCallActionProc(graph_edit, 
		     "unselect-all", (XEvent *)0, (String *)0, 0);
    refresh_graph_edit();
}

void DataDisp::enableCB(Widget w, XtPointer, XtPointer)
{
    set_last_origin(w);

    IntArray disp_nrs;

    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref); 
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	if (selected(dn) && dn->disabled())
	{
	    disp_nrs += dn->disp_nr();
	}
    }

    enable_display(disp_nrs, w);
}

void DataDisp::disableCB(Widget w, XtPointer, XtPointer)
{
    set_last_origin(w);

    IntArray disp_nrs;

    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref); 
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	if (selected(dn) && dn->enabled())
	{
	    disp_nrs += dn->disp_nr();
	}
    }

    disable_display(disp_nrs, w);
}


void DataDisp::shortcutCB(Widget w, XtPointer client_data, XtPointer)
{
    int number = ((int)(long)client_data) - 1;

    assert (number >= 0);
    assert (number < shortcut_exprs.size());

    set_last_origin(w);

    string expr = shortcut_exprs[number];

    string depends_on = "";
    DispNode *disp_node_arg   = selected_node();
    DispValue *disp_value_arg = selected_value();
    if (disp_node_arg != 0 
	&& disp_value_arg != 0
	&& !disp_node_arg->hidden())
    {
	if (gdb->recording())
	    depends_on = disp_node_arg->name();
	else
	    depends_on = itostring(disp_node_arg->disp_nr());
    }
	
    string arg = source_arg->get_string();

    // Avoid multiple /format specifications
    if (arg.contains('/', 0) && expr.contains('/', 0))
	arg = arg.after(rxwhite);

    expr.gsub("()", arg);

    new_display(expr, 0, depends_on, false, false, w);
}

// Set shortcut menu to expressions EXPRS
void DataDisp::set_shortcut_menu(const StringArray& exprs,
				 const StringArray& labels)
{
    shortcut_labels = labels;
    shortcut_exprs  = exprs;

    while (shortcut_labels.size() < exprs.size())
	shortcut_labels += "";

#if 0
    if (exprs.size() > shortcut_items)
    {
	post_warning("Shortcut menu capacity exceeded.",
		     "too_many_shortcuts_warning", last_origin);
    }
#endif

    for (int i = 0; i < shortcut_items; i++)
    {
	Widget popup1_item = shortcut_popup1[i].widget;
	Widget popup2_item = shortcut_popup2[i].widget;
	Widget menu_item   = shortcut_menu  [i].widget;

	if (i < exprs.size())
	{
	    string& expr  = shortcut_exprs[i];
	    string& label = shortcut_labels[i];

	    if (label.empty())
		label = "Display " + expr;

	    set_label(popup1_item, label);
	    set_label(popup2_item, label);
	    set_label(menu_item,   label);

	    XtManageChild(popup1_item);
	    XtManageChild(popup2_item);
	    XtManageChild(menu_item);
	}
	else
	{
	    // Unmanage widgets
	    XtUnmanageChild(popup1_item);
	    XtUnmanageChild(popup2_item);
	    XtUnmanageChild(menu_item);
	}
    }

    refresh_args();
}

// Add one expr to shortcut menus
void DataDisp::add_shortcut_expr(const string& expr)
{
    // Insert as first item in SHORTCUT_EXPRS
    shortcut_exprs  += string("");
    shortcut_labels += string("");
    for (int i = shortcut_exprs.size() - 1; i > 0; i--)
    {
	shortcut_exprs[i]  = shortcut_exprs[i - 1];
	shortcut_labels[i] = shortcut_labels[i - 1];
    }

    shortcut_exprs[0]  = expr;
    shortcut_labels[0] = "";

    set_shortcut_menu(shortcut_exprs, shortcut_labels);
    refresh_button_editor();
    refresh_args();
}

MString DataDisp::shortcut_help(Widget w)
{
    for (int i = 0; i < shortcut_items; i++)
    {
	if (w == shortcut_menu  [i].widget ||
	    w == shortcut_popup1[i].widget ||
	    w == shortcut_popup2[i].widget)
	{
	    MString ret = rm("Display ");
	    string expr = shortcut_exprs[i];

	    while (expr.contains("()"))
	    {
		ret += tt(expr.before("()"));
		ret += bf("()");
		expr = expr.after("()");
	    }
	    ret += tt(expr);
	    return ret;
	}
    }

    return MString(0, true);	// Not found
}


//-----------------------------------------------------------------------------
// Count displays
//-----------------------------------------------------------------------------

struct DataDispCount {
    int all;			// Total # of displays
    int visible;		// # of non-hidden displays
    int selected;		// # of selected displays
    int selected_expanded;	// # of selected and expanded displays
    int selected_collapsed;	// # of selected and collapsed displays
    int selected_clustered;     // # of selected clustered data displays
    int selected_unclustered;   // # of selected unclustered data displays
    int selected_titles;	// # of selected titles (no display parts)

    DataDispCount(DispGraph *disp_graph);
};

DataDispCount::DataDispCount(DispGraph *disp_graph)
    : all(0), visible(0), selected(0),
      selected_expanded(0),
      selected_collapsed(0),
      selected_clustered(0),
      selected_unclustered(0),
      selected_titles(0)
{

    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref); 
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	all++;

	if (!dn->hidden())
	    visible++;

	if (DataDisp::selected(dn))
	{
	    selected++;

	    if (dn->deferred())
	    {
		selected_titles++;
	    }
	    else
	    {
		DispValue *dv = dn->selected_value();
		if (dv == 0 || dv == dn->value())
		    selected_titles++;

		if (!dn->is_user_command() && !dn->clustered())
		    selected_unclustered++;
		if (!dn->is_user_command() && dn->clustered())
		    selected_clustered++;

		if (dn->disabled())
		{
		    selected_collapsed++;
		}
		else
		{
		    if (dv == 0)
			dv = dn->value();

		    if (dv != 0)
		    {
			selected_expanded  += int(dv->expanded());
			selected_collapsed += dv->collapsedAll();
		    }
		}
	    }
	}
    }
}


//-----------------------------------------------------------------------------
// Double click callback
//-----------------------------------------------------------------------------

void DataDisp::DoubleClickCB(Widget w, XtPointer, XtPointer call_data)
{
    GraphEditPreSelectionInfo *info = (GraphEditPreSelectionInfo *)call_data;

    if (!info->double_click)
	return;			// Single click

    if (info->node == 0)
	return;			// Double-click on background

    DispNode *disp_node_arg = ptr_cast(DispNode, info->node);
    if (disp_node_arg == 0)
	disp_node_arg = selected_node();
    if (disp_node_arg == 0)
	return;

    XEvent *ev = info->event;
    bool control = (ev != 0 && 
		    (ev->type == ButtonPress || ev->type == ButtonRelease) &&
		    (ev->xbutton.state & ControlMask) != 0);

    // Do the right thing
    if (disp_node_arg->disabled())
    {
	showMoreDetailCB(w, XtPointer(1), 0); // Show 1 level more
    }
    else
    {
	DispValue *disp_value_arg = disp_node_arg->selected_value();
	if (disp_value_arg == 0)
	    return;			// No selected value within node

	DataDispCount count(disp_graph);
	
	if (disp_value_arg->type() == Pointer && !disp_value_arg->collapsed())
	{
	    // Dereference
	    if (control)
		dereferenceInPlaceCB(w, XtPointer(true), 0);
	    else
		dereferenceCB(w, 0, 0);
	}
	else if (count.selected_collapsed > 0)
	{
	    // Show 1 level more
	    showMoreDetailCB(w, XtPointer(1), 0);
	}
	else
	{
	    // Hide all
	    hideDetailCB(w, XtPointer(-1), 0);
	}
    }

    // Don't do the default action
    info->doit = False;
}


//-----------------------------------------------------------------------------
// Popup menu callbacks
//-----------------------------------------------------------------------------

void DataDisp::popup_new_argCB (Widget    display_dialog,
				XtPointer client_data,
				XtPointer)
{
    set_last_origin(display_dialog);

    BoxPoint *p = (BoxPoint *) client_data;
    new_display(source_arg->get_string(), p, "", false, false, display_dialog);
}


void DataDisp::popup_newCB (Widget    display_dialog,
			    XtPointer client_data,
			    XtPointer)
{
    set_last_origin(display_dialog);

    BoxPoint *p = (BoxPoint *) client_data;
    new_displayCD(display_dialog, *p);
}




//-----------------------------------------------------------------------------
// Entering new Data Displays
//-----------------------------------------------------------------------------

class NewDisplayInfo {
public:
    string display_expression;
    string scope;
    StringArray display_expressions;
    BoxPoint point;
    BoxPoint *point_ptr;
    string depends_on;
    Widget origin;
    Widget shortcut;
    Widget text;
    bool verbose;
    bool prompt;
    bool constant;
    DeferMode deferred;
    bool clustered;
    bool plotted;
    bool create_cluster;
    string cluster_name;
    static int cluster_nr;
    static int cluster_offset;

    NewDisplayInfo()
	: display_expression(),
	  scope(),
	  display_expressions(),
	  point(),
	  point_ptr(0),
	  depends_on(),
	  origin(0),
	  shortcut(0),
	  text(0),
	  verbose(false),
	  prompt(false),
	  constant(false),
	  deferred(DeferNever),
	  clustered(false),
	  plotted(false),
	  create_cluster(false),
	  cluster_name()
    {}

    ~NewDisplayInfo()
    {}

    NewDisplayInfo(const NewDisplayInfo& info)
	: display_expression(info.display_expression),
	  scope(info.scope),
	  display_expressions(info.display_expressions),
	  point(info.point),
	  point_ptr(info.point_ptr),
	  depends_on(info.depends_on),
	  origin(info.origin),
	  shortcut(info.shortcut),
	  text(info.text),
	  verbose(info.verbose),
	  prompt(info.prompt),
	  constant(info.constant),
	  deferred(info.deferred),
	  clustered(info.clustered),
	  plotted(info.plotted),
	  create_cluster(info.create_cluster),
	  cluster_name(info.cluster_name)
    {}

private:
    NewDisplayInfo& operator = (const NewDisplayInfo&);
};

int NewDisplayInfo::cluster_nr     = 0;
int NewDisplayInfo::cluster_offset = 0;

void DataDisp::new_displayDCB (Widget dialog, XtPointer client_data, XtPointer)
{
    set_last_origin(dialog);

    NewDisplayInfo *info = (NewDisplayInfo *)client_data;

    char *inp = XmTextFieldGetString(info->text);
    string expr(inp);
    XtFree(inp);

    strip_leading_space(expr);
    strip_trailing_space(expr);

    if (!expr.empty())
    {
	new_display(expr, info->point_ptr, info->depends_on, info->clustered,
		    info->plotted, info->origin);

	if (info->shortcut != 0 && XmToggleButtonGetState(info->shortcut))
	{
	    // Add expression to shortcut menu
	    expr.gsub("()", "( )");
	    if (expr != info->display_expression)
		expr.gsub(info->display_expression, string("()"));
	    add_shortcut_expr(expr);
	}
    }
}

Widget DataDisp::create_display_dialog(Widget parent, const _XtString name,
				       NewDisplayInfo& info)
{
    Arg args[10];
    int arg = 0;

    Widget dialog = verify(XmCreatePromptDialog(find_shell(parent),
						XMST(name), args, arg));
    Delay::register_shell(dialog);

    if (lesstif_version <= 79)
	XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_APPLY_BUTTON));
    XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_TEXT));
    XtUnmanageChild(XmSelectionBoxGetChild(dialog, XmDIALOG_SELECTION_LABEL));

    XtAddCallback(dialog, XmNhelpCallback, ImmediateHelpCB, 0);
    XtAddCallback(dialog, XmNokCallback, new_displayDCB, XtPointer(&info));

    arg = 0;
    XtSetArg(args[arg], XmNmarginWidth,  0); arg++;
    XtSetArg(args[arg], XmNmarginHeight, 0); arg++;
    XtSetArg(args[arg], XmNborderWidth,  0); arg++;
    XtSetArg(args[arg], XmNadjustMargin, False); arg++;
    Widget box = verify(XmCreateRowColumn(dialog, XMST("box"), args, arg));
    XtManageChild(box);

    arg = 0;
    XtSetArg(args[arg], XmNalignment, XmALIGNMENT_BEGINNING); arg++;
    Widget label = verify(XmCreateLabel(box, XMST("label"), args, arg));
    XtManageChild(label);

    arg = 0;
    info.text = verify(CreateComboBox(box, "text", args, arg));
    XtManageChild(info.text);

    tie_combo_box_to_history(info.text, display_history_filter);

    arg = 0;
    XtSetArg(args[arg], XmNmarginWidth,  0); arg++;
    XtSetArg(args[arg], XmNmarginHeight, 0); arg++;
    XtSetArg(args[arg], XmNborderWidth,  0); arg++;
    XtSetArg(args[arg], XmNadjustMargin, False); arg++;
    XtSetArg(args[arg], XmNorientation, XmHORIZONTAL); arg++;
    Widget box2 = verify(XmCreateRowColumn(box, XMST("box2"), args, arg));
    XtManageChild(box2);

    arg = 0;
    XtSetArg(args[arg], XmNalignment, XmALIGNMENT_BEGINNING); arg++;
    info.shortcut = verify(
	XmCreateToggleButton(box2, XMST("shortcut"), args, arg));
    XtManageChild(info.shortcut);

    Widget display = verify(XmCreateLabel(box2, XMST("display"), args, arg));
    XtManageChild(display);
    Widget menu = verify(XmCreateLabel(box2, XMST("menu"), args, arg));
    XtManageChild(menu);

    return dialog;
}

// Enter a new Display at BOX_POINT
void DataDisp::new_displayCD (Widget w, const BoxPoint &box_point)
{
    static NewDisplayInfo info;
    if (info.point_ptr == 0)
	info.point_ptr = new BoxPoint;
    info.origin = w;

    static Widget new_display_dialog = 
	create_display_dialog(w, "new_display_dialog", info);

    XmToggleButtonSetState(info.shortcut, False, False);

    *(info.point_ptr) = box_point;
    info.display_expression = source_arg->get_string();
    XmTextSetString(info.text, XMST(info.display_expression.chars()));

    manage_and_raise(new_display_dialog);
}

// Create a new display
void DataDisp::newCB(Widget w, XtPointer, XtPointer)
{
    set_last_origin(w);
    new_displayCD(w);
}

// Create a new dependent display
void DataDisp::dependentCB(Widget w, XtPointer client_data, 
			   XtPointer call_data)
{
    set_last_origin(w);

    DispNode *disp_node_arg   = selected_node();
    DispValue *disp_value_arg = selected_value();
    if (disp_node_arg == 0 
	|| disp_value_arg == 0
	|| disp_node_arg->hidden())
    {
	newCB(w, client_data, call_data);
	return;
    }

    static NewDisplayInfo info;
    if (gdb->recording())
	info.depends_on = disp_node_arg->name();
    else
	info.depends_on = itostring(disp_node_arg->disp_nr());

    info.origin = w;

    static Widget dependent_display_dialog = 
	create_display_dialog(w, "dependent_display_dialog", info);

    XmToggleButtonSetState(info.shortcut, True, False);

    info.display_expression = disp_value_arg->full_name();
    XmTextSetString(info.text, XMST(info.display_expression.chars()));
    manage_and_raise(dependent_display_dialog);
}

void DataDisp::displayArgCB(Widget w, XtPointer client_data, 
			    XtPointer call_data)
{
    bool check_pointer = bool((int)(long)client_data);

    if (check_pointer)
    {
	DispValue *disp_value_arg = selected_value();

	if (disp_value_arg != 0 && disp_value_arg->type() == Pointer)
	{
	    // Dereference selected pointer
	    dereferenceCB(w, client_data, call_data);
	    return;
	}
    }

    // Create new display
    string arg = source_arg->get_string();

    string depends_on = "";
    DispNode *disp_node_arg = selected_node();
    if (disp_node_arg != 0)
    {
	if (gdb->recording())
	    depends_on = disp_node_arg->name();
	else
	    depends_on = itostring(disp_node_arg->disp_nr());
    }

    new_display(arg, 0, depends_on, false, false, w);
}

void DataDisp::plotArgCB(Widget w, XtPointer, XtPointer)
{
    DispValue *disp_value_arg = selected_value();
    if (disp_value_arg != 0)
    {
	// Plot selected value
	disp_value_arg->plot();
	return;
    }

    // Create new display and plot it
    string arg = source_arg->get_string();
    new_display(arg, 0, "", false, true, w);
}

void DataDisp::plotHistoryCB(Widget w, XtPointer, XtPointer)
{
    // Create new display and plot its history
    const string arg = "`graph history " + source_arg->get_string() + "`";
    new_display(arg, 0, "", false, true, w);
}

void DataDisp::deleteArgCB(Widget dialog, XtPointer client_data, 
			   XtPointer call_data)
{
    DataDispCount count(disp_graph);

    if (count.selected_titles > 0)
    {
	// Delete selected displays
	deleteCB(dialog, client_data, call_data);
	return;
    }

    DispValue *dv = selected_value();
    DispNode  *dn = selected_node();

    if (dn != 0 && dv != dn->value())
    {
	// Suppress selected value
	applyThemeCB(dialog, XtPointer(app_data.suppress_theme), call_data);
	return;
    }

    // Delete argument
    delete_display(source_arg->get_string());
}


//-----------------------------------------------------------------------------
// Redraw graph and update display list
//-----------------------------------------------------------------------------

struct GraphEditState {
    Boolean autoLayout;
    Boolean snapToGrid;
};

void DataDisp::refresh_graph_edit(bool silent)
{
    // Save current graph editor state
    static GraphEditState state;

    XtVaGetValues(graph_edit,
		  XtNautoLayout, &state.autoLayout,
		  XtNsnapToGrid, &state.snapToGrid,
		  XtPointer(0));

    if (refresh_graph_edit_timer == 0)
    {
	refresh_graph_edit_timer = 
	    XtAppAddTimeOut(XtWidgetToApplicationContext(graph_edit),
			    0, RefreshGraphEditCB, XtPointer(&state));
    }

    refresh_builtin_user_displays();
    refresh_args();
    refresh_display_list(silent);
}

void DataDisp::RefreshGraphEditCB(XtPointer client_data, XtIntervalId *id)
{
    (void) id;			// Use it

    assert(*id == refresh_graph_edit_timer);
    refresh_graph_edit_timer = 0;

    static GraphEditState state;

    XtVaGetValues(graph_edit,
		  XtNautoLayout, &state.autoLayout,
		  XtNsnapToGrid, &state.snapToGrid,
		  XtPointer(0));

    const GraphEditState& old_state = *((GraphEditState *) client_data);

    static Graph *dummy = new Graph;

    XtVaSetValues(graph_edit,
		  XtNautoLayout, old_state.autoLayout,
		  XtNsnapToGrid, old_state.snapToGrid,
		  XtNgraph, dummy,
		  XtPointer(0));
    XtVaSetValues(graph_edit,
		  XtNgraph, (Graph *)disp_graph,
		  XtPointer(0));
    XtVaSetValues(graph_edit,
		  XtNautoLayout, state.autoLayout,
		  XtNsnapToGrid, state.snapToGrid,
		  XtPointer(0));
}

// ***************************************************************************
//
inline int DataDisp::getDispNrAtPoint (const BoxPoint& point)
{
    GraphNode* gn = graphEditGetNodeAtPoint (graph_edit, point);
    if (gn == 0)
	return 0;

    BoxGraphNode* bgn = ptr_cast (BoxGraphNode, gn);
    if (bgn == 0)
	return 0;

    return disp_graph->get_nr (bgn);
}


//-----------------------------------------------------------------------------
// Make buttons sensitive or insensitive
//-----------------------------------------------------------------------------

void DataDisp::no_displaysHP (void*, void* , void* call_data)
{
    bool empty = bool(call_data);

    set_sensitive (graph_popup[GraphItms::Refresh].widget,
		   (!empty && can_do_gdb_command()));
}


//-----------------------------------------------------------------------------
// Unselect nodes when selection is lost
//-----------------------------------------------------------------------------

bool DataDisp::lose_selection = true;

void DataDisp::SelectionLostCB(Widget, XtPointer, XtPointer)
{
    if (!lose_selection)
	return;

    // Selection lost - clear all highlights
    bool changed = false;
    for (GraphNode *gn = disp_graph->firstNode();
	 gn != 0; gn = disp_graph->nextNode(gn))
    {
	if (gn->selected())
	{
	    gn->selected() = false;
	    changed = true;
	    graphEditRedrawNode(graph_edit, gn);
	}
    }

    if (changed)
    {
	refresh_args();
	refresh_display_list();
    }
}

//-----------------------------------------------------------------------------
// Action procs
//----------------------------------------------------------------------------

void DataDisp::graph_dereferenceAct (Widget w, XEvent*, String*, Cardinal*)
{
    dereferenceCB(w, 0, 0);
}

void DataDisp::graph_detailAct (Widget w, XEvent *, 
				String *params, Cardinal *num_params)
{
    int depth = -1;
    if (params != 0 && num_params != 0 && *num_params >= 1)
	depth = atoi(params[0]);

    toggleDetailCB(w, XtPointer(depth), 0);
}

void DataDisp::graph_rotateAct (Widget w, XEvent*, String*, Cardinal*)
{
    rotateCB(w, XtPointer(false), 0);
}

void DataDisp::graph_dependentAct (Widget w, XEvent*, String*, Cardinal*)
{
    dependentCB(w, 0, 0);
}


Time DataDisp::last_select_time = 0;

// The GraphEdit actions with some data display magic prepended
void DataDisp::call_selection_proc(Widget w,
				   const _XtString name,
				   XEvent* event,
				   String* args,
				   Cardinal num_args,
				   SelectionMode mode)
{
    // Let multi-clicks pass right through
    Time t = time(event);
    if (Time(t - last_select_time) > Time(XtGetMultiClickTime(XtDisplay(w))))
	set_args(point(event), mode);
    last_select_time = t;

    XtCallActionProc(w, name, event, args, num_args);
}

void DataDisp::graph_selectAct (Widget, XEvent* event, String* args, 
				Cardinal* num_args)
{
    call_selection_proc(graph_edit, "select", event, args, *num_args, 
			SetSelection);
}

void DataDisp::graph_select_or_moveAct (Widget, XEvent* event, String* args, 
					Cardinal* num_args)
{
    call_selection_proc(graph_edit, "select-or-move", event, args, *num_args,
			SetSelection);
}

void DataDisp::graph_extendAct (Widget, XEvent* event, String* args, 
				Cardinal* num_args)
{
    call_selection_proc(graph_edit, "extend", event, args, *num_args,
			ExtendSelection);
}

void DataDisp::graph_extend_or_moveAct (Widget, XEvent* event, String* args, 
					Cardinal* num_args)
{
    call_selection_proc(graph_edit, "extend-or-move", event, args, *num_args,
			ExtendSelection);
}

void DataDisp::graph_toggleAct (Widget, XEvent* event, String* args, 
				Cardinal* num_args)
{
    call_selection_proc(graph_edit, "toggle", event, args, *num_args,
			ToggleSelection);
}

void DataDisp::graph_toggle_or_moveAct (Widget, XEvent* event, String* args, 
					Cardinal* num_args)
{
    call_selection_proc(graph_edit, "toggle-or-move", event, args, *num_args,
			ToggleSelection);
}

void DataDisp::graph_popupAct (Widget, XEvent* event, String *args, 
			       Cardinal *num_args)
{
    static BoxPoint* p = 0;
    if (p == 0)
    {
	p = new BoxPoint;

	MMaddCallbacks(graph_popup,     XtPointer(p));
	MMaddCallbacks(node_popup,      XtPointer(p));
	MMaddCallbacks(shortcut_popup1, XtPointer(p));

	MMaddHelpCallback(graph_popup,     ImmediateHelpCB);
	MMaddHelpCallback(node_popup,      ImmediateHelpCB);
	MMaddHelpCallback(shortcut_popup1, ImmediateHelpCB);
    }
    *p = point(event);

    set_args(*p, SetSelection);

    string arg = "";
    if (num_args != 0 && *num_args > 0)
	arg = downcase(args[0]);

    Widget popup = 0;
    if (arg == "graph" || selected_node() == 0)
	popup = graph_popup_w;
    else if (arg == "shortcut" 
	     || (arg.empty() && event->xbutton.state & ShiftMask))
	popup = shortcut_popup_w;
    else if (arg == "node" || arg.empty())
	popup = node_popup_w;
    else
	std::cerr << "graph-popup: bad argument " << quote(arg) << "\n";

    if (popup != 0)
    {
	XmMenuPosition(popup, &event->xbutton);
	XtManageChild(popup);
    }
}

void DataDisp::set_args(const BoxPoint& p, SelectionMode mode)
{
    DispNode*  disp_node   = 0;
    DispValue* disp_value  = 0;

    bool was_selected = false;

    int disp_nr = getDispNrAtPoint(p);
    if (disp_nr)
    {
	disp_node = disp_graph->get (disp_nr);
	disp_value = (DispValue *)disp_node->box()->data(p);

	was_selected = selected(disp_node) && disp_value == 0;

	switch (mode)
	{
	case ExtendSelection:
	case ToggleSelection:
	    if (disp_node == selected_node()
		&& disp_node->selected_value() != 0
		&& disp_value != disp_node->selected_value())
	    {
		// Add another value in this node.  We can't do this,
		// so toggle the entire node.
		disp_node->selected() = false;
		disp_node->select(0);
		graphEditRedrawNode(graph_edit, disp_node);
		break;
	    }
	    // FALL THROUGH

	case SetSelection:
	    if (disp_value != disp_node->selected_value())
	    {
		disp_node->select(disp_value);
		graphEditRedrawNode(graph_edit, disp_node);
	    }
	    break;
	}
    }

    if (mode == SetSelection)
    {
	// Clear other highlights and selections
	MapRef ref;
	for (DispNode* dn = disp_graph->first(ref); 
	     dn != 0;
	     dn = disp_graph->next(ref))
	{
	    if (dn != disp_node)
	    {
		bool redraw = false;

		if (!was_selected)
		{
		    if (!redraw)
			redraw = dn->selected();
		    dn->selected() = false;
		}

		if (!redraw)
		    redraw = (dn->highlight() != 0);
		dn->select(0);

		if (redraw)
		    graphEditRedrawNode(graph_edit, dn);
	    }
	}
    }

    // Themes.
    static StringArray all_themes;
    all_themes = DispBox::theme_manager.themes();
    StringArray current_themes;
    DispValue *dv = selected_value();
    if (dv != 0)
	current_themes = DispBox::theme_manager.themes(dv->full_name());

    int i;
    for (i = 0; i < all_themes.size() && theme_menu[i].widget != 0; i++)
    {
	const string& theme = all_themes[i];
	Widget& button = theme_menu[i].widget;
	XtVaSetValues(button, XmNuserData, theme.chars(), XtPointer(0));

	bool set = false;
	for (int j = 0; j < current_themes.size(); j++)
	    if (theme == current_themes[j])
	    {
		set = true;
		break;
	    }

	XmToggleButtonSetState(button, set, False);

	string doc = vsldoc(theme, DispBox::vsllib_path);
	if (doc.contains("."))
	    doc = doc.before(".");
	else if (doc.empty())
	    doc = theme;
	set_label(button, doc);

	manage_child(button, true);
    }

    for (; theme_menu[i].widget != 0; i++)
    {
	Widget& button = theme_menu[i].widget;
	if (XmIsToggleButton(button))
	    manage_child(button, false);
    }

    refresh_args(true);
    refresh_display_list();
}

DispNode *DataDisp::selected_node()
{
    DispNode *selection = 0;

    // Check for selected nodes
    MapRef ref;
    for (DispNode *dn = disp_graph->first(ref); 
	 dn != 0; dn = disp_graph->next(ref))
    {
	if (!selected(dn))
	    continue;

	if (selection == 0)
	{
	    // First node found
	    selection = dn;
	}
	else if (selection == dn)
	{
	    // Already found
	}
	else if (is_cluster(dn))
	{
	    if (selection->clustered() != dn->disp_nr())
		return 0;	// Node is unclustered or clustered elsewhere

	    // Select cluster
	    selection = dn;
	}
	else if (is_cluster(selection))
	{
	    if (dn->clustered() != selection->disp_nr())
		return 0;	// Node is unclustered or clustered elsewhere

	    // Cluster remains selected
	}
	else if (dn->clustered() == selection->clustered())
	{
	    // Differing nodes in same cluster
	}
	else
	{
	    return 0;		// Differing nodes
	}
    }

    return selection;
}

DispValue *DataDisp::selected_value()
{
    DispNode *dn = selected_node();
    if (dn == 0)
	return 0;
    if (is_cluster(dn))
	return dn->value();

    DispValue *dv = dn->selected_value();
    if (dv != 0)
	return dv;

    // We treat the selected node just like the selected top value
    return dn->value();
}

// Select DV (and nothing else)
void DataDisp::select(DispValue *dv)
{
    bool changed = false;

    MapRef ref;
    for (DispNode *dn = disp_graph->first(ref); 
	 dn != 0; dn = disp_graph->next(ref))
    {
	if (dn->clustered())
	    continue;

	// Check whether DV occurs in DN
	dn->select(dv);

	if (dn->highlight() != 0)
	{
	    if (!dn->selected())
	    {
		// Found it
		dn->selected() = true;
		changed = true;
	    }
	}
	else
	{
	    if (dn->selected())
	    {
		// Did not find it
		dn->selected() = false;
		changed = true;
	    }

	    dn->select(0);
	}
    }

    if (changed)
    {
	refresh_args();
	refresh_graph_edit();
    }
}

void DataDisp::refresh_args(bool update_arg)
{
    if (update_arg)
	arg_needs_update = true;

    if (refresh_args_timer == 0)
    {
	refresh_args_timer = 
	    XtAppAddTimeOut(XtWidgetToApplicationContext(graph_edit),
			    0, RefreshArgsCB, XtPointer(graph_edit));
    }

    // Synchronize node selection with cluster: if cluster is
    // selected, select all contained nodes, too.
    MapRef ref;
    for (DispNode *dn = disp_graph->first(ref); 
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	if (!dn->clustered())
	    continue;

	DispNode *cluster = disp_graph->get(dn->clustered());
	if (cluster == 0)
	    continue;

	Box *old_highlight = dn->highlight();
	bool old_selected  = dn->selected();

	dn->selected() = false;
	if (cluster->selected())
	{
	    // Cluster is selected -- select display, too
	    if (cluster->selected_value() == 0 ||
		cluster->selected_value() == cluster->value())
	    {
		// Entire cluster is selected
		dn->selected() = true;
		dn->select(0);
	    }
	    else
	    {
		// Part of cluster is selected
		dn->select(cluster->selected_value());
		if (dn->highlight() != 0)
		{
		    // Found selected value in display
		    dn->selected() = true;

		    if (dn->selected_value() == dn->value())
		    {
			// Top value is selected
			dn->select(0);
		    }
		}
	    }
	}

	if (dn->selected() != old_selected || dn->highlight() != old_highlight)
	{
	    graphEditRedrawNode(graph_edit, dn);
	}
    }
}

void DataDisp::RefreshArgsCB(XtPointer, XtIntervalId *timer_id)
{
    (void) timer_id;		// Use it
    assert(*timer_id == refresh_args_timer);
    refresh_args_timer = 0;

    DataDispCount count(disp_graph);

    if (count.selected > 1)
    {
	// Clear all local highlights
	MapRef ref;
	for (DispNode* dn = disp_graph->first(ref); 
	     dn != 0;
	     dn = disp_graph->next(ref))
	{
	    bool redraw = (dn->highlight() != 0);

	    dn->select(0);
	    if (redraw)
		graphEditRedrawNode(graph_edit, dn);
	}
    }

    DispNode *disp_node_arg   = selected_node();
    DispValue *disp_value_arg = selected_value();

    // New ()
    set_sensitive(graph_popup[GraphItms::NewArg].widget, !source_arg->empty());

    // Refresh (), Select All ()
    set_sensitive(graph_popup[GraphItms::Refresh].widget,   count.all > 0);
    set_sensitive(graph_popup[GraphItms::SelectAll].widget, count.visible > 0);

    bool dereference_ok = false;
    bool rotate_ok      = false;
    bool rotate_plot_ok = false;

    if (disp_node_arg != 0 && disp_value_arg != 0)
    {
	// We have selected a single node
	switch (disp_value_arg->type())
	{
	case Simple:
	    rotate_plot_ok = disp_value_arg->has_plot_orientation();
	    break;

	case Text:
	case Reference:
	    break;

	case Pointer:
	    dereference_ok = true;
	    break;

	case Sequence:
	    break;

	case Array:
	case List:
	case Struct:
	    rotate_ok = disp_value_arg->expanded();
	    break;

	case UnknownType:
	    assert(0);
	    abort();
	}
    }

    // Earlier state
    bool undoing = undo_buffer.showing_earlier_state();

    // Argument
    bool arg_ok  = false;
    bool plot_ok = false;
    string arg;
    if (disp_node_arg != 0)
    {
	if (disp_value_arg != 0)
	{
	    arg = disp_value_arg->full_name();
	    arg_ok = true;
	    plot_ok = disp_value_arg->can_plot();
	}
    }
    else
    {
	// No node selected
	arg = source_arg->get_string();
	arg_ok = (!arg.empty()) && !is_file_pos(arg);
	plot_ok = arg_ok && !undoing;
    }

    // New
#if 0
    if (count.selected_titles > 0)
    {
	set_label(graph_cmd_area[CmdItms::New].widget,
		  "Undisplay ()", UNDISPLAY_ICON);
    }
    else
#endif
    if (dereference_ok)
    {
	string label("Display " + deref(arg, "()"));
	set_label(graph_cmd_area[CmdItms::New].widget, label, DISPREF_ICON);
    }
    else
    {
	set_label(graph_cmd_area[CmdItms::New].widget,
		  "Display ()", DISPLAY_ICON);
    }

    bool recording = gdb->recording() && emptyCommandQueue();
    bool record_ok = recording && arg_ok;

    set_sensitive(shortcut_menu[ShortcutItms::New2].widget, 
		  arg_ok && !undoing);
    set_sensitive(graph_cmd_area[CmdItms::New].widget, arg_ok && !undoing);
    set_sensitive(display_area[DisplayItms::New].widget, !undoing);

    // Dereference
    set_sensitive(node_popup[NodeItms::Dereference].widget,
		  dereference_ok && !undoing);
    set_sensitive(shortcut_menu[ShortcutItms::Dereference2].widget,
		  (record_ok || dereference_ok || 
		  (count.selected == 0 && arg_ok)) && !undoing);
    set_sensitive(graph_cmd_area[CmdItms::Dereference].widget,
		  (dereference_ok || (count.selected == 0 && arg_ok)) &&
		  !undoing);
    set_sensitive(display_area[DisplayItms::Dereference].widget,
		  dereference_ok && !undoing);

    bool can_dereference = !gdb->dereferenced_expr("").empty();
    manage_child(shortcut_menu[ShortcutItms::Dereference2].widget,
		 can_dereference);

    // Plot
    bool arg_is_displayed = (display_number(source_arg->get_string()) != 0);
    bool can_delete_arg = (count.selected == 0 && arg_is_displayed || 
			   record_ok || 
			   count.selected > 0);
    set_sensitive(graph_cmd_area[CmdItms::Plot].widget, plot_ok);
    set_sensitive(plot_menu[PlotItms::History].widget, can_delete_arg);

    // Rotate
    set_sensitive(node_popup[NodeItms::Rotate].widget,       
		  rotate_ok || rotate_plot_ok);
    set_sensitive(graph_cmd_area[CmdItms::Rotate].widget,    
		  rotate_ok || rotate_plot_ok);
    set_sensitive(rotate_menu[RotateItms::RotateAll].widget, rotate_ok);

    // Show/Hide Detail
    if (recording)
    {
	// Recording
	set_label(node_popup[NodeItms::Detail].widget, "Show All");
	set_label(graph_cmd_area[CmdItms::Detail].widget, 
		  "Show ()", SHOW_ICON);
	set_sensitive(node_popup[NodeItms::Detail].widget, record_ok);
	set_sensitive(graph_cmd_area[CmdItms::Detail].widget, record_ok);
    }
    else if (count.selected_expanded > 0 && count.selected_collapsed == 0)
    {
	// Only expanded displays selected
	set_label(node_popup[NodeItms::Detail].widget, "Hide All");
	set_label(graph_cmd_area[CmdItms::Detail].widget, 
		  "Hide ()", HIDE_ICON);
	set_sensitive(node_popup[NodeItms::Detail].widget, true);
	set_sensitive(graph_cmd_area[CmdItms::Detail].widget, true);
    }
    else if (count.selected_collapsed > 0)
    {
	// Some collapsed displays selected
	set_label(node_popup[NodeItms::Detail].widget, "Show All");
	set_label(graph_cmd_area[CmdItms::Detail].widget, 
		  "Show ()", SHOW_ICON);
	set_sensitive(node_popup[NodeItms::Detail].widget, true);
	set_sensitive(graph_cmd_area[CmdItms::Detail].widget, true);
    }
    else
    {
	// Expanded as well as collapsed displays selected
	set_sensitive(node_popup[NodeItms::Detail].widget, false);
	set_sensitive(graph_cmd_area[CmdItms::Detail].widget, false);
    }

    set_sensitive(display_area[DisplayItms::ShowDetail].widget, 
		  record_ok || count.selected_collapsed > 0);
    set_sensitive(display_area[DisplayItms::HideDetail].widget, 
		  record_ok || count.selected_expanded > 0);

    set_sensitive(detail_menu[DetailItms::ShowMore].widget, 
		  record_ok || count.selected_collapsed > 0);
    set_sensitive(detail_menu[DetailItms::ShowJust].widget, 
		  record_ok || count.selected > 0);
    set_sensitive(detail_menu[DetailItms::ShowDetail].widget, 
		  record_ok || count.selected_collapsed > 0);
    set_sensitive(detail_menu[DetailItms::HideDetail].widget, 
		  record_ok || count.selected_expanded > 0);

    // Delete
    set_sensitive(graph_cmd_area[CmdItms::Delete].widget, can_delete_arg);
    set_sensitive(display_area[DisplayItms::Delete].widget,
		  count.selected > 0);

    // Set
    bool can_set = gdb->has_assign_command() && !undoing;
    bool set_node_ok = 
	disp_node_arg != 0 && 
	(!disp_node_arg->is_user_command() || 
	 disp_value_arg != 0 && disp_value_arg != disp_node_arg->value());
    bool set_arg_ok = (disp_node_arg == 0 && arg_ok && !is_user_command(arg));

    set_sensitive(graph_cmd_area[CmdItms::Set].widget,
		  can_set && (set_arg_ok || set_node_ok));
    set_sensitive(display_area[DisplayItms::Set].widget,
		  can_set && (set_arg_ok || set_node_ok));
    set_sensitive(node_popup[NodeItms::Set].widget, 
		  can_set && set_node_ok);

    // Cluster
    if (count.selected_unclustered > 0 || count.selected_clustered == 0)
    {
	set_label(delete_menu[DeleteItms::Cluster].widget, "Cluster ()");
	set_sensitive(delete_menu[DeleteItms::Cluster].widget, 
		      count.selected_unclustered > 0);
    }
    else
    {
	set_label(delete_menu[DeleteItms::Cluster].widget, "Uncluster ()");
	set_sensitive(delete_menu[DeleteItms::Cluster].widget, true);
    }

    set_sensitive(display_area[DisplayItms::Cluster].widget,
		  count.selected_unclustered > 0);
    set_sensitive(display_area[DisplayItms::Uncluster].widget,
		  count.selected_clustered > 0);

    // Shortcut menu
    int i;
    for (i = 0; i < shortcut_items && i < shortcut_exprs.size(); i++)
    {
	const string& expr = shortcut_exprs[i];
	bool sens = false;
	if (!expr.contains("()"))
	    sens = true;	// Argument not needed
	else if (arg_ok)
	    sens = true;	// We have an argument
	else if (count.selected == 0)
	    sens = false;	// Nothing selected
	else if (disp_value_arg != 0)
	    sens = true;	// Exactly one value selected
 	else if (disp_node_arg != 0)
	    sens = true;	// Exactly one expression selected

	if (undoing)
	    sens = false;

	set_sensitive(shortcut_popup1[i].widget, sens);
	set_sensitive(shortcut_popup2[i].widget, sens);
	set_sensitive(shortcut_menu  [i].widget, sens);
    }

    // Argument field
    if (arg_needs_update)
    {
	if (count.selected > 0)
	{
	    string arg;
	    if (disp_value_arg)
	    {
		arg = disp_value_arg->full_name();
		source_arg->set_string(arg);
	    }
	    else if (disp_node_arg)
	    {
		arg = disp_node_arg->name();
		source_arg->set_string(arg);
	    }
	}
	arg_needs_update = false;
    }

    // Set selection.
    // If the entire graph is selected, include position info, too.
    bool include_position = (count.selected >= count.visible);
    std::ostringstream os;
    get_selection(os, include_position);
    const string cmd(os);

    // Setting the string causes the selection to be lost.  By setting
    // LOSE_SELECTION, we make sure the associated callbacks return
    // immediately.
    lose_selection = false;
    XmTextSetString(graph_selection_w, XMST(cmd.chars()));
    lose_selection = true;

    Time tm = XtLastTimestampProcessed(XtDisplay(graph_selection_w));

    if (cmd.empty())
    {
	// Nothing selected - clear selection explicitly
	XmTextClearSelection(graph_selection_w, tm);
    }
    else
    {
	// Own the selection
	TextSetSelection(graph_selection_w, 
			 0, XmTextGetLastPosition(graph_selection_w), tm);
    }
}


// The maximum display number when saving states
int DataDisp::max_display_number = 99;

// Get scopes in SCOPES
bool DataDisp::get_scopes(StringArray& scopes)
{
    // Fetch current backtrace and store scopes in SCOPES
    string backtrace = gdb_question(gdb->where_command(), -1, true);
    while (!backtrace.empty())
    {
	string scope = get_scope(backtrace);
	if (!scope.empty())
	    scopes += scope;
	backtrace = backtrace.after('\n');
    }

    return scopes.size() > 0;
}

// Write commands to restore frame #TARGET_FRAME in OS
void DataDisp::write_frame_command(std::ostream& os, int& current_frame, 
				   int target_frame)
{
    if (target_frame != current_frame)
    {
	os << "graph ";
	if (gdb->has_frame_command())
	{
	    // Issue `frame' command
	    os << gdb->frame_command(target_frame) << "\n";
	}
	else
	{
	    // Use `up' and `down' commands
	    int offset = current_frame - target_frame;
	    if (offset == -1)
		os << "up";
	    else if (offset < 0)
		os << "up " << -offset;
	    else if (offset == 1)
		os << "down";
	    else if (offset > 0)
		os << "down " << offset;
	}

	current_frame = target_frame;
    }
}

// Write commands to restore scope of DN in OS
void DataDisp::write_restore_scope_command(std::ostream& os,
					   int& current_frame,
					   const StringArray& scopes,
					   DispNode *dn,
					   const bool& ok)
{
    if (dn->deferred())
    {
	// No need to restore frame for a deferred node
	return;
    }
    if (dn->is_user_command())
    {
	// No need to restore frame for user displays
	return;
    }

    int target_frame = -1;

    if (dn->scope().empty())
    {
	// No scope - maybe a global?
	target_frame = scopes.size() - 1;	// Return to main frame
    }
    else
    {
	// Search scope in backtrace
	for (int i = 0; i < scopes.size(); i++)
	    if (scopes[i] == dn->scope())
	    {
		target_frame = i;
		break;
	    }
    }

    if (target_frame < 0)
    {
	// Not in current backtrace - deferring display
	MString msg;
	msg += rm("Deferring display ");
	msg += rm(itostring(dn->disp_nr()) + ": ");
	msg += tt(dn->name());

	if (!(dn->scope().empty()))
	{
	   msg += rm(" because ");
	   msg += tt(dn->scope());
	   msg += rm(" is not in current backtrace");
	}

	set_status_mstring(msg);

	// OK remains set here - this is no fatal error
	(void) ok;

	return;
    }

    write_frame_command(os, current_frame, target_frame);
}


void DataDisp::get_node_state(std::ostream& os, DispNode *dn, bool include_position)
{
    os << "graph ";
    if (dn->plotted())
	os << "plot ";
    else
	os << "display ";
    os << dn->name();

    // Write cluster
    if (dn->clustered())
	os << " clustered";

    // Write position
    if (include_position)
    {
	BoxPoint pos = dn->pos();

	if (pos.isValid())
	{
	    if (bump_displays && is_cluster(dn))
	    {
		// When this cluster will be restored, it will be
		// empty first, but later additions will bump it
		// to a new position.  Compensate for this.
		static DispNode empty_cluster(-1, dn->name(), 
					      dn->scope(), "No displays.", 
					      false);

		BoxPoint offset = 
		    (dn->box()->size() - empty_cluster.box()->size()) / 2;
		    
		pos = graphEditFinalPosition(graph_edit, pos - offset);
	    }

	    os << " at " << pos;
	}
    }

    // Write dependency
    string depends_on = "";
    if (dn->deferred())
    {
	depends_on = dn->depends_on();
    }
    else
    {
	for (GraphEdge *edge = dn->firstTo();
	     edge != 0; edge = dn->nextTo(edge))
	{
	    DispNode *ancestor = ptr_cast(DispNode, edge->from());
	    if (ancestor != 0)
	    {
		depends_on = ancestor->name();
		break;
	    }
	}
    }
    if (!depends_on.empty())
	os << " dependent on " << depends_on;

    // Write scope
    if (!(dn->scope().empty()))
	os << " now or when in " << dn->scope();

    os << '\n';
}

bool DataDisp::get_state(std::ostream& os,
			 bool restore_state,
			 bool include_position,
			 const StringArray& scopes,
			 int target_frame)
{
    // Sort displays by number, such that old displays appear before
    // newer ones.

    // Note: this fails with the negative numbers of user-defined
    // displays; a topological sort would make more sense here. (FIXME)
    IntArray nrs;
    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref); 
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	if (restore_state || selected(dn))
	    nrs += dn->disp_nr();
    }
    sort(nrs, absolute_le);

    bool ok = true;
    if (restore_state && scopes.size() == 0 && need_core_to_restore())
    {
	set_status("Cannot get current backtrace");
	ok = false;
    }

    // When restoring, we'll be at the lowest frame (#0).
    int current_frame = 0;

    for (int i = 0; i < nrs.size(); i++)
    {
	DispNode *dn = disp_graph->get(nrs[i]);
	if (dn == 0)
	    continue;

	if (restore_state && scopes.size() > 0)
	    write_restore_scope_command(os, current_frame, scopes, dn, ok);

	get_node_state(os, dn, include_position);
    }

    // That's it: return to target frame...
    write_frame_command(os, current_frame, target_frame);

    // ... and refresh the graph.
    if (restore_state && nrs.size() > 0)
	os << refresh_display_cmd() << "\n";

    return ok;
}


// Return true if a core dump is needed to restore displays
bool DataDisp::need_core_to_restore()
{
    MapRef ref;
    for (DispNode *dn = disp_graph->first(ref);
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
      if (!dn->deferred() && !(dn->scope().empty()))
	    return true;
    }

    return false;
}

// Reset all
void DataDisp::reset_done(const string& answer, void *)
{
    // Nothing special yet
    (void) answer;
}

void DataDisp::reset()
{
    // Clear all data displays
    IntArray display_nrs;
    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref); 
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	display_nrs += dn->disp_nr();
    }

    if (display_nrs.size() > 0)
    {
	Command c(delete_display_cmd(display_nrs));
	c.verbose  = false;
	c.prompt   = false;
	c.check    = true;
	c.priority = COMMAND_PRIORITY_INIT;
	c.callback = reset_done;
	gdb_command(c);
    }
}


// Return number of display aliased by NODE
int DataDisp::alias_display_nr(GraphNode *node)
{
    if (!node->isHint())
	return 0;
    AliasGraphEdge *edge = ptr_cast(AliasGraphEdge, node->firstTo());
    if (edge == 0)
	return 0;

    return edge->disp_nr();
}

// Update graph editor selection after a change in the display editor
void DataDisp::UpdateGraphEditorSelectionCB(Widget, XtPointer, XtPointer)
{
    IntArray display_nrs;
    getItemNumbers(display_list_w, display_nrs);

    // Update graph editor selection
    MapRef ref;
    DispNode *dn;
    for (dn = disp_graph->first(ref); dn != 0; dn = disp_graph->next(ref))
    {
	int display_nr = dn->disp_nr();

	bool select = false;
	for (int i = 0; i < display_nrs.size(); i++)
	{
	    if (display_nr == display_nrs[i])
	    {
		select = true;
		break;
	    }
	}

	if (dn->clustered())
	{
	    // Synchronize nodes with cluster
	    DispNode *cluster = disp_graph->get(dn->clustered());
	    if (cluster != 0 && cluster->selected() && 
		cluster->selected_value() == 0)
	    {
		// All cluster nodes are selected
		select = true;
		dn->select();
	    }
	}

	if (select != dn->selected())
	{
	    dn->selected() = select;
	    graphEditRedrawNode(graph_edit, dn);
	}

	if (dn->hidden())
	{
	    // Synchronize hint nodes with this alias node
	    for (GraphNode *node = disp_graph->firstNode();
		 node != 0;
		 node = disp_graph->nextNode(node))
	    {
		if (alias_display_nr(node) == display_nr)
		{
		    if (node->selected() != dn->selected())
		    {
			node->selected() = dn->selected();
			graphEditRedrawNode(graph_edit, node);
		    }
		}
	    }
	}
    }

    // Update cluster selection
    for (dn = disp_graph->first(ref); dn != 0; dn = disp_graph->next(ref))
    {
	// Clear all local cluster selections
	if (is_cluster(dn) && dn->selected_value() != 0)
	{
	    dn->select();
	    graphEditRedrawNode(graph_edit, dn);
	}
    }

    // Reset local cluster selections
    for (dn = disp_graph->first(ref); dn != 0; dn = disp_graph->next(ref))
    {
	if (!dn->selected())
	    continue;
	if (!dn->clustered())
	    continue;

	DispNode *cluster = disp_graph->get(dn->clustered());

	if (cluster == 0)
	    continue;		// No such cluster

	if (cluster->selected() && cluster->selected_value() == 0)
	    continue;		// Entire cluster selected

	DispValue *dv = cluster->value();
	if (dv == 0)
	    continue;

	// Find the display numbers in this cluster
	IntArray cluster_children;
	MapRef ref;
	for (DispNode *dn2 = disp_graph->first(ref); 
	     dn2 != 0; dn2 = disp_graph->next(ref))
	{
	    if (dn2->clustered() == cluster->disp_nr())
		cluster_children += dn2->disp_nr();
	}
	sort(cluster_children);

	// The number of children of the main cluster List should be
	// the same as the number of displays we just found.
	assert(dv->nchildren() == cluster_children.size());

	for (int i = 0; i < cluster_children.size(); i++)
	{
	    if (cluster_children[i] == dn->disp_nr())
	    {
		// Got it
		cluster->selected() = true;
		if (cluster->selected_value() == 0)
		{
		    // Select this child
		    cluster->select(dv->child(i));
		}
		else
		{
		    // Multiple nodes selected -- select them all
		    cluster->select(0);
		}
		break;
	    }
	}

	graphEditRedrawNode(graph_edit, cluster);
    }

    refresh_args(true);
    refresh_display_list();
}

// Update display editor selection after a change in the graph editor
void DataDisp::UpdateDisplayEditorSelectionCB(Widget, XtPointer, XtPointer)
{
    // Synchronize alias nodes with hint nodes
    for (GraphNode *node = disp_graph->firstNode();
	 node != 0;
	 node = disp_graph->nextNode(node))
    {
	int nr = alias_display_nr(node);
	if (nr < 0)
	    continue;

	DispNode *dn = disp_graph->get(nr);
	if (dn == 0)
	    continue;

	if (node->selected() != dn->selected())
	{
	    dn->selected() = node->selected();
	    graphEditRedrawNode(graph_edit, dn);
	}
    }

    refresh_args(true);
    refresh_display_list();
}


//-----------------------------------------------------------------------
// Sorting nodes for layout
//-----------------------------------------------------------------------

void DataDisp::CompareNodesCB(Widget, XtPointer, XtPointer call_data)
{
    GraphEditCompareNodesInfo *info = (GraphEditCompareNodesInfo *)call_data;

    BoxGraphNode *node1 = ptr_cast(BoxGraphNode, info->node1);
    BoxGraphNode *node2 = ptr_cast(BoxGraphNode, info->node2);

    DispNode *disp1 = 0;
    DispNode *disp2 = 0;

    if (node1 != 0 && node2 != 0)
    {
	int nr1 = disp_graph->get_nr(node1);
	int nr2 = disp_graph->get_nr(node2);

	disp1 = disp_graph->get(nr1);
	disp2 = disp_graph->get(nr2);
    }

    if (disp1 != 0 && disp2 != 0)
    {
	info->result = smart_compare(disp1->name(), disp2->name());
    }
    else
    {
	if (disp1 != 0)
	{
	    // Known nodes are ``larger'' than unknown nodes
	    info->result = 1;
	}
	else if (disp2 != 0)
	{
	    // Known nodes are ``larger'' than unknown nodes
	    info->result = -1;
	}
	else
	{
	    // Comparing the pointer values will keep pointers constant
	    info->result = long(info->node1) - long(info->node2);
	}
    }

#if LOG_COMPARE
    if (disp1 && disp2)
    {
	std::clog << "Comparing " << disp1->name() 
		  << " and " << disp2->name()
		  << " yields " << info->result << "\n";
    }
    else
    {
	std::clog << "Cannot compare: unknown nodes\n";
    }
#endif
}


//-----------------------------------------------------------------------
// Handle GDB input / output
//-----------------------------------------------------------------------

//-----------------------------------------------------------------------------
// Create new Display(s)
//-----------------------------------------------------------------------------

#if RUNTIME_REGEX
static regex rxmore_than_one ("-?[0-9]+\\.\\.-?[0-9]+");
#endif

void DataDisp::again_new_displaySQ (XtPointer client_data, XtIntervalId *)
{
    NewDisplayInfo *info = (NewDisplayInfo *)client_data;
    new_displaySQ(info->display_expression, info->scope, info->point_ptr, 
		  info->depends_on, info->deferred, info->clustered,
		  info->plotted, info->origin, info->verbose, info->prompt);
    delete info;
}

int DataDisp::display_number(const string& name, bool verbose)
{
    int nr = disp_graph->get_by_name(name);

    if (nr == 0)
    {
	if (verbose)
	    post_gdb_message("No display named " + quote(name) + ".\n");
	return 0;
    }

    DispNode *dn = disp_graph->get(nr);
    if (dn == 0)
    {
	if (verbose)
	    post_gdb_message("No display number " + itostring(nr) + ".\n");
	return 0;
    }

    return nr;
}

void DataDisp::get_display_numbers(const string& name, IntArray& numbers)
{
    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref); dn != 0;
	 dn = disp_graph->next(ref))
    {
	if (dn->name() == name)
	    numbers += dn->disp_nr();
    }
}

void DataDisp::new_displaySQ (const string& display_expression,
			      const string& scope, BoxPoint *p,
			      const string& depends_on,
			      DeferMode deferred, 
			      bool clustered, bool plotted,
			      Widget origin, bool verbose, bool do_prompt)
{
    CommandGroup cg;

    // Check arguments
    if (deferred != DeferAlways && !depends_on.empty())
    {
	int depend_nr = display_number(depends_on, verbose);
	if (depend_nr == 0)
	    return;
    }

    NewDisplayInfo info;
    info.display_expression = display_expression;
    info.scope              = scope;
    info.verbose            = verbose;
    info.prompt             = do_prompt;
    info.deferred           = deferred;
    info.clustered          = clustered;
    info.plotted            = plotted;

    if (p != 0)
    {
	info.point = *p;
	info.point_ptr = &info.point;
    }
    else
    {
	info.point = BoxPoint();
	info.point_ptr = 0;
    }
    info.depends_on = depends_on;
    info.origin     = origin;

    static Delay *reading_delay = 0;
    if (!DispBox::vsllib_initialized)
    {
	// We don't have the VSL library yet.  Try again later.
	if (VSLLib::background != 0)
	{
	    reading_delay = new StatusDelay("Reading VSL library");

	    // Disable background processing
	    VSLLib::background = 0;
	}

	// As soon as the VSL library will be completely read, we
	// shall enter the main DDD event loop and get called again.
	XtAppAddTimeOut(XtWidgetToApplicationContext(graph_edit),
			100, again_new_displaySQ, 
			new NewDisplayInfo(info));
	return;
    }
    delete reading_delay;
    reading_delay = 0;

    if (origin)
	set_last_origin(origin);

    if (display_expression.empty())
	return;

    if (deferred == DeferAlways)
    {
	// Create deferred display now
	DispNode *dn = new_deferred_node(display_expression, scope, 
					 info.point, depends_on, 
					 info.clustered, info.plotted);

	// Insert deferred node into graph
	disp_graph->insert(dn->disp_nr(), dn);

	if (do_prompt)
	    prompt();

	refresh_display_list();
    }
    else if (is_user_command(display_expression))
    {
	// User-defined display
	string cmd = user_command(display_expression);
	if (is_builtin_user_command(cmd))
	{
	    info.constant = true;
	    string answer = builtin_user_command(cmd);
	    new_user_displayOQC(answer, new NewDisplayInfo(info));
	}
	else
	{
	    gdb_command(cmd, last_origin, new_user_displayOQC, 
			new NewDisplayInfo(info));
	}
    }
    else
    {
	// Data display
	StringArray expressions;
	int ret = unfold_expressions(display_expression, expressions);
	if (ret || expressions.size() == 0)
	    return;

	info.cluster_nr     = 0;
	info.cluster_offset = 0;

	if (expressions.size() > 1)
	{
	    // Cluster multiple values
	    info.create_cluster = true;
	    info.cluster_name   = display_expression;
	    info.prompt         = false;
	}

	for (int i = 0; i < expressions.size(); i++)
	{
	    if (do_prompt && i == expressions.size() - 1)
		info.prompt = true;

	    NewDisplayInfo *infop = new NewDisplayInfo(info);

	    if (gdb->display_prints_values())
	    {
		gdb_command(gdb->display_command(expressions[i]),
			    last_origin, new_data_displayOQC, infop);
	    }
	    else
	    {
		// The cluster is created after the last expression.
		// Be sure to specify the correct display number
		info.cluster_offset = expressions.size() - 1;

		gdb_command(gdb->display_command(expressions[i]),
			    last_origin, OQCProc(0), (void *)0);
		gdb_command(gdb->print_command(expressions[i], true),
			    last_origin, new_data_displayOQC, infop);
	    }

	    info.create_cluster = false;
	}
    }
}

// Find all expressions in DISPLAY_EXPRESSIONS, using FROM..TO syntax
int DataDisp::unfold_expressions(const string& display_expression,
				 StringArray& expressions)
{
    if (!display_expression.contains(rxmore_than_one))
    {
	expressions += display_expression;
	return 0;
    }

    string prefix  = display_expression.before(rxmore_than_one);
    string postfix = display_expression.after(rxmore_than_one);
    string range   = display_expression.from(rxmore_than_one);

    int start = ::get_nr(range);
    range = range.after("..");
    int stop = ::get_nr(range);

    if (start > stop)
    {
	post_error("Invalid range in " + quote(display_expression), 
		   "invalid_range_error");
	return -1;
    }

    for (int i = start; i <= stop; i++)
    {
	string subexpression = prefix + itostring(i) + postfix;
	if (unfold_expressions(subexpression, expressions))
	    return -1;
    }

    return 0;
}

// Get display number and name from ANSWER; store them in NR and NAME
void DataDisp::read_number_and_name(string& answer, string& nr, string& name)
{
    nr   = "";
    name = "";

    if (gdb->has_numbered_displays())
    {
	nr = read_disp_nr_str(answer, gdb);
	if (!nr.empty())
	    name = read_disp_name(answer, gdb);
    }
    else
    {
	name = read_disp_name(answer, gdb);
	if (gdb->has_display_command())
	{
	    // Fetch number from `display' output
	    string ans = gdb_question(gdb->display_command(), -1);
	    int index  = ans.index(name + "\n", -1);
	    if (index > 0)
	    {
		while (index > 0 && ans[index - 1] != '\n')
		    index--;
		ans = ans.from(index);
		int n = get_nr(ans);
		nr = itostring(n);
	    }

	    if (nr.empty())
	    {
		// Could not determine number
		post_warning("Could not determine number of display " 
			     + quote(name), 
			     "no_display_number_warning", last_origin);
	    }
	}
	
	if (nr.empty())
	{
	    // Assign a default number
	    nr = itostring(next_ddd_display_number++);
	}
    }
}

string DataDisp::new_display_cmd(const string& display_expression, 
				 BoxPoint *p,
				 const string& depends_on, 
				 bool clustered, bool plotted)
{
    string cmd = "graph ";
    if (plotted)
	cmd += "plot ";
    else
	cmd += "display ";
    cmd += display_expression;

    if (clustered)
	cmd += " clustered ";
    if (p != 0 && *p != BoxPoint())
	cmd += " at (" + itostring((*p)[X]) + ", " + itostring((*p)[Y]) + ")";
    if (!depends_on.empty())
	cmd += " dependent on " + depends_on;

    return cmd;
}


//-----------------------------------------------------------------------------
// Built-in user commands
//-----------------------------------------------------------------------------

#define HOOK_PREFIX  "<?"
#define HOOK_POSTFIX ">"


bool DataDisp::is_builtin_user_command(const string& cmd)
{
    if (cmd.contains(CLUSTER_COMMAND, 0))
	return true;

    return false;
}

string DataDisp::builtin_user_command(const string& cmd, DispNode *node)
{
    if (cmd.contains(CLUSTER_COMMAND, 0))
    {
	MapRef ref;
	IntArray clustered_displays;
	for (DispNode* dn = disp_graph->first(ref); 
	     dn != 0;
	     dn = disp_graph->next(ref))
	{
	    if (!dn->clustered())
		continue;
	    if (dn->is_user_command())
		continue;
	    if (dn->deferred())
		continue;
	    if (!dn->active())
		continue;
	    if (node != 0 && dn->clustered() != node->disp_nr())
		continue;
	    if (node == 0 && dn->clustered() != -next_ddd_display_number)
		continue;

	    clustered_displays += dn->disp_nr();
	}

	sort(clustered_displays);

	std::ostringstream os;
	if (clustered_displays.size() == 0)
	{
	    os << "No displays.\n";
	}
	else
	{
	    for (int i = 0; i < clustered_displays.size(); i++)
	    {
		DispNode *dn = disp_graph->get(clustered_displays[i]);
		os << dn->name() << " = " HOOK_PREFIX 
		   << dn->disp_nr() << HOOK_POSTFIX "\n";
	    }
	}

	return string(os);
    }

    return NO_GDB_ANSWER;
}

// This function is called to update displays in clusters
DispValue *DataDisp::update_hook(string& value)
{
    if (!value.contains(HOOK_PREFIX, 0))
	return 0;

    value = value.after(HOOK_PREFIX);
    int nr = atoi(value.chars());
    value = value.after(HOOK_POSTFIX);

    DispNode *dn = disp_graph->get(nr);
    if (dn == 0 || dn->value() == 0)
	return 0;		// Ignore

    // Share the clustered DispValue with the original display
    return dn->value()->link();
}

void DataDisp::refresh_builtin_user_displays()
{
    DispValue::value_hook = update_hook;

    ProgressMeter *s = 0;

    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref); 
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	if (!dn->is_user_command())
	    continue;

	const string& cmd = dn->user_command();
	string answer = NO_GDB_ANSWER;

	if (is_internal_command(cmd))
	{
	    if (s == 0)
		s = new ProgressMeter("Updating histories");

	    answer = internal_command(cmd);
	} 
	else if (is_builtin_user_command(cmd))
	{
	    if (is_cluster(dn) && !needs_refresh(dn))
		continue;

	    if (s == 0)
		s = new ProgressMeter("Updating clusters");

	    answer = builtin_user_command(cmd, dn);

	    DispValue *dv = dn->value();
	    
	    // If any child is enabled, enable cluster as well.
	    bool enable = false;
	    for (int i = 0; dv != 0 && i < dv->nchildren(); i++)
		if (dv->child(i)->enabled())
		{
		    enable = true;
		    break;
		}

	    if (enable)
		dn->enable();
	    else
		dn->disable();
	}
	else
	{
	    continue;
	}

	if (answer != NO_GDB_ANSWER && dn->update(answer))
	{
	    // Clear old local selection
	    dn->select(0);
	}

	dn->refresh();
	graphEditRedrawNode(graph_edit, dn);
    }

    delete s;

    DispValue::value_hook = 0;
}


//-----------------------------------------------------------------------------
// Open and close data window
//-----------------------------------------------------------------------------

void DataDisp::open_data_window()
{
    // Make sure graph is visible
    gdbOpenDataWindowCB(graph_edit, 0, 0);
}

void DataDisp::close_data_window()
{
    if (app_data.separate_data_window)
    {
	// Don't close a separate data window.
    }
    else
    {
	gdbCloseDataWindowCB(graph_edit, 0, 0);
    }
}

//-----------------------------------------------------------------------------
// Create new data and user nodes
//-----------------------------------------------------------------------------

DispNode *DataDisp::new_data_node(const string& given_name,
				  const string& scope,
				  const string& answer,
				  bool plotted)
{
    string value = answer;

    string nr_s;
    string display_name;
    read_number_and_name(value, nr_s, display_name);

    gdb->munch_value(value, display_name);

    int nr = get_nr(nr_s);
    if (nr == 0 || display_name.empty())
    {
	post_gdb_message(answer, true, last_origin);
	return 0;
    }

    strip_space(value);

    // Naming a data display after the GDB display name causes trouble
    // when displaying functions: `display tree_test' creates a
    // display named `tree_test(void)', and while `print tree_test'
    // works fine, `print tree_test(void)' fails.  We may use quotes,
    // as in `print 'tree_test(void)'', but it is too hard to
    // determine where quotes are needed, and where not - just
    // consider `print tree_test(42)'.  Hence, if a function call
    // occurs in an expression, we use the name specified by the user,
    // not the name supplied by GDB.

    // Upon some occasions, GDB gives names like 
    // `{<text variable, no debug info>} 0x2270 <main>'.  In such cases,
    // also use the user-given name instead.

    // If the user quoted some part of a name, as in
    // `tree->date.'_vptr.'[0]', also prefer the user-given name,
    // since the quotes will be removed in the GDB output.
#if RUNTIME_REGEX
    static regex rxfunction_call("[a-zA-Z0-9_$][(]");
#endif
    string title = display_name;
    if (title.contains(rxfunction_call) || 
	title.contains('{') || 
	title.contains('}') || 
	given_name.contains('\''))
	title = given_name;

    bool disabling_occurred = false;
    if (is_disabling(value, gdb))
    {
	string error_msg = get_disp_value_str(value, gdb);
	post_gdb_message(error_msg, true, last_origin);
	value = " ";
	disabling_occurred = true;
    }

    ProgressMeter s("Creating display");
    s.total   = value.length();
    s.current = value.length();

    DispNode *dn = new DispNode(nr, title, scope, value, plotted);

    if (plotted && (dn->value() == 0 || dn->value()->can_plot() == 0))
    {
	post_gdb_message("Nothing to plot.", true, last_origin);

	if (gdb->has_display_command())
	{
	    gdb_command("undisplay " + itostring(dn->disp_nr()), 
			last_origin, OQCProc(0));
	}

	delete dn;
	return 0;
    }

    if (disabling_occurred)
    {
	dn->disable();
	dn->make_active();
    }

    open_data_window();

    undo_buffer.add_display(title, value);
    undo_buffer.add_command(delete_display_cmd(title), true);

    return dn;
}

DispNode *DataDisp::new_user_node(const string& name,
				  const string& /* scope */,
				  const string& answer,
				  bool plotted)
{
    // Assign a default number
    int nr = -(next_ddd_display_number++);

    ProgressMeter s("Creating status display");
    s.total   = answer.length();
    s.current = answer.length();

    // Set cluster update hook
    if (name.contains("`" CLUSTER_COMMAND, 0))
	DispValue::value_hook = update_hook;

    // User displays work regardless of scope
    static const string scope = "";

    DispNode *dn = new DispNode(nr, name, scope, answer, plotted);
    DispValue::value_hook = 0;

    if (plotted && (dn->value() == 0 || dn->value()->can_plot() == 0))
    {
	post_gdb_message("Nothing to plot.", true, last_origin);
	delete dn;
	return 0;
    }

    open_data_window();

    if (is_cluster(dn))
    {
	if (dn->user_command().contains(rxmore_than_one))
	{
	    // Align array slices horizontally
	    DispValue *dv = dn->value();
	    dv->set_orientation(Horizontal);
	    dv->set_member_names(false);
	    dn->refresh();
	}
    }
    else
    {
	undo_buffer.add_display(name, answer);
	undo_buffer.add_command(delete_display_cmd(name), true);
    }

    return dn;
}

DispNode *DataDisp::new_deferred_node(const string& expr, const string& scope,
				      const BoxPoint& pos,
				      const string& depends_on,
				      bool clustered, bool plotted)
{
    // Assign a default number
    int nr = -(next_ddd_display_number++);

    // A `dummy' answer (never shown)
    const string answer = "<deferred>";

    MString msg = rm("Creating deferred display " + itostring(nr) + ": ") + 
	tt(expr) + rm(" (scope ") + tt(scope) + rm(")");
    set_status_mstring(msg);

    DispNode *dn = new DispNode(nr, expr, scope, answer, plotted);
    dn->deferred() = true;
    if (clustered)
	dn->cluster(-1);
    dn->make_inactive();
    dn->depends_on() = depends_on;
    dn->plotted() = plotted;
    dn->moveTo(pos);

    undo_buffer.add_display(expr, answer);
    undo_buffer.add_command(delete_display_cmd(expr), true);

    return dn;
}



// Create new data display from ANSWER
void DataDisp::new_data_displayOQC (const string& answer, void* data)
{
    NewDisplayInfo *info = (NewDisplayInfo *)data;

    if (answer == NO_GDB_ANSWER)
    {
	delete info;		// Command was canceled
	return;
    }

    if (answer.empty())
    {
	if (gdb->has_display_command())
	{
	    // No display output (GDB bug).  Refresh displays explicitly.
	    gdb_command(gdb->display_command(), last_origin,
			new_data_display_extraOQC, data,
			false, false,
			COMMAND_PRIORITY_AGAIN);
	}
	else
	{
	    delete info;
	}
	return;
    }

    bool have_display_answer = contains_display(answer, gdb);
    bool have_valid_answer   = is_valid(answer, gdb);

    // Unselect all nodes
    for (GraphNode *gn = disp_graph->firstNode();
	 gn != 0; gn = disp_graph->nextNode(gn))
    {
	gn->selected() = false;
    }

    // Create new DispNode
    string ans = answer;
    DispNode *dn = 0;

    if (have_display_answer)
    {
	dn = new_data_node(info->display_expression, 
			   info->scope, ans, info->plotted);
    }

    if (dn == 0)
    {
	// Display could not be created
	if (info->deferred == DeferIfNeeded)
	{
	    // Create deferred display now
	    dn = new_deferred_node(info->display_expression, info->scope,
				   info->point, info->depends_on,
				   info->clustered, info->plotted);

	    // Insert deferred node into graph
	    disp_graph->insert(dn->disp_nr(), dn);

	    if (info->prompt)
		prompt();

	    refresh_display_list();
	}
	else if (!have_valid_answer)
	{
	    if (info->verbose)
		post_gdb_message(answer, info->prompt, last_origin);
	}

	delete info;
	return;
    }

    bool clustered = info->clustered;
    bool plotted   = info->plotted;

    if (!(info->cluster_name.empty()))
    {
	clustered = false;
	plotted   = false;
    }

    if (info->create_cluster)
    {
	// Cluster multiple values
	info->cluster_nr = new_cluster(info->cluster_name, info->plotted);
    }

    // Insert node into graph
    int depend_nr = disp_graph->get_by_name(info->depends_on);
    insert_data_node(dn, depend_nr, clustered, plotted);

    // Determine position
    BoxPoint box_point = info->point;
    if (box_point == BoxPoint())
	box_point = disp_graph->default_pos(dn, graph_edit, depend_nr);
    dn->moveTo(box_point);

    if (info->cluster_nr != 0)
	dn->cluster(info->cluster_nr - info->cluster_offset);

    select_node(dn, depend_nr);

    refresh_addr(dn);
    refresh_graph_edit();

    if (info->prompt)
	prompt();

    delete info;
}

// Create new user display from ANSWER
void DataDisp::new_user_displayOQC (const string& answer, void* data)
{
    NewDisplayInfo *info = (NewDisplayInfo *)data;

    if (answer == NO_GDB_ANSWER)
    {
	delete info;		// Command was canceled
	return;
    }

    // Unselect all nodes
    for (GraphNode *gn = disp_graph->firstNode();
	 gn != 0; gn = disp_graph->nextNode(gn))
    {
	gn->selected() = false;
    }

    // Create new user node and issue `disabling' messages
    string ans = answer;
    DispNode *dn = new_user_node(info->display_expression, info->scope, 
				 ans, info->plotted);
    if (dn != 0)
    {
	dn->constant() = info->constant;

	// Insert into graph
	int depend_nr = disp_graph->get_by_name(info->depends_on);
	disp_graph->insert(dn->disp_nr(), dn, depend_nr);

	// Plot new node
	if (dn->plotted() && dn->value() != 0)
	    dn->value()->plot();

	// Determine new position
	BoxPoint box_point = info->point;
	if (box_point == BoxPoint())
	    box_point = disp_graph->default_pos(dn, graph_edit, depend_nr);
	dn->moveTo(box_point);
	select_node(dn, depend_nr);

	refresh_addr(dn);
	refresh_graph_edit();
	update_infos();

	if (info->prompt)
	    prompt();
    }

    delete info;
}

// Create new display value from `display' output
void DataDisp::new_data_display_extraOQC (const string& answer, void* data)
{
    NewDisplayInfo *info = (NewDisplayInfo *)data;

    if (answer == NO_GDB_ANSWER)
    {
	delete info;		// Command was canceled
	return;
    }

    // The new display is the first one
    string ans = answer;
    string display = read_next_display(ans, gdb);

    if (!display.empty())
	new_data_displayOQC(display, data);
}

// Insert DN into graph, possibly clustering it
void DataDisp::insert_data_node(DispNode *dn, int depend_nr, 
				bool clustered, bool plotted)
{
    // Insert into graph
    disp_graph->insert(dn->disp_nr(), dn, depend_nr);
    if (plotted)
    {
	dn->plotted() = true;
	if (dn->value() != 0)
	    dn->value()->plot();
    }

    // Check for clusters
    if (!clustered && !cluster_displays)
	return;
    if (dn->is_user_command())
	return;
    if (depend_nr != 0)
	return;

    // Insert into current cluster
    dn->cluster(current_cluster());
}

// Create a new cluster named NAME and return its number
int DataDisp::new_cluster(const string& name, bool plotted)
{
    const string cmd = plotted ? "plot" : "display";

    string base = CLUSTER_COMMAND;
    if (!name.empty())
	base += " " + name;

    gdb_command("graph " + cmd + " `"  + base + "`", last_origin, 0);
    return -next_ddd_display_number;
}

// Return a cluster number; create a new cluster if necessary
int DataDisp::current_cluster()
{
    // Use last cluster or create a new one
    IntArray all_clusters;
    get_all_clusters(all_clusters);
    sort(all_clusters);

    if (all_clusters.size() > 0)
	return all_clusters[0];
    else
	return new_cluster();
}


//-----------------------------------------------------------------------------
// Refresh graph
//-----------------------------------------------------------------------------

class RefreshInfo {
public:
    bool verbose;
    bool prompt;
    IntArray display_nrs;
    StringArray cmds;

    RefreshInfo()
	: verbose(false), prompt(false), display_nrs(), cmds()
    {}

    ~RefreshInfo()
    {}

private:
    RefreshInfo(const RefreshInfo&);
    RefreshInfo& operator = (const RefreshInfo&);
};

int DataDisp::add_refresh_data_commands(StringArray& cmds)
{
    int initial_size = cmds.size();

    if (gdb->display_prints_values())
	cmds += gdb->display_command();
    else
    {
	MapRef ref;
	for (DispNode* dn = disp_graph->first(ref); 
	     dn != 0;
	     dn = disp_graph->next(ref))
	{
	    if (!dn->is_user_command() && !dn->deferred())
	    {
		string cmd = gdb->print_command(dn->name());
		while (!cmd.empty())
		{
		    string line = cmd;
		    if (line.contains('\n'))
			line = line.before('\n');
		    cmd = cmd.after('\n');
		    cmds += line;
		}
	    }
	}
    }

    return cmds.size() - initial_size;
}

int DataDisp::add_refresh_user_commands(StringArray& cmds)
{
    int initial_size = cmds.size();

    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref); 
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	if (dn->is_user_command() && dn->enabled() && 
	    !dn->deferred() && !dn->constant())
	{
	    const string& cmd = dn->user_command();
	    if (!is_internal_command(cmd))
		cmds += cmd;
	}
    }

    return cmds.size() - initial_size;
}

string DataDisp::refresh_display_cmd()
{
    return "graph refresh";
}

#define PROCESS_INFO_DISPLAY 0
#define PROCESS_DATA         1
#define PROCESS_USER         2
#define PROCESS_ADDR         3

void DataDisp::refresh_displaySQ(Widget origin, bool verbose, bool do_prompt)
{
    if (origin)
	set_last_origin(origin);

    // Some sanitizing actions...
    make_sane();

    // Now for the refreshments.  Process all displays.
    StringArray cmds;
    VoidArray dummy;

    if (gdb->has_info_display_command())
	cmds += gdb->info_display_command();
    while (dummy.size() < cmds.size())
	dummy += (void *)PROCESS_INFO_DISPLAY;

    add_refresh_data_commands(cmds);
    while (dummy.size() < cmds.size())
	dummy += (void *)PROCESS_DATA;

    add_refresh_user_commands(cmds);
    while (dummy.size() < cmds.size())
	dummy += (void *)PROCESS_USER;

    add_refresh_addr_commands(cmds);
    while (dummy.size() < cmds.size())
	dummy += (void *)PROCESS_ADDR;

    static RefreshInfo info;
    info.verbose = verbose;
    info.prompt  = do_prompt;
    info.cmds    = cmds;

    bool info_registered;
    bool ok = gdb->send_qu_array(cmds, dummy, cmds.size(), 
				 refresh_displayOQAC, (void *)&info,
				 info_registered);

    if (!ok || cmds.size() == 0)
    {
	// Simply redraw display
	refresh_graph_edit();
	if (do_prompt)
	    prompt();
    }
}

void DataDisp::refresh_displayOQAC (StringArray& answers,
				    const VoidArray& qu_datas,
				    void*  data)
{
    int count = answers.size();

    string data_answers;
    int data_answers_seen = 0;
    StringArray user_answers;
    StringArray addr_answers;

    RefreshInfo *info = (RefreshInfo *)data;

    for (int i = 0; i < count; i++)
    {
	switch ((int)(long)qu_datas[i])
	{
	case PROCESS_INFO_DISPLAY:
	    // Process 'info display' output (delete displays if necessary)
	    process_info_display(answers[i]);
	    break;

	case PROCESS_DATA:
	{
	    const string& cmd = info->cmds[i];
	    string var = cmd.after(rxwhite);

	    if (!gdb->has_named_values())
		data_answers += var + " = ";

	    string value = answers[i];
	    gdb->munch_value(value, var);
	    data_answers += value + "\n";

	    data_answers_seen++;
	    break;
	}

	case PROCESS_USER:
	    user_answers += answers[i];
	    break;

	case PROCESS_ADDR:
	    addr_answers += answers[i];
	    break;

	default:
	    assert(0);
	    ::abort();
	    break;
	}
    }

    // Process `display', user command, and addr command output
    if (data_answers_seen > 0)
    {
	bool disabling_occurred = false;
	process_displays(data_answers, disabling_occurred);

	// If we had a `disabling' message, refresh displays once more
	if (disabling_occurred)
	{
	    refresh_displaySQ(0, info->verbose, info->prompt);
	    info->prompt = false;	// No more prompts
	}
    }

    if (user_answers.size() > 0)
	process_user(user_answers);

    if (addr_answers.size() > 0)
    {
	force_check_aliases = true;
	process_addr(addr_answers);
    }

    if (info->prompt)
	prompt();
}



//-----------------------------------------------------------------------------
// Disabling Displays
//-----------------------------------------------------------------------------

// Convert A to a space-separated string
string DataDisp::numbers(IntArray& a)
{
    sort(a);

    string ret;
    for (int i = 0; i < a.size(); i++)
    {
	if (i > 0)
	    ret += " ";
	ret += itostring(a[i]);
    }

    return ret;
}

// Sort and verify the display numbers in DISPLAY_NRS
bool DataDisp::sort_and_check(IntArray& display_nrs)
{
    bool ok = true;
    sort(display_nrs);

    for (int i = 0; i < display_nrs.size(); i++)
    {
	DispNode *dn = disp_graph->get(display_nrs[i]);
	if (dn == 0)
	{
	    post_gdb_message("No display number " 
			     + itostring(display_nrs[i]) + ".\n");
	    display_nrs[i] = 0;
	    ok = false;
	}
    }

    return ok;
}

// For all nodes in DISPLAY_NRS, add their aliases
void DataDisp::add_aliases(IntArray& display_nrs)
{
    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref); 
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	if (dn->hidden())
	{
	    bool have_alias = false;
	    bool need_alias = false;

	    for (int i = 0; i < display_nrs.size(); i++)
	    {
		if (display_nrs[i] == dn->disp_nr())
		    have_alias = true;
		if (display_nrs[i] == dn->alias_of)
		    need_alias = true;
	    }

	    if (need_alias && !have_alias)
		display_nrs += dn->disp_nr();
	}
    }
}

string DataDisp::disable_display_cmd(IntArray& display_nrs)
{
    add_aliases(display_nrs);

    if (display_nrs.size() > 0)
	return "graph disable display " + numbers(display_nrs);
    else
	return "";
}

void DataDisp::disable_displaySQ(IntArray& display_nrs, bool verbose, 
				 bool do_prompt)
{
    bool ok = sort_and_check(display_nrs);
    if (!ok)
	do_prompt = false;

    int disabled_data_displays = 0;
    int i;
    string cmd = "disable display";
    for (i = 0; i < display_nrs.size(); i++)
    {
	if (gdb->has_disable_display_command() && display_nrs[i] > 0)
	{
	    cmd += " " + itostring(display_nrs[i]);
	    disabled_data_displays++;
	}
    }

    if (disabled_data_displays > 0)
    {
	static RefreshInfo info;
	info.verbose = verbose;
	info.prompt  = do_prompt;

	gdb_command(cmd, last_origin, disable_displayOQC, (void *)&info);
    }

    int disabled_user_displays = 0;
    for (i = 0; i < display_nrs.size(); i++)
    {
	DispNode *dn = disp_graph->get(display_nrs[i]);
	if (dn != 0 && dn->enabled())
	{
	    dn->disable();
	    dn->refresh();
	    disabled_user_displays++;
	}
    }

    if (disabled_data_displays == 0)
    {
	if (disabled_user_displays > 0)
	    refresh_graph_edit();

	if (do_prompt)
	    prompt();
    }
}

void DataDisp::disable_displayOQC (const string& answer, void *data)
{
    if (answer == NO_GDB_ANSWER)
	return;			// Command was canceled

    RefreshInfo *info = (RefreshInfo *)data;

    if (info->verbose)
	post_gdb_message(answer, info->prompt);
    if (info->prompt)
	prompt();

    refresh_graph_edit();
}


//-----------------------------------------------------------------------------
// Enable Displays
//-----------------------------------------------------------------------------

string DataDisp::enable_display_cmd(IntArray& display_nrs)
{
    add_aliases(display_nrs);

    if (display_nrs.size() > 0)
	return "graph enable display " + numbers(display_nrs);
    else
	return "";
}

void DataDisp::enable_displaySQ(IntArray& display_nrs, bool verbose, 
 				bool do_prompt)
{
    bool ok = sort_and_check(display_nrs);
    if (!ok)
	do_prompt = false;

    int enabled_data_displays = 0;
    int i;
    string cmd = "enable display";
    for (i = 0; i < display_nrs.size(); i++)
    {
	if (gdb->has_enable_display_command() && display_nrs[i] > 0)
	{
	    cmd += " " + itostring(display_nrs[i]);
	    enabled_data_displays++;
	}
    }

    // Have GDB enable data displays
    if (enabled_data_displays > 0)
    {
	static RefreshInfo info;
	info.verbose = verbose;
	info.prompt  = do_prompt;

	gdb_command(cmd, last_origin, enable_displayOQC, (void *)&info);
    }

    // Handle user displays
    int enabled_user_displays = 0;
    for (i = 0; i < display_nrs.size(); i++)
    {
	DispNode *dn = disp_graph->get(display_nrs[i]);
	if (dn != 0 && dn->is_user_command() && 
	    dn->disabled() && !dn->deferred())
	{
	    dn->enable();
	    if (dn->value() != 0)
		dn->value()->expandAll();
	    dn->refresh();
	    enabled_user_displays++;
	}
    }

    if (enabled_data_displays == 0)
    {
	refresh_graph_edit();

	if (do_prompt)
	    prompt();
    }
}

void DataDisp::enable_displayOQC (const string& answer, void *data)
{
    if (answer == NO_GDB_ANSWER)
	return;			// Command was canceled

    RefreshInfo *info = (RefreshInfo *)data;

    if (info->verbose)
	post_gdb_message(answer, false);

    refresh_displaySQ(0, info->verbose, info->prompt);
}


//-----------------------------------------------------------------------------
// Delete Displays
//-----------------------------------------------------------------------------

string DataDisp::delete_display_cmd(IntArray& display_nrs)
{
    if (app_data.delete_alias_displays)
	add_aliases(display_nrs);

    if (display_nrs.size() > 0)
	return delete_display_cmd(numbers(display_nrs));
    else
	return "";
}

string DataDisp::delete_display_cmd(const string& name)
{
    return "graph undisplay " + name;
}

// Return true iff DISPLAY_NRS contains all data displays
bool DataDisp::all_data_displays(IntArray& display_nrs)
{
    // Fetch given data displays
    IntArray data_display_nrs;
    int i;
    for (i = 0; i < display_nrs.size(); i++)
	if (display_nrs[i] > 0)
	    data_display_nrs += display_nrs[i];

    if (data_display_nrs.size() < 2)
	return false;

    // Fetch existing data displays
    IntArray all_data_display_nrs;

    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref); 
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	if (!dn->deferred() && !dn->is_user_command())
	    all_data_display_nrs += dn->disp_nr();
    }

    // Compare
    if (data_display_nrs.size() != all_data_display_nrs.size())
	return false;

    sort(all_data_display_nrs);
    sort(data_display_nrs);

    for (i = 0; i < data_display_nrs.size(); i++)
	if (data_display_nrs[i] != all_data_display_nrs[i])
	    return false;

    return true;
}

void DataDisp::delete_displaySQ(IntArray& display_nrs, bool verbose, 
				bool do_prompt)
{
    bool ok = sort_and_check(display_nrs);
    if (!ok)
	do_prompt = false;

    string cmd = "undisplay";

    int deleted_data_displays = 0;

    if (gdb->type() == GDB && verbose && all_data_displays(display_nrs))
    {
	// We want to delete all data displays.  Use GDB `undisplay'
	// command without args; this will ask for confirmation.
	deleted_data_displays = display_nrs.size();
    }
    else
    {
	// Build command
	for (int i = 0; i < display_nrs.size(); i++)
	{
	    if (display_nrs[i] > 0)
	    {
		if (deleted_data_displays++ > 0 && gdb->wants_display_comma())
		    cmd += ",";
		cmd += " " + itostring(display_nrs[i]);
	    }
	}
    }

    if (deleted_data_displays > 0 && gdb->has_display_command())
    {
	static RefreshInfo info;
	info.verbose     = verbose;
	info.prompt      = do_prompt;
	info.display_nrs = display_nrs;

	Command c(cmd, last_origin, delete_displayOQC, (void *)&info);
	if (gdb->has_redisplaying_undisplay())
	    c.verbose = false;
	else
	    c.verbose = verbose;
	gdb_command(c);
    }
    else
    {
	deletion_done(display_nrs, do_prompt);
    }
}

void DataDisp::delete_displayOQC (const string& answer, void *data)
{
    if (answer == NO_GDB_ANSWER)
	return;			// Command was canceled

    RefreshInfo *info = (RefreshInfo *)data;

    if (gdb->type() == GDB && answer.contains("(y or n)"))
    {
	// The `undisplay' command required confirmation.  Since GDBAgent
	// does not tell us the outcome, we must check the user reply.

	string reply = lastUserReply();
	if (!reply.contains('y', 0))
	{
	    // Deletion was canceled
	    static const IntArray empty;
	    info->display_nrs = empty;
	}
    }

    if (gdb->has_redisplaying_undisplay())
    {
	string ans = answer;

	// Upon `undisplay', Sun DBX redisplays remaining displays
	// with values.  Process them.
	if (!answer.empty() && !answer.contains("no such expression"))
	{
	    bool disabling_occurred;
	    process_displays(ans, disabling_occurred);
	}

	// Show remaining output
	post_gdb_message(ans);
    }

    deletion_done(info->display_nrs, info->prompt);
}

void DataDisp::deletion_done (IntArray& display_nrs, bool do_prompt)
{
    bool unclustered = false;

    // Build undo command
    std::ostringstream undo_commands;
    int i;
    for (i = 0; i < display_nrs.size(); i++)
    {
	int nr = display_nrs[i];
	DispNode *node = disp_graph->get(nr);
	if (node == 0)
	    continue;		// Already deleted or bad number
	if (is_cluster(node))
	    continue;		// Saving clusters is a bad idea

	// Save current state
	get_node_state(undo_commands, node, true);
    }

    string u = string(undo_commands);
    if (!u.empty())
	undo_buffer.add_command(u, true);

    // Delete nodes
    for (i = 0; i < display_nrs.size(); i++)
    {
	int nr = display_nrs[i];

	// Delete node from graph
	DispNode *node = disp_graph->get(nr);
	if (node == 0)
	    continue;		// Already deleted or bad number

	if (node->clustered())
	{
	    // Deleting a clustered node:
	    // force its cluster to be redisplayed
	    DispNode *cluster = disp_graph->get(node->clustered());
	    if (cluster != 0)
		cluster->set_last_refresh();
	}

	if (is_cluster(node))
	{
	    // Deleting a cluster: uncluster all contained nodes
	    MapRef ref;
	    for (DispNode* dn = disp_graph->first(ref); 
		 dn != 0; dn = disp_graph->next(ref))
	    {
		if (dn->clustered() == nr)
		{
		    disp_graph->uncluster(dn);
		    dn->selected() = true;
		    unclustered = true;
		}
	    }
	}

	// Delete the node itself
	disp_graph->del(nr);
    }

    if (display_nrs.size() > 0)
    {
	// Refresh arguments
	if (unclustered)
	    refresh_args();

	// Refresh editor
	refresh_graph_edit();

	// Refresh addresses now
	force_check_aliases = true;
	refresh_addr();
    }

    if (disp_graph->firstVisibleNode() == 0)
    {
	// Deleted last visible display
	if (app_data.auto_close_data_window)
	{
	    close_data_window();
	}
    }

    if (do_prompt)
	prompt();

    update_infos();
}


//-----------------------------------------------------------------------------
// Handle output of 'info display'
//-----------------------------------------------------------------------------

void DataDisp::process_info_display(string& info_display_answer,
				    bool defer_deleted)
{
    int disp_nr;
    StringMap info_disp_string_map;
    string *strptr;
    int max_disp_nr = 0;

    string next_disp_info = 
	read_first_disp_info (info_display_answer, gdb);
    while (!next_disp_info.empty())
    {
	disp_nr = get_positive_nr (next_disp_info);
	if (disp_nr >= 0)
	{
	    max_disp_nr = max(max_disp_nr, disp_nr);

	    if (disp_graph->contains(disp_nr)) 
	    {
		// This is a DDD display.
		strptr = new string(get_info_disp_str(next_disp_info, gdb));
		info_disp_string_map.insert (disp_nr, strptr);
	    }
	}
	next_disp_info = 
	    read_next_disp_info(info_display_answer, gdb);
    }
    next_gdb_display_number = max(next_gdb_display_number, max_disp_nr + 1);

    // Process DDD displays

    // Part 1.  Update existing display values.
    IntArray deleted_displays;
    bool changed = false;
    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref);
	 dn != 0; 
	 dn = disp_graph->next(ref))
    {
	if (!dn->is_user_command() && !dn->deferred())
	{
	    string *disp_info = info_disp_string_map.get(dn->disp_nr());
	    
	    if (disp_info == 0)
	    {
		// The DDD display is not contained in the GDB
		// `display' output.  This happens if the debuggee has
		// changed; in this case, GDB deletes all displays.
		// If DEFER_DELETED is set, we simply defer the
		// existing displays such that they can be restored
		// later.
		deleted_displays += dn->disp_nr();
	    }
	    else
	    {
		// Update values
		if (disp_is_disabled(*disp_info, gdb))
		{
		    if (dn->enabled())
		    {
			dn->disable();
			changed = true;
		    }
		}
		else
		{
		    if (dn->disabled())
		    {
			changed = true;
		    }
		}

		delete disp_info;
		info_disp_string_map.del(dn->disp_nr());
	    }
	}
    }

    assert(info_disp_string_map.length() == 0);


    // Part 2.  Defer deleted displays.
    sort(deleted_displays);

    // Give an appropriate message
    if (defer_deleted && deleted_displays.size() >= 1)
    {
	MString msg = rm("Deferring display");
	if (deleted_displays.size() >= 2)
	    msg += rm("s");
	msg += rm(" ");

	for (int i = 0; i < deleted_displays.size(); i++)
	{
	    if (i > 0)
	    {
		if (deleted_displays.size() == 2)
		    msg += rm(" and ");
		else if (i == deleted_displays.size() - 1)
		    msg += rm(", and ");
		else
		    msg += rm(", ");
	    }
	    msg += rm(itostring(deleted_displays[i]));
	}

	msg += rm(" because ");
	if (deleted_displays.size() >= 2)
	    msg += rm("they have");
	else
	    msg += rm("it has");
	msg += rm(" been deleted by " + gdb->title());

	set_status_mstring(msg);
    }

    if (defer_deleted)
    {
	// Create new deferred displays
	for (int i = 0; i < deleted_displays.size(); i++)
	{
	    DispNode *dn = disp_graph->get(deleted_displays[i]);

	    // Fetch old position and dependent info
	    BoxPoint pos = dn->pos();

	    string depends_on = "";
	    for (GraphEdge *edge = dn->firstTo();
		 edge != 0; edge = dn->nextTo(edge))
	    {
		BoxGraphNode *ancestor = ptr_cast(BoxGraphNode, edge->from());
		if (ancestor != 0)
		{
		    int depnr = disp_graph->get_nr(ancestor);
		    DispNode *depnode = disp_graph->get(depnr);
		    if (depnode != 0)
		    {
			depends_on = depnode->name();
			break;
		    }
		}
	    }

	    // Create new deferred node
	    new_displaySQ(dn->name(), dn->scope(), &pos,
			  depends_on, DeferIfNeeded, 
			  dn->clustered(), dn->plotted(),
			  0, false, false);
	}
    }

    // Delete remaining (= undeferred) displays
    for (int i = 0; i < deleted_displays.size(); i++)
    {
	disp_graph->del(deleted_displays[i]);
	changed = true;
    }

    if (deleted_displays.size() >= 1)
    {
	force_check_aliases = true;
	refresh_addr();
    }

    if (changed)
	refresh_graph_edit();

    refresh_display_list();
}



//-----------------------------------------------------------------------------
// Process `display' output
//-----------------------------------------------------------------------------

string DataDisp::process_displays(string& displays,
				  bool& disabling_occurred)
{
    string not_my_displays;
    disabling_occurred = false;

    strip_space(displays);
    if (displays.length() == 0)
    {
	bool have_displays = false;
	MapRef ref;
	for (DispNode* dn = disp_graph->first(ref); 
	     !have_displays && dn != 0;
	     dn = disp_graph->next(ref))
	{
	    have_displays = (!dn->is_user_command() && dn->active());
	}

	if (!have_displays)
	    return "";		// No data and no displays
    }

    ProgressMeter s("Updating displays");

    // Store graph displays in DISP_STRING_MAP; return all other
    // (text) displays as well as error messages
    int disp_nr = 0;
    StringMap disp_string_map;

#if LOG_DISPLAYS
    std::clog << "Updating displays " << quote(displays) << "...\n";
#endif

    string next_display = read_next_display (displays, gdb);
    while (!next_display.empty()) 
    {
#if LOG_DISPLAYS
        std::clog << "Updating display " << quote(next_display);
#endif
	if (gdb->has_numbered_displays())
	{
	    disp_nr = get_positive_nr (next_display);
	}
	else
	{
	    disp_nr = 0;
	    string disp_name = next_display;
	    disp_name = read_disp_name(disp_name, gdb);
	    if (!disp_name.empty())
	    {
		MapRef ref;
		for (DispNode* dn = disp_graph->first(ref); 
		     dn != 0;
		     dn = disp_graph->next(ref))
		{
		    if (dn->name() == disp_name)
		    {
			disp_nr = dn->disp_nr();
			break;
		    }
		}
	    }
	}

#if LOG_DISPLAYS
	std::clog << " (number " << disp_nr << ")\n";
#endif

	if (is_disabling (next_display, gdb))
	{
	    // A display was disabled: record this and try again
	    disabling_occurred = true;
	    DispNode *dn = disp_graph->get(disp_nr);
	    if (disp_nr >= 0 && dn != 0)
	    {
		string error_msg = get_disp_value_str(next_display, gdb);
		post_gdb_message(error_msg);
		dn->make_active();
		dn->disable();
		refresh_graph_edit();
	    }
	    else
	    {
		not_my_displays = next_display; // Memorize this one only
	    }

	    // Clear DISP_STRING_MAP and try again
	    disp_string_map.delete_all_contents();

	    return not_my_displays;
	}

	if (!is_valid(next_display, gdb))
	{
	    // Display is not active
	}
	else if (disp_nr >= 0 && disp_graph->contains(disp_nr))
	{
	    string *strptr = new string(get_disp_value_str(next_display, gdb));
	    disp_string_map.insert(disp_nr, strptr);
	    s.total += strptr->length();
	}
	else 
	{
	    not_my_displays += next_display + '\n';
	}

	next_display = read_next_display (displays, gdb);
    }

    // Process own displays
    bool changed   = false;
    bool activated = false;

    // Change `active' status.  This must be done before updating
    // values, since inactive nodes must not be bumped after a resize.
    MapRef ref;
    int k;
    for (k = disp_graph->first_nr(ref); k != 0; k = disp_graph->next_nr(ref))
    {
	DispNode *dn = disp_graph->get(k);
	if (dn->is_user_command() || dn->deferred())
	    continue;

	if (disp_string_map.contains(k))
	{
	    if (disp_graph->make_active(dn))
	    {
		// Now active
		changed = activated = true;
	    }
	}
	else
	{
	    // Node is no more part of `display' output
	    if (disp_graph->make_inactive(dn))
	    {
		// Now inactive
		changed = true;
	    }
	}
    }

    // Update values
    for (k = disp_graph->first_nr(ref); k != 0; k = disp_graph->next_nr(ref))
    {
	DispNode* dn = disp_graph->get(k);
	if (dn->is_user_command() || dn->deferred())
	    continue;

	if (!disp_string_map.contains(k))
	{
	    undo_buffer.remove_display(dn->name());
	    continue;
	}

	// Update existing node
	string *strptr = disp_string_map.get(k);
	s.current = strptr->length();

	undo_buffer.add_display(dn->name(), *strptr);

	if (dn->update(*strptr))
	{
	    // New value
	    changed = true;
	}
	if (!(*strptr).empty() && !(strptr->matches(rxwhite)))
	{
	    // After the `display' output, more info followed
	    // (e.g. the returned value when `finish'ing)
	    not_my_displays += strptr->after(rxwhite);
	}

	s.base += s.current;

	delete strptr;
	disp_string_map.del(k);
    }

    assert (disp_string_map.length() == 0);
    if (activated)
    {
	force_check_aliases = true;
	refresh_addr();
    }

    if (changed)
	refresh_graph_edit();

    return not_my_displays;
}


//-----------------------------------------------------------------------------
// Undo stuff
//-----------------------------------------------------------------------------

void DataDisp::update_displays(const StringArray& displays, 
			       const StringArray& values,
			       const StringArray& addrs)
{
    assert(displays.size() == values.size());
    assert(displays.size() == addrs.size());

    if (displays.size() == 0)
    {
	bool have_displays = false;
	MapRef ref;
	for (DispNode* dn = disp_graph->first(ref); 
	     !have_displays && dn != 0;
	     dn = disp_graph->next(ref))
	{
	    have_displays = dn->active();
	}

	if (!have_displays)
	    return;		// No data and no displays
    }

    bool changed      = false;
    bool addr_changed = false;

    // Check `active' status
    MapRef ref;
    for (DispNode *dn = disp_graph->first(ref);
	 dn != 0; dn = disp_graph->next(ref))
    {
	if (is_cluster(dn))
	    continue;
	if (dn->deferred())
	    continue;

	bool found = false;
	for (int i = 0; !found && i < displays.size(); i++)
	    found = (displays[i] == dn->name());

	if (found)
	{
	    if (!dn->active())
	    {
		disp_graph->make_active(dn);
		changed = true;
	    }
	}
	else
	{
	    if (dn->active())
	    {
		disp_graph->make_inactive(dn);
		changed = true;
	    }
	}
    }

    ProgressMeter s("Restoring displays");
    int i;
    for (i = 0; i < values.size(); i++)
	s.total += values[i].length();

    // Update values
    for (i = 0; i < displays.size(); i++)
    {
	const string& name  = displays[i];
	const string& value = values[i];
	const string& addr  = addrs[i];

	MapRef ref;
	for (DispNode *dn = disp_graph->first(ref);
	     dn != 0; dn = disp_graph->next(ref))
	{
	    if (dn->name() != name)
		continue;
	    if (dn->deferred())
		continue;

	    s.current = value.length();

	    string v = value;
	    if (dn->update(v))
		changed = true;

	    if (dn->addr() != addr)
	    {
		dn->set_addr(addr);
		addr_changed = true;
	    }

	    s.base += s.current;
	}
    }

    bool suppressed = false;
    if (addr_changed)
	suppressed = check_aliases();

    if (changed)
	refresh_graph_edit();

    if (addr_changed)
	refresh_display_list(suppressed);
}

// Restore sane state after undoing / redoing
void DataDisp::make_sane()
{
    // Activate all user displays.  Undo may leave them deactivated.
    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref); 
	 dn != 0; dn = disp_graph->next(ref))
    {
	if (dn->is_user_command())
	    dn->make_active();
    }
}

//-----------------------------------------------------------------------------
// Handle output of user commands
//-----------------------------------------------------------------------------

void DataDisp::process_user (StringArray& answers)
{
    if (answers.size() == 0)
	return;

    ProgressMeter s("Updating status displays");

    int i;
    for (i = 0; i < answers.size(); i++)
	s.total += answers[i].length();

    i = 0;
    bool changed = false;
    MapRef ref;
    for (int k = disp_graph->first_nr(ref); 
	     k != 0 && i < answers.size();
	     k = disp_graph->next_nr(ref))
    {
	DispNode* dn = disp_graph->get(k);

	if (dn->is_user_command() && dn->enabled() && 
	    !dn->deferred() && !dn->constant())
	{
	    string answer = answers[i++];

	    undo_buffer.add_display(dn->name(), answer);

	    s.current = answer.length();

	    if (dn->update(answer))
		changed = true;

	    s.base += s.current;
	}
    }

    if (changed)
	refresh_graph_edit();
}



//-----------------------------------------------------------------------------
// Handle change of current scope
//-----------------------------------------------------------------------------

bool DataDisp::need_scope()
{
    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref); 
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	if (dn->deferred())
	    return true;
    }

    return false;
}

void DataDisp::process_scope(const string& scope)
{
    CommandGroup cg;

    // Fetch deferred displays that are in current scope
    IntArray deferred_displays;
    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref); 
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	if (dn->deferred() && dn->scope() == scope)
	    deferred_displays += dn->disp_nr();
    }

    if (deferred_displays.size() > 0)
    {
	// Enable these deferred displays.
	sort(deferred_displays, absolute_le);

	MString msg = rm("Enabling deferred display");
	if (deferred_displays.size() >= 2)
	    msg += rm("s");
	msg += rm(" ");

	int i;
	for (i = 0; i < deferred_displays.size(); i++)
	{
	    if (i > 0)
	    {
		if (deferred_displays.size() == 2)
		    msg += rm(" and ");
		else if (i == deferred_displays.size() - 1)
		    msg += rm(", and ");
		else
		    msg += rm(", ");
	    }
	    msg += rm(itostring(deferred_displays[i]));
	}
	set_status_mstring(msg);

	for (i = 0; i < deferred_displays.size(); i++)
	{
	    DispNode *dn = disp_graph->get(deferred_displays[i]);
	    assert(dn != 0 && dn->deferred());

	    BoxPoint pos = dn->pos();
	    Command c(new_display_cmd(dn->name(), &pos, dn->depends_on(),
				      dn->clustered(), dn->plotted()));
	    c.verbose = false;
	    c.prompt  = false;
	    gdb_command(c);
	}

	for (i = 0; i < deferred_displays.size(); i++)
	{
	    disp_graph->del(deferred_displays[i]);
	}
    }
}



//----------------------------------------------------------------------------
// Display Editor
//----------------------------------------------------------------------------

// Sort LABELS and SELECTED
static void sort(string *labels, bool *selected, int size)
{
    // Shell sort -- simple and fast
    int h = 1;
    do {
	h = h * 3 + 1;
    } while (h <= size);
    do {
	h /= 3;
	for (int i = h; i < size; i++)
	{
	    string v = labels[i];
	    bool   b = selected[i];
	    int    j;
	    for (j = i; 
		 j >= h && get_positive_nr(labels[j - h]) > get_positive_nr(v);
		 j -= h)
	    {
		labels[j]   = labels[j - h];
		selected[j] = selected[j - h];
	    }
	    if (i != j)
	    {
		labels[j]   = v;
		selected[j] = b;
	    }
	}
    } while (h != 1);
}

static string fmt(string s, unsigned size)
{
    if (s.length() > size)
	s = s.before(int(size));
    else if (s.length() < size)
	s += replicate(' ', size - s.length());

    assert(s.length() == size);
    return s;
}

static int max_width(const StringArray& s)
{
    int w = 0;

    for (int i = 0; i < s.size(); i++)
	w = max(w, s[i].length());

    return w;
}

// Create labels for the list
void DataDisp::refresh_display_list(bool silent)
{
    if (display_list_w == 0)
	return;

    // We refresh the list as soon as we return from the callback
    // (LessTif is sensitive about this)
    XtAppAddTimeOut(XtWidgetToApplicationContext(display_list_w),
		    0, RefreshDisplayListCB,
		    (silent ? XtPointer(1):XtPointer(0)) );
}


void DataDisp::RefreshDisplayListCB(XtPointer client_data, XtIntervalId *id)
{
    (void) id;			// Use it

    const bool silent = client_data?true:false;
    const int number_of_displays = disp_graph->count_all();

    StringArray nums;
    StringArray states;
    StringArray exprs;
    StringArray scopes;
    StringArray addrs;

    if (number_of_displays > 0)
    {
	// Add titles
	nums   += "Num";
	states += "State";
	exprs  += "Expression";
	scopes += "Scope";
	addrs  += "Address";
    }
    else
    {
	nums   += "";
	states += "";
	exprs  += "";
	scopes += "";
	addrs  += "";
    }

    MapRef ref;
    int k;
    for (k = disp_graph->first_nr(ref); k != 0; k = disp_graph->next_nr(ref))
    {
	DispNode* dn = disp_graph->get(k);

	nums += itostring(dn->disp_nr()) + ":";

	if (dn->deferred())
	    states += "deferred";
	else if (!dn->active())
	    states += "not active";
	else if (dn->clustered())
	    states += "clustered";
	else if (dn->hidden() && dn->alias_of != 0)
	    states += "alias of " + itostring(dn->alias_of);
	else if (dn->enabled())
	    states += "enabled";
	else
	    states += "disabled";
	
	exprs  += dn->name();
	scopes += dn->scope();
	addrs  += dn->addr();
    }

    int nums_width   = max_width(nums);
    int exprs_width  = max_width(exprs)  + 1;
    int states_width = max_width(states) + 1;
    int scopes_width = max_width(scopes) + 1;
    int addrs_width  = max_width(addrs);

    string *label_list  = new string[number_of_displays + 1];
    bool *selected_list = new bool[number_of_displays + 1];

    // Set titles
    int display_count = 0;
    string line;
    if (number_of_displays > 0)
    {
	line = fmt(nums[display_count], nums_width) 
	    + " " + fmt(exprs[display_count], exprs_width)
	    + " " + fmt(states[display_count], states_width)
	    + " " + fmt(scopes[display_count], scopes_width);
	if (detect_aliases)
	    line += " " + fmt(addrs[display_count], addrs_width);
    }
    else
    {
	line = "No displays.                           ";
    }
    label_list   [display_count] = line;
    selected_list[display_count] = false;
    display_count++;

    int selected_displays = 0;	// Number of selected displays
    int index_selected    = -1;	// Index of single selected display

    // Set contents
    for (k = disp_graph->first_nr(ref); k != 0; k = disp_graph->next_nr(ref))
    {
	DispNode* dn = disp_graph->get(k);
	line = fmt(nums[display_count], nums_width) 
	    + " " + fmt(exprs[display_count], exprs_width)
	    + " " + fmt(states[display_count], states_width)
	    + " " + fmt(scopes[display_count], scopes_width);
	if (detect_aliases)
	    line += " " + fmt(addrs[display_count], addrs_width);

	bool select = selected(dn);

	label_list   [display_count] = line;
	selected_list[display_count] = select;

	if (select)
	{
	    selected_displays++;
	    index_selected = display_count;
	}

	display_count++;
    }

    sort(label_list + 1, selected_list + 1, display_count - 1);

    setLabelList(display_list_w, label_list, selected_list, display_count, 
		 number_of_displays > 0, false);

    if (!silent)
    {
	// Setup status line
	MString msg;

	if (selected_displays == 1)
	{
	    // Show info about single selected display
	    DispNode *dn = selected_node();
	    DispValue *dv = 0;
	    if (dn != 0)
	    {
		if (dn->disabled())
		    dv = dn->value();
		else
		    dv = dn->selected_value();
	    }

	    if (dv != 0)
	    {
		DataDispCount count(disp_graph);

		// Value within display selected
		msg = rm("In display " + nums[index_selected] + " ");

		string title = dv->full_name();
		// shorten(title, DispBox::max_display_title_length);
		msg += tt(title);
		msg += rm(" (double-click to ");
		if (dv->type() == Pointer && !dv->collapsed())
		    msg += rm("dereference");
		else if (count.selected_collapsed > 0)
		    msg += rm("show more");
		else
		    msg += rm("hide");

		msg += rm(")");
	    }
	    else
	    {
		// Display selected
		msg = rm("Display " + nums[index_selected] + " ");

		string title = exprs[index_selected];
		// shorten(title, DispBox::max_display_title_length);
		msg += tt(title);

		msg += rm(" (" + states[index_selected]);
		if (!scopes[index_selected].empty())
		{
		    msg += rm(", scope ");
		    msg += tt(scopes[index_selected]);
		}

		if (detect_aliases && !addrs[index_selected].empty())
		{
		    msg += rm(", address ");
		    msg += tt(addrs[index_selected]);
		}

		msg += rm(")");
	    }

	    set_status_mstring(msg);
	}
	else if (selected_displays > 1)
	{
	    // Show info about multiple selected displays
	    msg = rm("Displays ");
	    IntArray displays;
	    for (k = disp_graph->first_nr(ref); k != 0; 
		 k = disp_graph->next_nr(ref))
	    {
		DispNode* dn = disp_graph->get(k);
		if (selected(dn))
		    displays += dn->disp_nr();
	    }

	    sort(displays);
	    assert(displays.size() == selected_displays);

	    for (k = 0; k < displays.size(); k++)
	    {
		if (k > 0)
		{
		    if (displays.size() == 2)
			msg += rm(" and ");
		    else if (k == displays.size() - 1)
			msg += rm(", and ");
		    else
			msg += rm(", ");
		}
		msg += rm(itostring(displays[k]));
	    }
	    msg += rm(" (" + itostring(displays.size()) 
			      + " displays)");

	    set_status_mstring(msg);
	}
    }

    delete[] label_list;
    delete[] selected_list;
}


void DataDisp::EditDisplaysCB(Widget, XtPointer, XtPointer)
{
    manage_and_raise(edit_displays_dialog_w);
}


//----------------------------------------------------------------------------
// Value Editor
//----------------------------------------------------------------------------

struct SetInfo {
    string name;		// The variable to be set
    Widget text;		// The widget containing the value
    Widget dialog;		// The prompt dialog used
    bool running;		// True if a command has been submitted

    SetInfo()
	: name(""), text(0), dialog(0), running(false)
    {}

private:
    SetInfo(const SetInfo&);
    SetInfo& operator=(const SetInfo&);
};

void DataDisp::DeleteSetInfoCB(Widget, XtPointer client_data, XtPointer)
{
    SetInfo *info = (SetInfo *)client_data;
    info->dialog = 0;
    info->text   = 0;

    if (info->running)
    {
	// The command is still running - don't delete info now
    }
    else
    {
	delete info;
    }
}

void DataDisp::setCB(Widget w, XtPointer, XtPointer)
{
    if (!gdb->has_assign_command())
	return;

    string name;
    DispValue *disp_value = selected_value();
    if (disp_value != 0)
	name = disp_value->full_name();
    else
	name = source_arg->get_string();

    bool can_set = (!name.empty()) && !is_file_pos(name);
    if (!can_set)
	return;

    string value = gdbValue(name);
    if (value == NO_GDB_ANSWER)
    {
	value = "";		// GDB is busy - don't show old value
    }
    else if (!is_valid(value, gdb))
    {
	post_gdb_message(value);
	value = "";		// Variable cannot be accessed
    }
    value = assignment_value(value);

    // Make sure the old value is saved in the history
    add_to_history(gdb->assign_command(name, value));

    SetInfo *info = new SetInfo;
    info->name = name;
    info->running = false;

    Arg args[10];
    int arg = 0;

    XtSetArg(args[arg], XmNdeleteResponse, XmDESTROY); arg++;
    XtSetArg(args[arg], XmNautoUnmanage,   False);     arg++;
    info->dialog = 
	verify(XmCreatePromptDialog(find_shell(w), 
				    XMST("set_dialog"), args, arg));

    Delay::register_shell(info->dialog);

    XtAddCallback(info->dialog, XmNdestroyCallback, 
		  DeleteSetInfoCB, XtPointer(info));

    if (lesstif_version <= 79)
	XtUnmanageChild(XmSelectionBoxGetChild(info->dialog,
					       XmDIALOG_APPLY_BUTTON));
    XtUnmanageChild(XmSelectionBoxGetChild(info->dialog, 
					   XmDIALOG_TEXT));
    XtUnmanageChild(XmSelectionBoxGetChild(info->dialog, 
					   XmDIALOG_SELECTION_LABEL));

    arg = 0;
    XtSetArg(args[arg], XmNmarginWidth,  0); arg++;
    XtSetArg(args[arg], XmNmarginHeight, 0); arg++;
    XtSetArg(args[arg], XmNborderWidth,  0); arg++;
    XtSetArg(args[arg], XmNadjustMargin, False); arg++;
    Widget box = verify(XmCreateRowColumn(info->dialog, 
					  XMST("box"), args, arg));
    XtManageChild(box);

    arg = 0;
    MString prompt = MString("Set value of ") + tt(name);
    XtSetArg(args[arg], XmNalignment, XmALIGNMENT_BEGINNING); arg++;
    XtSetArg(args[arg], XmNlabelString, prompt.xmstring());   arg++;
    Widget label = verify(XmCreateLabel(box, XMST("label"), args, arg));
    XtManageChild(label);

    arg = 0;
    XtSetArg(args[arg], XmNvalue, value.chars()); arg++;
    info->text = verify(CreateComboBox(box, "text", args, arg));
    XtManageChild(info->text);

    tie_combo_box_to_history(info->text, set_history_filter);

    XtAddCallback(info->dialog, XmNokCallback,     setDCB, XtPointer(info));
    XtAddCallback(info->dialog, XmNapplyCallback,  setDCB, XtPointer(info));
    XtAddCallback(info->dialog, XmNhelpCallback,   ImmediateHelpCB, 0);

    XtAddCallback(info->dialog, XmNcancelCallback,
		  DestroyThisCB, XtPointer(info->dialog));

    Widget apply = XmSelectionBoxGetChild(info->dialog, XmDIALOG_APPLY_BUTTON);
    XtManageChild(apply);
    manage_and_raise(info->dialog);
}

void DataDisp::SetDone(const string& complete_answer, void *qu_data)
{
    SetInfo *info = (SetInfo *)qu_data;
    info->running = false;

    if (info->dialog == 0)
    {
	// Dialog has been destroyed while the command was in the queue
	delete info;
	return;
    }

    if (complete_answer == NO_GDB_ANSWER)
	return;			// Command was canceled - keep dialog open
    if (!is_valid(complete_answer, gdb))
	return;			// Bad value - keep dialog open

    // All done - pop down dialog
    XtDestroyWidget(info->dialog);
}

void DataDisp::setDCB(Widget, XtPointer client_data, XtPointer call_data)
{
    SetInfo *info = (SetInfo *)client_data;

    if (info->running)
	return;			// Already running with a value

    XmSelectionBoxCallbackStruct *cbs = 
	(XmSelectionBoxCallbackStruct *)call_data;

    String value_s = XmTextFieldGetString(info->text);
    string value(value_s);
    XtFree(value_s);

    Command c(gdb->assign_command(info->name, value), last_origin);
    if (cbs->reason != XmCR_APPLY)
    {
	// We've pressed OK => destroy widget as soon as command completes.

	info->running = true;
	c.callback    = SetDone;
	c.data        = XtPointer(info);
    }
    gdb_command(c);
}


//----------------------------------------------------------------------------
// Helpers for user displays
//-----------------------------------------------------------------------------

bool DataDisp::have_user_display(const string& name)
{
    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref); 
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	if (dn->user_command() == name)
	    return true;
    }

    return false;
}

void DataDisp::new_user_display(const string& name)
{
    // Check for duplicates
    if (have_user_display(name))
	return;

    gdb_command("graph display `" + name + "`", last_origin);
}

void DataDisp::delete_user_display(const string& name)
{
    IntArray killme;

    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref); 
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	if (dn->user_command() == name)
	{
	    killme += dn->disp_nr();
	}
    }

    delete_display(killme);

    refresh_graph_edit();
}


//----------------------------------------------------------------------------
// Language changed - re-label buttons
//----------------------------------------------------------------------------

void DataDisp::language_changedHP(Agent *, void *, void *)
{
    string arg = source_arg->get_string();
    if (selected_value() != 0)
	arg = selected_value()->full_name();

    string label("Display " + deref(arg, "()"));

    set_label(shortcut_menu[ShortcutItms::Dereference2].widget, label);
    set_label(node_popup[NodeItms::Dereference].widget, label);
    set_label(display_area[DisplayItms::Dereference].widget, label);
    set_label(graph_cmd_area[CmdItms::Dereference].widget, label);
}




//----------------------------------------------------------------------------
// Titles
//----------------------------------------------------------------------------

// Refresh titles after change in APP_DATA
void DataDisp::refresh_titles()
{
    bool changed = disp_graph->refresh_titles();
    if (changed)
	refresh_graph_edit();
}



//----------------------------------------------------------------------------
// Display Clustering
//----------------------------------------------------------------------------

// Set whether aliases are to be detected
void DataDisp::set_cluster_displays(bool value)
{
    if (value == cluster_displays)
	return;

    cluster_displays = value;

    if (cluster_displays)
    {
	// Cluster all independent data displays
	int target_cluster = 0;

	MapRef ref;
	for (DispNode *dn = disp_graph->first(ref); 
	     dn != 0; dn = disp_graph->next(ref))
	{
	    if (dn->is_user_command())
		continue;	// No data display

	    if (dn->firstTo() != 0 && dn->firstTo()->from() != dn)
		continue;	// Dependent display

	    if (dn->clustered())
		continue;	// Already clustered

	    if (target_cluster == 0)
		target_cluster = current_cluster();
	    disp_graph->cluster(dn, target_cluster);
	}

	if (target_cluster != 0)
	    refresh_graph_edit();
    }
    else
    {
	// Uncluster all
	IntArray all_clusters;
	get_all_clusters(all_clusters);

	IntArray killme;

	for (int i = 0; i < all_clusters.size(); i++)
	{
	    DispNode *cluster = disp_graph->get(all_clusters[i]);
	    if (cluster != 0)
	    {
		// Delete cluster
		killme += all_clusters[i];
	    }
	}

	if (killme.size() > 0)
	{
	    delete_display(killme);
	    refresh_graph_edit();
	}
    }
}

void DataDisp::toggleClusterSelectedCB(Widget w, XtPointer client_data, 
				       XtPointer call_data)
{
    DataDispCount count(disp_graph);

    if (count.selected_unclustered > 0)
    {
	clusterSelectedCB(w, client_data, call_data);
    }
    else
    {
	unclusterSelectedCB(w, client_data, call_data);
    }
}

// Uncluster selected nodes (and clusters)
void DataDisp::unclusterSelectedCB(Widget, XtPointer, XtPointer)
{
    // Uncluster selected nodes
    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref); 
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	if (selected(dn) && dn->clustered())
	{
	    // Force cluster to be redisplayed
	    DispNode *cluster = disp_graph->get(dn->clustered());
	    if (cluster != 0)
		cluster->set_last_refresh();

	    // Uncluster display
	    disp_graph->uncluster(dn);
	}
    }

    // Delete selected clusters
    IntArray all_clusters;
    get_all_clusters(all_clusters);

    IntArray killme;
    for (int i = 0; i < all_clusters.size(); i++)
    {
	DispNode *cluster = disp_graph->get(all_clusters[i]);
	if (cluster != 0 && selected(cluster))
	{
	    // Delete cluster
	    killme += all_clusters[i];
	}
    }

    delete_display(killme);
    refresh_args();
    refresh_graph_edit();
}

// Cluster selected nodes into a new cluster
void DataDisp::clusterSelectedCB(Widget, XtPointer, XtPointer)
{
    int target_cluster = 0;
    IntArray all_clusters;
    get_all_clusters(all_clusters);

    // If we have a selected cluster, choose this one as a target
    for (int i = 0; i < all_clusters.size(); i++)
    {
	DispNode *cluster = disp_graph->get(all_clusters[i]);
	if (cluster != 0 && selected(cluster))
	{
	    target_cluster = all_clusters[i];
	    break;
	}
    }

    if (target_cluster == 0)
    {
	// No target cluster selected - make a new one
	target_cluster = new_cluster();
    }

    // Cluster all selected displays into the current one
    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref); 
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	if (!dn->is_user_command() && selected(dn))
	    disp_graph->cluster(dn, target_cluster);
    }

    refresh_graph_edit();
}


//----------------------------------------------------------------------------
// Alias Detection
//----------------------------------------------------------------------------

// True iff aliases are to be checked regardless of address changes
bool DataDisp::force_check_aliases = false;

// Set whether aliases are to be detected
void DataDisp::set_detect_aliases(bool value)
{
    if (value == detect_aliases)
	return;

    detect_aliases = value;
    if (detect_aliases)
    {
	// Re-check for aliases
	force_check_aliases = true;
	refresh_addr();
    }
    else
    {
	bool changed = false;

	MapRef ref;
	for (int k = disp_graph->first_nr(ref); 
	     k != 0; 
	     k = disp_graph->next_nr(ref))
	{
	    // Unmerge all displays
	    changed = unmerge_display(k) || changed;
	}

	if (changed)
	    refresh_graph_edit();
    }
}

// Add address-printing commands to CMDS
int DataDisp::add_refresh_addr_commands(StringArray& cmds, DispNode *dn)
{
    if (!detect_aliases)
	return 0;

    int initial_size = cmds.size();

    if (dn != 0)
    {
 	if (dn->active() && !dn->is_user_command())
	{
	    string addr = gdb->address_expr(dn->name());
	    if (!addr.empty())
		cmds += gdb->print_command(addr);
	}
    }
    else
    {
	MapRef ref;
	for (dn = disp_graph->first(ref); 
	     dn != 0;
	     dn = disp_graph->next(ref))
	{
	    add_refresh_addr_commands(cmds, dn);
	}
    }

    return cmds.size() - initial_size;
}

// Refresh all addresses
void DataDisp::refresh_addr(DispNode *dn)
{
    if (refresh_addr_timer != 0)
    {
	XtRemoveTimeOut(refresh_addr_timer);
	refresh_addr_timer = 0;
	dn = 0;
    }

    RefreshAddrCB(XtPointer(dn), (XtIntervalId *)0);
}

void DataDisp::RefreshAddrCB(XtPointer client_data, XtIntervalId *id)
{
    if (id != 0)
    {
	assert (*id == refresh_addr_timer);
	refresh_addr_timer = 0;
    }

    DispNode *dn = (DispNode *)client_data;

    bool ok = false;
    bool sent = false;
    if (can_do_gdb_command())
    {
	StringArray cmds;
	VoidArray dummy;

	add_refresh_addr_commands(cmds, dn);
	if (cmds.size() > 0)
	{
	    while (dummy.size() < cmds.size())
		dummy += (void *)PROCESS_ADDR;

	    static RefreshInfo info;
	    info.verbose = false;
	    info.prompt  = false;
	    info.cmds    = cmds;
	    bool info_registered;
	    ok = gdb->send_qu_array(cmds, dummy, cmds.size(), 
				    refresh_displayOQAC, (void *)&info,
				    info_registered);

	    sent = cmds.size() > 0;
	}
	else
	{
	    // No refreshing commands - rely on addresses as read
	    bool suppressed = check_aliases();
	    force_check_aliases = false;
	    refresh_display_list(suppressed);
	    ok = true;
	}
    }

    if (!ok)
    {
	// Commands not sent - try again in 50 ms
	refresh_addr_timer = 
	    XtAppAddTimeOut(XtWidgetToApplicationContext(graph_edit),
			    50, RefreshAddrCB, client_data);
    }

    if (sent)
    {
	// At least one command sent - disable redisplay until we have
	// processed all addresses
	graphEditEnableRedisplay(graph_edit, False);
    }
}

// Handle output of addr commands
void DataDisp::process_addr (StringArray& answers)
{
    int i = 0;

    bool changed = false;

    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref); 
	 dn != 0 && i < answers.size();
	 dn = disp_graph->next(ref))
    {
	if (dn->active() && !dn->is_user_command())
	{
	    string addr = answers[i++];

	    if (addr.contains('(', 0) || addr.contains('{', 0))
	    {
		// Skip type prefix
		read_token(addr);
	    }

	    addr = addr.from(rxaddress);
	    addr = addr.through(rxaddress);

	    undo_buffer.add_display_address(dn->name(), addr);

	    if (dn->addr() != addr)
	    {
		dn->set_addr(addr);
		changed = true;
	    }
	}
    }

    bool suppressed = false;
    if (changed || force_check_aliases)
    {
	suppressed = check_aliases();
	force_check_aliases = false;
    }

    // Re-enable redisplay
    graphEditEnableRedisplay(graph_edit, True);

    if (changed)
	refresh_display_list(suppressed);
}

// Check for aliases after change; return true iff displays were suppressed
bool DataDisp::check_aliases()
{
    if (!detect_aliases)
	return false;

    // Group displays into equivalence classes depending on their
    // address and their structure.

    // EQUIVALENCES is an assoc table classed according to addresses.
    // Each entry is a list of tables.  Each table contains
    // structurally equivalent display numbers.

    // If the `typedAliases' resource is `off', all displays
    // go into one category, regardless of structure.

    StringIntArrayArrayAssoc equivalences;

    MapRef ref;
    for (int k = disp_graph->first_nr(ref); 
	     k != 0; 
	     k = disp_graph->next_nr(ref))
    {
	DispNode *dn = disp_graph->get(k);
	if (dn->value() == 0)
	    continue;

	if (dn != 0 && dn->alias_ok())
	{
	    IntArrayArray& list = equivalences[dn->addr()];

	    // Search for structurally equivalent entries in DISPLAY_TABLE.
	    bool added = false;
	    for (int i = 0; !added && i < list.size(); i++)
	    {
		IntArray& displays = list[i];
		assert (displays.size() > 0);

		DispNode *d1 = disp_graph->get(displays[0]);
		if (d1->value() == 0)
		    continue;

		if (!app_data.typed_aliases ||
		    dn->value()->structurally_equal(d1->value()))
		{
		    displays += k;
		    added = true;
		}
	    }

	    if (!added)
	    {
		IntArray new_displays;
		new_displays += k;
		list += new_displays;
	    }
	}
    }

    // Merge displays with identical address.
    bool changed    = false;
    bool suppressed = false;

    for (StringIntArrayArrayAssocIter iter(equivalences); iter.ok(); ++iter)
    {
	string addr = iter.key();
	IntArrayArray& list = iter.value();
	assert(list.size() > 0);

	for (int i = 0; i < list.size(); i++)
	{
	    IntArray& displays = list[i];
	    assert(displays.size() > 0);

	    if (addr.empty() || displays.size() == 1)
	    {
		// No address or just one display -- unmerge them
		for (int k = 0; k < displays.size(); k++)
		    changed = unmerge_display(displays[k]) || changed;
	    }
	    else
	    {
		// Multiple displays at one location
		merge_displays(displays, changed, suppressed);
	    }
	}
    }

    if (changed)
	refresh_graph_edit(suppressed);

    return suppressed;
}

// Return last change, or INT_MAX if hidden
int DataDisp::last_change_of_disp_nr(int disp_nr)
{
    DispNode *dn = disp_graph->get(disp_nr);
    assert(dn != 0);

    if (dn->hidden())
	return INT_MAX;

    return dn->last_change();
}

// Sort DISP_NRS according to the last change
void DataDisp::sort_last_change(IntArray& disp_nrs)
{
    // Shell sort -- simple and fast
    int h = 1;
    do {
	h = h * 3 + 1;
    } while (h <= disp_nrs.size());
    do {
	h /= 3;
	for (int i = h; i < disp_nrs.size(); i++)
	{
	    int v = disp_nrs[i];
	    int j;
	    for (j = i; j >= h && last_change_of_disp_nr(disp_nrs[j - h]) > 
		                  last_change_of_disp_nr(v); j -= h)
		disp_nrs[j] = disp_nrs[j - h];
	    if (i != j)
		disp_nrs[j] = v;
	}
    } while (h != 1);
}

// Merge displays in DISPLAYS.  Set CHANGED iff changed.  Set
// SUPPRESSED if displays were suppressed.
void DataDisp::merge_displays(IntArray displays,
			      bool& changed, bool& suppressed)
{
    assert(displays.size() > 0);

    // Hide all aliases except the node which has changed least recently.
    sort_last_change(displays);

    int i;
#if 0
    for (i = 0; i < displays.size(); i++)
    {
	std::clog << "Last change of display " << displays[i]
		  << ": " << last_change_of_disp_nr(displays[i]) << "\n";
    }
#endif

    DispNode *d0 = disp_graph->get(displays[0]);
    if (d0->active() && d0->hidden())
    {
	// All aliases are hidden.  Make sure we see at least the
	// least recently changed one.
	changed = unmerge_display(displays[0]) || changed;
    }

    IntArray suppressed_displays;
    for (i = 1; i < displays.size(); i++)
    {
	int disp_nr = displays[i];
	DispNode *dn = disp_graph->get(disp_nr);

	if (!dn->active())
	    continue;		// Out of scope

	bool hidden = dn->hidden();

	if (!hidden && dn->firstTo() == 0)
	{
	    // There is no edge pointing at this node.  Don't merge it
	    // because it would simply disappear otherwise.
	    changed = unmerge_display(disp_nr) || changed;
	}
	else
	{
	    bool c = disp_graph->alias(graph_edit, displays[0], disp_nr);
	    if (c)
	    {
		if (!hidden)
		    suppressed_displays += disp_nr;
		changed = true;
	    }
	}
    }

    if (suppressed_displays.size() > 0)
    {
	suppressed = true;

	sort(suppressed_displays);

	// Some displays have been suppressed.  Generate appropriate message.
	MString msg = rm("Suppressing ");

	if (suppressed_displays.size() == 1)
	{
	    DispNode *node = disp_graph->get(suppressed_displays[0]);
	    msg += rm("display " + itostring(node->disp_nr()) + ": ");
	    msg += tt(node->name());
	}
	else if (suppressed_displays.size() == 2)
	{
	    msg += rm("displays "
		      + itostring(suppressed_displays[0])
		      + " and "
		      + itostring(suppressed_displays[1]));
	}
	else
	{
	    msg += rm("displays ");
	    for (i = 1; i < suppressed_displays.size(); i++)
	    {
		if (i == suppressed_displays.size() - 1)
		    msg += rm(", and ");
		else if (i > 1)
		    msg += rm(", ");
		msg += rm(itostring(suppressed_displays[i]));
	    }
	}

	if (suppressed_displays.size() == 1)
	    msg += rm(" because it is an alias");
	else
	    msg += rm(" because they are aliases");

	DispNode *of = disp_graph->get(displays[0]);
	msg += rm(" of display " + itostring(of->disp_nr()) + ": ");
	msg += tt(of->name());

	set_status_mstring(msg);
    }
}

bool DataDisp::unmerge_display(int disp_nr)
{
    return disp_graph->unalias(disp_nr);
}

void DataDisp::PreLayoutCB(Widget w, XtPointer, XtPointer)
{
    if (detect_aliases)
    {
	// Don't redisplay while or after layouting
	graphEditEnableRedisplay(w, False);
    }
}

// Re-enable aliases after layouting
void DataDisp::PostLayoutCB(Widget w, XtPointer, XtPointer)
{
    if (detect_aliases)
    {
	// Unmerge and re-merge all displays
	MapRef ref;
	for (int k = disp_graph->first_nr(ref); 
	     k != 0; 
	     k = disp_graph->next_nr(ref))
	{
	    unmerge_display(k);
	}
	check_aliases();

	// Okay - we can redisplay now
	graphEditEnableRedisplay(w, True);
	refresh_graph_edit();
    }
}

// True iff we have some selection
bool DataDisp::have_selection()
{
    MapRef ref;
    for (DispNode* dn = disp_graph->first(ref);
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	if (dn->selected())
	    return true;
    }
    return false;
}

// Select node, copying selection state from NR
void DataDisp::select_node(DispNode *dn, int nr)
{
    dn->selected() = true;
    if (nr == 0)
	return;

    DispNode *src = disp_graph->get(nr);
    if (src == 0)
	return;
	
    dn->copy_selection_state(*src);
    refresh_args(true);
}


//----------------------------------------------------------------------------
// Bumper
//----------------------------------------------------------------------------

bool DataDisp::bump_displays = true;

static bool Yes(RegionGraphNode *, const BoxSize&)
{
    return true;
}

// This one is called whenever NODE is to be resized to NEWSIZE
bool DataDisp::bump(RegionGraphNode *node, const BoxSize& newSize)
{
    if (!bump_displays)
	return true;		// Okay

    if (node->pos() == BoxPoint())
	return true;		// No valid position yet

    DispNode *dn = ptr_cast(DispNode, node);
    if (dn != 0 && (!dn->active() || dn->clustered()))
	return true;		// Clustered or inactive

    const GraphGC& gc = graphEditGetGraphGC(graph_edit);
    BoxRegion oldRegion = node->region(gc);

    // Do the resize, but don't get called recursively
    RegionGraphNode::ResizeCB = Yes;
    node->resize(newSize);
    RegionGraphNode::ResizeCB = bump;

    // Let origin remain constant
    node->moveTo(node->originToPos(oldRegion.origin(), gc));

    // Move all nodes that are right or below NODE such that their
    // distance to NODE remains constant.

    // DELTA is the difference between old and new size
    BoxSize delta  = node->space(gc) - oldRegion.space();

    // NODE_ORIGIN is the (old) upper left corner of NODE
    // BoxPoint node_origin = oldRegion.origin();

    // BUMPER is the (old) lower right corner of NODE
    BoxPoint node_bumper = oldRegion.origin() + oldRegion.space();

    for (GraphNode *r = disp_graph->firstNode(); 
	 r != 0; r = disp_graph->nextNode(r))
    {
	if (r == node)
	    continue;

	// If R is inactive or clustered, don't bump it
	DispNode *rn = ptr_cast(DispNode, r);
	if (rn != 0 && (!rn->active() || rn->clustered()))
	    continue;

	// If ORIGIN (the upper left corner of R) is right of BUMPER,
	// move R DELTA units to the right.  If it is below BUMPER,
	// move R DELTA units down.

	BoxPoint r_origin = r->origin(gc);
	// BoxPoint r_bumper = r->origin(gc) + r->space(gc);

	BoxPoint r_pos = r->pos();

	if (r_origin[X] > node_bumper[X])
	    r_pos[X] += delta[X];
	if (r_origin[Y] > node_bumper[Y])
	    r_pos[Y] += delta[Y];

	r->moveTo(r_pos);
    }

    // All is done - don't use default behavior.
    return false;
}

//----------------------------------------------------------------------------
// Themes
//-----------------------------------------------------------------------------

void DataDisp::set_theme_manager(const ThemeManager& t)
{
    DispBox::theme_manager = t;
    DispBox::clear_vsllib_cache();

    // Recompute all
    MapRef ref;
    for (DispNode *dn = disp_graph->first(ref); 
	 dn != 0;
	 dn = disp_graph->next(ref))
    {
	dn->reset();
    }

    unselectAllCB(graph_edit, 0, 0);
}



//----------------------------------------------------------------------------
// Constructor
//----------------------------------------------------------------------------

DataDisp::DataDisp(Widget parent, Widget& data_buttons_w)
{
    XtAppContext app_context = XtWidgetToApplicationContext(parent);

    registerOwnConverters();

    // Init globals
    StringBox::fontTable      = new FontTable (XtDisplay(parent));
    DispBox::vsllib_name      = app_data.vsl_library;
    DispBox::vsllib_base_defs = app_data.vsl_base_defs;
    DispBox::vsllib_defs      = app_data.vsl_defs;

    string ddd_themes_dir = resolvePath("themes/", false);
    string path = ":" + string(app_data.vsl_path) + ":";
    path.gsub(":user_themes:", ":" + session_themes_dir() + ":");
    path.gsub(":" ddd_NAME "_themes:", ":" + ddd_themes_dir + ":");
    path = unquote(path);
    DispBox::vsllib_path = path;

    // Create graph
    disp_graph = new DispGraph();
    disp_graph->addHandler(DispGraph_Empty, no_displaysHP);

    // Create graph toolbar
    unsigned char label_type = XmSTRING;
    if (app_data.button_captions || app_data.button_images)
	label_type = XmPIXMAP;

    Widget arg_label = 0;
    if (graph_cmd_w == 0 && !app_data.toolbars_at_bottom)
    {
	graph_cmd_w = create_toolbar(parent, "graph", 
				     graph_cmd_area, 0, arg_label, graph_arg,
				     label_type);
    }

    // Add buttons
    if (data_buttons_w == 0 && !app_data.toolbars_at_bottom)
	data_buttons_w = 
	    make_buttons(parent, "data_buttons", app_data.data_buttons);

    // Create graph editor
    Arg args[10];
    int arg = 0;
    XtSetArg (args[arg], ARGSTR(XtNgraph), (Graph *)disp_graph); arg++;

    if (app_data.panned_graph_editor)
    {
	graph_edit = createPannedGraphEdit(parent, 
					   "graph_edit", args, arg);
	graph_form_w = pannerOfGraphEdit(graph_edit);
    }
    else
    {
	graph_edit = createScrolledGraphEdit(parent, "graph_edit", args, arg);
	graph_form_w = scrollerOfGraphEdit(graph_edit);
    }

    set_last_origin(graph_edit);

    // Add actions
    XtAppAddActions (app_context, actions, XtNumber (actions));
    XtManageChild (graph_edit);

    // Create buttons
    registerOwnConverters();

    if (graph_cmd_w == 0)
    {
	graph_cmd_w = create_toolbar(parent, "graph", 
				     graph_cmd_area, 0, arg_label, graph_arg,
				     label_type);
    }

    if (arg_label != 0)
    {
	XtAddCallback(arg_label, XmNactivateCallback,
		      SelectionLostCB, XtPointer(0));
	XtAddCallback(arg_label, XmNactivateCallback, 
		      ClearTextFieldCB, graph_arg->text());
    }

    // Create (unmanaged) selection widget
    graph_selection_w =
	verify(XmCreateText(graph_cmd_w, XMST("graph_selection"), 
			    ArgList(0), 0));
    XtAddCallback(graph_selection_w, XmNlosePrimaryCallback, 
		  SelectionLostCB, XtPointer(0));
}

void DataDisp::create_shells()
{
    Arg args[10];
    Cardinal arg = 0;

    // Create menus
    graph_popup_w = 
	MMcreatePopupMenu(graph_edit, "graph_popup", graph_popup);
    InstallButtonTips(graph_popup_w);

    node_popup_w = 
	MMcreatePopupMenu(graph_edit, "node_popup", node_popup);
    InstallButtonTips(node_popup_w);

    shortcut_popup_w = 
	MMcreatePopupMenu(graph_edit, "shortcut_popup", shortcut_popup1);
    InstallButtonTips(shortcut_popup_w);

    disp_graph->callHandlers();

    // Create display editor
    arg = 0;
    XtSetArg(args[arg], XmNvisibleItemCount, 0); arg++;
    edit_displays_dialog_w =
	verify(createTopLevelSelectionDialog(find_shell(graph_edit), 
					     "edit_displays_dialog", 
					     args, arg));
    Delay::register_shell(edit_displays_dialog_w);

    XtUnmanageChild(XmSelectionBoxGetChild(edit_displays_dialog_w,
					   XmDIALOG_TEXT));
    XtUnmanageChild(XmSelectionBoxGetChild(edit_displays_dialog_w,
					   XmDIALOG_CANCEL_BUTTON));
    XtUnmanageChild(XmSelectionBoxGetChild(edit_displays_dialog_w,
					   XmDIALOG_APPLY_BUTTON));
    XtUnmanageChild(XmSelectionBoxGetChild(edit_displays_dialog_w,
					   XmDIALOG_SELECTION_LABEL));
    XtUnmanageChild(XmSelectionBoxGetChild(edit_displays_dialog_w,
					   XmDIALOG_LIST_LABEL));

    display_list_w = 
	XmSelectionBoxGetChild(edit_displays_dialog_w, XmDIALOG_LIST);

    if (app_data.flat_dialog_buttons)
    {
	for (MMDesc *item = display_area; item != 0 && item->name != 0; item++)
	{
	    if ((item->type & MMTypeMask) == MMPush)
		item->type = (MMFlatPush | (item->type & ~MMTypeMask));
	}
    }

    Widget buttons = verify(MMcreateWorkArea(edit_displays_dialog_w, 
					     "buttons", display_area));
    XtVaSetValues(buttons,
		  XmNmarginWidth,     0, 
		  XmNmarginHeight,    0, 
		  XmNborderWidth,     0,
		  XmNshadowThickness, 0, 
		  XmNspacing,         0,
		  XtPointer(0));

    MMaddCallbacks (display_area);
    MMaddHelpCallback(display_area, ImmediateHelpCB);
    register_menu_shell(display_area);

    // Add widget callbacks
    XtAddCallback(graph_edit, XtNpreSelectionCallback,
		  DoubleClickCB, XtPointer(this));
    XtAddCallback(graph_edit, XtNselectionChangedCallback,
		  UpdateDisplayEditorSelectionCB, XtPointer(this));
    XtAddCallback(graph_edit, XtNcompareNodesCallback,
		  CompareNodesCB, XtPointer(this));
    XtAddCallback(graph_edit, XtNpreLayoutCallback,
		  PreLayoutCB, XtPointer(this));
    XtAddCallback(graph_edit, XtNpostLayoutCallback,
		  PostLayoutCB, XtPointer(this));

    if (display_list_w != 0)
    {
	XtAddCallback(display_list_w,
		      XmNsingleSelectionCallback,
		      UpdateGraphEditorSelectionCB,
		      0);
	XtAddCallback(display_list_w,
		      XmNmultipleSelectionCallback,
		      UpdateGraphEditorSelectionCB,
		      0);
	XtAddCallback(display_list_w,
		      XmNextendedSelectionCallback,
		      UpdateGraphEditorSelectionCB,
		      0);
	XtAddCallback(display_list_w,
		      XmNbrowseSelectionCallback,
		      UpdateGraphEditorSelectionCB,
		      0);
    }

    if (edit_displays_dialog_w != 0)
    {
	XtAddCallback(edit_displays_dialog_w,
		      XmNokCallback,
		      UnmanageThisCB,
		      edit_displays_dialog_w);
	XtAddCallback(edit_displays_dialog_w,
		      XmNhelpCallback,
		      ImmediateHelpCB,
		      0);
    }

    // Add graph callbacks
    RegionGraphNode::ResizeCB = bump;

    // Reset argument field and display editor buttons
    set_args();
}
