mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-13 21:17:48 +00:00
Merge pull request #6879 from dotCooCoo/fix/palette-opens-menu-dialog
Palette: a pick opens the dialog its menu item opens
This commit is contained in:
@@ -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 повреден
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -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 κατεστραμμένο
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -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é
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -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" होना चाहिए।
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2390 and column 5.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -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 アクティブなドキュメントがありません
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -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 დაზიანებულია
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -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 활성 문서가 없습니다
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -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 повреждено
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -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ı
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -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 пошкоджено
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 182 and column 5.
|
@@ -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
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 204 and column 5.
|
@@ -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 没有活动文档
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -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 沒有作用中的文件
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -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};
|
||||
|
||||
@@ -125,7 +125,7 @@ fn command_label(app: &VectorcraftApp, id: &str, label: &str) -> Option<String>
|
||||
/// 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",
|
||||
|
||||
@@ -91,7 +91,10 @@ fn build(lang: crate::i18n::Lang) -> Vec<Entry> {
|
||||
}
|
||||
|
||||
/// 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<String, serde_json::Value>)>, Option<usize>, 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::<Vec<_>>());
|
||||
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]
|
||||
|
||||
@@ -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<HashMap<&'static str, Vec<(&'static str, Value)>>> = 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<menus::Item>)]) -> 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<HashMap<&'static str, Vec<(&'static str, Value)>>> = 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<Result<(String, Value), String>> {
|
||||
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]
|
||||
|
||||
@@ -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<Result<(String, serde_json::Value), String>> {
|
||||
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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user