From 77fb0fb70861dbec74103566331d67f9fdd99609 Mon Sep 17 00:00:00 2001 From: Ben Nguyen <105088397+bpnguyen107@users.noreply.github.com> Date: Fri, 20 Sep 2024 03:33:28 -0700 Subject: [PATCH 1/9] =?UTF-8?q?Possible=20to=20show=20=E2=80=9Clast?= =?UTF-8?q?=E2=80=9D=20subdeck=20name=20in=20Browser=3F=20(#3387)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * elide middle of deck names * Update CONTRIBUTORS * made elide mode enum * add elide mode field * fix enum number * remove dataclass decorator * Update CONTRIBUTORS * format rust code * Update CONTRIBUTORS * formatting * Update CONTRIBUTORS * fix type hint * Update CONTRIBUTORS --- CONTRIBUTORS | 2 +- proto/anki/search.proto | 8 ++++++++ pylib/anki/collection.py | 11 +++++++---- qt/aqt/browser/table/__init__.py | 27 ++++++++++++++++++++++----- qt/aqt/browser/table/table.py | 1 + rslib/src/browser_table.rs | 12 ++++++++++++ 6 files changed, 51 insertions(+), 10 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index ffb1a1f8423..ab77c8e950e 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -187,7 +187,7 @@ Christian Donat Asuka Minato Dillon Baldwin Voczi -Ben Nguyen <105088397+bpnguyen107@users.noreply.github.com> +Ben Nguyen <105088397+bpnguyen107@users.noreply.github.com> Themis Demetriades Gregory Abrasaldo Taylor Obyen diff --git a/proto/anki/search.proto b/proto/anki/search.proto index 85ac01c84af..bb417294c40 100644 --- a/proto/anki/search.proto +++ b/proto/anki/search.proto @@ -173,8 +173,16 @@ message BrowserColumns { message BrowserRow { message Cell { + enum TextElideMode { + ElideLeft = 0; + ElideRight = 1; + ElideMiddle = 2; + ElideNone = 3; + } + string text = 1; bool is_rtl = 2; + TextElideMode elide_mode = 3; } enum Color { COLOR_DEFAULT = 0; diff --git a/pylib/anki/collection.py b/pylib/anki/collection.py index ce1c7135a20..6ae37befe90 100644 --- a/pylib/anki/collection.py +++ b/pylib/anki/collection.py @@ -861,12 +861,15 @@ def get_browser_column(self, key: str) -> BrowserColumns.Column | None: return column return None - def browser_row_for_id( - self, id_: int - ) -> tuple[Generator[tuple[str, bool], None, None], BrowserRow.Color.V, str, int]: + def browser_row_for_id(self, id_: int) -> tuple[ + Generator[tuple[str, bool, BrowserRow.Cell.TextElideMode.V], None, None], + BrowserRow.Color.V, + str, + int, + ]: row = self._backend.browser_row_for_id(id_) return ( - ((cell.text, cell.is_rtl) for cell in row.cells), + ((cell.text, cell.is_rtl, cell.elide_mode) for cell in row.cells), row.color, row.font_name, row.font_size, diff --git a/qt/aqt/browser/table/__init__.py b/qt/aqt/browser/table/__init__.py index dc3ed7d5d84..a5a8853117c 100644 --- a/qt/aqt/browser/table/__init__.py +++ b/qt/aqt/browser/table/__init__.py @@ -33,10 +33,15 @@ class SearchContext: ids: Sequence[ItemId] | None = None -@dataclass class Cell: - text: str - is_rtl: bool + def __init__( + self, text: str, is_rtl: bool, elide_mode: BrowserRow.Cell.TextElideMode.V + ) -> None: + self.text: str = text + self.is_rtl: bool = is_rtl + self.elide_mode: aqt.Qt.TextElideMode = backend_elide_mode_to_aqt_elide_mode( + elide_mode + ) class CellRow: @@ -44,7 +49,7 @@ class CellRow: def __init__( self, - cells: Generator[tuple[str, bool], None, None], + cells: Generator[tuple[str, bool, BrowserRow.Cell.TextElideMode.V], None, None], color: BrowserRow.Color.V, font_name: str, font_size: int, @@ -61,7 +66,7 @@ def is_stale(self, threshold: float) -> bool: @staticmethod def generic(length: int, cell_text: str) -> CellRow: return CellRow( - ((cell_text, False) for cell in range(length)), + ((cell_text, False, BrowserRow.Cell.ElideRight) for cell in range(length)), BrowserRow.COLOR_DEFAULT, "arial", 12, @@ -78,6 +83,18 @@ def disabled(length: int, cell_text: str) -> CellRow: return row +def backend_elide_mode_to_aqt_elide_mode( + elide_mode: BrowserRow.Cell.TextElideMode.V, +) -> aqt.Qt.TextElideMode: + if elide_mode == BrowserRow.Cell.ElideLeft: + return aqt.Qt.TextElideMode.ElideLeft + if elide_mode == BrowserRow.Cell.ElideMiddle: + return aqt.Qt.TextElideMode.ElideMiddle + if elide_mode == BrowserRow.Cell.ElideNone: + return aqt.Qt.TextElideMode.ElideNone + return aqt.Qt.TextElideMode.ElideRight + + def backend_color_to_aqt_color(color: BrowserRow.Color.V) -> dict[str, str] | None: temp_color = None diff --git a/qt/aqt/browser/table/table.py b/qt/aqt/browser/table/table.py index 54631ebfa5f..d7ba18baa5a 100644 --- a/qt/aqt/browser/table/table.py +++ b/qt/aqt/browser/table/table.py @@ -643,6 +643,7 @@ def __init__(self, browser: aqt.browser.Browser, model: DataModel) -> None: def paint( self, painter: QPainter, option: QStyleOptionViewItem, index: QModelIndex ) -> None: + option.textElideMode = self._model.get_cell(index).elide_mode if self._model.get_cell(index).is_rtl: option.direction = Qt.LayoutDirection.RightToLeft if row_color := self._model.get_row(index).color: diff --git a/rslib/src/browser_table.rs b/rslib/src/browser_table.rs index be4e6393e13..0a03e545220 100644 --- a/rslib/src/browser_table.rs +++ b/rslib/src/browser_table.rs @@ -413,6 +413,7 @@ impl RowContext { Ok(anki_proto::search::browser_row::Cell { text: self.get_cell_text(column)?, is_rtl: self.get_is_rtl(column), + elide_mode: self.get_elide_mode(column) as i32, }) } @@ -461,6 +462,17 @@ impl RowContext { } } + fn get_elide_mode( + &self, + column: Column, + ) -> anki_proto::search::browser_row::cell::TextElideMode { + use anki_proto::search::browser_row::cell::TextElideMode; + match column { + Column::Deck => TextElideMode::ElideMiddle, + _ => TextElideMode::ElideRight, + } + } + fn template(&self) -> Result<&CardTemplate> { self.notetype.get_template(self.cards[0].template_idx) } From 1b7123aa9456b877a87b0b051ed7306bb0400c87 Mon Sep 17 00:00:00 2001 From: Luke Bartholomew Date: Fri, 20 Sep 2024 06:47:04 -0400 Subject: [PATCH 2/9] Add comment about the usage of the input field in the statistics page (#3394) (#3398) * Add comment about the usage of the input field in the statistics page (#3394) * Fix formatting issues (#3394) * Update ts/routes/graphs/RangeBox.svelte Co-authored-by: Mike Hardy * Update ts/routes/graphs/RangeBox.svelte Co-authored-by: Mike Hardy --------- Co-authored-by: Damien Elmes Co-authored-by: Mike Hardy --- CONTRIBUTORS | 1 + ts/routes/graphs/RangeBox.svelte | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index ab77c8e950e..bc66279ee17 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -189,6 +189,7 @@ Dillon Baldwin Voczi Ben Nguyen <105088397+bpnguyen107@users.noreply.github.com> Themis Demetriades +Luke Bartholomew Gregory Abrasaldo Taylor Obyen diff --git a/ts/routes/graphs/RangeBox.svelte b/ts/routes/graphs/RangeBox.svelte index c8a487ca077..30791b80d60 100644 --- a/ts/routes/graphs/RangeBox.svelte +++ b/ts/routes/graphs/RangeBox.svelte @@ -83,8 +83,11 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html {collection} + { From 96ff4f1a4a748a536d02147a0fd78f361fe25d70 Mon Sep 17 00:00:00 2001 From: Taylor Obyen <162023405+taylorobyen@users.noreply.github.com> Date: Fri, 20 Sep 2024 06:48:44 -0400 Subject: [PATCH 3/9] Fix occlusion rounding bug (#3400) * Fix occlusion rounding bug * Fix contributors --- CONTRIBUTORS | 2 +- ts/routes/image-occlusion/tools/lib.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index bc66279ee17..4a8debe67ed 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -191,7 +191,7 @@ Ben Nguyen <105088397+bpnguyen107@users.noreply.github.com> Themis Demetriades Luke Bartholomew Gregory Abrasaldo -Taylor Obyen +Taylor Obyen ******************** diff --git a/ts/routes/image-occlusion/tools/lib.ts b/ts/routes/image-occlusion/tools/lib.ts index 3f7483949be..fd3609bd351 100644 --- a/ts/routes/image-occlusion/tools/lib.ts +++ b/ts/routes/image-occlusion/tools/lib.ts @@ -320,5 +320,5 @@ export const isPointerInBoundingBox = (pointer): boolean => { export const getBoundingBox = () => { const canvas = globalThis.canvas; - return canvas.getObjects().find((obj) => obj.fill === "transparent"); + return canvas.getObjects().find((obj) => obj.fill === "transparent").getBoundingRect(true); }; From 0a879bd2ed7b2fe75ad178f8cbe078fb974bcbca Mon Sep 17 00:00:00 2001 From: Kris Cherven <50562493+krischerven@users.noreply.github.com> Date: Fri, 20 Sep 2024 11:00:12 +0000 Subject: [PATCH 4/9] Fix pasting from the primary selection (#3413) * Fix clipboard pasting from the primary selection * Small renaming * Fix submodules * Fix pylint false positive --- CONTRIBUTORS | 7 ++++--- qt/aqt/editor.py | 22 ++++++++++++++++++---- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 4a8debe67ed..ce4c02e4463 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -163,7 +163,7 @@ Lucas Scharenbroch Antonio Cavallo Han Yeong-woo Jean Khawand -Pedro Schreiber +Pedro Schreiber Foxy_null Arbyste Vasll @@ -187,11 +187,12 @@ Christian Donat Asuka Minato Dillon Baldwin Voczi -Ben Nguyen <105088397+bpnguyen107@users.noreply.github.com> +Ben Nguyen <105088397+bpnguyen107@users.noreply.github.com> Themis Demetriades Luke Bartholomew Gregory Abrasaldo -Taylor Obyen +Taylor Obyen +Kris Cherven ******************** diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index 74cc6e55baa..2c53a9f1cee 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -1452,19 +1452,33 @@ def _wantsExtendedPaste(self) -> bool: return not strip_html def _onPaste(self, mode: QClipboard.Mode) -> None: - # Since _on_clipboard_change doesn't always trigger properly on macOS, we do a double check if any changes were made before pasting + # Since _on_clipboard_change doesn't always trigger properly on macOS, + # we do a double check if any changes were made before pasting if self._last_known_clipboard_mime != self.editor.mw.app.clipboard().mimeData(): self._on_clipboard_change() extended = self._wantsExtendedPaste() - if html := self._internal_field_text_for_paste: + + def reuse_internal(): print("reuse internal") - self.editor.doPaste(html, True, extended) - else: + if html := self._internal_field_text_for_paste: + self.editor.doPaste(html, True, extended) + return True + return False + + def use_clipboard(): print("use clipboard") mime = self.editor.mw.app.clipboard().mimeData(mode=mode) html, internal = self._processMime(mime, extended) if html: self.editor.doPaste(html, internal, extended) + return True + return False + + if mode == QClipboard.Mode.Selection: + if not use_clipboard(): + reuse_internal() + else: + reuse_internal() def onPaste(self) -> None: self._onPaste(QClipboard.Mode.Clipboard) From cb3a7579de232fccd4c5ab539f467bfbb88dec04 Mon Sep 17 00:00:00 2001 From: sorata <136738526+brishtibheja@users.noreply.github.com> Date: Fri, 20 Sep 2024 16:43:35 +0530 Subject: [PATCH 5/9] Update tooltip text (#3418) * Update deck-config.ftl * Update deck-config.ftl * remove the warning --- ftl/core/deck-config.ftl | 61 +++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/ftl/core/deck-config.ftl b/ftl/core/deck-config.ftl index b45bb29375c..707d67d6a04 100644 --- a/ftl/core/deck-config.ftl +++ b/ftl/core/deck-config.ftl @@ -24,7 +24,7 @@ deck-config-review-limit-tooltip = if cards are ready for review. deck-config-limit-deck-v3 = When studying a deck that has subdecks inside it, the limits set on each - subdeck control the maximum number of cards drawn from that particular deck. + subdeck control the maximum number of cards gathered from that particular deck. The selected deck's limits control the total cards that will be shown. deck-config-limit-new-bound-by-reviews = The review limit affects the new limit. For example, if your review limit is @@ -33,9 +33,9 @@ deck-config-limit-new-bound-by-reviews = shown. deck-config-limit-interday-bound-by-reviews = The review limit also affects interday learning cards. When applying the limit, - interday learning cards are fetched first, then reviews. + interday learning cards are gathered first, then review cards. deck-config-tab-description = - - `Preset`: The limit is shared with all decks using this preset. + - `Preset`: The limit applies to all decks using this preset. - `This deck`: The limit is specific to this deck. - `Today only`: Make a temporary change to this deck's limit. deck-config-new-cards-ignore-review-limit = New cards ignore review limit @@ -45,9 +45,10 @@ deck-config-new-cards-ignore-review-limit-tooltip = will be shown regardless of the review limit. deck-config-apply-all-parent-limits = Limits start from top deck-config-apply-all-parent-limits-tooltip = - By default, limits start from the deck you select. If this option is enabled, the limits will + By default, the daily limits of a higher-level deck do not apply if you're studying from its subdeck. + If this option is enabled, the limits will start from the top-level deck instead, which can be useful if you wish to study individual - sub-decks, while enforcing a total limit on cards/day. + subdecks, while enforcing a total limit on cards for the deck tree. deck-config-affects-entire-collection = Affects the entire collection. ## Daily limit tabs: please try to keep these as short as the English version, @@ -82,7 +83,7 @@ deck-config-new-insertion-order-tooltip = deck-config-new-insertion-order-sequential = Sequential (oldest cards first) deck-config-new-insertion-order-random = Random deck-config-new-insertion-order-random-with-v3 = - With the V3 scheduler, it is better to leave this set to sequential, and + With the v3 scheduler, it is better to leave this set to sequential, and adjust the new card gather order instead. ## Lapses section @@ -112,7 +113,7 @@ deck-config-bury-new-siblings = Bury new siblings deck-config-bury-review-siblings = Bury review siblings deck-config-bury-interday-learning-siblings = Bury interday learning siblings deck-config-bury-new-tooltip = - Whether other `new` cards of the same note (eg reverse cards, adjacent cloze deletions) + Whether other `new` cards of the same note (e.g. reverse cards, adjacent cloze deletions) will be delayed until the next day. deck-config-bury-review-tooltip = Whether other `review` cards of the same note will be delayed until the next day. deck-config-bury-interday-learning-tooltip = @@ -120,7 +121,7 @@ deck-config-bury-interday-learning-tooltip = will be delayed until the next day. deck-config-bury-priority-tooltip = When Anki gathers cards, it first gathers intraday learning cards, then - interday learning cards, then reviews, and finally new cards. This affects + interday learning cards, then review cards, and finally new cards. This affects how burying works: - If you have all burying options enabled, the sibling that comes earliest in @@ -136,22 +137,20 @@ deck-config-bury-priority-tooltip = deck-config-ordering-title = Display Order deck-config-new-gather-priority = New card gather order deck-config-new-gather-priority-tooltip-2 = - `Deck`: gathers cards from each deck in order, starting from the top. Cards from each deck are + `Deck`: Gathers cards from each subdeck in order, starting from the top. Cards from each subdeck are gathered in ascending position. If the daily limit of the selected deck is reached, gathering - may stop before all decks have been checked. This order is fastest in large collections, and + can stop before all subdecks have been checked. This order is fastest in large collections, and allows you to prioritize subdecks that are closer to the top. - `Ascending position`: gathers cards by ascending position (due #), which is typically + `Ascending position`: Gathers cards by ascending position (due #), which is typically the oldest-added first. - `Descending position`: gathers cards by descending position (due #), which is typically + `Descending position`: Gathers cards by descending position (due #), which is typically the latest-added first. - `Random notes`: gathers cards of randomly selected notes. When sibling burying is - disabled, this allows all cards of a note to be seen in a session (e.g. both a front→back - and back→front card). + `Random notes`: Picks notes at random, then gathers all of its cards. - `Random cards`: gathers cards completely randomly. + `Random cards`: Gathers cards in a random order. deck-config-new-gather-priority-deck = Deck deck-config-new-gather-priority-deck-then-random-notes = Deck then random notes deck-config-new-gather-priority-position-lowest-first = Ascending position @@ -160,23 +159,23 @@ deck-config-new-gather-priority-random-notes = Random notes deck-config-new-gather-priority-random-cards = Random cards deck-config-new-card-sort-order = New card sort order deck-config-new-card-sort-order-tooltip-2 = - `Card type, then order gathered`: Displays cards in order of card type number. If you have sibling burying - disabled, this will ensure all front→back cards are seen before any back→front cards. + `Card type, then order gathered`: Shows cards in order of card type number. + Cards of each card type number are shown in the order they were gathered. + If you have sibling burying disabled, this will ensure all front→back cards are seen before any back→front cards. This is useful to have all cards of the same note shown in the same session, but not too close to one another. `Order gathered`: Shows cards exactly as they were gathered. If sibling burying is disabled, this will typically result in all cards of a note being seen one after the other. - `Card type, then random`: Like `Card type`, but shuffles the cards of each card - type number. If you use `Ascending position` to gather the oldest cards, you could use - this setting to see those cards in a random order, but still ensure cards of the same - note do not end up too close to one another. + `Card type, then random`: Shows cards in order of card type number. Cards of each card + type number are shown in a random order. This order is useful if you don't want sibling cards + to appear too close to each other, but still want the cards to appear in a random order. - `Random note, then card type`: Picks notes at random, then shows all of their siblings + `Random note, then card type`: Picks notes at random, then shows all of its cards in order. - `Random`: Fully shuffles the gathered cards. + `Random`: Shows cards in a random order. deck-config-sort-order-card-template-then-random = Card type, then random deck-config-sort-order-random-note-then-template = Random note, then card type deck-config-sort-order-random = Random @@ -189,7 +188,7 @@ deck-config-interday-step-priority-tooltip = When to show (re)learning cards that cross a day boundary. The review limit is always applied first to interday learning cards, and - then reviews. This option will control the order the gathered cards are shown in, + then review cards. This option will control the order the gathered cards are shown in, but interday learning cards will always be gathered first. deck-config-review-mix-mix-with-reviews = Mix with reviews deck-config-review-mix-show-after-reviews = Show after reviews @@ -368,14 +367,12 @@ deck-config-reschedule-cards-on-change = Reschedule cards on change deck-config-fsrs-tooltip = Affects the entire collection. - The Free Spaced Repetition Scheduler (FSRS) is an alternative to Anki's legacy SuperMemo 2 (SM-2) scheduler. - By more accurately determining when you are likely to forget, it can help you remember - more material in the same amount of time. This setting is shared by all deck presets. + The Free Spaced Repetition Scheduler (FSRS) is an alternative to Anki's legacy SuperMemo 2 (SM-2) algorithm. + By more accurately determining how likely you are to forget a card, it can help you remember + more material in the same amount of time. This setting is shared by all presets. - If you previously used the 'custom scheduling' version of FSRS, please make - sure you clear out the custom scheduling section before enabling this option. deck-config-desired-retention-tooltip = - The default value of 0.9 will schedule cards so you have a 90% chance of remembering them when + The default value of 0.9 schedules cards so that you have a 90% chance of remembering them when they come up for review again. If you increase this value, Anki will show cards more frequently to increase the chances of you remembering them. If you decrease the value, Anki will show cards less frequently, and you will forget more of them. Be conservative when adjusting this - higher @@ -393,7 +390,7 @@ deck-config-historical-retention-tooltip = SRS program. The latter is quite rare, so unless you're using the former option, you probably don't need to adjust - this setting. + this option. deck-config-weights-tooltip2 = FSRS parameters affect how cards are scheduled. Anki will start with default parameters. You can use the option below to optimize the parameters to best match your performance in decks using this preset. From 847f3f6714b96f949b03b6021cde0e2779a131d2 Mon Sep 17 00:00:00 2001 From: Abdo Date: Fri, 20 Sep 2024 14:18:02 +0300 Subject: [PATCH 6/9] Fix FSRS progress update issues (#3420) * Delay optimal FSRS params alert to ensure progress updates are reported * Ensure progress updates arrive synchronously --- ts/lib/tslib/progress.ts | 12 +++++++++--- ts/routes/deck-options/FsrsOptions.svelte | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/ts/lib/tslib/progress.ts b/ts/lib/tslib/progress.ts index 86381b2644d..cbf13e60169 100644 --- a/ts/lib/tslib/progress.ts +++ b/ts/lib/tslib/progress.ts @@ -8,13 +8,19 @@ export async function runWithBackendProgress( callback: () => Promise, onUpdate: (progress: Progress) => void, ): Promise { - const intervalId = setInterval(async () => { + let done = false; + async function progressCallback() { const progress = await latestProgress({}); onUpdate(progress); - }, 100); + if (done) { + return; + } + setTimeout(progressCallback, 100); + } + setTimeout(progressCallback, 100); try { return await callback(); } finally { - clearInterval(intervalId); + done = true; } } diff --git a/ts/routes/deck-options/FsrsOptions.svelte b/ts/routes/deck-options/FsrsOptions.svelte index 905f41c137c..9c91d02e407 100644 --- a/ts/routes/deck-options/FsrsOptions.svelte +++ b/ts/routes/deck-options/FsrsOptions.svelte @@ -150,7 +150,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html )) || resp.weights.length === 0 ) { - alert(tr.deckConfigFsrsParamsOptimal()); + setTimeout(() => alert(tr.deckConfigFsrsParamsOptimal()), 100); } if (computeWeightsProgress) { computeWeightsProgress.current = computeWeightsProgress.total; From 5bd66db3c677e18f39653f1cdaf2db4959ab8ff0 Mon Sep 17 00:00:00 2001 From: Abdo Date: Fri, 20 Sep 2024 14:22:27 +0300 Subject: [PATCH 7/9] Call the profile_did_open() hook earlier (#3421) --- qt/aqt/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qt/aqt/main.py b/qt/aqt/main.py index a27ec8abdca..58be32fd4f5 100644 --- a/qt/aqt/main.py +++ b/qt/aqt/main.py @@ -529,8 +529,8 @@ def refresh_reviewer_on_day_rollover_change(): ) refresh_reviewer_on_day_rollover_change() - self.maybe_auto_sync_on_open_close(_onsuccess) gui_hooks.profile_did_open() + self.maybe_auto_sync_on_open_close(_onsuccess) def unloadProfile(self, onsuccess: Callable) -> None: def callback() -> None: From 02d25669983e379a0b57dcdea5996b2367d2f2c5 Mon Sep 17 00:00:00 2001 From: user1823 <92206575+user1823@users.noreply.github.com> Date: Fri, 20 Sep 2024 17:08:44 +0530 Subject: [PATCH 8/9] Add an option to show image from editor in folder (#3412) * Add "Show in folder" option to images in editor Credits: @abdnh's Reveal in File Manager add-on (https://github.com/abdnh/anki-misc/tree/master/reveal_in_file_manager) * Refactor --- ftl/core/editing.ftl | 1 + qt/aqt/editor.py | 41 ++++++++++++++++++++++++----------------- qt/aqt/utils.py | 28 ++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 17 deletions(-) diff --git a/ftl/core/editing.ftl b/ftl/core/editing.ftl index 5c9b766f56c..2da97d5397b 100644 --- a/ftl/core/editing.ftl +++ b/ftl/core/editing.ftl @@ -36,6 +36,7 @@ editing-mathjax-chemistry = MathJax chemistry editing-mathjax-inline = MathJax inline editing-mathjax-placeholder = Press { $accept } to accept, { $newline } for new line. editing-media = Media +editing-show-in-folder = Show in folder editing-ordered-list = Ordered list editing-outdent = Decrease indent editing-paste = Paste diff --git a/qt/aqt/editor.py b/qt/aqt/editor.py index 2c53a9f1cee..522fec329c3 100644 --- a/qt/aqt/editor.py +++ b/qt/aqt/editor.py @@ -36,8 +36,8 @@ from anki.httpclient import HttpClient from anki.models import NotetypeId, StockNotetype from anki.notes import Note, NoteFieldsCheckResult, NoteId -from anki.utils import checksum, is_lin, is_win, namedtmp -from aqt import AnkiQt, colors, gui_hooks +from anki.utils import checksum, is_lin, is_mac, is_win, namedtmp +from aqt import AnkiQt, colors, gui_hooks, mw from aqt.operations import QueryOp from aqt.operations.note import update_note from aqt.operations.notetype import update_notetype_legacy @@ -55,6 +55,7 @@ saveGeom, shortcut, showInfo, + showinFolder, showWarning, tooltip, tr, @@ -1599,28 +1600,34 @@ def _processImage(self, mime: QMimeData, extended: bool = False) -> str | None: def contextMenuEvent(self, evt: QContextMenuEvent) -> None: m = QMenu(self) - self._maybe_add_cut_action(m) - self._maybe_add_copy_action(m) + if self.hasSelection(): + self._add_cut_action(m) + self._add_copy_action(m) a = m.addAction(tr.editing_paste()) qconnect(a.triggered, self.onPaste) - self._maybe_add_copy_image_action(m) + if self._opened_context_menu_on_image(): + self._add_image_menu(AnkiWebView(self), m) gui_hooks.editor_will_show_context_menu(self, m) m.popup(QCursor.pos()) - def _maybe_add_cut_action(self, menu: QMenu) -> None: - if self.hasSelection(): - a = menu.addAction(tr.editing_cut()) - qconnect(a.triggered, self.onCut) + def _add_cut_action(self, menu: QMenu) -> None: + a = menu.addAction(tr.editing_cut()) + qconnect(a.triggered, self.onCut) - def _maybe_add_copy_action(self, menu: QMenu) -> None: - if self.hasSelection(): - a = menu.addAction(tr.actions_copy()) - qconnect(a.triggered, self.onCopy) + def _add_copy_action(self, menu: QMenu) -> None: + a = menu.addAction(tr.actions_copy()) + qconnect(a.triggered, self.onCopy) - def _maybe_add_copy_image_action(self, menu: QMenu) -> None: - if self._opened_context_menu_on_image(): - a = menu.addAction(tr.editing_copy_image()) - qconnect(a.triggered, self.on_copy_image) + def _add_image_menu(self, webview: AnkiWebView, menu: QMenu) -> None: + a = menu.addAction(tr.editing_copy_image()) + qconnect(a.triggered, self.on_copy_image) + + if is_win or is_mac: + url = webview.lastContextMenuRequest().mediaUrl() + file_name = url.fileName() + path = os.path.join(mw.col.media.dir(), file_name) + a = menu.addAction(tr.editing_show_in_folder()) + qconnect(a.triggered, lambda: showinFolder(path)) # QFont returns "Kozuka Gothic Pro L" but WebEngine expects "Kozuka Gothic Pro Light" diff --git a/qt/aqt/utils.py b/qt/aqt/utils.py index 8d777455522..b265b2f6e4d 100644 --- a/qt/aqt/utils.py +++ b/qt/aqt/utils.py @@ -21,6 +21,7 @@ from anki.collection import Collection, HelpPage from anki.lang import TR, tr_legacyglobal # pylint: disable=unused-import from anki.utils import ( + call, invalid_filename, is_mac, is_win, @@ -885,6 +886,33 @@ def openFolder(path: str) -> None: QDesktopServices.openUrl(QUrl(f"file://{path}")) +def showinFolder(path: str) -> None: + if is_win: + call(["explorer", "/select,", f"file://{path}"]) + elif is_mac: + script = f""" + tell application "Finder" + activate + select POSIX file '{path}' + end tell + """ + call(osascript_to_args(script)) + else: + # Just open the file in any other platform + with no_bundled_libs(): + QDesktopServices.openUrl(QUrl(f"file://{path}")) + + +def osascript_to_args(script: str): + args = [ + item + for line in script.splitlines() + for item in ("-e", line.strip()) + if line.strip() + ] + return ["osascript"] + args + + def shortcut(key: str) -> str: if is_mac: return re.sub("(?i)ctrl", "Command", key) From 722b9b53f4889073e34a80785bd9d3cbc25b0eb4 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Fri, 20 Sep 2024 22:04:52 +1000 Subject: [PATCH 9/9] Update translations --- ftl/core-repo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ftl/core-repo b/ftl/core-repo index 382eea48986..9a74db37394 160000 --- a/ftl/core-repo +++ b/ftl/core-repo @@ -1 +1 @@ -Subproject commit 382eea48986b8e625143f66d1e03b74805f37abf +Subproject commit 9a74db373945c4df7b7c65138d0a80061efcbfa8