/* * Copyright (c) 2021-2022 joshua stein * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "wikipedia.h" #include "browser.h" #include "focusable.h" #include "http.h" #include "util.h" #define PADDING 10 #define BROWSER_FONT_SIZE 10 #define BROWSER_FONT geneva bool browser_close(struct focusable *focusable); void browser_idle(struct focusable *focusable, EventRecord *event); void browser_update_menu(struct browser *browser); void browser_update(struct focusable *focusable, EventRecord *event); void browser_key_down(struct focusable *focusable, EventRecord *event); void browser_mouse_down(struct focusable *focusable, EventRecord *event); bool browser_handle_menu(struct focusable *focusable, short menu, short item); void browser_atexit(struct focusable *focusable); bool browser_will_te_overflow(struct browser *browser, TEHandle te, short line_height); bool browser_debug_enabled(struct browser *browser); void browser_live_search(struct browser *browser); void browser_hide_search_results(struct browser *browser); Pattern fill_pattern; void browser_idle(struct focusable *focusable, EventRecord *event) { struct browser *browser = (struct browser *)focusable->cookie; size_t len; switch (browser->state) { case BROWSER_STATE_IDLE: TEIdle(browser->input_te); if (browser->last_input_for_search != 0 && (Ticks - browser->last_input_for_search > 30)) { browser->last_input_for_search = 0; browser_live_search(browser); } break; case BROWSER_STATE_ARTICLE_GET: { TERec *te; char *input; HLock(browser->input_te); te = *(browser->input_te); HLock(te->hText); input = xstrndup(*(te->hText), te->teLength); HUnlock(te->hText); HUnlock(browser->input_te); if (input == NULL) { warn("Out of memory!"); break; } SetCursor(*(GetCursor(watchCursor))); if (browser->wpr) wikipedia_request_free(&browser->wpr); browser->wpr = wikipedia_fetch_article(browser, input); xfree(&input); browser->state = BROWSER_STATE_ARTICLE_PROCESS; break; } case BROWSER_STATE_ARTICLE_PROCESS: if (browser->wpr == NULL) { browser->state = BROWSER_STATE_IDLE; break; } wikipedia_request_process(browser->wpr); if (browser->wpr->state == WP_STATE_DONE) browser->state = BROWSER_STATE_ARTICLE_DONE; else if (browser->wpr->state == WP_STATE_HAVE_REDIRECT) { progress("Following redirect to %s...", browser->wpr->normalized_title); TESetText(browser->wpr->normalized_title, strlen(browser->wpr->normalized_title), browser->input_te); HLock(browser->input_te); InvalRect(&(*(browser->input_te))->viewRect); HUnlock(browser->input_te); browser->state = BROWSER_STATE_ARTICLE_GET; xfree(&browser->wpr); } break; case BROWSER_STATE_ARTICLE_DONE: UpdateScrollbarForTE(browser->win, browser->te_scroller, browser->te, false); progress(NULL); SetCursor(&arrow); browser->state = BROWSER_STATE_IDLE; break; } } struct browser * browser_init(char *query) { char title[256]; struct browser *browser; struct focusable *focusable; Rect bounds = { 0 }, te_bounds = { 0 }; Rect data_bounds = { 0, 0, 0, 1 }; /* tlbr */ Point cell_size = { 0, 0 }; Cell cell = { 0 }; short n, width, height; browser = xmalloczero(sizeof(struct browser)); if (browser == NULL) panic("Out of memory!"); browser->state = BROWSER_STATE_IDLE; GetIndPattern(&fill_pattern, sysPatListID, 22); /* main window */ width = screenBits.bounds.right - screenBits.bounds.left - PADDING; if (width > 540) width = 540; height = screenBits.bounds.bottom - screenBits.bounds.top - PADDING - (GetMBarHeight() * 2); if (height > 340) height = 340; center_in_screen(width, height, true, &bounds); snprintf(title, sizeof(title), "%s", PROGRAM_NAME); CtoPstr(title); browser->win = NewWindow(0L, &bounds, title, false, noGrowDocProc, (WindowPtr)-1L, true, 0); if (!browser->win) panic("Can't create window"); SetPort(browser->win); /* search input TE */ bounds.top = PADDING; bounds.left = PADDING; bounds.right = browser->win->portRect.right - PADDING; bounds.bottom = bounds.top + 16; te_bounds = bounds; InsetRect(&te_bounds, 2, 2); TextFont(geneva); TextSize(10); browser->input_te = TENew(&te_bounds, &bounds); if (browser->input_te == NULL) panic("Out of memory!"); TEAutoView(true, browser->input_te); TEActivate(browser->input_te); /* main article TE bounds */ browser->te_bounds.top = (*(browser->input_te))->viewRect.bottom + PADDING; browser->te_bounds.left = PADDING; browser->te_bounds.right = browser->win->portRect.right - SCROLLBAR_WIDTH - PADDING; browser->te_bounds.bottom = browser->win->portRect.bottom - PADDING; /* debug TE, off-screen until enabled */ bounds = browser->te_bounds; bounds.top += browser->win->portRect.bottom; bounds.bottom += browser->win->portRect.bottom; te_bounds = bounds; InsetRect(&te_bounds, 2, 2); browser->debug_te = TENew(&te_bounds, &bounds); if (browser->debug_te == NULL) panic("Out of memory!"); TEAutoView(false, browser->debug_te); (*(browser->debug_te))->caretHook = NullCaretHook; TEActivate(browser->debug_te); /* main article TE */ bounds = browser->te_bounds; te_bounds = bounds; InsetRect(&te_bounds, 2, 2); browser->te = TEStylNew(&te_bounds, &bounds); if (browser->te == NULL) panic("Out of memory!"); TEAutoView(false, browser->te); (*(browser->te))->caretHook = NullCaretHook; TEActivate(browser->te); /* scrollbar for diff text */ bounds.right = browser->win->portRect.right - PADDING; bounds.left = bounds.right - SCROLLBAR_WIDTH; bounds.bottom++; bounds.top--; browser->te_scroller = NewControl(browser->win, &bounds, "\p", true, 1, 1, 1, scrollBarProc, 0L); browser_update_menu(browser); UpdateScrollbarForTE(browser->win, browser->te_scroller, browser->te, true); focusable = xmalloczero(sizeof(struct focusable)); if (focusable == NULL) panic("Out of memory!"); focusable->cookie = browser; focusable->win = browser->win; focusable->idle = browser_idle; focusable->update = browser_update; focusable->mouse_down = browser_mouse_down; focusable->key_down = browser_key_down; focusable->menu = browser_handle_menu; focusable->close = browser_close; focusable->atexit = browser_atexit; focusable_add(focusable); if (query != NULL) { TESetText(query, strlen(query), browser->input_te); HLock(browser->input_te); InvalRect(&(*(browser->input_te))->viewRect); HUnlock(browser->input_te); browser->state = BROWSER_STATE_ARTICLE_GET; } return browser; } bool browser_close(struct focusable *focusable) { struct browser *browser = (struct browser *)focusable->cookie; if (browser->wpr) wikipedia_request_free(&browser->wpr); TEDispose(browser->te); DisposeWindow(browser->win); xfree(&browser); return true; } void browser_atexit(struct focusable *focusable) { struct browser *browser = (struct browser *)focusable->cookie; if (browser->wpr) { wikipedia_request_free(&browser->wpr); browser->wpr = NULL; } } void browser_update_menu(struct browser *browser) { size_t vlines; TERec *te; Cell cell = { 0, 0 }; TextFont(systemFont); TextSize(12); te = *(browser->te); DisableItem(edit_menu, EDIT_MENU_CUT_ID); if (te->selStart == te->selEnd) DisableItem(edit_menu, EDIT_MENU_COPY_ID); else EnableItem(edit_menu, EDIT_MENU_COPY_ID); DisableItem(edit_menu, EDIT_MENU_PASTE_ID); if (te->nLines == 0) { DisableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID); DisableItem(edit_menu, VIEW_MENU_DEBUG_ID); } else { EnableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID); EnableItem(edit_menu, VIEW_MENU_DEBUG_ID); } } void browser_update(struct focusable *focusable, EventRecord *event) { struct browser *browser = (struct browser *)focusable->cookie; Str255 buf; Rect r; short what = -1; if (event != NULL) what = event->what; switch (what) { case -1: case updateEvt: FillRect(&browser->win->portRect, fill_pattern); HLock(browser->input_te); r = (*(browser->input_te))->viewRect; HUnlock(browser->input_te); FillRect(&r, white); TEUpdate(&r, browser->input_te); InsetRect(&r, -1, -1); FrameRect(&r); HLock(browser->te); r = (*(browser->te))->viewRect; HUnlock(browser->te); FillRect(&r, white); TEUpdate(&r, browser->te); InsetRect(&r, -1, -1); FrameRect(&r); if (browser->search_results != NULL) { HLock(browser->search_results); r = (*(browser->search_results))->rView; HUnlock(browser->search_results); InsetRect(&r, -1, -1); FillRect(&r, white); FrameRect(&r); LUpdate(browser->win->visRgn, browser->search_results); } UpdtControl(browser->win, browser->win->visRgn); browser_update_menu(browser); break; } } void browser_mouse_down(struct focusable *focusable, EventRecord *event) { struct browser *browser = (struct browser *)focusable->cookie; struct browser_link *link; char str[255]; Cell selected = { 0 }; Point p; ControlHandle control; Rect r; short val, adj, page, len, part, off; size_t n; p = event->where; GlobalToLocal(&p); HLock(browser->input_te); r = (*(browser->input_te))->viewRect; HUnlock(browser->input_te); if (PtInRect(p, &r)) { TEClick(p, ((event->modifiers & shiftKey) != 0), browser->input_te); browser_update_menu(browser); return; } if (browser->search_results != NULL) { HLock(browser->search_results); r = (*(browser->search_results))->rView; HUnlock(browser->search_results); r.right += SCROLLBAR_WIDTH; if (PtInRect(p, &r)) { LClick(p, event->modifiers, browser->search_results); selected.v = 0; if (LGetSelect(true, &selected, browser->search_results)) { len = sizeof(str); LGetCell(&str, &len, selected, browser->search_results); TESetText(str, len, browser->input_te); HLock(browser->input_te); InvalRect(&(*(browser->input_te))->viewRect); HUnlock(browser->input_te); browser_hide_search_results(browser); browser->state = BROWSER_STATE_ARTICLE_GET; } return; } } if (browser_debug_enabled(browser)) { HLock(browser->debug_te); r = (*(browser->debug_te))->viewRect; HUnlock(browser->debug_te); if (PtInRect(p, &r)) { TEClick(p, ((event->modifiers & shiftKey) != 0), browser->debug_te); browser_update_menu(browser); return; } } else { HLock(browser->te); r = (*(browser->te))->viewRect; HUnlock(browser->te); if (PtInRect(p, &r)) { TEClick(p, ((event->modifiers & shiftKey) != 0), browser->te); off = TEGetOffset(p, browser->te); for (n = 0; n < browser->links_count; n++) { link = &browser->links[n]; if ((link->pos <= off) && (off < link->pos + link->len)) { if (event->modifiers & cmdKey) { browser_init(link->link); break; } TESetText(link->link, strlen(link->link), browser->input_te); HLock(browser->input_te); InvalRect(&(*(browser->input_te))->viewRect); HUnlock(browser->input_te); browser->state = BROWSER_STATE_ARTICLE_GET; break; } } browser_update_menu(browser); return; } } switch (part = FindControl(p, browser->win, &control)) { case inButton: break; case inUpButton: case inDownButton: case inPageUp: case inPageDown: if (control == browser->te_scroller) { if (browser_debug_enabled(browser)) SetTrackControlTE(browser->debug_te); else SetTrackControlTE(browser->te); } else break; TrackControl(control, p, TrackMouseDownInControl); break; case inThumb: val = GetCtlValue(control); if (TrackControl(control, p, 0L) == 0) break; adj = val - GetCtlValue(control); if (adj != 0) { val -= adj; if (control == browser->te_scroller) { if (browser_debug_enabled(browser)) TEScroll(0, adj * TEGetHeight(0, 0, browser->debug_te), browser->debug_te); else TEScroll(0, adj * TEGetHeight(0, 0, browser->te), browser->te); } SetCtlValue(control, val); } break; } } void browser_key_down(struct focusable *focusable, EventRecord *event) { struct browser *browser = (struct browser *)(focusable->cookie); char k; k = (event->message & charCodeMask); if (k == '\r') { browser->state = BROWSER_STATE_ARTICLE_GET; } else { TEKey(k, browser->input_te); TESelView(browser->input_te); browser->last_input_for_search = Ticks; } } void browser_live_search(struct browser *browser) { TERec *te; Rect bounds = { 0 }; Rect data_bounds = { 0, 0, 0, 1 }; /* tlbr */ char *input, **results, k; size_t nresults, n, len; Point cell_size = { 0, 0 }; Cell cell = { 0, 0 }; Rect r; HLock(browser->input_te); te = *(browser->input_te); if (te->teLength == 0) { HUnlock(browser->input_te); browser_hide_search_results(browser); return; } SetCursor(*(GetCursor(watchCursor))); HLock(te->hText); input = xstrndup(*(te->hText), te->teLength); HUnlock(te->hText); HUnlock(browser->input_te); if (input == NULL) { warn("Out of memory!"); return; } nresults = wikipedia_fetch_search_results(browser, input, &results); xfree(&input); if (browser->search_results == NULL) { bounds.top = (*(browser->input_te))->viewRect.bottom + 1; bounds.left = PADDING; bounds.bottom = bounds.top + 70; bounds.right = bounds.left + (((*(browser->input_te))->viewRect.right - (*(browser->input_te))->viewRect.left) / 2); browser->search_results = LNew(&bounds, &data_bounds, cell_size, 0, browser->win, false, false, false, true); if (!browser->search_results) panic("LNew failed"); (*(browser->search_results))->selFlags = lOnlyOne; LAddColumn(1, 0, browser->search_results); } else { LDelRow(0, 0, browser->search_results); } for (n = 0; n < nresults; n++) { len = strlen(results[n]); LAddRow(1, cell.v, browser->search_results); LSetCell(results[n], len, cell, browser->search_results); cell.v++; xfree(&results[n]); } r = (*(browser->search_results))->rView; FillRect(&r, white); InsetRect(&r, -1, -1); FrameRect(&r); LDoDraw(true, browser->search_results); LUpdate(browser->win->visRgn, browser->search_results); SetCursor(&arrow); } void browser_hide_search_results(struct browser *browser) { Rect r; if (browser->search_results == NULL) return; r = (*(browser->search_results))->rView; r.right += SCROLLBAR_WIDTH; InsetRect(&r, -1, -1); InvalRect(&r); LDispose(browser->search_results); browser->search_results = NULL; } bool browser_handle_menu(struct focusable *focusable, short menu, short item) { struct browser *browser = (struct browser *)focusable->cookie; switch (menu) { case EDIT_MENU_ID: switch (item) { case EDIT_MENU_COPY_ID: if (browser_debug_enabled(browser)) TECopy(browser->debug_te); else TECopy(browser->te); return true; case EDIT_MENU_SELECT_ALL_ID: if (browser_debug_enabled(browser)) TESetSelect(0, 1024 * 32, browser->debug_te); else TESetSelect(0, 1024 * 32, browser->te); return true; } break; case VIEW_MENU_ID: switch (item) { case VIEW_MENU_DEBUG_ID: { Rect bounds, te_bounds; bounds = browser->te_bounds; te_bounds = bounds; InsetRect(&te_bounds, 2, 2); HLock(browser->debug_te); HLock(browser->te); SetCtlValue(browser->te_scroller, GetCtlMin(browser->te_scroller)); if (browser_debug_enabled(browser)) { /* disable debugging */ SetItemMark(view_menu, VIEW_MENU_DEBUG_ID, noMark); (*(browser->debug_te))->destRect = (*(browser->te))->destRect; (*(browser->debug_te))->viewRect = (*(browser->te))->viewRect; (*(browser->te))->destRect = te_bounds; (*(browser->te))->viewRect = bounds; InsetRect(&te_bounds, -1, -1); EraseRect(&te_bounds); TEUpdate(&(*(browser->te))->destRect, browser->te); UpdateScrollbarForTE(browser->win, browser->te_scroller, browser->te, true); } else { /* enable debugging */ SetItemMark(view_menu, VIEW_MENU_DEBUG_ID, checkMark); (*(browser->te))->destRect = (*(browser->debug_te))->destRect; (*(browser->te))->viewRect = (*(browser->debug_te))->viewRect; (*(browser->debug_te))->destRect = te_bounds; (*(browser->debug_te))->viewRect = bounds; InsetRect(&te_bounds, -1, -1); EraseRect(&te_bounds); TEUpdate(&(*(browser->debug_te))->destRect, browser->debug_te); UpdateScrollbarForTE(browser->win, browser->te_scroller, browser->debug_te, true); } HUnlock(browser->debug_te); HLock(browser->te); break; } } break; } return false; } bool browser_debug_enabled(struct browser *browser) { short mark; GetItemMark(view_menu, VIEW_MENU_DEBUG_ID, &mark); return (mark != noMark); } size_t browser_debug_print(struct browser *browser, const char *str, size_t len) { char tstr[1024]; short line_height; short was_len; size_t n = 0; line_height = BROWSER_FONT_SIZE + 3; HLock(browser->debug_te); was_len = (*(browser->debug_te))->teLength; HUnlock(browser->debug_te); if (browser_will_te_overflow(browser, browser->debug_te, line_height)) return 0; while (len) { if (*str == '\n') tstr[n++] = '\r'; else tstr[n++] = *str; str++; len--; if (n == sizeof(tstr) || len == 0) { TESetSelect(SHRT_MAX, SHRT_MAX, browser->debug_te); TEInsert(tstr, n, browser->debug_te); if (len == 0) break; n = 0; } } if (was_len == 0) { SetCtlValue(browser->te_scroller, GetCtlMin(browser->te_scroller)); UpdateScrollbarForTE(browser->win, browser->te_scroller, browser->debug_te, false); } HUnlock(browser->debug_te); return len; } bool browser_will_te_overflow(struct browser *browser, TEHandle te, short line_height) { RgnHandle savergn; Rect zerorect = { 0, 0, 0, 0 }; HLock(te); /* too many lines */ if ((*te)->nLines >= (nitems((*te)->lineStarts) - 10)) return true; /* too many characters */ if ((*te)->teLength >= (SHRT_MAX - 500)) return true; /* rect of all lines is too tall */ if ((*te)->nLines * line_height >= (SHRT_MAX - 100)) return true; HUnlock(te); return false; } #define BROWSER_SCRAP_ELEMENTS 20 static Handle scrp_rec_h = NULL; bool browser_print(struct browser *browser, const char *str, size_t len, unsigned long style) { StScrpRec *scrp_rec; ScrpSTElement *scrp_ele; RgnHandle savergn; Rect zerorect = { 0, 0, 0, 0 }; size_t n; short line_height = 0, was_len = 0; static unsigned long last_style = 0; struct browser_link *link = NULL; if (browser_will_te_overflow(browser, browser->te, line_height)) return false; if (scrp_rec_h == NULL) { scrp_rec_h = xNewHandle(sizeof(short) + (sizeof(ScrpSTElement) * BROWSER_SCRAP_ELEMENTS)); HLock(scrp_rec_h); memset(*scrp_rec_h, 0, GetHandleSize(scrp_rec_h)); } else { HLock(scrp_rec_h); } line_height = BROWSER_FONT_SIZE + 3; scrp_rec = (StScrpRec *)(*scrp_rec_h); scrp_rec->scrpNStyles = 1; scrp_ele = &scrp_rec->scrpStyleTab[0]; scrp_ele->scrpHeight = line_height; scrp_ele->scrpAscent = BROWSER_FONT_SIZE; scrp_ele->scrpFont = BROWSER_FONT; scrp_ele->scrpSize = BROWSER_FONT_SIZE; scrp_ele->scrpFace = 0; if (style & STYLE_BOLD) scrp_ele->scrpFace |= bold | condense; if (style & (STYLE_H1 | STYLE_H2 | STYLE_H3 | STYLE_H4 | STYLE_H5)) scrp_ele->scrpFace |= bold; if (style & STYLE_ITALIC) scrp_ele->scrpFace |= italic; if (style & STYLE_LINK) scrp_ele->scrpFace |= underline; if (style & STYLE_H1) { scrp_ele->scrpSize += 8; scrp_ele->scrpHeight += 10; scrp_ele->scrpAscent += 8; } else if (style & STYLE_H2) { scrp_ele->scrpSize += 4; scrp_ele->scrpHeight += 6; scrp_ele->scrpAscent += 4; } else if (style & STYLE_H3) { scrp_ele->scrpSize += 2; scrp_ele->scrpHeight += 4; scrp_ele->scrpAscent += 2; } if (style & STYLE_LINK) { if (browser->links_count == browser->links_size) { browser->links_size += 256; browser->links = xreallocarray(browser->links, browser->links_size, sizeof(struct browser_link)); memset(&browser->links[browser->links_count], 0, sizeof(struct browser_link) * 256); } link = &browser->links[browser->links_count++]; /* Computer Storage|Storage -> Computer Storage */ /* Atari 2600 -> Atari 2600 */ for (n = 0; n <= len; n++) { if (n == len) { link->link = xstrndup(str, n); if (link->link == NULL) { warn("Out of memory"); return false; } break; } if (str[n] == '|') { link->link = xstrndup(str, n); if (link->link == NULL) { warn("Out of memory"); return false; } str += n + 1; len -= n + 1; break; } } link->len = len; } HUnlock(scrp_rec_h); HLock(browser->te); was_len = (*(browser->te))->teLength; if (style & STYLE_LINK) link->pos = was_len; TESetSelect(SHRT_MAX, SHRT_MAX, browser->te); if ((last_style & STYLE_ITALIC) && !(style & STYLE_ITALIC)) { TEStylInsert(" ", 1, scrp_rec_h, browser->te); browser->last_printed[0] = browser->last_printed[1]; browser->last_printed[1] = ' '; } if (style & (STYLE_H1 | STYLE_H2 | STYLE_H3 | STYLE_H4 | STYLE_H5)) { while (len && (str[0] == ' ' || str[0] == '\r')) { str++; len--; } while (len && (str[len - 1] == ' ' || str[len - 1] == '\r')) { len--; } if (browser->last_printed[1] != '\0') { if (browser->last_printed[1] == '\r') { if (browser->last_printed[0] != '\r') TEStylInsert("\r", 1, scrp_rec_h, browser->te); } else TEStylInsert("\r\r", 2, scrp_rec_h, browser->te); } browser->last_printed[0] = browser->last_printed[1] = '\r'; } TEStylInsert(str, len, scrp_rec_h, browser->te); if (len == 1) { browser->last_printed[0] = browser->last_printed[1]; browser->last_printed[1] = str[0]; } else { browser->last_printed[0] = str[len - 2]; browser->last_printed[1] = str[len - 1]; } if (style & (STYLE_H1 | STYLE_H2)) { browser_draw_line(browser); browser->last_printed[0] = '-'; browser->last_printed[1] = '\r'; } else if (style & (STYLE_H3 | STYLE_H4 | STYLE_H5)) { TEStylInsert("\r", 1, scrp_rec_h, browser->te); browser->last_printed[0] = browser->last_printed[1]; browser->last_printed[1] = '\r'; } if (was_len == 0) { SetCtlValue(browser->te_scroller, GetCtlMin(browser->te_scroller)); UpdateScrollbarForTE(browser->win, browser->te_scroller, browser->te, false); } HUnlock(browser->te); last_style = style; return true; } void browser_clear(struct browser *browser) { size_t n; for (n = 0; n < browser->links_count; n++) xfree(&browser->links[n].link); if (browser->links != NULL) xfree(&browser->links); browser->links_count = 0; browser->links_size = 0; TEPinScroll(0, SHRT_MAX, browser->debug_te); TESetText("", 0, browser->debug_te); HLock(browser->debug_te); InvalRect(&(*(browser->debug_te))->viewRect); HUnlock(browser->debug_te); TEPinScroll(0, SHRT_MAX, browser->te); TESetText("", 0, browser->te); browser->last_printed[0] = browser->last_printed[1] = '\0'; HLock(browser->te); InvalRect(&(*(browser->te))->viewRect); HUnlock(browser->te); UpdateScrollbarForTE(browser->win, browser->te_scroller, browser->te, true); } void browser_draw_line(struct browser *browser) { char line[255]; size_t n, lsize; short curfont, cursize, cwidth; unsigned char emd = 'Ñ'; /* em-dash 0xd1 */ curfont = thePort->txFont; cursize = thePort->txSize; TextFont(BROWSER_FONT); TextSize(BROWSER_FONT_SIZE); cwidth = CharWidth(emd); TextFont(curfont); TextSize(cursize); HLock(browser->te); lsize = (*(browser->te))->viewRect.right - (*(browser->te))->viewRect.left; HUnlock(browser->te); lsize /= cwidth; if (lsize > sizeof(line)) lsize = sizeof(line); line[0] = '\r'; for (n = 1; n < lsize - 1; n++) line[n] = emd; line[lsize - 1] = '\r'; browser_print(browser, line, lsize, STYLE_BOLD); }