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.c483
1 files changed, 268 insertions, 215 deletions
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 93b2166b..ee669c1a 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);
@@ -496,7 +507,8 @@ static int documentWidth_DocumentWidget_(const iDocumentWidget *d) {
496 -1.0f, 10.0f); /* adapt to width */ 507 -1.0f, 10.0f); /* adapt to width */
497 //printf("%f\n", adjust); fflush(stdout); 508 //printf("%f\n", adjust); fflush(stdout);
498 return iMini(iMax(minWidth, bounds.size.x - gap_UI * (d->pageMargin + adjust) * 2), 509 return iMini(iMax(minWidth, bounds.size.x - gap_UI * (d->pageMargin + adjust) * 2),
499 fontSize_UI * prefs->lineWidth * prefs->zoomPercent / 100); 510 fontSize_UI * //emRatio_Text(paragraph_FontId) * /* dependent on avg. glyph width */
511 prefs->lineWidth * prefs->zoomPercent / 100);
500} 512}
501 513
502static iRect documentBounds_DocumentWidget_(const iDocumentWidget *d) { 514static iRect documentBounds_DocumentWidget_(const iDocumentWidget *d) {
@@ -563,7 +575,8 @@ static void addVisible_DocumentWidget_(void *context, const iGmRun *run) {
563 pushBack_PtrArray(&d->visibleWideRuns, run); 575 pushBack_PtrArray(&d->visibleWideRuns, run);
564 } 576 }
565 } 577 }
566 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) {
567 iAssert(run->mediaId); 580 iAssert(run->mediaId);
568 pushBack_PtrArray(&d->visibleMedia, run); 581 pushBack_PtrArray(&d->visibleMedia, run);
569 } 582 }
@@ -757,14 +770,14 @@ static uint32_t mediaUpdateInterval_DocumentWidget_(const iDocumentWidget *d) {
757 uint32_t interval = invalidInterval_; 770 uint32_t interval = invalidInterval_;
758 iConstForEach(PtrArray, i, &d->visibleMedia) { 771 iConstForEach(PtrArray, i, &d->visibleMedia) {
759 const iGmRun *run = i.ptr; 772 const iGmRun *run = i.ptr;
760 if (run->mediaType == audio_GmRunMediaType) { 773 if (run->mediaType == audio_MediaType) {
761 iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), run->mediaId); 774 iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run));
762 if (flags_Player(plr) & adjustingVolume_PlayerFlag || 775 if (flags_Player(plr) & adjustingVolume_PlayerFlag ||
763 (isStarted_Player(plr) && !isPaused_Player(plr))) { 776 (isStarted_Player(plr) && !isPaused_Player(plr))) {
764 interval = iMin(interval, 1000 / 15); 777 interval = iMin(interval, 1000 / 15);
765 } 778 }
766 } 779 }
767 else if (run->mediaType == download_GmRunMediaType) { 780 else if (run->mediaType == download_MediaType) {
768 interval = iMin(interval, 1000); 781 interval = iMin(interval, 1000);
769 } 782 }
770 } 783 }
@@ -783,8 +796,8 @@ static void updateMedia_DocumentWidget_(iDocumentWidget *d) {
783 refresh_Widget(d); 796 refresh_Widget(d);
784 iConstForEach(PtrArray, i, &d->visibleMedia) { 797 iConstForEach(PtrArray, i, &d->visibleMedia) {
785 const iGmRun *run = i.ptr; 798 const iGmRun *run = i.ptr;
786 if (run->mediaType == audio_GmRunMediaType) { 799 if (run->mediaType == audio_MediaType) {
787 iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), run->mediaId); 800 iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run));
788 if (idleTimeMs_Player(plr) > 3000 && ~flags_Player(plr) & volumeGrabbed_PlayerFlag && 801 if (idleTimeMs_Player(plr) > 3000 && ~flags_Player(plr) & volumeGrabbed_PlayerFlag &&
789 flags_Player(plr) & adjustingVolume_PlayerFlag) { 802 flags_Player(plr) & adjustingVolume_PlayerFlag) {
790 setFlags_Player(plr, adjustingVolume_PlayerFlag, iFalse); 803 setFlags_Player(plr, adjustingVolume_PlayerFlag, iFalse);
@@ -928,8 +941,9 @@ static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) {
928 pushBackCStr_StringArray(title, "Lagrange"); 941 pushBackCStr_StringArray(title, "Lagrange");
929 } 942 }
930 /* Take away parts if it doesn't fit. */ 943 /* Take away parts if it doesn't fit. */
931 const int avail = bounds_Widget(as_Widget(tabButton)).size.x - 3 * gap_UI; 944 const int avail = bounds_Widget(as_Widget(tabButton)).size.x - 3 * gap_UI;
932 iBool setWindow = (document_App() == d && isUnderKeyRoot_Widget(d)); 945 iBool setWindow = (document_App() == d && isUnderKeyRoot_Widget(d));
946 const int font = uiLabel_FontId;
933 for (;;) { 947 for (;;) {
934 iString *text = collect_String(joinCStr_StringArray(title, " \u2014 ")); 948 iString *text = collect_String(joinCStr_StringArray(title, " \u2014 "));
935 if (setWindow) { 949 if (setWindow) {
@@ -945,7 +959,7 @@ static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) {
945 prependChar_String(text, siteIcon); 959 prependChar_String(text, siteIcon);
946 prependCStr_String(text, escape_Color(uiIcon_ColorId)); 960 prependCStr_String(text, escape_Color(uiIcon_ColorId));
947 } 961 }
948 const int width = measureRange_Text(default_FontId, range_String(text)).advance.x; 962 const int width = measureRange_Text(font, range_String(text)).advance.x;
949 if (width <= avail || 963 if (width <= avail ||
950 isEmpty_StringArray(title)) { 964 isEmpty_StringArray(title)) {
951 updateText_LabelWidget(tabButton, text); 965 updateText_LabelWidget(tabButton, text);
@@ -954,9 +968,9 @@ static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) {
954 if (size_StringArray(title) == 1) { 968 if (size_StringArray(title) == 1) {
955 /* Just truncate to fit. */ 969 /* Just truncate to fit. */
956 const char *endPos; 970 const char *endPos;
957 tryAdvanceNoWrap_Text(default_FontId, 971 tryAdvanceNoWrap_Text(font,
958 range_String(text), 972 range_String(text),
959 avail - measure_Text(default_FontId, "...").advance.x, 973 avail - measure_Text(font, "...").advance.x,
960 &endPos); 974 &endPos);
961 updateText_LabelWidget( 975 updateText_LabelWidget(
962 tabButton, 976 tabButton,
@@ -1242,6 +1256,9 @@ static const char *zipPageHeading_(const iRangecc mime) {
1242 if (equalCase_Rangecc(mime, "application/gpub+zip")) { 1256 if (equalCase_Rangecc(mime, "application/gpub+zip")) {
1243 return book_Icon " Gempub"; 1257 return book_Icon " Gempub";
1244 } 1258 }
1259 else if (equalCase_Rangecc(mime, mimeType_FontPack)) {
1260 return "\U0001f520 Fontpack";
1261 }
1245 iRangecc type = iNullRange; 1262 iRangecc type = iNullRange;
1246 nextSplit_Rangecc(mime, "/", &type); /* skip the part before the slash */ 1263 nextSplit_Rangecc(mime, "/", &type); /* skip the part before the slash */
1247 nextSplit_Rangecc(mime, "/", &type); 1264 nextSplit_Rangecc(mime, "/", &type);
@@ -1256,165 +1273,175 @@ static const char *zipPageHeading_(const iRangecc mime) {
1256 1273
1257static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool isCached) { 1274static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool isCached) {
1258 iWidget *w = as_Widget(d); 1275 iWidget *w = as_Widget(d);
1259 delete_Gempub(d->sourceGempub); 1276 /* Gempub page behavior and footer actions. */ {
1260 d->sourceGempub = NULL; 1277 /* TODO: move this to gempub.c */
1261 if (!cmpCase_String(&d->sourceMime, "application/octet-stream") || 1278 delete_Gempub(d->sourceGempub);
1262 !cmpCase_String(&d->sourceMime, mimeType_Gempub) || 1279 d->sourceGempub = NULL;
1263 endsWithCase_String(d->mod.url, ".gpub")) { 1280 if (!cmpCase_String(&d->sourceMime, "application/octet-stream") ||
1264 iGempub *gempub = new_Gempub(); 1281 !cmpCase_String(&d->sourceMime, mimeType_Gempub) ||
1265 if (open_Gempub(gempub, &d->sourceContent)) { 1282 endsWithCase_String(d->mod.url, ".gpub")) {
1266 setBaseUrl_Gempub(gempub, d->mod.url);
1267 setSource_DocumentWidget(d, collect_String(coverPageSource_Gempub(gempub)));
1268 setCStr_String(&d->sourceMime, mimeType_Gempub);
1269 d->sourceGempub = gempub;
1270 }
1271 else {
1272 delete_Gempub(gempub);
1273 }
1274 }
1275 if (!d->sourceGempub) {
1276 const iString *localPath = collect_String(localFilePathFromUrl_String(d->mod.url));
1277 iBool isInside = iFalse;
1278 if (localPath && !fileExists_FileInfo(localPath)) {
1279 /* This URL may refer to a file inside the archive. */
1280 localPath = findContainerArchive_Path(localPath);
1281 isInside = iTrue;
1282 }
1283 if (localPath && equal_CStr(mediaType_Path(localPath), "application/gpub+zip")) {
1284 iGempub *gempub = new_Gempub(); 1283 iGempub *gempub = new_Gempub();
1285 if (openFile_Gempub(gempub, localPath)) { 1284 if (open_Gempub(gempub, &d->sourceContent)) {
1286 setBaseUrl_Gempub(gempub, collect_String(makeFileUrl_String(localPath))); 1285 setBaseUrl_Gempub(gempub, d->mod.url);
1287 if (!isInside) { 1286 setSource_DocumentWidget(d, collect_String(coverPageSource_Gempub(gempub)));
1288 setSource_DocumentWidget(d, collect_String(coverPageSource_Gempub(gempub))); 1287 setCStr_String(&d->sourceMime, mimeType_Gempub);
1289 setCStr_String(&d->sourceMime, mimeType_Gempub);
1290 }
1291 d->sourceGempub = gempub; 1288 d->sourceGempub = gempub;
1292 } 1289 }
1293 else { 1290 else {
1294 delete_Gempub(gempub); 1291 delete_Gempub(gempub);
1295 } 1292 }
1296 } 1293 }
1297 } 1294 if (!d->sourceGempub) {
1298 if (d->sourceGempub) { 1295 const iString *localPath = collect_String(localFilePathFromUrl_String(d->mod.url));
1299 if (equal_String(d->mod.url, coverPageUrl_Gempub(d->sourceGempub))) { 1296 iBool isInside = iFalse;
1300 if (!isRemote_Gempub(d->sourceGempub)) { 1297 if (localPath && !fileExists_FileInfo(localPath)) {
1301 iArray *items = collectNew_Array(sizeof(iMenuItem)); 1298 /* This URL may refer to a file inside the archive. */
1302 pushBack_Array( 1299 localPath = findContainerArchive_Path(localPath);
1303 items, 1300 isInside = iTrue;
1304 &(iMenuItem){ book_Icon " ${gempub.cover.view}",
1305 0,
1306 0,
1307 format_CStr("!open url:%s",
1308 cstr_String(indexPageUrl_Gempub(d->sourceGempub))) });
1309 if (navSize_Gempub(d->sourceGempub) > 0) {
1310 pushBack_Array(
1311 items,
1312 &(iMenuItem){
1313 format_CStr(forwardArrow_Icon " %s",
1314 cstr_String(navLinkLabel_Gempub(d->sourceGempub, 0))),
1315 SDLK_RIGHT,
1316 0,
1317 format_CStr("!open url:%s",
1318 cstr_String(navLinkUrl_Gempub(d->sourceGempub, 0))) });
1319 }
1320 makeFooterButtons_DocumentWidget_(d, constData_Array(items), size_Array(items));
1321 } 1301 }
1322 else { 1302 if (localPath && equal_CStr(mediaType_Path(localPath), mimeType_Gempub)) {
1323 makeFooterButtons_DocumentWidget_( 1303 iGempub *gempub = new_Gempub();
1324 d, 1304 if (openFile_Gempub(gempub, localPath)) {
1325 (iMenuItem[]){ { book_Icon " ${menu.save.downloads.open}", 1305 setBaseUrl_Gempub(gempub, collect_String(makeFileUrl_String(localPath)));
1326 SDLK_s, 1306 if (!isInside) {
1327 KMOD_PRIMARY | KMOD_SHIFT, 1307 setSource_DocumentWidget(d, collect_String(coverPageSource_Gempub(gempub)));
1328 "document.save open:1" }, 1308 setCStr_String(&d->sourceMime, mimeType_Gempub);
1329 { download_Icon " " saveToDownloads_Label, 1309 }
1330 SDLK_s, 1310 d->sourceGempub = gempub;
1331 KMOD_PRIMARY, 1311 }
1332 "document.save" } }, 1312 else {
1333 2); 1313 delete_Gempub(gempub);
1334 } 1314 }
1335 if (preloadCoverImage_Gempub(d->sourceGempub, d->doc)) {
1336 redoLayout_GmDocument(d->doc);
1337 updateVisible_DocumentWidget_(d);
1338 invalidate_DocumentWidget_(d);
1339 } 1315 }
1340 } 1316 }
1341 else if (equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { 1317 if (d->sourceGempub) {
1342 makeFooterButtons_DocumentWidget_( 1318 if (equal_String(d->mod.url, coverPageUrl_Gempub(d->sourceGempub))) {
1343 d, 1319 if (!isRemote_Gempub(d->sourceGempub)) {
1344 (iMenuItem[]){ { format_CStr(book_Icon " %s", 1320 iArray *items = collectNew_Array(sizeof(iMenuItem));
1345 cstr_String(property_Gempub(d->sourceGempub,
1346 title_GempubProperty))),
1347 SDLK_LEFT,
1348 0,
1349 format_CStr("!open url:%s",
1350 cstr_String(coverPageUrl_Gempub(d->sourceGempub))) } },
1351 1);
1352 }
1353 else {
1354 /* Navigation buttons. */
1355 iArray *items = collectNew_Array(sizeof(iMenuItem));
1356 const size_t navIndex = navIndex_Gempub(d->sourceGempub, d->mod.url);
1357 if (navIndex != iInvalidPos) {
1358 if (navIndex < navSize_Gempub(d->sourceGempub) - 1) {
1359 pushBack_Array( 1321 pushBack_Array(
1360 items, 1322 items,
1361 &(iMenuItem){ 1323 &(iMenuItem){ book_Icon " ${gempub.cover.view}",
1362 format_CStr(forwardArrow_Icon " %s", 1324 0,
1363 cstr_String(navLinkLabel_Gempub(d->sourceGempub, navIndex + 1))), 1325 0,
1364 SDLK_RIGHT, 1326 format_CStr("!open url:%s",
1365 0, 1327 cstr_String(indexPageUrl_Gempub(d->sourceGempub))) });
1366 format_CStr("!open url:%s", 1328 if (navSize_Gempub(d->sourceGempub) > 0) {
1367 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));
1368 } 1340 }
1369 if (navIndex > 0) { 1341 else {
1370 pushBack_Array( 1342 makeFooterButtons_DocumentWidget_(
1371 items, 1343 d,
1372 &(iMenuItem){ 1344 (iMenuItem[]){ { book_Icon " ${menu.save.downloads.open}",
1373 format_CStr(backArrow_Icon " %s", 1345 SDLK_s,
1374 cstr_String(navLinkLabel_Gempub(d->sourceGempub, navIndex - 1))), 1346 KMOD_PRIMARY | KMOD_SHIFT,
1375 SDLK_LEFT, 1347 "document.save open:1" },
1376 0, 1348 { download_Icon " " saveToDownloads_Label,
1377 format_CStr("!open url:%s", 1349 SDLK_s,
1378 cstr_String(navLinkUrl_Gempub(d->sourceGempub, navIndex - 1))) }); 1350 KMOD_PRIMARY,
1351 "document.save" } },
1352 2);
1379 } 1353 }
1380 else if (!equalCase_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { 1354 if (preloadCoverImage_Gempub(d->sourceGempub, d->doc)) {
1381 pushBack_Array( 1355 redoLayout_GmDocument(d->doc);
1382 items, 1356 updateVisible_DocumentWidget_(d);
1383 &(iMenuItem){ 1357 invalidate_DocumentWidget_(d);
1384 format_CStr(book_Icon " %s",
1385 cstr_String(property_Gempub(d->sourceGempub, title_GempubProperty))),
1386 SDLK_LEFT,
1387 0,
1388 format_CStr("!open url:%s",
1389 cstr_String(coverPageUrl_Gempub(d->sourceGempub))) });
1390 } 1358 }
1391 } 1359 }
1392 if (!isEmpty_Array(items)) { 1360 else if (equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) {
1393 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);
1394 } 1371 }
1395 } 1372 else {
1396 if (!isCached && prefs_App()->pinSplit && 1373 /* Navigation buttons. */
1397 equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { 1374 iArray *items = collectNew_Array(sizeof(iMenuItem));
1398 const iString *navStart = navStartLinkUrl_Gempub(d->sourceGempub); 1375 const size_t navIndex = navIndex_Gempub(d->sourceGempub, d->mod.url);
1399 if (navStart) { 1376 if (navIndex != iInvalidPos) {
1400 iWindow *win = get_Window(); 1377 if (navIndex < navSize_Gempub(d->sourceGempub) - 1) {
1401 /* Auto-split to show index and the first navigation link. */ 1378 pushBack_Array(
1402 if (numRoots_Window(win) == 2) { 1379 items,
1403 /* This document is showing the index page. */ 1380 &(iMenuItem){
1404 iRoot *other = otherRoot_Window(win, w->root); 1381 format_CStr(forwardArrow_Icon " %s",
1405 postCommandf_Root(other, "open url:%s", cstr_String(navStart)); 1382 cstr_String(navLinkLabel_Gempub(d->sourceGempub, navIndex + 1))),
1406 if (prefs_App()->pinSplit == 1 && w->root == win->roots[1]) { 1383 SDLK_RIGHT,
1407 /* On the wrong side. */ 1384 0,
1408 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))) });
1409 } 1409 }
1410 } 1410 }
1411 else { 1411 if (!isEmpty_Array(items)) {
1412 postCommandf_App( 1412 makeFooterButtons_DocumentWidget_(d, constData_Array(items), size_Array(items));
1413 "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 }
1414 } 1434 }
1415 } 1435 }
1416 } 1436 }
1417 } 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 }
1418} 1445}
1419 1446
1420static void updateDocument_DocumentWidget_(iDocumentWidget *d, 1447static void updateDocument_DocumentWidget_(iDocumentWidget *d,
@@ -1482,7 +1509,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d,
1482 } 1509 }
1483 delete_String(localPath); 1510 delete_String(localPath);
1484 if (equalCase_Rangecc(urlScheme_String(d->mod.url), "file")) { 1511 if (equalCase_Rangecc(urlScheme_String(d->mod.url), "file")) {
1485 appendFormat_String(&str, "=> %s/ ${doc.archive.view}\n", 1512 appendFormat_String(&str, "=> %s/ " folder_Icon " ${doc.archive.view}\n",
1486 cstr_String(withSpacesEncoded_String(d->mod.url))); 1513 cstr_String(withSpacesEncoded_String(d->mod.url)));
1487 } 1514 }
1488 translate_Lang(&str); 1515 translate_Lang(&str);
@@ -1619,7 +1646,7 @@ static void parseUser_DocumentWidget_(iDocumentWidget *d) {
1619static void cacheRunGlyphs_(void *data, const iGmRun *run) { 1646static void cacheRunGlyphs_(void *data, const iGmRun *run) {
1620 iUnused(data); 1647 iUnused(data);
1621 if (!isEmpty_Range(&run->text)) { 1648 if (!isEmpty_Range(&run->text)) {
1622 cache_Text(run->textParams.font, run->text); 1649 cache_Text(run->font, run->text);
1623 } 1650 }
1624} 1651}
1625 1652
@@ -2087,7 +2114,7 @@ static iBool requestMedia_DocumentWidget_(iDocumentWidget *d, iGmLinkId linkId,
2087} 2114}
2088 2115
2089static iBool isDownloadRequest_DocumentWidget(const iDocumentWidget *d, const iMediaRequest *req) { 2116static iBool isDownloadRequest_DocumentWidget(const iDocumentWidget *d, const iMediaRequest *req) {
2090 return findLinkDownload_Media(constMedia_GmDocument(d->doc), req->linkId) != 0; 2117 return findMediaForLink_Media(constMedia_GmDocument(d->doc), req->linkId, download_MediaType).type != 0;
2091} 2118}
2092 2119
2093static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) { 2120static iBool handleMediaCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) {
@@ -2172,7 +2199,7 @@ static void allocVisBuffer_DocumentWidget_(const iDocumentWidget *d) {
2172static iBool fetchNextUnfetchedImage_DocumentWidget_(iDocumentWidget *d) { 2199static iBool fetchNextUnfetchedImage_DocumentWidget_(iDocumentWidget *d) {
2173 iConstForEach(PtrArray, i, &d->visibleLinks) { 2200 iConstForEach(PtrArray, i, &d->visibleLinks) {
2174 const iGmRun *run = i.ptr; 2201 const iGmRun *run = i.ptr;
2175 if (run->linkId && run->mediaType == none_GmRunMediaType && 2202 if (run->linkId && run->mediaType == none_MediaType &&
2176 ~run->flags & decoration_GmRunFlag) { 2203 ~run->flags & decoration_GmRunFlag) {
2177 const int linkFlags = linkFlags_GmDocument(d->doc, run->linkId); 2204 const int linkFlags = linkFlags_GmDocument(d->doc, run->linkId);
2178 if (isMediaLink_GmDocument(d->doc, run->linkId) && 2205 if (isMediaLink_GmDocument(d->doc, run->linkId) &&
@@ -2761,8 +2788,10 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2761 else if (equalWidget_Command(cmd, w, "document.downloadlink")) { 2788 else if (equalWidget_Command(cmd, w, "document.downloadlink")) {
2762 if (d->contextLink) { 2789 if (d->contextLink) {
2763 const iGmLinkId linkId = d->contextLink->linkId; 2790 const iGmLinkId linkId = d->contextLink->linkId;
2764 setDownloadUrl_Media( 2791 setUrl_Media(media_GmDocument(d->doc),
2765 media_GmDocument(d->doc), linkId, linkUrl_GmDocument(d->doc, linkId)); 2792 linkId,
2793 download_MediaType,
2794 linkUrl_GmDocument(d->doc, linkId));
2766 requestMedia_DocumentWidget_(d, linkId, iFalse /* no filters */); 2795 requestMedia_DocumentWidget_(d, linkId, iFalse /* no filters */);
2767 redoLayout_GmDocument(d->doc); /* inline downloader becomes visible */ 2796 redoLayout_GmDocument(d->doc); /* inline downloader becomes visible */
2768 updateVisible_DocumentWidget_(d); 2797 updateVisible_DocumentWidget_(d);
@@ -2872,7 +2901,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
2872 const iMedia * media = media_GmDocument(d->doc); 2901 const iMedia * media = media_GmDocument(d->doc);
2873 const size_t num = numAudio_Media(media); 2902 const size_t num = numAudio_Media(media);
2874 for (size_t id = 1; id <= num; id++) { 2903 for (size_t id = 1; id <= num; id++) {
2875 iPlayer *plr = audioPlayer_Media(media, id); 2904 iPlayer *plr = audioPlayer_Media(media, (iMediaId){ audio_MediaType, id });
2876 if (plr != startedPlr) { 2905 if (plr != startedPlr) {
2877 setPaused_Player(plr, iTrue); 2906 setPaused_Player(plr, iTrue);
2878 } 2907 }
@@ -3210,8 +3239,8 @@ static iRect runRect_DocumentWidget_(const iDocumentWidget *d, const iGmRun *run
3210} 3239}
3211 3240
3212static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *run) { 3241static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *run) {
3213 if (run && run->mediaType == audio_GmRunMediaType) { 3242 if (run && run->mediaType == audio_MediaType) {
3214 iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), run->mediaId); 3243 iPlayer *plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run));
3215 setFlags_Player(plr, volumeGrabbed_PlayerFlag, iTrue); 3244 setFlags_Player(plr, volumeGrabbed_PlayerFlag, iTrue);
3216 d->grabbedStartVolume = volume_Player(plr); 3245 d->grabbedStartVolume = volume_Player(plr);
3217 d->grabbedPlayer = run; 3246 d->grabbedPlayer = run;
@@ -3219,7 +3248,7 @@ static void setGrabbedPlayer_DocumentWidget_(iDocumentWidget *d, const iGmRun *r
3219 } 3248 }
3220 else if (d->grabbedPlayer) { 3249 else if (d->grabbedPlayer) {
3221 setFlags_Player( 3250 setFlags_Player(
3222 audioPlayer_Media(media_GmDocument(d->doc), d->grabbedPlayer->mediaId), 3251 audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(d->grabbedPlayer)),
3223 volumeGrabbed_PlayerFlag, 3252 volumeGrabbed_PlayerFlag,
3224 iFalse); 3253 iFalse);
3225 d->grabbedPlayer = NULL; 3254 d->grabbedPlayer = NULL;
@@ -3247,11 +3276,21 @@ static iBool processMediaEvents_DocumentWidget_(iDocumentWidget *d, const SDL_Ev
3247 const iInt2 mouse = init_I2(ev->button.x, ev->button.y); 3276 const iInt2 mouse = init_I2(ev->button.x, ev->button.y);
3248 iConstForEach(PtrArray, i, &d->visibleMedia) { 3277 iConstForEach(PtrArray, i, &d->visibleMedia) {
3249 const iGmRun *run = i.ptr; 3278 const iGmRun *run = i.ptr;
3250 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) {
3251 continue; 3289 continue;
3252 } 3290 }
3291 /* TODO: move this to mediaui.c */
3253 const iRect rect = runRect_DocumentWidget_(d, run); 3292 const iRect rect = runRect_DocumentWidget_(d, run);
3254 iPlayer * plr = audioPlayer_Media(media_GmDocument(d->doc), run->mediaId); 3293 iPlayer * plr = audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run));
3255 if (contains_Rect(rect, mouse)) { 3294 if (contains_Rect(rect, mouse)) {
3256 iPlayerUI ui; 3295 iPlayerUI ui;
3257 init_PlayerUI(&ui, plr, rect); 3296 init_PlayerUI(&ui, plr, rect);
@@ -3439,7 +3478,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
3439 return iTrue; 3478 return iTrue;
3440 } 3479 }
3441 break; 3480 break;
3442#if 1 3481#if !defined (NDEBUG)
3443 case SDLK_KP_1: 3482 case SDLK_KP_1:
3444 case '`': { 3483 case '`': {
3445 iBlock *seed = new_Block(64); 3484 iBlock *seed = new_Block(64);
@@ -3631,7 +3670,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
3631 cstr_String(linkUrl)) }, 3670 cstr_String(linkUrl)) },
3632 }, 3671 },
3633 3); 3672 3);
3634 if (isNative && d->contextLink->mediaType != download_GmRunMediaType) { 3673 if (isNative && d->contextLink->mediaType != download_MediaType) {
3635 pushBackN_Array(&items, (iMenuItem[]){ 3674 pushBackN_Array(&items, (iMenuItem[]){
3636 { "---" }, 3675 { "---" },
3637 { download_Icon " ${link.download}", 0, 0, "document.downloadlink" }, 3676 { download_Icon " ${link.download}", 0, 0, "document.downloadlink" },
@@ -3639,7 +3678,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
3639 } 3678 }
3640 iMediaRequest *mediaReq; 3679 iMediaRequest *mediaReq;
3641 if ((mediaReq = findMediaRequest_DocumentWidget_(d, d->contextLink->linkId)) != NULL && 3680 if ((mediaReq = findMediaRequest_DocumentWidget_(d, d->contextLink->linkId)) != NULL &&
3642 d->contextLink->mediaType != download_GmRunMediaType) { 3681 d->contextLink->mediaType != download_MediaType) {
3643 if (isFinished_GmRequest(mediaReq->req)) { 3682 if (isFinished_GmRequest(mediaReq->req)) {
3644 pushBack_Array(&items, 3683 pushBack_Array(&items,
3645 &(iMenuItem){ download_Icon " " saveToDownloads_Label, 3684 &(iMenuItem){ download_Icon " " saveToDownloads_Label,
@@ -3761,7 +3800,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
3761 case drag_ClickResult: { 3800 case drag_ClickResult: {
3762 if (d->grabbedPlayer) { 3801 if (d->grabbedPlayer) {
3763 iPlayer *plr = 3802 iPlayer *plr =
3764 audioPlayer_Media(media_GmDocument(d->doc), d->grabbedPlayer->mediaId); 3803 audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(d->grabbedPlayer));
3765 iPlayerUI ui; 3804 iPlayerUI ui;
3766 init_PlayerUI(&ui, plr, runRect_DocumentWidget_(d, d->grabbedPlayer)); 3805 init_PlayerUI(&ui, plr, runRect_DocumentWidget_(d, d->grabbedPlayer));
3767 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);
@@ -3887,8 +3926,9 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
3887 } 3926 }
3888 if (d->hoverLink) { 3927 if (d->hoverLink) {
3889 /* TODO: Move this to a method. */ 3928 /* TODO: Move this to a method. */
3890 const iGmLinkId linkId = d->hoverLink->linkId; 3929 const iGmLinkId linkId = d->hoverLink->linkId;
3891 const int linkFlags = linkFlags_GmDocument(d->doc, linkId); 3930 const iMediaId linkMedia = mediaId_GmRun(d->hoverLink);
3931 const int linkFlags = linkFlags_GmDocument(d->doc, linkId);
3892 iAssert(linkId); 3932 iAssert(linkId);
3893 /* Media links are opened inline by default. */ 3933 /* Media links are opened inline by default. */
3894 if (isMediaLink_GmDocument(d->doc, linkId)) { 3934 if (isMediaLink_GmDocument(d->doc, linkId)) {
@@ -3941,6 +3981,12 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
3941 } 3981 }
3942 refresh_Widget(w); 3982 refresh_Widget(w);
3943 } 3983 }
3984 else if (linkMedia.type == download_MediaType ||
3985 findMediaRequest_DocumentWidget_(d, linkId)) {
3986 /* TODO: What should be done when clicking on an inline download?
3987 Maybe dismiss if finished? */
3988 return iTrue;
3989 }
3944 else if (linkFlags & supportedScheme_GmLinkFlag) { 3990 else if (linkFlags & supportedScheme_GmLinkFlag) {
3945 int tabMode = openTabMode_Sym(modState_Keys()); 3991 int tabMode = openTabMode_Sym(modState_Keys());
3946 if (isPinned_DocumentWidget_(d)) { 3992 if (isPinned_DocumentWidget_(d)) {
@@ -4026,7 +4072,7 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol
4026 contains_Range(&mark, run->text.start))) { 4072 contains_Range(&mark, run->text.start))) {
4027 int x = 0; 4073 int x = 0;
4028 if (!*isInside) { 4074 if (!*isInside) {
4029 x = measureRange_Text(run->textParams.font, 4075 x = measureRange_Text(run->font,
4030 (iRangecc){ run->text.start, iMax(run->text.start, mark.start) }) 4076 (iRangecc){ run->text.start, iMax(run->text.start, mark.start) })
4031 .advance.x; 4077 .advance.x;
4032 } 4078 }
@@ -4035,7 +4081,7 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol
4035 iRangecc mk = !*isInside ? mark 4081 iRangecc mk = !*isInside ? mark
4036 : (iRangecc){ run->text.start, iMax(run->text.start, mark.end) }; 4082 : (iRangecc){ run->text.start, iMax(run->text.start, mark.end) };
4037 mk.start = iMax(mk.start, run->text.start); 4083 mk.start = iMax(mk.start, run->text.start);
4038 w = measureRange_Text(run->textParams.font, mk).advance.x; 4084 w = measureRange_Text(run->font, mk).advance.x;
4039 *isInside = iFalse; 4085 *isInside = iFalse;
4040 } 4086 }
4041 else { 4087 else {
@@ -4074,7 +4120,7 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol
4074 4120
4075static void drawMark_DrawContext_(void *context, const iGmRun *run) { 4121static void drawMark_DrawContext_(void *context, const iGmRun *run) {
4076 iDrawContext *d = context; 4122 iDrawContext *d = context;
4077 if (run->mediaType == none_GmRunMediaType) { 4123 if (run->mediaType == none_MediaType) {
4078 fillRange_DrawContext_(d, run, uiMatching_ColorId, d->widget->foundMark, &d->inFoundMark); 4124 fillRange_DrawContext_(d, run, uiMatching_ColorId, d->widget->foundMark, &d->inFoundMark);
4079 fillRange_DrawContext_(d, run, uiMarked_ColorId, d->widget->selectMark, &d->inSelectMark); 4125 fillRange_DrawContext_(d, run, uiMarked_ColorId, d->widget->selectMark, &d->inSelectMark);
4080 } 4126 }
@@ -4088,15 +4134,15 @@ static void drawBannerRun_DrawContext_(iDrawContext *d, const iGmRun *run, iInt2
4088 iInt2 bpos = add_I2(visPos, init_I2(0, lineHeight_Text(banner_FontId) / 2)); 4134 iInt2 bpos = add_I2(visPos, init_I2(0, lineHeight_Text(banner_FontId) / 2));
4089 if (icon) { 4135 if (icon) {
4090 appendChar_String(&str, icon); 4136 appendChar_String(&str, icon);
4091 const iRect iconRect = visualBounds_Text(run->textParams.font, range_String(&str)); 4137 const iRect iconRect = visualBounds_Text(run->font, range_String(&str));
4092 drawRange_Text( 4138 drawRange_Text(
4093 run->textParams.font, 4139 run->font,
4094 addY_I2(bpos, -mid_Rect(iconRect).y + lineHeight_Text(run->textParams.font) / 2), 4140 addY_I2(bpos, -mid_Rect(iconRect).y + lineHeight_Text(run->font) / 2),
4095 tmBannerIcon_ColorId, 4141 tmBannerIcon_ColorId,
4096 range_String(&str)); 4142 range_String(&str));
4097 bpos.x += right_Rect(iconRect) + 3 * gap_Text; 4143 bpos.x += right_Rect(iconRect) + 3 * gap_Text;
4098 } 4144 }
4099 drawRange_Text(run->textParams.font, 4145 drawRange_Text(run->font,
4100 bpos, 4146 bpos,
4101 tmBannerTitle_ColorId, 4147 tmBannerTitle_ColorId,
4102 bannerText_DocumentWidget_(d->widget)); 4148 bannerText_DocumentWidget_(d->widget));
@@ -4181,8 +4227,8 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4181 d->runsDrawn.end = run; 4227 d->runsDrawn.end = run;
4182 } 4228 }
4183 } 4229 }
4184 if (run->mediaType == image_GmRunMediaType) { 4230 if (run->mediaType == image_MediaType) {
4185 SDL_Texture *tex = imageTexture_Media(media_GmDocument(d->widget->doc), run->mediaId); 4231 SDL_Texture *tex = imageTexture_Media(media_GmDocument(d->widget->doc), mediaId_GmRun(run));
4186 const iRect dst = moved_Rect(run->visBounds, origin); 4232 const iRect dst = moved_Rect(run->visBounds, origin);
4187 if (tex) { 4233 if (tex) {
4188 fillRect_Paint(&d->paint, dst, tmBackground_ColorId); /* in case the image has alpha */ 4234 fillRect_Paint(&d->paint, dst, tmBackground_ColorId); /* in case the image has alpha */
@@ -4203,7 +4249,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4203 /* Media UIs are drawn afterwards as a dynamic overlay. */ 4249 /* Media UIs are drawn afterwards as a dynamic overlay. */
4204 return; 4250 return;
4205 } 4251 }
4206 enum iColorId fg = run->textParams.color; 4252 enum iColorId fg = run->color;
4207 const iGmDocument *doc = d->widget->doc; 4253 const iGmDocument *doc = d->widget->doc;
4208 const int linkFlags = linkFlags_GmDocument(doc, run->linkId); 4254 const int linkFlags = linkFlags_GmDocument(doc, run->linkId);
4209 /* Hover state of a link. */ 4255 /* Hover state of a link. */
@@ -4270,10 +4316,10 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4270 const iInt2 margin = preRunMargin_GmDocument(doc, run->preId); 4316 const iInt2 margin = preRunMargin_GmDocument(doc, run->preId);
4271 fillRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmBackgroundAltText_ColorId); 4317 fillRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmBackgroundAltText_ColorId);
4272 drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmQuoteIcon_ColorId); 4318 drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, tmQuoteIcon_ColorId);
4273 drawWrapRange_Text(run->textParams.font, 4319 drawWrapRange_Text(run->font,
4274 add_I2(visPos, margin), 4320 add_I2(visPos, margin),
4275 run->visBounds.size.x - 2 * margin.x, 4321 run->visBounds.size.x - 2 * margin.x,
4276 run->textParams.color, 4322 run->color,
4277 run->text); 4323 run->text);
4278 } 4324 }
4279 else if (run->flags & siteBanner_GmRunFlag) { 4325 else if (run->flags & siteBanner_GmRunFlag) {
@@ -4292,17 +4338,17 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4292 linkOrdinalChar_DocumentWidget_(d->widget, ord - d->widget->ordinalBase); 4338 linkOrdinalChar_DocumentWidget_(d->widget, ord - d->widget->ordinalBase);
4293 if (ordChar) { 4339 if (ordChar) {
4294 const char *circle = "\u25ef"; /* Large Circle */ 4340 const char *circle = "\u25ef"; /* Large Circle */
4295 const int circleFont = defaultContentRegular_FontId; 4341 const int circleFont = FONT_ID(default_FontId, regular_FontStyle, contentRegular_FontSize);
4296 iRect nbArea = { init_I2(d->viewPos.x - gap_UI / 3, visPos.y), 4342 iRect nbArea = { init_I2(d->viewPos.x - gap_UI / 3, visPos.y),
4297 init_I2(3.95f * gap_Text, 1.0f * lineHeight_Text(circleFont)) }; 4343 init_I2(3.95f * gap_Text, 1.0f * lineHeight_Text(circleFont)) };
4298 drawRange_Text( 4344 drawRange_Text(
4299 circleFont, topLeft_Rect(nbArea), tmQuote_ColorId, range_CStr(circle)); 4345 circleFont, topLeft_Rect(nbArea), tmQuote_ColorId, range_CStr(circle));
4300 iRect circleArea = visualBounds_Text(circleFont, range_CStr(circle)); 4346 iRect circleArea = visualBounds_Text(circleFont, range_CStr(circle));
4301 addv_I2(&circleArea.pos, topLeft_Rect(nbArea)); 4347 addv_I2(&circleArea.pos, topLeft_Rect(nbArea));
4302 drawCentered_Text(defaultContentSmall_FontId, 4348 drawCentered_Text(FONT_ID(default_FontId, regular_FontStyle, contentSmall_FontSize),
4303 circleArea, 4349 circleArea,
4304 iTrue, 4350 iTrue,
4305 tmQuote_ColorId, 4351 tmQuote_ColorId,
4306 "%lc", 4352 "%lc",
4307 (int) ordChar); 4353 (int) ordChar);
4308 goto runDrawn; 4354 goto runDrawn;
@@ -4312,15 +4358,15 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4312 if (run->flags & quoteBorder_GmRunFlag) { 4358 if (run->flags & quoteBorder_GmRunFlag) {
4313 drawVLine_Paint(&d->paint, 4359 drawVLine_Paint(&d->paint,
4314 addX_I2(visPos, 4360 addX_I2(visPos,
4315 !run->textParams.isRTL 4361 !run->isRTL
4316 ? -gap_Text * 5 / 2 4362 ? -gap_Text * 5 / 2
4317 : (width_Rect(run->visBounds) + gap_Text * 5 / 2)), 4363 : (width_Rect(run->visBounds) + gap_Text * 5 / 2)),
4318 height_Rect(run->visBounds), 4364 height_Rect(run->visBounds),
4319 tmQuoteIcon_ColorId); 4365 tmQuoteIcon_ColorId);
4320 } 4366 }
4321 drawBoundRange_Text(run->textParams.font, 4367 drawBoundRange_Text(run->font,
4322 visPos, 4368 visPos,
4323 (run->textParams.isRTL ? -1 : 1) * width_Rect(run->visBounds), 4369 (run->isRTL ? -1 : 1) * width_Rect(run->visBounds),
4324 fg, 4370 fg,
4325 run->text); 4371 run->text);
4326 runDrawn:; 4372 runDrawn:;
@@ -4337,31 +4383,31 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4337 fg = linkColor_GmDocument(doc, run->linkId, textHover_GmLinkPart); 4383 fg = linkColor_GmDocument(doc, run->linkId, textHover_GmLinkPart);
4338 iString text; 4384 iString text;
4339 init_String(&text); 4385 init_String(&text);
4340 iMediaId imageId = linkImage_GmDocument(doc, run->linkId); 4386 const iMediaId linkMedia = findMediaForLink_Media(constMedia_GmDocument(doc),
4341 iMediaId audioId = !imageId ? linkAudio_GmDocument(doc, run->linkId) : 0; 4387 run->linkId, none_MediaType);
4342 iMediaId downloadId = !imageId && !audioId ? 4388 iAssert(linkMedia.type != none_MediaType);
4343 findLinkDownload_Media(constMedia_GmDocument(doc), run->linkId) : 0; 4389 iGmMediaInfo info;
4344 iAssert(imageId || audioId || downloadId); 4390 info_Media(constMedia_GmDocument(doc), linkMedia, &info);
4345 if (imageId) { 4391 switch (linkMedia.type) {
4346 iAssert(!isEmpty_Rect(run->bounds)); 4392 case image_MediaType: {
4347 iGmMediaInfo info; 4393 iAssert(!isEmpty_Rect(run->bounds));
4348 imageInfo_Media(constMedia_GmDocument(doc), imageId, &info); 4394 const iInt2 imgSize = imageSize_Media(constMedia_GmDocument(doc), linkMedia);
4349 const iInt2 imgSize = imageSize_Media(constMedia_GmDocument(doc), imageId); 4395 format_String(&text, "%s \u2014 %d x %d \u2014 %.1f%s",
4350 format_String(&text, "%s \u2014 %d x %d \u2014 %.1f%s", 4396 info.type, imgSize.x, imgSize.y, info.numBytes / 1.0e6f,
4351 info.type, imgSize.x, imgSize.y, info.numBytes / 1.0e6f, 4397 cstr_Lang("mb"));
4352 cstr_Lang("mb")); 4398 break;
4353 } 4399 }
4354 else if (audioId) { 4400 case audio_MediaType:
4355 iGmMediaInfo info; 4401 format_String(&text, "%s", info.type);
4356 audioInfo_Media(constMedia_GmDocument(doc), audioId, &info); 4402 break;
4357 format_String(&text, "%s", info.type); 4403 case download_MediaType:
4358 } 4404 format_String(&text, "%s", info.type);
4359 else if (downloadId) { 4405 break;
4360 iGmMediaInfo info; 4406 default:
4361 downloadInfo_Media(constMedia_GmDocument(doc), downloadId, &info); 4407 break;
4362 format_String(&text, "%s", info.type);
4363 } 4408 }
4364 if (findMediaRequest_DocumentWidget_(d->widget, run->linkId)) { 4409 if (linkMedia.type != download_MediaType && /* can't cancel downloads currently */
4410 findMediaRequest_DocumentWidget_(d->widget, run->linkId)) {
4365 appendFormat_String( 4411 appendFormat_String(
4366 &text, " %s" close_Icon, isHover ? escape_Color(tmLinkText_ColorId) : ""); 4412 &text, " %s" close_Icon, isHover ? escape_Color(tmLinkText_ColorId) : "");
4367 } 4413 }
@@ -4436,7 +4482,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4436 append_String(&str, collect_String(format_Date(&date, "%b %d"))); 4482 append_String(&str, collect_String(format_Date(&date, "%b %d")));
4437 } 4483 }
4438 if (!isEmpty_String(&str)) { 4484 if (!isEmpty_String(&str)) {
4439 if (run->textParams.isRTL) { 4485 if (run->isRTL) {
4440 appendCStr_String(&str, " \u2014 "); 4486 appendCStr_String(&str, " \u2014 ");
4441 } 4487 }
4442 else { 4488 else {
@@ -4445,7 +4491,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
4445 const iInt2 textSize = measure_Text(metaFont, cstr_String(&str)).bounds.size; 4491 const iInt2 textSize = measure_Text(metaFont, cstr_String(&str)).bounds.size;
4446 int tx = topRight_Rect(linkRect).x; 4492 int tx = topRight_Rect(linkRect).x;
4447 const char *msg = cstr_String(&str); 4493 const char *msg = cstr_String(&str);
4448 if (run->textParams.isRTL) { 4494 if (run->isRTL) {
4449 tx = topLeft_Rect(linkRect).x - textSize.x; 4495 tx = topLeft_Rect(linkRect).x - textSize.x;
4450 } 4496 }
4451 if (tx + textSize.x > right_Rect(d->widgetBounds)) { 4497 if (tx + textSize.x > right_Rect(d->widgetBounds)) {
@@ -4592,18 +4638,25 @@ static void drawSideElements_DocumentWidget_(const iDocumentWidget *d) {
4592static void drawMedia_DocumentWidget_(const iDocumentWidget *d, iPaint *p) { 4638static void drawMedia_DocumentWidget_(const iDocumentWidget *d, iPaint *p) {
4593 iConstForEach(PtrArray, i, &d->visibleMedia) { 4639 iConstForEach(PtrArray, i, &d->visibleMedia) {
4594 const iGmRun * run = i.ptr; 4640 const iGmRun * run = i.ptr;
4595 if (run->mediaType == audio_GmRunMediaType) { 4641 if (run->mediaType == audio_MediaType) {
4596 iPlayerUI ui; 4642 iPlayerUI ui;
4597 init_PlayerUI(&ui, 4643 init_PlayerUI(&ui,
4598 audioPlayer_Media(media_GmDocument(d->doc), run->mediaId), 4644 audioPlayer_Media(media_GmDocument(d->doc), mediaId_GmRun(run)),
4599 runRect_DocumentWidget_(d, run)); 4645 runRect_DocumentWidget_(d, run));
4600 draw_PlayerUI(&ui, p); 4646 draw_PlayerUI(&ui, p);
4601 } 4647 }
4602 else if (run->mediaType == download_GmRunMediaType) { 4648 else if (run->mediaType == download_MediaType) {
4603 iDownloadUI ui; 4649 iDownloadUI ui;
4604 init_DownloadUI(&ui, d, run->mediaId, runRect_DocumentWidget_(d, run)); 4650 init_DownloadUI(&ui, constMedia_GmDocument(d->doc), run->mediaId,
4651 runRect_DocumentWidget_(d, run));
4605 draw_DownloadUI(&ui, p); 4652 draw_DownloadUI(&ui, p);
4606 } 4653 }
4654 else if (run->mediaType == fontpack_MediaType) {
4655 iFontpackUI ui;
4656 init_FontpackUI(&ui, constMedia_GmDocument(d->doc), run->mediaId,
4657 runRect_DocumentWidget_(d, run));
4658 draw_FontpackUI(&ui, p);
4659 }
4607 } 4660 }
4608} 4661}
4609 4662
@@ -4914,7 +4967,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
4914 } 4967 }
4915 /* Pinch zoom indicator. */ 4968 /* Pinch zoom indicator. */
4916 if (d->flags & pinchZoom_DocumentWidgetFlag) { 4969 if (d->flags & pinchZoom_DocumentWidgetFlag) {
4917 const int font = defaultLargeBold_FontId; 4970 const int font = uiLabelLargeBold_FontId;
4918 const int height = lineHeight_Text(font) * 2; 4971 const int height = lineHeight_Text(font) * 2;
4919 const iInt2 size = init_I2(height * 2, height); 4972 const iInt2 size = init_I2(height * 2, height);
4920 const iRect rect = { sub_I2(mid_Rect(bounds), divi_I2(size, 2)), size }; 4973 const iRect rect = { sub_I2(mid_Rect(bounds), divi_I2(size, 2)), size };