forked from Shinonome/dots-hyprland
sidebar: translator: language selector
This commit is contained in:
@@ -63,6 +63,14 @@ Singleton {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property QtObject language: QtObject {
|
||||||
|
property QtObject translator: QtObject {
|
||||||
|
property string engine: "auto" // Run `trans -list-engines` for available engines. auto should use google
|
||||||
|
property string sourceLanguage: "auto"
|
||||||
|
property string targetLanguage: "English" // Run `trans -list-all` for available languages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
property QtObject networking: QtObject {
|
property QtObject networking: QtObject {
|
||||||
property string userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
|
property string userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import QtQuick.Layouts
|
|||||||
*/
|
*/
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: root
|
id: root
|
||||||
default property alias content: rowLayout.data
|
default property alias data: rowLayout.data
|
||||||
property real spacing: 5
|
property real spacing: 5
|
||||||
property real padding: 0
|
property real padding: 0
|
||||||
property int clickIndex: rowLayout.clickIndex
|
property int clickIndex: rowLayout.clickIndex
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
import "root:/modules/common"
|
||||||
|
import "root:/modules/common/widgets"
|
||||||
|
import "root:/services"
|
||||||
|
import Qt5Compat.GraphicalEffects
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
property real dialogPadding: 15
|
||||||
|
property real dialogMargin: 30
|
||||||
|
property string titleText: "Selection Dialog"
|
||||||
|
property alias items: choiceModel.values
|
||||||
|
property int selectedId: -1 // -1 means no selection
|
||||||
|
|
||||||
|
signal canceled();
|
||||||
|
signal selected(var result);
|
||||||
|
|
||||||
|
Rectangle { // Scrim
|
||||||
|
id: scrimOverlay
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: Appearance.rounding.small
|
||||||
|
color: Appearance.colors.colScrim
|
||||||
|
MouseArea {
|
||||||
|
hoverEnabled: true
|
||||||
|
anchors.fill: parent
|
||||||
|
preventStealing: true
|
||||||
|
propagateComposedEvents: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle { // The dialog
|
||||||
|
id: dialog
|
||||||
|
color: Appearance.m3colors.m3surfaceContainerHigh
|
||||||
|
radius: Appearance.rounding.normal
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: dialogMargin
|
||||||
|
implicitHeight: dialogColumnLayout.implicitHeight
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: dialogColumnLayout
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 16
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: dialogTitle
|
||||||
|
Layout.topMargin: dialogPadding
|
||||||
|
Layout.leftMargin: dialogPadding
|
||||||
|
Layout.rightMargin: dialogPadding
|
||||||
|
Layout.alignment: Qt.AlignLeft
|
||||||
|
color: Appearance.m3colors.m3onSurface
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.larger
|
||||||
|
text: root.titleText
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
color: Appearance.m3colors.m3outline
|
||||||
|
implicitHeight: 1
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: dialogPadding
|
||||||
|
Layout.rightMargin: dialogPadding
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: choiceListView
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
model: ScriptModel {
|
||||||
|
id: choiceModel
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: StyledRadioButton {
|
||||||
|
id: radioButton
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
anchors {
|
||||||
|
left: parent?.left
|
||||||
|
right: parent?.right
|
||||||
|
leftMargin: root.dialogPadding
|
||||||
|
rightMargin: root.dialogPadding
|
||||||
|
}
|
||||||
|
|
||||||
|
description: modelData.toString()
|
||||||
|
checked: index === root.selectedId
|
||||||
|
|
||||||
|
onCheckedChanged: {
|
||||||
|
if (checked) {
|
||||||
|
root.selectedId = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
color: Appearance.m3colors.m3outline
|
||||||
|
implicitHeight: 1
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: dialogPadding
|
||||||
|
Layout.rightMargin: dialogPadding
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: dialogButtonsRowLayout
|
||||||
|
Layout.bottomMargin: dialogPadding
|
||||||
|
Layout.leftMargin: dialogPadding
|
||||||
|
Layout.rightMargin: dialogPadding
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
|
||||||
|
DialogButton {
|
||||||
|
buttonText: qsTr("Cancel")
|
||||||
|
onClicked: root.canceled()
|
||||||
|
}
|
||||||
|
DialogButton {
|
||||||
|
buttonText: qsTr("OK")
|
||||||
|
onClicked: root.selected(
|
||||||
|
root.selectedId === -1 ? null :
|
||||||
|
root.items[root.selectedId]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import "root:/services"
|
|||||||
import "root:/modules/common"
|
import "root:/modules/common"
|
||||||
import "root:/modules/common/widgets"
|
import "root:/modules/common/widgets"
|
||||||
import "root:/modules/common/functions/string_utils.js" as StringUtils
|
import "root:/modules/common/functions/string_utils.js" as StringUtils
|
||||||
|
import "./translator/"
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
@@ -15,11 +16,26 @@ import Quickshell.Hyprland
|
|||||||
*/
|
*/
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
property var inputField: inputTextArea
|
// Widgets
|
||||||
property var outputField: outputTextArea
|
property var inputField: inputCanvas.inputTextArea
|
||||||
|
// Widget variables
|
||||||
property bool translationFor: false // Indicates if the translation is for an autocorrected text
|
property bool translationFor: false // Indicates if the translation is for an autocorrected text
|
||||||
property string translatedText: ""
|
property string translatedText: ""
|
||||||
|
property list<string> languages: []
|
||||||
|
// Options
|
||||||
|
property string targetLanguage: ConfigOptions.language.translator.targetLanguage
|
||||||
|
property string sourceLanguage: ConfigOptions.language.translator.sourceLanguage
|
||||||
|
property string hostLanguage: targetLanguage
|
||||||
|
|
||||||
|
property bool showLanguageSelector: false
|
||||||
|
property bool languageSelectorTarget: false // true for target language, false for source language
|
||||||
|
property string languageSelectorLanguage: ""
|
||||||
|
|
||||||
|
function showLanguageSelectorDialog(isTargetLang: bool) {
|
||||||
|
root.showLanguageSelector = true
|
||||||
|
root.languageSelectorTarget = isTargetLang;
|
||||||
|
root.languageSelectorLanguage = isTargetLang ? root.targetLanguage : root.sourceLanguage;
|
||||||
|
}
|
||||||
|
|
||||||
onFocusChanged: (focus) => {
|
onFocusChanged: (focus) => {
|
||||||
if (focus) {
|
if (focus) {
|
||||||
@@ -32,19 +48,23 @@ Item {
|
|||||||
interval: ConfigOptions.sidebar.translator.delay
|
interval: ConfigOptions.sidebar.translator.delay
|
||||||
repeat: false
|
repeat: false
|
||||||
onTriggered: () => {
|
onTriggered: () => {
|
||||||
if (inputTextArea.text.trim().length > 0) {
|
if (root.inputField.text.trim().length > 0) {
|
||||||
|
console.log("Translating with command:", translateProc.command);
|
||||||
translateProc.running = false;
|
translateProc.running = false;
|
||||||
translateProc.buffer = ""; // Clear the buffer
|
translateProc.buffer = ""; // Clear the buffer
|
||||||
translateProc.running = true; // Restart the process
|
translateProc.running = true; // Restart the process
|
||||||
} else {
|
} else {
|
||||||
outputTextArea.text = "";
|
root.translatedText = "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: translateProc
|
id: translateProc
|
||||||
command: ["bash", "-c", `trans -no-theme -no-ansi '${StringUtils.shellSingleQuoteEscape(inputTextArea.text.trim())}'`]
|
command: ["bash", "-c", `trans -no-theme`
|
||||||
|
+ ` -source '${StringUtils.shellSingleQuoteEscape(root.sourceLanguage)}'`
|
||||||
|
+ ` -target '${StringUtils.shellSingleQuoteEscape(root.targetLanguage)}'`
|
||||||
|
+ ` -no-ansi '${StringUtils.shellSingleQuoteEscape(root.inputField.text.trim())}'`]
|
||||||
property string buffer: ""
|
property string buffer: ""
|
||||||
stdout: SplitParser {
|
stdout: SplitParser {
|
||||||
onRead: data => {
|
onRead: data => {
|
||||||
@@ -54,12 +74,29 @@ Item {
|
|||||||
onExited: (exitCode, exitStatus) => {
|
onExited: (exitCode, exitStatus) => {
|
||||||
// 1. Split into sections by double newlines
|
// 1. Split into sections by double newlines
|
||||||
const sections = translateProc.buffer.trim().split(/\n\s*\n/);
|
const sections = translateProc.buffer.trim().split(/\n\s*\n/);
|
||||||
// console.log("BUFFER:", translateProc.buffer);
|
console.log("BUFFER:", translateProc.buffer);
|
||||||
// console.log("SECTIONS:", sections);
|
console.log("SECTIONS:", sections);
|
||||||
|
|
||||||
// 2. Extract relevant data
|
// 2. Extract relevant data
|
||||||
root.translatedText = sections.length > 1 ? sections[1].trim() : "";
|
root.translatedText = sections.length > 1 ? sections[1].trim() : "";
|
||||||
root.outputField.text = root.translatedText;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: getLanguagesProc
|
||||||
|
command: ["trans", "-list-languages"]
|
||||||
|
property list<string> bufferList: ["auto"]
|
||||||
|
running: true
|
||||||
|
stdout: SplitParser {
|
||||||
|
onRead: data => {
|
||||||
|
getLanguagesProc.bufferList.push(data.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onExited: (exitCode, exitStatus) => {
|
||||||
|
root.languages = getLanguagesProc.bufferList
|
||||||
|
.filter(lang => lang.trim().length > 0) // Filter out empty lines
|
||||||
|
.sort((a, b) => a.localeCompare(b)); // Sort alphabetically
|
||||||
|
getLanguagesProc.bufferList = []; // Clear the buffer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,159 +108,133 @@ Item {
|
|||||||
id: contentColumn
|
id: contentColumn
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
Rectangle { // INPUT
|
LanguageSelectorButton { // Source language button
|
||||||
|
id: sourceLanguageButton
|
||||||
|
displayText: root.sourceLanguage
|
||||||
|
onClicked: {
|
||||||
|
root.showLanguageSelectorDialog(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextCanvas { // Content input
|
||||||
id: inputCanvas
|
id: inputCanvas
|
||||||
Layout.fillWidth: true
|
isInput: true
|
||||||
implicitHeight: Math.max(150, inputColumn.implicitHeight)
|
placeholderText: qsTr("Enter text to translate...")
|
||||||
color: Appearance.colors.colLayer1
|
onInputTextChanged: {
|
||||||
radius: Appearance.rounding.normal
|
translateTimer.restart();
|
||||||
border.color: Appearance.m3colors.m3outlineVariant
|
}
|
||||||
border.width: 1
|
GroupButton {
|
||||||
|
id: pasteButton
|
||||||
ColumnLayout {
|
baseWidth: height
|
||||||
id: inputColumn
|
buttonRadius: Appearance.rounding.small
|
||||||
anchors.fill: parent
|
contentItem: MaterialSymbol {
|
||||||
spacing: 0
|
anchors.centerIn: parent
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
StyledTextArea { // Input area
|
iconSize: Appearance.font.pixelSize.larger
|
||||||
id: inputTextArea
|
text: "content_paste"
|
||||||
Layout.fillWidth: true
|
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
|
||||||
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 = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
onClicked: {
|
||||||
Item { Layout.fillHeight: true }
|
root.inputField.text = Quickshell.clipboardText
|
||||||
|
}
|
||||||
RowLayout { // Status row
|
}
|
||||||
Layout.fillWidth: true
|
GroupButton {
|
||||||
Layout.margins: 10
|
id: deleteButton
|
||||||
spacing: 10
|
baseWidth: height
|
||||||
|
buttonRadius: Appearance.rounding.small
|
||||||
Text {
|
enabled: inputCanvas.inputTextArea.text.length > 0
|
||||||
Layout.leftMargin: 10
|
contentItem: MaterialSymbol {
|
||||||
text: qsTr("%1 characters").arg(inputTextArea.text.length)
|
anchors.centerIn: parent
|
||||||
color: Appearance.colors.colOnLayer1
|
horizontalAlignment: Text.AlignHCenter
|
||||||
font.pixelSize: Appearance.font.pixelSize.smaller
|
iconSize: Appearance.font.pixelSize.larger
|
||||||
}
|
text: "close"
|
||||||
Item { Layout.fillWidth: true }
|
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
|
||||||
ButtonGroup {
|
}
|
||||||
GroupButton {
|
onClicked: {
|
||||||
id: pasteButton
|
root.inputField.text = ""
|
||||||
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
|
LanguageSelectorButton { // Target language button
|
||||||
|
id: targetLanguageButton
|
||||||
|
displayText: root.targetLanguage
|
||||||
|
onClicked: {
|
||||||
|
root.showLanguageSelectorDialog(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextCanvas { // Content translation
|
||||||
id: outputCanvas
|
id: outputCanvas
|
||||||
Layout.fillWidth: true
|
isInput: false
|
||||||
implicitHeight: Math.max(150, outputColumn.implicitHeight)
|
placeholderText: qsTr("Translation goes here...")
|
||||||
color: Appearance.m3colors.m3surfaceContainer
|
property bool hasTranslation: (root.translatedText.trim().length > 0)
|
||||||
radius: Appearance.rounding.normal
|
text: hasTranslation ? root.translatedText : ""
|
||||||
|
GroupButton {
|
||||||
ColumnLayout { // Output column
|
id: copyButton
|
||||||
id: outputColumn
|
baseWidth: height
|
||||||
anchors.fill: parent
|
buttonRadius: Appearance.rounding.small
|
||||||
spacing: 0
|
enabled: outputCanvas.displayedText.trim().length > 0
|
||||||
|
contentItem: MaterialSymbol {
|
||||||
StyledText { // Output area
|
anchors.centerIn: parent
|
||||||
id: outputTextArea
|
horizontalAlignment: Text.AlignHCenter
|
||||||
Layout.fillWidth: true
|
iconSize: Appearance.font.pixelSize.larger
|
||||||
property bool hasTranslation: (root.translatedText.trim().length > 0)
|
text: "content_copy"
|
||||||
wrapMode: TextEdit.Wrap
|
color: copyButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
|
||||||
font.pixelSize: Appearance.font.pixelSize.small
|
|
||||||
color: hasTranslation ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
|
|
||||||
padding: 15
|
|
||||||
text: hasTranslation ? root.translatedText : ""
|
|
||||||
}
|
}
|
||||||
Item { Layout.fillHeight: true }
|
onClicked: {
|
||||||
RowLayout { // Status row
|
Quickshell.clipboardText = outputCanvas.displayedText
|
||||||
Layout.fillWidth: true
|
}
|
||||||
Layout.margins: 10
|
}
|
||||||
spacing: 10
|
GroupButton {
|
||||||
Item { Layout.fillWidth: true }
|
id: searchButton
|
||||||
ButtonGroup {
|
baseWidth: height
|
||||||
GroupButton {
|
buttonRadius: Appearance.rounding.small
|
||||||
id: copyButton
|
enabled: outputCanvas.displayedText.trim().length > 0
|
||||||
baseWidth: height
|
contentItem: MaterialSymbol {
|
||||||
buttonRadius: Appearance.rounding.small
|
anchors.centerIn: parent
|
||||||
enabled: root.outputField.text.trim().length > 0
|
horizontalAlignment: Text.AlignHCenter
|
||||||
contentItem: MaterialSymbol {
|
iconSize: Appearance.font.pixelSize.larger
|
||||||
anchors.centerIn: parent
|
text: "travel_explore"
|
||||||
horizontalAlignment: Text.AlignHCenter
|
color: searchButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
|
||||||
iconSize: Appearance.font.pixelSize.larger
|
}
|
||||||
text: "content_copy"
|
onClicked: {
|
||||||
color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
|
let url = ConfigOptions.search.engineBaseUrl + outputCanvas.displayedText;
|
||||||
}
|
for (let site of ConfigOptions.search.excludedSites) {
|
||||||
onClicked: {
|
url += ` -site:${site}`;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Qt.openUrlExternally(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
anchors.fill: parent
|
||||||
|
active: root.showLanguageSelector
|
||||||
|
visible: root.showLanguageSelector
|
||||||
|
z: 9999
|
||||||
|
sourceComponent: SelectionDialog {
|
||||||
|
id: languageSelectorDialog
|
||||||
|
titleText: qsTr("Select Language")
|
||||||
|
items: root.languages
|
||||||
|
onCanceled: () => {
|
||||||
|
root.showLanguageSelector = false;
|
||||||
|
}
|
||||||
|
onSelected: (result) => {
|
||||||
|
root.showLanguageSelector = false;
|
||||||
|
if (!result || result.length === 0) return; // No selection made
|
||||||
|
|
||||||
|
if (root.languageSelectorTarget) {
|
||||||
|
root.targetLanguage = result;
|
||||||
|
ConfigOptions.language.translator.targetLanguage = result; // Save to config
|
||||||
|
} else {
|
||||||
|
root.sourceLanguage = result;
|
||||||
|
ConfigOptions.language.translator.sourceLanguage = result; // Save to config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
RippleButton {
|
||||||
|
id: root
|
||||||
|
property string displayText: ""
|
||||||
|
colBackground: Appearance.colors.colLayer2
|
||||||
|
|
||||||
|
contentItem: Item {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
implicitWidth: languageRow.implicitWidth
|
||||||
|
implicitHeight: languageText.implicitHeight
|
||||||
|
RowLayout {
|
||||||
|
id: languageRow
|
||||||
|
anchors.centerIn: parent
|
||||||
|
spacing: 0
|
||||||
|
StyledText {
|
||||||
|
id: languageText
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
Layout.leftMargin: 5
|
||||||
|
text: root.displayText
|
||||||
|
color: Appearance.colors.colOnLayer2
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.small
|
||||||
|
}
|
||||||
|
MaterialSymbol {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
iconSize: Appearance.font.pixelSize.hugeass
|
||||||
|
text: "arrow_drop_down"
|
||||||
|
color: Appearance.colors.colOnLayer2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: root
|
||||||
|
property bool isInput: true // true for input, false for output
|
||||||
|
property string placeholderText
|
||||||
|
property string text: ""
|
||||||
|
property var inputTextArea: isInput ? inputLoader.item : undefined
|
||||||
|
readonly property string displayedText: isInput ? inputLoader.item.text :
|
||||||
|
root.text.length > 0 ? outputLoader.item.text : ""
|
||||||
|
default property alias actionButtons: actions.data
|
||||||
|
Layout.fillWidth: true
|
||||||
|
implicitHeight: Math.max(150, inputColumn.implicitHeight)
|
||||||
|
color: isInput ? Appearance.colors.colLayer1 : Appearance.m3colors.m3surfaceContainer
|
||||||
|
radius: Appearance.rounding.normal
|
||||||
|
border.color: isInput ? Appearance.m3colors.m3outlineVariant : "transparent"
|
||||||
|
border.width: isInput ? 1 : 0
|
||||||
|
|
||||||
|
signal inputTextChanged(); // Signal emitted when text changes
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: inputColumn
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: inputLoader
|
||||||
|
active: root.isInput
|
||||||
|
visible: root.isInput
|
||||||
|
Layout.fillWidth: true
|
||||||
|
sourceComponent: StyledTextArea { // Input area
|
||||||
|
id: inputTextArea
|
||||||
|
placeholderText: root.placeholderText
|
||||||
|
wrapMode: TextEdit.Wrap
|
||||||
|
textFormat: TextEdit.PlainText
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.small
|
||||||
|
color: Appearance.colors.colOnLayer1
|
||||||
|
padding: 15
|
||||||
|
background: null
|
||||||
|
onTextChanged: root.inputTextChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: outputLoader
|
||||||
|
active: !root.isInput
|
||||||
|
visible: !root.isInput
|
||||||
|
Layout.fillWidth: true
|
||||||
|
sourceComponent: StyledText { // Output area
|
||||||
|
id: outputTextArea
|
||||||
|
padding: 15
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.small
|
||||||
|
color: root.text.length > 0 ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext
|
||||||
|
text: root.text.length > 0 ? root.text : root.placeholderText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { Layout.fillHeight: true }
|
||||||
|
|
||||||
|
RowLayout { // Status row
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.margins: 10
|
||||||
|
spacing: 10
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
active: root.isInput
|
||||||
|
visible: root.isInput
|
||||||
|
Layout.leftMargin: 10
|
||||||
|
sourceComponent: Text {
|
||||||
|
text: qsTr("%1 characters").arg(inputLoader.item.text.length)
|
||||||
|
color: Appearance.colors.colOnLayer1
|
||||||
|
font.pixelSize: Appearance.font.pixelSize.smaller
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Item { Layout.fillWidth: true }
|
||||||
|
ButtonGroup {
|
||||||
|
id: actions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import Qt5Compat.GraphicalEffects
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
import Quickshell.Widgets
|
import Quickshell.Widgets
|
||||||
import Quickshell.Services.Pipewire
|
import Quickshell.Services.Pipewire
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ Item {
|
|||||||
property int dialogMargins: 16
|
property int dialogMargins: 16
|
||||||
property PwNode selectedDevice
|
property PwNode selectedDevice
|
||||||
|
|
||||||
function showDeviceSelectorDialog(input) {
|
function showDeviceSelectorDialog(input: bool) {
|
||||||
root.selectedDevice = null
|
root.selectedDevice = null
|
||||||
root.showDeviceSelector = true
|
root.showDeviceSelector = true
|
||||||
root.deviceSelectorInput = input
|
root.deviceSelectorInput = input
|
||||||
@@ -207,9 +208,11 @@ Item {
|
|||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: Pipewire.nodes.values.filter(node => {
|
model: ScriptModel {
|
||||||
return !node.isStream && node.isSink !== root.deviceSelectorInput && node.audio
|
values: Pipewire.nodes.values.filter(node => {
|
||||||
})
|
return !node.isStream && node.isSink !== root.deviceSelectorInput && node.audio
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// This could and should be refractored, but all data becomes null when passed wtf
|
// This could and should be refractored, but all data becomes null when passed wtf
|
||||||
delegate: StyledRadioButton {
|
delegate: StyledRadioButton {
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ ShellRoot {
|
|||||||
property bool enableBar: true
|
property bool enableBar: true
|
||||||
property bool enableBackgroundWidgets: true
|
property bool enableBackgroundWidgets: true
|
||||||
property bool enableCheatsheet: true
|
property bool enableCheatsheet: true
|
||||||
property bool enableDock: true
|
property bool enableDock: false
|
||||||
property bool enableMediaControls: true
|
property bool enableMediaControls: true
|
||||||
property bool enableNotificationPopup: true
|
property bool enableNotificationPopup: true
|
||||||
property bool enableOnScreenDisplayBrightness: true
|
property bool enableOnScreenDisplayBrightness: true
|
||||||
|
|||||||
Reference in New Issue
Block a user