ubuntu-touch-coreapps-reviewers team mailing list archive
-
ubuntu-touch-coreapps-reviewers team
-
Mailing list archive
-
Message #10106
[Merge] lp:~fboucault/ubuntu-terminal-app/tiled_view into lp:ubuntu-terminal-app
Florian Boucault has proposed merging lp:~fboucault/ubuntu-terminal-app/tiled_view into lp:ubuntu-terminal-app.
Commit message:
New feature: terminals can be organised as tiles.
Requested reviews:
Ubuntu Terminal Developers (ubuntu-terminal-dev)
For more details, see:
https://code.launchpad.net/~fboucault/ubuntu-terminal-app/tiled_view/+merge/314617
New feature: terminals can be organised as tiles.
--
Your team Ubuntu Terminal Developers is requested to review the proposed merge of lp:~fboucault/ubuntu-terminal-app/tiled_view into lp:ubuntu-terminal-app.
=== modified file 'po/com.ubuntu.terminal.pot'
--- po/com.ubuntu.terminal.pot 2017-01-06 08:39:01 +0000
+++ po/com.ubuntu.terminal.pot 2017-01-12 13:26:17 +0000
@@ -8,7 +8,7 @@
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2017-01-06 08:38+0000\n"
+"POT-Creation-Date: 2017-01-12 13:40+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@xxxxxx>\n"
@@ -29,15 +29,23 @@
msgid "Paste"
msgstr ""
-#: ../src/app/qml/AlternateActionPopover.qml:86 ../src/app/qml/TabsPage.qml:32
+#: ../src/app/qml/AlternateActionPopover.qml:86
+msgid "Split horizontally"
+msgstr ""
+
+#: ../src/app/qml/AlternateActionPopover.qml:92
+msgid "Split vertically"
+msgstr ""
+
+#: ../src/app/qml/AlternateActionPopover.qml:99 ../src/app/qml/TabsPage.qml:32
msgid "New tab"
msgstr ""
-#: ../src/app/qml/AlternateActionPopover.qml:91
+#: ../src/app/qml/AlternateActionPopover.qml:104
msgid "New window"
msgstr ""
-#: ../src/app/qml/AlternateActionPopover.qml:96
+#: ../src/app/qml/AlternateActionPopover.qml:109
msgid "Close"
msgstr ""
@@ -303,7 +311,7 @@
msgid "Tabs"
msgstr ""
-#: ../src/app/qml/TerminalPage.qml:252
+#: ../src/app/qml/TerminalPage.qml:132
msgid "Selection Mode"
msgstr ""
=== modified file 'src/app/qml/AlternateActionPopover.qml'
--- src/app/qml/AlternateActionPopover.qml 2017-01-06 08:38:45 +0000
+++ src/app/qml/AlternateActionPopover.qml 2017-01-12 13:26:17 +0000
@@ -83,6 +83,19 @@
property bool divider: true
}
Action {
+ text: i18n.tr("Split horizontally")
+ onTriggered: tiledTerminalView.splitTerminal(terminal, Qt.Vertical)
+ shortcut: settings.shortcutSplitHorizontally
+ enabled: terminal.height >= 2 * tiledTerminalView.minimumTileHeight
+ }
+ Action {
+ text: i18n.tr("Split vertically")
+ onTriggered: tiledTerminalView.splitTerminal(terminal, Qt.Horizontal)
+ shortcut: settings.shortcutSplitVertically
+ enabled: terminal.width >= 2 * tiledTerminalView.minimumTileWidth
+ property bool divider: true
+ }
+ Action {
text: i18n.tr("New tab")
onTriggered: tabsModel.addTerminalTab()
shortcut: settings.shortcutNewTab
@@ -94,7 +107,7 @@
}
Action {
text: i18n.tr("Close")
- onTriggered: tabsModel.removeItem(tabsModel.indexOf(tabsModel.currentItem))
+ onTriggered: terminal.finished()
shortcut: settings.shortcutCloseTab
}
}
=== modified file 'src/app/qml/Settings/SettingsShortcutsSection.qml'
--- src/app/qml/Settings/SettingsShortcutsSection.qml 2016-11-30 22:56:25 +0000
+++ src/app/qml/Settings/SettingsShortcutsSection.qml 2017-01-12 13:26:17 +0000
@@ -186,6 +186,36 @@
actionLabel: QT_TR_NOOP("Toggle fullscreen")
shortcutSetting: "shortcutFullscreen"
}
+ ListElement {
+ section: QT_TR_NOOP("View")
+ actionLabel: QT_TR_NOOP("Split terminal horizontally")
+ shortcutSetting: "shortcutSplitHorizontally"
+ }
+ ListElement {
+ section: QT_TR_NOOP("View")
+ actionLabel: QT_TR_NOOP("Split terminal vertically")
+ shortcutSetting: "shortcutSplitVertically"
+ }
+ ListElement {
+ section: QT_TR_NOOP("View")
+ actionLabel: QT_TR_NOOP("Navigate to terminal above")
+ shortcutSetting: "shortcutMoveToTileAbove"
+ }
+ ListElement {
+ section: QT_TR_NOOP("View")
+ actionLabel: QT_TR_NOOP("Navigate to terminal below")
+ shortcutSetting: "shortcutMoveToTileBelow"
+ }
+ ListElement {
+ section: QT_TR_NOOP("View")
+ actionLabel: QT_TR_NOOP("Navigate to terminal on the left")
+ shortcutSetting: "shortcutMoveToTileLeft"
+ }
+ ListElement {
+ section: QT_TR_NOOP("View")
+ actionLabel: QT_TR_NOOP("Navigate to terminal on the right")
+ shortcutSetting: "shortcutMoveToTileRight"
+ }
}
delegate: ShortcutRow {
=== modified file 'src/app/qml/TabsModel.qml'
--- src/app/qml/TabsModel.qml 2016-12-12 21:18:17 +0000
+++ src/app/qml/TabsModel.qml 2017-01-12 13:26:17 +0000
@@ -34,17 +34,18 @@
moveItem(from, to);
}
- property Component terminalComponent: TerminalComponent {}
+ property Component tiledViewComponent: TiledTerminalView {}
function addTerminalTab(initialWorkingDirectory) {
if (currentItem) {
- initialWorkingDirectory = currentItem.session.getWorkingDirectory();
+ initialWorkingDirectory = currentItem.focusedTerminal.session.getWorkingDirectory();
}
- var termObject = terminalComponent.createObject(terminalPage.terminalContainer,
+ var tiledView = tiledViewComponent.createObject(terminalPage.terminalContainer,
{"initialWorkingDirectory": initialWorkingDirectory,
- "visible": Qt.binding(function () { return tabsModel.currentItem === termObject})});
- tabsModel.addItem(termObject);
+ "visible": Qt.binding(function () { return tabsModel.currentItem === tiledView})});
+ tiledView.emptied.connect(function () {tabsModel.removeItem(tabsModel.indexOf(tiledView));})
+ tabsModel.addItem(tiledView);
currentIndex = tabsModel.count - 1;
}
=== modified file 'src/app/qml/TabsPage.qml'
--- src/app/qml/TabsPage.qml 2016-12-12 12:44:22 +0000
+++ src/app/qml/TabsPage.qml 2017-01-12 13:26:17 +0000
@@ -103,7 +103,7 @@
Label {
anchors { fill: blackRect; margins: units.dp(2) }
property var tab: tabsModel.itemAt(index)
- text: tab ? tab.session.title : ""
+ text: tab && tab.focusedTerminal ? tab.focusedTerminal.session.title : ""
wrapMode: Text.Wrap
color: "white"
}
=== renamed file 'src/app/qml/TerminalComponent.qml' => 'src/app/qml/Terminal.qml'
--- src/app/qml/TerminalComponent.qml 2016-12-12 12:44:22 +0000
+++ src/app/qml/Terminal.qml 2017-01-12 13:26:17 +0000
@@ -17,20 +17,21 @@
*/
import QtQuick 2.4
import Ubuntu.Components 1.3
+import Ubuntu.Components.Popups 1.3
import QMLTermWidget 1.0
import Terminal 0.1
QMLTermWidget {
id: terminal
- width: parent.width
- height: parent.height
colorScheme: settings.colorScheme
font.family: settings.fontStyle
font.pixelSize: FontUtils.sizeToPixels("medium") * settings.fontSize / 10
+ property bool isDarkBackground: ColorUtils.luminance(backgroundColor) <= 0.85
+ property color contourColor: isDarkBackground ? Qt.rgba(1.0, 1.0, 1.0, 0.4) : Qt.rgba(0.0, 0.0, 0.0, 0.2)
property string initialWorkingDirectory
- signal sessionFinished(var session);
+ signal finished()
session: QMLTermSession {
id: terminalSession
@@ -68,7 +69,7 @@
"-o", "LogLevel=Error",
"mkdir -p `dirname %1`; echo -n $$ > %1; cd %2; bash".arg(sshShellPidFile).arg(initialWorkingDirectory)]
: [])
- onFinished: tabsModel.removeItem(tabsModel.indexOf(terminal))
+ onFinished: terminal.finished()
}
property int totalLines: terminal.scrollbarMaximum - terminal.scrollbarMinimum + terminal.lines
@@ -77,4 +78,116 @@
terminalSession.startShellProgram();
forceActiveFocus();
}
+
+ // TODO: This invisible button is used to position the popover where the
+ // alternate action was called. Terrible terrible workaround!
+ Item {
+ id: hiddenButton
+ width: 1
+ height: 1
+ visible: false
+ enabled: false
+ }
+
+ TerminalInputArea {
+ id: inputArea
+ enabled: terminalPage.state != "SELECTION"
+ anchors.fill: parent
+ // FIXME: should anchor to the bottom of the window to cater for the case when the OSK is up
+
+ // This is the minimum wheel event registered by the plugin (with the current settings).
+ property real wheelValue: 40
+
+ // This is needed to fake a "flickable" scrolling.
+ swipeDelta: terminal.fontMetrics.height
+
+ // Mouse actions
+ onMouseMoveDetected: terminal.simulateMouseMove(x, y, button, buttons, modifiers);
+ onDoubleClickDetected: terminal.simulateMouseDoubleClick(x, y, button, buttons, modifiers);
+ onMousePressDetected: {
+ terminal.forceActiveFocus();
+ terminal.simulateMousePress(x, y, button, buttons, modifiers);
+ }
+ onMouseReleaseDetected: terminal.simulateMouseRelease(x, y, button, buttons, modifiers);
+ onMouseWheelDetected: terminal.simulateWheel(x, y, buttons, modifiers, angleDelta);
+
+ // Touch actions
+ onTouchPress: terminal.forceActiveFocus()
+ onTouchClick: terminal.simulateKeyPress(Qt.Key_Tab, Qt.NoModifier, true, 0, "");
+ onTouchPressAndHold: alternateAction(x, y);
+
+ // Swipe actions
+ onSwipeYDetected: {
+ if (steps > 0) {
+ simulateSwipeDown(steps);
+ } else {
+ simulateSwipeUp(-steps);
+ }
+ }
+ onSwipeXDetected: {
+ if (steps > 0) {
+ simulateSwipeRight(steps);
+ } else {
+ simulateSwipeLeft(-steps);
+ }
+ }
+ onTwoFingerSwipeYDetected: {
+ if (steps > 0) {
+ simulateDualSwipeDown(steps);
+ } else {
+ simulateDualSwipeUp(-steps);
+ }
+ }
+
+ function simulateSwipeUp(steps) {
+ while(steps > 0) {
+ terminal.simulateKeyPress(Qt.Key_Up, Qt.NoModifier, true, 0, "");
+ steps--;
+ }
+ }
+ function simulateSwipeDown(steps) {
+ while(steps > 0) {
+ terminal.simulateKeyPress(Qt.Key_Down, Qt.NoModifier, true, 0, "");
+ steps--;
+ }
+ }
+ function simulateSwipeLeft(steps) {
+ while(steps > 0) {
+ terminal.simulateKeyPress(Qt.Key_Left, Qt.NoModifier, true, 0, "");
+ steps--;
+ }
+ }
+ function simulateSwipeRight(steps) {
+ while(steps > 0) {
+ terminal.simulateKeyPress(Qt.Key_Right, Qt.NoModifier, true, 0, "");
+ steps--;
+ }
+ }
+ function simulateDualSwipeUp(steps) {
+ while(steps > 0) {
+ terminal.simulateWheel(width * 0.5, height * 0.5, Qt.NoButton, Qt.NoModifier, Qt.point(0, -wheelValue));
+ steps--;
+ }
+ }
+ function simulateDualSwipeDown(steps) {
+ while(steps > 0) {
+ terminal.simulateWheel(width * 0.5, height * 0.5, Qt.NoButton, Qt.NoModifier, Qt.point(0, wheelValue));
+ steps--;
+ }
+ }
+
+ // Semantic actions
+ onAlternateAction: {
+ // Force the hiddenButton in the event position.
+ hiddenButton.x = x;
+ hiddenButton.y = y;
+ PopupUtils.open(Qt.resolvedUrl("AlternateActionPopover.qml"),
+ hiddenButton);
+ }
+ }
+
+ QMLTermScrollbar {
+ anchors.fill: parent
+ terminal: terminal
+ }
}
=== modified file 'src/app/qml/TerminalPage.qml'
--- src/app/qml/TerminalPage.qml 2017-01-06 08:37:39 +0000
+++ src/app/qml/TerminalPage.qml 2017-01-12 13:26:17 +0000
@@ -27,7 +27,7 @@
Page {
id: terminalPage
property alias terminalContainer: terminalContainer
- property Item terminal
+ property Terminal terminal
property var tabsModel
property bool narrowLayout
theme: ThemeSettings {
@@ -50,15 +50,15 @@
top: parent.top
right: parent.right
}
- property bool isDarkBackground: ColorUtils.luminance(backgroundColor) <= 0.85
+ property bool isDarkBackground: terminalPage.terminal && terminalPage.terminal.isDarkBackground
actionColor: isDarkBackground ? "white" : "black"
backgroundColor: terminalPage.terminal ? terminalPage.terminal.backgroundColor : ""
foregroundColor: terminalPage.terminal ? terminalPage.terminal.foregroundColor : ""
- contourColor: isDarkBackground ? Qt.rgba(1.0, 1.0, 1.0, 0.4) : Qt.rgba(0.0, 0.0, 0.0, 0.2)
+ contourColor: terminalPage.terminal ? terminalPage.terminal.contourColor : ""
color: isDarkBackground ? Qt.tint(backgroundColor, "#0DFFFFFF") : Qt.tint(backgroundColor, "#0D000000")
model: terminalPage.tabsModel
function titleFromModelItem(modelItem) {
- return modelItem.session.title;
+ return modelItem.focusedTerminal ? modelItem.focusedTerminal.session.title : "";
}
actions: [
@@ -84,7 +84,6 @@
top: terminalPage.narrowLayout ? parent.top : tabsBar.bottom;
right: parent.right;
bottom: keyboardBarLoader.top
- margins: units.gu(1)
}
Binding {
@@ -94,125 +93,6 @@
}
}
- QMLTermScrollbar {
- anchors {
- top: terminalContainer.anchors.top
- bottom: terminalContainer.anchors.bottom
- left: terminalContainer.anchors.left
- right: terminalContainer.anchors.right
- }
-
- terminal: terminalPage.terminal
- z: inputArea.z + 1
- }
-
- // TODO: This invisible button is used to position the popover where the
- // alternate action was called. Terrible terrible workaround!
- Button {
- id: hiddenButton
- width: 5
- height: 5
- visible: false
- enabled: false
- }
-
- TerminalInputArea{
- id: inputArea
- anchors {
- left: terminalContainer.anchors.left
- top: terminalContainer.anchors.top
- right: terminalContainer.anchors.right
- bottom: parent.bottom
- margins: terminalContainer.anchors.margins
- }
- enabled: terminal
-
- // This is the minimum wheel event registered by the plugin (with the current settings).
- property real wheelValue: 40
-
- // This is needed to fake a "flickable" scrolling.
- swipeDelta: terminal ? terminal.fontMetrics.height : 0
-
- // Mouse actions
- onMouseMoveDetected: terminal.simulateMouseMove(x, y, button, buttons, modifiers);
- onDoubleClickDetected: terminal.simulateMouseDoubleClick(x, y, button, buttons, modifiers);
- onMousePressDetected: terminal.simulateMousePress(x, y, button, buttons, modifiers);
- onMouseReleaseDetected: terminal.simulateMouseRelease(x, y, button, buttons, modifiers);
- onMouseWheelDetected: terminal.simulateWheel(x, y, buttons, modifiers, angleDelta);
-
- // Touch actions
- onTouchClick: terminal.simulateKeyPress(Qt.Key_Tab, Qt.NoModifier, true, 0, "");
- onTouchPressAndHold: alternateAction(x, y);
-
- // Swipe actions
- onSwipeYDetected: {
- if (steps > 0) {
- simulateSwipeDown(steps);
- } else {
- simulateSwipeUp(-steps);
- }
- }
- onSwipeXDetected: {
- if (steps > 0) {
- simulateSwipeRight(steps);
- } else {
- simulateSwipeLeft(-steps);
- }
- }
- onTwoFingerSwipeYDetected: {
- if (steps > 0) {
- simulateDualSwipeDown(steps);
- } else {
- simulateDualSwipeUp(-steps);
- }
- }
-
- function simulateSwipeUp(steps) {
- while(steps > 0) {
- terminal.simulateKeyPress(Qt.Key_Up, Qt.NoModifier, true, 0, "");
- steps--;
- }
- }
- function simulateSwipeDown(steps) {
- while(steps > 0) {
- terminal.simulateKeyPress(Qt.Key_Down, Qt.NoModifier, true, 0, "");
- steps--;
- }
- }
- function simulateSwipeLeft(steps) {
- while(steps > 0) {
- terminal.simulateKeyPress(Qt.Key_Left, Qt.NoModifier, true, 0, "");
- steps--;
- }
- }
- function simulateSwipeRight(steps) {
- while(steps > 0) {
- terminal.simulateKeyPress(Qt.Key_Right, Qt.NoModifier, true, 0, "");
- steps--;
- }
- }
- function simulateDualSwipeUp(steps) {
- while(steps > 0) {
- terminal.simulateWheel(width * 0.5, height * 0.5, Qt.NoButton, Qt.NoModifier, Qt.point(0, -wheelValue));
- steps--;
- }
- }
- function simulateDualSwipeDown(steps) {
- while(steps > 0) {
- terminal.simulateWheel(width * 0.5, height * 0.5, Qt.NoButton, Qt.NoModifier, Qt.point(0, wheelValue));
- steps--;
- }
- }
-
- // Semantic actions
- onAlternateAction: {
- // Force the hiddenButton in the event position.
- hiddenButton.x = x;
- hiddenButton.y = y;
- PopupUtils.open(Qt.resolvedUrl("AlternateActionPopover.qml"), hiddenButton);
- }
- }
-
Loader {
id: keyboardBarLoader
height: active ? units.gu(5) : 0
@@ -227,7 +107,7 @@
backgroundColor: tabsBar.color
foregroundColor: tabsBar.foregroundColor
onSimulateKey: terminal.simulateKeyPress(key, mod, true, 0, "");
- onSimulateCommand: terminal.session.sendText(command);
+ onSimulateCommand: terminal.focusedTerminal.session.sendText(command);
}
}
@@ -267,7 +147,7 @@
iconName: "close"
onTriggered: {
terminalPage.state = "DEFAULT";
- PopupUtils.open(Qt.resolvedUrl("AlternateActionPopover.qml"), hiddenButton);
+ PopupUtils.open(Qt.resolvedUrl("AlternateActionPopover.qml"));
}
}
}
@@ -333,7 +213,6 @@
PropertyChanges { target: keyboardButton; visible: false }
PropertyChanges { target: bottomMessage; active: true }
PropertyChanges { target: keyboardBarLoader; enabled: false }
- PropertyChanges { target: inputArea; enabled: false }
}
]
}
=== modified file 'src/app/qml/TerminalSettings.qml'
--- src/app/qml/TerminalSettings.qml 2016-11-30 09:17:02 +0000
+++ src/app/qml/TerminalSettings.qml 2017-01-12 13:26:17 +0000
@@ -34,6 +34,12 @@
property alias shortcutCopy: innerSettings.shortcutCopy
property alias shortcutPaste: innerSettings.shortcutPaste
property alias shortcutFullscreen: innerSettings.shortcutFullscreen
+ property alias shortcutSplitHorizontally: innerSettings.shortcutSplitHorizontally
+ property alias shortcutSplitVertically: innerSettings.shortcutSplitVertically
+ property alias shortcutMoveToTileAbove: innerSettings.shortcutMoveToTileAbove
+ property alias shortcutMoveToTileBelow: innerSettings.shortcutMoveToTileBelow
+ property alias shortcutMoveToTileLeft: innerSettings.shortcutMoveToTileLeft
+ property alias shortcutMoveToTileRight: innerSettings.shortcutMoveToTileRight
readonly property int defaultFontSize: 12
readonly property int minFontSize: 4
@@ -73,6 +79,12 @@
property string shortcutCopy: "Ctrl+Shift+C"
property string shortcutPaste: "Ctrl+Shift+V"
property string shortcutFullscreen: "F11"
+ property string shortcutSplitHorizontally: "Ctrl+Shift+E"
+ property string shortcutSplitVertically: "Ctrl+Shift+O"
+ property string shortcutMoveToTileAbove: "Alt+Up"
+ property string shortcutMoveToTileBelow: "Alt+Down"
+ property string shortcutMoveToTileLeft: "Alt+Left"
+ property string shortcutMoveToTileRight: "Alt+Right"
}
// Load the keyboard profiles.
=== modified file 'src/app/qml/TerminalWindow.qml'
--- src/app/qml/TerminalWindow.qml 2016-12-12 13:41:58 +0000
+++ src/app/qml/TerminalWindow.qml 2017-01-12 13:26:17 +0000
@@ -25,7 +25,7 @@
Window {
id: terminalWindow
- title: tabsModel.currentItem ? tabsModel.currentItem.session.title : ""
+ title: tabsModel.currentItem && tabsModel.currentItem.focusedTerminal ? tabsModel.currentItem.focusedTerminal.session.title : ""
color: terminalPage.active && terminalPage.terminal ? terminalPage.terminal.backgroundColor : theme.palette.selected.overlay
contentOrientation: Screen.orientation
@@ -35,7 +35,7 @@
Binding {
target: terminalAppRoot
property: "focusedTerminal"
- value: tabsModel.currentItem
+ value: tabsModel.currentItem ? tabsModel.currentItem.focusedTerminal : null
when: terminalWindow.active
}
@@ -71,6 +71,21 @@
}
}
+ property TiledTerminalView tiledTerminalView: tabsModel.currentItem
+ Shortcut {
+ sequence: settings.shortcutSplitVertically
+ onActivated: tiledTerminalView.splitTerminal(tiledTerminalView.focusedTerminal,
+ Qt.Horizontal)
+ enabled: tiledTerminalView.focusedTerminal.width >= 2 * tiledTerminalView.minimumTileWidth
+ }
+
+ Shortcut {
+ sequence: settings.shortcutSplitHorizontally
+ onActivated: tiledTerminalView.splitTerminal(tiledTerminalView.focusedTerminal,
+ Qt.Vertical)
+ enabled: tiledTerminalView.focusedTerminal.height >= 2 * tiledTerminalView.minimumTileHeight
+ }
+
Shortcut {
sequence: settings.shortcutNewTab
onActivated: tabsModel.addTerminalTab()
@@ -98,12 +113,12 @@
Shortcut {
sequence: settings.shortcutCopy
- onActivated: tabsModel.currentItem.copyClipboard()
+ onActivated: tabsModel.currentItem.focusedTerminal.copyClipboard()
}
Shortcut {
sequence: settings.shortcutPaste
- onActivated: tabsModel.currentItem.pasteClipboard()
+ onActivated: tabsModel.currentItem.focusedTerminal.pasteClipboard()
}
Shortcut {
@@ -141,7 +156,7 @@
TerminalPage {
id: terminalPage
tabsModel: tabsModel
- terminal: tabsModel.currentItem
+ terminal: tabsModel.currentItem ? tabsModel.currentItem.focusedTerminal : null
narrowLayout: terminalWindow.narrowLayout
// Hide terminal data when the access is still not granted
layer.enabled: authService.isDialogVisible
=== added file 'src/app/qml/TiledTerminalView.qml'
--- src/app/qml/TiledTerminalView.qml 1970-01-01 00:00:00 +0000
+++ src/app/qml/TiledTerminalView.qml 2017-01-12 13:26:17 +0000
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016-2017 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored-by: Florian Boucault <florian.boucault@xxxxxxxxxxxxx>
+ */
+import QtQuick 2.5
+import Ubuntu.Components 1.3
+
+TiledView {
+ id: tiledTerminalView
+ anchors.fill: parent
+
+ property string initialWorkingDirectory
+ property Terminal focusedTerminal
+ signal emptied
+ onCountChanged: if (count == 0) emptied()
+
+ function splitTerminal(terminal, orientation) {
+ var initialWorkingDirectory = focusedTerminal.session.getWorkingDirectory();
+ var newTerminal = terminalComponent.createObject(tiledTerminalView,
+ {"initialWorkingDirectory": initialWorkingDirectory});
+ tiledTerminalView.setOrientation(terminal, orientation);
+ tiledTerminalView.add(terminal, newTerminal, Qt.AlignTrailing);
+ }
+
+ handleDelegate: Rectangle {
+ implicitWidth: units.dp(1)
+ implicitHeight: units.dp(1)
+ color: focusedTerminal ? focusedTerminal.contourColor : ""
+ }
+
+ Component.onCompleted: {
+ var newTerminal = terminalComponent.createObject(tiledTerminalView,
+ {"initialWorkingDirectory": initialWorkingDirectory});
+ setRootItem(newTerminal);
+ }
+
+ function moveFocus(direction) {
+ var terminal = tiledTerminalView.closestTileInDirection(focusedTerminal, direction);
+ if (terminal) {
+ terminal.focus = true;
+ }
+ }
+
+ Shortcut {
+ sequence: settings.shortcutMoveToTileRight
+ enabled: tiledTerminalView.focus
+ onActivated: moveFocus(Qt.AlignRight)
+ }
+
+ Shortcut {
+ sequence: settings.shortcutMoveToTileLeft
+ enabled: tiledTerminalView.focus
+ onActivated: moveFocus(Qt.AlignLeft)
+ }
+
+ Shortcut {
+ sequence: settings.shortcutMoveToTileAbove
+ enabled: tiledTerminalView.focus
+ onActivated: moveFocus(Qt.AlignTop)
+ }
+
+ Shortcut {
+ sequence: settings.shortcutMoveToTileBelow
+ enabled: tiledTerminalView.focus
+ onActivated: moveFocus(Qt.AlignBottom)
+ }
+
+ property real minimumTileWidth: units.gu(10)
+ property real minimumTileHeight: units.gu(10)
+ property Component terminalComponent: Terminal {
+ id: terminal
+ Component.onCompleted: if (focus) tiledTerminalView.focusedTerminal = terminal
+ onFocusChanged: if (focus) tiledTerminalView.focusedTerminal = terminal
+ onFinished: {
+ if (terminal.focus) {
+ var nextTerminal = tiledTerminalView.closestTile(terminal);
+ if (nextTerminal) nextTerminal.focus = true;
+ }
+ tiledTerminalView.remove(terminal);
+ terminal.destroy();
+ }
+ }
+}
=== added file 'src/app/qml/TiledView.qml'
--- src/app/qml/TiledView.qml 1970-01-01 00:00:00 +0000
+++ src/app/qml/TiledView.qml 2017-01-12 13:26:17 +0000
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2016-2017 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored-by: Florian Boucault <florian.boucault@xxxxxxxxxxxxx>
+ */
+import QtQuick 2.5
+import "binarytree.js" as BinaryTree
+
+FocusScope {
+ id: tiledView
+
+ property Component handleDelegate: Rectangle {
+ implicitWidth: units.dp(1)
+ implicitHeight: units.dp(1)
+ color: "white"
+ }
+
+ property int count: 0
+
+ // FIXME: odd semantics: what if setRootItem is called later?
+ function setRootItem(rootItem) {
+ if (rootItem && rootItem === __rootNode.value) {
+ return null;
+ }
+
+ var oldRoot = __rootNode.value;
+ if (rootItem) {
+ count = 1;
+ } else {
+ count = 0;
+ __rootNode.cleanup();
+ }
+ __rootNode.setValue(rootItem);
+
+ return oldRoot;
+ }
+
+ property var __rootNode: new BinaryTree.Node()
+ Component.onDestruction: __rootNode.cleanup()
+
+ Component.onCompleted: {
+ __rootNode.setWidth(width);
+ __rootNode.setHeight(height);
+ }
+
+ onWidthChanged: __rootNode.setWidth(width)
+ onHeightChanged: __rootNode.setHeight(height)
+
+ Component {
+ id: separatorComponent
+ TiledViewSeparator {
+ handleDelegate: tiledView.handleDelegate
+ }
+ }
+
+ function add(obj, newObj, side) {
+ var node = __rootNode.findNodeWithValue(obj);
+ var otherSide;
+ if (side == Qt.AlignLeading) {
+ otherSide = Qt.AlignTrailing;
+ } else {
+ otherSide = Qt.AlignLeading;
+ }
+
+ node.value = null;
+ node.setLeftRatio(0.5);
+ var separator = separatorComponent.createObject(tiledView, {"node": node});
+ node.setSeparator(separator);
+
+ var nodeSide = new BinaryTree.Node();
+ nodeSide.setValue(newObj);
+ node.setChild(side, nodeSide);
+
+ var nodeOtherSide = new BinaryTree.Node();
+ nodeOtherSide.setValue(obj);
+ node.setChild(otherSide, nodeOtherSide);
+ count += 1;
+ }
+
+ function remove(obj) {
+ var node = __rootNode.findNodeWithValue(obj);
+ var sibling = node.getSibling();
+ if (sibling) {
+ node.parent.copy(sibling);
+ }
+ count -= 1;
+ }
+
+ function closestTile(obj) {
+ var node = __rootNode.findNodeWithValue(obj);
+ var sibling = node.closestNodeWithValue();
+ if (sibling) {
+ return sibling.value;
+ } else {
+ return null;
+ }
+ }
+
+ function closestTileInDirection(obj, direction) {
+ var node = __rootNode.findNodeWithValue(obj);
+ var closestNode = node.closestNodeWithValueInDirection(direction);
+ if (closestNode && closestNode.value) {
+ return closestNode.value;
+ } else {
+ return null;
+ }
+ }
+
+ function getOrientation(obj) {
+ var node = __rootNode.findNodeWithValue(obj);
+ return node.orientation;
+ }
+
+ function setOrientation(obj, orientation) {
+ var node = __rootNode.findNodeWithValue(obj);
+ node.setOrientation(orientation);
+ }
+
+ function move(obj, targetObj, side) {
+ remove(obj);
+ add(targetObj, obj, side);
+ }
+}
=== added file 'src/app/qml/TiledViewSeparator.qml'
--- src/app/qml/TiledViewSeparator.qml 1970-01-01 00:00:00 +0000
+++ src/app/qml/TiledViewSeparator.qml 2017-01-12 13:26:17 +0000
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016-2017 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored-by: Florian Boucault <florian.boucault@xxxxxxxxxxxxx>
+ */
+import QtQuick 2.4
+import Ubuntu.Components 1.3
+
+Item {
+ id: separator
+
+ property int orientation: Qt.Horizontal
+ property var node
+ property Component handleDelegate
+
+ Loader {
+ id: handleLoader
+ sourceComponent: handleDelegate
+ anchors.fill: parent
+ }
+
+ implicitWidth: handleLoader.implicitWidth
+ implicitHeight: handleLoader.implicitHeight
+ z: 1
+
+ MouseArea {
+ anchors.centerIn: parent
+ width: orientation == Qt.Vertical ? units.gu(1) : parent.width
+ height: orientation == Qt.Vertical ? parent.height : units.gu(1)
+ cursorShape: orientation == Qt.Horizontal ? Qt.SizeVerCursor : Qt.SizeHorCursor
+ drag {
+ axis: orientation == Qt.Horizontal ? Drag.YAxis : Drag.XAxis
+ target: resizer
+ smoothed: false
+ }
+ onPressed: {
+ resizer.initialRatio = node.leftRatio;
+ resizer.x = 0;
+ resizer.y = 0;
+ }
+ enabled: separator.visible
+ }
+
+ function clamp(value, min, max) {
+ return Math.min(Math.max(min, value), max);
+ }
+
+ Item {
+ id: resizer
+ property real initialRatio
+ property real minimumRatio: 0.1
+ parent: null
+ onXChanged: {
+ var ratio = initialRatio + x / node.width;
+ ratio = clamp(ratio, minimumRatio, 1.0-minimumRatio);
+ node.setLeftRatio(ratio);
+ }
+ onYChanged: {
+ var ratio = initialRatio + y / node.height;
+ ratio = clamp(ratio, minimumRatio, 1.0-minimumRatio);
+ node.setLeftRatio(ratio);
+ }
+ }
+}
=== added file 'src/app/qml/binarytree.js'
--- src/app/qml/binarytree.js 1970-01-01 00:00:00 +0000
+++ src/app/qml/binarytree.js 2017-01-12 13:26:17 +0000
@@ -0,0 +1,378 @@
+/*
+ * Copyright (C) 2016-2017 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored-by: Florian Boucault <florian.boucault@xxxxxxxxxxxxx>
+ */
+.pragma library
+
+function Node() {
+ this.value = null;
+ this.parent = null;
+ this.left = null;
+ this.right = null;
+ this.separator = null;
+ this.leftRatio = 0.5;
+ this.rightRatio = 0.5;
+ this.orientation = Qt.Horizontal;
+ this.originX = 0;
+ this.originY = 0;
+ this.x = 0;
+ this.y = 0;
+ this.width = 0;
+ this.height = 0;
+}
+
+Node.prototype.cleanup = function cleanup(value) {
+ if (this.separator) {
+ this.separator.destroy();
+ this.separator = null;
+ }
+ this.parent = null;
+ this.value = null;
+ if (this.left) {
+ this.left.cleanup();
+ this.left = null;
+ }
+ if (this.right) {
+ this.right.cleanup();
+ this.right = null;
+ }
+}
+
+Node.prototype.setValue = function setValue(value) {
+ this.value = value;
+ this.updateX();
+ this.updateY();
+ this.updateWidth();
+ this.updateHeight();
+}
+
+Node.prototype.updateWidth = function updateWidth() {
+ if (this.value) {
+ this.value.width = this.width;
+ } else if (this.orientation == Qt.Horizontal) {
+ if (this.right && !this.left) {
+ this.right.setWidth(this.width);
+ this.right.setX(0);
+ }
+ if (this.left && !this.right) {
+ this.left.setWidth(this.width);
+ this.left.setX(0);
+ }
+ if (this.right && this.left) {
+ this.left.setWidth(this.width * this.leftRatio);
+ this.left.setX(0);
+ this.right.setWidth(this.width * this.rightRatio);
+ this.right.setX(this.left.width);
+ }
+ } else if (this.orientation == Qt.Vertical) {
+ if (this.left) {
+ this.left.setWidth(this.width);
+ this.left.setX(0);
+ }
+ if (this.right) {
+ this.right.setWidth(this.width);
+ this.right.setX(0);
+ }
+ }
+ this.updateSeparator();
+}
+
+Node.prototype.setWidth = function setWidth(width) {
+ this.width = width;
+ this.updateWidth();
+};
+
+Node.prototype.updateHeight = function updateHeight() {
+ if (this.value) {
+ this.value.height = this.height;
+ } else if (this.orientation == Qt.Vertical) {
+ if (this.right && !this.left) {
+ this.right.setHeight(this.height);
+ this.right.setY(0);
+ }
+ if (this.left && !this.right) {
+ this.left.setHeight(this.height);
+ this.left.setY(0);
+ }
+ if (this.right && this.left) {
+ this.left.setHeight(this.height * this.leftRatio);
+ this.left.setY(0);
+ this.right.setHeight(this.height * this.rightRatio);
+ this.right.setY(this.left.height);
+ }
+ } else if (this.orientation == Qt.Horizontal) {
+ if (this.left) {
+ this.left.setHeight(this.height);
+ this.left.setY(0);
+ }
+ if (this.right) {
+ this.right.setHeight(this.height);
+ this.right.setY(0);
+ }
+ }
+ this.updateSeparator();
+}
+
+Node.prototype.setHeight = function setHeight(height) {
+ this.height = height;
+ this.updateHeight();
+};
+
+Node.prototype.setLeftRatio = function setLeftRatio(ratio) {
+ this.leftRatio = ratio;
+ this.rightRatio = 1.0 - this.leftRatio;
+ if (this.orientation == Qt.Horizontal) {
+ this.updateWidth();
+ } else {
+ this.updateHeight();
+ }
+};
+
+Node.prototype.setRightRatio = function setRightRatio(ratio) {
+ this.rightRatio = ratio;
+ this.leftRatio = 1.0 - this.rightRatio;
+ if (this.orientation == Qt.Horizontal) {
+ this.updateWidth();
+ } else {
+ this.updateHeight();
+ }
+};
+
+Node.prototype.setChild = function setChild(side, childNode) {
+ switch (side) {
+ case Qt.AlignLeading:
+ if (this.left) {
+ // FIXME: breaks copy()
+// this.left.cleanup();
+ }
+ this.left = childNode;
+ break;
+ case Qt.AlignTrailing:
+ if (this.right) {
+ // FIXME: breaks copy()
+// this.right.cleanup();
+ }
+ this.right = childNode;
+ break;
+ default:
+ break;
+ }
+ if (childNode) {
+ childNode.parent = this;
+ }
+ this.updateX();
+ this.updateY();
+ this.updateWidth();
+ this.updateHeight();
+}
+
+Node.prototype.updateSeparator = function updateSeparator() {
+ if (this.separator) {
+ this.separator.orientation = this.orientation == Qt.Vertical ? Qt.Horizontal : Qt.Vertical;
+ if (this.left && this.right) {
+ // FIXME: separator should be centered
+ this.separator.x = this.right.originX + this.right.x;
+ this.separator.y = this.right.originY + this.right.y;
+ if (this.separator.orientation == Qt.Vertical) {
+ this.separator.width = this.separator.implicitWidth;
+ this.separator.height = this.right.height;
+ } else if (this.separator.orientation == Qt.Horizontal) {
+ this.separator.width = this.right.width;
+ this.separator.height = this.separator.implicitHeight;
+ }
+ this.separator.visible = true;
+ } else {
+ this.separator.visible = false;
+ }
+ }
+}
+
+Node.prototype.setSeparator = function setSeparator(separator) {
+ this.separator = separator;
+ this.updateSeparator();
+}
+
+Node.prototype.copy = function copy(otherNode) {
+ if (!otherNode) return;
+ var newParent = otherNode.parent;
+
+ this.orientation = otherNode.orientation;
+ this.setValue(otherNode.value);
+ this.setChild(Qt.AlignLeading, otherNode.left);
+ this.setChild(Qt.AlignTrailing, otherNode.right);
+
+ if (newParent.left === otherNode) {
+ newParent.setChild(Qt.AlignLeading, this);
+ } else if (otherNode.parent.right === otherNode) {
+ newParent.setChild(Qt.AlignTrailing, this);
+ }
+ otherNode.left = null;
+ otherNode.right = null;
+ otherNode.cleanup();
+ this.updateX();
+ this.updateY();
+ this.updateWidth();
+ this.updateHeight();
+}
+
+Node.prototype.setOrientation = function setOrientation(orientation) {
+ this.orientation = orientation;
+ this.updateWidth();
+ this.updateHeight();
+ this.updateSeparator();
+}
+
+Node.prototype.updateX = function updateX() {
+ var absoluteX = this.originX + this.x;
+ if (this.value) {
+ this.value.x = Math.round(absoluteX);
+ } else {
+ if (this.left) {
+ this.left.setOriginX(absoluteX);
+ }
+ if (this.right) {
+ this.right.setOriginX(absoluteX);
+ }
+ }
+ this.updateSeparator();
+}
+
+Node.prototype.setX = function setX(x) {
+ this.x = x;
+ this.updateX();
+};
+
+Node.prototype.setOriginX = function setOriginX(x) {
+ this.originX = x;
+ this.updateX();
+};
+
+Node.prototype.updateY = function updateY() {
+ var absoluteY = this.originY + this.y;
+ if (this.value) {
+ this.value.y = Math.round(absoluteY);
+ } else {
+ if (this.left) {
+ this.left.setOriginY(absoluteY);
+ }
+ if (this.right) {
+ this.right.setOriginY(absoluteY);
+ }
+ }
+ this.updateSeparator();
+}
+
+Node.prototype.setY = function setY(y) {
+ this.y = y;
+ this.updateY();
+};
+
+Node.prototype.setOriginY = function setOriginY(y) {
+ this.originY = y;
+ this.updateY();
+};
+
+Node.prototype.getSibling = function getSibling() {
+ if (this.parent) {
+ if (this.parent.left === this) {
+ return this.parent.right;
+ } else {
+ return this.parent.left;
+ }
+ }
+ return null;
+}
+
+Node.prototype.findNodeWithValue = function findNodeWithValue(value) {
+ if (this.value === value) return this;
+ var result;
+ if (this.left) {
+ result = this.left.findNodeWithValue(value);
+ if (result) {
+ return result;
+ }
+ }
+ if (this.right) {
+ result = this.right.findNodeWithValue(value);
+ if (result) {
+ return result;
+ }
+ }
+ return null;
+};
+
+Node.prototype.closestChildWithValue = function closestChildWithValue(sides) {
+ var currentLevelNodes = [this];
+ var nextLevelNodes = [];
+ while (currentLevelNodes.length != 0) {
+ for (var i=0; i<currentLevelNodes.length; i++) {
+ var node = currentLevelNodes[i];
+ if (node.value) {
+ return node;
+ }
+ if ((sides == undefined || sides & Qt.AlignLeading) && node.left) {
+ nextLevelNodes.push(node.left);
+ }
+ if ((sides == undefined || sides & Qt.AlignTrailing) && node.right) {
+ nextLevelNodes.push(node.right);
+ }
+ }
+ currentLevelNodes = nextLevelNodes;
+ nextLevelNodes = [];
+ }
+ return null;
+}
+
+Node.prototype.closestNodeWithValue = function closestNodeWithValue() {
+ // explore sibling hierarchy
+ var sibling = this.getSibling();
+ if (sibling) {
+ var closestChild = sibling.closestChildWithValue(Qt.AlignLeading | Qt.AlignTrailing);
+ if (closestChild) {
+ return closestChild;
+ }
+ }
+ // explore parent's sibling hierarchy
+ if (this.parent) {
+ var parentSibling = this.parent.getSibling();
+ if (parentSibling) {
+ var closestChild = parentSibling.closestChildWithValue(Qt.AlignLeading | Qt.AlignTrailing);
+ if (closestChild) {
+ return closestChild;
+ }
+ }
+ }
+ return null;
+};
+
+Node.prototype.closestNodeWithValueInDirection = function closestNodeWithValueInDirection(direction) {
+ if (this.parent) {
+ if (this.parent.left === this) {
+ if ((direction == Qt.AlignRight && this.parent.orientation == Qt.Horizontal) ||
+ (direction == Qt.AlignBottom && this.parent.orientation == Qt.Vertical)) {
+ return this.parent.right.closestChildWithValue(Qt.AlignLeading);
+ }
+ } else if (this.parent.right === this) {
+ if ((direction == Qt.AlignLeft && this.parent.orientation == Qt.Horizontal) ||
+ (direction == Qt.AlignTop && this.parent.orientation == Qt.Vertical)) {
+ return this.parent.left.closestChildWithValue(Qt.AlignTrailing);
+ }
+ }
+ return this.parent.closestNodeWithValueInDirection(direction);
+ } else {
+ return null;
+ }
+}
=== modified file 'src/plugin/qmltermwidget/lib/TerminalDisplay.h'
--- src/plugin/qmltermwidget/lib/TerminalDisplay.h 2017-01-06 08:37:18 +0000
+++ src/plugin/qmltermwidget/lib/TerminalDisplay.h 2017-01-12 13:26:17 +0000
@@ -898,8 +898,8 @@
//the delay in milliseconds between redrawing blinking text
static const int TEXT_BLINK_DELAY = 500;
- static const int DEFAULT_LEFT_MARGIN = 1;
- static const int DEFAULT_TOP_MARGIN = 1;
+ static const int DEFAULT_LEFT_MARGIN = 8;
+ static const int DEFAULT_TOP_MARGIN = 8;
// QMLTermWidget port functions
QFont m_font;
=== modified file 'tests/CMakeLists.txt'
--- tests/CMakeLists.txt 2015-02-24 23:23:36 +0000
+++ tests/CMakeLists.txt 2017-01-12 13:26:17 +0000
@@ -1,1 +1,2 @@
add_subdirectory(autopilot)
+add_subdirectory(qtquicktest)
=== added directory 'tests/qtquicktest'
=== added file 'tests/qtquicktest/CMakeLists.txt'
--- tests/qtquicktest/CMakeLists.txt 1970-01-01 00:00:00 +0000
+++ tests/qtquicktest/CMakeLists.txt 2017-01-12 13:26:17 +0000
@@ -0,0 +1,20 @@
+find_program(XVFB_RUN_BIN xvfb-run)
+if(NOT XVFB_RUN_BIN)
+ message(FATAL_ERROR "Could not find xvfb-run, please install the xvfb package")
+endif()
+set(XVFB_RUN_CMD ${XVFB_RUN_BIN} -a -s "-screen 0 1024x768x24")
+
+include(FindPkgConfig)
+find_package(Qt5Core)
+
+# copy qml test files to build directory
+if(NOT "${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}")
+add_custom_target(qmlTestFiles ALL
+ COMMAND cp ${CMAKE_CURRENT_SOURCE_DIR}/*.qml ${CMAKE_CURRENT_BINARY_DIR}
+)
+endif(NOT "${CMAKE_CURRENT_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_BINARY_DIR}")
+
+set(QTQUICK_TEST tst_qtquicktest)
+add_executable(${QTQUICK_TEST} qtquicktest.cpp)
+qt5_use_modules(${QTQUICK_TEST} Core Qml Quick Test QuickTest)
+add_test(${QTQUICK_TEST} ${XVFB_RUN_CMD} ${CMAKE_CURRENT_BINARY_DIR}/${QTQUICK_TEST})
=== added file 'tests/qtquicktest/qtquicktest.cpp'
--- tests/qtquicktest/qtquicktest.cpp 1970-01-01 00:00:00 +0000
+++ tests/qtquicktest/qtquicktest.cpp 2017-01-12 13:26:17 +0000
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2012 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 3.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <QtQuickTest/quicktest.h>
+QUICK_TEST_MAIN(qtquicktest)
=== added file 'tests/qtquicktest/tst_TiledView.qml'
--- tests/qtquicktest/tst_TiledView.qml 1970-01-01 00:00:00 +0000
+++ tests/qtquicktest/tst_TiledView.qml 2017-01-12 13:26:17 +0000
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) 2017 Canonical Ltd
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 3 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authored-by: Florian Boucault <florian.boucault@xxxxxxxxxxxxx>
+ */
+
+import QtQuick 2.4
+import QtTest 1.0
+import "../../src/app/qml"
+
+TestCase {
+ name: "TiledView"
+
+ TiledView {
+ id: tiledView
+ width: 800
+ height: 600
+ }
+
+ property Component objectComponent: Rectangle {
+ width: 100
+ height: 50
+ color: "red"
+ }
+
+ SignalSpy {
+ id: countSpy
+ target: tiledView
+ signalName: "countChanged"
+ }
+
+ function init() {
+ countSpy.clear();
+ }
+
+ function test_defaults() {
+ compare(tiledView.count, 0);
+ }
+
+ function test_setRootItemValid() {
+ var newObject = objectComponent.createObject(tiledView);
+ var oldRoot = tiledView.setRootItem(newObject);
+ compare(oldRoot, null);
+ compare(countSpy.count, 1);
+ compare(tiledView.count, 1);
+ compare(newObject.width, tiledView.width);
+ compare(newObject.height, tiledView.height);
+ compare(tiledView.getOrientation(newObject), Qt.Horizontal);
+ tiledView.setRootItem(null);
+ newObject.destroy();
+ }
+
+ function test_setRootItemNull() {
+ var oldRoot = tiledView.setRootItem(null);
+ compare(oldRoot, null);
+ compare(countSpy.count, 0);
+ compare(tiledView.count, 0);
+ }
+
+ function test_resetRootItem() {
+ // set to new object
+ var newObject = objectComponent.createObject(tiledView);
+ var oldRoot = tiledView.setRootItem(newObject);
+ compare(oldRoot, null);
+ compare(countSpy.count, 1);
+ compare(tiledView.count, 1);
+
+ // set to same object
+ oldRoot = tiledView.setRootItem(newObject);
+ compare(oldRoot, null);
+ compare(countSpy.count, 1);
+ compare(tiledView.count, 1);
+
+ // set to another new object
+ var newObject2 = objectComponent.createObject(tiledView);
+ oldRoot = tiledView.setRootItem(newObject2);
+ compare(oldRoot, newObject);
+ compare(countSpy.count, 1);
+ compare(tiledView.count, 1);
+
+ // set to null
+ oldRoot = tiledView.setRootItem(null);
+ compare(oldRoot, newObject2);
+ compare(countSpy.count, 2);
+ compare(tiledView.count, 0);
+
+ newObject.destroy();
+ newObject2.destroy();
+ }
+
+ function verifySetRootItem(object) {
+ tiledView.setRootItem(object);
+ compare(tiledView.count, 1);
+ compare(object.width, tiledView.width);
+ compare(object.height, tiledView.height);
+ }
+
+ function verifySetOrientation(object, orientation) {
+ tiledView.setOrientation(object, orientation);
+ compare(tiledView.getOrientation(object), orientation);
+ }
+
+ function verifyAdd(object, newObject, side) {
+ var previousX = object.x;
+ var previousY = object.y;
+ var previousWidth = object.width;
+ var previousHeight = object.height;
+ var previousCount = tiledView.count;
+ var orientation = tiledView.getOrientation(object);
+
+ tiledView.add(object, newObject, side);
+ compare(tiledView.count, previousCount+1);
+ compare(tiledView.getOrientation(object), Qt.Horizontal);
+ compare(tiledView.getOrientation(newObject), Qt.Horizontal);
+
+ if (orientation == Qt.Horizontal) {
+ compare(object.width, previousWidth / 2);
+ compare(object.height, previousHeight);
+ compare(newObject.width, previousWidth / 2);
+ compare(newObject.height, previousHeight);
+ if (side == Qt.AlignTrailing) {
+ compare(object.x, previousX);
+ compare(object.y, previousY);
+ compare(newObject.x, previousX + Math.round(previousWidth / 2));
+ compare(newObject.y, previousY);
+ } else if (side == Qt.AlignLeading) {
+ compare(newObject.x, previousX);
+ compare(newObject.y, previousY);
+ compare(object.x, previousX + Math.round(previousWidth / 2));
+ compare(object.y, previousY);
+ }
+ } else if (orientation == Qt.Vertical) {
+ compare(object.width, previousWidth);
+ compare(object.height, previousHeight / 2);
+ compare(newObject.width, previousWidth);
+ compare(newObject.height, previousHeight / 2);
+ if (side == Qt.AlignTrailing) {
+ compare(object.x, previousX);
+ compare(object.y, previousY);
+ compare(newObject.x, previousX);
+ compare(newObject.y, previousY + Math.round(previousHeight / 2));
+ } else if (side == Qt.AlignLeading) {
+ compare(newObject.x, previousX);
+ compare(newObject.y, previousY);
+ compare(object.x, previousX);
+ compare(object.y, previousY + Math.round(previousHeight / 2));
+ }
+ }
+ }
+
+ function verifyRemove(object) {
+ var node = tiledView.__rootNode.findNodeWithValue(object);
+ var siblingNode = node.getSibling();
+ var siblingObject = siblingNode.value;
+
+ var side;
+ if (node.parent.left === node) {
+ side = Qt.AlignLeading;
+ } else {
+ side = Qt.AlignTrailing;
+ }
+ var orientation = node.parent.orientation;
+
+ var expectedX;
+ var expectedY;
+ var expectedWidth;
+ var expectedHeight;
+ if (orientation == Qt.Horizontal) {
+ expectedWidth = object.width + siblingNode.width;
+ expectedHeight = siblingNode.height;
+ if (side == Qt.AlignTrailing) {
+ expectedX = siblingNode.x;
+ expectedY = siblingNode.y;
+ } else if (side == Qt.AlignLeading) {
+ expectedX = object.x;
+ expectedY = siblingNode.y;
+ }
+ } else if (orientation == Qt.Vertical) {
+ expectedWidth = siblingNode.width;
+ expectedHeight = object.height + siblingNode.height;
+ if (side == Qt.AlignTrailing) {
+ expectedX = siblingNode.x;
+ expectedY = siblingNode.y;
+ } else if (side == Qt.AlignLeading) {
+ expectedX = siblingNode.x;
+ expectedY = object.y;
+ }
+ }
+
+ var previousCount = tiledView.count;
+
+ tiledView.remove(object);
+ // TODO: we verify that the resulting node has the correct x/y,width/height
+ // but we could go further and verify that all its children also do
+ var removeNode = tiledView.__rootNode.findNodeWithValue(siblingObject);
+ compare(tiledView.count, previousCount-1);
+ compare(removeNode.width, expectedWidth);
+ compare(removeNode.height, expectedHeight);
+ compare(removeNode.x, expectedX);
+ compare(removeNode.y, expectedY);
+ }
+
+ function test_simpleAdd_data() {
+ return [
+ {orientation: Qt.Horizontal, side: Qt.AlignTrailing},
+ {orientation: Qt.Vertical, side: Qt.AlignTrailing},
+ {orientation: Qt.Horizontal, side: Qt.AlignLeading},
+ {orientation: Qt.Vertical, side: Qt.AlignLeading},
+ ];
+ }
+
+ function test_simpleAdd(data) {
+ var rootObject = objectComponent.createObject(tiledView);
+ verifySetRootItem(rootObject);
+ verifySetOrientation(rootObject, data.orientation);
+
+ var newObject = objectComponent.createObject(tiledView);
+ verifyAdd(rootObject, newObject, data.side);
+
+ tiledView.setRootItem(null);
+ rootObject.destroy();
+ newObject.destroy();
+ }
+
+ function test_nestedAdds() {
+ var objects = [];
+
+ objects["0"] = objectComponent.createObject(tiledView);
+ verifySetRootItem(objects["0"]);
+
+ objects["1"] = objectComponent.createObject(tiledView);
+ verifySetOrientation(objects["0"], Qt.Horizontal);
+ verifyAdd(objects["0"], objects["1"], Qt.AlignTrailing);
+
+ objects["2"] = objectComponent.createObject(tiledView);
+ verifySetOrientation(objects["1"], Qt.Horizontal);
+ verifyAdd(objects["1"], objects["2"], Qt.AlignTrailing);
+
+ objects["3"] = objectComponent.createObject(tiledView);
+ verifySetOrientation(objects["2"], Qt.Horizontal);
+ verifyAdd(objects["2"], objects["3"], Qt.AlignTrailing);
+
+ objects["4"] = objectComponent.createObject(tiledView);
+ verifySetOrientation(objects["3"], Qt.Horizontal);
+ verifyAdd(objects["3"], objects["4"], Qt.AlignTrailing);
+
+ objects["5"] = objectComponent.createObject(tiledView);
+ verifySetOrientation(objects["4"], Qt.Horizontal);
+ verifyAdd(objects["4"], objects["5"], Qt.AlignTrailing);
+
+ objects["6"] = objectComponent.createObject(tiledView);
+ verifySetOrientation(objects["5"], Qt.Horizontal);
+ verifyAdd(objects["5"], objects["6"], Qt.AlignTrailing);
+
+ tiledView.setRootItem(null);
+ for (var i=0; i<objects.length; i++) {
+ objects[i].destroy();
+ }
+ }
+
+ function test_resizeView() {
+ var leftObject = objectComponent.createObject(tiledView);
+ verifySetRootItem(leftObject);
+
+ var bottomRightObject = objectComponent.createObject(tiledView);
+ verifySetOrientation(leftObject, Qt.Horizontal);
+ verifyAdd(leftObject, bottomRightObject, Qt.AlignTrailing);
+
+ var topRightObject = objectComponent.createObject(tiledView);
+ verifySetOrientation(bottomRightObject, Qt.Vertical);
+ verifyAdd(bottomRightObject, topRightObject, Qt.AlignLeading);
+
+ var initialWidth = tiledView.width;
+ var initialHeight = tiledView.height;
+ var factor = 0.7;
+
+ // storing sizes before resizing
+ var sizes = {"leftObject": {"width": leftObject.width, "height": leftObject.height},
+ "bottomRightObject": {"width": bottomRightObject.width, "height": bottomRightObject.height},
+ "topRightObject": {"width": topRightObject.width, "height": topRightObject.height}};
+ tiledView.width = initialWidth * factor;
+ compare(leftObject.width, sizes.leftObject.width * factor);
+ compare(bottomRightObject.width, sizes.bottomRightObject.width * factor);
+ compare(topRightObject.width, sizes.topRightObject.width * factor);
+ compare(leftObject.height, sizes.leftObject.height);
+ compare(bottomRightObject.height, sizes.bottomRightObject.height);
+ compare(topRightObject.height, sizes.topRightObject.height);
+
+ tiledView.height = initialHeight * factor;
+ compare(leftObject.width, sizes.leftObject.width * factor);
+ compare(bottomRightObject.width, sizes.bottomRightObject.width * factor);
+ compare(topRightObject.width, sizes.topRightObject.width * factor);
+ compare(leftObject.height, sizes.leftObject.height * factor);
+ compare(bottomRightObject.height, sizes.bottomRightObject.height * factor);
+ compare(topRightObject.height, sizes.topRightObject.height * factor);
+
+ // resetting initial size
+ tiledView.width = initialWidth;
+ tiledView.height = initialHeight;
+ compare(leftObject.width, sizes.leftObject.width);
+ compare(bottomRightObject.width, sizes.bottomRightObject.width);
+ compare(topRightObject.width, sizes.topRightObject.width);
+ compare(leftObject.height, sizes.leftObject.height);
+ compare(bottomRightObject.height, sizes.bottomRightObject.height);
+ compare(topRightObject.height, sizes.topRightObject.height);
+
+ tiledView.setRootItem(null);
+ leftObject.destroy();
+ bottomRightObject.destroy();
+ topRightObject.destroy();
+ }
+
+ function test_simpleRemove_data() {
+ return [
+ {orientation: Qt.Horizontal, side: Qt.AlignTrailing},
+ {orientation: Qt.Vertical, side: Qt.AlignTrailing},
+ {orientation: Qt.Horizontal, side: Qt.AlignLeading},
+ {orientation: Qt.Vertical, side: Qt.AlignLeading},
+ ];
+ }
+
+ function test_simpleRemove(data) {
+ var rootObject = objectComponent.createObject(tiledView);
+ verifySetRootItem(rootObject);
+ verifySetOrientation(rootObject, data.orientation);
+
+ var newObject = objectComponent.createObject(tiledView);
+ verifyAdd(rootObject, newObject, data.side);
+ verifyRemove(newObject);
+
+ verifyAdd(rootObject, newObject, data.side);
+ verifyRemove(rootObject);
+
+ verifyAdd(newObject, rootObject, data.side);
+ verifyRemove(newObject);
+
+ tiledView.setRootItem(null);
+ rootObject.destroy();
+ newObject.destroy();
+ }
+
+ function test_nestedAddsRemoveRoot() {
+ var objects = [];
+
+ objects["0"] = objectComponent.createObject(tiledView);
+ verifySetRootItem(objects["0"]);
+
+ objects["1"] = objectComponent.createObject(tiledView);
+ verifySetOrientation(objects["0"], Qt.Horizontal);
+ verifyAdd(objects["0"], objects["1"], Qt.AlignTrailing);
+
+ objects["2"] = objectComponent.createObject(tiledView);
+ verifySetOrientation(objects["1"], Qt.Horizontal);
+ verifyAdd(objects["1"], objects["2"], Qt.AlignTrailing);
+
+ // remove root
+ verifyRemove(objects["0"]);
+
+ // add further
+ objects["3"] = objectComponent.createObject(tiledView);
+ verifySetOrientation(objects["2"], Qt.Horizontal);
+ verifyAdd(objects["2"], objects["3"], Qt.AlignTrailing);
+
+ verifyRemove(objects["2"]);
+
+ objects["4"] = objectComponent.createObject(tiledView);
+ verifySetOrientation(objects["1"], Qt.Horizontal);
+ verifyAdd(objects["1"], objects["4"], Qt.AlignTrailing);
+
+ verifyRemove(objects["1"]);
+
+ tiledView.setRootItem(null);
+ for (var i=0; i<objects.length; i++) {
+ objects[i].destroy();
+ }
+ }
+
+ function test_AddsVertical() {
+ var objects = [];
+
+ objects["0"] = objectComponent.createObject(tiledView);
+ verifySetRootItem(objects["0"]);
+
+ objects["1"] = objectComponent.createObject(tiledView);
+ verifySetOrientation(objects["0"], Qt.Vertical);
+ verifyAdd(objects["0"], objects["1"], Qt.AlignTrailing);
+
+ objects["2"] = objectComponent.createObject(tiledView);
+ verifySetOrientation(objects["1"], Qt.Vertical);
+ verifyAdd(objects["1"], objects["2"], Qt.AlignTrailing);
+
+ objects["3"] = objectComponent.createObject(tiledView);
+ verifySetOrientation(objects["2"], Qt.Vertical);
+ verifyAdd(objects["2"], objects["3"], Qt.AlignLeading);
+
+ objects["4"] = objectComponent.createObject(tiledView);
+ verifySetOrientation(objects["3"], Qt.Vertical);
+ verifyAdd(objects["3"], objects["4"], Qt.AlignTrailing);
+
+ tiledView.setRootItem(null);
+ for (var i=0; i<objects.length; i++) {
+ objects[i].destroy();
+ }
+ }
+}
Follow ups