forked from Shinonome/dots-hyprland
230 lines
9.7 KiB
QML
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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|