Files
illogical-impulse/.config/quickshell/modules/sidebarLeft/Translator.qml
T
2025-06-03 10:22:16 +02:00

230 lines
9.7 KiB
QML

import "root:/"
import "root:/services"
import "root:/modules/common"
import "root:/modules/common/widgets"
import "root:/modules/common/functions/string_utils.js" as StringUtils
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Io
import Quickshell.Hyprland
/**
* Translator widget with the `trans` commandline tool.
*/
Item {
id: root
property var inputField: inputTextArea
property var outputField: outputTextArea
property bool translationFor: false // Indicates if the translation is for an autocorrected text
property string translatedText: ""
onFocusChanged: (focus) => {
if (focus) {
root.inputField.forceActiveFocus()
}
}
Timer {
id: translateTimer
interval: ConfigOptions.sidebar.translator.delay
repeat: false
onTriggered: () => {
if (inputTextArea.text.trim().length > 0) {
translateProc.running = false;
translateProc.buffer = ""; // Clear the buffer
translateProc.running = true; // Restart the process
} else {
outputTextArea.text = "";
}
}
}
Process {
id: translateProc
command: ["bash", "-c", `trans -no-theme -no-ansi '${StringUtils.shellSingleQuoteEscape(inputTextArea.text.trim())}'`]
property string buffer: ""
stdout: SplitParser {
onRead: data => {
translateProc.buffer += data + "\n";
}
}
onExited: (exitCode, exitStatus) => {
// 1. Split into sections by double newlines
const sections = translateProc.buffer.trim().split(/\n\s*\n/);
// console.log("BUFFER:", translateProc.buffer);
// console.log("SECTIONS:", sections);
// 2. Extract relevant data
root.translatedText = sections.length > 1 ? sections[1].trim() : "";
root.outputField.text = root.translatedText;
}
}
Flickable {
anchors.fill: parent
contentHeight: contentColumn.implicitHeight
ColumnLayout {
id: contentColumn
anchors.fill: parent
Rectangle { // INPUT
id: inputCanvas
Layout.fillWidth: true
implicitHeight: Math.max(150, inputColumn.implicitHeight)
color: Appearance.colors.colLayer1
radius: Appearance.rounding.normal
border.color: Appearance.m3colors.m3outlineVariant
border.width: 1
ColumnLayout {
id: inputColumn
anchors.fill: parent
spacing: 0
StyledTextArea { // Input area
id: inputTextArea
Layout.fillWidth: true
placeholderText: qsTr("Enter text to translate...")
wrapMode: TextEdit.Wrap
textFormat: TextEdit.PlainText
font.pixelSize: Appearance.font.pixelSize.small
color: Appearance.colors.colOnLayer1
padding: 15
background: null
onTextChanged: {
if (inputTextArea.text.trim().length > 0) {
translateTimer.restart();
} else {
outputTextArea.text = "";
}
}
}
Item { Layout.fillHeight: true }
RowLayout { // Status row
Layout.fillWidth: true
Layout.margins: 10
spacing: 10
Text {
Layout.leftMargin: 10
text: qsTr("%1 characters").arg(inputTextArea.text.length)
color: Appearance.colors.colOnLayer1
font.pixelSize: Appearance.font.pixelSize.smaller
}
Item { Layout.fillWidth: true }
ButtonGroup {
GroupButton {
id: pasteButton
baseWidth: height
buttonRadius: Appearance.rounding.small
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.larger
text: "content_paste"
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
}
onClicked: {
root.inputField.text = Quickshell.clipboardText
}
}
GroupButton {
id: deleteButton
baseWidth: height
buttonRadius: Appearance.rounding.small
enabled: inputTextArea.text.length > 0
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.larger
text: "close"
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
}
onClicked: {
root.inputField.text = ""
}
}
}
}
}
}
Rectangle { // OUTPUT
id: outputCanvas
Layout.fillWidth: true
implicitHeight: Math.max(150, outputColumn.implicitHeight)
color: Appearance.m3colors.m3surfaceContainer
radius: Appearance.rounding.normal
ColumnLayout { // Output column
id: outputColumn
anchors.fill: parent
spacing: 0
StyledText { // Output area
id: outputTextArea
Layout.fillWidth: true
property bool hasTranslation: (root.translatedText.trim().length > 0)
wrapMode: TextEdit.Wrap
font.pixelSize: Appearance.font.pixelSize.small
color: hasTranslation ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
padding: 15
text: hasTranslation ? root.translatedText : ""
}
Item { Layout.fillHeight: true }
RowLayout { // Status row
Layout.fillWidth: true
Layout.margins: 10
spacing: 10
Item { Layout.fillWidth: true }
ButtonGroup {
GroupButton {
id: copyButton
baseWidth: height
buttonRadius: Appearance.rounding.small
enabled: root.outputField.text.trim().length > 0
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.larger
text: "content_copy"
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
}
onClicked: {
Quickshell.clipboardText = root.outputField.text
}
}
GroupButton {
id: searchButton
baseWidth: height
buttonRadius: Appearance.rounding.small
enabled: root.outputField.text.trim().length > 0
contentItem: MaterialSymbol {
anchors.centerIn: parent
horizontalAlignment: Text.AlignHCenter
iconSize: Appearance.font.pixelSize.larger
text: "travel_explore"
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
}
onClicked: {
let url = ConfigOptions.search.engineBaseUrl + root.outputField.text;
for (let site of ConfigOptions.search.excludedSites) {
url += ` -site:${site}`;
}
Qt.openUrlExternally(url);
}
}
}
}
}
}
}
}
}