diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-08-04 11:24:13 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-08-04 11:24:13 +0300 |
commit | 7a69d53ba72f9e5581c781dd4c54dd3a3e6b89b4 (patch) | |
tree | d84af4e3d72eb02c7cc3a15e08ceda42f59f9b02 | |
parent | 7fff669f5be5021862ae634b65f3ab18b17021dd (diff) |
Remember scroll positions on visited pages
-rw-r--r-- | src/app.c | 6 | ||||
-rw-r--r-- | src/app.h | 4 | ||||
-rw-r--r-- | src/gmutil.h | 20 | ||||
-rw-r--r-- | src/history.c | 15 | ||||
-rw-r--r-- | src/history.h | 1 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 42 | ||||
-rw-r--r-- | src/ui/documentwidget.h | 1 | ||||
-rw-r--r-- | src/ui/text.c | 8 |
8 files changed, 74 insertions, 23 deletions
@@ -318,7 +318,7 @@ iGmCerts *certs_App(void) { | |||
318 | return app_.certs; | 318 | return app_.certs; |
319 | } | 319 | } |
320 | 320 | ||
321 | const iHistory *history_App(void) { | 321 | iHistory *history_App(void) { |
322 | return app_.history; | 322 | return app_.history; |
323 | } | 323 | } |
324 | 324 | ||
@@ -346,7 +346,9 @@ iBool handleCommand_App(const char *cmd) { | |||
346 | } | 346 | } |
347 | } | 347 | } |
348 | print_History(d->history); | 348 | print_History(d->history); |
349 | setUrl_DocumentWidget(findChild_Widget(root, "document"), url); | 349 | iDocumentWidget *doc = findChild_Widget(root, "document"); |
350 | setUrl_DocumentWidget(doc, url); | ||
351 | setInitialScroll_DocumentWidget(doc, argLabel_Command(cmd, "scroll") * gap_UI); | ||
350 | } | 352 | } |
351 | else if (equal_Command(cmd, "document.request.cancelled")) { | 353 | else if (equal_Command(cmd, "document.request.cancelled")) { |
352 | /* TODO: How should cancelled requests be treated in the history? */ | 354 | /* TODO: How should cancelled requests be treated in the history? */ |
@@ -26,8 +26,8 @@ void processEvents_App (enum iAppEventMode mode); | |||
26 | iBool handleCommand_App (const char *cmd); | 26 | iBool handleCommand_App (const char *cmd); |
27 | void refresh_App (void); | 27 | void refresh_App (void); |
28 | 28 | ||
29 | iGmCerts * certs_App (void); | 29 | iGmCerts * certs_App (void); |
30 | const iHistory *history_App (void); | 30 | iHistory * history_App (void); |
31 | 31 | ||
32 | iAny * findWidget_App (const char *id); | 32 | iAny * findWidget_App (const char *id); |
33 | void addTicker_App (void (*ticker)(iAny *), iAny *context); | 33 | void addTicker_App (void (*ticker)(iAny *), iAny *context); |
diff --git a/src/gmutil.h b/src/gmutil.h index 6c1b01ad..d0ed4581 100644 --- a/src/gmutil.h +++ b/src/gmutil.h | |||
@@ -3,18 +3,26 @@ | |||
3 | #include <the_Foundation/range.h> | 3 | #include <the_Foundation/range.h> |
4 | #include <the_Foundation/string.h> | 4 | #include <the_Foundation/string.h> |
5 | 5 | ||
6 | iDeclareType(GmError) | 6 | iDeclareType(GmError) iDeclareType(Url) |
7 | iDeclareType(Url) | ||
8 | 7 | ||
9 | /* Response status codes. */ | 8 | /* Response status codes. */ |
10 | enum iGmStatusCode { | 9 | enum iGmStatusCode { |
11 | clientSide_GmStatusCode = -100, /* clientside status codes */ | 10 | /* clientside status codes */ |
11 | clientSide_GmStatusCode = -100, | ||
12 | invalidRedirect_GmStatusCode, | 12 | invalidRedirect_GmStatusCode, |
13 | invalidHeader_GmStatusCode, | 13 | invalidHeader_GmStatusCode, |
14 | unsupportedMimeType_GmStatusCode, | 14 | unsupportedMimeType_GmStatusCode, |
15 | failedToOpenFile_GmStatusCode, | 15 | failedToOpenFile_GmStatusCode, |
16 | unknownStatusCode_GmStatusCode, | 16 | unknownStatusCode_GmStatusCode, |
17 | none_GmStatusCode = 0, | 17 | none_GmStatusCode = 0, |
18 | /* general status code categories */ | ||
19 | categoryInput_GmStatusCode = 1, | ||
20 | categorySuccess_GmStatusCode = 2, | ||
21 | categoryRedirect_GmStatusCode = 3, | ||
22 | categoryTemporaryFailure_GmStatusCode = 4, | ||
23 | categoryPermanentFailure_GmStatusCode = 5, | ||
24 | categoryClientCertificate_GmStatus = 6, | ||
25 | /* detailed status codes */ | ||
18 | input_GmStatusCode = 10, | 26 | input_GmStatusCode = 10, |
19 | sensitiveInput_GmStatusCode = 11, | 27 | sensitiveInput_GmStatusCode = 11, |
20 | success_GmStatusCode = 20, | 28 | success_GmStatusCode = 20, |
@@ -35,6 +43,12 @@ enum iGmStatusCode { | |||
35 | certificateNotValid_GmStatusCode = 62, | 43 | certificateNotValid_GmStatusCode = 62, |
36 | }; | 44 | }; |
37 | 45 | ||
46 | iLocalDef enum iGmStatusCode category_GmStatusCode(enum iGmStatusCode code) { | ||
47 | if (code < 0) return 0; | ||
48 | if (code < 10) return code; | ||
49 | return code / 10; | ||
50 | } | ||
51 | |||
38 | struct Impl_GmError { | 52 | struct Impl_GmError { |
39 | iChar icon; | 53 | iChar icon; |
40 | const char *title; | 54 | const char *title; |
diff --git a/src/history.c b/src/history.c index edffd485..ca80ad3f 100644 --- a/src/history.c +++ b/src/history.c | |||
@@ -11,6 +11,7 @@ static const size_t maxAgeVisited_History_ = 3600 * 24 * 30; /* one month */ | |||
11 | void init_HistoryItem(iHistoryItem *d) { | 11 | void init_HistoryItem(iHistoryItem *d) { |
12 | initCurrent_Time(&d->when); | 12 | initCurrent_Time(&d->when); |
13 | init_String(&d->url); | 13 | init_String(&d->url); |
14 | d->scrollY = 0; | ||
14 | } | 15 | } |
15 | 16 | ||
16 | void deinit_HistoryItem(iHistoryItem *d) { | 17 | void deinit_HistoryItem(iHistoryItem *d) { |
@@ -52,13 +53,14 @@ static void writeItems_(const iArray *items, iFile *f) { | |||
52 | iDate date; | 53 | iDate date; |
53 | init_Date(&date, &item->when); | 54 | init_Date(&date, &item->when); |
54 | format_String(line, | 55 | format_String(line, |
55 | "%04d-%02d-%02dT%02d:%02d:%02d %s\n", | 56 | "%04d-%02d-%02dT%02d:%02d:%02d %04x %s\n", |
56 | date.year, | 57 | date.year, |
57 | date.month, | 58 | date.month, |
58 | date.day, | 59 | date.day, |
59 | date.hour, | 60 | date.hour, |
60 | date.minute, | 61 | date.minute, |
61 | date.second, | 62 | date.second, |
63 | item->scrollY, | ||
62 | cstr_String(&item->url)); | 64 | cstr_String(&item->url)); |
63 | writeData_File(f, cstr_String(line), size_String(line)); | 65 | writeData_File(f, cstr_String(line), size_String(line)); |
64 | } | 66 | } |
@@ -84,18 +86,19 @@ static void loadItems_(iArray *items, iFile *f, double maxAge) { | |||
84 | iTime now; | 86 | iTime now; |
85 | initCurrent_Time(&now); | 87 | initCurrent_Time(&now); |
86 | while (nextSplit_Rangecc(&src, "\n", &line)) { | 88 | while (nextSplit_Rangecc(&src, "\n", &line)) { |
87 | int y, m, D, H, M, S; | 89 | int y, m, D, H, M, S, scroll = 0; |
88 | sscanf(line.start, "%04d-%02d-%02dT%02d:%02d:%02d", &y, &m, &D, &H, &M, &S); | 90 | sscanf(line.start, "%04d-%02d-%02dT%02d:%02d:%02d %04x", &y, &m, &D, &H, &M, &S, &scroll); |
89 | if (!y) break; | 91 | if (!y) break; |
90 | iHistoryItem item; | 92 | iHistoryItem item; |
91 | init_HistoryItem(&item); | 93 | init_HistoryItem(&item); |
94 | item.scrollY = scroll; | ||
92 | init_Time( | 95 | init_Time( |
93 | &item.when, | 96 | &item.when, |
94 | &(iDate){ .year = y, .month = m, .day = D, .hour = H, .minute = M, .second = S }); | 97 | &(iDate){ .year = y, .month = m, .day = D, .hour = H, .minute = M, .second = S }); |
95 | if (maxAge > 0.0 && secondsSince_Time(&now, &item.when) > maxAge) { | 98 | if (maxAge > 0.0 && secondsSince_Time(&now, &item.when) > maxAge) { |
96 | continue; /* Too old. */ | 99 | continue; /* Too old. */ |
97 | } | 100 | } |
98 | initRange_String(&item.url, (iRangecc){ line.start + 20, line.end }); | 101 | initRange_String(&item.url, (iRangecc){ line.start + 25, line.end }); |
99 | pushBack_Array(items, &item); | 102 | pushBack_Array(items, &item); |
100 | } | 103 | } |
101 | } | 104 | } |
@@ -194,7 +197,9 @@ void addUrl_History(iHistory *d, const iString *url ){ | |||
194 | iBool goBack_History(iHistory *d) { | 197 | iBool goBack_History(iHistory *d) { |
195 | if (d->stackPos < size_Array(&d->stack) - 1) { | 198 | if (d->stackPos < size_Array(&d->stack) - 1) { |
196 | d->stackPos++; | 199 | d->stackPos++; |
197 | postCommandf_App("open history:1 url:%s", cstr_String(url_History(d, d->stackPos))); | 200 | postCommandf_App("open history:1 scroll:%d url:%s", |
201 | item_History(d)->scrollY, | ||
202 | cstr_String(url_History(d, d->stackPos))); | ||
198 | return iTrue; | 203 | return iTrue; |
199 | } | 204 | } |
200 | return iFalse; | 205 | return iFalse; |
diff --git a/src/history.h b/src/history.h index 4074b1aa..149e6b82 100644 --- a/src/history.h +++ b/src/history.h | |||
@@ -10,6 +10,7 @@ iDeclareTypeConstruction(HistoryItem) | |||
10 | struct Impl_HistoryItem { | 10 | struct Impl_HistoryItem { |
11 | iTime when; | 11 | iTime when; |
12 | iString url; | 12 | iString url; |
13 | int scrollY; /* unit is gap_UI */ | ||
13 | }; | 14 | }; |
14 | 15 | ||
15 | iDeclareType(History) | 16 | iDeclareType(History) |
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 7450ba12..dd119e3e 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -5,6 +5,7 @@ | |||
5 | #include "paint.h" | 5 | #include "paint.h" |
6 | #include "command.h" | 6 | #include "command.h" |
7 | #include "util.h" | 7 | #include "util.h" |
8 | #include "history.h" | ||
8 | #include "app.h" | 9 | #include "app.h" |
9 | #include "../gmdocument.h" | 10 | #include "../gmdocument.h" |
10 | #include "../gmrequest.h" | 11 | #include "../gmrequest.h" |
@@ -91,6 +92,7 @@ struct Impl_DocumentWidget { | |||
91 | iPtrArray visibleLinks; | 92 | iPtrArray visibleLinks; |
92 | const iGmRun *hoverLink; | 93 | const iGmRun *hoverLink; |
93 | iClick click; | 94 | iClick click; |
95 | int initialScrollY; | ||
94 | iScrollWidget *scroll; | 96 | iScrollWidget *scroll; |
95 | iWidget *menu; | 97 | iWidget *menu; |
96 | SDL_Cursor *arrowCursor; | 98 | SDL_Cursor *arrowCursor; |
@@ -242,6 +244,12 @@ static void updateVisible_DocumentWidget_(iDocumentWidget *d) { | |||
242 | clear_PtrArray(&d->visibleLinks); | 244 | clear_PtrArray(&d->visibleLinks); |
243 | render_GmDocument(d->doc, visRange, addVisibleLink_DocumentWidget_, d); | 245 | render_GmDocument(d->doc, visRange, addVisibleLink_DocumentWidget_, d); |
244 | updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window())); | 246 | updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window())); |
247 | /* Remember scroll positions of recently visited pages. */ { | ||
248 | iHistoryItem *it = item_History(history_App()); | ||
249 | if (it) { | ||
250 | it->scrollY = d->scrollY / gap_UI; | ||
251 | } | ||
252 | } | ||
245 | } | 253 | } |
246 | 254 | ||
247 | static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) { | 255 | static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) { |
@@ -300,9 +308,14 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode | |||
300 | break; | 308 | break; |
301 | } | 309 | } |
302 | setSource_DocumentWidget_(d, src); | 310 | setSource_DocumentWidget_(d, src); |
311 | d->scrollY = 0; | ||
312 | d->state = ready_DocumentState; | ||
303 | } | 313 | } |
304 | 314 | ||
305 | static void updateSource_DocumentWidget_(iDocumentWidget *d) { | 315 | static void updateSource_DocumentWidget_(iDocumentWidget *d) { |
316 | if (d->state == ready_DocumentState) { | ||
317 | return; | ||
318 | } | ||
306 | /* TODO: Do this in the background. However, that requires a text metrics calculator | 319 | /* TODO: Do this in the background. However, that requires a text metrics calculator |
307 | that does not try to cache the glyph bitmaps. */ | 320 | that does not try to cache the glyph bitmaps. */ |
308 | const enum iGmStatusCode statusCode = status_GmRequest(d->request); | 321 | const enum iGmStatusCode statusCode = status_GmRequest(d->request); |
@@ -384,6 +397,10 @@ void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) { | |||
384 | } | 397 | } |
385 | } | 398 | } |
386 | 399 | ||
400 | void setInitialScroll_DocumentWidget (iDocumentWidget *d, int scrollY) { | ||
401 | d->initialScrollY = scrollY; | ||
402 | } | ||
403 | |||
387 | iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) { | 404 | iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) { |
388 | return d->state == fetching_DocumentState || d->state == receivedPartialResponse_DocumentState; | 405 | return d->state == fetching_DocumentState || d->state == receivedPartialResponse_DocumentState; |
389 | } | 406 | } |
@@ -437,11 +454,14 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | |||
437 | return; | 454 | return; |
438 | } | 455 | } |
439 | enum iGmStatusCode statusCode = status_GmRequest(d->request); | 456 | enum iGmStatusCode statusCode = status_GmRequest(d->request); |
457 | if (statusCode == none_GmStatusCode) { | ||
458 | return; | ||
459 | } | ||
440 | if (d->state == fetching_DocumentState) { | 460 | if (d->state == fetching_DocumentState) { |
441 | d->state = receivedPartialResponse_DocumentState; | 461 | d->state = receivedPartialResponse_DocumentState; |
442 | updateTrust_DocumentWidget_(d); | 462 | updateTrust_DocumentWidget_(d); |
443 | switch (statusCode / 10) { | 463 | switch (category_GmStatusCode(statusCode)) { |
444 | case 1: /* input required */ { | 464 | case categoryInput_GmStatusCode: { |
445 | iUrl parts; | 465 | iUrl parts; |
446 | init_Url(&parts, d->url); | 466 | init_Url(&parts, d->url); |
447 | printf("%s\n", cstr_String(meta_GmRequest(d->request))); | 467 | printf("%s\n", cstr_String(meta_GmRequest(d->request))); |
@@ -460,16 +480,17 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | |||
460 | statusCode == sensitiveInput_GmStatusCode); | 480 | statusCode == sensitiveInput_GmStatusCode); |
461 | break; | 481 | break; |
462 | } | 482 | } |
463 | case 2: /* success */ | 483 | case categorySuccess_GmStatusCode: |
464 | d->scrollY = 0; | 484 | d->scrollY = d->initialScrollY; |
465 | reset_GmDocument(d->doc); /* new content incoming */ | 485 | reset_GmDocument(d->doc); /* new content incoming */ |
466 | updateSource_DocumentWidget_(d); | 486 | updateSource_DocumentWidget_(d); |
467 | break; | 487 | break; |
468 | case 3: /* redirect */ | 488 | case categoryRedirect_GmStatusCode: |
469 | if (isEmpty_String(meta_GmRequest(d->request))) { | 489 | if (isEmpty_String(meta_GmRequest(d->request))) { |
470 | showErrorPage_DocumentWidget_(d, invalidRedirect_GmStatusCode); | 490 | showErrorPage_DocumentWidget_(d, invalidRedirect_GmStatusCode); |
471 | } | 491 | } |
472 | else { | 492 | else { |
493 | /* TODO: only accept redirects that use gemini protocol */ | ||
473 | postCommandf_App( | 494 | postCommandf_App( |
474 | "open redirect:1 url:%s", | 495 | "open redirect:1 url:%s", |
475 | cstr_String(absoluteUrl_String(d->url, meta_GmRequest(d->request)))); | 496 | cstr_String(absoluteUrl_String(d->url, meta_GmRequest(d->request)))); |
@@ -480,18 +501,20 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | |||
480 | if (isDefined_GmError(statusCode)) { | 501 | if (isDefined_GmError(statusCode)) { |
481 | showErrorPage_DocumentWidget_(d, statusCode); | 502 | showErrorPage_DocumentWidget_(d, statusCode); |
482 | } | 503 | } |
483 | else if (statusCode / 10 == 4) { | 504 | else if (category_GmStatusCode(statusCode) == |
505 | categoryTemporaryFailure_GmStatusCode) { | ||
484 | showErrorPage_DocumentWidget_(d, temporaryFailure_GmStatusCode); | 506 | showErrorPage_DocumentWidget_(d, temporaryFailure_GmStatusCode); |
485 | } | 507 | } |
486 | else if (statusCode / 10 == 5) { | 508 | else if (category_GmStatusCode(statusCode) == |
509 | categoryPermanentFailure_GmStatusCode) { | ||
487 | showErrorPage_DocumentWidget_(d, permanentFailure_GmStatusCode); | 510 | showErrorPage_DocumentWidget_(d, permanentFailure_GmStatusCode); |
488 | } | 511 | } |
489 | break; | 512 | break; |
490 | } | 513 | } |
491 | } | 514 | } |
492 | else if (d->state == receivedPartialResponse_DocumentState) { | 515 | else if (d->state == receivedPartialResponse_DocumentState) { |
493 | switch (statusCode) { | 516 | switch (category_GmStatusCode(statusCode)) { |
494 | case success_GmStatusCode: | 517 | case categorySuccess_GmStatusCode: |
495 | /* More content available. */ | 518 | /* More content available. */ |
496 | updateSource_DocumentWidget_(d); | 519 | updateSource_DocumentWidget_(d); |
497 | break; | 520 | break; |
@@ -649,7 +672,6 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
649 | } | 672 | } |
650 | else if (isCommand_Widget(w, ev, "document.request.finished") && | 673 | else if (isCommand_Widget(w, ev, "document.request.finished") && |
651 | pointerLabel_Command(command_UserEvent(ev), "request") == d->request) { | 674 | pointerLabel_Command(command_UserEvent(ev), "request") == d->request) { |
652 | iAssert(d->state == receivedPartialResponse_DocumentState); /* must already have been updated at least once */ | ||
653 | checkResponse_DocumentWidget_(d); | 675 | checkResponse_DocumentWidget_(d); |
654 | d->state = ready_DocumentState; | 676 | d->state = ready_DocumentState; |
655 | iReleasePtr(&d->request); | 677 | iReleasePtr(&d->request); |
diff --git a/src/ui/documentwidget.h b/src/ui/documentwidget.h index dfd342af..6b0ea607 100644 --- a/src/ui/documentwidget.h +++ b/src/ui/documentwidget.h | |||
@@ -6,4 +6,5 @@ iDeclareWidgetClass(DocumentWidget) | |||
6 | iDeclareObjectConstruction(DocumentWidget) | 6 | iDeclareObjectConstruction(DocumentWidget) |
7 | 7 | ||
8 | void setUrl_DocumentWidget (iDocumentWidget *, const iString *url); | 8 | void setUrl_DocumentWidget (iDocumentWidget *, const iString *url); |
9 | void setInitialScroll_DocumentWidget (iDocumentWidget *, int scrollY); /* set after content received */ | ||
9 | iBool isRequestOngoing_DocumentWidget (const iDocumentWidget *); | 10 | iBool isRequestOngoing_DocumentWidget (const iDocumentWidget *); |
diff --git a/src/ui/text.c b/src/ui/text.c index 50b3bbab..f562e642 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -356,6 +356,7 @@ static iRect run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe | |||
356 | iChar prevCh = 0; | 356 | iChar prevCh = 0; |
357 | for (const char *chPos = text.start; chPos != text.end; ) { | 357 | for (const char *chPos = text.start; chPos != text.end; ) { |
358 | iAssert(chPos < text.end); | 358 | iAssert(chPos < text.end); |
359 | const char *currentPos = chPos; | ||
359 | if (*chPos == 0x1b) { | 360 | if (*chPos == 0x1b) { |
360 | /* ANSI escape. */ | 361 | /* ANSI escape. */ |
361 | chPos++; | 362 | chPos++; |
@@ -394,7 +395,12 @@ static iRect run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe | |||
394 | int x2 = x1 + glyph->rect[hoff].size.x; | 395 | int x2 = x1 + glyph->rect[hoff].size.x; |
395 | /* Out of the allotted space? */ | 396 | /* Out of the allotted space? */ |
396 | if (xposLimit > 0 && x2 > xposLimit) { | 397 | if (xposLimit > 0 && x2 > xposLimit) { |
397 | *continueFrom_out = lastWordEnd; | 398 | if (lastWordEnd != text.start) { |
399 | *continueFrom_out = lastWordEnd; | ||
400 | } | ||
401 | else { | ||
402 | *continueFrom_out = currentPos; /* forced break */ | ||
403 | } | ||
398 | break; | 404 | break; |
399 | } | 405 | } |
400 | const SDL_Rect dst = { x1 + glyph->d[hoff].x, | 406 | const SDL_Rect dst = { x1 + glyph->d[hoff].x, |