This is HyperText.m in view mode; [Download] [Up]
// HyperText Implementation HyperText.m // ------------------------ // // HyperText is like Text, but includes links to and from other hypertexts. // // Authors: // TBL Tim Berners-Lee CERN/CN // // See also: // The Anchor class, and the HyperAccess class. // // History: // 25 Sep 90 Written (TBL) with the help of the Interface builder. // 14 Mar 91 Page width is taken from application's page layout. // Notes. // // For all anchors to have addresses, the node must be made with // newAnchor:Server: . Do not use any other creation methods inherited. #import <appkit/appkit.h> #import "HyperText.h" #import "HyperAccess.h" #import "HTUtils.h" #import "HTParse.h" #import "HTStyle.h" #import "WWW.h" @implementation HyperText #define ANCHOR_ID_PREFIX 'z' /* Auto anchors run z1, z2, ... 921122 */ extern HTStyleSheet * styleSheet; /* see StyleToy */ static int window_sequence = 0; /* For stacking windows neatly */ #define SLOTS 30 /* Number of position slots */ static HyperText * slot[SLOTS]; /* Ids of HT objects taking them */ #define NICE_HEIGHT 600.0 /* Allows a few windows to be stacked */ #define MAX_HEIGHT 720.0 /* Worth going bigger to get it all in */ #define MIN_HEIGHT 80.0 /* Mustn't lose the window! */ #define MIN_WIDTH 200.0 static HyperText * HT; /* Global pointer to self to allow C mixing */ +initialize { int i; for (i=0; i<SLOTS; i++) slot[i] = 0; return [super initialize]; } // Get Application page layout's Page Width // ---------------------------------------- // // Returned in pixels // static float page_width() { PrintInfo * pi = [NXApp printInfo]; // Page layout details NXCoord topMargin, bottomMargin, leftMargin, rightMargin; const NXRect * paper = [pi paperRect]; // In points [pi getMarginLeft:&leftMargin right:&rightMargin top:&topMargin bottom:&bottomMargin]; /* In points */ return (paper->size.width - leftMargin - rightMargin); } // Class methods // ------------- // // Build a HyperText GIVEN its nodeAnchor. // -------------------------------------- + newAnchor:(Anchor *)anAnchor Server:(id)aServer { NXRect aFrame = {{0.0, 0.0}, {page_width(), NICE_HEIGHT}}; self = [super newFrame:&aFrame]; if (TRACE) printf("New node, server is %i\n", aServer); nextAnchorNumber = 0; protection = 0; // Can do anything format = WWW_HTML; // By default [self setMonoFont: NO]; // By default theRuns->chunk.growby = 16 * sizeof(theRuns->runs[0]); // efficiency server = (HyperAccess *)aServer; [self setDelegate:aServer]; /* For changes */ nodeAnchor= anAnchor; [nodeAnchor setNode:self]; return self; } // Instance Methods // ---------------- // Free the hypertext. - free { slot[slotNumber] = 0; // Allow slot to be reused [nodeAnchor setNode:nil]; // Invalidate the node return [super free]; } // Read and set format - (int) format {return format; } - setFormat:(int)f {format = f; return self; } // Useful diagnostic routine: Dump to standard output // --------------------------------------------------- // // This first lists the runs up to and including the current run, // then it lists the attributes of the current run. // - dump:sender { int pos; /* Start of run being scanned */ int sob=0; /* Start of text block being scanned */ NXRun * r = theRuns->runs; NXTextBlock * block = firstTextBlock; printf("Hypertext %i, selected(%i,%i)", self, sp0.cp, spN.cp); if (delegate) printf(", has delegate"); printf(".\n"); printf(" Frame is at (%f, %f, size is (%f, %f)\n", frame.origin.x, frame.origin.y, frame.size.width, frame.size.height); printf(" Text blocks and runs up to character %i:\n", sp0.cp); for (pos = 0; pos<=sp0.cp; pos = pos+((r++)->chars)) { while(sob <= pos) { printf("%5i: Block of %i/%i characters at 0x%x starts `%10.10s'\n", sob, block->chars, malloc_size(block->text), block->text, block->text); sob = sob + block->chars; block = block->next; } printf("%5i: %3i of fnt=%i p=%i gy=%3.2f RGB=%i i=%i fl=%x\n", pos, r->chars, (int)r->font, r->paraStyle, r->textGray,r->textRGBColor, (int)(r->info), *(int*)&(r->rFlags)); } r--; /* Point to run for start of selection */ printf("\n Current run:\n\tFont name:\t%s\n", [r->font name]); { NXTextStyle *p = (NXTextStyle *)r->paraStyle; if (!p) { printf("\tNo paragraph style!\n"); } else { int tab; printf("\tParagraph style %i\n", p); printf("\tIndents: first=%f, left=%f\n", p->indent1st, p->indent2nd); printf("\tAlignment type=%i, %i tabs:\n", p->alignment, p->numTabs); for (tab=0; tab<p->numTabs; tab++) { printf("\t Tab kind=%i at %f\n", p->tabs[tab].kind, p->tabs[tab].x); } } } printf("\n"); return self; } // Adjust Scrollers and Window size for current text size // ------------------------------------------------------ // //The scrollers are turned off if they possibly can be, to simplify the screen. // If the text is editable, they have to be left on, although formatted text is // allowed to wrap round, and so horizontal scroll bars are not necessary. // The window size is adjusted as a function of the text size and scrollers. // // @@ Bug: The resize bar should be removed if there are no scrollers. // This is difficult to do -- might have to make a new window. // - adjustWindow { #define MAX_WIDTH paperWidth NXRect scroll_frame; NXRect old_scroll_frame; NXSize size; BOOL scroll_X, scroll_Y; // Do we need scrollers? ScrollView* scrollview = [window contentView];// Pick up id of ScrollView float paperWidth = page_width(); // Get page layout width [window disableFlushWindow]; // Prevent flashes [self setVertResizable:YES]; // Can change size automatically [self setHorizResizable:tFlags.monoFont]; [self calcLine]; // Wrap text to current text size [self sizeToFit]; // Reduce size if possible. if (maxY > MAX_HEIGHT) { scroll_Y = YES; size.height = NICE_HEIGHT; } else { scroll_Y = [self isEditable]; size.height = maxY < MIN_HEIGHT ? MIN_HEIGHT : maxY; } if (tFlags.monoFont) { scroll_X = [self isEditable] || (maxX>MAX_WIDTH); [self setNoWrap]; } else { scroll_X = NO; [self setCharWrap:NO]; // Word wrap please } if (maxX > MAX_WIDTH) { size.width = MAX_WIDTH; } else { size.width = maxX < MIN_WIDTH ? MIN_WIDTH : maxX; } // maxX is the length of the longest line. // It only represnts the width of the page // needed if the line is quad left. If the longest line was // centered or flush right, it may be truncated unless we resize // it to fit. if (!scroll_X) { [self sizeTo:size.width:maxY]; [self calcLine]; [self sizeToFit]; // Algorithm found by trial and error. } // Set up the scroll view and window to match: [ScrollView getFrameSize:&scroll_frame.size forContentSize: &size horizScroller: scroll_X vertScroller: scroll_Y borderType: NX_LINE]; [scrollview setVertScrollerRequired:scroll_Y]; [scrollview setHorizScrollerRequired:scroll_X]; // Has the frame size changed? [scrollview getFrame:&old_scroll_frame]; if ( (old_scroll_frame.size.width != scroll_frame.size.width) ||(old_scroll_frame.size.height != scroll_frame.size.height)) { // Now we want to leave the top left corner of the window unmoved: #ifdef OLD_METHOD NXRect oldframe; [window getFrame:&oldframe]; [window sizeWindow:scroll_frame.size.width:scroll_frame.size.height]; [window moveTopLeftTo: oldframe.origin.x : oldframe.origin.y + oldframe.size.height]; #else NXRect newFrame; scroll_frame.origin.x = 150 + (slotNumber % 10) * 30 + ((slotNumber/10)%3)* 40; scroll_frame.origin.y = 185 + NICE_HEIGHT - scroll_frame.size.height - (slotNumber % 10) * 20 - ((slotNumber/10)%3)* 3; [Window getFrameRect:&newFrame forContentRect:&scroll_frame style:NX_TITLEDSTYLE]; // Doesn't allow space for resize bar newFrame.origin.y = newFrame.origin.y - 9.0; newFrame.size.height = newFrame.size.height + 9.0; // For resize bar [window placeWindow:&newFrame]; #endif } #ifdef VERSION_1_STRANGENESS // In version 2, the format of the last run is overwritten with the format // of the preceding run! { NXRect frm; /* Try this to get over "text strangeness" */ [self getFrame:&frm]; [self renewRuns:NULL text:NULL frame:&frm tag:0]; } #endif [window reenableFlushWindow]; [self calcLine]; /* Prevent messy screen */ [window display]; /* Ought to clean it up */ return self; } /* adjustWindow */ // Set up a window in the current application for this hypertext // ------------------------------------------------------------- - setupWindow { NXRect scroll_frame; // Calculated later NXSize min_size = {300.0,200.0}; // Minimum size of text NXSize max_size = {1.0e30,1.0e30}; // Maximum size of text ScrollView * scrollview; NXSize nice_size = { 0.0, NICE_HEIGHT }; // Guess height nice_size.width = page_width(); [ScrollView getFrameSize:&scroll_frame.size forContentSize: &nice_size horizScroller:NO vertScroller:YES borderType: NX_LINE]; { int i; /* Slot address */ for(i=0; (i<SLOTS) && slot[i]; i++) ; /* Find spare slot */ if (i=SLOTS) i = (window_sequence = (window_sequence+1) % SLOTS); slot[i] = self; slotNumber = i; scroll_frame.origin.x = 150 + (slotNumber % 10) * 30 + ((slotNumber/10)%3)* 40; scroll_frame.origin.y = 185 - (slotNumber % 10) * 20 - ((slotNumber/10)%3)* 3; } // Build a window around the text in order to display it. #define NX_ALLBUTTONS 7 // Fudge -- the followin methos is obsolete in 3.0: window = [Window newContent: &scroll_frame style: NX_TITLEDSTYLE backing: NX_BUFFERED buttonMask: NX_ALLBUTTONS defer: NO]; // display now [window setDelegate:self]; // Get closure warning [window makeKeyAndOrderFront:self]; // Make it visible [window setBackgroundGray: 1.0]; // White seems to be necessary. scrollview = [ScrollView newFrame:&scroll_frame]; [scrollview setVertScrollerRequired:YES]; [scrollview setHorizScrollerRequired:NO]; // Guess. [[window setContentView:scrollview] free]; // Free old view, size new one. [scrollview setDocView:self]; [self setOpaque:YES]; // Suggested in the book [self setVertResizable:YES]; // Changes size automatically [self setHorizResizable:NO]; [self setMinSize:&min_size]; // Stop it shrinking to nought [self setMaxSize:&max_size]; // Stop it being chopped when editing [self notifyAncestorWhenFrameChanged: YES]; // Tell scrollview See QA 555 [window display]; // Maybe we will see it now return self; } // Return Instance Variables // ------------------------- - server { return server;} - nodeAnchor { return nodeAnchor; } - (BOOL)isIndex { return isIndex; } /* Return reference to a part of, or all of, this node ** --------------------------------------------------- */ // Generate an anchor for a given part of this node, giving it an // arbitrary (numeric) name. - (Anchor *) anchor { Anchor * a; char s[20]; sprintf(s,"%c%i",ANCHOR_ID_PREFIX, nextAnchorNumber++); a = [Anchor newParent:nodeAnchor tag:s]; [delegate textDidChange:self]; return a; } // Check whether an anchor has been selected // ----------------------------------------- - (Anchor *) anchorSelected { int sor; NXRun *r, *s, *e; /* Scan, Start and end runs */ Anchor * a; for (sor = 0, s=theRuns->runs; sor+s->chars<=sp0.cp; sor = sor+((s++)->chars)) ; for (e=s; sor+e->chars<spN.cp; sor = sor+ (e++)->chars); for(r=s; r<=e; r++) { if (a= (Anchor *)r->info) return a; } if (TRACE) printf("HyperText: No anchor selected.\n"); return nil; } // Public method: Generate an anchor for the selected text // -------------------------------------------------------- // // If the document is not editable, then nil is returned // (unless the user asks for an existing anchor). // - (Anchor *) referenceSelected { Anchor * a; HTStyle * style = HTStyleNew(); a = [self anchorSelected]; if (a) return a; /* User asked for existing one */ if ([self isEditable]) [window setDocEdited:YES]; else return nil; a = [self anchor]; style->anchor = a; [self applyStyle:style]; if (TRACE) printf("HyperText: New dest anchor %i from %i to %i.\n", a, sp0.cp, spN.cp); [delegate textDidChange:self]; return a; } // Generate a live anchor for the text, and link it to a given one // ---------------------------------------------------------------- - (Anchor *) linkSelTo:(Anchor *)anAnchor { Anchor * a; HTStyle * style = HTStyleNew(); if (!anAnchor) return nil; /* Anchor must exist */ if ([self isEditable]) [window setDocEdited:YES]; else return nil; a = [self anchorSelected]; if (!a) { a = [self anchor]; if (TRACE) printf("HyperText: New source anchor %i from %i to %i.\n", a, sp0.cp, spN.cp); } else { [a select]; if (TRACE) printf("HyperText: Existing source anchor %i selected.\n",a); } style->anchor = a; [a linkTo:anAnchor]; // Link it up [self applyStyle:style]; // Will highlight it because linked free(style); return a; } // Purge anchor from selected text // ------------------------------- // // The anchor is left becuase in general we don't delete anchors. // In any case, we would have to check whether all text referencing it // was deleted. // - unlinkSelection { HTStyle * style = HTStyleNew(); if ([self isEditable]) [window setDocEdited:YES]; else return nil; style->anchor = CLEAR_POINTER; [self applyStyle:style]; free(style); return self; } - (Anchor *) referenceAll { return nodeAnchor; // Just return the same one each time } // Select an anchor // ---------------- // // If there are any runs linked to this anchor, we select them. Otherwise, // we just bring the window to the front. - selectAnchor:(Anchor *)anchor { NXRun *s, *e; /* run for start, run for end */ int start, sor; NXRun * limit = (NXRun *)((char *)theRuns->runs + theRuns->chunk.used); for (sor=0, s=theRuns->runs; s<limit; sor = sor+(s++)->chars) { if (s->info == (void *)anchor){ start = sor; for (e=s; (e < limit) &&((e+1)->info == (void*)anchor); sor = sor+(e++)->chars); [window makeKeyAndOrderFront: self]; [self setSel:start:sor+e->chars]; return [self scrollSelToVisible]; } } if (TRACE) printf("HT: Anchor has no explicitly related text.\n"); return [window makeKeyAndOrderFront: self]; } // // Return selected link (if any) selectedLink: // ----------------------------- // // This implementation scans down the list of anchors to find the first one // on the list which is at least partially selected. // - selectedLink { int sor; /* Start of run */ NXRun *r, *s, *e; /* Scan, Start and end runs */ Anchor * a; int startPos, endPos; for (sor = 0, s=theRuns->runs; sor+s->chars<=sp0.cp; sor = sor+((s++)->chars)) ; startPos = sor; /* Start of s */ for (e=s; sor+e->chars<spN.cp; sor = sor+ (e++)->chars); for(r=s, a=nil; r<=e; startPos = startPos + (r++)->chars) { if (a= (Anchor *)r->info) { break; } } if (!a) { if (TRACE) printf("HyperText: No anchor selected.\n"); return nil; } // Extend/reduce selection to entire anchor { endPos = startPos + r->chars; for (s=r; (Anchor *)((s-1)->info) == a; s--) startPos = startPos-(s-1)->chars; for (e=r; (Anchor *)((e+1)->info) == a; e++) endPos = endPos+(e+1)->chars; [self setSel:startPos:endPos]; } return a; } /// Follow selected link (if any) followLink: // ----------------------------- // // Find selected link and follow it - followLink { Anchor * a = [self selectedLink]; if (!a) return a; // No link selected if ([a follow]) return a; // Try to follow link if (TRACE) printf("HyperText: Can't follow anchor.\n"); return a; // ... but we did highlight it. } - setTitle:(const char *)title { return [window setTitle:title]; } // STYLE METHODS // ============= // // Find Unstyled Text // ------------------ // // We have to check whether the paragraph style for each run is one // on the style sheet. // - selectUnstyled: (HTStyleSheet *)sheet { NXRun * r = theRuns->runs; int sor; for (sor=0; sor<textLength; r++) { if (!HTStyleForParagraph(sheet, r->paraStyle)) { [self setSel:sor:sor+r->chars]; /* Select unstyled run */ return self; } sor = sor+r->chars; } return nil; } // Copy a style into a run // ----------------------- static void apply(HTStyle * style, NXRun * r) { if (style->font) { r->font = style->font; } if (style->paragraph) { r->paraStyle = style->paragraph; } if (style->anchor) { r->info = (style->anchor == CLEAR_POINTER) ? 0 : (style->anchor); } if (style->textGray>=0) r->textGray = style->textGray; r->rFlags.underline = NO; if (r->info) { // r->textGray = 0.166666666; /* Slightly grey - horrid */ if ([(Anchor *)(r->info) destination]) { // r->textGray = NX_DKGRAY; /* Anchor highlighting */ r->rFlags.underline = YES; } } r->rFlags.dummy = (r->info != 0); /* Keep track for typingRun */ if (style->textRGBColor>=0) r->textRGBColor = style->textRGBColor; } // Check whether copying a style into a run will change it // ------------------------------------------------------- static BOOL willChange(HTStyle * style, NXRun *r) { if (r->font != style->font) return YES; if (style->textRGBColor>=0) if (r->textRGBColor != style->textRGBColor) return YES; if (style->textGray>=0) if (r->textGray != style->textGray) return YES; if (style->paragraph) { if (r->paraStyle != style->paragraph) return YES; } if (style->anchor) { if (r->info != style->anchor) return YES; } return NO; } // Update a style // -------------- // // - updateStyle:(HTStyle *)style { NXRun * r = theRuns->runs; int sor; for (sor=0; sor<textLength; r++) { if (r->paraStyle == style->paragraph) apply(style, r); sor = sor+r->chars; } [self calcLine]; [window display]; return nil; } // Delete an anchor from this node, without freeing it. // ---------------- // // - disconnectAnchor:(Anchor *)anchor { NXRun * r = theRuns->runs; int sor; for (sor=0; sor<textLength; r++) { if (r->info == (void *)anchor) r->info = 0; r->textGray = NX_BLACK; sor = sor+r->chars; } [window display]; return nil; } // Find start of paragraph // ----------------------- // // Returns the position of the character after the newline, or 0. // - (int) startOfParagraph: (int) pos { NXTextBlock * block; int sob; unsigned char * p; for(block=firstTextBlock, sob=0; sob+block->chars <=pos; block=block->next) sob = sob + block->chars; for(p=block->text+(pos-sob)-1; p >= block->text; p--) if (*p == '\n') return sob + (p - block->text) +1 ;/* Position of newline */ while (block->prior) { block = block->prior; sob = sob - block->chars; for(p=block->text+(block->chars-1); p>=block->text; p--) if (*p == '\n') return sob + (p-block->text)+1;/* Position of newline */ } return 0; } // Find end of paragraph // ----------------------- // // Returns the position after the newline, or the length of the text. // Note that any number of trailing newline characters are included in // this paragraph .. basically because the text object does not support // the concept of space after or before paragraphs, so extra paragrpah // marks must be used. // - (int) endOfParagraph: (int) pos { NXTextBlock * block; int sob; unsigned char * p; BOOL found_newline = NO; if (pos>=textLength) return textLength; for(block=firstTextBlock, sob=0; sob+block->chars <=pos; block=block->next) sob = sob + block->chars; // Find text block for pos p = block->text+(pos-sob); // Start part way through this one while (block) { for(; p < block->text+block->chars; p++) { if (found_newline) { if (*p != '\n') return sob + (p-block->text); /* Position after newline */ } else { if (*p == '\n') { found_newline = YES; } } } sob = sob + block->chars; /* Move to next block */ block = block->next; if (block) p = block->text; } return textLength; } // Do two runs imply the same format? // ---------------------------------- BOOL run_match(NXRun* r1, NXRun *r2) { return (r1->font == r2->font) && (r1->paraStyle == r2->paraStyle) && (r1->textGray == r2->textGray) && (r1->textRGBColor == r2->textRGBColor) && (r1->superscript == r2->superscript) && (r1->subscript == r2->subscript) && (r1->info == r2->info) && (*(int *)&r1->rFlags == *(int*)&r2->rFlags); } // Check Consecutive runs and merge if necessary // --------------------------------------------- // // If the runs match in EVERY way, they are combined into one, and // all the other runs are shuffled down. // - (BOOL) mergeRun: (NXRun *) run { NXRun * r, *last; if (run_match(run, run+1) ) { if (TRACE) printf("HT: Merging run %i\n", run); run->chars = run->chars +(run+1)->chars; last = ((NXRun *)((char*)theRuns->runs + theRuns->chunk.used))-1; for(r=run+1; r<last; r++) r[0] = r[1]; theRuns->chunk.used = theRuns->chunk.used - sizeof(*r); return YES; } return NO; } // Apply style to a given region // ----------------------------- // // Note that one should not have two consecutive runs of the same style, // nor any zero length runs. We have a little calculation, therefore, // in order to work out how many runs will eventually be needed: // this may be more or less than we started with. // Remember that appling a style to a run may or may not change it. // // PS: Actually, we notice that text insertion does leave two consecutive // runs the same in the Text object, but deletion cleans up. - applyStyle:(HTStyle *)style from:(int)start to:(int)end { int pos; /* Character position within text */ int increase; /* Number of runs to be split */ int new_used; /* New number of bytes in runs */ BOOL need_run_before, need_run_after;/* Sometimes we don't need them */ int run_before_start, run_after_end;/* Start of run_before etc */ NXRun * s, *e; /* Start and end run */ NXRun *p; /* Pointer to run being read */ NXRun *w; /* Pointer to run being written */ NXRun *r; /* Pointer to end of runs */ if (start == end) { apply(style, &typingRun); /* Will this work? */ if (TRACE) printf("Style applied to typing run\n"); return nil; /* Can't operate on nothing */ } // First we determine in which runs the first and last characters to // be changed lie. for (pos=0, s=theRuns->runs; pos+s->chars<=start; pos = pos+((s++)->chars)) /*loop*/; /* s points to run containing char after selection start */ run_before_start = pos; for (e=s; pos+e->chars < end; pos = pos+((e++)->chars)) ; /* Find end run */ /* e points to run containing character before selection end */ run_after_end = pos+e->chars; r = (NXRun *) (((char *)(theRuns->runs)) +theRuns->chunk.used); /* The end*/ if (TRACE) { printf("Runs: used=%i, elt. size=%i, %i elts, total=%i\n", theRuns->chunk.used, sizeof(*r), r - theRuns->runs, (r-theRuns->runs)*sizeof(*r) ); printf(" runs at %i, r=%i. textLength:%i, r ends at:%i\n", theRuns->runs, r, textLength, pos); } // Move up runs as necessary in order to make room for the splitting // of the start and end runs into two. We only do this if necessary. if (!willChange(style, s)) start = run_before_start; /* No run before is needed now */ need_run_before = (start>run_before_start); if (!willChange(style, e)) end = run_after_end; /* No run after is needed now */ need_run_after = (end < run_after_end); if (TRACE) printf( "Run s=%i, starts at %i; changing (%i,%i); Run e=%i ends at %i\n", s-theRuns->runs, run_before_start, start, end, e-theRuns->runs, run_after_end); increase = need_run_after + need_run_before; if (increase) { new_used = theRuns->chunk.used + increase*sizeof(*r); if (new_used> theRuns->chunk.allocated) { NXRun* old = theRuns->runs; theRuns = (NXRunArray*)NXChunkGrow(&theRuns->chunk, new_used); if (theRuns->runs !=old) { /* Move pointers */ if (TRACE) printf("HT:Apply style: moving runs!\n"); e = theRuns->runs + (e-old); r = theRuns->runs + (r-old); s = theRuns->runs + (s-old); } } for (p=r-1; p>=e; p--) p[increase] = p[0]; /* Move up the runs after */ r = r+increase; /* Point to after them 910212*/ /* p = e-1 */ if (need_run_after) { e = e + increase-1; /* Point last to be changed */ e[0] = e[1]; /* Copy the last run */ e[1].chars = run_after_end - end; e[0].chars = e[0].chars - e[1].chars; /* Split the run into two */ } if (need_run_before) { for(;p>=s; p--) p[1] = p[0]; /* Move runs up, copying 1st*/ s[0].chars = start - run_before_start; /* Split the run into two */ if (need_run_after && (s+1==e)) { /* If only one middle run */ s[1].chars = end-start; /* The run we need */ } else { s[1].chars = s[1].chars - s[0].chars; /* The remainder */ } s++; /* Move on to point to first run to be changed */ if (!need_run_after) e++; /* First to be changed */ } theRuns->chunk.used = new_used; } /* end if increase */ // We consider the bit of text which is to be styled, s thru e. // We scan through, first, applying the style, until we find two runs which // need to be merged. p=s; if (p==theRuns->runs) { apply(style, p++); /* Don't merge with run -1! */ } for(; p<=e; p++) { apply(style, p); if (run_match(p,p-1)) { break; } } // Once we have merged two runs, we have to copy the rest of them across, // merging others as necessary. w = p-1; /* w now points to last written run */ for(;p<=e; p++) { apply(style, p); if (run_match(p,w)) { w->chars = w->chars+p->chars; /* Combine with w */ } else { w++; /* or skip */ *w = *p; /* and keep a copy */ } } // Now, is any runs were merged, we have to copy the rest of the runs down // and decrease the size of the chunk. w++; /* Point to next to be written */ if (w<p) { /* If any were moved, */ for(;p<r;) *w++ = *p++; /* Move the following runs down */ theRuns->chunk.used = (char*)w - (char*)theRuns->runs; } [self calcLine]; /* Update line breaks */ return [window display]; /* Update window */ } // Apply a style to the selection // ------------------------------ // // If the style is a paragraph style, the // selection is extended to encompass a number of paragraphs. // - applyStyle:(HTStyle *)style { int start, end; if (TRACE) printf("Applying style %i to (%i,%i)\n", style, sp0.cp, spN.cp); if (sp0.cp<0) { /* No selection */ return [self applyStyle:style from:0 to:0]; /* Apply to typing run */ } if (!style) return nil; if ([self isEditable]) [window setDocEdited:YES]; else return nil; start = sp0.cp; end = spN.cp; if (style->paragraph) { /* Extend to an integral number of paras. */ start = [self startOfParagraph:start]; end = [self endOfParagraph:end]; } return [self applyStyle:style from:start to:end]; } // Apply style to all similar text // ------------------------------- // // - applyToSimilar:(HTStyle*)style { NXRun * r = theRuns->runs; int sor; NXRun old_run; for (sor = 0; sor<=sp0.cp; sor = sor+((r++)->chars)) ;/* Find run after */ old_run = *(r-1); /* Point to run for start of selection */ if (TRACE) printf( "Applying style %i to unstyled text similar to (%i,%i)\n", style, sp0.cp, spN.cp); for(r=theRuns->runs; (char*)r-(char*)theRuns->runs < theRuns->chunk.used; r++) { if (r->paraStyle == old_run.paraStyle) { if(TRACE) printf(" Applying to run %i\n", r); apply(style,r); if (r!=theRuns->runs){ if ([self mergeRun:r-1]) r--; /* Do again if shuffled down */ } } } [self calcLine]; [window display]; return self; } // Pick up the style of the selection // ---------------------------------- - (HTStyle *) selectionStyle:(HTStyleSheet *) sheet { NXRun * r = theRuns->runs; int sor; for (sor = 0; sor<=sp0.cp; sor = sor+((r++)->chars)) ;/* Find run after */ r--; /* Run for start of selection */ return HTStyleForRun(sheet, r); /* for start of selection */ } // Another replaceSel method, this time using styles: // ------------------------------------------------- // // The style is as given, or where that is not defined, as the // current style of the selection. - replaceSel: (const char *)aString style:(HTStyle*)aStyle { NXRun * r = theRuns->runs; int sor; NXRunArray newRuns; for (sor = 0; sor<=sp0.cp; sor = sor+((r++)->chars)) ;/* Find run after */ r--; /* Run for start of selection */ newRuns.runs[0] = *r; /* Copy it */ newRuns.chunk.used = sizeof(*r); /* 1 run used */ apply(aStyle, newRuns.runs); /* change it */ newRuns.runs->chars = strlen(aString); /* Match the size to the string */ return [self replaceSel:aString length:newRuns.runs->chars runs:&newRuns]; } // Read in as Plain Text readText: // --------------------- // // This method overrides the method of Text, so as to force a plain text // hypertext to be monofont and fixed width. Also, the window is updated. // - readText: (NXStream *)stream { // [self setMonoFont:YES]; Seems to leave it in a strange state [self setHorizResizable:YES]; [self setNoWrap]; [self setFont:[Font newFont:"Ohlfs" size:10.0]]; // @@ Should be XMP [super readText:stream]; format = WWW_PLAINTEXT; // Remember #ifdef NOPE { NXRect frm; /* Try this to get over "text strangeness" */ [self getFrame:&frm]; /* on plain text only Aug 91 */ [self renewRuns:NULL text:NULL frame:&frm tag:0]; } #endif [self adjustWindow]; return self; } // Read in as Rich Text readRichText: // --------------------- // // This method overrides the method of Text, so as to force a plain text // hypertext to be monofont and fixed width. Also, the window is updated. // - readRichText: (NXStream *)stream { id status = [super readRichText:stream]; [self adjustWindow]; format = WWW_RICHTEXT; // Remember return status; } // Window Delegate Methods // ======================= // Prevent closure of edited window without save // - windowWillClose:sender { int choice; if (![window isDocEdited]) return self; choice = NXRunAlertPanel("Close", "Save changes to `%s'?", "Yes", "No", "Don't close", [window title]); if (choice == NX_ALERTALTERNATE) return self; if (choice == NX_ALERTOTHER) return nil; return [server saveNode:self]; } // Change configuration as window becomes key window // - windowDidBecomeMain:sender { return [delegate hyperTextDidBecomeMain:self]; } /* FORMAT CONVERSION FROM SGML ** =========================== ** ** As much as possible, this is written in C for portability. It is in a separate ** include file which could be used elsewhere. */ /* Input procedure for printing a trace as we go */ #define NEXT_CHAR NXGetc(sgmlStream) #define BACK_UP NXUngetc(sgmlStream) /* Globals for using many subroutines within a method */ static NXStream *sgmlStream; static HyperText * HT; /* Pointer to self for C */ // Inputting from the text object: // ------------------------------ static unsigned char * read_pointer; /* next character to be read */ static unsigned char * read_limit; static NXTextBlock * read_block; void start_input() { read_block = HT->firstTextBlock; read_pointer = read_block->text; /* next character to be read */ read_limit = read_pointer+read_block->chars; } unsigned char next_input_block() { char c = *read_pointer; read_block = read_block->next; if (!read_block) read_block = HT->firstTextBlock; /* @@@ FUDGE */ read_pointer = read_block->text; read_limit = read_pointer + read_block->chars; return c; } #define START_INPUT start_input() #define NEXT_TEXT_CHAR (read_pointer+1==read_limit? next_input_block():*read_pointer++) // Outputting to the text object // ============================= // // These macros are used by the parse routines // #define BLOCK_SIZE NX_TEXTPER /* Match what Text seems to use */ static NXTextBlock *write_block; /* Pointer to block being filled */ static unsigned char *write_pointer; /* Pointer to next characetr to be written */ static unsigned char *write_limit; /* Pointer to the end of the allocated area*/ static NXRun * lastRun; /* Pointer to the run being appended to */ static int original_length; /* of text */ #define OUTPUT(c) { *write_pointer++ = (c); \ if (write_pointer==write_limit) {end_output(); append_start_block(); }} #define OUTPUTS(string) {const char * p; for(p=(string);*p;p++) OUTPUT(*p);} #define START_OUTPUT append_begin() #define FINISH_OUTPUT finish_output() #define LOADPLAINTEXT loadPlainText() #define SET_STYLE(s) set_style(s) // Allocate a text block to accumulate text // // Bugs: // It might seem logical to set the "malloced" bit to 1, because the text block // has been allocted with malloc(). However, this crashes the program as at the // next edit of the text, the text object frees the block while still using it. // Chaos results, sometimes corrupting the stack and/or looping for ages. @@ // We therefore set it to zero! (This might have been something else -TBL) // void append_start_block() { NXTextBlock *previous_block=write_block; /* to previous write block */ if (TRACE)printf(" Starting to append new block.\n"); lastRun = ((NXRun*) ((char*)HT->theRuns->runs + HT->theRuns->chunk.used))-1; write_block = (NXTextBlock*)malloc(sizeof(*write_block)); write_block->tbFlags.malloced=0; /* See comment above */ write_block->text = (unsigned char *)malloc(BLOCK_SIZE); write_block->chars = 0; // For completeness: not used. write_pointer = write_block->text; write_limit = write_pointer + BLOCK_SIZE; // Add the block into the linked list after previous block: write_block->prior = previous_block; write_block->next = previous_block->next; if (write_block->next) write_block->next->prior = write_block; else HT->lastTextBlock = write_block; previous_block->next = write_block; } // Start the output process altogether // void append_begin() { if (TRACE)printf("Begin append to text.\n"); [HT setText:""]; // Delete everything there original_length = HT->textLength; if (TRACE) printf("Text now contains %i characters\n", original_length); lastRun = ((NXRun*) ((char*)HT->theRuns->runs + HT->theRuns->chunk.used))-1; // Use the last existing text block: write_block = HT->lastTextBlock; // It seems that the Text object doesn't like to be empty: it always wants to // have a newline in at leats. However, we need it seriously empty and so we // forcible empty it. CalcLine will crash if called with it in this state. if (original_length==1) { if (TRACE) printf("HT: Clearing out single character from Text.\n"); lastRun->chars = 0; /* Empty the run */ write_block->chars = 0; /* Empty the text block */ HT->textLength = 0; /* Empty the whole Text object */ original_length = 0; /* Note we have cleared it */ } write_pointer = write_block->text+write_block->chars; write_limit = write_pointer + BLOCK_SIZE; } // Set a style for new text // void set_style(HTStyle *style) { if (!style) { if (TRACE) printf("set_style: style is null!\n"); return; } if (TRACE) printf(" Changing to style `%s' -- %s change.\n", style->name, willChange(style, lastRun) ? "will" : "won't"); if (willChange(style, lastRun)) { int size = (write_pointer - write_block->text); lastRun->chars = lastRun->chars + size - write_block->chars; write_block->chars = size; if (lastRun->chars) { int new_used = (((char *)(lastRun+2)) - (char*)HT->theRuns->runs); if (new_used > HT->theRuns->chunk.allocated) { if (TRACE) printf(" HT: Extending runs.\n"); HT->theRuns = (NXRunArray*)NXChunkGrow( &HT->theRuns->chunk, new_used); lastRun = ((NXRun*) ((char*)HT->theRuns->runs + HT->theRuns->chunk.used))-1; } lastRun[1]=lastRun[0]; lastRun++; HT->theRuns->chunk.used = new_used; } apply(style, lastRun); lastRun->chars = 0; /* For now */ } } // Transfer text to date to the Text object // ---------------------------------------- void end_output() { int size = (write_pointer - write_block->text); if (TRACE)printf( " HT: Adding block of %i characters, starts: `%.20s...'\n", size, write_block->text); lastRun->chars = lastRun->chars + size - write_block->chars; write_block->chars = size; HT->textLength = HT->textLength + size; } // Finish altogether // ----------------- void finish_output() { int size = write_pointer - write_block->text; if (size==0) { HT->lastTextBlock = write_block->prior; /* Remove empty text block */ write_block->prior->next = 0; free(write_block->text); free(write_block); } else { end_output(); } // get rid of zero length run if any if (lastRun->chars==0) { /* Chop off last run */ HT->theRuns->chunk.used= (char*)lastRun - (char*)HT->theRuns; } // calcLine requires that the last character be a newline! { unsigned char * p = HT->lastTextBlock->text + HT->lastTextBlock->chars - 1; if (*p != '\n') { if (TRACE) printf( "HT: Warning: Last character was %i not newline: overwriting!\n", *p); *p = '\n'; } } [HT adjustWindow]; /* Adjustscrollers and window size */ } // Loading plain text void loadPlainText() { [HT setMonoFont:YES]; [HT setHorizResizable:YES]; [HT setNoWrap]; [HT readText:sgmlStream]; /* will read to end */ [HT adjustWindow]; /* Fix scrollers */ } // Methods enabling an external parser to add styled data // ------------------------------------------------------ // // These use the macros above in the same way as a built-in parser would. // - appendBegin { HT = self; START_OUTPUT; return self; } - appendStyle:(HTStyle *) style { SET_STYLE(style); return self; } - appendText: (const char *)text { OUTPUTS(text); return self; } - appendEnd { FINISH_OUTPUT; return self; } // Begin an anchor - (Anchor *)appendBeginAnchor: (const char *)name to:(const char *)reference { HTStyle * style = HTStyleNew(); char * parsed_address; Anchor * a = *name ? [Anchor newParent:nodeAnchor tag:name] : [self anchor]; style->anchor = a; [(Anchor *)style->anchor isLastChild]; /* Put in correct order */ if (*reference) { /* Link only if href */ parsed_address = HTParse(reference, [nodeAnchor address], PARSE_ALL); [(Anchor *)(style->anchor) linkTo: [Anchor newAddress:parsed_address]]; free(parsed_address); } SET_STYLE(style); /* Start anchor here */ free(style); return a; } - appendEndAnchor // End it { HTStyle * style = HTStyleNew(); style->anchor = CLEAR_POINTER; SET_STYLE(style); /* End anchor here */ free(style); return self; } // Reading from a NeXT stream // -------------------------- #define END_OF_FILE NXAtEOS(sgmlStream) #define NEXT_CHAR NXGetc(sgmlStream) #define BACK_UP NXUngetc(sgmlStream) #include "ParseHTML.h" // Methods overriding Text methods // =============================== // // Respond to mouse events // ----------------------- // // The first click will have set the selection point. On the second click, // we follow a link if possible, otherwise we allow Text to select a word as usual. // - mouseDown:(NXEvent*)theEvent { if (theEvent->data.mouse.click != 2) return [super mouseDown:theEvent]; if (![self followLink]) return [super mouseDown:theEvent]; return self; } // The following are necessary to undo damage done by the Text object // in version 2.0 of NeXTStep. For some reason, iff the "info" is // nonzero, text typed in is given // a different copy of the typingRun parastyle, and zero anchor info. // Otherwise, the current run is broken in two at the insertion point, // but no changes made to the run contents. // The problem with simply repairing is that many runs will be made inside // an anchor. // We have to use a "dummy" flag to mean "This has an anchor: be careful!" // This is horrible. - keyDown:(NXEvent*)theEvent #ifdef TRY1 { id result; NXTextStyle *typingPara = typingRun.paraStyle; int originalLength = textLength; int originalStart = sp0.cp; int originalEnd = spN.cp; result = [super keyDown:theEvent]; { int inserted = originalEnd-originalStart + textLength-originalLength; if (TRACE) printf( "KeyDown, size(sel) %i (%i-%i)before, %i (%i-%i)after.\n", originalLength, originalStart, originalEnd, textLength, sp0.cp, spN.cp); if (inserted>0) { NXRun * s; int pos; int start = sp0.cp-inserted; for (pos=0, s=theRuns->runs; pos+s->chars<=start; pos = pos+((s++)->chars)) /*loop*/; // s points to run containing first char of insertion if (pos!=start) printf( "HT: Strange: inserted %i at %i, start of run=%i !!\n", inserted, start, pos); if (s > theRuns->runs) { /* ie s-1 is valid */ s->paraStyle = typingPara; /* Repair damage to runs */ /* What about freeing the old paragraph style? @@ */ s->info = (s-1)->info; s->rFlags.dummy = 1; /* Pass on flag */ } } } return result; } #else // The typingRun field does not seem to reliably reflect the // format which would be appropriate if typing were to occur. // We have to use our own. { NXRun run; { NXRun * s; /* To point to run BEFORE selection */ int pos; /* If there is a nonzero selection, take the run containing the ** first character. If the selection is empty, take the run containing the ** character before the selection. */ if (sp0.cp == spN.cp) { for (pos=0, s=theRuns->runs; pos+s->chars<sp0.cp; /* Before */ pos = pos+((s++)->chars)) /*loop*/; } else { for (pos=0, s=theRuns->runs; pos+s->chars<=sp0.cp; /* First ch */ pos = pos+((s++)->chars)) /*loop*/; } /* Check our understanding */ if (typingRun.paraStyle != 0) { if (typingRun.paraStyle != s->paraStyle) printf("WWW: Strange: Typing run has bad style.\n"); if ((s->info != 0) && (typingRun.info != s->info)) printf( "WWW: Strange: Typing run has bad anchor info.\n"); } typingRun = *s; /* Copy run to be used for insertion */ run = *s; /* save a copy */ } if (!run.rFlags.dummy) return [super keyDown:theEvent]; // OK! { id result; int originalLength = textLength; int originalStart = sp0.cp; int originalEnd = spN.cp; result = [super keyDown:theEvent]; /* Does it really change? YES! */ if (TRACE) { if (typingRun.info != run.info) printf( "Typing run info was %p, now %p !!\n", run.info, typingRun.info); if (typingRun.paraStyle != run.paraStyle) printf( "Typing run paraStyle was %p, now %p !!\n", run.paraStyle, typingRun.paraStyle); } /* Patch the new run if necessary: */ { int inserted = originalEnd-originalStart + textLength-originalLength; if (TRACE) printf( "KeyDown, size(sel) %i (%i-%i)before, %i (%i-%i)after.\n", originalLength, originalStart, originalEnd, textLength, sp0.cp, spN.cp); if (inserted>0) { NXRun * s; int pos; int start = sp0.cp-inserted; for (pos=0, s=theRuns->runs; pos+s->chars<=start; pos = pos+((s++)->chars)) /*loop*/; // s points to run containing first char of insertion if (pos!=start) { /* insert in middle of run */ if (TRACE) printf( "HT: Inserted %i at %i, in run starting at=%i\n", inserted, start, pos); } else { /* inserted stuff starts run */ if (TRACE) printf ("Patching info from %d to %d\n", s->info, run.info); s->info = run.info; s->paraStyle = run.paraStyle; /* free old one? */ s->rFlags.dummy = 1; } } /* if inserted>0 */ } /* block */ return result; } } #endif // After paste, determine paragraph styles for pasted material: // ------------------------------------------------------------ - paste:sender; { id result; int originalLength = textLength; int originalStart = sp0.cp; int originalEnd = spN.cp; Anchor * typingInfo; result = [super paste:sender]; // Do the paste { int inserted = originalEnd-originalStart + textLength-originalLength; if (TRACE) printf( "Paste, size(sel) %i (%i-%i)before, %i (%i-%i)after.\n", originalLength, originalStart, originalEnd, textLength, sp0.cp, spN.cp); if (inserted>0) { NXRun *s, *r; int pos; int start = sp0.cp-inserted; for (pos=0, s=theRuns->runs; pos+s->chars<=start; pos = pos+((s++)->chars)) /*loop*/; // s points to run containing first char of insertion if (pos!=sp0.cp-inserted) printf("HT paste: Strange: insert@%i != run@%i !!\n", start, pos); if (s > theRuns->runs) typingInfo = (s-1)->info; else typingInfo = 0; for (r=s; pos+r->chars<sp0.cp; pos=pos+(r++)->chars) { r->paraStyle = HTStyleForRun(styleSheet, r)->paragraph; r->info = typingInfo; } } } return result; } @end
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.