diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-07-30 10:03:24 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-07-30 10:03:24 +0300 |
commit | 9eb27c0930017dd60d27d2e7f24561c369f5ac65 (patch) | |
tree | 2e9829eb37115e872a95068c97024ced904919fd | |
parent | 50f176335ed82c6517e5eeca107ce06b02a2d024 (diff) |
History of visited URLs; visualize time of last link visit
-rw-r--r-- | src/app.c | 19 | ||||
-rw-r--r-- | src/app.h | 4 | ||||
-rw-r--r-- | src/gmdocument.c | 30 | ||||
-rw-r--r-- | src/gmdocument.h | 4 | ||||
-rw-r--r-- | src/history.c | 208 | ||||
-rw-r--r-- | src/history.h | 5 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 22 |
7 files changed, 184 insertions, 108 deletions
@@ -41,7 +41,6 @@ static const char *dataDir_App_ = "~/AppData/Roaming/fi.skyjake.Lagrange"; | |||
41 | static const char *dataDir_App_ = "~/.config/lagrange"; | 41 | static const char *dataDir_App_ = "~/.config/lagrange"; |
42 | #endif | 42 | #endif |
43 | static const char *prefsFileName_App_ = "prefs.cfg"; | 43 | static const char *prefsFileName_App_ = "prefs.cfg"; |
44 | static const char *historyFileName_App_ = "history.txt"; | ||
45 | 44 | ||
46 | struct Impl_App { | 45 | struct Impl_App { |
47 | iCommandLine args; | 46 | iCommandLine args; |
@@ -96,10 +95,6 @@ static const iString *prefsFileName_(void) { | |||
96 | return collect_String(concatCStr_Path(&iStringLiteral(dataDir_App_), prefsFileName_App_)); | 95 | return collect_String(concatCStr_Path(&iStringLiteral(dataDir_App_), prefsFileName_App_)); |
97 | } | 96 | } |
98 | 97 | ||
99 | static const iString *historyFileName_(void) { | ||
100 | return collect_String(concatCStr_Path(&iStringLiteral(dataDir_App_), historyFileName_App_)); | ||
101 | } | ||
102 | |||
103 | static void loadPrefs_App_(iApp *d) { | 98 | static void loadPrefs_App_(iApp *d) { |
104 | iUnused(d); | 99 | iUnused(d); |
105 | /* Create the data dir if it doesn't exist yet. */ | 100 | /* Create the data dir if it doesn't exist yet. */ |
@@ -148,7 +143,7 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
148 | d->pendingRefresh = iFalse; | 143 | d->pendingRefresh = iFalse; |
149 | d->history = new_History(); | 144 | d->history = new_History(); |
150 | loadPrefs_App_(d); | 145 | loadPrefs_App_(d); |
151 | load_History(d->history, historyFileName_()); | 146 | load_History(d->history, dataDir_App_); |
152 | #if defined (iHaveLoadEmbed) | 147 | #if defined (iHaveLoadEmbed) |
153 | /* Load the resources from a file. */ { | 148 | /* Load the resources from a file. */ { |
154 | if (!load_Embed( | 149 | if (!load_Embed( |
@@ -166,7 +161,7 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
166 | 161 | ||
167 | static void deinit_App(iApp *d) { | 162 | static void deinit_App(iApp *d) { |
168 | savePrefs_App_(d); | 163 | savePrefs_App_(d); |
169 | save_History(d->history, historyFileName_()); | 164 | save_History(d->history, dataDir_App_); |
170 | delete_History(d->history); | 165 | delete_History(d->history); |
171 | deinit_SortedArray(&d->tickers); | 166 | deinit_SortedArray(&d->tickers); |
172 | delete_Window(d->window); | 167 | delete_Window(d->window); |
@@ -310,6 +305,10 @@ void addTicker_App(void (*ticker)(iAny *), iAny *context) { | |||
310 | insert_SortedArray(&d->tickers, &(iTicker){ context, ticker }); | 305 | insert_SortedArray(&d->tickers, &(iTicker){ context, ticker }); |
311 | } | 306 | } |
312 | 307 | ||
308 | const iHistory *history_App(void) { | ||
309 | return app_.history; | ||
310 | } | ||
311 | |||
313 | static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { | 312 | static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { |
314 | if (equal_Command(cmd, "prefs.dismiss") || equal_Command(cmd, "preferences")) { | 313 | if (equal_Command(cmd, "prefs.dismiss") || equal_Command(cmd, "preferences")) { |
315 | setUiScale_Window(get_Window(), | 314 | setUiScale_Window(get_Window(), |
@@ -327,11 +326,7 @@ iBool handleCommand_App(const char *cmd) { | |||
327 | const iString *url = collect_String(newCStr_String(suffixPtr_Command(cmd, "url"))); | 326 | const iString *url = collect_String(newCStr_String(suffixPtr_Command(cmd, "url"))); |
328 | if (!argLabel_Command(cmd, "history")) { | 327 | if (!argLabel_Command(cmd, "history")) { |
329 | if (argLabel_Command(cmd, "redirect")) { | 328 | if (argLabel_Command(cmd, "redirect")) { |
330 | /* Update in the history. */ | 329 | replace_History(d->history, url); |
331 | iHistoryItem *item = item_History(d->history); | ||
332 | if (item) { | ||
333 | set_String(&item->url, url); | ||
334 | } | ||
335 | } | 330 | } |
336 | else { | 331 | else { |
337 | addUrl_History(d->history, url); | 332 | addUrl_History(d->history, url); |
@@ -6,6 +6,7 @@ | |||
6 | #include <the_Foundation/time.h> | 6 | #include <the_Foundation/time.h> |
7 | 7 | ||
8 | iDeclareType(Window) | 8 | iDeclareType(Window) |
9 | iDeclareType(History) | ||
9 | 10 | ||
10 | enum iAppEventMode { | 11 | enum iAppEventMode { |
11 | waitForNewEvents_AppEventMode, | 12 | waitForNewEvents_AppEventMode, |
@@ -24,7 +25,8 @@ void processEvents_App (enum iAppEventMode mode); | |||
24 | iBool handleCommand_App (const char *cmd); | 25 | iBool handleCommand_App (const char *cmd); |
25 | void refresh_App (void); | 26 | void refresh_App (void); |
26 | 27 | ||
27 | iTime urlVisitTime_App (const iString *url); | 28 | const iHistory *history_App (void); |
29 | |||
28 | iAny * findWidget_App (const char *id); | 30 | iAny * findWidget_App (const char *id); |
29 | void addTicker_App (void (*ticker)(iAny *), iAny *context); | 31 | void addTicker_App (void (*ticker)(iAny *), iAny *context); |
30 | void postRefresh_App (void); | 32 | void postRefresh_App (void); |
diff --git a/src/gmdocument.c b/src/gmdocument.c index f7016ba4..bd54e6e5 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c | |||
@@ -4,6 +4,8 @@ | |||
4 | #include "ui/text.h" | 4 | #include "ui/text.h" |
5 | #include "ui/metrics.h" | 5 | #include "ui/metrics.h" |
6 | #include "ui/window.h" | 6 | #include "ui/window.h" |
7 | #include "history.h" | ||
8 | #include "app.h" | ||
7 | 9 | ||
8 | #include <the_Foundation/ptrarray.h> | 10 | #include <the_Foundation/ptrarray.h> |
9 | #include <the_Foundation/regexp.h> | 11 | #include <the_Foundation/regexp.h> |
@@ -16,11 +18,13 @@ iDeclareType(GmLink) | |||
16 | 18 | ||
17 | struct Impl_GmLink { | 19 | struct Impl_GmLink { |
18 | iString url; | 20 | iString url; |
21 | iTime when; | ||
19 | int flags; | 22 | int flags; |
20 | }; | 23 | }; |
21 | 24 | ||
22 | void init_GmLink(iGmLink *d) { | 25 | void init_GmLink(iGmLink *d) { |
23 | init_String(&d->url); | 26 | init_String(&d->url); |
27 | iZap(d->when); | ||
24 | d->flags = 0; | 28 | d->flags = 0; |
25 | } | 29 | } |
26 | 30 | ||
@@ -69,10 +73,13 @@ void deinit_GmImage(iGmImage *d) { | |||
69 | 73 | ||
70 | iDefineTypeConstructionArgs(GmImage, (const iBlock *data), data) | 74 | iDefineTypeConstructionArgs(GmImage, (const iBlock *data), data) |
71 | 75 | ||
76 | /*----------------------------------------------------------------------------------------------*/ | ||
77 | |||
72 | struct Impl_GmDocument { | 78 | struct Impl_GmDocument { |
73 | iObject object; | 79 | iObject object; |
74 | enum iGmDocumentFormat format; | 80 | enum iGmDocumentFormat format; |
75 | iString source; | 81 | iString source; |
82 | iString url; /* for resolving relative links */ | ||
76 | iString localHost; | 83 | iString localHost; |
77 | iInt2 size; | 84 | iInt2 size; |
78 | iArray layout; /* contents of source, laid out in document space */ | 85 | iArray layout; /* contents of source, laid out in document space */ |
@@ -200,6 +207,12 @@ static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li | |||
200 | delete_String(path); | 207 | delete_String(path); |
201 | } | 208 | } |
202 | } | 209 | } |
210 | /* Check if visited. */ { | ||
211 | link->when = urlVisitTime_History(history_App(), &link->url); | ||
212 | if (isValid_Time(&link->when)) { | ||
213 | link->flags |= visited_GmLinkFlag; | ||
214 | } | ||
215 | } | ||
203 | pushBack_PtrArray(&d->links, link); | 216 | pushBack_PtrArray(&d->links, link); |
204 | *linkId = size_PtrArray(&d->links); /* index + 1 */ | 217 | *linkId = size_PtrArray(&d->links); /* index + 1 */ |
205 | iRangecc desc = capturedRange_RegExpMatch(&m, 2); | 218 | iRangecc desc = capturedRange_RegExpMatch(&m, 2); |
@@ -474,6 +487,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
474 | void init_GmDocument(iGmDocument *d) { | 487 | void init_GmDocument(iGmDocument *d) { |
475 | d->format = gemini_GmDocumentFormat; | 488 | d->format = gemini_GmDocumentFormat; |
476 | init_String(&d->source); | 489 | init_String(&d->source); |
490 | init_String(&d->url); | ||
477 | init_String(&d->localHost); | 491 | init_String(&d->localHost); |
478 | d->size = zero_I2(); | 492 | d->size = zero_I2(); |
479 | init_Array(&d->layout, sizeof(iGmRun)); | 493 | init_Array(&d->layout, sizeof(iGmRun)); |
@@ -488,6 +502,7 @@ void deinit_GmDocument(iGmDocument *d) { | |||
488 | deinit_PtrArray(&d->links); | 502 | deinit_PtrArray(&d->links); |
489 | deinit_Array(&d->layout); | 503 | deinit_Array(&d->layout); |
490 | deinit_String(&d->localHost); | 504 | deinit_String(&d->localHost); |
505 | deinit_String(&d->url); | ||
491 | deinit_String(&d->source); | 506 | deinit_String(&d->source); |
492 | } | 507 | } |
493 | 508 | ||
@@ -499,6 +514,8 @@ void reset_GmDocument(iGmDocument *d) { | |||
499 | clear_PtrArray(&d->images); | 514 | clear_PtrArray(&d->images); |
500 | clearLinks_GmDocument_(d); | 515 | clearLinks_GmDocument_(d); |
501 | clear_Array(&d->layout); | 516 | clear_Array(&d->layout); |
517 | clear_String(&d->url); | ||
518 | clear_String(&d->localHost); | ||
502 | } | 519 | } |
503 | 520 | ||
504 | void setFormat_GmDocument(iGmDocument *d, enum iGmDocumentFormat format) { | 521 | void setFormat_GmDocument(iGmDocument *d, enum iGmDocumentFormat format) { |
@@ -570,8 +587,11 @@ static void normalize_GmDocument(iGmDocument *d) { | |||
570 | set_String(&d->source, collect_String(normalized)); | 587 | set_String(&d->source, collect_String(normalized)); |
571 | } | 588 | } |
572 | 589 | ||
573 | void setHost_GmDocument(iGmDocument *d, const iString *host) { | 590 | void setUrl_GmDocument(iGmDocument *d, const iString *url) { |
574 | set_String(&d->localHost, host); | 591 | set_String(&d->url, url); |
592 | iUrl parts; | ||
593 | init_Url(&parts, url); | ||
594 | setRange_String(&d->localHost, parts.host); | ||
575 | } | 595 | } |
576 | 596 | ||
577 | void setSource_GmDocument(iGmDocument *d, const iString *source, int width) { | 597 | void setSource_GmDocument(iGmDocument *d, const iString *source, int width) { |
@@ -698,6 +718,12 @@ int linkFlags_GmDocument(const iGmDocument *d, iGmLinkId linkId) { | |||
698 | return link ? link->flags : 0; | 718 | return link ? link->flags : 0; |
699 | } | 719 | } |
700 | 720 | ||
721 | const iTime *linkTime_GmDocument(const iGmDocument *d, iGmLinkId linkId) { | ||
722 | const iGmLink *link = link_GmDocument_(d, linkId); | ||
723 | return link ? &link->when : NULL; | ||
724 | } | ||
725 | |||
726 | |||
701 | uint16_t linkImage_GmDocument(const iGmDocument *d, iGmLinkId linkId) { | 727 | uint16_t linkImage_GmDocument(const iGmDocument *d, iGmLinkId linkId) { |
702 | size_t index = findLinkImage_GmDocument_(d, linkId); | 728 | size_t index = findLinkImage_GmDocument_(d, linkId); |
703 | if (index != iInvalidPos) { | 729 | if (index != iInvalidPos) { |
diff --git a/src/gmdocument.h b/src/gmdocument.h index 4997398b..92b9e1cb 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h | |||
@@ -4,6 +4,7 @@ | |||
4 | #include <the_Foundation/object.h> | 4 | #include <the_Foundation/object.h> |
5 | #include <the_Foundation/rect.h> | 5 | #include <the_Foundation/rect.h> |
6 | #include <the_Foundation/string.h> | 6 | #include <the_Foundation/string.h> |
7 | #include <the_Foundation/time.h> | ||
7 | 8 | ||
8 | #include <SDL_render.h> | 9 | #include <SDL_render.h> |
9 | 10 | ||
@@ -59,7 +60,7 @@ enum iGmDocumentFormat { | |||
59 | 60 | ||
60 | void setFormat_GmDocument (iGmDocument *, enum iGmDocumentFormat format); | 61 | void setFormat_GmDocument (iGmDocument *, enum iGmDocumentFormat format); |
61 | void setWidth_GmDocument (iGmDocument *, int width); | 62 | void setWidth_GmDocument (iGmDocument *, int width); |
62 | void setHost_GmDocument (iGmDocument *, const iString *host); /* local host name */ | 63 | void setUrl_GmDocument (iGmDocument *, const iString *url); |
63 | void setSource_GmDocument (iGmDocument *, const iString *source, int width); | 64 | void setSource_GmDocument (iGmDocument *, const iString *source, int width); |
64 | void setImage_GmDocument (iGmDocument *, iGmLinkId linkId, const iString *mime, const iBlock *data); | 65 | void setImage_GmDocument (iGmDocument *, iGmLinkId linkId, const iString *mime, const iBlock *data); |
65 | 66 | ||
@@ -80,6 +81,7 @@ const iString * linkUrl_GmDocument (const iGmDocument *, iGmLinkId linkId); | |||
80 | uint16_t linkImage_GmDocument (const iGmDocument *, iGmLinkId linkId); | 81 | uint16_t linkImage_GmDocument (const iGmDocument *, iGmLinkId linkId); |
81 | int linkFlags_GmDocument (const iGmDocument *, iGmLinkId linkId); | 82 | int linkFlags_GmDocument (const iGmDocument *, iGmLinkId linkId); |
82 | enum iColorId linkColor_GmDocument (const iGmDocument *, iGmLinkId linkId); | 83 | enum iColorId linkColor_GmDocument (const iGmDocument *, iGmLinkId linkId); |
84 | const iTime * linkTime_GmDocument (const iGmDocument *, iGmLinkId linkId); | ||
83 | iBool isMediaLink_GmDocument (const iGmDocument *, iGmLinkId linkId); | 85 | iBool isMediaLink_GmDocument (const iGmDocument *, iGmLinkId linkId); |
84 | const iString * title_GmDocument (const iGmDocument *); | 86 | const iString * title_GmDocument (const iGmDocument *); |
85 | 87 | ||
diff --git a/src/history.c b/src/history.c index 0bd23149..884e8592 100644 --- a/src/history.c +++ b/src/history.c | |||
@@ -2,9 +2,11 @@ | |||
2 | #include "app.h" | 2 | #include "app.h" |
3 | 3 | ||
4 | #include <the_Foundation/file.h> | 4 | #include <the_Foundation/file.h> |
5 | #include <the_Foundation/path.h> | ||
5 | #include <the_Foundation/sortedarray.h> | 6 | #include <the_Foundation/sortedarray.h> |
6 | 7 | ||
7 | static const size_t maxSize_History_ = 5000; | 8 | static const size_t maxStack_History_ = 50; /* back/forward navigable items */ |
9 | static const size_t maxAgeVisited_History_ = 3600 * 24 * 30; /* one month */ | ||
8 | 10 | ||
9 | void init_HistoryItem(iHistoryItem *d) { | 11 | void init_HistoryItem(iHistoryItem *d) { |
10 | initCurrent_Time(&d->when); | 12 | initCurrent_Time(&d->when); |
@@ -16,104 +18,119 @@ void deinit_HistoryItem(iHistoryItem *d) { | |||
16 | } | 18 | } |
17 | 19 | ||
18 | struct Impl_History { | 20 | struct Impl_History { |
19 | iArray history; | 21 | iArray stack; /* TODO: should be specific to a DocumentWidget */ |
20 | size_t historyPos; /* zero at the latest item */ | 22 | size_t stackPos; /* zero at the latest item */ |
21 | iSortedArray visitedUrls; | 23 | iSortedArray visitedUrls; |
22 | }; | 24 | }; |
23 | 25 | ||
24 | iDefineTypeConstruction(History) | 26 | iDefineTypeConstruction(History) |
25 | 27 | ||
26 | static int cmp_VisitedUrls_(const void *a, const void *b) { | 28 | static int cmpUrl_HistoryItem_(const void *a, const void *b) { |
27 | const iHistoryItem *elem[2] = { a, b }; | 29 | return cmpString_String(&((const iHistoryItem *) a)->url, &((const iHistoryItem *) b)->url); |
28 | return cmpString_String(&elem[0]->url, &elem[1]->url); | ||
29 | } | 30 | } |
30 | 31 | ||
31 | static int cmpWhen_HistoryItem_(const void *old, const void *ins) { | 32 | static int cmpNewer_HistoryItem_(const void *insert, const void *existing) { |
32 | const iHistoryItem *elem[2] = { old, ins }; | 33 | return seconds_Time(&((const iHistoryItem *) insert )->when) > |
33 | const double tOld = seconds_Time(&elem[0]->when), tIns = seconds_Time(&elem[1]->when); | 34 | seconds_Time(&((const iHistoryItem *) existing)->when); |
34 | return tIns > tOld; | ||
35 | } | ||
36 | |||
37 | static void updateVisitedUrls_History_(iHistory *d) { | ||
38 | clear_SortedArray(&d->visitedUrls); | ||
39 | iConstForEach(Array, i, &d->history) { | ||
40 | insertIf_SortedArray(&d->visitedUrls, &i.value, cmpWhen_HistoryItem_); | ||
41 | } | ||
42 | } | 35 | } |
43 | 36 | ||
44 | void init_History(iHistory *d) { | 37 | void init_History(iHistory *d) { |
45 | init_Array(&d->history, sizeof(iHistoryItem)); | 38 | init_Array(&d->stack, sizeof(iHistoryItem)); |
46 | d->historyPos = 0; | 39 | d->stackPos = 0; |
47 | init_SortedArray(&d->visitedUrls, sizeof(const iHistoryItem *), cmp_VisitedUrls_); | 40 | init_SortedArray(&d->visitedUrls, sizeof(iHistoryItem), cmpUrl_HistoryItem_); |
48 | } | 41 | } |
49 | 42 | ||
50 | void deinit_History(iHistory *d) { | 43 | void deinit_History(iHistory *d) { |
51 | deinit_SortedArray(&d->visitedUrls); | ||
52 | clear_History(d); | 44 | clear_History(d); |
53 | deinit_Array(&d->history); | 45 | deinit_Array(&d->stack); |
46 | } | ||
47 | |||
48 | static void writeItems_(const iArray *items, iFile *f) { | ||
49 | iString *line = new_String(); | ||
50 | iConstForEach(Array, i, items) { | ||
51 | const iHistoryItem *item = i.value; | ||
52 | iDate date; | ||
53 | init_Date(&date, &item->when); | ||
54 | format_String(line, | ||
55 | "%04d-%02d-%02dT%02d:%02d:%02d %s\n", | ||
56 | date.year, | ||
57 | date.month, | ||
58 | date.day, | ||
59 | date.hour, | ||
60 | date.minute, | ||
61 | date.second, | ||
62 | cstr_String(&item->url)); | ||
63 | writeData_File(f, cstr_String(line), size_String(line)); | ||
64 | } | ||
65 | delete_String(line); | ||
54 | } | 66 | } |
55 | 67 | ||
56 | void save_History(const iHistory *d, const iString *path) { | 68 | void save_History(const iHistory *d, const char *dirPath) { |
57 | iFile *f = new_File(path); | 69 | iFile *f = new_File(concatPath_CStr(dirPath, "recent.txt")); |
58 | if (open_File(f, writeOnly_FileMode | text_FileMode)) { | 70 | if (open_File(f, writeOnly_FileMode | text_FileMode)) { |
59 | iString *line = new_String(); | 71 | writeItems_(&d->stack, f); |
60 | iConstForEach(Array, i, &d->history) { | 72 | } |
61 | const iHistoryItem *item = i.value; | 73 | iRelease(f); |
62 | iDate date; | 74 | f = new_File(concatPath_CStr(dirPath, "visited.txt")); |
63 | init_Date(&date, &item->when); | 75 | if (open_File(f, writeOnly_FileMode | text_FileMode)) { |
64 | format_String(line, | 76 | writeItems_(&d->visitedUrls.values, f); |
65 | "%04d-%02d-%02dT%02d:%02d:%02d %s\n", | ||
66 | date.year, | ||
67 | date.month, | ||
68 | date.day, | ||
69 | date.hour, | ||
70 | date.minute, | ||
71 | date.second, | ||
72 | cstr_String(&item->url)); | ||
73 | writeData_File(f, cstr_String(line), size_String(line)); | ||
74 | } | ||
75 | delete_String(line); | ||
76 | } | 77 | } |
77 | iRelease(f); | 78 | iRelease(f); |
78 | } | 79 | } |
79 | 80 | ||
80 | void load_History(iHistory *d, const iString *path) { | 81 | static void loadItems_(iArray *items, iFile *f, double maxAge) { |
81 | iFile *f = new_File(path); | 82 | const iRangecc src = range_Block(collect_Block(readAll_File(f))); |
82 | if (open_File(f, readOnly_FileMode | text_FileMode)) { | 83 | iRangecc line = iNullRange; |
83 | iString *src = newBlock_String(collect_Block(readAll_File(f))); | 84 | iTime now; |
84 | const iRangecc range = range_String(src); | 85 | initCurrent_Time(&now); |
85 | iRangecc line = iNullRange; | 86 | while (nextSplit_Rangecc(&src, "\n", &line)) { |
86 | while (nextSplit_Rangecc(&range, "\n", &line)) { | 87 | int y, m, D, H, M, S; |
87 | int y, m, D, H, M, S; | 88 | sscanf(line.start, "%04d-%02d-%02dT%02d:%02d:%02d", &y, &m, &D, &H, &M, &S); |
88 | sscanf(line.start, "%04d-%02d-%02dT%02d:%02d:%02d", &y, &m, &D, &H, &M, &S); | 89 | if (!y) break; |
89 | if (!y) break; | 90 | iHistoryItem item; |
90 | iHistoryItem item; | 91 | init_HistoryItem(&item); |
91 | init_HistoryItem(&item); | 92 | init_Time( |
92 | init_Time( | 93 | &item.when, |
93 | &item.when, | 94 | &(iDate){ .year = y, .month = m, .day = D, .hour = H, .minute = M, .second = S }); |
94 | &(iDate){ .year = y, .month = m, .day = D, .hour = H, .minute = M, .second = S }); | 95 | if (maxAge > 0.0 && secondsSince_Time(&now, &item.when) > maxAge) { |
95 | initRange_String(&item.url, (iRangecc){ line.start + 20, line.end }); | 96 | continue; /* Too old. */ |
96 | pushBack_Array(&d->history, &item); | ||
97 | } | 97 | } |
98 | delete_String(src); | 98 | initRange_String(&item.url, (iRangecc){ line.start + 20, line.end }); |
99 | pushBack_Array(items, &item); | ||
100 | } | ||
101 | } | ||
102 | |||
103 | void load_History(iHistory *d, const char *dirPath) { | ||
104 | iFile *f = new_File(concatPath_CStr(dirPath, "recent.txt")); | ||
105 | if (open_File(f, readOnly_FileMode | text_FileMode)) { | ||
106 | loadItems_(&d->stack, f, 0); | ||
107 | } | ||
108 | iRelease(f); | ||
109 | f = new_File(concatPath_CStr(dirPath, "visited.txt")); | ||
110 | if (open_File(f, readOnly_FileMode | text_FileMode)) { | ||
111 | loadItems_(&d->visitedUrls.values, f, maxAgeVisited_History_); | ||
99 | } | 112 | } |
100 | iRelease(f); | 113 | iRelease(f); |
101 | } | 114 | } |
102 | 115 | ||
103 | void clear_History(iHistory *d) { | 116 | void clear_History(iHistory *d) { |
104 | iForEach(Array, i, &d->history) { | 117 | iForEach(Array, s, &d->stack) { |
105 | deinit_HistoryItem(i.value); | 118 | deinit_HistoryItem(s.value); |
106 | } | 119 | } |
107 | clear_Array(&d->history); | 120 | clear_Array(&d->stack); |
121 | iForEach(Array, v, &d->visitedUrls.values) { | ||
122 | deinit_HistoryItem(v.value); | ||
123 | } | ||
124 | clear_SortedArray(&d->visitedUrls); | ||
108 | } | 125 | } |
109 | 126 | ||
110 | iHistoryItem *itemAtPos_History(iHistory *d, size_t pos) { | 127 | iHistoryItem *itemAtPos_History(iHistory *d, size_t pos) { |
111 | if (isEmpty_Array(&d->history)) return NULL; | 128 | if (isEmpty_Array(&d->stack)) return NULL; |
112 | return &value_Array(&d->history, size_Array(&d->history) - 1 - pos, iHistoryItem); | 129 | return &value_Array(&d->stack, size_Array(&d->stack) - 1 - pos, iHistoryItem); |
113 | } | 130 | } |
114 | 131 | ||
115 | iHistoryItem *item_History(iHistory *d) { | 132 | iHistoryItem *item_History(iHistory *d) { |
116 | return itemAtPos_History(d, d->historyPos); | 133 | return itemAtPos_History(d, d->stackPos); |
117 | } | 134 | } |
118 | 135 | ||
119 | const iString *url_History(iHistory *d, size_t pos) { | 136 | const iString *url_History(iHistory *d, size_t pos) { |
@@ -124,15 +141,39 @@ const iString *url_History(iHistory *d, size_t pos) { | |||
124 | return collectNew_String(); | 141 | return collectNew_String(); |
125 | } | 142 | } |
126 | 143 | ||
144 | static void addVisited_History_(iHistory *d, const iString *url) { | ||
145 | iHistoryItem visit; | ||
146 | init_HistoryItem(&visit); | ||
147 | set_String(&visit.url, url); | ||
148 | size_t pos; | ||
149 | if (locate_SortedArray(&d->visitedUrls, &visit, &pos)) { | ||
150 | iHistoryItem *old = at_SortedArray(&d->visitedUrls, pos); | ||
151 | if (cmpNewer_HistoryItem_(&visit, old)) { | ||
152 | old->when = visit.when; | ||
153 | deinit_HistoryItem(&visit); | ||
154 | return; | ||
155 | } | ||
156 | } | ||
157 | insert_SortedArray(&d->visitedUrls, &visit); | ||
158 | } | ||
159 | |||
160 | void replace_History(iHistory *d, const iString *url) { | ||
161 | /* Update in the history. */ | ||
162 | iHistoryItem *item = item_History(d); | ||
163 | if (item) { | ||
164 | set_String(&item->url, url); | ||
165 | } | ||
166 | addVisited_History_(d, url); | ||
167 | } | ||
168 | |||
127 | void addUrl_History(iHistory *d, const iString *url ){ | 169 | void addUrl_History(iHistory *d, const iString *url ){ |
128 | /* Cut the trailing history items. */ | 170 | /* Cut the trailing history items. */ |
129 | if (d->historyPos > 0) { | 171 | if (d->stackPos > 0) { |
130 | for (size_t i = 0; i < d->historyPos - 1; i++) { | 172 | for (size_t i = 0; i < d->stackPos - 1; i++) { |
131 | deinit_HistoryItem(itemAtPos_History(d, i)); | 173 | deinit_HistoryItem(itemAtPos_History(d, i)); |
132 | } | 174 | } |
133 | removeN_Array( | 175 | removeN_Array(&d->stack, size_Array(&d->stack) - d->stackPos, iInvalidSize); |
134 | &d->history, size_Array(&d->history) - d->historyPos, iInvalidSize); | 176 | d->stackPos = 0; |
135 | d->historyPos = 0; | ||
136 | } | 177 | } |
137 | /* Insert new item. */ | 178 | /* Insert new item. */ |
138 | const iHistoryItem *lastItem = itemAtPos_History(d, 0); | 179 | const iHistoryItem *lastItem = itemAtPos_History(d, 0); |
@@ -140,30 +181,29 @@ void addUrl_History(iHistory *d, const iString *url ){ | |||
140 | iHistoryItem item; | 181 | iHistoryItem item; |
141 | init_HistoryItem(&item); | 182 | init_HistoryItem(&item); |
142 | set_String(&item.url, url); | 183 | set_String(&item.url, url); |
143 | pushBack_Array(&d->history, &item); | 184 | pushBack_Array(&d->stack, &item); |
144 | /* Don't make it too long. */ | 185 | /* Limit the number of items. */ |
145 | if (size_Array(&d->history) > maxSize_History_) { | 186 | if (size_Array(&d->stack) > maxStack_History_) { |
146 | deinit_HistoryItem(front_Array(&d->history)); | 187 | deinit_HistoryItem(front_Array(&d->stack)); |
147 | remove_Array(&d->history, 0); | 188 | remove_Array(&d->stack, 0); |
148 | } | 189 | } |
149 | } | 190 | } |
191 | addVisited_History_(d, url); | ||
150 | } | 192 | } |
151 | 193 | ||
152 | iBool goBack_History(iHistory *d) { | 194 | iBool goBack_History(iHistory *d) { |
153 | if (d->historyPos < size_Array(&d->history) - 1) { | 195 | if (d->stackPos < size_Array(&d->stack) - 1) { |
154 | d->historyPos++; | 196 | d->stackPos++; |
155 | postCommandf_App("open history:1 url:%s", | 197 | postCommandf_App("open history:1 url:%s", cstr_String(url_History(d, d->stackPos))); |
156 | cstr_String(url_History(d, d->historyPos))); | ||
157 | return iTrue; | 198 | return iTrue; |
158 | } | 199 | } |
159 | return iFalse; | 200 | return iFalse; |
160 | } | 201 | } |
161 | 202 | ||
162 | iBool goForward_History(iHistory *d) { | 203 | iBool goForward_History(iHistory *d) { |
163 | if (d->historyPos > 0) { | 204 | if (d->stackPos > 0) { |
164 | d->historyPos--; | 205 | d->stackPos--; |
165 | postCommandf_App("open history:1 url:%s", | 206 | postCommandf_App("open history:1 url:%s", cstr_String(url_History(d, d->stackPos))); |
166 | cstr_String(url_History(d, d->historyPos))); | ||
167 | return iTrue; | 207 | return iTrue; |
168 | } | 208 | } |
169 | return iFalse; | 209 | return iFalse; |
@@ -174,7 +214,7 @@ iTime urlVisitTime_History(const iHistory *d, const iString *url) { | |||
174 | size_t pos; | 214 | size_t pos; |
175 | iZap(item); | 215 | iZap(item); |
176 | initCopy_String(&item.url, url); | 216 | initCopy_String(&item.url, url); |
177 | if (locate_SortedArray(&d->visitedUrls, &(const void *){ &item }, &pos)) { | 217 | if (locate_SortedArray(&d->visitedUrls, &item, &pos)) { |
178 | item.when = ((const iHistoryItem *) constAt_SortedArray(&d->visitedUrls, pos))->when; | 218 | item.when = ((const iHistoryItem *) constAt_SortedArray(&d->visitedUrls, pos))->when; |
179 | } | 219 | } |
180 | deinit_String(&item.url); | 220 | deinit_String(&item.url); |
diff --git a/src/history.h b/src/history.h index 786096f2..4074b1aa 100644 --- a/src/history.h +++ b/src/history.h | |||
@@ -17,8 +17,8 @@ iDeclareTypeConstruction(History) | |||
17 | 17 | ||
18 | void clear_History (iHistory *); | 18 | void clear_History (iHistory *); |
19 | 19 | ||
20 | void load_History (iHistory *, const iString *path); | 20 | void load_History (iHistory *, const char *dirPath); |
21 | void save_History (const iHistory *, const iString *path); | 21 | void save_History (const iHistory *, const char *dirPath); |
22 | 22 | ||
23 | iHistoryItem * itemAtPos_History (iHistory *, size_t pos); | 23 | iHistoryItem * itemAtPos_History (iHistory *, size_t pos); |
24 | iHistoryItem * item_History (iHistory *); | 24 | iHistoryItem * item_History (iHistory *); |
@@ -27,6 +27,7 @@ iTime urlVisitTime_History(const iHistory *, const iString *url); | |||
27 | void print_History (const iHistory *); | 27 | void print_History (const iHistory *); |
28 | 28 | ||
29 | void addUrl_History (iHistory *, const iString *url); | 29 | void addUrl_History (iHistory *, const iString *url); |
30 | void replace_History (iHistory *, const iString *url); | ||
30 | 31 | ||
31 | iBool goBack_History (iHistory *); | 32 | iBool goBack_History (iHistory *); |
32 | iBool goForward_History (iHistory *); | 33 | iBool goForward_History (iHistory *); |
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 07761aa7..9bf40e5c 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -264,9 +264,7 @@ static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) { | |||
264 | } | 264 | } |
265 | 265 | ||
266 | static void setSource_DocumentWidget_(iDocumentWidget *d, const iString *source) { | 266 | static void setSource_DocumentWidget_(iDocumentWidget *d, const iString *source) { |
267 | iUrl parts; | 267 | setUrl_GmDocument(d->doc, d->url); |
268 | init_Url(&parts, d->url); | ||
269 | setHost_GmDocument(d->doc, collect_String(newRange_String(parts.host))); | ||
270 | setSource_GmDocument(d->doc, source, documentWidth_DocumentWidget_(d)); | 268 | setSource_GmDocument(d->doc, source, documentWidth_DocumentWidget_(d)); |
271 | d->foundMark = iNullRange; | 269 | d->foundMark = iNullRange; |
272 | d->selectMark = iNullRange; | 270 | d->selectMark = iNullRange; |
@@ -326,7 +324,7 @@ static void updateSource_DocumentWidget_(iDocumentWidget *d) { | |||
326 | iUrl parts; | 324 | iUrl parts; |
327 | init_Url(&parts, url_GmRequest(d->request)); | 325 | init_Url(&parts, url_GmRequest(d->request)); |
328 | if (!isEmpty_Range(&parts.path)) { | 326 | if (!isEmpty_Range(&parts.path)) { |
329 | imageTitle = baseName_Path(collect_String(newRange_String(parts.path))); | 327 | imageTitle = baseName_Path(collect_String(newRange_String(parts.path))).start; |
330 | } | 328 | } |
331 | format_String( | 329 | format_String( |
332 | &str, "=> %s %s\n", cstr_String(url_GmRequest(d->request)), imageTitle); | 330 | &str, "=> %s %s\n", cstr_String(url_GmRequest(d->request)), imageTitle); |
@@ -851,7 +849,8 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
851 | fillRange_DrawContext_(d, run, teal_ColorId, d->widget->foundMark, &d->inFoundMark); | 849 | fillRange_DrawContext_(d, run, teal_ColorId, d->widget->foundMark, &d->inFoundMark); |
852 | fillRange_DrawContext_(d, run, brown_ColorId, d->widget->selectMark, &d->inSelectMark); | 850 | fillRange_DrawContext_(d, run, brown_ColorId, d->widget->selectMark, &d->inSelectMark); |
853 | if (run->linkId && !isEmpty_Rect(run->bounds)) { | 851 | if (run->linkId && !isEmpty_Rect(run->bounds)) { |
854 | fg = white_ColorId; | 852 | const int flags = linkFlags_GmDocument(doc, run->linkId); |
853 | fg = /*flags & visited_GmLinkFlag ? gray88_ColorId :*/ white_ColorId; | ||
855 | if (isHover || linkFlags_GmDocument(doc, run->linkId) & content_GmLinkFlag) { | 854 | if (isHover || linkFlags_GmDocument(doc, run->linkId) & content_GmLinkFlag) { |
856 | fg = linkColor_GmDocument(doc, run->linkId); | 855 | fg = linkColor_GmDocument(doc, run->linkId); |
857 | } | 856 | } |
@@ -862,6 +861,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
862 | if (run->linkId) { | 861 | if (run->linkId) { |
863 | /* TODO: Show status of an ongoing media request. */ | 862 | /* TODO: Show status of an ongoing media request. */ |
864 | const int flags = linkFlags_GmDocument(doc, run->linkId); | 863 | const int flags = linkFlags_GmDocument(doc, run->linkId); |
864 | const iRect linkRect = moved_Rect(run->visBounds, origin); | ||
865 | if (flags & content_GmLinkFlag) { | 865 | if (flags & content_GmLinkFlag) { |
866 | fg = linkColor_GmDocument(doc, run->linkId); | 866 | fg = linkColor_GmDocument(doc, run->linkId); |
867 | if (!isEmpty_Rect(run->bounds)) { | 867 | if (!isEmpty_Rect(run->bounds)) { |
@@ -894,7 +894,6 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
894 | const iBool showHost = (!isEmpty_String(host) && flags & userFriendly_GmLinkFlag); | 894 | const iBool showHost = (!isEmpty_String(host) && flags & userFriendly_GmLinkFlag); |
895 | const iBool showImage = (flags & imageFileExtension_GmLinkFlag) != 0; | 895 | const iBool showImage = (flags & imageFileExtension_GmLinkFlag) != 0; |
896 | const iBool showAudio = (flags & audioFileExtension_GmLinkFlag) != 0; | 896 | const iBool showAudio = (flags & audioFileExtension_GmLinkFlag) != 0; |
897 | iRect linkRect = moved_Rect(run->visBounds, origin); | ||
898 | if (run->flags & endOfLine_GmRunFlag && | 897 | if (run->flags & endOfLine_GmRunFlag && |
899 | (flags & (imageFileExtension_GmLinkFlag | audioFileExtension_GmLinkFlag) || | 898 | (flags & (imageFileExtension_GmLinkFlag | audioFileExtension_GmLinkFlag) || |
900 | showHost)) { | 899 | showHost)) { |
@@ -925,6 +924,17 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
925 | deinit_String(&str); | 924 | deinit_String(&str); |
926 | } | 925 | } |
927 | } | 926 | } |
927 | else if (run->flags & endOfLine_GmRunFlag && flags & visited_GmLinkFlag) { | ||
928 | iDate date; | ||
929 | init_Date(&date, linkTime_GmDocument(doc, run->linkId)); | ||
930 | draw_Text(default_FontId, | ||
931 | topRight_Rect(linkRect), | ||
932 | linkColor_GmDocument(doc, run->linkId) - 1, | ||
933 | " \u2014 Visited on %04d-%02d-%02d", | ||
934 | date.year, | ||
935 | date.month, | ||
936 | date.day); | ||
937 | } | ||
928 | } | 938 | } |
929 | 939 | ||
930 | // drawRect_Paint(&d->paint, (iRect){ visPos, run->bounds.size }, green_ColorId); | 940 | // drawRect_Paint(&d->paint, (iRect){ visPos, run->bounds.size }, green_ColorId); |