diff --git a/apps/Launcher/CMakeLists.txt b/apps/Launcher/CMakeLists.txt index 0eae1644..97255a81 100644 --- a/apps/Launcher/CMakeLists.txt +++ b/apps/Launcher/CMakeLists.txt @@ -4,7 +4,6 @@ # Raphael Dumusc set(TIDELAUNCHER_HEADERS - FileInfoHelper.h Launcher.h ) diff --git a/apps/Launcher/FileInfoHelper.h b/apps/Launcher/FileInfoHelper.h deleted file mode 100644 index ba26ed4a..00000000 --- a/apps/Launcher/FileInfoHelper.h +++ /dev/null @@ -1,60 +0,0 @@ -/*********************************************************************/ -/* Copyright (c) 2016, EPFL/Blue Brain Project */ -/* Raphael Dumusc */ -/* All rights reserved. */ -/* */ -/* Redistribution and use in source and binary forms, with or */ -/* without modification, are permitted provided that the following */ -/* conditions are met: */ -/* */ -/* 1. Redistributions of source code must retain the above */ -/* copyright notice, this list of conditions and the following */ -/* disclaimer. */ -/* */ -/* 2. Redistributions in binary form must reproduce the above */ -/* copyright notice, this list of conditions and the following */ -/* disclaimer in the documentation and/or other materials */ -/* provided with the distribution. */ -/* */ -/* THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY OF TEXAS AT */ -/* AUSTIN ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, */ -/* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF */ -/* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE */ -/* DISCLAIMED. IN NO EVENT SHALL THE UNIVERSITY OF TEXAS AT */ -/* AUSTIN OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, */ -/* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES */ -/* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE */ -/* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR */ -/* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF */ -/* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT */ -/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT */ -/* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE */ -/* POSSIBILITY OF SUCH DAMAGE. */ -/* */ -/* The views and conclusions contained in the software and */ -/* documentation are those of the authors and should not be */ -/* interpreted as representing official policies, either expressed */ -/* or implied, of Ecole polytechnique federale de Lausanne. */ -/*********************************************************************/ - -#ifndef FILEINFOHELPER_H -#define FILEINFOHELPER_H - -#include -#include - -/** - * Expose QFileInfo helper functions to Qml. - */ -class FileInfoHelper : public QObject -{ - Q_OBJECT - -public: - Q_INVOKABLE QString baseNameFromPath( const QString& filePath ) const - { - return QFileInfo( filePath ).completeBaseName(); - } -}; - -#endif diff --git a/apps/Launcher/Launcher.h b/apps/Launcher/Launcher.h index bfb9ddb7..7b8f9c01 100644 --- a/apps/Launcher/Launcher.h +++ b/apps/Launcher/Launcher.h @@ -41,7 +41,7 @@ #ifndef LAUNCHER_H #define LAUNCHER_H -#include "FileInfoHelper.h" +#include "tide/master/FileInfoHelper.h" #include diff --git a/apps/Whiteboard/Whiteboard.cpp b/apps/Whiteboard/Whiteboard.cpp index 2d098d94..219664d1 100644 --- a/apps/Whiteboard/Whiteboard.cpp +++ b/apps/Whiteboard/Whiteboard.cpp @@ -43,6 +43,8 @@ #include "tide/master/localstreamer/QmlKeyInjector.h" #include "tide/master/MasterConfiguration.h" +#include + namespace { const std::string deflectHost( "localhost" ); @@ -66,6 +68,9 @@ Whiteboard::Whiteboard( int& argc, char* argv[] ) auto item = _qmlStreamer->getRootItem(); item->setProperty( "saveURL", config.getWhiteboardSaveFolder() ); + QQmlEngine* engine = _qmlStreamer->getQmlEngine(); + engine->rootContext()->setContextProperty( "fileInfo", &_fileInfoHelper ); + } bool Whiteboard::event( QEvent* event_ ) diff --git a/apps/Whiteboard/Whiteboard.h b/apps/Whiteboard/Whiteboard.h index b08e89f4..c40888cc 100644 --- a/apps/Whiteboard/Whiteboard.h +++ b/apps/Whiteboard/Whiteboard.h @@ -40,7 +40,7 @@ #ifndef WHITEBOARD_H #define WHITEBOARD_H -#include +#include "tide/master/FileInfoHelper.h" #include #include @@ -59,6 +59,7 @@ class Whiteboard : public QGuiApplication std::unique_ptr _qmlStreamer; bool event( QEvent* event ) final; + FileInfoHelper _fileInfoHelper; }; #endif diff --git a/apps/Whiteboard/main.cpp b/apps/Whiteboard/main.cpp index 4d4b03a2..acd78e59 100644 --- a/apps/Whiteboard/main.cpp +++ b/apps/Whiteboard/main.cpp @@ -45,6 +45,8 @@ int main( int argc, char** argv ) { logger_id = "whiteboard"; qInstallMessageHandler( qtMessageLogger ); + // Load virtualkeyboard input context plugin + qputenv( "QT_IM_MODULE", QByteArray( "virtualkeyboard" )); std::unique_ptr whiteboard; try diff --git a/apps/Whiteboard/qml/whiteboard.qml b/apps/Whiteboard/qml/whiteboard.qml index 348f8234..995c524a 100755 --- a/apps/Whiteboard/qml/whiteboard.qml +++ b/apps/Whiteboard/qml/whiteboard.qml @@ -9,7 +9,6 @@ import QtQuick.Controls.Styles 1.2 Item { id: root - objectName: "Whiteboard" width: 1920 @@ -27,11 +26,42 @@ Item { property int lastY property int offsetY: 0 property int offsetX: 0 - + property var path property var singleTouchPoint: [] property var singleLine: [] property var allCurves: [] + property var fileList: [] + property bool pathAvail: false + + function checkFileExists() { + if (textInput.text != "") { + path = saveURL + textInput.text + ".png" + if (fileInfo.fileExists(path)) { + infoBox.z = 2 + infoText.text = "Are you sure? File already exists" + textInput.focus = false + buttonOK.visible = true + } else { + saveCanvas(true) + } + } + } + function saveCanvas(overwriteConfirmed) { + buttonOK.visible = false + if (canvas.save(path) && overwriteConfirmed) + infoText.text = "Saved as: \n" + path + else + infoText.text = "Error saving as: \n" + path + infoBox.z = 2 + } + function toggleSavePanel() { + if (savePanel.state == "on") { + savePanel.state = "off" + } else { + savePanel.state = "on" + } + } onWidthChanged: { offsetX = (width - oldWidth) / 2 oldWidth = width @@ -119,6 +149,7 @@ Item { anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter color: "black" + radius: width * 0.5 } MouseArea { anchors.fill: parent @@ -158,7 +189,7 @@ Item { MouseArea { anchors.fill: parent onClicked: { - saveMenu.toggle() + toggleSavePanel() } } } @@ -237,28 +268,6 @@ Item { anchors.bottom: parent.bottom width: root.width height: root.height - headerHeight - Rectangle { - id: freezePane - anchors.fill: parent - anchors.topMargin: saveMenu.height - color: "lightsteelblue" - visible: false - opacity: 0.5 - z: 99 - } - - Text { - id: infoBox - text: "" - font.family: "Helvetica" - font.pointSize: 60 - color: "red" - - anchors.top: saveMenu.bottom - anchors.horizontalCenter: parent.horizontalCenter - z: 100 - visible: false - } Canvas { id: canvas @@ -275,10 +284,6 @@ Item { ctx.beginPath() ctx.strokeStyle = singleCurve[1] ctx.lineWidth = singleCurve[2] - ctx.lineCap = "round" - ctx.beginPath() - ctx.strokeStyle = singleCurve[1] - ctx.lineWidth = singleCurve[2] if (singleCurve[0].length < 4) { ctx.arc(singleCurve[0][0][0] + singleCurve[3][0], @@ -297,17 +302,34 @@ Item { ctx.save() } } - + MultiPointTouchArea { + id: savePanelBackground + anchors.fill: parent + enabled: false + onTouchUpdated: { + if (savePanel.visible) + toggleSavePanel() + } + Rectangle { + anchors.fill: parent + color: "lightsteelblue" + opacity: 0.3 + visible: parent.enabled + } + } MultiPointTouchArea { id: area - enabled: true + enabled: !savePanelBackground.enabled anchors.fill: parent property var paths: [] + + touchPoints: [ TouchPoint { id: point0 } ] + onPressed: { if (touchPoints[0] === point0) { singleLine = new Array(0) @@ -354,119 +376,170 @@ Item { } } + Loader { + id: virtualKeyboard + source: "qrc:/virtualkeyboard/InputPanel.qml" + anchors.bottom: savePanel.bottom + anchors.horizontalCenter: parent.horizontalCenter + width: 800 + height: 200 + visible: Qt.inputMethod.visible ? true : false + z: 100 + } + Rectangle { - color: "#e7edf5" - function toggle() { - if (saveMenu.state == "on") { - saveMenu.state = "off" - area.enabled = true - freezePane.visible = false - - } else { - saveMenu.state = "on" - area.enabled = false - freezePane.visible = true + id: savePanel + width: 800 + height: 250 + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter - } - } - id: saveMenu - width: root.width - height: 50 - anchors.top: parent.top visible: false states: [ State { name: "on" PropertyChanges { - target: saveMenu + target: savePanel visible: true } PropertyChanges { target: imgSave opacity: 0.1 } + PropertyChanges { + target: savePanelBackground + enabled: true + } + PropertyChanges { + target: textInput + focus: true + } + PropertyChanges { + target: infoBox + z: 0 + } }, State { name: "off" PropertyChanges { - target: saveMenu + target: savePanel visible: false } + PropertyChanges { + target: savePanelBackground + enabled: false + } + PropertyChanges { + target: textInput + focus: false + } } ] + TextField { + id: textInput + placeholderText: "File name" + focus: false + width: 700 + height: 50 + z: 1 + anchors.left: parent.left + style: TextFieldStyle { + font.pointSize: control.height * 0.5 + } + onFocusChanged: { + if (focus) + Qt.inputMethod.show() + else + Qt.inputMethod.hide() + } + selectByMouse: true + validator: RegExpValidator { + regExp: /[\w.]*/ + } + onAccepted: { + checkFileExists() + if (textInput.text == "") + Qt.inputMethod.show() + } + Button { + anchors.left: textInput.right + text: "Save" + implicitWidth: 100 + implicitHeight: 50 + onClicked: { + checkFileExists() + } + style: ButtonStyle { + label: Text { + renderType: Text.NativeRendering + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + font.pointSize: control.height * 0.4 + text: control.text + color: control.enabled ? "black" : "gray" + } + } + } + } Rectangle { - color: "#e7edf5" - anchors.centerIn: parent - width: 600 - height: parent.height - - Rectangle { - color: "#7b899b" - anchors.centerIn: parent - id: rect5 + id: infoBox + anchors.fill: parent + width: 800 + height: 250 + color: "black" + Text { + id: infoText anchors.fill: parent + anchors.margins: 10 + text: "" + font.family: "Verdana" + font.pointSize: 35 + color: "white" + wrapMode: Text.Wrap + } + Button { + id: buttonOK + anchors.bottom: parent.bottom anchors.leftMargin: 50 - anchors.rightMargin: 50 - height: parent.height - TextInput { - wrapMode: TextInput.Wrap - verticalAlignment: TextInput.AlignVCenter - horizontalAlignment: TextInput.AlignHCenter - id: inputFileName - color: "black" - font.pixelSize: 24 - text: "" - anchors.fill: parent - focus: true - } - - Button { - anchors.left: inputFileName.right - text: "Save!" - onClicked: { - if (inputFileName.text != "") { - var path = saveURL + inputFileName.text + ".png" - if (canvas.save(path)) { - infoBox.text = "SAVED as " + path - infoBox.visible = true - timer1.start() - } else { - infoBox.text = "Error saving!" - infoBox.visible = true - timer1.start() - } - } - } - - style: ButtonStyle { - background: Rectangle { - implicitWidth: 100 - implicitHeight: 50 - border.width: 0 - border.color: "#7b899b" - radius: 0 - gradient: Gradient { - GradientStop { - position: 0 - color: control.pressed ? "#7b899b" : "#b0c4de" - } - GradientStop { - position: 1 - color: control.pressed ? "#b0c4de" : "#7b899b" - } - } - } + implicitWidth: 100 + implicitHeight: 50 + + style: ButtonStyle { + label: Text { + renderType: Text.NativeRendering + horizontalAlignment: Text.AlignHCenter + font.pointSize: control.height * 0.3 + verticalAlignment: Text.AlignVCenter + text: "OK" + color: control.enabled ? "black" : "gray" } + } - Timer { - id: timer1 - interval: 750 - running: false - repeat: false - onTriggered: infoBox.visible = false + onClicked: { + saveCanvas(true) + } + } + Button { + id: buttonCancel + visible: buttonOK.visible + anchors.bottom: parent.bottom + anchors.left: buttonOK.right + implicitWidth: 100 + implicitHeight: 50 + style: ButtonStyle { + label: Text { + renderType: Text.NativeRendering + font.pointSize: control.height * 0.3 + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + text: "Cancel" + color: control.enabled ? "black" : "gray" } } + onClicked: { + saveCanvas(false) + } } } } diff --git a/tests/cpp/core/ContentControllerTests.cpp b/tests/cpp/core/ContentControllerTests.cpp index fc011ec7..f007eaf6 100644 --- a/tests/cpp/core/ContentControllerTests.cpp +++ b/tests/cpp/core/ContentControllerTests.cpp @@ -96,7 +96,7 @@ BOOST_AUTO_TEST_CASE( testFactoryMethod ) dummyContent.type = CONTENT_TYPE_WEBBROWSER; BOOST_CHECK_THROW( ContentController::create( window ), std::bad_cast ); - ContentWindow webWindow( ContentFactory::getWebbrowserContent( "abc" )); + ContentWindow webWindow( ContentFactory::getPixelStreamContent( "abc", dummyContent.type )); BOOST_CHECK_NO_THROW( controller = ContentController::create( webWindow )); BOOST_CHECK( dynamic_cast( controller.get( ))); #endif diff --git a/tide/core/scene/ContentFactory.cpp b/tide/core/scene/ContentFactory.cpp index 75867fd5..330b6c21 100644 --- a/tide/core/scene/ContentFactory.cpp +++ b/tide/core/scene/ContentFactory.cpp @@ -160,19 +160,21 @@ ContentPtr ContentFactory::getContent( const QString& uri ) return ContentPtr(); } -ContentPtr ContentFactory::getPixelStreamContent( const QString& uri ) +ContentPtr ContentFactory::getPixelStreamContent( const QString& uri, const CONTENT_TYPE contentType, const bool keyboard ) { - return ContentPtr( new PixelStreamContent( uri )); -} - -ContentPtr ContentFactory::getWebbrowserContent( const QString& uri ) -{ -#if TIDE_USE_QT5WEBKITWIDGETS || TIDE_USE_QT5WEBENGINE - return ContentPtr( new WebbrowserContent( uri )); -#else - Q_UNUSED( uri ); - throw std::runtime_error( "Tide was compiled without WebbrowserContent!" ); -#endif + if ( contentType == CONTENT_TYPE_WEBBROWSER ) + { + #if TIDE_USE_QT5WEBKITWIDGETS || TIDE_USE_QT5WEBENGINE + return ContentPtr( new WebbrowserContent( uri )); + #else + Q_UNUSED( uri ); + throw std::runtime_error( "Tide was compiled without WebbrowserContent!" ); + #endif + } + else + { + return ContentPtr( new PixelStreamContent( uri, keyboard )); + } } ContentPtr ContentFactory::getErrorContent( const QSize& size ) diff --git a/tide/core/scene/ContentFactory.h b/tide/core/scene/ContentFactory.h index 3e97ed0d..a327bfb0 100644 --- a/tide/core/scene/ContentFactory.h +++ b/tide/core/scene/ContentFactory.h @@ -41,7 +41,7 @@ #define CONTENTFACTORY_H #include "types.h" -#include "ContentType.h" +#include "scene/ContentType.h" #include @@ -54,10 +54,7 @@ class ContentFactory static ContentPtr getContent( const QString& uri ); /** Special case: PixelStreamContent type cannot be derived from its uri. */ - static ContentPtr getPixelStreamContent( const QString& uri ); - - /** Create a Webbrowser Content (special type of PixelStream). */ - static ContentPtr getWebbrowserContent( const QString& uri ); + static ContentPtr getPixelStreamContent( const QString& uri, const CONTENT_TYPE contentType = CONTENT_TYPE_ANY, const bool keyboard = true ); /** Get a Content object representing a loading error. */ static ContentPtr getErrorContent( const QSize& size = QSize( )); diff --git a/tide/core/scene/ContentType.h b/tide/core/scene/ContentType.h index f3633f1a..ab5d42ba 100644 --- a/tide/core/scene/ContentType.h +++ b/tide/core/scene/ContentType.h @@ -53,6 +53,7 @@ enum CONTENT_TYPE CONTENT_TYPE_TEXTURE, CONTENT_TYPE_PDF, CONTENT_TYPE_WEBBROWSER, + CONTENT_TYPE_WHITEBOARD, CONTENT_TYPE_IMAGE_PYRAMID }; diff --git a/tide/core/scene/PixelStreamContent.h b/tide/core/scene/PixelStreamContent.h index d3df630a..52a34803 100644 --- a/tide/core/scene/PixelStreamContent.h +++ b/tide/core/scene/PixelStreamContent.h @@ -56,7 +56,7 @@ class PixelStreamContent : public Content * @param uri The unique stream identifier. * @param keyboard Show the keyboard action. */ - explicit PixelStreamContent( const QString& uri, bool keyboard = true ); + explicit PixelStreamContent( const QString& uri, bool keyboard = false ); /** Get the content type **/ CONTENT_TYPE getType() const override; diff --git a/tide/master/CMakeLists.txt b/tide/master/CMakeLists.txt index e8fa691e..ef85a867 100644 --- a/tide/master/CMakeLists.txt +++ b/tide/master/CMakeLists.txt @@ -27,6 +27,7 @@ list(APPEND TIDEMASTER_PUBLIC_HEADERS localstreamer/PixelStreamerType.h localstreamer/ProcessForker.h localstreamer/QmlKeyInjector.h + FileInfoHelper.h LoggingUtility.h MasterApplication.h MasterConfiguration.h diff --git a/tide/master/PixelStreamWindowManager.cpp b/tide/master/PixelStreamWindowManager.cpp index d7a753b8..b8058c54 100644 --- a/tide/master/PixelStreamWindowManager.cpp +++ b/tide/master/PixelStreamWindowManager.cpp @@ -47,6 +47,7 @@ #include "log.h" #include "scene/ContentFactory.h" #include "scene/ContentWindow.h" +#include "scene/ContentType.h" #include "scene/DisplayGroup.h" #include "scene/PixelStreamContent.h" @@ -96,7 +97,7 @@ void PixelStreamWindowManager::showWindow( const QString& uri ) void PixelStreamWindowManager::openWindow( const QString& uri, const QPointF& pos, const QSize& size, - const StreamType stream) + const CONTENT_TYPE contentType) { if( getContentWindow( uri )) { @@ -111,9 +112,17 @@ void PixelStreamWindowManager::openWindow( const QString& uri, const auto type = _isPanel( uri ) ? ContentWindow::PANEL : ContentWindow::DEFAULT; - ContentPtr content = (stream == PixelStreamWindowManager::WEBBROWSER) ? - ContentFactory::getWebbrowserContent( uri ) : - ContentFactory::getPixelStreamContent( uri ); + + ContentPtr content; + + if ( contentType == CONTENT_TYPE::CONTENT_TYPE_WHITEBOARD ) + { + content = ContentFactory::getPixelStreamContent( uri, contentType, false ); + } + else + { + content = ContentFactory::getPixelStreamContent( uri, contentType); + } if( size.isValid( )) content->setDimensions( size ); @@ -127,7 +136,7 @@ void PixelStreamWindowManager::openWindow( const QString& uri, _streamerWindows[ uri ] = window->getID(); _displayGroup.addContentWindow( window ); - if( _autoFocusNewWindows && stream == PixelStreamWindowManager::STANDARD ) + if( _autoFocusNewWindows && contentType == CONTENT_TYPE::CONTENT_TYPE_ANY ) DisplayGroupController{ _displayGroup }.focus( window->getID( )); } diff --git a/tide/master/PixelStreamWindowManager.h b/tide/master/PixelStreamWindowManager.h index 56e59264..090e40e8 100644 --- a/tide/master/PixelStreamWindowManager.h +++ b/tide/master/PixelStreamWindowManager.h @@ -42,6 +42,7 @@ #define PIXEL_STREAM_WINDOW_MANAGER_H #include "types.h" +#include "scene/ContentType.h" #include @@ -60,14 +61,6 @@ class PixelStreamWindowManager : public QObject Q_OBJECT public: - /** - * Different types of stream used to determine content type and its focus upon opening - */ - enum StreamType { - WEBBROWSER, - WHITEBOARD, - STANDARD - }; /** * Create a window manager that handles windows for streamers. * @@ -105,11 +98,11 @@ class PixelStreamWindowManager : public QObject * @param pos the desired position for the center of the window in pixels. * If pos.isNull(), the window is centered on the DisplayGroup. * @param size the desired size of the window in pixels. - * @param stream the type of window to create + * @param contentType the type of window to create */ void openWindow( const QString& uri, const QPointF& pos, const QSize& size, - const StreamType stream = STANDARD ); + const CONTENT_TYPE contentType = CONTENT_TYPE_ANY ); /** Check if new windows open in focus mode. */ bool getAutoFocusNewWindows() const; diff --git a/tide/master/localstreamer/PixelStreamerLauncher.cpp b/tide/master/localstreamer/PixelStreamerLauncher.cpp index a488b2ec..b8605bf3 100644 --- a/tide/master/localstreamer/PixelStreamerLauncher.cpp +++ b/tide/master/localstreamer/PixelStreamerLauncher.cpp @@ -41,6 +41,7 @@ #include "log.h" #include "CommandLineOptions.h" +#include "scene/ContentType.h" #include "PixelStreamWindowManager.h" #include "MasterConfiguration.h" @@ -88,7 +89,7 @@ void PixelStreamerLauncher::openWebBrowser( QPointF pos, const QSize size, pos = _getDefaultWindowPosition(); _windowManager.openWindow( uri, pos, viewportSize, - PixelStreamWindowManager::WEBBROWSER ); + CONTENT_TYPE::CONTENT_TYPE_WEBBROWSER ); CommandLineOptions options; #ifdef TIDE_USE_QT5WEBKITWIDGETS @@ -146,7 +147,7 @@ void PixelStreamerLauncher::openWhiteboard() const QSize viewportSize = WHITEBOARD_DEFAULT_SIZE; _windowManager.openWindow( uri, centerPos, viewportSize, - PixelStreamWindowManager::WHITEBOARD ); + CONTENT_TYPE::CONTENT_TYPE_WHITEBOARD ); CommandLineOptions options; options.setStreamname( uri ); options.setConfiguration( _config.getFilename( ));