Logo Search packages:      
Sourcecode: ddd version File versions  Download package

DataDisp.C

// $Id$
// Data Display

// Copyright (C) 1995-1999 Technische Universitaet Braunschweig, Germany.
// Copyright (C) 2000 Universitaet Passau, Germany.
// 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 2 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, write to the Free Software Foundation, Inc.,
// 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
// 
// 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
00160 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.


00225 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 };

00247 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
};

00306 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
};


00328 struct DeleteItms { enum Itms {Cluster}; };

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

00337 struct PlotItms { enum Itms { History }; };

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


00347 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
};


00374 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
};

00390 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
//-----------------------------------------------------------------------------

01543 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
//-----------------------------------------------------------------------------

01710 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
//-----------------------------------------------------------------------------

02018 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
//-----------------------------------------------------------------------------

04295 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
//----------------------------------------------------------------------------

05903 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();
}

Generated by  Doxygen 1.6.0   Back to index