2019-02-05 04:59:03 +01:00
|
|
|
# Copyright: Ankitects Pty Ltd and contributors
|
2012-12-21 08:51:59 +01:00
|
|
|
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
|
2020-06-29 06:51:15 +02:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2019-12-20 10:19:03 +01:00
|
|
|
import time
|
2021-02-02 14:30:53 +01:00
|
|
|
from typing import Any
|
2019-12-20 10:19:03 +01:00
|
|
|
|
2012-12-21 08:51:59 +01:00
|
|
|
import aqt
|
2022-02-13 04:40:47 +01:00
|
|
|
import aqt.forms
|
|
|
|
import aqt.main
|
2023-03-29 05:32:00 +02:00
|
|
|
from anki.decks import DeckId
|
2020-07-16 21:48:46 +02:00
|
|
|
from aqt import gui_hooks
|
2023-03-29 05:32:00 +02:00
|
|
|
from aqt.operations.deck import set_current_deck
|
2019-12-20 10:19:03 +01:00
|
|
|
from aqt.qt import *
|
2020-06-30 08:23:46 +02:00
|
|
|
from aqt.theme import theme_manager
|
2019-12-23 01:34:10 +01:00
|
|
|
from aqt.utils import (
|
|
|
|
addCloseShortcut,
|
2021-01-07 05:24:49 +01:00
|
|
|
disable_help_button,
|
2019-12-23 01:34:10 +01:00
|
|
|
getSaveFile,
|
|
|
|
maybeHideClose,
|
|
|
|
restoreGeom,
|
|
|
|
saveGeom,
|
|
|
|
tooltip,
|
2020-11-18 02:53:33 +01:00
|
|
|
tr,
|
2019-12-23 01:34:10 +01:00
|
|
|
)
|
2023-02-10 05:53:11 +01:00
|
|
|
from aqt.webview import AnkiWebViewKind
|
2012-12-21 08:51:59 +01:00
|
|
|
|
|
|
|
|
2020-06-30 09:08:10 +02:00
|
|
|
class NewDeckStats(QDialog):
|
|
|
|
"""New deck stats."""
|
2012-12-21 08:51:59 +01:00
|
|
|
|
2021-02-01 14:28:21 +01:00
|
|
|
def __init__(self, mw: aqt.main.AnkiQt) -> None:
|
2021-10-05 05:53:01 +02:00
|
|
|
QDialog.__init__(self, mw, Qt.WindowType.Window)
|
2021-03-05 04:07:52 +01:00
|
|
|
mw.garbage_collect_on_dialog_finish(self)
|
2012-12-21 08:51:59 +01:00
|
|
|
self.mw = mw
|
|
|
|
self.name = "deckStats"
|
|
|
|
self.period = 0
|
|
|
|
self.form = aqt.forms.stats.Ui_Dialog()
|
|
|
|
self.oldPos = None
|
|
|
|
self.wholeCollection = False
|
|
|
|
self.setMinimumWidth(700)
|
2021-01-07 05:24:49 +01:00
|
|
|
disable_help_button(self)
|
2012-12-21 08:51:59 +01:00
|
|
|
f = self.form
|
|
|
|
f.setupUi(self)
|
2020-06-29 06:51:15 +02:00
|
|
|
f.groupBox.setVisible(False)
|
|
|
|
f.groupBox_2.setVisible(False)
|
2023-03-29 05:32:00 +02:00
|
|
|
if not is_mac:
|
|
|
|
f.horizontalLayout_4.setContentsMargins(0, 0, 0, 0)
|
2022-12-08 12:51:47 +01:00
|
|
|
restoreGeom(self, self.name, default_size=(800, 800))
|
2023-03-29 05:32:00 +02:00
|
|
|
|
|
|
|
from aqt.deckchooser import DeckChooser
|
|
|
|
|
2023-03-31 04:32:25 +02:00
|
|
|
self.deck_chooser = DeckChooser(
|
2023-03-29 05:32:00 +02:00
|
|
|
self.mw,
|
|
|
|
f.deckArea,
|
|
|
|
on_deck_changed=self.on_deck_changed,
|
|
|
|
)
|
|
|
|
|
2021-10-05 05:53:01 +02:00
|
|
|
b = f.buttonBox.addButton(
|
|
|
|
tr.statistics_save_pdf(), QDialogButtonBox.ButtonRole.ActionRole
|
|
|
|
)
|
2020-05-04 05:23:08 +02:00
|
|
|
qconnect(b.clicked, self.saveImage)
|
2012-12-21 08:51:59 +01:00
|
|
|
b.setAutoDefault(False)
|
2023-03-29 05:32:00 +02:00
|
|
|
b = f.buttonBox.button(QDialogButtonBox.StandardButton.Close)
|
|
|
|
b.setAutoDefault(False)
|
2012-12-21 08:51:59 +01:00
|
|
|
maybeHideClose(self.form.buttonBox)
|
2012-12-22 01:11:29 +01:00
|
|
|
addCloseShortcut(self)
|
2020-07-16 21:48:46 +02:00
|
|
|
gui_hooks.stats_dialog_will_show(self)
|
2023-02-10 05:53:11 +01:00
|
|
|
self.form.web.set_kind(AnkiWebViewKind.DECK_STATS)
|
2022-12-04 12:12:20 +01:00
|
|
|
self.form.web.hide_while_preserving_layout()
|
2016-05-31 10:51:40 +02:00
|
|
|
self.show()
|
2018-10-23 09:09:16 +02:00
|
|
|
self.refresh()
|
2021-01-05 22:04:31 +01:00
|
|
|
self.form.web.set_bridge_command(self._on_bridge_cmd, self)
|
2017-06-06 08:16:00 +02:00
|
|
|
self.activateWindow()
|
2012-12-21 08:51:59 +01:00
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def reject(self) -> None:
|
2023-03-31 04:32:25 +02:00
|
|
|
self.deck_chooser.cleanup()
|
2021-11-29 03:31:37 +01:00
|
|
|
self.form.web.cleanup()
|
2019-09-16 07:16:41 +02:00
|
|
|
self.form.web = None
|
2012-12-21 08:51:59 +01:00
|
|
|
saveGeom(self, self.name)
|
2020-06-30 09:08:10 +02:00
|
|
|
aqt.dialogs.markClosed("NewDeckStats")
|
2012-12-21 08:51:59 +01:00
|
|
|
QDialog.reject(self)
|
|
|
|
|
2021-02-02 14:30:53 +01:00
|
|
|
def closeWithCallback(self, callback: Callable[[], None]) -> None:
|
2017-08-16 04:45:33 +02:00
|
|
|
self.reject()
|
|
|
|
callback()
|
|
|
|
|
2023-03-29 05:32:00 +02:00
|
|
|
def on_deck_changed(self, deck_id: int) -> None:
|
|
|
|
set_current_deck(parent=self, deck_id=DeckId(deck_id)).success(
|
|
|
|
lambda _: self.refresh()
|
|
|
|
).run_in_background()
|
|
|
|
|
2021-02-01 14:28:21 +01:00
|
|
|
def _imagePath(self) -> str:
|
2019-12-23 01:34:10 +01:00
|
|
|
name = time.strftime("-%Y-%m-%d@%H-%M-%S.pdf", time.localtime(time.time()))
|
2021-03-26 04:48:26 +01:00
|
|
|
name = f"anki-{tr.statistics_stats()}{name}"
|
2019-12-23 01:34:10 +01:00
|
|
|
file = getSaveFile(
|
|
|
|
self,
|
2021-03-26 04:48:26 +01:00
|
|
|
title=tr.statistics_save_pdf(),
|
2019-12-23 01:34:10 +01:00
|
|
|
dir_description="stats",
|
|
|
|
key="stats",
|
|
|
|
ext=".pdf",
|
|
|
|
fname=name,
|
|
|
|
)
|
2017-09-10 07:50:34 +02:00
|
|
|
return file
|
2017-01-13 08:36:26 +01:00
|
|
|
|
2021-02-01 14:28:21 +01:00
|
|
|
def saveImage(self) -> None:
|
2017-01-13 08:36:26 +01:00
|
|
|
path = self._imagePath()
|
2017-09-10 07:50:34 +02:00
|
|
|
if not path:
|
|
|
|
return
|
2023-03-31 06:02:40 +02:00
|
|
|
|
2022-04-19 07:39:32 +02:00
|
|
|
# When scrolled down in dark mode, the top of the page in the
|
|
|
|
# final PDF will have a white background, making the text and graphs
|
|
|
|
# unreadable. A simple fix for now is to scroll to the top of the
|
|
|
|
# page first.
|
|
|
|
def after_scroll(arg: Any) -> None:
|
|
|
|
self.form.web.page().printToPdf(path)
|
|
|
|
tooltip(tr.statistics_saved())
|
|
|
|
|
|
|
|
self.form.web.evalWithCallback("window.scrollTo(0, 0);", after_scroll)
|
2012-12-21 08:51:59 +01:00
|
|
|
|
2021-02-02 14:30:53 +01:00
|
|
|
# legacy add-ons
|
|
|
|
def changePeriod(self, n: Any) -> None:
|
2020-06-29 06:51:15 +02:00
|
|
|
pass
|
2012-12-21 08:51:59 +01:00
|
|
|
|
2021-02-02 14:30:53 +01:00
|
|
|
def changeScope(self, type: Any) -> None:
|
2020-06-29 06:51:15 +02:00
|
|
|
pass
|
2012-12-21 08:51:59 +01:00
|
|
|
|
2021-01-05 22:04:31 +01:00
|
|
|
def _on_bridge_cmd(self, cmd: str) -> bool:
|
|
|
|
if cmd.startswith("browserSearch"):
|
2021-01-07 01:09:55 +01:00
|
|
|
_, query = cmd.split(":", 1)
|
2021-01-05 22:04:31 +01:00
|
|
|
browser = aqt.dialogs.open("Browser", self.mw)
|
|
|
|
browser.search_for(query)
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def refresh(self) -> None:
|
2020-08-27 13:53:28 +02:00
|
|
|
self.form.web.load_ts_page("graphs")
|
2020-06-30 09:08:10 +02:00
|
|
|
|
2020-08-27 13:46:34 +02:00
|
|
|
|
2020-06-30 09:08:10 +02:00
|
|
|
class DeckStats(QDialog):
|
|
|
|
"""Legacy deck stats, used by some add-ons."""
|
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def __init__(self, mw: aqt.main.AnkiQt) -> None:
|
2021-10-05 05:53:01 +02:00
|
|
|
QDialog.__init__(self, mw, Qt.WindowType.Window)
|
2021-03-05 04:07:52 +01:00
|
|
|
mw.garbage_collect_on_dialog_finish(self)
|
2020-06-30 09:08:10 +02:00
|
|
|
self.mw = mw
|
|
|
|
self.name = "deckStats"
|
|
|
|
self.period = 0
|
|
|
|
self.form = aqt.forms.stats.Ui_Dialog()
|
|
|
|
self.oldPos = None
|
|
|
|
self.wholeCollection = False
|
|
|
|
self.setMinimumWidth(700)
|
2021-01-07 05:24:49 +01:00
|
|
|
disable_help_button(self)
|
2020-06-30 09:08:10 +02:00
|
|
|
f = self.form
|
|
|
|
if theme_manager.night_mode and not theme_manager.macos_dark_mode():
|
|
|
|
# the grouping box renders incorrectly in the fusion theme. 5.9+
|
|
|
|
# 5.13 behave differently to 5.14, but it looks bad in either case,
|
|
|
|
# and adjusting the top margin makes the 'save PDF' button show in
|
|
|
|
# the wrong place, so for now we just disable the border instead
|
|
|
|
self.setStyleSheet("QGroupBox { border: 0; }")
|
|
|
|
f.setupUi(self)
|
|
|
|
restoreGeom(self, self.name)
|
2021-10-05 05:53:01 +02:00
|
|
|
b = f.buttonBox.addButton(
|
|
|
|
tr.statistics_save_pdf(), QDialogButtonBox.ButtonRole.ActionRole
|
|
|
|
)
|
2020-06-30 09:08:10 +02:00
|
|
|
qconnect(b.clicked, self.saveImage)
|
|
|
|
b.setAutoDefault(False)
|
|
|
|
qconnect(f.groups.clicked, lambda: self.changeScope("deck"))
|
|
|
|
f.groups.setShortcut("g")
|
|
|
|
qconnect(f.all.clicked, lambda: self.changeScope("collection"))
|
|
|
|
qconnect(f.month.clicked, lambda: self.changePeriod(0))
|
|
|
|
qconnect(f.year.clicked, lambda: self.changePeriod(1))
|
|
|
|
qconnect(f.life.clicked, lambda: self.changePeriod(2))
|
|
|
|
maybeHideClose(self.form.buttonBox)
|
|
|
|
addCloseShortcut(self)
|
2020-07-16 21:48:46 +02:00
|
|
|
gui_hooks.stats_dialog_old_will_show(self)
|
2020-06-30 09:08:10 +02:00
|
|
|
self.show()
|
|
|
|
self.refresh()
|
|
|
|
self.activateWindow()
|
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def reject(self) -> None:
|
2021-11-29 03:31:37 +01:00
|
|
|
self.form.web.cleanup()
|
2020-06-30 09:08:10 +02:00
|
|
|
self.form.web = None
|
|
|
|
saveGeom(self, self.name)
|
|
|
|
aqt.dialogs.markClosed("DeckStats")
|
|
|
|
QDialog.reject(self)
|
|
|
|
|
2021-02-02 14:30:53 +01:00
|
|
|
def closeWithCallback(self, callback: Callable[[], None]) -> None:
|
2020-06-30 09:08:10 +02:00
|
|
|
self.reject()
|
|
|
|
callback()
|
|
|
|
|
2021-02-01 14:28:21 +01:00
|
|
|
def _imagePath(self) -> str:
|
2020-06-30 09:08:10 +02:00
|
|
|
name = time.strftime("-%Y-%m-%d@%H-%M-%S.pdf", time.localtime(time.time()))
|
2021-03-26 04:48:26 +01:00
|
|
|
name = f"anki-{tr.statistics_stats()}{name}"
|
2020-06-30 09:08:10 +02:00
|
|
|
file = getSaveFile(
|
|
|
|
self,
|
2021-03-26 04:48:26 +01:00
|
|
|
title=tr.statistics_save_pdf(),
|
2020-06-30 09:08:10 +02:00
|
|
|
dir_description="stats",
|
|
|
|
key="stats",
|
|
|
|
ext=".pdf",
|
|
|
|
fname=name,
|
|
|
|
)
|
|
|
|
return file
|
|
|
|
|
2021-02-01 14:28:21 +01:00
|
|
|
def saveImage(self) -> None:
|
2020-06-30 09:08:10 +02:00
|
|
|
path = self._imagePath()
|
|
|
|
if not path:
|
|
|
|
return
|
|
|
|
self.form.web.page().printToPdf(path)
|
2021-03-26 04:48:26 +01:00
|
|
|
tooltip(tr.statistics_saved())
|
2020-06-30 09:08:10 +02:00
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def changePeriod(self, n: int) -> None:
|
2020-06-30 09:08:10 +02:00
|
|
|
self.period = n
|
|
|
|
self.refresh()
|
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def changeScope(self, type: str) -> None:
|
2020-06-30 09:08:10 +02:00
|
|
|
self.wholeCollection = type == "collection"
|
|
|
|
self.refresh()
|
|
|
|
|
2021-02-01 13:08:56 +01:00
|
|
|
def refresh(self) -> None:
|
2020-06-30 09:08:10 +02:00
|
|
|
self.mw.progress.start(parent=self)
|
|
|
|
stats = self.mw.col.stats()
|
|
|
|
stats.wholeCollection = self.wholeCollection
|
|
|
|
self.report = stats.report(type=self.period)
|
2023-02-10 05:53:11 +01:00
|
|
|
self.form.web.set_kind(AnkiWebViewKind.LEGACY_DECK_STATS)
|
2020-06-30 09:08:10 +02:00
|
|
|
self.form.web.stdHtml(
|
2021-02-11 01:09:06 +01:00
|
|
|
f"<html><body>{self.report}</body></html>",
|
2020-12-30 05:13:52 +01:00
|
|
|
js=["js/vendor/jquery.min.js", "js/vendor/plot.js"],
|
2020-06-30 09:08:10 +02:00
|
|
|
context=self,
|
|
|
|
)
|
|
|
|
self.mw.progress.finish()
|