summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-08-04 11:24:13 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-08-04 11:24:13 +0300
commit7a69d53ba72f9e5581c781dd4c54dd3a3e6b89b4 (patch)
treed84af4e3d72eb02c7cc3a15e08ceda42f59f9b02 /src
parent7fff669f5be5021862ae634b65f3ab18b17021dd (diff)
Remember scroll positions on visited pages
Diffstat (limited to 'src')
-rw-r--r--src/app.c6
-rw-r--r--src/app.h4
-rw-r--r--src/gmutil.h20
-rw-r--r--src/history.c15
-rw-r--r--src/history.h1
-rw-r--r--src/ui/documentwidget.c42
-rw-r--r--src/ui/documentwidget.h1
-rw-r--r--src/ui/text.c8
8 files changed, 74 insertions, 23 deletions
diff --git a/src/app.c b/src/app.c
index 21028edc..5ac211f9 100644
--- a/src/app.c
+++ b/src/app.c
@@ -318,7 +318,7 @@ iGmCerts *certs_App(void) {
318 return app_.certs; 318 return app_.certs;
319} 319}
320 320
321const iHistory *history_App(void) { 321iHistory *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? */
diff --git a/src/app.h b/src/app.h
index 23e30ea5..5cb68920 100644
--- a/src/app.h
+++ b/src/app.h
@@ -26,8 +26,8 @@ void processEvents_App (enum iAppEventMode mode);
26iBool handleCommand_App (const char *cmd); 26iBool handleCommand_App (const char *cmd);
27void refresh_App (void); 27void refresh_App (void);
28 28
29iGmCerts * certs_App (void); 29iGmCerts * certs_App (void);
30const iHistory *history_App (void); 30iHistory * history_App (void);
31 31
32iAny * findWidget_App (const char *id); 32iAny * findWidget_App (const char *id);
33void addTicker_App (void (*ticker)(iAny *), iAny *context); 33void 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
6iDeclareType(GmError) 6iDeclareType(GmError) iDeclareType(Url)
7iDeclareType(Url)
8 7
9/* Response status codes. */ 8/* Response status codes. */
10enum iGmStatusCode { 9enum 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
46iLocalDef 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
38struct Impl_GmError { 52struct 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 */
11void init_HistoryItem(iHistoryItem *d) { 11void 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
16void deinit_HistoryItem(iHistoryItem *d) { 17void 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 ){
194iBool goBack_History(iHistory *d) { 197iBool 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)
10struct Impl_HistoryItem { 10struct 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
15iDeclareType(History) 16iDeclareType(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
247static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) { 255static 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
305static void updateSource_DocumentWidget_(iDocumentWidget *d) { 315static 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
400void setInitialScroll_DocumentWidget (iDocumentWidget *d, int scrollY) {
401 d->initialScrollY = scrollY;
402}
403
387iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) { 404iBool 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)
6iDeclareObjectConstruction(DocumentWidget) 6iDeclareObjectConstruction(DocumentWidget)
7 7
8void setUrl_DocumentWidget (iDocumentWidget *, const iString *url); 8void setUrl_DocumentWidget (iDocumentWidget *, const iString *url);
9void setInitialScroll_DocumentWidget (iDocumentWidget *, int scrollY); /* set after content received */
9iBool isRequestOngoing_DocumentWidget (const iDocumentWidget *); 10iBool 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,