diff --git a/ftl/core/preferences.ftl b/ftl/core/preferences.ftl index 6e3bf0730..facd16271 100644 --- a/ftl/core/preferences.ftl +++ b/ftl/core/preferences.ftl @@ -37,4 +37,8 @@ preferences-you-can-restore-backups-via-fileswitch = You can restore backups via preferences-legacy-timezone-handling = Legacy timezone handling (buggy, but required for AnkiDroid <= 2.14) preferences-default-search-text = Default search text preferences-default-search-text-example = eg. 'deck:current ' +preferences-theme-label = Theme: { $theme } +preferences-theme-follow-system = Follow System +preferences-theme-light = Light +preferences-theme-dark = Dark preferences-v3-scheduler = V3 scheduler diff --git a/qt/aqt/browser/sidebar/searchbar.py b/qt/aqt/browser/sidebar/searchbar.py index ae08f22fb..9d12e3fae 100644 --- a/qt/aqt/browser/sidebar/searchbar.py +++ b/qt/aqt/browser/sidebar/searchbar.py @@ -19,6 +19,14 @@ class SidebarSearchBar(QLineEdit): self.timer.setInterval(600) self.timer.setSingleShot(True) self.setFrame(False) + self.setup_style() + + qconnect(self.timer.timeout, self.onSearch) + qconnect(self.textChanged, self.onTextChanged) + + aqt.gui_hooks.theme_did_change.append(self.setup_style) + + def setup_style(self) -> None: border = theme_manager.color(colors.MEDIUM_BORDER) styles = [ "padding: 1px", @@ -32,9 +40,6 @@ class SidebarSearchBar(QLineEdit): self.setStyleSheet("QLineEdit { %s }" % ";".join(styles)) - qconnect(self.timer.timeout, self.onSearch) - qconnect(self.textChanged, self.onTextChanged) - def onTextChanged(self, text: str) -> None: if not self.timer.isActive(): self.timer.start() @@ -49,3 +54,6 @@ class SidebarSearchBar(QLineEdit): self.onSearch() else: QLineEdit.keyPressEvent(self, evt) + + def cleanup(self) -> None: + aqt.gui_hooks.theme_did_change.remove(self.setup_style) diff --git a/qt/aqt/browser/sidebar/toolbar.py b/qt/aqt/browser/sidebar/toolbar.py index 3f544682d..030b47041 100644 --- a/qt/aqt/browser/sidebar/toolbar.py +++ b/qt/aqt/browser/sidebar/toolbar.py @@ -31,6 +31,7 @@ class SidebarToolbar(QToolBar): self.setIconSize(QSize(16, 16)) self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) self.setStyle(QStyleFactory.create("fusion")) + aqt.gui_hooks.theme_did_change.append(self._update_icons) def _setup_tools(self) -> None: for row, tool in enumerate(self._tools): @@ -48,3 +49,10 @@ class SidebarToolbar(QToolBar): def _on_action_group_triggered(self, action: QAction) -> None: index = self._action_group.actions().index(action) self.sidebar.tool = self._tools[index][0] + + def cleanup(self) -> None: + aqt.gui_hooks.theme_did_change.remove(self._update_icons) + + def _update_icons(self) -> None: + for idx, action in enumerate(self._action_group.actions()): + action.setIcon(theme_manager.icon_from_resources(self._tools[idx][1])) diff --git a/qt/aqt/browser/sidebar/tree.py b/qt/aqt/browser/sidebar/tree.py index b7b88d01f..06f138994 100644 --- a/qt/aqt/browser/sidebar/tree.py +++ b/qt/aqt/browser/sidebar/tree.py @@ -91,6 +91,16 @@ class SidebarTreeView(QTreeView): qconnect(self.expanded, self._on_expansion) qconnect(self.collapsed, self._on_collapse) + self._setup_style() + + # these do not really belong here, they should be in a higher-level class + self.toolbar = SidebarToolbar(self) + self.searchBar = SidebarSearchBar(self) + + gui_hooks.flag_label_did_change.append(self.refresh) + gui_hooks.theme_did_change.append(self._setup_style) + + def _setup_style(self) -> None: # match window background color and tweak style bgcolor = QPalette().window().color().name() border = theme_manager.color(colors.MEDIUM_BORDER) @@ -105,14 +115,11 @@ class SidebarTreeView(QTreeView): self.setStyleSheet("QTreeView { %s }" % ";".join(styles)) - # these do not really belong here, they should be in a higher-level class - self.toolbar = SidebarToolbar(self) - self.searchBar = SidebarSearchBar(self) - - gui_hooks.flag_label_did_change.append(self.refresh) - def cleanup(self) -> None: + self.toolbar.cleanup() + self.searchBar.cleanup() gui_hooks.flag_label_did_change.remove(self.refresh) + gui_hooks.theme_did_change.remove(self._setup_style) @property def tool(self) -> SidebarTool: diff --git a/qt/aqt/browser/table/table.py b/qt/aqt/browser/table/table.py index c11a518da..7ddc68b57 100644 --- a/qt/aqt/browser/table/table.py +++ b/qt/aqt/browser/table/table.py @@ -59,6 +59,7 @@ class Table: def cleanup(self) -> None: self._save_header() + gui_hooks.theme_did_change.remove(self._setup_style) # Public Methods ###################################################################### @@ -342,17 +343,10 @@ class Table: self._view.setHorizontalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel) self._view.horizontalScrollBar().setSingleStep(10) self._update_font() - if not theme_manager.night_mode: - self._view.setStyleSheet( - "QTableView{ selection-background-color: rgba(150, 150, 150, 50); " - "selection-color: black; }" - ) - elif theme_manager.macos_dark_mode(): - self._view.setStyleSheet( - f"QTableView {{ gridline-color: {colors.FRAME_BG} }}" - ) + self._setup_style() self._view.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) qconnect(self._view.customContextMenuRequested, self._on_context_menu) + gui_hooks.theme_did_change.append(self._setup_style) def _update_font(self) -> None: # we can't choose different line heights efficiently, so we need @@ -365,6 +359,19 @@ class Table: curmax = bsize self._view.verticalHeader().setDefaultSectionSize(curmax + 6) + def _setup_style(self) -> None: + if not theme_manager.night_mode: + self._view.setStyleSheet( + "QTableView{ selection-background-color: rgba(150, 150, 150, 50); " + "selection-color: black; }" + ) + elif theme_manager.macos_dark_mode(): + self._view.setStyleSheet( + f"QTableView {{ gridline-color: {colors.FRAME_BG} }}" + ) + else: + self._view.setStyleSheet("") + def _setup_headers(self) -> None: vh = self._view.verticalHeader() hh = self._view.horizontalHeader() diff --git a/qt/aqt/clayout.py b/qt/aqt/clayout.py index ca081af87..5771278f9 100644 --- a/qt/aqt/clayout.py +++ b/qt/aqt/clayout.py @@ -55,7 +55,7 @@ class CardLayout(QDialog): self.model = note.note_type() self.templates = self.model["tmpls"] self.fill_empty_action_toggled = fill_empty - self.night_mode_is_enabled = self.mw.pm.night_mode() + self.night_mode_is_enabled = theme_manager.night_mode self.mobile_emulation_enabled = False self.have_autoplayed = False self.mm._remove_from_cache(self.model["id"]) diff --git a/qt/aqt/forms/preferences.ui b/qt/aqt/forms/preferences.ui index 6ab365da3..9bff83670 100644 --- a/qt/aqt/forms/preferences.ui +++ b/qt/aqt/forms/preferences.ui @@ -55,6 +55,9 @@ + + + @@ -86,13 +89,6 @@ - - - - preferences_night_mode - - - @@ -602,12 +598,12 @@ lang + theme video_driver showPlayButtons interrupt_audio pastePNG paste_strips_formatting - nightMode useCurrent default_search_text uiScale diff --git a/qt/aqt/main.py b/qt/aqt/main.py index d40fcd935..47378c0e0 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -47,7 +47,7 @@ from aqt.qt import * from aqt.qt import sip from aqt.sync import sync_collection, sync_login from aqt.taskman import TaskManager -from aqt.theme import theme_manager +from aqt.theme import Theme, theme_manager from aqt.undo import UndoActionsInfo from aqt.utils import ( HelpPage, @@ -145,11 +145,11 @@ class AnkiQt(QMainWindow): self.setupMediaServer() self.setupSound() self.setupSpellCheck() + self.setupProgress() self.setupStyle() self.setupMainWindow() self.setupSystemSpecific() self.setupMenus() - self.setupProgress() self.setupErrorHandler() self.setupSignals() self.setupAutoUpdate() @@ -1004,8 +1004,14 @@ title="{}" {}>{}""".format( return True def setupStyle(self) -> None: - theme_manager.night_mode = self.pm.night_mode() - theme_manager.apply_style(self.app) + theme_manager.apply_style() + self.progress.timer( + 5 * 1000, theme_manager.apply_style_if_system_style_changed, True, False + ) + + def set_theme(self, theme: Theme) -> None: + self.pm.set_theme(theme) + self.setupStyle() # Key handling ########################################################################## diff --git a/qt/aqt/platform.py b/qt/aqt/platform.py index fdf038e1b..ace59fd44 100644 --- a/qt/aqt/platform.py +++ b/qt/aqt/platform.py @@ -3,20 +3,40 @@ """Platform-specific functionality.""" +from __future__ import annotations + import os import sys from ctypes import CDLL import aqt.utils -from anki.utils import isMac +from anki.utils import isMac, isWin -def set_dark_mode(enabled: bool) -> bool: +def get_windows_dark_mode() -> bool: + "True if Windows system is currently in dark mode." + if not isWin: + return False + + from winreg import ( # pylint: disable=import-error + HKEY_CURRENT_USER, + OpenKey, + QueryValueEx, + ) + + key = OpenKey( + HKEY_CURRENT_USER, + r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize", + ) + return not QueryValueEx(key, "AppsUseLightTheme")[0] + + +def set_macos_dark_mode(enabled: bool) -> bool: "True if setting successful." if not isMac: return False try: - _set_dark_mode(enabled) + _ankihelper().set_darkmode_enabled(enabled) return True except Exception as e: # swallow exceptions, as library will fail on macOS 10.13 @@ -24,9 +44,28 @@ def set_dark_mode(enabled: bool) -> bool: return False -def _set_dark_mode(enabled: bool) -> None: +def get_macos_dark_mode() -> bool: + "True if macOS system is currently in dark mode." + if not isMac: + return False + try: + return _ankihelper().system_is_dark() + except Exception as e: + # swallow exceptions, as library will fail on macOS 10.13 + print(e) + return False + + +_ankihelper_dll: CDLL | None = None + + +def _ankihelper() -> CDLL: + global _ankihelper_dll + if _ankihelper_dll: + return _ankihelper_dll if getattr(sys, "frozen", False): path = os.path.join(sys.prefix, "libankihelper.dylib") else: path = os.path.join(aqt.utils.aqt_data_folder(), "lib", "libankihelper.dylib") - CDLL(path).set_darkmode_enabled(enabled) + _ankihelper_dll = CDLL(path) + return _ankihelper_dll diff --git a/qt/aqt/preferences.py b/qt/aqt/preferences.py index 134507865..e677728e1 100644 --- a/qt/aqt/preferences.py +++ b/qt/aqt/preferences.py @@ -11,6 +11,7 @@ from aqt import AnkiQt from aqt.operations.collection import set_preferences from aqt.profiles import VideoDriver from aqt.qt import * +from aqt.theme import Theme from aqt.utils import HelpPage, disable_help_button, openHelp, showInfo, showWarning, tr @@ -199,7 +200,17 @@ class Preferences(QDialog): def setup_global(self) -> None: "Setup options global to all profiles." self.form.uiScale.setValue(int(self.mw.pm.uiScale() * 100)) - self.form.nightMode.setChecked(self.mw.pm.night_mode()) + themes = [ + tr.preferences_theme_label(theme=theme) + for theme in ( + tr.preferences_theme_follow_system(), + tr.preferences_theme_light(), + tr.preferences_theme_dark(), + ) + ] + self.form.theme.addItems(themes) + self.form.theme.setCurrentIndex(self.mw.pm.theme().value) + qconnect(self.form.theme.currentIndexChanged, self.on_theme_changed) self.setup_language() self.setup_video_driver() @@ -216,15 +227,14 @@ class Preferences(QDialog): self.mw.pm.setUiScale(newScale) restart_required = True - if self.mw.pm.night_mode() != self.form.nightMode.isChecked(): - self.mw.pm.set_night_mode(not self.mw.pm.night_mode()) - restart_required = True - if restart_required: showInfo(tr.preferences_changes_will_take_effect_when_you()) self.updateOptions() + def on_theme_changed(self, index: int) -> None: + self.mw.set_theme(Theme(index)) + # legacy - one of Henrik's add-ons is currently wrapping them def setupOptions(self) -> None: diff --git a/qt/aqt/profiles.py b/qt/aqt/profiles.py index 976ca37e7..da37dc53c 100644 --- a/qt/aqt/profiles.py +++ b/qt/aqt/profiles.py @@ -23,6 +23,7 @@ from anki.sync import SyncAuth from anki.utils import int_time, isMac, isWin from aqt import appHelpSite from aqt.qt import * +from aqt.theme import Theme from aqt.utils import disable_help_button, showWarning, tr # Profile handling @@ -515,6 +516,12 @@ create table if not exists profiles def set_night_mode(self, on: bool) -> None: self.meta["night_mode"] = on + def theme(self) -> Theme: + return Theme(self.meta.get("theme", 0)) + + def set_theme(self, theme: Theme) -> None: + self.meta["theme"] = theme.value + def dark_mode_widgets(self) -> bool: return self.meta.get("dark_mode_widgets", False) diff --git a/qt/aqt/theme.py b/qt/aqt/theme.py index 7fde423aa..b28860234 100644 --- a/qt/aqt/theme.py +++ b/qt/aqt/theme.py @@ -3,12 +3,14 @@ from __future__ import annotations +import enum import platform from dataclasses import dataclass +import aqt from anki.utils import isMac from aqt import QApplication, colors, gui_hooks, isWin -from aqt.platform import set_dark_mode +from aqt.platform import get_macos_dark_mode, get_windows_dark_mode, set_macos_dark_mode from aqt.qt import ( QColor, QGuiApplication, @@ -37,6 +39,12 @@ class ColoredIcon: return ColoredIcon(path=self.path, color=color) +class Theme(enum.IntEnum): + FOLLOW_SYSTEM = 0 + LIGHT = 1 + DARK = 2 + + class ThemeManager: _night_mode_preference = False _icon_cache_light: dict[str, QIcon] = {} @@ -44,6 +52,7 @@ class ThemeManager: _icon_size = 128 _dark_mode_available: bool | None = None default_palette: QPalette | None = None + _default_style: str | None = None # Qt applies a gradient to the buttons in dark mode # from about #505050 to #606060. @@ -58,7 +67,7 @@ class ThemeManager: return False if self._dark_mode_available is None: - self._dark_mode_available = set_dark_mode(True) + self._dark_mode_available = set_macos_dark_mode(True) from aqt import mw @@ -143,28 +152,57 @@ class ThemeManager: def qcolor(self, colors: tuple[str, str]) -> QColor: return QColor(self.color(colors)) - def apply_style(self, app: QApplication) -> None: - self.default_palette = QGuiApplication.palette() + def _determine_night_mode(self) -> bool: + theme = aqt.mw.pm.theme() + if theme == Theme.LIGHT: + return False + elif theme == Theme.DARK: + return True + else: + if isWin: + return get_windows_dark_mode() + elif isMac: + return get_macos_dark_mode() + else: + # not supported on Linux + return False + + def apply_style_if_system_style_changed(self) -> None: + theme = aqt.mw.pm.theme() + if theme != Theme.FOLLOW_SYSTEM: + return + if self._determine_night_mode() != self.night_mode: + self.apply_style() + + def apply_style(self) -> None: + "Apply currently configured style." + app = aqt.mw.app + self.night_mode = self._determine_night_mode() + if not self.default_palette: + self.default_palette = QGuiApplication.palette() + self._default_style = app.style().objectName() self._apply_palette(app) self._apply_style(app) + gui_hooks.theme_did_change() def _apply_style(self, app: QApplication) -> None: buf = "" - if isWin and platform.release() == "10" and not self.night_mode: - # add missing bottom border to menubar - buf += """ -QMenuBar { - border-bottom: 1px solid #aaa; - background: white; -} + if isWin and platform.release() == "10": + # day mode is missing a bottom border; background must be + # also set for border to apply + buf += f""" +QMenuBar {{ + border-bottom: 1px solid {self.color(colors.BORDER)}; + background: {self.color(colors.WINDOW_BG)}; +}} """ # qt bug? setting the above changes the browser sidebar # to white as well, so set it back - buf += """ -QTreeWidget { - background: #eee; -} + buf += f""" +QTreeWidget {{ + background: {self.color(colors.WINDOW_BG)}; +}} """ if self.night_mode: @@ -209,7 +247,11 @@ QTabWidget {{ background-color: {}; }} app.setStyleSheet(buf) def _apply_palette(self, app: QApplication) -> None: + set_macos_dark_mode(self.night_mode) + if not self.night_mode: + app.setStyle(QStyleFactory.create(self._default_style)) # type: ignore + app.setPalette(self.default_palette) return if not self.macos_dark_mode(): diff --git a/qt/aqt/webview.py b/qt/aqt/webview.py index 21e1e79df..dd3a7f8f9 100644 --- a/qt/aqt/webview.py +++ b/qt/aqt/webview.py @@ -252,6 +252,7 @@ class AnkiWebView(QWebEngineView): context=Qt.ShortcutContext.WidgetWithChildrenShortcut, activated=self.onEsc, ) + gui_hooks.theme_did_change.append(self.on_theme_did_change) def set_title(self, title: str) -> None: self.title = title # type: ignore[assignment] @@ -445,7 +446,7 @@ div[contenteditable="true"]:focus {{ lang_dir = "ltr" return f""" -body {{ zoom: {zoom}; background-color: {body_bg}; direction: {lang_dir}; }} +body {{ zoom: {zoom}; background-color: --window-bg; direction: {lang_dir}; }} html {{ {font} }} {button_style} :root {{ --window-bg: {window_bg_day} }} @@ -551,6 +552,8 @@ html {{ {font} }} self._maybeRunActions() def _maybeRunActions(self) -> None: + if sip.isdeleted(self): + return while self._pendingActions and self._domDone: name, args = self._pendingActions.pop(0) @@ -675,4 +678,29 @@ document.head.appendChild(style); # this will fail when __del__ is called during app shutdown return + gui_hooks.theme_did_change.remove(self.on_theme_did_change) mw.mediaServer.clear_page_html(id(self)) + + def on_theme_did_change(self) -> None: + # avoid flashes if page reloaded + self._page.setBackgroundColor( + self.get_window_bg_color(theme_manager.night_mode) + ) + # update night-mode class, and legacy nightMode/night-mode body classes + self.eval( + f""" +(function() {{ + const doc = document.documentElement.classList; + const body = document.body.classList; + if ({1 if theme_manager.night_mode else 0}) {{ + doc.add("night-mode"); + body.add("night-mode"); + body.add("nightMode"); + }} else {{ + doc.remove("night-mode"); + body.remove("night-mode"); + body.remove("nightMode"); + }} +}})(); +""" + ) diff --git a/qt/mac/AnkiHelper.m b/qt/mac/AnkiHelper.m index b0bda45e1..2b461ee82 100644 --- a/qt/mac/AnkiHelper.m +++ b/qt/mac/AnkiHelper.m @@ -4,6 +4,7 @@ @import Foundation; @import AppKit; +/// Force our app to be either light or dark mode. void set_darkmode_enabled(BOOL enabled) { NSAppearance *appearance; if (enabled) { @@ -14,3 +15,20 @@ void set_darkmode_enabled(BOOL enabled) { [NSApplication sharedApplication].appearance = appearance; } + +/// True if the system is set to dark mode. +BOOL system_is_dark(void) { + BOOL styleSet = [[NSUserDefaults standardUserDefaults] objectForKey:@"AppleInterfaceStyle"] != nil; + return styleSet; + // FIXME: confirm whether this is required on 10.15/16 (it + // does not appear to be on 11) + + // BOOL autoSwitch = [[NSUserDefaults standardUserDefaults] boolForKey:@"AppleInterfaceStyleSwitchesAutomatically"]; + // + // if (@available(macOS 10.15, *)) { + // return autoSwitch ? !styleSet : styleSet; + // } else { + // return styleSet; + // } + +} diff --git a/qt/mac/ankihelper.xcodeproj/project.pbxproj b/qt/mac/ankihelper.xcodeproj/project.pbxproj new file mode 100644 index 000000000..d03fb4567 --- /dev/null +++ b/qt/mac/ankihelper.xcodeproj/project.pbxproj @@ -0,0 +1,280 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 55; + objects = { + +/* Begin PBXBuildFile section */ + 1327600A274613D9001D63D7 /* AnkiHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 13276009274613D8001D63D7 /* AnkiHelper.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 13276009274613D8001D63D7 /* AnkiHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AnkiHelper.m; sourceTree = ""; }; + 138B770F2746137F003A3E4F /* libankihelper.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libankihelper.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 138B770D2746137F003A3E4F /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 138B77062746137F003A3E4F = { + isa = PBXGroup; + children = ( + 13276009274613D8001D63D7 /* AnkiHelper.m */, + 138B77102746137F003A3E4F /* Products */, + ); + sourceTree = ""; + }; + 138B77102746137F003A3E4F /* Products */ = { + isa = PBXGroup; + children = ( + 138B770F2746137F003A3E4F /* libankihelper.dylib */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 138B770B2746137F003A3E4F /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 138B770E2746137F003A3E4F /* ankihelper */ = { + isa = PBXNativeTarget; + buildConfigurationList = 138B77182746137F003A3E4F /* Build configuration list for PBXNativeTarget "ankihelper" */; + buildPhases = ( + 138B770B2746137F003A3E4F /* Headers */, + 138B770C2746137F003A3E4F /* Sources */, + 138B770D2746137F003A3E4F /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ankihelper; + productName = ankihelper; + productReference = 138B770F2746137F003A3E4F /* libankihelper.dylib */; + productType = "com.apple.product-type.library.dynamic"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 138B77072746137F003A3E4F /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastUpgradeCheck = 1310; + TargetAttributes = { + 138B770E2746137F003A3E4F = { + CreatedOnToolsVersion = 13.1; + }; + }; + }; + buildConfigurationList = 138B770A2746137F003A3E4F /* Build configuration list for PBXProject "ankihelper" */; + compatibilityVersion = "Xcode 13.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 138B77062746137F003A3E4F; + productRefGroup = 138B77102746137F003A3E4F /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 138B770E2746137F003A3E4F /* ankihelper */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 138B770C2746137F003A3E4F /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 1327600A274613D9001D63D7 /* AnkiHelper.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 138B77162746137F003A3E4F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 11.6; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + }; + name = Debug; + }; + 138B77172746137F003A3E4F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 11.6; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = macosx; + }; + name = Release; + }; + 138B77192746137F003A3E4F /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 7ZM8SLJM4P; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + EXECUTABLE_PREFIX = lib; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 138B771A2746137F003A3E4F /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = 7ZM8SLJM4P; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + EXECUTABLE_PREFIX = lib; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 138B770A2746137F003A3E4F /* Build configuration list for PBXProject "ankihelper" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 138B77162746137F003A3E4F /* Debug */, + 138B77172746137F003A3E4F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 138B77182746137F003A3E4F /* Build configuration list for PBXNativeTarget "ankihelper" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 138B77192746137F003A3E4F /* Debug */, + 138B771A2746137F003A3E4F /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 138B77072746137F003A3E4F /* Project object */; +} diff --git a/qt/mac/ankihelper.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/qt/mac/ankihelper.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000..919434a62 --- /dev/null +++ b/qt/mac/ankihelper.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/qt/mac/ankihelper.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/qt/mac/ankihelper.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/qt/mac/ankihelper.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/qt/mac/ankihelper.xcodeproj/project.xcworkspace/xcuserdata/dae.xcuserdatad/UserInterfaceState.xcuserstate b/qt/mac/ankihelper.xcodeproj/project.xcworkspace/xcuserdata/dae.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 000000000..255052f65 Binary files /dev/null and b/qt/mac/ankihelper.xcodeproj/project.xcworkspace/xcuserdata/dae.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/qt/mac/ankihelper.xcodeproj/xcuserdata/dae.xcuserdatad/xcschemes/xcschememanagement.plist b/qt/mac/ankihelper.xcodeproj/xcuserdata/dae.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 000000000..7b914bfe3 --- /dev/null +++ b/qt/mac/ankihelper.xcodeproj/xcuserdata/dae.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + ankihelper.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/qt/tools/genhooks_gui.py b/qt/tools/genhooks_gui.py index 8910a1c17..3c2938a0d 100644 --- a/qt/tools/genhooks_gui.py +++ b/qt/tools/genhooks_gui.py @@ -540,6 +540,10 @@ hooks = [ there are no outstanding ops. """, ), + Hook( + name="theme_did_change", + doc="Called after night mode is toggled.", + ), # Webview ################### Hook( diff --git a/ts/change-notetype/index.ts b/ts/change-notetype/index.ts index b57572b9f..eaa5aa639 100644 --- a/ts/change-notetype/index.ts +++ b/ts/change-notetype/index.ts @@ -9,7 +9,6 @@ import { ChangeNotetypeState, getChangeNotetypeInfo, getNotetypeNames } from "./ import { setupI18n, ModuleName } from "../lib/i18n"; import { checkNightMode } from "../lib/nightmode"; import ChangeNotetypePage from "./ChangeNotetypePage.svelte"; -import { nightModeKey } from "../components/context-keys"; export async function changeNotetypePage( target: HTMLDivElement, @@ -28,14 +27,11 @@ export async function changeNotetypePage( }), ]); - const nightMode = checkNightMode(); - const context = new Map(); - context.set(nightModeKey, nightMode); + checkNightMode(); const state = new ChangeNotetypeState(names, info); return new ChangeNotetypePage({ target, props: { state }, - context, } as any); } diff --git a/ts/components/ButtonToolbar.svelte b/ts/components/ButtonToolbar.svelte index baa79fd7a..b6b29b78e 100644 --- a/ts/components/ButtonToolbar.svelte +++ b/ts/components/ButtonToolbar.svelte @@ -16,13 +16,14 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html