summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2020-08-12 10:28:02 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2020-08-12 10:28:02 +0300
commit1d54f7b990ea7f676403681577fc4df984cab0be (patch)
tree94a8ac83159f021dbbb354a6d76ecbaf83a63aad /src
parent38e09f15ff3e9c4781236016bfbb0b0f9062590b (diff)
Save and load app state (tabs, history)
Diffstat (limited to 'src')
-rw-r--r--src/app.c66
-rw-r--r--src/app.h2
-rw-r--r--src/gmrequest.c18
-rw-r--r--src/gmrequest.h1
-rw-r--r--src/history.c66
-rw-r--r--src/history.h4
-rw-r--r--src/ui/documentwidget.c222
-rw-r--r--src/ui/documentwidget.h6
-rw-r--r--src/ui/widget.h5
-rw-r--r--src/ui/window.c2
10 files changed, 270 insertions, 122 deletions
diff --git a/src/app.c b/src/app.c
index 6f076c97..7885d154 100644
--- a/src/app.c
+++ b/src/app.c
@@ -48,6 +48,7 @@ static const char *dataDir_App_ = "~/AppData/Roaming/fi.skyjake.Lagrange";
48static const char *dataDir_App_ = "~/.config/lagrange"; 48static const char *dataDir_App_ = "~/.config/lagrange";
49#endif 49#endif
50static const char *prefsFileName_App_ = "prefs.cfg"; 50static const char *prefsFileName_App_ = "prefs.cfg";
51static const char *stateFileName_App_ = "state.bin";
51 52
52struct Impl_App { 53struct Impl_App {
53 iCommandLine args; 54 iCommandLine args;
@@ -143,6 +144,67 @@ static void savePrefs_App_(const iApp *d) {
143 delete_String(cfg); 144 delete_String(cfg);
144} 145}
145 146
147static const char *magicState_App_ = "lgL1";
148static const char *magicTabDocument_App_ = "tabd";
149
150static iBool loadState_App_(iApp *d) {
151 iFile *f = iClob(newCStr_File(concatPath_CStr(dataDir_App_, stateFileName_App_)));
152 if (open_File(f, readOnly_FileMode)) {
153 char magic[4];
154 readData_File(f, 4, magic);
155 if (memcmp(magic, magicState_App_, 4)) {
156 printf("%s: format not recognized\n", cstr_String(path_File(f)));
157 return iFalse;
158 }
159 const int version = read32_File(f);
160 /* Check supported versions. */
161 if (version != 0) {
162 printf("%s: unsupported version\n", cstr_String(path_File(f)));
163 return iFalse;
164 }
165 setVersion_Stream(stream_File(f), version);
166 iDocumentWidget *doc = document_App();
167 iDocumentWidget *current = NULL;
168 while (!atEnd_File(f)) {
169 readData_File(f, 4, magic);
170 if (!memcmp(magic, magicTabDocument_App_, 4)) {
171 if (!doc) {
172 doc = newTab_App(NULL);
173 }
174 if (read8_File(f)) {
175 current = doc;
176 }
177 deserializeState_DocumentWidget(doc, stream_File(f));
178 doc = NULL;
179 }
180 else {
181 printf("%s: unrecognized data\n", cstr_String(path_File(f)));
182 return iFalse;
183 }
184 }
185 postCommandf_App("tabs.switch page:%p", current);
186 return iTrue;
187 }
188 return iFalse;
189}
190
191static void saveState_App_(const iApp *d) {
192 iFile *f = newCStr_File(concatPath_CStr(dataDir_App_, stateFileName_App_));
193 if (open_File(f, writeOnly_FileMode)) {
194 writeData_File(f, magicState_App_, 4);
195 write32_File(f, 0); /* version */
196 iWidget *tabs = findChild_Widget(d->window->root, "doctabs");
197 iConstForEach(ObjectList, i, children_Widget(findChild_Widget(tabs, "tabs.pages"))) {
198 if (isInstance_Object(i.object, &Class_DocumentWidget)) {
199 writeData_File(f, magicTabDocument_App_, 4);
200 write8_File(f, document_App() == i.object ? 1 : 0);
201 serializeState_DocumentWidget(i.object, stream_File(f));
202 }
203 }
204 }
205 iRelease(f);
206}
207
146static void init_App_(iApp *d, int argc, char **argv) { 208static void init_App_(iApp *d, int argc, char **argv) {
147 init_CommandLine(&d->args, argc, argv); 209 init_CommandLine(&d->args, argc, argv);
148 init_SortedArray(&d->tickers, sizeof(iTicker), cmp_Ticker_); 210 init_SortedArray(&d->tickers, sizeof(iTicker), cmp_Ticker_);
@@ -166,12 +228,14 @@ static void init_App_(iApp *d, int argc, char **argv) {
166 } 228 }
167#endif 229#endif
168 d->window = new_Window(); 230 d->window = new_Window();
169 /* Widget state init. */ { 231 /* Widget state init. */
232 if (!loadState_App_(d)) {
170 postCommand_App("navigate.home"); 233 postCommand_App("navigate.home");
171 } 234 }
172} 235}
173 236
174static void deinit_App(iApp *d) { 237static void deinit_App(iApp *d) {
238 saveState_App_(d);
175 savePrefs_App_(d); 239 savePrefs_App_(d);
176 save_Visited(d->visited, dataDir_App_); 240 save_Visited(d->visited, dataDir_App_);
177 delete_Visited(d->visited); 241 delete_Visited(d->visited);
diff --git a/src/app.h b/src/app.h
index e40c5b2e..a3e815a2 100644
--- a/src/app.h
+++ b/src/app.h
@@ -32,7 +32,7 @@ iGmCerts * certs_App (void);
32iVisited * visited_App (void); 32iVisited * visited_App (void);
33iDocumentWidget * document_App (void); 33iDocumentWidget * document_App (void);
34iDocumentWidget * document_Command (const char *cmd); 34iDocumentWidget * document_Command (const char *cmd);
35 35iDocumentWidget * newTab_App (const iDocumentWidget *duplicateOf);
36 36
37iAny * findWidget_App (const char *id); 37iAny * findWidget_App (const char *id);
38void addTicker_App (void (*ticker)(iAny *), iAny *context); 38void addTicker_App (void (*ticker)(iAny *), iAny *context);
diff --git a/src/gmrequest.c b/src/gmrequest.c
index e1cc76d8..ce7a3522 100644
--- a/src/gmrequest.c
+++ b/src/gmrequest.c
@@ -51,6 +51,24 @@ iGmResponse *copy_GmResponse(const iGmResponse *d) {
51 return copied; 51 return copied;
52} 52}
53 53
54void serialize_GmResponse(const iGmResponse *d, iStream *outs) {
55 write32_Stream(outs, d->statusCode);
56 serialize_String(&d->meta, outs);
57 serialize_Block(&d->body, outs);
58 write32_Stream(outs, d->certFlags);
59 serialize_Date(&d->certValidUntil, outs);
60 serialize_String(&d->certSubject, outs);
61}
62
63void deserialize_GmResponse(iGmResponse *d, iStream *ins) {
64 d->statusCode = read32_Stream(ins);
65 deserialize_String(&d->meta, ins);
66 deserialize_Block(&d->body, ins);
67 d->certFlags = read32_Stream(ins);
68 deserialize_Date(&d->certValidUntil, ins);
69 deserialize_String(&d->certSubject, ins);
70}
71
54/*----------------------------------------------------------------------------------------------*/ 72/*----------------------------------------------------------------------------------------------*/
55 73
56static const int bodyTimeout_GmRequest_ = 3000; /* ms */ 74static const int bodyTimeout_GmRequest_ = 3000; /* ms */
diff --git a/src/gmrequest.h b/src/gmrequest.h
index 05290128..cb7c151a 100644
--- a/src/gmrequest.h
+++ b/src/gmrequest.h
@@ -25,6 +25,7 @@ struct Impl_GmResponse {
25}; 25};
26 26
27iDeclareTypeConstruction(GmResponse) 27iDeclareTypeConstruction(GmResponse)
28iDeclareTypeSerialization(GmResponse)
28 29
29iGmResponse * copy_GmResponse (const iGmResponse *); 30iGmResponse * copy_GmResponse (const iGmResponse *);
30 31
diff --git a/src/history.c b/src/history.c
index 5be24608..9779795a 100644
--- a/src/history.c
+++ b/src/history.c
@@ -55,39 +55,38 @@ iHistory *copy_History(const iHistory *d) {
55 return copy; 55 return copy;
56} 56}
57 57
58void save_History(const iHistory *d, const char *dirPath) { 58void serialize_History(const iHistory *d, iStream *outs) {
59 iString *line = new_String(); 59 writeU16_Stream(outs, d->recentPos);
60 iFile *f = newCStr_File(concatPath_CStr(dirPath, "recent.txt")); 60 writeU16_Stream(outs, size_Array(&d->recent));
61 if (open_File(f, writeOnly_FileMode | text_FileMode)) { 61 iConstForEach(Array, i, &d->recent) {
62 iConstForEach(Array, i, &d->recent) { 62 const iRecentUrl *item = i.value;
63 const iRecentUrl *item = i.value; 63 serialize_String(&item->url, outs);
64 format_String(line, "%04x %s\n", item->scrollY, cstr_String(&item->url)); 64 write32_Stream(outs, item->scrollY);
65 writeData_File(f, cstr_String(line), size_String(line)); 65 if (item->cachedResponse) {
66 write8_Stream(outs, 1);
67 serialize_GmResponse(item->cachedResponse, outs);
68 }
69 else {
70 write8_Stream(outs, 0);
66 } 71 }
67 } 72 }
68 iRelease(f); 73}
69 delete_String(line); 74
70} 75void deserialize_History(iHistory *d, iStream *ins) {
71 76 clear_History(d);
72void load_History(iHistory *d, const char *dirPath) { 77 d->recentPos = readU16_Stream(ins);
73 iFile *f = newCStr_File(concatPath_CStr(dirPath, "recent.txt")); 78 size_t count = readU16_Stream(ins);
74 if (open_File(f, readOnly_FileMode | text_FileMode)) { 79 while (count--) {
75 const iRangecc src = range_Block(collect_Block(readAll_File(f))); 80 iRecentUrl item;
76 iRangecc line = iNullRange; 81 init_RecentUrl(&item);
77 while (nextSplit_Rangecc(&src, "\n", &line)) { 82 deserialize_String(&item.url, ins);
78 iRangecc nonwhite = line; 83 item.scrollY = read32_Stream(ins);
79 trim_Rangecc(&nonwhite); 84 if (read8_Stream(ins)) {
80 if (isEmpty_Range(&nonwhite)) continue; 85 item.cachedResponse = new_GmResponse();
81 int scroll = 0; 86 deserialize_GmResponse(item.cachedResponse, ins);
82 sscanf(nonwhite.start, "%04x", &scroll);
83 iRecentUrl item;
84 init_RecentUrl(&item);
85 item.scrollY = scroll;
86 initRange_String(&item.url, (iRangecc){ nonwhite.start + 5, nonwhite.end });
87 pushBack_Array(&d->recent, &item);
88 } 87 }
88 pushBack_Array(&d->recent, &item);
89 } 89 }
90 iRelease(f);
91} 90}
92 91
93void clear_History(iHistory *d) { 92void clear_History(iHistory *d) {
@@ -123,6 +122,15 @@ const iString *url_History(const iHistory *d, size_t pos) {
123 return collectNew_String(); 122 return collectNew_String();
124} 123}
125 124
125iRecentUrl *findUrl_History(iHistory *d, const iString *url) {
126 iReverseForEach(Array, i, &d->recent) {
127 if (cmpStringCase_String(url, &((iRecentUrl *) i.value)->url) == 0) {
128 return i.value;
129 }
130 }
131 return NULL;
132}
133
126void replace_History(iHistory *d, const iString *url) { 134void replace_History(iHistory *d, const iString *url) {
127 /* Update in the history. */ 135 /* Update in the history. */
128 iRecentUrl *item = mostRecentUrl_History(d); 136 iRecentUrl *item = mostRecentUrl_History(d);
diff --git a/src/history.h b/src/history.h
index 9deae8ea..6ccfd199 100644
--- a/src/history.h
+++ b/src/history.h
@@ -19,11 +19,11 @@ struct Impl_RecentUrl {
19 19
20iDeclareType(History) 20iDeclareType(History)
21iDeclareTypeConstruction(History) 21iDeclareTypeConstruction(History)
22iDeclareTypeSerialization(History)
22 23
23iHistory * copy_History (const iHistory *); 24iHistory * copy_History (const iHistory *);
24 25
25void clear_History (iHistory *); 26void clear_History (iHistory *);
26void load_History (iHistory *, const char *dirPath);
27void add_History (iHistory *, const iString *url); 27void add_History (iHistory *, const iString *url);
28void replace_History (iHistory *, const iString *url); 28void replace_History (iHistory *, const iString *url);
29void setCachedResponse_History (iHistory *, const iGmResponse *response); 29void setCachedResponse_History (iHistory *, const iGmResponse *response);
@@ -31,8 +31,8 @@ iBool goBack_History (iHistory *);
31iBool goForward_History (iHistory *); 31iBool goForward_History (iHistory *);
32iRecentUrl *recentUrl_History (iHistory *, size_t pos); 32iRecentUrl *recentUrl_History (iHistory *, size_t pos);
33iRecentUrl *mostRecentUrl_History (iHistory *); 33iRecentUrl *mostRecentUrl_History (iHistory *);
34iRecentUrl *findUrl_History (iHistory *, const iString *url);
34 35
35void save_History (const iHistory *, const char *dirPath);
36const iString * 36const iString *
37 url_History (const iHistory *, size_t pos); 37 url_History (const iHistory *, size_t pos);
38const iRecentUrl * 38const iRecentUrl *
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 0ba1e6b0..57709384 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -21,22 +21,14 @@
21#include <SDL_clipboard.h> 21#include <SDL_clipboard.h>
22#include <SDL_timer.h> 22#include <SDL_timer.h>
23 23
24enum iDocumentState {
25 blank_DocumentState,
26 fetching_DocumentState,
27 receivedPartialResponse_DocumentState,
28 layout_DocumentState,
29 ready_DocumentState,
30};
31
32iDeclareClass(MediaRequest) 24iDeclareClass(MediaRequest)
33 25
34struct Impl_MediaRequest { 26struct Impl_MediaRequest {
35 iObject object; 27 iObject object;
36 iDocumentWidget *doc; 28 iDocumentWidget *doc;
37 iGmLinkId linkId; 29 iGmLinkId linkId;
38 iGmRequest *req; 30 iGmRequest * req;
39 iAtomicInt isUpdated; 31 iAtomicInt isUpdated;
40}; 32};
41 33
42static void updated_MediaRequest_(iAnyObject *obj) { 34static void updated_MediaRequest_(iAnyObject *obj) {
@@ -74,30 +66,75 @@ iDefineObjectConstructionArgs(MediaRequest,
74 doc, linkId, url) 66 doc, linkId, url)
75iDefineClass(MediaRequest) 67iDefineClass(MediaRequest)
76 68
69/*----------------------------------------------------------------------------------------------*/
70
71iDeclareType(Model)
72iDeclareTypeConstruction(Model)
73iDeclareTypeSerialization(Model)
74
75struct Impl_Model {
76 /* state that persists across sessions */
77 iHistory *history;
78 iString * url;
79 int textSizePercent;
80};
81
82void init_Model(iModel *d) {
83 d->history = new_History();
84 d->url = new_String();
85 d->textSizePercent = 100;
86}
87
88void deinit_Model(iModel *d) {
89 delete_String(d->url);
90 delete_History(d->history);
91}
92
93void serialize_Model(const iModel *d, iStream *outs) {
94 serialize_String(d->url, outs);
95 write16_Stream(outs, d->textSizePercent);
96 serialize_History(d->history, outs);
97}
98
99void deserialize_Model(iModel *d, iStream *ins) {
100 deserialize_String(d->url, ins);
101 d->textSizePercent = read16_Stream(ins);
102 deserialize_History(d->history, ins);
103}
104
105iDefineTypeConstruction(Model)
106
107/*----------------------------------------------------------------------------------------------*/
108
109enum iRequestState {
110 blank_RequestState,
111 fetching_RequestState,
112 receivedPartialResponse_RequestState,
113 ready_RequestState,
114};
115
77struct Impl_DocumentWidget { 116struct Impl_DocumentWidget {
78 iWidget widget; 117 iWidget widget;
79 iHistory *history; 118 enum iRequestState state;
80 enum iDocumentState state; 119 iModel mod;
81 iString *url;
82 iString *titleUser; 120 iString *titleUser;
83 iGmRequest *request; 121 iGmRequest *request;
84 iAtomicInt isRequestUpdated; /* request has new content, need to parse it */ 122 iAtomicInt isRequestUpdated; /* request has new content, need to parse it */
85 iObjectList *media; 123 iObjectList *media;
86 int textSizePercent;
87 iGmDocument *doc; 124 iGmDocument *doc;
88 int certFlags; 125 int certFlags;
89 iDate certExpiry; 126 iDate certExpiry;
90 iString *certSubject; 127 iString * certSubject;
91 iBool selecting; 128 iBool selecting;
92 iRangecc selectMark; 129 iRangecc selectMark;
93 iRangecc foundMark; 130 iRangecc foundMark;
94 int pageMargin; 131 int pageMargin;
95 int scrollY;
96 iPtrArray visibleLinks; 132 iPtrArray visibleLinks;
97 const iGmRun *hoverLink; 133 const iGmRun *hoverLink;
98 iBool noHoverWhileScrolling; 134 iBool noHoverWhileScrolling;
99 iClick click; 135 iClick click;
100 int initialScrollY; 136 int initialScrollY;
137 int scrollY;
101 iScrollWidget *scroll; 138 iScrollWidget *scroll;
102 iWidget *menu; 139 iWidget *menu;
103 SDL_Cursor *arrowCursor; /* TODO: cursors belong in Window */ 140 SDL_Cursor *arrowCursor; /* TODO: cursors belong in Window */
@@ -112,23 +149,21 @@ void init_DocumentWidget(iDocumentWidget *d) {
112 init_Widget(w); 149 init_Widget(w);
113 setId_Widget(w, "document000"); 150 setId_Widget(w, "document000");
114 setFlags_Widget(w, hover_WidgetFlag, iTrue); 151 setFlags_Widget(w, hover_WidgetFlag, iTrue);
152 init_Model(&d->mod);
115 iZap(d->certExpiry); 153 iZap(d->certExpiry);
116 d->history = new_History(); 154 d->certFlags = 0;
117 d->state = blank_DocumentState; 155 d->certSubject = new_String();
118 d->url = new_String(); 156 d->state = blank_RequestState;
119 d->titleUser = new_String(); 157 d->titleUser = new_String();
120 d->request = NULL; 158 d->request = NULL;
121 d->isRequestUpdated = iFalse; 159 d->isRequestUpdated = iFalse;
122 d->media = new_ObjectList(); 160 d->media = new_ObjectList();
123 d->textSizePercent = 100;
124 d->doc = new_GmDocument(); 161 d->doc = new_GmDocument();
125 d->certFlags = 0; 162 d->scrollY = 0;
126 d->certSubject = new_String();
127 d->selecting = iFalse; 163 d->selecting = iFalse;
128 d->selectMark = iNullRange; 164 d->selectMark = iNullRange;
129 d->foundMark = iNullRange; 165 d->foundMark = iNullRange;
130 d->pageMargin = 5; 166 d->pageMargin = 5;
131 d->scrollY = 0;
132 d->hoverLink = NULL; 167 d->hoverLink = NULL;
133 d->noHoverWhileScrolling = iFalse; 168 d->noHoverWhileScrolling = iFalse;
134 d->arrowCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); 169 d->arrowCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW);
@@ -156,20 +191,19 @@ void deinit_DocumentWidget(iDocumentWidget *d) {
156 iRelease(d->request); 191 iRelease(d->request);
157 iRelease(d->doc); 192 iRelease(d->doc);
158 deinit_PtrArray(&d->visibleLinks); 193 deinit_PtrArray(&d->visibleLinks);
159 delete_String(d->url);
160 delete_String(d->certSubject); 194 delete_String(d->certSubject);
161 delete_String(d->titleUser); 195 delete_String(d->titleUser);
162 SDL_FreeCursor(d->arrowCursor); 196 SDL_FreeCursor(d->arrowCursor);
163 SDL_FreeCursor(d->beamCursor); 197 SDL_FreeCursor(d->beamCursor);
164 SDL_FreeCursor(d->handCursor); 198 SDL_FreeCursor(d->handCursor);
165 delete_History(d->history); 199 deinit_Model(&d->mod);
166} 200}
167 201
168static int documentWidth_DocumentWidget_(const iDocumentWidget *d) { 202static int documentWidth_DocumentWidget_(const iDocumentWidget *d) {
169 const iWidget *w = constAs_Widget(d); 203 const iWidget *w = constAs_Widget(d);
170 const iRect bounds = bounds_Widget(w); 204 const iRect bounds = bounds_Widget(w);
171 return iMini(bounds.size.x - gap_UI * d->pageMargin * 2, 205 return iMini(bounds.size.x - gap_UI * d->pageMargin * 2,
172 fontSize_UI * 38 * d->textSizePercent / 100); /* TODO: Add user preference .*/ 206 fontSize_UI * 38 * d->mod.textSizePercent / 100); /* TODO: Add user preference .*/
173} 207}
174 208
175static iRect documentBounds_DocumentWidget_(const iDocumentWidget *d) { 209static iRect documentBounds_DocumentWidget_(const iDocumentWidget *d) {
@@ -240,7 +274,7 @@ static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) {
240 d->hoverLink = NULL; 274 d->hoverLink = NULL;
241 const iInt2 hoverPos = addY_I2(sub_I2(mouse, topLeft_Rect(docBounds)), d->scrollY); 275 const iInt2 hoverPos = addY_I2(sub_I2(mouse, topLeft_Rect(docBounds)), d->scrollY);
242 if (!d->noHoverWhileScrolling && 276 if (!d->noHoverWhileScrolling &&
243 (d->state == ready_DocumentState || d->state == receivedPartialResponse_DocumentState)) { 277 (d->state == ready_RequestState || d->state == receivedPartialResponse_RequestState)) {
244 iConstForEach(PtrArray, i, &d->visibleLinks) { 278 iConstForEach(PtrArray, i, &d->visibleLinks) {
245 const iGmRun *run = i.ptr; 279 const iGmRun *run = i.ptr;
246 if (contains_Rect(run->bounds, hoverPos)) { 280 if (contains_Rect(run->bounds, hoverPos)) {
@@ -273,7 +307,7 @@ static void updateVisible_DocumentWidget_(iDocumentWidget *d) {
273 render_GmDocument(d->doc, visRange, addVisibleLink_DocumentWidget_, d); 307 render_GmDocument(d->doc, visRange, addVisibleLink_DocumentWidget_, d);
274 updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window())); 308 updateHover_DocumentWidget_(d, mouseCoord_Window(get_Window()));
275 /* Remember scroll positions of recently visited pages. */ { 309 /* Remember scroll positions of recently visited pages. */ {
276 iRecentUrl *recent = mostRecentUrl_History(d->history); 310 iRecentUrl *recent = mostRecentUrl_History(d->mod.history);
277 if (recent) { 311 if (recent) {
278 recent->scrollY = d->scrollY / gap_UI; 312 recent->scrollY = d->scrollY / gap_UI;
279 } 313 }
@@ -295,7 +329,7 @@ static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) {
295 } 329 }
296 else { 330 else {
297 iUrl parts; 331 iUrl parts;
298 init_Url(&parts, d->url); 332 init_Url(&parts, d->mod.url);
299 if (!isEmpty_Range(&parts.host)) { 333 if (!isEmpty_Range(&parts.host)) {
300 pushBackRange_StringArray(title, parts.host); 334 pushBackRange_StringArray(title, parts.host);
301 } 335 }
@@ -344,7 +378,7 @@ static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) {
344} 378}
345 379
346static void setSource_DocumentWidget_(iDocumentWidget *d, const iString *source) { 380static void setSource_DocumentWidget_(iDocumentWidget *d, const iString *source) {
347 setUrl_GmDocument(d->doc, d->url); 381 setUrl_GmDocument(d->doc, d->mod.url);
348 setSource_GmDocument(d->doc, source, documentWidth_DocumentWidget_(d)); 382 setSource_GmDocument(d->doc, source, documentWidth_DocumentWidget_(d));
349 d->foundMark = iNullRange; 383 d->foundMark = iNullRange;
350 d->selectMark = iNullRange; 384 d->selectMark = iNullRange;
@@ -376,13 +410,13 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode
376 } 410 }
377 setSource_DocumentWidget_(d, src); 411 setSource_DocumentWidget_(d, src);
378 d->scrollY = 0; 412 d->scrollY = 0;
379 d->state = ready_DocumentState; 413 d->state = ready_RequestState;
380} 414}
381 415
382static void updateTheme_DocumentWidget_(iDocumentWidget *d) { 416static void updateTheme_DocumentWidget_(iDocumentWidget *d) {
383 if (isEmpty_String(d->titleUser)) { 417 if (isEmpty_String(d->titleUser)) {
384 setThemeSeed_GmDocument(d->doc, 418 setThemeSeed_GmDocument(d->doc,
385 collect_Block(newRange_Block(urlHost_String(d->url)))); 419 collect_Block(newRange_Block(urlHost_String(d->mod.url))));
386 } 420 }
387 else { 421 else {
388 setThemeSeed_GmDocument(d->doc, &d->titleUser->chars); 422 setThemeSeed_GmDocument(d->doc, &d->titleUser->chars);
@@ -390,7 +424,7 @@ static void updateTheme_DocumentWidget_(iDocumentWidget *d) {
390} 424}
391 425
392static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse *response) { 426static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse *response) {
393 if (d->state == ready_DocumentState) { 427 if (d->state == ready_RequestState) {
394 return; 428 return;
395 } 429 }
396 /* TODO: Do this in the background. However, that requires a text metrics calculator 430 /* TODO: Do this in the background. However, that requires a text metrics calculator
@@ -422,13 +456,13 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
422 /* Make a simple document with an image. */ 456 /* Make a simple document with an image. */
423 const char *imageTitle = "Image"; 457 const char *imageTitle = "Image";
424 iUrl parts; 458 iUrl parts;
425 init_Url(&parts, d->url); 459 init_Url(&parts, d->mod.url);
426 if (!isEmpty_Range(&parts.path)) { 460 if (!isEmpty_Range(&parts.path)) {
427 imageTitle = 461 imageTitle =
428 baseName_Path(collect_String(newRange_String(parts.path))).start; 462 baseName_Path(collect_String(newRange_String(parts.path))).start;
429 } 463 }
430 format_String( 464 format_String(
431 &str, "=> %s %s\n", cstr_String(d->url), imageTitle); 465 &str, "=> %s %s\n", cstr_String(d->mod.url), imageTitle);
432 setImage_GmDocument(d->doc, 1, mimeStr, &response->body); 466 setImage_GmDocument(d->doc, 1, mimeStr, &response->body);
433 } 467 }
434 else { 468 else {
@@ -467,13 +501,13 @@ static void fetch_DocumentWidget_(iDocumentWidget *d) {
467 iRelease(d->request); 501 iRelease(d->request);
468 d->request = NULL; 502 d->request = NULL;
469 } 503 }
470 postCommandf_App("document.request.started doc:%p url:%s", d, cstr_String(d->url)); 504 postCommandf_App("document.request.started doc:%p url:%s", d, cstr_String(d->mod.url));
471 clear_ObjectList(d->media); 505 clear_ObjectList(d->media);
472 d->certFlags = 0; 506 d->certFlags = 0;
473 d->state = fetching_DocumentState; 507 d->state = fetching_RequestState;
474 set_Atomic(&d->isRequestUpdated, iFalse); 508 set_Atomic(&d->isRequestUpdated, iFalse);
475 d->request = new_GmRequest(certs_App()); 509 d->request = new_GmRequest(certs_App());
476 setUrl_GmRequest(d->request, d->url); 510 setUrl_GmRequest(d->request, d->mod.url);
477 iConnect(GmRequest, d->request, updated, d, requestUpdated_DocumentWidget_); 511 iConnect(GmRequest, d->request, updated, d, requestUpdated_DocumentWidget_);
478 iConnect(GmRequest, d->request, timeout, d, requestTimedOut_DocumentWidget_); 512 iConnect(GmRequest, d->request, timeout, d, requestTimedOut_DocumentWidget_);
479 iConnect(GmRequest, d->request, finished, d, requestFinished_DocumentWidget_); 513 iConnect(GmRequest, d->request, finished, d, requestFinished_DocumentWidget_);
@@ -507,44 +541,58 @@ static void updateTrust_DocumentWidget_(iDocumentWidget *d, const iGmResponse *r
507} 541}
508 542
509iHistory *history_DocumentWidget(iDocumentWidget *d) { 543iHistory *history_DocumentWidget(iDocumentWidget *d) {
510 return d->history; 544 return d->mod.history;
511} 545}
512 546
513const iString *url_DocumentWidget(const iDocumentWidget *d) { 547const iString *url_DocumentWidget(const iDocumentWidget *d) {
514 return d->url; 548 return d->mod.url;
515} 549}
516 550
517const iGmDocument *document_DocumentWidget(const iDocumentWidget *d) { 551const iGmDocument *document_DocumentWidget(const iDocumentWidget *d) {
518 return d->doc; 552 return d->doc;
519} 553}
520 554
555static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) {
556 const iRecentUrl *recent = findUrl_History(d->mod.history, d->mod.url);
557 if (recent && recent->cachedResponse) {
558 const iGmResponse *resp = recent->cachedResponse;
559 d->state = fetching_RequestState;
560 /* Use the cached response data. */
561 d->scrollY = recent->scrollY;
562 updateTrust_DocumentWidget_(d, resp);
563 updateDocument_DocumentWidget_(d, resp);
564 d->state = ready_RequestState;
565 postCommandf_App("document.changed doc:%p url:%s", d, cstr_String(d->mod.url));
566 return iTrue;
567 }
568 return iFalse;
569}
570
571void serializeState_DocumentWidget(const iDocumentWidget *d, iStream *outs) {
572 serialize_Model(&d->mod, outs);
573}
574
575void deserializeState_DocumentWidget(iDocumentWidget *d, iStream *ins) {
576 deserialize_Model(&d->mod, ins);
577 updateFromHistory_DocumentWidget_(d);
578}
579
521void setUrlFromCache_DocumentWidget(iDocumentWidget *d, const iString *url, iBool isFromCache) { 580void setUrlFromCache_DocumentWidget(iDocumentWidget *d, const iString *url, iBool isFromCache) {
522 if (cmpStringSc_String(d->url, url, &iCaseInsensitive)) { 581 if (cmpStringSc_String(d->mod.url, url, &iCaseInsensitive)) {
523 set_String(d->url, url); 582 set_String(d->mod.url, url);
524 /* See if there a username in the URL. */ { 583 /* See if there a username in the URL. */ {
525 clear_String(d->titleUser); 584 clear_String(d->titleUser);
526 iRegExp *userPats[2] = { new_RegExp("~([^/?]+)", 0), 585 iRegExp *userPats[2] = { new_RegExp("~([^/?]+)", 0),
527 new_RegExp("/users/([^/?]+)", caseInsensitive_RegExpOption) }; 586 new_RegExp("/users/([^/?]+)", caseInsensitive_RegExpOption) };
528 iRegExpMatch m; 587 iRegExpMatch m;
529 iForIndices(i, userPats) { 588 iForIndices(i, userPats) {
530 if (matchString_RegExp(userPats[i], d->url, &m)) { 589 if (matchString_RegExp(userPats[i], d->mod.url, &m)) {
531 setRange_String(d->titleUser, capturedRange_RegExpMatch(&m, 1)); 590 setRange_String(d->titleUser, capturedRange_RegExpMatch(&m, 1));
532 } 591 }
533 iRelease(userPats[i]); 592 iRelease(userPats[i]);
534 } 593 }
535 } 594 }
536 const iRecentUrl *recent = mostRecentUrl_History(d->history); 595 if (!isFromCache || !updateFromHistory_DocumentWidget_(d)) {
537 if (isFromCache && recent && recent->cachedResponse) {
538 const iGmResponse *resp = recent->cachedResponse;
539 d->state = fetching_DocumentState;
540 /* Use the cached response data. */
541 d->scrollY = d->initialScrollY;
542 updateTrust_DocumentWidget_(d, resp);
543 updateDocument_DocumentWidget_(d, resp);
544 d->state = ready_DocumentState;
545 postCommandf_App("document.changed url:%s", cstr_String(d->url));
546 }
547 else {
548 fetch_DocumentWidget_(d); 596 fetch_DocumentWidget_(d);
549 } 597 }
550 } 598 }
@@ -552,11 +600,11 @@ void setUrlFromCache_DocumentWidget(iDocumentWidget *d, const iString *url, iBoo
552 600
553iDocumentWidget *duplicate_DocumentWidget(const iDocumentWidget *orig) { 601iDocumentWidget *duplicate_DocumentWidget(const iDocumentWidget *orig) {
554 iDocumentWidget *d = new_DocumentWidget(); 602 iDocumentWidget *d = new_DocumentWidget();
555 delete_History(d->history); 603 delete_History(d->mod.history);
556 d->textSizePercent = orig->textSizePercent; 604 d->mod.textSizePercent = orig->mod.textSizePercent;
557 d->initialScrollY = orig->scrollY; 605 d->initialScrollY = orig->scrollY;
558 d->history = copy_History(orig->history); 606 d->mod.history = copy_History(orig->mod.history);
559 setUrlFromCache_DocumentWidget(d, orig->url, iTrue); 607 setUrlFromCache_DocumentWidget(d, orig->mod.url, iTrue);
560 return d; 608 return d;
561} 609}
562 610
@@ -569,7 +617,7 @@ void setInitialScroll_DocumentWidget (iDocumentWidget *d, int scrollY) {
569} 617}
570 618
571iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) { 619iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) {
572 return d->state == fetching_DocumentState || d->state == receivedPartialResponse_DocumentState; 620 return d->state == fetching_RequestState || d->state == receivedPartialResponse_RequestState;
573} 621}
574 622
575static void scroll_DocumentWidget_(iDocumentWidget *d, int offset) { 623static void scroll_DocumentWidget_(iDocumentWidget *d, int offset) {
@@ -602,13 +650,13 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
602 if (statusCode == none_GmStatusCode) { 650 if (statusCode == none_GmStatusCode) {
603 return; 651 return;
604 } 652 }
605 if (d->state == fetching_DocumentState) { 653 if (d->state == fetching_RequestState) {
606 d->state = receivedPartialResponse_DocumentState; 654 d->state = receivedPartialResponse_RequestState;
607 updateTrust_DocumentWidget_(d, response_GmRequest(d->request)); 655 updateTrust_DocumentWidget_(d, response_GmRequest(d->request));
608 switch (category_GmStatusCode(statusCode)) { 656 switch (category_GmStatusCode(statusCode)) {
609 case categoryInput_GmStatusCode: { 657 case categoryInput_GmStatusCode: {
610 iUrl parts; 658 iUrl parts;
611 init_Url(&parts, d->url); 659 init_Url(&parts, d->mod.url);
612 printf("%s\n", cstr_String(meta_GmRequest(d->request))); 660 printf("%s\n", cstr_String(meta_GmRequest(d->request)));
613 iWidget *dlg = makeValueInput_Widget( 661 iWidget *dlg = makeValueInput_Widget(
614 as_Widget(d), 662 as_Widget(d),
@@ -636,7 +684,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
636 /* TODO: only accept redirects that use gemini protocol */ 684 /* TODO: only accept redirects that use gemini protocol */
637 postCommandf_App( 685 postCommandf_App(
638 "open redirect:1 url:%s", 686 "open redirect:1 url:%s",
639 cstr_String(absoluteUrl_String(d->url, meta_GmRequest(d->request)))); 687 cstr_String(absoluteUrl_String(d->mod.url, meta_GmRequest(d->request))));
640 iReleasePtr(&d->request); 688 iReleasePtr(&d->request);
641 } 689 }
642 break; 690 break;
@@ -655,7 +703,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) {
655 break; 703 break;
656 } 704 }
657 } 705 }
658 else if (d->state == receivedPartialResponse_DocumentState) { 706 else if (d->state == receivedPartialResponse_RequestState) {
659 switch (category_GmStatusCode(statusCode)) { 707 switch (category_GmStatusCode(statusCode)) {
660 case categorySuccess_GmStatusCode: 708 case categorySuccess_GmStatusCode:
661 /* More content available. */ 709 /* More content available. */
@@ -722,7 +770,7 @@ static iBool requestMedia_DocumentWidget_(iDocumentWidget *d, iGmLinkId linkId)
722 pushBack_ObjectList( 770 pushBack_ObjectList(
723 d->media, 771 d->media,
724 iClob(new_MediaRequest( 772 iClob(new_MediaRequest(
725 d, linkId, absoluteUrl_String(d->url, linkUrl_GmDocument(d->doc, linkId))))); 773 d, linkId, absoluteUrl_String(d->mod.url, linkUrl_GmDocument(d->doc, linkId)))));
726 return iTrue; 774 return iTrue;
727 } 775 }
728 return iFalse; 776 return iFalse;
@@ -764,16 +812,16 @@ static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char *
764 812
765static void changeTextSize_DocumentWidget_(iDocumentWidget *d, int delta) { 813static void changeTextSize_DocumentWidget_(iDocumentWidget *d, int delta) {
766 if (delta == 0) { 814 if (delta == 0) {
767 d->textSizePercent = 100; 815 d->mod.textSizePercent = 100;
768 } 816 }
769 else { 817 else {
770 if (d->textSizePercent < 100 || (delta < 0 && d->textSizePercent == 100)) { 818 if (d->mod.textSizePercent < 100 || (delta < 0 && d->mod.textSizePercent == 100)) {
771 delta /= 2; 819 delta /= 2;
772 } 820 }
773 d->textSizePercent += delta; 821 d->mod.textSizePercent += delta;
774 d->textSizePercent = iClamp(d->textSizePercent, 50, 200); 822 d->mod.textSizePercent = iClamp(d->mod.textSizePercent, 50, 200);
775 } 823 }
776 postCommandf_App("font.setfactor arg:%d", d->textSizePercent); 824 postCommandf_App("font.setfactor arg:%d", d->mod.textSizePercent);
777} 825}
778 826
779static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) { 827static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) {
@@ -848,17 +896,17 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
848 else if (equalWidget_Command(cmd, w, "document.copylink")) { 896 else if (equalWidget_Command(cmd, w, "document.copylink")) {
849 if (d->hoverLink) { 897 if (d->hoverLink) {
850 SDL_SetClipboardText(cstr_String( 898 SDL_SetClipboardText(cstr_String(
851 absoluteUrl_String(d->url, linkUrl_GmDocument(d->doc, d->hoverLink->linkId)))); 899 absoluteUrl_String(d->mod.url, linkUrl_GmDocument(d->doc, d->hoverLink->linkId))));
852 } 900 }
853 else { 901 else {
854 SDL_SetClipboardText(cstr_String(d->url)); 902 SDL_SetClipboardText(cstr_String(d->mod.url));
855 } 903 }
856 return iTrue; 904 return iTrue;
857 } 905 }
858 else if (equal_Command(cmd, "document.input.submit")) { 906 else if (equal_Command(cmd, "document.input.submit")) {
859 iString *value = collect_String(suffix_Command(cmd, "value")); 907 iString *value = collect_String(suffix_Command(cmd, "value"));
860 urlEncode_String(value); 908 urlEncode_String(value);
861 iString *url = collect_String(copy_String(d->url)); 909 iString *url = collect_String(copy_String(d->mod.url));
862 const size_t qPos = indexOfCStr_String(url, "?"); 910 const size_t qPos = indexOfCStr_String(url, "?");
863 if (qPos != iInvalidPos) { 911 if (qPos != iInvalidPos) {
864 remove_Block(&url->chars, qPos, iInvalidSize); 912 remove_Block(&url->chars, qPos, iInvalidSize);
@@ -881,10 +929,10 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
881 else if (equalWidget_Command(cmd, w, "document.request.finished") && 929 else if (equalWidget_Command(cmd, w, "document.request.finished") &&
882 pointerLabel_Command(cmd, "request") == d->request) { 930 pointerLabel_Command(cmd, "request") == d->request) {
883 checkResponse_DocumentWidget_(d); 931 checkResponse_DocumentWidget_(d);
884 d->state = ready_DocumentState; 932 d->state = ready_RequestState;
885 setCachedResponse_History(d->history, response_GmRequest(d->request)); 933 setCachedResponse_History(d->mod.history, response_GmRequest(d->request));
886 iReleasePtr(&d->request); 934 iReleasePtr(&d->request);
887 postCommandf_App("document.changed url:%s", cstr_String(d->url)); 935 postCommandf_App("document.changed url:%s", cstr_String(d->mod.url));
888 return iFalse; 936 return iFalse;
889 } 937 }
890 else if (equal_Command(cmd, "document.request.timeout") && 938 else if (equal_Command(cmd, "document.request.timeout") &&
@@ -898,9 +946,9 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
898 } 946 }
899 else if (equal_Command(cmd, "document.stop")) { 947 else if (equal_Command(cmd, "document.stop")) {
900 if (d->request) { 948 if (d->request) {
901 postCommandf_App("document.request.cancelled doc:%p url:%s", d, cstr_String(d->url)); 949 postCommandf_App("document.request.cancelled doc:%p url:%s", d, cstr_String(d->mod.url));
902 iReleasePtr(&d->request); 950 iReleasePtr(&d->request);
903 d->state = ready_DocumentState; 951 d->state = ready_RequestState;
904 return iTrue; 952 return iTrue;
905 } 953 }
906 } 954 }
@@ -912,11 +960,11 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
912 return iTrue; 960 return iTrue;
913 } 961 }
914 else if (equal_Command(cmd, "navigate.back") && document_App() == d) { 962 else if (equal_Command(cmd, "navigate.back") && document_App() == d) {
915 goBack_History(d->history); 963 goBack_History(d->mod.history);
916 return iTrue; 964 return iTrue;
917 } 965 }
918 else if (equal_Command(cmd, "navigate.forward") && document_App() == d) { 966 else if (equal_Command(cmd, "navigate.forward") && document_App() == d) {
919 goForward_History(d->history); 967 goForward_History(d->mod.history);
920 return iTrue; 968 return iTrue;
921 } 969 }
922 else if (equalWidget_Command(cmd, w, "scroll.moved")) { 970 else if (equalWidget_Command(cmd, w, "scroll.moved")) {
@@ -1135,7 +1183,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
1135 postCommandf_App("open newtab:%d url:%s", 1183 postCommandf_App("open newtab:%d url:%s",
1136 (SDL_GetModState() & KMOD_PRIMARY) != 0, 1184 (SDL_GetModState() & KMOD_PRIMARY) != 0,
1137 cstr_String(absoluteUrl_String( 1185 cstr_String(absoluteUrl_String(
1138 d->url, linkUrl_GmDocument(d->doc, linkId)))); 1186 d->mod.url, linkUrl_GmDocument(d->doc, linkId))));
1139 } 1187 }
1140 } 1188 }
1141 if (d->selectMark.start) { 1189 if (d->selectMark.start) {
diff --git a/src/ui/documentwidget.h b/src/ui/documentwidget.h
index 1e63034b..810c1392 100644
--- a/src/ui/documentwidget.h
+++ b/src/ui/documentwidget.h
@@ -1,6 +1,7 @@
1#pragma once 1#pragma once
2 2
3#include "widget.h" 3#include "widget.h"
4#include <the_Foundation/stream.h>
4 5
5iDeclareType(GmDocument) 6iDeclareType(GmDocument)
6iDeclareType(History) 7iDeclareType(History)
@@ -8,6 +9,9 @@ iDeclareType(History)
8iDeclareWidgetClass(DocumentWidget) 9iDeclareWidgetClass(DocumentWidget)
9iDeclareObjectConstruction(DocumentWidget) 10iDeclareObjectConstruction(DocumentWidget)
10 11
12void serializeState_DocumentWidget (const iDocumentWidget *, iStream *outs);
13void deserializeState_DocumentWidget (iDocumentWidget *, iStream *ins);
14
11iDocumentWidget * duplicate_DocumentWidget (const iDocumentWidget *); 15iDocumentWidget * duplicate_DocumentWidget (const iDocumentWidget *);
12iHistory * history_DocumentWidget (iDocumentWidget *); 16iHistory * history_DocumentWidget (iDocumentWidget *);
13 17
@@ -16,5 +20,5 @@ iBool isRequestOngoing_DocumentWidget (const iDocumentWidget *);
16const iGmDocument * document_DocumentWidget (const iDocumentWidget *); 20const iGmDocument * document_DocumentWidget (const iDocumentWidget *);
17 21
18void setUrl_DocumentWidget (iDocumentWidget *, const iString *url); 22void setUrl_DocumentWidget (iDocumentWidget *, const iString *url);
19void setUrlFromCache_DocumentWidget (iDocumentWidget *d, const iString *url, iBool isFromCache); 23void setUrlFromCache_DocumentWidget (iDocumentWidget *, const iString *url, iBool isFromCache);
20void setInitialScroll_DocumentWidget (iDocumentWidget *, int scrollY); /* set after content received */ 24void setInitialScroll_DocumentWidget (iDocumentWidget *, int scrollY); /* set after content received */
diff --git a/src/ui/widget.h b/src/ui/widget.h
index bf6489a0..c82e37f8 100644
--- a/src/ui/widget.h
+++ b/src/ui/widget.h
@@ -114,6 +114,11 @@ iAny * findFocusable_Widget(const iWidget *startFrom, enum iWidgetFocusDir focu
114size_t childCount_Widget (const iWidget *); 114size_t childCount_Widget (const iWidget *);
115void draw_Widget (const iWidget *); 115void draw_Widget (const iWidget *);
116 116
117iLocalDef iObjectList *children_Widget(iAnyObject *d) {
118 iAssert(isInstance_Object(d, &Class_Widget));
119 return ((iWidget *) d)->children;
120}
121
117iBool isVisible_Widget (const iWidget *); 122iBool isVisible_Widget (const iWidget *);
118iBool isDisabled_Widget (const iWidget *); 123iBool isDisabled_Widget (const iWidget *);
119iBool isFocused_Widget (const iWidget *); 124iBool isFocused_Widget (const iWidget *);
diff --git a/src/ui/window.c b/src/ui/window.c
index c68331a0..3a6b6e30 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -104,7 +104,7 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
104 setFlags_Widget( 104 setFlags_Widget(
105 child, tight_WidgetFlag, isNarrow || !cmp_String(id_Widget(child), "lock")); 105 child, tight_WidgetFlag, isNarrow || !cmp_String(id_Widget(child), "lock"));
106 if (isInstance_Object(i.object, &Class_LabelWidget)) { 106 if (isInstance_Object(i.object, &Class_LabelWidget)) {
107 iLabelWidget *label = (iLabelWidget *) i.object; 107 iLabelWidget *label = i.object;
108 updateSize_LabelWidget(label); 108 updateSize_LabelWidget(label);
109 } 109 }
110 } 110 }