Path: utzoo!utgpu!news-server.csri.toronto.edu!bonnie.concordia.ca!uunet!sparky!kent From: tom@hcx2.ssd.csd.harris.com (Tom Horsley) Newsgroups: comp.sources.misc Subject: v20i028: ivinfo - InterViews emacs info file browser in C++, Part03/04 Message-ID: <1991May30.023534.881@sparky.IMD.Sterling.COM> Date: 30 May 91 02:35:34 GMT References: Sender: kent@sparky.IMD.Sterling.COM (Kent Landfield) Organization: Sterling Software, IMD Lines: 1245 Approved: kent@sparky.imd.sterling.com X-Md4-Signature: 0b72df17921ad0ae253a8949e74cbb6e Submitted-by: Tom Horsley 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 '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 X#include 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 'name' '/' '..' 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 string left over the '/' at the end X // of . 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 ../.. X// /fred/george /barney/xyzzy ../../fred/george X// fred/george barney/xyzzy 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 X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include X#include "hypertext.h" X#include "dialogbox.h" X#include "info.h" X X#include X#include X#include X#include X#include X#include X#include X#include 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 X#include 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@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@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@uunet.uu.net.