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 | } |