overview: search

This commit is contained in:
end-4
2025-04-25 20:35:37 +02:00
parent e1359116b8
commit 63c844cdeb
4 changed files with 214 additions and 29 deletions
@@ -17,15 +17,106 @@ Item { // Wrapper
implicitWidth: searchWidgetContent.implicitWidth + Appearance.sizes.elevationMargin * 2
implicitHeight: searchWidgetContent.implicitHeight + Appearance.sizes.elevationMargin * 2
Keys.onPressed: {
// Only handle printable characters (ignore modifiers, arrows, etc.)
if (event.text && event.text.length === 1 && event.key !== Qt.Key_Enter && event.key !== Qt.Key_Return) {
property string mathResult: ""
Timer {
id: nonAppResultsTimer
interval: ConfigOptions.search.nonAppResultDelay
onTriggered: {
mathProcess.calculateExpression(root.searchingText);
}
}
Process {
id: mathProcess
property list<string> baseCommand: ["qalc", "-t"]
function calculateExpression(expression) {
// mathProcess.running = false
mathProcess.command = baseCommand.concat(expression)
mathProcess.running = true
}
stdout: SplitParser {
onRead: data => {
root.mathResult = data
if (searchInput.focus) appResults.currentIndex = 0; // Focus the first item
}
}
}
Process {
id: copyText
property list<string> baseCommand: ["wl-copy"]
function copyTextToClipboard(text) {
copyText.running = false
copyText.command = baseCommand.concat(text)
copyText.running = true
}
}
Process {
id: webSearch
property list<string> baseCommand: ["xdg-open"]
function search(query) {
webSearch.running = false
let url = ConfigOptions.search.engineBaseUrl + query
for (let site of ConfigOptions.search.excludedSites) {
url += ` -site:${site}`;
}
webSearch.command = baseCommand.concat(url)
webSearch.running = true
}
}
Keys.onPressed: (event) => {
// Prevent Esc and Backspace from registering
if (event.key === Qt.Key_Escape) return;
// Handle Backspace: focus and delete character if not focused
if (event.key === Qt.Key_Backspace) {
if (!searchInput.activeFocus) {
searchInput.forceActiveFocus();
if (event.modifiers & Qt.ControlModifier) {
// Delete word before cursor
let text = searchInput.text;
let pos = searchInput.cursorPosition;
if (pos > 0) {
// Find the start of the previous word
let left = text.slice(0, pos);
let match = left.match(/(\s*\S+)\s*$/);
let deleteLen = match ? match[0].length : 1;
searchInput.text = text.slice(0, pos - deleteLen) + text.slice(pos);
searchInput.cursorPosition = pos - deleteLen;
}
} else {
// Delete character before cursor if any
if (searchInput.cursorPosition > 0) {
searchInput.text = searchInput.text.slice(0, searchInput.cursorPosition - 1) +
searchInput.text.slice(searchInput.cursorPosition);
searchInput.cursorPosition -= 1;
}
}
// Always move cursor to end after programmatic edit
searchInput.cursorPosition = searchInput.text.length;
event.accepted = true;
}
// If already focused, let TextField handle it
return;
}
// Only handle visible printable characters (ignore control chars, arrows, etc.)
if (
event.text &&
event.text.length === 1 &&
event.key !== Qt.Key_Enter &&
event.key !== Qt.Key_Return &&
event.text.charCodeAt(0) >= 0x20 // ignore control chars like Backspace, Tab, etc.
) {
if (!searchInput.activeFocus) {
searchInput.forceActiveFocus();
// Insert the character at the cursor position
searchInput.text = searchInput.text.slice(0, searchInput.cursorPosition) +
event.text +
searchInput.text.slice(searchInput.cursorPosition);
event.text +
searchInput.text.slice(searchInput.cursorPosition);
searchInput.cursorPosition += 1;
event.accepted = true;
}
@@ -57,7 +148,12 @@ Item { // Wrapper
RowLayout {
id: searchBar
spacing: 5
KeyNavigation.down: appResults
KeyNavigation.down: {
if (appResults.count > 1) {
appResults.currentIndex = 1;
appResults.forceActiveFocus();
}
}
MaterialSymbol {
id: searchIcon
Layout.leftMargin: 15
@@ -75,8 +171,14 @@ Item { // Wrapper
selectedTextColor: Appearance.m3colors.m3onSurface
placeholderText: qsTr("Search")
placeholderTextColor: Appearance.m3colors.m3outline
implicitWidth: Appearance.sizes.searchWidth
implicitWidth: root.searchingText == "" ? Appearance.sizes.searchWidthCollapsed : Appearance.sizes.searchWidth
Behavior on implicitWidth {
NumberAnimation {
duration: Appearance.animation.elementDecelFast.duration
easing.type: Appearance.animation.elementDecelFast.type
}
}
onTextChanged: root.searchingText = text
Connections {
@@ -125,20 +227,65 @@ Item { // Wrapper
spacing: 0
KeyNavigation.up: searchBar
Connections {
target: root
function onSearchingTextChanged() {
if (appResults.count > 0)
appResults.currentIndex = 0;
}
}
model: ScriptModel {
id: model
values: DesktopEntries.applications.values
.filter((entry) => {
if (root.searchingText == "") return false
return entry.name.toLowerCase().includes(root.searchingText.toLowerCase())
})
.map((entry) => {
entry.clickActionName = "Launch";
return entry;
})
values: {
if(root.searchingText == "") return [];
// Start math and other non-app stuff
nonAppResultsTimer.restart();
// Init result array
let result = [];
// Add filtered application entries
result = result.concat(
DesktopEntries.applications.values
.filter((entry) => {
if (root.searchingText == "") return false
return entry.name.toLowerCase().includes(root.searchingText.toLowerCase())
})
.map((entry) => {
entry.clickActionName = "Launch";
entry.type = "App"
return entry;
})
);
// Add non-app results
result.push({
name: root.mathResult,
clickActionName: "Copy",
type: qsTr("Math result"),
fontType: "monospace",
materialSymbol: 'calculate',
execute: () => {
copyText.copyTextToClipboard(root.mathResult);
}
});
result.push({
name: root.searchingText,
clickActionName: "Search",
type: "Search the web",
materialSymbol: 'travel_explore',
execute: () => {
webSearch.search(root.searchingText);
}
});
return result;
}
}
delegate: SearchItem {
desktopEntry: modelData
entry: modelData
// itemName: modelData.name
// itemIcon: modelData.icon
}