diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 48bbd87032..2bceb84620 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -4249,6 +4249,7 @@ axis Both И двете @msg colorTable.{what}: `{h}` is not a colour such as "#ff8800" colorTable.{what}: `{h}` не е цвят като "#ff8800" @msg colorTable.{what}: at most {MAX_TABLE} colours colorTable.{what}: най-много {MAX_TABLE} цвята @msg command `{0}` is not available right now: {1} команда `{0}` не е налична в момента: {1} +@msg command `{id}` is not available right now команда `{id}` не е налична в момента @msg compound paths can only contain paths съставните пътища могат да съдържат само пътища @msg compressed (RLE) bitmaps can't be read yet компресираните (RLE) битови карти все още не могат да бъдат четени @msg damaged повреден diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index 8b401bda51..8939105bef 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -4639,6 +4639,7 @@ imageTrace Outline Obris @msg colorTable.{what}: `{h}` is not a colour such as "#ff8800" colorTable.{what}: `{h}` nije boja poput "#ff8800" @msg colorTable.{what}: at most {MAX_TABLE} colours colorTable.{what}: najviše {MAX_TABLE} boja @msg command `{0}` is not available right now: {1} naredba `{0}` trenutno nije dostupna: {1} +@msg command `{id}` is not available right now naredba `{id}` trenutno nije dostupna @msg compound paths can only contain paths složene putanje mogu sadržavati samo putanje @msg compressed (RLE) bitmaps can't be read yet komprimirane (RLE) bitmape se još ne mogu čitati @msg damaged oštećeno diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index ffc9d97622..2d532078e4 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -5314,6 +5314,7 @@ digits Malayalam Malaiàlam @msg column must be true or a number from 1 column ha de ser true o un nombre a partir d’1 @msg Combine Slices: the slices have no bounds Combina els talls: els talls no tenen límits @msg command `{0}` is not available right now: {1} l’ordre `{0}` no està disponible ara mateix: {1} +@msg command `{id}` is not available right now l’ordre `{id}` no està disponible ara mateix @msg compound paths can only contain paths els traçats compostos només poden contenir traçats @msg compressed (RLE) bitmaps can't be read yet encara no es poden llegir els mapes de bits comprimits (RLE) @msg control must be fillStroke, coloring, drawMode or screenMode control ha de ser fillStroke, coloring, drawMode o screenMode diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 9774911885..3b04567253 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -4720,6 +4720,7 @@ imageTrace Outline Obrys @msg colorTable.{what}: `{h}` is not a colour such as "#ff8800" colorTable.{what}: `{h}` není barva jako "#ff8800" @msg colorTable.{what}: at most {MAX_TABLE} colours colorTable.{what}: nejvýše {MAX_TABLE} barev @msg command `{0}` is not available right now: {1} příkaz `{0}` nyní není k dispozici: {1} +@msg command `{id}` is not available right now příkaz `{id}` nyní není k dispozici @msg compound paths can only contain paths složené cesty mohou obsahovat jen cesty @msg compressed (RLE) bitmaps can't be read yet komprimované bitmapy (RLE) zatím nelze načíst @msg damaged poškozeno diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 0fc66b4c1e..37b5a2c26a 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -4361,6 +4361,7 @@ imageTrace Outline Kontur @msg colorTable.{what}: `{h}` is not a colour such as "#ff8800" colorTable.{what}: `{h}` er ikke en farve som "#ff8800" @msg colorTable.{what}: at most {MAX_TABLE} colours colorTable.{what}: højst {MAX_TABLE} farver @msg command `{0}` is not available right now: {1} kommandoen `{0}` er ikke tilgængelig lige nu: {1} +@msg command `{id}` is not available right now kommandoen `{id}` er ikke tilgængelig lige nu @msg compound paths can only contain paths sammensatte kurver kan kun indeholde kurver @msg compressed (RLE) bitmaps can't be read yet komprimerede (RLE) bitmaps kan endnu ikke læses @msg damaged beskadiget diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 2894538a03..4c8b33d8ab 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -4335,6 +4335,7 @@ theme Appearance Erscheinungsbild @msg colorTable.{what}: `{h}` is not a colour such as "#ff8800" colorTable.{what}: `{h}` ist keine Farbe wie "#ff8800" @msg colorTable.{what}: at most {MAX_TABLE} colours colorTable.{what}: höchstens {MAX_TABLE} Farben @msg command `{0}` is not available right now: {1} der Befehl `{0}` ist derzeit nicht verfügbar: {1} +@msg command `{id}` is not available right now der Befehl `{id}` ist derzeit nicht verfügbar @msg compound paths can only contain paths zusammengesetzte Pfade können nur Pfade enthalten @msg compressed (RLE) bitmaps can't be read yet komprimierte Bitmaps (RLE) können noch nicht gelesen werden @msg damaged beschädigt diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 717df0dfcb..31eedd8719 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -4364,6 +4364,7 @@ digits Malayalam Μαλαγιάλαμ @msg colorTable.{what}: `{h}` is not a colour such as "#ff8800" colorTable.{what}: το `{h}` δεν είναι έγκυρο χρώμα, όπως το "#ff8800" @msg colorTable.{what}: at most {MAX_TABLE} colours colorTable.{what}: επιτρέπονται το πολύ {MAX_TABLE} χρώματα @msg command `{0}` is not available right now: {1} η εντολή `{0}` δεν είναι διαθέσιμη αυτήν τη στιγμή: {1} +@msg command `{id}` is not available right now η εντολή `{id}` δεν είναι διαθέσιμη αυτήν τη στιγμή @msg compound paths can only contain paths οι σύνθετες διαδρομές μπορούν να περιέχουν μόνο διαδρομές @msg compressed (RLE) bitmaps can't be read yet τα συμπιεσμένα (RLE) bitmap δεν μπορούν ακόμη να διαβαστούν @msg damaged κατεστραμμένο diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 1e448a0873..1a3a8938b5 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -4411,6 +4411,7 @@ axis Both Ambos @msg colorTable.{what}: `{h}` is not a colour such as "#ff8800" colorTable.{what}: `{h}` no es un color como "#ff8800" @msg colorTable.{what}: at most {MAX_TABLE} colours colorTable.{what}: como máximo {MAX_TABLE} colores @msg command `{0}` is not available right now: {1} el comando `{0}` no está disponible ahora: {1} +@msg command `{id}` is not available right now el comando `{id}` no está disponible ahora @msg compound paths can only contain paths los trazados compuestos solo pueden contener trazados @msg compressed (RLE) bitmaps can't be read yet todavía no se pueden leer mapas de bits comprimidos (RLE) @msg damaged dañado diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 8621169050..04b33ba7fe 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -4626,6 +4626,7 @@ imageTrace Outline Ääriviiva @msg colorTable.{what}: `{h}` is not a colour such as "#ff8800" colorTable.{what}: `{h}` ei ole väri, kuten "#ff8800" @msg colorTable.{what}: at most {MAX_TABLE} colours colorTable.{what}: enintään {MAX_TABLE} väriä @msg command `{0}` is not available right now: {1} komento `{0}` ei ole nyt käytettävissä: {1} +@msg command `{id}` is not available right now komento `{id}` ei ole nyt käytettävissä @msg compound paths can only contain paths yhdistelmäpolut voivat sisältää vain polkuja @msg compressed (RLE) bitmaps can't be read yet pakattuja (RLE) bittikarttoja ei voi vielä lukea @msg damaged vaurioitunut diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 2584f54eb3..a0a27c6f1e 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -4400,6 +4400,7 @@ axis Both Les deux @msg colorTable.{what}: `{h}` is not a colour such as "#ff8800" colorTable.{what} : `{h}` n’est pas une couleur comme "#ff8800" @msg colorTable.{what}: at most {MAX_TABLE} colours colorTable.{what} : {MAX_TABLE} couleurs au maximum @msg command `{0}` is not available right now: {1} la commande `{0}` n’est pas disponible pour le moment : {1} +@msg command `{id}` is not available right now la commande `{id}` n’est pas disponible pour le moment @msg compound paths can only contain paths les tracés transparents ne peuvent contenir que des tracés @msg compressed (RLE) bitmaps can't be read yet les images bitmap compressées (RLE) ne peuvent pas encore être lues @msg damaged endommagé diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index ce27103c5c..888bd6b69a 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -6311,6 +6311,7 @@ antiAlias Sharp शार्प @msg columns {first} and {column} both name the variable `{name}` कॉलम {first} और {column} दोनों नाम चर `{name}` @msg Combine Slices: the slices have no bounds स्लाइस को मिलाएं: स्लाइस में कोई सीमा नहीं है @msg command `{0}` is not available right now: {1} आदेश `{0}` अभी उपलब्ध नहीं है: {1} +@msg command `{id}` is not available right now आदेश `{id}` अभी उपलब्ध नहीं है @msg command must be a shape.* command command: आदेश एक आकार होना चाहिए।* आदेश @msg commands[{i}]: missing `command` आदेश [{i}]: लापता `command` @msg composer must be "singleLine" or "everyLine" composer: संगीतकार "singleLine" या "everyLine" होना चाहिए। diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 1620b61e05..1021075f7d 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -4962,6 +4962,7 @@ imageTrace Outline Körvonal @msg column must be true or a number from 1 a column értéke true vagy 1-től kezdődő szám lehet @msg Combine Slices: the slices have no bounds Szeletek egyesítése: a szeleteknek nincsenek határai @msg command `{0}` is not available right now: {1} a parancs jelenleg nem érhető el: `{0}`: {1} +@msg command `{id}` is not available right now a parancs jelenleg nem érhető el: `{id}` @msg compound paths can only contain paths összetett útvonal csak útvonalakat tartalmazhat @msg compressed (RLE) bitmaps can't be read yet a tömörített (RLE) bitképek még nem olvashatók @msg Convert to Artboards: selection has no area Átalakítás rajztáblákká: a kijelölésnek nincs területe diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 90aef8df12..03be745a2c 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -4361,6 +4361,7 @@ imageTrace Outline Kontur @msg colorTable.{what}: `{h}` is not a colour such as "#ff8800" colorTable.{what}: `{h}` bukan warna seperti "#ff8800" @msg colorTable.{what}: at most {MAX_TABLE} colours colorTable.{what}: maksimal {MAX_TABLE} warna @msg command `{0}` is not available right now: {1} perintah `{0}` tidak tersedia saat ini: {1} +@msg command `{id}` is not available right now perintah `{id}` tidak tersedia saat ini @msg compound paths can only contain paths jalur majemuk hanya dapat berisi jalur @msg compressed (RLE) bitmaps can't be read yet bitmap terkompresi (RLE) belum dapat dibaca @msg damaged rusak diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 59ec43b1e8..3852d45cb2 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -4462,6 +4462,7 @@ axis Both Entrambi @msg colorTable.{what}: `{h}` is not a colour such as "#ff8800" colorTable.{what}: `{h}` non è un colore come "#ff8800" @msg colorTable.{what}: at most {MAX_TABLE} colours colorTable.{what}: al massimo {MAX_TABLE} colori @msg command `{0}` is not available right now: {1} il comando `{0}` non è disponibile in questo momento: {1} +@msg command `{id}` is not available right now il comando `{id}` non è disponibile in questo momento @msg compound paths can only contain paths i tracciati composti possono contenere solo tracciati @msg compressed (RLE) bitmaps can't be read yet le bitmap compresse (RLE) non possono ancora essere lette @msg damaged danneggiato diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 294a543606..34ab381353 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -4594,6 +4594,7 @@ swatch Bright Violet ビビッドバイオレット @msg nothing to distort 変形するものがありません @msg unknown option `{path}.{k}` `{path}.{k}` は不明なオプションです @msg command `{0}` is not available right now: {1} コマンド `{0}` は現在使用できません: {1} +@msg command `{id}` is not available right now コマンド `{id}` は現在使用できません @msg internal error in `{cmd}`: {msg} (the document was left as it was before; please report this bug) `{cmd}` で内部エラーが発生しました: {msg} (ドキュメントは変更されていません。このバグを報告してください) @msg invalid parameters for `{cmd}`: {msg} `{cmd}` のパラメーターが正しくありません: {msg} @msg no active document アクティブなドキュメントがありません diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index f1f7b5729c..21bf53df97 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -4180,6 +4180,7 @@ font-technology Variable ცვლადი @msg colorTable.{what}: `{h}` is not a colour such as "#ff8800" colorTable.{what}: `{h}` ფერი არ არის, მაგალითად "#ff8800" @msg colorTable.{what}: at most {MAX_TABLE} colours colorTable.{what}: არაუმეტეს {MAX_TABLE} ფერი @msg command `{0}` is not available right now: {1} ბრძანება `{0}` ახლა ხელმისაწვდომი არ არის: {1} +@msg command `{id}` is not available right now ბრძანება `{id}` ახლა ხელმისაწვდომი არ არის @msg compound paths can only contain paths შედგენილი ტრაექტორია მხოლოდ ტრაექტორიებს შეიძლება შეიცავდეს @msg compressed (RLE) bitmaps can't be read yet შეკუმშული (RLE) ბიტმაპების წაკითხვა ჯერ არ ხერხდება @msg damaged დაზიანებულია diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 69ad078300..d25ace6243 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -4521,6 +4521,7 @@ theme Appearance 외관 @msg nothing to distort 왜곡할 항목이 없습니다 @msg unknown option `{path}.{k}` 알 수 없는 옵션 `{path}.{k}` @msg command `{0}` is not available right now: {1} 명령 `{0}`은(는) 지금 사용할 수 없습니다: {1} +@msg command `{id}` is not available right now 명령 `{id}`은(는) 지금 사용할 수 없습니다 @msg internal error in `{cmd}`: {msg} (the document was left as it was before; please report this bug) `{cmd}`에서 내부 오류가 발생했습니다: {msg}(문서는 이전 상태로 유지되었습니다. 이 버그를 신고해 주세요) @msg invalid parameters for `{cmd}`: {msg} `{cmd}`의 매개변수가 올바르지 않습니다: {msg} @msg no active document 활성 문서가 없습니다 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index e65468c296..0a6a4060b3 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -4375,6 +4375,7 @@ imageTrace Outline Contour @msg colorTable.{what}: `{h}` is not a colour such as "#ff8800" colorTable.{what}: `{h}` is geen kleur zoals "#ff8800" @msg colorTable.{what}: at most {MAX_TABLE} colours colorTable.{what}: maximaal {MAX_TABLE} kleuren @msg command `{0}` is not available right now: {1} opdracht `{0}` is nu niet beschikbaar: {1} +@msg command `{id}` is not available right now opdracht `{id}` is nu niet beschikbaar @msg compound paths can only contain paths samengestelde paden kunnen alleen paden bevatten @msg compressed (RLE) bitmaps can't be read yet gecomprimeerde (RLE) bitmaps kunnen nog niet gelezen worden @msg damaged beschadigd diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 91eb119d4c..5577ae7e49 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -4543,6 +4543,7 @@ imageTrace Outline Kontur @msg colorTable.{what}: `{h}` is not a colour such as "#ff8800" colorTable.{what}: `{h}` nie jest kolorem w rodzaju "#ff8800" @msg colorTable.{what}: at most {MAX_TABLE} colours colorTable.{what}: maksymalnie {MAX_TABLE} kolorów @msg command `{0}` is not available right now: {1} polecenie `{0}` jest teraz niedostępne: {1} +@msg command `{id}` is not available right now polecenie `{id}` jest teraz niedostępne @msg compound paths can only contain paths ścieżki złożone mogą zawierać tylko ścieżki @msg compressed (RLE) bitmaps can't be read yet skompresowanych bitmap (RLE) nie można jeszcze odczytać @msg damaged uszkodzony diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index c124b6969f..248ea97d1a 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -3949,6 +3949,7 @@ axis Both Ambos @msg colorTable.{what}: `{h}` is not a colour such as "#ff8800" colorTable.{what}: `{h}` não é uma cor como "#ff8800" @msg colorTable.{what}: at most {MAX_TABLE} colours colorTable.{what}: no máximo {MAX_TABLE} cores @msg command `{0}` is not available right now: {1} o comando `{0}` não está disponível agora: {1} +@msg command `{id}` is not available right now o comando `{id}` não está disponível agora @msg compound paths can only contain paths traçados compostos só podem conter traçados @msg compressed (RLE) bitmaps can't be read yet bitmaps compactados (RLE) ainda não podem ser lidos @msg damaged danificado diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 8bd7fc247a..77bff282db 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -4448,6 +4448,7 @@ axis Both Оба @msg colorTable.{what}: `{h}` is not a colour such as "#ff8800" colorTable.{what}: `{h}` не является цветом, например "#ff8800" @msg colorTable.{what}: at most {MAX_TABLE} colours colorTable.{what}: не более {MAX_TABLE} цветов @msg command `{0}` is not available right now: {1} команда `{0}` сейчас недоступна: {1} +@msg command `{id}` is not available right now команда `{id}` сейчас недоступна @msg compound paths can only contain paths составные контуры могут содержать только контуры @msg compressed (RLE) bitmaps can't be read yet сжатые (RLE) растровые изображения пока не читаются @msg damaged повреждено diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 55aab9710f..b506e412ff 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -4312,6 +4312,7 @@ axis Both Båda @msg colorTable.{what}: `{h}` is not a colour such as "#ff8800" colorTable.{what}: `{h}` är inte en färg som "#ff8800" @msg colorTable.{what}: at most {MAX_TABLE} colours colorTable.{what}: högst {MAX_TABLE} färger @msg command `{0}` is not available right now: {1} kommandot `{0}` är inte tillgängligt just nu: {1} +@msg command `{id}` is not available right now kommandot `{id}` är inte tillgängligt just nu @msg compound paths can only contain paths sammansatta banor kan bara innehålla banor @msg compressed (RLE) bitmaps can't be read yet komprimerade (RLE) bitmappar kan inte läsas än @msg damaged skadad diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index f062537ebd..108f07d914 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -4176,6 +4176,7 @@ axis Both Her İkisi @msg colorTable.{what}: `{h}` is not a colour such as "#ff8800" colorTable.{what}: `{h}`, "#ff8800" gibi bir renk değil @msg colorTable.{what}: at most {MAX_TABLE} colours colorTable.{what}: en fazla {MAX_TABLE} renginde @msg command `{0}` is not available right now: {1} `{0}` komutu şu anda kullanılamıyor: {1} +@msg command `{id}` is not available right now `{id}` komutu şu anda kullanılamıyor @msg compound paths can only contain paths bileşik yollar yalnızca yollar içerebilir @msg compressed (RLE) bitmaps can't be read yet sıkıştırılmış (RLE) bitmapler henüz okunamıyor @msg damaged hasarlı diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 91cf905a9c..7e6aa692e3 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -4446,6 +4446,7 @@ axis Both Обидві @msg colorTable.{what}: `{h}` is not a colour such as "#ff8800" colorTable.{what}: `{h}` не є кольором, наприклад "#ff8800" @msg colorTable.{what}: at most {MAX_TABLE} colours colorTable.{what}: не більше {MAX_TABLE} кольорів @msg command `{0}` is not available right now: {1} команда `{0}` зараз недоступна: {1} +@msg command `{id}` is not available right now команда `{id}` зараз недоступна @msg compound paths can only contain paths складені контури можуть містити лише контури @msg compressed (RLE) bitmaps can't be read yet стислі (RLE) растрові зображення поки не читаються @msg damaged пошкоджено diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 370d481120..e0d419681f 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -4619,6 +4619,7 @@ theme Appearance Giao diện @msg column must be true or a number from 1 column phải là true hoặc số từ 1 @msg Combine Slices: the slices have no bounds Kết hợp lát cắt: các lát cắt không có giới hạn @msg command `{0}` is not available right now: {1} lệnh `{0}` hiện không khả dụng: {1} +@msg command `{id}` is not available right now lệnh `{id}` hiện không khả dụng @msg compound paths can only contain paths đường dẫn phức chỉ có thể chứa đường dẫn @msg compressed (RLE) bitmaps can't be read yet bitmap nén (RLE) chưa thể đọc được @msg Convert to Artboards: selection has no area Chuyển thành bảng vẽ: vùng chọn không có diện tích diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 706de0eef1..d18c274fb7 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -4298,6 +4298,7 @@ imageTrace Outline 轮廓 @msg nothing to distort 没有可扭曲的内容 @msg unknown option `{path}.{k}` 未知选项 `{path}.{k}` @msg command `{0}` is not available right now: {1} 命令 `{0}` 当前不可用:{1} +@msg command `{id}` is not available right now 命令 `{id}` 当前不可用 @msg internal error in `{cmd}`: {msg} (the document was left as it was before; please report this bug) `{cmd}` 中出现内部错误:{msg} (文档保持原样;请报告此错误) @msg invalid parameters for `{cmd}`: {msg} `{cmd}` 的参数无效:{msg} @msg no active document 没有活动文档 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 5e69b944cd..a68b47221f 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -2080,6 +2080,7 @@ file-filter Templates 範本 @msg nothing to distort 沒有可扭曲的內容 @msg unknown option `{path}.{k}` 未知的選項 `{path}.{k}` @msg command `{0}` is not available right now: {1} 指令 `{0}` 目前無法使用:{1} +@msg command `{id}` is not available right now 指令 `{id}` 目前無法使用 @msg internal error in `{cmd}`: {msg} (the document was left as it was before; please report this bug) `{cmd}` 發生內部錯誤:{msg} (文件維持原狀;請回報此錯誤) @msg invalid parameters for `{cmd}`: {msg} `{cmd}` 的參數無效:{msg} @msg no active document 沒有作用中的文件 diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 1698e06ed4..26dd765d29 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -71,10 +71,8 @@ pub(crate) use invoke::graph_dialog; #[cfg(test)] pub(crate) use invoke::with_held_keys; pub use invoke::{click_target, invoke, invoke_from_system_menu}; -#[cfg(test)] pub(crate) use labels::lists_plugin; -#[cfg(test)] -use labels::shows_a_name; +pub(crate) use labels::shows_a_name; pub use labels::{CONTEXT_LABELS, DYNAMIC_LABELS, display_label, dynamic_label, item_shortcut, menu_strings}; pub use run::{check_params, run_ui_command}; pub(crate) use run::{normalize_panel, opt_bool, opt_index, opt_number}; diff --git a/crates/ui-egui/src/menus/labels.rs b/crates/ui-egui/src/menus/labels.rs index 408e5f077a..b8829ac0a2 100644 --- a/crates/ui-egui/src/menus/labels.rs +++ b/crates/ui-egui/src/menus/labels.rs @@ -125,7 +125,7 @@ fn command_label(app: &VectorcraftApp, id: &str, label: &str) -> Option /// label: a recent file, a saved view, a font, a user library or preset, a custom workspace, a /// plug-in, an SVG filter, a script of the Scripts folder? Such names are shown as they are, never /// translated (a workspace the user calls "Layers" stays "Layers"). -pub(super) fn shows_a_name(id: &str, label: &str) -> bool { +pub(crate) fn shows_a_name(id: &str, label: &str) -> bool { const SLOTS: [&str; 8] = [ "file.openRecent", "view.goto", diff --git a/crates/ui-egui/src/palette.rs b/crates/ui-egui/src/palette.rs index d5bf7b6098..7b82af2e53 100644 --- a/crates/ui-egui/src/palette.rs +++ b/crates/ui-egui/src/palette.rs @@ -91,7 +91,10 @@ fn build(lang: crate::i18n::Lang) -> Vec { } /// Runs the palette result `id`: selects a tool, or invokes a command with the params its entry -/// stands for. +/// stands for. A command whose menu bar item gives params runs what a click on that item runs +/// ([`crate::shortcut_editor::menu_target_now`]), so Clean Up… and Find and Replace… open their +/// dialogs. While that item is dimmed, no dialog opens and the status line says why the command +/// can't run. pub(crate) fn run_pick(app: &mut VectorcraftApp, id: &str) { if let Some(tool) = id.strip_prefix("tool:") { app.select_tool(tool); @@ -99,7 +102,11 @@ pub(crate) fn run_pick(app: &mut VectorcraftApp, id: &str) { let command = crate::keymap::command_of(id); menus::invoke(app, &command.id, command.params); } else { - menus::invoke(app, id, json!({})); + match crate::shortcut_editor::menu_target_now(app, id) { + Some(Ok((id, p))) => menus::invoke(app, &id, p), + Some(Err(why)) => app.status(why), + None => menus::invoke(app, id, json!({})), + } } } @@ -217,7 +224,7 @@ mod tests { let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), crate::Services::default()); let before = app.session.prefs.use_precise_cursors; let first = matches.first().expect("query has a result"); - menus::invoke(&mut app, &first.id, json!({})); + run_pick(&mut app, &first.id); assert_ne!(app.session.prefs.use_precise_cursors, before, "first result toggles the preference"); } @@ -238,8 +245,7 @@ mod tests { app.run("select.set", json!({"ids": [a, b]})).unwrap(); app.run("select.key", json!({"id": b})).unwrap(); assert!(app.session.active().unwrap().selection.key.is_some()); - // Run a pick the way `show` runs it. - menus::invoke(&mut app, ids[0], json!({})); + run_pick(&mut app, ids[0]); let st = app.session.active().unwrap(); assert_eq!((st.selection.len(), st.selection.key), (2, None), "both objects stay selected, without a key"); } @@ -255,20 +261,163 @@ mod tests { app.run("file.new", json!({"width": 200, "height": 200})).unwrap(); app.ui.status.clear(); assert!(!crate::panel_docking::showing(&app.ui, crate::panels::asset_export::ID), "{id}"); - // Run a pick the way `show` runs it. - menus::invoke(&mut app, id, json!({})); + run_pick(&mut app, id); assert!(crate::panel_docking::showing(&app.ui, crate::panels::asset_export::ID), "{id}: {}", app.ui.status); assert!(!app.ui.status.contains("invalid parameters"), "{id}: {}", app.ui.status); // A second pick leaves the open panel open, docked or floating. - menus::invoke(&mut app, id, json!({})); + run_pick(&mut app, id); assert!(crate::panel_docking::showing(&app.ui, crate::panels::asset_export::ID), "{id}: picked twice"); app.run("window.panel.float", json!({"panel": crate::panels::asset_export::ID})).unwrap(); assert!(crate::panel_docking::showing(&app.ui, crate::panels::asset_export::ID), "{id}: floating"); - menus::invoke(&mut app, id, json!({})); + run_pick(&mut app, id); assert!(crate::panel_docking::showing(&app.ui, crate::panels::asset_export::ID), "{id}: floating, picked again"); } } + /// The open dialog's kind and fields, the active document's undo depth and the status line. + type Outcome = (Option<(String, serde_json::Map)>, Option, String); + + /// The [`Outcome`] `app` shows. + fn outcome(app: &VectorcraftApp) -> Outcome { + ( + app.ui.dialog.as_ref().map(|d| (d.kind.clone(), d.fields.clone())), + app.session.active().map(|st| st.history.undo.len()), + app.ui.status.clone(), + ) + } + + /// A pick of a "…" command whose menu bar item gives params opens that item's dialog, and Smooth + /// opens its slider; nothing changes until the dialog is confirmed. + #[test] + fn a_pick_opens_the_dialog_its_menu_item_opens() { + let expected = [ + ("object.path.cleanUp", "command"), + ("object.mesh.create", "command"), + ("type.smartPunctuation", "command"), + ("edit.findReplace", "command"), + ("object.path.smooth", crate::dialogs::smooth_slider::KIND), + ]; + for (id, kind) in expected { + assert!(items().iter().any(|(_, item, _)| item == id), "{id} is in the palette"); + let mut app = VectorcraftApp::new(vectorcraft_testkit::fixtures::Fixture::Single.session(), crate::Services::default()); + app.ui.status.clear(); + let before = app.session.active().unwrap().history.undo.len(); + run_pick(&mut app, id); + let d = app.ui.dialog.as_ref().unwrap_or_else(|| panic!("{id} opens its dialog: {}", app.ui.status)); + assert_eq!(d.kind, kind, "{id}"); + if kind == "command" { + assert_eq!(d.fields["__command"], json!(id), "{id}: {:?}", d.fields); + } + assert_eq!(app.session.active().unwrap().history.undo.len(), before, "{id} changes nothing yet"); + assert!(app.ui.status.is_empty(), "{id}: {}", app.ui.status); + } + } + + /// Every palette command whose menu bar items all give it the same params does what a click on + /// such an item does, as the menu row shows it ([`menus::entry`]): with no document open, in an + /// empty document and with one or several objects selected, the pick opens the same dialog with + /// the same fields and leaves the same undo steps and status. While the row is dimmed, the pick + /// opens no dialog, changes nothing and says why the command can't run. + #[test] + fn every_pick_runs_what_its_menu_item_runs() { + use vectorcraft_testkit::fixtures::Fixture; + fn walk<'a>(items: &'a [menus::Item], out: &mut Vec<&'a menus::Item>) { + for it in items { + match it { + menus::Item::Sub(_, children) => walk(children, out), + // Items that list an installed plug-in or show a name that is user data come and + // go as tests beside this one install plug-ins or add toolbars, and no shortcut or + // pick reads them. + menus::Item::Cmd(label, id, p) if !menus::lists_plugin(id, p) && !menus::shows_a_name(id, label) => out.push(it), + _ => {} + } + } + } + let tree = menus::menu_tree(); + let mut rows = vec![]; + for (_, items) in &tree { + walk(items, &mut rows); + } + let mut checked: Vec<(String, &menus::Item)> = vec![]; + for (_, id, _) in items() { + let of_id: Vec<&menus::Item> = rows.iter().copied().filter(|it| matches!(it, menus::Item::Cmd(_, c, _) if *c == id)).collect(); + let params: Vec<&serde_json::Value> = + of_id.iter().filter_map(|it| if let menus::Item::Cmd(_, _, p) = it { Some(p) } else { None }).collect(); + let Some(first) = params.first() else { continue }; + if first.as_object().is_none_or(serde_json::Map::is_empty) || params.iter().any(|p| p != first) { + continue; + } + checked.push((id, of_id[0])); + } + assert!(checked.iter().any(|(id, _)| id == "edit.findReplace"), "{:?}", checked.iter().map(|c| &c.0).collect::>()); + let state = |n: usize| match n.checked_sub(1) { + None => vectorcraft_engine::Session::new(), + Some(k) => Fixture::ALL[k].session(), + }; + let (mut compared, mut dimmed) = (0, 0); + for (id, item) in &checked { + for n in 0..=Fixture::ALL.len() { + let mut picked = VectorcraftApp::new(state(n), crate::Services::default()); + let mut clicked = VectorcraftApp::new(state(n), crate::Services::default()); + picked.ui.status.clear(); + clicked.ui.status.clear(); + // A row the menu leaves out in this state has no click to compare with. + let Some(menus::Entry::Item(row)) = menus::entry(&clicked, item) else { continue }; + let (target, enabled) = (row.target().expect("the item is implemented"), row.enabled); + let before = outcome(&picked); + run_pick(&mut picked, id); + if enabled { + menus::invoke(&mut clicked, &target.0, target.1); + assert_eq!(outcome(&picked), outcome(&clicked), "{id} [state {n}]"); + compared += 1; + } else { + let after = outcome(&picked); + assert_eq!((after.0, after.1), (None, before.1), "{id} [state {n}] is dimmed"); + assert!(after.2.contains("not available"), "{id} [state {n}]: {}", after.2); + dimmed += 1; + } + } + } + assert!(compared > 0 && dimmed > 0, "{compared} compared, {dimmed} dimmed"); + } + + /// A pick of a "…" command whose menu bar item is dimmed (Remove Background and Object Mosaic + /// with no image selected, Find and Replace with no document open) opens no dialog, changes + /// nothing and reports why the command can't run. + #[test] + fn a_pick_of_a_dimmed_command_says_why_it_cannot_run() { + let cases = [ + ("addon.image.removeBackground", vectorcraft_testkit::fixtures::Fixture::Multi.session()), + ("object.createObjectMosaic", vectorcraft_testkit::fixtures::Fixture::Multi.session()), + ("edit.findReplace", vectorcraft_engine::Session::new()), + ]; + for (id, session) in cases { + assert!(crate::shortcut_editor::menu_target(id).is_some(), "{id} has a dialog"); + let mut app = VectorcraftApp::new(session, crate::Services::default()); + assert!(!menus::enabled(&app, id), "{id}"); + app.ui.status.clear(); + let before = app.session.active().map(|st| st.history.undo.len()); + run_pick(&mut app, id); + assert!(app.ui.dialog.is_none(), "{id}"); + assert_eq!(app.session.active().map(|st| st.history.undo.len()), before, "{id}"); + assert!(app.ui.status.contains("not available"), "{id}: {}", app.ui.status); + } + } + + /// A pick of a command that opens no dialog runs it at once. + #[test] + fn a_pick_of_a_command_without_a_dialog_runs_at_once() { + for id in ["object.group", "paint.swap"] { + assert!(items().iter().any(|(_, item, _)| item == id), "{id} is in the palette"); + assert_eq!(crate::shortcut_editor::menu_target(id), None, "{id}"); + let mut app = VectorcraftApp::new(vectorcraft_testkit::fixtures::Fixture::Multi.session(), crate::Services::default()); + let before = app.session.active().unwrap().history.undo.len(); + run_pick(&mut app, id); + assert!(app.ui.dialog.is_none(), "{id}"); + assert_eq!(app.session.active().unwrap().history.undo.len(), before + 1, "{id}: {}", app.ui.status); + } + } + /// #1757: Up and Down move the highlighted result, wrapping at the ends; a new query starts /// from the first again. #[test] diff --git a/crates/ui-egui/src/shortcut_editor.rs b/crates/ui-egui/src/shortcut_editor.rs index 7ada99ee5c..f3a201124b 100644 --- a/crates/ui-egui/src/shortcut_editor.rs +++ b/crates/ui-egui/src/shortcut_editor.rs @@ -653,23 +653,31 @@ const NEEDS_A_PARAM: [&str; 20] = [ /// The (label, params) of each menu bar item, by command id. fn menu_bar_items() -> &'static HashMap<&'static str, Vec<(&'static str, Value)>> { + static ITEMS: OnceLock>> = OnceLock::new(); + ITEMS.get_or_init(|| bar_items(&menus::menu_tree())) +} + +/// The (label, params) of each item of the menus `tree`, by command id. Items that list an +/// installed plug-in ([`menus::lists_plugin`]) or show a name that is user data +/// ([`menus::shows_a_name`]: a custom toolbar, a script, a recent file, a saved selection) are left +/// out: [`menu_bar_items`] is built once, and such items come and go after that. +fn bar_items(tree: &[(&'static str, Vec)]) -> HashMap<&'static str, Vec<(&'static str, Value)>> { fn walk(items: &[menus::Item], out: &mut HashMap<&'static str, Vec<(&'static str, Value)>>) { for it in items { match it { - menus::Item::Cmd(label, id, p) => out.entry(*id).or_default().push((*label, p.clone())), + menus::Item::Cmd(label, id, p) if !menus::lists_plugin(id, p) && !menus::shows_a_name(id, label) => { + out.entry(*id).or_default().push((*label, p.clone())) + } menus::Item::Sub(_, children) => walk(children, out), _ => {} } } } - static ITEMS: OnceLock>> = OnceLock::new(); - ITEMS.get_or_init(|| { - let mut out = HashMap::new(); - for (_, items) in menus::menu_tree() { - walk(&items, &mut out); - } - out - }) + let mut out = HashMap::new(); + for (_, items) in tree { + walk(items, &mut out); + } + out } fn is_bare(p: &Value) -> bool { @@ -686,6 +694,25 @@ pub(crate) fn menu_target(id: &str) -> Option<(String, Value)> { items.iter().all(|(_, q)| q == p).then(|| menus::click_target(label, id, p)) } +/// What a command search pick, or a shortcut without params, of command `id` runs now: `Ok` with +/// what a click on its menu bar item runs ([`menu_target`]), or `None` when no such item gives it +/// params. While that item is dimmed ([`menus::enabled`]), no dialog opens for it: `Err` holds the +/// status line ([`dimmed`]), as for Remove Background… with no image selected. +pub(crate) fn menu_target_now(app: &VectorcraftApp, id: &str) -> Option> { + let target = menu_target(id)?; + Some(if menus::enabled(app, id) { Ok(target) } else { dimmed(app, id) }) +} + +/// What [`menu_target_now`] gives for command `id` while its menu bar item is dimmed: the status +/// line, with the engine's reason when it gives one. The app's own rules ([`menus::enabled`]) dim +/// UI commands, and some commands the engine would run, without giving a reason. +fn dimmed(app: &VectorcraftApp, id: &str) -> Result<(String, Value), String> { + let Some(why) = vectorcraft_engine::find_command(id).and_then(|c| (c.enabled)(&app.session).err()) else { + return Err(format!("command `{id}` is not available right now")); + }; + Err(vectorcraft_engine::EngineError::Disabled(id.to_string(), why).to_string()) +} + /// Is `key` a command with no default shortcut that a key press can't run: no item of the menu /// bar runs it without params or with one set of params ([`menu_target`]), it opens no dialog when /// run without them (as the Variables panel's commands do), and it is a query, whose answer a key @@ -2270,6 +2297,47 @@ mod tests { assert_eq!(menu_target("edit.undo"), None, "runs without params"); } + /// The menu bar items a shortcut or a command search pick reads leave out items that list an + /// installed plug-in (a File › Open With importer, an Object › Plug-ins filter) or show a name + /// that is user data (a custom toolbar, a script of the Scripts folder), which come and go after + /// the items are read; the other items stay. + #[test] + fn menu_bar_items_leave_out_installed_plug_ins_and_user_data() { + let tree = vec![( + "File", + vec![ + menus::Item::Sub("Open With", vec![menus::Item::Cmd("Spins…", "file.openWith", json!({"importer": "plugin.test.spins"}))]), + menus::Item::Cmd("Filter…", "plugin.dialog", json!({"id": "test.filter"})), + menus::Item::Cmd("Mine", "window.toolbar.custom", json!({"name": "Mine"})), + menus::Item::Cmd("tidy.js", crate::scripts_menu::COMMAND, json!({"path": "/scripts/tidy.js"})), + menus::Item::Cmd("Clean Up…", "object.path.cleanUp", json!({"strayPoints": true})), + ], + )]; + let items = bar_items(&tree); + assert!(!items.contains_key("file.openWith"), "{items:?}"); + assert!(!items.contains_key("plugin.dialog"), "{items:?}"); + assert!(!items.contains_key("window.toolbar.custom"), "{items:?}"); + assert!(!items.contains_key(crate::scripts_menu::COMMAND), "{items:?}"); + assert_eq!(items.get("object.path.cleanUp"), Some(&vec![("Clean Up…", json!({"strayPoints": true}))])); + } + + /// While a menu item is dimmed, a pick or a shortcut gets a status line and no target: with the + /// engine's reason when it gives one, and without one when the app's own rules dim the item (an + /// Appearance command with nothing targeted, a UI command). + #[test] + fn a_dimmed_item_gives_a_status_line_with_or_without_a_reason() { + use vectorcraft_testkit::fixtures::Fixture; + let none = VectorcraftApp::new(vectorcraft_engine::Session::new(), crate::Services::default()); + assert!(!menus::enabled(&none, "edit.findReplace")); + assert_eq!(dimmed(&none, "edit.findReplace"), Err("command `edit.findReplace` is not available right now: no document open".into())); + let empty = VectorcraftApp::new(Fixture::Empty.session(), crate::Services::default()); + for id in ["appearance.addFill", "ui.cropImage"] { + assert!(!menus::enabled(&empty, id), "{id}"); + assert!(vectorcraft_engine::find_command(id).is_none_or(|c| (c.enabled)(&empty.session).is_ok()), "{id}: the engine gives no reason"); + assert_eq!(dimmed(&empty, id), Err(format!("command `{id}` is not available right now"))); + } + } + /// Every command the editor lists, run as a key press runs it on a document with one or with /// several objects selected, reports no missing param. #[test] diff --git a/crates/ui-egui/src/shortcuts.rs b/crates/ui-egui/src/shortcuts.rs index 83d53d700c..f0ff34ef64 100644 --- a/crates/ui-egui/src/shortcuts.rs +++ b/crates/ui-egui/src/shortcuts.rs @@ -985,9 +985,10 @@ fn dispatch(app: &mut VectorcraftApp, ctx: &egui::Context, accept: impl FnMut(&H } /// What a shortcut without params runs when its command's menu bar item gives params -/// ([`crate::shortcut_editor::menu_target`]): what a click on that item runs. -fn menu_target(c: &CommandRef) -> Option<(String, serde_json::Value)> { - c.params.as_object().is_none_or(serde_json::Map::is_empty).then(|| crate::shortcut_editor::menu_target(&c.id)).flatten() +/// ([`crate::shortcut_editor::menu_target_now`]): what a click on that item runs, or while the item +/// is dimmed, why the command can't run. +fn menu_target(app: &VectorcraftApp, c: &CommandRef) -> Option> { + c.params.as_object().is_none_or(serde_json::Map::is_empty).then(|| crate::shortcut_editor::menu_target_now(app, &c.id)).flatten() } /// Params used when a shortcut invokes its command. The named panel function keys retain their @@ -1030,8 +1031,11 @@ fn run_hit(app: &mut VectorcraftApp, ctx: &egui::Context, c: &CommandRef) { // every artboard instead of the art. crate::panels::artboards::select_all(app); } else { - let (id, p) = menu_target(c).unwrap_or_else(|| (c.id.clone(), shortcut_params(c))); - crate::menus::invoke(app, &id, p); + match menu_target(app, c) { + Some(Ok((id, p))) => crate::menus::invoke(app, &id, p), + Some(Err(why)) => app.status(why), + None => crate::menus::invoke(app, &c.id, shortcut_params(c)), + } } } diff --git a/crates/ui-egui/src/shortcuts/tests.rs b/crates/ui-egui/src/shortcuts/tests.rs index 8d32a4de22..785fca82e2 100644 --- a/crates/ui-egui/src/shortcuts/tests.rs +++ b/crates/ui-egui/src/shortcuts/tests.rs @@ -412,6 +412,38 @@ fn a_single_key_shortcut_without_a_dialog_runs_at_once() { assert_eq!((open_dialog(&app), undo_len(&app)), (None, before + 1), "Shift+X swaps fill and stroke: {}", app.ui.status); } +/// A key or a chord of a command whose menu item is dimmed opens no dialog: Remove Background… and +/// Object Mosaic… with a rectangle selected change nothing and say why they can't run, and so does +/// Find and Replace… with no document open. +#[test] +fn a_shortcut_of_a_dimmed_command_opens_no_dialog_and_says_why() { + let chord = vec![ + egui::Event::ModifiersChanged(cmd() | Modifiers::ALT | Modifiers::SHIFT), + egui::Event::Key { key: Key::F9, physical_key: None, pressed: true, repeat: false, modifiers: cmd() | Modifiers::ALT | Modifiers::SHIFT }, + ]; + for id in ["addon.image.removeBackground", "object.createObjectMosaic", "edit.findReplace"] { + assert!(crate::shortcut_editor::menu_target(id).is_some(), "{id} has a dialog"); + for (shortcut, events) in [("F", press_f()), ("Cmd+Alt+Shift+F9", chord.clone())] { + let mut app = if id == "edit.findReplace" { + let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default()); + crate::keymap::set_override(&mut app.ui.shortcut_overrides, "view.screenMode", ""); + crate::keymap::set_override(&mut app.ui.shortcut_overrides, id, shortcut); + app.ui.status.clear(); + app + } else { + app_with_shortcut(id, shortcut) + }; + assert!(!crate::menus::enabled(&app, id), "{id}"); + let before = app.session.active().map(|st| st.history.undo.len()); + frame(&mut app, events); + crate::shortcut_editor::sync(&crate::state::UiState::default()); + let after = app.session.active().map(|st| st.history.undo.len()); + assert_eq!((open_dialog(&app), after), (None, before), "{id} on {shortcut}: {}", app.ui.status); + assert!(app.ui.status.contains("not available"), "{id} on {shortcut}: {}", app.ui.status); + } + } +} + /// With no document open, a command's key without a modifier reports what the same command /// reports on a chord. #[test] @@ -1616,6 +1648,29 @@ fn the_command_search_that_opens_while_enter_is_down_waits_for_a_new_press() { assert!(!app.ui.palette_open, "a new press runs the highlighted result"); } +/// Enter on a command search result whose menu item opens a dialog (Create Gradient Mesh…) opens +/// that dialog: the press and its repeats do not confirm it, and a new press does. +#[test] +fn enter_on_a_command_search_result_opens_its_dialog_and_a_new_press_confirms_it() { + let mut app = app_with_a_selected_rectangle(); + let held = HeldKey::new(Key::Enter, Modifiers::NONE); + app.select_tool("selection"); + app.ui.palette_open = true; + app.ui.palette_query = "object.mesh.create".into(); + let before = undo_len(&app); + for repeat in 0..4 { + held.press(&mut app); + let command = app.ui.dialog.as_ref().and_then(|d| d.fields.get("__command").cloned()); + assert_eq!(command, Some(json!("object.mesh.create")), "repeat {repeat}: its dialog stays open: {}", app.ui.status); + assert_eq!(undo_len(&app), before, "repeat {repeat}: nothing changed"); + } + assert!(!app.ui.palette_open); + held.release(&mut app); + held.press(&mut app); + assert_eq!(open_dialog(&app), None, "a new press confirms"); + assert_eq!(undo_len(&app), before + 1, "the mesh is one undo step: {}", app.ui.status); +} + /// A field that rejects its text on Enter keeps the dialog open, while the key is held too. #[test] fn holding_enter_on_a_rejected_value_leaves_the_dialog_open() {