summaryrefslogtreecommitdiff
path: root/src/app.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/app.c')
-rw-r--r--src/app.c553
1 files changed, 401 insertions, 152 deletions
diff --git a/src/app.c b/src/app.c
index b6c48062..543467ef 100644
--- a/src/app.c
+++ b/src/app.c
@@ -125,7 +125,8 @@ struct Impl_App {
125 iGmCerts * certs; 125 iGmCerts * certs;
126 iVisited * visited; 126 iVisited * visited;
127 iBookmarks * bookmarks; 127 iBookmarks * bookmarks;
128 iMainWindow *window; 128 iMainWindow *window; /* currently active MainWindow */
129 iPtrArray mainWindows;
129 iPtrArray popupWindows; 130 iPtrArray popupWindows;
130 iSortedArray tickers; /* per-frame callbacks, used for animations */ 131 iSortedArray tickers; /* per-frame callbacks, used for animations */
131 uint32_t lastTickerTime; 132 uint32_t lastTickerTime;
@@ -153,7 +154,8 @@ struct Impl_App {
153 /* Preferences: */ 154 /* Preferences: */
154 iBool commandEcho; /* --echo */ 155 iBool commandEcho; /* --echo */
155 iBool forceSoftwareRender; /* --sw */ 156 iBool forceSoftwareRender; /* --sw */
156 iRect initialWindowRect; 157 //iRect initialWindowRect;
158 iArray initialWindowRects; /* one per window */
157 iPrefs prefs; 159 iPrefs prefs;
158}; 160};
159 161
@@ -199,33 +201,43 @@ static iString *serializePrefs_App_(const iApp *d) {
199 appendFormat_String(str, "window.retain arg:%d\n", d->prefs.retainWindowSize); 201 appendFormat_String(str, "window.retain arg:%d\n", d->prefs.retainWindowSize);
200 if (d->prefs.retainWindowSize) { 202 if (d->prefs.retainWindowSize) {
201 int w, h, x, y; 203 int w, h, x, y;
202 x = d->window->place.normalRect.pos.x; 204 iConstForEach(PtrArray, i, &d->mainWindows) {
203 y = d->window->place.normalRect.pos.y; 205 const iMainWindow *win = i.ptr;
204 w = d->window->place.normalRect.size.x; 206 const size_t winIndex = index_PtrArrayConstIterator(&i);
205 h = d->window->place.normalRect.size.y; 207 x = win->place.normalRect.pos.x;
206 appendFormat_String(str, "window.setrect width:%d height:%d coord:%d %d\n", w, h, x, y); 208 y = win->place.normalRect.pos.y;
207 /* On macOS, maximization should be applied at creation time or the window will take 209 w = win->place.normalRect.size.x;
208 a moment to animate to its maximized size. */ 210 h = win->place.normalRect.size.y;
211 appendFormat_String(str,
212 "window.setrect index:%zu width:%d height:%d coord:%d %d\n",
213 winIndex,
214 w,
215 h,
216 x,
217 y);
218 /* On macOS, maximization should be applied at creation time or the window will take
219 a moment to animate to its maximized size. */
209#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) 220#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME)
210 if (snap_MainWindow(d->window)) { 221 if (snap_MainWindow(win)) {
211 if (snap_MainWindow(d->window) == maximized_WindowSnap) { 222 if (snap_MainWindow(win) == maximized_WindowSnap) {
212 appendFormat_String(str, "~window.maximize\n"); 223 appendFormat_String(str, "~window.maximize index:%zu\n", winIndex);
213 } 224 }
214 else if (~SDL_GetWindowFlags(d->window->base.win) & SDL_WINDOW_MINIMIZED) { 225 else if (~SDL_GetWindowFlags(win->base.win) & SDL_WINDOW_MINIMIZED) {
215 /* Save the actual visible window position, too, because snapped windows may 226 /* Save the actual visible window position, too, because snapped windows may
216 still be resized/moved without affecting normalRect. */ 227 still be resized/moved without affecting normalRect. */
217 SDL_GetWindowPosition(d->window->base.win, &x, &y); 228 SDL_GetWindowPosition(win->base.win, &x, &y);
218 SDL_GetWindowSize(d->window->base.win, &w, &h); 229 SDL_GetWindowSize(win->base.win, &w, &h);
219 appendFormat_String( 230 appendFormat_String(
220 str, "~window.setrect snap:%d width:%d height:%d coord:%d %d\n", 231 str, "~window.setrect index:%zu snap:%d width:%d height:%d coord:%d %d\n",
221 snap_MainWindow(d->window), w, h, x, y); 232 winIndex, snap_MainWindow(d->window), w, h, x, y);
233 }
222 } 234 }
223 }
224#elif !defined (iPlatformApple) 235#elif !defined (iPlatformApple)
225 if (snap_MainWindow(d->window) == maximized_WindowSnap) { 236 if (snap_MainWindow(win) == maximized_WindowSnap) {
226 appendFormat_String(str, "~window.maximize\n"); 237 appendFormat_String(str, "~window.maximize index:%zu\n", winIndex);
227 } 238 }
228#endif 239#endif
240 }
229 } 241 }
230 appendFormat_String(str, "uilang id:%s\n", cstr_String(&d->prefs.strings[uiLanguage_PrefsString])); 242 appendFormat_String(str, "uilang id:%s\n", cstr_String(&d->prefs.strings[uiLanguage_PrefsString]));
231 appendFormat_String(str, "uiscale arg:%f\n", uiScale_Window(as_Window(d->window))); 243 appendFormat_String(str, "uiscale arg:%f\n", uiScale_Window(as_Window(d->window)));
@@ -244,6 +256,7 @@ static iString *serializePrefs_App_(const iApp *d) {
244 appendFormat_String(str, "imageloadscroll arg:%d\n", d->prefs.loadImageInsteadOfScrolling); 256 appendFormat_String(str, "imageloadscroll arg:%d\n", d->prefs.loadImageInsteadOfScrolling);
245 appendFormat_String(str, "cachesize.set arg:%d\n", d->prefs.maxCacheSize); 257 appendFormat_String(str, "cachesize.set arg:%d\n", d->prefs.maxCacheSize);
246 appendFormat_String(str, "memorysize.set arg:%d\n", d->prefs.maxMemorySize); 258 appendFormat_String(str, "memorysize.set arg:%d\n", d->prefs.maxMemorySize);
259 appendFormat_String(str, "urlsize.set arg:%d\n", d->prefs.maxUrlSize);
247 appendFormat_String(str, "decodeurls arg:%d\n", d->prefs.decodeUserVisibleURLs); 260 appendFormat_String(str, "decodeurls arg:%d\n", d->prefs.decodeUserVisibleURLs);
248 appendFormat_String(str, "linewidth.set arg:%d\n", d->prefs.lineWidth); 261 appendFormat_String(str, "linewidth.set arg:%d\n", d->prefs.lineWidth);
249 appendFormat_String(str, "linespacing.set arg:%f\n", d->prefs.lineSpacing); 262 appendFormat_String(str, "linespacing.set arg:%f\n", d->prefs.lineSpacing);
@@ -279,6 +292,7 @@ static iString *serializePrefs_App_(const iApp *d) {
279 { "prefs.collapsepreonload", &d->prefs.collapsePreOnLoad }, 292 { "prefs.collapsepreonload", &d->prefs.collapsePreOnLoad },
280 { "prefs.hoverlink", &d->prefs.hoverLink }, 293 { "prefs.hoverlink", &d->prefs.hoverLink },
281 { "prefs.bookmarks.addbottom", &d->prefs.addBookmarksToBottom }, 294 { "prefs.bookmarks.addbottom", &d->prefs.addBookmarksToBottom },
295 { "prefs.dataurl.openimages", &d->prefs.openDataUrlImagesOnLoad },
282 { "prefs.archive.openindex", &d->prefs.openArchiveIndexPages }, 296 { "prefs.archive.openindex", &d->prefs.openArchiveIndexPages },
283 { "prefs.font.warnmissing", &d->prefs.warnAboutMissingGlyphs }, 297 { "prefs.font.warnmissing", &d->prefs.warnAboutMissingGlyphs },
284 { "prefs.blink", &d->prefs.blinkingCursor }, 298 { "prefs.blink", &d->prefs.blinkingCursor },
@@ -401,9 +415,14 @@ static void loadPrefs_App_(iApp *d) {
401 d->prefs.customFrame = arg_Command(cmd); 415 d->prefs.customFrame = arg_Command(cmd);
402 } 416 }
403 else if (equal_Command(cmd, "window.setrect") && !argLabel_Command(cmd, "snap")) { 417 else if (equal_Command(cmd, "window.setrect") && !argLabel_Command(cmd, "snap")) {
404 const iInt2 pos = coord_Command(cmd); 418 const int index = argLabel_Command(cmd, "index");
405 d->initialWindowRect = init_Rect( 419 const iInt2 pos = coord_Command(cmd);
420 iRect winRect = init_Rect(
406 pos.x, pos.y, argLabel_Command(cmd, "width"), argLabel_Command(cmd, "height")); 421 pos.x, pos.y, argLabel_Command(cmd, "width"), argLabel_Command(cmd, "height"));
422 if (index >= 0 && index < 100) {
423 resize_Array(&d->initialWindowRects, index + 1);
424 set_Array(&d->initialWindowRects, index, &winRect);
425 }
407 } 426 }
408 else if (equal_Command(cmd, "fontpack.disable")) { 427 else if (equal_Command(cmd, "fontpack.disable")) {
409 insert_StringSet(d->prefs.disabledFontPacks, 428 insert_StringSet(d->prefs.disabledFontPacks,
@@ -439,6 +458,7 @@ static void loadPrefs_App_(iApp *d) {
439 } 458 }
440 iRelease(f); 459 iRelease(f);
441 /* Upgrade checks. */ 460 /* Upgrade checks. */
461#if 0 /* disabled in v1.11 (font library search) */
442 if (cmp_Version(&upgradedFromAppVersion, &(iVersion){ 1, 8, 0 }) < 0) { 462 if (cmp_Version(&upgradedFromAppVersion, &(iVersion){ 1, 8, 0 }) < 0) {
443#if !defined (iPlatformAppleMobile) && !defined (iPlatformAndroidMobile) 463#if !defined (iPlatformAppleMobile) && !defined (iPlatformAndroidMobile)
444 /* When upgrading to v1.8.0, the old hardcoded font library is gone and that means 464 /* When upgrading to v1.8.0, the old hardcoded font library is gone and that means
@@ -448,6 +468,7 @@ static void loadPrefs_App_(iApp *d) {
448 postCommand_App("~fontpack.suggest.classic"); 468 postCommand_App("~fontpack.suggest.classic");
449#endif 469#endif
450 } 470 }
471#endif
451#if !defined (LAGRANGE_ENABLE_CUSTOM_FRAME) 472#if !defined (LAGRANGE_ENABLE_CUSTOM_FRAME)
452 d->prefs.customFrame = iFalse; 473 d->prefs.customFrame = iFalse;
453#endif 474#endif
@@ -470,10 +491,36 @@ static const char *magicTabDocument_App_ = "tabd";
470static const char *magicSidebar_App_ = "side"; 491static const char *magicSidebar_App_ = "side";
471 492
472enum iDocumentStateFlag { 493enum iDocumentStateFlag {
473 current_DocumentStateFlag = iBit(1), 494 current_DocumentStateFlag = iBit(1),
474 rootIndex1_DocumentStateFlag = iBit(2) 495 rootIndex1_DocumentStateFlag = iBit(2),
496};
497
498enum iWindowStateFlag {
499 current_WindowStateFlag = iBit(9),
475}; 500};
476 501
502static iRect initialWindowRect_App_(const iApp *d, size_t windowIndex) {
503 if (windowIndex < size_Array(&d->initialWindowRects)) {
504 return constValue_Array(&d->initialWindowRects, windowIndex, iRect);
505 }
506 /* The default window rectangle. */
507 iRect rect = init_Rect(-1, -1, 900, 560);
508#if defined (iPlatformMsys)
509 /* Must scale by UI scaling factor. */
510 mulfv_I2(&rect.size, desktopDPI_Win32());
511#endif
512#if defined (iPlatformLinux) && !defined (iPlatformAndroid)
513 /* Scale by the primary (?) monitor DPI. */
514 if (isRunningUnderWindowSystem_App()) {
515 float vdpi;
516 SDL_GetDisplayDPI(0, NULL, NULL, &vdpi);
517 const float factor = vdpi / 96.0f;
518 mulfv_I2(&rect.size, iMax(factor, 1.0f));
519 }
520#endif
521 return rect;
522}
523
477static iBool loadState_App_(iApp *d) { 524static iBool loadState_App_(iApp *d) {
478 iUnused(d); 525 iUnused(d);
479 const char *oldPath = concatPath_CStr(dataDir_App_(), oldStateFileName_App_); 526 const char *oldPath = concatPath_CStr(dataDir_App_(), oldStateFileName_App_);
@@ -494,19 +541,49 @@ static iBool loadState_App_(iApp *d) {
494 } 541 }
495 setVersion_Stream(stream_File(f), version); 542 setVersion_Stream(stream_File(f), version);
496 /* Window state. */ 543 /* Window state. */
497 iDocumentWidget *doc = NULL; 544 iDeclareType(CurrentTabs);
498 iDocumentWidget *current[2] = { NULL, NULL }; 545 struct Impl_CurrentTabs {
499 iBool isFirstTab[2] = { iTrue, iTrue }; 546 iDocumentWidget *currentTab[2]; /* for each root */
547 };
548 int numWins = 0;
549 iMainWindow * win = NULL;
550 iMainWindow * currentWin = d->window;
551 iArray * currentTabs; /* two per window (per root per window) */
552 iBool isFirstTab[2];
553 currentTabs = collectNew_Array(sizeof(iCurrentTabs));
500 while (!atEnd_File(f)) { 554 while (!atEnd_File(f)) {
501 readData_File(f, 4, magic); 555 readData_File(f, 4, magic);
502 if (!memcmp(magic, magicWindow_App_, 4)) { 556 if (!memcmp(magic, magicWindow_App_, 4)) {
503 const int splitMode = read32_File(f); 557 numWins++;
504 const int keyRoot = read32_File(f); 558 const int splitMode = read32_File(f);
505 d->window->pendingSplitMode = splitMode; 559 const int winState = read32_File(f);
506 setSplitMode_MainWindow(d->window, splitMode | noEvents_WindowSplit); 560 const int keyRoot = (winState & 1);
507 d->window->base.keyRoot = d->window->base.roots[keyRoot]; 561 const iBool isCurrent = (winState & current_WindowStateFlag) != 0;
562// printf("[State] '%.4s' split:%d state:%x\n", magic, splitMode, winState);
563 if (numWins == 1) {
564 win = d->window;
565 }
566 else {
567 win = new_MainWindow(initialWindowRect_App_(d, numWins - 1));
568 addWindow_App(win);
569 }
570 pushBack_Array(currentTabs, &(iCurrentTabs){ { NULL, NULL } });
571 isFirstTab[0] = isFirstTab[1] = iTrue;
572 if (isCurrent) {
573 currentWin = win;
574 }
575 setCurrent_Window(win);
576 setCurrent_Root(NULL);
577 win->pendingSplitMode = splitMode;
578 setSplitMode_MainWindow(win, splitMode | noEvents_WindowSplit);
579 win->base.keyRoot = win->base.roots[keyRoot];
508 } 580 }
509 else if (!memcmp(magic, magicSidebar_App_, 4)) { 581 else if (!memcmp(magic, magicSidebar_App_, 4)) {
582 if (!win) {
583 printf("%s: missing window\n", cstr_String(path_File(f)));
584 setCurrent_Root(NULL);
585 return iFalse;
586 }
510 const uint16_t bits = readU16_File(f); 587 const uint16_t bits = readU16_File(f);
511 const uint8_t modes = readU8_File(f); 588 const uint8_t modes = readU8_File(f);
512 const float widths[2] = { 589 const float widths[2] = {
@@ -523,7 +600,7 @@ static iBool loadState_App_(iApp *d) {
523 } 600 }
524 const uint8_t rootIndex = bits & 0xff; 601 const uint8_t rootIndex = bits & 0xff;
525 const uint8_t flags = bits >> 8; 602 const uint8_t flags = bits >> 8;
526 iRoot *root = d->window->base.roots[rootIndex]; 603 iRoot *root = win->base.roots[rootIndex];
527 if (root) { 604 if (root) {
528 iSidebarWidget *sidebar = findChild_Widget(root->widget, "sidebar"); 605 iSidebarWidget *sidebar = findChild_Widget(root->widget, "sidebar");
529 iSidebarWidget *sidebar2 = findChild_Widget(root->widget, "sidebar2"); 606 iSidebarWidget *sidebar2 = findChild_Widget(root->widget, "sidebar2");
@@ -546,12 +623,18 @@ static iBool loadState_App_(iApp *d) {
546 } 623 }
547 } 624 }
548 else if (!memcmp(magic, magicTabDocument_App_, 4)) { 625 else if (!memcmp(magic, magicTabDocument_App_, 4)) {
626 if (!win) {
627 printf("%s: missing window\n", cstr_String(path_File(f)));
628 setCurrent_Root(NULL);
629 return iFalse;
630 }
549 const int8_t flags = read8_File(f); 631 const int8_t flags = read8_File(f);
550 int rootIndex = flags & rootIndex1_DocumentStateFlag ? 1 : 0; 632 int rootIndex = flags & rootIndex1_DocumentStateFlag ? 1 : 0;
551 if (rootIndex > numRoots_Window(as_Window(d->window)) - 1) { 633 if (rootIndex > numRoots_Window(as_Window(win)) - 1) {
552 rootIndex = 0; 634 rootIndex = 0;
553 } 635 }
554 setCurrent_Root(d->window->base.roots[rootIndex]); 636 setCurrent_Root(win->base.roots[rootIndex]);
637 iDocumentWidget *doc;
555 if (isFirstTab[rootIndex]) { 638 if (isFirstTab[rootIndex]) {
556 isFirstTab[rootIndex] = iFalse; 639 isFirstTab[rootIndex] = iFalse;
557 /* There is one pre-created tab in each root. */ 640 /* There is one pre-created tab in each root. */
@@ -561,7 +644,7 @@ static iBool loadState_App_(iApp *d) {
561 doc = newTab_App(NULL, iFalse /* no switching */); 644 doc = newTab_App(NULL, iFalse /* no switching */);
562 } 645 }
563 if (flags & current_DocumentStateFlag) { 646 if (flags & current_DocumentStateFlag) {
564 current[rootIndex] = doc; 647 value_Array(currentTabs, numWins - 1, iCurrentTabs).currentTab[rootIndex] = doc;
565 } 648 }
566 deserializeState_DocumentWidget(doc, stream_File(f)); 649 deserializeState_DocumentWidget(doc, stream_File(f));
567 doc = NULL; 650 doc = NULL;
@@ -572,12 +655,23 @@ static iBool loadState_App_(iApp *d) {
572 return iFalse; 655 return iFalse;
573 } 656 }
574 } 657 }
575 if (d->window->splitMode) { 658 iForEach(Array, i, currentTabs) {
576 /* Update root placement. */ 659 const iCurrentTabs *cur = i.value;
577 resize_MainWindow(d->window, -1, -1); 660 win = at_PtrArray(&d->mainWindows, index_ArrayIterator(&i));
661 for (size_t j = 0; j < 2; ++j) {
662 postCommandf_Root(win->base.roots[j], "tabs.switch page:%p", cur->currentTab[j]);
663 }
664 if (win->splitMode) {
665 /* Update root placement. */
666 resize_MainWindow(win, -1, -1);
667 }
668// postCommand_Root(win->base.roots[0], "window.unfreeze");
669 win->isDrawFrozen = iFalse;
670 SDL_ShowWindow(win->base.win);
578 } 671 }
579 iForIndices(i, current) { 672 if (numWindows_App() > 1) {
580 postCommandf_Root(NULL, "tabs.switch page:%p", current[i]); 673 SDL_RaiseWindow(currentWin->base.win);
674 setActiveWindow_App(currentWin);
581 } 675 }
582 setCurrent_Root(NULL); 676 setCurrent_Root(NULL);
583 return iTrue; 677 return iTrue;
@@ -588,7 +682,6 @@ static iBool loadState_App_(iApp *d) {
588static void saveState_App_(const iApp *d) { 682static void saveState_App_(const iApp *d) {
589 iUnused(d); 683 iUnused(d);
590 trimCache_App(); 684 trimCache_App();
591 iMainWindow *win = d->window;
592 /* UI state is saved in binary because it is quite complex (e.g., 685 /* UI state is saved in binary because it is quite complex (e.g.,
593 navigation history, cached content) and depends closely on the widget 686 navigation history, cached content) and depends closely on the widget
594 tree. The data is largely not reorderable and should not be modified 687 tree. The data is largely not reorderable and should not be modified
@@ -597,43 +690,48 @@ static void saveState_App_(const iApp *d) {
597 if (open_File(f, writeOnly_FileMode)) { 690 if (open_File(f, writeOnly_FileMode)) {
598 writeData_File(f, magicState_App_, 4); 691 writeData_File(f, magicState_App_, 4);
599 writeU32_File(f, latest_FileVersion); /* version */ 692 writeU32_File(f, latest_FileVersion); /* version */
600 /* Begin with window state. */ { 693 iConstForEach(PtrArray, winIter, &d->mainWindows) {
601 writeData_File(f, magicWindow_App_, 4); 694 const iMainWindow *win = winIter.ptr;
602 writeU32_File(f, win->splitMode); 695 setCurrent_Window(winIter.ptr);
603 writeU32_File(f, win->base.keyRoot == win->base.roots[0] ? 0 : 1); 696 /* Window state. */ {
604 } 697 writeData_File(f, magicWindow_App_, 4);
605 /* State of UI elements. */ { 698 writeU32_File(f, win->splitMode);
606 iForIndices(i, win->base.roots) { 699 writeU32_File(f, (win->base.keyRoot == win->base.roots[0] ? 0 : 1) |
607 const iRoot *root = win->base.roots[i]; 700 (win == d->window ? current_WindowStateFlag : 0));
608 if (root) { 701 }
609 writeData_File(f, magicSidebar_App_, 4); 702 /* State of UI elements. */ {
610 const iSidebarWidget *sidebar = findChild_Widget(root->widget, "sidebar"); 703 iForIndices(i, win->base.roots) {
611 const iSidebarWidget *sidebar2 = findChild_Widget(root->widget, "sidebar2"); 704 const iRoot *root = win->base.roots[i];
612 writeU16_File(f, i | 705 if (root) {
613 (isVisible_Widget(sidebar) ? 0x100 : 0) | 706 writeData_File(f, magicSidebar_App_, 4);
614 (isVisible_Widget(sidebar2) ? 0x200 : 0) | 707 const iSidebarWidget *sidebar = findChild_Widget(root->widget, "sidebar");
615 (feedsMode_SidebarWidget(sidebar) == unread_FeedsMode ? 0x400 : 0) | 708 const iSidebarWidget *sidebar2 = findChild_Widget(root->widget, "sidebar2");
616 (feedsMode_SidebarWidget(sidebar2) == unread_FeedsMode ? 0x800 : 0)); 709 writeU16_File(f, i |
617 writeU8_File(f, 710 (isVisible_Widget(sidebar) ? 0x100 : 0) |
618 mode_SidebarWidget(sidebar) | 711 (isVisible_Widget(sidebar2) ? 0x200 : 0) |
619 (mode_SidebarWidget(sidebar2) << 4)); 712 (feedsMode_SidebarWidget(sidebar) == unread_FeedsMode ? 0x400 : 0) |
620 writef_Stream(stream_File(f), width_SidebarWidget(sidebar)); 713 (feedsMode_SidebarWidget(sidebar2) == unread_FeedsMode ? 0x800 : 0));
621 writef_Stream(stream_File(f), width_SidebarWidget(sidebar2)); 714 writeU8_File(f,
622 serialize_IntSet(closedFolders_SidebarWidget(sidebar), stream_File(f)); 715 mode_SidebarWidget(sidebar) |
623 serialize_IntSet(closedFolders_SidebarWidget(sidebar2), stream_File(f)); 716 (mode_SidebarWidget(sidebar2) << 4));
717 writef_Stream(stream_File(f), width_SidebarWidget(sidebar));
718 writef_Stream(stream_File(f), width_SidebarWidget(sidebar2));
719 serialize_IntSet(closedFolders_SidebarWidget(sidebar), stream_File(f));
720 serialize_IntSet(closedFolders_SidebarWidget(sidebar2), stream_File(f));
721 }
624 } 722 }
625 } 723 }
626 } 724 iConstForEach(ObjectList, i, iClob(listDocuments_App(NULL))) {
627 iConstForEach(ObjectList, i, iClob(listDocuments_App(NULL))) { 725 iAssert(isInstance_Object(i.object, &Class_DocumentWidget));
628 iAssert(isInstance_Object(i.object, &Class_DocumentWidget)); 726 const iWidget *widget = constAs_Widget(i.object);
629 const iWidget *widget = constAs_Widget(i.object); 727 writeData_File(f, magicTabDocument_App_, 4);
630 writeData_File(f, magicTabDocument_App_, 4); 728 int8_t flags = (document_Root(widget->root) == i.object ? current_DocumentStateFlag : 0);
631 int8_t flags = (document_Root(widget->root) == i.object ? current_DocumentStateFlag : 0); 729 if (widget->root == win->base.roots[1]) {
632 if (widget->root == win->base.roots[1]) { 730 flags |= rootIndex1_DocumentStateFlag;
633 flags |= rootIndex1_DocumentStateFlag; 731 }
732 write8_File(f, flags);
733 serializeState_DocumentWidget(i.object, stream_File(f));
634 } 734 }
635 write8_File(f, flags);
636 serializeState_DocumentWidget(i.object, stream_File(f));
637 } 735 }
638 iRelease(f); 736 iRelease(f);
639 } 737 }
@@ -760,6 +858,7 @@ static void init_App_(iApp *d, int argc, char **argv) {
760 d->isDarkSystemTheme = iTrue; /* will be updated by system later on, if supported */ 858 d->isDarkSystemTheme = iTrue; /* will be updated by system later on, if supported */
761 d->isSuspended = iFalse; 859 d->isSuspended = iFalse;
762 d->tempFilesPendingDeletion = new_StringSet(); 860 d->tempFilesPendingDeletion = new_StringSet();
861 init_Array(&d->initialWindowRects, sizeof(iRect));
763 init_CommandLine(&d->args, argc, argv); 862 init_CommandLine(&d->args, argc, argv);
764 /* Where was the app started from? We ask SDL first because the command line alone 863 /* Where was the app started from? We ask SDL first because the command line alone
765 cannot be relied on (behavior differs depending on OS). */ { 864 cannot be relied on (behavior differs depending on OS). */ {
@@ -894,20 +993,6 @@ static void init_App_(iApp *d, int argc, char **argv) {
894 d->elapsedSinceLastTicker = 0; 993 d->elapsedSinceLastTicker = 0;
895 d->commandEcho = iClob(checkArgument_CommandLine(&d->args, "echo;E")) != NULL; 994 d->commandEcho = iClob(checkArgument_CommandLine(&d->args, "echo;E")) != NULL;
896 d->forceSoftwareRender = iClob(checkArgument_CommandLine(&d->args, "sw")) != NULL; 995 d->forceSoftwareRender = iClob(checkArgument_CommandLine(&d->args, "sw")) != NULL;
897 d->initialWindowRect = init_Rect(-1, -1, 900, 560);
898#if defined (iPlatformMsys)
899 /* Must scale by UI scaling factor. */
900 mulfv_I2(&d->initialWindowRect.size, desktopDPI_Win32());
901#endif
902#if defined (iPlatformLinux) && !defined (iPlatformAndroid)
903 /* Scale by the primary (?) monitor DPI. */
904 if (isRunningUnderWindowSystem_App()) {
905 float vdpi;
906 SDL_GetDisplayDPI(0, NULL, NULL, &vdpi);
907 const float factor = vdpi / 96.0f;
908 mulfv_I2(&d->initialWindowRect.size, iMax(factor, 1.0f));
909 }
910#endif
911 init_Prefs(&d->prefs); 996 init_Prefs(&d->prefs);
912 init_SiteSpec(dataDir_App_()); 997 init_SiteSpec(dataDir_App_());
913 setCStr_String(&d->prefs.strings[downloadDir_PrefsString], downloadDir_App_()); 998 setCStr_String(&d->prefs.strings[downloadDir_PrefsString], downloadDir_App_());
@@ -929,21 +1014,30 @@ static void init_App_(iApp *d, int argc, char **argv) {
929 init_Fonts(dataDir_App_()); 1014 init_Fonts(dataDir_App_());
930 loadPalette_Color(dataDir_App_()); 1015 loadPalette_Color(dataDir_App_());
931 setThemePalette_Color(d->prefs.theme); /* default UI colors */ 1016 setThemePalette_Color(d->prefs.theme); /* default UI colors */
932 loadPrefs_App_(d); 1017 /* Initial window rectangle of the first window. */ {
1018 iAssert(isEmpty_Array(&d->initialWindowRects));
1019 const iRect winRect = initialWindowRect_App_(d, 0); /* calculated */
1020 resize_Array(&d->initialWindowRects, 1);
1021 set_Array(&d->initialWindowRects, 0, &winRect);
1022 }
1023 loadPrefs_App_(d);
933 updateActive_Fonts(); 1024 updateActive_Fonts();
934 load_Keys(dataDir_App_()); 1025 load_Keys(dataDir_App_());
1026 iRect *winRect0 = at_Array(&d->initialWindowRects, 0);
935 /* See if the user wants to override the window size. */ { 1027 /* See if the user wants to override the window size. */ {
936 iCommandLineArg *arg = iClob(checkArgument_CommandLine(&d->args, windowWidth_CommandLineOption)); 1028 iCommandLineArg *arg = iClob(checkArgument_CommandLine(&d->args, windowWidth_CommandLineOption));
937 if (arg) { 1029 if (arg) {
938 d->initialWindowRect.size.x = toInt_String(value_CommandLineArg(arg, 0)); 1030 winRect0->size.x = toInt_String(value_CommandLineArg(arg, 0));
939 } 1031 }
940 arg = iClob(checkArgument_CommandLine(&d->args, windowHeight_CommandLineOption)); 1032 arg = iClob(checkArgument_CommandLine(&d->args, windowHeight_CommandLineOption));
941 if (arg) { 1033 if (arg) {
942 d->initialWindowRect.size.y = toInt_String(value_CommandLineArg(arg, 0)); 1034 winRect0->size.y = toInt_String(value_CommandLineArg(arg, 0));
943 } 1035 }
944 } 1036 }
1037 init_PtrArray(&d->mainWindows);
945 init_PtrArray(&d->popupWindows); 1038 init_PtrArray(&d->popupWindows);
946 d->window = new_MainWindow(d->initialWindowRect); 1039 d->window = new_MainWindow(*winRect0); /* first window is always created */
1040 addWindow_App(d->window);
947 load_Visited(d->visited, dataDir_App_()); 1041 load_Visited(d->visited, dataDir_App_());
948 load_Bookmarks(d->bookmarks, dataDir_App_()); 1042 load_Bookmarks(d->bookmarks, dataDir_App_());
949 load_MimeHooks(d->mimehooks, dataDir_App_()); 1043 load_MimeHooks(d->mimehooks, dataDir_App_());
@@ -968,8 +1062,8 @@ static void init_App_(iApp *d, int argc, char **argv) {
968 } 1062 }
969 postCommand_App("~navbar.actions.changed"); 1063 postCommand_App("~navbar.actions.changed");
970 postCommand_App("~toolbar.actions.changed"); 1064 postCommand_App("~toolbar.actions.changed");
971 postCommand_Root(NULL, "~window.unfreeze"); 1065 postCommand_App("~window.unfreeze");
972 postCommand_Root(NULL, "font.reset"); 1066 postCommand_App("font.reset");
973 d->autoReloadTimer = SDL_AddTimer(60 * 1000, postAutoReloadCommand_App_, NULL); 1067 d->autoReloadTimer = SDL_AddTimer(60 * 1000, postAutoReloadCommand_App_, NULL);
974 postCommand_Root(NULL, "document.autoreload"); 1068 postCommand_Root(NULL, "document.autoreload");
975#if defined (LAGRANGE_ENABLE_IDLE_SLEEP) 1069#if defined (LAGRANGE_ENABLE_IDLE_SLEEP)
@@ -1008,7 +1102,11 @@ static void deinit_App(iApp *d) {
1008 SDL_RemoveTimer(d->autoReloadTimer); 1102 SDL_RemoveTimer(d->autoReloadTimer);
1009 saveState_App_(d); 1103 saveState_App_(d);
1010 savePrefs_App_(d); 1104 savePrefs_App_(d);
1011 delete_MainWindow(d->window); 1105 iReverseForEach(PtrArray, j, &d->mainWindows) {
1106 delete_MainWindow(j.ptr);
1107 }
1108 iAssert(isEmpty_PtrArray(&d->mainWindows));
1109 deinit_PtrArray(&d->mainWindows);
1012 d->window = NULL; 1110 d->window = NULL;
1013 deinit_Feeds(); 1111 deinit_Feeds();
1014 save_Keys(dataDir_App_()); 1112 save_Keys(dataDir_App_());
@@ -1023,7 +1121,6 @@ static void deinit_App(iApp *d) {
1023 delete_GmCerts(d->certs); 1121 delete_GmCerts(d->certs);
1024 save_MimeHooks(d->mimehooks); 1122 save_MimeHooks(d->mimehooks);
1025 delete_MimeHooks(d->mimehooks); 1123 delete_MimeHooks(d->mimehooks);
1026 d->window = NULL;
1027 deinit_CommandLine(&d->args); 1124 deinit_CommandLine(&d->args);
1028 iRelease(d->launchCommands); 1125 iRelease(d->launchCommands);
1029 delete_String(d->execPath); 1126 delete_String(d->execPath);
@@ -1037,7 +1134,8 @@ static void deinit_App(iApp *d) {
1037 /* Delete all temporary files created while running. */ 1134 /* Delete all temporary files created while running. */
1038 iConstForEach(StringSet, tmp, d->tempFilesPendingDeletion) { 1135 iConstForEach(StringSet, tmp, d->tempFilesPendingDeletion) {
1039 remove(cstr_String(tmp.value)); 1136 remove(cstr_String(tmp.value));
1040} 1137 }
1138 deinit_Array(&d->initialWindowRects);
1041 iRelease(d->tempFilesPendingDeletion); 1139 iRelease(d->tempFilesPendingDeletion);
1042} 1140}
1043 1141
@@ -1282,10 +1380,23 @@ static iPtrArray *listWindows_App_(const iApp *d, iPtrArray *windows) {
1282 iReverseConstForEach(PtrArray, i, &d->popupWindows) { 1380 iReverseConstForEach(PtrArray, i, &d->popupWindows) {
1283 pushBack_PtrArray(windows, i.ptr); 1381 pushBack_PtrArray(windows, i.ptr);
1284 } 1382 }
1285 pushBack_PtrArray(windows, d->window); 1383 if (d->window) {
1384 pushBack_PtrArray(windows, d->window);
1385 }
1386 iConstForEach(PtrArray, j, &d->mainWindows) {
1387 if (j.ptr != d->window) {
1388 pushBack_PtrArray(windows, j.ptr);
1389 }
1390 }
1286 return windows; 1391 return windows;
1287} 1392}
1288 1393
1394iPtrArray *listWindows_App(void) {
1395 iPtrArray *wins = new_PtrArray();
1396 listWindows_App_(&app_, wins);
1397 return wins;
1398}
1399
1289void processEvents_App(enum iAppEventMode eventMode) { 1400void processEvents_App(enum iAppEventMode eventMode) {
1290 iApp *d = &app_; 1401 iApp *d = &app_;
1291 iRoot *oldCurrentRoot = current_Root(); /* restored afterwards */ 1402 iRoot *oldCurrentRoot = current_Root(); /* restored afterwards */
@@ -1365,6 +1476,10 @@ void processEvents_App(enum iAppEventMode eventMode) {
1365 dispatchCommands_Periodic(&d->periodic); 1476 dispatchCommands_Periodic(&d->periodic);
1366 continue; 1477 continue;
1367 } 1478 }
1479 if (ev.type == SDL_USEREVENT && ev.user.code == releaseObject_UserEventCode) {
1480 iRelease(ev.user.data1);
1481 continue;
1482 }
1368 if (ev.type == SDL_USEREVENT && ev.user.code == refresh_UserEventCode) { 1483 if (ev.type == SDL_USEREVENT && ev.user.code == refresh_UserEventCode) {
1369 gotRefresh = iTrue; 1484 gotRefresh = iTrue;
1370 continue; 1485 continue;
@@ -1470,6 +1585,7 @@ void processEvents_App(enum iAppEventMode eventMode) {
1470 window->lastHover = window->hover; 1585 window->lastHover = window->hover;
1471 wasUsed = processEvent_Window(window, &ev); 1586 wasUsed = processEvent_Window(window, &ev);
1472 if (ev.type == SDL_MOUSEMOTION || ev.type == SDL_MOUSEBUTTONDOWN) { 1587 if (ev.type == SDL_MOUSEMOTION || ev.type == SDL_MOUSEBUTTONDOWN) {
1588 /* Only offered to the frontmost window. */
1473 break; 1589 break;
1474 } 1590 }
1475 if (wasUsed) break; 1591 if (wasUsed) break;
@@ -1563,6 +1679,9 @@ static void runTickers_App_(iApp *d) {
1563 iConstForEach(Array, i, &pending->values) { 1679 iConstForEach(Array, i, &pending->values) {
1564 const iTicker *ticker = i.value; 1680 const iTicker *ticker = i.value;
1565 if (ticker->callback) { 1681 if (ticker->callback) {
1682 if (ticker->root) {
1683 setCurrent_Window(ticker->root->window);
1684 }
1566 setCurrent_Root(ticker->root); /* root might be NULL */ 1685 setCurrent_Root(ticker->root); /* root might be NULL */
1567 ticker->callback(ticker->context); 1686 ticker->callback(ticker->context);
1568 } 1687 }
@@ -1755,11 +1874,11 @@ void postCommand_Root(iRoot *d, const char *command) {
1755 } 1874 }
1756 SDL_Event ev = { .type = SDL_USEREVENT }; 1875 SDL_Event ev = { .type = SDL_USEREVENT };
1757 ev.user.code = command_UserEventCode; 1876 ev.user.code = command_UserEventCode;
1758 /*ev.user.windowID = id_Window(get_Window());*/
1759 ev.user.data1 = strdup(command); 1877 ev.user.data1 = strdup(command);
1760 ev.user.data2 = d; /* all events are root-specific */ 1878 ev.user.data2 = d; /* all events are root-specific */
1879 ev.user.windowID = d ? id_Window(d->window) : 0; /* root-specific means window-specific */
1761 SDL_PushEvent(&ev); 1880 SDL_PushEvent(&ev);
1762 iWindow *win = get_Window(); 1881 iWindow *win = d ? d->window : NULL;
1763#if defined (iPlatformAndroid) 1882#if defined (iPlatformAndroid)
1764 SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "%s[command] {%d} %s", 1883 SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "%s[command] {%d} %s",
1765 app_.isLoadingPrefs ? "[Prefs] " : "", 1884 app_.isLoadingPrefs ? "[Prefs] " : "",
@@ -1767,10 +1886,17 @@ void postCommand_Root(iRoot *d, const char *command) {
1767 command); 1886 command);
1768#else 1887#else
1769 if (app_.commandEcho) { 1888 if (app_.commandEcho) {
1770 printf("%s[command] {%d} %s\n", 1889 const int windowIndex =
1771 app_.isLoadingPrefs ? "[Prefs] " : "", 1890 win && type_Window(win) == main_WindowType ? windowIndex_App(as_MainWindow(win)) + 1 : 0;
1772 (d == NULL || win == NULL ? 0 : d == win->roots[0] ? 1 : 2), 1891 printf("%s%s[command] {%d:%d} %s\n",
1773 command); fflush(stdout); 1892 !app_.isFinishedLaunching ? "<Ln> " : "",
1893 app_.isLoadingPrefs ? "<Pr> " : "",
1894 windowIndex,
1895 (d == NULL || win == NULL ? 0
1896 : d == win->roots[0] ? 1
1897 : 2),
1898 command);
1899 fflush(stdout);
1774 } 1900 }
1775#endif 1901#endif
1776} 1902}
@@ -1835,6 +1961,41 @@ void removeTicker_App(iTickerFunc ticker, iAny *context) {
1835 remove_SortedArray(&d->tickers, &(iTicker){ context, NULL, ticker }); 1961 remove_SortedArray(&d->tickers, &(iTicker){ context, NULL, ticker });
1836} 1962}
1837 1963
1964void addWindow_App(iMainWindow *win) {
1965 iApp *d = &app_;
1966 pushBack_PtrArray(&d->mainWindows, win);
1967}
1968
1969void removeWindow_App(iMainWindow *win) {
1970 iApp *d = &app_;
1971 removeOne_PtrArray(&d->mainWindows, win);
1972}
1973
1974size_t numWindows_App(void) {
1975 return size_PtrArray(&app_.mainWindows);
1976}
1977
1978size_t windowIndex_App(const iMainWindow *win) {
1979 return indexOf_PtrArray(&app_.mainWindows, win);
1980}
1981
1982iMainWindow *newMainWindow_App(void) {
1983 iApp *d = &app_;
1984 iMainWindow *win = new_MainWindow(initialWindowRect_App_(d, size_PtrArray(&d->mainWindows)));
1985 addWindow_App(win);
1986 return win;
1987}
1988
1989const iPtrArray *mainWindows_App(void) {
1990 return &app_.mainWindows;
1991}
1992
1993void setActiveWindow_App(iMainWindow *win) {
1994 iApp *d = &app_;
1995 d->window = win;
1996 printf("Active window: %p\n", win); fflush(stdout);
1997}
1998
1838void addPopup_App(iWindow *popup) { 1999void addPopup_App(iWindow *popup) {
1839 iApp *d = &app_; 2000 iApp *d = &app_;
1840 pushBack_PtrArray(&d->popupWindows, popup); 2001 pushBack_PtrArray(&d->popupWindows, popup);
@@ -1970,6 +2131,8 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) {
1970 toInt_String(text_InputWidget(findChild_Widget(d, "prefs.cachesize")))); 2131 toInt_String(text_InputWidget(findChild_Widget(d, "prefs.cachesize"))));
1971 postCommandf_App("memorysize.set arg:%d", 2132 postCommandf_App("memorysize.set arg:%d",
1972 toInt_String(text_InputWidget(findChild_Widget(d, "prefs.memorysize")))); 2133 toInt_String(text_InputWidget(findChild_Widget(d, "prefs.memorysize"))));
2134 postCommandf_App("urlsize.set arg:%d",
2135 toInt_String(text_InputWidget(findChild_Widget(d, "prefs.urlsize"))));
1973 postCommandf_App("ca.file path:%s", 2136 postCommandf_App("ca.file path:%s",
1974 cstrText_InputWidget(findChild_Widget(d, "prefs.ca.file"))); 2137 cstrText_InputWidget(findChild_Widget(d, "prefs.ca.file")));
1975 postCommandf_App("ca.path path:%s", 2138 postCommandf_App("ca.path path:%s",
@@ -2111,6 +2274,22 @@ iDocumentWidget *newTab_App(const iDocumentWidget *duplicateOf, iBool switchToNe
2111 return doc; 2274 return doc;
2112} 2275}
2113 2276
2277void closeWindow_App(iMainWindow *win) {
2278 iApp *d = &app_;
2279 delete_MainWindow(win);
2280 if (d->window == win) {
2281 /* Activate another window. */
2282 iForEach(PtrArray, i, &d->mainWindows) {
2283 if (i.ptr != d->window) {
2284 SDL_RaiseWindow(i.ptr);
2285 setActiveWindow_App(i.ptr);
2286 setCurrent_Window(i.ptr);
2287 break;
2288 }
2289 }
2290 }
2291}
2292
2114static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) { 2293static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) {
2115 iApp *d = &app_; 2294 iApp *d = &app_;
2116 if (equal_Command(cmd, "ident.showmore")) { 2295 if (equal_Command(cmd, "ident.showmore")) {
@@ -2257,6 +2436,13 @@ void resetFonts_App(void) {
2257 } 2436 }
2258} 2437}
2259 2438
2439void availableFontsChanged_App(void) {
2440 iApp *d = &app_;
2441 iConstForEach(PtrArray, win, listWindows_App_(d, collectNew_PtrArray())) {
2442 resetMissing_Text(text_Window(win.ptr));
2443 }
2444}
2445
2260static void invalidateCachedDocuments_App_(void) { 2446static void invalidateCachedDocuments_App_(void) {
2261 iForEach(ObjectList, i, iClob(listDocuments_App(NULL))) { 2447 iForEach(ObjectList, i, iClob(listDocuments_App(NULL))) {
2262 invalidateCachedLayout_History(history_DocumentWidget(i.object)); 2448 invalidateCachedLayout_History(history_DocumentWidget(i.object));
@@ -2274,6 +2460,7 @@ iBool handleCommand_App(const char *cmd) {
2274 suffixPtr_Command(cmd, "where"))); 2460 suffixPtr_Command(cmd, "where")));
2275 return iTrue; 2461 return iTrue;
2276 } 2462 }
2463#if 0 /* disabled in v1.11 */
2277 else if (equal_Command(cmd, "fontpack.suggest.classic")) { 2464 else if (equal_Command(cmd, "fontpack.suggest.classic")) {
2278 /* TODO: Don't use this when system fonts are accessible. */ 2465 /* TODO: Don't use this when system fonts are accessible. */
2279 if (!isInstalled_Fonts("classic-set") && !isInstalled_Fonts("cjk")) { 2466 if (!isInstalled_Fonts("classic-set") && !isInstalled_Fonts("cjk")) {
@@ -2290,6 +2477,7 @@ iBool handleCommand_App(const char *cmd) {
2290 } 2477 }
2291 return iTrue; 2478 return iTrue;
2292 } 2479 }
2480#endif
2293 else if (equal_Command(cmd, "prefs.changed")) { 2481 else if (equal_Command(cmd, "prefs.changed")) {
2294 savePrefs_App_(d); 2482 savePrefs_App_(d);
2295 return iTrue; 2483 return iTrue;
@@ -2360,12 +2548,16 @@ iBool handleCommand_App(const char *cmd) {
2360 return iTrue; 2548 return iTrue;
2361 } 2549 }
2362 else if (equal_Command(cmd, "window.maximize")) { 2550 else if (equal_Command(cmd, "window.maximize")) {
2363 if (!argLabel_Command(cmd, "toggle")) { 2551 const size_t winIndex = argU32Label_Command(cmd, "index");
2364 setSnap_MainWindow(d->window, maximized_WindowSnap); 2552 if (winIndex < size_PtrArray(&d->mainWindows)) {
2365 } 2553 iMainWindow *win = at_PtrArray(&d->mainWindows, winIndex);
2366 else { 2554 if (!argLabel_Command(cmd, "toggle")) {
2367 setSnap_MainWindow(d->window, snap_MainWindow(d->window) == maximized_WindowSnap ? 0 : 2555 setSnap_MainWindow(win, maximized_WindowSnap);
2368 maximized_WindowSnap); 2556 }
2557 else {
2558 setSnap_MainWindow(
2559 win, snap_MainWindow(win) == maximized_WindowSnap ? 0 : maximized_WindowSnap);
2560 }
2369 } 2561 }
2370 return iTrue; 2562 return iTrue;
2371 } 2563 }
@@ -2383,24 +2575,49 @@ iBool handleCommand_App(const char *cmd) {
2383 reload_Fonts(); /* also does font cache reset, window invalidation */ 2575 reload_Fonts(); /* also does font cache reset, window invalidation */
2384 return iTrue; 2576 return iTrue;
2385 } 2577 }
2386#if 0 2578 else if (equal_Command(cmd, "font.find")) {
2387 else if (equal_Command(cmd, "font.user")) { 2579 searchOnlineLibraryForCharacters_Fonts(string_Command(cmd, "chars"));
2388 const char *path = suffixPtr_Command(cmd, "path"); 2580 return iTrue;
2389 if (cmp_String(&d->prefs.symbolFontPath, path)) { 2581 }
2390 if (!isFrozen) { 2582 else if (equal_Command(cmd, "font.found")) {
2391 setFreezeDraw_MainWindow(get_MainWindow(), iTrue); 2583 if (hasLabel_Command(cmd, "error")) {
2392 } 2584 makeSimpleMessage_Widget("${heading.glyphfinder}",
2393 setCStr_String(&d->prefs.symbolFontPath, path); 2585 format_CStr("%d %s",
2394 loadUserFonts_Text(); 2586 argLabel_Command(cmd, "error"),
2395 resetFonts_App(d); 2587 suffixPtr_Command(cmd, "msg")));
2396 if (!isFrozen) { 2588 return iTrue;
2397 postCommand_App("font.changed");
2398 postCommand_App("window.unfreeze");
2399 }
2400 } 2589 }
2590 iString *src = collectNew_String();
2591 setCStr_String(src, "# ${heading.glyphfinder.results}\n\n");
2592 iRangecc path = iNullRange;
2593 iBool isFirst = iTrue;
2594 while (nextSplit_Rangecc(range_Command(cmd, "packs"), ",", &path)) {
2595 if (isFirst) {
2596 appendCStr_String(src, "${glyphfinder.results}\n\n");
2597 }
2598 iRangecc fpath = path;
2599 iRangecc fsize = path;
2600 fpath.end = strchr(fpath.start, ';');
2601 fsize.start = fpath.end + 1;
2602 const uint32_t size = strtoul(fsize.start, NULL, 10);
2603 appendFormat_String(src, "=> gemini://skyjake.fi/fonts/%s %s (%.1f MB)\n",
2604 cstr_Rangecc(fpath),
2605 cstr_Rangecc(fpath),
2606 (double) size / 1.0e6);
2607 isFirst = iFalse;
2608 }
2609 if (isFirst) {
2610 appendFormat_String(src, "${glyphfinder.results.empty}\n");
2611 }
2612 appendCStr_String(src, "\n=> about:fonts ${menu.fonts}");
2613 iDocumentWidget *page = newTab_App(NULL, iTrue);
2614 translate_Lang(src);
2615 setUrlAndSource_DocumentWidget(page,
2616 collectNewCStr_String(""),
2617 collectNewCStr_String("text/gemini"),
2618 utf8_String(src));
2401 return iTrue; 2619 return iTrue;
2402 } 2620 }
2403#endif
2404 else if (equal_Command(cmd, "font.set")) { 2621 else if (equal_Command(cmd, "font.set")) {
2405 if (!isFrozen) { 2622 if (!isFrozen) {
2406 setFreezeDraw_MainWindow(get_MainWindow(), iTrue); 2623 setFreezeDraw_MainWindow(get_MainWindow(), iTrue);
@@ -2690,6 +2907,10 @@ iBool handleCommand_App(const char *cmd) {
2690 postRefresh_App(); 2907 postRefresh_App();
2691 return iTrue; 2908 return iTrue;
2692 } 2909 }
2910 else if (equal_Command(cmd, "prefs.dataurl.openimages.changed")) {
2911 d->prefs.openDataUrlImagesOnLoad = arg_Command(cmd) != 0;
2912 return iTrue;
2913 }
2693 else if (equal_Command(cmd, "prefs.archive.openindex.changed")) { 2914 else if (equal_Command(cmd, "prefs.archive.openindex.changed")) {
2694 d->prefs.openArchiveIndexPages = arg_Command(cmd) != 0; 2915 d->prefs.openArchiveIndexPages = arg_Command(cmd) != 0;
2695 return iTrue; 2916 return iTrue;
@@ -2735,6 +2956,13 @@ iBool handleCommand_App(const char *cmd) {
2735 } 2956 }
2736 return iTrue; 2957 return iTrue;
2737 } 2958 }
2959 else if (equal_Command(cmd, "urlsize.set")) {
2960 d->prefs.maxUrlSize = arg_Command(cmd);
2961 if (d->prefs.maxUrlSize < 1024) {
2962 d->prefs.maxUrlSize = 1024; /* Gemini protocol requirement */
2963 }
2964 return iTrue;
2965 }
2738 else if (equal_Command(cmd, "searchurl")) { 2966 else if (equal_Command(cmd, "searchurl")) {
2739 iString *url = &d->prefs.strings[searchUrl_PrefsString]; 2967 iString *url = &d->prefs.strings[searchUrl_PrefsString];
2740 setCStr_String(url, suffixPtr_Command(cmd, "address")); 2968 setCStr_String(url, suffixPtr_Command(cmd, "address"));
@@ -2817,7 +3045,18 @@ iBool handleCommand_App(const char *cmd) {
2817 return iTrue; /* invalid command */ 3045 return iTrue; /* invalid command */
2818 } 3046 }
2819 if (findWidget_App("prefs")) { 3047 if (findWidget_App("prefs")) {
2820 postCommand_App("prefs.dismiss"); 3048 postCommand_App("prefs.dismiss");
3049 }
3050 if (argLabel_Command(cmd, "newwindow")) {
3051 const iRangecc gotoheading = range_Command(cmd, "gotoheading");
3052 const iRangecc gotourlheading = range_Command(cmd, "gotourlheading");
3053 postCommandf_Root(get_Root(), "window.new%s%s%s%s url:%s",
3054 isEmpty_Range(&gotoheading) ? "" : " gotoheading:",
3055 isEmpty_Range(&gotoheading) ? "" : cstr_Rangecc(gotoheading),
3056 isEmpty_Range(&gotourlheading) ? "" : " gotourlheading:",
3057 isEmpty_Range(&gotourlheading) ? "" : cstr_Rangecc(gotourlheading),
3058 urlArg);
3059 return iTrue;
2821 } 3060 }
2822 iString *url = collectNewCStr_String(urlArg); 3061 iString *url = collectNewCStr_String(urlArg);
2823 const iBool noProxy = argLabel_Command(cmd, "noproxy") != 0; 3062 const iBool noProxy = argLabel_Command(cmd, "noproxy") != 0;
@@ -2961,6 +3200,20 @@ iBool handleCommand_App(const char *cmd) {
2961#endif 3200#endif
2962 return iFalse; 3201 return iFalse;
2963 } 3202 }
3203 else if (equal_Command(cmd, "window.new")) {
3204 iMainWindow *newWin = new_MainWindow(initialWindowRect_App_(d, numWindows_App()));
3205 addWindow_App(newWin); /* takes ownership */
3206 SDL_ShowWindow(newWin->base.win);
3207 setCurrent_Window(newWin);
3208 if (hasLabel_Command(cmd, "url")) {
3209 postCommandf_Root(newWin->base.roots[0], "~open %s", cmd + 11 /* all arguments passed on */);
3210 }
3211 else {
3212 postCommand_Root(newWin->base.roots[0], "~navigate.home");
3213 }
3214 postCommand_Root(newWin->base.roots[0], "~window.unfreeze");
3215 return iTrue;
3216 }
2964 else if (equal_Command(cmd, "tabs.new")) { 3217 else if (equal_Command(cmd, "tabs.new")) {
2965 const iBool isDuplicate = argLabel_Command(cmd, "duplicate") != 0; 3218 const iBool isDuplicate = argLabel_Command(cmd, "duplicate") != 0;
2966 newTab_App(isDuplicate ? document_App() : NULL, iTrue); 3219 newTab_App(isDuplicate ? document_App() : NULL, iTrue);
@@ -3021,6 +3274,9 @@ iBool handleCommand_App(const char *cmd) {
3021 } 3274 }
3022 } 3275 }
3023 } 3276 }
3277 else if (numWindows_App() > 1) {
3278 closeWindow_App(d->window);
3279 }
3024 else { 3280 else {
3025 postCommand_App("quit"); 3281 postCommand_App("quit");
3026 } 3282 }
@@ -3050,6 +3306,7 @@ iBool handleCommand_App(const char *cmd) {
3050 setToggle_Widget(findChild_Widget(dlg, "prefs.hidetoolbarscroll"), d->prefs.hideToolbarOnScroll); 3306 setToggle_Widget(findChild_Widget(dlg, "prefs.hidetoolbarscroll"), d->prefs.hideToolbarOnScroll);
3051 setToggle_Widget(findChild_Widget(dlg, "prefs.bookmarks.addbottom"), d->prefs.addBookmarksToBottom); 3307 setToggle_Widget(findChild_Widget(dlg, "prefs.bookmarks.addbottom"), d->prefs.addBookmarksToBottom);
3052 setToggle_Widget(findChild_Widget(dlg, "prefs.font.warnmissing"), d->prefs.warnAboutMissingGlyphs); 3308 setToggle_Widget(findChild_Widget(dlg, "prefs.font.warnmissing"), d->prefs.warnAboutMissingGlyphs);
3309 setToggle_Widget(findChild_Widget(dlg, "prefs.dataurl.openimages"), d->prefs.openDataUrlImagesOnLoad);
3053 setToggle_Widget(findChild_Widget(dlg, "prefs.archive.openindex"), d->prefs.openArchiveIndexPages); 3310 setToggle_Widget(findChild_Widget(dlg, "prefs.archive.openindex"), d->prefs.openArchiveIndexPages);
3054 setToggle_Widget(findChild_Widget(dlg, "prefs.ostheme"), d->prefs.useSystemTheme); 3311 setToggle_Widget(findChild_Widget(dlg, "prefs.ostheme"), d->prefs.useSystemTheme);
3055 setToggle_Widget(findChild_Widget(dlg, "prefs.customframe"), d->prefs.customFrame); 3312 setToggle_Widget(findChild_Widget(dlg, "prefs.customframe"), d->prefs.customFrame);
@@ -3122,6 +3379,8 @@ iBool handleCommand_App(const char *cmd) {
3122 collectNewFormat_String("%d", d->prefs.maxCacheSize)); 3379 collectNewFormat_String("%d", d->prefs.maxCacheSize));
3123 setText_InputWidget(findChild_Widget(dlg, "prefs.memorysize"), 3380 setText_InputWidget(findChild_Widget(dlg, "prefs.memorysize"),
3124 collectNewFormat_String("%d", d->prefs.maxMemorySize)); 3381 collectNewFormat_String("%d", d->prefs.maxMemorySize));
3382 setText_InputWidget(findChild_Widget(dlg, "prefs.urlsize"),
3383 collectNewFormat_String("%d", d->prefs.maxUrlSize));
3125 setToggle_Widget(findChild_Widget(dlg, "prefs.decodeurls"), d->prefs.decodeUserVisibleURLs); 3384 setToggle_Widget(findChild_Widget(dlg, "prefs.decodeurls"), d->prefs.decodeUserVisibleURLs);
3126 setText_InputWidget(findChild_Widget(dlg, "prefs.searchurl"), &d->prefs.strings[searchUrl_PrefsString]); 3385 setText_InputWidget(findChild_Widget(dlg, "prefs.searchurl"), &d->prefs.strings[searchUrl_PrefsString]);
3127 setText_InputWidget(findChild_Widget(dlg, "prefs.ca.file"), &d->prefs.strings[caFile_PrefsString]); 3386 setText_InputWidget(findChild_Widget(dlg, "prefs.ca.file"), &d->prefs.strings[caFile_PrefsString]);
@@ -3509,21 +3768,7 @@ void revealPath_App(const iString *path) {
3509} 3768}
3510 3769
3511iObjectList *listDocuments_App(const iRoot *rootOrNull) { 3770iObjectList *listDocuments_App(const iRoot *rootOrNull) {
3512 iWindow *win = get_Window(); 3771 return listDocuments_MainWindow(get_MainWindow(), rootOrNull);
3513 iObjectList *docs = new_ObjectList();
3514 iForIndices(i, win->roots) {
3515 iRoot *root = win->roots[i];
3516 if (!root) continue;
3517 if (!rootOrNull || root == rootOrNull) {
3518 const iWidget *tabs = findChild_Widget(root->widget, "doctabs");
3519 iForEach(ObjectList, i, children_Widget(findChild_Widget(tabs, "tabs.pages"))) {
3520 if (isInstance_Object(i.object, &Class_DocumentWidget)) {
3521 pushBack_ObjectList(docs, i.object);
3522 }
3523 }
3524 }
3525 }
3526 return docs;
3527} 3772}
3528 3773
3529iStringSet *listOpenURLs_App(void) { 3774iStringSet *listOpenURLs_App(void) {
@@ -3540,12 +3785,16 @@ iMainWindow *mainWindow_App(void) {
3540 return app_.window; 3785 return app_.window;
3541} 3786}
3542 3787
3543void closePopups_App(void) { 3788void closePopups_App(iBool doForce) {
3544 iApp *d = &app_; 3789 iApp *d = &app_;
3545 const uint32_t now = SDL_GetTicks(); 3790 const uint32_t now = SDL_GetTicks();
3546 iConstForEach(PtrArray, i, &d->popupWindows) { 3791 iForEach(PtrArray, i, &d->popupWindows) {
3547 const iWindow *win = i.ptr; 3792 iWindow *win = i.ptr;
3548 if (now - win->focusGainedAt > 200) { 3793// if (doForce) {
3794// collect_Garbage(win, (iDeleteFunc) delete_Window);
3795// }
3796// else
3797 if (now - win->focusGainedAt > 200) {
3549 postCommand_Root(((const iWindow *) i.ptr)->roots[0], "cancel"); 3798 postCommand_Root(((const iWindow *) i.ptr)->roots[0], "cancel");
3550 } 3799 }
3551 } 3800 }