From d8431223d34476a17835a05d9508e92447f22479 Mon Sep 17 00:00:00 2001 From: Adrian Kummerlaender Date: Sat, 15 Aug 2015 16:06:00 +0200 Subject: Restructure QML and JS sources `list` holds the components of the central list UI element. `command` holds the UI and implementation parts of the command mode. `widget` holds more or less general purpose elements that may also be of use in other circumstances. --- MetaTerm.pro | 4 +- main.cc | 19 +++++ main.cpp | 19 ----- qml/CommandInput.qml | 146 --------------------------------- qml/EmbeddedTerminal.qml | 153 ---------------------------------- qml/Highlighter.qml | 33 -------- qml/StateHandler.qml | 158 ------------------------------------ qml/TerminalItem.qml | 176 ---------------------------------------- qml/TerminalList.qml | 125 ---------------------------- qml/commands.js | 55 ------------- qml/main.qml | 53 ------------ qml/ui.qrc | 12 --- src/StateHandler.qml | 158 ++++++++++++++++++++++++++++++++++++ src/command/CommandInput.qml | 146 +++++++++++++++++++++++++++++++++ src/command/commands.js | 55 +++++++++++++ src/list/TerminalItem.qml | 176 ++++++++++++++++++++++++++++++++++++++++ src/list/TerminalList.qml | 125 ++++++++++++++++++++++++++++ src/main.qml | 53 ++++++++++++ src/ui.qrc | 12 +++ src/widget/EmbeddedTerminal.qml | 153 ++++++++++++++++++++++++++++++++++ src/widget/Highlighter.qml | 33 ++++++++ 21 files changed, 932 insertions(+), 932 deletions(-) create mode 100644 main.cc delete mode 100644 main.cpp delete mode 100644 qml/CommandInput.qml delete mode 100644 qml/EmbeddedTerminal.qml delete mode 100644 qml/Highlighter.qml delete mode 100644 qml/StateHandler.qml delete mode 100644 qml/TerminalItem.qml delete mode 100644 qml/TerminalList.qml delete mode 100644 qml/commands.js delete mode 100644 qml/main.qml delete mode 100644 qml/ui.qrc create mode 100644 src/StateHandler.qml create mode 100644 src/command/CommandInput.qml create mode 100644 src/command/commands.js create mode 100644 src/list/TerminalItem.qml create mode 100644 src/list/TerminalList.qml create mode 100644 src/main.qml create mode 100644 src/ui.qrc create mode 100644 src/widget/EmbeddedTerminal.qml create mode 100644 src/widget/Highlighter.qml diff --git a/MetaTerm.pro b/MetaTerm.pro index 532ba39..5c579ec 100644 --- a/MetaTerm.pro +++ b/MetaTerm.pro @@ -2,6 +2,6 @@ TEMPLATE = app QT += qml quick widgets -SOURCES += main.cpp +SOURCES += main.cc -RESOURCES += qml/ui.qrc +RESOURCES += src/ui.qrc diff --git a/main.cc b/main.cc new file mode 100644 index 0000000..617b43a --- /dev/null +++ b/main.cc @@ -0,0 +1,19 @@ +#include +#include + +int main(int argc, char *argv[]) { + QApplication application(argc, argv); + application.setOrganizationName("akr"); + application.setApplicationName("MetaTerm"); + + QQmlApplicationEngine engine(QUrl(QStringLiteral("qrc:/main.qml"))); + + QObject::connect( + static_cast(&engine), + SIGNAL(quit()), + static_cast(&application), + SLOT(quit()) + ); + + return application.exec(); +} diff --git a/main.cpp b/main.cpp deleted file mode 100644 index 617b43a..0000000 --- a/main.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include -#include - -int main(int argc, char *argv[]) { - QApplication application(argc, argv); - application.setOrganizationName("akr"); - application.setApplicationName("MetaTerm"); - - QQmlApplicationEngine engine(QUrl(QStringLiteral("qrc:/main.qml"))); - - QObject::connect( - static_cast(&engine), - SIGNAL(quit()), - static_cast(&application), - SLOT(quit()) - ); - - return application.exec(); -} diff --git a/qml/CommandInput.qml b/qml/CommandInput.qml deleted file mode 100644 index 99f5d0e..0000000 --- a/qml/CommandInput.qml +++ /dev/null @@ -1,146 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Layouts 1.1 -import Qt.labs.settings 1.0 - -import "commands.js" as Commands - -Item { - id: item - - signal executed - - visible: false - - Layout.preferredHeight: container.height - - Settings { - id: settings - category: "command" - - property string background : "black" - property int fontSize : 12 - property string fontFamily : "Monospace" - property string fontColor : "white" - property string errorColor : "red" - } - - onVisibleChanged: container.reset() - - function focus(prefix) { - visible = true; - command.text = prefix; - command.forceActiveFocus(); - } - - function unfocus() { - visible = false; - } - - Rectangle { - anchors { - top: parent.top - left: parent.left - right: parent.right - } - - height: container.height - - color: settings.background - - ColumnLayout { - id: container - - function reset() { - command.initialize(); - output.initialize(); - } - - TextInput { - id: command - - Layout.fillWidth: true - - font { - family: settings.fontFamily - pointSize: settings.fontSize - } - - color: settings.fontColor - selectionColor: settings.fontColor - selectedTextColor: settings.background - selectByMouse: true - - function initialize() { - text = ''; - } - - onAccepted: { - output.initialize(); - - const prefix = String(text).charAt(0); - const cmd = String(text).substring(1, String(text).length); - - switch ( prefix ) { - case ':': { - Commands.execute(output, cmd); - break; - } - default: { - output.error('"' + prefix + '"' + ' is not a command prefix.'); - } - } - - if ( output.isInitial() ) { - item.executed(); - } - } - } - - Text { - id: output - - Layout.fillWidth: true - Layout.preferredHeight: 0 - - font { - family: settings.fontFamily - pointSize: settings.fontSize - } - - color: settings.fontColor - - function isInitial() { - return text === ''; - } - - function initialize() { - text = ''; - } - - function log(msg) { - if ( isInitial() ) { - text = msg; - } else { - text += '
' + msg; - } - } - - function error(msg) { - text = '' - + msg - + ''; - } - - onTextChanged: { - if ( isInitial() ) { - Layout.preferredHeight = 0; - } else { - Layout.preferredHeight = contentHeight; - } - } - } - } - } -} diff --git a/qml/EmbeddedTerminal.qml b/qml/EmbeddedTerminal.qml deleted file mode 100644 index 6d0dc6e..0000000 --- a/qml/EmbeddedTerminal.qml +++ /dev/null @@ -1,153 +0,0 @@ -import QtQuick 2.0 -import QMLTermWidget 1.0 -import QtQuick.Layouts 1.1 -import Qt.labs.settings 1.0 - -Item { - id: item - - property string program - property string workingDirectory - - Settings { - id: settings - category: "terminal" - - property int initialLines : 20 - property int frameWidth : 10 - property int fontSize : 8 - property string fontFamily : "Monospace" - property string colorScheme : "cool-retro-term" - property string overlayBackground : "black" - property string overlayFontColor : "white" - } - - property int lines : settings.initialLines - - height: terminal.height - width: parent.width - settings.frameWidth - - function select() { highlighter.select() } - function deselect() { highlighter.deselect() } - function displayOverlay() { overlay.displayBriefly() } - - RowLayout { - id: container - - anchors { - left: parent.left - right: parent.right - } - - spacing: 0 - - Highlighter { - id: highlighter - - width: settings.frameWidth - Layout.fillHeight: true - } - - QMLTermWidget { - id: terminal - - font { - family: settings.fontFamily - pointSize: settings.fontSize - } - - Layout.fillWidth: true - Layout.preferredHeight: fontMetrics.height * item.lines - - colorScheme: settings.colorScheme - - session: QMLTermSession { - initialWorkingDirectory: item.workingDirectory - - shellProgram: { - return (item.program).split(" ")[0]; - } - - shellProgramArgs: { - const elements = (item.program).split(" "); - elements.shift(); - - return elements; - } - } - - Component.onCompleted: { - forceActiveFocus(); - highlighter.select(); - session.startShellProgram(); - overlay.enabled = true; - } - - onTermGetFocus: highlighter.focus() - onTermLostFocus: highlighter.unfocus() - onHeightChanged: overlay.displayBriefly(); - onWidthChanged: overlay.displayBriefly(); - - Rectangle { - id: overlay - - property bool enabled : false - - function displayBriefly() { - if ( enabled ) { animation.restart() } - } - - anchors.fill: parent - opacity: 0 - color: settings.overlayBackground - - SequentialAnimation { - id: animation - - ScriptAction { - script: overlay.opacity = 0.8 - } - - PauseAnimation { - duration: 500 - } - - NumberAnimation { - target: overlay - property: "opacity" - - easing.type: Easing.InSine - duration: 300 - from: 0.8 - to: 0 - } - } - - Text { - anchors { - horizontalCenter: overlay.horizontalCenter - verticalCenter: overlay.verticalCenter - } - - font { - family: settings.fontFamily - pointSize: settings.fontSize * 2 - } - color: settings.overlayFontColor - - text: { - return item.lines - + 'x' - + Math.floor(terminal.width / terminal.fontMetrics.width); - } - } - } - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.NoButton - onWheel: { } - } - } - } -} diff --git a/qml/Highlighter.qml b/qml/Highlighter.qml deleted file mode 100644 index e42aeb1..0000000 --- a/qml/Highlighter.qml +++ /dev/null @@ -1,33 +0,0 @@ -import QtQuick 2.0 -import Qt.labs.settings 1.0 - -Item { - Settings { - id: settings - category: "highlighter" - - property string defaultColor : "#909636" - property string focusColor : "#352F6A" - } - - function select() { bar.opacity = 1 } - function deselect() { bar.opacity = 0 } - function focus() { bar.color = settings.focusColor } - function unfocus() { bar.color = settings.defaultColor } - - Rectangle { - id: bar - - anchors.fill: parent - - opacity: 0 - color: settings.defaultColor - - Behavior on opacity { - NumberAnimation { - duration: 300 - easing.type: Easing.OutCubic - } - } - } -} diff --git a/qml/StateHandler.qml b/qml/StateHandler.qml deleted file mode 100644 index d9f5f5a..0000000 --- a/qml/StateHandler.qml +++ /dev/null @@ -1,158 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Controls 1.2 -import Qt.labs.settings 1.0 - -Item { - id: item - - property Item terminalList : null - property CommandInput commandInput : null - - Settings { - id: settings - category: "keybinding" - - property string insertMode : "i" - property string normalMode : "Shift+ESC" - property string commandMode : ":" - property string nextItem : "j" - property string prevItem : "k" - property string firstItem : "g" - property string resetItem : "d" - property string lastItem : "Shift+G" - property string heightenItem : "Shift+J" - property string shortenItem : "Shift+K" - } - - state: "INSERT" - - function enterInsertMode() { - enterInsertAction.trigger(); - } - - function enterNormalMode() { - enterNormalAction.trigger(); - } - - states: [ - State { - name: "NORMAL" - - PropertyChanges { target: enterNormalAction; enabled: false } - PropertyChanges { target: enterInsertAction; enabled: true } - PropertyChanges { target: enterCommandAction; enabled: true } - PropertyChanges { target: nextTerminalAction; enabled: true } - PropertyChanges { target: heightenTerminalAction; enabled: true } - PropertyChanges { target: shortenTerminalAction; enabled: true } - PropertyChanges { target: prevTerminalAction; enabled: true } - PropertyChanges { target: lastTerminalAction; enabled: true } - PropertyChanges { target: firstTerminalAction; enabled: true } - PropertyChanges { target: resetTerminalAction; enabled: true } - }, - State { - name: "INSERT" - - PropertyChanges { target: enterNormalAction; enabled: true } - PropertyChanges { target: enterInsertAction; enabled: false } - PropertyChanges { target: enterCommandAction; enabled: false } - PropertyChanges { target: nextTerminalAction; enabled: false } - PropertyChanges { target: heightenTerminalAction; enabled: false } - PropertyChanges { target: shortenTerminalAction; enabled: false } - PropertyChanges { target: prevTerminalAction; enabled: false } - PropertyChanges { target: lastTerminalAction; enabled: false } - PropertyChanges { target: firstTerminalAction; enabled: false } - PropertyChanges { target: resetTerminalAction; enabled: false } - }, - State { - name: "COMMAND" - - PropertyChanges { target: enterNormalAction; enabled: true } - PropertyChanges { target: enterInsertAction; enabled: false } - PropertyChanges { target: enterCommandAction; enabled: false } - PropertyChanges { target: nextTerminalAction; enabled: false } - PropertyChanges { target: heightenTerminalAction; enabled: false } - PropertyChanges { target: shortenTerminalAction; enabled: false } - PropertyChanges { target: prevTerminalAction; enabled: false } - PropertyChanges { target: lastTerminalAction; enabled: false } - PropertyChanges { target: firstTerminalAction; enabled: false } - PropertyChanges { target: resetTerminalAction; enabled: false } - } - ] - - Action { - id: enterNormalAction - shortcut: settings.normalMode - onTriggered: { - item.state = "NORMAL"; - - terminalList.forceActiveFocus(); - terminalList.unfocusCurrent(); - commandInput.unfocus(); - } - } - - Action { - id: enterInsertAction - shortcut: settings.insertMode - onTriggered: { - item.state = "INSERT"; - - terminalList.focusCurrent(); - } - } - - Action { - id: enterCommandAction - shortcut: settings.commandMode - onTriggered: { - item.state = "COMMAND"; - - commandInput.focus(shortcut); - } - } - - Action { - id: nextTerminalAction - shortcut: settings.nextItem - onTriggered: terminalList.selectNext() - } - - Action { - id: heightenTerminalAction - shortcut: settings.heightenItem - onTriggered: terminalList.getCurrent().heighten() - } - - Action { - id: shortenTerminalAction - shortcut: settings.shortenItem - onTriggered: terminalList.getCurrent().shorten() - } - - Action { - id: prevTerminalAction - shortcut: settings.prevItem - onTriggered: terminalList.selectPrev() - } - - Action { - id: lastTerminalAction - shortcut: settings.lastItem - onTriggered: terminalList.selectItem(terminalList.children.length - 1) - } - - Action { - id: firstTerminalAction - shortcut: settings.firstItem - onTriggered: terminalList.selectItem(0) - } - - Action { - id: resetTerminalAction - shortcut: settings.resetItem - onTriggered: { - terminalList.getCurrent().reset(); - terminalList.getCurrent().select(); - } - } -} diff --git a/qml/TerminalItem.qml b/qml/TerminalItem.qml deleted file mode 100644 index 57197bd..0000000 --- a/qml/TerminalItem.qml +++ /dev/null @@ -1,176 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Controls 1.2 -import QtQuick.Layouts 1.1 -import Qt.labs.settings 1.0 - -Item { - id: item - - property int index : 0 - property EmbeddedTerminal terminal : null - - signal executed (int index) - - Settings { - id: settings - category: "item" - - property int fontSize : 18 - property string fontFamily : "Monospace" - property string fontColor : "white" - } - - anchors { - left: parent.left - right: parent.right - } - - height: elementList.height - - function select() { - if ( terminal === null ) { - highlighter.select(); - } else { - terminal.select(); - } - } - - function deselect() { - if ( terminal === null ) { - highlighter.deselect(); - } else { - terminal.deselect(); - } - } - - function forceActiveFocus() { - scope.forceActiveFocus(); - - if ( terminal === null ) { - scope.forceActiveFocus(); - highlighter.select(); - highlighter.focus(); - } - } - - function unfocus() { - if ( terminal === null ) { - highlighter.unfocus(); - } - } - - function heighten() { - if ( terminal !== null ) { - terminal.lines += 1; - } - } - - function shorten() { - if ( terminal !== null ) { - if ( terminal.lines > 10 ) { - terminal.lines -= 1; - } else { - terminal.displayOverlay(); - } - } - } - - function reset() { - if ( terminal !== null ) { - terminal.destroy(); - - terminal = null; - command.readOnly = false; - command.focus = true; - - unfocus(); - } - } - - FocusScope { - id: scope - - anchors { - left: parent.left - right: parent.right - } - - Column { - id: elementList - - anchors { - left: parent.left - right: parent.right - } - - function createTerminal(program) { - var terminalComponent = Qt.createComponent("qrc:/EmbeddedTerminal.qml"); - var instantiateTerminal = function() { - item.terminal = terminalComponent.createObject(elementList, { - "program" : program, - "workingDirectory" : "$HOME", - "focus" : true - }); - } - - if ( terminalComponent.status === Component.Ready ) { - instantiateTerminal(); - } else { - terminalComponent.statusChanged.connect(instantiateTerminal); - } - } - - RowLayout { - anchors { - left: parent.left - right: parent.right - } - - Highlighter { - id: highlighter - - width: 10 - height: command.height - } - - TextInput { - id: command - - font { - family: settings.fontFamily - pointSize: settings.fontSize - } - - color: settings.fontColor - selectionColor: settings.fontColor - selectedTextColor: "#161616" - - selectByMouse: true - focus: true - Layout.fillWidth: true - - onAccepted: { - if ( item.terminal === null ) { - readOnly = true; - focus = false; - - elementList.createTerminal(text); - item.executed(item.index); - highlighter.deselect(); - } - } - } - - Text { - font { - family: settings.fontFamily - pointSize: settings.fontSize / 1.5 - } - color: settings.fontColor - - text: item.index - } - } - } - } -} diff --git a/qml/TerminalList.qml b/qml/TerminalList.qml deleted file mode 100644 index 6c6465b..0000000 --- a/qml/TerminalList.qml +++ /dev/null @@ -1,125 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Layouts 1.1 - -Item { - id: item - - property StateHandler state : null - property int activeItem : 0 - property int itemIndex : 0 - - property alias children : column.children - - function onItemExecuted(index) { - if ( index === (children.length - 1) ) { - createItem(); - } - } - - function createItem() { - var terminalItem = Qt.createComponent("qrc:/TerminalItem.qml"); - var instantiateTerminal = function() { - var instance = terminalItem.createObject(column, { - "index": itemIndex, - "width": flickable.width - }); - instance.onExecuted.connect(onItemExecuted); - - ++itemIndex; - } - - if ( terminalItem.status === Component.Ready ) { - instantiateTerminal(); - } else { - terminalItem.statusChanged.connect(instantiateTerminal); - } - } - - function scrollTo(index) { - if ( column.height >= flickable.height ) { - var offset = children[index].y - + (children[index].height / 2) - - (flickable.height / 2); - - var bound = column.height - - flickable.height; - - if ( offset < 0 ) { - flickable.contentY = 0; - } else if ( offset >= bound ) { - flickable.contentY = bound; - } else { - flickable.contentY = offset; - } - } - } - - function selectItem(index) { - children[activeItem].deselect(); - children[index ].select(); - - activeItem = typeof index === "number" ? index : parseInt(index); - - scrollTo(index); - } - - function selectNext() { - if ( activeItem < (children.length - 1) ) { - selectItem(activeItem + 1); - } else { - state.enterInsertMode(); - } - } - - function selectPrev() { - if ( activeItem > 0 ) { - selectItem(activeItem - 1); - } - } - - function focusCurrent() { - children[activeItem].forceActiveFocus(); - } - - function unfocusCurrent() { - children[activeItem].unfocus(); - } - - function getCurrent() { - return children[activeItem]; - } - - function get(index) { - return children[index]; - } - - function iterate(func) { - for ( var i = 0; i < children.length; i++ ) { - func(children[i]); - } - } - - Flickable { - id: flickable - - anchors.fill: parent - - boundsBehavior: Flickable.StopAtBounds - contentHeight: column.height - contentWidth: parent.width - pixelAligned: true - - Column { - id: column - - anchors { - left: parent.left - right: parent.right - } - - spacing: 10 - - onHeightChanged: scrollTo(activeItem) - } - } -} diff --git a/qml/commands.js b/qml/commands.js deleted file mode 100644 index f76af01..0000000 --- a/qml/commands.js +++ /dev/null @@ -1,55 +0,0 @@ -function execute(output, command) { - var notImplemented = function(name) { - output.error('"' + name + '"' + ' is not implemented.'); - }; - var args = command.split(' '); - - try { - var closure = eval(args[0]); - - if ( typeof closure === 'function' ) { - args.shift(); - closure(output, args); - } else { - notImplemented(args[0]); - } - } catch (exception) { - notImplemented(args[0]); - } -} - -function exec(output, args) { - try { - var result = eval(args.join(' ')); - - if ( typeof result !== 'undefined' ) { - output.log(result); - } - } catch (exception) { - output.error(exception); - } -} - -function jump(output, index) { - terminalList.selectItem(index); -} - -function kill(output, index) { - terminalList.get(index).reset(); -} - -function next() { - terminalList.selectNext(); -} - -function prev() { - terminalList.selectPrev(); -} - -function ls(output) { - terminalList.iterate(function(item) { - if ( item.terminal !== null ) { - output.log(item.index + ': ' + item.terminal.program); - } - }); -} diff --git a/qml/main.qml b/qml/main.qml deleted file mode 100644 index f7673dc..0000000 --- a/qml/main.qml +++ /dev/null @@ -1,53 +0,0 @@ -import QtQuick 2.0 -import QtQuick.Window 2.0 -import QtQuick.Controls 1.2 -import QtQuick.Layouts 1.1 -import Qt.labs.settings 1.0 - -ApplicationWindow { - id: root - - visible: true - - Settings { - id: settings - category: "window" - - property string background : "#161616" - } - - color: settings.background - - Component.onCompleted: { - terminalList.createItem(); - terminalList.focusCurrent(); - } - - ColumnLayout { - anchors.fill: parent - - TerminalList { - id: terminalList - - state: state - - Layout.fillHeight: true - Layout.fillWidth: true - } - - CommandInput { - id: command - - Layout.fillWidth: true - - onExecuted: state.enterNormalMode() - } - } - - StateHandler { - id: state - - terminalList: terminalList - commandInput: command - } -} diff --git a/qml/ui.qrc b/qml/ui.qrc deleted file mode 100644 index c8bd040..0000000 --- a/qml/ui.qrc +++ /dev/null @@ -1,12 +0,0 @@ - - - main.qml - StateHandler.qml - TerminalItem.qml - EmbeddedTerminal.qml - CommandInput.qml - TerminalList.qml - Highlighter.qml - commands.js - - diff --git a/src/StateHandler.qml b/src/StateHandler.qml new file mode 100644 index 0000000..d9f5f5a --- /dev/null +++ b/src/StateHandler.qml @@ -0,0 +1,158 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.2 +import Qt.labs.settings 1.0 + +Item { + id: item + + property Item terminalList : null + property CommandInput commandInput : null + + Settings { + id: settings + category: "keybinding" + + property string insertMode : "i" + property string normalMode : "Shift+ESC" + property string commandMode : ":" + property string nextItem : "j" + property string prevItem : "k" + property string firstItem : "g" + property string resetItem : "d" + property string lastItem : "Shift+G" + property string heightenItem : "Shift+J" + property string shortenItem : "Shift+K" + } + + state: "INSERT" + + function enterInsertMode() { + enterInsertAction.trigger(); + } + + function enterNormalMode() { + enterNormalAction.trigger(); + } + + states: [ + State { + name: "NORMAL" + + PropertyChanges { target: enterNormalAction; enabled: false } + PropertyChanges { target: enterInsertAction; enabled: true } + PropertyChanges { target: enterCommandAction; enabled: true } + PropertyChanges { target: nextTerminalAction; enabled: true } + PropertyChanges { target: heightenTerminalAction; enabled: true } + PropertyChanges { target: shortenTerminalAction; enabled: true } + PropertyChanges { target: prevTerminalAction; enabled: true } + PropertyChanges { target: lastTerminalAction; enabled: true } + PropertyChanges { target: firstTerminalAction; enabled: true } + PropertyChanges { target: resetTerminalAction; enabled: true } + }, + State { + name: "INSERT" + + PropertyChanges { target: enterNormalAction; enabled: true } + PropertyChanges { target: enterInsertAction; enabled: false } + PropertyChanges { target: enterCommandAction; enabled: false } + PropertyChanges { target: nextTerminalAction; enabled: false } + PropertyChanges { target: heightenTerminalAction; enabled: false } + PropertyChanges { target: shortenTerminalAction; enabled: false } + PropertyChanges { target: prevTerminalAction; enabled: false } + PropertyChanges { target: lastTerminalAction; enabled: false } + PropertyChanges { target: firstTerminalAction; enabled: false } + PropertyChanges { target: resetTerminalAction; enabled: false } + }, + State { + name: "COMMAND" + + PropertyChanges { target: enterNormalAction; enabled: true } + PropertyChanges { target: enterInsertAction; enabled: false } + PropertyChanges { target: enterCommandAction; enabled: false } + PropertyChanges { target: nextTerminalAction; enabled: false } + PropertyChanges { target: heightenTerminalAction; enabled: false } + PropertyChanges { target: shortenTerminalAction; enabled: false } + PropertyChanges { target: prevTerminalAction; enabled: false } + PropertyChanges { target: lastTerminalAction; enabled: false } + PropertyChanges { target: firstTerminalAction; enabled: false } + PropertyChanges { target: resetTerminalAction; enabled: false } + } + ] + + Action { + id: enterNormalAction + shortcut: settings.normalMode + onTriggered: { + item.state = "NORMAL"; + + terminalList.forceActiveFocus(); + terminalList.unfocusCurrent(); + commandInput.unfocus(); + } + } + + Action { + id: enterInsertAction + shortcut: settings.insertMode + onTriggered: { + item.state = "INSERT"; + + terminalList.focusCurrent(); + } + } + + Action { + id: enterCommandAction + shortcut: settings.commandMode + onTriggered: { + item.state = "COMMAND"; + + commandInput.focus(shortcut); + } + } + + Action { + id: nextTerminalAction + shortcut: settings.nextItem + onTriggered: terminalList.selectNext() + } + + Action { + id: heightenTerminalAction + shortcut: settings.heightenItem + onTriggered: terminalList.getCurrent().heighten() + } + + Action { + id: shortenTerminalAction + shortcut: settings.shortenItem + onTriggered: terminalList.getCurrent().shorten() + } + + Action { + id: prevTerminalAction + shortcut: settings.prevItem + onTriggered: terminalList.selectPrev() + } + + Action { + id: lastTerminalAction + shortcut: settings.lastItem + onTriggered: terminalList.selectItem(terminalList.children.length - 1) + } + + Action { + id: firstTerminalAction + shortcut: settings.firstItem + onTriggered: terminalList.selectItem(0) + } + + Action { + id: resetTerminalAction + shortcut: settings.resetItem + onTriggered: { + terminalList.getCurrent().reset(); + terminalList.getCurrent().select(); + } + } +} diff --git a/src/command/CommandInput.qml b/src/command/CommandInput.qml new file mode 100644 index 0000000..99f5d0e --- /dev/null +++ b/src/command/CommandInput.qml @@ -0,0 +1,146 @@ +import QtQuick 2.0 +import QtQuick.Layouts 1.1 +import Qt.labs.settings 1.0 + +import "commands.js" as Commands + +Item { + id: item + + signal executed + + visible: false + + Layout.preferredHeight: container.height + + Settings { + id: settings + category: "command" + + property string background : "black" + property int fontSize : 12 + property string fontFamily : "Monospace" + property string fontColor : "white" + property string errorColor : "red" + } + + onVisibleChanged: container.reset() + + function focus(prefix) { + visible = true; + command.text = prefix; + command.forceActiveFocus(); + } + + function unfocus() { + visible = false; + } + + Rectangle { + anchors { + top: parent.top + left: parent.left + right: parent.right + } + + height: container.height + + color: settings.background + + ColumnLayout { + id: container + + function reset() { + command.initialize(); + output.initialize(); + } + + TextInput { + id: command + + Layout.fillWidth: true + + font { + family: settings.fontFamily + pointSize: settings.fontSize + } + + color: settings.fontColor + selectionColor: settings.fontColor + selectedTextColor: settings.background + selectByMouse: true + + function initialize() { + text = ''; + } + + onAccepted: { + output.initialize(); + + const prefix = String(text).charAt(0); + const cmd = String(text).substring(1, String(text).length); + + switch ( prefix ) { + case ':': { + Commands.execute(output, cmd); + break; + } + default: { + output.error('"' + prefix + '"' + ' is not a command prefix.'); + } + } + + if ( output.isInitial() ) { + item.executed(); + } + } + } + + Text { + id: output + + Layout.fillWidth: true + Layout.preferredHeight: 0 + + font { + family: settings.fontFamily + pointSize: settings.fontSize + } + + color: settings.fontColor + + function isInitial() { + return text === ''; + } + + function initialize() { + text = ''; + } + + function log(msg) { + if ( isInitial() ) { + text = msg; + } else { + text += '
' + msg; + } + } + + function error(msg) { + text = '' + + msg + + ''; + } + + onTextChanged: { + if ( isInitial() ) { + Layout.preferredHeight = 0; + } else { + Layout.preferredHeight = contentHeight; + } + } + } + } + } +} diff --git a/src/command/commands.js b/src/command/commands.js new file mode 100644 index 0000000..f76af01 --- /dev/null +++ b/src/command/commands.js @@ -0,0 +1,55 @@ +function execute(output, command) { + var notImplemented = function(name) { + output.error('"' + name + '"' + ' is not implemented.'); + }; + var args = command.split(' '); + + try { + var closure = eval(args[0]); + + if ( typeof closure === 'function' ) { + args.shift(); + closure(output, args); + } else { + notImplemented(args[0]); + } + } catch (exception) { + notImplemented(args[0]); + } +} + +function exec(output, args) { + try { + var result = eval(args.join(' ')); + + if ( typeof result !== 'undefined' ) { + output.log(result); + } + } catch (exception) { + output.error(exception); + } +} + +function jump(output, index) { + terminalList.selectItem(index); +} + +function kill(output, index) { + terminalList.get(index).reset(); +} + +function next() { + terminalList.selectNext(); +} + +function prev() { + terminalList.selectPrev(); +} + +function ls(output) { + terminalList.iterate(function(item) { + if ( item.terminal !== null ) { + output.log(item.index + ': ' + item.terminal.program); + } + }); +} diff --git a/src/list/TerminalItem.qml b/src/list/TerminalItem.qml new file mode 100644 index 0000000..57197bd --- /dev/null +++ b/src/list/TerminalItem.qml @@ -0,0 +1,176 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.2 +import QtQuick.Layouts 1.1 +import Qt.labs.settings 1.0 + +Item { + id: item + + property int index : 0 + property EmbeddedTerminal terminal : null + + signal executed (int index) + + Settings { + id: settings + category: "item" + + property int fontSize : 18 + property string fontFamily : "Monospace" + property string fontColor : "white" + } + + anchors { + left: parent.left + right: parent.right + } + + height: elementList.height + + function select() { + if ( terminal === null ) { + highlighter.select(); + } else { + terminal.select(); + } + } + + function deselect() { + if ( terminal === null ) { + highlighter.deselect(); + } else { + terminal.deselect(); + } + } + + function forceActiveFocus() { + scope.forceActiveFocus(); + + if ( terminal === null ) { + scope.forceActiveFocus(); + highlighter.select(); + highlighter.focus(); + } + } + + function unfocus() { + if ( terminal === null ) { + highlighter.unfocus(); + } + } + + function heighten() { + if ( terminal !== null ) { + terminal.lines += 1; + } + } + + function shorten() { + if ( terminal !== null ) { + if ( terminal.lines > 10 ) { + terminal.lines -= 1; + } else { + terminal.displayOverlay(); + } + } + } + + function reset() { + if ( terminal !== null ) { + terminal.destroy(); + + terminal = null; + command.readOnly = false; + command.focus = true; + + unfocus(); + } + } + + FocusScope { + id: scope + + anchors { + left: parent.left + right: parent.right + } + + Column { + id: elementList + + anchors { + left: parent.left + right: parent.right + } + + function createTerminal(program) { + var terminalComponent = Qt.createComponent("qrc:/EmbeddedTerminal.qml"); + var instantiateTerminal = function() { + item.terminal = terminalComponent.createObject(elementList, { + "program" : program, + "workingDirectory" : "$HOME", + "focus" : true + }); + } + + if ( terminalComponent.status === Component.Ready ) { + instantiateTerminal(); + } else { + terminalComponent.statusChanged.connect(instantiateTerminal); + } + } + + RowLayout { + anchors { + left: parent.left + right: parent.right + } + + Highlighter { + id: highlighter + + width: 10 + height: command.height + } + + TextInput { + id: command + + font { + family: settings.fontFamily + pointSize: settings.fontSize + } + + color: settings.fontColor + selectionColor: settings.fontColor + selectedTextColor: "#161616" + + selectByMouse: true + focus: true + Layout.fillWidth: true + + onAccepted: { + if ( item.terminal === null ) { + readOnly = true; + focus = false; + + elementList.createTerminal(text); + item.executed(item.index); + highlighter.deselect(); + } + } + } + + Text { + font { + family: settings.fontFamily + pointSize: settings.fontSize / 1.5 + } + color: settings.fontColor + + text: item.index + } + } + } + } +} diff --git a/src/list/TerminalList.qml b/src/list/TerminalList.qml new file mode 100644 index 0000000..6c6465b --- /dev/null +++ b/src/list/TerminalList.qml @@ -0,0 +1,125 @@ +import QtQuick 2.0 +import QtQuick.Layouts 1.1 + +Item { + id: item + + property StateHandler state : null + property int activeItem : 0 + property int itemIndex : 0 + + property alias children : column.children + + function onItemExecuted(index) { + if ( index === (children.length - 1) ) { + createItem(); + } + } + + function createItem() { + var terminalItem = Qt.createComponent("qrc:/TerminalItem.qml"); + var instantiateTerminal = function() { + var instance = terminalItem.createObject(column, { + "index": itemIndex, + "width": flickable.width + }); + instance.onExecuted.connect(onItemExecuted); + + ++itemIndex; + } + + if ( terminalItem.status === Component.Ready ) { + instantiateTerminal(); + } else { + terminalItem.statusChanged.connect(instantiateTerminal); + } + } + + function scrollTo(index) { + if ( column.height >= flickable.height ) { + var offset = children[index].y + + (children[index].height / 2) + - (flickable.height / 2); + + var bound = column.height + - flickable.height; + + if ( offset < 0 ) { + flickable.contentY = 0; + } else if ( offset >= bound ) { + flickable.contentY = bound; + } else { + flickable.contentY = offset; + } + } + } + + function selectItem(index) { + children[activeItem].deselect(); + children[index ].select(); + + activeItem = typeof index === "number" ? index : parseInt(index); + + scrollTo(index); + } + + function selectNext() { + if ( activeItem < (children.length - 1) ) { + selectItem(activeItem + 1); + } else { + state.enterInsertMode(); + } + } + + function selectPrev() { + if ( activeItem > 0 ) { + selectItem(activeItem - 1); + } + } + + function focusCurrent() { + children[activeItem].forceActiveFocus(); + } + + function unfocusCurrent() { + children[activeItem].unfocus(); + } + + function getCurrent() { + return children[activeItem]; + } + + function get(index) { + return children[index]; + } + + function iterate(func) { + for ( var i = 0; i < children.length; i++ ) { + func(children[i]); + } + } + + Flickable { + id: flickable + + anchors.fill: parent + + boundsBehavior: Flickable.StopAtBounds + contentHeight: column.height + contentWidth: parent.width + pixelAligned: true + + Column { + id: column + + anchors { + left: parent.left + right: parent.right + } + + spacing: 10 + + onHeightChanged: scrollTo(activeItem) + } + } +} diff --git a/src/main.qml b/src/main.qml new file mode 100644 index 0000000..f7673dc --- /dev/null +++ b/src/main.qml @@ -0,0 +1,53 @@ +import QtQuick 2.0 +import QtQuick.Window 2.0 +import QtQuick.Controls 1.2 +import QtQuick.Layouts 1.1 +import Qt.labs.settings 1.0 + +ApplicationWindow { + id: root + + visible: true + + Settings { + id: settings + category: "window" + + property string background : "#161616" + } + + color: settings.background + + Component.onCompleted: { + terminalList.createItem(); + terminalList.focusCurrent(); + } + + ColumnLayout { + anchors.fill: parent + + TerminalList { + id: terminalList + + state: state + + Layout.fillHeight: true + Layout.fillWidth: true + } + + CommandInput { + id: command + + Layout.fillWidth: true + + onExecuted: state.enterNormalMode() + } + } + + StateHandler { + id: state + + terminalList: terminalList + commandInput: command + } +} diff --git a/src/ui.qrc b/src/ui.qrc new file mode 100644 index 0000000..48554f7 --- /dev/null +++ b/src/ui.qrc @@ -0,0 +1,12 @@ + + + main.qml + StateHandler.qml + list/TerminalItem.qml + list/TerminalList.qml + command/CommandInput.qml + command/commands.js + widget/EmbeddedTerminal.qml + widget/Highlighter.qml + + diff --git a/src/widget/EmbeddedTerminal.qml b/src/widget/EmbeddedTerminal.qml new file mode 100644 index 0000000..6d0dc6e --- /dev/null +++ b/src/widget/EmbeddedTerminal.qml @@ -0,0 +1,153 @@ +import QtQuick 2.0 +import QMLTermWidget 1.0 +import QtQuick.Layouts 1.1 +import Qt.labs.settings 1.0 + +Item { + id: item + + property string program + property string workingDirectory + + Settings { + id: settings + category: "terminal" + + property int initialLines : 20 + property int frameWidth : 10 + property int fontSize : 8 + property string fontFamily : "Monospace" + property string colorScheme : "cool-retro-term" + property string overlayBackground : "black" + property string overlayFontColor : "white" + } + + property int lines : settings.initialLines + + height: terminal.height + width: parent.width - settings.frameWidth + + function select() { highlighter.select() } + function deselect() { highlighter.deselect() } + function displayOverlay() { overlay.displayBriefly() } + + RowLayout { + id: container + + anchors { + left: parent.left + right: parent.right + } + + spacing: 0 + + Highlighter { + id: highlighter + + width: settings.frameWidth + Layout.fillHeight: true + } + + QMLTermWidget { + id: terminal + + font { + family: settings.fontFamily + pointSize: settings.fontSize + } + + Layout.fillWidth: true + Layout.preferredHeight: fontMetrics.height * item.lines + + colorScheme: settings.colorScheme + + session: QMLTermSession { + initialWorkingDirectory: item.workingDirectory + + shellProgram: { + return (item.program).split(" ")[0]; + } + + shellProgramArgs: { + const elements = (item.program).split(" "); + elements.shift(); + + return elements; + } + } + + Component.onCompleted: { + forceActiveFocus(); + highlighter.select(); + session.startShellProgram(); + overlay.enabled = true; + } + + onTermGetFocus: highlighter.focus() + onTermLostFocus: highlighter.unfocus() + onHeightChanged: overlay.displayBriefly(); + onWidthChanged: overlay.displayBriefly(); + + Rectangle { + id: overlay + + property bool enabled : false + + function displayBriefly() { + if ( enabled ) { animation.restart() } + } + + anchors.fill: parent + opacity: 0 + color: settings.overlayBackground + + SequentialAnimation { + id: animation + + ScriptAction { + script: overlay.opacity = 0.8 + } + + PauseAnimation { + duration: 500 + } + + NumberAnimation { + target: overlay + property: "opacity" + + easing.type: Easing.InSine + duration: 300 + from: 0.8 + to: 0 + } + } + + Text { + anchors { + horizontalCenter: overlay.horizontalCenter + verticalCenter: overlay.verticalCenter + } + + font { + family: settings.fontFamily + pointSize: settings.fontSize * 2 + } + color: settings.overlayFontColor + + text: { + return item.lines + + 'x' + + Math.floor(terminal.width / terminal.fontMetrics.width); + } + } + } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.NoButton + onWheel: { } + } + } + } +} diff --git a/src/widget/Highlighter.qml b/src/widget/Highlighter.qml new file mode 100644 index 0000000..e42aeb1 --- /dev/null +++ b/src/widget/Highlighter.qml @@ -0,0 +1,33 @@ +import QtQuick 2.0 +import Qt.labs.settings 1.0 + +Item { + Settings { + id: settings + category: "highlighter" + + property string defaultColor : "#909636" + property string focusColor : "#352F6A" + } + + function select() { bar.opacity = 1 } + function deselect() { bar.opacity = 0 } + function focus() { bar.color = settings.focusColor } + function unfocus() { bar.color = settings.defaultColor } + + Rectangle { + id: bar + + anchors.fill: parent + + opacity: 0 + color: settings.defaultColor + + Behavior on opacity { + NumberAnimation { + duration: 300 + easing.type: Easing.OutCubic + } + } + } +} -- cgit v1.2.3