summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-07-30 10:03:24 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-07-30 10:03:24 +0300
commit9eb27c0930017dd60d27d2e7f24561c369f5ac65 (patch)
tree2e9829eb37115e872a95068c97024ced904919fd
parent50f176335ed82c6517e5eeca107ce06b02a2d024 (diff)
History of visited URLs; visualize time of last link visit
-rw-r--r--src/app.c19
-rw-r--r--src/app.h4
-rw-r--r--src/gmdocument.c30
-rw-r--r--src/gmdocument.h4
-rw-r--r--src/history.c208
-rw-r--r--src/history.h5
-rw-r--r--src/ui/documentwidget.c22
7 files changed, 184 insertions, 108 deletions
diff --git a/src/app.c b/src/app.c
index cff7c7d6..f7f0b044 100644
--- a/src/app.c
+++ b/src/app.c
@@ -41,7 +41,6 @@ static const char *dataDir_App_ = "~/AppData/Roaming/fi.skyjake.Lagrange";
41static const char *dataDir_App_ = "~/.config/lagrange"; 41static const char *dataDir_App_ = "~/.config/lagrange";
42#endif 42#endif
43static const char *prefsFileName_App_ = "prefs.cfg"; 43static const char *prefsFileName_App_ = "prefs.cfg";
44static const char *historyFileName_App_ = "history.txt";
45 44
46struct Impl_App { 45struct 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
99static const iString *historyFileName_(void) {
100 return collect_String(concatCStr_Path(&iStringLiteral(dataDir_App_), historyFileName_App_));
101}
102
103static void loadPrefs_App_(iApp *d) { 98static 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
167static void deinit_App(iApp *d) { 162static 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
308const iHistory *history_App(void) {
309 return app_.history;
310}
311
313static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { 312static 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);
diff --git a/src/app.h b/src/app.h
index 43de8b5a..777692b0 100644
--- a/src/app.h
+++ b/src/app.h
@@ -6,6 +6,7 @@
6#include <the_Foundation/time.h> 6#include <the_Foundation/time.h>
7 7
8iDeclareType(Window) 8iDeclareType(Window)
9iDeclareType(History)
9 10
10enum iAppEventMode { 11enum iAppEventMode {
11 waitForNewEvents_AppEventMode, 12 waitForNewEvents_AppEventMode,
@@ -24,7 +25,8 @@ void processEvents_App (enum iAppEventMode mode);
24iBool handleCommand_App (const char *cmd); 25iBool handleCommand_App (const char *cmd);
25void refresh_App (void); 26void refresh_App (void);
26 27
27iTime urlVisitTime_App (const iString *url); 28const iHistory *history_App (void);
29
28iAny * findWidget_App (const char *id); 30iAny * findWidget_App (const char *id);
29void addTicker_App (void (*ticker)(iAny *), iAny *context); 31void addTicker_App (void (*ticker)(iAny *), iAny *context);
30void postRefresh_App (void); 32void 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
17struct Impl_GmLink { 19struct Impl_GmLink {
18 iString url; 20 iString url;
21 iTime when;
19 int flags; 22 int flags;
20}; 23};
21 24
22void init_GmLink(iGmLink *d) { 25void 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
70iDefineTypeConstructionArgs(GmImage, (const iBlock *data), data) 74iDefineTypeConstructionArgs(GmImage, (const iBlock *data), data)
71 75
76/*----------------------------------------------------------------------------------------------*/
77
72struct Impl_GmDocument { 78struct 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) {
474void init_GmDocument(iGmDocument *d) { 487void 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
504void setFormat_GmDocument(iGmDocument *d, enum iGmDocumentFormat format) { 521void 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
573void setHost_GmDocument(iGmDocument *d, const iString *host) { 590void 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
577void setSource_GmDocument(iGmDocument *d, const iString *source, int width) { 597void 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
721const 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
701uint16_t linkImage_GmDocument(const iGmDocument *d, iGmLinkId linkId) { 727uint16_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
60void setFormat_GmDocument (iGmDocument *, enum iGmDocumentFormat format); 61void setFormat_GmDocument (iGmDocument *, enum iGmDocumentFormat format);
61void setWidth_GmDocument (iGmDocument *, int width); 62void setWidth_GmDocument (iGmDocument *, int width);
62void setHost_GmDocument (iGmDocument *, const iString *host); /* local host name */ 63void setUrl_GmDocument (iGmDocument *, const iString *url);
63void setSource_GmDocument (iGmDocument *, const iString *source, int width); 64void setSource_GmDocument (iGmDocument *, const iString *source, int width);
64void setImage_GmDocument (iGmDocument *, iGmLinkId linkId, const iString *mime, const iBlock *data); 65void 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);
80uint16_t linkImage_GmDocument (const iGmDocument *, iGmLinkId linkId); 81uint16_t linkImage_GmDocument (const iGmDocument *, iGmLinkId linkId);
81int linkFlags_GmDocument (const iGmDocument *, iGmLinkId linkId); 82int linkFlags_GmDocument (const iGmDocument *, iGmLinkId linkId);
82enum iColorId linkColor_GmDocument (const iGmDocument *, iGmLinkId linkId); 83enum iColorId linkColor_GmDocument (const iGmDocument *, iGmLinkId linkId);
84const iTime * linkTime_GmDocument (const iGmDocument *, iGmLinkId linkId);
83iBool isMediaLink_GmDocument (const iGmDocument *, iGmLinkId linkId); 85iBool isMediaLink_GmDocument (const iGmDocument *, iGmLinkId linkId);
84const iString * title_GmDocument (const iGmDocument *); 86const 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
7static const size_t maxSize_History_ = 5000; 8static const size_t maxStack_History_ = 50; /* back/forward navigable items */
9static const size_t maxAgeVisited_History_ = 3600 * 24 * 30; /* one month */
8 10
9void init_HistoryItem(iHistoryItem *d) { 11void 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
18struct Impl_History { 20struct 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
24iDefineTypeConstruction(History) 26iDefineTypeConstruction(History)
25 27
26static int cmp_VisitedUrls_(const void *a, const void *b) { 28static 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
31static int cmpWhen_HistoryItem_(const void *old, const void *ins) { 32static 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
37static 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
44void init_History(iHistory *d) { 37void 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
50void deinit_History(iHistory *d) { 43void 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
48static 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
56void save_History(const iHistory *d, const iString *path) { 68void 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
80void load_History(iHistory *d, const iString *path) { 81static 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
103void 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
103void clear_History(iHistory *d) { 116void 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
110iHistoryItem *itemAtPos_History(iHistory *d, size_t pos) { 127iHistoryItem *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
115iHistoryItem *item_History(iHistory *d) { 132iHistoryItem *item_History(iHistory *d) {
116 return itemAtPos_History(d, d->historyPos); 133 return itemAtPos_History(d, d->stackPos);
117} 134}
118 135
119const iString *url_History(iHistory *d, size_t pos) { 136const 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
144static 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
160void 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
127void addUrl_History(iHistory *d, const iString *url ){ 169void 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
152iBool goBack_History(iHistory *d) { 194iBool 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
162iBool goForward_History(iHistory *d) { 203iBool 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
18void clear_History (iHistory *); 18void clear_History (iHistory *);
19 19
20void load_History (iHistory *, const iString *path); 20void load_History (iHistory *, const char *dirPath);
21void save_History (const iHistory *, const iString *path); 21void save_History (const iHistory *, const char *dirPath);
22 22
23iHistoryItem * itemAtPos_History (iHistory *, size_t pos); 23iHistoryItem * itemAtPos_History (iHistory *, size_t pos);
24iHistoryItem * item_History (iHistory *); 24iHistoryItem * item_History (iHistory *);
@@ -27,6 +27,7 @@ iTime urlVisitTime_History(const iHistory *, const iString *url);
27void print_History (const iHistory *); 27void print_History (const iHistory *);
28 28
29void addUrl_History (iHistory *, const iString *url); 29void addUrl_History (iHistory *, const iString *url);
30void replace_History (iHistory *, const iString *url);
30 31
31iBool goBack_History (iHistory *); 32iBool goBack_History (iHistory *);
32iBool goForward_History (iHistory *); 33iBool 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
266static void setSource_DocumentWidget_(iDocumentWidget *d, const iString *source) { 266static 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);