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

SourceView.C

// $Id$
// Use the Source, Luke.

// Copyright (C) 1995-1998 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 SourceView_rcsid[] =
    "$Id$";


// Fixing Some Bugs on a Sunday Evening
// ------------------------------------
// 
// Whose bugs these are I think I know,
// But now he works at 3DO;
// He will not see me working here
// To fix his code and make it go.
// 
// The saner folk must think it queer
// To trace without the source code near
// After a launch and frozen mouse
// The weirdest stack crawl of the year.
// 
// I give my nodding head a shake
// To see if I can stay awake
// The only other thing to do
// Is find some more coffeine to take.
// 
// This bug is pretty hard to nip,
// But I have other ones to fix,
// And tons to go before we ship,
// And tons to go before we ship.
//
//
// Written by David A. Lyons <dlyons@apple.com>, January 1994
// (with apologies to Robert Frost)
// 
// Hey, it's fiction.  Close to reality in spirit,
// but does not refer to a specific project, bug, Sunday,
// or brand of soft drink.

#ifndef LOG_GLYPHS
#define LOG_GlYPHS 0
#endif

//-----------------------------------------------------------------------------

#include "SourceView.h"

// DDD stuff
#include "AppData.h"
#include "ComboBox.h"
#include "Command.h"
#include "DataDisp.h"         // Only for `DataDisp::SelectionLostCB'
#include "Delay.h"
#include "DestroyCB.h"
#include "HelpCB.h"
#include "HistoryD.h"
#include "InitImage.h"
#include "IntArray.h"
#include "MakeMenu.h"
#include "PosBuffer.h"
#include "RefreshDI.h"
#include "TextSetS.h"
#include "TimeOut.h"
#include "UndoBuffer.h"
#include "assert.h"
#include "basename.h"
#include "buttons.h"
#include "casts.h"
#include "charsets.h"
#include "cmdtty.h"
#include "cook.h"
#include "cwd.h"
#include "dbx-lookup.h"
#include "ddd.h"
#include "deref.h"
#include "disp-read.h"
#include "editing.h"
#include "events.h"
#include "file.h"
#include "filetype.h"
#include "fortranize.h"
#include "history.h"
#include "index.h"
#include "isid.h"
#include "java.h"
#include "logo.h"
#include "misc.h"
#include "mydialogs.h"
#include "options.h"
#include "post.h"
#include "question.h"
#include "regexps.h"
#include "shell.h"
#include "shorten.h"
#include "status.h"
#include "string-fun.h"
#include "strtoul.h"
#include "tabs.h"
#include "verify.h"
#include "version.h"
#include "windows.h"
#include "wm.h"

// Motif stuff
#include <Xm/Xm.h>
#include <Xm/Form.h>
#include <Xm/Label.h>
#include <Xm/MessageB.h>
#include <Xm/Text.h>
#include <Xm/TextF.h>
#include <Xm/RowColumn.h>
#include <Xm/PushB.h>
#include <Xm/SelectioB.h>
#include <Xm/List.h>
#include <Xm/PanedW.h>
#include <Xm/ToggleB.h>
#include <X11/StringDefs.h>
#include <X11/cursorfont.h>

#if XmVersion >= 2000
#include <Xm/SpinB.h>
#ifndef XmIsSpinBox
#define XmIsSpinBox(w) XtIsSubclass((w), xmSpinBoxWidgetClass)
#endif
#endif

// LessTif hacks
#include <X11/IntrinsicP.h>
#include "LessTifH.h"

// System stuff
extern "C" {
#include <sys/types.h>
#include <sys/stat.h>
}
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <limits.h>

// Test for regular file - see stat(3)
#ifndef S_ISREG
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#endif


//-----------------------------------------------------------------------
// Xt stuff
//-----------------------------------------------------------------------
XtActionsRec SourceView::actions [] = {
    {XTARECSTR("source-popup-menu"),        SourceView::srcpopupAct        },
    {XTARECSTR("source-start-select-word"), SourceView::startSelectWordAct },
    {XTARECSTR("source-end-select-word"),   SourceView::endSelectWordAct   },
    {XTARECSTR("source-update-glyphs"),     SourceView::updateGlyphsAct    },
    {XTARECSTR("source-drag-glyph"),        SourceView::dragGlyphAct       },
    {XTARECSTR("source-follow-glyph"),      SourceView::followGlyphAct     },
    {XTARECSTR("source-drop-glyph"),        SourceView::dropGlyphAct       },
    {XTARECSTR("source-delete-glyph"),      SourceView::deleteGlyphAct     },
    {XTARECSTR("source-double-click"),      SourceView::doubleClickAct     },
    {XTARECSTR("source-set-arg"),           SourceView::setArgAct          },
};

//-----------------------------------------------------------------------
// Menus
//-----------------------------------------------------------------------

// Popup menus - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
00197 struct LineItms { enum Itms {SetBP, SetTempBP, Sep1, TempNContBP, 
                       Sep2, SetPC}; };
MMDesc SourceView::line_popup[] = 
{
    {"set",         MMPush, {SourceView::line_popup_setCB, 0}, 0, 0, 0, 0},
    {"set_temp",    MMPush, 
     {SourceView::line_popup_set_tempCB, 0}, 0, 0, 0, 0},
    MMSep,
    {"temp_n_cont", MMPush, 
     {SourceView::line_popup_temp_n_contCB, 0}, 0, 0, 0, 0},
    MMSep,
    {"set_pc",      MMPush, {SourceView::line_popup_set_pcCB, 0}, 0, 0, 0, 0},
    MMEnd
};

00212 struct BPItms { enum Itms {Properties, Disable, Delete, Sep, SetPC}; };
MMDesc SourceView::bp_popup[] =
{
    {"properties",   MMPush, 
     {SourceView::EditBreakpointPropertiesCB, 0}, 0, 0, 0, 0},
    {"disable",      MMPush, {SourceView::bp_popup_disableCB, 0}, 0, 0, 0, 0},
    {"delete",       MMPush, {SourceView::bp_popup_deleteCB, 0}, 0, 0, 0, 0},
    MMSep,
    {"set_pc",       MMPush, {SourceView::bp_popup_set_pcCB, 0}, 0, 0, 0, 0},
    MMEnd
};

00224 struct BPButtons { enum Itms {Properties, Lookup, NewBP, NewWP, Print, 
                        Enable, Disable, Delete}; };
MMDesc SourceView::bp_area[] =
{
    {"properties",   MMPush,   
     {SourceView::EditBreakpointPropertiesCB, 0}, 0, 0, 0, 0},
    {"lookup",       MMPush,   
     {SourceView::LookupBreakpointCB, XtPointer(0) }, 0, 0, 0, 0},
    {"new_bp",       MMPush,   {SourceView::NewBreakpointCB, 0}, 0, 0, 0, 0},
    {"new_wp",       MMPush,   {SourceView::NewWatchpointCB, 0}, 0, 0, 0, 0},
    {"print",        MMPush,   
     {SourceView::PrintWatchpointCB, XtPointer(0) }, 0, 0, 0, 0},
    {"enable",       MMPush,   
     {SourceView::BreakpointCmdCB, XtPointer("enable") }, 0, 0, 0, 0},
    {"disable",      MMPush,   
     {SourceView::BreakpointCmdCB, XtPointer("disable") }, 0, 0, 0, 0},
    {"delete",       MMPush | MMHelp,   
     {SourceView::BreakpointCmdCB, XtPointer("delete") }, 0, 0, 0, 0},
    MMEnd
};


00246 struct TextItms { 
    enum Itms {
      Print, 
      Disp, 
      Watch,
      Sep1, 
      PrintRef, 
      DispRef,
      WatchRef,
      Sep2,
      Whatis,
      Sep3,
      Lookup, 
      Break,
      Clear
    };
};

static const _XtString const text_cmd_labels[] =
{
    "Print ", 
    "Display ", 
    "Watch ",
    "",
    "Print ", 
    "Display ", 
    "Watch ",
    "",
    "What is ",
    "",
    "Lookup " ,
    "Break at ",
    "Clear at "
};

MMDesc SourceView::text_popup[] =
{
    {"print",      MMPush, {SourceView::text_popup_printCB, 0}, 0, 0, 0, 0},
    {"disp",       MMPush, {SourceView::text_popup_dispCB, 0}, 0, 0, 0, 0},
    {"watch",      MMPush | MMUnmanaged, 
     {SourceView::text_popup_watchCB, 0}, 0, 0, 0, 0},
    MMSep,
    {"printRef",   MMPush, 
     {SourceView::text_popup_print_refCB, 0}, 0, 0, 0, 0},
    {"dispRef",    MMPush, 
     {SourceView::text_popup_disp_refCB, 0}, 0, 0, 0, 0},
    {"watchRef",   MMPush | MMUnmanaged, 
     {SourceView::text_popup_watch_refCB, 0}, 0, 0, 0, 0},
    MMSep,
    {"whatis",     MMPush, {SourceView::text_popup_whatisCB, 0}, 0, 0, 0, 0},
    MMSep,
    {"lookup",     MMPush, {SourceView::text_popup_lookupCB, 0}, 0, 0, 0, 0},
    {"breakAt",    MMPush, {SourceView::text_popup_breakCB, 0}, 0, 0, 0, 0},
    {"clearAt",    MMPush, {SourceView::text_popup_clearCB, 0}, 0, 0, 0, 0},
    MMEnd
};


//-----------------------------------------------------------------------
// Glyphs and images
//-----------------------------------------------------------------------

#include "icons/glyphs/arrow.xbm"
#include "icons/glyphs/greyarrow.xbm"
#include "icons/glyphs/pastarrow.xbm"
#include "icons/glyphs/signalarrow.xbm"
#include "icons/glyphs/dragarrow.xbm"
#include "icons/glyphs/stop.xbm"
#include "icons/glyphs/greystop.xbm"
#include "icons/glyphs/dragstop.xbm"
#include "icons/glyphs/cond.xbm"
#include "icons/glyphs/greycond.xbm"
#include "icons/glyphs/dragcond.xbm"
#include "icons/glyphs/temp.xbm"
#include "icons/glyphs/greytemp.xbm"
#include "icons/glyphs/dragtemp.xbm"


//-----------------------------------------------------------------------
// Data.  Lots of 'em.
//-----------------------------------------------------------------------

Widget SourceView::toplevel_w                = 0;
Widget SourceView::source_form_w             = 0;
Widget SourceView::source_text_w             = 0;
Widget SourceView::code_form_w               = 0;
Widget SourceView::code_text_w               = 0;
Widget SourceView::edit_breakpoints_dialog_w = 0;
Widget SourceView::breakpoint_list_w         = 0;
Widget SourceView::stack_dialog_w            = 0;
Widget SourceView::frame_list_w              = 0;
Widget SourceView::up_w                      = 0;
Widget SourceView::down_w                    = 0;
Widget SourceView::register_dialog_w         = 0;
Widget SourceView::register_list_w           = 0;
Widget SourceView::all_registers_w           = 0;
Widget SourceView::int_registers_w           = 0;
Widget SourceView::thread_dialog_w           = 0;
Widget SourceView::thread_list_w             = 0;

bool SourceView::stack_dialog_popped_up    = false;
bool SourceView::register_dialog_popped_up = false;
bool SourceView::thread_dialog_popped_up   = false;

bool SourceView::cache_source_files     = true;
bool SourceView::cache_machine_code     = true;
bool SourceView::display_glyphs         = true;
bool SourceView::display_line_numbers   = false;
bool SourceView::disassemble            = true;
bool SourceView::all_registers          = false;

int  SourceView::source_indent_amount = 4;
int  SourceView::script_indent_amount = 4;
int  SourceView::code_indent_amount   = 4;
int  SourceView::line_indent_amount   = 4;
int  SourceView::tab_width            = DEFAULT_TAB_WIDTH;

int  SourceView::lines_above_cursor   = 2;
int  SourceView::lines_below_cursor   = 3;

SourceOrigin SourceView::current_origin = ORIGIN_NONE;

Map<int, BreakPoint> SourceView::bp_map;

string SourceView::current_file_name = "";
int    SourceView::line_count = 0;
IntIntArrayAssoc SourceView::bps_in_line;
TextPositionArray SourceView::_pos_of_line;
StringArray SourceView::bp_addresses;
StringStringAssoc SourceView::file_cache;
StringOriginAssoc SourceView::origin_cache;
StringStringAssoc SourceView::source_name_cache;
StringStringAssoc SourceView::file_name_cache;
CodeCache SourceView::code_cache;

string SourceView::current_source;
string SourceView::current_code;
string SourceView::current_code_start;
string SourceView::current_code_end;

string SourceView::current_pwd        = cwd();
string SourceView::current_class_path = NO_GDB_ANSWER;

XmTextPosition SourceView::last_top                = 0;
XmTextPosition SourceView::last_pos                = 0;
XmTextPosition SourceView::last_start_highlight    = 0;
XmTextPosition SourceView::last_end_highlight      = 0;

XmTextPosition SourceView::last_top_pc             = 0;
XmTextPosition SourceView::last_pos_pc             = 0;
XmTextPosition SourceView::last_start_highlight_pc = 0;
XmTextPosition SourceView::last_end_highlight_pc   = 0;

string SourceView::last_execution_file = "";
int    SourceView::last_execution_line = 0;
string SourceView::last_execution_pc   = "";
string SourceView::last_shown_pc       = "";

int SourceView::last_frame_pos = 0;
bool SourceView::frame_pos_locked = false;
int SourceView::current_frame = -1;

bool SourceView::checking_scroll = false;

bool SourceView::at_lowest_frame = true;
bool SourceView::signal_received = false;

int SourceView::max_popup_expr_length = 20;

int SourceView::max_breakpoint_number_seen = 0;

//-----------------------------------------------------------------------
// Selection stuff
//-----------------------------------------------------------------------

static XmTextPosition selection_startpos;
static XmTextPosition selection_endpos;
static Time           selection_time;
#if XtSpecificationRelease < 6
static XEvent         selection_event;
#endif


//-----------------------------------------------------------------------
// Helping functions.
//-----------------------------------------------------------------------

// Sort A
static void sort(IntArray& a)
{
    // 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 && a[j - h] > v; j -= h)
            a[j] = a[j - h];
          if (i != j)
            a[j] = v;
      }
    } while (h != 1);
}

// Return index of RXADDRESS in S, beginning from POS.  Stop search at newline.
static int address_index(const string& s, int pos)
{
    int eol = s.index('\n', pos);
    if (eol < 0)
      eol = s.length();

    const string first_line = s.at(pos, eol - pos);
    int i = 0;
    while (i < int(first_line.length()) && isspace(first_line[i]))
      i++;
    i = first_line.index(rxaddress_start, i);
    if (i < 0)
      return -1;
    else
      return pos + i;
}

// Return true if W is a descendant of code_form_w
bool SourceView::is_code_widget(Widget w)
{
    while (w != 0)
    {
      if (w == code_form_w)
          return true;
      else
          w = XtParent(w);
    }
    return false;
}

// Return true if W is a descendant of source_form_w
bool SourceView::is_source_widget(Widget w)
{
    while (w != 0)
    {
      if (w == source_form_w)
          return true;
      else
          w = XtParent(w);
    }
    return false;
}

string& SourceView::current_text(Widget w)
{
    assert(is_source_widget(w) || is_code_widget(w));

    if (is_code_widget(w))
      return current_code;
    else
      return current_source;
}


static const int MAX_INDENT = 64;

int SourceView::indent_amount(Widget w, int pos)
{
    assert(is_source_widget(w) || is_code_widget(w));

    int indent = 0;
    if (is_code_widget(w))
    {
      indent = code_indent_amount;
    }
    else
    {
      indent = source_indent_amount;

      if (display_line_numbers)
          indent += line_indent_amount;

      // Set a minimum indentation for scripting languages
      if (gdb->requires_script_indent())
          indent = max(indent, script_indent_amount);
    }

    // Make sure indentation stays within reasonable bounds
    indent = min(max(indent, 0), MAX_INDENT);

    if (pos >= 0)
    {
      const string& text = current_text(w);
      while (pos < int(text.length()) && text[pos] == ' ')
      {
          pos++;
          indent++;
      }
    }

    return indent;
}


//-----------------------------------------------------------------------
// Methods
//-----------------------------------------------------------------------


// ***************************************************************************
//
void SourceView::line_popup_setCB (Widget w,
                           XtPointer client_data,
                           XtPointer)
{
    const string address = *((const string *)client_data);
    create_bp(address, w);
}

void SourceView::line_popup_set_tempCB (Widget w,
                              XtPointer client_data,
                              XtPointer)
{
    const string address = *((const string *)client_data);
    create_temp_bp(address, w);
}

// Create or clear a breakpoint at position A.  If SET, create a
// breakpoint; if not SET, delete it.  If TEMP, make the breakpoint
// temporary.  If COND is given, break only iff COND evals to true. W
// is the origin.
void SourceView::set_bp(const string& a, bool set, bool temp, 
                  const char *cond, Widget w)
{
    CommandGroup cg;

    int new_bps = max_breakpoint_number_seen + 1;
    string address = a;

    if (address.contains('0', 0) && !address.contains(":"))
      address.prepend("*");   // Machine code address given

    if (!set)
    {
      // Clear bp
      gdb_command(clear_command(address), w);
    }
    else
    {
      // Set bp
      switch (gdb->type())
      {
      case GDB:
      case BASH:
      case PYDB:
      case DBG:
          if (temp)
            gdb_command("tbreak " + address, w);
          else
            gdb_command("break " + address, w);
          break;

      case DBX:
      {
          string cond_suffix = "";
          if (strlen(cond) != 0)
          {
            if (gdb->has_handler_command())
                cond_suffix = string(" -if ") + cond;
            else
                cond_suffix = string(" if ") + cond;
          }

          if (address.contains('*', 0))
          {
            // Address given
            address = address.after('*');
            gdb_command("stopi at " + address + cond_suffix, w);

            if (temp)
            {
                syncCommandQueue();
                gdb_command("when $pc == " + address + " "
                        + command_list(clear_command(address, true, 
                                               new_bps)),
                        w);
            }
          }
          else
          {
            string line = "";
            if (address.matches(rxint))
            {
                // Line number given
                line = address;
                gdb_command("stop at " + address + cond_suffix, w);
            }
            else if (is_file_pos(address))
            {
                // FILE:LINE given
                int colon_index = address.index(':', -1);
                assert(colon_index >= 0);
                string file = address.before(colon_index);
                line = address.after(colon_index);

                gdb_command("file " + file, w);
                gdb_command("stop at " + line + cond_suffix, w);
            }
            else
            {
                // Function given
                string pos = dbx_lookup(address);

                if (pos.contains(':'))
                {
                  string file = pos.before(':');
                  line = pos.after(':');

                  gdb_command("file " + file, w);
                  gdb_command("stop at " + line + cond_suffix, w);
                }
                else
                {
                  // Cannot determine function position - try this one
                  gdb_command("stop in " + address + cond_suffix, w);
                }
            }

            if (temp && !line.empty())
            {
                syncCommandQueue();
                const string clear_cmd = clear_command(line, true, new_bps);
                gdb_command("when at " + line + " " 
                        + command_list(clear_cmd), w);
            }
          }
          break;
      }

      case JDB:
      {
          if (is_file_pos(address))
            gdb_command("stop at " + address);
          else
            gdb_command("stop in " + address);
          break;
      }

      case XDB:
      {
          string command;
          if (address.contains('*', 0))
            command = "ba " + address.after('*');
          else
            command = "b " + address;

          if (temp)
            command += " \\1t";

          if (strlen(cond) != 0 && !gdb->has_condition_command())
            command += " {if " + string(cond) + " {} {Q;c}}";

          gdb_command(command, w);
          break;
      }

      case PERL:
      {
          if (is_file_pos(address))
          {
            string file = address.before(':');
            address = address.after(':');

            if (!file_matches(file, current_file_name))
                gdb_command("f " + file, w);
          }

          string command = "b " + address;
          if (strlen(cond) != 0 && !gdb->has_condition_command()) {
            command += ' ';
            command += cond;
          }

          gdb_command(command, w);

          if (temp)
          {
            // Perl actions include Perl commands, but not
            // debugger commands.  Use an auto-command instead.
            string del = "d " + address;
            add_auto_command_prefix(del);
            string clear = "a " + address;
            add_auto_command_prefix(clear);

            command = "a " + address + " " + del + "; " + clear;
            gdb_command(command, w);
          }

          break;
      }
      }

      if (strlen(cond) != 0 && gdb->has_condition_command())
      {
          // Add condition
          gdb_command(gdb->condition_command(itostring(new_bps), cond), w);
      }
    }
}

// ***************************************************************************
//
void SourceView::clearBP(XtPointer client_data, XtIntervalId *)
{
    int bp_nr = (int)(long)client_data;
    BreakPoint *bp = bp_map.get(bp_nr);
    if (bp != 0)
      delete_bp(bp_nr);
}

// Save last `jump' target for XDB
static string last_jump_address;

void SourceView::clearJumpBP(const string& msg, void *data)
{
    set_status(msg);

    if (gdb->type() == XDB && msg.empty())
    {
      // Moving PC was successful.
      show_execution_position(last_jump_address, true);
    }

    int old_max_breakpoint_number_seen = (int)(long)data;

    for (int i = old_max_breakpoint_number_seen + 1;
       i <= max_breakpoint_number_seen; i++)
    {
      // Delete all recently created breakpoints
      XtAppAddTimeOut(XtWidgetToApplicationContext(source_text_w),
                  0, clearBP, XtPointer(i));
    }
}

void SourceView::line_popup_temp_n_contCB (Widget w,
                                 XtPointer client_data,
                                 XtPointer)
{
    const string address = *((const string *)client_data);
    temp_n_cont(address, w);
}

void SourceView::temp_n_cont(const string& a, Widget w)
{
    CommandGroup cg;

    string address = a;

    switch (gdb->type())
    {
    case GDB:
#if 0                   // GDB `until' only works in the current frame
      gdb_command("until " + address, w);
      break;
#endif
    
    case BASH: // Is this correct? 
    case DBG:  // Is this correct? 
    case DBX:
    case JDB:
    case PYDB:
    {
      int old_max_breakpoint_number_seen = max_breakpoint_number_seen;

      // Create a temporary breakpoint
      create_temp_bp(address, w);

      // Make sure the temporary breakpoint is deleted after `cont'
      Command c("cont", w);
      c.callback = clearJumpBP;
      c.data     = XtPointer(old_max_breakpoint_number_seen);
      gdb_command(c);
      break;
    }

    case XDB:
      if (address.contains('*', 0))
          address = address.after('*');
      gdb_command("c " + address, w);
      break;

    case PERL:
      if (is_file_pos(address))
          address = address.after(':');
      gdb_command("c " + address, w);
      break;
    }
}

// ***************************************************************************
//
void SourceView::line_popup_set_pcCB(Widget w, 
                             XtPointer client_data,
                             XtPointer)
{
    const string address = *((const string *)client_data);
    move_pc(address, w);
}

// ***************************************************************************
//
bool SourceView::move_pc(const string& a, Widget w)
{
    string address = a;

    if (address.contains('*', 0))
    {
      if (compare_address(address.after('*'), last_execution_pc) == 0)
          return false; // PC already at address
    }
    else
    {
      string file = address.before(':');
      int line    = get_positive_nr(address.after(':'));

      if (file_matches(file, last_execution_file)
          && line == last_execution_line)
          return false; // PC already at address
    }

    if (gdb->has_jump_command())
    {
      int old_max_breakpoint_number_seen = max_breakpoint_number_seen;

      // We prefer the GDB `jump' command to setting `$pc'
      // immediately since `jump' requires confirmation when jumping
      // out of the current function.

      switch (gdb->type())
      {
      case DBX:
          // DBX immediately resumes execution - create a temp
          // breakpoint at ADDRESS
          create_temp_bp(address, w);

          // DBX `cont at ' requires a line number.
          gdb_command("file " + address.before(':'));
          // FALL THROUGH

      case XDB:
          // XDB 'g' wants only a line number
          address = address.after(':');
          break;

      case GDB:
          // GDB immediately resumes execution - create a temp
          // breakpoint at ADDRESS
          create_temp_bp(address, w);
          break;

      case BASH:
      case DBG:
      case JDB:
      case PERL:
      case PYDB:
          break;        // Never reached
      }

      // Jump to the new address and clear the breakpoint again
      last_jump_address = a;
      Command c(gdb->jump_command(address), w);
      c.callback = clearJumpBP;
      c.data     = XtPointer(old_max_breakpoint_number_seen);
      gdb_command(c);

      return true;
    }
    else if (gdb->type() != JDB && gdb->has_assign_command())
    {
      // Try the `set $pc = ADDR' alternative.
      if (address.contains('*', 0))
      {
          address = address.after('*');
      }
      else
      {
          lookup(address, true);
          syncCommandQueue();
          address = last_shown_pc;
      }

      if (address.empty())
      {
          set_status("Cannot determine address of " + a);
      }
      else
      {
          gdb_command(gdb->assign_command("$pc", address), w);
          return true;
      }
    }

    return false;
}

bool SourceView::move_bp(int bp_nr, const string& a, Widget w, bool copy)
{
    CommandGroup cg;

    string address = a;

    // std::clog << "Moving breakpoint " << bp_nr << " to " << address << '\n';

    BreakPoint *bp = bp_map.get(bp_nr);
    if (bp == 0)
      return false;           // No such breakpoint

    if (!copy)
    {
      if (address.contains('*', 0))
      {
          if (compare_address(address.after('*'), bp->address()) == 0)
            return false;     // Breakpoint already at address
      }
      else
      {
          string file = address.before(':');
          int line    = get_positive_nr(address.after(':'));

          if (bp_matches(bp, file, line))
            return false;     // Breakpoint already at address
      }
    }

    // Create a new breakpoint at ADDRESS, making it inherit the
    // current settings
    std::ostringstream os;
    bool ok = bp->get_state(os, 0, false, address);
    if (!ok)
      return false;           // Command failed

    int new_bp_nr = next_breakpoint_number();
    string commands(os);
    commands.gsub("@0@", itostring(new_bp_nr));

    gdb_command(commands, w);

    if (copy)
    {
      // Copy properties
      copy_breakpoint_properties(bp_nr, new_bp_nr);
    }
    else
    {
      // Rename properties
      move_breakpoint_properties(bp_nr, new_bp_nr);

      // Delete old breakpoint
      delete_bp(bp_nr, w);
    }

    return true;
}

void SourceView::_set_bps_cond(const IntArray& _nrs, const string& cond,
                         int make_false, Widget w)
{
    CommandGroup cg;

    // _NRS might be changed via MOVE_BREAKPOINT_PROPERTIES, 
    // so we make a copy
    IntArray nrs(_nrs);

    int count = 0;
    for (int i = 0; i < nrs.size(); i++)
    {
      int bp_nr = nrs[i];
      BreakPoint *bp = bp_map.get(bp_nr);
      if (bp == 0)
          continue;           // No such breakpoint

      string c = cond;
      if (c == char(-1))
          c = bp->condition();

      int m = make_false;
      if (m < 0)
          m = (!bp->enabled() && !gdb->has_enable_command());
      if (m)
          c = BreakPoint::make_false(c);

      if (gdb->has_condition_command())
      {
          // Use the `cond' command to assign a condition
          gdb_command(gdb->condition_command(itostring(bp_nr), c), w);
      }
      else
      {
          // Create a new breakpoint with a new condition COND, making it
          // inherit the current settings
          std::ostringstream os;
          bool ok = bp->get_state(os, 0, false, "", c);
          if (!ok)
            continue;         // Command failed

          string commands(os);

          int new_bp_nr = bp_nr;
          if (gdb->has_numbered_breakpoints())
          {
            new_bp_nr = next_breakpoint_number() + count;
            commands.gsub("@0@", itostring(new_bp_nr));
          }

          gdb_command(commands, w);

          if (gdb->has_numbered_breakpoints())
          {
            // Copy properties to new breakpoint
            move_breakpoint_properties(bp_nr, new_bp_nr);

            // Delete old breakpoint
            delete_bp(bp_nr, w);

            // Next breakpoint will get the next number
            count++;
          }
      }
    }
}


// ***************************************************************************
//
void SourceView::bp_popup_deleteCB (Widget w,
                            XtPointer client_data,
                            XtPointer)
{
    int bp_nr = *((int *)client_data);
    delete_bp(bp_nr, w);
}


// ***************************************************************************
//
void SourceView::bp_popup_disableCB (Widget w, 
                             XtPointer client_data,
                             XtPointer)
{
    int bp_nr = *((int *)client_data);
    BreakPoint *bp = bp_map.get(bp_nr);
    if (bp != 0)
    {
      if (bp->enabled())
          disable_bp(bp_nr, w);
      else
          enable_bp(bp_nr, w);
    }
}

// Convert NRS to a list of numbers
string SourceView::numbers(const IntArray& nrs)
{
    string cmd = ""; 
    for (int i = 0; i < nrs.size(); i++)
    {
      if (i > 0)
          cmd += " ";
      cmd += itostring(nrs[i]);
    }
    return cmd;
}

// Same, but use "" if we have GDB and all numbers are used
string SourceView::all_numbers(const IntArray& nrs)
{
    if ((gdb->type() == GDB || gdb->type() == PYDB || gdb->type() == DBG) && all_bps(nrs))
      return "";        // In GDB, no arg means `all'
    else
      return numbers(nrs);
}

// Return true if NRS contains all breakpoints and
// a GDB delete/disable/enable command can be given without args.
bool SourceView::all_bps(const IntArray& nrs)
{
    if ((gdb->type() != GDB && gdb->type() != PYDB) || nrs.size() < 2)
      return false;

    MapRef ref;
    BreakPoint *bp = 0;
    for (bp = bp_map.first(ref); bp != 0; bp = bp_map.next(ref))
    {
      bool found = false;
      for (int i = 0; !found && i < nrs.size(); i++)
      {
          if (bp->number() == nrs[i])
            found = true;
      }

      if (!found)
          return false;
    }

    return true;
}

void SourceView::enable_bps(const IntArray& nrs, Widget w)
{
    CommandGroup cg;

    if (gdb->has_enable_command())
    {
      gdb_command(gdb->enable_command(all_numbers(nrs)), w);
    }
    else if (gdb->has_conditions())
    {
      // Unset `false' breakpoint condition
      enable_bps_cond(nrs, w);
    }
}

void SourceView::disable_bps(const IntArray& nrs, Widget w)
{
    CommandGroup cg;

    if (gdb->has_disable_command())
    {
      gdb_command(gdb->disable_command(all_numbers(nrs)), w);
    }
    else if (gdb->has_conditions())
    {
      // Set breakpoint condition to `false'
      disable_bps_cond(nrs, w);
    }
}

void SourceView::delete_bps(const IntArray& nrs, Widget w)
{
    CommandGroup cg;

    if (gdb->recording() && gdb->has_clear_command())
    {
      // While recording, prefer commands without explicit numbers.
        for (int i = 0; i < nrs.size(); i++)
      {
          BreakPoint *bp = bp_map.get(nrs[i]);
          if (bp != 0)
            gdb_command(clear_command(bp->pos()));
      }
    }
    else if (gdb->has_delete_command())
    {
      gdb_command(gdb->delete_command(all_numbers(nrs)), w);
    }
    else
    {
        for (int i = 0; i < nrs.size(); i++)
          gdb_command(delete_command(nrs[i]));
    }
}

// A generic deletion command for breakpoint BP_NR - either `clear' or `delete'
string SourceView::delete_command(int bp_nr)
{
    if (gdb->has_delete_command())
    {
      return gdb->delete_command(itostring(bp_nr));
    }
    else if (gdb->has_clear_command())
    {
      BreakPoint *bp = bp_map.get(bp_nr);
      if (bp != 0)
          return clear_command(bp->pos());
    }

    return "";                // No way to delete a breakpoint (*sigh*)
}

// Return `clear ARG' command.  If CLEAR_NEXT is set, attempt to guess
// the next event number and clear this one as well.  (This is useful
// for setting temporary breakpoints, as `delete' must also clear the
// event handler we're about to install.)  Consider only breakpoints
// whose number is >= FIRST_BP.
string SourceView::clear_command(string pos, bool clear_next, int first_bp)
{
    string file = current_file_name;
    string line = pos;
    MapRef ref;

    if (gdb->type() == DBX && !pos.contains(':') && !pos.matches(rxint))
      pos = dbx_lookup(pos);

    if (pos.contains(':'))
    {
      file = pos.before(':');
      line = pos.after(':');
    }

    int line_no = atoi(line.chars());

    if (!clear_next && gdb->has_clear_command())
    {
      switch (gdb->type())
      {
      case BASH:
      case GDB:
      case JDB:
      case PYDB:
          return "clear " + pos;

      case PERL:
          if (line_no > 0 && file_matches(file, current_file_name))
          {
            // Clear the breakpoint
            string command = "B " + line;

            // Check whether there are any other breakpoints with actions
            bool have_other_actions = false;
            bool need_clear_actions = false;
            BreakPoint *bp;
            for (bp = bp_map.first(ref); bp != 0; bp = bp_map.next(ref))
            {
                if (bp->type() == ACTIONPOINT)
                {
                  // We have an action without associated breakpoint.
                  // Be sure to clear this as soon as possible.
                  need_clear_actions = true;
                }

                if (!bp_matches(bp, file, line_no) &&
                  bp->type() == BREAKPOINT && 
                  bp->commands().size() > 0)
                {
                  // We have other breakpoints with actions.
                  have_other_actions = true;
                }
            }

            for (bp = bp_map.first(ref); bp != 0; bp = bp_map.next(ref))
            {
                // If we have any associated actions, clear them all
                if (bp_matches(bp, file, line_no) && 
                  bp->commands().size() > 0)
                {
                  // This breakpoint has actions that must be cleared

                  if (have_other_actions)
                  {
                      // Clear only this action
                      command += "\na " + line;
                  }
                  else
                  {
                      // Clear all actions (including this one)
                      need_clear_actions = true;
                  }
                  break;
                }
            }

            if (!have_other_actions && need_clear_actions)
            {
                // Clear all actions
                command += "\nA";
            }

            return command;
          }
          break;

      case DBX:
          if (line_no > 0 && file_matches(file, current_file_name))
            return "clear " + line;
          break;

      case DBG:
      case XDB:
          break;
      }
    }

    // Delete all matching breakpoints
    int max_bp_nr = -1;
    string bps = "";
    for (BreakPoint* bp = bp_map.first(ref);
       bp != 0;
       bp = bp_map.next(ref))
    {
      if (bp->number() >= first_bp
          && bp_matches(bp, file, line_no))
          {
            if (!bps.empty())
                bps += gdb->wants_delete_comma() ? ", " : " ";
            bps += itostring(bp->number());
            max_bp_nr = max(max_bp_nr, bp->number());
          }
    }

    if (bps.empty())
      return "";

    if (clear_next && max_bp_nr >= 0)
    {
      bps += (gdb->wants_delete_comma() ? ", " : " ");
      bps += itostring(max_bp_nr + 1);
    }

    return gdb->delete_command(bps);
}


// ***************************************************************************
//
void SourceView::bp_popup_set_pcCB(Widget w, XtPointer client_data, 
                           XtPointer call_data)
{
    int bp_nr = *((int *)client_data);
    BreakPoint *bp = bp_map.get(bp_nr);
    if (bp != 0 && !bp->address().empty())
    {
      string address = string('*') + bp->address();
      line_popup_set_pcCB(w, XtPointer(&address), call_data);
    }
}

// ***************************************************************************
//
void SourceView::text_popup_breakCB (Widget w,
                             XtPointer client_data,
                             XtPointer)
{
    const string* word_ptr = (const string*)client_data;
    create_bp(fortranize(*word_ptr, true), w);
}

void SourceView::text_popup_clearCB (Widget w, 
                             XtPointer client_data, 
                             XtPointer)
{
    const string* word_ptr = (const string*)client_data;
    clear_bp(fortranize(*word_ptr, true), w);
}



// ***************************************************************************
//
void SourceView::text_popup_printCB (Widget w, 
                             XtPointer client_data, 
                             XtPointer)
{
    const string* word_ptr = (const string*)client_data;
    assert(word_ptr->length() > 0);

    gdb_command(gdb->print_command(fortranize(*word_ptr), false), w);
}

void SourceView::text_popup_print_refCB (Widget w, 
                               XtPointer client_data, XtPointer)
{
    const string* word_ptr = (const string*)client_data;
    assert(word_ptr->length() > 0);

    gdb_command(gdb->print_command(deref(fortranize(*word_ptr)), false), w);
}


// ***************************************************************************
//
void SourceView::text_popup_watchCB (Widget w, 
                             XtPointer client_data, 
                             XtPointer)
{
    const string* word_ptr = (const string*)client_data;
    assert(word_ptr->length() > 0);

    gdb_command(gdb->watch_command(fortranize(*word_ptr)), w);
}

void SourceView::text_popup_watch_refCB (Widget w, 
                               XtPointer client_data, XtPointer)
{
    const string* word_ptr = (const string*)client_data;
    assert(word_ptr->length() > 0);

    gdb_command(gdb->watch_command(deref(fortranize(*word_ptr))), w);
}


// ***************************************************************************
//
void SourceView::text_popup_dispCB (Widget w, XtPointer client_data, XtPointer)
{
    const string* word_ptr = (const string*)client_data;
    assert(word_ptr->length() > 0);

    gdb_command("graph display " + fortranize(*word_ptr), w);
}

void SourceView::text_popup_disp_refCB (Widget w, 
                              XtPointer client_data, XtPointer)
{
    const string* word_ptr = (const string*)client_data;
    assert(word_ptr->length() > 0);

    gdb_command("graph display " + deref(fortranize(*word_ptr)), w);
}

// ***************************************************************************
//
void SourceView::text_popup_whatisCB (Widget w, XtPointer client_data, 
                              XtPointer)
{
    const string* word_ptr = (const string*)client_data;
    assert(word_ptr->length() > 0);

    gdb_command(gdb->whatis_command(fortranize(*word_ptr)), w);
}

// ***************************************************************************
//
void SourceView::text_popup_lookupCB (Widget, XtPointer client_data, XtPointer)
{
    const string* word_ptr = (const string*)client_data;
    lookup(fortranize(*word_ptr, true));
}


// ***************************************************************************
//

// Return the normalized full path of FILE
string SourceView::full_path(string file)
{
    /* Chris van Engelen <engelen@lucent.com>, Jul 10, 1997
     *
     * The regular expression is used to remove parts from the full path
     * which look like "/aap/../". However, it should ***NOT*** remove
     * the sequence "/../../" from the path, obviously ! On the other
     * hand, sequences like "/.tmpdir.test/../" should be removed.
     * Therefore, the regular expression reads now like:
     *
     * - forward slash
     * - zero or more  characters other than forward slash (dot is allowed)
     * - any character other than forward slash or dot: this makes sure that
     *   a sequence like "/../../" is not modified.
     * - forward slash, two dots, and the final forward slash.
     *
     * The only valid patterns which are not normalized are patterns ending
     * in a dot: too bad, you can't win them all.
     */

#if RUNTIME_REGEX
    static regex rxdotdot("/[^/]*[^/.]/\\.\\./");
#endif

    file += '/';

    if (!file.contains('/', 0))
        file = current_pwd + "/" + file;

    /* CvE, Jul 10, 1997
     *
     * Repeatedly remove patterns like /dir1/../ from the file name.
     * Note that a number of /../ patterns may follow each other, like
     * in "/dir1/dir1/dir3/../../../"
     */
    unsigned int file_length = file.length();
    unsigned int prev_file_length;
    do {
        prev_file_length = file_length;
        file.gsub(rxdotdot, "/");
        file_length = file.length();
    } while (file_length != prev_file_length);

    /* CvE, Jul 10, 1997
     *
     * Repeatedly remove pattern /./ from the file name.
     * Note that a number of /./ patterns may follow each other.
     * Note that if the first parameter of gsub is a C-string,
     * the pattern is not regarded to be a regular expression,
     * so the dot in the pattern does not need to be escaped!
     */
    file_length = file.length();
    do {
        prev_file_length = file_length;
        file.gsub("/./", "/");
        file_length = file.length();
    } while (file_length != prev_file_length);

    // Don't do this - it breaks file access on Cygwin, where
    // `//c/foo/bar' is translated to `c:\foo\bar'.
    // file.gsub("//", "/");

    if (file.contains('/', -1))
      file = file.before(int(file.length() - 1));

    return file;
}

bool SourceView::file_matches(const string& file1, const string& file2)
{
    if (gdb->type() == JDB)
      return file1 == file2;

    if (gdb->type() == GDB || app_data.use_source_path)
      return file1 == file2 || full_path(file1) == full_path(file2);

    return base_matches(file1, file2);
}

bool SourceView::is_current_file(const string& file)
{
    if (gdb->type() == JDB || gdb->type() == PYDB)
      return file == current_source_name();
    else
      return file_matches(file, current_file_name);
}

bool SourceView::base_matches(const string& file1, const string& file2)
{
    return string(basename(file1.chars())) == string(basename(file2.chars()));
}

// Check if BP occurs in the current source text
bool SourceView::bp_matches(BreakPoint *bp, int line)
{
    return bp_matches(bp, current_source_name(), line) || 
      bp_matches(bp, current_file_name, line);
}

bool SourceView::bp_matches(BreakPoint *bp, const string& file, int line)
{
    switch (bp->type())
    {
    case BREAKPOINT:
    case ACTIONPOINT:
    case TRACEPOINT:
      return (line == 0 || bp->line_nr() == line) &&
          (bp->file_name().empty() || file_matches(bp->file_name(), file));

    case WATCHPOINT:
      return false;
    }

    return false;       // Never reached
}

// ***************************************************************************
//

// If this is true, no motion occurred while selecting
static bool selection_click = false;

static string last_info_output = "";

void SourceView::set_source_argCB(Widget text_w, 
                          XtPointer client_data, 
                          XtPointer call_data)
{
    const string& text = current_text(text_w);
    if (text.empty())
      return;

    XmTextVerifyCallbackStruct *cbs = (XmTextVerifyCallbackStruct *)call_data;
    bool motion = bool((int)(long)client_data);
    if (motion)
      selection_click = false;

    XmTextPosition startPos, endPos;
    Boolean have_selection = 
      XmTextGetSelectionPosition(text_w, &startPos, &endPos);

    if (have_selection && lesstif_version <= 89)
    {
      // In LessTif 0.89 and earlier, the unmanaged
      // DataDisp::graph_selection_w text widget is not notified
      // that it just has lost the selection.  The effect is that
      // selecting an item from the source (or a selection in any
      // other window) does *not* clear the selection in the data
      // window, as it should.  As a workaround, notify explicitly.
      data_disp->SelectionLostCB();
    }

    if (!have_selection || (app_data.source_editing && startPos == endPos))
    {
      // No selection?  If the current motion was caused by a mouse
      // click, fetch word at current cursor position instead.
      if (cbs != 0 && cbs->reason == XmCR_MOVING_INSERT_CURSOR)
      {
          XEvent *event = cbs->event;
          if (event == 0)
          {
            // LessTif 0.79 may not pass a reasonable event here.
#if XtSpecificationRelease >= 6
            // Use the last processed event instead.
            event = XtLastEventProcessed(XtDisplay(text_w));
#else
            // Use the last recorded selection event instead.
            event = &selection_event;
#endif
            selection_click = true;
          }

          // Button4 and Button5 events are generated by wheel mouses.
          // Don't change the selection when the wheel is activated.
          if (event != 0 && 
            (event->type == ButtonPress || event->type == ButtonRelease) &&
            event->xbutton.button != Button4 &&
            event->xbutton.button != Button5)
          {
            find_word_bounds(text_w, cbs->newInsert, 
                         startPos, endPos);
            have_selection = True;
          }
      }
    }

    if (!have_selection && cbs == 0)
    {
      // Still no selection?  We're probably called from setSelection();
      // use the last selected word instead.
      startPos = selection_startpos;
      endPos   = selection_endpos;
      have_selection = True;
    }

#if XtSpecificationRelease < 6
    // Don't use this selection event again.
    selection_event.type = KeyPress;
#endif

    if (!have_selection)
    {
      // No selection - sorry
      return;
    }

    int startIndex = 0;
    if (startPos > 0)
      startIndex = text.index('\n', startPos - text.length()) + 1;

    int endIndex = 0;
    if (endPos > 0)
      endIndex = text.index('\n', endPos - text.length()) + 1;

    bool in_bp_area = false;
    if (selection_click && startIndex == endIndex)
    {
      string pos = "";

      if (text_w == source_text_w)
      {
          int line_nr = 0;
          bool in_text;
          int bp_nr;
          string address;

          if (get_line_of_pos(source_text_w, startPos, line_nr, address, 
                        in_text, bp_nr) 
            && !in_text)
          {
            in_bp_area = true;

            // Selection from line number area: prepend source file name
            pos = current_source_name() + ":" + itostring(line_nr);
            source_arg->set_string(pos);

            // If a breakpoint is here, select this one only
            MapRef ref;
            for (BreakPoint* bp = bp_map.first(ref);
                 bp != 0;
                 bp = bp_map.next(ref))
            {
                bp->selected() = (bp_matches(bp, line_nr));
            }
          }
      }
      else if (text_w == code_text_w
             && startPos - startIndex <= indent_amount(text_w)
             && endPos - endIndex <= indent_amount(text_w))
      {
          // Selection from address area
          int index = address_index(text, startPos);
          if (index >= 0)
          {
            in_bp_area = true;

            pos = text.from(index);
            pos = pos.through(rxaddress);

            source_arg->set_string(pos);

            // If a breakpoint is here, select this one only
            MapRef ref;
            for (BreakPoint* bp = bp_map.first(ref);
                 bp != 0;
                 bp = bp_map.next(ref))
            {
                bp->selected() = 
                  (bp->type() == BREAKPOINT && 
                   compare_address(pos, bp->address()) == 0);
            }
          }
      }

      selection_click = false;
    }

    if (in_bp_area)
    {
      // Update breakpoint selection
      process_breakpoints(last_info_output);
    }
    else if (!motion && !selection_click)
    {
      // Selection from source or code
      string s;
      if (startPos < XmTextPosition(text.length())
          && endPos < XmTextPosition(text.length()))
      {
          s = text.at((int)startPos, (int)(endPos - startPos));
      }

      while (s.contains('\n'))
          s = s.after('\n');

      if (!s.empty())
          source_arg->set_string(s);
    }
}


BreakPoint *SourceView::breakpoint_at(const string& arg)
{
    MapRef ref;
    for (BreakPoint* bp = bp_map.first(ref); bp != 0; bp = bp_map.next(ref))
    {
      if (bp->type() != BREAKPOINT)
          continue;

      if (arg.matches(rxint))
      {
          // Line number for current source given
          if (bp_matches(bp, atoi(arg.chars())))
            return bp;
      }
      else
      {
          string pos = arg;

          if (!is_file_pos(pos))
          {
            // Function given
            if (bp->arg() == pos)
                return bp;

            if (gdb->type() == DBX)
                pos = dbx_lookup(arg);
          }
          
          if (is_file_pos(pos))
          {
            // File:line given
            string file = pos.before(':');
            string line = pos.after(':');

            if (bp_matches(bp, file, atoi(line.chars())))
                return bp;
          }
      }
    }

    return 0;
}

BreakPoint *SourceView::watchpoint_at(const string& expr)
{
    for (int trial = 0; trial <= 2; trial++)
    {
      MapRef ref;
      for (BreakPoint* bp = bp_map.first(ref); bp != 0; 
           bp = bp_map.next(ref))
      {
          if (bp->type() != WATCHPOINT)
            continue;

          switch (trial)
          {
          case 0:
            if (bp->expr() == expr)
            {
                // Expression matches exactly
                return bp;
            }
            break;

          case 1:
            if (bp->expr().contains('(') && bp->expr().before('(') == expr)
            {
                // Expr matches EXPR(...)  (e.g. a qualified function name)
                return bp;
            }

          case 2:
            if (bp->expr().contains("`" + expr, -1) ||
                bp->expr().contains("::" + expr, -1))
            {
                // Expr matches ...`EXPR (a Sun DBX identifier)
                // or ...::EXPR (an SGI DBX identifier)
                return bp;
            }
          }
      }
    }

    return 0;
}


// ***************************************************************************

// Show position POS in TEXT_W, scrolling nicely
void SourceView::ShowPosition(Widget text_w, XmTextPosition pos, bool fromTop)
{
    const string& text = current_text(text_w);
    if (text.length() == 0)
      return;                 // No position to show

    short rows = 0;
    XmTextPosition current_top = 0;
    XtVaGetValues(text_w,
              XmNrows, &rows,
              XmNtopCharacter, &current_top,
              XtPointer(0));

    // Find current relative row
    short relative_row = 1;
    for (XmTextPosition p = min(text.length() - 1, pos); p > current_top; p--)
      if (text[p] == '\n')
          relative_row++;

    if (relative_row <= lines_above_cursor 
      || relative_row >= rows - (lines_below_cursor + 1))
    {
      // Determine new TOP position
      short n = rows / 2;     // #Lines between new TOP and POS
      if (fromTop || relative_row <= lines_above_cursor)
          n = lines_above_cursor;
      else if (relative_row >= rows - (lines_below_cursor + 1))
          n = rows - (lines_below_cursor + 1);

      XmTextPosition new_top = pos;
      for (;;) {
          while (new_top > 0 && text[new_top - 1] != '\n')
            new_top--;
          if (new_top == 0 || n-- <= 0)
            break;
          new_top--;
      }

      XmTextSetTopCharacter(text_w, new_top);
    }

    XmTextShowPosition(text_w, pos);      // just to make sure
}

void SourceView::SetInsertionPosition(Widget text_w, 
                              XmTextPosition pos, bool fromTop)
{
    ShowPosition(text_w, pos, fromTop);
    XmTextSetInsertionPosition(text_w, pos);
}



//-----------------------------------------------------------------------
// Error handling
//-----------------------------------------------------------------------

StringArray SourceView::bad_files;
bool SourceView::new_bad_file(const string& file_name)
{
    for (int i = 0; i < bad_files.size(); i++)
      if (file_name == bad_files[i])
          return false;
    bad_files += file_name;
    return true;
}

void SourceView::post_file_error(const string& file_name,
                         const string& text, const _XtString name,
                         Widget origin)
{
    if (new_bad_file(file_name))
      post_error(text, name, origin);
}

void SourceView::post_file_warning(const string& file_name,
                           const string& text, const _XtString name,
                           Widget origin)
{
    if (new_bad_file(file_name))
      post_warning(text, name, origin);
}




//-----------------------------------------------------------------------
// Read file
//-----------------------------------------------------------------------

// Read local file from FILE_NAME
String SourceView::read_local(const string& file_name, long& length,
                        bool silent)
{
    StatusDelay delay("Reading file " + quote(file_name));
    length = 0;

    // Make sure the file is a regular text file and open it
    int fd;
    if ((fd = open(file_name.chars(), O_RDONLY)) < 0)
    {
      delay.outcome = strerror(errno);
      if (!silent)
          post_file_error(file_name, 
                      file_name + ": " + delay.outcome, 
                      "source_file_error", source_text_w);
        return 0;
    }

    struct stat statb;
    if (fstat(fd, &statb) < 0)
    {
      delay.outcome = strerror(errno);
      if (!silent)
          post_file_error(file_name,
                      file_name + ": " + delay.outcome, 
                      "source_file_error", source_text_w);
      return 0;
    }

    // Avoid loading from directory, socket, device, or otherwise.
    if (!S_ISREG(statb.st_mode))
    {
      delay.outcome = "not a regular file";
      if (!silent)
          post_file_error(file_name,
                      file_name + ": " + delay.outcome, 
                      "source_file_error", source_text_w);
      return 0;
    }

    // Put the contents of the file in the Text widget by allocating
    // enough space for the entire file and reading the file into the
    // allocated space.
    char* text = XtMalloc(unsigned(statb.st_size + 1));
    if ((length = read(fd, text, statb.st_size)) != statb.st_size)
    {
      delay.outcome = "truncated";
      if (!silent)
          post_file_error(file_name,
                      file_name + ": " + delay.outcome,
                      "source_trunc_error", source_text_w);
    }
    close(fd);

    text[statb.st_size] = '\0'; // be sure to null-terminate

    if (statb.st_size == 0)
    {
      delay.outcome = "empty file";
      if (!silent)
          post_file_warning(file_name,
                        file_name + ": " + delay.outcome,
                        "source_empty_warning", source_text_w);
    }

    return text;
}


// Read (possibly remote) file FILE_NAME; a little slower
String SourceView::read_remote(const string& file_name, long& length, 
                         bool silent)
{
    StatusDelay delay("Reading file " + 
                  quote(file_name) + " from " + gdb_host);
    length = 0;

    string cat_command = sh_command("cat " + file_name);

    Agent cat(cat_command);
    cat.start();

    FILE *fp = cat.inputfp();
    if (fp == 0)
    {
      delay.outcome = "failed";
      return 0;
    }

    String text = XtMalloc(1);

    do {
      text = XtRealloc(text, length + BUFSIZ + 1);
      length += fread(text + length, sizeof(char), BUFSIZ, fp);
    } while (!feof(fp));

    text[length] = '\0';  // be sure to null-terminate

    if (length == 0)
    {
      if (!silent)
          post_file_error(file_name,
                      "Cannot access remote file " + quote(file_name), 
                      "remote_file_error", source_text_w);
      delay.outcome = "failed";
    }

    return text;
}

// Read class CLASS_NAME
String SourceView::read_class(const string& class_name, 
                        string& file_name, SourceOrigin& origin,
                        long& length, bool silent)
{
    StatusDelay delay("Loading class " + quote(class_name));
    
    String text = 0;
    length = 0;

    file_name = java_class_file(class_name);

    if (!file_name.empty())
    {
      if (remote_gdb())
          text = read_remote(file_name, length, true);
      else
      {
          file_name = full_path(file_name);
          text = read_local(file_name, length, true);
      }
    }

    if (text != 0 && length != 0)
    {
      // Save class name for further reference
      source_name_cache[file_name] = class_name;
      origin = remote_gdb() ? ORIGIN_REMOTE : ORIGIN_LOCAL;
      return text;
    }
    else
    {
      // Could not load class
      file_name = class_name;
      origin = ORIGIN_NONE;
      delay.outcome = "failed";
      if (!silent)
          post_file_error(class_name,
                      "Cannot access class " + quote(class_name),
                      "class_error", source_text_w);

      return 0;
    }
}

#define HUGE_LINE_NUMBER "1000000"

// Read file FILE_NAME via the GDB `list' function
// Really slow, is guaranteed to work for source files.
String SourceView::read_from_gdb(const string& file_name, long& length, 
                         bool /* silent */)
{
    length = 0;
    if (!can_do_gdb_command())
      return 0;
    if (gdb->type() == JDB)
      return 0;         // Won't work with JDB

    StatusDelay delay("Reading file " + quote(file_name) + 
                  " from " + gdb->title());

    string command;
    switch (gdb->type())
    {
    case DBG: // Is this correct? DBG "list" sommand seems not to do anything
    case GDB:
      command = "list " + file_name + ":1," HUGE_LINE_NUMBER;
      break;

    case DBX:
    case PYDB:
      command = "list 1," HUGE_LINE_NUMBER;
      break;

    case PERL:
      command = "l 1-" HUGE_LINE_NUMBER;
      break;

    case BASH:
      command = "list 1 " HUGE_LINE_NUMBER;
      break;

    case JDB:
      command = "list " + file_name;
      break;

    case XDB:
      command = "w " HUGE_LINE_NUMBER;
      break;
    }
    string listing = gdb_question(command, -1, true);

    // GDB listings have the format <NUMBER>\t<LINE>.
    // Copy LINE only; line numbers will be re-added later.
    // Note that tabs may be expanded to spaces due to a PTY interface.
    String text = XtMalloc(listing.length());

    int i = 0;
    length = 0;
    while (i < int(listing.length()))
    {
      int count = 0;

      // Skip leading spaces.  Some debuggers also issue `*', `=',
      // or `>' to indicate the current position.
      while (count < 8
             && i < int(listing.length())
             && (isspace(listing[i])
               || listing[i] == '=' 
               || listing[i] == '*'
               || listing[i] == '>'))
          i++, count++;

      if (i < int(listing.length()) && isdigit(listing[i]))
      {
          // Skip line number
          while (i < int(listing.length()) && isdigit(listing[i]))
            i++, count++;

          // Skip `:' (XDB output)
          if (count < 8 && i < int(listing.length()) && listing[i] == ':')
            i++, count++;

          // Break at first non-blank character or after 8 characters
          while (count < 8 && i < int(listing.length()) && listing[i] == ' ')
            i++, count++;

          // Skip tab character
          if (count < 8 && i < int(listing.length()) && listing[i] == '\t')
            i++;

          // Copy line
          while (i < int(listing.length()) && listing[i] != '\n')
            text[length++] = listing[i++];

          // Copy newline character
          text[length++] = '\n';
          i++;
      }
      else
      {
          int start = i;

          // Some other line -- the prompt, maybe?
          while (i < int(listing.length()) && listing[i] != '\n')
            i++;
          if (i < int(listing.length()))
            i++;

          string msg = listing.from(start);
          msg = msg.before('\n');
          if (!msg.contains("end of file")) // XDB issues this
            post_gdb_message(msg, true, source_text_w);
      }
    }

    if (text[0] == 'i' && text[1] == 'n' && text[2] == ' ')
    {
      // GDB 5.0 issues only `LINE_NUMBER in FILE_NAME'.  Treat this
      // like an error message.
      length = 0;
    }

    text[length] = '\0';  // be sure to null-terminate

    if (length == 0)
      delay.outcome = "failed";

    return text;
}


// Read file FILE_NAME and format it
String SourceView::read_indented(string& file_name, long& length, 
                         SourceOrigin& origin, bool silent)
{
    length = 0;
    Delay delay;
    long t;
      
    String text = 0;
    origin = ORIGIN_NONE;
    string full_file_name = file_name;

    if (gdb->type() == JDB && !file_name.contains('/'))
    {
      // FILE_NAME is a class name.  Search class in JDB `use' path.
      text = read_class(file_name, full_file_name, origin, length, true);
    }

    if (gdb->type() == PERL && file_name.contains('-', 0))
    {
      // Attempt to load `-e' in Perl or likewise
      origin = ORIGIN_NONE;
      return 0;
    }

    if (text == 0 || length == 0)
    {
      for (int trial = 1; (text == 0 || length == 0) && trial <= 2; trial++)
      {
          switch (trial)
          {
          case 1:
            // Loop #1: use full path of file
            full_file_name = full_path(file_name);
            break;

          case 2:
            // Loop #2: ask debugger for full path, using `edit'
            full_file_name = full_path(dbx_path(file_name));
            if (full_file_name == full_path(file_name))
                continue;
            break;
          }

          // Attempt #1.  Try to read file from remote source.
          if ((text == 0 || length == 0) && remote_gdb())
          {
            text = read_remote(full_file_name, length, true);
            if (text != 0)
                origin = ORIGIN_REMOTE;
          }

          // Attempt #2.  Read file from local source.
          if ((text == 0 || length == 0) && !remote_gdb())
          {
            text = read_local(full_file_name, length, true);
            if (text != 0)
                origin = ORIGIN_LOCAL;
          }

          // Attempt #3.  Read file from local source, even if we are remote.
          if ((text == 0 || length == 0) && remote_gdb())
          {
            text = read_local(full_file_name, length, true);
            if (text != 0)
                origin = ORIGIN_LOCAL;
          }
      }
    }

    // Attempt #4.  Read file from GDB.
    if (text == 0 || length == 0)
    {
      string saved_current_file_name = current_file_name;
      current_file_name = full_file_name;
      string source_name = current_source_name();
      current_file_name = saved_current_file_name;

      text = read_from_gdb(source_name, length, silent);

      if (text != 0 && length != 0)
      {
          // Use the source name as file name
          full_file_name = source_name;
          if (text != 0)
            origin = ORIGIN_GDB;
      }
    }

    if ((text == 0 || length == 0) && !silent)
    {
      // All failed - produce an appropriate error message.
      if (gdb->type() == JDB)
          text = read_class(file_name, full_file_name, origin, 
                        length, false);
      else if (!remote_gdb())
          text = read_local(full_file_name, length, false);
      else
          text = read_remote(full_file_name, length, false);
    }

    if (text == 0 || length == 0)
    {
      origin = ORIGIN_NONE;
      return 0;
    }

    // At this point, we have a source text.
    file_name = full_file_name;

    // Determine text length and number of lines
    int lines = 0;
    for (t = 0; t < length; t++)
      if (text[t] == '\n')
          lines++;

    int indented_text_length = length;
    if (length > 0 && text[length - 1] != '\n')
    {
      // Text does not end in '\n':
      // Make room for final '\n'
      indented_text_length += 1;

      // Make room for final line
      lines++;
    }

    // Make room for line numbers
    int indent = indent_amount(source_text_w);
    indented_text_length += (indent + script_indent_amount) * lines;

    String indented_text = XtMalloc(indented_text_length + 1);

    string line_no_s = replicate(' ', indent);

    t = 0;
    char *pos_ptr = indented_text; // Writing position in indented_text
    while (t < length)
    {
      assert (pos_ptr - indented_text <= indented_text_length);

      // Increase line number
      int i;
      for (i = indent - 2; i >= 0; i--)
      {
          char& c = line_no_s[i];
          if (c == ' ')
          {
            c = '1';
            break;
          }
          else if (c < '9')
          {
            c++;
            break;
          }
          else
            c = '0';
      }

      // Copy line number
      for (i = 0; i < indent; i++)
          *pos_ptr++ = display_line_numbers ? line_no_s[i] : ' ';

      if (indent < script_indent_amount)
      {
          // Check for empty line or line starting with '\t'
          int spaces = 0;
          while (t + spaces < length && text[t + spaces] == ' ')
            spaces++;

          if (spaces < script_indent_amount)
          {
            if (t + spaces >= length ||
                text[t + spaces] == '\n' ||
                text[t + spaces] == '\t')
            {
                // Prepend a few space characters
                while (spaces < script_indent_amount)
                {
                  *pos_ptr++ = ' ';
                  spaces++;
                }
            }
          }
      }

      // Copy remainder of line
      while (t < length && text[t] != '\n')
          *pos_ptr++ = text[t++];

      // Copy '\n' or '\0'
      if (t == length)
      {
          // Text doesn't end in '\n'
          *pos_ptr++ = '\n';
      }
      else
      {
          *pos_ptr++ = text[t++];
      }
    }
    *pos_ptr = '\0';

    XtFree(text);

    length = pos_ptr - indented_text;
    return indented_text;
}


// Read file FILE_NAME into current_source; get it from the cache if possible
int SourceView::read_current(string& file_name, bool force_reload, bool silent)
{
    string requested_file_name = file_name;

    if (cache_source_files && !force_reload && file_cache.has(file_name))
    {
      current_source = file_cache[file_name];
      current_origin = origin_cache[file_name];
      file_name      = file_name_cache[file_name];

      if (gdb->type() == JDB)
      {
          // In JDB, a single source may contain multiple classes.
          // Store current class name FILE_NAME as source name.
          source_name_cache[file_name] = requested_file_name;
      }
    }
    else
    {
      long length = 0;
      SourceOrigin orig;
      String indented_text = read_indented(file_name, length, orig, silent);
      if (indented_text == 0 || length == 0)
          return -1;          // Failure

      current_source = string(indented_text, length);
      current_origin = orig;
      XtFree(indented_text);

      if (current_source.length() > 0)
      {
          file_cache[file_name]             = current_source;
          origin_cache[file_name]           = current_origin;
          file_name_cache[file_name]        = file_name;

          if (file_name != requested_file_name)
          {
            file_cache[requested_file_name]      = current_source;
            origin_cache[requested_file_name]    = current_origin;
            file_name_cache[requested_file_name] = file_name;
          }
      }

      int null_count = current_source.freq('\0');
      if (null_count > 0 && !silent)
          post_warning(file_name + ": binary file",
                   "source_binary_warning", source_text_w);
    }

    // Untabify current source, using the current tab width
    untabify(current_source, tab_width, indent_amount(source_text_w));

    // Setup global parameters

    // Number of lines
    line_count   = current_source.freq('\n');
    _pos_of_line = TextPositionArray(line_count + 2);
    _pos_of_line.operator += (XmTextPosition(0));
    _pos_of_line.operator += (XmTextPosition(0));

    for (int i = 0; i < int(current_source.length()); i++)
      if (current_source[i] == '\n')
          _pos_of_line.operator += (XmTextPosition(i + 1));

    assert(_pos_of_line.size() == line_count + 2);

    if (current_source.length() == 0)
      return -1;
    else
      return 0;
}

// Return position of line LINE
XmTextPosition SourceView::pos_of_line(int line)
{
    if (line < 0 || line > line_count || line >= _pos_of_line.size())
      return 0;
    else
      return _pos_of_line[line];
}

// Clear the file cache
void SourceView::clear_file_cache()
{
    static const StringStringAssoc string_empty;
    file_cache        = string_empty;
    source_name_cache = string_empty;
    file_name_cache   = string_empty;

    static const StringOriginAssoc origin_empty;
    origin_cache      = origin_empty;

    static const StringArray bad_files_empty;
    bad_files         = bad_files_empty;
}

void SourceView::reload()
{
    // Reload current file
    if (current_file_name.empty())
      return;

    string file;
    if (gdb->type() == JDB)
      file = line_of_cursor();
    else
      file = file_of_cursor();

    string line = file.after(':');
    file        = file.before(':');

    // StatusDelay delay("Reloading " + quote(file));

    read_file(file, atoi(line.chars()), true);

    // Restore breakpoints
    refresh_bp_disp(true);

    // Restore execution position
    if (!last_execution_file.empty())
      show_execution_position(last_execution_file + ":" + 
                        itostring(last_execution_line),
                        at_lowest_frame, signal_received);
}


static const int MAX_TAB_WIDTH = 256;

// Change tab width
void SourceView::set_tab_width(int width)
{
    if (width <= 0)
      return;

    if (tab_width != width)
    {
      // Make sure the tab width stays within reasonable ranges
      tab_width = min(max(width, 1), MAX_TAB_WIDTH);

      if (!current_file_name.empty())
      {
          StatusDelay delay("Reformatting");
          reload();
      }
    }
}

// Change indentation
void SourceView::set_indent(int source_indent, int code_indent)
{
    if (source_indent < 0 || code_indent < 0)
      return;

    if (source_indent == source_indent_amount &&
      code_indent == code_indent_amount)
      return;

    if (source_indent != source_indent_amount)
    {
      source_indent_amount = min(max(source_indent, 0), MAX_INDENT);
      if (!current_file_name.empty())
      {
          StatusDelay delay("Reformatting");
          reload();
      }
    }

    if (code_indent != code_indent_amount)
    {
      code_indent_amount = min(max(code_indent, 0), MAX_INDENT);

      clear_code_cache();
      show_pc(last_shown_pc);
    }
}

void SourceView::read_file (string file_name, 
                      int initial_line,
                      bool force_reload,
                      bool silent)
{
    if (file_name.empty())
      return;

    /*
      Yves Arrouye <Yves.Arrouye@marin.fdn.fr> states:

      Paths to filename are not `normalized' before being passed to
      emacslient (or gnuclient). I mean that if one of your source
      files was compiled as

                /some/path//there/file.C

      (which happens fairly often due to empty paths components in
      Makefiles, for example), the file will not appear in Emacs
      because the Emacs visit-file function and friends do consider
      that when there is a // or a /~ in a path then everything
      before the first / is garbage. The file that will be looked
      for in the example is

                /there/file.C

      (the GUD Emacs mode exhibits the same bug ;-)). Thus // in
      file pathes must be changed to / (or Emacs changed so that
      only interactively-called versions of visit-file and friends
      do that, but I don't think this will be done...).
    */
    file_name.gsub("//", "/");

    // Read in current_source
    int error = read_current(file_name, force_reload, silent);
    if (error)
      return;

    add_position_to_history(file_name, initial_line, false);

    // The remainder may take some time...
    Delay delay;

    // Set source and initial line
    XmTextSetString(source_text_w, XMST(current_source.chars()));

    XmTextPosition initial_pos = 0;
    if (initial_line > 0 && initial_line <= line_count)
      initial_pos = pos_of_line(initial_line) + indent_amount(source_text_w);

    SetInsertionPosition(source_text_w, initial_pos, true);

    // Set current file name
    current_file_name = file_name;

    // Refresh title
    update_title();

    // Refresh breakpoints
    static const IntIntArrayAssoc empty_bps;
    bps_in_line = empty_bps;
    static const StringArray empty_addresses;
    bp_addresses = empty_addresses;
    refresh_bp_disp(true);

    XtManageChild(source_text_w);

    MString msg;
    switch (current_origin)
    {
    case ORIGIN_LOCAL:
      msg += rm("File " + quote(file_name));
      if (remote_gdb())
          msg += rm(" (from local host)");
      break;

    case ORIGIN_REMOTE:
      msg += rm("File " + quote(file_name));
      msg += rm(" (from " + gdb_host + ")");
      break;

    case ORIGIN_GDB:
      msg += rm("Source " + quote(file_name));
      msg += rm(" (from " + gdb->title() + ")");
      break;

    case ORIGIN_NONE:
      msg += tt(file_name);
      break;
    }
    msg += rm(" ");

    if (line_count == 1)
      msg += rm("1 line, ");
    else
      msg += rm(itostring(line_count) + " lines, ");
    if (current_source.length() == 1)
      msg += rm("1 character");
    else
      msg += rm(itostring(current_source.length()) + " characters");

    set_status_mstring(msg);

    XmTextClearSelection(source_text_w, 
                   XtLastTimestampProcessed(XtDisplay(source_text_w)));
    XmTextSetHighlight(source_text_w,
                   0, XmTextGetLastPosition(source_text_w),
                   XmHIGHLIGHT_NORMAL);
    last_top = last_pos = last_start_highlight = last_end_highlight = 0;
    update_glyphs(source_text_w);

    if (app_data.source_window)
    {
      static bool popped_up = false;

      if (!popped_up)
      {
          // Make sure source is visible
          Widget shell = (source_view_shell != 0) ? 
            source_view_shell : command_shell;

          if (source_view_shell != 0 || app_data.tty_mode)
          {
            initial_popup_shell(shell);
          }

          if (!app_data.command_toolbar)
            initial_popup_shell(tool_shell);

          if (!started_iconified(shell))
            gdbOpenSourceWindowCB(source_text_w, 0, 0);
      }

      popped_up = true;
    }
}

void SourceView::update_title()
{
    if (toplevel_w == 0)
      return;

    string title   = DDD_NAME ": " + current_file_name;
    const _XtString title_s = title.chars();

    string icon   = 
      DDD_NAME ": " + string(basename(current_file_name.chars()));
    const _XtString icon_s = icon.chars();

    XtVaSetValues(toplevel_w,
              XmNtitle, title_s,
              XmNiconName, icon_s,
              XtPointer(0));
}



//-----------------------------------------------------------------------
// Breakpoint handling
//-----------------------------------------------------------------------

// Update breakpoint locations
void SourceView::refresh_bp_disp(bool reset)
{
    refresh_source_bp_disp(reset);
    refresh_code_bp_disp(reset);
    update_glyphs();
}

void SourceView::refresh_source_bp_disp(bool reset)
{
    if (display_glyphs && !reset)
      return;

    // Overwrite old breakpoint displays - - - - - - - - - - - - -
    for (IntIntArrayAssocIter b_i_l_iter(bps_in_line);
       b_i_l_iter.ok(); 
       ++b_i_l_iter)
    {
      int line_nr = b_i_l_iter.key();
      if (line_nr < 0 || line_nr > line_count)
          continue;

      int pos = pos_of_line(line_nr);
      int indent = indent_amount(source_text_w, pos);

      if (indent > 0)
      {
          string s(current_source.at(pos, indent - 1));

          if (s.length() > 0)
            XmTextReplace(source_text_w,
                        pos_of_line(line_nr),
                        pos_of_line(line_nr) + s.length(),
                        XMST(s.chars()));
      }
    }

    static const IntIntArrayAssoc empty_bps;
    bps_in_line = empty_bps;

    if (display_glyphs)
      return;

    // Find all breakpoints referring to this file
    MapRef ref;
    for (BreakPoint* bp = bp_map.first(ref); bp != 0; bp = bp_map.next(ref))
    {
      if ((bp->type() == BREAKPOINT || bp->type() == TRACEPOINT) && 
          bp_matches(bp))
      {
          bps_in_line[bp->line_nr()] += bp->number();
      }
    }

    // Show breakpoints in text
    for (IntIntArrayAssocIter b_i_l_iter2(bps_in_line);
       b_i_l_iter2.ok();
       ++b_i_l_iter2)
    {
      int line_nr = b_i_l_iter2.key();
      if (line_nr < 0 || line_nr > line_count)
          continue;

      XmTextPosition pos = pos_of_line(line_nr);
      int indent = indent_amount(source_text_w, pos);

      if (indent > 0)
      {
          // Display all breakpoints in a line
          VarIntArray& bps = bps_in_line[line_nr];

          string insert_string = "";
          for (int i = 0; i < bps.size(); i++)
          {
            BreakPoint *bp = bp_map.get(bps[i]);
            insert_string += bp->symbol();
          }

          if (int(insert_string.length()) >= indent - 1)
          {
            insert_string = insert_string.before(indent - 1);
          }
          else
          {
            for (int i = insert_string.length(); i < indent - 1; i++)
            {
                insert_string += current_source[pos + i];
            }
          }

          assert(int(insert_string.length()) == indent - 1);

          if (insert_string.length() > 0)
            XmTextReplace(source_text_w, pos, 
                        pos + indent - 1,
                        XMST(insert_string.chars()));
      }
    }
}

void SourceView::refresh_code_bp_disp(bool reset)
{
    if (display_glyphs && !reset)
      return;

    // Clear all addresses
    int i;
    for (i = 0; i < bp_addresses.size(); i++)
    {
      const string& address = bp_addresses[i];
      XmTextPosition pos = find_pc(address);
      if (pos == XmTextPosition(-1))
          continue;

      // Process all breakpoints at ADDRESS
      int indent = indent_amount(code_text_w, pos);
      if (indent > 0)
      {
          string spaces = replicate(' ', indent);
          XmTextReplace(code_text_w, pos, pos + indent, XMST(spaces.chars()));
      }
    }

    static const StringArray empty;
    bp_addresses = empty;

    if (display_glyphs)
      return;

    // Collect all addresses
    MapRef ref;
    for (BreakPoint *bp = bp_map.first(ref); bp != 0;
       bp = bp_map.next(ref))
    {
      if (bp->type() != BREAKPOINT)
          continue;

      bp_addresses += bp->address();
    }

    // Process all bp_addresses
    for (i = 0; i < bp_addresses.size(); i++)
    {
      const string& address = bp_addresses[i];
      XmTextPosition pos = find_pc(address);
      if (pos == XmTextPosition(-1))
          continue;

      // Process all breakpoints at ADDRESS
      string insert_string = "";
      for (BreakPoint *bp = bp_map.first(ref);
           bp != 0;
           bp = bp_map.next(ref))
      {
          if (bp->address() == address)
            insert_string += bp->symbol();
      }

      int indent = indent_amount(code_text_w, pos);
      if (indent > 0)
      {
          insert_string += replicate(' ', indent);
          insert_string = insert_string.before(indent);

          XmTextReplace(code_text_w, pos, pos + indent, 
                    XMST(insert_string.chars()));
      }
    }
}


//-----------------------------------------------------------------------
// Position management
//-----------------------------------------------------------------------

// Find the line number at POS
// LINE_NR becomes the line number at POS
// IN_TEXT becomes true iff POS is in the source area
// BP_NR is the number of the breakpoint at POS (none: 0)
// Return false iff failure
bool SourceView::get_line_of_pos (Widget   w,
                          XmTextPosition pos,
                          int&     line_nr,
                          string&  address,
                          bool&    in_text,
                          int&     bp_nr)
{
    bool found = false;

    line_nr = 0;
    address = "";
    in_text = true;
    bp_nr   = 0;

    Widget text_w;
    if (is_source_widget(w))
      text_w = source_text_w;
    else if (is_code_widget(w))
      text_w = code_text_w;
    else
      return false;

    if (w != text_w)
    {
      // Glyph selected

      MapRef ref;
      for (BreakPoint *bp = bp_map.first(ref);
           bp != 0;
           bp = bp_map.next(ref))
      {
          if (w == bp->source_glyph() || w == bp->code_glyph())
          {
            // Breakpoint glyph found
            line_nr = bp->line_nr();
            address = bp->address();
            in_text = false;
            bp_nr   = bp->number();
            return true;
          }
      }
    }

    if (pos >= int(current_text(text_w).length()))
    {
      // Position is on the right of text
      in_text = false;
      line_nr = line_count;
      return true;
    }

    if (text_w == source_text_w)
    {
      // Search in source code
      XmTextPosition line_pos = 0;
      XmTextPosition next_line_pos = 0;

      while (!found && line_count >= line_nr)
      {
          next_line_pos = (line_count >= line_nr + 1) ?
            pos_of_line(line_nr + 1) :
            XmTextGetLastPosition (text_w) + 1;

          bool left_of_first_nonblank = false;
          if (pos < next_line_pos)
          {
            // Check if we're left of first non-blank source character
            int first_nonblank = line_pos + indent_amount(text_w);
            const string& text = current_text(text_w);
            while (first_nonblank < next_line_pos
                   && first_nonblank < int(text.length())
                   && isspace(text[first_nonblank]))
                first_nonblank++;
            left_of_first_nonblank = (pos < first_nonblank);
          }

          if (pos == line_pos
            || left_of_first_nonblank
            || pos < (line_pos + indent_amount(text_w) - 1))
          {
            // Position in breakpoint area
            found = true;
            in_text = false;
            line_nr = max(line_nr, 1);

            // Check for breakpoints...
            VarIntArray& bps = bps_in_line[line_nr];
            if (bps.size() == 1)
            {
                // Return single breakpoint in this line
                bp_nr = bps[0];
            }
            else if (bps.size() > 1)
            {
                // Find which breakpoint was selected
                XmTextPosition bp_disp_pos = line_pos;
                int i;
                for (i = 0; i < bps.size(); i++)
                {
                  BreakPoint* bp = bp_map.get(bps[i]);
                  assert(bp != NULL);

                  bp_disp_pos += 2; // respect '#' and '_';
                  bp_disp_pos += itostring(bp->number()).length();
                  if (pos < bp_disp_pos)
                  {
                      bp_nr = bps[i];
                      break; // exit for loop
                  }
                }
            }
          }
          else if (pos < next_line_pos)
          {
            // Position is in text
            found   = true;
            in_text = true;
          }
          else
          {
            // Position is in one of the following lines
            line_pos = next_line_pos;
            line_nr++;
          }
      }
    }
    else if (text_w == code_text_w)
    {
      // Search in machine code
      XmTextPosition line_pos = pos;
      while (line_pos >= 0 && current_code[line_pos] != '\n')
          line_pos--;
      line_pos++;

      if (pos == line_pos || pos - line_pos < indent_amount(text_w))
      {
          // Breakpoint area
          in_text = false;

          // Check if we have a breakpoint around here
          int index = address_index(current_code, pos);
          if (index >= 0)
          {
            address = current_code.from(index);
            address = address.through(rxaddress);

            VarIntArray bps;

            MapRef ref;
            for (BreakPoint *bp = bp_map.first(ref);
                 bp != 0;
                 bp = bp_map.next(ref))
            {
                if (compare_address(address, bp->address()) == 0)
                  bps += bp->number();
            }
            if (bps.size() == 1)
            {
                // Return single breakpoint in this line
                bp_nr = bps[0];
            }
            else if (bps.size() > 1)
            {
                // Find which breakpoint was selected
                int i;
                XmTextPosition bp_disp_pos = line_pos;
                for (i = 0; i < bps.size(); i++)
                {
                  BreakPoint* bp = bp_map.get(bps[i]);
                  assert(bp != NULL);
                  bp_disp_pos += 2; // respect '#' and '_';
                  bp_disp_pos += itostring(bp->number()).length();
                  if (pos < bp_disp_pos)
                  {
                      bp_nr = bps[i];
                      break; // exit for loop
                  }
                }
            }
          }
      }

      found = true;
    }

    return found;
}

// ***************************************************************************
// Find word around POS.  STARTPOS is the first character, ENDPOS + 1
// is the last character in the word.
void SourceView::find_word_bounds (Widget text_w,
                           const XmTextPosition pos,
                           XmTextPosition& startpos,
                           XmTextPosition& endpos)
{
    startpos = endpos = pos;

    const string& text = current_text(text_w);

    XmTextPosition line_pos = pos;
    if (line_pos < XmTextPosition(text.length()))
      while (line_pos > 0 && text[line_pos - 1] != '\n')
          line_pos--;

    int offset = pos - line_pos;
    if (offset == 0 || offset < indent_amount(text_w))
    {
      // Do not select words in breakpoint area
      return;
    }

    // Find end of word
    while (endpos < XmTextPosition(text.length()) && isid(text[endpos]))
      endpos++;

    // Find start of word
    if (startpos >= XmTextPosition(text.length()))
      startpos = XmTextPosition(text.length() - 1);

    while (startpos > 0)
    {
      while (startpos > 0 && isid(text[startpos - 1]))
          startpos--;

      if (gdb->program_language() == LANGUAGE_PERL &&
          startpos > 1 &&
          is_perl_prefix(text[startpos - 1]))
      {
          // Include Perl prefix character
          startpos -= 1;
          break;
      }
      else if (gdb->program_language() == LANGUAGE_BASH &&
             startpos > 1 &&
             is_bash_prefix(text[startpos - 1]))
      {
        // Include $variable rather than variable
        startpos -= 1;
        break;
      }
      else if (gdb->program_language() == LANGUAGE_BASH &&
             startpos > 2 && text[startpos -1] == '{' &&
             is_bash_prefix(text[startpos - 2])
             )
      {
        // Include ${...} rather than ...
        int brace_count=1;
        int new_endpos=startpos;
        for (new_endpos=startpos+1; 
             new_endpos < XmTextPosition(text.length()) 
             && new_endpos-startpos < 30; 
             new_endpos++) 
          {
            if (text[new_endpos] == '{') brace_count++;
            if (text[new_endpos] == '}') {
            brace_count--;
            if (brace_count==0) {
              startpos -= 2; // Go back over ${
              endpos=new_endpos+1;
              break;
            }
            }
          }
        break;
      }
      else if (startpos > 2 && 
          isid(text[startpos - 2]) &&
          text[startpos - 1] == '.')
      {
          // Select A.B as a whole
          startpos -= 1;
      }
      else if (startpos > 3 && 
             isid(text[startpos - 3]) &&
             text[startpos - 2] == '-' &&
             text[startpos - 1] == '>')
      {
          // Select A->B as a whole
          startpos -= 2;
      }
      else if (startpos > 3 && 
             isid(text[startpos - 3]) &&
             text[startpos - 2] == ':' &&
             text[startpos - 1] == ':')
      {
          // Select A::B as a whole
          startpos -= 2;
      }
      else
          break;
    }
}

// Get the word at event position
string SourceView::get_word_at_event(Widget text_w,
                             XEvent *event,
                             XmTextPosition& startpos,
                             XmTextPosition& endpos)
{
    BoxPoint event_pos = point(event);
    XmTextPosition pos = XmTextXYToPos(text_w, event_pos[X], event_pos[Y]);

    return get_word_at_pos(text_w, pos, startpos, endpos);
}


// Get the word at POS
string SourceView::get_word_at_pos(Widget text_w,
                           XmTextPosition pos,
                           XmTextPosition& startpos,
                           XmTextPosition& endpos)
{
    const string& text = current_text(text_w);
    if (text.empty())
      {
      startpos = 0;
      endpos = 0;
      return "";
      }

    if (!XmTextGetSelectionPosition(text_w, &startpos, &endpos)
      || pos < startpos
      || pos > endpos)
    {
      find_word_bounds(text_w, pos, startpos, endpos);
    }

    string word = "";
    if (startpos < XmTextPosition(text.length())
      && startpos < endpos)
      word = text.at(int(startpos), int(endpos - startpos));

    strip_space(word);

    return word;
}



//----------------------------------------------------------------------------
// Create GDB requests and evaluate replies
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
// Constructor
//----------------------------------------------------------------------------

// Install the given X bitmap as NAME
static void InstallBitmapAsImage(unsigned char *bits, int width, int height, 
                         const char *name)
{
    Boolean ok = InstallBitmap(bits, width, height, name);
    if (!ok)
      std::cerr << "Could not install " << quote(name) << " bitmap\n";
}


SourceView::SourceView(Widget parent)
{
    XtAppContext app_context = XtWidgetToApplicationContext(parent);

    // Find application shell
    toplevel_w = parent;
    while (toplevel_w != 0 && !XtIsWMShell(toplevel_w))
      toplevel_w = XtParent(toplevel_w);

    // Install glyph images
    InstallBitmapAsImage(arrow_bits, arrow_width, arrow_height, 
                   "plain_arrow");
    InstallBitmapAsImage(grey_arrow_bits, grey_arrow_width, grey_arrow_height, 
                   "grey_arrow");
    InstallBitmapAsImage(past_arrow_bits, past_arrow_width, past_arrow_height, 
                   "past_arrow");
    InstallBitmapAsImage(signal_arrow_bits, signal_arrow_width, 
                   signal_arrow_height, "signal_arrow");
    InstallBitmapAsImage(drag_arrow_bits, drag_arrow_width, drag_arrow_height, 
                   "drag_arrow");

    InstallBitmapAsImage(stop_bits, stop_width, stop_height, 
                   "plain_stop");
    InstallBitmapAsImage(cond_bits, cond_width, cond_height, 
                   "plain_cond");
    InstallBitmapAsImage(temp_bits, temp_width, temp_height, 
                   "plain_temp");

    InstallBitmapAsImage(grey_stop_bits, grey_stop_width, grey_stop_height, 
                   "grey_stop");
    InstallBitmapAsImage(grey_cond_bits, grey_cond_width, grey_cond_height, 
                   "grey_cond");
    InstallBitmapAsImage(grey_temp_bits, grey_temp_width, grey_temp_height, 
                   "grey_temp");

    InstallBitmapAsImage(drag_stop_bits, drag_stop_width, drag_stop_height, 
                   "drag_stop");
    InstallBitmapAsImage(drag_cond_bits, drag_cond_width, drag_cond_height, 
                   "drag_cond");
    InstallBitmapAsImage(drag_temp_bits, drag_temp_width, drag_temp_height, 
                   "drag_temp");

    // Setup actions
    XtAppAddActions (app_context, actions, XtNumber (actions));

    // Create source code window
    create_text(parent, "source", app_data.source_editing,
            source_form_w, source_text_w);
    XtManageChild(source_form_w);

    // Create machine code window
    create_text(parent, "code", false, code_form_w, code_text_w);
    if (disassemble)
      XtManageChild(code_form_w);
}

void SourceView::create_shells()
{
    Widget parent = XtParent(source_form_w);
    XtAppContext app_context = XtWidgetToApplicationContext(parent);

    // Create breakpoint editor
    Arg args[10];
    Cardinal arg = 0;

    arg = 0;
    XtSetArg(args[arg], XmNvisibleItemCount, 0); arg++;
    edit_breakpoints_dialog_w =
      verify(createTopLevelSelectionDialog(parent, "edit_breakpoints_dialog",
                                   args, arg));
    Delay::register_shell(edit_breakpoints_dialog_w);

    XtUnmanageChild(XmSelectionBoxGetChild(edit_breakpoints_dialog_w,
                                 XmDIALOG_TEXT));
    XtUnmanageChild(XmSelectionBoxGetChild(edit_breakpoints_dialog_w,
                                 XmDIALOG_CANCEL_BUTTON));
    XtUnmanageChild(XmSelectionBoxGetChild(edit_breakpoints_dialog_w,
                                 XmDIALOG_APPLY_BUTTON));
    XtUnmanageChild(XmSelectionBoxGetChild(edit_breakpoints_dialog_w,
                                 XmDIALOG_SELECTION_LABEL));
    XtUnmanageChild(XmSelectionBoxGetChild(edit_breakpoints_dialog_w,
                                 XmDIALOG_LIST_LABEL));

    breakpoint_list_w = 
      XmSelectionBoxGetChild(edit_breakpoints_dialog_w, XmDIALOG_LIST);

    if (app_data.flat_dialog_buttons)
    {
      for (MMDesc *item = bp_area; item != 0 && item->name != 0; item++)
      {
          if ((item->type & MMTypeMask) == MMPush)
            item->type = (MMFlatPush | (item->type & ~MMTypeMask));
      }
    }

    Widget buttons = verify(MMcreateWorkArea(edit_breakpoints_dialog_w, 
                                   "buttons", bp_area));
    XtVaSetValues(buttons,
              XmNmarginWidth,     0, 
              XmNmarginHeight,    0, 
              XmNborderWidth,     0,
              XmNshadowThickness, 0, 
              XmNspacing,         0,
              XtPointer(0));

    MMaddCallbacks(bp_area);
    MMaddHelpCallback(bp_area, ImmediateHelpCB);

    if (breakpoint_list_w != 0)
    {
      XtAddCallback(breakpoint_list_w,
                  XmNsingleSelectionCallback,
                  UpdateBreakpointButtonsCB,
                  0);
      XtAddCallback(breakpoint_list_w,
                  XmNmultipleSelectionCallback,
                  UpdateBreakpointButtonsCB,
                  0);
#if 0
      XtAddCallback(breakpoint_list_w,
                  XmNmultipleSelectionCallback,
                  LookupBreakpointCB,
                  0);
#endif
      XtAddCallback(breakpoint_list_w,
                  XmNextendedSelectionCallback,
                  UpdateBreakpointButtonsCB,
                  0);
      XtAddCallback(breakpoint_list_w,
                  XmNbrowseSelectionCallback,
                  UpdateBreakpointButtonsCB,
                  0);
    }

    if (edit_breakpoints_dialog_w != 0)
    {
      XtAddCallback(edit_breakpoints_dialog_w,
                  XmNokCallback,
                  UnmanageThisCB,
                  edit_breakpoints_dialog_w);
      XtAddCallback(edit_breakpoints_dialog_w,
                  XmNhelpCallback,
                  ImmediateHelpCB,
                  0);
    }

    // Create stack view
    arg = 0;
    XtSetArg(args[arg], XmNautoUnmanage, False); arg++;
    stack_dialog_w =
      verify(createTopLevelSelectionDialog(parent, 
                                   "stack_dialog", args, arg));
    Delay::register_shell(stack_dialog_w);

    XtUnmanageChild(XmSelectionBoxGetChild(stack_dialog_w, 
                                 XmDIALOG_TEXT));
    XtUnmanageChild(XmSelectionBoxGetChild(stack_dialog_w, 
                                 XmDIALOG_SELECTION_LABEL));

    up_w   = XmSelectionBoxGetChild(stack_dialog_w, XmDIALOG_OK_BUTTON);
    down_w = XmSelectionBoxGetChild(stack_dialog_w, XmDIALOG_APPLY_BUTTON);

    set_sensitive(up_w,   False);
    set_sensitive(down_w, False);
    refresh_buttons();

    arg = 0;
    frame_list_w = XmSelectionBoxGetChild(stack_dialog_w, XmDIALOG_LIST);
    XtVaSetValues(frame_list_w,
              XmNselectionPolicy, XmSINGLE_SELECT,
              XtPointer(0));

    XtAddCallback(frame_list_w,
              XmNsingleSelectionCallback, SelectFrameCB, 0);
    XtAddCallback(frame_list_w,
              XmNmultipleSelectionCallback, SelectFrameCB, 0);
    XtAddCallback(frame_list_w,
              XmNextendedSelectionCallback, SelectFrameCB, 0);
    XtAddCallback(frame_list_w,
              XmNbrowseSelectionCallback, SelectFrameCB, 0);

    XtAddCallback(stack_dialog_w,
              XmNokCallback, gdbCommandCB, XtPointer("up"));
    XtAddCallback(stack_dialog_w,
              XmNapplyCallback, gdbCommandCB, XtPointer("down"));
    XtAddCallback(stack_dialog_w,
              XmNcancelCallback, UnmanageThisCB, stack_dialog_w);
    XtAddCallback(stack_dialog_w,
              XmNcancelCallback, StackDialogPoppedDownCB, 0);
    XtAddCallback(stack_dialog_w,
              XmNhelpCallback, ImmediateHelpCB, 0);

    Widget cancel_w = XmSelectionBoxGetChild(stack_dialog_w, 
                                   XmDIALOG_CANCEL_BUTTON);

    XtVaSetValues(stack_dialog_w, XmNdefaultButton, cancel_w, XtPointer(0));

    // Create register view
    arg = 0;
    XtSetArg(args[arg], XmNautoUnmanage, False); arg++;
    register_dialog_w = 
      verify(createTopLevelSelectionDialog(parent, 
                                   "register_dialog", args, arg));
    Delay::register_shell(register_dialog_w);

    XtUnmanageChild(XmSelectionBoxGetChild(register_dialog_w, 
                                 XmDIALOG_TEXT));
    XtUnmanageChild(XmSelectionBoxGetChild(register_dialog_w, 
                                 XmDIALOG_SELECTION_LABEL));
    XtUnmanageChild(XmSelectionBoxGetChild(register_dialog_w, 
                                 XmDIALOG_APPLY_BUTTON));
    XtUnmanageChild(XmSelectionBoxGetChild(register_dialog_w, 
                                 XmDIALOG_CANCEL_BUTTON));

    arg = 0;
    Widget box = XmCreateRadioBox(register_dialog_w, XMST("box"), args, arg);
    XtManageChild(box);

    arg = 0;
    XtSetArg(args[arg], XmNset, !all_registers); arg++;
    int_registers_w = 
      XmCreateToggleButton(box, XMST("int_registers"), args, arg);
    XtManageChild(int_registers_w);

    arg = 0;
    XtSetArg(args[arg], XmNset, all_registers); arg++;
    all_registers_w = 
      XmCreateToggleButton(box, XMST("all_registers"), args, arg);
    XtManageChild(all_registers_w);

    XtAddCallback(int_registers_w, XmNvalueChangedCallback, 
              sourceSetIntRegistersCB, XtPointer(0));
    XtAddCallback(all_registers_w, XmNvalueChangedCallback, 
              sourceSetAllRegistersCB, XtPointer(0));

    arg = 0;
    register_list_w = XmSelectionBoxGetChild(register_dialog_w, XmDIALOG_LIST);
    XtVaSetValues(register_list_w,
              XmNselectionPolicy, XmSINGLE_SELECT,
              XtPointer(0));

    XtAddCallback(register_list_w,
              XmNsingleSelectionCallback, SelectRegisterCB, 0);
    XtAddCallback(register_list_w,
              XmNmultipleSelectionCallback, SelectRegisterCB, 0);
    XtAddCallback(register_list_w,
              XmNextendedSelectionCallback, SelectRegisterCB, 0);
    XtAddCallback(register_list_w,
              XmNbrowseSelectionCallback, SelectRegisterCB, 0);

    XtAddCallback(register_dialog_w,
              XmNokCallback, UnmanageThisCB, register_dialog_w);
    XtAddCallback(register_dialog_w,
              XmNokCallback, RegisterDialogPoppedDownCB, 0);
    XtAddCallback(register_dialog_w,
              XmNhelpCallback, ImmediateHelpCB, 0);


    // Create thread view
    arg = 0;
    XtSetArg(args[arg], XmNautoUnmanage, False); arg++;
    thread_dialog_w = 
      verify(createTopLevelSelectionDialog(parent, 
                                   "thread_dialog", args, arg));
    Delay::register_shell(thread_dialog_w);

    XtUnmanageChild(XmSelectionBoxGetChild(thread_dialog_w, 
                                 XmDIALOG_TEXT));
    XtUnmanageChild(XmSelectionBoxGetChild(thread_dialog_w, 
                                 XmDIALOG_SELECTION_LABEL));

    if (gdb->type() != JDB)
    {
      XtUnmanageChild(XmSelectionBoxGetChild(thread_dialog_w, 
                                     XmDIALOG_OK_BUTTON));
      XtUnmanageChild(XmSelectionBoxGetChild(thread_dialog_w, 
                                     XmDIALOG_APPLY_BUTTON));
    }

    arg = 0;
    thread_list_w = XmSelectionBoxGetChild(thread_dialog_w, XmDIALOG_LIST);
    XtVaSetValues(thread_list_w,
              XmNselectionPolicy, XmSINGLE_SELECT,
              XtPointer(0));

    XtAddCallback(thread_list_w,
              XmNsingleSelectionCallback, SelectThreadCB, 0);
    XtAddCallback(thread_list_w,
              XmNmultipleSelectionCallback, SelectThreadCB, 0);
    XtAddCallback(thread_list_w,
              XmNextendedSelectionCallback, SelectThreadCB, 0);
    XtAddCallback(thread_list_w,
              XmNbrowseSelectionCallback, SelectThreadCB, 0);

    XtAddCallback(thread_dialog_w,
              XmNcancelCallback, UnmanageThisCB, thread_dialog_w);
    XtAddCallback(thread_dialog_w,
              XmNcancelCallback, ThreadDialogPoppedDownCB, 0);
    XtAddCallback(thread_dialog_w,
              XmNokCallback,     ThreadCommandCB, XtPointer("suspend"));
    XtAddCallback(thread_dialog_w,
              XmNapplyCallback,  ThreadCommandCB, XtPointer("resume"));
    XtAddCallback(thread_dialog_w,
              XmNhelpCallback, ImmediateHelpCB, 0);

    // Create remaining glyphs in the background
    XtAppAddWorkProc (app_context, CreateGlyphsWorkProc, XtPointer(0));
}

// Check for modifications
void SourceView::CheckModificationCB(Widget, XtPointer client_data, 
                             XtPointer call_data)
{
    bool editable = bool((int)(long)client_data);
    XmTextVerifyCallbackStruct *cbs = (XmTextVerifyCallbackStruct *)call_data;
    if (!editable && cbs != 0 && cbs->event != 0)
    {
      cbs->doit = False;
      return;
    }

    // Follow text modifications here... (FIXME)
}

// Create source or code window
void SourceView::create_text(Widget parent, const char *base, bool editable,
                       Widget& form, Widget& text)
{
    Arg args[15];
    int arg = 0;

    // Create text window
    arg = 0;
    XtSetArg(args[arg], XmNmarginHeight, 0);    arg++;
    XtSetArg(args[arg], XmNmarginWidth,  0);    arg++;
    const string form_name = string(base) + "_form_w";
    form = verify(XmCreateForm(parent, XMST(form_name.chars()), args, arg));

    arg = 0;
    XtSetArg(args[arg], XmNselectionArrayCount, 1);               arg++;
    XtSetArg(args[arg], XmNtopAttachment,     XmATTACH_FORM);     arg++;
    XtSetArg(args[arg], XmNbottomAttachment,  XmATTACH_FORM);     arg++;
    XtSetArg(args[arg], XmNleftAttachment,    XmATTACH_FORM);     arg++;
    XtSetArg(args[arg], XmNrightAttachment,   XmATTACH_FORM);     arg++;
    XtSetArg(args[arg], XmNallowResize,       True);              arg++;
    XtSetArg(args[arg], XmNeditMode,          XmMULTI_LINE_EDIT); arg++;
    XtSetArg(args[arg], XmNcursorPositionVisible, True);          arg++;

    if (lesstif_version <= 82)
    {
      // LessTif 0.81 has a bad implementation of auto-show
      // position: rather than scrolling only when needed, the line
      // containing the cursor is *always* scrolled such that it
      // becomes the first line.  Hence, disable the LessTif
      // auto-show mechanism and rely on the DDD ones.
      XtSetArg(args[arg], XmNautoShowCursorPosition, False);    arg++;
    }
    else
    {
      XtSetArg(args[arg], XmNautoShowCursorPosition, True);     arg++;
    }

    if (lesstif_version <= 86)
    {
      // LessTif 0.86 and earlier has trouble with non-editable text
      // windows: cursor movement is inhibited.  Rely on
      // `CheckModificationCB' instead.
      XtSetArg(args[arg], XmNeditable, True); arg++;
    }
    else
    {
      XtSetArg(args[arg], XmNeditable, editable); arg++;
    }

    const string text_name = string(base) + "_text_w";
    text = verify(XmCreateScrolledText(form, XMST(text_name.chars()), args, arg));
    XtManageChild(text);

    // Set up the scrolled window
    XtVaSetValues(XtParent(text),
              XmNspacing,         0,
              XmNborderWidth,     0,
              XmNshadowThickness, 0,
              XtPointer(0));

    // Give the form the size specified for the text
    set_scrolled_window_size(text, form);

    // Set callbacks
    XtAddCallback(text, XmNgainPrimaryCallback, 
              set_source_argCB, XtPointer(false));
    XtAddCallback(text, XmNmotionVerifyCallback,
              set_source_argCB, XtPointer(true));
    XtAddCallback(text, XmNmotionVerifyCallback, 
              CheckScrollCB, XtPointer(0));
    XtAddCallback(text, XmNmodifyVerifyCallback,
              CheckModificationCB, XtPointer(editable));
    InstallTextTips(text);

    // Fetch scrollbar ID and add callbacks
    Widget scrollbar = 0;
    XtVaGetValues(XtParent(text), 
              XmNverticalScrollBar, &scrollbar,
              XtPointer(0));
    if (scrollbar != 0)
    {
      XtAddCallback(scrollbar, XmNincrementCallback,     CheckScrollCB, 0);
      XtAddCallback(scrollbar, XmNdecrementCallback,     CheckScrollCB, 0);
      XtAddCallback(scrollbar, XmNpageIncrementCallback, CheckScrollCB, 0);
      XtAddCallback(scrollbar, XmNpageDecrementCallback, CheckScrollCB, 0);
      XtAddCallback(scrollbar, XmNtoTopCallback,         CheckScrollCB, 0);
      XtAddCallback(scrollbar, XmNtoBottomCallback,      CheckScrollCB, 0);
      XtAddCallback(scrollbar, XmNdragCallback,          CheckScrollCB, 0);
      XtAddCallback(scrollbar, XmNvalueChangedCallback,  CheckScrollCB, 0);
    }
}



//-----------------------------------------------------------------------
// Position management
//-----------------------------------------------------------------------

// Set current execution position, based on the GDB position info
// POSITION; no arg means clear current position.
// STOPPED indicates that the program just stopped.
// SIGNALED indicates that the program received a signal.
void SourceView::show_execution_position (const string& position_,
                                bool stopped,   bool signaled,
                                bool silent)
{
    if (stopped)
    {
      at_lowest_frame = true;
      signal_received = signaled;
    }

    if (position_.empty())
    {
      if (!display_glyphs)
      {
          // Remove old marker
          int indent = indent_amount(source_text_w);
          if (indent > 0)
          {
            static const string no_marker = " ";
            XmTextReplace (source_text_w,
                         last_pos + indent - no_marker.length(),
                         last_pos + indent,
                         XMST(no_marker.chars()));
          }

          if (last_start_highlight)
            XmTextSetHighlight (source_text_w,
                            last_start_highlight, last_end_highlight,
                            XmHIGHLIGHT_NORMAL);

      }
      last_pos = last_start_highlight = last_end_highlight = 0;
      last_execution_file = "";
      last_execution_line = 0;
      update_glyphs();

      undo_buffer.remove_position();
      undo_buffer.add_state();
      return;
    }

    string file_name = current_file_name;
    string position = position_;

    if (position.contains(':'))
    {
      file_name = position.before(":");
      position  = position.after(":");
    }

    int line = get_positive_nr(position);
    if (line < 0)
      return;

    if (!is_current_file(file_name))
      read_file(file_name, line, silent);

    if (is_current_file(file_name))
    {
      int indent = indent_amount(source_text_w);

      if (!display_glyphs && indent > 0)
      {
          // Remove old marker
          static const string no_marker = " ";
          XmTextReplace (source_text_w,
                     last_pos + indent - no_marker.length(),
                     last_pos + indent,
                     XMST(no_marker.chars()));
      }

      // Show current position
      _show_execution_position(file_name, line, silent, stopped);
    }
}

// Unset current execution position (program terminated)
void SourceView::clear_execution_position()
{
    show_execution_position();
    last_execution_pc = "";
    last_shown_pc     = "";
    update_glyphs();

    undo_buffer.remove_address();
    undo_buffer.add_state();
}


void SourceView::_show_execution_position(const string& file, int line, 
                                bool silent, bool stopped)
{
    last_execution_file = file;
    last_execution_line = line;

    if (!is_current_file(file))
      read_file(file, line, silent);

    if (!is_current_file(file) || line < 1 || line > line_count)
      return;

    add_position_to_history(file, line, stopped);

    XmTextPosition pos = pos_of_line(line);
    int indent = indent_amount(source_text_w);
    SetInsertionPosition(source_text_w, pos + indent, false);

    // Mark current line
    if (!display_glyphs && indent > 0)
    {
      // Set new marker
      static const string marker = ">";
      XmTextReplace (source_text_w,
                   pos + indent - marker.length(),
                   pos + indent,
                   XMST(marker.chars()));
    }

    XmTextPosition pos_line_end = 0;
    if (!current_source.empty())
      pos_line_end = current_source.index('\n', pos) + 1;

    if (!display_glyphs && 
      (pos != last_start_highlight || pos_line_end != last_end_highlight))
    {
      if (last_start_highlight)
      {
          XmTextSetHighlight (source_text_w,
                        last_start_highlight, last_end_highlight,
                        XmHIGHLIGHT_NORMAL);
      }

      XmTextSetHighlight (source_text_w,
                      pos, pos_line_end,
                      XmHIGHLIGHT_SELECTED);
    }

    last_pos             = pos;
    last_start_highlight = pos;
    last_end_highlight   = pos_line_end;

    update_glyphs();
}


void SourceView::show_position(string position, bool silent)
{
    string file_name = current_file_name;

    if (position.contains(':'))
    {
      file_name = position.before(':');
      position  = position.after(':');
    }
    int line = get_positive_nr(position);

    // In case of `Open Source', we get FILE:1 positions.  Be sure to
    // reload the file in this case.
    bool force_reload = (line == 1);
    if (!is_current_file(file_name) || force_reload)
      read_file(file_name, line, force_reload, silent);

    // Have window scroll to correct position
    if (is_current_file(file_name))
    {
      if (line == 0 && gdb->type() == JDB)
      {
          // Scroll to current class
          int pos = java_class_start(current_source, current_source_name());

          if (pos >= 0)
          {
            int line_nr = 0;
            bool in_text;
            int bp_nr;
            string address;

            if (get_line_of_pos(source_text_w, pos, line_nr, address, 
                            in_text, bp_nr))
            {
                line = line_nr;
            }
          }
      }
         
      if (line > 0 && line <= line_count)
      {
          add_position_to_history(file_name, line, false);
    
          XmTextPosition pos = pos_of_line(line);
          int indent = indent_amount(source_text_w, pos);
          SetInsertionPosition(source_text_w, pos + indent, true);
            
          last_pos = pos;
      }
    }
}



//-----------------------------------------------------------------------
// Process GDB output
//-----------------------------------------------------------------------

// Process reply on 'info breakpoints'.
// Update breakpoints in BP_BAP, adding new ones or deleting existing ones.
// Update breakpoint display by calling REFRESH_BP_DISP.
void SourceView::process_info_bp (string& info_output,
                          const string& break_arg)
{
    // DEC DBX issues empty lines, which causes trouble
    info_output.gsub("\n\n", "\n");

    // SGI DBX issues `Process PID' before numbers
#if RUNTIME_REGEX
    static regex rxprocess1("Process[ \t]+[0-9]+:[ \t]*");
#endif
    info_output.gsub(rxprocess1, "");

    last_info_output = info_output;
    string keep_me = "";

    switch (gdb->type())
    {
    case GDB:
    case BASH:
      // If this is no breakpoint info, process it as GDB message
      if (!info_output.contains("Num", 0) && 
          !info_output.contains("No breakpoints", 0))
          check_remainder(info_output);
      break;

    case DBG:
    case DBX:
    case XDB:
    case JDB:
    case PYDB:
    case PERL:
      break;
    }
                            
    VarIntArray bps_not_read;
    MapRef ref;
    int i;
    for (i = bp_map.first_key(ref); i != 0; i = bp_map.next_key(ref))
      bps_not_read += i;

    bool changed = false;
    bool added   = false;
    std::ostringstream undo_commands;
    string file = current_file_name;

    while (!info_output.empty())
    {
      int bp_nr = -1;
      switch(gdb->type())
      {
      case BASH:
      case GDB:
      case PYDB:
      case DBG:
          if (!has_nr(info_output))
          {
            // Skip this line
            info_output = info_output.after('\n');
            continue;
          }
          bp_nr = get_positive_nr (info_output);
          break;

      case DBX:
          {
            // SGI IRIX DBX issues `Process PID: ' 
            // before status lines.
#if RUNTIME_REGEX
            static regex rxprocess2("Process[ \t]+[0-9]+:");
#endif
            if (info_output.contains(rxprocess2, 0))
                info_output = info_output.after(':');
            strip_leading_space(info_output);
                
            if (!info_output.contains('(', 0)
                && !info_output.contains('[', 0)
                && !info_output.contains('#', 0))
            {
                // No breakpoint info - skip this line
                info_output = info_output.after('\n');
                continue;
            }
            string bp_nr_s = info_output.after(0);
            bp_nr = get_positive_nr (bp_nr_s);
          }
          break;


      case XDB:
          bp_nr = get_positive_nr(info_output);
          break;

      case PERL:
      case JDB:
      {
          // JDB and Perl have no breakpoint numbers.
          // Check if we already have a breakpoint at this location.
          bp_nr = breakpoint_number(info_output, file);
          if (bp_nr == 0)
            bp_nr = max_breakpoint_number_seen + 1;   // new breakpoint
          if (bp_nr < 0)
          {
            // Not a breakpoint
            string line = info_output.before('\n');
            if (!line.contains("Current breakpoints set"))
                keep_me += line;
            
            // Skip this line
            info_output = info_output.after('\n');
            continue;
          }
          break;
      }
      }

      if (bp_nr <= 0)
      {
          info_output = info_output.after('\n');
          continue;
      }

      if (bp_map.contains (bp_nr))
      {
          // Update existing breakpoint
          bps_not_read -= bp_nr;
          BreakPoint *bp = bp_map.get(bp_nr);

          std::ostringstream old_state;
          undo_buffer.add_breakpoint_state(old_state, bp);

          std::ostringstream local_commands;
          bool need_total_undo = false;

          bool bp_changed = 
            bp->update(info_output, local_commands, need_total_undo);

          if (bp_changed)
          {
            if (bp->position_changed() || bp->enabled_changed())
            {
                changed = true;
            }

            if (need_total_undo)
            {
                // To undo this change, we must delete the old
                // breakpoint and create a new one.
                undo_commands << delete_command(bp->number()) << "\n"
                          << string(old_state);
            }
            else
            {
                // A simple command suffices to undo this change.
                undo_commands << string(local_commands);
            }
          }
      }
      else
      {
          // New breakpoint
          changed = true;
          BreakPoint *new_bp = 
            new BreakPoint(info_output, break_arg, bp_nr, file);
          bp_map.insert(bp_nr, new_bp);

          if (gdb->has_delete_command())
          {
            const string num = "@" + itostring(bp_nr) + "@";
            undo_commands << gdb->delete_command(num) << '\n';
          }
          else
          {
            undo_commands << delete_command(bp_nr) << '\n';
          }

          if (!added)
          {
            added = true;
            // Select this breakpoint only
            MapRef ref;
            for (BreakPoint* bp = bp_map.first(ref);
                 bp != 0;
                 bp = bp_map.next(ref))
            {
                bp->selected() = false;
            }
          }
          new_bp->selected() = true;
      }

      max_breakpoint_number_seen = max(max_breakpoint_number_seen, bp_nr);
    }

    // Keep this stuff for further processing
    info_output = keep_me;

    // Delete all breakpoints not found now
    for (i = 0; i < bps_not_read.size(); i++)
    {
      BreakPoint *bp = bp_map.get(bps_not_read[i]);

      // Older Perl versions only listed breakpoints in the current file
      if (gdb->type() == PERL && !bp_matches(bp, current_file_name))
          continue;

      // Delete it
      undo_buffer.add_breakpoint_state(undo_commands, bp);
      delete bp;
      bp_map.del(bps_not_read[i]);

      changed = true;
    }

    if (changed)
      refresh_bp_disp();

    // Set up breakpoint editor contents
    process_breakpoints(last_info_output);

    undo_buffer.add_command(string(undo_commands));

    // Set up existing panels
    update_properties_panels();
}

int SourceView::next_breakpoint_number()
{
    return max_breakpoint_number_seen + 1;
}


// Process GDB `info line main' output
void SourceView::process_info_line_main(string& info_output)
{
    clear_file_cache();
    clear_code_cache();
    clear_dbx_lookup_cache();

    if (info_output.empty())
      return;

    current_file_name = "";

    switch (gdb->type())
    {
    case BASH:
    case DBG:
    case GDB:
    case JDB:
    case PERL:
    case PYDB:
    case XDB:
    {
      PosBuffer pos_buffer;
      pos_buffer.filter(info_output);
      pos_buffer.answer_ended();
      if (pos_buffer.pos_found())
          show_position(pos_buffer.get_position());
      if (pos_buffer.pc_found())
          show_pc(pos_buffer.get_pc());
      if (!pos_buffer.pos_found() && !pos_buffer.pc_found())
          add_current_to_history();
    }
    break;

    case DBX:
    {
      show_position(info_output);
      info_output = "";
    }
    break;
    }

    // Strip 'Line <n> of <file> starts at <address>...' info
    // Strip 'No symbol table is loaded.' info
    const _XtString strips[] = {"Line ", "No symbol table is loaded."};

    for (int i = 0; i < int(XtNumber(strips)); i++)
    {
      int line = info_output.index(strips[i]);
      if (line >= 0)
      {
          int end_line = info_output.index('\n', line);
          if (end_line >= 0)
            info_output.at(line, end_line - line) = "";
      }
    }

    check_remainder(info_output);
}

void SourceView::check_remainder(string& info_output)
{
    // Any remaining input is an informative GDB message.
    // Strip newlines around the message and post it.

    while (info_output.length() > 0 && 
         info_output[0] == '\n')
      info_output = info_output.after(0);
    while (info_output.length() > 0 && 
         info_output[info_output.length() - 1] == '\n')
      info_output = info_output.before(int(info_output.length() - 1));

    post_gdb_message(info_output, true, source_text_w);
}



//-----------------------------------------------------------------------
// Locate position
//-----------------------------------------------------------------------

void SourceView::lookup(string s, bool silent)
{
    if (!s.empty() && isspace(s[0]))
      s = s.after(rxwhite);

    undo_buffer.start("lookup");

    if (s.empty())
    {
      // Empty argument given
      if (!last_execution_pc.empty())
      {
          // Show last PC
          show_pc(last_execution_pc, XmHIGHLIGHT_SELECTED);
      }
      else
      {
          // Show cursor position
          SetInsertionPosition(code_text_w,
                         XmTextGetInsertionPosition(code_text_w));
      }

      if (!last_execution_file.empty())
      {
          // Show last execution position
          _show_execution_position(last_execution_file, 
                             last_execution_line,
                             silent, false);
      }
      else
      {
          // Show cursor position
          SetInsertionPosition(source_text_w,
                         XmTextGetInsertionPosition(source_text_w));
      }
    }
    else if (is_file_pos(s))
    {
      // FILE:LINE given
      add_current_to_history();
      if (gdb->type() == GDB)
      {
          Command c("list " + s);
          c.verbose = !silent;
          c.echo    = !silent;
          c.prompt  = !silent;
          gdb_command(c);
      }
      else
      {
          show_position(s);
      }
    }
    else if (s[0] != '0' && isdigit(s[0]))
    {
      // Line number given
      int line = atoi(s.chars());
      if (line > 0 && line <= line_count)
      {
          add_current_to_history();

          switch (gdb->type())
          {
          case GDB:
          {
            Command c("list " + current_source_name() + ":" + 
                    itostring(line));
            c.verbose = !silent;
            c.echo    = !silent;
            c.prompt  = !silent;
            gdb_command(c);
            break;
          }
            
          case JDB:
            show_position(current_source_name() + ":" + itostring(line));
            break;

          case BASH:
          case DBG: 
          case DBX:
          case PERL:
          case XDB:
          case PYDB:
            show_position(full_path(current_file_name) 
                        + ":" + itostring(line));
            break;
          }
      }
      else
      {
          if (!silent)
            post_error("No line " 
                     + itostring(line) + " in current source.",
                     "no_such_line_error", source_text_w);
      }
    }
    else if (s[0] == '#')
    {
      // Breakpoint given
      string nr_str = s.after('#');
      int nr = get_positive_nr(nr_str);
      if (nr >= 0)
      {
          MapRef ref;
          BreakPoint *bp;
          for (bp = bp_map.first(ref); bp != 0; bp = bp_map.next(ref))
          {
            if (nr == bp->number())
            {
                add_current_to_history();
                show_position(bp->pos());
                show_pc(bp->address());
                break;
            }
          }

          if (bp == 0 && !silent)
            post_error("No breakpoint number " + itostring(nr) + ".", 
                     "no_such_breakpoint_error", source_text_w);
      }
    }
    else
    {
      // Function or *address given
      add_current_to_history();
      switch (gdb->type())
      {
      case GDB:
      case PYDB:
      {
          if (s[0] == '0')    // Address given
            s.prepend("*");
          if (gdb->has_quotes())
          {
            if (s.length() > 0 && s[0] != '\'' && s[0] != '*')
                s = string('\'') + s + '\'';
          }

          Command c("list " + s);
          c.verbose = !silent;
          c.echo    = !silent;
          c.prompt  = !silent;
          gdb_command(c);
          break;
      }

      case BASH:
      case DBG:
      case PERL:
      {
          Command c("l " + s);
          c.verbose = !silent;
          c.echo    = !silent;
          c.prompt  = !silent;
          gdb_command(c);
          break;
      }

      case DBX:
      case JDB:
      {
          string pos = dbx_lookup(s, silent);
          if (!pos.empty())
            show_position(pos);
          break;
      }

      case XDB:
      {
          Command c("v " + s);
          c.verbose = !silent;
          c.echo    = !silent;
          c.prompt  = !silent;
          gdb_command(c);
          break;
      }
      }
    }
}



//-----------------------------------------------------------------------
// Position history
//-----------------------------------------------------------------------

// Add current position to history
void SourceView::add_current_to_history()
{
    XmTextPosition pos;
    int line_nr;
    bool in_text;
    int bp_nr;
    string address;
    bool pos_found;

    // Get position in source code
    pos = XmTextGetInsertionPosition(source_text_w);
    pos_found = get_line_of_pos(source_text_w, pos, line_nr, address, 
                        in_text, bp_nr);
    if (pos_found)
      add_position_to_history(current_source_name(), line_nr, false);

    // Get position in machine code
    pos = XmTextGetInsertionPosition(code_text_w);
    pos_found = get_line_of_pos(code_text_w, pos, line_nr, address, 
                        in_text, bp_nr);
    if (pos_found && !address.empty())
      undo_buffer.add_address(address, false);
}

// Add position to history
void SourceView::add_position_to_history(const string& file_name, int line, 
                               bool stopped)
{
    string source_name = file_name;
    switch (gdb->type())
    {
    case GDB:
    case JDB:
    case PYDB:
      // Use source names instead.
      if (source_name_cache.has(file_name))
          source_name = source_name_cache[file_name];
      break;

    case BASH:
    case DBG:
    case DBX:
    case XDB:
    case PERL:
      break;
    }

    undo_buffer.add_position(source_name, line, stopped);
    undo_buffer.add_state();
}

// Lookup entry from position history
void SourceView::goto_entry(const string& file_name, int line, 
                      const string& address, bool exec_pos)
{
#if 0
    // Show position in status line
    string msg = "";
    if (!file_name.empty())
      msg = "File " + quote(file_name);
    if (line != 0)
    {
      if (msg.empty())
          msg = "Line ";
      else
          msg += ", line ";
      msg += itostring(line);
    }
    if (!address.empty())
    {
      if (msg.empty())
          msg = "Address ";
      else
          msg += ", address ";
      msg += address;
    }
    set_status(msg);
#endif

    if (!file_name.empty())
    {
      // Lookup source
      if (!is_current_file(file_name))
      {
          read_file(file_name, line);
      }

      if (is_current_file(file_name) && line > 0 && line <= line_count)
      {
          if (exec_pos)
          {
            _show_execution_position(file_name, line, true, true);
          }
          else
          {
            XmTextPosition pos = pos_of_line(line);
            int indent = indent_amount(source_text_w, pos);
            SetInsertionPosition(source_text_w, pos + indent, true);
          }
      }
    }

    if (!address.empty())
    {
      // Lookup address
      show_pc(address, 
            (exec_pos || address == last_execution_pc) ? 
            XmHIGHLIGHT_SELECTED : XmHIGHLIGHT_NORMAL);
    }
}



//-----------------------------------------------------------------------
// Process current working directory
//-----------------------------------------------------------------------

void SourceView::process_pwd(string& pwd_output)
{
    strip_space(pwd_output);

    while (!pwd_output.empty())
    {
      string pwd;
      if (pwd_output.contains('\n'))
      {
          pwd        = pwd_output.before('\n');
          pwd_output = pwd_output.after('\n');
      }
      else
      {
          pwd        = pwd_output;
          pwd_output = "";
      }

      switch (gdb->type())
      {
      case GDB:               // 'Working directory PATH.'
      case PYDB:
          if (pwd.contains("Working directory", 0))
          {
            pwd = pwd.before('.', -1);
            pwd = pwd.after(' ', -1);
          }
          // FALL THROUGH

      case BASH:
      case DBG:
      case DBX:         // 'PATH'
      case JDB:
      case PERL:
      case XDB:
          if (pwd.contains('/', 0) && !pwd.contains(" "))
          {
            current_pwd = pwd;
            process_cd(current_pwd);
            return;
          }
          break;
      }
    }
}


//-----------------------------------------------------------------------
// Process current use path
//-----------------------------------------------------------------------

void SourceView::process_use(string& use_output)
{
    if (use_output == NO_GDB_ANSWER)
      return;

    strip_space(use_output);

    string path_prefix = "";
    const char *p = getenv("CLASSPATH");
    if (p != 0)
      path_prefix = string(p) + ":";
    if (!use_output.contains(path_prefix, 0))
      use_output.prepend(path_prefix);

    if (current_class_path != use_output)
    {
      current_class_path = use_output;
      clear_file_cache();
      reload();
    }
}

string SourceView::class_path()
{
    if (gdb->type() == JDB && current_class_path == NO_GDB_ANSWER)
    {
      string use = gdb_question("use");
      process_use(use);
    }

    if (current_class_path == NO_GDB_ANSWER)
    {
      const char *p = getenv("CLASSPATH");
      return p ? p : ".";
    }

    return current_class_path;
}


//-----------------------------------------------------------------------
// Searching
//-----------------------------------------------------------------------

void SourceView::find(const string& s, 
                  SourceView::SearchDirection direction,
                  bool words_only,
                  bool case_sensitive,
                  Time time)
{
    int matchlen = s.length();
    int pos = -1;
    XmTextPosition cursor = XmTextGetInsertionPosition(source_text_w);
    XmTextPosition initial_cursor = cursor;
    int wraps = 0;

    if (!have_source())
    {
      post_error("No source.", "no_source_error", source_text_w);
      return;
    }

    string key  = s;
    string text = current_source;
    if (!case_sensitive)
    {
      // FIXME: This should be done according to the current locale
      key.downcase();
      text.downcase();
    }

    // Make sure we don't re-find the currently found word
    XmTextPosition startpos;
    XmTextPosition endpos;

    if (XmTextGetSelectionPosition(source_text_w, &startpos, &endpos))
    {
      switch (direction)
      {
      case forward:
          if (cursor == startpos
            && cursor < XmTextPosition(text.length()))
            cursor++;
          break;
      case backward:
          if (cursor == endpos && cursor > 0)
            cursor--;
          break;
      }
    }

    // Go and find the word
    for (;;) {
      switch (direction)
      {
      case forward:
          pos = text.index(key, cursor);
          if (pos < 0)
          {
            if (wraps++)
                break;
            pos = text.index(key, 0);
          }
          break;
      case backward:
          pos = text.index(key, cursor - text.length() - 1);
          if (pos < 0)
          {
            if (wraps++)
                break;
            pos = text.index(key, -1);
          }
          break;
      }

      if (pos < 0)
          break;        // String not found or double wrap

      // Set the cursor to the appropriate position
      switch (direction)
      {
      case forward:
          cursor = pos + matchlen;
          break;
      case backward:
          cursor = pos;
          break;
      }

      if (words_only)
      {
          // Make sure the occurrence is not part of a larger word
          if (pos > 0 && pos < int(text.length()))
          {
            if (isid(text[pos]) && isid(text[pos - 1]))
                continue;
          }

          if (pos + matchlen < int(text.length()))
          {
            if (isid(text[pos + matchlen - 1]) && 
                isid(text[pos + matchlen]))
                continue;
          }
      }

      // We got it!
      break;
    }

    string msg;

    if (pos < 0)
    {
      // Clear selection
      XmTextClearSelection(source_text_w, time);

      msg = quote(s) + " not found";
    }
    else
    {
      // Highlight occurrence
      TextSetSelection(source_text_w, pos, pos + matchlen, time);

      // Set position
      SetInsertionPosition(source_text_w, cursor, false);

      if (cursor == initial_cursor)
      {
          // Unchanged position
          msg = "No other occurrences of " + quote(s) + ".";
      }
      else
      {
          int line_nr;
          bool in_text;
          int bp_nr;
          string address;

          if (!get_line_of_pos(source_text_w, pos, line_nr, 
                         address, in_text, bp_nr))
            line_nr = line_count;

          string occurrence = current_source.at(pos, matchlen);
          msg = "Found " + quote(occurrence) + " in " + line_of_cursor();
          if (wraps)
            msg += " (wrapped)";
      }
    }

    set_status(msg);
}




//-----------------------------------------------------------------------
// Return source name
//-----------------------------------------------------------------------

string SourceView::current_source_name()
{
    string source = "";

    switch (gdb->type())
    {
    case GDB:
      // GDB internally recognizes only `source names', i.e., the
      // source file name as compiled into the executable.
      if (source_name_cache[current_file_name].empty())
      {
          // Try the current source.
          string ans = gdb_question("info source");
          if (ans != NO_GDB_ANSWER)
          {
            ans = ans.before('\n');
            ans = ans.after(' ', -1);

            if (base_matches(ans, current_file_name))
            {
                // For security, we request that source and current
                // file have the same basename.
                source_name_cache[current_file_name] = ans;
            }
            else
            {
                // The current source does not match the current file.
                // Try all sources.
                static const string all_sources = "<ALL SOURCES>";

                if (source_name_cache[all_sources].empty())
                {
                  StringArray sources;
                  get_gdb_sources(sources);

                  if (sources.size() > 0)
                  {
                      ans = "";
                      for (int i = 0; i < sources.size(); i++)
                        ans += sources[i] + '\n';

                      source_name_cache[all_sources] = ans;
                  }
                }

                ans = source_name_cache[all_sources];
                if (!ans.empty())
                {
                  int n = ans.freq('\n');
                  string *sources = new string[n + 1];
                  split(ans, sources, n + 1, '\n');

                  for (int i = 0; i < n + 1; i++)
                  {
                      if (base_matches(sources[i], current_file_name))
                      {
                        const string& src = sources[i];
                        source_name_cache[current_file_name] = src;
                        break;
                      }
                  }
                  
                  delete[] sources;

                  if (source_name_cache[current_file_name].empty())
                  {
                      // No such source text.  Store the base name
                      // such that GDB is not asked again.
                      string base = basename(current_file_name.chars());
                      source_name_cache[current_file_name] = base;
                  }
                }
            }
          }
      }

      source = source_name_cache[current_file_name];
      break;

    case BASH:
    case DBG:
    case DBX:
    case PERL:
    case PYDB:
    case XDB:
      if (app_data.use_source_path)
      {
          // These debuggers use full file names.
          source = full_path(current_file_name);
      }
      break;

    case JDB:
      if (source_name_cache.has(current_file_name))
      {
          // Use the source name as stored by read_class()
          source = source_name_cache[current_file_name];
      }
      if (source.empty())
      {
          source = basename(current_file_name.chars());
          strip_java_suffix(source);
      }
      break;
    }

    // In case this does not work, use the current base name.
    if (source.empty())
      source = basename(current_file_name.chars());

    return source;
}

string SourceView::line_of_cursor()
{
    XmTextPosition pos = XmTextGetInsertionPosition(source_text_w);

    string s = current_source_name();
    if (s.empty())
      return "";        // No source

    int line_nr;
    bool in_text;
    int bp_nr;
    string address;

    if (get_line_of_pos(source_text_w, pos, line_nr, address, in_text, bp_nr))
      return s + ":" + itostring(line_nr);    // Cursor within source
    else
      return s + ":" + itostring(line_count);   // Cursor in last line
}

string SourceView::file_of_cursor()
{
    string pos = line_of_cursor();
    return full_path(current_file_name) + pos.from(':');
}


//-----------------------------------------------------------------------------
// Handle mouse selections
//----------------------------------------------------------------------------

void SourceView::setSelection(XtPointer client_data, XtIntervalId *)
{
    Widget w = (Widget)client_data;

    assert(XmIsText(w));

    TextSetSelection(w, selection_startpos, selection_endpos, 
                 selection_time);
    selection_time = 0;
    set_source_argCB(w, XtPointer(false), 0);
}

void SourceView::startSelectWordAct (Widget text_w, XEvent* e, 
                             String *params, Cardinal *num_params)
{
#if XtSpecificationRelease < 6
    selection_event = *e;
#endif

    XtCallActionProc(text_w, "grab-focus", e, params, *num_params);

    if (e->type != ButtonPress && e->type != ButtonRelease)
      return;

    XButtonEvent *event = &e->xbutton;

    XmTextPosition pos = XmTextXYToPos (text_w, event->x, event->y);

    XmTextPosition startpos, endpos;
    if (app_data.source_editing)
      startpos = endpos = pos;
    else
      find_word_bounds(text_w, pos, startpos, endpos);

    selection_click    = true;
    selection_startpos = startpos;
    selection_endpos   = endpos;
    selection_time     = time(e);

    XtAppAddTimeOut(XtWidgetToApplicationContext(text_w), 0, setSelection, 
                (XtPointer)text_w);
}

void SourceView::endSelectWordAct (Widget text_w, XEvent* e, 
                           String *params, Cardinal *num_params)
{
#if XtSpecificationRelease < 6
    selection_event = *e;
#endif
    selection_click = false;

    XtCallActionProc(text_w, "extend-end", e, params, *num_params);

    if (e->type != ButtonPress && e->type != ButtonRelease)
      return;

    XmTextPosition startpos, endpos;
    if (XmTextGetSelectionPosition(text_w, &startpos, &endpos))
    {
      selection_startpos = startpos;
      selection_endpos   = endpos;
    }

    selection_time = time(e);

    XtAppAddTimeOut(XtWidgetToApplicationContext(text_w), 0, setSelection,
               (XtPointer)text_w);
}


//-----------------------------------------------------------------------------
// Handle source popup
//----------------------------------------------------------------------------

void SourceView::set_text_popup_label(int item, const string& arg, bool sens)
{
    Widget w = text_popup[item].widget;
    MString label = MString(text_cmd_labels[item]) + tt(arg);

    XtVaSetValues(w, XmNlabelString, label.xmstring(), XtPointer(0));
    set_sensitive(w, sens);
}

void SourceView::set_text_popup_resource(int item, const string& arg)
{
    if (lesstif_version <= 82)
    {
      // Set up resources for yet-to-be-created popup menu
      string db = string(DDD_CLASS_NAME "*text_popup.") 
          + text_popup[item].name + "." + XmNlabelString + ": "
          + "@" + CHARSET_RM + " " + text_cmd_labels[item] 
          + " @" + CHARSET_TT + " " + arg;

      XrmDatabase res = XrmGetStringDatabase(db.chars());
      XrmDatabase target = XtDatabase(XtDisplay(source_text_w));
      XrmMergeDatabases(res, &target);
    }
}

// Get relative coordinates of GLYPH in TEXT
void SourceView::translate_glyph_pos(Widget glyph, Widget text, int& x, int& y)
{
    int dest_x, dest_y;
    Window child;
    XTranslateCoordinates(XtDisplay(glyph), 
                    XtWindow(glyph), XtWindow(text), 
                    x, y, &dest_x, &dest_y, &child);

    x = dest_x;
    y = dest_y;
}

// Popup button3 source menu
void SourceView::srcpopupAct (Widget w, XEvent* e, String *, Cardinal *)
{
    if (e->type != ButtonPress && e->type != ButtonRelease)
      return;

    Widget text_w;
    if (is_source_widget(w))
      text_w = source_text_w;
    else if (is_code_widget(w))
      text_w = code_text_w;
    else
      return;

    XButtonEvent* event = &e->xbutton;

    int x = event->x;
    int y = event->y;

    if (w != source_text_w && w != code_text_w)
    {
      // Called from a glyph: translate glyph position to text position
      translate_glyph_pos(w, text_w, x, y);
    }

    // Get the position
    XmTextPosition pos = XmTextXYToPos(text_w, x, y);

    // Move the insertion cursor to this position, but don't disturb the
    // selection
    XmTextPosition left, right;
    Boolean have_selection = XmTextGetSelectionPosition(text_w, &left, &right);
    if (have_selection && pos >= left && pos <= right)
    {
      // Do not scroll here.  Do not use SetInsertionPosition().
      XmTextSetInsertionPosition(text_w, pos);
      TextSetSelection(text_w, left, right, time(e));
    }
    else
    {
      // Do not scroll here.  Do not use SetInsertionPosition().
      XmTextClearSelection(text_w, time(e));
      XmTextSetInsertionPosition(text_w, pos);
    }

    int line_nr;
    bool in_text;
    static int bp_nr;
    static string address;
    bool pos_found = get_line_of_pos(w, pos, line_nr, address, in_text, bp_nr);

    bool right_of_text = 
      pos < XmTextPosition(current_text(w).length()) 
      && current_text(w)[pos] == '\n';

    if (pos_found && bp_nr != 0)
    {
      // Clicked on breakpoint: create breakpoint menu
      static Widget bp_popup_w      = 0;
      static Widget bp_popup_parent = 0;

      if (lesstif_version <= 84 && w != bp_popup_parent)
      {
          // LessTif 0.84 and earlier wants this menu re-created
          // every time the parent has changed.  Otherwise, it gets
          // insensitive.
          if (bp_popup_w != 0)
            XtDestroyWidget(bp_popup_w);
          bp_popup_w = 0;
      }

      if (bp_popup_w == 0)
      {
          bp_popup_parent = w;
          bp_popup_w = MMcreatePopupMenu(w, "bp_popup", bp_popup);
          MMaddCallbacks (bp_popup, XtPointer(&bp_nr));
          MMaddHelpCallback(bp_popup, ImmediateHelpCB);
          InstallButtonTips(bp_popup_w);
      }

      // Grey out unsupported functions
      set_sensitive(bp_popup[BPItms::Disable].widget, gdb->can_disable());
      set_sensitive(bp_popup[BPItms::SetPC].widget,
                   gdb->has_jump_command() || gdb->has_assign_command());

      MString label(bp_map.get(bp_nr)->enabled() ? 
                  "Disable Breakpoint" : "Enable Breakpoint");
      XtVaSetValues(bp_popup[BPItms::Disable].widget,
                  XmNlabelString, label.xmstring(),
                  XtPointer(0));

      XmMenuPosition(bp_popup_w, event);
      XtManageChild(bp_popup_w);
    }
    else if (pos_found 
           && (line_nr > 0 || !address.empty()) 
           && (!in_text || right_of_text))
    {
      // Create popup menu for selected line
      static Widget line_popup_w = 0;
      if (line_popup_w == 0)
      {
          line_popup_w = MMcreatePopupMenu (w, "line_popup", line_popup);
          MMaddCallbacks(line_popup, XtPointer(&address));
          MMaddHelpCallback(line_popup, ImmediateHelpCB);
          InstallButtonTips(line_popup_w);

          set_sensitive(line_popup[LineItms::SetTempBP].widget, 
                     gdb->has_temporary_breakpoints());
          set_sensitive(line_popup[LineItms::SetPC].widget,
                     gdb->has_jump_command() || 
                     gdb->has_assign_command());
      }

      if (is_source_widget(w))
          address = current_source_name() + ":" + itostring(line_nr);
      else
          address = string('*') + address;
      XmMenuPosition (line_popup_w, event);
      XtManageChild (line_popup_w);
    }
    else
    {
      // Determine surrounding token (or selection) and create popup
      static string word;

      XmTextPosition startpos = 0;
      XmTextPosition endpos   = 0;

      if (pos_found)
          word = get_word_at_pos(text_w, pos, startpos, endpos);

      // Popup specific word menu
      string current_arg = word;
      shorten(current_arg, max_popup_expr_length);
      string current_ref_arg = deref(current_arg);

      if (lesstif_version <= 82)
      {
          set_text_popup_resource(TextItms::Print,    current_arg);
          set_text_popup_resource(TextItms::Disp,     current_arg);
          set_text_popup_resource(TextItms::Watch,    current_arg);
          set_text_popup_resource(TextItms::PrintRef, current_ref_arg);
          set_text_popup_resource(TextItms::DispRef,  current_ref_arg);
          set_text_popup_resource(TextItms::WatchRef, current_ref_arg);
          set_text_popup_resource(TextItms::Whatis,   current_arg);
          set_text_popup_resource(TextItms::Lookup,   current_arg);
          set_text_popup_resource(TextItms::Break,    current_arg);
          set_text_popup_resource(TextItms::Clear,    current_arg);
      }

      Widget text_popup_w = 
          MMcreatePopupMenu(text_w, "text_popup", text_popup);
      MMaddCallbacks(text_popup, XtPointer(&word));
      MMaddHelpCallback(text_popup, ImmediateHelpCB);
      InstallButtonTips(text_popup_w);

      // The popup menu is destroyed immediately after having popped down.
      Widget shell = XtParent(text_popup_w);
      XtAddCallback(shell, XtNpopdownCallback, DestroyThisCB, shell);

      bool has_arg = (word.length() > 0);
      bool has_watch = has_arg && gdb->has_watch_command();
      set_text_popup_label(TextItms::Print,    current_arg, has_arg);
      set_text_popup_label(TextItms::Disp,     current_arg, has_arg);
      set_text_popup_label(TextItms::Watch,    current_arg, has_watch);
      set_text_popup_label(TextItms::PrintRef, current_ref_arg, has_arg);
      set_text_popup_label(TextItms::DispRef,  current_ref_arg, has_arg);
      set_text_popup_label(TextItms::WatchRef, current_ref_arg, has_watch);
      set_text_popup_label(TextItms::Whatis,   current_arg, has_arg);
      set_text_popup_label(TextItms::Lookup,   current_arg, has_arg);
      set_text_popup_label(TextItms::Break,    current_arg, has_arg);
      set_text_popup_label(TextItms::Clear,    current_arg, has_arg);

      if (current_arg != current_ref_arg)
      {
          XtManageChild(text_popup[TextItms::Sep1].widget);
          XtManageChild(text_popup[TextItms::PrintRef].widget);
          XtManageChild(text_popup[TextItms::DispRef].widget);
          // XtManageChild(text_popup[TextItms::WatchRef].widget);
      }
      else
      {
          XtUnmanageChild(text_popup[TextItms::Sep1].widget);
          XtUnmanageChild(text_popup[TextItms::PrintRef].widget);
          XtUnmanageChild(text_popup[TextItms::DispRef].widget);
          XtUnmanageChild(text_popup[TextItms::WatchRef].widget);
      }

      XmMenuPosition (text_popup_w, event);
      XtManageChild (text_popup_w);
    }
}


void SourceView::doubleClickAct(Widget w, XEvent *e, String *params, 
                        Cardinal *num_params)
{
    if (e->type != ButtonPress && e->type != ButtonRelease)
      return;

    Widget text_w;
    if (is_source_widget(w))
      text_w = source_text_w;
    else if (is_code_widget(w))
      text_w = code_text_w;
    else
      return;

    XButtonEvent* event = &e->xbutton;

    int x = event->x;
    int y = event->y;
    bool control = ((event->state & ControlMask) != 0);

    if (w != source_text_w && w != code_text_w)
    {
      // Called from a glyph: translate glyph position to text position
      translate_glyph_pos(w, text_w, x, y);
    }

    if (w == source_text_w || w == code_text_w)
    {
      // Called from text: check for double click
      Time selection_time = time(e);
      static Time last_selection_time = 0;

      bool double_click = 
          last_selection_time != 0 &&
          (Time(selection_time - last_selection_time) <= 
           Time(XtGetMultiClickTime(XtDisplay(text_w))));

      if (double_click)
          last_selection_time = 0;
      else
          last_selection_time = selection_time;

      if (!double_click)
          return;
    }

    // Get the position
    XmTextPosition pos = XmTextXYToPos(text_w, x, y);

    int line_nr;
    bool in_text;
    static int bp_nr;
    static string address;
    bool pos_found = get_line_of_pos(w, pos, line_nr, address, in_text, bp_nr);

    if (!pos_found)
      return;

    if (bp_nr != 0)
    {
      // Clicked on breakpoint
      if (control)
          delete_bp(bp_nr, text_w);
      else
          edit_bp(bp_nr, text_w);
      return;
    }

    XmTextPosition startpos = 0;
    XmTextPosition endpos   = 0;

    string arg  = source_arg->get_string();
    string word = get_word_at_pos(text_w, pos, startpos, endpos);

    if (in_text && arg == word)
    {
      // In text: do some action on current word
      if (text_w == source_text_w)
      {
          // Check for function call
          int p = endpos;
          while (p < (int)current_source.length() && 
               isspace(current_source[p]))
            p++;

          if (control || current_source.contains('(', p))
          {
            if (*num_params >= 3)
                gdb_button_command(params[2]);
            else
                gdb_button_command("list ()");
            return;
          }
      }

      if (*num_params >= 1)
          gdb_button_command(params[0]);
      else
          gdb_button_command("graph display ()");
      return;
    }

    if (!in_text)
    {
      // In breakpoint area
      IntArray bps;
      if (text_w == source_text_w)
      {
          MapRef ref;
          for (BreakPoint* bp = bp_map.first(ref);
             bp != 0;
             bp = bp_map.next(ref))
          {
            if (bp_matches(bp, line_nr))
                bps += bp->number();
          }
      }
      else
      {
          MapRef ref;
          for (BreakPoint* bp = bp_map.first(ref);
             bp != 0;
             bp = bp_map.next(ref))
          {
            if (bp->type() == BREAKPOINT && 
                compare_address(address, bp->address()) == 0)
                bps += bp->number();
          }
      }

      if (bps.size() > 0)
      {
          // In breakpoint area, and we already have a breakpoint.
          if (control)
            delete_bps(bps, text_w);
          else
            edit_bps(bps, text_w);
      }
      else
      {
          // In breakpoint area, and we have no breakpoint: create a new one
          if (*num_params >= 2)
            gdb_button_command(params[1]);
          else if (control)
            create_temp_bp(source_arg->get_string(), w);
          else
            create_bp(source_arg->get_string(), w);
      }
    }
}

void SourceView::setArgAct(Widget w, XEvent *, String *, Cardinal *)
{
    String s = 0;
    if (XmIsText(w))
      s = XmTextGetSelection(w);
    else if (XmIsTextField(w))
      s = XmTextFieldGetSelection(w);

    if (s != 0)
    {
      source_arg->set_string(s);
      XtFree(s);
    }
}


//-----------------------------------------------------------------------------
// Breakpoint selection
//----------------------------------------------------------------------------

void SourceView::NewBreakpointDCB(Widget w, XtPointer client_data, XtPointer)
{
    Widget text = Widget(client_data);
    String _input = XmTextFieldGetString(text);
    string input(_input);
    XtFree(_input);
    if (input.empty())
      return;

    create_bp(input, w);
}

void SourceView::NewBreakpointCB(Widget w, XtPointer, XtPointer)
{
    static Widget dialog = 0;
    if (dialog == 0)
    {
      Arg args[10];
      Cardinal arg = 0;
      dialog = verify(XmCreatePromptDialog(find_shell(w),
                                   XMST("new_breakpoint_dialog"),
                                   args, arg));
      Delay::register_shell(dialog);

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

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

      Widget label = XmCreateLabel(box, XMST("label"), args, arg);
      XtManageChild(label);

      arg = 0;
      Widget text = CreateComboBox(box, "text", args, arg);
      tie_combo_box_to_history(text, break_history_filter);

      XtAddCallback(dialog, XmNhelpCallback, ImmediateHelpCB, 
                  XtPointer(0));
      XtAddCallback(dialog, XmNokCallback, NewBreakpointDCB, 
                  XtPointer(text));
    }

    manage_and_raise(dialog);
}

WatchMode SourceView::selected_watch_mode = WATCH_CHANGE;

void SourceView::SetWatchModeCB(Widget, XtPointer client_data, 
                        XtPointer call_data)
{
    XmToggleButtonCallbackStruct *info = 
      (XmToggleButtonCallbackStruct *)call_data;

    if (info->set)
      selected_watch_mode = WatchMode((int)(long)client_data);
}

void SourceView::NewWatchpointDCB(Widget w, XtPointer client_data, XtPointer)
{
    Widget text = Widget(client_data);
    String _input = XmTextFieldGetString(text);
    string input(_input);
    XtFree(_input);

    strip_space(input);
    if (input.empty())
      return;

    gdb_command(gdb->watch_command(input, selected_watch_mode), w);
}

void SourceView::NewWatchpointCB(Widget w, XtPointer, XtPointer)
{
    static Widget dialog = 0;
    if (dialog == 0)
    {
      static Widget cwatch_w, rwatch_w, awatch_w;

      static MMDesc wp_modes[] =
      {
          { "cwatch",  MMPush, { SetWatchModeCB, XtPointer(WATCH_CHANGE) }, 
            0, &cwatch_w, 0, 0 },
          { "rwatch",  MMPush, { SetWatchModeCB, XtPointer(WATCH_READ) }, 
            0, &rwatch_w, 0, 0 },
          { "awatch", MMPush, { SetWatchModeCB, XtPointer(WATCH_ACCESS)},
            0, &awatch_w, 0, 0 },
          MMEnd
      };

      static MMDesc wp_menu[] = 
      {
          { "set",      MMLabel, MMNoCB, 0, 0, 0, 0 },
          { "method",   MMOptionMenu, MMNoCB, wp_modes, 0, 0, 0 },
          { "on",       MMLabel, MMNoCB, 0, 0, 0, 0 },
          MMEnd
      };

      Arg args[10];
      Cardinal arg = 0;
      dialog = verify(XmCreatePromptDialog(find_shell(w),
                                   XMST("new_watchpoint_dialog"),
                                   args, arg));
      Delay::register_shell(dialog);

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

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

      arg = 0;
      XtSetArg(args[arg], XmNorientation, XmHORIZONTAL); arg++;
      XtSetArg(args[arg], XmNborderWidth,  0); arg++;
      XtSetArg(args[arg], XmNentryBorder,  0); arg++;
      XtSetArg(args[arg], XmNspacing,      0); arg++;
      XtSetArg(args[arg], XmNmarginWidth,  0); arg++;
      XtSetArg(args[arg], XmNmarginHeight, 0); arg++;
      Widget panel = MMcreateButtonPanel(box, "panel", wp_menu, args, arg);
      (void) panel;
      MMaddCallbacks(wp_menu);
      MMaddHelpCallback(wp_menu, ImmediateHelpCB);

      set_sensitive(cwatch_w, (gdb->has_watch_command() & WATCH_CHANGE) 
                   == WATCH_CHANGE);
      set_sensitive(rwatch_w, (gdb->has_watch_command() & WATCH_READ) 
                   == WATCH_READ);
      set_sensitive(awatch_w, (gdb->has_watch_command() & WATCH_ACCESS) 
                   == WATCH_ACCESS);

      // Initialize: use CWATCH as default menu item
      XtCallActionProc(cwatch_w, "ArmAndActivate", 
                   (XEvent *)0, (String *)0, 0);

      arg = 0;
      Widget text = CreateComboBox(box, "text", args, arg);
      tie_combo_box_to_history(text, watch_history_filter);

      XtAddCallback(dialog, XmNhelpCallback, ImmediateHelpCB, 
                  XtPointer(0));
      XtAddCallback(dialog, XmNokCallback, NewWatchpointDCB, 
                  XtPointer(text));

    }

    manage_and_raise(dialog);
}


//-----------------------------------------------------------------------------
// Breakpoint properties
//----------------------------------------------------------------------------

05622 struct BreakpointPropertiesInfo {
    IntArray nrs;       // The affected breakpoints

    Widget dialog;            // The widgets of the properties panel
    Widget title;
    Widget lookup;
    Widget print;
    Widget enable;
    Widget disable;
    Widget temp;
    Widget del;
    Widget ignore;
    Widget condition;
    Widget record;
    Widget end;
    Widget edit;
    Widget editor;

    XtIntervalId timer;       // The spinbox timer
    bool spin_locked;         // If true, don't invoke spinbox callbacks
    int ignore_spin_update;   // If > 0, don't update spinbox from bp info
    bool sync_commands;       // If true, propagate cmd to other breakpoints
    BreakpointPropertiesInfo *next; // Next info in list

    static BreakpointPropertiesInfo *all; // List of all infos

    BreakpointPropertiesInfo()
      : nrs(),
        dialog(0), title(0), lookup(0), print(0),
        enable(0), disable(0), temp(0), del(0),
        ignore(0), condition(0), record(0), end(0), edit(0), editor(0),
        timer(0), spin_locked(false), ignore_spin_update(0),
        sync_commands(false), next(all)
    {
      all = this;
    }

    ~BreakpointPropertiesInfo()
    {
      if (all == this)
      {
          all = next;
      }
      else
      {
          BreakpointPropertiesInfo *info = all;
          while (info->next != this)
            info = info->next;
          info->next = next;
      }
    }

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

BreakpointPropertiesInfo *BreakpointPropertiesInfo::all = 0;

void SourceView::DeleteInfoCB(Widget, XtPointer client_data, 
                        XtPointer call_data)
{
    BreakpointPropertiesInfo *info = 
      (BreakpointPropertiesInfo *)client_data;

    gdb->removeHandler(Recording, RecordingHP, (void *)info);
    if (gdb->recording())
      gdb_command("\003");    // Abort recording

    if (XtIsManaged(XtParent(info->editor)))
    {
      // Finish entering commands.  Since W is being destroyed, pass
      // SOURCE_TEXT_W as reference.
      EditBreakpointCommandsCB(source_text_w, client_data, call_data);

      // Update all remaining panels in the next run
      gdb->addHandler(Recording, RecordingHP, XtPointer(0));
    }

    delete info;
}

void SourceView::update_properties_panels()
{
    // Update all panels
    BreakpointPropertiesInfo *info = BreakpointPropertiesInfo::all;
    while (info != 0)
    {
      update_properties_panel(info);
      info = info->next;
    }
}

void SourceView::move_breakpoint_properties(int old_bp, int new_bp)
{
    // Update all panels
    bool update = false;
    BreakpointPropertiesInfo *info = BreakpointPropertiesInfo::all;
    while (info != 0)
    {
      bool changed = false;
      for (int i = 0; i < info->nrs.size(); i++)
      {
          if (info->nrs[i] == old_bp)
          {
            info->nrs[i] = new_bp;
            update = changed = true;
          }
      }

      if (changed)
          sort(info->nrs);    // Sort again

      info = info->next;
    }

    if (update)
      update_properties_panels();
}

void SourceView::copy_breakpoint_properties(int old_bp, int new_bp)
{
    // Update all panels
    bool update = false;
    BreakpointPropertiesInfo *info = BreakpointPropertiesInfo::all;
    while (info != 0)
    {
      for (int i = 0; i < info->nrs.size(); i++)
      {
          if (info->nrs[i] == old_bp)
          {
            info->nrs += new_bp;
            update = true;
            break;
          }
      }

      info = info->next;
    }

    if (update)
      update_properties_panels();
}

void SourceView::update_properties_panel(BreakpointPropertiesInfo *info)
{
    // Remove breakpoints from list
    bool future = false;
    int i;
    for (i = 0; i < info->nrs.size(); i++)
    {
      if (info->nrs[i] >= next_breakpoint_number())
      {
          // Panel for future (renamed) breakpoint
          future = true;
          continue;
      }

      BreakPoint *bp = bp_map.get(info->nrs[i]);
      if (bp == 0)
      {
          // Breakpoint not found -- mark as deleted
          info->nrs[i] = 0;
      }
    }
    IntArray new_nrs;

    for (i = 0; i < info->nrs.size(); i++)
    {
      if (info->nrs[i] != 0)
          new_nrs += info->nrs[i];
    }

    info->nrs = new_nrs;

    if (info->nrs.size() == 0)
    {
      // No breakpoint left -- destroy dialog shell
      XtUnmanageChild(info->dialog);
      return;
    }

    if (future)
      return;                 // We cannot update yet

    // Use first breakpoint for getting values
    BreakPoint *bp = bp_map.get(info->nrs[0]);
    assert(bp != 0);

    // Set titles
    string what = bp->title();

    string label;
    if (info->nrs.size() == 1)
    {
      label = what + " " + itostring(info->nrs[0]);
    }
    else
    {
      label = what + "s ";
      for (i = 0; i < info->nrs.size(); i++)
      {
          if (i > 0)
          {
            if (info->nrs.size() == 2)
                label += " and ";
            else if (i == info->nrs.size() - 1)
                label += ", and ";
            else
                label += ", ";
          }
          label += itostring(info->nrs[i]);
      }
    }

    set_label(info->title, label);

    MString title = string(DDD_NAME) + ": Properties: " + label;
    XtVaSetValues(info->dialog, XmNdialogTitle,
              title.xmstring(), XtPointer(0));

    // Set values
    string commands = "";
    for (i = 0; i < bp->commands().size(); i++)
    {
      string cmd = bp->commands()[i];
      strip_auto_command_prefix(cmd);
      commands += cmd + "\n";
    }

    XmTextSetString(info->editor, XMST(commands.chars()));

    if (info->ignore_spin_update > 0)
    {
      info->ignore_spin_update--;
    }
    else
    {
      bool lock = info->spin_locked;
      info->spin_locked = true;
#if XmVersion >= 2000
      if (XmIsSpinBox(XtParent(info->ignore)))
      {
          XtVaSetValues(info->ignore, XmNposition,
                    bp->ignore_count(), XtPointer(0));
      }
      else
#endif
      {
          String old_ignore = XmTextFieldGetString(info->ignore);
          if (atoi(old_ignore) != bp->ignore_count())
          {
            string ignore = itostring(bp->ignore_count());
            if (ignore == "0")
                ignore = "";

            XmTextFieldSetString(info->ignore, XMST(ignore.chars()));
          }
          XtFree(old_ignore);
      }
      info->spin_locked = lock;
    }

    // Don't update unchanged condition to prevent OSF/Motif 2.0
    // ComboBox from growing
    String old_condition = XmTextFieldGetString(info->condition);
    if (bp->condition() != old_condition)
    {
        const string s1 = bp->condition();
      XmTextFieldSetString(info->condition, XMST(s1.chars()));
    }
    XtFree(old_condition);

    bool can_enable   = false;
    bool can_disable  = false;
    bool can_maketemp = false;
    bool can_print    = false;

    for (i = 0; i < info->nrs.size(); i++)
    {
      BreakPoint *bp = bp_map.get(info->nrs[i]);
      if (bp->enabled())
          can_disable = gdb->can_disable();
      else
          can_enable  = gdb->can_enable();

      if (bp->dispo() != BPDEL)
          can_maketemp = (gdb->type() == GDB || gdb->type() == PYDB);

      if (bp->type() == WATCHPOINT)
          can_print = true;
    }

    if (can_print)
      XtManageChild(info->print);
    else
      XtUnmanageChild(info->print);

    set_sensitive(info->enable,  can_enable);
    set_sensitive(info->disable, can_disable);
    set_sensitive(info->temp,    can_maketemp);

    set_sensitive(info->ignore,           gdb->has_ignore_command());
    set_sensitive(XtParent(info->ignore), gdb->has_ignore_command());

    set_sensitive(info->condition,           gdb->has_breakpoint_conditions());
    set_sensitive(XtParent(info->condition), gdb->has_breakpoint_conditions());

    bool can_record = gdb->type() == GDB && !gdb->recording();
    bool can_edit   = gdb->has_breakpoint_commands() && !gdb->recording();
    set_sensitive(info->record,    can_record);
    set_sensitive(info->end,       gdb->recording());
    set_sensitive(info->edit,      can_edit);
    set_sensitive(info->editor,    can_edit);

    if (info->sync_commands)
    {
      for (i = 1; i < info->nrs.size(); i++)
          set_bp_commands(info->nrs[i], bp->commands());
      info->sync_commands = false;
    }
}

static string cond_filter(const string& cmd)
{
    switch (gdb->type())
    {
    case GDB:
    case PYDB:
      if (cmd.contains("cond", 0))
      {
          // Skip command
          string arg = cmd.after(rxwhite);

          // Skip breakpoint number
          arg = arg.after(rxwhite);

          strip_space(arg);
          return arg;
      }
      break;

    case DBX:
      if (cmd.contains("if "))
      {
          string arg = cmd.after("if ");
          strip_space(arg);
          return arg;
      }
      break;

    case XDB:
      if (cmd.contains("{if "))
      {
          string arg = cmd.after("if ");
          if (arg.contains("{}"))
            arg = arg.before("{}");
          strip_space(arg);
          return arg;
      }
      break;

    case JDB:
      // No conditions in JDB
      break;

    case BASH:
    case PERL:
    case DBG:
    {
      // FIXME
      break;
    }
    }

    return "";                // No condition
}


// Get selected breakpoint numbers
void SourceView::getBreakpointNumbers(IntArray& breakpoint_nrs)
{
    if (breakpoint_list_w == 0)
      return;

    IntArray numbers;
    getItemNumbers(breakpoint_list_w, numbers);

    // Double-check each number for safety.  One may define commands
    // as breakpoint numbers and cause DDD to crash.
    for (int i = 0; i < numbers.size(); i++)
    {
      BreakPoint *bp = bp_map.get(numbers[i]);
      if (bp != 0)
          breakpoint_nrs += numbers[i];
    }
}


// Edit breakpoint properties
void SourceView::EditBreakpointPropertiesCB(Widget, 
                                  XtPointer client_data, 
                                  XtPointer)
{
    IntArray breakpoint_nrs;
    if (client_data == 0)
    {
      getBreakpointNumbers(breakpoint_nrs);
    }
    else
    {
      breakpoint_nrs += *((int *)client_data);
    }

    edit_bps(breakpoint_nrs);
}

void SourceView::edit_bps(IntArray& breakpoint_nrs, Widget /* origin */)
{
    if (breakpoint_nrs.size() == 0)
      return;                 // No breakpoints given

    sort(breakpoint_nrs);

    // Check for first breakpoint
    BreakPoint *bp = bp_map.get(breakpoint_nrs[0]);
    if (bp == 0)
      return;                 // No such breakpoint

    BreakpointPropertiesInfo *info = new BreakpointPropertiesInfo;
    info->spin_locked = true;
    info->nrs = breakpoint_nrs;

    Arg args[10];
    int arg = 0;
    XtSetArg(args[arg], XmNautoUnmanage, False); arg++;
    info->dialog = 
      verify(XmCreatePromptDialog(source_text_w,
                            XMST("breakpoint_properties"),
                            args, arg));

    Widget apply = XmSelectionBoxGetChild(info->dialog, XmDIALOG_APPLY_BUTTON);
    XtVaSetValues(info->dialog, XmNdefaultButton, apply, XtPointer(0));
    XtManageChild(apply);

    XtUnmanageChild(XmSelectionBoxGetChild(info->dialog, XmDIALOG_OK_BUTTON));

    // Remove old prompt
    Widget text = XmSelectionBoxGetChild(info->dialog, XmDIALOG_TEXT);
    XtUnmanageChild(text);
    Widget old_label = 
      XmSelectionBoxGetChild(info->dialog, XmDIALOG_SELECTION_LABEL);
    XtUnmanageChild(old_label);

    Delay::register_shell(info->dialog);

    MMDesc commands_menu[] =
    {
      { "record", MMPush,
        { RecordBreakpointCommandsCB, XtPointer(info) }, 
        0, &info->record, 0, 0 },
      { "end",    MMPush | MMInsensitive,
        { EndBreakpointCommandsCB, XtPointer(info) }, 
        0, &info->end, 0, 0 },
      { "edit",   MMPush | MMInsensitive,
        { EditBreakpointCommandsCB, XtPointer(info) }, 
        0, &info->edit, 0, 0 },
      MMEnd
    };

    MMDesc enabled_menu[] = 
    {
      { "lookup",    MMPush,
        { LookupBreakpointCB,    XtPointer(info) }, 0, &info->lookup, 0, 0 },
      { "print",     MMPush,
        { PrintWatchpointCB,     XtPointer(info) }, 0, &info->print, 0, 0 },
      { "enable",    MMPush,
        { EnableBreakpointsCB,   XtPointer(info) }, 0, &info->enable, 0, 0 },
      { "disable",   MMPush,
        { DisableBreakpointsCB,  XtPointer(info) }, 0, &info->disable, 0, 0},
      { "temporary", MMPush,
        { MakeBreakpointsTempCB, XtPointer(info) }, 0, &info->temp, 0, 0 },
      { "delete",    MMPush | MMHelp,
        { DeleteBreakpointsCB,   XtPointer(info) }, 0, &info->del, 0, 0 },
      MMEnd
    };

    if (app_data.flat_dialog_buttons)
    {
      for (MMDesc *item = enabled_menu; item != 0 && item->name != 0; item++)
      {
          if ((item->type & MMTypeMask) == MMPush)
            item->type = (MMFlatPush | (item->type & ~MMTypeMask));
      }
    }

    MMDesc panel_menu[] = 
    {
      { "title", MMButtonPanel, MMNoCB, enabled_menu, 0, 0, 0 },
      { "condition", MMComboBox,
        { SetBreakpointConditionCB, XtPointer(info) }, 
        0, &info->condition, 0, 0 },
      { "ignore", MMSpinBox,
        { SetBreakpointIgnoreCountCB, XtPointer(info) }, 
        0, &info->ignore, 0, 0 },
      { "commands", MMButtonPanel, MMNoCB, commands_menu, 0, 0, 0 },
      MMEnd
    };

    arg = 0;
    XtSetArg(args[arg], XmNorientation, XmHORIZONTAL); arg++;
    Widget form = XmCreateRowColumn(info->dialog, XMST("form"), args, arg);
    XtManageChild(form);

    Widget panel = MMcreatePanel(form, "panel", panel_menu);

    XtVaSetValues(panel,
              XmNmarginWidth,    0,
              XmNmarginHeight,   0,
              XtPointer(0));

    Widget buttons = XtParent(info->lookup);
    XtVaSetValues(buttons,
              XmNmarginWidth,     0, 
              XmNmarginHeight,    0, 
              XmNborderWidth,     0,
              XmNshadowThickness, 0, 
              XmNspacing,         0,
              XtPointer(0));

    arg = 0;
    XtSetArg(args[arg], XmNeditMode, XmMULTI_LINE_EDIT); arg++;
    info->editor = XmCreateScrolledText(form, XMST("text"), args, arg);
    XtUnmanageChild(XtParent(info->editor));
    XtManageChild(info->editor);

    info->title = panel_menu[0].label;
    MMaddCallbacks(panel_menu, XtPointer(info));

    update_properties_panel(info);
    InstallButtonTips(panel);

    MMadjustPanel(panel_menu);

    XtAddCallback(info->dialog, XmNokCallback,
              ApplyBreakpointPropertiesCB, XtPointer(info));
    XtAddCallback(info->dialog, XmNokCallback,
              UnmanageThisCB, XtPointer(info->dialog));

    XtAddCallback(info->dialog, XmNapplyCallback,
              ApplyBreakpointPropertiesCB, XtPointer(info));

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

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

    XtAddCallback(info->dialog, XmNunmapCallback,
              DestroyThisCB, XtParent(info->dialog));
    XtAddCallback(info->dialog, XmNdestroyCallback,
              DeleteInfoCB,  XtPointer(info));

    tie_combo_box_to_history(info->condition, cond_filter);

    manage_and_raise(info->dialog);
    info->spin_locked = false;
}

// Set breakpoint condition
void SourceView::SetBreakpointConditionCB(Widget w,
                                XtPointer client_data, 
                                XtPointer call_data)
{
    XmAnyCallbackStruct *cbs = (XmAnyCallbackStruct *)call_data;
    switch (cbs->reason)
    {
    case XmCR_OK:       // OK button
    case XmCR_APPLY:          // Apply button

    case XmCR_SINGLE_SELECT:  // Selection from ComboBox
    case XmCR_MULTIPLE_SELECT:
    case XmCR_EXTENDED_SELECT:
    case XmCR_BROWSE_SELECT:
      break;

    case XmCR_ACTIVATE:       // Return Key
      return;                 // Already handled by Apply button

    default:
      return;                 // Value changed
    }

    BreakpointPropertiesInfo *info = 
      (BreakpointPropertiesInfo *)client_data;

    String cond = XmTextFieldGetString(info->condition);
    set_bps_cond(info->nrs, cond, w);
    XtFree(cond);
}

// Apply all property changes
void SourceView::ApplyBreakpointPropertiesCB(Widget w,
                                  XtPointer client_data, 
                                  XtPointer call_data)
{ 
    BreakpointPropertiesInfo *info = 
      (BreakpointPropertiesInfo *)client_data;

    // End recording
    if (gdb->recording())
      EndBreakpointCommandsCB(w, client_data, call_data);

    // Apply condition
    String cond = XmTextFieldGetString(info->condition);
    set_bps_cond(info->nrs, cond, w);
    XtFree(cond);

    if (XtIsManaged(XtParent(info->editor)))
    {
      // Apply commands
      EditBreakpointCommandsCB(w, client_data, call_data);
    }
}

// Set breakpoint ignore count
void SourceView::SetBreakpointIgnoreCountCB(Widget w,
                                  XtPointer client_data, 
                                  XtPointer call_data)
{
    XmAnyCallbackStruct *cbs = (XmAnyCallbackStruct *)call_data;
    BreakpointPropertiesInfo *info = 
      (BreakpointPropertiesInfo *)client_data;

    if (info->spin_locked)
      return;                 // Ignore the SetValue change

    int delay = 500;          // Wait until the SpinBox stops spinning
    if (cbs->reason == XmCR_ACTIVATE)
      delay = 0;

    if (info->timer != 0)
    {
      XtRemoveTimeOut(info->timer);
      info->timer = 0;
    }

    info->timer = XtAppAddTimeOut(XtWidgetToApplicationContext(w),
                          delay,
                          SetBreakpointIgnoreCountNowCB, 
                          client_data);
}

void SourceView::SetBreakpointIgnoreCountNowCB(XtPointer client_data, 
                                     XtIntervalId *id)
{
    CommandGroup cg;

    BreakpointPropertiesInfo *info = 
      (BreakpointPropertiesInfo *)client_data;

    assert (info->timer == *id);
    (void) id;                // Use it
    info->timer = 0;

    String _count = XmTextFieldGetString(info->ignore);
    int count = atoi(_count);
    XtFree(_count);

    for (int i = 0; i < info->nrs.size(); i++)
    {
      gdb_command(gdb->ignore_command(itostring(info->nrs[i]), count));
      info->ignore_spin_update++;
    }
}


// Make breakpoint temporary
void SourceView::MakeBreakpointsTempCB(Widget, XtPointer client_data, 
                               XtPointer)
{
    BreakpointPropertiesInfo *info = 
      (BreakpointPropertiesInfo *)client_data;

    gdb_command("enable delete " + numbers(info->nrs));
}


// Delete Breakpoint
void SourceView::DeleteBreakpointsCB(Widget w, XtPointer client_data, 
                             XtPointer)
{
    BreakpointPropertiesInfo *info = 
      (BreakpointPropertiesInfo *)client_data;

    delete_bps(info->nrs, w);
}

// Enable Breakpoints
void SourceView::EnableBreakpointsCB(Widget w, XtPointer client_data,
                             XtPointer)
{
    BreakpointPropertiesInfo *info = 
      (BreakpointPropertiesInfo *)client_data;

    enable_bps(info->nrs, w);
}

// Disable Breakpoints
void SourceView::DisableBreakpointsCB(Widget, XtPointer client_data, XtPointer)
{
    BreakpointPropertiesInfo *info = 
      (BreakpointPropertiesInfo *)client_data;

    disable_bps(info->nrs);
}

// Record breakpoint commands
void SourceView::RecordBreakpointCommandsCB(Widget w,
                                  XtPointer client_data, 
                                  XtPointer)
{
    BreakpointPropertiesInfo *info = 
      (BreakpointPropertiesInfo *)client_data;

    gdb->removeHandler(Recording, RecordingHP, (void *)info);
    gdb->addHandler(Recording, RecordingHP, (void *)info);
    gdb_command("commands " + itostring(info->nrs[0]), w);
}

// End recording breakpoint commands
void SourceView::EndBreakpointCommandsCB(Widget w, XtPointer, XtPointer)
{
    gdb_command("end", w);
}

void SourceView::RefreshBreakpointsHP(Agent *, void *, void *call_data)
{
    bool gdb_ready = bool(call_data);
    if (gdb_ready && !gdb->recording())
    {
      // Don't get called recursively
      gdb->removeHandler(ReadyForQuestion, RefreshBreakpointsHP);

      string breakpoints = gdb_question("info breakpoints");
      if (breakpoints == NO_GDB_ANSWER)
      {
          // Try again next time
          gdb->addHandler(ReadyForQuestion, RefreshBreakpointsHP);
      }
      else
      {
          process_info_bp(breakpoints);
      }
    }
}

// Log recording state
void SourceView::RecordingHP(Agent *, void *client_data, void *call_data)
{
    BreakpointPropertiesInfo *info = 
      (BreakpointPropertiesInfo *)client_data;
    bool recording = bool(call_data);

    // Refresh buttons
    if (info == 0)
      update_properties_panels();
    else
      update_properties_panel(info);

    if (!recording)
    {
      // Recording is over.  Don't get called again.
      gdb->removeHandler(Recording, RecordingHP, (void *)info);

      // Update breakpoints
      gdb->addHandler(ReadyForQuestion, RefreshBreakpointsHP);

      if (info != 0)
      {
          // Upon next panel update, propagate command to other breakpoints
          info->sync_commands = true;
      }
    }
}

// Set breakpoint commands
void SourceView::set_bp_commands(IntArray& nrs, const StringArray& commands,
                         Widget origin)
{
    CommandGroup cg;

    for (int i = 0; i < nrs.size(); i++)
    {
      // Check for breakpoint
      MapRef ref;
      BreakPoint *bp = 0;
      for (bp = bp_map.first(ref); bp != 0; bp = bp_map.next(ref))
      {
          if (bp->number() == nrs[i])
            break;
      }
      if (bp == 0)
          continue;           // No such breakpoint

      if (commands.size() == bp->commands().size())
      {
          bool same_commands = true;
          for (int j = 0; same_commands && j < bp->commands().size(); j++)
          {
            string c1 = bp->commands()[j];
            strip_auto_command_prefix(c1);
            string c2 = commands[j];
            strip_auto_command_prefix(c2);

            if (c1 != c2)
                same_commands = false;
          }

          if (same_commands)
            continue;   // Commands unchanged
      }

      string action = "";
      if (gdb->type() != GDB)
      {
          // Get action for non-GDB types - a semicolon-separated
          // list of commands
          for (int j = 0; j < commands.size(); j++)
          {
            if (j > 0 && 
                !action.contains(";", -1) && !action.contains("; ", -1))
            {
                action += "; ";
            }

            string command = commands[j];

            if (is_graph_cmd(command) || is_running_cmd(command) ||
                gdb->type() == PERL && command.contains(' ', 1))
            {
                // If:
                // - this is a DDD command, or
                // - this is a running command, or
                // - this is a Perl debugger command,
                // make this a DDD auto-command.
                add_auto_command_prefix(command);
            }

            action += command;
          }
      }

      switch (gdb->type())
      {
      case GDB:
      {
          gdb_command("commands " + itostring(nrs[i]), origin);
          for (int j = 0; j < commands.size(); j++)
            gdb_command(commands[j], origin);
          gdb_command("end", origin);
          break;
      }

      case DBX:
      {
          // Use `when' to set breakpoint commands.
          if (gdb->has_when_semicolon())
            action += "; ";

          string cmd;
          if (!bp->func().empty())
            cmd = "when in " + bp->func();
          else
          {
            gdb_command("file " + bp->file_name());
            cmd = "when at " + itostring(bp->line_nr());
          }

          cmd += " { " + action + " }";
          gdb_command(cmd, origin);
          break;
      }

      case XDB:
      {
          // Replace breakpoint by new one with command.
          const string cmd = "b " + 
            bp->file_name() + ":" + itostring(bp->line_nr()) + 
            " {" + action + "}";
          gdb_command(cmd, origin);
          delete_bp(bp->number(), origin);
          break;
      }

      case PERL:
      {
          // Just set an action.
          gdb_command("f " + bp->file_name(), origin);
          const string cmd = "a " + itostring(bp->line_nr()) + " " + action;
          gdb_command(cmd, origin);
          break;
      }

      default:
          assert(!gdb->has_breakpoint_commands());
      }
    }
}


// Edit breakpoint commands
void SourceView::EditBreakpointCommandsCB(Widget w,
                                XtPointer client_data, 
                                XtPointer)
{
    BreakpointPropertiesInfo *info = 
      (BreakpointPropertiesInfo *)client_data;

    if (XtIsManaged(XtParent(info->editor)))
    {
      XtUnmanageChild(XtParent(info->editor));
      MString label = "Edit " + MString(">>", CHARSET_SMALL);
      set_label(info->edit, label);

      String _commands = XmTextGetString(info->editor);
      string cmd = _commands;
      XtFree(_commands);

      if (!cmd.contains('\n', -1))
          cmd += '\n';
      StringArray commands;

      while (!cmd.empty())
      {
          string c = cmd.before('\n');
          if (!c.empty())
            commands += c;
          cmd = cmd.after('\n');
      }

      set_bp_commands(info->nrs, commands, w);

      // Update all panels in the next run
      gdb->addHandler(Recording, RecordingHP, (void *)0);
    }
    else
    {
      XtManageChild(XtParent(info->editor));
      MString label = "Edit " + MString("<<", CHARSET_SMALL);
      set_label(info->edit, label);
    }
}

void SourceView::edit_breakpoint_properties(int bp_nr)
{
    static int n;
    n = bp_nr;
    EditBreakpointPropertiesCB(source_text_w, XtPointer(&n), 0);
}





//-----------------------------------------------------------------------------
// Breakpoint commands
//----------------------------------------------------------------------------

void SourceView::BreakpointCmdCB(Widget,
                         XtPointer client_data,
                         XtPointer)
{
    if (breakpoint_list_w == 0)
      return;

    IntArray nrs;
    getBreakpointNumbers(nrs);

    if (nrs.size() == 0)
        return;

    const string cmd = (String)client_data;

    if (cmd == "delete")
        delete_bps(nrs);
    else if (cmd == "enable")
      enable_bps(nrs);
    else if (cmd == "disable")
      disable_bps(nrs);
}

void SourceView::LookupBreakpointCB(Widget, XtPointer client_data, XtPointer)
{
    if (breakpoint_list_w == 0)
      return;

    IntArray breakpoint_nrs;

    if (client_data == 0)
    {
      getBreakpointNumbers(breakpoint_nrs);
    }
    else
    {
      BreakpointPropertiesInfo *info = 
          (BreakpointPropertiesInfo *)client_data;
      breakpoint_nrs = info->nrs;
    }
    if (breakpoint_nrs.size() == 0)
      return;

    BreakPoint *bp = bp_map.get(breakpoint_nrs[0]);
    if (bp == 0)
      return;

    switch (bp->type())
    {
    case BREAKPOINT:
    case TRACEPOINT:
    case ACTIONPOINT:
      lookup("#" + itostring(breakpoint_nrs[0]));
      break;

    case WATCHPOINT:
      lookup(bp->expr());
      break;
    }
}

void SourceView::PrintWatchpointCB(Widget w, XtPointer client_data, XtPointer)
{
    if (breakpoint_list_w == 0)
      return;

    IntArray breakpoint_nrs;

    if (client_data == 0)
    {
      getBreakpointNumbers(breakpoint_nrs);
    }
    else
    {
      BreakpointPropertiesInfo *info = 
          (BreakpointPropertiesInfo *)client_data;
      breakpoint_nrs = info->nrs;
    }
    if (breakpoint_nrs.size() < 1)
      return;

    BreakPoint *bp = bp_map.get(breakpoint_nrs[0]);
    if (bp == 0)
      return;

    switch (bp->type())
    {
    case BREAKPOINT:
    case TRACEPOINT:
    case ACTIONPOINT:
      // How should we print a breakpoint?  (FIXME)
      break;

    case WATCHPOINT:
      gdb_command(gdb->print_command(bp->expr(), false), w);
      break;
    }
}


// Return breakpoint of BP_INFO; 0 if new; -1 if none
int SourceView::breakpoint_number(const string& bp_info, string& file)
{ 
    int line = 0;


    switch (gdb->type())
    {
    case JDB:
    {
        int colon = bp_info.index(':');
      if (colon < 0)
          return -1;          // No breakpoint

      file = bp_info.before(colon);
      line = get_positive_nr(bp_info.after(colon));
      break;
    }
    case PERL:
    {
      string info_output = bp_info;

      // Check for `FILE:' at the beginning
      if (!info_output.contains(' ', 0))
      {
          string first_line;
          if (info_output.contains('\n'))
            first_line = info_output.before('\n');
          else
            first_line = info_output;

          if (first_line.contains(':', -1))
          {
            // Get leading file name
            file = first_line.before(':');
            info_output = info_output.after('\n');
          }
      }

      line = get_positive_nr(info_output);
      break;
    }

    default:
      return -1;              // Never reached
    }

    if (line <= 0)
      return -1;        // No breakpoint

    // Strip JDB 1.2 info like `breakpoint', etc.
    strip_space(file);
    int last_space = file.index(" ", -1);
    if (last_space > 0)
      file = file.after(last_space);

    MapRef ref;
    for (BreakPoint* bp = bp_map.first(ref); bp != 0; bp = bp_map.next(ref))
      if (bp_matches(bp, file, line))
          return bp->number(); // Existing breakpoint

    return 0;                  // New breakpoint
}


// Handle breakpoint info
void SourceView::process_breakpoints(string& info_breakpoints_output)
{
    if (breakpoint_list_w == 0)
      return;

    strip_space(info_breakpoints_output);
    if (info_breakpoints_output.empty())
    {
      if (gdb->has_watch_command())
          info_breakpoints_output = "No breakpoints or watchpoints.";
      else
          info_breakpoints_output = "No breakpoints.";
    }

    int count = info_breakpoints_output.freq('\n') + 1;

    string *breakpoint_list = new string[count];
    bool *selected          = new bool[count];

    split(info_breakpoints_output, breakpoint_list, count, '\n');

    while (count > 0 && breakpoint_list[count - 1].empty())
      count--;

    bool select = false;
    string file = current_source_name();

    for (int i = 0; i < count; i++)
    {
      string& bp_info = breakpoint_list[i];
      if (!gdb->has_numbered_breakpoints())
      {
          // JDB and Perl have no breakpoint numbers -- insert our own
          int bp_nr = breakpoint_number(bp_info, file);
          if (bp_nr > 0)
          {
            string s = itostring(bp_nr) + "    ";
            bp_info.prepend(s.at(0, 4));
          }
      }

      // Select number
      int bp_number = get_positive_nr(bp_info);
      if (bp_number > 0)
      {
          MapRef ref;
          for (BreakPoint* bp = bp_map.first(ref);
             bp != 0;
             bp = bp_map.next(ref))
          {
            if (bp->number() == bp_number)
            {
                select = bp->selected();
                break;
            }
          }
      }

      selected[i] = select;
      strip_auto_command_prefix(bp_info);
      setup_where_line(bp_info);
    }

    setLabelList(breakpoint_list_w, breakpoint_list, selected, count,
             (gdb->type() == GDB || 
              gdb->type() == DBG || 
              gdb->type() == PYDB) && count > 1, false);

    UpdateBreakpointButtonsCB(breakpoint_list_w, XtPointer(0), XtPointer(0));

    delete[] breakpoint_list;
    delete[] selected;
}

void SourceView::UpdateBreakpointButtonsCB(Widget, XtPointer, 
                                 XtPointer call_data)
{
    (void) call_data;         // Use it

    if (edit_breakpoints_dialog_w == 0)
      return;

    IntArray breakpoint_nrs;
    getBreakpointNumbers(breakpoint_nrs);

    // Update selection
    MapRef ref;
    BreakPoint *bp;
    for (bp = bp_map.first(ref); bp != 0; bp = bp_map.next(ref))
      bp->selected() = false;

    for (int i = 0; i < breakpoint_nrs.size(); i++)
    {
      int bp_number = breakpoint_nrs[i];
      for (bp = bp_map.first(ref); bp != 0; bp = bp_map.next(ref))
      {
          if (bp->number() == bp_number)
          {
            bp->selected() = true;
            break;
          }
      }
    }

#if 0
    if (call_data != 0)
    {
      // Update status line
      if (breakpoint_nrs.size() == 1)
          set_status_mstring(help_on_bp(breakpoint_nrs[0], true));
      else
          set_status("");
    }
#endif

    // Count selected ones
    BreakPoint *selected_bp = 0;
    int selected          = 0;
    int selected_enabled  = 0;
    int selected_disabled = 0;
    for (bp = bp_map.first(ref); bp != 0; bp = bp_map.next(ref))
    {
      if (bp->selected())
      {
          selected_bp = bp;
          selected++;

          if (bp->enabled())
            selected_enabled++;
          else
            selected_disabled++;
      }
    }

    // Update buttons
    set_sensitive(bp_area[BPButtons::NewWP].widget, gdb->has_watch_command());
    set_sensitive(bp_area[BPButtons::Lookup].widget, selected == 1);
    set_sensitive(bp_area[BPButtons::Print].widget, 
               selected == 1 && selected_bp->type() == WATCHPOINT);
    set_sensitive(bp_area[BPButtons::Enable].widget,
               gdb->can_enable() && selected_disabled > 0);
    set_sensitive(bp_area[BPButtons::Disable].widget,
               gdb->can_disable() && selected_enabled > 0);
    set_sensitive(bp_area[BPButtons::Properties].widget, selected > 0);
    set_sensitive(bp_area[BPButtons::Delete].widget, selected > 0);
}

void SourceView::EditBreakpointsCB(Widget, XtPointer, XtPointer)
{
    manage_and_raise(edit_breakpoints_dialog_w);
}



//-----------------------------------------------------------------------------
// Stack frame selection
//----------------------------------------------------------------------------

void SourceView::StackDialogPoppedDownCB (Widget, XtPointer, XtPointer)
{
    stack_dialog_popped_up = false;
    refresh_buttons();
}

void SourceView::SelectFrameCB (Widget w, XtPointer, XtPointer call_data)
{
    XmListCallbackStruct *cbs = (XmListCallbackStruct *)call_data;

    int count = 0;
    XtVaGetValues(w,
              XmNitemCount, &count,
              XtPointer(0));

    set_sensitive(up_w,   cbs->item_position > 1);
    set_sensitive(down_w, cbs->item_position < count);
    refresh_buttons();

    switch (gdb->type())
    {
    case BASH:
    case GDB:
    case DBG:
      // GDB frame output is caught by our routines.
      gdb_command(gdb->frame_command(count - cbs->item_position));
      break;
    
    case XDB:
      // XDB frame output is caught by our routines.
      gdb_command(gdb->frame_command(cbs->item_position - 1));
      break;

    case DBX:
    case JDB:
    case PYDB:
    case PERL:
      if (gdb->has_frame_command())
      {
          // Issue `frame' command
          gdb_command(gdb->frame_command(count - cbs->item_position + 1));
      }
      else
      {
          // JDB, PYDB and some DBXes lack a `frame' command.
          // Use `up N'/`down N' instead.
          int offset = cbs->item_position - last_frame_pos;
          if (offset != 0)
            gdb_command(gdb->relative_frame_command(-offset));

          // Call `set_frame_pos' now.
          frame_pos_locked = false;
          set_frame_pos(0, cbs->item_position);

          // Ignore the `up'/`down' reply.
          frame_pos_locked = (offset != 0);
      }
      break;
    }
}

void SourceView::refresh_stack_frames()
{
    // Allow unlimited time to find out where we are
    string where_s = gdb_question(gdb->where_command(), -1, true);
    if (where_s == NO_GDB_ANSWER)
      where_s = "No stack.";
    process_where(where_s);

    if (gdb->has_frame_command())
    {
      string frame = gdb_question(gdb->frame_command());
      process_frame(frame);
    }
}

void SourceView::ViewStackFramesCB(Widget, XtPointer, XtPointer)
{
    refresh_stack_frames();
    manage_and_raise(stack_dialog_w);
    stack_dialog_popped_up = true;
    refresh_buttons();
}

// Remove file paths and argument lists from `where' output
void SourceView::setup_where_line(string& line)
{
    if (gdb->type() != JDB)
    {
      // Remove file paths (otherwise line can be too long for DBX)
      //   ... n.b. with templates, line can still be rather long
#if RUNTIME_REGEX
      static regex rxfilepath("[^\"\'` /]*/");
#endif
      line.gsub(rxfilepath, "");
    }

    if (gdb->type() != JDB)
    {
      // Shorten argument lists `(a = 1, b = 2, ...)' to `()'
#if RUNTIME_REGEX
      static regex rxarglist("[(][^0-9][^)]*[)]");
#endif
      int start = index(line, rxarglist, "(");
      if (start > 0)
      {
          int end = line.index(')', -1);
          if (end > start)
            line = line.through(start) + line.from(end);
      }
    }

    const int min_width = 40;
    if (int(line.length()) < min_width)
      line += replicate(' ', min_width - line.length());
}

// Return current JDB frame; 0 if none
inline int jdb_frame()
{
    return get_positive_nr(gdb->prompt().from("["));
}

// Return current JDB thread; "" if none
inline string jdb_thread()
{
    return gdb->prompt().before("[");
}

// Process `where' output
void SourceView::process_where(const string& where_output)
{
    if (!where_output.contains("No ", 0))
      undo_buffer.add_where(where_output);

    int count          = where_output.freq('\n') + 1;
    string *frame_list = new string[count];
    bool *selected     = new bool[count];

    split(where_output, frame_list, count, '\n');

    StringArray frames;
    int i;
    for (i = 0; i < count; i++)
    {
      const string& frame = frame_list[i];
      if (frame.empty())
          continue;           // Skip empty lines
      if (frame.contains("Reading ", 0))
          continue;           // Skip GDB `Reading in symbols' messages

      frames += frame;
    }
    delete[] frame_list;
    frame_list = frames.values();
    count = frames.size();

    if (gdb->type() != XDB)
    {
      // Invert list such that `Up' and `Down' make sense
      for (i = 0; i < count / 2; i++)
      {
          string tmp = frame_list[i];
          frame_list[i] = frame_list[count - i - 1];
          frame_list[count - i - 1] = tmp;
      }
    }

    // Make sure we have a minimum width
    for (i = 0; i < count; i++)
    {
      selected[i] = false;
      setup_where_line(frame_list[i]);
    }

    // JDB does not report frames above the current one.  Hence, we
    // only update the reported frames and others unchanged.
    if (gdb->type() == JDB && jdb_frame() != 1)
      updateLabelList(frame_list_w, frame_list, count);
    else
      setLabelList(frame_list_w, frame_list, selected, count, false, false);
    set_frame_pos(0, 0);

    delete[] selected;
}

// Give a hint whether we're showing earlier state
void SourceView::showing_earlier_state(bool set)
{
#if 0
    set_sensitive(stack_dialog_w, !set);
    set_sensitive(thread_dialog_w, !set);
    set_sensitive(register_dialog_w, !set);
#endif

    static bool up_state;
    static bool down_state;

    if (set)
    {
      up_state   = XtIsSensitive(up_w);
      down_state = XtIsSensitive(down_w);

      set_sensitive(up_w, False);
      set_sensitive(down_w, False);
    }
    else
    {
      set_sensitive(up_w, up_state);
      set_sensitive(down_w, down_state);
    }
    set_sensitive(all_registers_w, !set);
    set_sensitive(int_registers_w, !set);

    refresh_buttons();
    update_glyphs();
}

// Process `frame' (or `up'/`down') output
void SourceView::process_frame(string& frame_output)
{
    if (!frame_output.empty() 
      && (frame_output[0] == '#' || gdb->type() != GDB))
    {
      string frame_nr;

      switch (gdb->type())
      {
      case BASH:
      case DBG:
      case GDB:
      case PYDB:
          frame_nr = frame_output.after(0);
          break;

      case DBX:
          frame_nr = frame_output;

          // Sun DBX 4.0 issues `=>' before current frame
          if (frame_nr.contains("=>", 0))
            frame_nr = frame_nr.after("=>");
          break;

      case XDB:
          frame_nr = frame_output.after(" = ", -1);
          break;

      case JDB:
          frame_nr = frame_output.after("[");
          break;

      case PERL:
      {
          // FIXME
          break;
      }
      }

      int frame = get_positive_nr(frame_nr);

      // In GDB, the lowest frame is #0, in DBX and JDB, it is #1
      if (gdb->type() == DBX || gdb->type() == JDB)
          frame--;

      process_frame(frame);
    }
    else
    {
      process_frame(-1);      // No frame
    }
}

// Same, but accept a frame number
void SourceView::process_frame(int frame)
{
    if (frame >= 0)
    {
      at_lowest_frame = (frame == 0);

      if (current_frame < 0)
      {
          // We have not seen a `frame' output yet - assume we're at
          // the lowest frame.
          current_frame = 0;
      }

      // Save undoing command
      string c;
      if (gdb->has_frame_command())
          c = gdb->frame_command(current_frame);
      else
          c = gdb->relative_frame_command(current_frame - frame);
      undo_buffer.add_command(c, true);

      // Save state
      undo_buffer.add_frame(itostring(frame));

      int count         = 0;
      int top_item      = 0;
      int visible_items = 0;
      XtVaGetValues(frame_list_w,
                  XmNitemCount, &count,
                  XmNtopItemPosition, &top_item,
                  XmNvisibleItemCount, &visible_items,
                  XtPointer(0));

      int pos = 1;
      switch (gdb->type())
      {
      case GDB:
      case DBX:
      case JDB:
      case PYDB:
      case BASH:
      case PERL:
      case DBG:
          pos = count - frame;
          break;

      case XDB:
          pos = frame + 1;
          break;
      }

      ListSetAndSelectPos(frame_list_w, pos);

      set_sensitive(up_w,   pos > 1);
      set_sensitive(down_w, pos < count);
      refresh_buttons();

      update_glyphs();
      current_frame = frame;
    }
    else
    {
      set_sensitive(up_w,   False);
      set_sensitive(down_w, False);
      refresh_buttons();
      current_frame = -1;
    }
}

// Set frame manually to function FUNC; return TRUE if successful
bool SourceView::set_frame_func(const string& func)
{
    int count = 0;
    XmStringTable items;

    XtVaGetValues(frame_list_w,
              XmNitemCount, &count,
              XmNitems, &items,
              XtPointer(0));

    for (int i = count - 1; i >= 0; i--)
    {
      String _item;
      XmStringGetLtoR(items[i], LIST_CHARSET, &_item);
      string item(_item);
      XtFree(_item);

      int func_index  = item.index(func);
      int paren_index = item.index('(');

      if (func_index >= 0 &&
          (func_index < paren_index || paren_index < 0))
      {
          set_frame_pos(0, i + 1);
          return true;
      }
    }

    return false;
}

// Set frame manually: ARG = 0: POS, ARG = +/- N: down/up N levels
void SourceView::set_frame_pos(int arg, int pos)
{
    if (frame_pos_locked)
    {
      frame_pos_locked = false;
      return;
    }

    int items = 0;
    XtVaGetValues(frame_list_w, XmNitemCount, &items, XtPointer(0));

    if (pos == 0)
      pos = items;
    if (arg != 0)
    {
      int *position_list;
      int position_count;
      if (XmListGetSelectedPos(frame_list_w,
                         &position_list, &position_count))
      {
          if (position_count == 1)
            pos = position_list[0] + arg;
          XtFree((char *)position_list);
      } else
          return;
      if (position_count != 1 || pos < 1 || pos > items)
          return;
    }

    ListSetAndSelectPos(frame_list_w, pos);

    last_frame_pos = pos;

    set_sensitive(up_w,   pos > 1);
    set_sensitive(down_w, pos < items);
    refresh_buttons();
}

bool SourceView::where_required()    { return stack_dialog_popped_up; }
bool SourceView::register_required() { return register_dialog_popped_up; }
bool SourceView::thread_required()   { return thread_dialog_popped_up; }

bool SourceView::can_go_up()
{
    return !gdb->relative_frame_command(1).empty() && 
      (!where_required() || XtIsSensitive(up_w));
}

bool SourceView::can_go_down()
{
    return !gdb->relative_frame_command(-1).empty() && 
      (!where_required() || XtIsSensitive(down_w));
}


//-----------------------------------------------------------------------------
// Register stuff
//----------------------------------------------------------------------------

void SourceView::process_registers(string& register_output)
{
    register_output.gsub(";\n", "\n");
    register_output.gsub("; ", "\n");
    register_output.gsub(";", "\n");
    register_output.gsub("\n\n", "\n");

    if (!register_output.contains("No ", 0))
      undo_buffer.add_registers(register_output);

    int count             = register_output.freq('\n') + 1;
    string *register_list = new string[count];
    bool *selected        = new bool[count];

    split(register_output, register_list, count, '\n');

    while (count > 0 && register_list[count - 1].empty())
      count--;

    for (int i = 0; i < count; i++)
    {
      tabto(register_list[i], 26);
      untabify(register_list[i]);
      selected[i] = false;
    }

    setLabelList(register_list_w, register_list, selected, count,
             false, false);

    delete[] register_list;
    delete[] selected;
}

void SourceView::refresh_registers()
{
    string registers = gdb_question(refresh_registers_command());
    if (registers == NO_GDB_ANSWER)
      registers = "No registers.";
    process_registers(registers);
}

string SourceView::refresh_registers_command()
{
    return gdb->regs_command(all_registers);
}

void SourceView::ViewRegistersCB(Widget, XtPointer, XtPointer)
{
    refresh_registers();
    manage_and_raise(register_dialog_w);
    register_dialog_popped_up = true;
}

void SourceView::RegisterDialogPoppedDownCB (Widget, XtPointer, XtPointer)
{
    register_dialog_popped_up = false;
}

void SourceView::SelectRegisterCB (Widget, XtPointer, XtPointer call_data)
{
    XmListCallbackStruct *cbs = (XmListCallbackStruct *)call_data;

    // Get the selected line
    String _item;
    XmStringGetLtoR(cbs->item, LIST_CHARSET, &_item);
    string item(_item);
    XtFree(_item);

    if (!item.empty() && item[item.length() - 1] != '.')
    {
      string regname = item.through(rxidentifier);
      strip_space(regname);
      item = "/x $" + regname;
      source_arg->set_string(item);
    }
}

//-----------------------------------------------------------------------------
// Thread stuff
//----------------------------------------------------------------------------

string SourceView::current_threadgroup = "system";

void SourceView::process_threads(string& threads_output)
{
    bool valid_threads_output = true;

    if (threads_output == NO_GDB_ANSWER 
      || threads_output.empty()
      || threads_output.contains("No ", 0)
      || threads_output.matches(rxwhite))
    {
      valid_threads_output = false;
      threads_output = "No threads.\n";
    }

    if (valid_threads_output)
      undo_buffer.add_threads(threads_output);

    int count           = threads_output.freq('\n') + 1;
    string *thread_list = new string[count];
    bool *selected      = new bool[count];

    split(threads_output, thread_list, count, '\n');

    while (count > 0 && thread_list[count - 1].empty())
      count--;

    switch (gdb->type())
    {
    case GDB:
    {
      for (int i = 0; i < count; i++)
      {
          selected[i] = thread_list[i].contains('*', 0);
          if (selected[i])
            thread_list[i] = thread_list[i].after(0);
          strip_leading_space(thread_list[i]);
          setup_where_line(thread_list[i]);
      }
      break;
    }

    case JDB:
    {
      string current_thread = jdb_thread();
      current_threadgroup = "";
      for (int i = 0; i < count; i++)
      {
          selected[i] = false;
          string& item = thread_list[i];

          if (item.contains("Group ", 0))
          {
            // Format is: `Group THREADGROUP:'

            if (!current_threadgroup.empty())
            {
                // Multiple threadgroups are shown
                current_threadgroup = "system";
            }
            else
            {
                current_threadgroup = item.after(" ");
                strip_leading_space(current_threadgroup);
                current_threadgroup = current_threadgroup.before(":");
            }
          }
          else
          {
            // Format is: ` NUMBER. (CLASS)ADDRESS NAME STATE'

            int addr_index = item.index("(");
            if (addr_index < 0)
                addr_index = item.index("0x");

            if (addr_index >= 0)
            {
                // If is the current thread, select it
                string thread = item.after("(");
                thread = thread.after(" ");
                strip_leading_space(thread);

                if (thread.contains(current_thread + " ", 0))
                  selected[i] = true;

                // Leave only ` NUMBER. NAME STATE'
                int info_index = addr_index;
                while (info_index < int(item.length()) &&
                     item[info_index] != ' ')
                  info_index++;
                while (info_index < int(item.length()) &&
                     item[info_index] == ' ')
                  info_index++;
                item = item.before(addr_index) + item.from(info_index);

                // Give more verbose output on system threads
                if (item.contains("runni", -1))
                  item += "ng";
                else if (item.contains("suspe", -1))
                  item += "nded";
                else if (item.contains("cond.", -1))
                  item += " waiting";
            }
          }
      }
      break;
    }

    case BASH:
    case DBG:
    case DBX:
    case XDB:
    case PERL:
    case PYDB:
    {
        if (gdb->type() == DBX && gdb->isSunDBX())
        {
          for (int i = 0; i < count; i++)
            selected[i] = thread_list[i].contains('>', 1);
        } else
      {
          for (int i = 0; i < count; i++)
            selected[i] = false;
      }
      break;
    }
    }

    setLabelList(thread_list_w, thread_list, selected, count, false, false);

    delete[] thread_list;
    delete[] selected;
}

void SourceView::refresh_threads(bool all_threadgroups)
{
    switch (gdb->type())
    {
    case GDB:
    {
      string threads = gdb_question("info threads");
      process_threads(threads);
      break;
    }
    case JDB:
    {
      if (all_threadgroups)
      {
          // In JDB, `threadgroup system' seems to make `threads' list
          // the threads of *all* threadgroups, not only system threads.
          // This command will also automatically trigger an update.
          gdb_command("threadgroup system");
          syncCommandQueue();
      }

      string threads = gdb_question("threads");
      process_threads(threads);
      break;
    }

    case DBX:
    {
        if (gdb->isSunDBX())
        {
          string threads = gdb_question("threads");
          process_threads(threads);
        }
        break;
    }
    case BASH:
    case DBG:
    case XDB:
    case PERL:
    case PYDB:
      // No threads.
      break;
    }
}

void SourceView::ViewThreadsCB(Widget, XtPointer, XtPointer)
{
    refresh_threads(true);
    manage_and_raise(thread_dialog_w);
    thread_dialog_popped_up = true;
}

void SourceView::ThreadDialogPoppedDownCB(Widget, XtPointer, XtPointer)
{
    thread_dialog_popped_up = false;
}

void SourceView::ThreadCommandCB(Widget w, XtPointer client_data, XtPointer)
{
    string command = (char *)client_data;

    // Get the selected threads
    IntArray threads;
    getItemNumbers(thread_list_w, threads);

    for (int i = 0; i < threads.size(); i++)
      command += " " + itostring(threads[i]);

    gdb_command(command, w);
}

void SourceView::SelectThreadCB(Widget w, XtPointer, XtPointer)
{
    // Get the selected threads
    IntArray threads;
    getItemNumbers(thread_list_w, threads);

    if (threads.size() == 1)
    {
      // Make single thread the default thread.
      gdb_command("thread " + itostring(threads[0]), w);
    }
    else if (threads.size() == 0 &&
           ( gdb->type() == JDB || (gdb->type() == DBX && gdb->isSunDBX()) )
           )
    {
      // Check if we have selected a threadgroup
      XmStringTable selected_items;
      int selected_items_count = 0;

      XtVaGetValues(thread_list_w,
                  XmNselectedItemCount, &selected_items_count,
                  XmNselectedItems, &selected_items,
                  XtPointer(0));

      if (selected_items_count == 1)
      {
          String _item;
          XmStringGetLtoR(selected_items[0], LIST_CHARSET, &_item);
          string item(_item);
          XtFree(_item);

          // Output has the form `Group jtest.main:'
          if (gdb->type() == JDB)
          {
              if (item.contains("Group ", 0))
              {
              string threadgroup = item.after(" ");
              strip_leading_space(threadgroup);
              threadgroup = threadgroup.before(":");

              if (threadgroup == current_threadgroup)
                threadgroup = "system"; // show all threadgroups

              gdb_command("threadgroup " + threadgroup, w);
              }
          } else
            {
            string thread = item.after("t@");
            thread = thread.before(" ");
            gdb_command("thread t@" + thread, w);
          }
      }
    }
}

//-----------------------------------------------------------------------------
// Get Line in GDB format
//----------------------------------------------------------------------------

string SourceView::get_line(string position)
{
    string file_name = current_file_name;

    if (position.contains(':'))
    {
      file_name = position.before(':');
      position  = position.after(':');
    }
    int line = get_positive_nr(position);

    // Sanity check: make sure the line # isn't too big
    line = min(line, line_count);
    if (line < 1)
      return "";

    if (!is_current_file(file_name))
      read_file(file_name, line);
    if (!is_current_file(file_name))
      return "";

    XmTextPosition start = pos_of_line(line) + indent_amount(source_text_w);
    XmTextPosition end   = current_source.index('\n', start);
    if (end < 0)
      end = current_source.length();

    const string text = current_source.at(int(start), end - start);
    return itostring(line) + "\t" + text;
}


//----------------------------------------------------------------------------
// Glyph stuff
//----------------------------------------------------------------------------


// Whether to cache glyph images
bool SourceView::cache_glyph_images = true;

static
void DestroyOldWidgets(WidgetArray& Array){
  const int size = Array.size();
  for (int i = 0; i < size; i++)
    {
      if (Array[i] != 0)
      XtDestroyWidget(Array[i]);
    }
}

// Change number of glyphs
void SourceView::set_max_glyphs (int nmax)
{
    static const WidgetArray empty;

    for (int k = 0; k < 2; k++)
    {
      // Destroy old widgets...
        DestroyOldWidgets(plain_stops[k]);
      DestroyOldWidgets(grey_stops[k]);
      DestroyOldWidgets(plain_conds[k]);
      DestroyOldWidgets(grey_conds[k]);
      DestroyOldWidgets(plain_temps[k]);
      DestroyOldWidgets(grey_temps[k]);

      // ...make array empty...
      plain_stops[k] = empty;
      grey_stops[k]  = empty;

      plain_conds[k] = empty;
      grey_conds[k]  = empty;

      plain_temps[k] = empty;
      grey_temps[k]  = empty;

      // ...and make room for new widgets.  The last one is a null pointer.
      int i;
      for (i = 0; i < nmax + 1; i++)
      {
          plain_stops[k] += Widget(0);
          grey_stops[k]  += Widget(0);

          plain_conds[k] += Widget(0);
          grey_conds[k]  += Widget(0);

          plain_temps[k] += Widget(0);
          grey_temps[k]  += Widget(0);
      }
    }
}


// Glyph has been activated - catch the double click in Motif 1.x
void SourceView::ActivateGlyphCB(Widget glyph, XtPointer, XtPointer call_data)
{
    XmPushButtonCallbackStruct *cbs = (XmPushButtonCallbackStruct *)call_data;
    XEvent *e = cbs->event;
    if (e->type != ButtonRelease)
      return;

    String *params = { 0 };
    XtCallActionProc(glyph, "source-drop-glyph", e, params, 0);

    if (cbs->click_count > 1)
      XtCallActionProc(glyph, "source-double-click", e, params, 0);
}


// Create a pixmap from BITS suitable for the widget W
Pixmap SourceView::pixmap(Widget w, unsigned char *bits, int width, int height)
{
    Pixel foreground, background;

    XtVaGetValues(w,
              XmNforeground, &foreground,
              XmNbackground, &background,
              XtPointer(0));

    int depth = PlanesOfScreen(XtScreen(w));
    Pixmap pix = XCreatePixmapFromBitmapData(XtDisplay(w), XtWindow(w), 
                                   (char *)bits, width, height, 
                                   foreground, background, depth);
    return pix;
}


// Create glyph in FORM_W named NAME from given BITS
Widget SourceView::create_glyph(Widget form_w,
                        const _XtString name,
                        unsigned char *bits,
                        int width, int height)
{
    // Get background color from text
    Pixel background;
    Widget text_w;
    if (form_w == code_form_w)
      text_w = code_text_w;
    else
      text_w = source_text_w;
    XtVaGetValues(text_w, XmNbackground, &background, XtPointer(0));

    // Create push button
    Arg args[30];
    Cardinal arg = 0;
    XtSetArg(args[arg], XmNmappedWhenManaged,  False);         arg++;
    XtSetArg(args[arg], XmNtopAttachment,      XmATTACH_FORM); arg++;
    XtSetArg(args[arg], XmNleftAttachment,     XmATTACH_FORM); arg++;
    XtSetArg(args[arg], XmNrecomputeSize,      False);         arg++;
    XtSetArg(args[arg], XmNmarginBottom,       0);             arg++;
    XtSetArg(args[arg], XmNmarginTop,          0);             arg++;
    XtSetArg(args[arg], XmNmarginLeft,         0);             arg++;
    XtSetArg(args[arg], XmNmarginRight,        0);             arg++;
    XtSetArg(args[arg], XmNmarginWidth,        0);             arg++;
    XtSetArg(args[arg], XmNmarginHeight,       0);             arg++;
    XtSetArg(args[arg], XmNshadowThickness,    0);             arg++;
    XtSetArg(args[arg], XmNhighlightThickness, 0);             arg++;
    XtSetArg(args[arg], XmNborderWidth,        0);             arg++;
    XtSetArg(args[arg], XmNlabelType,  XmPIXMAP);              arg++;
    XtSetArg(args[arg], XmNmultiClick, XmMULTICLICK_KEEP);     arg++;
    XtSetArg(args[arg], XmNalignment, XmALIGNMENT_BEGINNING);  arg++;
    XtSetArg(args[arg], XmNuserData,           XtPointer(0));  arg++;
    XtSetArg(args[arg], XmNfillOnArm,          True);          arg++;
    XtSetArg(args[arg], XmNarmColor,           background);    arg++;
    XtSetArg(args[arg], XmNbackground,         background);    arg++;
    Widget w = verify(XmCreatePushButton(form_w, XMST(name), args, arg));

    if (XtIsRealized(form_w))
      XtRealizeWidget(w);

    XtManageChild(w);

    arg = 0;
    if (!cache_glyph_images)
    {
      Pixmap pix = pixmap(w, bits, width, height);
      XtSetArg(args[arg], XmNlabelPixmap, pix); arg++;
    }
    XtSetArg(args[arg], XmNwidth,  width);  arg++;
    XtSetArg(args[arg], XmNheight, height); arg++;
    XtSetValues(w, args, arg);

    XtAddCallback(w, XmNactivateCallback, ActivateGlyphCB, 0);

    InstallButtonTips(w);
    return w;
}

// Return height of a single line
int SourceView::line_height(Widget text_w)
{
    static int source_height = 0;
    static int code_height   = 0;
    if (text_w == source_text_w && source_height > 0)
      return source_height;
    else if (text_w == code_text_w && code_height > 0)
      return code_height;

    bool ok;

    XmTextPosition top = XmTextGetTopCharacter(text_w);
    Position top_x, top_y;
    ok = XmTextPosToXY(text_w, top, &top_x, &top_y);
    if (!ok)
      return 0;

    const string& text = current_text(text_w);
    XmTextPosition second = text.index('\n', top) + 1;
    Position second_x, second_y;
    ok = XmTextPosToXY(text_w, second, &second_x, &second_y);
    if (!ok)
      return 0;

    int height = abs(second_y - top_y);

    if (text_w == source_text_w)
      source_height = height;
    else if (text_w == code_text_w)
      code_height = height;

    return height;
}


// If false, don't change glyphs - just check if they would change
bool SourceView::change_glyphs = true;

// All glyphs that have changed during update_glyphs_now()
WidgetArray SourceView::changed_glyphs;

// Unmap glyph W
void SourceView::unmap_glyph(Widget glyph)
{
    if (glyph == 0)
      return;

    assert(is_code_widget(glyph) || is_source_widget(glyph));

    XtPointer user_data;
    XtVaGetValues(glyph, XmNuserData, &user_data, XtPointer(0));
    if (user_data == 0)
      return;                 // Already unmapped

    if (change_glyphs)
    {
      const Position invisible_x = -100;
      const Position invisible_y = -100;

      // Unmapping the glyph while dragging breaks the drag.
      // Move the glyph to an invisible position instead.
      XtVaSetValues(glyph,
                  XmNleftOffset, invisible_x,
                  XmNtopOffset,  invisible_y,
                  XmNuserData, XtPointer(0),
                  XtPointer(0));

      if (lesstif_version <= 85)
      {
          // LessTif 0.84 and earlier wants it the hard way.
          XtMoveWidget(glyph, invisible_x, invisible_y);
      }
      log_glyph(glyph);
    }

    changed_glyphs += glyph;
}

// Map glyph GLYPH in (X, Y)
void SourceView::map_glyph(Widget& glyph, Position x, Position y)
{
    while (glyph == 0)
      CreateGlyphsWorkProc(0);

    assert(is_code_widget(glyph) || is_source_widget(glyph));

    Widget text_w;
    if (is_source_widget(glyph))
      text_w = source_text_w;
    else
      text_w = code_text_w;

    XtPointer user_data;
    Dimension height              = 0;
    Dimension border_width        = 0;
    Dimension margin_height       = 0;
    Dimension shadow_thickness    = 0;
    Dimension highlight_thickness = 0;
    int old_x                     = 0;
    int old_y                     = 0; 
    XtVaGetValues(glyph,
              XmNheight,             &height,
              XmNborderWidth,        &border_width,
              XmNmarginHeight,       &margin_height,
              XmNshadowThickness,    &shadow_thickness,
              XmNhighlightThickness, &highlight_thickness,
              XmNuserData,           &user_data,
              XmNleftOffset,         &old_x,
              XmNtopOffset,          &old_y,
              XtPointer(0));
    Dimension glyph_height = 
      height + border_width + margin_height
      + shadow_thickness + highlight_thickness;

    y -= (line_height(text_w) + glyph_height) / 2 - 2;

    if (lesstif_version <= 87)
      x += 2;

    if (x != old_x || y != old_y)
    {
      if (change_glyphs)
      {
          if (lesstif_version <= 84)
          {
            // LessTif 0.84 and earlier want it the hard way.
            XtMoveWidget(glyph, x, y);
          }

          XtVaSetValues(glyph, XmNleftOffset, x, XmNtopOffset, y, 
                    XtPointer(0));
          log_glyph(glyph);
      }
      changed_glyphs += glyph;
    }

    if (user_data != 0)
      return;                 // Already mapped

    if (change_glyphs)
    {
      XtMapWidget(glyph);
      XtVaSetValues(glyph, XmNuserData, XtPointer(1), XtPointer(0));
      log_glyph(glyph);
      changed_glyphs += glyph;
    }
}


// True if code/source glyphs need to be updated
bool SourceView::update_code_glyphs   = false;
bool SourceView::update_source_glyphs = false;

// Update glyphs for widget GLYPH (0: all)
void SourceView::update_glyphs(Widget glyph)
{
    static XtWorkProcId update_glyph_id = 0;

    if (glyph == 0)
      update_source_glyphs = update_code_glyphs = true;
    else if (is_source_widget(glyph))
      update_source_glyphs = true;
    else if (is_code_widget(glyph))
      update_code_glyphs = true;

    if (update_glyph_id != 0)
      XtRemoveTimeOut(update_glyph_id);

    // Chris van Engelen reports:
    // When reading in a new file, there is an infinite loop involving
    // function SourceView::UpdateGlyphsWorkProc: function
    // XtAppPending always returns a value indicating that there are
    // events pending, and since there always is at least one glyph to
    // be updated (the execution position arrow), the function returns
    // with a new call to UpdateGlyphsWorkProc scheduled. This problem
    // was solved by increasing the delay time for the first
    // scheduling to 50ms.
    update_glyph_id = 
      XtAppAddTimeOut(XtWidgetToApplicationContext(source_text_w), 50,
                  UpdateGlyphsWorkProc, XtPointer(&update_glyph_id));
}


// Invoked by scrolling keys
void SourceView::updateGlyphsAct(Widget w, XEvent*, String *, Cardinal *)
{
    CheckScrollCB(w, 0, 0);
}

// Invoked whenever the text widget may be about to scroll
void SourceView::CheckScrollCB(Widget, XtPointer, XtPointer)
{
    static XtIntervalId check_scroll_id = 0;

    if (check_scroll_id != 0)
    {
      XtRemoveTimeOut(check_scroll_id);
      check_scroll_id = 0;
    }

    check_scroll_id = 
      XtAppAddTimeOut(XtWidgetToApplicationContext(source_text_w),
                  app_data.glyph_update_delay,
                  CheckScrollWorkProc, XtPointer(&check_scroll_id));
}

void SourceView::CheckScrollWorkProc(XtPointer client_data, XtIntervalId *id)
{
    (void) id;                // Use it

    XtIntervalId *timer = (XtIntervalId *)client_data;
    if (timer != 0)
    {
      assert(*timer == *id);
      *timer = 0;
    }

    XmTextPosition old_top = last_top;
    last_top = XmTextGetTopCharacter(source_text_w);

    XmTextPosition old_top_pc = last_top_pc;
    last_top_pc = XmTextGetTopCharacter(code_text_w);

    if (old_top != last_top && old_top_pc != last_top_pc)
      update_glyphs();
    else if (old_top != last_top)
      update_glyphs(source_text_w);
    else if (old_top_pc != last_top_pc)
      update_glyphs(code_text_w);
}


// Pixel offsets

// Horizontal arrow offset (pixels)
Position SourceView::arrow_x_offset = -5;

// Horizontal breakpoint symbol offset (pixels)
Position SourceView::stop_x_offset = +6;

// Additional offset for multiple breakpoints (pixels)
Position SourceView::multiple_stop_x_offset = stop_width;


// Glyph locations: x[0] is source, x[1] is code
Widget SourceView::plain_arrows[2]  = {0, 0};
Widget SourceView::grey_arrows[2]   = {0, 0};
Widget SourceView::past_arrows[2]   = {0, 0};
Widget SourceView::signal_arrows[2] = {0, 0};
Widget SourceView::drag_arrows[2]   = {0, 0};

Widget SourceView::drag_stops[2]    = {0, 0};
Widget SourceView::drag_conds[2]    = {0, 0};
Widget SourceView::drag_temps[2]    = {0, 0};

WidgetArray SourceView::plain_stops[2];
WidgetArray SourceView::grey_stops[2];

WidgetArray SourceView::plain_conds[2];
WidgetArray SourceView::grey_conds[2];

WidgetArray SourceView::plain_temps[2];
WidgetArray SourceView::grey_temps[2];


// Create glyphs in the background
Boolean SourceView::CreateGlyphsWorkProc(XtPointer)
{
    int k;
    for (k = 0; k < 2; k++)
    {
      // On the Form widget, later children are displayed on top of
      // earlier children.  A stop sign hiding an arrow gives more
      // pleasing results than vice-versa, so place arrow glyph
      // below sign glyphs.

      Widget form_w = k ? code_form_w : source_form_w;

      if (form_w == 0)
          continue;

      if (past_arrows[k] == 0)
      {
          past_arrows[k] = 
            create_glyph(form_w, "past_arrow",
                       past_arrow_bits, 
                       past_arrow_width, 
                       past_arrow_height);
          return False;
      }

      if (plain_arrows[k] == 0)
      {
          plain_arrows[k] = 
            create_glyph(form_w, "plain_arrow",
                       arrow_bits, 
                       arrow_width,
                       arrow_height);
          return False;
      }

      if (grey_arrows[k] == 0)
      {
          grey_arrows[k] = 
            create_glyph(form_w, "grey_arrow",
                       grey_arrow_bits, 
                       grey_arrow_width, 
                       grey_arrow_height);
          return False;
      }

      if (signal_arrows[k] == 0)
      {
          signal_arrows[k] = 
            create_glyph(form_w, "signal_arrow",
                       signal_arrow_bits, 
                       signal_arrow_width,
                       signal_arrow_height);
          return False;
      }

      if (drag_arrows[k] == 0)
      {
          drag_arrows[k] = 
            create_glyph(form_w, "drag_arrow",
                       drag_arrow_bits, 
                       drag_arrow_width,
                       drag_arrow_height);
          return False;
      }
    }
   
    for (k = 0; k < 2; k++)
    {
      Widget form_w = k ? code_form_w : source_form_w;

      if (form_w == 0)
          continue;

      int i;

      for (i = 0; i < plain_stops[k].size() - 1; i++)
      {
          if (plain_stops[k][i] == 0)
          {
            plain_stops[k][i] = 
                create_glyph(form_w, "plain_stop",
                         stop_bits, 
                         stop_width,
                         stop_height);
            return False;
          }
      }

      for (i = 0; i < plain_temps[k].size() - 1; i++)
      {
          if (plain_temps[k][i] == 0)
          {
            plain_temps[k][i] = 
                create_glyph(form_w, "plain_temp",
                         temp_bits, 
                         temp_width,
                         temp_height);
            return False;
          }
      }

      for (i = 0; i < plain_conds[k].size() - 1; i++)
      {
          if (plain_conds[k][i] == 0)
          {
            plain_conds[k][i] = 
                create_glyph(form_w, "plain_cond",
                         cond_bits, 
                         cond_width,
                         cond_height);
            return False;
          }
      }
      for (i = 0; i < grey_stops[k].size() - 1; i++)
      {
          if (grey_stops[k][i] == 0)
          {
            grey_stops[k][i] = 
                create_glyph(form_w, "grey_stop",
                         grey_stop_bits, 
                         grey_stop_width,
                         grey_stop_height);
            return False;
          }
      }

      for (i = 0; i < grey_temps[k].size() - 1; i++)
      {
          if (grey_temps[k][i] == 0)
          {
            grey_temps[k][i] = 
                create_glyph(form_w, "grey_temp",
                         grey_temp_bits, 
                         grey_temp_width,
                         grey_temp_height);
            return False;
          }
      }

      for (i = 0; i < grey_conds[k].size() - 1; i++)
      {
          if (grey_conds[k][i] == 0)
          {
            grey_conds[k][i] = 
                create_glyph(form_w, "grey_cond",
                         grey_cond_bits, 
                         grey_cond_width,
                         grey_cond_height);
            return False;
          }
      }

      if (drag_stops[k] == 0)
      {
          drag_stops[k] = 
            create_glyph(form_w, "drag_stop",
                       drag_stop_bits, 
                       drag_stop_width,
                       drag_stop_height);
          return False;
      }

      if (drag_temps[k] == 0)
      {
          drag_temps[k] = 
            create_glyph(form_w, "drag_temp",
                       drag_temp_bits, 
                       drag_temp_width,
                       drag_temp_height);
          return False;
      }

      if (drag_conds[k] == 0)
      {
          drag_conds[k] = 
            create_glyph(form_w, "drag_cond",
                       drag_cond_bits, 
                       drag_cond_width,
                       drag_cond_height);
          return False;
      }
    }

    return True;        // all done
}

// Return position POS of glyph GLYPH in X/Y.  Return true iff displayed.
bool SourceView::glyph_pos_to_xy(Widget glyph, XmTextPosition pos,
                         Position& x, Position& y)
{
    assert (is_source_widget(glyph) || is_code_widget(glyph));

    if (pos == XmTextPosition(-1))
      return false;           // Not displayed

    Widget text_w;
    if (is_source_widget(glyph))
      text_w = source_text_w;
    else
      text_w = code_text_w;

    Boolean pos_displayed = XmTextPosToXY(text_w, pos, &x, &y);

    // In LessTif 0.87 and later, XmTextPosToXY() returns True even if
    // the position is *below* the last displayed line.  Verify.
    Dimension width, height;
    XtVaGetValues(text_w, XmNwidth, &width, XmNheight, &height, XtPointer(0));
    
    if (pos_displayed && (x > width || y > height))
    {
      // Below last displayed position
      pos_displayed = False;
    }

    return pos_displayed;
}


// Map stop sign GLYPH at position POS.  Get widget from STOPS[COUNT];
// store location in POSITIONS.  Return mapped widget (0 if none)
Widget SourceView::map_stop_at(Widget glyph, XmTextPosition pos,
                         WidgetArray& stops, int& count,
                         TextPositionArray& positions)
{
    Position x, y;
    bool pos_displayed = glyph_pos_to_xy(glyph, pos, x, y);

    if (pos_displayed)
    {
      while (stops[count] == 0)
      {
          if (CreateGlyphsWorkProc(0))
            break;
      }

      Widget glyph = stops[count] ? stops[count++] : 0;

      if (glyph != 0)
      {
          for (int i = 0; i < positions.size(); i++)
            if (pos == positions[i])
                x += multiple_stop_x_offset;

          map_glyph(glyph, x + stop_x_offset, y);
          positions += pos;
          return glyph;
      }
      else
      {
          // Max number of glyphs exceeded
          const string msg = "Out of glyphs (used " + 
            itostring(stops.size() - 1) + " of " +
            itostring(stops.size() - 1) + ")";

          set_status(msg);

          static bool warning_posted = false;

          if (!warning_posted)
          {
            post_warning(msg, "out_of_glyphs_warning", glyph);
            warning_posted = true;
          }
      }
    }

    return 0;
}

// Map arrow in GLYPH at POS.  Return mapped arrow widget (0 if none)
Widget SourceView::map_arrow_at(Widget glyph, XmTextPosition pos)
{
    assert (is_source_widget(glyph) || is_code_widget(glyph));

    Position x, y;
    bool pos_displayed = glyph_pos_to_xy(glyph, pos, x, y);

    int k = int(is_code_widget(glyph));

    Widget& signal_arrow = signal_arrows[k];
    Widget& plain_arrow  = plain_arrows[k];
    Widget& grey_arrow   = grey_arrows[k];
    Widget& past_arrow   = past_arrows[k];

    while (signal_arrow == 0 || plain_arrow == 0 || 
         grey_arrow == 0 || past_arrow == 0)
    {
      if (CreateGlyphsWorkProc(0))
          break;
    }

    if (pos_displayed)
    {
      if (undo_buffer.showing_earlier_state())
      {
          map_glyph(past_arrow, x + arrow_x_offset, y);
          unmap_glyph(grey_arrow);
          unmap_glyph(signal_arrow);
          unmap_glyph(plain_arrow);
          return past_arrow;
      }
      else if (at_lowest_frame && signal_received)
      {
          map_glyph(signal_arrow, x + arrow_x_offset, y);
          unmap_glyph(plain_arrow);
          unmap_glyph(grey_arrow);
          unmap_glyph(past_arrow);
          return signal_arrow;
      }
      else if (at_lowest_frame)
      {
          map_glyph(plain_arrow, x + arrow_x_offset, y);
          unmap_glyph(signal_arrow);
          unmap_glyph(grey_arrow);
          unmap_glyph(past_arrow);
          return plain_arrow;
      }
      else
      {
          map_glyph(grey_arrow, x + arrow_x_offset, y);
          unmap_glyph(signal_arrow);
          unmap_glyph(plain_arrow);
          unmap_glyph(past_arrow);
          return grey_arrow;
      }
    }
    else
    {
      unmap_glyph(signal_arrow);
      unmap_glyph(plain_arrow);
      unmap_glyph(grey_arrow);
      unmap_glyph(past_arrow);
    }
    return 0;
}

// Copy glyph foreground and background colors from ORIGIN to GLYPH
void SourceView::copy_colors(Widget glyph, Widget origin)
{
    if (origin == 0)
      return;

    Pixel background, foreground;
    XtVaGetValues(origin,
              XmNforeground, &foreground,
              XmNbackground, &background,
              XtPointer(0));

    Pixmap pixmap = 
      XmGetPixmap(XtScreen(glyph), XtName(glyph), foreground, background);
    if (pixmap != XmUNSPECIFIED_PIXMAP)
    {
      Pixmap old_pixmap;
      XtVaGetValues(glyph, XmNlabelPixmap, &old_pixmap, XtPointer(0));
      XmDestroyPixmap(XtScreen(glyph), old_pixmap);

      XtVaSetValues(glyph, XmNlabelPixmap, pixmap, XtPointer(0));
    }
}

// Map temporary stop sign at position POS.  If ORIGIN is given, use
// colors from ORIGIN.
Widget SourceView::map_drag_stop_at(Widget glyph, XmTextPosition pos, 
                            Widget origin)
{
    assert (is_source_widget(glyph) || is_code_widget(glyph));

    Position x, y;
    bool pos_displayed = glyph_pos_to_xy(glyph, pos, x, y);

    int k = int(is_code_widget(glyph));

    if (pos_displayed)
    {
      bool cond = (origin != 0 && string(XtName(origin)).contains("cond"));
      bool temp = (origin != 0 && string(XtName(origin)).contains("temp"));

      Widget& drag_stop = 
          temp ? drag_temps[k] : 
          cond ? drag_conds[k] : 
          drag_stops[k];
      
      while (drag_stop == 0)
      {
          if (CreateGlyphsWorkProc(0))
            break;
      }

      copy_colors(drag_stop, origin);

      if (origin != 0)
      {
          static Position last_x = x + stop_x_offset;

          Position origin_x = -1;
          XtVaGetValues(origin, XmNx, &origin_x, XtPointer(0));
          if (lesstif_version <= 87)
            origin_x -= 2;

          if (origin_x >= 0)
          {
            // Origin is mapped
            x = last_x = origin_x;
          }
          else
          {
            // Origin is unmapped - use last recorded value
            x = last_x;
          }
      }
      else
      {
          x += stop_x_offset;
      }

      map_glyph(drag_stop, x, y);
      if (temp)
      {
          unmap_glyph(drag_conds[k]);
          unmap_glyph(drag_stops[k]);
      }
      else if (cond)
      {
          unmap_glyph(drag_temps[k]);
          unmap_glyph(drag_stops[k]);
      }
      else
      {
          unmap_glyph(drag_conds[k]);
          unmap_glyph(drag_temps[k]);
      }

      return drag_stop;
    }
    else
    {
      unmap_glyph(drag_conds[k]);
      unmap_glyph(drag_temps[k]);
      unmap_glyph(drag_stops[k]);

      return 0;
    }
}

// Map temporary arrow at position POS.  If ORIGIN is given, use
// colors from ORIGIN.
Widget SourceView::map_drag_arrow_at(Widget glyph, XmTextPosition pos, 
                             Widget origin)
{
    assert (is_source_widget(glyph) || is_code_widget(glyph));

    Position x, y;
    bool pos_displayed = glyph_pos_to_xy(glyph, pos, x, y);

    int k = int(is_code_widget(glyph));

    Widget& drag_arrow = drag_arrows[k];

    while (drag_arrow == 0)
    {
      if (CreateGlyphsWorkProc(0))
          break;
    }

    copy_colors(drag_arrow, origin);

    if (pos_displayed)
      map_glyph(drag_arrow, x + arrow_x_offset, y);
    else
      unmap_glyph(drag_arrow);

    return drag_arrow;
}



// Update glyphs after interval
void SourceView::UpdateGlyphsWorkProc(XtPointer client_data, XtIntervalId *id)
{
    (void) id;                // Use it

    // Allow new invocations
    XtIntervalId *proc_id = ((XtIntervalId *) client_data);
    if (proc_id != 0)
    {
      assert(*proc_id == *id);
      *proc_id = 0;
    }

    XtAppContext app_context = XtWidgetToApplicationContext(source_text_w);
    if (XtAppPending(app_context) & (XtIMXEvent | XtIMAlternateInput))
    {
      // Other events pending - check if we shall change something
      const WidgetArray& glyphs = glyphs_to_be_updated();

      if (glyphs.size() > 0)
      {
          // Change is imminent - unmap all glyphs that will change
          // and try again in 50ms
          for (int i = 0; i < glyphs.size(); i++)
            unmap_glyph(glyphs[i]);

          XtIntervalId new_id = 
            XtAppAddTimeOut(app_context, 50,
                        UpdateGlyphsWorkProc, client_data);
          if (proc_id != 0)
            *proc_id = new_id;
          return;
      }
    }

    change_glyphs = true;
    update_glyphs_now();
}


// The function that does the real work
void SourceView::update_glyphs_now()
{
    // std::clog << "Updating glyphs...";

    static const WidgetArray empty;
    changed_glyphs = empty;

    if (update_source_glyphs)
    {
      // Show current execution position
      XmTextPosition pos = XmTextPosition(-1);

      if (display_glyphs &&
          (is_current_file(last_execution_file) ||
           base_matches(last_execution_file, current_file_name)) &&
           line_count > 0 &&
           last_execution_line > 0 &&
           last_execution_line <= line_count)
      {
          pos = pos_of_line(last_execution_line);
      }

      map_arrow_at(source_text_w, pos);
    }

    if (update_code_glyphs)
    {
      // Show current PC
      XmTextPosition pos = XmTextPosition(-1);

      if (display_glyphs && !last_execution_pc.empty())
          pos = find_pc(last_execution_pc);

      map_arrow_at(code_text_w, pos);
    }

    // Map breakpoint glyphs
    for (int k = 0; k < 2; k++)
    {
      if (k == 0 && !update_source_glyphs)
          continue;
      if (k == 1 && !update_code_glyphs)
          continue;

      int plain_stops_count = 0;
      int grey_stops_count  = 0;

      int plain_conds_count = 0;
      int grey_conds_count  = 0;

      int plain_temps_count = 0;
      int grey_temps_count  = 0;

      if (display_glyphs)
      {
          TextPositionArray positions;
          
          MapRef ref;
          for (BreakPoint *bp = bp_map.first(ref);
             bp != 0;
             bp = bp_map.next(ref))
          {
            if (bp->type() != BREAKPOINT)
                continue;

            Widget& bp_glyph = k ? bp->code_glyph() : bp->source_glyph();
            Widget text_w    = k ? code_text_w      : source_text_w;
            bp_glyph = 0;

            XmTextPosition pos;
            if (k == 0)
            {
                // Find source position
                if (!bp_matches(bp)
                  || line_count <= 0
                  || bp->line_nr() <= 0
                  || bp->line_nr() > line_count)
                  continue;

                pos = pos_of_line(bp->line_nr());
            }
            else
            {
                // Find code position
                pos = find_pc(bp->address());
            }

            if (bp->dispo() != BPKEEP)
            {
                // Temporary breakpoint
                if (bp->enabled())
                  bp_glyph = map_stop_at(text_w, pos, plain_temps[k],
                                     plain_temps_count, positions);
                else
                  bp_glyph = map_stop_at(text_w, pos, grey_temps[k],
                                     grey_temps_count, positions);
            }
            else if (!bp->condition().empty() || bp->ignore_count() != 0)
            {
                // Conditional breakpoint
                if (bp->enabled())
                  bp_glyph = map_stop_at(text_w, pos, plain_conds[k],
                                     plain_conds_count, positions);
                else
                  bp_glyph = map_stop_at(text_w, pos, grey_conds[k],
                                     grey_conds_count, positions);
            }
            else
            {
                // Ordinary breakpoint
                if (bp->enabled())
                  bp_glyph = map_stop_at(text_w, pos, plain_stops[k],
                                     plain_stops_count, positions);
                else
                  bp_glyph = map_stop_at(text_w, pos, grey_stops[k],
                                     grey_stops_count, positions);
            }
          }
      }

      // Unmap remaining breakpoint glyphs
      Widget glyph;
      while ((glyph = plain_stops[k][plain_stops_count++]))
          unmap_glyph(glyph);
      while ((glyph = grey_stops[k][grey_stops_count++]))
          unmap_glyph(glyph);
      while ((glyph = plain_conds[k][plain_conds_count++]))
          unmap_glyph(glyph);
      while ((glyph = grey_conds[k][grey_conds_count++]))
          unmap_glyph(glyph);
      while ((glyph = plain_temps[k][plain_temps_count++]))
          unmap_glyph(glyph);
      while ((glyph = grey_temps[k][grey_temps_count++]))
          unmap_glyph(glyph);
    }

    if (change_glyphs)
    {
      update_source_glyphs = false;
      update_code_glyphs   = false;
    }

    // std::clog << "done.\n";
}


// Return all glyphs that would change
const WidgetArray& SourceView::glyphs_to_be_updated()
{
    change_glyphs = false;
    update_glyphs_now();
    change_glyphs = true;

    // std::clog << "Glyphs to be updated:";
    // for (int i = 0; i < changed_glyphs.size(); i++)
    //     std::clog << " " << XtName(changed_glyphs[i]);
    // std::clog << "\n";

    return changed_glyphs;
}


// Change setting of display_glyphs
void SourceView::set_display_glyphs(bool set)
{
    if (display_glyphs != set)
    {
      // Save current execution position
      string file   = last_execution_file;
      int    line   = last_execution_line;
      string pc     = last_execution_pc;
      bool stopped  = at_lowest_frame;
      bool signaled = signal_received;

      if (XtIsRealized(source_text_w))
      {
          display_glyphs = false;   
          show_execution_position();
          UpdateGlyphsWorkProc(0, 0);

          display_glyphs = true;
          refresh_bp_disp(true);
      }

      display_glyphs = set;

      if (XtIsRealized(source_text_w))
      {
          StatusDelay delay(set ? "Enabling glyphs" : "Disabling glyphs");

          refresh_bp_disp(true);
          if (!file.empty())
            show_execution_position(file + ":" + itostring(line), 
                              stopped, signaled);
          if (!pc.empty())
            show_pc(pc, XmHIGHLIGHT_SELECTED);
      }
    }
}

// Change setting of display_line_numbers
void SourceView::set_display_line_numbers(bool set)
{
    if (display_line_numbers != set)
    {
      display_line_numbers = set;

      if (XtIsRealized(source_text_w))
      {
          StatusDelay delay(set ? "Enabling line numbers" : 
                        "Disabling line numbers");
          reload();
      }
    }
}

// Return help on a glyph
MString SourceView::help_on_glyph(Widget glyph, bool detailed)
{
    XmTextPosition dummy;
    return help_on_pos(glyph, 0, dummy, detailed);
}

// Return help on a breakpoint position
MString SourceView::help_on_pos(Widget w, XmTextPosition pos, 
                        XmTextPosition& ref, bool detailed)
{
    if (w == 0)
      return MString(0, true);

    int line_nr;
    bool in_text;
    int bp_nr;
    string address;
    bool pos_found = get_line_of_pos(w, pos, line_nr, address, in_text, bp_nr);

    if (!pos_found || bp_nr == 0)
      return MString(0, true);

    ref = pos_of_line(line_nr) + 2;
    return help_on_bp(bp_nr, detailed);
}

// Return help on a glyph
MString SourceView::help_on_bp(int bp_nr, bool detailed)
{
    BreakPoint *bp = bp_map.get(bp_nr);
    if (bp == 0)
      return MString(0, true);

    MString info = rm(bp->title() + " ") + tt(itostring(bp->number()));

    if (detailed)
    {
      if (bp->enabled())
          info += rm(" (enabled");
      else
          info += rm(" (disabled");

      string infos = bp->infos();
      strip_space(infos);
      infos.gsub("\n", "; ");

      if (!bp->infos().empty())
          info += rm("; " + infos);

      switch (bp->dispo())
      {
      case BPKEEP:
          break;

      case BPDEL:
          info += rm("; delete when hit");
          break;

      case BPDIS:
          info += rm("; disable when hit");
          break;
      }
      info += rm(")");
    }

    return info;
}


// Glyph drag & drop

XmTextPosition SourceView::glyph_position(Widget glyph, XEvent *e, 
                                bool normalize)
{
    Widget text_w;
    if (is_source_widget(glyph))
      text_w = source_text_w;
    else if (is_code_widget(glyph))
      text_w = code_text_w;
    else
      return XmTextPosition(-1);

    BoxPoint p = point(e);
    if (glyph != source_text_w && glyph != code_text_w)
    {
      // Called from a glyph: add glyph position to event position
      translate_glyph_pos(glyph, text_w, p[X], p[Y]);
    }

    // Get the position
    XmTextPosition pos;
    if (p[Y] <= 0)
      pos = 0;          // We may drag outside the text window
    else
      pos = XmTextXYToPos(text_w, p[X], p[Y]);

    // Stay within viewable text +/-1 row, such that we don't scroll too fast
    short rows = 0;
    XmTextPosition current_top = 0;
    XtVaGetValues(text_w,
              XmNrows, &rows,
              XmNtopCharacter, &current_top,
              XtPointer(0));

    const string& text = current_text(text_w);
    XmTextPosition current_bottom = current_top;
    while (current_bottom < int(text.length()) && rows > 0)
      if (text[current_bottom++] == '\n')
          rows--;

    if (pos < current_top)
      pos = max(current_top - 1, 0);
    else if (pos > current_bottom)
      pos = min(current_bottom + 1, text.length());

    if (normalize)
    {
      const string& text = current_text(glyph);
      pos = min(pos, text.length());
      while (pos > 0 && text[pos - 1] != '\n')
          pos--;
    }

    return pos;
}

// Data associated with current drag operation
// The Glyph being dragged
Widget SourceView::current_drag_origin     = 0;

// The breakpoint being dragged, or 0 if execution position
int    SourceView::current_drag_breakpoint = -1;

void SourceView::dragGlyphAct(Widget glyph, XEvent *e, String *params, 
                        Cardinal *num_params)
{
    if (e->type != ButtonPress && e->type != ButtonRelease)
      return;

    Widget text_w;
    if (is_source_widget(glyph))
      text_w = source_text_w;
    else if (is_code_widget(glyph))
      text_w = code_text_w;
    else
      return;                 // Bad widget

    if (!XtIsRealized(text_w))
      return;

    // Move cursor to glyph position
    XButtonEvent *event = &e->xbutton;
    translate_glyph_pos(glyph, text_w, event->x, event->y);
    event->window = XtWindow(text_w);
    XtCallActionProc(text_w, "source-start-select-word", e, 
                 params, *num_params);

    // Check for double clicks
    XtCallActionProc(text_w, "source-double-click", e,
                 params, *num_params);

    // Now start the drag
    int k;
    for (k = 0; k < 2; k++)
    {
      if (glyph == grey_arrows[k] || glyph == past_arrows[k])
      {
          // Cannot drag last execution position
          return;
      }
      else if (glyph == plain_arrows[k])
      {
          if (!gdb->has_jump_command() && !gdb->has_assign_command())
          {
            // Execution position cannot be dragged
            return;
          }
      }
      else if (glyph == drag_stops[k] || 
             glyph == drag_conds[k] || 
             glyph == drag_temps[k] || 
             glyph == drag_arrows[k])
      {
          // Temp glyph cannot be dragged
          return;
      }
    }

    static Cursor move_cursor = XCreateFontCursor(XtDisplay(glyph), XC_fleur);

    // std::clog << "Dragging " << XtName(glyph) << " [" << glyph << "]\n";

    XDefineCursor(XtDisplay(glyph), XtWindow(glyph), move_cursor);

    unmap_drag_stop(text_w);
    unmap_drag_arrow(text_w);

    current_drag_origin     = glyph;
    current_drag_breakpoint = 0;

    // Check for breakpoint
    MapRef ref;
    for (BreakPoint *bp = bp_map.first(ref); bp != 0; bp = bp_map.next(ref))
    {
      if (glyph == bp->source_glyph() || glyph == bp->code_glyph())
      {
          current_drag_breakpoint = bp->number();
          break;
      }
    }
}

void SourceView::followGlyphAct(Widget glyph, XEvent *e, String *, Cardinal *)
{
    if (glyph != current_drag_origin)
      return;

    Widget text_w;
    if (is_source_widget(glyph))
      text_w = source_text_w;
    else if (is_code_widget(glyph))
      text_w = code_text_w;
    else
      return;                 // Bad widget

    // Protect against more than one invocation per 50ms.
    static Time last_time = 0;
    if (time(e) - last_time < 50)
      return;
    last_time = time(e);

    XmTextPosition pos = glyph_position(glyph, e);

    // Make sure we see the position
    ShowPosition(text_w, pos);

    // Update glyphs in case we had to scroll
    CheckScrollCB(glyph, XtPointer(0), XtPointer(0));

    if (current_drag_breakpoint)
      map_drag_stop_at(text_w, pos, glyph);
    else
      map_drag_arrow_at(text_w, pos, glyph);
}

void SourceView::dropGlyphAct (Widget glyph, XEvent *e, 
                         String *params, Cardinal *num_params)
{
    if (e->type != ButtonPress && e->type != ButtonRelease)
      return;

    if (glyph != current_drag_origin)
      return;

    Widget text_w;
    if (is_source_widget(glyph))
      text_w = source_text_w;
    else if (is_code_widget(glyph))
      text_w = code_text_w;
    else
      return;                 // Bad widget

    if (!XtIsRealized(text_w))
      return;

    XUndefineCursor(XtDisplay(glyph), XtWindow(glyph));

    // Unmap temp glyphs
    unmap_drag_stop(text_w);
    unmap_drag_arrow(text_w);

    // Show all other glyphs
    update_glyphs();

    int k;
    for (k = 0; k < 2; k++)
      if (glyph == grey_arrows[k] || 
          glyph == past_arrows[k] ||
          glyph == drag_stops[k] || 
          glyph == drag_conds[k] || 
          glyph == drag_temps[k] || 
          glyph == drag_arrows[k])
          return;

    XmTextPosition pos = glyph_position(glyph, e);
    if (pos == XmTextPosition(-1))
      return;                 // No position

    int line_nr = 0;
    bool in_text;
    int bp_nr;
    string address;
    if (!get_line_of_pos(text_w, pos, line_nr, address, in_text, bp_nr))
      return;                 // No location

    if (text_w == code_text_w)
    {
      // Selection from code
      if (address.empty())
          return;       // No address
    }
    else
    {
      // Selection from source
      if (line_nr == 0)
          return;       // No line
      address = current_source_name() + ':' + itostring(line_nr);
    }

    // std::clog << "Dropping " << XtName(glyph) << " [" << glyph << "] at " 
    //      << address << "\n";

    if (text_w == code_text_w)
    {
      // Selection from code
      if (address.empty())
          return;       // No address
      address = string('*') + address;
    }
    else
    {
      // Selection from source
      if (line_nr == 0)
          return;       // No line
      address = current_source_name() + ':' + itostring(line_nr);
    }

    string p = "move";
    if (num_params != 0 && *num_params == 1)
      p = params[0];
    if (num_params != 0 && *num_params > 1)
      std::cerr << "source-drop-glyph: too many parameters\n";
    p.downcase();

    bool copy = false;
    if (p == "move")
      copy = false;
    else if (p == "copy")
      copy = true;
    else
      std::cerr << "source-drop-glyph: unknown parameter " << quote(p) << "\n";

    bool changed = false;
    if (current_drag_breakpoint)
    {
      // Move breakpoint
      changed = move_bp(current_drag_breakpoint, address, text_w, copy);
    }
    else
    {
      // Move exec pos
      changed = move_pc(address, text_w);
    }

    if (changed)
    {
      // Make sure this position is kept visible
      SetInsertionPosition(text_w, pos);
    }

    current_drag_origin     = 0;
    current_drag_breakpoint = 0;
}

// Report glyph state (for debugging)
void SourceView::log_glyph(Widget glyph, int n)
{
    (void) n;                 // Use it
    (void) glyph;

#if LOG_GLYPHS
    if (glyph == 0)
      return;

    int left = 0;
    int top  = 0;
    Position x = 0;
    Position y = 0;
    XtPointer user_data;
    XtVaGetValues(glyph,
              XmNuserData,           &user_data,
              XmNleftOffset,         &left,
              XmNtopOffset,          &top,
              XmNx,                  &x,
              XmNy,                  &y,
              XtPointer(0));

    std::clog << XtName(glyph);
    if (n >= 0)
      std::clog << "s[" << n << "]";
    std::clog << ": ";
    if (user_data)
      std::clog << "mapped";
    else
      std::clog << "unmapped";

    std::clog << " at (" << left << ", " << top << " / "
            << x << ", " << y << ")\n";
#endif
}

void SourceView::log_glyphs()
{
#if LOG_GLYPHS
    for (int k = 0; k < 2; k++)
    {
      if (k && !disassemble)
          continue;

      if (k == 0)
          std::clog << "Source glyphs:\n";
      else
          std::clog << "\nCode glyphs:\n";

      int i;
      for (i = 0; i < plain_stops[k].size() - 1; i++)
          log_glyph(plain_stops[k][i], i);
      for (i = 0; i < grey_stops[k].size() - 1; i++)
          log_glyph(grey_stops[k][i], i);

      for (i = 0; i < plain_conds[k].size() - 1; i++)
          log_glyph(plain_conds[k][i], i);
      for (i = 0; i < grey_conds[k].size() - 1; i++)
          log_glyph(grey_conds[k][i], i);

      for (i = 0; i < plain_temps[k].size() - 1; i++)
          log_glyph(plain_temps[k][i], i);
      for (i = 0; i < grey_temps[k].size() - 1; i++)
          log_glyph(grey_temps[k][i], i);

      log_glyph(plain_arrows[k]);
      log_glyph(grey_arrows[k]);
      log_glyph(past_arrows[k]);
      log_glyph(signal_arrows[k]);
      log_glyph(drag_arrows[k]);

      log_glyph(drag_stops[k]);
      log_glyph(drag_conds[k]);
      log_glyph(drag_temps[k]);
    }
#endif
}


// Delete glyph (breakpoints)
void SourceView::deleteGlyphAct(Widget glyph, XEvent *, String *, Cardinal *)
{
    IntArray bps;
    MapRef ref;
    for (BreakPoint *bp = bp_map.first(ref); bp != 0; bp = bp_map.next(ref))
    {
      if (glyph == bp->source_glyph() || glyph == bp->code_glyph())
      {
          bps += bp->number();
      }
    }

    delete_bps(bps, glyph);
}


//----------------------------------------------------------------------------
// Machine code stuff
//----------------------------------------------------------------------------

// Clear the code cache
void SourceView::clear_code_cache()
{
    static CodeCache empty;
    code_cache = empty;
    process_disassemble("No code.");
}

#if RUNTIME_REGEX
static regex rxnladdress("\n *" RXADDRESS);
#endif

static string first_address(const string& s)
{
    int idx = index(s, rxnladdress, "\n");
    if (idx < 0)
      return "";
    idx++;

    int eol = s.index('\n', idx);
    if (eol < 0)
      eol = s.length();

    string addr = s.at(idx, eol - idx);
    return addr.through(rxaddress);
}

static string last_address(const string& s)
{
    int idx = index(s, rxnladdress, "\n", -1);
    if (idx < 0)
      return "";
    idx++;

    int eol = s.index('\n', idx);
    if (eol < 0)
      eol = s.length();

    string addr = s.at(idx, eol - idx);
    return addr.through(rxaddress);
}

void SourceView::set_code(const string& code,
                    const string& start,
                    const string& end)
{
    XmTextSetString(code_text_w, XMST(code.chars()));
    XmTextSetHighlight (code_text_w, 0, code.length(), XmHIGHLIGHT_NORMAL);
    
    current_code       = code;
    current_code_start = start;
    current_code_end   = end;

    last_pos_pc             = 0;
    last_start_highlight_pc = 0;
    last_end_highlight_pc   = 0;
}

// Process output of `disassemble' command
void SourceView::process_disassemble(const string& disassemble_output)
{
    int count             = disassemble_output.freq('\n') + 1;
    string *code_list     = new string[count];

    split(disassemble_output, code_list, count, '\n');

    string indented_code;
    for (int i = 0; i < count; i++)
    {
      string& line = code_list[i];
      untabify(line);
      if (line.length() > 0 && line[0] == '0')
          line = replicate(' ', indent_amount(code_text_w)) + line;
      indented_code += line + '\n';
    }
    delete[] code_list;
    code_list = 0;

    set_code(indented_code,
           first_address(disassemble_output),
           last_address(disassemble_output));

    if (cache_machine_code
      && !current_code_start.empty()
      && !current_code_end.empty())
      code_cache += CodeCacheEntry(current_code_start, 
                             current_code_end, 
                             current_code);
}

// Search PC in the current code; return beginning of line if found
XmTextPosition SourceView::find_pc(const string& pc)
{
    if (compare_address(pc, current_code_start) < 0
      || compare_address(pc, current_code_end) > 0)
      return XmTextPosition(-1);

    XmTextPosition pos = XmTextPosition(-1);

    int i = 0;
    while (i < int(current_code.length()))
    {
      int eol = current_code.index('\n', i);
      if (eol < 0)
          break;

      int j = i;
      while (j < int(current_code.length()) && isspace(current_code[j]))
          j++;

      if (j + 2 < int(current_code.length())
          && (is_address_start(current_code[j])))
      {
          // Use first word of line as address.  Much faster than
          // checking address regexps.
          string address = current_code.at(j, eol - j);
          int k = 0;
          while (k < int(address.length()) && !isspace(address[k]))
            k++;
          address = address.before(k);
          if (compare_address(pc, address) == 0)
          {
            pos = i;
            break;
          }
      }

      i = eol + 1;
    }

    return pos;
}


// Process `disassemble' output
void SourceView::refresh_codeOQC(const string& answer, void *client_data)
{
    RefreshDisassembleInfo *info = (RefreshDisassembleInfo *)client_data;

    if (answer == NO_GDB_ANSWER)
    {
      info->delay.outcome = "failed";
    }
    else
    {
      process_disassemble(answer);

      if (find_pc(info->pc) != XmTextPosition(-1))
          show_pc(info->pc, info->mode);
    }

    delete info;
}

void SourceView::normalize_address(string& addr)
{
    addr.downcase();
    if (addr.contains("0", 0))
      addr = addr.after("0");
    if (addr.contains("x", 0))
      addr = addr.after("x");
    if (addr.contains("h'", 0))
      addr = addr.after("h'");
    if (addr.contains("h", -1))
      addr = addr.before(int(addr.length()) - 1);
    addr.prepend("0x");
}

string SourceView::make_address(long pc)
{
    char buffer[BUFSIZ];
    sprintf(buffer, "0x%lx", (unsigned long) pc);
    return string(buffer);
}

// Return FUNCTION and OFFSET at ADDRESS
void SourceView::get_func_at(const string& address, string& func, int& offset)
{
    // GDB issues /i lines in the format
    // `ADDR <FUNC[+OFFSET]> INSTRUCTIONS', as in
    // `0xef7be49c <_IO_file_underflow+128>:\torcc  %o0, %g0, %o2\n'
    offset = 0;
    func = gdb_question("x /i " + address);
    if (func == NO_GDB_ANSWER)
      return;

    // Find func
    func = func.after("<");
    func = func.before(">");

    // Find offset
    int plus = func.index('+');
    if (plus >= 0)
    {
      offset = atoi(func.chars() + plus + 1);
      func = func.before(plus);
    }
}

// Return TRUE iff the function at PC is larger than MAX_SIZE.
bool SourceView::function_is_larger_than(string pc, int max_size)
{
    if (gdb->type() != GDB)
      return false;

    // Get function name at PC
    normalize_address(pc);
    string pc_func;
    int pc_offset;
    get_func_at(pc, pc_func, pc_offset);
    if (pc_func == NO_GDB_ANSWER)
      return true;            // In doubt, treat function as `too large'.

    if (pc_offset > max_size)
    {
      // We're already more than MAX_SIZE bytes away from the
      // function start: function is too large.
      return true;
    }

    // Get the function name at function start + MAX_SIZE.  If this is
    // the same name as the name at start, the function is too large.
    unsigned long pc_l = strtoul(pc.chars(), (char **)0, 0);
    unsigned long next_l = pc_l - pc_offset + max_size;
    if (next_l < pc_l)
    {
      // Overflow
      next_l = STATIC_CAST(unsigned long, -1);
    }
    string next = make_address(next_l);

    string next_func;
    int next_offset;
    get_func_at(next, next_func, next_offset);

    if (next_func == NO_GDB_ANSWER)
      return true;            // In doubt, treat function as `too large'.

    if (pc_func == next_func)
    {
      // We're still within the same function: function is too large.
      return true;
    }

    return false;
}


// Show program counter location PC
// If MODE is given, highlight PC line.
// STOPPED indicates that the program just stopped.
// SIGNALED indicates that the program just received a signal.
void SourceView::show_pc(const string& pc, XmHighlightMode mode,
                   bool stopped, bool signaled)
{
    last_shown_pc = pc;
    if (mode == XmHIGHLIGHT_SELECTED)
      last_execution_pc = pc;

    if (stopped)
    {
      at_lowest_frame = true;
      signal_received = signaled;
    }

    if (mode == XmHIGHLIGHT_SELECTED)
      undo_buffer.add_address(pc, stopped);
    else
      undo_buffer.remove_address();
    undo_buffer.add_state();

    if (!disassemble)
      return;

    // std::clog << "Showing PC " << pc << "\n";

    XmTextPosition pos = find_pc(pc);

    // While PC not found, look for code in cache
    for (int i = 0; 
       pos == XmTextPosition(-1) && i < code_cache.size(); 
       i++)
    {
      const CodeCacheEntry& cce = code_cache[i];
      if (compare_address(pc, cce.start) >= 0 
          && compare_address(pc, cce.end) <= 0)
      {
          set_code(cce.code, cce.start, cce.end);
          pos = find_pc(pc);
      }
    }

    if (pos == XmTextPosition(-1))
    {
      // PC not found in current code: disassemble location

      string start = pc;
      string end   = "";
      if (app_data.max_disassemble > 0 && 
          function_is_larger_than(pc, app_data.max_disassemble))
      {
          // Disassemble only MAX_DISASSEMBLE bytes after PC
          unsigned long pc_l = strtoul(pc.chars(), (char **)0, 0);
          unsigned long next_l = pc_l + app_data.max_disassemble;
          if (next_l < pc_l)
          {
            // Overflow
            next_l = STATIC_CAST(unsigned long, -1);
          }
          end = make_address(next_l);
      }

      string msg = "Disassembling location " + start;
      if (!end.empty())
          msg += " to " + end;

      RefreshDisassembleInfo *info = 
          new RefreshDisassembleInfo(pc, mode, msg);

      gdb_command(gdb->disassemble_command(start, end), 0,
                refresh_codeOQC, (void *)info);
      return;
    }

    if (pos == XmTextPosition(-1))
      return;

    SetInsertionPosition(code_text_w, pos + indent_amount(code_text_w));

    XmTextPosition pos_line_end = 0;
    if (!current_code.empty())
      pos_line_end = current_code.index('\n', pos) + 1;

    // Clear old selection
    if (last_start_highlight_pc)
    {
      XmTextSetHighlight (code_text_w,
                      last_start_highlight_pc, 
                      last_end_highlight_pc,
                      XmHIGHLIGHT_NORMAL);
      last_start_highlight_pc = 0;
      last_end_highlight_pc   = 0;
    }

    // Mark current line
    if (mode == XmHIGHLIGHT_SELECTED)
    {
      if (!display_glyphs)
      {
          // Set new marker
          int indent = indent_amount(code_text_w);
          static const string marker = ">";
          if (last_pos_pc)
          {
            static const string no_marker = " ";
            XmTextReplace (code_text_w,
                         last_pos_pc + indent - no_marker.length(),
                         last_pos_pc + indent,
                         XMST(no_marker.chars()));
          }

          XmTextReplace (code_text_w,
                     pos + indent - marker.length(),
                     pos + indent,
                     XMST(marker.chars()));
    
          if (pos_line_end)
          {
            XmTextSetHighlight (code_text_w,
                            pos, pos_line_end,
                            XmHIGHLIGHT_SELECTED);

            last_start_highlight_pc = pos;
            last_end_highlight_pc   = pos_line_end;
          }

          last_pos_pc = pos;
      }
    }

    if (mode == XmHIGHLIGHT_SELECTED)
      update_glyphs(code_text_w);
}

void SourceView::set_disassemble(bool set)
{
    if (disassemble != set)
    {
      disassemble = set;

      if (!disassemble)
      {
          unmanage_paned_child(code_form_w);
      }
      else
      {
          manage_paned_child(code_form_w);

          if (!last_execution_pc.empty())
            show_pc(last_execution_pc, XmHIGHLIGHT_SELECTED);
          else if (!last_shown_pc.empty())
            show_pc(last_shown_pc);
          else
            lookup(line_of_cursor());
      }
    }
}

void SourceView::set_all_registers(bool set)
{
    if (all_registers != set)
    {
      all_registers = set;

      if (all_registers_w)
          XmToggleButtonSetState(all_registers_w, (Boolean)set, False);
      if (int_registers_w)
          XmToggleButtonSetState(int_registers_w, !(Boolean)set, False);

      refresh_registers();
    }
}


// Some DBXes require `{ COMMAND; }', others `{ COMMAND }'.
string SourceView::command_list(const string& cmd)
{
    if (gdb->has_when_semicolon())
      return "{ " + cmd + "; }";
    else
      return "{ " + cmd + " }";
}

// Get the position of breakpoint NUM
string SourceView::bp_pos(int num)
{
    BreakPoint *bp = bp_map.get(num);
    if (bp == 0)
      return "";
    else
      return bp->pos();
}


// True iff we have some selection
bool SourceView::have_selection()
{
    XmTextPosition left, right;

    return (XmTextGetSelectionPosition(source_text_w, &left, &right)
          || XmTextGetSelectionPosition(code_text_w, &left, &right)) 
      && left != right;
}



//----------------------------------------------------------------------------
// Session stuff
//----------------------------------------------------------------------------

int SourceView::max_breakpoint_number = 99;

// Return DDD commands to restore current state (breakpoints, etc.)
bool SourceView::get_state(std::ostream& os)
{
    IntArray breakpoint_nrs;
    bool ok = true;

    // Restore breakpoints
    MapRef ref;
    for (BreakPoint *bp = bp_map.first(ref); bp != 0; bp = bp_map.next(ref))
      breakpoint_nrs += bp->number();

    if (breakpoint_nrs.size() > 0)
    {
      sort(breakpoint_nrs);

      // If all breakpoint numbers are less than
      // MAX_BREAKPOINT_NUMBER, insert `dummy' breakpoints that are
      // immediately deleted after creation, such that the
      // breakpoint numbers are preserved.  Otherwise, begin
      // numbering with 1.

      int max_number = breakpoint_nrs[breakpoint_nrs.size() - 1];
      bool restore_old_numbers = max_number < max_breakpoint_number;

      int num = 1;
      for (int i = 0; i < breakpoint_nrs.size(); i++)
      {
          BreakPoint *bp = bp_map.get(breakpoint_nrs[i]);
          if (restore_old_numbers)
          {
            while (num < breakpoint_nrs[i])
                ok = ok && bp->get_state(os, num++, true);
            assert(num == breakpoint_nrs[i]);
          }
          ok = ok && bp->get_state(os, num++);
      }
    }

    // Restore current cursor position
    switch (gdb->type())
    {
    case GDB:
    case PYDB:
      os << "info line " << line_of_cursor() << '\n';
      break;

    case BASH:
    case DBG:
    case DBX:
    case JDB:
    case PERL:
      break;                  // FIXME

    case XDB:
      os << "v " << line_of_cursor() << '\n';
      break;
    }

    return ok;
}

void SourceView::reset_done(const string&, void *)
{
    // All breakpoints should be deleted now -- clear all other information
    clear_file_cache();
    clear_code_cache();
    clear_dbx_lookup_cache();
    current_file_name = "";

    // Reset execution positions
    last_execution_file = "";
    last_execution_line = 0;
    last_execution_pc   = "";
    last_shown_pc       = "";

    // Reset frame info
    current_frame   = -1;
    at_lowest_frame = true;
}

void SourceView::reset()
{
    CommandGroup cg;

    bool reset_later = false;

    // Delete all breakpoints
    if (gdb->has_delete_command())
    {
      string del = gdb->delete_command();

      MapRef ref;
      int n = 0;
      for (BreakPoint *bp = bp_map.first(ref); bp != 0; 
           bp = bp_map.next(ref))
      {
          n++;
          del += " " + itostring(bp->number());
      }

      if (n > 0)
      {
          Command c(del);
          c.verbose  = false;
          c.prompt   = false;
          c.check    = true;
          c.priority = COMMAND_PRIORITY_INIT;
          c.callback = reset_done;
          gdb_command(c);

          reset_later = true;
      }
    }
    else if (gdb->has_clear_command())
    {
      MapRef ref;
      for (BreakPoint *bp = bp_map.first(ref); bp != 0; 
           bp = bp_map.next(ref))
      {
          Command c(clear_command(bp->pos()));
          c.verbose  = false;
          c.prompt   = false;
          c.check    = true;
          c.priority = COMMAND_PRIORITY_INIT;

          if (bp_map.next(ref) == 0)
          {
            // Last command
            c.callback = reset_done;
            reset_later = true;
          }
          gdb_command(c);
      }
    }

    if (!reset_later)
      reset_done("", 0);
}

Generated by  Doxygen 1.6.0   Back to index