v20i028: ivinfo - InterViews emacs info file browser in C++, Part03/04
Tom Horsley
tom at hcx2.ssd.csd.harris.com
Thu May 30 12:35:34 AEST 1991
Submitted-by: Tom Horsley <tom at hcx2.ssd.csd.harris.com>
Posting-number: Volume 20, Issue 28
Archive-name: ivinfo/part03
#! /bin/sh
# This is a shell archive. Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file". To overwrite existing
# files, type "sh file -c". You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g.. If this archive is complete, you
# will see the following message at the end:
# "End of archive 3 (of 4)."
# Contents: filename.c ivinfo.c
# Wrapped by tom at hcx2 on Wed May 29 10:48:52 1991
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'filename.c' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'filename.c'\"
else
echo shar: Extracting \"'filename.c'\" \(17247 characters\)
sed "s/^X//" >'filename.c' <<'END_OF_FILE'
X// Implementation of the filename class. If you think this ought to be
X// simple, look through the different cases the Cannonize routine has to
X// deal with!
X
X#include <stdio.h>
X#include <string.h>
X
X#include "filename.h"
X
X//************************************************************ Static Variables
X
X// Certain common file name components are always identified by these
X// constant strings (this simplifies equality tests for special components).
Xstatic const char * slash = "/";
Xstatic const char * dot = ".";
Xstatic const char * dotdot = "..";
X
X//************************************************************ Static Functions
X
X// In order to deal with all the idosyncratic nonsense that a typical *nix
X// programmer winds up sticking in pathnames (sometimes) we have to break up
X// the path name into separate components composed of slashes and names (an
X// array of components is simpler to deal with than a char array). This
X// routine does that disintegration and fills in an array of pointers to
X// component strings as well as an array holding the component strings. The
X// special components /, ., and .. are always represented by the constant
X// strings above.
X
Xtypedef char ** component_array;
Xtypedef char * component_body;
X
Xstatic int DisintegrateName(
X const char * name,
X component_array& ca,
X component_body& cb)
X{
X int count = 0;
X int len = 0;
X char * s = (char *)name;
X
X // scan the name once counting the number of / characters.
X while (*s != '\0') {
X ++len;
X if (*s == '/') ++count;
X ++s;
X }
X
X // Might have about twice as many components as /'s.
X // Might need space for whole string plus null byte for each component.
X count = count * 2 + 1;
X len = len + count;
X ca = new component_body[count];
X cb = new char[len];
X
X // Now loop through the string once more busting it apart.
X char * d = cb;
X s = (char *)name;
X count = 0;
X while (*s != '\0') {
X if (*s == '/') {
X ca[count++] = (char *)slash;
X ++s;
X } else {
X ca[count] = d;
X do {
X *d++ = *s++;
X } while (*s != '/' && *s != '\0');
X *d++ = '\0';
X if (strcmp(ca[count], dot) == 0) {
X d = ca[count];
X ca[count] = (char *)dot;
X } else if (strcmp(ca[count], dotdot) == 0) {
X d = ca[count];
X ca[count] = (char *)dotdot;
X }
X ++count;
X }
X }
X ca[count] = NULL;
X
X // Return the number of components found.
X return count;
X}
X
X// Cannonize transforms an array of components to be in cannonical *nix
X// format with ./ squeezed out, etc. The main thing that is an undefined
X// *nixism is two or more '/' characters in a row. This routine interprets
X// that a-la emacs, discarding the components prior to the multiple / and
X// starting over at the root.
X
Xstatic int Cannonize(int count, component_array ca)
X{
X // Ignore empty names
X if (count == 0) return count;
X
X // If the name currently ends in a series of '/' or '.' components,
X // then delete them (but never delete the first component of the name).
X while ((count > 1) &&
X ((ca[count-1] == (char *)slash) || (ca[count-1] == (char *)dot)))
X --count;
X ca[count] = NULL;
X
X // Now go through the components squeezing out any series of ./
X // components that occur.
X int source = 0;
X int dest = 0;
X while (source < count) {
X if ((ca[source] == (char *)dot) && (ca[source+1] == (char *)slash)) {
X // don't copy up the . /, just skip over it.
X source += 2;
X } else {
X ca[dest++] = ca[source++];
X }
X }
X count = dest;
X ca[count] = NULL;
X
X // Now find multiple /// in the name (if any) and treat the final one in
X // the series as root, copying it up to the start of the component array.
X source = count - 1;
X while (source >= 1) {
X if ((ca[source] == (char *)slash) && (ca[source-1] == (char *)slash)) {
X for (dest = 0; source < count; ++dest, ++source) {
X ca[dest] = ca[source];
X }
X count = dest;
X ca[count] = NULL;
X break;
X }
X --source;
X }
X
X // Now look through the name for components of the form name/.. (where
X // name is *not* ..) and completely squeeze out the whole name/..
X // sequence. (Note: if this winds up with a completely empty component
X // list that means the name looked like fred/.. or fred/barney/../..
X // which are all just complicated ways of writing '.').
X
X source = count - 2;
X while (source >= 1) {
X if ((ca[source] == (char *)slash) &&
X (ca[source+1] == (char *)dotdot) &&
X (ca[source-1] != (char *)dotdot)) {
X
X // OK we found 3 components <prefix> 'name' '/' '..' <suffix> and
X // source currently points at the '/' component.
X if (source == 1) {
X
X // The prefix string is empty ('name' is 1st component).
X if (source == count - 2) {
X
X // The suffix is also empty, this is the degenerate
X // case of 'name' '/' '..' which we transform to '.'
X ca[0] = (char *)dot;
X source = 0;
X count = 1;
X ca[count] = NULL;
X } else {
X
X // This is something like fred/../barney, which is a
X // complex way to just say barney.
X dest = 0;
X source += 3;
X while (source < count) ca[dest++] = ca[source++];
X count = dest;
X ca[count] = NULL;
X source = 0;
X }
X } else if (source == count - 2) {
X
X // The suffix is empty, but the prefix is non-empty
X // Delete the '/' 'name' '/' '..' sequence at the end of the
X // name.
X count -= 4;
X ca[count] = NULL;
X source = count - 2;
X } else {
X
X // Both the prefix and suffix are non-empty.
X // Shift the <suffix> string left over the '/' at the end
X // of <prefix>. And then re-scan for more name/.. patterns.
X dest = source - 2;
X source += 2;
X while (source < count) {
X ca[dest++] = ca[source++];
X }
X count = dest;
X ca[count] = NULL;
X source = count - 2;
X }
X } else {
X --source;
X }
X }
X
X // If you are sitting at root, then .. is the same as root, so remove
X // any leading /.. sequences in name.
X for (source = 0; (source < count) &&
X (ca[source] == (char *)slash) &&
X (ca[source+1] == (char *)dotdot); source += 2) ;
X if (source > 0) {
X if (source >= count) {
X // The name seems to consist of nothing but /.. sequences, just
X // leave one /.
X count = 1;
X ca[count] = NULL;
X } else {
X // shift the components left.
X for (dest = 0; source < count; ++dest, ++source) {
X ca[dest] = ca[source];
X }
X count = dest;
X ca[count] = NULL;
X }
X }
X
X // Return the new component count
X return count;
X}
X
X//************************************************* Member Function Definitions
X
X// Initializer function used to build the cannonical name string
Xchar *
XFileName::make_cannonical_rep(const char * name)
X{
X component_array ca;
X component_body cb;
X char * rval;
X
X if (name != NULL) {
X int count = DisintegrateName(name, ca, cb);
X count = Cannonize(count, ca);
X int totlen = 0;
X for (int j = 0; j < count; ++j) {
X totlen += strlen(ca[j]);
X }
X rval = new char[totlen+1];
X rval[0] = '\0';
X for (j = 0; j < count; ++j) {
X strcat(rval, ca[j]);
X }
X delete ca;
X delete cb;
X } else {
X rval = NULL;
X }
X return rval;
X}
X
X// Initializer function used to build string from separate directory and
X// filename parts. (NOTE: defintion of the way multiple slashes are handled
X// in a name insures this will work properly even if name is absolute).
Xchar *
XFileName::make_cannonical_rep(const char * dir, const char * name)
X{
X if (name == NULL) {
X return NULL;
X }
X char * cannondir = make_cannonical_rep(dir);
X char * catstr = new char[strlen(cannondir) + 1 + strlen(name) + 1];
X char * rval =
X make_cannonical_rep(strcat(strcat(strcpy(catstr, cannondir), "/"), name));
X delete cannondir;
X delete catstr;
X return rval;
X}
X
X// Initializer function used to make a copy of a string
Xchar *
XFileName::make_copy(const char * name)
X{
X if (name == NULL) {
X return NULL;
X }
X char * rval = new char[strlen(name)+1];
X return strcpy(rval, name);
X}
X
X// Destroy a FileName and reclaim space.
XFileName::~FileName()
X{
X if (filename) delete (void *)filename;
X}
X
X// Return the full name minus the last component (unless the name is '/' in
X// which case '/' is returned).
XFileName
XFileName::GetDirectory() const
X{
X if (Error()) return FileName(NULL);
X char * baseptr = strrchr(filename, '/');
X if (baseptr == NULL) {
X // No directory part, report directory as '.'
X return FileName(".");
X } else if (baseptr == filename) {
X // The name is just '/', keep reporting that as name.
X return FileName(*this);
X } else {
X // Normal case, generate a new string without the trailing component.
X int len = baseptr - (char *)filename;
X char * dirname = new char[len+1];
X strncpy(dirname, filename, len);
X dirname[len] = '\0';
X UnsafeFileName rval(dirname);
X return FileName(rval);
X }
X}
X
X// Return just the base name minus any leading directory info (if name is
X// '/', return '/').
XFileName
XFileName::GetBasename() const
X{
X if (Error()) return FileName(NULL);
X char * baseptr = strrchr(filename, '/');
X if (baseptr == NULL) {
X // The name has no directory part, use whole name.
X baseptr = (char *)filename;
X } else {
X // Skip over the '/' to get to the basename part.
X ++baseptr;
X }
X char * basename = new char[strlen(baseptr)+1];
X strcpy(basename, baseptr);
X UnsafeFileName rval(basename);
X return FileName(rval);
X}
X
X// Look for the last '.' character in the basename and return that string
X// (including the '.'). If there is no '.' return a NULL pointer.
X//
X// NOTE: Make sure you don't treat the final '.' in a .. component as a
X// suffix.
XFileName
XFileName::GetSuffix() const
X{
X if (Error()) return FileName(NULL);
X char * dotptr = strrchr(filename, '.');
X if (dotptr != NULL) {
X char * slashptr = strrchr(filename, '/');
X if (((slashptr == NULL) &&
X (strcmp(filename, ".") != 0) &&
X (strcmp(filename, "..") != 0)) ||
X ((slashptr != NULL) &&
X (slashptr < dotptr) &&
X (strcmp(slashptr, "/..") != 0))) {
X char * suffixname = new char[strlen(dotptr)+1];
X strcpy(suffixname, dotptr);
X UnsafeFileName rval(suffixname);
X return FileName(rval);
X }
X }
X return FileName(NULL);
X}
X
X// Return the basename (like previous function), but if the trailing part of
X// the basename matches the specified suffix string then strip the suffix
X// (the suffix string must include any "." you want to strip)
XFileName
XFileName::GetBasename(const char * suffix) const
X{
X if (Error()) return FileName(NULL);
X if (suffix == NULL || suffix[0] == '\0') return GetBasename();
X char * baseptr = strrchr(filename, '/');
X if (baseptr == NULL) {
X // The name has no directory part, use whole name.
X baseptr = (char *)filename;
X } else {
X // Skip over the '/' to get to the basename part.
X ++baseptr;
X }
X int baselen = strlen(baseptr);
X int suflen = strlen(suffix);
X if ((suflen < baselen) && (strcmp(&baseptr[baselen-suflen], suffix) == 0)) {
X // The suffix matches exactly, cut it off the end.
X baselen -= suflen;
X }
X char * basename = new char[baselen+1];
X strncpy(basename, baseptr, baselen);
X basename[baselen] = '\0';
X UnsafeFileName rval(basename);
X return FileName(rval);
X}
X
X// Given filename 'dir', generate a relative pathname string which would
X// reference this file if the current directory were 'dir'
X//
X// If the first component of both names is not the same, then there is no
X// common path to relate the names and we return a FileName with a NULL
X// filename pointer (the Error() function will report TRUE).
X//
X// NOTE: The rule about initial components is complicated by a leading ..
X// in the directory name that shows up after leading common components
X// are skipped.
X//
X// Examples:
X//
X// this dir result
X// ------------------ ---------------------- -----------------
X// fred/barney fred/george/xyzzy ../../barney
X// ../fred .. fred
X// ../fred ../.. <NULL>
X// /fred/george /barney/xyzzy ../../fred/george
X// fred/george barney/xyzzy <NULL>
X// ../fred ../george ../fred
X// a/b/c/d/e a/b/c/d/f ../e
X// a/b/c/d a/b/c/d/e ..
X// a/b/c a/b/c .
X//
X// Algorithm:
X// Skip over common leading components of both names (error if none).
X// Error out if first component remaining in dir is '..'. Stick one ../
X// in front of remaining *this for each filename component remaining in
X// dir. (If all components are identical, just return '.' as a special
X// case).
XFileName
XFileName::GetNameRelativeTo(const FileName& dir) const
X{
X if (Error() || dir.Error() ||
X ((filename[0] == '/') != (dir.filename[0] == '/'))) {
X return FileName(NULL);
X }
X char * tp = (char *)filename;
X char * dp = (char *)dir.filename;
X char * futp = tp; // points at first unmatched component of *this
X char * fudp = dp; // points at first unmatched component of dir
X
X // Scan through the two names skipping over identical components. When
X // done futp and fudp point to the first unmatched component of each name
X // (note that they may be pointing at the null byte at the end of the
X // string).
X for ( ; ; ) {
X char t = *tp;
X char d = *dp;
X
X if (t == '\0') {
X if (d == '\0') {
X // Reached the end of *this and dir at the same time, this
X // is the degenerate case of identical names, just return '.'.
X return FileName(".");
X } else {
X // Reached the end of *this, but not dir.
X if (d == '/') {
X // If dir is at a '/' then the entire *this string is a
X // common prefix and the next component of dir is the first
X // unmatched one
X futp = tp;
X fudp = dp+1;
X break;
X } else {
X // In this case dir does not terminate at a '/', just exit the
X // loop with the previously located components as the first
X // unmatched ones.
X break;
X }
X }
X } else if (d == '\0') {
X // Reached the end if dir string but not this
X if (t == '/') {
X // If *this is at a '/' then entire dir string is a common
X // prefix.
X fudp = dp;
X futp = tp+1;
X break;
X } else {
X // Merely un-matched components
X break;
X }
X } else if (t == d) {
X if ((t == '/') || (*futp == '/')) {
X // If this is a slash or the first character following a slash
X // remember this as the starting point of a component.
X futp = tp;
X fudp = dp;
X }
X } else {
X // We reached an un-matched place, just exit with previous
X // first un-matched pointers. (If this is the first character
X // in a name component following a slash, then increment past
X // the slash characters).
X if (*futp == '/') {
X ++futp;
X ++fudp;
X }
X break;
X }
X ++tp;
X ++dp;
X }
X
X // If the very first components mis-matched or the head of the
X // remaining directory component is a '..' name then we cannot
X // relate these two names, return a NULL pointer
X if ((futp == (char *)filename) || (fudp == (char *)dir.filename) ||
X ((strncmp(fudp, "..", 2) == 0) &&
X (fudp[2] == '/' || fudp[2] == '\0'))) {
X return FileName(NULL);
X }
X
X // Count the remaining filename components of 'dir' to determine
X // how many ../ components to stick in front of remaining part
X // of *this.
X int dotdotcount = 0;
X if (*fudp != '\0') {
X // There is at least one component (plus one more for each '/').
X dotdotcount = 1;
X while (*fudp != '\0') {
X if (*fudp++ == '/') ++dotdotcount;
X }
X }
X
X // Allocate space for new string and generate any ../ parts required.
X char * relname = new char[3*dotdotcount + strlen(futp) + 1];
X relname[0] = '\0';
X while (dotdotcount-- > 0) {
X strcat(relname, "../");
X }
X if (*futp != '\0') {
X strcat(relname, futp);
X } else {
X // Get rid of the pesky trailing / on the ../ sequence
X relname[strlen(relname)-1] = '\0';
X }
X UnsafeFileName rval(relname);
X return FileName(rval);
X}
END_OF_FILE
if test 17247 -ne `wc -c <'filename.c'`; then
echo shar: \"'filename.c'\" unpacked with wrong size!
fi
# end of 'filename.c'
fi
if test -f 'ivinfo.c' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'ivinfo.c'\"
else
echo shar: Extracting \"'ivinfo.c'\" \(18710 characters\)
sed "s/^X//" >'ivinfo.c' <<'END_OF_FILE'
X/* Hacked up version of the Interviews sample program 'sted'. This version
X * is an emacs info file viewer. For 'file' in most comments and variable
X * names throughout the source, you should read 'info node'.
X */
X
X#include <InterViews/adjuster.h>
X#include <InterViews/border.h>
X#include <InterViews/box.h>
X#include <InterViews/glue.h>
X#include <InterViews/regexp.h>
X#include <InterViews/painter.h>
X#include <InterViews/scene.h>
X#include <InterViews/scroller.h>
X#include <InterViews/sensor.h>
X#include <InterViews/textbuffer.h>
X#include <InterViews/texteditor.h>
X#include <InterViews/world.h>
X#include <InterViews/frame.h>
X#include "hypertext.h"
X#include "dialogbox.h"
X#include "info.h"
X
X#include <ctype.h>
X#include <stdio.h>
X#include <stdlib.h>
X#include <string.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <unistd.h>
X#include <fcntl.h>
X
X#include "yesorno.h"
X
X// InterViews (2.6 anyway) has no natural access to cut buffers, so
X// the following stuff descends directly to X to get the job done.
X#define _XEVENT_
X#include <InterViews/X11/worldrep.h>
X#include <X11/keysym.h>
Xextern "C" int XStoreBytes(...);
X
Xstatic int one_window = 1;
X
Xstatic InfoRoot * info_root;
X
Xstatic const int MINTEXTSIZE = 10000;
Xstatic const int BUFFERSIZE = 100;
Xstatic const int FILENAMESIZE = 100;
X
Xclass DemoWindow;
X
X// DemoWindowInfo keeps track of which files are being displayed in which
X// windows. If someone asks to view a file which already has a window, we
X// simply de-iconify that window or whatever is appropriate.
Xstruct DemoWindowInfo {
Xpublic:
X DemoWindow* window;
X boolean closed;
X Coord x, y;
X DemoWindowInfo* next;
X};
X
Xclass NewWindowLink;
X
X// Only one Demo exists. It is inserted in the World and has the various
X// DemoWindows created hung off of it. It centralizes management of
X// the windows, looks up existing ones, creates new ones, etc.
Xclass Demo {
Xpublic:
X Demo(World* world);
X ~Demo();
X void Open(InfoNode * filename, DemoWindow * window = nil);
X void Edit(DemoWindow * window, InfoNode * filename);
X void Scroll(DemoWindow * window, int pos)
X { edit_window = window; do_scroll = true; scroll_to_here = pos;};
X void Close(InfoNode * filename);
X void Quit();
X void Run();
X void Complain();
X World* world;
X int window_count;
Xprivate:
X DemoWindowInfo* Find(InfoNode * filename);
X
X DemoWindowInfo* windows;
X DemoWindow * edit_window;
X InfoNode * edit_filename;
X int scroll_to_here;
X boolean do_scroll;
X boolean done;
X};
X
X// A DemoWindow is a window displaying a single file at a time.
Xclass DemoWindow : public MonoScene {
Xfriend class NewWindowLink;
Xpublic:
X DemoWindow(Demo*, InfoNode * filename);
X virtual ~DemoWindow();
X const char* Filename();
X
X // override Interactor::Handle to provide various commands
X // attached to keystrokes or mouse events.
X virtual void Handle(Event&);
X InfoNode * GetNode() { return node; };
X void Edit(InfoNode * node);
X void ScrollHere(int pos) {
X editor->Select(pos);
X editor->ScrollToSelection();
X };
Xprotected:
X
X void HandleChar(Event&, char);
X void LeftMouse(Event&);
X
X void common_init(Demo * s);
X
X Demo* demo;
X HyperTextEditor* editor;
X char lastchar;
X char filename[FILENAMESIZE];
X int size;
X TextBuffer* text;
X NewWindowLink * up_link;
X NewWindowLink * next_link;
X NewWindowLink * prev_link;
X InfoNode * node;
X Namer * gotodialog;
X Finder * filedialog;
X Namer * redialog;
X char * lastre;
X Messager * nohitmsg;
X};
X
Xclass NewWindowLink : public HyperTextLink {
Xpublic:
X virtual void FollowLink(void);
X NewWindowLink(
X char * fn,
X DemoWindow * dp,
X int p,
X int l,
X int s=(int)Plain
X ) : HyperTextLink(dp->editor, p, l, s) { local_init(fn, dp); };
X ~NewWindowLink() { delete linkfile; };
Xprotected:
X char * linkfile;
X DemoWindow * demo_ptr;
X void local_init(char * fn, DemoWindow * dp);
X};
X
Xvoid
XNewWindowLink::local_init(
X char * fn,
X DemoWindow * dp)
X{
X linkfile = new char[strlen(fn)+1];
X strcpy(linkfile, fn);
X demo_ptr = dp;
X}
X
Xvoid
XNewWindowLink::FollowLink()
X{
X InfoNode * fn = info_root->GetNode(linkfile);
X if (fn != NULL) {
X demo_ptr->demo->Open(fn, demo_ptr);
X }
X}
X
X// Create a new Demo in the given World with no DemoWindows to start with
XDemo::Demo (World* w) {
X world = w;
X windows = nil;
X done = false;
X window_count = 0;
X edit_window = nil;
X edit_filename = nil;
X do_scroll = false;
X}
X
X// Delete the Demo and any windows attached to it.
XDemo::~Demo () {
X DemoWindowInfo* w = windows;
X while (w != nil) {
X DemoWindowInfo* prev = w;
X w = w->next;
X if (!prev->closed) {
X world->Remove(prev->window);
X }
X delete prev->window;
X delete prev;
X }
X}
X
X// Remember file & window to edit (Run command has to do this on our behalf
X// or we get all confused when data structures we are currently accessing
X// get deleted out from under us).
Xvoid
XDemo::Edit(DemoWindow * window, InfoNode * filename)
X{
X edit_window = window;
X edit_filename = filename;
X}
X
X// Open a new file - if there is a window already attached to the file,
X// just switch to it.
Xvoid Demo::Open (InfoNode * filename, DemoWindow * dw) {
X DemoWindowInfo* w = Find(filename);
X if (one_window && dw != nil && w == nil) {
X Edit(dw, filename);
X return;
X }
X if (w == nil) {
X w = new DemoWindowInfo();
X w->window = new DemoWindow(this, filename);
X w->closed = false;
X w->next = windows;
X if (windows == nil) {
X w->x = 0;
X w->y = 0;
X world->InsertApplication(w->window);
X w->window->GetRelative(w->x, w->y);
X } else {
X w->x = windows->x - 15;
X w->y = windows->y - 20;
X world->InsertApplication(w->window, w->x, w->y);
X }
X windows = w;
X window_count++;
X } else {
X if (w->closed) {
X w->closed = false;
X world->InsertApplication(w->window, w->x, w->y);
X } else {
X w->window->DeIconify();
X world->Raise(w->window);
X }
X }
X}
X
X// Close a window attached to a given file.
Xvoid Demo::Close (InfoNode * filename) {
X DemoWindowInfo* w = Find(filename);
X if (w != nil) {
X w->closed = true;
X }
X}
X
X// Search the list of windows to find a given file
XDemoWindowInfo* Demo::Find (InfoNode * f) {
X DemoWindowInfo* w;
X for (w = windows; w != nil; w = w->next) {
X if (w->window->GetNode() == f) {
X return w;
X }
X }
X return nil;
X}
X
X// Should change this to popup a dialog with the error message (for that
X// matter, it needs to take a message as its argument).
Xvoid Demo::Complain () {
X world->RingBell(1);
X}
X
X// We are outta here!
Xvoid Demo::Quit () {
X done = true;
X}
X
X// Keep processing events until one of them winds up setting the done flag.
Xvoid Demo::Run () {
X Event e;
X boolean alive;
X do {
X world->Read(e);
X e.target->Handle(e);
X if (edit_window != nil) {
X if (edit_filename != nil) {
X edit_window->Edit(edit_filename);
X }
X if (do_scroll) {
X edit_window->ScrollHere(scroll_to_here);
X }
X edit_window = nil;
X edit_filename = nil;
X do_scroll = false;
X }
X DemoWindowInfo* w;
X for (DemoWindowInfo** wpp = &windows; *wpp != nil; wpp = &(*wpp)->next){
X w = *wpp;
X if (w->closed) {
X world->Remove(w->window);
X *wpp = w->next;
X delete w->window;
X delete w;
X --window_count;
X }
X if (*wpp == nil) break;
X }
X } while (window_count > 0 && !done && e.target != nil);
X}
X
X// Common processing for creating a new DemoWindow
Xvoid DemoWindow::common_init(Demo* s) {
X demo = s;
X input = new Sensor();
X input->Catch(KeyEvent);
X input->Catch(DownEvent);
X size = 0;
X text = nil;
X gotodialog = nil;
X filedialog = nil;
X redialog = nil;
X lastre = nil;
X nohitmsg = nil;
X editor = new HyperTextEditor(24, 80, 8, Reversed);
X Insert(
X new Frame(
X new HBox(
X new HGlue(5, 0, 0),
X new VBox(
X new VGlue(3, 0, 0),
X editor,
X new VGlue(3, 0, 0)
X ),
X new HGlue(5, 0, 0),
X new VBorder,
X new VBox(
X new UpMover(editor, 1),
X new HBorder(),
X new VScroller(editor),
X new HBorder(),
X new DownMover(editor, 1)
X )
X )
X )
X );
X}
X
X// This creates a new DemoWindow attached to the given file. It creates
X// all the nifty scroll bars and glues them together with the text window.
XDemoWindow::DemoWindow (Demo* s, InfoNode * name) {
X common_init(s);
X Edit(name);
X}
X
X// Free up resources associated with a DemoWindow
XDemoWindow::~DemoWindow () {
X demo->Close(node);
X delete text;
X if (gotodialog != nil) delete gotodialog;
X if (filedialog != nil) delete filedialog;
X if (redialog != nil) delete redialog;
X if (lastre != nil) delete lastre;
X if (nohitmsg != nil) delete nohitmsg;
X}
X
X
X// Provide access to the filename of a window.
Xconst char* DemoWindow::Filename () {
X return filename;
X}
X
X// Suck up a file into a text window.
Xvoid DemoWindow::Edit (InfoNode * f) {
X delete text;
X up_link = nil;
X next_link = nil;
X prev_link = nil;
X node = f;
X char * b;
X const char * fn = f->GetFileName();
X const char * bfn = strrchr(fn, '/');
X if (bfn == NULL) {
X bfn = fn;
X } else {
X ++bfn;
X }
X sprintf(filename, "(%s)%s", bfn, f->GetName());
X size = f->Length();
X b = f->Text();
X text = new TextBuffer(b, size, size);
X editor->Edit(text);
X SetName(filename);
X SetIconName(filename);
X InfoLink * ln = f->FirstLink();
X char fname[1024];
X while (ln != NULL) {
X int p = ln->GetNode();
X int l = ln->GetNodeLength();
X if (b[p] == '(') {
X strncpy(fname, &b[p], l);
X fname[l] = '\0';
X } else {
X strcpy(fname, "(");
X strcat(fname, f->GetFileName());
X strcat(fname, ")");
X int flen = strlen(fname);
X strncpy(&fname[flen], &b[p], l);
X fname[flen+l] = '\0';
X }
X NewWindowLink * nwl =
X new NewWindowLink(fname, this, p, l, (int)Reversed);
X if (strcmp(ln->GetName(), "Previous") == 0) {
X prev_link = nwl;
X } else if (strcmp(ln->GetName(), "Next") == 0) {
X next_link = nwl;
X } else if (strcmp(ln->GetName(), "Up") == 0) {
X up_link = nwl;
X }
X ln = ln->GetNext();
X }
X}
X
X// This is the DemoWindow version of Handle which deals with events
X// the InterViews library passes to an Interactor.
Xvoid DemoWindow::Handle (Event& e) {
X int save_one_window = one_window;
X one_window = ! e.meta;
X if (e.eventType == KeyEvent && e.len > 0) {
X // Look at single keystroke events and process them.
X HandleChar(e, e.keystring[0]);
X
X } else if (e.eventType == DownEvent) {
X // Look at mouse events and process them
X switch (e.button) {
X case LEFTMOUSE: LeftMouse(e); break;
X case MIDDLEMOUSE: editor->GrabScroll(e); break;
X case RIGHTMOUSE: editor->RateScroll(e); break;
X }
X }
X one_window = save_one_window;
X}
X
X// Handle a single keystroke in a text window.
Xvoid DemoWindow::HandleChar (Event& e, char c) {
X // If there is only one window left, then quit rather than close.
X InfoNode * n;
X if (c == 'c' && demo->window_count == 1) c = 'q';
X switch(c) {
X case 'q':
X if (e.meta || YesOrNo(e, "Really Quit?")) demo->Quit(); break;
X case 'c':
X if (e.meta || YesOrNo(e, "Close Window?")) demo->Close(node); break;
X case '?':
X case 'h':
X // Try for ivinfo help first, then regular emacs info help,
X // then just give up...
X n = info_root->GetNode("(ivinfo-info)Top");
X if (n == nil)
X n = info_root->GetNode("(info)Help");
X if (n != nil) demo->Open(n, this);
X break;
X case 'n': if (next_link) next_link->FollowLink(); break;
X case 'p': if (prev_link) prev_link->FollowLink(); break;
X case 'u': if (up_link) up_link->FollowLink(); break;
X case '.':
X case 'b':
X demo->Scroll(this, 0);
X break;
X case ' ':
X editor->ForwardPage();
X demo->Scroll(this, editor->Dot());
X break;
X case '\177':
X editor->BackwardPage();
X demo->Scroll(this, editor->Dot());
X break;
X case 'l':
X n = info_root->PopHistory();
X if (n != NULL) demo->Open(n, this);
X break;
X case 'd': demo->Open(info_root->GetNode("(dir)"), this); break;
X case 't':
X n = info_root->GetNode("Top");
X if (n != NULL) demo->Open(n, this);
X break;
X case 'g':
X if (gotodialog == nil)
X gotodialog = new Namer(this, "Goto what node?");
X char * nn = gotodialog->Edit(nil);
X if (nn != nil) {
X n = info_root->GetNode(nn);
X if (n != nil) demo->Open(n, this);
X }
X break;
X case 'o':
X if (filedialog == nil)
X filedialog = new Finder(this, "Open Top node of what file?");
X const char * fn = filedialog->Find();
X if (fn != nil) {
X int len = strlen(fn);
X char * topnode = new char [len + 2 + 3 + 1];
X strcpy(topnode, "(");
X strcat(topnode, fn);
X strcat(topnode, ")");
X strcat(topnode, "Top");
X n = info_root->GetNode(topnode);
X if (n == nil) {
X // If there is no 'Top' node, try to read whole file (*).
X strcpy(&topnode[len+2], "*");
X n = info_root->GetNode(topnode);
X }
X if (n != nil) demo->Open(n, this);
X delete topnode;
X }
X break;
X case 's':
X int reoffset;
X int triedsearch=0;
X n = nil;
X if (! e.meta || lastre == nil) {
X // If just 's' was pressed or there was no previous RE, prompt
X // for a new RE.
X if (redialog == nil)
X redialog = new Namer(this, "Search for what regular expression?");
X char * re = redialog->Edit(lastre);
X if ((re != nil) && (*re != '\0')) {
X if (lastre != nil && strcmp(re, lastre) == 0) {
X // Just search for same RE again.
X info_root->ReSearchAgain(n, reoffset);
X triedsearch=1;
X } else {
X // Start search for new RE.
X if (lastre != nil) delete lastre;
X lastre = new char [strlen(re) + 1];
X strcpy(lastre, re);
X info_root->ReSearch(lastre, n, reoffset);
X triedsearch=1;
X }
X }
X } else if (lastre != nil) {
X // If Meta-s was pressed, just search for last RE again.
X info_root->ReSearchAgain(n, reoffset);
X triedsearch=1;
X }
X if (n != nil) {
X if (n != GetNode()) {
X one_window = 1;
X demo->Open(n, this);
X }
X demo->Scroll(this, reoffset);
X } else if (triedsearch) {
X if (nohitmsg == nil)
X nohitmsg = new Messager(this, "Pattern Not Found.");
X nohitmsg->Display();
X }
X break;
X default: demo->Complain(); break;
X }
X}
X
X// The LeftMouse function first checks to see if the event occurred in a link,
X// if it did, the link is followed and we return, otherwise it selects a
X// region of text.
Xvoid DemoWindow::LeftMouse (Event& e) {
X if (editor->FollowAllLinks(e)) return;
X editor->Select(editor->Locate(e.x, e.y));
X do {
X editor->ScrollToView(e.x, e.y);
X editor->SelectMore(editor->Locate(e.x, e.y));
X Poll(e);
X GetRelative(e.x, e.y, editor);
X } while (e.leftmouse);
X
X // Stuff the new selection into cut buffer 0 using low level X call
X // since InterViews provides no interface to this.
X int d = editor->Dot();
X int m = editor->Mark();
X int t;
X if (d < m) {
X t = d;
X d = m;
X m = t;
X }
X XStoreBytes(demo->world->Rep()->display(), text->Text(m,d), d-m);
X}
X
Xstatic PropertyData properties[] = {
X { "*path", DEFAULT_INFO_PATH }, // must be 1st entry
X { "*reverseVideo", "off" },
X { nil }
X};
X
Xstatic OptionDesc options[] = {
X { "-r", "*reverseVideo", OptionValueImplicit, "on" },
X { nil }
X};
X
X// Main program - just create the world then the Demo and run until the
X// user quits.
Xint main (int argc, char* argv[]) {
X // If there is an INFO_PATH environment variable, set the default
X // path resource value to it.
X const char * info_path = getenv(INFO_PATH);
X if (info_path != NULL) {
X properties[0].value = info_path;
X }
X
X World* world = new World("Ivinfo", properties, options, argc, argv);
X
X // Now get the path resource (which may have been overridden by a user
X // defined X resource definition.
X info_path = world->GetAttribute("path");
X
X // Establish the global InfoRoot using the path info obtained through
X // this torturous route...
X info_root = new InfoRoot(info_path);
X
X // Now proceed with normal intialization.
X Demo* demo = new Demo(world);
X int opencount = 0;
X if (argc > 1) {
X for (int i = 1; i < argc; ++i) {
X InfoNode * f = info_root->GetNode(argv[i]);
X if (f != nil) {
X demo->Open(f);
X ++opencount;
X }
X }
X }
X if (opencount <= 0) {
X InfoNode * f = info_root->GetNode("(dir)");
X if (f != nil) {
X demo->Open(f);
X ++opencount;
X }
X }
X
X if (opencount > 0) {
X // Rebind Home PgDown and PgUp to map onto standard single character
X // commands for moving text around.
X XRebindKeysym(demo->world->Rep()->display(), XK_Home, 0, 0,
X (const unsigned char *)"b", 1);
X XRebindKeysym(demo->world->Rep()->display(), XK_Next, 0, 0,
X (const unsigned char *)" ", 1);
X XRebindKeysym(demo->world->Rep()->display(), XK_Prior, 0, 0,
X (const unsigned char *)"\177", 1);
X demo->Run();
X } else {
X fprintf(stderr,
X "ivinfo: Sorry, could not find any info nodes!\n");
X fprintf(stderr,
X "ivinfo: Check ivinfo*path resource or INFO_PATH environment variable.\n");
X fflush(stderr);
X }
X
X delete demo;
X delete world;
X return 0;
X}
END_OF_FILE
if test 18710 -ne `wc -c <'ivinfo.c'`; then
echo shar: \"'ivinfo.c'\" unpacked with wrong size!
fi
# end of 'ivinfo.c'
fi
echo shar: End of archive 3 \(of 4\).
cp /dev/null ark3isdone
MISSING=""
for I in 1 2 3 4 ; do
if test ! -f ark${I}isdone ; then
MISSING="${MISSING} ${I}"
fi
done
if test "${MISSING}" = "" ; then
echo You have unpacked all 4 archives.
rm -f ark[1-9]isdone
else
echo You still need to unpack the following archives:
echo " " ${MISSING}
fi
## End of shell archive.
exit 0
======================================================================
domain: tahorsley at csd.harris.com USMail: Tom Horsley
uucp: ...!uunet!hcx1!tahorsley 511 Kingbird Circle
Delray Beach, FL 33444
+==== Censorship is the only form of Obscenity ======================+
| (Wait, I forgot government tobacco subsidies...) |
+====================================================================+
exit 0 # Just in case...
--
Kent Landfield INTERNET: kent at sparky.IMD.Sterling.COM
Sterling Software, IMD UUCP: uunet!sparky!kent
Phone: (402) 291-8300 FAX: (402) 291-4362
Please send comp.sources.misc-related mail to kent at uunet.uu.net.
More information about the Comp.sources.misc
mailing list