AmendHub

Download

jcs

/

wikipedia

/

browser.c

 

(View History)

jcs   main+browser: Make cmd+click on links open them in a new window Latest amendment: 54 on 2023-10-30

1 /*
2 * Copyright (c) 2021-2022 joshua stein <jcs@jcs.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <stdarg.h>
18 #include <stdio.h>
19 #include <string.h>
20
21 #include "wikipedia.h"
22 #include "browser.h"
23 #include "focusable.h"
24 #include "http.h"
25 #include "util.h"
26
27 #define PADDING 10
28 #define BROWSER_FONT_SIZE 10
29 #define BROWSER_FONT geneva
30
31 bool browser_close(struct focusable *focusable);
32 void browser_idle(struct focusable *focusable, EventRecord *event);
33 void browser_update_menu(struct browser *browser);
34 void browser_update(struct focusable *focusable, EventRecord *event);
35 void browser_key_down(struct focusable *focusable, EventRecord *event);
36 void browser_mouse_down(struct focusable *focusable, EventRecord *event);
37 bool browser_handle_menu(struct focusable *focusable, short menu,
38 short item);
39 void browser_atexit(struct focusable *focusable);
40 bool browser_will_te_overflow(struct browser *browser, TEHandle te,
41 short line_height);
42 bool browser_debug_enabled(struct browser *browser);
43 void browser_live_search(struct browser *browser);
44 void browser_hide_search_results(struct browser *browser);
45
46 Pattern fill_pattern;
47
48 void
49 browser_idle(struct focusable *focusable, EventRecord *event)
50 {
51 struct browser *browser = (struct browser *)focusable->cookie;
52 size_t len;
53
54 switch (browser->state) {
55 case BROWSER_STATE_IDLE:
56 TEIdle(browser->input_te);
57
58 if (browser->last_input_for_search != 0 &&
59 (Ticks - browser->last_input_for_search > 30)) {
60 browser->last_input_for_search = 0;
61 browser_live_search(browser);
62 }
63 break;
64 case BROWSER_STATE_ARTICLE_GET: {
65 TERec *te;
66 char *input;
67
68 HLock(browser->input_te);
69 te = *(browser->input_te);
70 HLock(te->hText);
71 input = xstrndup(*(te->hText), te->teLength);
72 HUnlock(te->hText);
73 HUnlock(browser->input_te);
74 if (input == NULL) {
75 warn("Out of memory!");
76 break;
77 }
78
79 SetCursor(*(GetCursor(watchCursor)));
80
81 if (browser->wpr)
82 wikipedia_request_free(&browser->wpr);
83
84 browser->wpr = wikipedia_fetch_article(browser, input);
85 xfree(&input);
86 browser->state = BROWSER_STATE_ARTICLE_PROCESS;
87 break;
88 }
89 case BROWSER_STATE_ARTICLE_PROCESS:
90 if (browser->wpr == NULL) {
91 browser->state = BROWSER_STATE_IDLE;
92 break;
93 }
94
95 wikipedia_request_process(browser->wpr);
96
97 if (browser->wpr->state == WP_STATE_DONE)
98 browser->state = BROWSER_STATE_ARTICLE_DONE;
99 else if (browser->wpr->state == WP_STATE_HAVE_REDIRECT) {
100 progress("Following redirect to %s...",
101 browser->wpr->normalized_title);
102 TESetText(browser->wpr->normalized_title,
103 strlen(browser->wpr->normalized_title), browser->input_te);
104 HLock(browser->input_te);
105 InvalRect(&(*(browser->input_te))->viewRect);
106 HUnlock(browser->input_te);
107 browser->state = BROWSER_STATE_ARTICLE_GET;
108 xfree(&browser->wpr);
109 }
110
111 break;
112 case BROWSER_STATE_ARTICLE_DONE:
113 UpdateScrollbarForTE(browser->win, browser->te_scroller,
114 browser->te, false);
115 progress(NULL);
116 SetCursor(&arrow);
117 browser->state = BROWSER_STATE_IDLE;
118 break;
119 }
120 }
121
122 struct browser *
123 browser_init(char *query)
124 {
125 char title[256];
126 struct browser *browser;
127 struct focusable *focusable;
128 Rect bounds = { 0 }, te_bounds = { 0 };
129 Rect data_bounds = { 0, 0, 0, 1 }; /* tlbr */
130 Point cell_size = { 0, 0 };
131 Cell cell = { 0 };
132 short n, width, height;
133
134 browser = xmalloczero(sizeof(struct browser));
135 if (browser == NULL)
136 panic("Out of memory!");
137 browser->state = BROWSER_STATE_IDLE;
138
139 GetIndPattern(&fill_pattern, sysPatListID, 22);
140
141 /* main window */
142 width = screenBits.bounds.right - screenBits.bounds.left - PADDING;
143 if (width > 540)
144 width = 540;
145 height = screenBits.bounds.bottom - screenBits.bounds.top -
146 PADDING - (GetMBarHeight() * 2);
147 if (height > 340)
148 height = 340;
149 center_in_screen(width, height, true, &bounds);
150
151 snprintf(title, sizeof(title), "%s", PROGRAM_NAME);
152 CtoPstr(title);
153 browser->win = NewWindow(0L, &bounds, title, false, noGrowDocProc,
154 (WindowPtr)-1L, true, 0);
155 if (!browser->win)
156 panic("Can't create window");
157 SetPort(browser->win);
158
159 /* search input TE */
160 bounds.top = PADDING;
161 bounds.left = PADDING;
162 bounds.right = browser->win->portRect.right - PADDING;
163 bounds.bottom = bounds.top + 16;
164 te_bounds = bounds;
165 InsetRect(&te_bounds, 2, 2);
166 TextFont(geneva);
167 TextSize(10);
168 browser->input_te = TENew(&te_bounds, &bounds);
169 if (browser->input_te == NULL)
170 panic("Out of memory!");
171 TEAutoView(true, browser->input_te);
172 TEActivate(browser->input_te);
173
174 /* main article TE bounds */
175 browser->te_bounds.top = (*(browser->input_te))->viewRect.bottom +
176 PADDING;
177 browser->te_bounds.left = PADDING;
178 browser->te_bounds.right = browser->win->portRect.right -
179 SCROLLBAR_WIDTH - PADDING;
180 browser->te_bounds.bottom = browser->win->portRect.bottom - PADDING;
181
182 /* debug TE, off-screen until enabled */
183 bounds = browser->te_bounds;
184 bounds.top += browser->win->portRect.bottom;
185 bounds.bottom += browser->win->portRect.bottom;
186 te_bounds = bounds;
187 InsetRect(&te_bounds, 2, 2);
188 browser->debug_te = TENew(&te_bounds, &bounds);
189 if (browser->debug_te == NULL)
190 panic("Out of memory!");
191 TEAutoView(false, browser->debug_te);
192 (*(browser->debug_te))->caretHook = NullCaretHook;
193 TEActivate(browser->debug_te);
194
195 /* main article TE */
196 bounds = browser->te_bounds;
197 te_bounds = bounds;
198 InsetRect(&te_bounds, 2, 2);
199 browser->te = TEStylNew(&te_bounds, &bounds);
200 if (browser->te == NULL)
201 panic("Out of memory!");
202 TEAutoView(false, browser->te);
203 (*(browser->te))->caretHook = NullCaretHook;
204 TEActivate(browser->te);
205
206 /* scrollbar for diff text */
207 bounds.right = browser->win->portRect.right - PADDING;
208 bounds.left = bounds.right - SCROLLBAR_WIDTH;
209 bounds.bottom++;
210 bounds.top--;
211 browser->te_scroller = NewControl(browser->win, &bounds, "\p", true,
212 1, 1, 1, scrollBarProc, 0L);
213
214 browser_update_menu(browser);
215 UpdateScrollbarForTE(browser->win, browser->te_scroller,
216 browser->te, true);
217
218 focusable = xmalloczero(sizeof(struct focusable));
219 if (focusable == NULL)
220 panic("Out of memory!");
221 focusable->cookie = browser;
222 focusable->win = browser->win;
223 focusable->idle = browser_idle;
224 focusable->update = browser_update;
225 focusable->mouse_down = browser_mouse_down;
226 focusable->key_down = browser_key_down;
227 focusable->menu = browser_handle_menu;
228 focusable->close = browser_close;
229 focusable->atexit = browser_atexit;
230 focusable_add(focusable);
231
232 if (query != NULL) {
233 TESetText(query, strlen(query), browser->input_te);
234 HLock(browser->input_te);
235 InvalRect(&(*(browser->input_te))->viewRect);
236 HUnlock(browser->input_te);
237 browser->state = BROWSER_STATE_ARTICLE_GET;
238 }
239
240 return browser;
241 }
242
243 bool
244 browser_close(struct focusable *focusable)
245 {
246 struct browser *browser = (struct browser *)focusable->cookie;
247
248 if (browser->wpr)
249 wikipedia_request_free(&browser->wpr);
250
251 TEDispose(browser->te);
252 DisposeWindow(browser->win);
253
254 xfree(&browser);
255
256 return true;
257 }
258
259 void
260 browser_atexit(struct focusable *focusable)
261 {
262 struct browser *browser = (struct browser *)focusable->cookie;
263
264 if (browser->wpr) {
265 wikipedia_request_free(&browser->wpr);
266 browser->wpr = NULL;
267 }
268 }
269
270 void
271 browser_update_menu(struct browser *browser)
272 {
273 size_t vlines;
274 TERec *te;
275 Cell cell = { 0, 0 };
276
277 TextFont(systemFont);
278 TextSize(12);
279
280 te = *(browser->te);
281
282 DisableItem(edit_menu, EDIT_MENU_CUT_ID);
283
284 if (te->selStart == te->selEnd)
285 DisableItem(edit_menu, EDIT_MENU_COPY_ID);
286 else
287 EnableItem(edit_menu, EDIT_MENU_COPY_ID);
288
289 DisableItem(edit_menu, EDIT_MENU_PASTE_ID);
290
291 if (te->nLines == 0) {
292 DisableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID);
293 DisableItem(edit_menu, VIEW_MENU_DEBUG_ID);
294 } else {
295 EnableItem(edit_menu, EDIT_MENU_SELECT_ALL_ID);
296 EnableItem(edit_menu, VIEW_MENU_DEBUG_ID);
297 }
298 }
299
300 void
301 browser_update(struct focusable *focusable, EventRecord *event)
302 {
303 struct browser *browser = (struct browser *)focusable->cookie;
304 Str255 buf;
305 Rect r;
306 short what = -1;
307
308 if (event != NULL)
309 what = event->what;
310
311 switch (what) {
312 case -1:
313 case updateEvt:
314 FillRect(&browser->win->portRect, fill_pattern);
315
316 HLock(browser->input_te);
317 r = (*(browser->input_te))->viewRect;
318 HUnlock(browser->input_te);
319 FillRect(&r, white);
320 TEUpdate(&r, browser->input_te);
321 InsetRect(&r, -1, -1);
322 FrameRect(&r);
323
324 HLock(browser->te);
325 r = (*(browser->te))->viewRect;
326 HUnlock(browser->te);
327 FillRect(&r, white);
328 TEUpdate(&r, browser->te);
329 InsetRect(&r, -1, -1);
330 FrameRect(&r);
331
332 if (browser->search_results != NULL) {
333 HLock(browser->search_results);
334 r = (*(browser->search_results))->rView;
335 HUnlock(browser->search_results);
336 InsetRect(&r, -1, -1);
337 FillRect(&r, white);
338 FrameRect(&r);
339 LUpdate(browser->win->visRgn, browser->search_results);
340 }
341
342 UpdtControl(browser->win, browser->win->visRgn);
343
344 browser_update_menu(browser);
345
346 break;
347 }
348 }
349
350 void
351 browser_mouse_down(struct focusable *focusable, EventRecord *event)
352 {
353 struct browser *browser = (struct browser *)focusable->cookie;
354 struct browser_link *link;
355 char str[255];
356 Cell selected = { 0 };
357 Point p;
358 ControlHandle control;
359 Rect r;
360 short val, adj, page, len, part, off;
361 size_t n;
362
363 p = event->where;
364 GlobalToLocal(&p);
365
366 HLock(browser->input_te);
367 r = (*(browser->input_te))->viewRect;
368 HUnlock(browser->input_te);
369 if (PtInRect(p, &r)) {
370 TEClick(p, ((event->modifiers & shiftKey) != 0), browser->input_te);
371 browser_update_menu(browser);
372 return;
373 }
374
375 if (browser->search_results != NULL) {
376 HLock(browser->search_results);
377 r = (*(browser->search_results))->rView;
378 HUnlock(browser->search_results);
379 r.right += SCROLLBAR_WIDTH;
380 if (PtInRect(p, &r)) {
381 LClick(p, event->modifiers, browser->search_results);
382 selected.v = 0;
383 if (LGetSelect(true, &selected, browser->search_results)) {
384 len = sizeof(str);
385 LGetCell(&str, &len, selected, browser->search_results);
386 TESetText(str, len, browser->input_te);
387 HLock(browser->input_te);
388 InvalRect(&(*(browser->input_te))->viewRect);
389 HUnlock(browser->input_te);
390 browser_hide_search_results(browser);
391 browser->state = BROWSER_STATE_ARTICLE_GET;
392 }
393
394 return;
395 }
396 }
397
398 if (browser_debug_enabled(browser)) {
399 HLock(browser->debug_te);
400 r = (*(browser->debug_te))->viewRect;
401 HUnlock(browser->debug_te);
402 if (PtInRect(p, &r)) {
403 TEClick(p, ((event->modifiers & shiftKey) != 0),
404 browser->debug_te);
405 browser_update_menu(browser);
406 return;
407 }
408 } else {
409 HLock(browser->te);
410 r = (*(browser->te))->viewRect;
411 HUnlock(browser->te);
412 if (PtInRect(p, &r)) {
413 TEClick(p, ((event->modifiers & shiftKey) != 0), browser->te);
414
415 off = TEGetOffset(p, browser->te);
416 for (n = 0; n < browser->links_count; n++) {
417 link = &browser->links[n];
418 if ((link->pos <= off) && (off < link->pos + link->len)) {
419 if (event->modifiers & cmdKey) {
420 browser_init(link->link);
421 break;
422 }
423 TESetText(link->link, strlen(link->link),
424 browser->input_te);
425 HLock(browser->input_te);
426 InvalRect(&(*(browser->input_te))->viewRect);
427 HUnlock(browser->input_te);
428 browser->state = BROWSER_STATE_ARTICLE_GET;
429 break;
430 }
431 }
432
433 browser_update_menu(browser);
434 return;
435 }
436 }
437
438 switch (part = FindControl(p, browser->win, &control)) {
439 case inButton:
440 break;
441 case inUpButton:
442 case inDownButton:
443 case inPageUp:
444 case inPageDown:
445 if (control == browser->te_scroller) {
446 if (browser_debug_enabled(browser))
447 SetTrackControlTE(browser->debug_te);
448 else
449 SetTrackControlTE(browser->te);
450 } else
451 break;
452 TrackControl(control, p, TrackMouseDownInControl);
453 break;
454 case inThumb:
455 val = GetCtlValue(control);
456 if (TrackControl(control, p, 0L) == 0)
457 break;
458 adj = val - GetCtlValue(control);
459 if (adj != 0) {
460 val -= adj;
461 if (control == browser->te_scroller) {
462 if (browser_debug_enabled(browser))
463 TEScroll(0, adj * TEGetHeight(0, 0,
464 browser->debug_te), browser->debug_te);
465 else
466 TEScroll(0, adj * TEGetHeight(0, 0,
467 browser->te), browser->te);
468 }
469 SetCtlValue(control, val);
470 }
471 break;
472 }
473 }
474
475 void
476 browser_key_down(struct focusable *focusable, EventRecord *event)
477 {
478 struct browser *browser = (struct browser *)(focusable->cookie);
479 char k;
480
481 k = (event->message & charCodeMask);
482 if (k == '\r') {
483 browser->state = BROWSER_STATE_ARTICLE_GET;
484 } else {
485 TEKey(k, browser->input_te);
486 TESelView(browser->input_te);
487 browser->last_input_for_search = Ticks;
488 }
489 }
490
491 void
492 browser_live_search(struct browser *browser)
493 {
494 TERec *te;
495 Rect bounds = { 0 };
496 Rect data_bounds = { 0, 0, 0, 1 }; /* tlbr */
497 char *input, **results, k;
498 size_t nresults, n, len;
499 Point cell_size = { 0, 0 };
500 Cell cell = { 0, 0 };
501 Rect r;
502
503 HLock(browser->input_te);
504 te = *(browser->input_te);
505
506 if (te->teLength == 0) {
507 HUnlock(browser->input_te);
508 browser_hide_search_results(browser);
509 return;
510 }
511
512 SetCursor(*(GetCursor(watchCursor)));
513
514 HLock(te->hText);
515 input = xstrndup(*(te->hText), te->teLength);
516 HUnlock(te->hText);
517 HUnlock(browser->input_te);
518 if (input == NULL) {
519 warn("Out of memory!");
520 return;
521 }
522
523 nresults = wikipedia_fetch_search_results(browser, input, &results);
524 xfree(&input);
525
526 if (browser->search_results == NULL) {
527 bounds.top = (*(browser->input_te))->viewRect.bottom + 1;
528 bounds.left = PADDING;
529 bounds.bottom = bounds.top + 70;
530 bounds.right = bounds.left +
531 (((*(browser->input_te))->viewRect.right -
532 (*(browser->input_te))->viewRect.left) / 2);
533
534 browser->search_results = LNew(&bounds, &data_bounds, cell_size, 0,
535 browser->win, false, false, false, true);
536 if (!browser->search_results)
537 panic("LNew failed");
538 (*(browser->search_results))->selFlags = lOnlyOne;
539 LAddColumn(1, 0, browser->search_results);
540 } else {
541 LDelRow(0, 0, browser->search_results);
542 }
543
544 for (n = 0; n < nresults; n++) {
545 len = strlen(results[n]);
546 LAddRow(1, cell.v, browser->search_results);
547 LSetCell(results[n], len, cell, browser->search_results);
548 cell.v++;
549 xfree(&results[n]);
550 }
551
552 r = (*(browser->search_results))->rView;
553 FillRect(&r, white);
554 InsetRect(&r, -1, -1);
555 FrameRect(&r);
556 LDoDraw(true, browser->search_results);
557 LUpdate(browser->win->visRgn, browser->search_results);
558
559 SetCursor(&arrow);
560 }
561
562 void
563 browser_hide_search_results(struct browser *browser)
564 {
565 Rect r;
566
567 if (browser->search_results == NULL)
568 return;
569
570 r = (*(browser->search_results))->rView;
571 r.right += SCROLLBAR_WIDTH;
572 InsetRect(&r, -1, -1);
573 InvalRect(&r);
574
575 LDispose(browser->search_results);
576 browser->search_results = NULL;
577 }
578
579 bool
580 browser_handle_menu(struct focusable *focusable, short menu, short item)
581 {
582 struct browser *browser = (struct browser *)focusable->cookie;
583
584 switch (menu) {
585 case EDIT_MENU_ID:
586 switch (item) {
587 case EDIT_MENU_COPY_ID:
588 if (browser_debug_enabled(browser))
589 TECopy(browser->debug_te);
590 else
591 TECopy(browser->te);
592 return true;
593 case EDIT_MENU_SELECT_ALL_ID:
594 if (browser_debug_enabled(browser))
595 TESetSelect(0, 1024 * 32, browser->debug_te);
596 else
597 TESetSelect(0, 1024 * 32, browser->te);
598 return true;
599 }
600 break;
601 case VIEW_MENU_ID:
602 switch (item) {
603 case VIEW_MENU_DEBUG_ID: {
604 Rect bounds, te_bounds;
605
606 bounds = browser->te_bounds;
607 te_bounds = bounds;
608 InsetRect(&te_bounds, 2, 2);
609
610 HLock(browser->debug_te);
611 HLock(browser->te);
612
613 SetCtlValue(browser->te_scroller,
614 GetCtlMin(browser->te_scroller));
615
616 if (browser_debug_enabled(browser)) {
617 /* disable debugging */
618 SetItemMark(view_menu, VIEW_MENU_DEBUG_ID, noMark);
619
620 (*(browser->debug_te))->destRect =
621 (*(browser->te))->destRect;
622 (*(browser->debug_te))->viewRect =
623 (*(browser->te))->viewRect;
624 (*(browser->te))->destRect = te_bounds;
625 (*(browser->te))->viewRect = bounds;
626
627 InsetRect(&te_bounds, -1, -1);
628 EraseRect(&te_bounds);
629 TEUpdate(&(*(browser->te))->destRect, browser->te);
630 UpdateScrollbarForTE(browser->win, browser->te_scroller,
631 browser->te, true);
632 } else {
633 /* enable debugging */
634 SetItemMark(view_menu, VIEW_MENU_DEBUG_ID, checkMark);
635
636 (*(browser->te))->destRect =
637 (*(browser->debug_te))->destRect;
638 (*(browser->te))->viewRect =
639 (*(browser->debug_te))->viewRect;
640 (*(browser->debug_te))->destRect = te_bounds;
641 (*(browser->debug_te))->viewRect = bounds;
642
643 InsetRect(&te_bounds, -1, -1);
644 EraseRect(&te_bounds);
645 TEUpdate(&(*(browser->debug_te))->destRect,
646 browser->debug_te);
647 UpdateScrollbarForTE(browser->win, browser->te_scroller,
648 browser->debug_te, true);
649 }
650
651 HUnlock(browser->debug_te);
652 HLock(browser->te);
653 break;
654 }
655 }
656 break;
657 }
658
659 return false;
660 }
661
662 bool
663 browser_debug_enabled(struct browser *browser)
664 {
665 short mark;
666
667 GetItemMark(view_menu, VIEW_MENU_DEBUG_ID, &mark);
668
669 return (mark != noMark);
670 }
671
672 size_t
673 browser_debug_print(struct browser *browser, const char *str,
674 size_t len)
675 {
676 char tstr[1024];
677 short line_height;
678 short was_len;
679 size_t n = 0;
680
681 line_height = BROWSER_FONT_SIZE + 3;
682
683 HLock(browser->debug_te);
684 was_len = (*(browser->debug_te))->teLength;
685 HUnlock(browser->debug_te);
686
687 if (browser_will_te_overflow(browser, browser->debug_te, line_height))
688 return 0;
689
690 while (len) {
691 if (*str == '\n')
692 tstr[n++] = '\r';
693 else
694 tstr[n++] = *str;
695
696 str++;
697 len--;
698
699 if (n == sizeof(tstr) || len == 0) {
700 TESetSelect(SHRT_MAX, SHRT_MAX, browser->debug_te);
701 TEInsert(tstr, n, browser->debug_te);
702 if (len == 0)
703 break;
704 n = 0;
705 }
706 }
707
708 if (was_len == 0) {
709 SetCtlValue(browser->te_scroller, GetCtlMin(browser->te_scroller));
710 UpdateScrollbarForTE(browser->win, browser->te_scroller,
711 browser->debug_te, false);
712 }
713
714 HUnlock(browser->debug_te);
715
716 return len;
717 }
718
719 bool
720 browser_will_te_overflow(struct browser *browser, TEHandle te,
721 short line_height)
722 {
723 RgnHandle savergn;
724 Rect zerorect = { 0, 0, 0, 0 };
725
726 HLock(te);
727
728 /* too many lines */
729 if ((*te)->nLines >= (nitems((*te)->lineStarts) - 10))
730 return true;
731
732 /* too many characters */
733 if ((*te)->teLength >= (SHRT_MAX - 500))
734 return true;
735
736 /* rect of all lines is too tall */
737 if ((*te)->nLines * line_height >= (SHRT_MAX - 100))
738 return true;
739
740 HUnlock(te);
741
742 return false;
743 }
744
745 #define BROWSER_SCRAP_ELEMENTS 20
746 static Handle scrp_rec_h = NULL;
747
748 bool
749 browser_print(struct browser *browser, const char *str, size_t len,
750 unsigned long style)
751 {
752 StScrpRec *scrp_rec;
753 ScrpSTElement *scrp_ele;
754 RgnHandle savergn;
755 Rect zerorect = { 0, 0, 0, 0 };
756 size_t n;
757 short line_height = 0, was_len = 0;
758 static unsigned long last_style = 0;
759 struct browser_link *link = NULL;
760
761 if (browser_will_te_overflow(browser, browser->te, line_height))
762 return false;
763
764 if (scrp_rec_h == NULL) {
765 scrp_rec_h = xNewHandle(sizeof(short) +
766 (sizeof(ScrpSTElement) * BROWSER_SCRAP_ELEMENTS));
767 HLock(scrp_rec_h);
768 memset(*scrp_rec_h, 0, GetHandleSize(scrp_rec_h));
769 } else {
770 HLock(scrp_rec_h);
771 }
772
773 line_height = BROWSER_FONT_SIZE + 3;
774
775 scrp_rec = (StScrpRec *)(*scrp_rec_h);
776 scrp_rec->scrpNStyles = 1;
777 scrp_ele = &scrp_rec->scrpStyleTab[0];
778 scrp_ele->scrpHeight = line_height;
779 scrp_ele->scrpAscent = BROWSER_FONT_SIZE;
780 scrp_ele->scrpFont = BROWSER_FONT;
781 scrp_ele->scrpSize = BROWSER_FONT_SIZE;
782 scrp_ele->scrpFace = 0;
783
784 if (style & STYLE_BOLD)
785 scrp_ele->scrpFace |= bold | condense;
786 if (style & (STYLE_H1 | STYLE_H2 | STYLE_H3 | STYLE_H4 | STYLE_H5))
787 scrp_ele->scrpFace |= bold;
788 if (style & STYLE_ITALIC)
789 scrp_ele->scrpFace |= italic;
790 if (style & STYLE_LINK)
791 scrp_ele->scrpFace |= underline;
792 if (style & STYLE_H1) {
793 scrp_ele->scrpSize += 8;
794 scrp_ele->scrpHeight += 10;
795 scrp_ele->scrpAscent += 8;
796 } else if (style & STYLE_H2) {
797 scrp_ele->scrpSize += 4;
798 scrp_ele->scrpHeight += 6;
799 scrp_ele->scrpAscent += 4;
800 } else if (style & STYLE_H3) {
801 scrp_ele->scrpSize += 2;
802 scrp_ele->scrpHeight += 4;
803 scrp_ele->scrpAscent += 2;
804 }
805
806 if (style & STYLE_LINK) {
807 if (browser->links_count == browser->links_size) {
808 browser->links_size += 256;
809 browser->links = xreallocarray(browser->links,
810 browser->links_size, sizeof(struct browser_link));
811 memset(&browser->links[browser->links_count], 0,
812 sizeof(struct browser_link) * 256);
813 }
814
815 link = &browser->links[browser->links_count++];
816
817 /* Computer Storage|Storage -> Computer Storage */
818 /* Atari 2600 -> Atari 2600 */
819 for (n = 0; n <= len; n++) {
820 if (n == len) {
821 link->link = xstrndup(str, n);
822 if (link->link == NULL) {
823 warn("Out of memory");
824 return false;
825 }
826 break;
827 }
828
829 if (str[n] == '|') {
830 link->link = xstrndup(str, n);
831 if (link->link == NULL) {
832 warn("Out of memory");
833 return false;
834 }
835 str += n + 1;
836 len -= n + 1;
837 break;
838 }
839 }
840
841 link->len = len;
842 }
843
844 HUnlock(scrp_rec_h);
845
846 HLock(browser->te);
847 was_len = (*(browser->te))->teLength;
848
849 if (style & STYLE_LINK)
850 link->pos = was_len;
851
852 TESetSelect(SHRT_MAX, SHRT_MAX, browser->te);
853 if ((last_style & STYLE_ITALIC) && !(style & STYLE_ITALIC)) {
854 TEStylInsert(" ", 1, scrp_rec_h, browser->te);
855 browser->last_printed[0] = browser->last_printed[1];
856 browser->last_printed[1] = ' ';
857 }
858
859 if (style & (STYLE_H1 | STYLE_H2 | STYLE_H3 | STYLE_H4 | STYLE_H5)) {
860 while (len && (str[0] == ' ' || str[0] == '\r')) {
861 str++;
862 len--;
863 }
864 while (len && (str[len - 1] == ' ' || str[len - 1] == '\r')) {
865 len--;
866 }
867
868 if (browser->last_printed[1] != '\0') {
869 if (browser->last_printed[1] == '\r') {
870 if (browser->last_printed[0] != '\r')
871 TEStylInsert("\r", 1, scrp_rec_h, browser->te);
872 } else
873 TEStylInsert("\r\r", 2, scrp_rec_h, browser->te);
874 }
875
876 browser->last_printed[0] = browser->last_printed[1] = '\r';
877 }
878
879 TEStylInsert(str, len, scrp_rec_h, browser->te);
880
881 if (len == 1) {
882 browser->last_printed[0] = browser->last_printed[1];
883 browser->last_printed[1] = str[0];
884 } else {
885 browser->last_printed[0] = str[len - 2];
886 browser->last_printed[1] = str[len - 1];
887 }
888
889 if (style & (STYLE_H1 | STYLE_H2)) {
890 browser_draw_line(browser);
891 browser->last_printed[0] = '-';
892 browser->last_printed[1] = '\r';
893 } else if (style & (STYLE_H3 | STYLE_H4 | STYLE_H5)) {
894 TEStylInsert("\r", 1, scrp_rec_h, browser->te);
895 browser->last_printed[0] = browser->last_printed[1];
896 browser->last_printed[1] = '\r';
897 }
898
899 if (was_len == 0) {
900 SetCtlValue(browser->te_scroller, GetCtlMin(browser->te_scroller));
901 UpdateScrollbarForTE(browser->win, browser->te_scroller,
902 browser->te, false);
903 }
904
905 HUnlock(browser->te);
906
907 last_style = style;
908
909 return true;
910 }
911
912 void
913 browser_clear(struct browser *browser)
914 {
915 size_t n;
916
917 for (n = 0; n < browser->links_count; n++)
918 xfree(&browser->links[n].link);
919
920 if (browser->links != NULL)
921 xfree(&browser->links);
922 browser->links_count = 0;
923 browser->links_size = 0;
924
925 TEPinScroll(0, SHRT_MAX, browser->debug_te);
926 TESetText("", 0, browser->debug_te);
927 HLock(browser->debug_te);
928 InvalRect(&(*(browser->debug_te))->viewRect);
929 HUnlock(browser->debug_te);
930
931 TEPinScroll(0, SHRT_MAX, browser->te);
932 TESetText("", 0, browser->te);
933 browser->last_printed[0] = browser->last_printed[1] = '\0';
934 HLock(browser->te);
935 InvalRect(&(*(browser->te))->viewRect);
936 HUnlock(browser->te);
937
938 UpdateScrollbarForTE(browser->win, browser->te_scroller,
939 browser->te, true);
940 }
941
942 void
943 browser_draw_line(struct browser *browser)
944 {
945 char line[255];
946 size_t n, lsize;
947 short curfont, cursize, cwidth;
948 unsigned char emd = '—'; /* em-dash 0xd1 */
949
950 curfont = thePort->txFont;
951 cursize = thePort->txSize;
952 TextFont(BROWSER_FONT);
953 TextSize(BROWSER_FONT_SIZE);
954 cwidth = CharWidth(emd);
955 TextFont(curfont);
956 TextSize(cursize);
957
958 HLock(browser->te);
959 lsize = (*(browser->te))->viewRect.right -
960 (*(browser->te))->viewRect.left;
961 HUnlock(browser->te);
962
963 lsize /= cwidth;
964 if (lsize > sizeof(line))
965 lsize = sizeof(line);
966
967 line[0] = '\r';
968 for (n = 1; n < lsize - 1; n++)
969 line[n] = emd;
970 line[lsize - 1] = '\r';
971 browser_print(browser, line, lsize, STYLE_BOLD);
972 }