summaryrefslogtreecommitdiff
path: root/src/ui/documentwidget.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui/documentwidget.c')
-rw-r--r--src/ui/documentwidget.c429
1 files changed, 240 insertions, 189 deletions
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 45a8cf2d..44db3e5b 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -400,7 +400,18 @@ void init_DocumentWidget(iDocumentWidget *d) {
400 addAction_Widget(w, navigateRoot_KeyShortcut, "navigate.root"); 400 addAction_Widget(w, navigateRoot_KeyShortcut, "navigate.root");
401} 401}
402 402
403void cancelAllRequests_DocumentWidget(iDocumentWidget *d) {
404 iForEach(ObjectList, i, d->media) {
405 iMediaRequest *mr = i.object;
406 cancel_GmRequest(mr->req);
407 }
408 if (d->request) {
409 cancel_GmRequest(d->request);
410 }
411}
412
403void deinit_DocumentWidget(iDocumentWidget *d) { 413void deinit_DocumentWidget(iDocumentWidget *d) {
414 cancelAllRequests_DocumentWidget(d);
404 pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue); 415 pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue);
405 removeTicker_App(animate_DocumentWidget_, d); 416 removeTicker_App(animate_DocumentWidget_, d);
406 removeTicker_App(prerender_DocumentWidget_, d); 417 removeTicker_App(prerender_DocumentWidget_, d);
@@ -564,7 +575,8 @@ static void addVisible_DocumentWidget_(void *context, const iGmRun *run) {
564 pushBack_PtrArray(&d->visibleWideRuns, run); 575 pushBack_PtrArray(&d->visibleWideRuns, run);
565 } 576 }
566 } 577 }
567 if (run->mediaType == audio_GmRunMediaType || run->mediaType == download_GmRunMediaType) { 578 /* Image runs are static so they're drawn as part of the content. */
579 if (run->mediaType && run->mediaType != image_MediaType) {
568 iAssert(run->mediaId); 580 iAssert(run->mediaId);
569 pushBack_PtrArray(&d->visibleMedia, run); 581 pushBack_PtrArray(&d->visibleMedia, run);
570 } 582 }
@@ -758,14 +770,14 @@ static uint32_t mediaUpdateInterval_DocumentWidget_(const iDocumentWidget *d) {
758 uint32_t interval = invalidInterval_; 770 uint32_t interval = invalidInterval_;
759 iConstForEach(PtrArray, i, &d->visibleMedia) { 771 iConstForEach(PtrArray, i, &d->visibleMedia) {
760 const iGmRun *run = i.ptr; 772 const iGmRun *run = i.ptr;
761 if (run->mediaType == audio_GmRunMediaType) { 773 if (run->mediaType == audio_MediaType) {
762 iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), run->mediaId); 774 iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run));
763 if (flags_Player(plr) & adjustingVolume_PlayerFlag || 775 if (flags_Player(plr) & adjustingVolume_PlayerFlag ||
764 (isStarted_Player(plr) && !isPaused_Player(plr))) { 776 (isStarted_Player(plr) && !isPaused_Player(plr))) {
765 interval = iMin(interval, 1000 / 15); 777 interval = iMin(interval, 1000 / 15);
766 } 778 }
767 } 779 }
768 else if (run->mediaType == download_GmRunMediaType) { 780 else if (run->mediaType == download_MediaType) {
769 interval = iMin(interval, 1000); 781 interval = iMin(interval, 1000);
770 } 782 }
771 } 783 }
@@ -784,8 +796,8 @@ static void updateMedia_DocumentWidget_(iDocumentWidget *d) {
784 refresh_Widget(d); 796 refresh_Widget(d);
785 iConstForEach(PtrArray, i, &d->visibleMedia) { 797 iConstForEach(PtrArray, i, &d->visibleMedia) {
786 const iGmRun *run = i.ptr; 798 const iGmRun *run = i.ptr;
787 if (run->mediaType == audio_GmRunMediaType) { 799 if (run->mediaType == audio_MediaType) {
788 iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), run->mediaId); 800 iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run));
789 if (idleTimeMs_Player(plr) > 3000 && ~flags_Player(plr) & volumeGrabbed_PlayerFlag && 801 if (idleTimeMs_Player(plr) > 3000 && ~flags_Player(plr) & volumeGrabbed_PlayerFlag &&
790 flags_Player(plr) & adjustingVolume_PlayerFlag) { 802 flags_Player(plr) & adjustingVolume_PlayerFlag) {
791 setFlags_Player(plr, adjustingVolume_PlayerFlag, iFalse); 803 setFlags_Player(plr, adjustingVolume_PlayerFlag, iFalse);
@@ -1244,6 +1256,9 @@ static const char *zipPageHeading_(const iRangecc mime) {
1244 if (equalCase_Rangecc(mime, "application/gpub+zip")) { 1256 if (equalCase_Rangecc(mime, "application/gpub+zip")) {
1245 return book_Icon " Gempub"; 1257 return book_Icon " Gempub";
1246 } 1258 }
1259 else if (equalCase_Rangecc(mime, mimeType_FontPack)) {
1260 return "\U0001f520 Fontpack";
1261 }
1247 iRangecc type = iNullRange; 1262 iRangecc type = iNullRange;
1248 nextSplit_Rangecc(mime, "/", &type); /* skip the part before the slash */ 1263 nextSplit_Rangecc(mime, "/", &type); /* skip the part before the slash */
1249 nextSplit_Rangecc(mime, "/", &type); 1264 nextSplit_Rangecc(mime, "/", &type);
@@ -1258,165 +1273,175 @@ static const char *zipPageHeading_(const iRangecc mime) {
1258 1273
1259static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool isCached) { 1274static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool isCached) {
1260 iWidget *w = as_Widget(d); 1275 iWidget *w = as_Widget(d);
1261 delete_Gempub(d->sourceGempub); 1276 /* Gempub page behavior and footer actions. */ {
1262 d->sourceGempub = NULL; 1277 /* TODO: move this to gempub.c */
1263 if (!cmpCase_String(&d->sourceMime, "application/octet-stream") || 1278 delete_Gempub(d->sourceGempub);
1264 !cmpCase_String(&d->sourceMime, mimeType_Gempub) || 1279 d->sourceGempub = NULL;
1265 endsWithCase_String(d->mod.url, ".gpub")) { 1280 if (!cmpCase_String(&d->sourceMime, "application/octet-stream") ||
1266 iGempub *gempub = new_Gempub(); 1281 !cmpCase_String(&d->sourceMime, mimeType_Gempub) ||
1267 if (open_Gempub(gempub, &d->sourceContent)) { 1282 endsWithCase_String(d->mod.url, ".gpub")) {
1268 setBaseUrl_Gempub(gempub, d->mod.url);
1269 setSource_DocumentWidget(d, collect_String(coverPageSource_Gempub(gempub)));
1270 setCStr_String(&d->sourceMime, mimeType_Gempub);
1271 d->sourceGempub = gempub;
1272 }
1273 else {
1274 delete_Gempub(gempub);
1275 }
1276 }
1277 if (!d->sourceGempub) {
1278 const iString *localPath = collect_String(localFilePathFromUrl_String(d->mod.url));
1279 iBool isInside = iFalse;
1280 if (localPath && !fileExists_FileInfo(localPath)) {
1281 /* This URL may refer to a file inside the archive. */
1282 localPath = findContainerArchive_Path(localPath);
1283 isInside = iTrue;
1284 }
1285 if (localPath && equal_CStr(mediaType_Path(localPath), "application/gpub+zip")) {
1286 iGempub *gempub = new_Gempub(); 1283 iGempub *gempub = new_Gempub();
1287 if (openFile_Gempub(gempub, localPath)) { 1284 if (open_Gempub(gempub, &d->sourceContent)) {
1288 setBaseUrl_Gempub(gempub, collect_String(makeFileUrl_String(localPath))); 1285 setBaseUrl_Gempub(gempub, d->mod.url);
1289 if (!isInside) { 1286 setSource_DocumentWidget(d, collect_String(coverPageSource_Gempub(gempub)));
1290 setSource_DocumentWidget(d, collect_String(coverPageSource_Gempub(gempub))); 1287 setCStr_String(&d->sourceMime, mimeType_Gempub);
1291 setCStr_String(&d->sourceMime, mimeType_Gempub);
1292 }
1293 d->sourceGempub = gempub; 1288 d->sourceGempub = gempub;
1294 } 1289 }
1295 else { 1290 else {
1296 delete_Gempub(gempub); 1291 delete_Gempub(gempub);
1297 } 1292 }
1298 } 1293 }
1299 } 1294 if (!d->sourceGempub) {
1300 if (d->sourceGempub) { 1295 const iString *localPath = collect_String(localFilePathFromUrl_String(d->mod.url));
1301 if (equal_String(d->mod.url, coverPageUrl_Gempub(d->sourceGempub))) { 1296 iBool isInside = iFalse;
1302 if (!isRemote_Gempub(d->sourceGempub)) { 1297 if (localPath && !fileExists_FileInfo(localPath)) {
1303 iArray *items = collectNew_Array(sizeof(iMenuItem)); 1298 /* This URL may refer to a file inside the archive. */
1304 pushBack_Array( 1299 localPath = findContainerArchive_Path(localPath);
1305 items, 1300 isInside = iTrue;
1306 &(iMenuItem){ book_Icon " ${gempub.cover.view}",
1307 0,
1308 0,
1309 format_CStr("!open url:%s",
1310 cstr_String(indexPageUrl_Gempub(d->sourceGempub))) });
1311 if (navSize_Gempub(d->sourceGempub) > 0) {
1312 pushBack_Array(
1313 items,
1314 &(iMenuItem){
1315 format_CStr(forwardArrow_Icon " %s",
1316 cstr_String(navLinkLabel_Gempub(d->sourceGempub, 0))),
1317 SDLK_RIGHT,
1318 0,
1319 format_CStr("!open url:%s",
1320 cstr_String(navLinkUrl_Gempub(d->sourceGempub, 0))) });
1321 }
1322 makeFooterButtons_DocumentWidget_(d, constData_Array(items), size_Array(items));
1323 } 1301 }
1324 else { 1302 if (localPath && equal_CStr(mediaType_Path(localPath), mimeType_Gempub)) {
1325 makeFooterButtons_DocumentWidget_( 1303 iGempub *gempub = new_Gempub();
1326 d, 1304 if (openFile_Gempub(gempub, localPath)) {
1327 (iMenuItem[]){ { book_Icon " ${menu.save.downloads.open}", 1305 setBaseUrl_Gempub(gempub, collect_String(makeFileUrl_String(localPath)));
1328 SDLK_s, 1306 if (!isInside) {
1329 KMOD_PRIMARY | KMOD_SHIFT, 1307 setSource_DocumentWidget(d, collect_String(coverPageSource_Gempub(gempub)));
1330 "document.save open:1" }, 1308 setCStr_String(&d->sourceMime, mimeType_Gempub);
1331 { download_Icon " " saveToDownloads_Label, 1309 }
1332 SDLK_s, 1310 d->sourceGempub = gempub;
1333 KMOD_PRIMARY, 1311 }
1334 "document.save" } }, 1312 else {
1335 2); 1313 delete_Gempub(gempub);
1336 } 1314 }
1337 if (preloadCoverImage_Gempub(d->sourceGempub, d->doc)) {
1338 redoLayout_GmDocument(d->doc);
1339 updateVisible_DocumentWidget_(d);
1340 invalidate_DocumentWidget_(d);
1341 } 1315 }
1342 } 1316 }
1343 else if (equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { 1317 if (d->sourceGempub) {
1344 makeFooterButtons_DocumentWidget_( 1318 if (equal_String(d->mod.url, coverPageUrl_Gempub(d->sourceGempub))) {
1345 d, 1319 if (!isRemote_Gempub(d->sourceGempub)) {
1346 (iMenuItem[]){ { format_CStr(book_Icon " %s", 1320 iArray *items = collectNew_Array(sizeof(iMenuItem));
1347 cstr_String(property_Gempub(d->sourceGempub,
1348 title_GempubProperty))),
1349 SDLK_LEFT,
1350 0,
1351 format_CStr("!open url:%s",
1352 cstr_String(coverPageUrl_Gempub(d->sourceGempub))) } },
1353 1);
1354 }
1355 else {
1356 /* Navigation buttons. */
1357 iArray *items = collectNew_Array(sizeof(iMenuItem));
1358 const size_t navIndex = navIndex_Gempub(d->sourceGempub, d->mod.url);
1359 if (navIndex != iInvalidPos) {
1360 if (navIndex < navSize_Gempub(d->sourceGempub) - 1) {
1361 pushBack_Array( 1321 pushBack_Array(
1362 items, 1322 items,
1363 &(iMenuItem){ 1323 &(iMenuItem){ book_Icon " ${gempub.cover.view}",
1364 format_CStr(forwardArrow_Icon " %s", 1324 0,
1365 cstr_String(navLinkLabel_Gempub(d->sourceGempub, navIndex + 1))), 1325 0,
1366 SDLK_RIGHT, 1326 format_CStr("!open url:%s",
1367 0, 1327 cstr_String(indexPageUrl_Gempub(d->sourceGempub))) });
1368 format_CStr("!open url:%s", 1328 if (navSize_Gempub(d->sourceGempub) > 0) {
1369 cstr_String(navLinkUrl_Gempub(d->sourceGempub, navIndex + 1))) }); 1329 pushBack_Array(
1330 items,
1331 &(iMenuItem){
1332 format_CStr(forwardArrow_Icon " %s",
1333 cstr_String(navLinkLabel_Gempub(d->sourceGempub, 0))),
1334 SDLK_RIGHT,
1335 0,
1336 format_CStr("!open url:%s",
1337 cstr_String(navLinkUrl_Gempub(d->sourceGempub, 0))) });
1338 }
1339 makeFooterButtons_DocumentWidget_(d, constData_Array(items), size_Array(items));
1370 } 1340 }
1371 if (navIndex > 0) { 1341 else {
1372 pushBack_Array( 1342 makeFooterButtons_DocumentWidget_(
1373 items, 1343 d,
1374 &(iMenuItem){ 1344 (iMenuItem[]){ { book_Icon " ${menu.save.downloads.open}",
1375 format_CStr(backArrow_Icon " %s", 1345 SDLK_s,
1376 cstr_String(navLinkLabel_Gempub(d->sourceGempub, navIndex - 1))), 1346 KMOD_PRIMARY | KMOD_SHIFT,
1377 SDLK_LEFT, 1347 "document.save open:1" },
1378 0, 1348 { download_Icon " " saveToDownloads_Label,
1379 format_CStr("!open url:%s", 1349 SDLK_s,
1380 cstr_String(navLinkUrl_Gempub(d->sourceGempub, navIndex - 1))) }); 1350 KMOD_PRIMARY,
1351 "document.save" } },
1352 2);
1381 } 1353 }
1382 else if (!equalCase_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { 1354 if (preloadCoverImage_Gempub(d->sourceGempub, d->doc)) {
1383 pushBack_Array( 1355 redoLayout_GmDocument(d->doc);
1384 items, 1356 updateVisible_DocumentWidget_(d);
1385 &(iMenuItem){ 1357 invalidate_DocumentWidget_(d);
1386 format_CStr(book_Icon " %s",
1387 cstr_String(property_Gempub(d->sourceGempub, title_GempubProperty))),
1388 SDLK_LEFT,
1389 0,
1390 format_CStr("!open url:%s",
1391 cstr_String(coverPageUrl_Gempub(d->sourceGempub))) });
1392 } 1358 }
1393 } 1359 }
1394 if (!isEmpty_Array(items)) { 1360 else if (equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) {
1395 makeFooterButtons_DocumentWidget_(d, constData_Array(items), size_Array(items)); 1361 makeFooterButtons_DocumentWidget_(
1362 d,
1363 (iMenuItem[]){ { format_CStr(book_Icon " %s",
1364 cstr_String(property_Gempub(d->sourceGempub,
1365 title_GempubProperty))),
1366 SDLK_LEFT,
1367 0,
1368 format_CStr("!open url:%s",
1369 cstr_String(coverPageUrl_Gempub(d->sourceGempub))) } },
1370 1);
1396 } 1371 }
1397 } 1372 else {
1398 if (!isCached && prefs_App()->pinSplit && 1373 /* Navigation buttons. */
1399 equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { 1374 iArray *items = collectNew_Array(sizeof(iMenuItem));
1400 const iString *navStart = navStartLinkUrl_Gempub(d->sourceGempub); 1375 const size_t navIndex = navIndex_Gempub(d->sourceGempub, d->mod.url);
1401 if (navStart) { 1376 if (navIndex != iInvalidPos) {
1402 iWindow *win = get_Window(); 1377 if (navIndex < navSize_Gempub(d->sourceGempub) - 1) {
1403 /* Auto-split to show index and the first navigation link. */ 1378 pushBack_Array(
1404 if (numRoots_Window(win) == 2) { 1379 items,
1405 /* This document is showing the index page. */ 1380 &(iMenuItem){
1406 iRoot *other = otherRoot_Window(win, w->root); 1381 format_CStr(forwardArrow_Icon " %s",
1407 postCommandf_Root(other, "open url:%s", cstr_String(navStart)); 1382 cstr_String(navLinkLabel_Gempub(d->sourceGempub, navIndex + 1))),
1408 if (prefs_App()->pinSplit == 1 && w->root == win->roots[1]) { 1383 SDLK_RIGHT,
1409 /* On the wrong side. */ 1384 0,
1410 postCommand_App("ui.split swap:1"); 1385 format_CStr("!open url:%s",
1386 cstr_String(navLinkUrl_Gempub(d->sourceGempub, navIndex + 1))) });
1387 }
1388 if (navIndex > 0) {
1389 pushBack_Array(
1390 items,
1391 &(iMenuItem){
1392 format_CStr(backArrow_Icon " %s",
1393 cstr_String(navLinkLabel_Gempub(d->sourceGempub, navIndex - 1))),
1394 SDLK_LEFT,
1395 0,
1396 format_CStr("!open url:%s",
1397 cstr_String(navLinkUrl_Gempub(d->sourceGempub, navIndex - 1))) });
1398 }
1399 else if (!equalCase_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) {
1400 pushBack_Array(
1401 items,
1402 &(iMenuItem){
1403 format_CStr(book_Icon " %s",
1404 cstr_String(property_Gempub(d->sourceGempub, title_GempubProperty))),
1405 SDLK_LEFT,
1406 0,
1407 format_CStr("!open url:%s",
1408 cstr_String(coverPageUrl_Gempub(d->sourceGempub))) });
1411 } 1409 }
1412 } 1410 }
1413 else { 1411 if (!isEmpty_Array(items)) {
1414 postCommandf_App( 1412 makeFooterButtons_DocumentWidget_(d, constData_Array(items), size_Array(items));
1415 "open newtab:%d url:%s", otherRoot_OpenTabFlag, cstr_String(navStart)); 1413 }
1414 }
1415 if (!isCached && prefs_App()->pinSplit &&
1416 equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) {
1417 const iString *navStart = navStartLinkUrl_Gempub(d->sourceGempub);
1418 if (navStart) {
1419 iWindow *win = get_Window();
1420 /* Auto-split to show index and the first navigation link. */
1421 if (numRoots_Window(win) == 2) {
1422 /* This document is showing the index page. */
1423 iRoot *other = otherRoot_Window(win, w->root);
1424 postCommandf_Root(other, "open url:%s", cstr_String(navStart));
1425 if (prefs_App()->pinSplit == 1 && w->root == win->roots[1]) {
1426 /* On the wrong side. */
1427 postCommand_App("ui.split swap:1");
1428 }
1429 }
1430 else {
1431 postCommandf_App(
1432 "open newtab:%d url:%s", otherRoot_OpenTabFlag, cstr_String(navStart));
1433 }
1416 } 1434 }
1417 } 1435 }
1418 } 1436 }
1419 } 1437 }
1438 /* Local fontpacks are automatically shown. */
1439 if (preloadLocalFontpackForPreview_Fonts(d->doc)) {
1440 documentRunsInvalidated_DocumentWidget_(d);
1441 redoLayout_GmDocument(d->doc);
1442 updateVisible_DocumentWidget_(d);
1443 invalidate_DocumentWidget_(d);
1444 }
1420} 1445}
1421 1446
1422static void updateDocument_DocumentWidget_(iDocumentWidget *d, 1447static void updateDocument_DocumentWidget_(iDocumentWidget *d,
@@ -1484,7 +1509,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d,
1484 } 1509 }
1485 delete_String(localPath); 1510 delete_String(localPath);
1486 if (equalCase_Rangecc(urlScheme_String(d->mod.url), "file")) { 1511 if (equalCase_Rangecc(urlScheme_String(d->mod.url), "file")) {
1487 appendFormat_String(&str, "=> %s/ ${doc.archive.view}\n", 1512 appendFormat_String(&str, "=> %s/ " folder_Icon " ${doc.archive.view}\n",
1488 cstr_String(withSpacesEncoded_String(d->mod.url))); 1513 cstr_String(withSpacesEncoded_String(d->mod.url)));
1489 } 1514 }
1490 translate_Lang(&str); 1515 translate_Lang(&str);
@@ -2089,7 +2114,7 @@ static iBool requestMedia_DocumentWidget_(iDocumentWidget *d, iGmLinkId linkId,
2089} 2114}
2090 2115
2091static iBool isDownloadRequest_DocumentWidget(const iDocumentWidget *d, const iMediaRequest *req) { 2116static iBool isDownloadRequest_DocumentWidget(const iDocumentWidget *d, const iMediaRequest *req) {
2092 return findLinkDownload_Media(constMedia_GmDocument(d->doc), req->linkId) != 0; 2117 return findMediaForLink_Media(constMedia_GmDocument(d->doc), req->linkId, download_MediaType).type != 0;
2093} 2118}
2094 2119
2095static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) { 2120static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) {
@@ -2174,7 +2199,7 @@ static void allocVisBuffer_DocumentWidget_(const iDocumentWidget *d) {
2174static iBool fetchNextUnfetchedImage_DocumentWidget_(iDocumentWidget *d) { 2199static iBool fetchNextUnfetchedImage_DocumentWidget_(iDocumentWidget *d) {
2175 iConstForEach(PtrArray, i, &d->visibleLinks) { 2200 iConstForEach(PtrArray, i, &d->visibleLinks) {
2176 const iGmRun *run = i.ptr; 2201 const iGmRun *run = i.ptr;
2177 if (run->linkId && run->mediaType == none_GmRunMediaType && 2202 if (run->linkId && run->mediaType == none_MediaType &&
2178 ~run->flags & decoration_GmRunFlag) { 2203 ~run->flags & decoration_GmRunFlag) {
2179 const int linkFlags = linkFlags_GmDocument(d->doc, run->linkId); 2204 const int linkFlags = linkFlags_GmDocument(d->doc, run->linkId);
2180 if (isMediaLink_GmDocument(d->doc, run->linkId) && 2205 if (isMediaLink_GmDocument(d->doc, run->linkId) &&
@@ -2763,8 +2788,10 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2763 else if (equalWidget_Command(cmd, w, "document.downloadlink")) { 2788 else if (equalWidget_Command(cmd, w, "document.downloadlink")) {
2764 if (d->contextLink) { 2789 if (d->contextLink) {
2765 const iGmLinkId linkId = d->contextLink->linkId; 2790 const iGmLinkId linkId = d->contextLink->linkId;
2766 setDownloadUrl_Media( 2791 setUrl_Media(media_GmDocument(d->doc),
2767 media_GmDocument(d->doc), linkId, linkUrl_GmDocument(d->doc, linkId)); 2792 linkId,
2793 download_MediaType,
2794 linkUrl_GmDocument(d->doc, linkId));
2768 requestMedia_DocumentWidget_(d, linkId, iFalse /* no filters */); 2795 requestMedia_DocumentWidget_(d, linkId, iFalse /* no filters */);
2769 redoLayout_GmDocument(d->doc); /* inline downloader becomes visible */ 2796 redoLayout_GmDocument(d->doc); /* inline downloader becomes visible */
2770 updateVisible_DocumentWidget_(d); 2797 updateVisible_DocumentWidget_(d);
@@ -2874,7 +2901,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2874 const iMedia * media = media_GmDocument(d->doc); 2901 const iMedia * media = media_GmDocument(d->doc);
2875 const size_t num = numAudio_Media(media); 2902 const size_t num = numAudio_Media(media);
2876 for (size_t id = 1; id <= num; id++) { 2903 for (size_t id = 1; id <= num; id++) {
2877 iPlayer *plr = audioPlayer_Media(media, id); 2904 iPlayer *plr = audioPlayer_Media(media, (iMediaId){ audio_MediaType, id });
2878 if (plr != startedPlr) { 2905 if (plr != startedPlr) {
2879 setPaused_Player(plr, iTrue); 2906 setPaused_Player(plr, iTrue);
2880 } 2907 }
@@ -3212,8 +3239,8 @@ static iRect runRect_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run
3212} 3239}
3213 3240
3214static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *run) { 3241static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *run) {
3215 if (run && run->mediaType == audio_GmRunMediaType) { 3242 if (run && run->mediaType == audio_MediaType) {
3216 iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), run->mediaId); 3243 iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run));
3217 setFlags_Player(plr, volumeGrabbed_PlayerFlag, iTrue); 3244 setFlags_Player(plr, volumeGrabbed_PlayerFlag, iTrue);
3218 d->grabbedStartVolume = volume_Player(plr); 3245 d->grabbedStartVolume = volume_Player(plr);
3219 d->grabbedPlayer = run; 3246 d->grabbedPlayer = run;
@@ -3221,7 +3248,7 @@ static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *r
3221 } 3248 }
3222 else if (d->grabbedPlayer) { 3249 else if (d->grabbedPlayer) {
3223 setFlags_Player( 3250 setFlags_Player(
3224 audioPlayer_Media(media_GmDocument(d->doc), d->grabbedPlayer->mediaId), 3251 audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(d->grabbedPlayer)),
3225 volumeGrabbed_PlayerFlag, 3252 volumeGrabbed_PlayerFlag,
3226 iFalse); 3253 iFalse);
3227 d->grabbedPlayer = NULL; 3254 d->grabbedPlayer = NULL;
@@ -3249,11 +3276,21 @@ static iBool processMediaEvents_DocumentWidget_(iDocumentWidget *d, const SDL_Ev
3249 const iInt2 mouse = init_I2(ev->button.x, ev->button.y); 3276 const iInt2 mouse = init_I2(ev->button.x, ev->button.y);
3250 iConstForEach(PtrArray, i, &d->visibleMedia) { 3277 iConstForEach(PtrArray, i, &d->visibleMedia) {
3251 const iGmRun *run = i.ptr; 3278 const iGmRun *run = i.ptr;
3252 if (run->mediaType != audio_GmRunMediaType) { 3279 if (run->mediaType == fontpack_MediaType) {
3280 iFontpackUI ui;
3281 init_FontpackUI(&ui, media_GmDocument(d->doc), run->mediaId,
3282 runRect_DocumentWidget_(d, run));
3283 if (processEvent_FontpackUI(&ui, ev)) {
3284 refresh_Widget(d);
3285 return iTrue;
3286 }
3287 }
3288 if (run->mediaType != audio_MediaType) {
3253 continue; 3289 continue;
3254 } 3290 }
3291 /* TODO: move this to mediaui.c */
3255 const iRect rect = runRect_DocumentWidget_(d, run); 3292 const iRect rect = runRect_DocumentWidget_(d, run);
3256 iPlayer * plr = audioPlayer_Media(media_GmDocument(d->doc), run->mediaId); 3293 iPlayer * plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run));
3257 if (contains_Rect(rect, mouse)) { 3294 if (contains_Rect(rect, mouse)) {
3258 iPlayerUI ui; 3295 iPlayerUI ui;
3259 init_PlayerUI(&ui, plr, rect); 3296 init_PlayerUI(&ui, plr, rect);
@@ -3633,7 +3670,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
3633 cstr_String(linkUrl)) }, 3670 cstr_String(linkUrl)) },
3634 }, 3671 },
3635 3); 3672 3);
3636 if (isNative && d->contextLink->mediaType != download_GmRunMediaType) { 3673 if (isNative && d->contextLink->mediaType != download_MediaType) {
3637 pushBackN_Array(&items, (iMenuItem[]){ 3674 pushBackN_Array(&items, (iMenuItem[]){
3638 { "---" }, 3675 { "---" },
3639 { download_Icon " ${link.download}", 0, 0, "document.downloadlink" }, 3676 { download_Icon " ${link.download}", 0, 0, "document.downloadlink" },
@@ -3641,7 +3678,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
3641 } 3678 }
3642 iMediaRequest *mediaReq; 3679 iMediaRequest *mediaReq;
3643 if ((mediaReq = findMediaRequest_DocumentWidget_(d, d->contextLink->linkId)) != NULL && 3680 if ((mediaReq = findMediaRequest_DocumentWidget_(d, d->contextLink->linkId)) != NULL &&
3644 d->contextLink->mediaType != download_GmRunMediaType) { 3681 d->contextLink->mediaType != download_MediaType) {
3645 if (isFinished_GmRequest(mediaReq->req)) { 3682 if (isFinished_GmRequest(mediaReq->req)) {
3646 pushBack_Array(&items, 3683 pushBack_Array(&items,
3647 &(iMenuItem){ download_Icon " " saveToDownloads_Label, 3684 &(iMenuItem){ download_Icon " " saveToDownloads_Label,
@@ -3763,7 +3800,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
3763 case drag_ClickResult: { 3800 case drag_ClickResult: {
3764 if (d->grabbedPlayer) { 3801 if (d->grabbedPlayer) {
3765 iPlayer *plr = 3802 iPlayer *plr =
3766 audioPlayer_Media(media_GmDocument(d->doc), d->grabbedPlayer->mediaId); 3803 audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(d->grabbedPlayer));
3767 iPlayerUI ui; 3804 iPlayerUI ui;
3768 init_PlayerUI(&ui, plr, runRect_DocumentWidget_(d, d->grabbedPlayer)); 3805 init_PlayerUI(&ui, plr, runRect_DocumentWidget_(d, d->grabbedPlayer));
3769 float off = (float) delta_Click(&d->click).x / (float) width_Rect(ui.volumeSlider); 3806 float off = (float) delta_Click(&d->click).x / (float) width_Rect(ui.volumeSlider);
@@ -3883,8 +3920,9 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
3883 } 3920 }
3884 if (d->hoverLink) { 3921 if (d->hoverLink) {
3885 /* TODO: Move this to a method. */ 3922 /* TODO: Move this to a method. */
3886 const iGmLinkId linkId = d->hoverLink->linkId; 3923 const iGmLinkId linkId = d->hoverLink->linkId;
3887 const int linkFlags = linkFlags_GmDocument(d->doc, linkId); 3924 const iMediaId linkMedia = mediaId_GmRun(d->hoverLink);
3925 const int linkFlags = linkFlags_GmDocument(d->doc, linkId);
3888 iAssert(linkId); 3926 iAssert(linkId);
3889 /* Media links are opened inline by default. */ 3927 /* Media links are opened inline by default. */
3890 if (isMediaLink_GmDocument(d->doc, linkId)) { 3928 if (isMediaLink_GmDocument(d->doc, linkId)) {
@@ -3937,6 +3975,12 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
3937 } 3975 }
3938 refresh_Widget(w); 3976 refresh_Widget(w);
3939 } 3977 }
3978 else if (linkMedia.type == download_MediaType ||
3979 findMediaRequest_DocumentWidget_(d, linkId)) {
3980 /* TODO: What should be done when clicking on an inline download?
3981 Maybe dismiss if finished? */
3982 return iTrue;
3983 }
3940 else if (linkFlags & supportedScheme_GmLinkFlag) { 3984 else if (linkFlags & supportedScheme_GmLinkFlag) {
3941 int tabMode = openTabMode_Sym(modState_Keys()); 3985 int tabMode = openTabMode_Sym(modState_Keys());
3942 if (isPinned_DocumentWidget_(d)) { 3986 if (isPinned_DocumentWidget_(d)) {
@@ -4071,7 +4115,7 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol
4071 4115
4072static void drawMark_DrawContext_(void *context, const iGmRun *run) { 4116static void drawMark_DrawContext_(void *context, const iGmRun *run) {
4073 iDrawContext *d = context; 4117 iDrawContext *d = context;
4074 if (run->mediaType == none_GmRunMediaType) { 4118 if (run->mediaType == none_MediaType) {
4075 fillRange_DrawContext_(d, run, uiMatching_ColorId, d->widget->foundMark, &d->inFoundMark); 4119 fillRange_DrawContext_(d, run, uiMatching_ColorId, d->widget->foundMark, &d->inFoundMark);
4076 fillRange_DrawContext_(d, run, uiMarked_ColorId, d->widget->selectMark, &d->inSelectMark); 4120 fillRange_DrawContext_(d, run, uiMarked_ColorId, d->widget->selectMark, &d->inSelectMark);
4077 } 4121 }
@@ -4178,8 +4222,8 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4178 d->runsDrawn.end = run; 4222 d->runsDrawn.end = run;
4179 } 4223 }
4180 } 4224 }
4181 if (run->mediaType == image_GmRunMediaType) { 4225 if (run->mediaType == image_MediaType) {
4182 SDL_Texture *tex = imageTexture_Media(media_GmDocument(d->widget->doc), run->mediaId); 4226 SDL_Texture *tex = imageTexture_Media(media_GmDocument(d->widget->doc), mediaId_GmRun(run));
4183 const iRect dst = moved_Rect(run->visBounds, origin); 4227 const iRect dst = moved_Rect(run->visBounds, origin);
4184 if (tex) { 4228 if (tex) {
4185 fillRect_Paint(&d->paint, dst, tmBackground_ColorId); /* in case the image has alpha */ 4229 fillRect_Paint(&d->paint, dst, tmBackground_ColorId); /* in case the image has alpha */
@@ -4334,31 +4378,31 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4334 fg = linkColor_GmDocument(doc, run->linkId, textHover_GmLinkPart); 4378 fg = linkColor_GmDocument(doc, run->linkId, textHover_GmLinkPart);
4335 iString text; 4379 iString text;
4336 init_String(&text); 4380 init_String(&text);
4337 iMediaId imageId = linkImage_GmDocument(doc, run->linkId); 4381 const iMediaId linkMedia = findMediaForLink_Media(constMedia_GmDocument(doc),
4338 iMediaId audioId = !imageId ? linkAudio_GmDocument(doc, run->linkId) : 0; 4382 run->linkId, none_MediaType);
4339 iMediaId downloadId = !imageId && !audioId ? 4383 iAssert(linkMedia.type != none_MediaType);
4340 findLinkDownload_Media(constMedia_GmDocument(doc), run->linkId) : 0; 4384 iGmMediaInfo info;
4341 iAssert(imageId || audioId || downloadId); 4385 info_Media(constMedia_GmDocument(doc), linkMedia, &info);
4342 if (imageId) { 4386 switch (linkMedia.type) {
4343 iAssert(!isEmpty_Rect(run->bounds)); 4387 case image_MediaType: {
4344 iGmMediaInfo info; 4388 iAssert(!isEmpty_Rect(run->bounds));
4345 imageInfo_Media(constMedia_GmDocument(doc), imageId, &info); 4389 const iInt2 imgSize = imageSize_Media(constMedia_GmDocument(doc), linkMedia);
4346 const iInt2 imgSize = imageSize_Media(constMedia_GmDocument(doc), imageId); 4390 format_String(&text, "%s \u2014 %d x %d \u2014 %.1f%s",
4347 format_String(&text, "%s \u2014 %d x %d \u2014 %.1f%s", 4391 info.type, imgSize.x, imgSize.y, info.numBytes / 1.0e6f,
4348 info.type, imgSize.x, imgSize.y, info.numBytes / 1.0e6f, 4392 cstr_Lang("mb"));
4349 cstr_Lang("mb")); 4393 break;
4350 } 4394 }
4351 else if (audioId) { 4395 case audio_MediaType:
4352 iGmMediaInfo info; 4396 format_String(&text, "%s", info.type);
4353 audioInfo_Media(constMedia_GmDocument(doc), audioId, &info); 4397 break;
4354 format_String(&text, "%s", info.type); 4398 case download_MediaType:
4355 } 4399 format_String(&text, "%s", info.type);
4356 else if (downloadId) { 4400 break;
4357 iGmMediaInfo info; 4401 default:
4358 downloadInfo_Media(constMedia_GmDocument(doc), downloadId, &info); 4402 break;
4359 format_String(&text, "%s", info.type);
4360 } 4403 }
4361 if (findMediaRequest_DocumentWidget_(d->widget, run->linkId)) { 4404 if (linkMedia.type != download_MediaType && /* can't cancel downloads currently */
4405 findMediaRequest_DocumentWidget_(d->widget, run->linkId)) {
4362 appendFormat_String( 4406 appendFormat_String(
4363 &text, " %s" close_Icon, isHover ? escape_Color(tmLinkText_ColorId) : ""); 4407 &text, " %s" close_Icon, isHover ? escape_Color(tmLinkText_ColorId) : "");
4364 } 4408 }
@@ -4589,18 +4633,25 @@ static void drawSideElements_DocumentWidget_(const iDocumentWidget *d) {
4589static void drawMedia_DocumentWidget_(const iDocumentWidget *d, iPaint *p) { 4633static void drawMedia_DocumentWidget_(const iDocumentWidget *d, iPaint *p) {
4590 iConstForEach(PtrArray, i, &d->visibleMedia) { 4634 iConstForEach(PtrArray, i, &d->visibleMedia) {
4591 const iGmRun * run = i.ptr; 4635 const iGmRun * run = i.ptr;
4592 if (run->mediaType == audio_GmRunMediaType) { 4636 if (run->mediaType == audio_MediaType) {
4593 iPlayerUI ui; 4637 iPlayerUI ui;
4594 init_PlayerUI(&ui, 4638 init_PlayerUI(&ui,
4595 audioPlayer_Media(media_GmDocument(d->doc), run->mediaId), 4639 audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run)),
4596 runRect_DocumentWidget_(d, run)); 4640 runRect_DocumentWidget_(d, run));
4597 draw_PlayerUI(&ui, p); 4641 draw_PlayerUI(&ui, p);
4598 } 4642 }
4599 else if (run->mediaType == download_GmRunMediaType) { 4643 else if (run->mediaType == download_MediaType) {
4600 iDownloadUI ui; 4644 iDownloadUI ui;
4601 init_DownloadUI(&ui, d, run->mediaId, runRect_DocumentWidget_(d, run)); 4645 init_DownloadUI(&ui, constMedia_GmDocument(d->doc), run->mediaId,
4646 runRect_DocumentWidget_(d, run));
4602 draw_DownloadUI(&ui, p); 4647 draw_DownloadUI(&ui, p);
4603 } 4648 }
4649 else if (run->mediaType == fontpack_MediaType) {
4650 iFontpackUI ui;
4651 init_FontpackUI(&ui, constMedia_GmDocument(d->doc), run->mediaId,
4652 runRect_DocumentWidget_(d, run));
4653 draw_FontpackUI(&ui, p);
4654 }
4604 } 4655 }
4605} 4656}
4606 4657