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