summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-10-11 12:22:54 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-10-11 12:22:54 +0300
commit960df03c17091aca37f53eaab8fc27c669d26a5e (patch)
tree18721567f227928c528430daf9d06248a2443598 /src/ui
parentbf9158b29df506698e267d4583459ee4ebf20685 (diff)
Media refactoring; working on FontPack management
Media still needs more work to get rid of redundancies and make lookups faster. FontPacks are manipulated as Media items (not unlike images) so they can be previewed on page, and installed via a click. FontPack management is not trivial as it includes such details as versioning and whether individual packs are enabled or disabled.
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/documentwidget.c429
-rw-r--r--src/ui/documentwidget.h2
-rw-r--r--src/ui/mediaui.c88
-rw-r--r--src/ui/mediaui.h20
4 files changed, 338 insertions, 201 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
diff --git a/src/ui/documentwidget.h b/src/ui/documentwidget.h
index cc09c72d..1f2ecfc0 100644
--- a/src/ui/documentwidget.h
+++ b/src/ui/documentwidget.h
@@ -32,6 +32,8 @@ iDeclareType(History)
32iDeclareWidgetClass(DocumentWidget) 32iDeclareWidgetClass(DocumentWidget)
33iDeclareObjectConstruction(DocumentWidget) 33iDeclareObjectConstruction(DocumentWidget)
34 34
35void cancelAllRequests_DocumentWidget(iDocumentWidget *);
36
35void serializeState_DocumentWidget (const iDocumentWidget *, iStream *outs); 37void serializeState_DocumentWidget (const iDocumentWidget *, iStream *outs);
36void deserializeState_DocumentWidget (iDocumentWidget *, iStream *ins); 38void deserializeState_DocumentWidget (iDocumentWidget *, iStream *ins);
37 39
diff --git a/src/ui/mediaui.c b/src/ui/mediaui.c
index 22552027..aa45d73a 100644
--- a/src/ui/mediaui.c
+++ b/src/ui/mediaui.c
@@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
30#include "lang.h" 30#include "lang.h"
31 31
32#include <the_Foundation/path.h> 32#include <the_Foundation/path.h>
33#include <the_Foundation/stringlist.h>
33 34
34static const char *volumeChar_(float volume) { 35static const char *volumeChar_(float volume) {
35 if (volume <= 0) { 36 if (volume <= 0) {
@@ -61,7 +62,7 @@ void init_PlayerUI(iPlayerUI *d, const iPlayer *player, iRect bounds) {
61 } 62 }
62} 63}
63 64
64static void drawPlayerButton_(iPaint *p, iRect rect, const char *label, int font) { 65static void drawInlineButton_(iPaint *p, iRect rect, const char *label, int font) {
65 const iInt2 mouse = mouseCoord_Window(get_Window(), 0); 66 const iInt2 mouse = mouseCoord_Window(get_Window(), 0);
66 const iBool isHover = contains_Rect(rect, mouse); 67 const iBool isHover = contains_Rect(rect, mouse);
67 const iBool isPressed = isHover && (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_LEFT) != 0; 68 const iBool isPressed = isHover && (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_LEFT) != 0;
@@ -113,14 +114,14 @@ void draw_PlayerUI(iPlayerUI *d, iPaint *p) {
113 const iBool isAdjusting = (flags_Player(d->player) & adjustingVolume_PlayerFlag) != 0; 114 const iBool isAdjusting = (flags_Player(d->player) & adjustingVolume_PlayerFlag) != 0;
114 fillRect_Paint(p, d->bounds, playerBackground_ColorId); 115 fillRect_Paint(p, d->bounds, playerBackground_ColorId);
115 drawRect_Paint(p, d->bounds, playerFrame_ColorId); 116 drawRect_Paint(p, d->bounds, playerFrame_ColorId);
116 drawPlayerButton_(p, 117 drawInlineButton_(p,
117 d->playPauseRect, 118 d->playPauseRect,
118 isPaused_Player(d->player) ? "\U0001f782" : "\u23f8", 119 isPaused_Player(d->player) ? "\U0001f782" : "\u23f8",
119 uiContent_FontId); 120 uiContent_FontId);
120 drawPlayerButton_(p, d->rewindRect, "\u23ee", uiContent_FontId); 121 drawInlineButton_(p, d->rewindRect, "\u23ee", uiContent_FontId);
121 drawPlayerButton_(p, d->menuRect, menu_Icon, uiContent_FontId); 122 drawInlineButton_(p, d->menuRect, menu_Icon, uiContent_FontId);
122 if (!isAdjusting) { 123 if (!isAdjusting) {
123 drawPlayerButton_( 124 drawInlineButton_(
124 p, d->volumeRect, volumeChar_(volume_Player(d->player)), uiContentSymbols_FontId); 125 p, d->volumeRect, volumeChar_(volume_Player(d->player)), uiContentSymbols_FontId);
125 } 126 }
126 const int hgt = lineHeight_Text(uiLabelBig_FontId); 127 const int hgt = lineHeight_Text(uiLabelBig_FontId);
@@ -228,24 +229,26 @@ static void drawSevenSegmentBytes_(iInt2 pos, int color, size_t numBytes) {
228 deinit_String(&digits); 229 deinit_String(&digits);
229} 230}
230 231
231void init_DownloadUI(iDownloadUI *d, const iDocumentWidget *doc, uint16_t mediaId, iRect bounds) { 232void init_DownloadUI(iDownloadUI *d, const iMedia *media, uint16_t mediaId, iRect bounds) {
232 d->doc = doc; 233 d->media = media;
233 d->mediaId = mediaId; 234 d->mediaId = mediaId;
234 d->bounds = bounds; 235 d->bounds = bounds;
235} 236}
236 237
238/*----------------------------------------------------------------------------------------------*/
239
237iBool processEvent_DownloadUI(iDownloadUI *d, const SDL_Event *ev) { 240iBool processEvent_DownloadUI(iDownloadUI *d, const SDL_Event *ev) {
238 return iFalse; 241 return iFalse;
239} 242}
240 243
241void draw_DownloadUI(const iDownloadUI *d, iPaint *p) { 244void draw_DownloadUI(const iDownloadUI *d, iPaint *p) {
242 const iMedia *media = constMedia_GmDocument(document_DocumentWidget(d->doc));
243 iGmMediaInfo info; 245 iGmMediaInfo info;
244 float bytesPerSecond; 246 float bytesPerSecond;
245 const iString *path; 247 const iString *path;
246 iBool isFinished; 248 iBool isFinished;
247 downloadInfo_Media(media, d->mediaId, &info); 249 downloadInfo_Media(d->media, d->mediaId, &info);
248 downloadStats_Media(media, d->mediaId, &path, &bytesPerSecond, &isFinished); 250 downloadStats_Media(d->media, (iMediaId){ download_MediaType, d->mediaId },
251 &path, &bytesPerSecond, &isFinished);
249 fillRect_Paint(p, d->bounds, uiBackground_ColorId); 252 fillRect_Paint(p, d->bounds, uiBackground_ColorId);
250 drawRect_Paint(p, d->bounds, uiSeparator_ColorId); 253 drawRect_Paint(p, d->bounds, uiSeparator_ColorId);
251 iRect rect = d->bounds; 254 iRect rect = d->bounds;
@@ -275,3 +278,68 @@ void draw_DownloadUI(const iDownloadUI *d, iPaint *p) {
275 translateCStr_Lang("\u2014 ${mb.per.sec}")); 278 translateCStr_Lang("\u2014 ${mb.per.sec}"));
276 } 279 }
277} 280}
281
282/*----------------------------------------------------------------------------------------------*/
283
284void init_FontpackUI(iFontpackUI *d, const iMedia *media, uint16_t mediaId, iRect bounds) {
285 d->media = media;
286 d->mediaId = mediaId;
287 d->bounds = bounds;
288 d->installRect.size = add_I2(measure_Text(uiLabel_FontId, "${media.fontpack.install}").bounds.size,
289 muli_I2(gap2_UI, 3));
290 d->installRect.pos.x = right_Rect(d->bounds) - gap_UI - d->installRect.size.x;
291 d->installRect.pos.y = mid_Rect(d->bounds).y - d->installRect.size.y / 2;
292}
293
294iBool processEvent_FontpackUI(iFontpackUI *d, const SDL_Event *ev) {
295 switch (ev->type) {
296 case SDL_MOUSEBUTTONDOWN:
297 case SDL_MOUSEBUTTONUP: {
298 const iInt2 pos = init_I2(ev->button.x, ev->button.y);
299 if (contains_Rect(d->installRect, pos)) {
300 return iTrue;
301 }
302 break;
303 }
304 case SDL_MOUSEMOTION:
305 if (contains_Rect(d->bounds, init_I2(ev->motion.x, ev->motion.y))) {
306 return iTrue;
307 }
308 break;
309 }
310 return iFalse;
311}
312
313int height_FontpackUI(const iMedia *media, uint16_t mediaId, int width) {
314 const iStringList *names;
315 iFontpackMediaInfo info;
316 fontpackInfo_Media(media, (iMediaId){ fontpack_MediaType, mediaId }, &info);
317 return lineHeight_Text(uiContent_FontId) +
318 lineHeight_Text(uiLabel_FontId) * (1 + size_StringList(info.names));
319}
320
321void draw_FontpackUI(const iFontpackUI *d, iPaint *p) {
322 /* Draw a background box. */
323 fillRect_Paint(p, d->bounds, uiBackground_ColorId);
324 drawRect_Paint(p, d->bounds, uiSeparator_ColorId);
325 iFontpackMediaInfo info;
326 fontpackInfo_Media(d->media, (iMediaId){ fontpack_MediaType, d->mediaId }, &info);
327 iInt2 pos = topLeft_Rect(d->bounds);
328 const char *checks[] = { "\u2610", "\u2611" };
329 draw_Text(uiContentBold_FontId, pos, uiHeading_ColorId, "\"%s\" v%d",
330 cstr_String(info.packId.id), info.packId.version);
331 pos.y += lineHeight_Text(uiContentBold_FontId);
332 draw_Text(uiLabelBold_FontId, pos, uiText_ColorId, "%.1f MB, %d fonts %s %s %s",
333 info.sizeInBytes / 1.0e6, size_StringList(info.names),
334// checks[info.isValid], info.isValid ? "No errors" : "Errors detected",
335 checks[info.isInstalled], info.isInstalled ? "Installed" : "Not installed",
336 info.isReadOnly ? "Read-Only" : "");
337 pos.y += lineHeight_Text(uiLabelBold_FontId);
338 iConstForEach(StringList, i, info.names) {
339 drawRange_Text(uiLabel_FontId, pos, uiText_ColorId, range_String(i.value));
340 pos.y += lineHeight_Text(uiLabel_FontId);
341 }
342 /* Buttons. */
343 drawInlineButton_(p, d->installRect,
344 "${media.fontpack.install}", uiLabel_FontId);
345}
diff --git a/src/ui/mediaui.h b/src/ui/mediaui.h
index e79dedc0..73de1994 100644
--- a/src/ui/mediaui.h
+++ b/src/ui/mediaui.h
@@ -51,11 +51,27 @@ iDeclareType(Media)
51iDeclareType(DownloadUI) 51iDeclareType(DownloadUI)
52 52
53struct Impl_DownloadUI { 53struct Impl_DownloadUI {
54 const iDocumentWidget *doc; 54 const iMedia *media;
55 uint16_t mediaId; 55 uint16_t mediaId;
56 iRect bounds; 56 iRect bounds;
57}; 57};
58 58
59void init_DownloadUI (iDownloadUI *, const iDocumentWidget *doc, uint16_t mediaId, iRect bounds); 59void init_DownloadUI (iDownloadUI *, const iMedia *media, uint16_t mediaId, iRect bounds);
60iBool processEvent_DownloadUI (iDownloadUI *, const SDL_Event *ev); 60iBool processEvent_DownloadUI (iDownloadUI *, const SDL_Event *ev);
61void draw_DownloadUI (const iDownloadUI *, iPaint *p); 61void draw_DownloadUI (const iDownloadUI *, iPaint *p);
62
63/*----------------------------------------------------------------------------------------------*/
64
65iDeclareType(FontpackUI)
66
67struct Impl_FontpackUI {
68 const iMedia *media;
69 uint16_t mediaId;
70 iRect bounds;
71 iRect installRect;
72};
73
74void init_FontpackUI (iFontpackUI *, const iMedia *media, uint16_t mediaId, iRect bounds);
75int height_FontpackUI (const iMedia *media, uint16_t mediaId, int width);
76iBool processEvent_FontpackUI (iFontpackUI *, const SDL_Event *ev);
77void draw_FontpackUI (const iFontpackUI *, iPaint *p);