// $Id$ -*- C++ -*-
// DDD file functions

// Copyright (C) 1996-1998 Technische Universitaet Braunschweig, Germany.
// Copyright (C) 2000-2001 Universitaet Passau, Germany.
// Copyright (C) 2001, 2003, 2004, 2005 Free Software Foundation, Inc.
// Written by Andreas Zeller <zeller@gnu.org>.
// 
// This file is part of DDD.
// 
// DDD is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 3 of the License, or (at your option) any later version.
// 
// DDD is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
// See the GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public
// License along with DDD -- see the file COPYING.
// If not, see <http://www.gnu.org/licenses/>.
// 
// DDD is the data display debugger.
// For details, see the DDD World-Wide-Web page, 
// `http://www.gnu.org/software/ddd/',
// or send a mail to the DDD developers <ddd@gnu.org>.

char file_rcsid[] = 
    "$Id$";

#include "file.h"

#include "AppData.h"
#include "GDBAgent.h"
#include "MString.h"
#include "Delay.h"
#include "DestroyCB.h"
#include "ExitCB.h"
#include "HelpCB.h"
#include "SmartC.h"
#include "SourceView.h"
#include "VarArray.h"
#include "Command.h"
#include "basename.h"
#include "cook.h"
#include "ddd.h"
#include "filetype.h"
#include "glob.h"
#include "history.h"
#include "java.h"
#include "mydialogs.h"
#include "post.h"
#include "question.h"
#include "regexps.h"
#include "shell.h"
#include "strclass.h"
#include "status.h"
#include "string-fun.h"
#include "uniquify.h"
#include "verify.h"
#include "wm.h"

#include <limits.h>
#include <string.h>		// strerror()
#include <errno.h>

#include <Xm/Xm.h>
#include <Xm/FileSB.h>
#include <Xm/List.h>
#include <Xm/SelectioB.h>
#include <Xm/MessageB.h>
#include <Xm/Text.h>
#include <Xm/RowColumn.h>
#include <Xm/TextF.h>
#include <Xm/Label.h>
#include <Xm/PushB.h>

// ANSI C++ doesn't like the XtIsRealized() macro
#ifdef XtIsRealized
#undef XtIsRealized
#endif

#if !HAVE_POPEN_DECL
extern "C" FILE *popen(const char *command, const char *mode);
#endif
#if !HAVE_PCLOSE_DECL
extern "C" int pclose(FILE *stream);
#endif


//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
// Last opened file
string open_file_reply;


//-----------------------------------------------------------------------------
// Opening files
//-----------------------------------------------------------------------------

typedef void (*FileSearchProc)(Widget fs, 
			       XmFileSelectionBoxCallbackStruct *cbs);

static WidgetArray file_filters;
static WidgetArray file_dialogs;

static string current_file_filter = "";

// Make sure that every change in one filter is reflected in all others
static void SyncFiltersCB(Widget dialog, XtPointer, XtPointer)
{
    static bool entered = false;

    if (entered)
	return;

    entered = true;

    // std::clog << "widget = " << longName(text) << "\n";

    while (dialog != 0 && !XmIsFileSelectionBox(dialog))
	dialog = XtParent(dialog);
	
    // std::clog << "dialog = " << longName(dialog) << "\n";

    Widget text = XmFileSelectionBoxGetChild(dialog, XmDIALOG_FILTER_TEXT);
    String _current_file_filter = XmTextGetString(text);
    current_file_filter = _current_file_filter;
    XtFree(_current_file_filter);

    for (int i = 0; i < file_filters.size(); i++)
    {
	if (file_dialogs[i] != dialog)
	{
	    // std::clog << "other dialog = " << longName(file_dialogs[i]) << "\n";
	    XmTextSetString(file_filters[i], XMST(current_file_filter.chars()));
	}
    }

    entered = false;
}

// Make sure that every new filter call is performed in all other
// dialogs as well
static void FilterAllCB(Widget dialog, XtPointer client_data, 
			XtPointer call_data)
{
    SyncFiltersCB(dialog, client_data, call_data);

    // std::clog << "widget = " << longName(dialog) << "\n";

    while (dialog != 0 && !XmIsFileSelectionBox(dialog))
	dialog = XtParent(dialog);
	
    // std::clog << "dialog = " << longName(dialog) << "\n";

    for (int i = 0; i < file_dialogs.size(); i++)
    {
	if (file_dialogs[i] != dialog)
	{
	    // std::clog << "other dialog = " << longName(file_dialogs[i]) << "\n";
	    XmFileSelectionDoSearch(file_dialogs[i], 0);
	}
    }
}

static void ClearStatusCB(Widget, XtPointer, XtPointer)
{
    set_status("");
}

// Create a file dialog NAME with DO_SEARCH_FILES and DO_SEARCH_DIRS
// as search procedures for files and directories, respectively, and
// OK_CALLBACK as the procedure called when a file is selected.
static Widget file_dialog(Widget w, const string& name,
			  FileSearchProc do_search_files = 0,
			  FileSearchProc do_search_dirs  = 0,
			  XtCallbackProc ok_callback     = 0)
{
    Delay delay(w);

    Arg args[10];
    int arg = 0;

    string pwd;

    arg = 0;
    if (do_search_files)
    {
	XtSetArg(args[arg], XmNfileSearchProc, do_search_files); arg++;
    }
    if (do_search_dirs)
    {
	XtSetArg(args[arg], XmNdirSearchProc, do_search_dirs); arg++;
    }

    if (remote_gdb())
    {
	static MString xmpwd;
	xmpwd = source_view->pwd();
	XtSetArg(args[arg], XmNdirectory, xmpwd.xmstring()); arg++;
    }

    Widget dialog = 
	verify(XmCreateFileSelectionDialog(w, XMST(name.chars()), args, arg));
    Delay::register_shell(dialog);

    if (ok_callback != 0)
	XtAddCallback(dialog, XmNokCallback,     ok_callback, 0);

    XtAddCallback(dialog, XmNcancelCallback, UnmanageThisCB, 
		  XtPointer(dialog));
    XtAddCallback(dialog, XmNhelpCallback,   ImmediateHelpCB, 0);

    Widget filter = XmFileSelectionBoxGetChild(dialog, XmDIALOG_FILTER_TEXT);
    file_filters += filter;
    if (!current_file_filter.empty())
	XmTextSetString(filter, XMST(current_file_filter.chars()));
    XtAddCallback(filter, XmNvalueChangedCallback, SyncFiltersCB, 0);

    Widget filter_button = 
	XmFileSelectionBoxGetChild(dialog, XmDIALOG_APPLY_BUTTON);
    XtAddCallback(filter_button, XmNactivateCallback, FilterAllCB, 0);
    XtAddCallback(dialog, XmNunmapCallback, ClearStatusCB, 0);

    file_dialogs += dialog;

    return dialog;
}

// Create various file dialogs
static Widget create_file_dialog(Widget w, const _XtString name,
				 FileSearchProc searchRemoteFiles       = 0,
				 FileSearchProc searchRemoteDirectories = 0,
				 FileSearchProc searchLocalFiles        = 0,
				 FileSearchProc searchLocalDirectories  = 0,
				 XtCallbackProc openDone = 0)
{			     
    if (remote_gdb())
	return file_dialog(find_shell(w), name,
			   searchRemoteFiles, searchRemoteDirectories, 
			   openDone);
    else if (app_data.filter_files)
	return file_dialog(find_shell(w), name,
			   searchLocalFiles, searchLocalDirectories, 
			   openDone);
    else
	return file_dialog(find_shell(w), name, 0, 0, openDone);
}


// Synchronize file dialogs with current directory
void process_cd(const string& pwd)
{
    current_file_filter = pwd + "/*";

    for (int i = 0; i < file_filters.size(); i++)
    {
	if (file_filters[i] != 0)
	{
	    XmTextSetString(file_filters[i], XMST(current_file_filter.chars()));
	    break;
	}
    }
}

static const char *delay_message = "Filtering files";

// Search for remote files and directories, using the command CMD
static void searchRemote(Widget fs,
			 XmFileSelectionBoxCallbackStruct *cbs,
			 const _XtString cmd,
			 bool search_dirs)
{
    StatusDelay delay(delay_message);

    int nitems = 0;
    int size = 256;
    XmStringTable items = 
	XmStringTable(XtMalloc(size * sizeof(XmString)));

    String mask;
    if (!XmStringGetLtoR(cbs->mask, MSTRING_DEFAULT_CHARSET, &mask))
    {
	delay.outcome = "failed";
	return;
    }
    String dir;
    if (!XmStringGetLtoR(cbs->dir, MSTRING_DEFAULT_CHARSET, &dir))
    {
	delay.outcome = "failed";
	return;
    }

    if (search_dirs)
    {
	string extra_dir = string(dir) + ".";
	items[nitems++] = 
	    XmStringCreateLtoR(XMST(extra_dir.chars()), MSTRING_DEFAULT_CHARSET);
	extra_dir = string(dir) + "..";
	items[nitems++] = 
	    XmStringCreateLtoR(XMST(extra_dir.chars()), MSTRING_DEFAULT_CHARSET);
    }

    string command = cmd;
    command.gsub("@MASK@", mask);
    command = sh_command(command);

    Agent search(command);
    search.start();

    FILE *fp = search.inputfp();
    if (fp == 0)
    {
	delay.outcome = strerror(errno);
	return;
    }

    char buf[BUFSIZ];
    while (fgets(buf, sizeof(buf), fp))
    {
	if (buf[0] && buf[strlen(buf) - 1] == '\n')
	    buf[strlen(buf) - 1] = '\0';

	if (nitems >= size)
	{
	    size += 256;
	    items = XmStringTable(XtRealloc((char *)items,
					    size * sizeof(XmString)));
	}
	    
	items[nitems++] = XmStringCreateLtoR(buf, MSTRING_DEFAULT_CHARSET);

	if (nitems == 1 || nitems % 10 == 0)
	{
	    std::ostringstream status;
	    status << delay_message << "... ("
		   << nitems << " processed)";
	    string s(status);
	    set_status(s, true);
	}
    }

    if (search_dirs)
    {
	XtVaSetValues(fs,
		      XmNdirListItems,     items,
		      XmNdirListItemCount, nitems,
		      XmNdirectoryValid,   True,
		      XmNlistUpdated,      True,
		      XtPointer(0));
    }
    else
    {
	if (nitems > 0)
	{
	    XtVaSetValues(fs,
			  XmNfileListItems,     items,
			  XmNfileListItemCount, nitems,
			  XmNdirSpec,           items[0],
			  XmNlistUpdated,       True,
			  XtPointer(0));
	}
	else
	{
	    XtVaSetValues(fs,
			  XmNfileListItems,     0,
			  XmNfileListItemCount, 0,
			  XmNlistUpdated,       True,
			  XtPointer(0));
	}
    }
}


static void searchRemoteExecFiles(Widget fs,
				  XmFileSelectionBoxCallbackStruct *cbs)
{
    searchRemote(fs, cbs, app_data.list_exec_command, false);
}

static void searchRemoteCoreFiles(Widget fs,
				  XmFileSelectionBoxCallbackStruct *cbs)
{
    searchRemote(fs, cbs, app_data.list_core_command, false);
}

static void searchRemoteSourceFiles(Widget fs,
				    XmFileSelectionBoxCallbackStruct *cbs)
{
    searchRemote(fs, cbs, app_data.list_source_command, false);
}

static void searchRemoteDirectories(Widget fs,
				    XmFileSelectionBoxCallbackStruct *cbs)
{
    searchRemote(fs, cbs, app_data.list_dir_command, true);
}

// Search for local files and directories, using the predicate IS_OKAY
static void searchLocal(Widget fs,
			XmFileSelectionBoxCallbackStruct *cbs,
			bool is_okay(const string& file_name))
{
    String mask;
    if (!XmStringGetLtoR(cbs->mask, MSTRING_DEFAULT_CHARSET, &mask))
	return;

    char **files = glob_filename(mask);
    if (files == (char **)0)
    {
	std::cerr << mask << ": glob failed\n";
    }
    else if (files == (char **)-1)
    {
	post_error(string(mask) + ": " + strerror(errno));
    }
    else
    {
	StatusDelay delay(delay_message);

	int count;
	for (count = 0; files[count] != 0; count++)
	    ;
	smart_sort(files, count);

	XmStringTable items = 
	    XmStringTable(XtMalloc(count * sizeof(XmString)));

	int nitems = 0;
	int i;
	for (i = 0; files[i] != 0; i++)
	{
	    if (is_okay(files[i]))
		items[nitems++] = XmStringCreateLtoR(files[i], 
						     MSTRING_DEFAULT_CHARSET);
	    free(files[i]);

	    int percent     = (i * 100) / count;
	    int old_percent = ((i - 1) * 100) / count;
	    if (percent % 10 == 0 && old_percent % 10 != 0)
	    {
		std::ostringstream status;
		status << delay_message << "... ("
		       << percent << "% processed)";
		string s(status);
		set_status(s, true);
	    }
	}
	free((char *)files);

	if (nitems > 0)
	{
	    XtVaSetValues(fs,
			  XmNfileListItems,     items,
			  XmNfileListItemCount, nitems,
			  XmNdirSpec,           items[0],
			  XmNlistUpdated,       True,
			  XtPointer(0));
	}

	freeXmStringTable(items, nitems);

	if (nitems > 0)
	    return;
    }

    // Error or nothing found 
    XtVaSetValues(fs,
		  XmNfileListItems,     0,
		  XmNfileListItemCount, 0,
		  XmNlistUpdated,       True,
		  XtPointer(0));
}

static void searchLocalExecFiles(Widget fs,
				 XmFileSelectionBoxCallbackStruct *cbs)
{
    switch (gdb->type())
    {
    case BASH:
	searchLocal(fs, cbs, is_bash_file);
	break;

    case DBG:
	searchLocal(fs, cbs, is_php_file);
	break;
    
    case GDB:
	searchLocal(fs, cbs, is_debuggee_file);
	break;

    case MAKE:
	searchLocal(fs, cbs, is_make_file);
	break;

    case PERL:
	searchLocal(fs, cbs, is_perl_file);
	break;

    case PYDB:
	searchLocal(fs, cbs, is_python_file);
	break;

    default:
	searchLocal(fs, cbs, is_exec_file);
    }
}

static void searchLocalCoreFiles(Widget fs,
				 XmFileSelectionBoxCallbackStruct *cbs)
{
    searchLocal(fs, cbs, is_core_file);
}

static void searchLocalSourceFiles(Widget fs,
				   XmFileSelectionBoxCallbackStruct *cbs)
{
    switch (gdb->type())
    {
    case PYDB:
	searchLocal(fs, cbs, is_python_file);
	break;

    case PERL:
	searchLocal(fs, cbs, is_perl_file);
	break;

    default:
	searchLocal(fs, cbs, is_source_file);
    }
}

// Get the file name from the file selection box W
string get_file(Widget w, XtPointer, XtPointer call_data)
{
    XmFileSelectionBoxCallbackStruct *cbs = 
	(XmFileSelectionBoxCallbackStruct *)call_data;

    String s;
    if (!XmStringGetLtoR(cbs->value, MSTRING_DEFAULT_CHARSET, &s))
	return NO_GDB_ANSWER;

    string filename = s;
    XtFree(s);

    if (filename.empty() || filename[0] != '/')
    {
	String dir;
	if (!XmStringGetLtoR(cbs->dir, MSTRING_DEFAULT_CHARSET, &dir))
	    return NO_GDB_ANSWER;

	filename = string(dir) + "/" + filename;
	XtFree(dir);
    }

    if (is_directory(filename))
    {
	MString filter(filename);
	XmFileSelectionDoSearch(w, filter.xmstring());
	return "";
    }

    return filename;
}



//-----------------------------------------------------------------------------
// OK pressed
//-----------------------------------------------------------------------------

static void open_file(const string& filename)
{
    open_file_reply = filename;

    if (gdb->type() == GDB)
    {
	// GDB does not always detach processes upon opening new
	// files, so we do it explicitly
	ProgramInfo info;
    	if (info.attached)
	    gdb_command("detach");
    }

    if (gdb_initialized)
    {
	string cmd = gdb->debug_command(filename);
	if (gdb->type() == PERL)
	    cmd.gsub("perl", string(app_data.debugger_command));

	gdb_command(cmd);
    }
}

// OK pressed in `Open File'
static void openFileDone(Widget w, XtPointer client_data, XtPointer call_data)
{
    string filename = get_file(w, client_data, call_data);
    if (filename.empty())
	return;

    XtUnmanageChild(w);

    if (filename == NO_GDB_ANSWER)
	return;

    open_file(filename);
}


// OK pressed in `Open Core'
static void openCoreDone(Widget w, XtPointer client_data, XtPointer call_data)
{
    string corefile = get_file(w, client_data, call_data);
    if (corefile.empty())
	return;

    ProgramInfo info;

    XtUnmanageChild(w);

    if (corefile != NO_GDB_ANSWER)
    {
	switch(gdb->type())
	{
	case GDB:
	    gdb_command("core-file " + gdb->quote_file(corefile));
	    break;

	case DBX:
	    if (info.file != NO_GDB_ANSWER && !info.file.empty())
		gdb_command(gdb->debug_command(info.file) + " " + 
			    gdb->quote_file(info.core));
	    else
		post_error("No program.", "no_program", w);
	    break;

	case BASH:
	case DBG:
	case JDB:
	case MAKE:
	case PERL:
	case PYDB:
	case XDB:
	    break;		// FIXME
	}
    }
}

// OK pressed in `Open Source'
static void openSourceDone(Widget w, XtPointer client_data, 
			   XtPointer call_data)
{
    string filename = get_file(w, client_data, call_data);
    if (filename.empty())
	return;

    XtUnmanageChild(w);
    set_status("");

    // For PYDB, issue a 'file filename' command
    if (gdb->type() == PYDB)
	gdb_command(gdb->debug_command(filename));

    if (filename != NO_GDB_ANSWER)
	source_view->read_file(filename);
}


//-----------------------------------------------------------------------------
// Program Info
//-----------------------------------------------------------------------------

// Get information on current debuggee
ProgramInfo::ProgramInfo()
    : file(NO_GDB_ANSWER),
      core(NO_GDB_ANSWER),
      pid(0),
      attached(false),
      running(false),
      state()
{
    if (!source_view) {
	post_error("Cannot create ProgramInfo, no source view.",
		   "no_source_error");
	return;
    }
    if (!gdb) {
	post_error("Cannot create ProgramInfo, no debugger agent.\n",
		   "gdb_io_error");
	return;
    }
    if (source_view->have_exec_pos())
    {
	state = "has stopped";
	running = true;
    }
    else
    {
	state = "is not being run";
	running = false;
    }

    switch(gdb->type())
    {
    case GDB:
    case PYDB:
    case DBG:
    {
    	string ans;

    	if (gdb->is_windriver_gdb())
	{
	    // Windriver GDB (VxWorks).

	    // VxWorks allows multiple dynamically relocatable
	    // programs to be loaded simultaneously. Before a program
	    // is run, the 'info source' command can report
	    // information from a source file not related to the
	    // program just downloaded for debugging. (The WindRiver
	    // version of gdb does not support the 'info files'
	    // command.)

	    // In order to tell DDD that there is indeed a source file
	    // available, we need to first see if a program has
	    // already been started. If so, then the 'info source'
	    // command can be used.

	    // Otherwise, the 'info sources' command is used, and the
	    // first file in the list is used, assuming it is related
	    // to the current program that was downloaded.

	    // See if the program has been run first
	    ans = gdb_question("info frame");
	    if (ans == NO_GDB_ANSWER)
		break;

	    file = "";
	    if (ans.contains("No stack"))
	    {
		// Then try using info sources and use first file listed
		ans = gdb_question("info sources");
		if (ans == NO_GDB_ANSWER)
		    break;

		if (ans.contains("Source files for which "
				 "symbols have been read in:"))
		{
		    file = ans.after("\n");
		    file = file.before(" ");
		    file = unquote(file);
		}
	    }
	    else
	    {
	    	// Try using `info source'.
		ans = gdb_question("info source");
		if (ans == NO_GDB_ANSWER)
		    break;

		if (ans.contains("Current source file is "))
		{
		    file = ans.after("Current source file is ");
		    file = file.before(".\n");
		    file = unquote(file);
		}
	    }
	}
	else
	{
	    // Ordinary GDB.
	    ans = gdb_question("info files");
	    if (ans == NO_GDB_ANSWER)
		break;

	    file = "";
	    if (ans.contains("Symbols from "))
	    {
		file = ans.after("Symbols from ");
		file = file.before(".\n");
		file = unquote(file);
	    }

	}
	core = "";
	if (ans.contains("core dump"))
	{
	    core = ans.after("core dump");
	    core = core.after('`');
	    core = core.before("',");
	}

	if (ans.contains("process "))
	{
	    string p = ans.after("process ");
	    pid = atoi(p.chars());
	}

	attached = ans.contains("attached process ");

	ans = gdb_question("info program");
	if (ans == NO_GDB_ANSWER)
	    break;

	if (ans.contains("not being run"))
	    running = false;
	else if (ans.contains("\nIt stopped "))
	{
	    state = ans.from("\nIt stopped ");
	    state = "has " + state.after("\nIt ");
	    state = state.before('.');
	    running = true;
	}
	break;
    }

    case DBX:
    {
	if (gdb->is_ladebug())
	{
	    string ans = gdb_question("show process");
	    // typical answers:
	    // 1:no process 
	    // There are no processes being debugged.
	    // 2: process has been paused.
	    // Current Process: localhost:26177 (a.out) paused.
	    // 3: process terminated
	    // Current Process: localhost:13614 (ddd) terminated.
	    // TODO: treat the terminated case 
     
	    if (ans != NO_GDB_ANSWER && 
		!ans.contains("no processes being debugged"))
	    {
		if (ans.contains("Current Process: "))
		{
		    ans = ans.after(": ");
		    {
			string p = ans.after(":");
			p = p.before(" ");
			pid = atoi(p.chars());
		    }
		    ans = ans.after(" (");
		    file = ans.before(")");

		    ans = ans.after(") ");
		    running = ans.contains("paused");
		    // ladebug always creates a process.
		    // It may be "loaded" or attached.
		    attached = true;
		}
	    }	    
	}

	// AD TODO: shouldn't it be an "else" ?

	string ans = gdb_question(gdb->debug_command());
	if (ans != NO_GDB_ANSWER)
	{
	    if (ans.contains("Current givenfile is ", 0))
		ans = ans.after("Current givenfile is ");
	    else if (ans.contains("Debugging: ", 0))
		ans = ans.after(": ");

	    strip_space(ans);
	    if (!ans.contains(' ')) // Sanity check
		file = ans;
	}
	break;
    }

    case XDB:
	break;			// FIXME

    case PERL:
	// Use the program we were invoked with
	file = gdb->program();
	if (file.matches(rxint))
	    file = "";		// Invoked with a constant expression

	if (file.empty())
	{
	    // Not invoked with a program?  Use the current file, then.
	    file = source_view->file_of_cursor();
	    file = file.before(":");
	}
	core = "";
	break;

    case BASH:
	// Use the program we were invoked with
	file = gdb->program();
	if (file.matches(rxint))
	    file = "";		// Invoked with a constant expression

	if (file.empty())
	{
	    // Not invoked with a program?  Use the current file, then.
	    file = source_view->file_of_cursor();
	    file = file.before(":");
	}
	core = "";
	break;

    case MAKE:
	// Use the program we were invoked with
	file = gdb->program();
	if (file.matches(rxint))
	    file = "";		// Invoked with a constant expression

	if (file.empty())
	{
	    // Not invoked with a program?  Use the current file, then.
	    file = source_view->file_of_cursor();
	    file = file.before(":");
	}
	core = "";
	break;

    case JDB:
	// Just use the current class.
	file = source_view->line_of_cursor();
	file = file.before(":");
	core = "";

	// Save whether JDB's VM is running
	static int last_jdb_pid = -1;
	static bool jvm_running = false;

	if (gdb->pid() != last_jdb_pid)
	{
	    // New JDB: reset info
	    jvm_running = false;
	    last_jdb_pid = gdb->pid();
	}

	// The VM is running iff the prompt contains a backtrace
	// level ("[n]").
	if (!jvm_running && gdb->prompt().contains("["))
	    jvm_running = true;

	running = jvm_running;
	break;
    }

    if (file == NO_GDB_ANSWER)
    {
	// As a fallback, get core file and executable from argument list.
	// Works only on local file system and is more a guess.
	char **argv = saved_argv();
	int argc = 0;
	while (argv[argc] != 0)
	    argc++;

	for (int i = argc - 1; i > 0 && file == NO_GDB_ANSWER; i--)
	{
	    // All debuggers supported by DDD have [EXEC [CORE]] as
	    // their last arguments.
	    string arg = argv[i];
	    if (is_core_file(arg))
		core = arg;
	    else if (is_debuggee_file(arg))
		file = arg;
	}
    }

    if (file != NO_GDB_ANSWER)
	add_to_recent(file);
}


//-----------------------------------------------------------------------------
// Processes
//-----------------------------------------------------------------------------

// Process selection
static int ps_pid_index = 0;

// Retrieve PID from PS output
static int ps_pid(const string& line)
{
    const char *s = line.chars() + ps_pid_index;
    while (s > line.chars() && isdigit(s[-1]))
	--s;

    return atoi(s);
}

// Fill the pids in DISP_NRS
static void getPIDs(Widget selectionList, IntArray& disp_nrs)
{
    static const IntArray empty;
    disp_nrs = empty;

    XmStringTable selected_items;
    int selected_items_count = 0;

    assert(XmIsList(selectionList));

    XtVaGetValues(selectionList,
		  XmNselectedItemCount, &selected_items_count,
		  XmNselectedItems, &selected_items,
		  XtPointer(0));

    for (int i = 0; i < selected_items_count; i++)
    {
	String _item;
	XmStringGetLtoR(selected_items[i], LIST_CHARSET, &_item);
	string item(_item);
	XtFree(_item);

	int p = ps_pid(item);
	if (p > 0)
	    disp_nrs += p;
    }
}

// Get the PID from the selection list in CLIENT_DATA
static int get_pid(Widget, XtPointer client_data, XtPointer)
{
    IntArray pids;
    Widget processes = Widget(client_data);
    if (processes != 0)
	getPIDs(processes, pids);

    if (pids.size() == 1)
	return pids[0];
    else
	return 0;
}

// Process selection

static void sortProcesses(StringArray& 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++)
	{
	    string v = a[i];
	    int j;
	    for (j = i; j >= h && ps_pid(a[j - h]) > ps_pid(v); j -= h)
		a[j] = a[j - h];
	    if (i != j)
		a[j] = v;
	}
    } while (h != 1);
}


inline bool is_separator(char c)
{
    return c == ' ' || c == '\'' || c == '\"';
}

// Check whether LINE is a valid PS line.  Exclude occurrences of PS_COMMAND.
static bool valid_ps_line(const string& line, const string& ps_command)
{
    int pid = ps_pid(line);
    if (pid == 0)
	return false;		// No PID

    // You don't want to debug DDD, don't you?
    if (!remote_gdb() && pid == getpid())
	return false;

    // Neither should you debug GDB by itself.
    if (pid == gdb->pid())
	return false;

    // Don't issue lines containing `ps' (or whatever the first word
    // in PS_COMMAND is).
    string ps = ps_command;
    if (ps.contains(' '))
	ps = ps.before(' ');
    ps = basename(ps.chars());
    int index = line.index(ps);
    if (index > 0
	&& (line[index - 1] == '/' || is_separator(line[index - 1]))
	&& (line.length() == index + ps.length()
	    || is_separator(line[index + ps.length()])))
	return false;

    // Okay, just leave it
    return true;
}


// Create list of processes
static void update_processes(Widget processes, bool keep_selection)
{
    StatusDelay delay("Getting list of processes");

    string cmd = sh_command(app_data.ps_command) + " 2>&1";
    FILE *fp = popen(cmd.chars(), "r");
    if (fp == 0)
    {
	delay.outcome = strerror(errno);
	return;
    }

    StringArray all_process_list;
    int c;
    string line = "";
    bool first_line = true;

    while ((c = getc(fp)) != EOF)
    {
	if (c == '\n')
	{
	    if (first_line || valid_ps_line(line, app_data.ps_command))
		all_process_list += line;
#if 0
	    else
		std::clog << "Excluded: " << line << "\n";
#endif

	    if (first_line)
	    {
		// Find first occurrence of `PID' title
		ps_pid_index = line.index(" PID ");
		if (ps_pid_index < 0)
		    ps_pid_index = 0;
	    }

	    line = "";
	    first_line = false;
	}
	else
	{
	    line += c;
	}
    }

    pclose(fp);
    sortProcesses(all_process_list);
    DynIntArray pids(all_process_list.size());

    // If GDB cannot send a signal to the process, we cannot debug it.
    // Try a `kill -0' (via GDB, as it may be setuid) and filter out
    // all processes in the `kill' diagnostic -- that is, all
    // processes that `kill' could not send a signal.
    string kill = "kill -0";

    if (gdb->has_handler_command())
	kill = "/usr/bin/kill -0"; // Bypass built-in SUN DBX command

    int i;
    for (i = 0; i < all_process_list.size(); i++)
    {
	pids[i] = ps_pid(all_process_list[i]);
	if (pids[i])
	    kill += string(" ") + itostring(pids[i]);
    }
#if defined(__sun) 
    // bypass underlying debugger
    // Fix for Sun: use /usr/bin/kill
    string kill_result;
    {
      std::ostringstream os;
      kill += " 2>&1";
      FILE *fp = popen(kill.chars(), "r");
      if (fp != 0)
      {
	  int c;
	  while ((c = getc(fp)) != EOF)
	  {
	      os << (char)c;
	  }
	  pclose(fp);
      }
      kill_result = os;
    }
#else
    string kill_result = gdb_question(gdb->shell_command(kill));
#endif
    i = 0;
    while (i >= 0)
    {
	i = kill_result.index(rxint, i);
	if (i >= 0)
	{
	    int bad_pid = atoi(kill_result.chars() + i);
	    for (int k = 0; k < all_process_list.size(); k++)
	    {
		if (pids[k] != 0 && pids[k] == bad_pid)
		{
#if 0
		    std::clog << "Excluded: " << all_process_list[k] << "\n";
#endif
		    all_process_list[k] = NO_GDB_ANSWER;
		}
	    }
	    i++;
	}
    }

    StringArray process_list;
    for (i = 0; i < all_process_list.size(); i++)
	if (all_process_list[i] != NO_GDB_ANSWER)
	    process_list += all_process_list[i];

    // Now set the selection.
    bool *selected = new bool[process_list.size()];
    for (i = 0; i < process_list.size(); i++)
	selected[i] = false;

    int pos = -1;
    if (keep_selection)
    {
	// Preserve old selection: each PID selected before will also be
	// selected after.
	IntArray selection;
	getPIDs(processes, selection);

	for (i = 0; i < selection.size(); i++)
	{
	    for (int j = 0; j < process_list.size(); j++)
		if (selection[i] == ps_pid(process_list[j]))
		{
		    if (pos < 0)
			pos = j;
		    selected[j] = true;
		}
	}
    }

    if (pos < 0)
    {
	// Create new selection from current file and current pid.
	ProgramInfo info;

	// Check for current pid; if found, highlight it.
	for (i = 0; pos < 0 && i < process_list.size(); i++)
	{
	    if (info.pid != 0 && ps_pid(process_list[i]) == info.pid)
		pos = i;
	}

	if (pos < 0)
	{
	    // Not found? Try leftmost occurrence of process base name.
	    string current_base = basename(info.file.chars());
	    int leftmost = INT_MAX;
	    for (i = 0; i < process_list.size(); i++)
	    {
		int occurrence = process_list[i].index(current_base);
		if (occurrence >= 0 && occurrence < leftmost 
		    && ps_pid(process_list[i]) > 0)
		{
		    leftmost = occurrence;
		    pos = i;
		}
	    }
	}
    }

    if (pos >= 0)
	selected[pos] = true;

    setLabelList(processes, process_list.values(),
		 selected, process_list.size(), true, false);

    if (pos >= 0)
	ListSetAndSelectPos(processes, pos + 1);

    delete[] selected;
}


static void gdbUpdateProcessesCB(Widget, XtPointer client_data, XtPointer)
{
    Widget processes = Widget(client_data);
    update_processes(processes, true);
}

// Select a process
static void SelectProcessCB(Widget w, XtPointer client_data, 
			    XtPointer call_data)
{
    XmListCallbackStruct *cbs = (XmListCallbackStruct *)call_data;
    int pos = cbs->item_position;
    if (pos == 1)
	XmListDeselectAllItems(w); // Title selected
    else
	ListSetAndSelectPos(w, pos);

    int pid = get_pid(w, client_data, call_data);
    if (pid <= 0)
	set_status("");
    else
	set_status("Process " + itostring(pid));
}


// OK pressed in `Open Process'
static void openProcessDone(Widget w, XtPointer client_data, 
			    XtPointer call_data)
{
    int pid = get_pid(w, client_data, call_data);
    if (pid <= 0)
    {
	gdbUpdateProcessesCB(w, client_data, call_data);	
	return;
    }

    XtUnmanageChild(w);

    ProgramInfo info;

    if (pid == info.pid)
    {
	set_status("Already attached to process " + itostring(pid) + ".");
	return;
    }

    if (info.file == NO_GDB_ANSWER || info.file.empty())
    {
	post_error("No program.", "no_program", w);
	return;
    }

    // GDB does not always detach processes upon opening new
    // files, so we do it explicitly
    if (info.attached)
	gdb_command(gdb->detach_command(info.pid));

    // Attach to new process
    gdb_command(gdb->attach_command(pid, info.file));
}

// When W is to be destroyed, remove all references in Widget(CLIENT_DATA)
static void RemoveCallbacksCB(Widget w, XtPointer client_data, XtPointer)
{
    Widget ref = Widget(client_data);
    XtRemoveCallback(ref, XmNokCallback,      UnmanageThisCB, XtPointer(w));
    XtRemoveCallback(ref, XmNcancelCallback,  UnmanageThisCB, XtPointer(w));
    XtRemoveCallback(ref, XmNdestroyCallback, RemoveCallbacksCB, XtPointer(w));
}

// If we don't have a current executable, issue a warning.
static void warn_if_no_program(Widget popdown)
{
    ProgramInfo info;

    if (info.file.empty())
    {
	Widget warning = post_warning("Please open a program first.", 
				      "no_program", popdown);

	if (popdown != 0 && warning != 0)
	{
	    // Tie the warning to the dialog - if one is popped down,
	    // so is the other.
	    XtAddCallback(warning, XmNokCallback, 
			  UnmanageThisCB, XtPointer(popdown));
	    XtAddCallback(warning, XmNcancelCallback, 
			  UnmanageThisCB, XtPointer(popdown));
	    XtAddCallback(popdown, XmNdestroyCallback,
			  RemoveCallbacksCB, XtPointer(warning));

	    XtAddCallback(popdown, XmNokCallback,
			  UnmanageThisCB, XtPointer(warning));
	    XtAddCallback(popdown, XmNcancelCallback,
			  UnmanageThisCB, XtPointer(warning));
	    XtAddCallback(warning, XmNdestroyCallback,
			  RemoveCallbacksCB, XtPointer(popdown));
	}
    }
}



//-----------------------------------------------------------------------------
// Helpers for class and source selection
//-----------------------------------------------------------------------------

// Get the selected item ids
static void get_items(Widget selectionList, StringArray& itemids)
{
    static const StringArray empty;
    itemids = empty;

    XmStringTable selected_items;
    int selected_items_count = 0;

    assert(XmIsList(selectionList));

    XtVaGetValues(selectionList,
		  XmNselectedItemCount, &selected_items_count,
		  XmNselectedItems, &selected_items,
		  XtPointer(0));

    for (int i = 0; i < selected_items_count; i++)
    {
	String _item;
	XmStringGetLtoR(selected_items[i], LIST_CHARSET, &_item);
	string item(_item);
	XtFree(_item);

	itemids += item;
    }
}

// Get the item from the selection list in CLIENT_DATA
static string get_item(Widget, XtPointer client_data, XtPointer)
{
    StringArray itemids;
    Widget items = Widget(client_data);
    if (items != 0)
	get_items(items, itemids);

    if (itemids.size() == 1)
	return itemids[0];

    return "";
}


//-----------------------------------------------------------------------------
// Classes (JDB only)
//-----------------------------------------------------------------------------

// Select a class
static void SelectClassCB(Widget w, XtPointer client_data, 
			  XtPointer call_data)
{
    XmListCallbackStruct *cbs = (XmListCallbackStruct *)call_data;
    int pos = cbs->item_position;
    ListSetAndSelectPos(w, pos);

    string cls = get_item(w, client_data, call_data);
    if (cls.empty())
	set_status("");
    else
	set_status(source_view->full_path(java_class_file(cls)));
}

static void update_classes(Widget classes)
{
    StatusDelay delay("Getting list of classes");
    StringArray classes_list;
    get_java_classes(classes_list);

    // Now set the selection.
    bool *selected = new bool[classes_list.size()];
    for (int i = 0; i < classes_list.size(); i++)
	selected[i] = false;

    setLabelList(classes, classes_list.values(),
		 selected, classes_list.size(), false, false);

    delete[] selected;
}

static void gdbUpdateClassesCB(Widget, XtPointer client_data, XtPointer)
{
    Widget classes = Widget(client_data);
    update_classes(classes);
}

// OK pressed in `Open Class'
static void openClassDone(Widget w, XtPointer client_data, 
			  XtPointer call_data)
{
    string cls = get_item(w, client_data, call_data);
    if (cls.empty())
    {
	gdbUpdateClassesCB(w, client_data, call_data);	
	return;
    }

    XtUnmanageChild(w);

    gdb_command(gdb->debug_command(cls));
}


//-----------------------------------------------------------------------------
// Lookup sources and functions (GDB only)
//-----------------------------------------------------------------------------

static StringArray all_sources;

// Select a source; show the full path name in the status line
static void SelectSourceCB(Widget w, XtPointer, XtPointer call_data)
{
    XmListCallbackStruct *cbs = (XmListCallbackStruct *)call_data;
    int pos = cbs->item_position;
    ListSetAndSelectPos(w, pos);

    pos--;
    if (pos < 0)
	pos = all_sources.size() - 1;
    set_status(all_sources[pos]);
}

// Get list of sources into SOURCES_LIST
void get_gdb_sources(StringArray& sources_list)
{
    static const StringArray empty;
    sources_list = empty;

    string ans = gdb_question("info sources");
    if (ans != NO_GDB_ANSWER)
    {
	// Create a newline-separated list of sources
	string new_ans;
	while (!ans.empty())
	{
	    string line = ans.before('\n');
	    ans = ans.after('\n');

	    if (line.empty() || line.contains(':', -1))
		continue;

	    line.gsub(", ", "\n");
	    new_ans += line + '\n';
	}

	ans = new_ans;
	while (!ans.empty())
	{
	    string line = ans.before('\n');
	    ans = ans.after('\n');
	    
	    sources_list += line;
	}

	smart_sort(sources_list);
	uniq(sources_list);
    }
}

// Remove adjacent duplicates in A1
static void uniq(StringArray& a1, StringArray& a2)
{
    StringArray b1;
    StringArray b2;

    for (int i = 0; i < a1.size(); i++)
    {
	if (i == 0 || a1[i - 1] != a1[i])
	{
	    b1 += a1[i];
	    b2 += a2[i];
	}
    }
    
    a1 = b1;
    a2 = b2;
}

// Sort A1 and A2 according to the values in A1
static void sort(StringArray& a1, StringArray& a2)
{
    assert(a1.size() == a2.size());

    // Shell sort -- simple and fast
    int h = 1;
    do {
	h = h * 3 + 1;
    } while (h <= a1.size());
    do {
	h /= 3;
	for (int i = h; i < a1.size(); i++)
	{
	    string v1 = a1[i];
	    string v2 = a2[i];
	    int j;
	    for (j = i; j >= h && smart_compare(a1[j - h], v1) > 0; j -= h)
	    {
		a1[j] = a1[j - h];
		a2[j] = a2[j - h];
	    }
	    if (i != j)
	    {
		a1[j] = v1;
		a2[j] = v2;
	    }
	}
    } while (h != 1);
}

static void filter_sources(StringArray& labels, StringArray& sources,
			   const string& pattern)
{
    assert(labels.size() == sources.size());

    StringArray new_labels;
    StringArray new_sources;

    for (int i = 0; i < labels.size(); i++)
    {
	if (glob_match(pattern.chars(), labels[i].chars(), 0) ||
	    glob_match(pattern.chars(), sources[i].chars(), 0))
	{
	    new_labels  += labels[i];
	    new_sources += sources[i];
	}
    }

    labels  = new_labels;
    sources = new_sources;
}

static void update_sources(Widget sources, Widget filter)
{
    StatusDelay delay("Getting sources");
    get_gdb_sources(all_sources);

    String pattern_s = XmTextFieldGetString(filter);
    string pattern = pattern_s;
    XtFree(pattern_s);

    strip_space(pattern);
    if (pattern.empty())
	pattern = "*";
    XmTextFieldSetString(filter, XMST(pattern.chars()));

    StringArray labels;
    uniquify(all_sources, labels);

    // Sort and remove duplicates
    sort(labels, all_sources);
    uniq(labels, all_sources);

    // Filter pattern
    filter_sources(labels, all_sources, pattern);

    // Now set the selection.
    bool *selected = new bool[labels.size()];
    for (int i = 0; i < labels.size(); i++)
	selected[i] = false;

    setLabelList(sources, labels.values(),
		 selected, labels.size(), false, false);

    delete[] selected;
}

// OK pressed in `Lookup Source'
static void lookupSourceDone(Widget w,
			     XtPointer client_data, 
			     XtPointer call_data)
{
    Widget sources = Widget(client_data);
    XmSelectionBoxCallbackStruct *cbs = 
	(XmSelectionBoxCallbackStruct *)call_data;

    set_status("");

    string source = get_item(w, client_data, call_data);

    if (source.contains('/'))
    {
	// Expand to full path name
	int *position_list = 0;
	int position_count = 0;
	if (XmListGetSelectedPos(sources, &position_list, &position_count))
	{
	    if (position_count == 1)
	    {
		int pos = position_list[0];
		pos--;
		if (pos < 0)
		    pos = all_sources.size() - 1;
		source = all_sources[pos];
	    }

	    XtFree((char *)position_list);
	}
    }

    if (!source.empty())
    {
	source_view->lookup(source + ":1");

	if (cbs != 0 && 
	    cbs->reason != XmCR_APPLY && 
	    cbs->reason != XmCR_ACTIVATE)
	{
	    Widget scroll = XtParent(sources);
	    Widget dialog = XtParent(scroll);
	    XtUnmanageChild(dialog);
	}
    }
}

static void open_source_msg()
{
    set_status("Open Source is an idea whose time has finally come.  "
	       "See http://www.opensource.org/.");
}



//-----------------------------------------------------------------------------
// Entry Points
//-----------------------------------------------------------------------------

void gdbOpenFileCB(Widget w, XtPointer, XtPointer)
{
    static Widget dialog = 
	create_file_dialog(w, "exec_files", 
			   searchRemoteExecFiles, 
			   searchRemoteDirectories,
			   searchLocalExecFiles, 0,
			   openFileDone);
    manage_and_raise(dialog);
}

void gdbOpenRecentCB(Widget, XtPointer client_data, XtPointer)
{
    int index = ((int)(long)client_data) - 1;

    StringArray recent_files;
    get_recent(recent_files);

    if (index >= 0 && index < recent_files.size())
    {
	string file = recent_files[index];
	open_file(file);
	// This is a kludge as I don't [yet] understand how to force the
	// reading of the source file automatically, as is done when an
	// compiled executable is opened.
	if (gdb->type() == PYDB)
	    source_view->read_file(file);
    }
}

void gdbOpenCoreCB(Widget w, XtPointer, XtPointer)
{
    static Widget dialog = 
	create_file_dialog(w, "core_files", 
			   searchRemoteCoreFiles, searchRemoteDirectories,
			   searchLocalCoreFiles, 0,
			   openCoreDone);
    manage_and_raise(dialog);
    warn_if_no_program(dialog);
}

void gdbOpenSourceCB(Widget w, XtPointer, XtPointer)
{
    static Widget dialog = 
	create_file_dialog(w, "source_files", 
			   searchRemoteSourceFiles, searchRemoteDirectories,
			   searchLocalSourceFiles, 0,
			   openSourceDone);
    manage_and_raise(dialog);

    open_source_msg();

    if ((gdb->type() != JDB) && (gdb->type() != PYDB))
    {
	warn_if_no_program(dialog);
    }
    else
    {
	// JDB works well without executable
	// PYDB doesn't use an executable
    }
}

void gdbOpenProcessCB(Widget w, XtPointer, XtPointer)
{
    static Widget dialog = 0;
    static Widget processes = 0;

    if (dialog == 0)
    {
	Arg args[10];
	int arg = 0;
    
	XtSetArg(args[arg], XmNautoUnmanage, False); arg++;
	dialog = verify(XmCreateSelectionDialog(find_shell(w), 
						XMST("processes"), 
						args, arg));

	Delay::register_shell(dialog);

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

	processes = XmSelectionBoxGetChild(dialog, XmDIALOG_LIST);

	XtAddCallback(processes, XmNsingleSelectionCallback,
		      SelectProcessCB, XtPointer(processes));
	XtAddCallback(processes, XmNmultipleSelectionCallback,
		      SelectProcessCB, XtPointer(processes));
	XtAddCallback(processes, XmNextendedSelectionCallback,
		      SelectProcessCB, XtPointer(processes));
	XtAddCallback(processes, XmNbrowseSelectionCallback,
		      SelectProcessCB, XtPointer(processes));

	XtAddCallback(dialog, XmNokCallback, 
		      openProcessDone, XtPointer(processes));
	XtAddCallback(dialog, XmNapplyCallback, 
		      gdbUpdateProcessesCB, XtPointer(processes));
	XtAddCallback(dialog, XmNcancelCallback, 
		      UnmanageThisCB, XtPointer(dialog));
	XtAddCallback(dialog, XmNhelpCallback, ImmediateHelpCB, 0);
    }

    update_processes(processes, false);
    manage_and_raise(dialog);
    warn_if_no_program(dialog);
}

void gdbOpenClassCB(Widget w, XtPointer, XtPointer)
{
    static Widget dialog = 0;
    static Widget classes = 0;

    if (dialog == 0)
    {
	Arg args[10];
	int arg = 0;
    
	XtSetArg(args[arg], XmNautoUnmanage, False); arg++;
	dialog = verify(XmCreateSelectionDialog(find_shell(w), 
						XMST("classes"), args, arg));

	Delay::register_shell(dialog);

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

	classes = XmSelectionBoxGetChild(dialog, XmDIALOG_LIST);

	XtAddCallback(classes, XmNsingleSelectionCallback,
		      SelectClassCB, XtPointer(classes));
	XtAddCallback(classes, XmNmultipleSelectionCallback,
		      SelectClassCB, XtPointer(classes));
	XtAddCallback(classes, XmNextendedSelectionCallback,
		      SelectClassCB, XtPointer(classes));
	XtAddCallback(classes, XmNbrowseSelectionCallback,
		      SelectClassCB, XtPointer(classes));

	XtAddCallback(dialog, XmNokCallback, 
		      openClassDone, XtPointer(classes));
	XtAddCallback(dialog, XmNapplyCallback, 
		      gdbUpdateClassesCB, XtPointer(classes));
	XtAddCallback(dialog, XmNcancelCallback, 
		      UnmanageThisCB, XtPointer(dialog));
	XtAddCallback(dialog, XmNhelpCallback, ImmediateHelpCB, 0);
    }

    update_classes(classes);
    manage_and_raise(dialog);
}

static Widget source_list   = 0;
static Widget source_filter = 0;

void update_sources()
{
    if (source_list != 0)
	update_sources(source_list, source_filter);
}

static void FilterSourcesCB(Widget, XtPointer, XtPointer)
{
    update_sources();
}

static void LoadSharedLibrariesCB(Widget, XtPointer, XtPointer)
{
    StatusDelay delay("Loading shared object library symbols");
    
    gdb_question("sharedlibrary");
    update_sources();
}

void gdbLookupSourceCB(Widget w, XtPointer client_data, XtPointer call_data)
{
    if (gdb->type() != GDB)
    {
	gdbOpenSourceCB(w, client_data, call_data);
	return;
    }

    static Widget dialog  = 0;

    if (dialog == 0)
    {
	Arg args[10];
	int arg = 0;
    
	XtSetArg(args[arg], XmNautoUnmanage, False); arg++;
#if XmVersion >= 1002
	XtSetArg(args[arg], XmNchildPlacement, XmPLACE_TOP); arg++;
#endif
	dialog = verify(XmCreateSelectionDialog(find_shell(w), 
						XMST("sources"), args, arg));

	Delay::register_shell(dialog);

	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++;
	XtSetArg(args[arg], XmNadjustMargin,    False); arg++;
	XtSetArg(args[arg], XmNshadowThickness, 0);     arg++;
	XtSetArg(args[arg], XmNspacing,         0);     arg++;
	Widget bigbox = XmCreateRowColumn(dialog, XMST("bigbox"), args, arg);
	XtManageChild(bigbox);

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

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

	arg = 0;
	source_filter = XmCreateTextField(box, XMST("filter"), args, arg);
	XtManageChild(source_filter);

	arg = 0;
	Widget sharedlibrary = 
	    XmCreatePushButton(bigbox, XMST("sharedlibrary"), args, arg);
	XtManageChild(sharedlibrary);

#if XmVersion >= 1002
	arg = 0;
	Widget lookup = XmCreatePushButton(dialog, 
					   XMST("lookup"), args, arg);
	XtManageChild(lookup);
#endif

	source_list = XmSelectionBoxGetChild(dialog, XmDIALOG_LIST);

	XtAddCallback(source_list, XmNsingleSelectionCallback,
		      SelectSourceCB, XtPointer(source_list));
	XtAddCallback(source_list, XmNmultipleSelectionCallback,
		      SelectSourceCB, XtPointer(source_list));
	XtAddCallback(source_list, XmNextendedSelectionCallback,
		      SelectSourceCB, XtPointer(source_list));
	XtAddCallback(source_list, XmNbrowseSelectionCallback,
		      SelectSourceCB, XtPointer(source_list));

	XtAddCallback(dialog, XmNokCallback, 
		      lookupSourceDone, XtPointer(source_list));
	XtAddCallback(dialog, XmNapplyCallback, FilterSourcesCB, 0);
	XtAddCallback(dialog, XmNcancelCallback, 
		      UnmanageThisCB, XtPointer(dialog));
	XtAddCallback(dialog, XmNunmapCallback, ClearStatusCB, 0);
	XtAddCallback(dialog, XmNhelpCallback, ImmediateHelpCB, 0);

	XtAddCallback(source_filter, XmNactivateCallback, 
		      FilterSourcesCB, 0);
	XtAddCallback(sharedlibrary, XmNactivateCallback, 
		      LoadSharedLibrariesCB, 0);

#if XmVersion >= 1002
	XtAddCallback(lookup, XmNactivateCallback, 
		      lookupSourceDone, XtPointer(source_list));
#endif
    }

    update_sources(source_list, source_filter);

    open_source_msg();
    manage_and_raise(dialog);
    warn_if_no_program(dialog);
}
