mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 21:42:51 -05:00
Compare commits
6 Commits
7cb39f00ad
...
chroma
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1ecb5af70 | ||
|
|
f2be6cfeb1 | ||
|
|
65486ed3cf | ||
|
|
cc30e2a9e4 | ||
|
|
ac68451cdf | ||
|
|
0f6ae11c3d |
@@ -1,5 +1,12 @@
|
|||||||
This file is more of a quick reference so I know what to account for before next releases.
|
This file is more of a quick reference so I know what to account for before next releases.
|
||||||
|
|
||||||
|
# 1.4.0
|
||||||
|
|
||||||
|
- Overhauled system monitor, graphs, styling
|
||||||
|
- dbus API for plugins, KDEConnect
|
||||||
|
- new dank16 algorithm
|
||||||
|
- launcher actions, customize env, args, name, icon
|
||||||
|
|
||||||
# 1.2.0
|
# 1.2.0
|
||||||
|
|
||||||
- Added clipboard and clipboard history integration
|
- Added clipboard and clipboard history integration
|
||||||
|
|||||||
193
core/cmd/dms/commands_chroma.go
Normal file
193
core/cmd/dms/commands_chroma.go
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/alecthomas/chroma/v2"
|
||||||
|
"github.com/alecthomas/chroma/v2/formatters/html"
|
||||||
|
"github.com/alecthomas/chroma/v2/lexers"
|
||||||
|
"github.com/alecthomas/chroma/v2/styles"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/yuin/goldmark"
|
||||||
|
highlighting "github.com/yuin/goldmark-highlighting/v2"
|
||||||
|
"github.com/yuin/goldmark/extension"
|
||||||
|
"github.com/yuin/goldmark/parser"
|
||||||
|
ghtml "github.com/yuin/goldmark/renderer/html"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
chromaLanguage string
|
||||||
|
chromaStyle string
|
||||||
|
chromaInline bool
|
||||||
|
chromaMarkdown bool
|
||||||
|
)
|
||||||
|
|
||||||
|
var chromaCmd = &cobra.Command{
|
||||||
|
Use: "chroma [file]",
|
||||||
|
Short: "Syntax highlight source code",
|
||||||
|
Long: `Generate syntax-highlighted HTML from source code.
|
||||||
|
|
||||||
|
Reads from file or stdin, outputs HTML with syntax highlighting.
|
||||||
|
Language is auto-detected from filename or can be specified with --language.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
dms chroma main.go
|
||||||
|
dms chroma --language python script.py
|
||||||
|
echo "def foo(): pass" | dms chroma -l python
|
||||||
|
cat code.rs | dms chroma -l rust --style dracula
|
||||||
|
dms chroma --markdown README.md
|
||||||
|
dms chroma --markdown --style github-dark notes.md
|
||||||
|
dms chroma list-languages
|
||||||
|
dms chroma list-styles`,
|
||||||
|
Args: cobra.MaximumNArgs(1),
|
||||||
|
Run: runChroma,
|
||||||
|
}
|
||||||
|
|
||||||
|
var chromaListLanguagesCmd = &cobra.Command{
|
||||||
|
Use: "list-languages",
|
||||||
|
Short: "List all supported languages",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
for _, name := range lexers.Names(true) {
|
||||||
|
fmt.Println(name)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var chromaListStylesCmd = &cobra.Command{
|
||||||
|
Use: "list-styles",
|
||||||
|
Short: "List all available color styles",
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
for _, name := range styles.Names() {
|
||||||
|
fmt.Println(name)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
chromaCmd.Flags().StringVarP(&chromaLanguage, "language", "l", "", "Language for highlighting (auto-detect if not specified)")
|
||||||
|
chromaCmd.Flags().StringVarP(&chromaStyle, "style", "s", "monokai", "Color style (monokai, dracula, github, etc.)")
|
||||||
|
chromaCmd.Flags().BoolVar(&chromaInline, "inline", false, "Output inline styles instead of CSS classes")
|
||||||
|
chromaCmd.Flags().BoolVarP(&chromaMarkdown, "markdown", "m", false, "Render markdown with syntax-highlighted code blocks")
|
||||||
|
|
||||||
|
chromaCmd.AddCommand(chromaListLanguagesCmd)
|
||||||
|
chromaCmd.AddCommand(chromaListStylesCmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runChroma(cmd *cobra.Command, args []string) {
|
||||||
|
var source string
|
||||||
|
var filename string
|
||||||
|
|
||||||
|
// Read from file or stdin
|
||||||
|
if len(args) > 0 {
|
||||||
|
filename = args[0]
|
||||||
|
content, err := os.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error reading file: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
source = string(content)
|
||||||
|
} else {
|
||||||
|
content, err := io.ReadAll(os.Stdin)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error reading stdin: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
source = string(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle empty input
|
||||||
|
if strings.TrimSpace(source) == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle Markdown rendering
|
||||||
|
if chromaMarkdown {
|
||||||
|
md := goldmark.New(
|
||||||
|
goldmark.WithExtensions(
|
||||||
|
extension.GFM,
|
||||||
|
highlighting.NewHighlighting(
|
||||||
|
highlighting.WithStyle(chromaStyle),
|
||||||
|
highlighting.WithFormatOptions(
|
||||||
|
html.WithClasses(!chromaInline),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
goldmark.WithParserOptions(
|
||||||
|
parser.WithAutoHeadingID(),
|
||||||
|
),
|
||||||
|
goldmark.WithRendererOptions(
|
||||||
|
ghtml.WithHardWraps(),
|
||||||
|
ghtml.WithXHTML(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := md.Convert([]byte(source), &buf); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Markdown rendering error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Print(buf.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect or use specified lexer
|
||||||
|
var lexer chroma.Lexer
|
||||||
|
if chromaLanguage != "" {
|
||||||
|
lexer = lexers.Get(chromaLanguage)
|
||||||
|
if lexer == nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Unknown language: %s\n", chromaLanguage)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
} else if filename != "" {
|
||||||
|
lexer = lexers.Match(filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try content analysis if no lexer found
|
||||||
|
if lexer == nil {
|
||||||
|
lexer = lexers.Analyse(source)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to plaintext
|
||||||
|
if lexer == nil {
|
||||||
|
lexer = lexers.Fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
lexer = chroma.Coalesce(lexer)
|
||||||
|
|
||||||
|
// Get style
|
||||||
|
style := styles.Get(chromaStyle)
|
||||||
|
if style == nil {
|
||||||
|
style = styles.Fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create HTML formatter
|
||||||
|
var formatter *html.Formatter
|
||||||
|
if chromaInline {
|
||||||
|
formatter = html.New(
|
||||||
|
html.WithClasses(false),
|
||||||
|
html.TabWidth(4),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
formatter = html.New(
|
||||||
|
html.WithClasses(true),
|
||||||
|
html.TabWidth(4),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tokenize
|
||||||
|
iterator, err := lexer.Tokenise(nil, source)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Tokenization error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format and output
|
||||||
|
if err := formatter.Format(os.Stdout, style, iterator); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Formatting error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -517,5 +517,6 @@ func getCommonCommands() []*cobra.Command {
|
|||||||
clipboardCmd,
|
clipboardCmd,
|
||||||
doctorCmd,
|
doctorCmd,
|
||||||
configCmd,
|
configCmd,
|
||||||
|
chromaCmd,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ go 1.24.6
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Wifx/gonetworkmanager/v2 v2.2.0
|
github.com/Wifx/gonetworkmanager/v2 v2.2.0
|
||||||
|
github.com/alecthomas/chroma/v2 v2.17.2
|
||||||
github.com/charmbracelet/bubbles v0.21.0
|
github.com/charmbracelet/bubbles v0.21.0
|
||||||
github.com/charmbracelet/bubbletea v1.3.10
|
github.com/charmbracelet/bubbletea v1.3.10
|
||||||
github.com/charmbracelet/lipgloss v1.1.0
|
github.com/charmbracelet/lipgloss v1.1.0
|
||||||
@@ -28,6 +29,7 @@ require (
|
|||||||
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
|
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
|
||||||
github.com/cloudflare/circl v1.6.2 // indirect
|
github.com/cloudflare/circl v1.6.2 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||||
|
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
github.com/go-git/gcfg/v2 v2.0.2 // indirect
|
github.com/go-git/gcfg/v2 v2.0.2 // indirect
|
||||||
github.com/go-git/go-billy/v6 v6.0.0-20260114122816-19306b749ecc // indirect
|
github.com/go-git/go-billy/v6 v6.0.0-20260114122816-19306b749ecc // indirect
|
||||||
@@ -38,6 +40,8 @@ require (
|
|||||||
github.com/pjbgf/sha1cd v0.5.0 // indirect
|
github.com/pjbgf/sha1cd v0.5.0 // indirect
|
||||||
github.com/sergi/go-diff v1.4.0 // indirect
|
github.com/sergi/go-diff v1.4.0 // indirect
|
||||||
github.com/stretchr/objx v0.5.3 // indirect
|
github.com/stretchr/objx v0.5.3 // indirect
|
||||||
|
github.com/yuin/goldmark v1.7.16 // indirect
|
||||||
|
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc // indirect
|
||||||
golang.org/x/crypto v0.47.0 // indirect
|
golang.org/x/crypto v0.47.0 // indirect
|
||||||
golang.org/x/net v0.49.0 // indirect
|
golang.org/x/net v0.49.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
44
core/go.sum
44
core/go.sum
@@ -4,6 +4,14 @@ github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBi
|
|||||||
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
||||||
github.com/Wifx/gonetworkmanager/v2 v2.2.0 h1:kPstgsQtY8CmDOOFZd81ytM9Gi3f6ImzPCKF7nNhQ2U=
|
github.com/Wifx/gonetworkmanager/v2 v2.2.0 h1:kPstgsQtY8CmDOOFZd81ytM9Gi3f6ImzPCKF7nNhQ2U=
|
||||||
github.com/Wifx/gonetworkmanager/v2 v2.2.0/go.mod h1:fMDb//SHsKWxyDUAwXvCqurV3npbIyyaQWenGpZ/uXg=
|
github.com/Wifx/gonetworkmanager/v2 v2.2.0/go.mod h1:fMDb//SHsKWxyDUAwXvCqurV3npbIyyaQWenGpZ/uXg=
|
||||||
|
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
|
||||||
|
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
|
||||||
|
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
||||||
|
github.com/alecthomas/chroma/v2 v2.17.2 h1:Rm81SCZ2mPoH+Q8ZCc/9YvzPUN/E7HgPiPJD8SLV6GI=
|
||||||
|
github.com/alecthomas/chroma/v2 v2.17.2/go.mod h1:RVX6AvYm4VfYe/zsk7mjHueLDZor3aWCNE14TFlepBk=
|
||||||
|
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||||
|
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
|
||||||
|
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||||
@@ -24,16 +32,12 @@ github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoF
|
|||||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||||
github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
|
github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
|
||||||
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
|
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
|
||||||
github.com/charmbracelet/x/ansi v0.11.3 h1:6DcVaqWI82BBVM/atTyq6yBoRLZFBsnoDoX9GCu2YOI=
|
|
||||||
github.com/charmbracelet/x/ansi v0.11.3/go.mod h1:yI7Zslym9tCJcedxz5+WBq+eUGMJT0bM06Fqy1/Y4dI=
|
|
||||||
github.com/charmbracelet/x/ansi v0.11.4 h1:6G65PLu6HjmE858CnTUQY1LXT3ZUWwfvqEROLF8vqHI=
|
github.com/charmbracelet/x/ansi v0.11.4 h1:6G65PLu6HjmE858CnTUQY1LXT3ZUWwfvqEROLF8vqHI=
|
||||||
github.com/charmbracelet/x/ansi v0.11.4/go.mod h1:/5AZ+UfWExW3int5H5ugnsG/PWjNcSQcwYsHBlPFQN4=
|
github.com/charmbracelet/x/ansi v0.11.4/go.mod h1:/5AZ+UfWExW3int5H5ugnsG/PWjNcSQcwYsHBlPFQN4=
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4=
|
github.com/charmbracelet/x/cellbuf v0.0.14 h1:iUEMryGyFTelKW3THW4+FfPgi4fkmKnnaLOXuc+/Kj4=
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA=
|
github.com/charmbracelet/x/cellbuf v0.0.14/go.mod h1:P447lJl49ywBbil/KjCk2HexGh4tEY9LH0/1QrZZ9rA=
|
||||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
||||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
||||||
github.com/clipperhouse/displaywidth v0.6.2 h1:ZDpTkFfpHOKte4RG5O/BOyf3ysnvFswpyYrV7z2uAKo=
|
|
||||||
github.com/clipperhouse/displaywidth v0.6.2/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
|
|
||||||
github.com/clipperhouse/displaywidth v0.7.0 h1:QNv1GYsnLX9QBrcWUtMlogpTXuM5FVnBwKWp1O5NwmE=
|
github.com/clipperhouse/displaywidth v0.7.0 h1:QNv1GYsnLX9QBrcWUtMlogpTXuM5FVnBwKWp1O5NwmE=
|
||||||
github.com/clipperhouse/displaywidth v0.7.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
|
github.com/clipperhouse/displaywidth v0.7.0/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
|
||||||
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
|
||||||
@@ -48,6 +52,10 @@ github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
|
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
|
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||||
|
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
||||||
@@ -58,14 +66,10 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
|||||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||||
github.com/go-git/gcfg/v2 v2.0.2 h1:MY5SIIfTGGEMhdA7d7JePuVVxtKL7Hp+ApGDJAJ7dpo=
|
github.com/go-git/gcfg/v2 v2.0.2 h1:MY5SIIfTGGEMhdA7d7JePuVVxtKL7Hp+ApGDJAJ7dpo=
|
||||||
github.com/go-git/gcfg/v2 v2.0.2/go.mod h1:/lv2NsxvhepuMrldsFilrgct6pxzpGdSRC13ydTLSLs=
|
github.com/go-git/gcfg/v2 v2.0.2/go.mod h1:/lv2NsxvhepuMrldsFilrgct6pxzpGdSRC13ydTLSLs=
|
||||||
github.com/go-git/go-billy/v6 v6.0.0-20251217170237-e9738f50a3cd h1:Gd/f9cGi/3h1JOPaa6er+CkKUGyGX2DBJdFbDKVO+R0=
|
|
||||||
github.com/go-git/go-billy/v6 v6.0.0-20251217170237-e9738f50a3cd/go.mod h1:d3XQcsHu1idnquxt48kAv+h+1MUiYKLH/e7LAzjP+pI=
|
|
||||||
github.com/go-git/go-billy/v6 v6.0.0-20260114122816-19306b749ecc h1:rhkjrnRkamkRC7woapp425E4CAH6RPcqsS9X8LA93IY=
|
github.com/go-git/go-billy/v6 v6.0.0-20260114122816-19306b749ecc h1:rhkjrnRkamkRC7woapp425E4CAH6RPcqsS9X8LA93IY=
|
||||||
github.com/go-git/go-billy/v6 v6.0.0-20260114122816-19306b749ecc/go.mod h1:X1oe0Z2qMsa9hkar3AAPuL9hu4Mi3ztXEjdqRhr6fcc=
|
github.com/go-git/go-billy/v6 v6.0.0-20260114122816-19306b749ecc/go.mod h1:X1oe0Z2qMsa9hkar3AAPuL9hu4Mi3ztXEjdqRhr6fcc=
|
||||||
github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20251229094738-4b14af179146 h1:xYfxAopYyL44ot6dMBIb1Z1njFM0ZBQ99HdIB99KxLs=
|
github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20251229094738-4b14af179146 h1:xYfxAopYyL44ot6dMBIb1Z1njFM0ZBQ99HdIB99KxLs=
|
||||||
github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20251229094738-4b14af179146/go.mod h1:QE/75B8tBSLNGyUUbA9tw3EGHoFtYOtypa2h8YJxsWI=
|
github.com/go-git/go-git-fixtures/v5 v5.1.2-0.20251229094738-4b14af179146/go.mod h1:QE/75B8tBSLNGyUUbA9tw3EGHoFtYOtypa2h8YJxsWI=
|
||||||
github.com/go-git/go-git/v6 v6.0.0-20251231065035-29ae690a9f19 h1:0lz2eJScP8v5YZQsrEw+ggWC5jNySjg4bIZo5BIh6iI=
|
|
||||||
github.com/go-git/go-git/v6 v6.0.0-20251231065035-29ae690a9f19/go.mod h1:L+Evfcs7EdTqxwv854354cb6+++7TFL3hJn3Wy4g+3w=
|
|
||||||
github.com/go-git/go-git/v6 v6.0.0-20260114124804-a8db3a6585a6 h1:Yo1MlE8LpvD0pr7mZ04b6hKZKQcPvLrQFgyY1jNMEyU=
|
github.com/go-git/go-git/v6 v6.0.0-20260114124804-a8db3a6585a6 h1:Yo1MlE8LpvD0pr7mZ04b6hKZKQcPvLrQFgyY1jNMEyU=
|
||||||
github.com/go-git/go-git/v6 v6.0.0-20260114124804-a8db3a6585a6/go.mod h1:enMzPHv+9hL4B7tH7OJGQKNzCkMzXovUoaiXfsLF7Xs=
|
github.com/go-git/go-git/v6 v6.0.0-20260114124804-a8db3a6585a6/go.mod h1:enMzPHv+9hL4B7tH7OJGQKNzCkMzXovUoaiXfsLF7Xs=
|
||||||
github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
|
github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
|
||||||
@@ -78,6 +82,8 @@ github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUv
|
|||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||||
|
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||||
github.com/holoplot/go-evdev v0.0.0-20250804134636-ab1d56a1fe83 h1:B+A58zGFuDrvEZpPN+yS6swJA0nzqgZvDzgl/OPyefU=
|
github.com/holoplot/go-evdev v0.0.0-20250804134636-ab1d56a1fe83 h1:B+A58zGFuDrvEZpPN+yS6swJA0nzqgZvDzgl/OPyefU=
|
||||||
github.com/holoplot/go-evdev v0.0.0-20250804134636-ab1d56a1fe83/go.mod h1:iHAf8OIncO2gcQ8XOjS7CMJ2aPbX2Bs0wl5pZyanEqk=
|
github.com/holoplot/go-evdev v0.0.0-20250804134636-ab1d56a1fe83/go.mod h1:iHAf8OIncO2gcQ8XOjS7CMJ2aPbX2Bs0wl5pZyanEqk=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
@@ -133,42 +139,35 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
|
|||||||
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
||||||
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
|
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||||
|
github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE=
|
||||||
|
github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||||
|
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ=
|
||||||
|
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I=
|
||||||
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
|
||||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
|
||||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||||
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0=
|
|
||||||
golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU=
|
|
||||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
|
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
|
||||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
|
golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
|
||||||
golang.org/x/image v0.34.0 h1:33gCkyw9hmwbZJeZkct8XyR11yH889EQt/QH4VmXMn8=
|
|
||||||
golang.org/x/image v0.34.0/go.mod h1:2RNFBZRB+vnwwFil8GkMdRvrJOFd1AzdZI6vOY+eJVU=
|
|
||||||
golang.org/x/image v0.35.0 h1:LKjiHdgMtO8z7Fh18nGY6KDcoEtVfsgLDPeLyguqb7I=
|
golang.org/x/image v0.35.0 h1:LKjiHdgMtO8z7Fh18nGY6KDcoEtVfsgLDPeLyguqb7I=
|
||||||
golang.org/x/image v0.35.0/go.mod h1:MwPLTVgvxSASsxdLzKrl8BRFuyqMyGhLwmC+TO1Sybk=
|
golang.org/x/image v0.35.0/go.mod h1:MwPLTVgvxSASsxdLzKrl8BRFuyqMyGhLwmC+TO1Sybk=
|
||||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
|
||||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
|
||||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
|
||||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
|
||||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
|
||||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
|
||||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
|
||||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@@ -177,5 +176,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN
|
|||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ Singleton {
|
|||||||
property bool _isReadOnly: false
|
property bool _isReadOnly: false
|
||||||
property bool _hasUnsavedChanges: false
|
property bool _hasUnsavedChanges: false
|
||||||
property var _loadedSessionSnapshot: null
|
property var _loadedSessionSnapshot: null
|
||||||
|
readonly property var _hooks: ({})
|
||||||
readonly property string _stateUrl: StandardPaths.writableLocation(StandardPaths.GenericStateLocation)
|
readonly property string _stateUrl: StandardPaths.writableLocation(StandardPaths.GenericStateLocation)
|
||||||
readonly property string _stateDir: Paths.strip(_stateUrl)
|
readonly property string _stateDir: Paths.strip(_stateUrl)
|
||||||
|
|
||||||
@@ -102,6 +103,10 @@ Singleton {
|
|||||||
property string weatherLocation: "New York, NY"
|
property string weatherLocation: "New York, NY"
|
||||||
property string weatherCoordinates: "40.7128,-74.0060"
|
property string weatherCoordinates: "40.7128,-74.0060"
|
||||||
|
|
||||||
|
property var hiddenApps: []
|
||||||
|
property var appOverrides: ({})
|
||||||
|
property bool searchAppActions: true
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (!isGreeterMode) {
|
if (!isGreeterMode) {
|
||||||
loadSettings();
|
loadSettings();
|
||||||
@@ -261,6 +266,10 @@ Singleton {
|
|||||||
_checkSessionWritable();
|
_checkSessionWritable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function set(key, value) {
|
||||||
|
Spec.set(root, key, value, saveSettings, _hooks);
|
||||||
|
}
|
||||||
|
|
||||||
function migrateFromUndefinedToV1(settings) {
|
function migrateFromUndefinedToV1(settings) {
|
||||||
console.info("SessionData: Migrating configuration from undefined to version 1");
|
console.info("SessionData: Migrating configuration from undefined to version 1");
|
||||||
if (typeof SettingsData !== "undefined") {
|
if (typeof SettingsData !== "undefined") {
|
||||||
@@ -906,6 +915,61 @@ Singleton {
|
|||||||
saveSettings();
|
saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hideApp(appId) {
|
||||||
|
if (!appId)
|
||||||
|
return;
|
||||||
|
const current = [...hiddenApps];
|
||||||
|
if (current.indexOf(appId) === -1) {
|
||||||
|
current.push(appId);
|
||||||
|
hiddenApps = current;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showApp(appId) {
|
||||||
|
if (!appId)
|
||||||
|
return;
|
||||||
|
hiddenApps = hiddenApps.filter(id => id !== appId);
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAppHidden(appId) {
|
||||||
|
return appId && hiddenApps.indexOf(appId) !== -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAppOverride(appId, overrides) {
|
||||||
|
if (!appId)
|
||||||
|
return;
|
||||||
|
const newOverrides = Object.assign({}, appOverrides);
|
||||||
|
if (!overrides || Object.keys(overrides).length === 0) {
|
||||||
|
delete newOverrides[appId];
|
||||||
|
} else {
|
||||||
|
newOverrides[appId] = overrides;
|
||||||
|
}
|
||||||
|
appOverrides = newOverrides;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAppOverride(appId) {
|
||||||
|
if (!appId)
|
||||||
|
return null;
|
||||||
|
return appOverrides[appId] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearAppOverride(appId) {
|
||||||
|
if (!appId)
|
||||||
|
return;
|
||||||
|
const newOverrides = Object.assign({}, appOverrides);
|
||||||
|
delete newOverrides[appId];
|
||||||
|
appOverrides = newOverrides;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSearchAppActions(enabled) {
|
||||||
|
searchAppActions = enabled;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
function syncWallpaperForCurrentMode() {
|
function syncWallpaperForCurrentMode() {
|
||||||
if (!perModeWallpaper)
|
if (!perModeWallpaper)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ Singleton {
|
|||||||
property bool reverseScrolling: false
|
property bool reverseScrolling: false
|
||||||
property bool dwlShowAllTags: false
|
property bool dwlShowAllTags: false
|
||||||
property string workspaceColorMode: "default"
|
property string workspaceColorMode: "default"
|
||||||
property string workspaceOccupiedColorMode: "default"
|
property string workspaceOccupiedColorMode: "none"
|
||||||
property string workspaceUnfocusedColorMode: "default"
|
property string workspaceUnfocusedColorMode: "default"
|
||||||
property string workspaceUrgentColorMode: "default"
|
property string workspaceUrgentColorMode: "default"
|
||||||
property bool workspaceFocusedBorderEnabled: false
|
property bool workspaceFocusedBorderEnabled: false
|
||||||
|
|||||||
@@ -55,9 +55,23 @@ var SPEC = {
|
|||||||
enabledGpuPciIds: { def: [] },
|
enabledGpuPciIds: { def: [] },
|
||||||
|
|
||||||
wifiDeviceOverride: { def: "" },
|
wifiDeviceOverride: { def: "" },
|
||||||
weatherHourlyDetailed: { def: true }
|
weatherHourlyDetailed: { def: true },
|
||||||
|
|
||||||
|
hiddenApps: { def: [] },
|
||||||
|
appOverrides: { def: {} },
|
||||||
|
searchAppActions: { def: true }
|
||||||
};
|
};
|
||||||
|
|
||||||
function getValidKeys() {
|
function getValidKeys() {
|
||||||
return Object.keys(SPEC).concat(["configVersion"]);
|
return Object.keys(SPEC).concat(["configVersion"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function set(root, key, value, saveFn, hooks) {
|
||||||
|
if (!(key in SPEC)) return;
|
||||||
|
root[key] = value;
|
||||||
|
var hookName = SPEC[key].onChange;
|
||||||
|
if (hookName && hooks && hooks[hookName]) {
|
||||||
|
hooks[hookName](root);
|
||||||
|
}
|
||||||
|
saveFn();
|
||||||
|
}
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ var SPEC = {
|
|||||||
reverseScrolling: { def: false },
|
reverseScrolling: { def: false },
|
||||||
dwlShowAllTags: { def: false },
|
dwlShowAllTags: { def: false },
|
||||||
workspaceColorMode: { def: "default" },
|
workspaceColorMode: { def: "default" },
|
||||||
workspaceOccupiedColorMode: { def: "default" },
|
workspaceOccupiedColorMode: { def: "none" },
|
||||||
workspaceUnfocusedColorMode: { def: "default" },
|
workspaceUnfocusedColorMode: { def: "default" },
|
||||||
workspaceUrgentColorMode: { def: "default" },
|
workspaceUrgentColorMode: { def: "default" },
|
||||||
workspaceFocusedBorderEnabled: { def: false },
|
workspaceFocusedBorderEnabled: { def: false },
|
||||||
|
|||||||
@@ -71,6 +71,14 @@ FloatingWindow {
|
|||||||
return (bytes / (1024 * 1024 * 1024)).toFixed(2) + " GB/s";
|
return (bytes / (1024 * 1024 * 1024)).toFixed(2) + " GB/s";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function nextTab() {
|
||||||
|
currentTab = (currentTab + 1) % 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
function previousTab() {
|
||||||
|
currentTab = (currentTab - 1 + 4) % 4;
|
||||||
|
}
|
||||||
|
|
||||||
objectName: "processListModal"
|
objectName: "processListModal"
|
||||||
title: I18n.tr("System Monitor", "sysmon window title")
|
title: I18n.tr("System Monitor", "sysmon window title")
|
||||||
minimumSize: Qt.size(750, 550)
|
minimumSize: Qt.size(750, 550)
|
||||||
@@ -79,16 +87,25 @@ FloatingWindow {
|
|||||||
color: Theme.surfaceContainer
|
color: Theme.surfaceContainer
|
||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
|
onCurrentTabChanged: {
|
||||||
|
if (visible && currentTab === 0 && searchField.visible)
|
||||||
|
searchField.forceActiveFocus();
|
||||||
|
}
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
closingModal();
|
closingModal();
|
||||||
searchText = "";
|
searchText = "";
|
||||||
expandedPid = "";
|
expandedPid = "";
|
||||||
|
if (processesTabLoader.item)
|
||||||
|
processesTabLoader.item.reset();
|
||||||
DgopService.removeRef(["cpu", "memory", "network", "disk", "system"]);
|
DgopService.removeRef(["cpu", "memory", "network", "disk", "system"]);
|
||||||
} else {
|
} else {
|
||||||
DgopService.addRef(["cpu", "memory", "network", "disk", "system"]);
|
DgopService.addRef(["cpu", "memory", "network", "disk", "system"]);
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
if (contentFocusScope)
|
if (currentTab === 0 && searchField.visible)
|
||||||
|
searchField.forceActiveFocus();
|
||||||
|
else if (contentFocusScope)
|
||||||
contentFocusScope.forceActiveFocus();
|
contentFocusScope.forceActiveFocus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -96,6 +113,11 @@ FloatingWindow {
|
|||||||
|
|
||||||
ProcessContextMenu {
|
ProcessContextMenu {
|
||||||
id: processContextMenu
|
id: processContextMenu
|
||||||
|
parentFocusItem: contentFocusScope
|
||||||
|
onProcessKilled: {
|
||||||
|
if (processesTabLoader.item)
|
||||||
|
processesTabLoader.item.forceRefresh(3);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FocusScope {
|
FocusScope {
|
||||||
@@ -108,6 +130,9 @@ FloatingWindow {
|
|||||||
focus: true
|
focus: true
|
||||||
|
|
||||||
Keys.onPressed: event => {
|
Keys.onPressed: event => {
|
||||||
|
if (processContextMenu.visible)
|
||||||
|
return;
|
||||||
|
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case Qt.Key_1:
|
case Qt.Key_1:
|
||||||
currentTab = 0;
|
currentTab = 0;
|
||||||
@@ -125,12 +150,25 @@ FloatingWindow {
|
|||||||
currentTab = 3;
|
currentTab = 3;
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
return;
|
return;
|
||||||
|
case Qt.Key_Tab:
|
||||||
|
nextTab();
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
|
case Qt.Key_Backtab:
|
||||||
|
previousTab();
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
case Qt.Key_Escape:
|
case Qt.Key_Escape:
|
||||||
if (searchText.length > 0) {
|
if (searchText.length > 0) {
|
||||||
searchText = "";
|
searchText = "";
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (currentTab === 0 && processesTabLoader.item?.keyboardNavigationActive) {
|
||||||
|
processesTabLoader.item.reset();
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
hide();
|
hide();
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
return;
|
return;
|
||||||
@@ -142,6 +180,9 @@ FloatingWindow {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentTab === 0 && processesTabLoader.item)
|
||||||
|
processesTabLoader.item.handleKey(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -336,6 +377,8 @@ FloatingWindow {
|
|||||||
text: searchText
|
text: searchText
|
||||||
visible: currentTab === 0
|
visible: currentTab === 0
|
||||||
onTextChanged: searchText = text
|
onTextChanged: searchText = text
|
||||||
|
ignoreUpDownKeys: true
|
||||||
|
keyForwardTargets: [contentFocusScope]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.Spotlight
|
import qs.Modals.Spotlight
|
||||||
@@ -19,6 +20,10 @@ Item {
|
|||||||
property string searchMode: "apps"
|
property string searchMode: "apps"
|
||||||
property bool usePopupContextMenu: false
|
property bool usePopupContextMenu: false
|
||||||
|
|
||||||
|
property bool editMode: false
|
||||||
|
property var editingApp: null
|
||||||
|
property string editAppId: ""
|
||||||
|
|
||||||
function resetScroll() {
|
function resetScroll() {
|
||||||
if (searchMode === "apps") {
|
if (searchMode === "apps") {
|
||||||
resultsView.resetScroll();
|
resultsView.resetScroll();
|
||||||
@@ -43,6 +48,49 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openEditMode(app) {
|
||||||
|
if (!app)
|
||||||
|
return;
|
||||||
|
editingApp = app;
|
||||||
|
editAppId = app.id || app.execString || app.exec || "";
|
||||||
|
const existing = SessionData.getAppOverride(editAppId);
|
||||||
|
editNameField.text = existing?.name || "";
|
||||||
|
editIconField.text = existing?.icon || "";
|
||||||
|
editCommentField.text = existing?.comment || "";
|
||||||
|
editEnvVarsField.text = existing?.envVars || "";
|
||||||
|
editExtraFlagsField.text = existing?.extraFlags || "";
|
||||||
|
editMode = true;
|
||||||
|
Qt.callLater(() => editNameField.forceActiveFocus());
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeEditMode() {
|
||||||
|
editMode = false;
|
||||||
|
editingApp = null;
|
||||||
|
editAppId = "";
|
||||||
|
Qt.callLater(() => searchField.forceActiveFocus());
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveAppOverride() {
|
||||||
|
const override = {};
|
||||||
|
if (editNameField.text.trim())
|
||||||
|
override.name = editNameField.text.trim();
|
||||||
|
if (editIconField.text.trim())
|
||||||
|
override.icon = editIconField.text.trim();
|
||||||
|
if (editCommentField.text.trim())
|
||||||
|
override.comment = editCommentField.text.trim();
|
||||||
|
if (editEnvVarsField.text.trim())
|
||||||
|
override.envVars = editEnvVarsField.text.trim();
|
||||||
|
if (editExtraFlagsField.text.trim())
|
||||||
|
override.extraFlags = editExtraFlagsField.text.trim();
|
||||||
|
SessionData.setAppOverride(editAppId, override);
|
||||||
|
closeEditMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetAppOverride() {
|
||||||
|
SessionData.clearAppOverride(editAppId);
|
||||||
|
closeEditMode();
|
||||||
|
}
|
||||||
|
|
||||||
onSearchModeChanged: {
|
onSearchModeChanged: {
|
||||||
if (searchMode === "files") {
|
if (searchMode === "files") {
|
||||||
appLauncher.keyboardNavigationActive = false;
|
appLauncher.keyboardNavigationActive = false;
|
||||||
@@ -55,10 +103,16 @@ Item {
|
|||||||
focus: true
|
focus: true
|
||||||
clip: false
|
clip: false
|
||||||
Keys.onPressed: event => {
|
Keys.onPressed: event => {
|
||||||
|
if (editMode) {
|
||||||
|
if (event.key === Qt.Key_Escape) {
|
||||||
|
closeEditMode();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (event.key === Qt.Key_Escape) {
|
if (event.key === Qt.Key_Escape) {
|
||||||
if (parentModal)
|
if (parentModal)
|
||||||
parentModal.hide();
|
parentModal.hide();
|
||||||
|
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
} else if (event.key === Qt.Key_Down) {
|
} else if (event.key === Qt.Key_Down) {
|
||||||
if (searchMode === "apps") {
|
if (searchMode === "apps") {
|
||||||
@@ -155,7 +209,6 @@ Item {
|
|||||||
if (searchMode === "apps" && appLauncher.model.count > 0) {
|
if (searchMode === "apps" && appLauncher.model.count > 0) {
|
||||||
const selectedApp = appLauncher.model.get(appLauncher.selectedIndex);
|
const selectedApp = appLauncher.model.get(appLauncher.selectedIndex);
|
||||||
const menu = usePopupContextMenu ? popupContextMenu : layerContextMenuLoader.item;
|
const menu = usePopupContextMenu ? popupContextMenu : layerContextMenuLoader.item;
|
||||||
|
|
||||||
if (selectedApp && menu && resultsView) {
|
if (selectedApp && menu && resultsView) {
|
||||||
const itemPos = resultsView.getSelectedItemPosition();
|
const itemPos = resultsView.getSelectedItemPosition();
|
||||||
const contentPos = resultsView.mapToItem(spotlightKeyHandler, itemPos.x, itemPos.y);
|
const contentPos = resultsView.mapToItem(spotlightKeyHandler, itemPos.x, itemPos.y);
|
||||||
@@ -168,7 +221,6 @@ Item {
|
|||||||
|
|
||||||
AppLauncher {
|
AppLauncher {
|
||||||
id: appLauncher
|
id: appLauncher
|
||||||
|
|
||||||
viewMode: SettingsData.spotlightModalViewMode
|
viewMode: SettingsData.spotlightModalViewMode
|
||||||
gridColumns: SettingsData.appLauncherGridColumns
|
gridColumns: SettingsData.appLauncherGridColumns
|
||||||
onAppLaunched: () => {
|
onAppLaunched: () => {
|
||||||
@@ -185,7 +237,6 @@ Item {
|
|||||||
|
|
||||||
FileSearchController {
|
FileSearchController {
|
||||||
id: fileSearchController
|
id: fileSearchController
|
||||||
|
|
||||||
onFileOpened: () => {
|
onFileOpened: () => {
|
||||||
if (parentModal)
|
if (parentModal)
|
||||||
parentModal.hide();
|
parentModal.hide();
|
||||||
@@ -197,7 +248,6 @@ Item {
|
|||||||
|
|
||||||
SpotlightContextMenuPopup {
|
SpotlightContextMenuPopup {
|
||||||
id: popupContextMenu
|
id: popupContextMenu
|
||||||
|
|
||||||
parent: spotlightKeyHandler
|
parent: spotlightKeyHandler
|
||||||
appLauncher: spotlightKeyHandler.appLauncher
|
appLauncher: spotlightKeyHandler.appLauncher
|
||||||
parentHandler: spotlightKeyHandler
|
parentHandler: spotlightKeyHandler
|
||||||
@@ -231,20 +281,37 @@ Item {
|
|||||||
target: parentModal
|
target: parentModal
|
||||||
function onSpotlightOpenChanged() {
|
function onSpotlightOpenChanged() {
|
||||||
if (parentModal && !parentModal.spotlightOpen) {
|
if (parentModal && !parentModal.spotlightOpen) {
|
||||||
if (layerContextMenuLoader.item) {
|
if (layerContextMenuLoader.item)
|
||||||
layerContextMenuLoader.item.hide();
|
layerContextMenuLoader.item.hide();
|
||||||
}
|
|
||||||
popupContextMenu.hide();
|
popupContextMenu.hide();
|
||||||
|
if (editMode)
|
||||||
|
closeEditMode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
enabled: parentModal !== null
|
enabled: parentModal !== null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: popupContextMenu
|
||||||
|
function onEditAppRequested(app) {
|
||||||
|
spotlightKeyHandler.openEditMode(app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: layerContextMenuLoader.item
|
||||||
|
function onEditAppRequested(app) {
|
||||||
|
spotlightKeyHandler.openEditMode(app);
|
||||||
|
}
|
||||||
|
enabled: layerContextMenuLoader.item !== null
|
||||||
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Theme.spacingM
|
anchors.margins: Theme.spacingM
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
clip: false
|
clip: false
|
||||||
|
visible: !editMode
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: searchRow
|
id: searchRow
|
||||||
@@ -275,18 +342,14 @@ Item {
|
|||||||
ignoreTabKeys: true
|
ignoreTabKeys: true
|
||||||
keyForwardTargets: [spotlightKeyHandler]
|
keyForwardTargets: [spotlightKeyHandler]
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
if (searchMode === "apps") {
|
if (searchMode === "apps")
|
||||||
appLauncher.searchQuery = text;
|
appLauncher.searchQuery = text;
|
||||||
}
|
|
||||||
}
|
|
||||||
onTextEdited: {
|
|
||||||
updateSearchMode();
|
|
||||||
}
|
}
|
||||||
|
onTextEdited: updateSearchMode()
|
||||||
Keys.onPressed: event => {
|
Keys.onPressed: event => {
|
||||||
if (event.key === Qt.Key_Escape) {
|
if (event.key === Qt.Key_Escape) {
|
||||||
if (parentModal)
|
if (parentModal)
|
||||||
parentModal.hide();
|
parentModal.hide();
|
||||||
|
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
} else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length > 0) {
|
} else if ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && text.length > 0) {
|
||||||
if (searchMode === "apps") {
|
if (searchMode === "apps") {
|
||||||
@@ -334,13 +397,10 @@ Item {
|
|||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: listViewArea
|
id: listViewArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: () => {
|
onClicked: appLauncher.setViewMode("list")
|
||||||
appLauncher.setViewMode("list");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,13 +419,10 @@ Item {
|
|||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: gridViewArea
|
id: gridViewArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: () => {
|
onClicked: appLauncher.setViewMode("grid")
|
||||||
appLauncher.setViewMode("grid");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -379,7 +436,6 @@ Item {
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: filenameFilterButton
|
id: filenameFilterButton
|
||||||
|
|
||||||
width: 36
|
width: 36
|
||||||
height: 36
|
height: 36
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
@@ -394,13 +450,10 @@ Item {
|
|||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: filenameFilterArea
|
id: filenameFilterArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: () => {
|
onClicked: fileSearchController.searchField = "filename"
|
||||||
fileSearchController.searchField = "filename";
|
|
||||||
}
|
|
||||||
onEntered: {
|
onEntered: {
|
||||||
filenameTooltipLoader.active = true;
|
filenameTooltipLoader.active = true;
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
@@ -413,7 +466,6 @@ Item {
|
|||||||
onExited: {
|
onExited: {
|
||||||
if (filenameTooltipLoader.item)
|
if (filenameTooltipLoader.item)
|
||||||
filenameTooltipLoader.item.hide();
|
filenameTooltipLoader.item.hide();
|
||||||
|
|
||||||
filenameTooltipLoader.active = false;
|
filenameTooltipLoader.active = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -421,7 +473,6 @@ Item {
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: contentFilterButton
|
id: contentFilterButton
|
||||||
|
|
||||||
width: 36
|
width: 36
|
||||||
height: 36
|
height: 36
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
@@ -436,13 +487,10 @@ Item {
|
|||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: contentFilterArea
|
id: contentFilterArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: () => {
|
onClicked: fileSearchController.searchField = "body"
|
||||||
fileSearchController.searchField = "body";
|
|
||||||
}
|
|
||||||
onEntered: {
|
onEntered: {
|
||||||
contentTooltipLoader.active = true;
|
contentTooltipLoader.active = true;
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
@@ -455,7 +503,6 @@ Item {
|
|||||||
onExited: {
|
onExited: {
|
||||||
if (contentTooltipLoader.item)
|
if (contentTooltipLoader.item)
|
||||||
contentTooltipLoader.item.hide();
|
contentTooltipLoader.item.hide();
|
||||||
|
|
||||||
contentTooltipLoader.active = false;
|
contentTooltipLoader.active = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -474,13 +521,10 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
appLauncher: spotlightKeyHandler.appLauncher
|
appLauncher: spotlightKeyHandler.appLauncher
|
||||||
visible: searchMode === "apps"
|
visible: searchMode === "apps"
|
||||||
|
|
||||||
onItemRightClicked: (index, modelData, mouseX, mouseY) => {
|
onItemRightClicked: (index, modelData, mouseX, mouseY) => {
|
||||||
const menu = usePopupContextMenu ? popupContextMenu : layerContextMenuLoader.item;
|
const menu = usePopupContextMenu ? popupContextMenu : layerContextMenuLoader.item;
|
||||||
|
|
||||||
if (menu?.show) {
|
if (menu?.show) {
|
||||||
const isPopup = menu.contentItem !== undefined;
|
const isPopup = menu.contentItem !== undefined;
|
||||||
|
|
||||||
if (isPopup) {
|
if (isPopup) {
|
||||||
const localPos = popupContextMenu.parent.mapFromItem(null, mouseX, mouseY);
|
const localPos = popupContextMenu.parent.mapFromItem(null, mouseX, mouseY);
|
||||||
menu.show(localPos.x, localPos.y, modelData, false);
|
menu.show(localPos.x, localPos.y, modelData, false);
|
||||||
@@ -500,16 +544,320 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FocusScope {
|
||||||
|
id: editView
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingM
|
||||||
|
visible: editMode
|
||||||
|
focus: editMode
|
||||||
|
|
||||||
|
Keys.onPressed: event => {
|
||||||
|
switch (event.key) {
|
||||||
|
case Qt.Key_Escape:
|
||||||
|
closeEditMode();
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
|
case Qt.Key_Return:
|
||||||
|
case Qt.Key_Enter:
|
||||||
|
if (event.modifiers & Qt.ControlModifier) {
|
||||||
|
saveAppOverride();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case Qt.Key_S:
|
||||||
|
if (event.modifiers & Qt.ControlModifier) {
|
||||||
|
saveAppOverride();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case Qt.Key_R:
|
||||||
|
if ((event.modifiers & Qt.ControlModifier) && SessionData.getAppOverride(editAppId) !== null) {
|
||||||
|
resetAppOverride();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 40
|
||||||
|
height: 40
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: backButtonArea.containsMouse ? Theme.surfaceHover : "transparent"
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "arrow_back"
|
||||||
|
size: 20
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: backButtonArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: closeEditMode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
width: 40
|
||||||
|
height: 40
|
||||||
|
source: editingApp?.icon ? "image://icon/" + editingApp.icon : "image://icon/application-x-executable"
|
||||||
|
sourceSize.width: 40
|
||||||
|
sourceSize.height: 40
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Edit App")
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: editingApp?.name || ""
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 1
|
||||||
|
color: Theme.outlineMedium
|
||||||
|
}
|
||||||
|
|
||||||
|
Flickable {
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height - y - buttonsRow.height - Theme.spacingM
|
||||||
|
contentHeight: editFieldsColumn.height
|
||||||
|
clip: true
|
||||||
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: editFieldsColumn
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Name")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
id: editNameField
|
||||||
|
width: parent.width
|
||||||
|
height: 44
|
||||||
|
placeholderText: editingApp?.name || ""
|
||||||
|
keyNavigationTab: editIconField
|
||||||
|
keyNavigationBacktab: editExtraFlagsField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Icon")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
id: editIconField
|
||||||
|
width: parent.width
|
||||||
|
height: 44
|
||||||
|
placeholderText: editingApp?.icon || ""
|
||||||
|
keyNavigationTab: editCommentField
|
||||||
|
keyNavigationBacktab: editNameField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Description")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
id: editCommentField
|
||||||
|
width: parent.width
|
||||||
|
height: 44
|
||||||
|
placeholderText: editingApp?.comment || ""
|
||||||
|
keyNavigationTab: editEnvVarsField
|
||||||
|
keyNavigationBacktab: editIconField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Environment Variables")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "KEY=value KEY2=value2"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall - 1
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
id: editEnvVarsField
|
||||||
|
width: parent.width
|
||||||
|
height: 44
|
||||||
|
placeholderText: "VAR=value"
|
||||||
|
keyNavigationTab: editExtraFlagsField
|
||||||
|
keyNavigationBacktab: editCommentField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Extra Arguments")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
id: editExtraFlagsField
|
||||||
|
width: parent.width
|
||||||
|
height: 44
|
||||||
|
placeholderText: "--flag --option=value"
|
||||||
|
keyNavigationTab: editNameField
|
||||||
|
keyNavigationBacktab: editEnvVarsField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: buttonsRow
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: resetButton
|
||||||
|
width: 90
|
||||||
|
height: 40
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: resetButtonArea.containsMouse ? Theme.surfacePressed : Theme.surfaceVariantAlpha
|
||||||
|
visible: SessionData.getAppOverride(editAppId) !== null
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Reset")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.error
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: resetButtonArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: resetAppOverride()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: cancelButton
|
||||||
|
width: 90
|
||||||
|
height: 40
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: cancelButtonArea.containsMouse ? Theme.surfacePressed : Theme.surfaceVariantAlpha
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Cancel")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: cancelButtonArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: closeEditMode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: saveButton
|
||||||
|
width: 90
|
||||||
|
height: 40
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: saveButtonArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.9) : Theme.primary
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Save")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.primaryText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: saveButtonArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: saveAppOverride()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: filenameTooltipLoader
|
id: filenameTooltipLoader
|
||||||
|
|
||||||
active: false
|
active: false
|
||||||
sourceComponent: DankTooltip {}
|
sourceComponent: DankTooltip {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: contentTooltipLoader
|
id: contentTooltipLoader
|
||||||
|
|
||||||
active: false
|
active: false
|
||||||
sourceComponent: DankTooltip {}
|
sourceComponent: DankTooltip {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
import Quickshell.Widgets
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.Spotlight
|
import qs.Modals.Spotlight
|
||||||
|
|
||||||
@@ -19,6 +18,8 @@ PanelWindow {
|
|||||||
property real menuPositionX: 0
|
property real menuPositionX: 0
|
||||||
property real menuPositionY: 0
|
property real menuPositionY: 0
|
||||||
|
|
||||||
|
signal editAppRequested(var app)
|
||||||
|
|
||||||
readonly property real shadowBuffer: 5
|
readonly property real shadowBuffer: 5
|
||||||
|
|
||||||
screen: parentModal?.effectiveScreen
|
screen: parentModal?.effectiveScreen
|
||||||
@@ -106,6 +107,7 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onHideRequested: root.hide()
|
onHideRequested: root.hide()
|
||||||
|
onEditAppRequested: app => root.editAppRequested(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
|
|||||||
@@ -68,6 +68,27 @@ Item {
|
|||||||
hideRequested();
|
hideRequested();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
readonly property bool isRegularApp: desktopEntry && !currentApp?.isPlugin && !currentApp?.isCore && !currentApp?.isAction && !currentApp?.isBuiltInLauncher
|
||||||
|
|
||||||
|
signal editAppRequested(var app)
|
||||||
|
|
||||||
|
function hideCurrentApp() {
|
||||||
|
if (!desktopEntry)
|
||||||
|
return;
|
||||||
|
const appId = desktopEntry.id || desktopEntry.execString || "";
|
||||||
|
SessionData.hideApp(appId);
|
||||||
|
if (appLauncher)
|
||||||
|
appLauncher.updateFilteredModel();
|
||||||
|
hideRequested();
|
||||||
|
}
|
||||||
|
|
||||||
|
function editCurrentApp() {
|
||||||
|
if (!desktopEntry)
|
||||||
|
return;
|
||||||
|
editAppRequested(desktopEntry);
|
||||||
|
hideRequested();
|
||||||
|
}
|
||||||
|
|
||||||
readonly property var menuItems: {
|
readonly property var menuItems: {
|
||||||
const items = [];
|
const items = [];
|
||||||
|
|
||||||
@@ -103,6 +124,21 @@ Item {
|
|||||||
action: togglePin
|
action: togglePin
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (isRegularApp) {
|
||||||
|
items.push({
|
||||||
|
type: "item",
|
||||||
|
icon: "visibility_off",
|
||||||
|
text: I18n.tr("Hide App"),
|
||||||
|
action: hideCurrentApp
|
||||||
|
});
|
||||||
|
items.push({
|
||||||
|
type: "item",
|
||||||
|
icon: "edit",
|
||||||
|
text: I18n.tr("Edit App"),
|
||||||
|
action: editCurrentApp
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (desktopEntry && desktopEntry.actions) {
|
if (desktopEntry && desktopEntry.actions) {
|
||||||
items.push({
|
items.push({
|
||||||
type: "separator"
|
type: "separator"
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import Quickshell
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.Spotlight
|
import qs.Modals.Spotlight
|
||||||
|
|
||||||
@@ -11,6 +10,8 @@ Popup {
|
|||||||
property var parentHandler: null
|
property var parentHandler: null
|
||||||
property var searchField: null
|
property var searchField: null
|
||||||
|
|
||||||
|
signal editAppRequested(var app)
|
||||||
|
|
||||||
function show(x, y, app, fromKeyboard) {
|
function show(x, y, app, fromKeyboard) {
|
||||||
fromKeyboard = fromKeyboard || false;
|
fromKeyboard = fromKeyboard || false;
|
||||||
menuContent.currentApp = app;
|
menuContent.currentApp = app;
|
||||||
@@ -53,7 +54,7 @@ Popup {
|
|||||||
if (parentHandler) {
|
if (parentHandler) {
|
||||||
parentHandler.enabled = true;
|
parentHandler.enabled = true;
|
||||||
}
|
}
|
||||||
if (searchField) {
|
if (searchField?.visible) {
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
searchField.forceActiveFocus();
|
searchField.forceActiveFocus();
|
||||||
});
|
});
|
||||||
@@ -84,5 +85,6 @@ Popup {
|
|||||||
id: menuContent
|
id: menuContent
|
||||||
appLauncher: root.appLauncher
|
appLauncher: root.appLauncher
|
||||||
onHideRequested: root.hide()
|
onHideRequested: root.hide()
|
||||||
|
onEditAppRequested: app => root.editAppRequested(app)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,6 +65,20 @@ DankModal {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showWithEditApp(app) {
|
||||||
|
openedFromOverview = false;
|
||||||
|
isClosing = false;
|
||||||
|
resetContent();
|
||||||
|
spotlightOpen = true;
|
||||||
|
open();
|
||||||
|
Qt.callLater(() => {
|
||||||
|
if (spotlightContent?.appLauncher)
|
||||||
|
spotlightContent.appLauncher.ensureInitialized();
|
||||||
|
if (spotlightContent?.openEditMode)
|
||||||
|
spotlightContent.openEditMode(app);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function hide() {
|
function hide() {
|
||||||
openedFromOverview = false;
|
openedFromOverview = false;
|
||||||
isClosing = true;
|
isClosing = true;
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Widgets
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Modals.Spotlight
|
import qs.Modals.Spotlight
|
||||||
import qs.Modules.AppDrawer
|
import qs.Modules.AppDrawer
|
||||||
import qs.Services
|
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
DankPopout {
|
DankPopout {
|
||||||
@@ -15,6 +11,63 @@ DankPopout {
|
|||||||
|
|
||||||
property string searchMode: "apps"
|
property string searchMode: "apps"
|
||||||
property alias fileSearch: fileSearchController
|
property alias fileSearch: fileSearchController
|
||||||
|
property bool editMode: false
|
||||||
|
property var editingApp: null
|
||||||
|
property string editAppId: ""
|
||||||
|
|
||||||
|
function openEditMode(app) {
|
||||||
|
if (!app)
|
||||||
|
return;
|
||||||
|
editingApp = app;
|
||||||
|
editAppId = app.id || app.execString || app.exec || "";
|
||||||
|
const existing = SessionData.getAppOverride(editAppId);
|
||||||
|
if (contentLoader.item) {
|
||||||
|
contentLoader.item.searchField.focus = false;
|
||||||
|
contentLoader.item.editNameField.text = existing?.name || "";
|
||||||
|
contentLoader.item.editIconField.text = existing?.icon || "";
|
||||||
|
contentLoader.item.editCommentField.text = existing?.comment || "";
|
||||||
|
contentLoader.item.editEnvVarsField.text = existing?.envVars || "";
|
||||||
|
contentLoader.item.editExtraFlagsField.text = existing?.extraFlags || "";
|
||||||
|
}
|
||||||
|
editMode = true;
|
||||||
|
Qt.callLater(() => {
|
||||||
|
if (contentLoader.item?.editNameField)
|
||||||
|
contentLoader.item.editNameField.forceActiveFocus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeEditMode() {
|
||||||
|
editMode = false;
|
||||||
|
editingApp = null;
|
||||||
|
editAppId = "";
|
||||||
|
Qt.callLater(() => {
|
||||||
|
if (contentLoader.item?.searchField)
|
||||||
|
contentLoader.item.searchField.forceActiveFocus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveAppOverride() {
|
||||||
|
const override = {};
|
||||||
|
if (contentLoader.item) {
|
||||||
|
if (contentLoader.item.editNameField.text.trim())
|
||||||
|
override.name = contentLoader.item.editNameField.text.trim();
|
||||||
|
if (contentLoader.item.editIconField.text.trim())
|
||||||
|
override.icon = contentLoader.item.editIconField.text.trim();
|
||||||
|
if (contentLoader.item.editCommentField.text.trim())
|
||||||
|
override.comment = contentLoader.item.editCommentField.text.trim();
|
||||||
|
if (contentLoader.item.editEnvVarsField.text.trim())
|
||||||
|
override.envVars = contentLoader.item.editEnvVarsField.text.trim();
|
||||||
|
if (contentLoader.item.editExtraFlagsField.text.trim())
|
||||||
|
override.extraFlags = contentLoader.item.editExtraFlagsField.text.trim();
|
||||||
|
}
|
||||||
|
SessionData.setAppOverride(editAppId, override);
|
||||||
|
closeEditMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetAppOverride() {
|
||||||
|
SessionData.clearAppOverride(editAppId);
|
||||||
|
closeEditMode();
|
||||||
|
}
|
||||||
|
|
||||||
function updateSearchMode(text) {
|
function updateSearchMode(text) {
|
||||||
if (text.startsWith("/")) {
|
if (text.startsWith("/")) {
|
||||||
@@ -42,16 +95,25 @@ DankPopout {
|
|||||||
popupHeight: 600
|
popupHeight: 600
|
||||||
triggerWidth: 40
|
triggerWidth: 40
|
||||||
positioning: ""
|
positioning: ""
|
||||||
|
contentHandlesKeys: editMode
|
||||||
|
|
||||||
onBackgroundClicked: {
|
onBackgroundClicked: {
|
||||||
if (contextMenu.visible) {
|
if (contextMenu.visible) {
|
||||||
contextMenu.close();
|
contextMenu.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (editMode) {
|
||||||
|
closeEditMode();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
onOpened: {
|
onOpened: {
|
||||||
searchMode = "apps";
|
searchMode = "apps";
|
||||||
|
editMode = false;
|
||||||
|
editingApp = null;
|
||||||
|
editAppId = "";
|
||||||
appLauncher.ensureInitialized();
|
appLauncher.ensureInitialized();
|
||||||
appLauncher.searchQuery = "";
|
appLauncher.searchQuery = "";
|
||||||
appLauncher.selectedIndex = 0;
|
appLauncher.selectedIndex = 0;
|
||||||
@@ -100,8 +162,45 @@ DankPopout {
|
|||||||
LayoutMirroring.childrenInherit: true
|
LayoutMirroring.childrenInherit: true
|
||||||
|
|
||||||
property alias searchField: searchField
|
property alias searchField: searchField
|
||||||
|
property alias keyHandler: keyHandler
|
||||||
|
property alias editNameField: editNameField
|
||||||
|
property alias editIconField: editIconField
|
||||||
|
property alias editCommentField: editCommentField
|
||||||
|
property alias editEnvVarsField: editEnvVarsField
|
||||||
|
property alias editExtraFlagsField: editExtraFlagsField
|
||||||
|
|
||||||
|
focus: true
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
|
Keys.onPressed: function (event) {
|
||||||
|
if (appDrawerPopout.editMode) {
|
||||||
|
switch (event.key) {
|
||||||
|
case Qt.Key_Escape:
|
||||||
|
appDrawerPopout.closeEditMode();
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
|
case Qt.Key_Return:
|
||||||
|
case Qt.Key_Enter:
|
||||||
|
if (event.modifiers & Qt.ControlModifier) {
|
||||||
|
appDrawerPopout.saveAppOverride();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case Qt.Key_S:
|
||||||
|
if (event.modifiers & Qt.ControlModifier) {
|
||||||
|
appDrawerPopout.saveAppOverride();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case Qt.Key_R:
|
||||||
|
if ((event.modifiers & Qt.ControlModifier) && SessionData.getAppOverride(appDrawerPopout.editAppId) !== null) {
|
||||||
|
appDrawerPopout.resetAppOverride();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
antialiasing: true
|
antialiasing: true
|
||||||
smooth: true
|
smooth: true
|
||||||
@@ -140,7 +239,7 @@ DankPopout {
|
|||||||
id: keyHandler
|
id: keyHandler
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
focus: true
|
focus: !appDrawerPopout.editMode
|
||||||
|
|
||||||
function selectNext() {
|
function selectNext() {
|
||||||
switch (appDrawerPopout.searchMode) {
|
switch (appDrawerPopout.searchMode) {
|
||||||
@@ -172,6 +271,29 @@ DankPopout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSelectedItemPosition() {
|
||||||
|
const index = appLauncher.selectedIndex;
|
||||||
|
if (appLauncher.viewMode === "list") {
|
||||||
|
const y = index * (appList.itemHeight + appList.itemSpacing) - appList.contentY;
|
||||||
|
return Qt.point(appList.width / 2, y + appList.itemHeight / 2 + appList.y);
|
||||||
|
}
|
||||||
|
const row = Math.floor(index / appGrid.actualColumns);
|
||||||
|
const col = index % appGrid.actualColumns;
|
||||||
|
const x = col * appGrid.cellWidth + appGrid.cellWidth / 2;
|
||||||
|
const y = row * appGrid.cellHeight - appGrid.contentY + appGrid.cellHeight / 2 + appGrid.y;
|
||||||
|
return Qt.point(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
function openContextMenuForSelected() {
|
||||||
|
if (appDrawerPopout.searchMode !== "apps" || appLauncher.model.count === 0)
|
||||||
|
return;
|
||||||
|
const selectedApp = appLauncher.model.get(appLauncher.selectedIndex);
|
||||||
|
if (!selectedApp)
|
||||||
|
return;
|
||||||
|
const pos = getSelectedItemPosition();
|
||||||
|
contextMenu.show(pos.x, pos.y, selectedApp, true);
|
||||||
|
}
|
||||||
|
|
||||||
readonly property var keyMappings: {
|
readonly property var keyMappings: {
|
||||||
const mappings = {};
|
const mappings = {};
|
||||||
mappings[Qt.Key_Escape] = () => appDrawerPopout.close();
|
mappings[Qt.Key_Escape] = () => appDrawerPopout.close();
|
||||||
@@ -181,6 +303,8 @@ DankPopout {
|
|||||||
mappings[Qt.Key_Enter] = () => keyHandler.activateSelected();
|
mappings[Qt.Key_Enter] = () => keyHandler.activateSelected();
|
||||||
mappings[Qt.Key_Tab] = () => appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid" ? appLauncher.selectNextInRow() : keyHandler.selectNext();
|
mappings[Qt.Key_Tab] = () => appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid" ? appLauncher.selectNextInRow() : keyHandler.selectNext();
|
||||||
mappings[Qt.Key_Backtab] = () => appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid" ? appLauncher.selectPreviousInRow() : keyHandler.selectPrevious();
|
mappings[Qt.Key_Backtab] = () => appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid" ? appLauncher.selectPreviousInRow() : keyHandler.selectPrevious();
|
||||||
|
mappings[Qt.Key_Menu] = () => keyHandler.openContextMenuForSelected();
|
||||||
|
mappings[Qt.Key_F10] = () => keyHandler.openContextMenuForSelected();
|
||||||
|
|
||||||
if (appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid") {
|
if (appDrawerPopout.searchMode === "apps" && appLauncher.viewMode === "grid") {
|
||||||
mappings[Qt.Key_Right] = () => I18n.isRtl ? appLauncher.selectPreviousInRow() : appLauncher.selectNextInRow();
|
mappings[Qt.Key_Right] = () => I18n.isRtl ? appLauncher.selectPreviousInRow() : appLauncher.selectNextInRow();
|
||||||
@@ -191,6 +315,9 @@ DankPopout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Keys.onPressed: function (event) {
|
Keys.onPressed: function (event) {
|
||||||
|
if (appDrawerPopout.editMode)
|
||||||
|
return;
|
||||||
|
|
||||||
if (keyMappings[event.key]) {
|
if (keyMappings[event.key]) {
|
||||||
keyMappings[event.key]();
|
keyMappings[event.key]();
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
@@ -198,9 +325,8 @@ DankPopout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hasCtrl = event.modifiers & Qt.ControlModifier;
|
const hasCtrl = event.modifiers & Qt.ControlModifier;
|
||||||
if (!hasCtrl) {
|
if (!hasCtrl)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case Qt.Key_N:
|
case Qt.Key_N:
|
||||||
@@ -234,6 +360,7 @@ DankPopout {
|
|||||||
x: Theme.spacingS
|
x: Theme.spacingS
|
||||||
y: Theme.spacingS
|
y: Theme.spacingS
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
|
visible: !appDrawerPopout.editMode
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@@ -487,7 +614,7 @@ DankPopout {
|
|||||||
appLauncher.launchApp(modelData);
|
appLauncher.launchApp(modelData);
|
||||||
}
|
}
|
||||||
onItemRightClicked: function (index, modelData, mouseX, mouseY) {
|
onItemRightClicked: function (index, modelData, mouseX, mouseY) {
|
||||||
contextMenu.show(mouseX, mouseY, modelData);
|
contextMenu.show(mouseX, mouseY, modelData, false);
|
||||||
}
|
}
|
||||||
onKeyboardNavigationReset: {
|
onKeyboardNavigationReset: {
|
||||||
appLauncher.keyboardNavigationActive = false;
|
appLauncher.keyboardNavigationActive = false;
|
||||||
@@ -574,7 +701,7 @@ DankPopout {
|
|||||||
appLauncher.launchApp(modelData);
|
appLauncher.launchApp(modelData);
|
||||||
}
|
}
|
||||||
onItemRightClicked: function (index, modelData, mouseX, mouseY) {
|
onItemRightClicked: function (index, modelData, mouseX, mouseY) {
|
||||||
contextMenu.show(mouseX, mouseY, modelData);
|
contextMenu.show(mouseX, mouseY, modelData, false);
|
||||||
}
|
}
|
||||||
onKeyboardNavigationReset: {
|
onKeyboardNavigationReset: {
|
||||||
appLauncher.keyboardNavigationActive = false;
|
appLauncher.keyboardNavigationActive = false;
|
||||||
@@ -615,6 +742,281 @@ DankPopout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: editView
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: Theme.spacingS
|
||||||
|
visible: appDrawerPopout.editMode
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 40
|
||||||
|
height: 40
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: backButtonArea.containsMouse ? Theme.surfaceHover : "transparent"
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "arrow_back"
|
||||||
|
size: 20
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: backButtonArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: appDrawerPopout.closeEditMode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
width: 40
|
||||||
|
height: 40
|
||||||
|
source: appDrawerPopout.editingApp?.icon ? "image://icon/" + appDrawerPopout.editingApp.icon : "image://icon/application-x-executable"
|
||||||
|
sourceSize.width: 40
|
||||||
|
sourceSize.height: 40
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Edit App")
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: appDrawerPopout.editingApp?.name || ""
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: parent.width
|
||||||
|
height: 1
|
||||||
|
color: Theme.outlineMedium
|
||||||
|
}
|
||||||
|
|
||||||
|
Flickable {
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height - y - editButtonsRow.height - Theme.spacingM
|
||||||
|
contentHeight: editFieldsColumn.height
|
||||||
|
clip: true
|
||||||
|
boundsBehavior: Flickable.StopAtBounds
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: editFieldsColumn
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Name")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
id: editNameField
|
||||||
|
width: parent.width
|
||||||
|
height: 44
|
||||||
|
focus: true
|
||||||
|
placeholderText: appDrawerPopout.editingApp?.name || ""
|
||||||
|
keyNavigationTab: editIconField
|
||||||
|
keyNavigationBacktab: editExtraFlagsField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Icon")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
id: editIconField
|
||||||
|
width: parent.width
|
||||||
|
height: 44
|
||||||
|
placeholderText: appDrawerPopout.editingApp?.icon || ""
|
||||||
|
keyNavigationTab: editCommentField
|
||||||
|
keyNavigationBacktab: editNameField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Description")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
id: editCommentField
|
||||||
|
width: parent.width
|
||||||
|
height: 44
|
||||||
|
placeholderText: appDrawerPopout.editingApp?.comment || ""
|
||||||
|
keyNavigationTab: editEnvVarsField
|
||||||
|
keyNavigationBacktab: editIconField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Environment Variables")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: "KEY=value KEY2=value2"
|
||||||
|
font.pixelSize: Theme.fontSizeSmall - 1
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
id: editEnvVarsField
|
||||||
|
width: parent.width
|
||||||
|
height: 44
|
||||||
|
placeholderText: "VAR=value"
|
||||||
|
keyNavigationTab: editExtraFlagsField
|
||||||
|
keyNavigationBacktab: editCommentField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Extra Arguments")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
id: editExtraFlagsField
|
||||||
|
width: parent.width
|
||||||
|
height: 44
|
||||||
|
placeholderText: "--flag --option=value"
|
||||||
|
keyNavigationTab: editNameField
|
||||||
|
keyNavigationBacktab: editEnvVarsField
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: editButtonsRow
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 90
|
||||||
|
height: 40
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: resetButtonArea.containsMouse ? Theme.surfacePressed : Theme.surfaceVariantAlpha
|
||||||
|
visible: SessionData.getAppOverride(appDrawerPopout.editAppId) !== null
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Reset")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.error
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: resetButtonArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: appDrawerPopout.resetAppOverride()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 90
|
||||||
|
height: 40
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: cancelButtonArea.containsMouse ? Theme.surfacePressed : Theme.surfaceVariantAlpha
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Cancel")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: cancelButtonArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: appDrawerPopout.closeEditMode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: 90
|
||||||
|
height: 40
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: saveButtonArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.9) : Theme.primary
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Save")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.onPrimary
|
||||||
|
font.weight: Font.Medium
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: saveButtonArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: appDrawerPopout.saveAppOverride()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
visible: contextMenu.visible
|
visible: contextMenu.visible
|
||||||
@@ -624,338 +1026,21 @@ DankPopout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Popup {
|
SpotlightContextMenuPopup {
|
||||||
id: contextMenu
|
id: contextMenu
|
||||||
|
|
||||||
property var currentApp: null
|
parent: contentLoader.item
|
||||||
readonly property var desktopEntry: (currentApp && !currentApp.isPlugin && appLauncher && appLauncher._uniqueApps && currentApp.appIndex >= 0 && currentApp.appIndex < appLauncher._uniqueApps.length) ? appLauncher._uniqueApps[currentApp.appIndex] : null
|
appLauncher: appLauncher
|
||||||
readonly property string appId: desktopEntry ? (desktopEntry.id || desktopEntry.execString || "") : ""
|
parentHandler: contentLoader.item?.keyHandler ?? null
|
||||||
readonly property bool isPinned: appId && SessionData.isPinnedApp(appId)
|
searchField: contentLoader.item?.searchField ?? null
|
||||||
|
visible: false
|
||||||
|
z: 1000
|
||||||
|
}
|
||||||
|
|
||||||
function show(x, y, app) {
|
Connections {
|
||||||
currentApp = app;
|
target: contextMenu
|
||||||
let finalX = x + 4;
|
function onEditAppRequested(app) {
|
||||||
let finalY = y + 4;
|
appDrawerPopout.openEditMode(app);
|
||||||
|
|
||||||
if (contextMenu.parent) {
|
|
||||||
const parentWidth = contextMenu.parent.width;
|
|
||||||
const parentHeight = contextMenu.parent.height;
|
|
||||||
const menuWidth = contextMenu.width;
|
|
||||||
const menuHeight = contextMenu.height;
|
|
||||||
|
|
||||||
if (finalX + menuWidth > parentWidth) {
|
|
||||||
finalX = Math.max(0, parentWidth - menuWidth);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (finalY + menuHeight > parentHeight) {
|
|
||||||
finalY = Math.max(0, parentHeight - menuHeight);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
contextMenu.x = finalX;
|
|
||||||
contextMenu.y = finalY;
|
|
||||||
contextMenu.open();
|
|
||||||
}
|
|
||||||
|
|
||||||
function hide() {
|
|
||||||
contextMenu.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
width: Math.max(180, menuColumn.implicitWidth + Theme.spacingS * 2)
|
|
||||||
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
|
||||||
padding: 0
|
|
||||||
closePolicy: Popup.CloseOnPressOutside
|
|
||||||
modal: false
|
|
||||||
dim: false
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
|
||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
|
||||||
border.width: 1
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.topMargin: 4
|
|
||||||
anchors.leftMargin: 2
|
|
||||||
anchors.rightMargin: -2
|
|
||||||
anchors.bottomMargin: -4
|
|
||||||
radius: parent.radius
|
|
||||||
color: Qt.rgba(0, 0, 0, 0.15)
|
|
||||||
z: -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enter: Transition {
|
|
||||||
NumberAnimation {
|
|
||||||
property: "opacity"
|
|
||||||
from: 0
|
|
||||||
to: 1
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exit: Transition {
|
|
||||||
NumberAnimation {
|
|
||||||
property: "opacity"
|
|
||||||
from: 1
|
|
||||||
to: 0
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.emphasizedEasing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: menuColumn
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: Theme.spacingS
|
|
||||||
spacing: 1
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 32
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: pinMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: contextMenu.isPinned ? "keep_off" : "push_pin"
|
|
||||||
size: Theme.iconSize - 2
|
|
||||||
color: Theme.surfaceText
|
|
||||||
opacity: 0.7
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: contextMenu.isPinned ? I18n.tr("Unpin from Dock") : I18n.tr("Pin to Dock")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Normal
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: pinMouseArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (!contextMenu.desktopEntry) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (contextMenu.isPinned) {
|
|
||||||
SessionData.removePinnedApp(contextMenu.appId);
|
|
||||||
} else {
|
|
||||||
SessionData.addPinnedApp(contextMenu.appId);
|
|
||||||
}
|
|
||||||
contextMenu.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width - Theme.spacingS * 2
|
|
||||||
height: 5
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: parent.width
|
|
||||||
height: 1
|
|
||||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: contextMenu.desktopEntry && contextMenu.desktopEntry.actions ? contextMenu.desktopEntry.actions : []
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: Math.max(parent.width, actionRow.implicitWidth + Theme.spacingS * 2)
|
|
||||||
height: 32
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: actionMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: actionRow
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
Item {
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
width: Theme.iconSize - 2
|
|
||||||
height: Theme.iconSize - 2
|
|
||||||
visible: modelData.icon && modelData.icon !== ""
|
|
||||||
|
|
||||||
IconImage {
|
|
||||||
anchors.fill: parent
|
|
||||||
source: modelData.icon ? Quickshell.iconPath(modelData.icon, true) : ""
|
|
||||||
smooth: true
|
|
||||||
asynchronous: true
|
|
||||||
visible: status === Image.Ready
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: modelData.name || ""
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Normal
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: actionMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (modelData && contextMenu.desktopEntry) {
|
|
||||||
SessionService.launchDesktopAction(contextMenu.desktopEntry, modelData);
|
|
||||||
if (contextMenu.currentApp) {
|
|
||||||
appLauncher.appLaunched(contextMenu.currentApp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
contextMenu.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
visible: contextMenu.desktopEntry && contextMenu.desktopEntry.actions && contextMenu.desktopEntry.actions.length > 0
|
|
||||||
width: parent.width - Theme.spacingS * 2
|
|
||||||
height: 5
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: parent.width
|
|
||||||
height: 1
|
|
||||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width
|
|
||||||
height: 32
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: launchMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "launch"
|
|
||||||
size: Theme.iconSize - 2
|
|
||||||
color: Theme.surfaceText
|
|
||||||
opacity: 0.7
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Launch")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Normal
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: launchMouseArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (contextMenu.currentApp)
|
|
||||||
appLauncher.launchApp(contextMenu.currentApp);
|
|
||||||
|
|
||||||
contextMenu.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
visible: SessionService.nvidiaCommand
|
|
||||||
width: parent.width - Theme.spacingS * 2
|
|
||||||
height: 5
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
color: "transparent"
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
width: parent.width
|
|
||||||
height: 1
|
|
||||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
visible: SessionService.nvidiaCommand
|
|
||||||
width: parent.width
|
|
||||||
height: 32
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: nvidiaMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: "memory"
|
|
||||||
size: Theme.iconSize - 2
|
|
||||||
color: Theme.surfaceText
|
|
||||||
opacity: 0.7
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Launch on dGPU")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Normal
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: nvidiaMouseArea
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: {
|
|
||||||
if (contextMenu.desktopEntry) {
|
|
||||||
SessionService.launchDesktopEntry(contextMenu.desktopEntry, true);
|
|
||||||
if (contextMenu.currentApp) {
|
|
||||||
appLauncher.appLaunched(contextMenu.currentApp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
contextMenu.hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,6 +72,16 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: SessionData
|
||||||
|
function onHiddenAppsChanged() {
|
||||||
|
updateFilteredModel();
|
||||||
|
}
|
||||||
|
function onAppOverridesChanged() {
|
||||||
|
updateFilteredModel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function updateFilteredModel() {
|
function updateFilteredModel() {
|
||||||
if (suppressUpdatesWhileLaunching) {
|
if (suppressUpdatesWhileLaunching) {
|
||||||
suppressUpdatesWhileLaunching = false;
|
suppressUpdatesWhileLaunching = false;
|
||||||
@@ -124,7 +134,7 @@ Item {
|
|||||||
emptyTriggerItems = emptyTriggerItems.concat(items);
|
emptyTriggerItems = emptyTriggerItems.concat(items);
|
||||||
});
|
});
|
||||||
const coreItems = AppSearchService.getCoreApps("");
|
const coreItems = AppSearchService.getCoreApps("");
|
||||||
apps = AppSearchService.applications.concat(emptyTriggerItems).concat(coreItems);
|
apps = AppSearchService.getVisibleApplications().concat(emptyTriggerItems).concat(coreItems);
|
||||||
} else {
|
} else {
|
||||||
apps = AppSearchService.getAppsInCategory(selectedCategory).slice(0, maxResults);
|
apps = AppSearchService.getAppsInCategory(selectedCategory).slice(0, maxResults);
|
||||||
const coreItems = AppSearchService.getCoreApps("").filter(app => app.categories.includes(selectedCategory));
|
const coreItems = AppSearchService.getCoreApps("").filter(app => app.categories.includes(selectedCategory));
|
||||||
@@ -205,6 +215,7 @@ Item {
|
|||||||
"isPlugin": isPluginItem,
|
"isPlugin": isPluginItem,
|
||||||
"isCore": app.isCore === true,
|
"isCore": app.isCore === true,
|
||||||
"isBuiltInLauncher": app.isBuiltInLauncher === true,
|
"isBuiltInLauncher": app.isBuiltInLauncher === true,
|
||||||
|
"isAction": app.isAction === true,
|
||||||
"appIndex": uniqueApps.length - 1,
|
"appIndex": uniqueApps.length - 1,
|
||||||
"pinned": app._pinned === true
|
"pinned": app._pinned === true
|
||||||
});
|
});
|
||||||
@@ -283,6 +294,13 @@ Item {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (appData.isAction && actualApp.parentApp && actualApp.actionData) {
|
||||||
|
SessionService.launchDesktopAction(actualApp.parentApp, actualApp.actionData);
|
||||||
|
appLaunched(appData);
|
||||||
|
AppUsageHistoryData.addAppUsage(actualApp.parentApp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
SessionService.launchDesktopEntry(actualApp);
|
SessionService.launchDesktopEntry(actualApp);
|
||||||
appLaunched(appData);
|
appLaunched(appData);
|
||||||
AppUsageHistoryData.addAppUsage(actualApp);
|
AppUsageHistoryData.addAppUsage(actualApp);
|
||||||
|
|||||||
@@ -757,12 +757,13 @@ Item {
|
|||||||
}
|
}
|
||||||
property bool isOccupied: {
|
property bool isOccupied: {
|
||||||
if (CompositorService.isHyprland)
|
if (CompositorService.isHyprland)
|
||||||
return Array.from(Hyprland.toplevels?.values || [])
|
return Array.from(Hyprland.toplevels?.values || []).some(tl => tl.workspace?.id === modelData?.id);
|
||||||
.some(tl => tl.workspace?.id === modelData?.id);
|
|
||||||
if (CompositorService.isDwl)
|
if (CompositorService.isDwl)
|
||||||
return modelData.clients > 0;
|
return modelData.clients > 0;
|
||||||
if (CompositorService.isNiri)
|
if (CompositorService.isNiri) {
|
||||||
return NiriService.windows?.some(win => win.workspace_id === modelData?.id) ?? false;
|
const workspace = NiriService.allWorkspaces.find(ws => ws.idx + 1 === modelData && ws.output === root.effectiveScreenName);
|
||||||
|
return workspace ? (NiriService.windows?.some(win => win.workspace_id === workspace.id) ?? false) : false;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
property bool isPlaceholder: {
|
property bool isPlaceholder: {
|
||||||
@@ -848,16 +849,18 @@ Item {
|
|||||||
|
|
||||||
readonly property color occupiedColor: {
|
readonly property color occupiedColor: {
|
||||||
switch (SettingsData.workspaceOccupiedColorMode) {
|
switch (SettingsData.workspaceOccupiedColorMode) {
|
||||||
|
case "sec":
|
||||||
|
return Theme.secondary;
|
||||||
case "s":
|
case "s":
|
||||||
return Theme.surface;
|
return Theme.surface;
|
||||||
case "sc":
|
case "sc":
|
||||||
return Theme.surfaceContainer;
|
return Theme.surfaceContainer;
|
||||||
case "sch":
|
case "sch":
|
||||||
return Theme.surfaceContainerHigh;
|
return Theme.surfaceContainerHigh;
|
||||||
case "none":
|
case "schh":
|
||||||
return unfocusedColor;
|
return Theme.surfaceContainerHighest;
|
||||||
default:
|
default:
|
||||||
return Theme.secondary;
|
return unfocusedColor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -182,30 +182,20 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onOpenRequested: {
|
onOpenRequested: {
|
||||||
if (hasUnsavedChanges()) {
|
textEditor.autoSaveToSession();
|
||||||
root.pendingAction = "open";
|
if (textEditor.text.length > 0) {
|
||||||
root.confirmationDialogOpen = true;
|
createNewTab();
|
||||||
confirmationDialogLoader.active = true;
|
|
||||||
if (confirmationDialogLoader.item)
|
|
||||||
confirmationDialogLoader.item.open();
|
|
||||||
} else {
|
|
||||||
root.fileDialogOpen = true;
|
|
||||||
loadBrowserLoader.active = true;
|
|
||||||
if (loadBrowserLoader.item)
|
|
||||||
loadBrowserLoader.item.open();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
root.fileDialogOpen = true;
|
||||||
|
loadBrowserLoader.active = true;
|
||||||
|
if (loadBrowserLoader.item)
|
||||||
|
loadBrowserLoader.item.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
onNewRequested: {
|
onNewRequested: {
|
||||||
if (hasUnsavedChanges()) {
|
textEditor.autoSaveToSession();
|
||||||
root.pendingAction = "new";
|
createNewTab();
|
||||||
root.confirmationDialogOpen = true;
|
|
||||||
confirmationDialogLoader.active = true;
|
|
||||||
if (confirmationDialogLoader.item)
|
|
||||||
confirmationDialogLoader.item.open();
|
|
||||||
} else {
|
|
||||||
createNewTab();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onEscapePressed: {
|
onEscapePressed: {
|
||||||
@@ -266,7 +256,7 @@ Item {
|
|||||||
id: saveBrowserLoader
|
id: saveBrowserLoader
|
||||||
active: false
|
active: false
|
||||||
|
|
||||||
FileBrowserModal {
|
FileBrowserSurfaceModal {
|
||||||
id: saveBrowser
|
id: saveBrowser
|
||||||
|
|
||||||
browserTitle: I18n.tr("Save Notepad File")
|
browserTitle: I18n.tr("Save Notepad File")
|
||||||
@@ -332,7 +322,7 @@ Item {
|
|||||||
id: loadBrowserLoader
|
id: loadBrowserLoader
|
||||||
active: false
|
active: false
|
||||||
|
|
||||||
FileBrowserModal {
|
FileBrowserSurfaceModal {
|
||||||
id: loadBrowser
|
id: loadBrowser
|
||||||
|
|
||||||
browserTitle: I18n.tr("Open Notepad File")
|
browserTitle: I18n.tr("Open Notepad File")
|
||||||
|
|||||||
@@ -52,20 +52,15 @@ Column {
|
|||||||
|
|
||||||
readonly property bool isActive: NotepadStorageService.currentTabIndex === index
|
readonly property bool isActive: NotepadStorageService.currentTabIndex === index
|
||||||
readonly property bool isHovered: tabMouseArea.containsMouse && !closeMouseArea.containsMouse
|
readonly property bool isHovered: tabMouseArea.containsMouse && !closeMouseArea.containsMouse
|
||||||
readonly property real calculatedWidth: {
|
readonly property real tabWidth: 128
|
||||||
const textWidth = tabText.paintedWidth || 100;
|
|
||||||
const closeButtonWidth = NotepadStorageService.tabs.length > 1 ? 20 : 0;
|
|
||||||
const spacing = Theme.spacingXS;
|
|
||||||
const padding = Theme.spacingM * 2;
|
|
||||||
return Math.max(120, Math.min(200, textWidth + closeButtonWidth + spacing + padding));
|
|
||||||
}
|
|
||||||
|
|
||||||
width: calculatedWidth
|
width: tabWidth
|
||||||
height: 32
|
height: 32
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: isActive ? Theme.primaryPressed : isHovered ? Theme.primaryHoverLight : Theme.withAlpha(Theme.primaryPressed, 0)
|
color: isActive ? Theme.primaryPressed : isHovered ? Theme.primaryHoverLight : Theme.withAlpha(Theme.primaryPressed, 0)
|
||||||
border.width: isActive ? 0 : 1
|
border.width: isActive ? 0 : 1
|
||||||
border.color: Theme.outlineMedium
|
border.color: Theme.outlineMedium
|
||||||
|
clip: true
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: tabMouseArea
|
id: tabMouseArea
|
||||||
@@ -79,11 +74,14 @@ Column {
|
|||||||
|
|
||||||
Row {
|
Row {
|
||||||
id: tabContent
|
id: tabContent
|
||||||
anchors.centerIn: parent
|
anchors.fill: parent
|
||||||
|
anchors.leftMargin: Theme.spacingM
|
||||||
|
anchors.rightMargin: Theme.spacingM
|
||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
id: tabText
|
id: tabText
|
||||||
|
width: parent.width - (tabCloseButton.visible ? tabCloseButton.width + Theme.spacingXS : 0)
|
||||||
text: {
|
text: {
|
||||||
var prefix = "";
|
var prefix = "";
|
||||||
if (hasUnsavedChangesForTab(modelData)) {
|
if (hasUnsavedChangesForTab(modelData)) {
|
||||||
@@ -96,6 +94,7 @@ Column {
|
|||||||
font.weight: isActive ? Font.Medium : Font.Normal
|
font.weight: isActive ? Font.Medium : Font.Normal
|
||||||
elide: Text.ElideMiddle
|
elide: Text.ElideMiddle
|
||||||
maximumLineCount: 1
|
maximumLineCount: 1
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
pragma ComponentBehavior: Bound
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
@@ -6,8 +7,6 @@ import qs.Common
|
|||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
pragma ComponentBehavior: Bound
|
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
@@ -22,55 +21,181 @@ Column {
|
|||||||
property int currentMatchIndex: -1
|
property int currentMatchIndex: -1
|
||||||
property int matchCount: 0
|
property int matchCount: 0
|
||||||
|
|
||||||
signal saveRequested()
|
// Plugin-provided markdown/syntax highlighting (via builtInPluginSettings)
|
||||||
signal openRequested()
|
property bool pluginInstalled: SettingsData.getBuiltInPluginSetting("dankNotepadMarkdown", "enabled", false)
|
||||||
signal newRequested()
|
property bool pluginMarkdownEnabled: SettingsData.getBuiltInPluginSetting("dankNotepadMarkdown", "markdownPreview", false)
|
||||||
signal escapePressed()
|
property bool pluginSyntaxEnabled: SettingsData.getBuiltInPluginSetting("dankNotepadMarkdown", "syntaxHighlighting", false)
|
||||||
signal contentChanged()
|
property string pluginHighlightedHtml: SettingsData.getBuiltInPluginSetting("dankNotepadMarkdown", "highlightedHtml", "")
|
||||||
signal settingsRequested()
|
property string pluginFileExtension: SettingsData.getBuiltInPluginSetting("dankNotepadMarkdown", "currentFileExtension", "")
|
||||||
|
|
||||||
|
// Local toggle for markdown preview (can be toggled from UI)
|
||||||
|
property bool markdownPreviewActive: pluginMarkdownEnabled
|
||||||
|
|
||||||
|
// Toggle markdown preview
|
||||||
|
function toggleMarkdownPreview() {
|
||||||
|
if (!markdownPreviewActive) {
|
||||||
|
// Entering preview mode
|
||||||
|
syncContentToPlugin();
|
||||||
|
markdownPreviewActive = true;
|
||||||
|
} else {
|
||||||
|
// Exiting preview mode
|
||||||
|
markdownPreviewActive = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Local toggle for syntax highlighting preview (read-only view with colors)
|
||||||
|
property bool syntaxPreviewActive: false
|
||||||
|
|
||||||
|
// Store original text when entering syntax preview mode
|
||||||
|
property string syntaxPreviewOriginalText: ""
|
||||||
|
|
||||||
|
// Function to refresh plugin settings (called from Connections inside TextArea)
|
||||||
|
function refreshPluginSettings() {
|
||||||
|
pluginInstalled = SettingsData.getBuiltInPluginSetting("dankNotepadMarkdown", "enabled", false);
|
||||||
|
pluginMarkdownEnabled = SettingsData.getBuiltInPluginSetting("dankNotepadMarkdown", "markdownPreview", false);
|
||||||
|
pluginSyntaxEnabled = SettingsData.getBuiltInPluginSetting("dankNotepadMarkdown", "syntaxHighlighting", false);
|
||||||
|
pluginHighlightedHtml = SettingsData.getBuiltInPluginSetting("dankNotepadMarkdown", "highlightedHtml", "");
|
||||||
|
pluginFileExtension = SettingsData.getBuiltInPluginSetting("dankNotepadMarkdown", "currentFileExtension", "");
|
||||||
|
|
||||||
|
console.warn("NotepadTextEditor: Plugin settings refreshed. MdEnabled:", pluginMarkdownEnabled, "HtmlLength:", pluginHighlightedHtml.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle syntax preview mode
|
||||||
|
function toggleSyntaxPreview() {
|
||||||
|
if (!syntaxPreviewActive) {
|
||||||
|
// Entering preview mode
|
||||||
|
syncContentToPlugin();
|
||||||
|
syntaxPreviewActive = true;
|
||||||
|
} else {
|
||||||
|
// Exiting preview mode
|
||||||
|
syntaxPreviewActive = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// File extension detection for current tab
|
||||||
|
readonly property string currentFilePath: currentTab?.filePath || ""
|
||||||
|
readonly property string currentFileExtension: {
|
||||||
|
if (!currentFilePath)
|
||||||
|
return "";
|
||||||
|
var parts = currentFilePath.split('.');
|
||||||
|
return parts.length > 1 ? parts[parts.length - 1].toLowerCase() : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
onCurrentTabChanged: handleCurrentTabChanged()
|
||||||
|
|
||||||
|
Component.onCompleted: handleCurrentTabChanged()
|
||||||
|
|
||||||
|
function handleCurrentTabChanged() {
|
||||||
|
if (!currentTab)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Reset preview state ONLY when tab actually changes
|
||||||
|
markdownPreviewActive = false;
|
||||||
|
syntaxPreviewActive = false;
|
||||||
|
syntaxPreviewOriginalText = "";
|
||||||
|
textArea.readOnly = false;
|
||||||
|
|
||||||
|
syncContentToPlugin();
|
||||||
|
}
|
||||||
|
|
||||||
|
function syncContentToPlugin() {
|
||||||
|
if (!currentTab)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Notify plugin of content update
|
||||||
|
// console.warn("NotepadTextEditor: Pushing content to plugin. Length:", textArea.text.length, "Path:", currentFilePath);
|
||||||
|
SettingsData.setBuiltInPluginSetting("dankNotepadMarkdown", "currentFilePath", currentFilePath);
|
||||||
|
SettingsData.setBuiltInPluginSetting("dankNotepadMarkdown", "currentFileExtension", currentFileExtension);
|
||||||
|
SettingsData.setBuiltInPluginSetting("dankNotepadMarkdown", "sourceContent", textArea.text);
|
||||||
|
SettingsData.setBuiltInPluginSetting("dankNotepadMarkdown", "currentTabChanged", Date.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debounce content updates to plugin to keep preview ready
|
||||||
|
Timer {
|
||||||
|
id: syncTimer
|
||||||
|
interval: 500
|
||||||
|
repeat: false
|
||||||
|
onTriggered: syncContentToPlugin()
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: textArea
|
||||||
|
function onTextChanged() {
|
||||||
|
if (!markdownPreviewActive && !syntaxPreviewActive) {
|
||||||
|
syncTimer.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property string fileExtension: {
|
||||||
|
if (!currentFilePath)
|
||||||
|
return "";
|
||||||
|
var parts = currentFilePath.split('.');
|
||||||
|
return parts.length > 1 ? parts[parts.length - 1].toLowerCase() : "";
|
||||||
|
}
|
||||||
|
readonly property bool isMarkdownFile: fileExtension === "md" || fileExtension === "markdown" || fileExtension === "mdown"
|
||||||
|
readonly property bool isCodeFile: fileExtension !== "" && fileExtension !== "txt" && !isMarkdownFile
|
||||||
|
|
||||||
|
signal saveRequested
|
||||||
|
signal openRequested
|
||||||
|
signal newRequested
|
||||||
|
signal escapePressed
|
||||||
|
signal contentChanged
|
||||||
|
signal settingsRequested
|
||||||
|
|
||||||
function hasUnsavedChanges() {
|
function hasUnsavedChanges() {
|
||||||
if (!currentTab || !contentLoaded) {
|
if (!currentTab || !contentLoaded) {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentTab.isTemporary) {
|
if (currentTab.isTemporary) {
|
||||||
return textArea.text.length > 0
|
return textArea.text.length > 0;
|
||||||
}
|
}
|
||||||
return textArea.text !== lastSavedContent
|
|
||||||
|
// If in preview mode, compare original text
|
||||||
|
if (markdownPreviewActive || syntaxPreviewActive) {
|
||||||
|
return syntaxPreviewOriginalText !== lastSavedContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return textArea.text !== lastSavedContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadCurrentTabContent() {
|
function loadCurrentTabContent() {
|
||||||
if (!currentTab) return
|
if (!currentTab)
|
||||||
|
return;
|
||||||
|
contentLoaded = false;
|
||||||
|
// Reset preview states on load
|
||||||
|
markdownPreviewActive = false;
|
||||||
|
syntaxPreviewActive = false;
|
||||||
|
syntaxPreviewOriginalText = "";
|
||||||
|
|
||||||
contentLoaded = false
|
NotepadStorageService.loadTabContent(NotepadStorageService.currentTabIndex, content => {
|
||||||
NotepadStorageService.loadTabContent(
|
lastSavedContent = content;
|
||||||
NotepadStorageService.currentTabIndex,
|
textArea.text = content;
|
||||||
(content) => {
|
contentLoaded = true;
|
||||||
lastSavedContent = content
|
textArea.readOnly = false;
|
||||||
textArea.text = content
|
});
|
||||||
contentLoaded = true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveCurrentTabContent() {
|
function saveCurrentTabContent() {
|
||||||
if (!currentTab || !contentLoaded) return
|
if (!currentTab || !contentLoaded)
|
||||||
|
return;
|
||||||
|
|
||||||
NotepadStorageService.saveTabContent(
|
// If in preview mode, save the original text, NOT the HTML
|
||||||
NotepadStorageService.currentTabIndex,
|
var contentToSave = (markdownPreviewActive || syntaxPreviewActive) ? syntaxPreviewOriginalText : textArea.text;
|
||||||
textArea.text
|
|
||||||
)
|
NotepadStorageService.saveTabContent(NotepadStorageService.currentTabIndex, contentToSave);
|
||||||
lastSavedContent = textArea.text
|
lastSavedContent = contentToSave;
|
||||||
}
|
}
|
||||||
|
|
||||||
function autoSaveToSession() {
|
function autoSaveToSession() {
|
||||||
if (!currentTab || !contentLoaded) return
|
if (!currentTab || !contentLoaded)
|
||||||
saveCurrentTabContent()
|
return;
|
||||||
|
saveCurrentTabContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
function setTextDocumentLineHeight() {
|
function setTextDocumentLineHeight() {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
property string lastTextForLineModel: ""
|
property string lastTextForLineModel: ""
|
||||||
@@ -78,100 +203,102 @@ Column {
|
|||||||
|
|
||||||
function updateLineModel() {
|
function updateLineModel() {
|
||||||
if (!SettingsData.notepadShowLineNumbers) {
|
if (!SettingsData.notepadShowLineNumbers) {
|
||||||
lineModel = []
|
lineModel = [];
|
||||||
lastTextForLineModel = ""
|
lastTextForLineModel = "";
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// In preview mode, line numbers might not match visual lines correctly due to wrapping/HTML
|
||||||
|
// But for now let's use the current text (plain or HTML)
|
||||||
if (textArea.text !== lastTextForLineModel || lineModel.length === 0) {
|
if (textArea.text !== lastTextForLineModel || lineModel.length === 0) {
|
||||||
lastTextForLineModel = textArea.text
|
lastTextForLineModel = textArea.text;
|
||||||
lineModel = textArea.text.split('\n')
|
lineModel = textArea.text.split('\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function performSearch() {
|
function performSearch() {
|
||||||
let matches = []
|
let matches = [];
|
||||||
currentMatchIndex = -1
|
currentMatchIndex = -1;
|
||||||
|
|
||||||
if (!searchQuery || searchQuery.length === 0) {
|
if (!searchQuery || searchQuery.length === 0) {
|
||||||
searchMatches = []
|
searchMatches = [];
|
||||||
matchCount = 0
|
matchCount = 0;
|
||||||
textArea.select(0, 0)
|
textArea.select(0, 0);
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const text = textArea.text
|
const text = textArea.text;
|
||||||
const query = searchQuery.toLowerCase()
|
const query = searchQuery.toLowerCase();
|
||||||
let index = 0
|
let index = 0;
|
||||||
|
|
||||||
while (index < text.length) {
|
while (index < text.length) {
|
||||||
const foundIndex = text.toLowerCase().indexOf(query, index)
|
const foundIndex = text.toLowerCase().indexOf(query, index);
|
||||||
if (foundIndex === -1) break
|
if (foundIndex === -1)
|
||||||
|
break;
|
||||||
matches.push({
|
matches.push({
|
||||||
start: foundIndex,
|
start: foundIndex,
|
||||||
end: foundIndex + searchQuery.length
|
end: foundIndex + searchQuery.length
|
||||||
})
|
});
|
||||||
index = foundIndex + 1
|
index = foundIndex + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
searchMatches = matches
|
searchMatches = matches;
|
||||||
matchCount = matches.length
|
matchCount = matches.length;
|
||||||
|
|
||||||
if (matchCount > 0) {
|
if (matchCount > 0) {
|
||||||
currentMatchIndex = 0
|
currentMatchIndex = 0;
|
||||||
highlightCurrentMatch()
|
highlightCurrentMatch();
|
||||||
} else {
|
} else {
|
||||||
textArea.select(0, 0)
|
textArea.select(0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function highlightCurrentMatch() {
|
function highlightCurrentMatch() {
|
||||||
if (currentMatchIndex >= 0 && currentMatchIndex < searchMatches.length) {
|
if (currentMatchIndex >= 0 && currentMatchIndex < searchMatches.length) {
|
||||||
const match = searchMatches[currentMatchIndex]
|
const match = searchMatches[currentMatchIndex];
|
||||||
|
|
||||||
textArea.cursorPosition = match.start
|
textArea.cursorPosition = match.start;
|
||||||
textArea.moveCursorSelection(match.end, TextEdit.SelectCharacters)
|
textArea.moveCursorSelection(match.end, TextEdit.SelectCharacters);
|
||||||
|
|
||||||
const flickable = textArea.parent
|
const flickable = textArea.parent;
|
||||||
if (flickable && flickable.contentY !== undefined) {
|
if (flickable && flickable.contentY !== undefined) {
|
||||||
const lineHeight = textArea.font.pixelSize * 1.5
|
const lineHeight = textArea.font.pixelSize * 1.5;
|
||||||
const approxLine = textArea.text.substring(0, match.start).split('\n').length
|
const approxLine = textArea.text.substring(0, match.start).split('\n').length;
|
||||||
const targetY = approxLine * lineHeight - flickable.height / 2
|
const targetY = approxLine * lineHeight - flickable.height / 2;
|
||||||
flickable.contentY = Math.max(0, Math.min(targetY, flickable.contentHeight - flickable.height))
|
flickable.contentY = Math.max(0, Math.min(targetY, flickable.contentHeight - flickable.height));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function findNext() {
|
function findNext() {
|
||||||
if (matchCount === 0 || searchMatches.length === 0) return
|
if (matchCount === 0 || searchMatches.length === 0)
|
||||||
|
return;
|
||||||
currentMatchIndex = (currentMatchIndex + 1) % matchCount
|
currentMatchIndex = (currentMatchIndex + 1) % matchCount;
|
||||||
highlightCurrentMatch()
|
highlightCurrentMatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
function findPrevious() {
|
function findPrevious() {
|
||||||
if (matchCount === 0 || searchMatches.length === 0) return
|
if (matchCount === 0 || searchMatches.length === 0)
|
||||||
|
return;
|
||||||
currentMatchIndex = currentMatchIndex <= 0 ? matchCount - 1 : currentMatchIndex - 1
|
currentMatchIndex = currentMatchIndex <= 0 ? matchCount - 1 : currentMatchIndex - 1;
|
||||||
highlightCurrentMatch()
|
highlightCurrentMatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
function showSearch() {
|
function showSearch() {
|
||||||
searchVisible = true
|
searchVisible = true;
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
searchField.forceActiveFocus()
|
searchField.forceActiveFocus();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function hideSearch() {
|
function hideSearch() {
|
||||||
searchVisible = false
|
searchVisible = false;
|
||||||
searchQuery = ""
|
searchQuery = "";
|
||||||
searchMatches = []
|
searchMatches = [];
|
||||||
matchCount = 0
|
matchCount = 0;
|
||||||
currentMatchIndex = -1
|
currentMatchIndex = -1;
|
||||||
textArea.select(0, 0)
|
textArea.select(0, 0);
|
||||||
textArea.forceActiveFocus()
|
textArea.forceActiveFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
spacing: Theme.spacingM
|
spacing: Theme.spacingM
|
||||||
@@ -221,43 +348,43 @@ Column {
|
|||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
text = root.searchQuery
|
text = root.searchQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: root
|
target: root
|
||||||
function onSearchQueryChanged() {
|
function onSearchQueryChanged() {
|
||||||
if (searchField.text !== root.searchQuery) {
|
if (searchField.text !== root.searchQuery) {
|
||||||
searchField.text = root.searchQuery
|
searchField.text = root.searchQuery;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
if (root.searchQuery !== text) {
|
if (root.searchQuery !== text) {
|
||||||
root.searchQuery = text
|
root.searchQuery = text;
|
||||||
root.performSearch()
|
root.performSearch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Keys.onEscapePressed: event => {
|
Keys.onEscapePressed: event => {
|
||||||
root.hideSearch()
|
root.hideSearch();
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
Keys.onReturnPressed: event => {
|
Keys.onReturnPressed: event => {
|
||||||
if (event.modifiers & Qt.ShiftModifier) {
|
if (event.modifiers & Qt.ShiftModifier) {
|
||||||
root.findPrevious()
|
root.findPrevious();
|
||||||
} else {
|
} else {
|
||||||
root.findNext()
|
root.findNext();
|
||||||
}
|
}
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
Keys.onEnterPressed: event => {
|
Keys.onEnterPressed: event => {
|
||||||
if (event.modifiers & Qt.ShiftModifier) {
|
if (event.modifiers & Qt.ShiftModifier) {
|
||||||
root.findPrevious()
|
root.findPrevious();
|
||||||
} else {
|
} else {
|
||||||
root.findNext()
|
root.findNext();
|
||||||
}
|
}
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,6 +452,7 @@ Column {
|
|||||||
|
|
||||||
DankFlickable {
|
DankFlickable {
|
||||||
id: flickable
|
id: flickable
|
||||||
|
visible: !root.markdownPreviewActive && !root.syntaxPreviewActive
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 1
|
anchors.margins: 1
|
||||||
clip: true
|
clip: true
|
||||||
@@ -383,7 +511,7 @@ Column {
|
|||||||
|
|
||||||
TextArea.flickable: TextArea {
|
TextArea.flickable: TextArea {
|
||||||
id: textArea
|
id: textArea
|
||||||
placeholderText: I18n.tr("Start typing your notes here...")
|
placeholderText: ""
|
||||||
placeholderTextColor: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
placeholderTextColor: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||||
font.family: SettingsData.notepadUseMonospace ? SettingsData.monoFontFamily : (SettingsData.notepadFontFamily || SettingsData.fontFamily)
|
font.family: SettingsData.notepadUseMonospace ? SettingsData.monoFontFamily : (SettingsData.notepadFontFamily || SettingsData.fontFamily)
|
||||||
font.pixelSize: SettingsData.notepadFontSize * SettingsData.fontScale
|
font.pixelSize: SettingsData.notepadFontSize * SettingsData.fontScale
|
||||||
@@ -397,6 +525,7 @@ Column {
|
|||||||
focus: true
|
focus: true
|
||||||
activeFocusOnTab: true
|
activeFocusOnTab: true
|
||||||
textFormat: TextEdit.PlainText
|
textFormat: TextEdit.PlainText
|
||||||
|
// readOnly: root.syntaxPreviewActive || root.markdownPreviewActive // Handled by visibility now
|
||||||
inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase
|
inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase
|
||||||
persistentSelection: true
|
persistentSelection: true
|
||||||
tabStopDistance: 40
|
tabStopDistance: 40
|
||||||
@@ -404,27 +533,57 @@ Column {
|
|||||||
topPadding: Theme.spacingM
|
topPadding: Theme.spacingM
|
||||||
rightPadding: Theme.spacingM
|
rightPadding: Theme.spacingM
|
||||||
bottomPadding: Theme.spacingM
|
bottomPadding: Theme.spacingM
|
||||||
|
cursorDelegate: Rectangle {
|
||||||
|
width: 1.5
|
||||||
|
radius: 1
|
||||||
|
color: Theme.surfaceText
|
||||||
|
x: textArea.cursorRectangle.x
|
||||||
|
y: textArea.cursorRectangle.y
|
||||||
|
height: textArea.cursorRectangle.height
|
||||||
|
opacity: 1.0
|
||||||
|
|
||||||
|
SequentialAnimation on opacity {
|
||||||
|
running: textArea.activeFocus
|
||||||
|
loops: Animation.Infinite
|
||||||
|
PropertyAnimation {
|
||||||
|
from: 1.0
|
||||||
|
to: 0.0
|
||||||
|
duration: 650
|
||||||
|
easing.type: Easing.InOutQuad
|
||||||
|
}
|
||||||
|
PropertyAnimation {
|
||||||
|
from: 0.0
|
||||||
|
to: 1.0
|
||||||
|
duration: 650
|
||||||
|
easing.type: Easing.InOutQuad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
loadCurrentTabContent()
|
loadCurrentTabContent();
|
||||||
setTextDocumentLineHeight()
|
setTextDocumentLineHeight();
|
||||||
root.updateLineModel()
|
root.updateLineModel();
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
textArea.forceActiveFocus()
|
textArea.forceActiveFocus();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: NotepadStorageService
|
target: NotepadStorageService
|
||||||
function onCurrentTabIndexChanged() {
|
function onCurrentTabIndexChanged() {
|
||||||
loadCurrentTabContent()
|
// Exit syntax preview mode when switching tabs
|
||||||
|
if (root.syntaxPreviewActive) {
|
||||||
|
root.syntaxPreviewActive = false;
|
||||||
|
}
|
||||||
|
loadCurrentTabContent();
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
textArea.forceActiveFocus()
|
textArea.forceActiveFocus();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
function onTabsChanged() {
|
function onTabsChanged() {
|
||||||
if (NotepadStorageService.tabs.length > 0 && !contentLoaded) {
|
if (NotepadStorageService.tabs.length > 0 && !contentLoaded) {
|
||||||
loadCurrentTabContent()
|
loadCurrentTabContent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -432,46 +591,49 @@ Column {
|
|||||||
Connections {
|
Connections {
|
||||||
target: SettingsData
|
target: SettingsData
|
||||||
function onNotepadShowLineNumbersChanged() {
|
function onNotepadShowLineNumbersChanged() {
|
||||||
root.updateLineModel()
|
root.updateLineModel();
|
||||||
|
}
|
||||||
|
function onBuiltInPluginSettingsChanged() {
|
||||||
|
root.refreshPluginSettings();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onTextChanged: {
|
onTextChanged: {
|
||||||
if (contentLoaded && text !== lastSavedContent) {
|
if (contentLoaded && text !== lastSavedContent) {
|
||||||
autoSaveTimer.restart()
|
autoSaveTimer.restart();
|
||||||
}
|
}
|
||||||
root.contentChanged()
|
root.contentChanged();
|
||||||
root.updateLineModel()
|
root.updateLineModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
Keys.onEscapePressed: (event) => {
|
Keys.onEscapePressed: event => {
|
||||||
root.escapePressed()
|
root.escapePressed();
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Keys.onPressed: (event) => {
|
Keys.onPressed: event => {
|
||||||
if (event.modifiers & Qt.ControlModifier) {
|
if (event.modifiers & Qt.ControlModifier) {
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case Qt.Key_S:
|
case Qt.Key_S:
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
root.saveRequested()
|
root.saveRequested();
|
||||||
break
|
break;
|
||||||
case Qt.Key_O:
|
case Qt.Key_O:
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
root.openRequested()
|
root.openRequested();
|
||||||
break
|
break;
|
||||||
case Qt.Key_N:
|
case Qt.Key_N:
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
root.newRequested()
|
root.newRequested();
|
||||||
break
|
break;
|
||||||
case Qt.Key_A:
|
case Qt.Key_A:
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
selectAll()
|
selectAll();
|
||||||
break
|
break;
|
||||||
case Qt.Key_F:
|
case Qt.Key_F:
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
root.showSearch()
|
root.showSearch();
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -479,6 +641,66 @@ Column {
|
|||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Make links clickable in markdown preview mode
|
||||||
|
onLinkActivated: link => {
|
||||||
|
Qt.openUrlExternally(link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: placeholderOverlay
|
||||||
|
text: I18n.tr("Start typing your notes here...")
|
||||||
|
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
|
||||||
|
font.family: textArea.font.family
|
||||||
|
font.pixelSize: textArea.font.pixelSize
|
||||||
|
visible: textArea.text.length === 0
|
||||||
|
anchors.left: textArea.left
|
||||||
|
anchors.top: textArea.top
|
||||||
|
anchors.leftMargin: textArea.leftPadding
|
||||||
|
anchors.topMargin: textArea.topPadding
|
||||||
|
z: textArea.z + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dedicated Flickable for Preview Mode
|
||||||
|
DankFlickable {
|
||||||
|
id: previewFlickable
|
||||||
|
visible: root.markdownPreviewActive || root.syntaxPreviewActive
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 1
|
||||||
|
clip: true
|
||||||
|
contentWidth: width - 11
|
||||||
|
|
||||||
|
TextArea.flickable: TextArea {
|
||||||
|
id: previewAreaReal
|
||||||
|
text: root.pluginHighlightedHtml
|
||||||
|
textFormat: TextEdit.RichText
|
||||||
|
readOnly: true
|
||||||
|
|
||||||
|
// Copy styling from main textArea
|
||||||
|
placeholderText: ""
|
||||||
|
font.family: SettingsData.notepadUseMonospace ? SettingsData.monoFontFamily : (SettingsData.notepadFontFamily || SettingsData.fontFamily)
|
||||||
|
font.pixelSize: SettingsData.notepadFontSize * SettingsData.fontScale
|
||||||
|
font.letterSpacing: 0
|
||||||
|
color: Theme.surfaceText
|
||||||
|
selectedTextColor: Theme.background
|
||||||
|
selectionColor: Theme.primary
|
||||||
|
selectByMouse: true
|
||||||
|
selectByKeyboard: true
|
||||||
|
wrapMode: TextArea.Wrap
|
||||||
|
focus: true
|
||||||
|
activeFocusOnTab: true
|
||||||
|
|
||||||
|
leftPadding: Theme.spacingM
|
||||||
|
topPadding: Theme.spacingM
|
||||||
|
rightPadding: Theme.spacingM
|
||||||
|
bottomPadding: Theme.spacingM
|
||||||
|
|
||||||
|
// Make links clickable
|
||||||
|
onLinkActivated: link => {
|
||||||
|
Qt.openUrlExternally(link);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -545,6 +767,44 @@ Column {
|
|||||||
color: Theme.surfaceTextMedium
|
color: Theme.surfaceTextMedium
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Markdown preview toggle (only visible when plugin installed and viewing .md file)
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
visible: root.pluginInstalled && root.isMarkdownFile
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
iconName: root.markdownPreviewActive ? "visibility" : "visibility_off"
|
||||||
|
iconSize: Theme.iconSize - 2
|
||||||
|
iconColor: root.markdownPreviewActive ? Theme.primary : Theme.surfaceTextMedium
|
||||||
|
onClicked: root.toggleMarkdownPreview()
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: I18n.tr("Preview")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: root.markdownPreviewActive ? Theme.primary : Theme.surfaceTextMedium
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Syntax highlighting toggle (only visible when plugin installed and viewing code file)
|
||||||
|
Row {
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
visible: root.pluginInstalled && root.pluginSyntaxEnabled && root.isCodeFile
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
iconName: root.syntaxPreviewActive ? "code" : "code_off"
|
||||||
|
iconSize: Theme.iconSize - 2
|
||||||
|
iconColor: root.syntaxPreviewActive ? Theme.primary : Theme.surfaceTextMedium
|
||||||
|
onClicked: root.toggleSyntaxPreview()
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: root.syntaxPreviewActive ? I18n.tr("Edit") : I18n.tr("Highlight")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: root.syntaxPreviewActive ? Theme.primary : Theme.surfaceTextMedium
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
@@ -578,29 +838,29 @@ Column {
|
|||||||
StyledText {
|
StyledText {
|
||||||
text: {
|
text: {
|
||||||
if (autoSaveTimer.running) {
|
if (autoSaveTimer.running) {
|
||||||
return I18n.tr("Auto-saving...")
|
return I18n.tr("Auto-saving...");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasUnsavedChanges()) {
|
if (hasUnsavedChanges()) {
|
||||||
if (currentTab && currentTab.isTemporary) {
|
if (currentTab && currentTab.isTemporary) {
|
||||||
return I18n.tr("Unsaved note...")
|
return I18n.tr("Unsaved note...");
|
||||||
} else {
|
} else {
|
||||||
return I18n.tr("Unsaved changes")
|
return I18n.tr("Unsaved changes");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return I18n.tr("Saved")
|
return I18n.tr("Saved");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: {
|
color: {
|
||||||
if (autoSaveTimer.running) {
|
if (autoSaveTimer.running) {
|
||||||
return Theme.primary
|
return Theme.primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasUnsavedChanges()) {
|
if (hasUnsavedChanges()) {
|
||||||
return Theme.warning
|
return Theme.warning;
|
||||||
} else {
|
} else {
|
||||||
return Theme.success
|
return Theme.success;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
opacity: textArea.text.length > 0 ? 1.0 : 0.0
|
opacity: textArea.text.length > 0 ? 1.0 : 0.0
|
||||||
@@ -613,7 +873,7 @@ Column {
|
|||||||
interval: 2000
|
interval: 2000
|
||||||
repeat: false
|
repeat: false
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
autoSaveToSession()
|
autoSaveToSession();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,61 @@ Popup {
|
|||||||
id: processContextMenu
|
id: processContextMenu
|
||||||
|
|
||||||
property var processData: null
|
property var processData: null
|
||||||
|
property int selectedIndex: -1
|
||||||
|
property bool keyboardNavigation: false
|
||||||
|
property var parentFocusItem: null
|
||||||
|
|
||||||
function show(x, y) {
|
signal menuClosed
|
||||||
|
signal processKilled
|
||||||
|
|
||||||
|
readonly property var menuItems: [
|
||||||
|
{
|
||||||
|
text: I18n.tr("Copy PID"),
|
||||||
|
icon: "tag",
|
||||||
|
action: copyPid,
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: I18n.tr("Copy Name"),
|
||||||
|
icon: "content_copy",
|
||||||
|
action: copyName,
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: I18n.tr("Copy Full Command"),
|
||||||
|
icon: "code",
|
||||||
|
action: copyFullCommand,
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "separator"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: I18n.tr("Kill Process"),
|
||||||
|
icon: "close",
|
||||||
|
action: killProcess,
|
||||||
|
enabled: true,
|
||||||
|
dangerous: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: I18n.tr("Force Kill (SIGKILL)"),
|
||||||
|
icon: "dangerous",
|
||||||
|
action: forceKillProcess,
|
||||||
|
enabled: processData && processData.pid > 1000,
|
||||||
|
dangerous: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
readonly property int visibleItemCount: {
|
||||||
|
let count = 0;
|
||||||
|
for (let i = 0; i < menuItems.length; i++) {
|
||||||
|
if (menuItems[i].type !== "separator")
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
function show(x, y, fromKeyboard) {
|
||||||
let finalX = x;
|
let finalX = x;
|
||||||
let finalY = y;
|
let finalY = y;
|
||||||
|
|
||||||
@@ -27,17 +80,98 @@ Popup {
|
|||||||
|
|
||||||
processContextMenu.x = finalX;
|
processContextMenu.x = finalX;
|
||||||
processContextMenu.y = finalY;
|
processContextMenu.y = finalY;
|
||||||
|
keyboardNavigation = fromKeyboard || false;
|
||||||
|
selectedIndex = fromKeyboard ? 0 : -1;
|
||||||
open();
|
open();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function selectNext() {
|
||||||
|
if (visibleItemCount === 0)
|
||||||
|
return;
|
||||||
|
let current = selectedIndex;
|
||||||
|
let next = current;
|
||||||
|
do {
|
||||||
|
next = (next + 1) % menuItems.length;
|
||||||
|
} while (menuItems[next].type === "separator" && next !== current)
|
||||||
|
selectedIndex = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectPrevious() {
|
||||||
|
if (visibleItemCount === 0)
|
||||||
|
return;
|
||||||
|
let current = selectedIndex;
|
||||||
|
let prev = current;
|
||||||
|
do {
|
||||||
|
prev = (prev - 1 + menuItems.length) % menuItems.length;
|
||||||
|
} while (menuItems[prev].type === "separator" && prev !== current)
|
||||||
|
selectedIndex = prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
function activateSelected() {
|
||||||
|
if (selectedIndex < 0 || selectedIndex >= menuItems.length)
|
||||||
|
return;
|
||||||
|
const item = menuItems[selectedIndex];
|
||||||
|
if (item.type === "separator" || !item.enabled)
|
||||||
|
return;
|
||||||
|
item.action();
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyPid() {
|
||||||
|
if (processData)
|
||||||
|
Quickshell.execDetached(["dms", "cl", "copy", processData.pid.toString()]);
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyName() {
|
||||||
|
if (processData) {
|
||||||
|
const name = processData.command || "";
|
||||||
|
Quickshell.execDetached(["dms", "cl", "copy", name]);
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyFullCommand() {
|
||||||
|
if (processData) {
|
||||||
|
const fullCmd = processData.fullCommand || processData.command || "";
|
||||||
|
Quickshell.execDetached(["dms", "cl", "copy", fullCmd]);
|
||||||
|
}
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function killProcess() {
|
||||||
|
if (processData)
|
||||||
|
Quickshell.execDetached(["kill", processData.pid.toString()]);
|
||||||
|
processKilled();
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
|
function forceKillProcess() {
|
||||||
|
if (processData)
|
||||||
|
Quickshell.execDetached(["kill", "-9", processData.pid.toString()]);
|
||||||
|
processKilled();
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
|
||||||
width: 200
|
width: 200
|
||||||
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||||
padding: 0
|
padding: 0
|
||||||
modal: false
|
modal: false
|
||||||
closePolicy: Popup.CloseOnEscape
|
closePolicy: Popup.CloseOnEscape
|
||||||
|
|
||||||
onClosed: closePolicy = Popup.CloseOnEscape
|
onClosed: {
|
||||||
onOpened: outsideClickTimer.start()
|
closePolicy = Popup.CloseOnEscape;
|
||||||
|
keyboardNavigation = false;
|
||||||
|
selectedIndex = -1;
|
||||||
|
menuClosed();
|
||||||
|
if (parentFocusItem)
|
||||||
|
Qt.callLater(() => parentFocusItem.forceActiveFocus());
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpened: {
|
||||||
|
outsideClickTimer.start();
|
||||||
|
if (keyboardNavigation)
|
||||||
|
Qt.callLater(() => keyboardHandler.forceActiveFocus());
|
||||||
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: outsideClickTimer
|
id: outsideClickTimer
|
||||||
@@ -55,142 +189,145 @@ Popup {
|
|||||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.08)
|
||||||
border.width: 1
|
border.width: 1
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: keyboardHandler
|
||||||
|
anchors.fill: parent
|
||||||
|
focus: keyboardNavigation
|
||||||
|
|
||||||
|
Keys.onPressed: event => {
|
||||||
|
switch (event.key) {
|
||||||
|
case Qt.Key_Down:
|
||||||
|
case Qt.Key_J:
|
||||||
|
keyboardNavigation = true;
|
||||||
|
selectNext();
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
|
case Qt.Key_Up:
|
||||||
|
case Qt.Key_K:
|
||||||
|
keyboardNavigation = true;
|
||||||
|
selectPrevious();
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
|
case Qt.Key_Return:
|
||||||
|
case Qt.Key_Enter:
|
||||||
|
case Qt.Key_Space:
|
||||||
|
activateSelected();
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
|
case Qt.Key_Escape:
|
||||||
|
case Qt.Key_Left:
|
||||||
|
case Qt.Key_H:
|
||||||
|
close();
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: menuColumn
|
id: menuColumn
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Theme.spacingS
|
anchors.margins: Theme.spacingS
|
||||||
spacing: 1
|
spacing: 1
|
||||||
|
|
||||||
MenuItem {
|
Repeater {
|
||||||
text: I18n.tr("Copy PID")
|
model: menuItems
|
||||||
iconName: "tag"
|
|
||||||
onClicked: {
|
|
||||||
if (processContextMenu.processData)
|
|
||||||
Quickshell.execDetached(["dms", "cl", "copy", processContextMenu.processData.pid.toString()]);
|
|
||||||
processContextMenu.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItem {
|
Item {
|
||||||
text: I18n.tr("Copy Name")
|
width: parent.width
|
||||||
iconName: "content_copy"
|
height: modelData.type === "separator" ? 5 : 32
|
||||||
onClicked: {
|
visible: modelData.type !== "separator" || index > 0
|
||||||
if (processContextMenu.processData) {
|
|
||||||
const name = processContextMenu.processData.command || "";
|
property int itemVisibleIndex: {
|
||||||
Quickshell.execDetached(["dms", "cl", "copy", name]);
|
let count = 0;
|
||||||
|
for (let i = 0; i < index; i++) {
|
||||||
|
if (menuItems[i].type !== "separator")
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
}
|
}
|
||||||
processContextMenu.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItem {
|
Rectangle {
|
||||||
text: I18n.tr("Copy Full Command")
|
visible: modelData.type === "separator"
|
||||||
iconName: "code"
|
width: parent.width - Theme.spacingS * 2
|
||||||
onClicked: {
|
height: 1
|
||||||
if (processContextMenu.processData) {
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
const fullCmd = processContextMenu.processData.fullCommand || processContextMenu.processData.command || "";
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
Quickshell.execDetached(["dms", "cl", "copy", fullCmd]);
|
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.15)
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: menuItem
|
||||||
|
visible: modelData.type !== "separator"
|
||||||
|
width: parent.width
|
||||||
|
height: 32
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: {
|
||||||
|
if (!modelData.enabled)
|
||||||
|
return "transparent";
|
||||||
|
const isSelected = keyboardNavigation && selectedIndex === index;
|
||||||
|
if (modelData.dangerous) {
|
||||||
|
if (isSelected)
|
||||||
|
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.2);
|
||||||
|
return menuItemArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent";
|
||||||
|
}
|
||||||
|
if (isSelected)
|
||||||
|
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2);
|
||||||
|
return menuItemArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent";
|
||||||
|
}
|
||||||
|
opacity: modelData.enabled ? 1 : 0.5
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingS
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
name: modelData.icon || ""
|
||||||
|
size: 16
|
||||||
|
color: {
|
||||||
|
if (!modelData.enabled)
|
||||||
|
return Theme.surfaceVariantText;
|
||||||
|
const isSelected = keyboardNavigation && selectedIndex === index;
|
||||||
|
if (modelData.dangerous && (menuItemArea.containsMouse || isSelected))
|
||||||
|
return Theme.error;
|
||||||
|
return Theme.surfaceText;
|
||||||
|
}
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.text || ""
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.Normal
|
||||||
|
color: {
|
||||||
|
if (!modelData.enabled)
|
||||||
|
return Theme.surfaceVariantText;
|
||||||
|
const isSelected = keyboardNavigation && selectedIndex === index;
|
||||||
|
if (modelData.dangerous && (menuItemArea.containsMouse || isSelected))
|
||||||
|
return Theme.error;
|
||||||
|
return Theme.surfaceText;
|
||||||
|
}
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: menuItemArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: modelData.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
enabled: modelData.enabled
|
||||||
|
onEntered: {
|
||||||
|
keyboardNavigation = false;
|
||||||
|
selectedIndex = index;
|
||||||
|
}
|
||||||
|
onClicked: modelData.action()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
processContextMenu.close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: parent.width - Theme.spacingS * 2
|
|
||||||
height: 1
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.15)
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
text: I18n.tr("Kill Process")
|
|
||||||
iconName: "close"
|
|
||||||
dangerous: true
|
|
||||||
enabled: processContextMenu.processData
|
|
||||||
onClicked: {
|
|
||||||
if (processContextMenu.processData)
|
|
||||||
Quickshell.execDetached(["kill", processContextMenu.processData.pid.toString()]);
|
|
||||||
processContextMenu.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
text: I18n.tr("Force Kill (SIGKILL)")
|
|
||||||
iconName: "dangerous"
|
|
||||||
dangerous: true
|
|
||||||
enabled: processContextMenu.processData && processContextMenu.processData.pid > 1000
|
|
||||||
onClicked: {
|
|
||||||
if (processContextMenu.processData)
|
|
||||||
Quickshell.execDetached(["kill", "-9", processContextMenu.processData.pid.toString()]);
|
|
||||||
processContextMenu.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
component MenuItem: Rectangle {
|
|
||||||
id: menuItem
|
|
||||||
|
|
||||||
property string text: ""
|
|
||||||
property string iconName: ""
|
|
||||||
property bool dangerous: false
|
|
||||||
property bool enabled: true
|
|
||||||
|
|
||||||
signal clicked
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
height: 32
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: {
|
|
||||||
if (!enabled)
|
|
||||||
return "transparent";
|
|
||||||
if (dangerous)
|
|
||||||
return menuItemArea.containsMouse ? Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) : "transparent";
|
|
||||||
return menuItemArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent";
|
|
||||||
}
|
|
||||||
opacity: enabled ? 1 : 0.5
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.leftMargin: Theme.spacingS
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
DankIcon {
|
|
||||||
name: menuItem.iconName
|
|
||||||
size: 16
|
|
||||||
color: {
|
|
||||||
if (!menuItem.enabled)
|
|
||||||
return Theme.surfaceVariantText;
|
|
||||||
if (menuItem.dangerous && menuItemArea.containsMouse)
|
|
||||||
return Theme.error;
|
|
||||||
return Theme.surfaceText;
|
|
||||||
}
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: menuItem.text
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
font.weight: Font.Normal
|
|
||||||
color: {
|
|
||||||
if (!menuItem.enabled)
|
|
||||||
return Theme.surfaceVariantText;
|
|
||||||
if (menuItem.dangerous && menuItemArea.containsMouse)
|
|
||||||
return Theme.error;
|
|
||||||
return Theme.surfaceText;
|
|
||||||
}
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: menuItemArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: menuItem.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
|
||||||
enabled: menuItem.enabled
|
|
||||||
onClicked: menuItem.clicked()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ DankPopout {
|
|||||||
if (!shouldBeVisible) {
|
if (!shouldBeVisible) {
|
||||||
searchText = "";
|
searchText = "";
|
||||||
expandedPid = "";
|
expandedPid = "";
|
||||||
|
if (processesView)
|
||||||
|
processesView.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,9 +71,13 @@ DankPopout {
|
|||||||
if (processListPopout.shouldBeVisible)
|
if (processListPopout.shouldBeVisible)
|
||||||
forceActiveFocus();
|
forceActiveFocus();
|
||||||
processContextMenu.parent = processListContent;
|
processContextMenu.parent = processListContent;
|
||||||
|
processContextMenu.parentFocusItem = processListContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
Keys.onPressed: event => {
|
Keys.onPressed: event => {
|
||||||
|
if (processContextMenu.visible)
|
||||||
|
return;
|
||||||
|
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case Qt.Key_Escape:
|
case Qt.Key_Escape:
|
||||||
if (processListPopout.searchText.length > 0) {
|
if (processListPopout.searchText.length > 0) {
|
||||||
@@ -79,6 +85,11 @@ DankPopout {
|
|||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (processesView.keyboardNavigationActive) {
|
||||||
|
processesView.reset();
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
processListPopout.close();
|
processListPopout.close();
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
return;
|
return;
|
||||||
@@ -90,14 +101,15 @@ DankPopout {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
processesView.handleKey(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: processListPopout
|
target: processListPopout
|
||||||
function onShouldBeVisibleChanged() {
|
function onShouldBeVisibleChanged() {
|
||||||
if (processListPopout.shouldBeVisible) {
|
if (processListPopout.shouldBeVisible)
|
||||||
Qt.callLater(() => processListContent.forceActiveFocus());
|
Qt.callLater(() => processListContent.forceActiveFocus());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,6 +154,8 @@ DankPopout {
|
|||||||
showClearButton: true
|
showClearButton: true
|
||||||
text: processListPopout.searchText
|
text: processListPopout.searchText
|
||||||
onTextChanged: processListPopout.searchText = text
|
onTextChanged: processListPopout.searchText = text
|
||||||
|
ignoreUpDownKeys: true
|
||||||
|
keyForwardTargets: [processListContent]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,6 +328,7 @@ DankPopout {
|
|||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
ProcessesView {
|
ProcessesView {
|
||||||
|
id: processesView
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Theme.spacingS
|
anchors.margins: Theme.spacingS
|
||||||
searchText: processListPopout.searchText
|
searchText: processListPopout.searchText
|
||||||
|
|||||||
@@ -11,18 +11,27 @@ Item {
|
|||||||
property string searchText: ""
|
property string searchText: ""
|
||||||
property string expandedPid: ""
|
property string expandedPid: ""
|
||||||
property var contextMenu: null
|
property var contextMenu: null
|
||||||
property bool hoveringExpandedItem: false
|
|
||||||
|
|
||||||
readonly property bool pauseUpdates: hoveringExpandedItem || (contextMenu?.visible ?? false)
|
property int selectedIndex: -1
|
||||||
|
property bool keyboardNavigationActive: false
|
||||||
|
property int forceRefreshCount: 0
|
||||||
|
|
||||||
|
readonly property bool pauseUpdates: (contextMenu?.visible ?? false) || expandedPid.length > 0
|
||||||
|
readonly property bool shouldUpdate: !pauseUpdates || forceRefreshCount > 0
|
||||||
property var cachedProcesses: []
|
property var cachedProcesses: []
|
||||||
|
|
||||||
|
signal openContextMenuRequested(int index, real x, real y, bool fromKeyboard)
|
||||||
|
|
||||||
onFilteredProcessesChanged: {
|
onFilteredProcessesChanged: {
|
||||||
if (!pauseUpdates)
|
if (!shouldUpdate)
|
||||||
cachedProcesses = filteredProcesses;
|
return;
|
||||||
|
cachedProcesses = filteredProcesses;
|
||||||
|
if (forceRefreshCount > 0)
|
||||||
|
forceRefreshCount--;
|
||||||
}
|
}
|
||||||
|
|
||||||
onPauseUpdatesChanged: {
|
onShouldUpdateChanged: {
|
||||||
if (!pauseUpdates)
|
if (shouldUpdate)
|
||||||
cachedProcesses = filteredProcesses;
|
cachedProcesses = filteredProcesses;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +84,135 @@ Item {
|
|||||||
return procs;
|
return procs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function selectNext() {
|
||||||
|
if (cachedProcesses.length === 0)
|
||||||
|
return;
|
||||||
|
keyboardNavigationActive = true;
|
||||||
|
selectedIndex = Math.min(selectedIndex + 1, cachedProcesses.length - 1);
|
||||||
|
ensureVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectPrevious() {
|
||||||
|
if (cachedProcesses.length === 0)
|
||||||
|
return;
|
||||||
|
keyboardNavigationActive = true;
|
||||||
|
if (selectedIndex <= 0) {
|
||||||
|
selectedIndex = -1;
|
||||||
|
keyboardNavigationActive = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
selectedIndex = selectedIndex - 1;
|
||||||
|
ensureVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectFirst() {
|
||||||
|
if (cachedProcesses.length === 0)
|
||||||
|
return;
|
||||||
|
keyboardNavigationActive = true;
|
||||||
|
selectedIndex = 0;
|
||||||
|
ensureVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectLast() {
|
||||||
|
if (cachedProcesses.length === 0)
|
||||||
|
return;
|
||||||
|
keyboardNavigationActive = true;
|
||||||
|
selectedIndex = cachedProcesses.length - 1;
|
||||||
|
ensureVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleExpand() {
|
||||||
|
if (selectedIndex < 0 || selectedIndex >= cachedProcesses.length)
|
||||||
|
return;
|
||||||
|
const process = cachedProcesses[selectedIndex];
|
||||||
|
const pidStr = (process?.pid ?? -1).toString();
|
||||||
|
expandedPid = (expandedPid === pidStr) ? "" : pidStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openContextMenu() {
|
||||||
|
if (selectedIndex < 0 || selectedIndex >= cachedProcesses.length)
|
||||||
|
return;
|
||||||
|
const delegate = processListView.itemAtIndex(selectedIndex);
|
||||||
|
if (!delegate)
|
||||||
|
return;
|
||||||
|
const process = cachedProcesses[selectedIndex];
|
||||||
|
if (!process || !contextMenu)
|
||||||
|
return;
|
||||||
|
contextMenu.processData = process;
|
||||||
|
const itemPos = delegate.mapToItem(contextMenu.parent, delegate.width / 2, delegate.height / 2);
|
||||||
|
contextMenu.parentFocusItem = root;
|
||||||
|
contextMenu.show(itemPos.x, itemPos.y, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
selectedIndex = -1;
|
||||||
|
keyboardNavigationActive = false;
|
||||||
|
expandedPid = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function forceRefresh(count) {
|
||||||
|
forceRefreshCount = count || 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureVisible() {
|
||||||
|
if (selectedIndex < 0)
|
||||||
|
return;
|
||||||
|
processListView.positionViewAtIndex(selectedIndex, ListView.Contain);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKey(event) {
|
||||||
|
switch (event.key) {
|
||||||
|
case Qt.Key_Down:
|
||||||
|
selectNext();
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
|
case Qt.Key_Up:
|
||||||
|
selectPrevious();
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
|
case Qt.Key_J:
|
||||||
|
if (event.modifiers & Qt.ControlModifier) {
|
||||||
|
selectNext();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case Qt.Key_K:
|
||||||
|
if (event.modifiers & Qt.ControlModifier) {
|
||||||
|
selectPrevious();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case Qt.Key_Home:
|
||||||
|
selectFirst();
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
|
case Qt.Key_End:
|
||||||
|
selectLast();
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
|
case Qt.Key_Space:
|
||||||
|
if (keyboardNavigationActive) {
|
||||||
|
toggleExpand();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case Qt.Key_Return:
|
||||||
|
case Qt.Key_Enter:
|
||||||
|
if (keyboardNavigationActive) {
|
||||||
|
toggleExpand();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case Qt.Key_Menu:
|
||||||
|
case Qt.Key_F10:
|
||||||
|
if (keyboardNavigationActive && selectedIndex >= 0) {
|
||||||
|
openContextMenu();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
DgopService.addRef(["processes", "cpu", "memory", "system"]);
|
DgopService.addRef(["processes", "cpu", "memory", "system"]);
|
||||||
cachedProcesses = filteredProcesses;
|
cachedProcesses = filteredProcesses;
|
||||||
@@ -163,22 +301,28 @@ Item {
|
|||||||
|
|
||||||
delegate: ProcessItem {
|
delegate: ProcessItem {
|
||||||
required property var modelData
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
|
||||||
width: processListView.width
|
width: processListView.width
|
||||||
process: modelData
|
process: modelData
|
||||||
isExpanded: root.expandedPid === (modelData?.pid ?? -1).toString()
|
isExpanded: root.expandedPid === (modelData?.pid ?? -1).toString()
|
||||||
|
isSelected: root.keyboardNavigationActive && root.selectedIndex === index
|
||||||
contextMenu: root.contextMenu
|
contextMenu: root.contextMenu
|
||||||
onToggleExpand: {
|
onToggleExpand: {
|
||||||
const pidStr = (modelData?.pid ?? -1).toString();
|
const pidStr = (modelData?.pid ?? -1).toString();
|
||||||
root.expandedPid = (root.expandedPid === pidStr) ? "" : pidStr;
|
root.expandedPid = (root.expandedPid === pidStr) ? "" : pidStr;
|
||||||
}
|
}
|
||||||
onHoveringExpandedChanged: {
|
onClicked: {
|
||||||
if (hoveringExpanded)
|
root.keyboardNavigationActive = true;
|
||||||
root.hoveringExpandedItem = true;
|
root.selectedIndex = index;
|
||||||
else
|
}
|
||||||
Qt.callLater(() => {
|
onContextMenuRequested: (mouseX, mouseY) => {
|
||||||
root.hoveringExpandedItem = false;
|
if (root.contextMenu) {
|
||||||
});
|
root.contextMenu.processData = modelData;
|
||||||
|
root.contextMenu.parentFocusItem = root;
|
||||||
|
const globalPos = mapToItem(root.contextMenu.parent, mouseX, mouseY);
|
||||||
|
root.contextMenu.show(globalPos.x, globalPos.y, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,10 +432,12 @@ Item {
|
|||||||
|
|
||||||
property var process: null
|
property var process: null
|
||||||
property bool isExpanded: false
|
property bool isExpanded: false
|
||||||
|
property bool isSelected: false
|
||||||
property var contextMenu: null
|
property var contextMenu: null
|
||||||
readonly property bool hoveringExpanded: (isExpanded && processMouseArea.containsMouse) || copyMouseArea.containsMouse
|
|
||||||
|
|
||||||
signal toggleExpand
|
signal toggleExpand
|
||||||
|
signal clicked
|
||||||
|
signal contextMenuRequested(real mouseX, real mouseY)
|
||||||
|
|
||||||
readonly property int processPid: process?.pid ?? 0
|
readonly property int processPid: process?.pid ?? 0
|
||||||
readonly property real processCpu: process?.cpu ?? 0
|
readonly property real processCpu: process?.cpu ?? 0
|
||||||
@@ -301,8 +447,16 @@ Item {
|
|||||||
|
|
||||||
height: isExpanded ? (44 + expandedRect.height + Theme.spacingXS) : 44
|
height: isExpanded ? (44 + expandedRect.height + Theme.spacingXS) : 44
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06) : "transparent"
|
color: {
|
||||||
border.color: processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
if (isSelected)
|
||||||
|
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.15);
|
||||||
|
return processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06) : "transparent";
|
||||||
|
}
|
||||||
|
border.color: {
|
||||||
|
if (isSelected)
|
||||||
|
return Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.3);
|
||||||
|
return processMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent";
|
||||||
|
}
|
||||||
border.width: 1
|
border.width: 1
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
@@ -327,14 +481,10 @@ Item {
|
|||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
onClicked: mouse => {
|
onClicked: mouse => {
|
||||||
if (mouse.button === Qt.RightButton) {
|
if (mouse.button === Qt.RightButton) {
|
||||||
if (processItemRoot.processPid > 0 && processItemRoot.contextMenu) {
|
processItemRoot.contextMenuRequested(mouse.x, mouse.y);
|
||||||
processItemRoot.contextMenu.processData = processItemRoot.process;
|
|
||||||
const globalPos = processMouseArea.mapToGlobal(mouse.x, mouse.y);
|
|
||||||
const localPos = processItemRoot.contextMenu.parent ? processItemRoot.contextMenu.parent.mapFromGlobal(globalPos.x, globalPos.y) : globalPos;
|
|
||||||
processItemRoot.contextMenu.show(localPos.x, localPos.y);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
processItemRoot.clicked();
|
||||||
processItemRoot.toggleExpand();
|
processItemRoot.toggleExpand();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -462,6 +462,250 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
width: parent.width
|
||||||
|
iconName: "search"
|
||||||
|
title: I18n.tr("Search Options")
|
||||||
|
settingKey: "searchOptions"
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
settingKey: "searchAppActions"
|
||||||
|
tags: ["launcher", "search", "actions", "shortcuts"]
|
||||||
|
text: I18n.tr("Search App Actions")
|
||||||
|
description: I18n.tr("Include desktop actions (shortcuts) in search results.")
|
||||||
|
checked: SessionData.searchAppActions
|
||||||
|
onToggled: checked => SessionData.setSearchAppActions(checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
id: hiddenAppsCard
|
||||||
|
width: parent.width
|
||||||
|
iconName: "visibility_off"
|
||||||
|
title: I18n.tr("Hidden Apps")
|
||||||
|
settingKey: "hiddenApps"
|
||||||
|
|
||||||
|
property var hiddenAppsModel: {
|
||||||
|
SessionData.hiddenApps;
|
||||||
|
const apps = [];
|
||||||
|
const allApps = AppSearchService.applications || [];
|
||||||
|
for (const hiddenId of SessionData.hiddenApps) {
|
||||||
|
const app = allApps.find(a => (a.id || a.execString || a.exec) === hiddenId);
|
||||||
|
if (app) {
|
||||||
|
apps.push({
|
||||||
|
id: hiddenId,
|
||||||
|
name: app.name || hiddenId,
|
||||||
|
icon: app.icon || "",
|
||||||
|
comment: app.comment || ""
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
apps.push({
|
||||||
|
id: hiddenId,
|
||||||
|
name: hiddenId,
|
||||||
|
icon: "",
|
||||||
|
comment: ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return apps.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: parent.width
|
||||||
|
text: I18n.tr("Hidden apps won't appear in the launcher. Right-click an app and select 'Hide App' to hide it.")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: hiddenAppsList
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: hiddenAppsCard.hiddenAppsModel
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
width: hiddenAppsList.width
|
||||||
|
height: 48
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.3)
|
||||||
|
border.width: 0
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Image {
|
||||||
|
width: 24
|
||||||
|
height: 24
|
||||||
|
source: modelData.icon ? "image://icon/" + modelData.icon : "image://icon/application-x-executable"
|
||||||
|
sourceSize.width: 24
|
||||||
|
sourceSize.height: 24
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
onStatusChanged: {
|
||||||
|
if (status === Image.Error)
|
||||||
|
source = "image://icon/application-x-executable";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.name
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.comment || modelData.id
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
visible: text.length > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
iconName: "visibility"
|
||||||
|
iconSize: 18
|
||||||
|
iconColor: Theme.primary
|
||||||
|
onClicked: SessionData.showApp(modelData.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: parent.width
|
||||||
|
text: I18n.tr("No hidden apps.")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
visible: hiddenAppsCard.hiddenAppsModel.length === 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsCard {
|
||||||
|
id: appOverridesCard
|
||||||
|
width: parent.width
|
||||||
|
iconName: "edit"
|
||||||
|
title: I18n.tr("App Customizations")
|
||||||
|
settingKey: "appOverrides"
|
||||||
|
|
||||||
|
property var overridesModel: {
|
||||||
|
SessionData.appOverrides;
|
||||||
|
const items = [];
|
||||||
|
const allApps = AppSearchService.applications || [];
|
||||||
|
for (const appId in SessionData.appOverrides) {
|
||||||
|
const override = SessionData.appOverrides[appId];
|
||||||
|
const app = allApps.find(a => (a.id || a.execString || a.exec) === appId);
|
||||||
|
items.push({
|
||||||
|
id: appId,
|
||||||
|
name: override.name || app?.name || appId,
|
||||||
|
originalName: app?.name || appId,
|
||||||
|
icon: override.icon || app?.icon || "",
|
||||||
|
hasOverride: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return items.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: parent.width
|
||||||
|
text: I18n.tr("Apps with custom display name, icon, or launch options. Right-click an app and select 'Edit App' to customize.")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: overridesList
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: appOverridesCard.overridesModel
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
width: overridesList.width
|
||||||
|
height: 48
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.3)
|
||||||
|
border.width: 0
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Image {
|
||||||
|
width: 24
|
||||||
|
height: 24
|
||||||
|
source: modelData.icon ? "image://icon/" + modelData.icon : "image://icon/application-x-executable"
|
||||||
|
sourceSize.width: 24
|
||||||
|
sourceSize.height: 24
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
onStatusChanged: {
|
||||||
|
if (status === Image.Error)
|
||||||
|
source = "image://icon/application-x-executable";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: 2
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.name
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: modelData.originalName !== modelData.name ? modelData.originalName : modelData.id
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: Theme.spacingM
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
iconName: "delete"
|
||||||
|
iconSize: 18
|
||||||
|
iconColor: Theme.error
|
||||||
|
onClicked: SessionData.clearAppOverride(modelData.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
width: parent.width
|
||||||
|
text: I18n.tr("No app customizations.")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceVariantText
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
visible: appOverridesCard.overridesModel.length === 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
id: recentAppsCard
|
id: recentAppsCard
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ Item {
|
|||||||
|
|
||||||
SettingsButtonGroupRow {
|
SettingsButtonGroupRow {
|
||||||
text: I18n.tr("Occupied Color")
|
text: I18n.tr("Occupied Color")
|
||||||
model: ["sec", "s", "sc", "sch", "none"]
|
model: ["none", "sec", "s", "sc", "sch", "schh"]
|
||||||
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl
|
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl
|
||||||
buttonHeight: 22
|
buttonHeight: 22
|
||||||
minButtonWidth: 36
|
minButtonWidth: 36
|
||||||
@@ -210,15 +210,17 @@ Item {
|
|||||||
textSize: Theme.fontSizeSmall - 1
|
textSize: Theme.fontSizeSmall - 1
|
||||||
spacing: 1
|
spacing: 1
|
||||||
currentIndex: {
|
currentIndex: {
|
||||||
switch (SettingsData.wokspaceColorMode) {
|
switch (SettingsData.workspaceOccupiedColorMode) {
|
||||||
case "s":
|
case "sec":
|
||||||
return 1;
|
return 1;
|
||||||
case "sc":
|
case "s":
|
||||||
return 2;
|
return 2;
|
||||||
case "sch":
|
case "sc":
|
||||||
return 3;
|
return 3;
|
||||||
case "none":
|
case "sch":
|
||||||
return 4;
|
return 4;
|
||||||
|
case "schh":
|
||||||
|
return 5;
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -226,7 +228,7 @@ Item {
|
|||||||
onSelectionChanged: (index, selected) => {
|
onSelectionChanged: (index, selected) => {
|
||||||
if (!selected)
|
if (!selected)
|
||||||
return;
|
return;
|
||||||
const modes = ["default", "s", "sc", "sch", "none"];
|
const modes = ["none", "sec", "s", "sc", "sch", "schh"];
|
||||||
SettingsData.set("workspaceOccupiedColorMode", modes[index]);
|
SettingsData.set("workspaceOccupiedColorMode", modes[index]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ Singleton {
|
|||||||
|
|
||||||
property var applications: []
|
property var applications: []
|
||||||
property var _cachedCategories: null
|
property var _cachedCategories: null
|
||||||
|
property var _cachedVisibleApps: null
|
||||||
|
property var _hiddenAppsSet: new Set()
|
||||||
|
|
||||||
readonly property int maxResults: 10
|
readonly property int maxResults: 10
|
||||||
readonly property int frecencySampleSize: 10
|
readonly property int frecencySampleSize: 10
|
||||||
@@ -40,6 +42,51 @@ Singleton {
|
|||||||
function refreshApplications() {
|
function refreshApplications() {
|
||||||
applications = DesktopEntries.applications.values;
|
applications = DesktopEntries.applications.values;
|
||||||
_cachedCategories = null;
|
_cachedCategories = null;
|
||||||
|
_cachedVisibleApps = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _rebuildHiddenSet() {
|
||||||
|
_hiddenAppsSet = new Set(SessionData.hiddenApps || []);
|
||||||
|
_cachedVisibleApps = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAppHidden(app) {
|
||||||
|
if (!app)
|
||||||
|
return false;
|
||||||
|
const appId = app.id || app.execString || app.exec || "";
|
||||||
|
return _hiddenAppsSet.has(appId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVisibleApplications() {
|
||||||
|
if (_cachedVisibleApps === null) {
|
||||||
|
_cachedVisibleApps = applications.filter(app => !isAppHidden(app));
|
||||||
|
}
|
||||||
|
return _cachedVisibleApps.map(app => applyAppOverride(app));
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: SessionData
|
||||||
|
function onHiddenAppsChanged() {
|
||||||
|
root._rebuildHiddenSet();
|
||||||
|
}
|
||||||
|
function onAppOverridesChanged() {
|
||||||
|
root._cachedVisibleApps = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyAppOverride(app) {
|
||||||
|
if (!app)
|
||||||
|
return app;
|
||||||
|
const appId = app.id || app.execString || app.exec || "";
|
||||||
|
const override = SessionData.getAppOverride(appId);
|
||||||
|
if (!override)
|
||||||
|
return app;
|
||||||
|
return Object.assign({}, app, {
|
||||||
|
name: override.name || app.name,
|
||||||
|
icon: override.icon || app.icon,
|
||||||
|
comment: override.comment || app.comment,
|
||||||
|
_override: override
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property string dmsLogoPath: Qt.resolvedUrl("../assets/danklogo2.svg")
|
readonly property string dmsLogoPath: Qt.resolvedUrl("../assets/danklogo2.svg")
|
||||||
@@ -226,7 +273,10 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: refreshApplications()
|
Component.onCompleted: {
|
||||||
|
_rebuildHiddenSet();
|
||||||
|
refreshApplications();
|
||||||
|
}
|
||||||
|
|
||||||
function tokenize(text) {
|
function tokenize(text) {
|
||||||
return text.toLowerCase().trim().split(/[\s\-_]+/).filter(w => w.length > 0);
|
return text.toLowerCase().trim().split(/[\s\-_]+/).filter(w => w.length > 0);
|
||||||
@@ -345,17 +395,17 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function searchApplications(query) {
|
function searchApplications(query) {
|
||||||
if (!query || query.length === 0) {
|
if (!query || query.length === 0)
|
||||||
return applications;
|
return getVisibleApplications();
|
||||||
}
|
|
||||||
if (applications.length === 0)
|
if (applications.length === 0)
|
||||||
return [];
|
return [];
|
||||||
|
|
||||||
const queryLower = query.toLowerCase().trim();
|
const queryLower = query.toLowerCase().trim();
|
||||||
const scoredApps = [];
|
const scoredApps = [];
|
||||||
const results = [];
|
const results = [];
|
||||||
|
const visibleApps = getVisibleApplications();
|
||||||
|
|
||||||
for (const app of applications) {
|
for (const app of visibleApps) {
|
||||||
const name = (app.name || "").toLowerCase();
|
const name = (app.name || "").toLowerCase();
|
||||||
const genericName = (app.genericName || "").toLowerCase();
|
const genericName = (app.genericName || "").toLowerCase();
|
||||||
const comment = (app.comment || "").toLowerCase();
|
const comment = (app.comment || "").toLowerCase();
|
||||||
@@ -440,10 +490,58 @@ Singleton {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (SessionData.searchAppActions) {
|
||||||
|
const actionResults = searchAppActions(queryLower, visibleApps);
|
||||||
|
for (const actionResult of actionResults) {
|
||||||
|
scoredApps.push({
|
||||||
|
app: actionResult.app,
|
||||||
|
score: actionResult.score
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
scoredApps.sort((a, b) => b.score - a.score);
|
scoredApps.sort((a, b) => b.score - a.score);
|
||||||
return scoredApps.slice(0, maxResults).map(item => item.app);
|
return scoredApps.slice(0, maxResults).map(item => item.app);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function searchAppActions(query, apps) {
|
||||||
|
const results = [];
|
||||||
|
for (const app of apps) {
|
||||||
|
if (!app.actions || app.actions.length === 0)
|
||||||
|
continue;
|
||||||
|
for (const action of app.actions) {
|
||||||
|
const actionName = (action.name || "").toLowerCase();
|
||||||
|
if (!actionName)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
let score = 0;
|
||||||
|
if (actionName === query) {
|
||||||
|
score = 8000;
|
||||||
|
} else if (actionName.startsWith(query)) {
|
||||||
|
score = 4000;
|
||||||
|
} else if (actionName.includes(query)) {
|
||||||
|
score = 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (score > 0) {
|
||||||
|
results.push({
|
||||||
|
app: {
|
||||||
|
name: action.name,
|
||||||
|
icon: action.icon || app.icon,
|
||||||
|
comment: app.name,
|
||||||
|
categories: app.categories || [],
|
||||||
|
isAction: true,
|
||||||
|
parentApp: app,
|
||||||
|
actionData: action
|
||||||
|
},
|
||||||
|
score: score
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
function getCategoriesForApp(app) {
|
function getCategoriesForApp(app) {
|
||||||
if (!app?.categories)
|
if (!app?.categories)
|
||||||
return [];
|
return [];
|
||||||
@@ -525,17 +623,15 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getAppsInCategory(category) {
|
function getAppsInCategory(category) {
|
||||||
if (category === I18n.tr("All")) {
|
const visibleApps = getVisibleApplications();
|
||||||
return applications;
|
if (category === I18n.tr("All"))
|
||||||
}
|
return visibleApps;
|
||||||
|
|
||||||
// Check if it's a plugin category
|
|
||||||
const pluginItems = getPluginItems(category, "");
|
const pluginItems = getPluginItems(category, "");
|
||||||
if (pluginItems.length > 0) {
|
if (pluginItems.length > 0)
|
||||||
return pluginItems;
|
return pluginItems;
|
||||||
}
|
|
||||||
|
|
||||||
return applications.filter(app => {
|
return visibleApps.filter(app => {
|
||||||
const appCategories = getCategoriesForApp(app);
|
const appCategories = getCategoriesForApp(app);
|
||||||
return appCategories.includes(category);
|
return appCategories.includes(category);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -182,17 +182,44 @@ Singleton {
|
|||||||
return /[;&|<>()$`\\"']/.test(prefix);
|
return /[;&|<>()$`\\"']/.test(prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseEnvVars(envVarsStr) {
|
||||||
|
if (!envVarsStr || envVarsStr.trim().length === 0)
|
||||||
|
return {};
|
||||||
|
const envObj = {};
|
||||||
|
const pairs = envVarsStr.trim().split(/\s+/);
|
||||||
|
for (const pair of pairs) {
|
||||||
|
const eqIndex = pair.indexOf("=");
|
||||||
|
if (eqIndex > 0) {
|
||||||
|
const key = pair.substring(0, eqIndex);
|
||||||
|
const value = pair.substring(eqIndex + 1);
|
||||||
|
envObj[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return envObj;
|
||||||
|
}
|
||||||
|
|
||||||
function launchDesktopEntry(desktopEntry, useNvidia) {
|
function launchDesktopEntry(desktopEntry, useNvidia) {
|
||||||
let cmd = desktopEntry.command;
|
let cmd = desktopEntry.command;
|
||||||
if (useNvidia && nvidiaCommand)
|
if (useNvidia && nvidiaCommand)
|
||||||
cmd = [nvidiaCommand].concat(cmd);
|
cmd = [nvidiaCommand].concat(cmd);
|
||||||
|
|
||||||
|
const appId = desktopEntry.id || desktopEntry.execString || desktopEntry.exec || "";
|
||||||
|
const override = SessionData.getAppOverride(appId);
|
||||||
|
|
||||||
|
if (override?.extraFlags) {
|
||||||
|
const extraArgs = override.extraFlags.trim().split(/\s+/).filter(arg => arg.length > 0);
|
||||||
|
cmd = cmd.concat(extraArgs);
|
||||||
|
}
|
||||||
|
|
||||||
const userPrefix = SettingsData.launchPrefix?.trim() || "";
|
const userPrefix = SettingsData.launchPrefix?.trim() || "";
|
||||||
const defaultPrefix = Quickshell.env("DMS_DEFAULT_LAUNCH_PREFIX") || "";
|
const defaultPrefix = Quickshell.env("DMS_DEFAULT_LAUNCH_PREFIX") || "";
|
||||||
const prefix = userPrefix.length > 0 ? userPrefix : defaultPrefix;
|
const prefix = userPrefix.length > 0 ? userPrefix : defaultPrefix;
|
||||||
const workDir = desktopEntry.workingDirectory || Quickshell.env("HOME");
|
const workDir = desktopEntry.workingDirectory || Quickshell.env("HOME");
|
||||||
const cursorEnv = typeof SettingsData.getCursorEnvironment === "function" ? SettingsData.getCursorEnvironment() : {};
|
const cursorEnv = typeof SettingsData.getCursorEnvironment === "function" ? SettingsData.getCursorEnvironment() : {};
|
||||||
|
|
||||||
|
const overrideEnv = override?.envVars ? parseEnvVars(override.envVars) : {};
|
||||||
|
const finalEnv = Object.assign({}, cursorEnv, overrideEnv);
|
||||||
|
|
||||||
if (desktopEntry.runInTerminal) {
|
if (desktopEntry.runInTerminal) {
|
||||||
const terminal = Quickshell.env("TERMINAL") || "xterm";
|
const terminal = Quickshell.env("TERMINAL") || "xterm";
|
||||||
const escapedCmd = cmd.map(arg => escapeShellArg(arg)).join(" ");
|
const escapedCmd = cmd.map(arg => escapeShellArg(arg)).join(" ");
|
||||||
@@ -200,7 +227,7 @@ Singleton {
|
|||||||
Quickshell.execDetached({
|
Quickshell.execDetached({
|
||||||
command: [terminal, "-e", "sh", "-c", shellCmd],
|
command: [terminal, "-e", "sh", "-c", shellCmd],
|
||||||
workingDirectory: workDir,
|
workingDirectory: workDir,
|
||||||
environment: cursorEnv
|
environment: finalEnv
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -210,7 +237,7 @@ Singleton {
|
|||||||
Quickshell.execDetached({
|
Quickshell.execDetached({
|
||||||
command: ["sh", "-c", `${prefix} ${escapedCmd}`],
|
command: ["sh", "-c", `${prefix} ${escapedCmd}`],
|
||||||
workingDirectory: workDir,
|
workingDirectory: workDir,
|
||||||
environment: cursorEnv
|
environment: finalEnv
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -221,7 +248,7 @@ Singleton {
|
|||||||
Quickshell.execDetached({
|
Quickshell.execDetached({
|
||||||
command: cmd,
|
command: cmd,
|
||||||
workingDirectory: workDir,
|
workingDirectory: workDir,
|
||||||
environment: cursorEnv
|
environment: finalEnv
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ Item {
|
|||||||
property bool shouldBeVisible: false
|
property bool shouldBeVisible: false
|
||||||
property var customKeyboardFocus: null
|
property var customKeyboardFocus: null
|
||||||
property bool backgroundInteractive: true
|
property bool backgroundInteractive: true
|
||||||
|
property bool contentHandlesKeys: false
|
||||||
|
|
||||||
property real storedBarThickness: Theme.barHeight - 4
|
property real storedBarThickness: Theme.barHeight - 4
|
||||||
property real storedBarSpacing: 4
|
property real storedBarSpacing: 4
|
||||||
@@ -461,8 +462,12 @@ Item {
|
|||||||
id: focusHelper
|
id: focusHelper
|
||||||
parent: contentContainer
|
parent: contentContainer
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
focus: true
|
visible: !root.contentHandlesKeys
|
||||||
|
enabled: !root.contentHandlesKeys
|
||||||
|
focus: !root.contentHandlesKeys
|
||||||
Keys.onPressed: event => {
|
Keys.onPressed: event => {
|
||||||
|
if (root.contentHandlesKeys)
|
||||||
|
return;
|
||||||
if (event.key === Qt.Key_Escape) {
|
if (event.key === Qt.Key_Escape) {
|
||||||
close();
|
close();
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ StyledRect {
|
|||||||
property real topPadding: Theme.spacingM
|
property real topPadding: Theme.spacingM
|
||||||
property real bottomPadding: Theme.spacingM
|
property real bottomPadding: Theme.spacingM
|
||||||
property bool ignoreLeftRightKeys: false
|
property bool ignoreLeftRightKeys: false
|
||||||
|
property bool ignoreUpDownKeys: false
|
||||||
property bool ignoreTabKeys: false
|
property bool ignoreTabKeys: false
|
||||||
property var keyForwardTargets: []
|
property var keyForwardTargets: []
|
||||||
property Item keyNavigationTab: null
|
property Item keyNavigationTab: null
|
||||||
@@ -145,9 +146,16 @@ StyledRect {
|
|||||||
if (root.ignoreTabKeys && (event.key === Qt.Key_Tab || event.key === Qt.Key_Backtab)) {
|
if (root.ignoreTabKeys && (event.key === Qt.Key_Tab || event.key === Qt.Key_Backtab)) {
|
||||||
event.accepted = false;
|
event.accepted = false;
|
||||||
for (var i = 0; i < root.keyForwardTargets.length; i++) {
|
for (var i = 0; i < root.keyForwardTargets.length; i++) {
|
||||||
if (root.keyForwardTargets[i]) {
|
if (root.keyForwardTargets[i])
|
||||||
|
root.keyForwardTargets[i].Keys.pressed(event);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (root.ignoreUpDownKeys && (event.key === Qt.Key_Up || event.key === Qt.Key_Down)) {
|
||||||
|
event.accepted = false;
|
||||||
|
for (var i = 0; i < root.keyForwardTargets.length; i++) {
|
||||||
|
if (root.keyForwardTargets[i])
|
||||||
root.keyForwardTargets[i].Keys.pressed(event);
|
root.keyForwardTargets[i].Keys.pressed(event);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user