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