summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/documentwidget.c295
-rw-r--r--src/ui/window.c4
2 files changed, 177 insertions, 122 deletions
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 71399d3b..121ca7cf 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -6,16 +6,20 @@
6#include "app.h" 6#include "app.h"
7#include "../gemini.h" 7#include "../gemini.h"
8#include "../gmdocument.h" 8#include "../gmdocument.h"
9#include "../gmrequest.h"
10#include "../gmutil.h"
9 11
10#include <the_Foundation/file.h> 12#include <the_Foundation/file.h>
11#include <the_Foundation/path.h> 13#include <the_Foundation/path.h>
12#include <the_Foundation/ptrarray.h> 14#include <the_Foundation/ptrarray.h>
13#include <the_Foundation/regexp.h> 15#include <the_Foundation/regexp.h>
14#include <the_Foundation/tlsrequest.h> 16
17#include <SDL_timer.h>
15 18
16enum iDocumentState { 19enum iDocumentState {
17 blank_DocumentState, 20 blank_DocumentState,
18 fetching_DocumentState, 21 fetching_DocumentState,
22 receivedPartialResponse_DocumentState,
19 layout_DocumentState, 23 layout_DocumentState,
20 ready_DocumentState, 24 ready_DocumentState,
21}; 25};
@@ -24,9 +28,8 @@ struct Impl_DocumentWidget {
24 iWidget widget; 28 iWidget widget;
25 enum iDocumentState state; 29 enum iDocumentState state;
26 iString *url; 30 iString *url;
27 iTlsRequest *request; 31 iGmRequest *request;
28 int statusCode; 32 iAtomicInt isSourcePending; /* request has new content, need to parse it */
29 iString *newSource;
30 iGmDocument *doc; 33 iGmDocument *doc;
31 int pageMargin; 34 int pageMargin;
32 int scrollY; 35 int scrollY;
@@ -36,37 +39,6 @@ struct Impl_DocumentWidget {
36 iScrollWidget *scroll; 39 iScrollWidget *scroll;
37}; 40};
38 41
39iDeclareType(Url)
40
41struct Impl_Url {
42 iRangecc protocol;
43 iRangecc host;
44 iRangecc port;
45 iRangecc path;
46 iRangecc query;
47};
48
49void init_Url(iUrl *d, const iString *text) {
50 iRegExp *pattern =
51 new_RegExp("(.+)://([^/:?]*)(:[0-9]+)?([^?]*)(\\?.*)?", caseInsensitive_RegExpOption);
52 iRegExpMatch m;
53 if (matchString_RegExp(pattern, text, &m)) {
54 d->protocol = capturedRange_RegExpMatch(&m, 1);
55 d->host = capturedRange_RegExpMatch(&m, 2);
56 d->port = capturedRange_RegExpMatch(&m, 3);
57 if (!isEmpty_Range(&d->port)) {
58 /* Don't include the colon. */
59 d->port.start++;
60 }
61 d->path = capturedRange_RegExpMatch(&m, 4);
62 d->query = capturedRange_RegExpMatch(&m, 5);
63 }
64 else {
65 iZap(*d);
66 }
67 iRelease(pattern);
68}
69
70iDefineObjectConstruction(DocumentWidget) 42iDefineObjectConstruction(DocumentWidget)
71 43
72void init_DocumentWidget(iDocumentWidget *d) { 44void init_DocumentWidget(iDocumentWidget *d) {
@@ -75,9 +47,13 @@ void init_DocumentWidget(iDocumentWidget *d) {
75 setId_Widget(w, "document"); 47 setId_Widget(w, "document");
76 d->state = blank_DocumentState; 48 d->state = blank_DocumentState;
77 d->url = new_String(); 49 d->url = new_String();
78 d->statusCode = 0; 50// d->statusCode = 0;
79 d->request = NULL; 51 d->request = NULL;
80 d->newSource = new_String(); 52 d->isSourcePending = iFalse;
53// d->requestTimeout = 0;
54// d->readPending = iFalse;
55// d->newSource = new_String();
56// d->needSourceUpdate = iFalse;
81 d->doc = new_GmDocument(); 57 d->doc = new_GmDocument();
82 d->pageMargin = 5; 58 d->pageMargin = 5;
83 d->scrollY = 0; 59 d->scrollY = 0;
@@ -90,7 +66,8 @@ void init_DocumentWidget(iDocumentWidget *d) {
90void deinit_DocumentWidget(iDocumentWidget *d) { 66void deinit_DocumentWidget(iDocumentWidget *d) {
91 deinit_PtrArray(&d->visibleLinks); 67 deinit_PtrArray(&d->visibleLinks);
92 delete_String(d->url); 68 delete_String(d->url);
93 delete_String(d->newSource); 69// delete_String(d->newSource);
70 iRelease(d->request);
94 iRelease(d->doc); 71 iRelease(d->doc);
95} 72}
96 73
@@ -111,90 +88,63 @@ static iRect documentBounds_DocumentWidget_(const iDocumentWidget *d) {
111 return rect; 88 return rect;
112} 89}
113 90
114#if 0
115void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) {
116 /* TODO: lock source during update */
117 setSource_GmDocument(d->doc, source, documentWidth_DocumentWidget_(d));
118 d->state = ready_DocumentState;
119}
120#endif
121
122static iRangecc getLine_(iRangecc text) { 91static iRangecc getLine_(iRangecc text) {
123 iRangecc line = { text.start, text.start }; 92 iRangecc line = { text.start, text.start };
124 for (; *line.end != '\n' && line.end != text.end; line.end++) {} 93 for (; *line.end != '\n' && line.end != text.end; line.end++) {}
125 return line; 94 return line;
126} 95}
127 96
128static void requestFinished_DocumentWidget_(iAnyObject *obj) { 97static void requestUpdated_DocumentWidget_(iAnyObject *obj) {
98#if 0
129 iDocumentWidget *d = obj; 99 iDocumentWidget *d = obj;
130 iBlock *response = readAll_TlsRequest(d->request); 100 iBlock *response = readAll_TlsRequest(d->request);
131 iRangecc responseRange = { constBegin_Block(response), constEnd_Block(response) }; 101 if (d->state == fetching_DocumentState) {
132 iRangecc respLine = getLine_(responseRange); 102 iRangecc responseRange = { constBegin_Block(response), constEnd_Block(response) };
133 responseRange.start = respLine.end + 1; 103 iRangecc respLine = getLine_(responseRange);
134 /* First line is the status code. */ { 104 responseRange.start = respLine.end + 1;
135 iString *line = newRange_String(respLine); 105 /* First line is the status code. */ {
136 trim_String(line); 106 iString *line = newRange_String(respLine);
137 d->statusCode = toInt_String(line); 107 trim_String(line);
138 printf("response (%02d): %s\n", d->statusCode, cstr_String(line)); 108 d->statusCode = toInt_String(line);
139 /* TODO: post a command with the status code */ 109 printf("response (%02d): %s\n", d->statusCode, cstr_String(line));
140 switch (d->statusCode) { 110 /* TODO: post a command with the status code */
141 case redirectPermanent_GmStatusCode: 111 switch (d->statusCode) {
142 case redirectTemporary_GmStatusCode: 112 case redirectPermanent_GmStatusCode:
143 postCommandf_App("open url:%s", cstr_String(line) + 3); 113 case redirectTemporary_GmStatusCode:
144 break; 114 postCommandf_App("open url:%s", cstr_String(line) + 3);
115 break;
116 }
117 delete_String(line);
145 } 118 }
146 delete_String(line); 119 setCStrN_String(d->newSource, responseRange.start, size_Range(&responseRange));
120 d->requestTimeout = SDL_AddTimer(2000, requestTimedOut_DocumentWidget_, d);
121 d->state = receivedPartialResponse_DocumentState;
147 } 122 }
148 setCStrN_String(d->newSource, responseRange.start, size_Range(&responseRange)); 123 else if (d->state == receivedPartialResponse_DocumentState) {
149 delete_Block(response); 124 appendCStr_String(d->newSource, cstr_Block(response));
150 iReleaseLater(d->request); 125 d->needSourceUpdate = iTrue;
151 d->request = NULL;
152 fflush(stdout);
153 refresh_Widget(constAs_Widget(d));
154}
155
156static void fetch_DocumentWidget_(iDocumentWidget *d) {
157 iAssert(!d->request);
158 d->state = fetching_DocumentState;
159 d->statusCode = 0;
160 iUrl url;
161 init_Url(&url, d->url);
162 if (!cmpCStrSc_Rangecc(&url.protocol, "file", &iCaseInsensitive)) {
163 iFile *f = new_File(collect_String(newRange_String(url.path)));
164 if (open_File(f, readOnly_FileMode)) {
165 setBlock_String(d->newSource, collect_Block(readAll_File(f)));
166 refresh_Widget(constAs_Widget(d));
167 }
168 iRelease(f);
169 return;
170 } 126 }
171 d->request = new_TlsRequest(); 127 delete_Block(response);
172 uint16_t port = toInt_String(collect_String(newRange_String(url.port))); 128 refresh_Widget(as_Widget(d));
173 if (port == 0) { 129#endif
174 port = 1965; /* default Gemini port */ 130 iDocumentWidget *d = obj;
131 const int wasPending = exchange_Atomic(&d->isSourcePending, iTrue);
132 if (!wasPending) {
133 postCommand_Widget(obj, "document.request.updated request:%p", d->request);
175 } 134 }
176 setUrl_TlsRequest(d->request, collect_String(newRange_String(url.host)), port);
177 /* The request string is an UTF-8 encoded absolute URL. */
178 iString *content = collectNew_String();
179 append_String(content, d->url);
180 appendCStr_String(content, "\r\n");
181 setContent_TlsRequest(d->request, utf8_String(content));
182 iConnect(TlsRequest, d->request, finished, d, requestFinished_DocumentWidget_);
183 submit_TlsRequest(d->request);
184} 135}
185 136
186void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) { 137static void requestFinished_DocumentWidget_(iAnyObject *obj) {
187 iString *newUrl = new_String(); 138 iDocumentWidget *d = obj;
188 if (indexOfCStr_String(url, "://") == iInvalidPos && !startsWithCase_String(url, "gemini:")) { 139 /*
189 /* Prepend default protocol. */ 140 iReleaseLater(d->request);
190 setCStr_String(newUrl, "gemini://"); 141 d->request = NULL;
191 } 142 if (d->requestTimeout) {
192 append_String(newUrl, url); 143 SDL_RemoveTimer(d->requestTimeout);
193 if (cmpStringSc_String(d->url, newUrl, &iCaseInsensitive)) { 144 d->requestTimeout = 0;
194 set_String(d->url, newUrl);
195 fetch_DocumentWidget_(d);
196 } 145 }
197 delete_String(newUrl); 146 refresh_Widget(constAs_Widget(d));*/
147 postCommand_Widget(obj, "document.request.finished request:%p", d->request);
198} 148}
199 149
200static iRangei visibleRange_DocumentWidget_(const iDocumentWidget *d) { 150static iRangei visibleRange_DocumentWidget_(const iDocumentWidget *d) {
@@ -227,6 +177,54 @@ static void updateVisible_DocumentWidget_(iDocumentWidget *d) {
227 render_GmDocument(d->doc, visRange, addVisibleLink_DocumentWidget_, d); 177 render_GmDocument(d->doc, visRange, addVisibleLink_DocumentWidget_, d);
228} 178}
229 179
180static void updateSource_DocumentWidget_(iDocumentWidget *d) {
181 /* Update the document? */
182 // if (d->needSourceUpdate) {
183 /* TODO: Do this in the background. However, that requires a text metrics calculator
184 that does not try to cache the glyph bitmaps. */
185 iString str;
186 initBlock_String(&str, body_GmRequest(d->request));
187 setSource_GmDocument(d->doc, &str, documentWidth_DocumentWidget_(d));
188 deinit_String(&str);
189 updateVisible_DocumentWidget_(d);
190 refresh_Widget(as_Widget(d));
191 // d->state = ready_DocumentState;
192 // if (!d->request) {
193 // d->needSourceUpdate = iFalse;
194 // postCommandf_App("document.changed url:%s", cstr_String(d->url));
195 // }
196 // }
197}
198
199static void fetch_DocumentWidget_(iDocumentWidget *d) {
200 /* Forget the previous request. */
201 if (d->request) {
202 iRelease(d->request);
203 d->request = NULL;
204 }
205 d->state = fetching_DocumentState;
206 set_Atomic(&d->isSourcePending, iFalse);
207 d->request = new_GmRequest();
208 setUrl_GmRequest(d->request, d->url);
209 iConnect(GmRequest, d->request, updated, d, requestUpdated_DocumentWidget_);
210 iConnect(GmRequest, d->request, finished, d, requestFinished_DocumentWidget_);
211 submit_GmRequest(d->request);
212}
213
214void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) {
215 iString *newUrl = new_String();
216 if (indexOfCStr_String(url, "://") == iInvalidPos && !startsWithCase_String(url, "gemini:")) {
217 /* Prepend default protocol. */
218 setCStr_String(newUrl, "gemini://");
219 }
220 append_String(newUrl, url);
221 if (cmpStringSc_String(d->url, newUrl, &iCaseInsensitive)) {
222 set_String(d->url, newUrl);
223 fetch_DocumentWidget_(d);
224 }
225 delete_String(newUrl);
226}
227
230static void scroll_DocumentWidget_(iDocumentWidget *d, int offset) { 228static void scroll_DocumentWidget_(iDocumentWidget *d, int offset) {
231 d->scrollY += offset; 229 d->scrollY += offset;
232 if (d->scrollY < 0) { 230 if (d->scrollY < 0) {
@@ -296,6 +294,61 @@ static const iString *absoluteUrl_DocumentWidget_(const iDocumentWidget *d, cons
296 return collect_String(absolute); 294 return collect_String(absolute);
297} 295}
298 296
297static void readResponse_DocumentWidget_(iDocumentWidget *d) {
298#if 0
299 d->readPending = iFalse;
300 iBlock *response = collect_Block(readAll_TlsRequest(d->request));
301 if (isEmpty_Block(response)) {
302 return;
303 }
304 if (d->state == fetching_DocumentState) {
305 /* TODO: Bug here is that the first read may occur before the first line is
306 available, so nothing gets done. Should ensure that the status code is
307 read successully. */
308 iRangecc responseRange = { constBegin_Block(response), constEnd_Block(response) };
309 iRangecc respLine = getLine_(responseRange);
310 responseRange.start = respLine.end + 1;
311 /* First line is the status code. */ {
312 iString *line = collect_String(newRange_String(respLine));
313 trim_String(line);
314 d->statusCode = toInt_String(line);
315 printf("response (%02d): %s\n", d->statusCode, cstr_String(line));
316 /* TODO: post a command with the status code */
317 switch (d->statusCode) {
318 case redirectPermanent_GmStatusCode:
319 case redirectTemporary_GmStatusCode:
320 postCommandf_App("open url:%s", cstr_String(line) + 3);
321 return;
322 }
323 }
324 setCStrN_String(d->newSource, responseRange.start, size_Range(&responseRange));
325 d->requestTimeout = SDL_AddTimer(2000, requestTimedOut_DocumentWidget_, d);
326 d->state = receivedPartialResponse_DocumentState;
327 d->scrollY = 0;
328 }
329 else if (d->state == receivedPartialResponse_DocumentState) {
330 appendCStr_String(d->newSource, cstr_Block(response));
331 }
332#endif
333 updateSource_DocumentWidget_(d);
334}
335
336static void checkResponseCode_DocumentWidget_(iDocumentWidget *d) {
337 if (d->state == fetching_DocumentState) {
338 d->state = receivedPartialResponse_DocumentState;
339 d->scrollY = 0;
340 switch (status_GmRequest(d->request)) {
341 case redirectTemporary_GmStatusCode:
342 case redirectPermanent_GmStatusCode:
343 postCommandf_App("open url:%s", cstr_String(meta_GmRequest(d->request)));
344 iReleasePtr(&d->request);
345 break;
346 default:
347 break;
348 }
349 }
350}
351
299static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *ev) { 352static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *ev) {
300 iWidget *w = as_Widget(d); 353 iWidget *w = as_Widget(d);
301 if (isResize_UserEvent(ev)) { 354 if (isResize_UserEvent(ev)) {
@@ -303,6 +356,20 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
303 updateVisible_DocumentWidget_(d); 356 updateVisible_DocumentWidget_(d);
304 refresh_Widget(w); 357 refresh_Widget(w);
305 } 358 }
359 else if (isCommand_Widget(w, ev, "document.request.updated") &&
360 pointerLabel_Command(command_UserEvent(ev), "request") == d->request) {
361 updateSource_DocumentWidget_(d);
362 checkResponseCode_DocumentWidget_(d);
363 return iTrue;
364 }
365 else if (isCommand_Widget(w, ev, "document.request.finished") &&
366 pointerLabel_Command(command_UserEvent(ev), "request") == d->request) {
367 updateSource_DocumentWidget_(d);
368 checkResponseCode_DocumentWidget_(d);
369 d->state = ready_DocumentState;
370 iReleasePtr(&d->request);
371 return iTrue;
372 }
306 else if (isCommand_Widget(w, ev, "scroll.moved")) { 373 else if (isCommand_Widget(w, ev, "scroll.moved")) {
307 d->scrollY = arg_Command(command_UserEvent(ev)); 374 d->scrollY = arg_Command(command_UserEvent(ev));
308 updateVisible_DocumentWidget_(d); 375 updateVisible_DocumentWidget_(d);
@@ -417,19 +484,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
417 iDrawContext ctx = { .widget = d, .bounds = documentBounds_DocumentWidget_(d) }; 484 iDrawContext ctx = { .widget = d, .bounds = documentBounds_DocumentWidget_(d) };
418 init_Paint(&ctx.paint); 485 init_Paint(&ctx.paint);
419 fillRect_Paint(&ctx.paint, bounds, gray25_ColorId); 486 fillRect_Paint(&ctx.paint, bounds, gray25_ColorId);
420 /* Update the document? */ 487// if (d->state != ready_DocumentState) return;
421 if (!isEmpty_String(d->newSource)) {
422 iDocumentWidget *m = iConstCast(iDocumentWidget *, d);
423 /* TODO: Do this in the background. However, that requires a text metrics calculator
424 that does not try to cache the glyph bitmaps. */
425 setSource_GmDocument(m->doc, m->newSource, width_Rect(ctx.bounds));
426 postCommandf_App("document.changed url:%s", cstr_String(d->url));
427 clear_String(m->newSource);
428 m->scrollY = 0;
429 m->state = ready_DocumentState;
430 updateVisible_DocumentWidget_(m);
431 }
432 if (d->state != ready_DocumentState) return;
433 setClip_Paint(&ctx.paint, bounds); 488 setClip_Paint(&ctx.paint, bounds);
434 render_GmDocument(d->doc, visibleRange_DocumentWidget_(d), drawRun_DrawContext_, &ctx); 489 render_GmDocument(d->doc, visibleRange_DocumentWidget_(d), drawRun_DrawContext_, &ctx);
435 clearClip_Paint(&ctx.paint); 490 clearClip_Paint(&ctx.paint);
diff --git a/src/ui/window.c b/src/ui/window.c
index 43233010..2cd30459 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -49,7 +49,7 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) {
49 } 49 }
50 return iTrue; 50 return iTrue;
51 } 51 }
52 else if (equal_Command(cmd, "setfocus")) { 52 else if (equal_Command(cmd, "focus.set")) {
53 setFocus_Widget(findWidget_App(cstr_String(string_Command(cmd, "id")))); 53 setFocus_Widget(findWidget_App(cstr_String(string_Command(cmd, "id"))));
54 return iTrue; 54 return iTrue;
55 } 55 }
@@ -273,7 +273,7 @@ static void setupUserInterface_Window(iWindow *d) {
273#endif 273#endif
274 /* Glboal keyboard shortcuts. */ { 274 /* Glboal keyboard shortcuts. */ {
275 // addAction_Widget(d->root, SDLK_LEFTBRACKET, KMOD_SHIFT | KMOD_PRIMARY, "tabs.prev"); 275 // addAction_Widget(d->root, SDLK_LEFTBRACKET, KMOD_SHIFT | KMOD_PRIMARY, "tabs.prev");
276 addAction_Widget(d->root, 'l', KMOD_PRIMARY, "setfocus id:url"); 276 addAction_Widget(d->root, 'l', KMOD_PRIMARY, "focus.set id:url");
277 } 277 }
278} 278}
279 279