diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/gmdocument.c | 109 | ||||
-rw-r--r-- | src/gmdocument.h | 21 | ||||
-rw-r--r-- | src/gmrequest.c | 11 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 227 | ||||
-rw-r--r-- | src/ui/util.c | 4 | ||||
-rw-r--r-- | src/ui/window.h | 1 |
6 files changed, 317 insertions, 56 deletions
diff --git a/src/gmdocument.c b/src/gmdocument.c index 3dbef514..f8998c17 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c | |||
@@ -3,10 +3,15 @@ | |||
3 | #include "ui/color.h" | 3 | #include "ui/color.h" |
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 | 7 | ||
7 | #include <the_Foundation/ptrarray.h> | 8 | #include <the_Foundation/ptrarray.h> |
8 | #include <the_Foundation/regexp.h> | 9 | #include <the_Foundation/regexp.h> |
9 | 10 | ||
11 | #include <SDL_hints.h> | ||
12 | #include <SDL_render.h> | ||
13 | #include <stb_image.h> | ||
14 | |||
10 | iDeclareType(GmLink) | 15 | iDeclareType(GmLink) |
11 | 16 | ||
12 | struct Impl_GmLink { | 17 | struct Impl_GmLink { |
@@ -25,6 +30,40 @@ void deinit_GmLink(iGmLink *d) { | |||
25 | 30 | ||
26 | iDefineTypeConstruction(GmLink) | 31 | iDefineTypeConstruction(GmLink) |
27 | 32 | ||
33 | iDeclareType(GmImage) | ||
34 | |||
35 | struct Impl_GmImage { | ||
36 | iInt2 size; | ||
37 | SDL_Texture *texture; | ||
38 | iGmLinkId linkId; | ||
39 | }; | ||
40 | |||
41 | void init_GmImage(iGmImage *d, const iBlock *data) { | ||
42 | uint8_t *imgData = stbi_load_from_memory( | ||
43 | constData_Block(data), size_Block(data), &d->size.x, &d->size.y, NULL, 4); | ||
44 | if (!imgData) { | ||
45 | d->size = zero_I2(); | ||
46 | d->texture = NULL; | ||
47 | } | ||
48 | else { | ||
49 | SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormatFrom( | ||
50 | imgData, d->size.x, d->size.y, 32, d->size.x * 4, SDL_PIXELFORMAT_ABGR8888); | ||
51 | /* TODO: In multiwindow case, all windows must have the same shared renderer? | ||
52 | Or at least a shared context. */ | ||
53 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1"); /* linear scaling */ | ||
54 | d->texture = SDL_CreateTextureFromSurface(renderer_Window(get_Window()), surface); | ||
55 | SDL_FreeSurface(surface); | ||
56 | stbi_image_free(imgData); | ||
57 | } | ||
58 | d->linkId = 0; | ||
59 | } | ||
60 | |||
61 | void deinit_GmImage(iGmImage *d) { | ||
62 | SDL_DestroyTexture(d->texture); | ||
63 | } | ||
64 | |||
65 | iDefineTypeConstructionArgs(GmImage, (const iBlock *data), data) | ||
66 | |||
28 | struct Impl_GmDocument { | 67 | struct Impl_GmDocument { |
29 | iObject object; | 68 | iObject object; |
30 | enum iGmDocumentFormat format; | 69 | enum iGmDocumentFormat format; |
@@ -34,6 +73,7 @@ struct Impl_GmDocument { | |||
34 | iArray layout; /* contents of source, laid out in document space */ | 73 | iArray layout; /* contents of source, laid out in document space */ |
35 | iPtrArray links; | 74 | iPtrArray links; |
36 | iString title; /* the first top-level title */ | 75 | iString title; /* the first top-level title */ |
76 | iPtrArray images; /* persistent across layouts, references links by ID */ | ||
37 | }; | 77 | }; |
38 | 78 | ||
39 | iDefineObjectConstruction(GmDocument) | 79 | iDefineObjectConstruction(GmDocument) |
@@ -178,6 +218,17 @@ static void clearLinks_GmDocument_(iGmDocument *d) { | |||
178 | clear_PtrArray(&d->links); | 218 | clear_PtrArray(&d->links); |
179 | } | 219 | } |
180 | 220 | ||
221 | static size_t findLinkImage_GmDocument_(const iGmDocument *d, iGmLinkId linkId) { | ||
222 | /* TODO: use a hash */ | ||
223 | iConstForEach(PtrArray, i, &d->images) { | ||
224 | const iGmImage *img = i.ptr; | ||
225 | if (img->linkId == linkId) { | ||
226 | return index_PtrArrayConstIterator(&i); | ||
227 | } | ||
228 | } | ||
229 | return iInvalidPos; | ||
230 | } | ||
231 | |||
181 | static void doLayout_GmDocument_(iGmDocument *d) { | 232 | static void doLayout_GmDocument_(iGmDocument *d) { |
182 | /* TODO: Collect these parameters into a GmTheme. */ | 233 | /* TODO: Collect these parameters into a GmTheme. */ |
183 | static const int fonts[max_GmLineType] = { | 234 | static const int fonts[max_GmLineType] = { |
@@ -236,6 +287,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
236 | iGmRun run; | 287 | iGmRun run; |
237 | run.color = white_ColorId; | 288 | run.color = white_ColorId; |
238 | run.linkId = 0; | 289 | run.linkId = 0; |
290 | run.imageId = 0; | ||
239 | enum iGmLineType type; | 291 | enum iGmLineType type; |
240 | int indent = 0; | 292 | int indent = 0; |
241 | if (!isPreformat) { | 293 | if (!isPreformat) { |
@@ -373,6 +425,24 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
373 | trimStart_Rangecc(&runLine); | 425 | trimStart_Rangecc(&runLine); |
374 | pos.y += lineHeight_Text(run.font); | 426 | pos.y += lineHeight_Text(run.font); |
375 | } | 427 | } |
428 | /* Image content. */ | ||
429 | if (type == link_GmLineType) { | ||
430 | const size_t imgIndex = findLinkImage_GmDocument_(d, run.linkId); | ||
431 | if (imgIndex != iInvalidPos) { | ||
432 | const iGmImage *img = constAt_PtrArray(&d->images, imgIndex); | ||
433 | run.bounds.pos = pos; | ||
434 | run.bounds.size.x = d->size.x; | ||
435 | const float aspect = (float) img->size.y / (float) img->size.x; | ||
436 | run.bounds.size.y = d->size.x * aspect; | ||
437 | run.visBounds = run.bounds; /* TODO: limit max height? */ | ||
438 | run.text = iNullRange; | ||
439 | run.font = 0; | ||
440 | run.color = 0; | ||
441 | run.imageId = imgIndex + 1; | ||
442 | pushBack_Array(&d->layout, &run); | ||
443 | pos.y += run.bounds.size.y; | ||
444 | } | ||
445 | } | ||
376 | prevType = type; | 446 | prevType = type; |
377 | } | 447 | } |
378 | d->size.y = pos.y; | 448 | d->size.y = pos.y; |
@@ -386,6 +456,7 @@ void init_GmDocument(iGmDocument *d) { | |||
386 | init_Array(&d->layout, sizeof(iGmRun)); | 456 | init_Array(&d->layout, sizeof(iGmRun)); |
387 | init_PtrArray(&d->links); | 457 | init_PtrArray(&d->links); |
388 | init_String(&d->title); | 458 | init_String(&d->title); |
459 | init_PtrArray(&d->images); | ||
389 | } | 460 | } |
390 | 461 | ||
391 | void deinit_GmDocument(iGmDocument *d) { | 462 | void deinit_GmDocument(iGmDocument *d) { |
@@ -397,6 +468,16 @@ void deinit_GmDocument(iGmDocument *d) { | |||
397 | deinit_String(&d->source); | 468 | deinit_String(&d->source); |
398 | } | 469 | } |
399 | 470 | ||
471 | void reset_GmDocument(iGmDocument *d) { | ||
472 | /* Free the loaded images. */ | ||
473 | iForEach(PtrArray, i, &d->images) { | ||
474 | deinit_GmImage(i.ptr); | ||
475 | } | ||
476 | clear_PtrArray(&d->images); | ||
477 | clearLinks_GmDocument_(d); | ||
478 | clear_Array(&d->layout); | ||
479 | } | ||
480 | |||
400 | void setFormat_GmDocument(iGmDocument *d, enum iGmDocumentFormat format) { | 481 | void setFormat_GmDocument(iGmDocument *d, enum iGmDocumentFormat format) { |
401 | d->format = format; | 482 | d->format = format; |
402 | } | 483 | } |
@@ -477,6 +558,21 @@ void setSource_GmDocument(iGmDocument *d, const iString *source, int width) { | |||
477 | /* TODO: just flag need-layout and do it later */ | 558 | /* TODO: just flag need-layout and do it later */ |
478 | } | 559 | } |
479 | 560 | ||
561 | void setImage_GmDocument(iGmDocument *d, iGmLinkId linkId, const iString *mime, const iBlock *data) { | ||
562 | /* TODO: check if we know this MIME type */ | ||
563 | /* Load the image. */ { | ||
564 | iGmImage *img = new_GmImage(data); | ||
565 | img->linkId = linkId; /* TODO: use a hash? */ | ||
566 | if (img->texture) { | ||
567 | pushBack_PtrArray(&d->images, img); | ||
568 | } | ||
569 | else { | ||
570 | delete_GmImage(img); | ||
571 | } | ||
572 | } | ||
573 | doLayout_GmDocument_(d); | ||
574 | } | ||
575 | |||
480 | void render_GmDocument(const iGmDocument *d, iRangei visRangeY, iGmDocumentRenderFunc render, | 576 | void render_GmDocument(const iGmDocument *d, iRangei visRangeY, iGmDocumentRenderFunc render, |
481 | void *context) { | 577 | void *context) { |
482 | iBool isInside = iFalse; | 578 | iBool isInside = iFalse; |
@@ -579,6 +675,19 @@ enum iColorId linkColor_GmDocument(const iGmDocument *d, iGmLinkId linkId) { | |||
579 | return white_ColorId; | 675 | return white_ColorId; |
580 | } | 676 | } |
581 | 677 | ||
678 | iBool isMediaLink_GmDocument(const iGmDocument *d, iGmLinkId linkId) { | ||
679 | return (linkFlags_GmDocument(d, linkId) & | ||
680 | (imageFileExtension_GmLinkFlag | audioFileExtension_GmLinkFlag)) != 0; | ||
681 | } | ||
682 | |||
683 | SDL_Texture *imageTexture_GmDocument(const iGmDocument *d, uint16_t imageId) { | ||
684 | if (imageId > 0 && imageId <= size_PtrArray(&d->images)) { | ||
685 | const iGmImage *img = constAt_PtrArray(&d->images, imageId - 1); | ||
686 | return img->texture; | ||
687 | } | ||
688 | return NULL; | ||
689 | } | ||
690 | |||
582 | const iString *title_GmDocument(const iGmDocument *d) { | 691 | const iString *title_GmDocument(const iGmDocument *d) { |
583 | return &d->title; | 692 | return &d->title; |
584 | } | 693 | } |
diff --git a/src/gmdocument.h b/src/gmdocument.h index c671eaaf..c619c47b 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h | |||
@@ -5,6 +5,8 @@ | |||
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 | 7 | ||
8 | #include <SDL_render.h> | ||
9 | |||
8 | iDeclareType(GmRun) | 10 | iDeclareType(GmRun) |
9 | 11 | ||
10 | typedef uint16_t iGmLinkId; | 12 | typedef uint16_t iGmLinkId; |
@@ -20,12 +22,13 @@ enum iGmLinkFlags { | |||
20 | }; | 22 | }; |
21 | 23 | ||
22 | struct Impl_GmRun { | 24 | struct Impl_GmRun { |
23 | iRangecc text; | 25 | iRangecc text; |
24 | iRect bounds; /* used for hit testing, extends to edge */ | 26 | uint8_t font; |
25 | iRect visBounds; /* actual text bounds */ | 27 | uint8_t color; |
26 | uint8_t font; | 28 | iRect bounds; /* used for hit testing, may extend to edges */ |
27 | uint8_t color; | 29 | iRect visBounds; /* actual visual bounds */ |
28 | iGmLinkId linkId; /* zero for non-links */ | 30 | iGmLinkId linkId; /* zero for non-links */ |
31 | uint16_t imageId; /* zero for images */ | ||
29 | }; | 32 | }; |
30 | 33 | ||
31 | const char * findLoc_GmRun (const iGmRun *, iInt2 pos); | 34 | const char * findLoc_GmRun (const iGmRun *, iInt2 pos); |
@@ -42,6 +45,9 @@ void setFormat_GmDocument (iGmDocument *, enum iGmDocumentFormat format); | |||
42 | void setWidth_GmDocument (iGmDocument *, int width); | 45 | void setWidth_GmDocument (iGmDocument *, int width); |
43 | void setHost_GmDocument (iGmDocument *, const iString *host); /* local host name */ | 46 | void setHost_GmDocument (iGmDocument *, const iString *host); /* local host name */ |
44 | void setSource_GmDocument (iGmDocument *, const iString *source, int width); | 47 | void setSource_GmDocument (iGmDocument *, const iString *source, int width); |
48 | void setImage_GmDocument (iGmDocument *, iGmLinkId linkId, const iString *mime, const iBlock *data); | ||
49 | |||
50 | void reset_GmDocument (iGmDocument *); /* free images */ | ||
45 | 51 | ||
46 | typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *); | 52 | typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *); |
47 | 53 | ||
@@ -57,4 +63,7 @@ const iGmRun * findRunAtLoc_GmDocument (const iGmDocument *, const char *loc); | |||
57 | const iString * linkUrl_GmDocument (const iGmDocument *, iGmLinkId linkId); | 63 | const iString * linkUrl_GmDocument (const iGmDocument *, iGmLinkId linkId); |
58 | int linkFlags_GmDocument (const iGmDocument *, iGmLinkId linkId); | 64 | int linkFlags_GmDocument (const iGmDocument *, iGmLinkId linkId); |
59 | enum iColorId linkColor_GmDocument (const iGmDocument *, iGmLinkId linkId); | 65 | enum iColorId linkColor_GmDocument (const iGmDocument *, iGmLinkId linkId); |
66 | iBool isMediaLink_GmDocument (const iGmDocument *, iGmLinkId linkId); | ||
60 | const iString * title_GmDocument (const iGmDocument *); | 67 | const iString * title_GmDocument (const iGmDocument *); |
68 | |||
69 | SDL_Texture * imageTexture_GmDocument (const iGmDocument *, uint16_t imageId); | ||
diff --git a/src/gmrequest.c b/src/gmrequest.c index ad0c5ed7..25ff90af 100644 --- a/src/gmrequest.c +++ b/src/gmrequest.c | |||
@@ -163,8 +163,6 @@ static void requestFinished_GmRequest_(iAnyObject *obj) { | |||
163 | } | 163 | } |
164 | SDL_RemoveTimer(d->timeoutId); | 164 | SDL_RemoveTimer(d->timeoutId); |
165 | d->timeoutId = 0; | 165 | d->timeoutId = 0; |
166 | // iReleaseLater(d->req); | ||
167 | // d->req = NULL; | ||
168 | d->state = finished_GmRequestState; | 166 | d->state = finished_GmRequestState; |
169 | unlock_Mutex(&d->mutex); | 167 | unlock_Mutex(&d->mutex); |
170 | iNotifyAudience(d, finished, GmRequestFinished); | 168 | iNotifyAudience(d, finished, GmRequestFinished); |
@@ -193,6 +191,15 @@ void submit_GmRequest(iGmRequest *d) { | |||
193 | else if (endsWithCase_String(path, ".txt")) { | 191 | else if (endsWithCase_String(path, ".txt")) { |
194 | setCStr_String(&d->header, "text/plain"); | 192 | setCStr_String(&d->header, "text/plain"); |
195 | } | 193 | } |
194 | else if (endsWithCase_String(path, ".png")) { | ||
195 | setCStr_String(&d->header, "image/png"); | ||
196 | } | ||
197 | else if (endsWithCase_String(path, ".jpg") || endsWithCase_String(path, ".jpeg")) { | ||
198 | setCStr_String(&d->header, "image/jpeg"); | ||
199 | } | ||
200 | else if (endsWithCase_String(path, ".gif")) { | ||
201 | setCStr_String(&d->header, "image/gif"); | ||
202 | } | ||
196 | else { | 203 | else { |
197 | setCStr_String(&d->header, "application/octet-stream"); | 204 | setCStr_String(&d->header, "application/octet-stream"); |
198 | } | 205 | } |
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 10af43f3..cffd284f 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -11,6 +11,7 @@ | |||
11 | #include "../gmutil.h" | 11 | #include "../gmutil.h" |
12 | 12 | ||
13 | #include <the_Foundation/file.h> | 13 | #include <the_Foundation/file.h> |
14 | #include <the_Foundation/objectlist.h> | ||
14 | #include <the_Foundation/path.h> | 15 | #include <the_Foundation/path.h> |
15 | #include <the_Foundation/ptrarray.h> | 16 | #include <the_Foundation/ptrarray.h> |
16 | #include <the_Foundation/regexp.h> | 17 | #include <the_Foundation/regexp.h> |
@@ -26,13 +27,59 @@ enum iDocumentState { | |||
26 | ready_DocumentState, | 27 | ready_DocumentState, |
27 | }; | 28 | }; |
28 | 29 | ||
30 | iDeclareClass(MediaRequest) | ||
31 | |||
32 | struct Impl_MediaRequest { | ||
33 | iObject object; | ||
34 | iDocumentWidget *doc; | ||
35 | iGmLinkId linkId; | ||
36 | iGmRequest *req; | ||
37 | iAtomicInt isUpdated; | ||
38 | }; | ||
39 | |||
40 | static void updated_MediaRequest_(iAnyObject *obj) { | ||
41 | iMediaRequest *d = obj; | ||
42 | int wasUpdated = exchange_Atomic(&d->isUpdated, iTrue); | ||
43 | if (!wasUpdated) { | ||
44 | postCommandf_App("media.updated link:%u request:%p", d->linkId, d); | ||
45 | } | ||
46 | } | ||
47 | |||
48 | static void finished_MediaRequest_(iAnyObject *obj) { | ||
49 | iMediaRequest *d = obj; | ||
50 | postCommandf_App("media.finished link:%u request:%p", d->linkId, d); | ||
51 | } | ||
52 | |||
53 | void init_MediaRequest(iMediaRequest *d, iDocumentWidget *doc, iGmLinkId linkId, const iString *url) { | ||
54 | d->doc = doc; | ||
55 | d->linkId = linkId; | ||
56 | d->req = new_GmRequest(); | ||
57 | setUrl_GmRequest(d->req, url); | ||
58 | iConnect(GmRequest, d->req, updated, d, updated_MediaRequest_); | ||
59 | iConnect(GmRequest, d->req, finished, d, finished_MediaRequest_); | ||
60 | set_Atomic(&d->isUpdated, iFalse); | ||
61 | submit_GmRequest(d->req); | ||
62 | } | ||
63 | |||
64 | void deinit_MediaRequest(iMediaRequest *d) { | ||
65 | iDisconnect(GmRequest, d->req, updated, d, updated_MediaRequest_); | ||
66 | iDisconnect(GmRequest, d->req, finished, d, finished_MediaRequest_); | ||
67 | iRelease(d->req); | ||
68 | } | ||
69 | |||
70 | iDefineObjectConstructionArgs(MediaRequest, | ||
71 | (iDocumentWidget *doc, iGmLinkId linkId, const iString *url), | ||
72 | doc, linkId, url) | ||
73 | iDefineClass(MediaRequest) | ||
74 | |||
29 | struct Impl_DocumentWidget { | 75 | struct Impl_DocumentWidget { |
30 | iWidget widget; | 76 | iWidget widget; |
31 | enum iDocumentState state; | 77 | enum iDocumentState state; |
32 | iString *url; | 78 | iString *url; |
33 | iString *titleUser; | 79 | iString *titleUser; |
34 | iGmRequest *request; | 80 | iGmRequest *request; |
35 | iAtomicInt isSourcePending; /* request has new content, need to parse it */ | 81 | iAtomicInt isRequestUpdated; /* request has new content, need to parse it */ |
82 | iObjectList *media; | ||
36 | iGmDocument *doc; | 83 | iGmDocument *doc; |
37 | iBool selecting; | 84 | iBool selecting; |
38 | iRangecc selectMark; | 85 | iRangecc selectMark; |
@@ -55,21 +102,22 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
55 | iWidget *w = as_Widget(d); | 102 | iWidget *w = as_Widget(d); |
56 | init_Widget(w); | 103 | init_Widget(w); |
57 | setId_Widget(w, "document"); | 104 | setId_Widget(w, "document"); |
58 | d->state = blank_DocumentState; | 105 | d->state = blank_DocumentState; |
59 | d->url = new_String(); | 106 | d->url = new_String(); |
60 | d->titleUser = new_String(); | 107 | d->titleUser = new_String(); |
61 | d->request = NULL; | 108 | d->request = NULL; |
62 | d->isSourcePending = iFalse; | 109 | d->isRequestUpdated = iFalse; |
63 | d->doc = new_GmDocument(); | 110 | d->media = new_ObjectList(); |
64 | d->selecting = iFalse; | 111 | d->doc = new_GmDocument(); |
65 | d->selectMark = iNullRange; | 112 | d->selecting = iFalse; |
66 | d->foundMark = iNullRange; | 113 | d->selectMark = iNullRange; |
67 | d->pageMargin = 5; | 114 | d->foundMark = iNullRange; |
68 | d->scrollY = 0; | 115 | d->pageMargin = 5; |
69 | d->hoverLink = NULL; | 116 | d->scrollY = 0; |
70 | d->arrowCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); | 117 | d->hoverLink = NULL; |
71 | d->beamCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); | 118 | d->arrowCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); |
72 | d->handCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); | 119 | d->beamCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_IBEAM); |
120 | d->handCursor = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND); | ||
73 | init_PtrArray(&d->visibleLinks); | 121 | init_PtrArray(&d->visibleLinks); |
74 | init_Click(&d->click, d, SDL_BUTTON_LEFT); | 122 | init_Click(&d->click, d, SDL_BUTTON_LEFT); |
75 | addChild_Widget(w, iClob(d->scroll = new_ScrollWidget())); | 123 | addChild_Widget(w, iClob(d->scroll = new_ScrollWidget())); |
@@ -85,16 +133,26 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
85 | } | 133 | } |
86 | 134 | ||
87 | void deinit_DocumentWidget(iDocumentWidget *d) { | 135 | void deinit_DocumentWidget(iDocumentWidget *d) { |
136 | iRelease(d->media); | ||
137 | iRelease(d->request); | ||
138 | iRelease(d->doc); | ||
88 | deinit_PtrArray(&d->visibleLinks); | 139 | deinit_PtrArray(&d->visibleLinks); |
89 | delete_String(d->url); | 140 | delete_String(d->url); |
90 | delete_String(d->titleUser); | 141 | delete_String(d->titleUser); |
91 | iRelease(d->request); | ||
92 | iRelease(d->doc); | ||
93 | SDL_FreeCursor(d->arrowCursor); | 142 | SDL_FreeCursor(d->arrowCursor); |
94 | SDL_FreeCursor(d->beamCursor); | 143 | SDL_FreeCursor(d->beamCursor); |
95 | SDL_FreeCursor(d->handCursor); | 144 | SDL_FreeCursor(d->handCursor); |
96 | } | 145 | } |
97 | 146 | ||
147 | static iString *cleanUrl_(const iString *url) { | ||
148 | iString *clean = copy_String(url); | ||
149 | if (indexOfCStr_String(url, "://") == iInvalidPos && !startsWithCase_String(url, "gemini:")) { | ||
150 | /* Prepend default protocol. */ | ||
151 | prependCStr_String(clean, "gemini://"); | ||
152 | } | ||
153 | return clean; | ||
154 | } | ||
155 | |||
98 | static int documentWidth_DocumentWidget_(const iDocumentWidget *d) { | 156 | static int documentWidth_DocumentWidget_(const iDocumentWidget *d) { |
99 | const iWidget *w = constAs_Widget(d); | 157 | const iWidget *w = constAs_Widget(d); |
100 | const iRect bounds = bounds_Widget(w); | 158 | const iRect bounds = bounds_Widget(w); |
@@ -124,8 +182,8 @@ static iInt2 documentPos_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) { | |||
124 | 182 | ||
125 | static void requestUpdated_DocumentWidget_(iAnyObject *obj) { | 183 | static void requestUpdated_DocumentWidget_(iAnyObject *obj) { |
126 | iDocumentWidget *d = obj; | 184 | iDocumentWidget *d = obj; |
127 | const int wasPending = exchange_Atomic(&d->isSourcePending, iTrue); | 185 | const int wasUpdated = exchange_Atomic(&d->isRequestUpdated, iTrue); |
128 | if (!wasPending) { | 186 | if (!wasUpdated) { |
129 | postCommand_Widget(obj, "document.request.updated request:%p", d->request); | 187 | postCommand_Widget(obj, "document.request.updated request:%p", d->request); |
130 | } | 188 | } |
131 | } | 189 | } |
@@ -234,6 +292,10 @@ static void updateSource_DocumentWidget_(iDocumentWidget *d) { | |||
234 | else if (startsWith_String(mime, "text/gemini")) { | 292 | else if (startsWith_String(mime, "text/gemini")) { |
235 | setFormat_GmDocument(d->doc, gemini_GmDocumentFormat); | 293 | setFormat_GmDocument(d->doc, gemini_GmDocumentFormat); |
236 | } | 294 | } |
295 | else if (startsWith_String(mime, "image/")) { | ||
296 | /* TODO: Make a simple document with an image. */ | ||
297 | clear_String(&str); | ||
298 | } | ||
237 | else { | 299 | else { |
238 | showErrorPage_DocumentWidget_(d, unsupportedMimeType_GmStatusCode); | 300 | showErrorPage_DocumentWidget_(d, unsupportedMimeType_GmStatusCode); |
239 | deinit_String(&str); | 301 | deinit_String(&str); |
@@ -252,8 +314,9 @@ static void fetch_DocumentWidget_(iDocumentWidget *d) { | |||
252 | d->request = NULL; | 314 | d->request = NULL; |
253 | } | 315 | } |
254 | postCommandf_App("document.request.started url:%s", cstr_String(d->url)); | 316 | postCommandf_App("document.request.started url:%s", cstr_String(d->url)); |
317 | clear_ObjectList(d->media); | ||
255 | d->state = fetching_DocumentState; | 318 | d->state = fetching_DocumentState; |
256 | set_Atomic(&d->isSourcePending, iFalse); | 319 | set_Atomic(&d->isRequestUpdated, iFalse); |
257 | d->request = new_GmRequest(); | 320 | d->request = new_GmRequest(); |
258 | setUrl_GmRequest(d->request, d->url); | 321 | setUrl_GmRequest(d->request, d->url); |
259 | iConnect(GmRequest, d->request, updated, d, requestUpdated_DocumentWidget_); | 322 | iConnect(GmRequest, d->request, updated, d, requestUpdated_DocumentWidget_); |
@@ -262,12 +325,7 @@ static void fetch_DocumentWidget_(iDocumentWidget *d) { | |||
262 | } | 325 | } |
263 | 326 | ||
264 | void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) { | 327 | void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) { |
265 | iString *newUrl = new_String(); | 328 | iString *newUrl = collect_String(cleanUrl_(url)); |
266 | if (indexOfCStr_String(url, "://") == iInvalidPos && !startsWithCase_String(url, "gemini:")) { | ||
267 | /* Prepend default protocol. */ | ||
268 | setCStr_String(newUrl, "gemini://"); | ||
269 | } | ||
270 | append_String(newUrl, url); | ||
271 | if (cmpStringSc_String(d->url, newUrl, &iCaseInsensitive)) { | 329 | if (cmpStringSc_String(d->url, newUrl, &iCaseInsensitive)) { |
272 | set_String(d->url, newUrl); | 330 | set_String(d->url, newUrl); |
273 | fetch_DocumentWidget_(d); | 331 | fetch_DocumentWidget_(d); |
@@ -284,7 +342,6 @@ void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) { | |||
284 | iRelease(userPats[i]); | 342 | iRelease(userPats[i]); |
285 | } | 343 | } |
286 | } | 344 | } |
287 | delete_String(newUrl); | ||
288 | } | 345 | } |
289 | 346 | ||
290 | iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) { | 347 | iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) { |
@@ -372,6 +429,7 @@ static void checkResponseCode_DocumentWidget_(iDocumentWidget *d) { | |||
372 | if (d->state == fetching_DocumentState) { | 429 | if (d->state == fetching_DocumentState) { |
373 | d->state = receivedPartialResponse_DocumentState; | 430 | d->state = receivedPartialResponse_DocumentState; |
374 | d->scrollY = 0; | 431 | d->scrollY = 0; |
432 | reset_GmDocument(d->doc); /* new content incoming */ | ||
375 | enum iGmStatusCode statusCode = status_GmRequest(d->request); | 433 | enum iGmStatusCode statusCode = status_GmRequest(d->request); |
376 | switch (statusCode) { | 434 | switch (statusCode) { |
377 | case none_GmStatusCode: | 435 | case none_GmStatusCode: |
@@ -384,10 +442,10 @@ static void checkResponseCode_DocumentWidget_(iDocumentWidget *d) { | |||
384 | iWidget *dlg = makeValueInput_Widget( | 442 | iWidget *dlg = makeValueInput_Widget( |
385 | as_Widget(d), | 443 | as_Widget(d), |
386 | NULL, | 444 | NULL, |
387 | cstrFormat_String(cyan_ColorEscape "%s", | 445 | format_CStr(cyan_ColorEscape "%s", |
388 | cstr_String(collect_String(newRange_String(parts.host)))), | 446 | cstr_String(collect_String(newRange_String(parts.host)))), |
389 | isEmpty_String(meta_GmRequest(d->request)) | 447 | isEmpty_String(meta_GmRequest(d->request)) |
390 | ? cstrFormat_String( | 448 | ? format_CStr( |
391 | "Please enter input for %s:", | 449 | "Please enter input for %s:", |
392 | cstr_String(collect_String(newRange_String(parts.path)))) | 450 | cstr_String(collect_String(newRange_String(parts.path)))) |
393 | : cstr_String(meta_GmRequest(d->request)), | 451 | : cstr_String(meta_GmRequest(d->request)), |
@@ -415,14 +473,73 @@ static void checkResponseCode_DocumentWidget_(iDocumentWidget *d) { | |||
415 | } | 473 | } |
416 | } | 474 | } |
417 | 475 | ||
418 | const iString *valueString_Command(const char *cmd, const char *label) { | ||
419 | return collect_String(newCStr_String(suffixPtr_Command(cmd, label))); | ||
420 | } | ||
421 | |||
422 | static const char *sourceLoc_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) { | 476 | static const char *sourceLoc_DocumentWidget_(const iDocumentWidget *d, iInt2 pos) { |
423 | return findLoc_GmDocument(d->doc, documentPos_DocumentWidget_(d, pos)); | 477 | return findLoc_GmDocument(d->doc, documentPos_DocumentWidget_(d, pos)); |
424 | } | 478 | } |
425 | 479 | ||
480 | static void removeMediaRequest_DocumentWidget_(iDocumentWidget *d, iGmLinkId linkId) { | ||
481 | iForEach(ObjectList, i, d->media) { | ||
482 | iMediaRequest *req = (iMediaRequest *) i.object; | ||
483 | if (req->linkId == linkId) { | ||
484 | remove_ObjectListIterator(&i); | ||
485 | break; | ||
486 | } | ||
487 | } | ||
488 | } | ||
489 | |||
490 | static iMediaRequest *findMediaRequest_DocumentWidget_(const iDocumentWidget *d, iGmLinkId linkId) { | ||
491 | iConstForEach(ObjectList, i, d->media) { | ||
492 | const iMediaRequest *req = (const iMediaRequest *) i.object; | ||
493 | if (req->linkId == linkId) { | ||
494 | return iConstCast(iMediaRequest *, req); | ||
495 | } | ||
496 | } | ||
497 | return NULL; | ||
498 | } | ||
499 | |||
500 | static void requestMedia_DocumentWidget_(iDocumentWidget *d, iGmLinkId linkId) { | ||
501 | if (!findMediaRequest_DocumentWidget_(d, linkId)) { | ||
502 | pushBack_ObjectList( | ||
503 | d->media, | ||
504 | iClob(new_MediaRequest( | ||
505 | d, linkId, absoluteUrl_DocumentWidget_(d, linkUrl_GmDocument(d->doc, linkId))))); | ||
506 | } | ||
507 | } | ||
508 | |||
509 | static iBool handleMediaEvent_DocumentWidget_(iDocumentWidget *d, const char *cmd) { | ||
510 | iMediaRequest *req = pointerLabel_Command(cmd, "request"); | ||
511 | if (!req || req->doc != d) { | ||
512 | return iFalse; /* not our request */ | ||
513 | } | ||
514 | if (equal_Command(cmd, "media.updated")) { | ||
515 | /* TODO: Show a progress indicator */ | ||
516 | return iTrue; | ||
517 | } | ||
518 | else if (equal_Command(cmd, "media.finished")) { | ||
519 | const enum iGmStatusCode code = status_GmRequest(req->req); | ||
520 | /* Give the media to the document for presentation. */ | ||
521 | if (code == success_GmStatusCode) { | ||
522 | printf("media finished: %s\n size: %zu\n type: %s\n", | ||
523 | cstr_String(url_GmRequest(req->req)), | ||
524 | size_Block(body_GmRequest(req->req)), | ||
525 | cstr_String(meta_GmRequest(req->req))); | ||
526 | if (startsWith_String(meta_GmRequest(req->req), "image/")) { | ||
527 | setImage_GmDocument(d->doc, req->linkId, meta_GmRequest(req->req), | ||
528 | body_GmRequest(req->req)); | ||
529 | updateVisible_DocumentWidget_(d); | ||
530 | refresh_Widget(as_Widget(d)); | ||
531 | } | ||
532 | } | ||
533 | else { | ||
534 | const iGmError *err = get_GmError(code); | ||
535 | makeMessage_Widget(format_CStr(orange_ColorEscape "%s", err->title), err->info); | ||
536 | removeMediaRequest_DocumentWidget_(d, req->linkId); | ||
537 | } | ||
538 | return iTrue; | ||
539 | } | ||
540 | return iFalse; | ||
541 | } | ||
542 | |||
426 | static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *ev) { | 543 | static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *ev) { |
427 | iWidget *w = as_Widget(d); | 544 | iWidget *w = as_Widget(d); |
428 | if (isResize_UserEvent(ev)) { | 545 | if (isResize_UserEvent(ev)) { |
@@ -499,6 +616,9 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
499 | } | 616 | } |
500 | return iTrue; | 617 | return iTrue; |
501 | } | 618 | } |
619 | else if (isCommand_UserEvent(ev, "media.updated") || isCommand_UserEvent(ev, "media.finished")) { | ||
620 | return handleMediaEvent_DocumentWidget_(d, command_UserEvent(ev)); | ||
621 | } | ||
502 | else if (isCommand_UserEvent(ev, "document.reload")) { | 622 | else if (isCommand_UserEvent(ev, "document.reload")) { |
503 | fetch_DocumentWidget_(d); | 623 | fetch_DocumentWidget_(d); |
504 | return iTrue; | 624 | return iTrue; |
@@ -653,10 +773,17 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
653 | case finished_ClickResult: | 773 | case finished_ClickResult: |
654 | if (!isMoved_Click(&d->click)) { | 774 | if (!isMoved_Click(&d->click)) { |
655 | if (d->hoverLink) { | 775 | if (d->hoverLink) { |
656 | iAssert(d->hoverLink->linkId); | 776 | const iGmLinkId linkId = d->hoverLink->linkId; |
657 | postCommandf_App("open url:%s", | 777 | iAssert(linkId); |
658 | cstr_String(absoluteUrl_DocumentWidget_( | 778 | /* Media links are opened inline by default. */ |
659 | d, linkUrl_GmDocument(d->doc, d->hoverLink->linkId)))); | 779 | if (isMediaLink_GmDocument(d->doc, linkId)) { |
780 | requestMedia_DocumentWidget_(d, linkId); | ||
781 | } | ||
782 | else { | ||
783 | postCommandf_App("open url:%s", | ||
784 | cstr_String(absoluteUrl_DocumentWidget_( | ||
785 | d, linkUrl_GmDocument(d->doc, linkId)))); | ||
786 | } | ||
660 | } | 787 | } |
661 | if (d->selectMark.start) { | 788 | if (d->selectMark.start) { |
662 | d->selectMark = iNullRange; | 789 | d->selectMark = iNullRange; |
@@ -714,20 +841,27 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol | |||
714 | 841 | ||
715 | static void drawRun_DrawContext_(void *context, const iGmRun *run) { | 842 | static void drawRun_DrawContext_(void *context, const iGmRun *run) { |
716 | iDrawContext *d = context; | 843 | iDrawContext *d = context; |
844 | const iInt2 origin = addY_I2(d->bounds.pos, -d->widget->scrollY); | ||
845 | if (run->imageId) { | ||
846 | SDL_Texture *tex = imageTexture_GmDocument(d->widget->doc, run->imageId); | ||
847 | if (tex) { | ||
848 | const iRect dst = moved_Rect(run->visBounds, origin); | ||
849 | SDL_RenderCopy(d->paint.dst->render, tex, NULL, | ||
850 | &(SDL_Rect){ dst.pos.x, dst.pos.y, dst.size.x, dst.size.y }); | ||
851 | } | ||
852 | return; | ||
853 | } | ||
717 | iString text; | 854 | iString text; |
718 | /* TODO: making a copy is unnecessary; the text routines should accept Rangecc */ | 855 | /* TODO: making a copy is unnecessary; the text routines should accept Rangecc */ |
719 | initRange_String(&text, run->text); | 856 | initRange_String(&text, run->text); |
720 | iInt2 origin = addY_I2(d->bounds.pos, -d->widget->scrollY); | ||
721 | enum iColorId fg = run->color; | 857 | enum iColorId fg = run->color; |
722 | if (run == d->widget->hoverLink) { | 858 | if (run == d->widget->hoverLink) { |
723 | //const char *desc = ""; | 859 | const iGmDocument *doc = d->widget->doc; |
724 | const iGmDocument *doc = d->widget->doc; | 860 | const iGmLinkId linkId = d->widget->hoverLink->linkId; |
725 | const iGmLinkId linkId = d->widget->hoverLink->linkId; | 861 | const iString * url = linkUrl_GmDocument(doc, linkId); |
726 | const iString *url = linkUrl_GmDocument(doc, linkId); | 862 | const int flags = linkFlags_GmDocument(doc, linkId); |
727 | const int flags = linkFlags_GmDocument(doc, linkId); | ||
728 | iUrl parts; | 863 | iUrl parts; |
729 | init_Url(&parts, url); | 864 | init_Url(&parts, url); |
730 | // desc = cstrFormat_String("\u2192 %s", cstr_String(collect_String(newRange_String(parts.protocol)))); | ||
731 | const iString *host = collect_String(newRange_String(parts.host)); | 865 | const iString *host = collect_String(newRange_String(parts.host)); |
732 | fg = linkColor_GmDocument(doc, linkId); | 866 | fg = linkColor_GmDocument(doc, linkId); |
733 | const iBool showHost = (!isEmpty_String(host) && flags & userFriendly_GmLinkFlag); | 867 | const iBool showHost = (!isEmpty_String(host) && flags & userFriendly_GmLinkFlag); |
@@ -739,9 +873,10 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
739 | topRight_Rect(linkRect), | 873 | topRight_Rect(linkRect), |
740 | fg - 1, | 874 | fg - 1, |
741 | left_Alignment, | 875 | left_Alignment, |
742 | " \u2014%s%s\r%c%s", | 876 | " \u2014%s%s%s\r%c%s", |
743 | showHost ? " " : "", | 877 | showHost ? " " : "", |
744 | showHost ? cstr_String(host) : "", | 878 | showHost ? cstr_String(host) : "", |
879 | showHost && (showImage || showAudio) ? " \u2014" : "", | ||
745 | showImage || showAudio ? '0' + fg : ('0' + fg - 1), | 880 | showImage || showAudio ? '0' + fg : ('0' + fg - 1), |
746 | showImage ? " View Image \U0001f5bc" | 881 | showImage ? " View Image \U0001f5bc" |
747 | : showAudio ? " Play Audio \U0001f3b5" : ""); | 882 | : showAudio ? " Play Audio \U0001f3b5" : ""); |
diff --git a/src/ui/util.c b/src/ui/util.c index cf75a2b0..785841c9 100644 --- a/src/ui/util.c +++ b/src/ui/util.c | |||
@@ -308,7 +308,7 @@ static void addTabPage_Widget_(iWidget *tabs, enum iWidgetAddPos addPos, iWidget | |||
308 | const iBool isSel = childCount_Widget(pages) == 0; | 308 | const iBool isSel = childCount_Widget(pages) == 0; |
309 | iWidget * button = addChildPos_Widget( | 309 | iWidget * button = addChildPos_Widget( |
310 | findChild_Widget(tabs, "tabs.buttons"), | 310 | findChild_Widget(tabs, "tabs.buttons"), |
311 | iClob(new_LabelWidget(label, key, kmods, cstrFormat_String("tabs.switch page:%p", page))), | 311 | iClob(new_LabelWidget(label, key, kmods, format_CStr("tabs.switch page:%p", page))), |
312 | addPos); | 312 | addPos); |
313 | setFlags_Widget(button, selected_WidgetFlag, isSel); | 313 | setFlags_Widget(button, selected_WidgetFlag, isSel); |
314 | addChildPos_Widget(pages, page, addPos); | 314 | addChildPos_Widget(pages, page, addPos); |
@@ -615,7 +615,7 @@ static iBool toggleHandler_(iWidget *d, const char *cmd) { | |||
615 | if (equal_Command(cmd, "toggle") && pointer_Command(cmd) == d) { | 615 | if (equal_Command(cmd, "toggle") && pointer_Command(cmd) == d) { |
616 | setToggle_Widget(d, (flags_Widget(d) & selected_WidgetFlag) == 0); | 616 | setToggle_Widget(d, (flags_Widget(d) & selected_WidgetFlag) == 0); |
617 | postCommand_Widget(d, | 617 | postCommand_Widget(d, |
618 | cstrFormat_String("%s.changed arg:%d", | 618 | format_CStr("%s.changed arg:%d", |
619 | cstr_String(id_Widget(d)), | 619 | cstr_String(id_Widget(d)), |
620 | isSelected_Widget(d) ? 1 : 0)); | 620 | isSelected_Widget(d) ? 1 : 0)); |
621 | return iTrue; | 621 | return iTrue; |
diff --git a/src/ui/window.h b/src/ui/window.h index 70e52751..413c454c 100644 --- a/src/ui/window.h +++ b/src/ui/window.h | |||
@@ -31,5 +31,6 @@ float uiScale_Window (const iWindow *); | |||
31 | iInt2 coord_Window (const iWindow *, int x, int y); | 31 | iInt2 coord_Window (const iWindow *, int x, int y); |
32 | iInt2 mouseCoord_Window (const iWindow *); | 32 | iInt2 mouseCoord_Window (const iWindow *); |
33 | uint32_t frameTime_Window (const iWindow *); | 33 | uint32_t frameTime_Window (const iWindow *); |
34 | SDL_Renderer *renderer_Window (const iWindow *); | ||
34 | 35 | ||
35 | iWindow * get_Window (void); | 36 | iWindow * get_Window (void); |