diff options
Diffstat (limited to 'src/ui')
-rw-r--r-- | src/ui/documentwidget.c | 227 | ||||
-rw-r--r-- | src/ui/util.c | 4 | ||||
-rw-r--r-- | src/ui/window.h | 1 |
3 files changed, 184 insertions, 48 deletions
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 10af43f3..cffd284f 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -11,6 +11,7 @@ | |||
11 | #include "../gmutil.h" | 11 | #include "../gmutil.h" |
12 | 12 | ||
13 | #include <the_Foundation/file.h> | 13 | #include <the_Foundation/file.h> |
14 | #include <the_Foundation/objectlist.h> | ||
14 | #include <the_Foundation/path.h> | 15 | #include <the_Foundation/path.h> |
15 | #include <the_Foundation/ptrarray.h> | 16 | #include <the_Foundation/ptrarray.h> |
16 | #include <the_Foundation/regexp.h> | 17 | #include <the_Foundation/regexp.h> |
@@ -26,13 +27,59 @@ enum iDocumentState { | |||
26 | ready_DocumentState, | 27 | ready_DocumentState, |
27 | }; | 28 | }; |
28 | 29 | ||
30 | iDeclareClass(MediaRequest) | ||
31 | |||
32 | struct Impl_MediaRequest { | ||
33 | iObject object; | ||
34 | iDocumentWidget *doc; | ||
35 | iGmLinkId linkId; | ||
36 | iGmRequest *req; | ||
37 | iAtomicInt isUpdated; | ||
38 | }; | ||
39 | |||
40 | static void updated_MediaRequest_(iAnyObject *obj) { | ||
41 | iMediaRequest *d = obj; | ||
42 | int wasUpdated = exchange_Atomic(&d->isUpdated, iTrue); | ||
43 | if (!wasUpdated) { | ||
44 | postCommandf_App("media.updated link:%u request:%p", d->linkId, d); | ||
45 | } | ||
46 | } | ||
47 | |||
48 | static void finished_MediaRequest_(iAnyObject *obj) { | ||
49 | iMediaRequest *d = obj; | ||
50 | postCommandf_App("media.finished link:%u request:%p", d->linkId, d); | ||
51 | } | ||
52 | |||
53 | void init_MediaRequest(iMediaRequest *d, iDocumentWidget *doc, iGmLinkId linkId, const iString *url) { | ||
54 | d->doc = doc; | ||
55 | d->linkId = linkId; | ||
56 | d->req = new_GmRequest(); | ||
57 | setUrl_GmRequest(d->req, url); | ||
58 | iConnect(GmRequest, d->req, updated, d, updated_MediaRequest_); | ||
59 | iConnect(GmRequest, d->req, finished, d, finished_MediaRequest_); | ||
60 | set_Atomic(&d->isUpdated, iFalse); | ||
61 | submit_GmRequest(d->req); | ||
62 | } | ||
63 | |||
64 | void deinit_MediaRequest(iMediaRequest *d) { | ||
65 | iDisconnect(GmRequest, d->req, updated, d, updated_MediaRequest_); | ||
66 | iDisconnect(GmRequest, d->req, finished, d, finished_MediaRequest_); | ||
67 | iRelease(d->req); | ||
68 | } | ||
69 | |||
70 | iDefineObjectConstructionArgs(MediaRequest, | ||
71 | (iDocumentWidget *doc, iGmLinkId linkId, const iString *url), | ||
72 | doc, linkId, url) | ||
73 | iDefineClass(MediaRequest) | ||
74 | |||
29 | struct Impl_DocumentWidget { | 75 | struct Impl_DocumentWidget { |
30 | iWidget widget; | 76 | iWidget widget; |
31 | enum iDocumentState state; | 77 | enum iDocumentState state; |
32 | iString *url; | 78 | iString *url; |
33 | iString *titleUser; | 79 | iString *titleUser; |
34 | iGmRequest *request; | 80 | iGmRequest *request; |
35 | iAtomicInt isSourcePending; /* request has new content, need to parse it */ | 81 | iAtomicInt isRequestUpdated; /* request has new content, need to parse it */ |
82 | iObjectList *media; | ||
36 | iGmDocument *doc; | 83 | iGmDocument *doc; |
37 | iBool selecting; | 84 | iBool selecting; |
38 | iRangecc selectMark; | 85 | iRangecc selectMark; |
@@ -55,21 +102,22 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
55 | iWidget *w = as_Widget(d); | 102 | iWidget *w = as_Widget(d); |
56 | init_Widget(w); | 103 | init_Widget(w); |
57 | setId_Widget(w, "document"); | 104 | setId_Widget(w, "document"); |
58 | d->state = blank_DocumentState; | 105 | d->state = blank_DocumentState; |
59 | d->url = new_String(); | 106 | d->url = new_String(); |
60 | d->titleUser = new_String(); | 107 | d->titleUser = new_String(); |
61 | d->request = NULL; | 108 | d->request = NULL; |
62 | d->isSourcePending = iFalse; | 109 | d->isRequestUpdated = iFalse; |
63 | d->doc = new_GmDocument(); | 110 | d->media = new_ObjectList(); |
64 | d->selecting = iFalse; | 111 | d->doc = new_GmDocument(); |
65 | d->selectMark = iNullRange; | 112 | d->selecting = iFalse; |
66 | d->foundMark = iNullRange; | 113 | d->selectMark = iNullRange; |
67 | d->pageMargin = 5; | 114 | d->foundMark = iNullRange; |
68 | d->scrollY = 0; | 115 | d->pageMargin = 5; |
69 | d->hoverLink = NULL; | 116 | d->scrollY = 0; |
70 | d->arrowCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); | 117 | d->hoverLink = NULL; |
71 | d->beamCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); | 118 | d->arrowCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); |
72 | d->handCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); | 119 | d->beamCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); |
120 | d->handCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); | ||
73 | init_PtrArray(&d->visibleLinks); | 121 | init_PtrArray(&d->visibleLinks); |
74 | init_Click(&d->click, d, SDL_BUTTON_LEFT); | 122 | init_Click(&d->click, d, SDL_BUTTON_LEFT); |
75 | addChild_Widget(w, iClob(d->scroll = new_ScrollWidget())); | 123 | addChild_Widget(w, iClob(d->scroll = new_ScrollWidget())); |
@@ -85,16 +133,26 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
85 | } | 133 | } |
86 | 134 | ||
87 | void deinit_DocumentWidget(iDocumentWidget *d) { | 135 | void deinit_DocumentWidget(iDocumentWidget *d) { |
136 | iRelease(d->media); | ||
137 | iRelease(d->request); | ||
138 | iRelease(d->doc); | ||
88 | deinit_PtrArray(&d->visibleLinks); | 139 | deinit_PtrArray(&d->visibleLinks); |
89 | delete_String(d->url); | 140 | delete_String(d->url); |
90 | delete_String(d->titleUser); | 141 | delete_String(d->titleUser); |
91 | iRelease(d->request); | ||
92 | iRelease(d->doc); | ||
93 | SDL_FreeCursor(d->arrowCursor); | 142 | SDL_FreeCursor(d->arrowCursor); |
94 | SDL_FreeCursor(d->beamCursor); | 143 | SDL_FreeCursor(d->beamCursor); |
95 | SDL_FreeCursor(d->handCursor); | 144 | SDL_FreeCursor(d->handCursor); |
96 | } | 145 | } |
97 | 146 | ||
147 | static iString *cleanUrl_(const iString *url) { | ||
148 | iString *clean = copy_String(url); | ||
149 | if (indexOfCStr_String(url, "://") == iInvalidPos && !startsWithCase_String(url, "gemini:")) { | ||
150 | /* Prepend default protocol. */ | ||
151 | prependCStr_String(clean, "gemini://"); | ||
152 | } | ||
153 | return clean; | ||
154 | } | ||
155 | |||
98 | static int documentWidth_DocumentWidget_(const iDocumentWidget *d) { | 156 | static int documentWidth_DocumentWidget_(const iDocumentWidget *d) { |
99 | const iWidget *w = constAs_Widget(d); | 157 | const iWidget *w = constAs_Widget(d); |
100 | const iRect bounds = bounds_Widget(w); | 158 | const iRect bounds = bounds_Widget(w); |
@@ -124,8 +182,8 @@ static iInt2 documentPos_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) { | |||
124 | 182 | ||
125 | static void requestUpdated_DocumentWidget_(iAnyObject *obj) { | 183 | static void requestUpdated_DocumentWidget_(iAnyObject *obj) { |
126 | iDocumentWidget *d = obj; | 184 | iDocumentWidget *d = obj; |
127 | const int wasPending = exchange_Atomic(&d->isSourcePending, iTrue); | 185 | const int wasUpdated = exchange_Atomic(&d->isRequestUpdated, iTrue); |
128 | if (!wasPending) { | 186 | if (!wasUpdated) { |
129 | postCommand_Widget(obj, "document.request.updated request:%p", d->request); | 187 | postCommand_Widget(obj, "document.request.updated request:%p", d->request); |
130 | } | 188 | } |
131 | } | 189 | } |
@@ -234,6 +292,10 @@ static void updateSource_DocumentWidget_(iDocumentWidget *d) { | |||
234 | else if (startsWith_String(mime, "text/gemini")) { | 292 | else if (startsWith_String(mime, "text/gemini")) { |
235 | setFormat_GmDocument(d->doc, gemini_GmDocumentFormat); | 293 | setFormat_GmDocument(d->doc, gemini_GmDocumentFormat); |
236 | } | 294 | } |
295 | else if (startsWith_String(mime, "image/")) { | ||
296 | /* TODO: Make a simple document with an image. */ | ||
297 | clear_String(&str); | ||
298 | } | ||
237 | else { | 299 | else { |
238 | showErrorPage_DocumentWidget_(d, unsupportedMimeType_GmStatusCode); | 300 | showErrorPage_DocumentWidget_(d, unsupportedMimeType_GmStatusCode); |
239 | deinit_String(&str); | 301 | deinit_String(&str); |
@@ -252,8 +314,9 @@ static void fetch_DocumentWidget_(iDocumentWidget *d) { | |||
252 | d->request = NULL; | 314 | d->request = NULL; |
253 | } | 315 | } |
254 | postCommandf_App("document.request.started url:%s", cstr_String(d->url)); | 316 | postCommandf_App("document.request.started url:%s", cstr_String(d->url)); |
317 | clear_ObjectList(d->media); | ||
255 | d->state = fetching_DocumentState; | 318 | d->state = fetching_DocumentState; |
256 | set_Atomic(&d->isSourcePending, iFalse); | 319 | set_Atomic(&d->isRequestUpdated, iFalse); |
257 | d->request = new_GmRequest(); | 320 | d->request = new_GmRequest(); |
258 | setUrl_GmRequest(d->request, d->url); | 321 | setUrl_GmRequest(d->request, d->url); |
259 | iConnect(GmRequest, d->request, updated, d, requestUpdated_DocumentWidget_); | 322 | iConnect(GmRequest, d->request, updated, d, requestUpdated_DocumentWidget_); |
@@ -262,12 +325,7 @@ static void fetch_DocumentWidget_(iDocumentWidget *d) { | |||
262 | } | 325 | } |
263 | 326 | ||
264 | void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) { | 327 | void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) { |
265 | iString *newUrl = new_String(); | 328 | iString *newUrl = collect_String(cleanUrl_(url)); |
266 | if (indexOfCStr_String(url, "://") == iInvalidPos && !startsWithCase_String(url, "gemini:")) { | ||
267 | /* Prepend default protocol. */ | ||
268 | setCStr_String(newUrl, "gemini://"); | ||
269 | } | ||
270 | append_String(newUrl, url); | ||
271 | if (cmpStringSc_String(d->url, newUrl, &iCaseInsensitive)) { | 329 | if (cmpStringSc_String(d->url, newUrl, &iCaseInsensitive)) { |
272 | set_String(d->url, newUrl); | 330 | set_String(d->url, newUrl); |
273 | fetch_DocumentWidget_(d); | 331 | fetch_DocumentWidget_(d); |
@@ -284,7 +342,6 @@ void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) { | |||
284 | iRelease(userPats[i]); | 342 | iRelease(userPats[i]); |
285 | } | 343 | } |
286 | } | 344 | } |
287 | delete_String(newUrl); | ||
288 | } | 345 | } |
289 | 346 | ||
290 | iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) { | 347 | iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) { |
@@ -372,6 +429,7 @@ static void checkResponseCode_DocumentWidget_(iDocumentWidget *d) { | |||
372 | if (d->state == fetching_DocumentState) { | 429 | if (d->state == fetching_DocumentState) { |
373 | d->state = receivedPartialResponse_DocumentState; | 430 | d->state = receivedPartialResponse_DocumentState; |
374 | d->scrollY = 0; | 431 | d->scrollY = 0; |
432 | reset_GmDocument(d->doc); /* new content incoming */ | ||
375 | enum iGmStatusCode statusCode = status_GmRequest(d->request); | 433 | enum iGmStatusCode statusCode = status_GmRequest(d->request); |
376 | switch (statusCode) { | 434 | switch (statusCode) { |
377 | case none_GmStatusCode: | 435 | case none_GmStatusCode: |
@@ -384,10 +442,10 @@ static void checkResponseCode_DocumentWidget_(iDocumentWidget *d) { | |||
384 | iWidget *dlg = makeValueInput_Widget( | 442 | iWidget *dlg = makeValueInput_Widget( |
385 | as_Widget(d), | 443 | as_Widget(d), |
386 | NULL, | 444 | NULL, |
387 | cstrFormat_String(cyan_ColorEscape "%s", | 445 | format_CStr(cyan_ColorEscape "%s", |
388 | cstr_String(collect_String(newRange_String(parts.host)))), | 446 | cstr_String(collect_String(newRange_String(parts.host)))), |
389 | isEmpty_String(meta_GmRequest(d->request)) | 447 | isEmpty_String(meta_GmRequest(d->request)) |
390 | ? cstrFormat_String( | 448 | ? format_CStr( |
391 | "Please enter input for %s:", | 449 | "Please enter input for %s:", |
392 | cstr_String(collect_String(newRange_String(parts.path)))) | 450 | cstr_String(collect_String(newRange_String(parts.path)))) |
393 | : cstr_String(meta_GmRequest(d->request)), | 451 | : cstr_String(meta_GmRequest(d->request)), |
@@ -415,14 +473,73 @@ static void checkResponseCode_DocumentWidget_(iDocumentWidget *d) { | |||
415 | } | 473 | } |
416 | } | 474 | } |
417 | 475 | ||
418 | const iString *valueString_Command(const char *cmd, const char *label) { | ||
419 | return collect_String(newCStr_String(suffixPtr_Command(cmd, label))); | ||
420 | } | ||
421 | |||
422 | static const char *sourceLoc_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) { | 476 | static const char *sourceLoc_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) { |
423 | return findLoc_GmDocument(d->doc, documentPos_DocumentWidget_(d, pos)); | 477 | return findLoc_GmDocument(d->doc, documentPos_DocumentWidget_(d, pos)); |
424 | } | 478 | } |
425 | 479 | ||
480 | static void removeMediaRequest_DocumentWidget_(iDocumentWidget *d, iGmLinkId linkId) { | ||
481 | iForEach(ObjectList, i, d->media) { | ||
482 | iMediaRequest *req = (iMediaRequest *) i.object; | ||
483 | if (req->linkId == linkId) { | ||
484 | remove_ObjectListIterator(&i); | ||
485 | break; | ||
486 | } | ||
487 | } | ||
488 | } | ||
489 | |||
490 | static iMediaRequest *findMediaRequest_DocumentWidget_(const iDocumentWidget *d, iGmLinkId linkId) { | ||
491 | iConstForEach(ObjectList, i, d->media) { | ||
492 | const iMediaRequest *req = (const iMediaRequest *) i.object; | ||
493 | if (req->linkId == linkId) { | ||
494 | return iConstCast(iMediaRequest *, req); | ||
495 | } | ||
496 | } | ||
497 | return NULL; | ||
498 | } | ||
499 | |||
500 | static void requestMedia_DocumentWidget_(iDocumentWidget *d, iGmLinkId linkId) { | ||
501 | if (!findMediaRequest_DocumentWidget_(d, linkId)) { | ||
502 | pushBack_ObjectList( | ||
503 | d->media, | ||
504 | iClob(new_MediaRequest( | ||
505 | d, linkId, absoluteUrl_DocumentWidget_(d, linkUrl_GmDocument(d->doc, linkId))))); | ||
506 | } | ||
507 | } | ||
508 | |||
509 | static iBool handleMediaEvent_DocumentWidget_(iDocumentWidget *d, const char *cmd) { | ||
510 | iMediaRequest *req = pointerLabel_Command(cmd, "request"); | ||
511 | if (!req || req->doc != d) { | ||
512 | return iFalse; /* not our request */ | ||
513 | } | ||
514 | if (equal_Command(cmd, "media.updated")) { | ||
515 | /* TODO: Show a progress indicator */ | ||
516 | return iTrue; | ||
517 | } | ||
518 | else if (equal_Command(cmd, "media.finished")) { | ||
519 | const enum iGmStatusCode code = status_GmRequest(req->req); | ||
520 | /* Give the media to the document for presentation. */ | ||
521 | if (code == success_GmStatusCode) { | ||
522 | printf("media finished: %s\n size: %zu\n type: %s\n", | ||
523 | cstr_String(url_GmRequest(req->req)), | ||
524 | size_Block(body_GmRequest(req->req)), | ||
525 | cstr_String(meta_GmRequest(req->req))); | ||
526 | if (startsWith_String(meta_GmRequest(req->req), "image/")) { | ||
527 | setImage_GmDocument(d->doc, req->linkId, meta_GmRequest(req->req), | ||
528 | body_GmRequest(req->req)); | ||
529 | updateVisible_DocumentWidget_(d); | ||
530 | refresh_Widget(as_Widget(d)); | ||
531 | } | ||
532 | } | ||
533 | else { | ||
534 | const iGmError *err = get_GmError(code); | ||
535 | makeMessage_Widget(format_CStr(orange_ColorEscape "%s", err->title), err->info); | ||
536 | removeMediaRequest_DocumentWidget_(d, req->linkId); | ||
537 | } | ||
538 | return iTrue; | ||
539 | } | ||
540 | return iFalse; | ||
541 | } | ||
542 | |||
426 | static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *ev) { | 543 | static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *ev) { |
427 | iWidget *w = as_Widget(d); | 544 | iWidget *w = as_Widget(d); |
428 | if (isResize_UserEvent(ev)) { | 545 | if (isResize_UserEvent(ev)) { |
@@ -499,6 +616,9 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
499 | } | 616 | } |
500 | return iTrue; | 617 | return iTrue; |
501 | } | 618 | } |
619 | else if (isCommand_UserEvent(ev, "media.updated") || isCommand_UserEvent(ev, "media.finished")) { | ||
620 | return handleMediaEvent_DocumentWidget_(d, command_UserEvent(ev)); | ||
621 | } | ||
502 | else if (isCommand_UserEvent(ev, "document.reload")) { | 622 | else if (isCommand_UserEvent(ev, "document.reload")) { |
503 | fetch_DocumentWidget_(d); | 623 | fetch_DocumentWidget_(d); |
504 | return iTrue; | 624 | return iTrue; |
@@ -653,10 +773,17 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
653 | case finished_ClickResult: | 773 | case finished_ClickResult: |
654 | if (!isMoved_Click(&d->click)) { | 774 | if (!isMoved_Click(&d->click)) { |
655 | if (d->hoverLink) { | 775 | if (d->hoverLink) { |
656 | iAssert(d->hoverLink->linkId); | 776 | const iGmLinkId linkId = d->hoverLink->linkId; |
657 | postCommandf_App("open url:%s", | 777 | iAssert(linkId); |
658 | cstr_String(absoluteUrl_DocumentWidget_( | 778 | /* Media links are opened inline by default. */ |
659 | d, linkUrl_GmDocument(d->doc, d->hoverLink->linkId)))); | 779 | if (isMediaLink_GmDocument(d->doc, linkId)) { |
780 | requestMedia_DocumentWidget_(d, linkId); | ||
781 | } | ||
782 | else { | ||
783 | postCommandf_App("open url:%s", | ||
784 | cstr_String(absoluteUrl_DocumentWidget_( | ||
785 | d, linkUrl_GmDocument(d->doc, linkId)))); | ||
786 | } | ||
660 | } | 787 | } |
661 | if (d->selectMark.start) { | 788 | if (d->selectMark.start) { |
662 | d->selectMark = iNullRange; | 789 | d->selectMark = iNullRange; |
@@ -714,20 +841,27 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol | |||
714 | 841 | ||
715 | static void drawRun_DrawContext_(void *context, const iGmRun *run) { | 842 | static void drawRun_DrawContext_(void *context, const iGmRun *run) { |
716 | iDrawContext *d = context; | 843 | iDrawContext *d = context; |
844 | const iInt2 origin = addY_I2(d->bounds.pos, -d->widget->scrollY); | ||
845 | if (run->imageId) { | ||
846 | SDL_Texture *tex = imageTexture_GmDocument(d->widget->doc, run->imageId); | ||
847 | if (tex) { | ||
848 | const iRect dst = moved_Rect(run->visBounds, origin); | ||
849 | SDL_RenderCopy(d->paint.dst->render, tex, NULL, | ||
850 | &(SDL_Rect){ dst.pos.x, dst.pos.y, dst.size.x, dst.size.y }); | ||
851 | } | ||
852 | return; | ||
853 | } | ||
717 | iString text; | 854 | iString text; |
718 | /* TODO: making a copy is unnecessary; the text routines should accept Rangecc */ | 855 | /* TODO: making a copy is unnecessary; the text routines should accept Rangecc */ |
719 | initRange_String(&text, run->text); | 856 | initRange_String(&text, run->text); |
720 | iInt2 origin = addY_I2(d->bounds.pos, -d->widget->scrollY); | ||
721 | enum iColorId fg = run->color; | 857 | enum iColorId fg = run->color; |
722 | if (run == d->widget->hoverLink) { | 858 | if (run == d->widget->hoverLink) { |
723 | //const char *desc = ""; | 859 | const iGmDocument *doc = d->widget->doc; |
724 | const iGmDocument *doc = d->widget->doc; | 860 | const iGmLinkId linkId = d->widget->hoverLink->linkId; |
725 | const iGmLinkId linkId = d->widget->hoverLink->linkId; | 861 | const iString * url = linkUrl_GmDocument(doc, linkId); |
726 | const iString *url = linkUrl_GmDocument(doc, linkId); | 862 | const int flags = linkFlags_GmDocument(doc, linkId); |
727 | const int flags = linkFlags_GmDocument(doc, linkId); | ||
728 | iUrl parts; | 863 | iUrl parts; |
729 | init_Url(&parts, url); | 864 | init_Url(&parts, url); |
730 | // desc = cstrFormat_String("\u2192 %s", cstr_String(collect_String(newRange_String(parts.protocol)))); | ||
731 | const iString *host = collect_String(newRange_String(parts.host)); | 865 | const iString *host = collect_String(newRange_String(parts.host)); |
732 | fg = linkColor_GmDocument(doc, linkId); | 866 | fg = linkColor_GmDocument(doc, linkId); |
733 | const iBool showHost = (!isEmpty_String(host) && flags & userFriendly_GmLinkFlag); | 867 | const iBool showHost = (!isEmpty_String(host) && flags & userFriendly_GmLinkFlag); |
@@ -739,9 +873,10 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
739 | topRight_Rect(linkRect), | 873 | topRight_Rect(linkRect), |
740 | fg - 1, | 874 | fg - 1, |
741 | left_Alignment, | 875 | left_Alignment, |
742 | " \u2014%s%s\r%c%s", | 876 | " \u2014%s%s%s\r%c%s", |
743 | showHost ? " " : "", | 877 | showHost ? " " : "", |
744 | showHost ? cstr_String(host) : "", | 878 | showHost ? cstr_String(host) : "", |
879 | showHost && (showImage || showAudio) ? " \u2014" : "", | ||
745 | showImage || showAudio ? '0' + fg : ('0' + fg - 1), | 880 | showImage || showAudio ? '0' + fg : ('0' + fg - 1), |
746 | showImage ? " View Image \U0001f5bc" | 881 | showImage ? " View Image \U0001f5bc" |
747 | : showAudio ? " Play Audio \U0001f3b5" : ""); | 882 | : showAudio ? " Play Audio \U0001f3b5" : ""); |
diff --git a/src/ui/util.c b/src/ui/util.c index cf75a2b0..785841c9 100644 --- a/src/ui/util.c +++ b/src/ui/util.c | |||
@@ -308,7 +308,7 @@ static void addTabPage_Widget_(iWidget *tabs, enum iWidgetAddPos addPos, iWidget | |||
308 | const iBool isSel = childCount_Widget(pages) == 0; | 308 | const iBool isSel = childCount_Widget(pages) == 0; |
309 | iWidget * button = addChildPos_Widget( | 309 | iWidget * button = addChildPos_Widget( |
310 | findChild_Widget(tabs, "tabs.buttons"), | 310 | findChild_Widget(tabs, "tabs.buttons"), |
311 | iClob(new_LabelWidget(label, key, kmods, cstrFormat_String("tabs.switch page:%p", page))), | 311 | iClob(new_LabelWidget(label, key, kmods, format_CStr("tabs.switch page:%p", page))), |
312 | addPos); | 312 | addPos); |
313 | setFlags_Widget(button, selected_WidgetFlag, isSel); | 313 | setFlags_Widget(button, selected_WidgetFlag, isSel); |
314 | addChildPos_Widget(pages, page, addPos); | 314 | addChildPos_Widget(pages, page, addPos); |
@@ -615,7 +615,7 @@ static iBool toggleHandler_(iWidget *d, const char *cmd) { | |||
615 | if (equal_Command(cmd, "toggle") && pointer_Command(cmd) == d) { | 615 | if (equal_Command(cmd, "toggle") && pointer_Command(cmd) == d) { |
616 | setToggle_Widget(d, (flags_Widget(d) & selected_WidgetFlag) == 0); | 616 | setToggle_Widget(d, (flags_Widget(d) & selected_WidgetFlag) == 0); |
617 | postCommand_Widget(d, | 617 | postCommand_Widget(d, |
618 | cstrFormat_String("%s.changed arg:%d", | 618 | format_CStr("%s.changed arg:%d", |
619 | cstr_String(id_Widget(d)), | 619 | cstr_String(id_Widget(d)), |
620 | isSelected_Widget(d) ? 1 : 0)); | 620 | isSelected_Widget(d) ? 1 : 0)); |
621 | return iTrue; | 621 | return iTrue; |
diff --git a/src/ui/window.h b/src/ui/window.h index 70e52751..413c454c 100644 --- a/src/ui/window.h +++ b/src/ui/window.h | |||
@@ -31,5 +31,6 @@ float uiScale_Window (const iWindow *); | |||
31 | iInt2 coord_Window (const iWindow *, int x, int y); | 31 | iInt2 coord_Window (const iWindow *, int x, int y); |
32 | iInt2 mouseCoord_Window (const iWindow *); | 32 | iInt2 mouseCoord_Window (const iWindow *); |
33 | uint32_t frameTime_Window (const iWindow *); | 33 | uint32_t frameTime_Window (const iWindow *); |
34 | SDL_Renderer *renderer_Window (const iWindow *); | ||
34 | 35 | ||
35 | iWindow * get_Window (void); | 36 | iWindow * get_Window (void); |