diff options
Diffstat (limited to 'src/ui')
-rw-r--r-- | src/ui/color.h | 3 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 268 | ||||
-rw-r--r-- | src/ui/inputwidget.c | 2 | ||||
-rw-r--r-- | src/ui/labelwidget.c | 49 | ||||
-rw-r--r-- | src/ui/lookupwidget.c | 6 | ||||
-rw-r--r-- | src/ui/util.c | 80 | ||||
-rw-r--r-- | src/ui/util.h | 1 | ||||
-rw-r--r-- | src/ui/visbuf.c | 1 | ||||
-rw-r--r-- | src/ui/widget.c | 9 | ||||
-rw-r--r-- | src/ui/widget.h | 1 | ||||
-rw-r--r-- | src/ui/window.c | 69 | ||||
-rw-r--r-- | src/ui/window.h | 1 |
12 files changed, 354 insertions, 136 deletions
diff --git a/src/ui/color.h b/src/ui/color.h index 2c481d13..51d3370f 100644 --- a/src/ui/color.h +++ b/src/ui/color.h | |||
@@ -48,7 +48,7 @@ enum iColorId { | |||
48 | gray50_ColorId, | 48 | gray50_ColorId, |
49 | gray75_ColorId, | 49 | gray75_ColorId, |
50 | white_ColorId, | 50 | white_ColorId, |
51 | brown_ColorId, | 51 | brown_ColorId, |
52 | orange_ColorId, | 52 | orange_ColorId, |
53 | teal_ColorId, | 53 | teal_ColorId, |
54 | cyan_ColorId, | 54 | cyan_ColorId, |
@@ -109,6 +109,7 @@ enum iColorId { | |||
109 | tmParagraph_ColorId, | 109 | tmParagraph_ColorId, |
110 | tmFirstParagraph_ColorId, | 110 | tmFirstParagraph_ColorId, |
111 | tmQuote_ColorId, | 111 | tmQuote_ColorId, |
112 | tmQuoteIcon_ColorId, | ||
112 | tmPreformatted_ColorId, | 113 | tmPreformatted_ColorId, |
113 | tmHeading1_ColorId, | 114 | tmHeading1_ColorId, |
114 | tmHeading2_ColorId, | 115 | tmHeading2_ColorId, |
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index fce548b4..70e66180 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -36,6 +36,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
36 | #include "../gmutil.h" | 36 | #include "../gmutil.h" |
37 | 37 | ||
38 | #include <the_Foundation/file.h> | 38 | #include <the_Foundation/file.h> |
39 | #include <the_Foundation/fileinfo.h> | ||
39 | #include <the_Foundation/objectlist.h> | 40 | #include <the_Foundation/objectlist.h> |
40 | #include <the_Foundation/path.h> | 41 | #include <the_Foundation/path.h> |
41 | #include <the_Foundation/ptrarray.h> | 42 | #include <the_Foundation/ptrarray.h> |
@@ -46,6 +47,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
46 | #include <SDL_timer.h> | 47 | #include <SDL_timer.h> |
47 | #include <SDL_render.h> | 48 | #include <SDL_render.h> |
48 | #include <ctype.h> | 49 | #include <ctype.h> |
50 | #include <errno.h> | ||
49 | 51 | ||
50 | iDeclareClass(MediaRequest) | 52 | iDeclareClass(MediaRequest) |
51 | 53 | ||
@@ -54,16 +56,12 @@ struct Impl_MediaRequest { | |||
54 | iDocumentWidget *doc; | 56 | iDocumentWidget *doc; |
55 | iGmLinkId linkId; | 57 | iGmLinkId linkId; |
56 | iGmRequest * req; | 58 | iGmRequest * req; |
57 | iAtomicInt isUpdated; | ||
58 | }; | 59 | }; |
59 | 60 | ||
60 | static void updated_MediaRequest_(iAnyObject *obj) { | 61 | static void updated_MediaRequest_(iAnyObject *obj) { |
61 | iMediaRequest *d = obj; | 62 | iMediaRequest *d = obj; |
62 | int wasUpdated = exchange_Atomic(&d->isUpdated, iTrue); | ||
63 | if (!wasUpdated) { | ||
64 | postCommandf_App("media.updated link:%u request:%p", d->linkId, d); | 63 | postCommandf_App("media.updated link:%u request:%p", d->linkId, d); |
65 | } | 64 | } |
66 | } | ||
67 | 65 | ||
68 | static void finished_MediaRequest_(iAnyObject *obj) { | 66 | static void finished_MediaRequest_(iAnyObject *obj) { |
69 | iMediaRequest *d = obj; | 67 | iMediaRequest *d = obj; |
@@ -77,7 +75,6 @@ void init_MediaRequest(iMediaRequest *d, iDocumentWidget *doc, iGmLinkId linkId, | |||
77 | setUrl_GmRequest(d->req, url); | 75 | setUrl_GmRequest(d->req, url); |
78 | iConnect(GmRequest, d->req, updated, d, updated_MediaRequest_); | 76 | iConnect(GmRequest, d->req, updated, d, updated_MediaRequest_); |
79 | iConnect(GmRequest, d->req, finished, d, finished_MediaRequest_); | 77 | iConnect(GmRequest, d->req, finished, d, finished_MediaRequest_); |
80 | set_Atomic(&d->isUpdated, iFalse); | ||
81 | submit_GmRequest(d->req); | 78 | submit_GmRequest(d->req); |
82 | } | 79 | } |
83 | 80 | ||
@@ -105,8 +102,8 @@ struct Impl_Model { | |||
105 | }; | 102 | }; |
106 | 103 | ||
107 | void init_Model(iModel *d) { | 104 | void init_Model(iModel *d) { |
108 | d->history = new_History(); | 105 | d->history = new_History(); |
109 | d->url = new_String(); | 106 | d->url = new_String(); |
110 | } | 107 | } |
111 | 108 | ||
112 | void deinit_Model(iModel *d) { | 109 | void deinit_Model(iModel *d) { |
@@ -147,6 +144,8 @@ struct Impl_DocumentWidget { | |||
147 | iGmRequest * request; | 144 | iGmRequest * request; |
148 | iAtomicInt isRequestUpdated; /* request has new content, need to parse it */ | 145 | iAtomicInt isRequestUpdated; /* request has new content, need to parse it */ |
149 | iObjectList * media; | 146 | iObjectList * media; |
147 | iString sourceMime; | ||
148 | iBlock sourceContent; /* original content as received, for saving */ | ||
150 | iGmDocument * doc; | 149 | iGmDocument * doc; |
151 | int certFlags; | 150 | int certFlags; |
152 | iDate certExpiry; | 151 | iDate certExpiry; |
@@ -208,6 +207,8 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
208 | d->showLinkNumbers = iFalse; | 207 | d->showLinkNumbers = iFalse; |
209 | d->visBuf = new_VisBuf(); | 208 | d->visBuf = new_VisBuf(); |
210 | d->invalidRuns = new_PtrSet(); | 209 | d->invalidRuns = new_PtrSet(); |
210 | init_String(&d->sourceMime); | ||
211 | init_Block(&d->sourceContent, 0); | ||
211 | init_PtrArray(&d->visibleLinks); | 212 | init_PtrArray(&d->visibleLinks); |
212 | init_Click(&d->click, d, SDL_BUTTON_LEFT); | 213 | init_Click(&d->click, d, SDL_BUTTON_LEFT); |
213 | addChild_Widget(w, iClob(d->scroll = new_ScrollWidget())); | 214 | addChild_Widget(w, iClob(d->scroll = new_ScrollWidget())); |
@@ -225,6 +226,8 @@ void deinit_DocumentWidget(iDocumentWidget *d) { | |||
225 | delete_PtrSet(d->invalidRuns); | 226 | delete_PtrSet(d->invalidRuns); |
226 | iRelease(d->media); | 227 | iRelease(d->media); |
227 | iRelease(d->request); | 228 | iRelease(d->request); |
229 | deinit_Block(&d->sourceContent); | ||
230 | deinit_String(&d->sourceMime); | ||
228 | iRelease(d->doc); | 231 | iRelease(d->doc); |
229 | deinit_PtrArray(&d->visibleLinks); | 232 | deinit_PtrArray(&d->visibleLinks); |
230 | delete_String(d->certSubject); | 233 | delete_String(d->certSubject); |
@@ -269,7 +272,7 @@ static iRect documentBounds_DocumentWidget_(const iDocumentWidget *d) { | |||
269 | } | 272 | } |
270 | 273 | ||
271 | static int forceBreakWidth_DocumentWidget_(const iDocumentWidget *d) { | 274 | static int forceBreakWidth_DocumentWidget_(const iDocumentWidget *d) { |
272 | if (isLineWrapForced_App()) { | 275 | if (forceLineWrap_App()) { |
273 | const iRect bounds = bounds_Widget(constAs_Widget(d)); | 276 | const iRect bounds = bounds_Widget(constAs_Widget(d)); |
274 | const iRect docBounds = documentBounds_DocumentWidget_(d); | 277 | const iRect docBounds = documentBounds_DocumentWidget_(d); |
275 | return right_Rect(bounds) - left_Rect(docBounds) - gap_UI * d->pageMargin; | 278 | return right_Rect(bounds) - left_Rect(docBounds) - gap_UI * d->pageMargin; |
@@ -363,8 +366,12 @@ static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) { | |||
363 | if (isHover_Widget(w) && !contains_Widget(constAs_Widget(d->scroll), mouse)) { | 366 | if (isHover_Widget(w) && !contains_Widget(constAs_Widget(d->scroll), mouse)) { |
364 | setCursor_Window(get_Window(), | 367 | setCursor_Window(get_Window(), |
365 | d->hoverLink ? SDL_SYSTEM_CURSOR_HAND : SDL_SYSTEM_CURSOR_IBEAM); | 368 | d->hoverLink ? SDL_SYSTEM_CURSOR_HAND : SDL_SYSTEM_CURSOR_IBEAM); |
369 | if (d->hoverLink && | ||
370 | linkFlags_GmDocument(d->doc, d->hoverLink->linkId) & permanent_GmLinkFlag) { | ||
371 | setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); /* not dismissable */ | ||
366 | } | 372 | } |
367 | } | 373 | } |
374 | } | ||
368 | 375 | ||
369 | static void updateVisible_DocumentWidget_(iDocumentWidget *d) { | 376 | static void updateVisible_DocumentWidget_(iDocumentWidget *d) { |
370 | const iRangei visRange = visibleRange_DocumentWidget_(d); | 377 | const iRangei visRange = visibleRange_DocumentWidget_(d); |
@@ -507,9 +514,17 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode | |||
507 | case certificateNotValid_GmStatusCode: | 514 | case certificateNotValid_GmStatusCode: |
508 | appendFormat_String(src, "\n\n%s", cstr_String(meta)); | 515 | appendFormat_String(src, "\n\n%s", cstr_String(meta)); |
509 | break; | 516 | break; |
510 | case unsupportedMimeType_GmStatusCode: | 517 | case unsupportedMimeType_GmStatusCode: { |
511 | appendFormat_String(src, "\n```\n%s\n```\n", cstr_String(meta)); | 518 | iString *key = collectNew_String(); |
519 | toString_Sym(SDLK_s, KMOD_PRIMARY, key); | ||
520 | appendFormat_String(src, | ||
521 | "\n```\n%s\n```\n" | ||
522 | "You can save it as a file to your Downloads folder, though. " | ||
523 | "Press %s or select Save Page from the menu.", | ||
524 | cstr_String(meta), | ||
525 | cstr_String(key)); | ||
512 | break; | 526 | break; |
527 | } | ||
513 | case slowDown_GmStatusCode: | 528 | case slowDown_GmStatusCode: |
514 | appendFormat_String(src, "\n\nWait %s seconds before your next request.", | 529 | appendFormat_String(src, "\n\nWait %s seconds before your next request.", |
515 | cstr_String(meta)); | 530 | cstr_String(meta)); |
@@ -534,6 +549,20 @@ static void updateTheme_DocumentWidget_(iDocumentWidget *d) { | |||
534 | } | 549 | } |
535 | } | 550 | } |
536 | 551 | ||
552 | static void updateFetchProgress_DocumentWidget_(iDocumentWidget *d) { | ||
553 | iLabelWidget *prog = findWidget_App("document.progress"); | ||
554 | const size_t dlSize = d->request ? size_Block(body_GmRequest(d->request)) : 0; | ||
555 | setFlags_Widget(as_Widget(prog), hidden_WidgetFlag, dlSize < 250000); | ||
556 | if (isVisible_Widget(prog)) { | ||
557 | updateText_LabelWidget(prog, | ||
558 | collectNewFormat_String("%s%.3f MB", | ||
559 | isFinished_GmRequest(d->request) | ||
560 | ? uiHeading_ColorEscape | ||
561 | : uiTextCaution_ColorEscape, | ||
562 | dlSize / 1.0e6f)); | ||
563 | } | ||
564 | } | ||
565 | |||
537 | static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse *response) { | 566 | static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse *response) { |
538 | if (d->state == ready_RequestState) { | 567 | if (d->state == ready_RequestState) { |
539 | return; | 568 | return; |
@@ -545,12 +574,15 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse | |||
545 | iString str; | 574 | iString str; |
546 | invalidate_DocumentWidget_(d); | 575 | invalidate_DocumentWidget_(d); |
547 | updateTheme_DocumentWidget_(d); | 576 | updateTheme_DocumentWidget_(d); |
577 | clear_String(&d->sourceMime); | ||
578 | // set_Block(&d->sourceContent, &response->body); | ||
548 | initBlock_String(&str, &response->body); | 579 | initBlock_String(&str, &response->body); |
549 | if (category_GmStatusCode(statusCode) == categorySuccess_GmStatusCode) { | 580 | if (category_GmStatusCode(statusCode) == categorySuccess_GmStatusCode) { |
550 | /* Check the MIME type. */ | 581 | /* Check the MIME type. */ |
551 | iRangecc charset = range_CStr("utf-8"); | 582 | iRangecc charset = range_CStr("utf-8"); |
552 | enum iGmDocumentFormat docFormat = undefined_GmDocumentFormat; | 583 | enum iGmDocumentFormat docFormat = undefined_GmDocumentFormat; |
553 | const iString *mimeStr = collect_String(lower_String(&response->meta)); /* for convenience */ | 584 | const iString *mimeStr = collect_String(lower_String(&response->meta)); /* for convenience */ |
585 | set_String(&d->sourceMime, mimeStr); | ||
554 | iRangecc mime = range_String(mimeStr); | 586 | iRangecc mime = range_String(mimeStr); |
555 | iRangecc seg = iNullRange; | 587 | iRangecc seg = iNullRange; |
556 | while (nextSplit_Rangecc(mime, ";", &seg)) { | 588 | while (nextSplit_Rangecc(mime, ";", &seg)) { |
@@ -558,12 +590,15 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse | |||
558 | trim_Rangecc(¶m); | 590 | trim_Rangecc(¶m); |
559 | if (equal_Rangecc(param, "text/plain")) { | 591 | if (equal_Rangecc(param, "text/plain")) { |
560 | docFormat = plainText_GmDocumentFormat; | 592 | docFormat = plainText_GmDocumentFormat; |
593 | setRange_String(&d->sourceMime, param); | ||
561 | } | 594 | } |
562 | else if (equal_Rangecc(param, "text/gemini")) { | 595 | else if (equal_Rangecc(param, "text/gemini")) { |
563 | docFormat = gemini_GmDocumentFormat; | 596 | docFormat = gemini_GmDocumentFormat; |
597 | setRange_String(&d->sourceMime, param); | ||
564 | } | 598 | } |
565 | else if (startsWith_Rangecc(param, "image/")) { | 599 | else if (startsWith_Rangecc(param, "image/")) { |
566 | docFormat = gemini_GmDocumentFormat; | 600 | docFormat = gemini_GmDocumentFormat; |
601 | setRange_String(&d->sourceMime, param); | ||
567 | if (!d->request || isFinished_GmRequest(d->request)) { | 602 | if (!d->request || isFinished_GmRequest(d->request)) { |
568 | /* Make a simple document with an image. */ | 603 | /* Make a simple document with an image. */ |
569 | const char *imageTitle = "Image"; | 604 | const char *imageTitle = "Image"; |
@@ -573,9 +608,9 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse | |||
573 | imageTitle = | 608 | imageTitle = |
574 | baseName_Path(collect_String(newRange_String(parts.path))).start; | 609 | baseName_Path(collect_String(newRange_String(parts.path))).start; |
575 | } | 610 | } |
576 | format_String( | 611 | format_String(&str, "=> %s %s\n", cstr_String(d->mod.url), imageTitle); |
577 | &str, "=> %s %s\n", cstr_String(d->mod.url), imageTitle); | 612 | setImage_GmDocument( |
578 | setImage_GmDocument(d->doc, 1, mimeStr, &response->body); | 613 | d->doc, 1, mimeStr, &response->body, iFalse /* it's fixed */); |
579 | } | 614 | } |
580 | else { | 615 | else { |
581 | clear_String(&str); | 616 | clear_String(&str); |
@@ -747,7 +782,9 @@ void setRedirectCount_DocumentWidget(iDocumentWidget *d, int count) { | |||
747 | } | 782 | } |
748 | 783 | ||
749 | iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) { | 784 | iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) { |
750 | return d->state == fetching_RequestState || d->state == receivedPartialResponse_RequestState; | 785 | /*return d->state == fetching_RequestState || |
786 | d->state == receivedPartialResponse_RequestState;*/ | ||
787 | return d->request != NULL; | ||
751 | } | 788 | } |
752 | 789 | ||
753 | static void scroll_DocumentWidget_(iDocumentWidget *d, int offset) { | 790 | static void scroll_DocumentWidget_(iDocumentWidget *d, int offset) { |
@@ -961,7 +998,9 @@ static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char * | |||
961 | return iFalse; /* not our request */ | 998 | return iFalse; /* not our request */ |
962 | } | 999 | } |
963 | if (equal_Command(cmd, "media.updated")) { | 1000 | if (equal_Command(cmd, "media.updated")) { |
964 | /* TODO: Show a progress indicator */ | 1001 | /* Update the link's progress. */ |
1002 | invalidateLink_DocumentWidget_(d, req->linkId); | ||
1003 | refresh_Widget(d); | ||
965 | return iTrue; | 1004 | return iTrue; |
966 | } | 1005 | } |
967 | else if (equal_Command(cmd, "media.finished")) { | 1006 | else if (equal_Command(cmd, "media.finished")) { |
@@ -974,7 +1013,7 @@ static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char * | |||
974 | // cstr_String(meta_GmRequest(req->req))); | 1013 | // cstr_String(meta_GmRequest(req->req))); |
975 | if (startsWith_String(meta_GmRequest(req->req), "image/")) { | 1014 | if (startsWith_String(meta_GmRequest(req->req), "image/")) { |
976 | setImage_GmDocument(d->doc, req->linkId, meta_GmRequest(req->req), | 1015 | setImage_GmDocument(d->doc, req->linkId, meta_GmRequest(req->req), |
977 | body_GmRequest(req->req)); | 1016 | body_GmRequest(req->req), iTrue); |
978 | updateVisible_DocumentWidget_(d); | 1017 | updateVisible_DocumentWidget_(d); |
979 | invalidate_DocumentWidget_(d); | 1018 | invalidate_DocumentWidget_(d); |
980 | refresh_Widget(as_Widget(d)); | 1019 | refresh_Widget(as_Widget(d)); |
@@ -999,20 +1038,6 @@ static void allocVisBuffer_DocumentWidget_(const iDocumentWidget *d) { | |||
999 | } | 1038 | } |
1000 | else { | 1039 | else { |
1001 | dealloc_VisBuf(d->visBuf); | 1040 | dealloc_VisBuf(d->visBuf); |
1002 | #if 0 | ||
1003 | iZap(d->visBuffer->validRange); | ||
1004 | d->visBuffer->size = size; | ||
1005 | iAssert(size.x > 0); | ||
1006 | iForIndices(i, d->visBuffer->texture) { | ||
1007 | d->visBuffer->texture[i] = | ||
1008 | SDL_CreateTexture(renderer_Window(get_Window()), | ||
1009 | SDL_PIXELFORMAT_RGBA8888, | ||
1010 | SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET, | ||
1011 | size.x, | ||
1012 | size.y); | ||
1013 | SDL_SetTextureBlendMode(d->visBuffer->texture[i], SDL_BLENDMODE_NONE); | ||
1014 | } | ||
1015 | #endif | ||
1016 | } | 1041 | } |
1017 | } | 1042 | } |
1018 | 1043 | ||
@@ -1054,6 +1079,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
1054 | updateTheme_DocumentWidget_(d); | 1079 | updateTheme_DocumentWidget_(d); |
1055 | updateTrust_DocumentWidget_(d, NULL); | 1080 | updateTrust_DocumentWidget_(d, NULL); |
1056 | updateSize_DocumentWidget(d); | 1081 | updateSize_DocumentWidget(d); |
1082 | updateFetchProgress_DocumentWidget_(d); | ||
1057 | } | 1083 | } |
1058 | updateWindowTitle_DocumentWidget_(d); | 1084 | updateWindowTitle_DocumentWidget_(d); |
1059 | allocVisBuffer_DocumentWidget_(d); | 1085 | allocVisBuffer_DocumentWidget_(d); |
@@ -1135,17 +1161,25 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
1135 | } | 1161 | } |
1136 | else if (equalWidget_Command(cmd, w, "document.request.updated") && | 1162 | else if (equalWidget_Command(cmd, w, "document.request.updated") && |
1137 | pointerLabel_Command(cmd, "request") == d->request) { | 1163 | pointerLabel_Command(cmd, "request") == d->request) { |
1164 | set_Block(&d->sourceContent, body_GmRequest(d->request)); | ||
1165 | if (document_App() == d) { | ||
1166 | updateFetchProgress_DocumentWidget_(d); | ||
1167 | } | ||
1138 | checkResponse_DocumentWidget_(d); | 1168 | checkResponse_DocumentWidget_(d); |
1169 | set_Atomic(&d->isRequestUpdated, iFalse); /* ready to be notified again */ | ||
1139 | return iFalse; | 1170 | return iFalse; |
1140 | } | 1171 | } |
1141 | else if (equalWidget_Command(cmd, w, "document.request.finished") && | 1172 | else if (equalWidget_Command(cmd, w, "document.request.finished") && |
1142 | pointerLabel_Command(cmd, "request") == d->request) { | 1173 | pointerLabel_Command(cmd, "request") == d->request) { |
1174 | set_Block(&d->sourceContent, body_GmRequest(d->request)); | ||
1175 | updateFetchProgress_DocumentWidget_(d); | ||
1143 | checkResponse_DocumentWidget_(d); | 1176 | checkResponse_DocumentWidget_(d); |
1144 | resetSmoothScroll_DocumentWidget_(d); | 1177 | resetSmoothScroll_DocumentWidget_(d); |
1145 | d->scrollY = d->initNormScrollY * size_GmDocument(d->doc).y; | 1178 | d->scrollY = d->initNormScrollY * size_GmDocument(d->doc).y; |
1146 | d->state = ready_RequestState; | 1179 | d->state = ready_RequestState; |
1147 | /* The response may be cached. */ { | 1180 | /* The response may be cached. */ { |
1148 | if (!equal_Rangecc(urlScheme_String(d->mod.url), "about")) { | 1181 | if (!equal_Rangecc(urlScheme_String(d->mod.url), "about") && |
1182 | startsWithCase_String(meta_GmRequest(d->request), "text/")) { | ||
1149 | setCachedResponse_History(d->mod.history, response_GmRequest(d->request)); | 1183 | setCachedResponse_History(d->mod.history, response_GmRequest(d->request)); |
1150 | } | 1184 | } |
1151 | } | 1185 | } |
@@ -1159,20 +1193,102 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
1159 | cancel_GmRequest(d->request); | 1193 | cancel_GmRequest(d->request); |
1160 | return iFalse; | 1194 | return iFalse; |
1161 | } | 1195 | } |
1196 | /* | ||
1162 | else if (equal_Command(cmd, "document.request.cancelled") && document_Command(cmd) == d) { | 1197 | else if (equal_Command(cmd, "document.request.cancelled") && document_Command(cmd) == d) { |
1163 | postCommand_App("navigate.back"); | 1198 | postCommand_App("navigate.back"); |
1164 | return iFalse; | 1199 | return iFalse; |
1165 | } | 1200 | } |
1201 | */ | ||
1202 | else if (equal_Command(cmd, "media.updated") || equal_Command(cmd, "media.finished")) { | ||
1203 | return handleMediaCommand_DocumentWidget_(d, cmd); | ||
1204 | } | ||
1166 | else if (equal_Command(cmd, "document.stop") && document_App() == d) { | 1205 | else if (equal_Command(cmd, "document.stop") && document_App() == d) { |
1167 | if (d->request) { | 1206 | if (d->request) { |
1168 | postCommandf_App("document.request.cancelled doc:%p url:%s", d, cstr_String(d->mod.url)); | 1207 | postCommandf_App( |
1208 | "document.request.cancelled doc:%p url:%s", d, cstr_String(d->mod.url)); | ||
1169 | iReleasePtr(&d->request); | 1209 | iReleasePtr(&d->request); |
1210 | if (d->state != ready_RequestState) { | ||
1170 | d->state = ready_RequestState; | 1211 | d->state = ready_RequestState; |
1212 | postCommand_App("navigate.back"); | ||
1213 | } | ||
1214 | updateFetchProgress_DocumentWidget_(d); | ||
1171 | return iTrue; | 1215 | return iTrue; |
1172 | } | 1216 | } |
1173 | } | 1217 | } |
1174 | else if (equal_Command(cmd, "media.updated") || equal_Command(cmd, "media.finished")) { | 1218 | else if (equal_Command(cmd, "document.save") && document_App() == d) { |
1175 | return handleMediaCommand_DocumentWidget_(d, cmd); | 1219 | if (d->request) { |
1220 | makeMessage_Widget(uiTextCaution_ColorEscape "PAGE INCOMPLETE", | ||
1221 | "The page contents are still being downloaded."); | ||
1222 | } | ||
1223 | else if (!isEmpty_Block(&d->sourceContent)) { | ||
1224 | /* Figure out a file name from the URL. */ | ||
1225 | /* TODO: Make this a utility function. */ | ||
1226 | iUrl parts; | ||
1227 | init_Url(&parts, d->mod.url); | ||
1228 | while (startsWith_Rangecc(parts.path, "/")) { | ||
1229 | parts.path.start++; | ||
1230 | } | ||
1231 | while (endsWith_Rangecc(parts.path, "/")) { | ||
1232 | parts.path.end--; | ||
1233 | } | ||
1234 | iString *name = collectNewCStr_String("pagecontent"); | ||
1235 | if (isEmpty_Range(&parts.path)) { | ||
1236 | if (!isEmpty_Range(&parts.host)) { | ||
1237 | setRange_String(name, parts.host); | ||
1238 | replace_Block(&name->chars, '.', '_'); | ||
1239 | } | ||
1240 | } | ||
1241 | else { | ||
1242 | iRangecc fn = { parts.path.start + lastIndexOfCStr_Rangecc(parts.path, "/") + 1, | ||
1243 | parts.path.end }; | ||
1244 | if (!isEmpty_Range(&fn)) { | ||
1245 | setRange_String(name, fn); | ||
1246 | } | ||
1247 | } | ||
1248 | iString *savePath = concat_Path(downloadDir_App(), name); | ||
1249 | if (lastIndexOfCStr_String(savePath, ".") == iInvalidPos) { | ||
1250 | /* No extension specified in URL. */ | ||
1251 | if (startsWith_String(&d->sourceMime, "text/gemini")) { | ||
1252 | appendCStr_String(savePath, ".gmi"); | ||
1253 | } | ||
1254 | else if (startsWith_String(&d->sourceMime, "text/")) { | ||
1255 | appendCStr_String(savePath, ".txt"); | ||
1256 | } | ||
1257 | else if (startsWith_String(&d->sourceMime, "image/")) { | ||
1258 | appendCStr_String(savePath, cstr_String(&d->sourceMime) + 6); | ||
1259 | if (fileExists_FileInfo(savePath)) { | ||
1260 | } | ||
1261 | } | ||
1262 | /* Make it unique. */ | ||
1263 | iDate now; | ||
1264 | initCurrent_Date(&now); | ||
1265 | size_t insPos = lastIndexOfCStr_String(savePath, "."); | ||
1266 | if (insPos == iInvalidPos) { | ||
1267 | insPos = size_String(savePath); | ||
1268 | } | ||
1269 | const iString *date = collect_String(format_Date(&now, "_%Y-%m-%d_%H%M%S")); | ||
1270 | insertData_Block(&savePath->chars, insPos, cstr_String(date), size_String(date)); | ||
1271 | } | ||
1272 | /* Write the file. */ { | ||
1273 | iFile *f = new_File(savePath); | ||
1274 | if (open_File(f, writeOnly_FileMode)) { | ||
1275 | write_File(f, &d->sourceContent); | ||
1276 | const size_t size = size_Block(&d->sourceContent); | ||
1277 | const iBool isMega = size >= 1000000; | ||
1278 | makeMessage_Widget(uiHeading_ColorEscape "PAGE SAVED", | ||
1279 | format_CStr("%s\nSize: %.3f %s", cstr_String(path_File(f)), | ||
1280 | isMega ? size / 1.0e6f : (size / 1.0e3f), | ||
1281 | isMega ? "MB" : "KB")); | ||
1282 | } | ||
1283 | else { | ||
1284 | makeMessage_Widget(uiTextCaution_ColorEscape "ERROR SAVING PAGE", | ||
1285 | strerror(errno)); | ||
1286 | } | ||
1287 | iRelease(f); | ||
1288 | } | ||
1289 | delete_String(savePath); | ||
1290 | } | ||
1291 | return iTrue; | ||
1176 | } | 1292 | } |
1177 | else if (equal_Command(cmd, "document.reload") && document_App() == d) { | 1293 | else if (equal_Command(cmd, "document.reload") && document_App() == d) { |
1178 | d->initNormScrollY = normScrollPos_DocumentWidget_(d); | 1294 | d->initNormScrollY = normScrollPos_DocumentWidget_(d); |
@@ -1368,7 +1484,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
1368 | case '`': { | 1484 | case '`': { |
1369 | iBlock *seed = new_Block(64); | 1485 | iBlock *seed = new_Block(64); |
1370 | for (size_t i = 0; i < 64; ++i) { | 1486 | for (size_t i = 0; i < 64; ++i) { |
1371 | setByte_Block(seed, i, iRandom(0, 255)); | 1487 | setByte_Block(seed, i, iRandom(0, 256)); |
1372 | } | 1488 | } |
1373 | setThemeSeed_GmDocument(d->doc, seed); | 1489 | setThemeSeed_GmDocument(d->doc, seed); |
1374 | delete_Block(seed); | 1490 | delete_Block(seed); |
@@ -1400,8 +1516,8 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
1400 | } | 1516 | } |
1401 | smoothScroll_DocumentWidget_( | 1517 | smoothScroll_DocumentWidget_( |
1402 | d, | 1518 | d, |
1403 | -3 * ev->wheel.y * lineHeight_Text(default_FontId), | 1519 | -3 * ev->wheel.y * lineHeight_Text(paragraph_FontId), |
1404 | gap_UI * smoothSpeed_DocumentWidget_ + | 1520 | gap_Text * smoothSpeed_DocumentWidget_ + |
1405 | (isSmoothScrolling_DocumentWidget_(d) ? d->smoothSpeed : 0)); | 1521 | (isSmoothScrolling_DocumentWidget_(d) ? d->smoothSpeed : 0)); |
1406 | #endif | 1522 | #endif |
1407 | d->noHoverWhileScrolling = iTrue; | 1523 | d->noHoverWhileScrolling = iTrue; |
@@ -1433,23 +1549,48 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
1433 | } | 1549 | } |
1434 | iArray items; | 1550 | iArray items; |
1435 | init_Array(&items, sizeof(iMenuItem)); | 1551 | init_Array(&items, sizeof(iMenuItem)); |
1552 | if (d->contextLink) { | ||
1553 | pushBackN_Array( | ||
1554 | &items, | ||
1555 | (iMenuItem[]){ { "Open Link in New Tab", | ||
1556 | 0, | ||
1557 | 0, | ||
1558 | format_CStr("!open newtab:1 url:%s", | ||
1559 | cstr_String(linkUrl_GmDocument( | ||
1560 | d->doc, d->contextLink->linkId))) }, | ||
1561 | { "---", 0, 0, NULL }, | ||
1562 | { "Copy Link", | ||
1563 | 0, | ||
1564 | 0, | ||
1565 | "document.copylink" }}, | ||
1566 | 3); | ||
1567 | } | ||
1568 | else { | ||
1569 | if (!isEmpty_Range(&d->selectMark)) { | ||
1436 | pushBackN_Array( | 1570 | pushBackN_Array( |
1437 | &items, | 1571 | &items, |
1572 | (iMenuItem[]){ { "Copy", 0, 0, "copy" }, { "---", 0, 0, NULL } }, | ||
1573 | 2); | ||
1574 | } | ||
1575 | pushBackN_Array( | ||
1576 | &items, | ||
1438 | (iMenuItem[]){ | 1577 | (iMenuItem[]){ |
1439 | { "Go Back", navigateBack_KeyShortcut, "navigate.back" }, | 1578 | { "Go Back", navigateBack_KeyShortcut, "navigate.back" }, |
1440 | { "Go Forward", navigateForward_KeyShortcut, "navigate.forward" }, | 1579 | { "Go Forward", navigateForward_KeyShortcut, "navigate.forward" }, |
1441 | { "Reload Page", reload_KeyShortcut, "navigate.reload" }, | 1580 | { "Reload Page", reload_KeyShortcut, "navigate.reload" }, |
1442 | { "---", 0, 0, NULL }, | 1581 | { "---", 0, 0, NULL }, |
1443 | { d->contextLink ? "Copy Link URL" : "Copy Page URL", | 1582 | { "Copy Page URL", 0, 0, "document.copylink" }, |
1444 | 0, | 1583 | { "---", 0, 0, NULL } }, |
1445 | 0, | ||
1446 | "document.copylink" }, | ||
1447 | { isEmpty_Range(&d->selectMark) ? "Copy Full Source" : "Copy Selected", | ||
1448 | 'c', | ||
1449 | KMOD_PRIMARY, | ||
1450 | "copy" }, | ||
1451 | }, | ||
1452 | 6); | 1584 | 6); |
1585 | if (isEmpty_Range(&d->selectMark)) { | ||
1586 | pushBackN_Array( | ||
1587 | &items, | ||
1588 | (iMenuItem[]){ | ||
1589 | { "Copy Page Source", 'c', KMOD_PRIMARY, "copy" }, | ||
1590 | { "Save to Downloads", SDLK_s, KMOD_PRIMARY, "document.save" } }, | ||
1591 | 2); | ||
1592 | } | ||
1593 | } | ||
1453 | d->menu = makeMenu_Widget(w, data_Array(&items), size_Array(&items)); | 1594 | d->menu = makeMenu_Widget(w, data_Array(&items), size_Array(&items)); |
1454 | deinit_Array(&items); | 1595 | deinit_Array(&items); |
1455 | } | 1596 | } |
@@ -1489,10 +1630,16 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
1489 | iAssert(linkId); | 1630 | iAssert(linkId); |
1490 | /* Media links are opened inline by default. */ | 1631 | /* Media links are opened inline by default. */ |
1491 | if (isMediaLink_GmDocument(d->doc, linkId)) { | 1632 | if (isMediaLink_GmDocument(d->doc, linkId)) { |
1633 | const int linkFlags = linkFlags_GmDocument(d->doc, linkId); | ||
1634 | if (linkFlags & content_GmLinkFlag && linkFlags & permanent_GmLinkFlag) { | ||
1635 | /* We have the image and it cannot be dismissed, so nothing | ||
1636 | further to do. */ | ||
1637 | return iTrue; | ||
1638 | } | ||
1492 | if (!requestMedia_DocumentWidget_(d, linkId)) { | 1639 | if (!requestMedia_DocumentWidget_(d, linkId)) { |
1493 | if (linkFlags_GmDocument(d->doc, linkId) & content_GmLinkFlag) { | 1640 | if (linkFlags & content_GmLinkFlag) { |
1494 | /* Dismiss shown content on click. */ | 1641 | /* Dismiss shown content on click. */ |
1495 | setImage_GmDocument(d->doc, linkId, NULL, NULL); | 1642 | setImage_GmDocument(d->doc, linkId, NULL, NULL, iTrue); |
1496 | d->hoverLink = NULL; | 1643 | d->hoverLink = NULL; |
1497 | scroll_DocumentWidget_(d, 0); | 1644 | scroll_DocumentWidget_(d, 0); |
1498 | updateVisible_DocumentWidget_(d); | 1645 | updateVisible_DocumentWidget_(d); |
@@ -1505,7 +1652,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
1505 | iMediaRequest *req = findMediaRequest_DocumentWidget_(d, linkId); | 1652 | iMediaRequest *req = findMediaRequest_DocumentWidget_(d, linkId); |
1506 | if (req) { | 1653 | if (req) { |
1507 | setImage_GmDocument(d->doc, linkId, meta_GmRequest(req->req), | 1654 | setImage_GmDocument(d->doc, linkId, meta_GmRequest(req->req), |
1508 | body_GmRequest(req->req)); | 1655 | body_GmRequest(req->req), iTrue); |
1509 | updateVisible_DocumentWidget_(d); | 1656 | updateVisible_DocumentWidget_(d); |
1510 | invalidate_DocumentWidget_(d); | 1657 | invalidate_DocumentWidget_(d); |
1511 | refresh_Widget(w); | 1658 | refresh_Widget(w); |
@@ -1671,8 +1818,8 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
1671 | const int flags = linkFlags_GmDocument(doc, run->linkId); | 1818 | const int flags = linkFlags_GmDocument(doc, run->linkId); |
1672 | const iRect linkRect = moved_Rect(run->visBounds, origin); | 1819 | const iRect linkRect = moved_Rect(run->visBounds, origin); |
1673 | iMediaRequest *mr = NULL; | 1820 | iMediaRequest *mr = NULL; |
1674 | /* Show inline content. */ | 1821 | /* Show metadata about inline content. */ |
1675 | if (flags & content_GmLinkFlag) { | 1822 | if (flags & content_GmLinkFlag && run->flags & endOfLine_GmRunFlag) { |
1676 | fg = linkColor_GmDocument(doc, run->linkId, textHover_GmLinkPart); | 1823 | fg = linkColor_GmDocument(doc, run->linkId, textHover_GmLinkPart); |
1677 | iAssert(!isEmpty_Rect(run->bounds)); | 1824 | iAssert(!isEmpty_Rect(run->bounds)); |
1678 | iGmImageInfo info; | 1825 | iGmImageInfo info; |
@@ -1704,7 +1851,8 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
1704 | draw_Text(metaFont, | 1851 | draw_Text(metaFont, |
1705 | topRight_Rect(linkRect), | 1852 | topRight_Rect(linkRect), |
1706 | tmInlineContentMetadata_ColorId, | 1853 | tmInlineContentMetadata_ColorId, |
1707 | " \u2014 Fetching\u2026"); | 1854 | " \u2014 Fetching\u2026 (%.1f MB)", |
1855 | (float) size_Block(body_GmRequest(mr->req)) / 1.0e6f); | ||
1708 | } | 1856 | } |
1709 | } | 1857 | } |
1710 | else if (isHover) { | 1858 | else if (isHover) { |
@@ -1713,20 +1861,23 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
1713 | const int flags = linkFlags_GmDocument(doc, linkId); | 1861 | const int flags = linkFlags_GmDocument(doc, linkId); |
1714 | iUrl parts; | 1862 | iUrl parts; |
1715 | init_Url(&parts, url); | 1863 | init_Url(&parts, url); |
1716 | fg = linkColor_GmDocument(doc, linkId, textHover_GmLinkPart); | 1864 | fg = linkColor_GmDocument(doc, linkId, textHover_GmLinkPart); |
1717 | const iBool showHost = (!isEmpty_Range(&parts.host) && flags & userFriendly_GmLinkFlag); | 1865 | const iBool showHost = (flags & humanReadable_GmLinkFlag && |
1866 | (!isEmpty_Range(&parts.host) || flags & mailto_GmLinkFlag)); | ||
1718 | const iBool showImage = (flags & imageFileExtension_GmLinkFlag) != 0; | 1867 | const iBool showImage = (flags & imageFileExtension_GmLinkFlag) != 0; |
1719 | const iBool showAudio = (flags & audioFileExtension_GmLinkFlag) != 0; | 1868 | const iBool showAudio = (flags & audioFileExtension_GmLinkFlag) != 0; |
1720 | iString str; | 1869 | iString str; |
1721 | init_String(&str); | 1870 | init_String(&str); |
1871 | /* Show scheme and host. */ | ||
1722 | if (run->flags & endOfLine_GmRunFlag && | 1872 | if (run->flags & endOfLine_GmRunFlag && |
1723 | (flags & (imageFileExtension_GmLinkFlag | audioFileExtension_GmLinkFlag) || | 1873 | (flags & (imageFileExtension_GmLinkFlag | audioFileExtension_GmLinkFlag) || |
1724 | showHost)) { | 1874 | showHost)) { |
1725 | format_String( | 1875 | format_String(&str, |
1726 | &str, | ||
1727 | " \u2014%s%s%s\r%c%s", | 1876 | " \u2014%s%s%s\r%c%s", |
1728 | showHost ? " " : "", | 1877 | showHost ? " " : "", |
1729 | showHost ? (!equalCase_Rangecc(parts.scheme, "gemini") | 1878 | showHost ? (flags & mailto_GmLinkFlag |
1879 | ? cstr_String(url) | ||
1880 | : ~flags & gemini_GmLinkFlag | ||
1730 | ? format_CStr("%s://%s", | 1881 | ? format_CStr("%s://%s", |
1731 | cstr_Rangecc(parts.scheme), | 1882 | cstr_Rangecc(parts.scheme), |
1732 | cstr_Rangecc(parts.host)) | 1883 | cstr_Rangecc(parts.host)) |
@@ -1735,7 +1886,8 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
1735 | showHost && (showImage || showAudio) ? " \u2014" : "", | 1886 | showHost && (showImage || showAudio) ? " \u2014" : "", |
1736 | showImage || showAudio | 1887 | showImage || showAudio |
1737 | ? asciiBase_ColorEscape + fg | 1888 | ? asciiBase_ColorEscape + fg |
1738 | : (asciiBase_ColorEscape + linkColor_GmDocument(doc, run->linkId, domain_GmLinkPart)), | 1889 | : (asciiBase_ColorEscape + |
1890 | linkColor_GmDocument(doc, run->linkId, domain_GmLinkPart)), | ||
1739 | showImage ? " View Image \U0001f5bc" | 1891 | showImage ? " View Image \U0001f5bc" |
1740 | : showAudio ? " Play Audio \U0001f3b5" : ""); | 1892 | : showAudio ? " Play Audio \U0001f3b5" : ""); |
1741 | } | 1893 | } |
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c index 11098c80..2d6d84dd 100644 --- a/src/ui/inputwidget.c +++ b/src/ui/inputwidget.c | |||
@@ -182,6 +182,7 @@ void setText_InputWidget(iInputWidget *d, const iString *text) { | |||
182 | pushBack_Array(&d->text, &i.value); | 182 | pushBack_Array(&d->text, &i.value); |
183 | } | 183 | } |
184 | iZap(d->mark); | 184 | iZap(d->mark); |
185 | d->cursor = iMin(d->cursor, size_Array(&d->text)); | ||
185 | refresh_Widget(as_Widget(d)); | 186 | refresh_Widget(as_Widget(d)); |
186 | } | 187 | } |
187 | 188 | ||
@@ -733,6 +734,7 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
733 | deinit_String(&cur); | 734 | deinit_String(&cur); |
734 | } | 735 | } |
735 | delete_String(text); | 736 | delete_String(text); |
737 | drawChildren_Widget(w); | ||
736 | } | 738 | } |
737 | 739 | ||
738 | iBeginDefineSubclass(InputWidget, Widget) | 740 | iBeginDefineSubclass(InputWidget, Widget) |
diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c index 8b2506e7..28f43173 100644 --- a/src/ui/labelwidget.c +++ b/src/ui/labelwidget.c | |||
@@ -94,54 +94,7 @@ static iBool processEvent_LabelWidget_(iLabelWidget *d, const SDL_Event *ev) { | |||
94 | } | 94 | } |
95 | 95 | ||
96 | static void keyStr_LabelWidget_(const iLabelWidget *d, iString *str) { | 96 | static void keyStr_LabelWidget_(const iLabelWidget *d, iString *str) { |
97 | #if defined (iPlatformApple) | 97 | toString_Sym(d->key, d->kmods, str); |
98 | if (d->kmods & KMOD_CTRL) { | ||
99 | appendChar_String(str, 0x2303); | ||
100 | } | ||
101 | if (d->kmods & KMOD_ALT) { | ||
102 | appendChar_String(str, 0x2325); | ||
103 | } | ||
104 | if (d->kmods & KMOD_SHIFT) { | ||
105 | appendChar_String(str, 0x21e7); | ||
106 | } | ||
107 | if (d->kmods & KMOD_GUI) { | ||
108 | appendChar_String(str, 0x2318); | ||
109 | } | ||
110 | #else | ||
111 | if (d->kmods & KMOD_CTRL) { | ||
112 | appendCStr_String(str, "Ctrl+"); | ||
113 | } | ||
114 | if (d->kmods & KMOD_ALT) { | ||
115 | appendCStr_String(str, "Alt+"); | ||
116 | } | ||
117 | if (d->kmods & KMOD_SHIFT) { | ||
118 | appendCStr_String(str, "Shift+"); | ||
119 | } | ||
120 | if (d->kmods & KMOD_GUI) { | ||
121 | appendCStr_String(str, "Meta+"); | ||
122 | } | ||
123 | #endif | ||
124 | if (d->key == 0x20) { | ||
125 | appendCStr_String(str, "Space"); | ||
126 | } | ||
127 | else if (d->key == SDLK_LEFT) { | ||
128 | appendChar_String(str, 0x2190); | ||
129 | } | ||
130 | else if (d->key == SDLK_RIGHT) { | ||
131 | appendChar_String(str, 0x2192); | ||
132 | } | ||
133 | else if (d->key < 128 && (isalnum(d->key) || ispunct(d->key))) { | ||
134 | appendChar_String(str, upper_Char(d->key)); | ||
135 | } | ||
136 | else if (d->key == SDLK_BACKSPACE) { | ||
137 | appendChar_String(str, 0x232b); /* Erase to the Left */ | ||
138 | } | ||
139 | else if (d->key == SDLK_DELETE) { | ||
140 | appendChar_String(str, 0x2326); /* Erase to the Right */ | ||
141 | } | ||
142 | else { | ||
143 | appendCStr_String(str, SDL_GetKeyName(d->key)); | ||
144 | } | ||
145 | } | 98 | } |
146 | 99 | ||
147 | static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int *frame1, int *frame2) { | 100 | static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int *frame1, int *frame2) { |
diff --git a/src/ui/lookupwidget.c b/src/ui/lookupwidget.c index b7de5872..dcde7d79 100644 --- a/src/ui/lookupwidget.c +++ b/src/ui/lookupwidget.c | |||
@@ -265,7 +265,7 @@ static void searchIdentities_LookupJob_(iLookupJob *d) { | |||
265 | 265 | ||
266 | static iThreadResult worker_LookupWidget_(iThread *thread) { | 266 | static iThreadResult worker_LookupWidget_(iThread *thread) { |
267 | iLookupWidget *d = userData_Thread(thread); | 267 | iLookupWidget *d = userData_Thread(thread); |
268 | printf("[LookupWidget] worker is running\n"); fflush(stdout); | 268 | // printf("[LookupWidget] worker is running\n"); fflush(stdout); |
269 | lock_Mutex(d->mtx); | 269 | lock_Mutex(d->mtx); |
270 | for (;;) { | 270 | for (;;) { |
271 | wait_Condition(&d->jobAvailable, d->mtx); | 271 | wait_Condition(&d->jobAvailable, d->mtx); |
@@ -312,13 +312,13 @@ static iThreadResult worker_LookupWidget_(iThread *thread) { | |||
312 | /* Previous results haven't been taken yet. */ | 312 | /* Previous results haven't been taken yet. */ |
313 | delete_LookupJob(d->finishedJob); | 313 | delete_LookupJob(d->finishedJob); |
314 | } | 314 | } |
315 | printf("[LookupWidget] worker has %zu results\n", size_PtrArray(&job->results)); | 315 | // printf("[LookupWidget] worker has %zu results\n", size_PtrArray(&job->results)); |
316 | fflush(stdout); | 316 | fflush(stdout); |
317 | d->finishedJob = job; | 317 | d->finishedJob = job; |
318 | postCommand_Widget(as_Widget(d), "lookup.ready"); | 318 | postCommand_Widget(as_Widget(d), "lookup.ready"); |
319 | } | 319 | } |
320 | unlock_Mutex(d->mtx); | 320 | unlock_Mutex(d->mtx); |
321 | printf("[LookupWidget] worker has quit\n"); fflush(stdout); | 321 | // printf("[LookupWidget] worker has quit\n"); fflush(stdout); |
322 | return 0; | 322 | return 0; |
323 | } | 323 | } |
324 | 324 | ||
diff --git a/src/ui/util.c b/src/ui/util.c index 0af33138..ff6f8822 100644 --- a/src/ui/util.c +++ b/src/ui/util.c | |||
@@ -52,6 +52,57 @@ const char *command_UserEvent(const SDL_Event *d) { | |||
52 | return ""; | 52 | return ""; |
53 | } | 53 | } |
54 | 54 | ||
55 | void toString_Sym(int key, int kmods, iString *str) { | ||
56 | #if defined (iPlatformApple) | ||
57 | if (kmods & KMOD_CTRL) { | ||
58 | appendChar_String(str, 0x2303); | ||
59 | } | ||
60 | if (kmods & KMOD_ALT) { | ||
61 | appendChar_String(str, 0x2325); | ||
62 | } | ||
63 | if (kmods & KMOD_SHIFT) { | ||
64 | appendChar_String(str, 0x21e7); | ||
65 | } | ||
66 | if (kmods & KMOD_GUI) { | ||
67 | appendChar_String(str, 0x2318); | ||
68 | } | ||
69 | #else | ||
70 | if (kmods & KMOD_CTRL) { | ||
71 | appendCStr_String(str, "Ctrl+"); | ||
72 | } | ||
73 | if (kmods & KMOD_ALT) { | ||
74 | appendCStr_String(str, "Alt+"); | ||
75 | } | ||
76 | if (kmods & KMOD_SHIFT) { | ||
77 | appendCStr_String(str, "Shift+"); | ||
78 | } | ||
79 | if (kmods & KMOD_GUI) { | ||
80 | appendCStr_String(str, "Meta+"); | ||
81 | } | ||
82 | #endif | ||
83 | if (key == 0x20) { | ||
84 | appendCStr_String(str, "Space"); | ||
85 | } | ||
86 | else if (key == SDLK_LEFT) { | ||
87 | appendChar_String(str, 0x2190); | ||
88 | } | ||
89 | else if (key == SDLK_RIGHT) { | ||
90 | appendChar_String(str, 0x2192); | ||
91 | } | ||
92 | else if (key < 128 && (isalnum(key) || ispunct(key))) { | ||
93 | appendChar_String(str, upper_Char(key)); | ||
94 | } | ||
95 | else if (key == SDLK_BACKSPACE) { | ||
96 | appendChar_String(str, 0x232b); /* Erase to the Left */ | ||
97 | } | ||
98 | else if (key == SDLK_DELETE) { | ||
99 | appendChar_String(str, 0x2326); /* Erase to the Right */ | ||
100 | } | ||
101 | else { | ||
102 | appendCStr_String(str, SDL_GetKeyName(key)); | ||
103 | } | ||
104 | } | ||
105 | |||
55 | int keyMods_Sym(int kmods) { | 106 | int keyMods_Sym(int kmods) { |
56 | kmods &= (KMOD_SHIFT | KMOD_ALT | KMOD_CTRL | KMOD_GUI); | 107 | kmods &= (KMOD_SHIFT | KMOD_ALT | KMOD_CTRL | KMOD_GUI); |
57 | /* Don't treat left/right modifiers differently. */ | 108 | /* Don't treat left/right modifiers differently. */ |
@@ -192,6 +243,12 @@ iWidget *addAction_Widget(iWidget *parent, int key, int kmods, const char *comma | |||
192 | 243 | ||
193 | /*-----------------------------------------------------------------------------------------------*/ | 244 | /*-----------------------------------------------------------------------------------------------*/ |
194 | 245 | ||
246 | static iBool isCommandIgnoredByMenus_(const char *cmd) { | ||
247 | return equal_Command(cmd, "media.updated") || equal_Command(cmd, "document.request.updated") || | ||
248 | equal_Command(cmd, "window.resized") || | ||
249 | (equal_Command(cmd, "mouse.clicked") && !arg_Command(cmd)); /* button released */ | ||
250 | } | ||
251 | |||
195 | static iBool menuHandler_(iWidget *menu, const char *cmd) { | 252 | static iBool menuHandler_(iWidget *menu, const char *cmd) { |
196 | if (isVisible_Widget(menu)) { | 253 | if (isVisible_Widget(menu)) { |
197 | if (equalWidget_Command(cmd, menu, "menu.opened")) { | 254 | if (equalWidget_Command(cmd, menu, "menu.opened")) { |
@@ -201,13 +258,13 @@ static iBool menuHandler_(iWidget *menu, const char *cmd) { | |||
201 | /* Don't reopen self; instead, root will close the menu. */ | 258 | /* Don't reopen self; instead, root will close the menu. */ |
202 | return iFalse; | 259 | return iFalse; |
203 | } | 260 | } |
204 | if (equal_Command(cmd, "mouse.clicked") && arg_Command(cmd)) { | 261 | if ((equal_Command(cmd, "mouse.clicked") || equal_Command(cmd, "mouse.missed")) && |
262 | arg_Command(cmd)) { | ||
205 | /* Dismiss open menus when clicking outside them. */ | 263 | /* Dismiss open menus when clicking outside them. */ |
206 | closeMenu_Widget(menu); | 264 | closeMenu_Widget(menu); |
207 | return iTrue; | 265 | return iTrue; |
208 | } | 266 | } |
209 | if (!equal_Command(cmd, "window.resized") && | 267 | if (!isCommandIgnoredByMenus_(cmd)) { |
210 | !(equal_Command(cmd, "mouse.clicked") && !arg_Command(cmd)) /* ignore button release */) { | ||
211 | closeMenu_Widget(menu); | 268 | closeMenu_Widget(menu); |
212 | } | 269 | } |
213 | } | 270 | } |
@@ -252,6 +309,7 @@ void openMenu_Widget(iWidget *d, iInt2 coord) { | |||
252 | postCommand_App("cancel"); /* dismiss any other menus */ | 309 | postCommand_App("cancel"); /* dismiss any other menus */ |
253 | processEvents_App(postedEventsOnly_AppEventMode); | 310 | processEvents_App(postedEventsOnly_AppEventMode); |
254 | setFlags_Widget(d, hidden_WidgetFlag, iFalse); | 311 | setFlags_Widget(d, hidden_WidgetFlag, iFalse); |
312 | setFlags_Widget(d, commandOnMouseMiss_WidgetFlag, iTrue); | ||
255 | setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iFalse); | 313 | setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iFalse); |
256 | arrange_Widget(d); | 314 | arrange_Widget(d); |
257 | d->rect.pos = coord; | 315 | d->rect.pos = coord; |
@@ -681,10 +739,9 @@ void updateValueInput_Widget(iWidget *d, const char *title, const char *prompt) | |||
681 | 739 | ||
682 | static iBool messageHandler_(iWidget *msg, const char *cmd) { | 740 | static iBool messageHandler_(iWidget *msg, const char *cmd) { |
683 | /* Almost any command dismisses the sheet. */ | 741 | /* Almost any command dismisses the sheet. */ |
684 | // if (equal_Command(cmd, "menu.closed")) { | 742 | if (!(equal_Command(cmd, "media.updated") || equal_Command(cmd, "document.request.updated"))) { |
685 | // return iFalse; | 743 | destroy_Widget(msg); |
686 | // } | 744 | } |
687 | destroy_Widget(msg); | ||
688 | return iFalse; | 745 | return iFalse; |
689 | } | 746 | } |
690 | 747 | ||
@@ -755,14 +812,16 @@ iWidget *makePreferences_Widget(void) { | |||
755 | addChild_Widget(dlg, iClob(page)); | 812 | addChild_Widget(dlg, iClob(page)); |
756 | setFlags_Widget(page, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue); | 813 | setFlags_Widget(page, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue); |
757 | iWidget *headings = addChildFlags_Widget( | 814 | iWidget *headings = addChildFlags_Widget( |
758 | page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); | 815 | page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); |
759 | iWidget *values = addChildFlags_Widget( | 816 | iWidget *values = addChildFlags_Widget( |
760 | page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); | 817 | page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); |
818 | addChild_Widget(headings, iClob(makeHeading_Widget("Downloads folder:"))); | ||
819 | setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.downloads"); | ||
761 | #if defined (iPlatformApple) || defined (iPlatformMSys) | 820 | #if defined (iPlatformApple) || defined (iPlatformMSys) |
762 | addChild_Widget(headings, iClob(makeHeading_Widget("Use system theme:"))); | 821 | addChild_Widget(headings, iClob(makeHeading_Widget("Use system theme:"))); |
763 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.ostheme"))); | 822 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.ostheme"))); |
764 | #endif | 823 | #endif |
765 | addChild_Widget(headings, iClob(makeHeading_Widget("Theme:"))); | 824 | addChild_Widget(headings, iClob(makeHeading_Widget("Theme:"))); |
766 | iWidget *themes = new_Widget(); | 825 | iWidget *themes = new_Widget(); |
767 | /* Themes. */ { | 826 | /* Themes. */ { |
768 | setId_Widget(addChild_Widget(themes, iClob(new_LabelWidget("Pure Black", 0, 0, "theme.set arg:0"))), "prefs.theme.0"); | 827 | setId_Widget(addChild_Widget(themes, iClob(new_LabelWidget("Pure Black", 0, 0, "theme.set arg:0"))), "prefs.theme.0"); |
@@ -782,8 +841,9 @@ iWidget *makePreferences_Widget(void) { | |||
782 | addChild_Widget(headings, iClob(makeHeading_Widget("HTTP proxy:"))); | 841 | addChild_Widget(headings, iClob(makeHeading_Widget("HTTP proxy:"))); |
783 | setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.proxy.http"); | 842 | setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.proxy.http"); |
784 | arrange_Widget(dlg); | 843 | arrange_Widget(dlg); |
785 | /* Text input widths. */ { | 844 | /* Set text input widths. */ { |
786 | const int inputWidth = width_Rect(page->rect) - width_Rect(headings->rect); | 845 | const int inputWidth = width_Rect(page->rect) - width_Rect(headings->rect); |
846 | as_Widget(findChild_Widget(values, "prefs.downloads"))->rect.size.x = inputWidth; | ||
787 | as_Widget(findChild_Widget(values, "prefs.proxy.http"))->rect.size.x = inputWidth; | 847 | as_Widget(findChild_Widget(values, "prefs.proxy.http"))->rect.size.x = inputWidth; |
788 | as_Widget(findChild_Widget(values, "prefs.proxy.gopher"))->rect.size.x = inputWidth; | 848 | as_Widget(findChild_Widget(values, "prefs.proxy.gopher"))->rect.size.x = inputWidth; |
789 | } | 849 | } |
diff --git a/src/ui/util.h b/src/ui/util.h index 8ca9dd53..5590d008 100644 --- a/src/ui/util.h +++ b/src/ui/util.h | |||
@@ -49,6 +49,7 @@ iLocalDef iBool isResize_UserEvent(const SDL_Event *d) { | |||
49 | #endif | 49 | #endif |
50 | 50 | ||
51 | int keyMods_Sym (int kmods); /* shift, alt, control, or gui */ | 51 | int keyMods_Sym (int kmods); /* shift, alt, control, or gui */ |
52 | void toString_Sym (int key, int kmods, iString *str); | ||
52 | 53 | ||
53 | iRangei intersect_Rangei (iRangei a, iRangei b); | 54 | iRangei intersect_Rangei (iRangei a, iRangei b); |
54 | iRangei union_Rangei (iRangei a, iRangei b); | 55 | iRangei union_Rangei (iRangei a, iRangei b); |
diff --git a/src/ui/visbuf.c b/src/ui/visbuf.c index 64d861c6..8a66c300 100644 --- a/src/ui/visbuf.c +++ b/src/ui/visbuf.c | |||
@@ -37,6 +37,7 @@ void deinit_VisBuf(iVisBuf *d) { | |||
37 | 37 | ||
38 | void invalidate_VisBuf(iVisBuf *d) { | 38 | void invalidate_VisBuf(iVisBuf *d) { |
39 | iForIndices(i, d->buffers) { | 39 | iForIndices(i, d->buffers) { |
40 | d->buffers[i].origin = i * d->texSize.y; | ||
40 | iZap(d->buffers[i].validRange); | 41 | iZap(d->buffers[i].validRange); |
41 | } | 42 | } |
42 | } | 43 | } |
diff --git a/src/ui/widget.c b/src/ui/widget.c index d3f28b08..05bb62cc 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c | |||
@@ -542,6 +542,15 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) { | |||
542 | break; | 542 | break; |
543 | } | 543 | } |
544 | } | 544 | } |
545 | if (d->flags & commandOnMouseMiss_WidgetFlag && ev->type == SDL_MOUSEBUTTONDOWN && | ||
546 | !contains_Widget(d, init_I2(ev->button.x, ev->button.y))) { | ||
547 | postCommand_Widget(d, | ||
548 | "mouse.missed arg:%d button:%d coord:%d %d", | ||
549 | ev->type == SDL_MOUSEBUTTONDOWN ? 1 : 0, | ||
550 | ev->button.button, | ||
551 | ev->button.x, | ||
552 | ev->button.y); | ||
553 | } | ||
545 | if (d->flags & mouseModal_WidgetFlag && isMouseEvent_(ev)) { | 554 | if (d->flags & mouseModal_WidgetFlag && isMouseEvent_(ev)) { |
546 | setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); | 555 | setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); |
547 | return iTrue; | 556 | return iTrue; |
diff --git a/src/ui/widget.h b/src/ui/widget.h index 25208c30..fa4fbe0f 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h | |||
@@ -58,6 +58,7 @@ enum iWidgetFlag { | |||
58 | tight_WidgetFlag = iBit(12), /* smaller padding */ | 58 | tight_WidgetFlag = iBit(12), /* smaller padding */ |
59 | keepOnTop_WidgetFlag = iBit(13), /* gets events first; drawn last */ | 59 | keepOnTop_WidgetFlag = iBit(13), /* gets events first; drawn last */ |
60 | mouseModal_WidgetFlag = iBit(14), /* eats all unprocessed mouse events */ | 60 | mouseModal_WidgetFlag = iBit(14), /* eats all unprocessed mouse events */ |
61 | commandOnMouseMiss_WidgetFlag = iBit(15), | ||
61 | /* arrange behavior */ | 62 | /* arrange behavior */ |
62 | fixedPosition_WidgetFlag = iBit(16), | 63 | fixedPosition_WidgetFlag = iBit(16), |
63 | arrangeHorizontal_WidgetFlag = iBit(17), /* arrange children horizontally */ | 64 | arrangeHorizontal_WidgetFlag = iBit(17), /* arrange children horizontally */ |
diff --git a/src/ui/window.c b/src/ui/window.c index 0a63a941..8ebb67a8 100644 --- a/src/ui/window.c +++ b/src/ui/window.c | |||
@@ -91,10 +91,13 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) { | |||
91 | #endif | 91 | #endif |
92 | 92 | ||
93 | #if !defined (iHaveNativeMenus) | 93 | #if !defined (iHaveNativeMenus) |
94 | /* TODO: Submenus wouldn't hurt here. */ | ||
94 | static const iMenuItem navMenuItems[] = { | 95 | static const iMenuItem navMenuItems[] = { |
95 | { "New Tab", 't', KMOD_PRIMARY, "tabs.new" }, | 96 | { "New Tab", 't', KMOD_PRIMARY, "tabs.new" }, |
96 | { "Open Location...", SDLK_l, KMOD_PRIMARY, "focus.set id:url" }, | 97 | { "Open Location...", SDLK_l, KMOD_PRIMARY, "focus.set id:url" }, |
97 | { "---", 0, 0, NULL }, | 98 | { "---", 0, 0, NULL }, |
99 | { "Save to Downloads", SDLK_s, KMOD_PRIMARY, "document.save" }, | ||
100 | { "---", 0, 0, NULL }, | ||
98 | { "Copy Source Text", SDLK_c, KMOD_PRIMARY, "copy" }, | 101 | { "Copy Source Text", SDLK_c, KMOD_PRIMARY, "copy" }, |
99 | { "Bookmark This Page", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, | 102 | { "Bookmark This Page", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, |
100 | { "---", 0, 0, NULL }, | 103 | { "---", 0, 0, NULL }, |
@@ -104,7 +107,7 @@ static const iMenuItem navMenuItems[] = { | |||
104 | { "Reset Zoom", SDLK_0, KMOD_PRIMARY, "zoom.set arg:100" }, | 107 | { "Reset Zoom", SDLK_0, KMOD_PRIMARY, "zoom.set arg:100" }, |
105 | { "---", 0, 0, NULL }, | 108 | { "---", 0, 0, NULL }, |
106 | { "Preferences...", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, | 109 | { "Preferences...", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, |
107 | { "Help", 0, 0, "!open url:about:help" }, | 110 | { "Help", SDLK_F1, 0, "!open url:about:help" }, |
108 | { "Release Notes", 0, 0, "!open url:about:version" }, | 111 | { "Release Notes", 0, 0, "!open url:about:version" }, |
109 | { "---", 0, 0, NULL }, | 112 | { "---", 0, 0, NULL }, |
110 | { "Quit Lagrange", 'q', KMOD_PRIMARY, "quit" } | 113 | { "Quit Lagrange", 'q', KMOD_PRIMARY, "quit" } |
@@ -116,6 +119,8 @@ static const iMenuItem navMenuItems[] = { | |||
116 | static const iMenuItem fileMenuItems[] = { | 119 | static const iMenuItem fileMenuItems[] = { |
117 | { "New Tab", SDLK_t, KMOD_PRIMARY, "tabs.new" }, | 120 | { "New Tab", SDLK_t, KMOD_PRIMARY, "tabs.new" }, |
118 | { "Open Location...", SDLK_l, KMOD_PRIMARY, "focus.set id:url" }, | 121 | { "Open Location...", SDLK_l, KMOD_PRIMARY, "focus.set id:url" }, |
122 | { "---", 0, 0, NULL }, | ||
123 | { "Save to Downloads", SDLK_s, KMOD_PRIMARY, "document.save" }, | ||
119 | }; | 124 | }; |
120 | 125 | ||
121 | static const iMenuItem editMenuItems[] = { | 126 | static const iMenuItem editMenuItems[] = { |
@@ -362,12 +367,24 @@ static void setupUserInterface_Window(iWindow *d) { | |||
362 | setId_Widget(as_Widget(lock), "navbar.lock"); | 367 | setId_Widget(as_Widget(lock), "navbar.lock"); |
363 | setFont_LabelWidget(lock, defaultSymbols_FontId); | 368 | setFont_LabelWidget(lock, defaultSymbols_FontId); |
364 | updateTextCStr_LabelWidget(lock, "\U0001f512"); | 369 | updateTextCStr_LabelWidget(lock, "\U0001f512"); |
365 | iInputWidget *url = new_InputWidget(0); | 370 | /* URL input field. */ { |
366 | setSelectAllOnFocus_InputWidget(url, iTrue); | 371 | iInputWidget *url = new_InputWidget(0); |
367 | setId_Widget(as_Widget(url), "url"); | 372 | setSelectAllOnFocus_InputWidget(url, iTrue); |
368 | setNotifyEdits_InputWidget(url, iTrue); | 373 | setId_Widget(as_Widget(url), "url"); |
369 | setTextCStr_InputWidget(url, "gemini://"); | 374 | setNotifyEdits_InputWidget(url, iTrue); |
370 | addChildFlags_Widget(navBar, iClob(url), expand_WidgetFlag); | 375 | setTextCStr_InputWidget(url, "gemini://"); |
376 | addChildFlags_Widget(navBar, iClob(url), expand_WidgetFlag); | ||
377 | /* Download progress indicator is inside the input field, but hidden normally. */ | ||
378 | setPadding_Widget(as_Widget(url),0, 0, gap_UI * 1, 0); | ||
379 | iLabelWidget *progress = new_LabelWidget(uiTextCaution_ColorEscape "00.000 MB", 0, 0, NULL); | ||
380 | setId_Widget(as_Widget(progress), "document.progress"); | ||
381 | setAlignVisually_LabelWidget(progress, iTrue); | ||
382 | shrink_Rect(&as_Widget(progress)->rect, init_I2(0, gap_UI)); | ||
383 | addChildFlags_Widget(as_Widget(url), | ||
384 | iClob(progress), | ||
385 | moveToParentRightEdge_WidgetFlag); | ||
386 | setBackgroundColor_Widget(as_Widget(progress), uiBackground_ColorId); | ||
387 | } | ||
371 | setId_Widget(addChild_Widget( | 388 | setId_Widget(addChild_Widget( |
372 | navBar, iClob(newIcon_LabelWidget(reloadCStr_, 0, 0, "navigate.reload"))), | 389 | navBar, iClob(newIcon_LabelWidget(reloadCStr_, 0, 0, "navigate.reload"))), |
373 | "reload"); | 390 | "reload"); |
@@ -477,26 +494,36 @@ static void drawBlank_Window_(iWindow *d) { | |||
477 | SDL_RenderPresent(d->render); | 494 | SDL_RenderPresent(d->render); |
478 | } | 495 | } |
479 | 496 | ||
480 | // #define ENABLE_SWRENDER | 497 | iBool create_Window_(iWindow *d, iRect rect, uint32_t flags) { |
498 | flags |= SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI; | ||
499 | if (SDL_CreateWindowAndRenderer( | ||
500 | width_Rect(rect), height_Rect(rect), flags, &d->win, &d->render)) { | ||
501 | return iFalse; | ||
502 | } | ||
503 | return iTrue; | ||
504 | } | ||
481 | 505 | ||
482 | void init_Window(iWindow *d, iRect rect) { | 506 | void init_Window(iWindow *d, iRect rect) { |
483 | theWindow_ = d; | 507 | theWindow_ = d; |
484 | iZap(d->cursors); | 508 | iZap(d->cursors); |
509 | d->initialPos = rect.pos; | ||
485 | d->pendingCursor = NULL; | 510 | d->pendingCursor = NULL; |
486 | d->isDrawFrozen = iTrue; | 511 | d->isDrawFrozen = iTrue; |
487 | uint32_t flags = SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI; | 512 | uint32_t flags = 0; |
488 | #if defined (ENABLE_SWRENDER) | 513 | #if defined (iPlatformApple) |
489 | SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software"); | ||
490 | #elif defined (iPlatformApple) | ||
491 | SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal"); | 514 | SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal"); |
492 | #else | 515 | #else |
493 | flags |= SDL_WINDOW_OPENGL; | 516 | flags |= SDL_WINDOW_OPENGL; |
494 | #endif | 517 | #endif |
495 | SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1"); | 518 | SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1"); |
496 | if (SDL_CreateWindowAndRenderer( | 519 | /* First try SDL's default renderer that should be the best option. */ |
497 | width_Rect(rect), height_Rect(rect), flags, &d->win, &d->render)) { | 520 | if (forceSoftwareRender_App() || !create_Window_(d, rect, flags)) { |
498 | fprintf(stderr, "Error when creating window: %s\n", SDL_GetError()); | 521 | /* No luck, maybe software only? This should always work as long as there is a display. */ |
499 | exit(-2); | 522 | SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software"); |
523 | if (!create_Window_(d, rect, 0)) { | ||
524 | fprintf(stderr, "Error when creating window: %s\n", SDL_GetError()); | ||
525 | exit(-2); | ||
526 | } | ||
500 | } | 527 | } |
501 | if (left_Rect(rect) >= 0) { | 528 | if (left_Rect(rect) >= 0) { |
502 | SDL_SetWindowPosition(d->win, left_Rect(rect), top_Rect(rect)); | 529 | SDL_SetWindowPosition(d->win, left_Rect(rect), top_Rect(rect)); |
@@ -571,6 +598,16 @@ SDL_Renderer *renderer_Window(const iWindow *d) { | |||
571 | 598 | ||
572 | static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) { | 599 | static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) { |
573 | switch (ev->event) { | 600 | switch (ev->event) { |
601 | #if defined (LAGRANGE_ENABLE_WINDOWPOS_FIX) | ||
602 | case SDL_WINDOWEVENT_EXPOSED: | ||
603 | if (d->initialPos.x >= 0) { | ||
604 | int bx, by; | ||
605 | SDL_GetWindowBordersSize(d->win, &by, &bx, NULL, NULL); | ||
606 | SDL_SetWindowPosition(d->win, d->initialPos.x + bx, d->initialPos.y + by); | ||
607 | d->initialPos = init1_I2(-1); | ||
608 | } | ||
609 | return iFalse; | ||
610 | #endif | ||
574 | case SDL_WINDOWEVENT_MOVED: | 611 | case SDL_WINDOWEVENT_MOVED: |
575 | /* No need to do anything. */ | 612 | /* No need to do anything. */ |
576 | return iTrue; | 613 | return iTrue; |
diff --git a/src/ui/window.h b/src/ui/window.h index 4aec2fa7..b067d30e 100644 --- a/src/ui/window.h +++ b/src/ui/window.h | |||
@@ -34,6 +34,7 @@ iDeclareTypeConstructionArgs(Window, iRect rect) | |||
34 | 34 | ||
35 | struct Impl_Window { | 35 | struct Impl_Window { |
36 | SDL_Window * win; | 36 | SDL_Window * win; |
37 | iInt2 initialPos; | ||
37 | iBool isDrawFrozen; /* avoids premature draws while restoring window state */ | 38 | iBool isDrawFrozen; /* avoids premature draws while restoring window state */ |
38 | SDL_Renderer *render; | 39 | SDL_Renderer *render; |
39 | iWidget * root; | 40 | iWidget * root; |