From 9ddf2107791cb150f37ba5744bd849422755a863 Mon Sep 17 00:00:00 2001 From: Mathias Paulin <mathias.paulin@irit.fr> Date: Thu, 25 Mar 2021 18:19:40 +0100 Subject: [PATCH] Improve the Visualization renderer controller. Add a gui ton interact with the controler. Modify the demo to give access to this controller. --- src/DemoApp/main.cpp | 17 + src/libRender/CMakeLists.txt | 26 + .../RadiumNBR/Gui/CodeEditor/CodeEditor.cpp | 287 +++++++++ .../RadiumNBR/Gui/CodeEditor/CodeEditor.hpp | 285 +++++++++ .../Gui/CodeEditor/CodeEditorDesign.cpp | 596 ++++++++++++++++++ .../Gui/CodeEditor/CodeEditorDesign.hpp | 399 ++++++++++++ .../Gui/CodeEditor/CodeEditorEvents.cpp | 295 +++++++++ .../Gui/CodeEditor/CodeEditorHighlighter.cpp | 244 +++++++ .../Gui/CodeEditor/CodeEditorHighlighter.hpp | 142 +++++ .../Gui/CodeEditor/CodeEditorLineWidget.cpp | 125 ++++ .../Gui/CodeEditor/CodeEditorLineWidget.hpp | 56 ++ .../Gui/CodeEditor/CodeEditorPopup.cpp | 91 +++ .../Gui/CodeEditor/CodeEditorPopup.hpp | 51 ++ .../Gui/CodeEditor/CodeEditorResources.qrc | 6 + .../Gui/CodeEditor/CodeEditorSheets.cpp | 98 +++ .../Gui/CodeEditor/CodeEditorSheets.hpp | 91 +++ .../Gui/CodeEditor/CodeEditorSlots.cpp | 86 +++ .../Gui/CodeEditor/EditorDefaultDesign.xml | 25 + .../Gui/CodeEditor/HighlightRules_cpp.xml | 76 +++ .../Gui/CodeEditor/LineColumnPadding.cpp | 95 +++ .../Gui/CodeEditor/LineColumnPadding.hpp | 80 +++ .../RadiumNBR/Gui/CodeEditor/SyntaxRule.cpp | 358 +++++++++++ .../RadiumNBR/Gui/CodeEditor/SyntaxRule.hpp | 218 +++++++ .../RadiumNBR/Gui/CodeEditor/XmlHelper.cpp | 252 ++++++++ .../RadiumNBR/Gui/CodeEditor/XmlHelper.hpp | 71 +++ src/libRender/RadiumNBR/Gui/GlslEditor.cpp | 68 ++ src/libRender/RadiumNBR/Gui/GlslEditor.hpp | 54 ++ src/libRender/RadiumNBR/Gui/RendererPanel.cpp | 56 +- src/libRender/RadiumNBR/Gui/RendererPanel.hpp | 13 +- .../RadiumNBR/Gui/VisualizationGui.cpp | 61 +- 30 files changed, 4305 insertions(+), 17 deletions(-) create mode 100644 src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditor.cpp create mode 100644 src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditor.hpp create mode 100644 src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorDesign.cpp create mode 100644 src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorDesign.hpp create mode 100644 src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorEvents.cpp create mode 100644 src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorHighlighter.cpp create mode 100644 src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorHighlighter.hpp create mode 100644 src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorLineWidget.cpp create mode 100644 src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorLineWidget.hpp create mode 100644 src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorPopup.cpp create mode 100644 src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorPopup.hpp create mode 100644 src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorResources.qrc create mode 100644 src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorSheets.cpp create mode 100644 src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorSheets.hpp create mode 100644 src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorSlots.cpp create mode 100644 src/libRender/RadiumNBR/Gui/CodeEditor/EditorDefaultDesign.xml create mode 100644 src/libRender/RadiumNBR/Gui/CodeEditor/HighlightRules_cpp.xml create mode 100644 src/libRender/RadiumNBR/Gui/CodeEditor/LineColumnPadding.cpp create mode 100644 src/libRender/RadiumNBR/Gui/CodeEditor/LineColumnPadding.hpp create mode 100644 src/libRender/RadiumNBR/Gui/CodeEditor/SyntaxRule.cpp create mode 100644 src/libRender/RadiumNBR/Gui/CodeEditor/SyntaxRule.hpp create mode 100644 src/libRender/RadiumNBR/Gui/CodeEditor/XmlHelper.cpp create mode 100644 src/libRender/RadiumNBR/Gui/CodeEditor/XmlHelper.hpp create mode 100644 src/libRender/RadiumNBR/Gui/GlslEditor.cpp create mode 100644 src/libRender/RadiumNBR/Gui/GlslEditor.hpp diff --git a/src/DemoApp/main.cpp b/src/DemoApp/main.cpp index 9c7b0d2..2ee6066 100644 --- a/src/DemoApp/main.cpp +++ b/src/DemoApp/main.cpp @@ -9,6 +9,7 @@ #include <Engine/Scene/GeometrySystem.hpp> // Include the customizable renderer system (only the used part here) +#include <RadiumNBR/Gui/VisualizationGui.hpp> #include <RadiumNBR/NodeBasedRenderer.hpp> #include <RadiumNBR/Renderer/Visualization.hpp> @@ -21,6 +22,8 @@ #include <Core/Utils/Log.hpp> using namespace Ra::Core::Utils; // log +#include <QDockWidget> +#include <QToolBar> /** * This class parameterize the renderer just after the OpenGL system was initialized. * when a method of this controller is called, the OpenGL context of the drawing window is @@ -181,6 +184,20 @@ class DemoWindowFactory : public Ra::Gui::BaseApplication::WindowFactory inline Ra::Gui::MainWindowInterface* createMainWindow() const override { auto window = new Ra::Gui::SimpleWindow(); window->addRenderer( renderer->getRendererName(), renderer ); + // Build the gui for the renderer + auto controlPanel = + RadiumNBR::buildControllerGui( renderer.get(), [window]() { window->frameUpdate(); } ); + auto dock = new QDockWidget( "Renderer Controller", window ); + dock->setAllowedAreas( Qt::BottomDockWidgetArea ); + dock->setWidget( controlPanel ); + window->addDockWidget( Qt::BottomDockWidgetArea, dock ); + dock->hide(); + + auto toolbar = new QToolBar( "Show Controls ", window ); + toolbar->setAllowedAreas( Qt::BottomToolBarArea ); + toolbar->addAction( dock->toggleViewAction() ); + window->addToolBar( Qt::BottomToolBarArea, toolbar ); + return window; } diff --git a/src/libRender/CMakeLists.txt b/src/libRender/CMakeLists.txt index 7e0f3e8..f32cea2 100644 --- a/src/libRender/CMakeLists.txt +++ b/src/libRender/CMakeLists.txt @@ -135,18 +135,44 @@ set(gui_sources RadiumNBR/Gui/FullFeaturedRendererGui.cpp RadiumNBR/Gui/VisualizationGui.cpp RadiumNBR/Gui/RendererPanel.cpp + + RadiumNBR/Gui/CodeEditor/CodeEditor.cpp + RadiumNBR/Gui/CodeEditor/CodeEditorDesign.cpp + RadiumNBR/Gui/CodeEditor/CodeEditorEvents.cpp + RadiumNBR/Gui/CodeEditor/CodeEditorHighlighter.cpp + RadiumNBR/Gui/CodeEditor/CodeEditorLineWidget.cpp + RadiumNBR/Gui/CodeEditor/CodeEditorPopup.cpp + RadiumNBR/Gui/CodeEditor/CodeEditorSheets.cpp + RadiumNBR/Gui/CodeEditor/CodeEditorSlots.cpp + RadiumNBR/Gui/CodeEditor/LineColumnPadding.cpp + RadiumNBR/Gui/CodeEditor/SyntaxRule.cpp + RadiumNBR/Gui/CodeEditor/XmlHelper.cpp + RadiumNBR/Gui/GlslEditor.cpp ) set(gui_public_headers RadiumNBR/Gui/FullFeaturedRendererGui.hpp RadiumNBR/Gui/VisualizationGui.hpp RadiumNBR/Gui/RendererPanel.hpp + + RadiumNBR/Gui/CodeEditor/CodeEditor.hpp + RadiumNBR/Gui/CodeEditor/CodeEditorDesign.hpp + RadiumNBR/Gui/CodeEditor/CodeEditorHighlighter.hpp + RadiumNBR/Gui/CodeEditor/CodeEditorLineWidget.hpp + RadiumNBR/Gui/CodeEditor/CodeEditorPopup.hpp + RadiumNBR/Gui/CodeEditor/CodeEditorSheets.hpp + RadiumNBR/Gui/CodeEditor/LineColumnPadding.hpp + RadiumNBR/Gui/CodeEditor/SyntaxRule.hpp + RadiumNBR/Gui/CodeEditor/XmlHelper.hpp + RadiumNBR/Gui/GlslEditor.hpp ) set(gui_headers + ) set(gui_resources + RadiumNBR/Gui/CodeEditor/CodeEditorResources.qrc ) # Our library project uses these sources and headers. diff --git a/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditor.cpp b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditor.cpp new file mode 100644 index 0000000..aebcd85 --- /dev/null +++ b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditor.cpp @@ -0,0 +1,287 @@ +// +// QCodeEditor - Widget to highlight and auto-complete code. +// Copyright (C) 2016 Nicolas Kogler (kogler.cml@hotmail.com) +// +// This file is part of QCodeEditor. +// +// QCodeEditor is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with QCodeEditor. If not, see <http://www.gnu.org/licenses/>. +// +// + +// +// Included headers +// +#include <QCompleter> +#include <RadiumNBR/Gui/CodeEditor/CodeEditor.hpp> +#include <RadiumNBR/Gui/CodeEditor/CodeEditorHighlighter.hpp> +#include <RadiumNBR/Gui/CodeEditor/CodeEditorLineWidget.hpp> +#include <RadiumNBR/Gui/CodeEditor/CodeEditorPopup.hpp> +#include <RadiumNBR/Gui/CodeEditor/CodeEditorSheets.hpp> + +namespace RadiumNBR::Gui { + +/// +/// @fn Default constructor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +QCodeEditor::QCodeEditor( QWidget* parent ) : + QPlainTextEdit( parent ), + m_Popup( NULL ), + m_Highlighter( NULL ), + m_SourceModel( NULL ), + m_RuleFilter( NULL ), + m_AutoComplete( NULL ), + m_CompletionTrigger( 3 ) { + + // Changes miscellaneous properties + setAutoFillBackground( true ); + setFrameStyle( QFrame::NoFrame ); + + // Specifies the default font for the code editor + QFont editorFont( "Courier" ); + editorFont.setPointSize( 14 ); + editorFont.setKerning( true ); + editorFont.setStyleHint( QFont::TypeWriter ); + editorFont.setStyleStrategy( QFont::PreferAntialias ); + setFont( editorFont ); + + // Constructs the highlighter and the intelliBox + m_Highlighter = new QCodeEditorHighlighter( this ); + m_Popup = new QCodeEditorPopup( this ); + + // Constructs the intelliBox's completer + m_AutoComplete = new QCompleter; + m_AutoComplete->setWidget( this ); + m_AutoComplete->setCompletionMode( QCompleter::PopupCompletion ); + m_AutoComplete->setModelSorting( QCompleter::CaseInsensitivelySortedModel ); + m_AutoComplete->setPopup( m_Popup ); + + // Constructs the source model + m_SourceModel = new QStandardItemModel; + + // Constructs the sorting proxy model + m_RuleFilter = new QSortFilterProxyModel; + m_RuleFilter->setFilterCaseSensitivity( Qt::CaseInsensitive ); + m_RuleFilter->setDynamicSortFilter( false ); + + // Constructs the line widget + m_LineWidget = new QCodeEditorLineWidget( this ); + updateLineColumn( 0 ); + + // Connects signals with slots + connect( + m_AutoComplete, SIGNAL( activated( QString ) ), this, SLOT( completeWord( QString ) ) ); + connect( + this, SIGNAL( updateRequest( QRect, int ) ), this, SLOT( scrollLineColumn( QRect, int ) ) ); + connect( this, SIGNAL( blockCountChanged( int ) ), this, SLOT( updateLineColumn( int ) ) ); + connect( this, SIGNAL( textChanged() ), this, SLOT( textChanged() ) ); +} + +/// +/// @fn Destructor +/// @author Nicolas Kogler +/// +QCodeEditor::~QCodeEditor() { + delete m_Highlighter; + delete m_LineWidget; + delete m_RuleFilter; + delete m_AutoComplete; +} + +/// +/// @fn rules +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +const QList<QSyntaxRule>& QCodeEditor::rules() const { + return m_Rules; +} + +/// +/// @fn design +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +const QCodeEditorDesign& QCodeEditor::design() const { + return m_Design; +} + +/// +/// @fn lineCount +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +quint32 QCodeEditor::lineCount() const { + return static_cast<quint32>( document()->blockCount() ); +} + +/// +/// @fn textAtLine +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +QString QCodeEditor::textAtLine( quint32 index ) const { + return document()->findBlockByLineNumber( index ).text(); +} + +/// +/// @fn highlighter +/// @author Nicolas Kogler +/// @date October 15th, 2016 +/// +QCodeEditorHighlighter* QCodeEditor::highlighter() const { + return m_Highlighter; +} + +/// +/// @fn setRules +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditor::setRules( const QList<QSyntaxRule>& rules ) { + m_Rules = rules; + m_Highlighter->updateFormats(); + m_Highlighter->rehighlight(); +} + +/// +/// @fn setDesign +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditor::setDesign( const QCodeEditorDesign& design ) { + m_Design = design; + setFont( design.editorFont() ); + + // Modifies the palette of the underlying QPlainTextEdit + QPalette palette; + palette.setColor( QPalette::Base, design.editorBackColor() ); + palette.setColor( QPalette::Text, design.editorTextColor() ); + setPalette( palette ); + + // Modifies the border through a style-sheet + setStyleSheet( QCodeEditorSheets::border( design ) ); + m_Highlighter->updateFormats(); +} + +/// +/// @fn setDesign +/// @author Nicolas Kogler +/// @date October 6th, 2016 +/// +void QCodeEditor::setCompletionTrigger( qint32 amount ) { + m_CompletionTrigger = amount; +} + +/// +/// @fn setKeywords +/// @author Nicolas Kogler +/// @date October 9th, 2016 +/// +void QCodeEditor::setKeywords( const QStringList& keywords ) { + qint32 size = keywords.size(); + + // Constructs the model from the keywords + for ( int i = 0; i < size; ++i ) + { + QStandardItem* item = new QStandardItem; + item->setText( keywords.at( i ) ); + m_SourceModel->appendRow( item ); + } + + // Changes the auto complete menu model + m_RuleFilter->setSourceModel( m_SourceModel ); + m_RuleFilter->sort( 0 ); + m_AutoComplete->setModel( m_RuleFilter ); +} + +/// +/// @fn setKeywordModel +/// @author Nicolas Kogler +/// @date October 9th, 2016 +/// +void QCodeEditor::setKeywordModel( QStandardItemModel* model ) { + m_RuleFilter->setSourceModel( model ); + m_AutoComplete->setModel( m_RuleFilter ); +} + +/// +/// @fn addKeyword +/// @author Nicolas Kogler +/// @date October 19th, 2016 +/// +void QCodeEditor::addKeyword( const QString& keyword ) { + if ( !keywordExists( keyword ) ) + { + QStandardItem* item = new QStandardItem; + item->setText( keyword ); + m_SourceModel->appendRow( item ); + m_RuleFilter->sort( 0 ); + } +} + +/// +/// @fn removeKeyword +/// @author Nicolas Kogler +/// @date October 19th, 2016 +/// +void QCodeEditor::removeKeyword( const QString& keyword ) { + QList<QStandardItem*> m = m_SourceModel->findItems( keyword ); + if ( m.size() == 1 ) { m_SourceModel->removeRow( m.at( 0 )->row() ); } +} + +/// +/// @fn keywordExists +/// @author Nicolas Kogler +/// @date October 19th, 2016 +/// +bool QCodeEditor::keywordExists( const QString& keyword ) { + return !m_SourceModel->findItems( keyword ).isEmpty(); +} + +/// +/// @fn rehighlight +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditor::rehighlight() { + m_Highlighter->rehighlight(); +} + +/// +/// @fn lineColumnWidth +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +int QCodeEditor::lineColumnWidth() const { + // Gets the amount of lines + int minDigits = 1; + int maxDigits = qMax( minDigits, document()->blockCount() ); + + // Calculates the number of digits in the biggest line number + while ( maxDigits >= 10 ) + { + maxDigits /= 10; + minDigits++; + } + + // Retrieves the line column padding + QLineColumnPadding pad = m_Design.lineColumnPadding(); + + // Instead of going through all numbers and picking the one + // rendering the widest, we approximate the width of it. + return fontMetrics().horizontalAdvance( '0' ) * minDigits + pad.left() + pad.right(); +} +} // namespace RadiumNBR::Gui diff --git a/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditor.hpp b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditor.hpp new file mode 100644 index 0000000..c76889a --- /dev/null +++ b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditor.hpp @@ -0,0 +1,285 @@ +#pragma once +#include <RadiumNBR/NodeBasedRendererMacro.hpp> + +#include <RadiumNBR/Gui/CodeEditor/CodeEditorDesign.hpp> +#include <RadiumNBR/Gui/CodeEditor/SyntaxRule.hpp> + +#include <QAbstractProxyModel> +#include <QCompleter> +#include <QPlainTextEdit> +#include <QStandardItemModel> +#include <QTextBlock> + +namespace RadiumNBR::Gui { + +// +// Forward declarations +// +class QCodeEditorLineWidget; +class QCodeEditorHighlighter; +class QCodeEditorPopup; + +/// +/// @file QCodeEditor.hpp +/// @author Nicolas Kogler +/// @date October 4th, 2016 +/// @class QCodeEditor +/// @brief Highlights and auto-completes code. +/// +class NodeBasedRenderer_LIBRARY_API QCodeEditor : public QPlainTextEdit +{ + Q_OBJECT + public: + /// + /// @fn Default constructor + /// @brief Initializes a new instance of QCodeEditor. + /// + QCodeEditor( QWidget* parent = NULL ); + + /// + /// @fn Destructor + /// @brief Frees all resources allocated by QCodeEditor. + /// + ~QCodeEditor(); + + /// + /// @fn rules : const + /// @brief Retrieves all the syntax highlighting rules. + /// @returns a ref to a list of QSyntaxRule instances. + /// + const QList<QSyntaxRule>& rules() const; + + /// + /// @fn design : const + /// @brief Retrieves all the visual properties. + /// @returns a ref to a QCodeEditorDesign instance. + /// + const QCodeEditorDesign& design() const; + + /// + /// @fn lineCount : const + /// @brief Retrieves the amount of lines. + /// @returns the the amount of lines. + /// + quint32 lineCount() const; + + /// + /// @fn textAtLine : const + /// @brief Retrieves the text on the specified line number. + /// @param index Line index starting from zero + /// @returns the string value at line 'index'. + /// + QString textAtLine( quint32 index ) const; + + /// + /// @fn highlighter : const + /// @brief Retrieves the syntax highlighter for this editor. + /// @returns the sytax highlighter. + /// + QCodeEditorHighlighter* highlighter() const; + + /// + /// @fn setRules + /// @brief Specifies the syntax highlighting rules. + /// @param rules List of QSyntaxRule instances + /// + void setRules( const QList<QSyntaxRule>& rules ); + + /// + /// @fn setDesign + /// @brief Specifies the visual properties. + /// @param design Instance of QCodeEditorDesign + /// + void setDesign( const QCodeEditorDesign& design ); + + /// + /// @fn setCompletionTrigger + /// @brief Sets the completion trigger. + /// + /// Specifies the amount of characters to be typed in the + /// currently written word until the auto-completion menu + /// is opened (in case it is part of a registered keyword, function ...). + /// + /// @param amount Amount of characters to be typed + /// @default The default value is 3. + /// + void setCompletionTrigger( qint32 amount ); + + /// + /// @fn setKeywords + /// @brief Specifies the keywords for auto-completion. + /// + /// It is not possible to add icons or anything else + /// if only the keywords are specified. The keywords + /// are sorted alphabetically. If you want to add + /// icons or other things to the keywords, you have to + /// specify a custom model through setKeywordModel. + /// + /// @param keywords List of keywords + /// + void setKeywords( const QStringList& keywords ); + + /// + /// @fn setKeywordModel + /// @brief Specifies the auto-complete model directly. + /// @param model QStandardItemModel to use + /// + void setKeywordModel( QStandardItemModel* model ); + + /// + /// @fn addKeyword + /// @brief Adds a keyword to the existing model. + /// @param keyword New auto-complete keyword. + /// @note If you specified a custom model, append + /// the keyword on your own and it will be + /// immediately visible to the intelliBox. + /// + void addKeyword( const QString& keyword ); + + /// + /// @fn removeKeyword + /// @brief Removes a keyword from the existing model. + /// @param keyword Keyword to remove from the list + /// + void removeKeyword( const QString& keyword ); + + /// + /// @fn keywordsExist + /// @brief Determines whether the given keyword exists. + /// @param keyword Keyword to check + /// @returns true if the keyword exĂsts. + /// + bool keywordExists( const QString& keyword ); + + /// + /// @fn rehighlight + /// @brief Applies syntax highlighting manually. + /// + /// Normally one would not need this, because 'setRules' + /// already performs rehighlighting of the QCodeEditor. + /// + void rehighlight(); + + /// + /// @fn lineColumnWidth + /// @brief Retrieves the width of the line column. + /// @returns the width of the line column. + /// + int lineColumnWidth() const; + + protected: + /// + /// @fn paintEvent + /// @brief Paints additional widgets or plugins. + /// @param event Pointer to a QPaintEvent instance + /// + void paintEvent( QPaintEvent* event ) Q_DECL_OVERRIDE; + + /// + /// @fn keyPressEvent + /// @brief Intercepts the key-press event. + /// + /// Under the hood, it triggers the intellisense box + /// in case a partial keyword or function has been + /// detected or if one of the trigger keys for the + /// intellisense box have been pressed (e.g dot or + /// arrow for C++ methods). + /// + /// @param event Pointer to a QKeyEvent instance + /// + void keyPressEvent( QKeyEvent* event ) Q_DECL_OVERRIDE; + + /// + /// @fn keyReleaseEvent + /// @brief Intercepts the key-release event. + /// + /// Almost all IDEs convert tabulator characters to + /// whitespaces (mostly 4). This ensures that the + /// document looks the same, regardless of the context. + /// + /// @param event Pointer to a QKeyEvent instance + /// + void keyReleaseEvent( QKeyEvent* event ) Q_DECL_OVERRIDE; + + /// + /// @fn resizeEvent + /// @brief Intercepts the resize event. + /// + /// Whenever the user resizes the code editor in the + /// vertical direction, the line-number widget should + /// also be resized. + /// + /// @param event Pointer to a QResizeEvent instance + /// + void resizeEvent( QResizeEvent* event ) Q_DECL_OVERRIDE; + + signals: + + /// + /// @fn lineChanged + /// @brief Is emitted when a line's text changed. + /// @param block Block (line) that was changed + /// + void lineChanged( QTextBlock block ); + + private slots: + + /// + /// @fn updateLineColumn : slot + /// @brief Updates the line column width. + /// + /// We need to recalculate the width of the line + /// column because the amount of digits per line + /// number changes if the user exceeds line 9, 99, ... + /// + /// @param lineCount Current amount of lines + /// + void updateLineColumn( int lineCount ); + + /// + /// @fn scrollLineColumn : slot + /// @brief Updates the scroll value of the line widget. + /// + /// The first visible line number must be updated + /// in case the user scrolled the code editor vertically. + /// + /// @param view Visible rectangle within the editor + /// @param scroll Vertical scroll value + /// + void scrollLineColumn( QRect view, int scroll ); + + /// + /// @fn completeWord : slot + /// @brief Auto-completes the given keyword, function, ... + /// + /// This slot is executed as soon as the user picked an + /// element from the QCodeEditorPopup list. + /// + /// @param word The word to complete + /// + void completeWord( const QString& word ); + + /// + /// @fn textChanged : slot + /// @brief Emits a line-changed event when appropriate + /// + void textChanged(); + + private: + // + // Private class members + // + QList<QSyntaxRule> m_Rules; ///< Holds all syntax highlighting rules + QCodeEditorDesign m_Design; ///< Holds all visual properties + QCodeEditorLineWidget* m_LineWidget; ///< Line column widget + QCodeEditorPopup* m_Popup; ///< Intellisense popup widget + QCodeEditorHighlighter* m_Highlighter; ///< The syntax highlighter + QStandardItemModel* m_SourceModel; ///< Source model for the rule filter + QSortFilterProxyModel* m_RuleFilter; ///< Filters all the keywords, funcs, ... + QCompleter* m_AutoComplete; ///< Auto completion widget + qint32 m_CompletionTrigger; ///< Amount of characters till AC trigger + + // Allow the line widget to access vars/funcs while rendering + friend class QCodeEditorLineWidget; +}; +} // namespace RadiumNBR::Gui diff --git a/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorDesign.cpp b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorDesign.cpp new file mode 100644 index 0000000..c52170a --- /dev/null +++ b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorDesign.cpp @@ -0,0 +1,596 @@ +// +// QCodeEditor - Widget to highlight and auto-complete code. +// Copyright (C) 2016 Nicolas Kogler (kogler.cml@hotmail.com) +// +// This file is part of QCodeEditor. +// +// QCodeEditor is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with QCodeEditor. If not, see <http://www.gnu.org/licenses/>. +// +// + +// +// Included headers +// +#include <RadiumNBR/Gui/CodeEditor/CodeEditorDesign.hpp> +#include <RadiumNBR/Gui/CodeEditor/XmlHelper.hpp> +#include <qrgb.h> + +namespace RadiumNBR::Gui { + +/// +/// @fn Default constructor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +QCodeEditorDesign::QCodeEditorDesign() : + m_EditorBackColor( 0xffffffff ), + m_EditorTextColor( 0xff333333 ), + m_EditorBorderColor( 0xffb9b9b9 ), + m_LineColumnBackColor( 0xffe9e9e9 ), + m_LineColumnTextColor( 0xff6a9fc6 ), + m_LineColumnSeparatorColor( 0xffb9b9b9 ), + m_ActiveLineColor( 0xfffc9100 ), + m_IntelliBoxBackColor( 0xfffafafa ), + m_IntelliBoxTextColor( 0xff333333 ), + m_IntelliBoxBorderColor( 0xffb9b9b9 ), + m_IntelliBoxSelectionBackColor( 0xffc0dcf3 ), + m_IntelliBoxSelectionBorderColor( 0xff90c8f6 ), + m_IntelliBoxPressBackColor( 0xff90c8f6 ), + m_IntelliBoxPressBorderColor( 0xff60b0f9 ), + m_EditorBorder( QMargins( 0, 0, 0, 0 ) ), + m_IntelliBoxBorder( QMargins( 1, 1, 1, 1 ) ), + m_PopupSize( 200, 200 ), + m_HasLineColumn( true ), + m_ShowFocusRect( false ), + m_FirstLineOne( true ) { + + // Tries to find a monospaced font +#ifndef Q_OS_WIN32 + m_EditorFont.setFamily( "Courier" ); + m_IntelliBoxFont.setFamily( "Courier" ); + m_EditorFont.setStyleHint( QFont::TypeWriter ); + m_IntelliBoxFont.setStyleHint( QFont::TypeWriter ); +#else + m_EditorFont.setFamily( "Consolas" ); + m_IntelliBoxFont.setFamily( "Consolas" ); +#endif + m_EditorFont.setPointSize( 14 ); + m_IntelliBoxFont.setPointSize( 14 ); +} + +/// +/// @fn Copy constructor +/// @author Nicolas Kogler +/// @date October 6th, 2016 +/// +QCodeEditorDesign::QCodeEditorDesign( const QCodeEditorDesign& design ) : + m_EditorBackColor( design.editorBackColor() ), + m_EditorTextColor( design.editorTextColor() ), + m_EditorBorderColor( design.editorBorderColor() ), + m_LineColumnBackColor( design.lineColumnBackColor() ), + m_LineColumnTextColor( design.lineColumnTextColor() ), + m_LineColumnSeparatorColor( design.lineColumnSeparatorColor() ), + m_ActiveLineColor( design.activeLineColor() ), + m_IntelliBoxBackColor( design.intelliBoxBackColor() ), + m_IntelliBoxTextColor( design.intelliBoxTextColor() ), + m_IntelliBoxBorderColor( design.intelliBoxBorderColor() ), + m_IntelliBoxSelectionBackColor( design.intelliBoxSelectionBackColor() ), + m_IntelliBoxSelectionBorderColor( design.intelliBoxSelectionBorderColor() ), + m_IntelliBoxPressBackColor( design.intelliBoxPressBackColor() ), + m_IntelliBoxPressBorderColor( design.intelliBoxPressBorderColor() ), + m_EditorFont( design.editorFont() ), + m_IntelliBoxFont( design.intelliBoxFont() ), + m_EditorBorder( design.editorBorder() ), + m_IntelliBoxBorder( design.intelliBoxBorder() ), + m_PopupSize( design.popupSize() ), + m_HasLineColumn( design.isLineColumnVisible() ), + m_ShowFocusRect( design.hasFocusRect() ) {} + +/// +/// @fn Constructor +/// @author Nicolas Kogler +/// @date October 18th, 2016 +/// +QCodeEditorDesign::QCodeEditorDesign( const QString& path ) : QCodeEditorDesign() { + QFile file( path ); + + // Determines whether file can be opened + if ( !file.open( QIODevice::ReadOnly ) ) + { + qDebug( "kgl::QCodeEditorDesign: Cannot open XML file." ); + return; + } + + // Attempts to read top element + QXmlStreamReader xmlReader( &file ); + if ( !xmlReader.readNextStartElement() && xmlReader.name() != "design" ) + { + qDebug( "kgl::QCodeEditorDesign: Top tag is not <design>." ); + return; + } + + while ( !xmlReader.hasError() && !xmlReader.atEnd() ) + { + if ( !xmlReader.readNextStartElement() ) { continue; } + + QString name = xmlReader.name().toString().toLower(); + + // Order and case of elements does not matter + if ( name == "editorbackcolor" ) { m_EditorBackColor = readColor( &xmlReader ); } + else if ( name == "editortextcolor" ) + { m_EditorTextColor = readColor( &xmlReader ); } + else if ( name == "editorbordercolor" ) + { m_EditorBorderColor = readColor( &xmlReader ); } + else if ( name == "linecolumnbackcolor" ) + { m_LineColumnBackColor = readColor( &xmlReader ); } + else if ( name == "linecolumntextcolor" ) + { m_LineColumnTextColor = readColor( &xmlReader ); } + else if ( name == "linecolumnseparatorcolor" ) + { m_LineColumnSeparatorColor = readColor( &xmlReader ); } + else if ( name == "activelinecolor" ) + { m_ActiveLineColor = readColor( &xmlReader ); } + else if ( name == "intelliboxbackcolor" ) + { m_IntelliBoxBackColor = readColor( &xmlReader ); } + else if ( name == "intelliboxtextcolor" ) + { m_IntelliBoxTextColor = readColor( &xmlReader ); } + else if ( name == "intelliboxbordercolor" ) + { m_IntelliBoxBorderColor = readColor( &xmlReader ); } + else if ( name == "intelliboxselectionbackcolor" ) + { m_IntelliBoxSelectionBackColor = readColor( &xmlReader ); } + else if ( name == "intelliboxselectionbordercolor" ) + { m_IntelliBoxSelectionBorderColor = readColor( &xmlReader ); } + else if ( name == "intelliboxpressbackcolor" ) + { m_IntelliBoxPressBackColor = readColor( &xmlReader ); } + else if ( name == "intelliboxpressbordercolor" ) + { m_IntelliBoxPressBorderColor = readColor( &xmlReader ); } + else if ( name == "editorborder" ) + { m_EditorBorder = readMargin( &xmlReader ); } + else if ( name == "intelliboxborder" ) + { m_IntelliBoxBorder = readMargin( &xmlReader ); } + else if ( name == "linecolumnpadding" ) + { m_LineColumnPadding = QLineColumnPadding( readSize( &xmlReader ) ); } + else if ( name == "popupsize" ) + { m_PopupSize = readSize( &xmlReader ); } + else if ( name == "haslinecolumn" ) + { m_HasLineColumn = readBool( &xmlReader ); } + else if ( name == "showfocusrect" ) + { m_ShowFocusRect = readBool( &xmlReader ); } + else if ( name == "firstlineone" ) + { m_FirstLineOne = readBool( &xmlReader ); } + else if ( name == "editorfont" ) + { m_EditorFont = readFont( &xmlReader, QFont( "Courier" ) ); } + else if ( name == "intelliboxfont" ) + { m_IntelliBoxFont = readFont( &xmlReader, QFont( "Courier" ) ); } + else + { + QString s( "kgl::QCodeEditorDesign: Element '%0' is unknown." ); + qDebug( s.arg( name ).toStdString().c_str() ); + } + } + + file.close(); +} + +/// +/// @fn Destructor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +QCodeEditorDesign::~QCodeEditorDesign() {} + +/// +/// @fn editorBackColor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +const QColor& QCodeEditorDesign::editorBackColor() const { + return m_EditorBackColor; +} + +/// +/// @fn editorTextColor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +const QColor& QCodeEditorDesign::editorTextColor() const { + return m_EditorTextColor; +} + +/// +/// @fn editorBorderColor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +const QColor& QCodeEditorDesign::editorBorderColor() const { + return m_EditorBorderColor; +} + +/// +/// @fn lineColumnBackColor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +const QColor& QCodeEditorDesign::lineColumnBackColor() const { + return m_LineColumnBackColor; +} + +/// +/// @fn lineColumnTextColor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +const QColor& QCodeEditorDesign::lineColumnTextColor() const { + return m_LineColumnTextColor; +} + +/// +/// @fn lineColumnSeparatorColor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +const QColor& QCodeEditorDesign::lineColumnSeparatorColor() const { + return m_LineColumnSeparatorColor; +} + +/// +/// @fn activeLineColor +/// @author Nicolas Kogler +/// @date October 15th, 2016 +/// +const QColor& QCodeEditorDesign::activeLineColor() const { + return m_ActiveLineColor; +} + +/// +/// @fn intelliBoxBackColor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +const QColor& QCodeEditorDesign::intelliBoxBackColor() const { + return m_IntelliBoxBackColor; +} + +/// +/// @fn intelliBoxTextColor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +const QColor& QCodeEditorDesign::intelliBoxTextColor() const { + return m_IntelliBoxTextColor; +} + +/// +/// @fn intelliBoxBorderColor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +const QColor& QCodeEditorDesign::intelliBoxBorderColor() const { + return m_IntelliBoxBorderColor; +} + +/// +/// @fn intelliBoxSelectionBackColor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +const QColor& QCodeEditorDesign::intelliBoxSelectionBackColor() const { + return m_IntelliBoxSelectionBackColor; +} + +/// +/// @fn intelliBoxSelectionBorderColor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +const QColor& QCodeEditorDesign::intelliBoxSelectionBorderColor() const { + return m_IntelliBoxSelectionBorderColor; +} + +/// +/// @fn intelliBoxPressBackColor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +const QColor& QCodeEditorDesign::intelliBoxPressBackColor() const { + return m_IntelliBoxPressBackColor; +} + +/// +/// @fn intelliBoxPressBorderColor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +const QColor& QCodeEditorDesign::intelliBoxPressBorderColor() const { + return m_IntelliBoxPressBorderColor; +} + +/// +/// @fn editorFont +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +const QFont& QCodeEditorDesign::editorFont() const { + return m_EditorFont; +} + +/// +/// @fn intelliBoxFont +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +const QFont& QCodeEditorDesign::intelliBoxFont() const { + return m_IntelliBoxFont; +} + +/// +/// @fn editorBorder +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +const QMargins& QCodeEditorDesign::editorBorder() const { + return m_EditorBorder; +} + +/// +/// @fn intelliBoxBorder +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +const QMargins& QCodeEditorDesign::intelliBoxBorder() const { + return m_IntelliBoxBorder; +} + +/// +/// @fn lineColumnPadding +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +const QLineColumnPadding& QCodeEditorDesign::lineColumnPadding() const { + return m_LineColumnPadding; +} + +/// +/// @fn popupSize +/// @author Nicolas Kogler +/// @date October 6th, 2016 +/// +const QSize& QCodeEditorDesign::popupSize() const { + return m_PopupSize; +} + +/// +/// @fn isLineColumnVisible +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +bool QCodeEditorDesign::isLineColumnVisible() const { + return m_HasLineColumn; +} + +/// +/// @fn hasFocusRect +/// @author Nicolas Kogler +/// @date October 6th, 2016 +/// +bool QCodeEditorDesign::hasFocusRect() const { + return m_ShowFocusRect; +} + +/// +/// @fn startsWithOne +/// @author Nicolas Kogler +/// @date October 6th, 2016 +/// +bool QCodeEditorDesign::startsWithOne() const { + return m_FirstLineOne; +} + +/// +/// @fn setEditorBackColor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditorDesign::setEditorBackColor( const QColor& color ) { + m_EditorBackColor = color; +} + +/// +/// @fn setEditorTextColor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditorDesign::setEditorTextColor( const QColor& color ) { + m_EditorTextColor = color; +} + +/// +/// @fn setEditorBorderColor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditorDesign::setEditorBorderColor( const QColor& color ) { + m_EditorBorderColor = color; +} + +/// +/// @fn setLineColumnBackColor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditorDesign::setLineColumnBackColor( const QColor& color ) { + m_LineColumnBackColor = color; +} + +/// +/// @fn setLineColumnTextColor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditorDesign::setLineColumnTextColor( const QColor& color ) { + m_LineColumnTextColor = color; +} + +/// +/// @fn setActiveLineColor +/// @author Nicolas Kogler +/// @date October 15th, 2016 +/// +void QCodeEditorDesign::setActiveLineColor( const QColor& color ) { + m_ActiveLineColor = color; +} + +/// +/// @fn setLineColumnSeparatorColor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditorDesign::setLineColumnSeparatorColor( const QColor& color ) { + m_LineColumnSeparatorColor = color; +} + +/// +/// @fn setIntelliBoxBackColor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditorDesign::setIntelliBoxBackColor( const QColor& color ) { + m_IntelliBoxBackColor = color; +} + +/// +/// @fn setIntelliBoxTextColor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditorDesign::setIntelliBoxTextColor( const QColor& color ) { + m_IntelliBoxTextColor = color; +} + +/// +/// @fn setIntelliBoxBorderColor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditorDesign::setIntelliBoxBorderColor( const QColor& color ) { + m_IntelliBoxBorderColor = color; +} + +/// +/// @fn setIntelliBoxSelectionBackColor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditorDesign::setIntelliBoxSelectionBackColor( const QColor& color ) { + m_IntelliBoxSelectionBackColor = color; +} + +/// +/// @fn setIntelliBoxSelectionBorderColor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditorDesign::setIntelliBoxSelectionBorderColor( const QColor& color ) { + m_IntelliBoxSelectionBorderColor = color; +} + +/// +/// @fn setIntelliBoxPressBackColor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditorDesign::setIntelliBoxPressBackColor( const QColor& color ) { + m_IntelliBoxPressBackColor = color; +} + +/// +/// @fn setIntelliBoxPressBorderColor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditorDesign::setIntelliBoxPressBorderColor( const QColor& color ) { + m_IntelliBoxPressBorderColor = color; +} + +/// +/// @fn setEditorFont +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditorDesign::setEditorFont( const QFont& font ) { + m_EditorFont = font; +} + +/// +/// @fn setIntelliBoxFont +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditorDesign::setIntelliBoxFont( const QFont& font ) { + m_IntelliBoxFont = font; +} + +/// +/// @fn setEditorBorder +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditorDesign::setEditorBorder( const QMargins& border ) { + m_EditorBorder = border; +} + +/// +/// @fn setIntelliBoxBorder +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditorDesign::setIntelliBoxBorder( const QMargins& border ) { + m_IntelliBoxBorder = border; +} + +/// +/// @fn setLineColumnPadding +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditorDesign::setLineColumnPadding( const QLineColumnPadding& padding ) { + m_LineColumnPadding = padding; +} + +/// +/// @fn setLineColumnVisible +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditorDesign::setLineColumnVisible( bool visible ) { + m_HasLineColumn = visible; +} + +/// +/// @fn showFocusRect +/// @author Nicolas Kogler +/// @date October 6th, 2016 +/// +void QCodeEditorDesign::showFocusRect( bool show ) { + m_ShowFocusRect = show; +} + +/// +/// @fn setFirstLineAsOne +/// @author Nicolas Kogler +/// @date October 6th, 2016 +/// +void QCodeEditorDesign::setFirstLineAsOne( bool one ) { + m_FirstLineOne = one; +} +} // namespace RadiumNBR::Gui diff --git a/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorDesign.hpp b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorDesign.hpp new file mode 100644 index 0000000..dd56240 --- /dev/null +++ b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorDesign.hpp @@ -0,0 +1,399 @@ +#pragma once +#include <RadiumNBR/NodeBasedRendererMacro.hpp> + +// +// Included headers +// +#include <QColor> +#include <QFont> +#include <RadiumNBR/Gui/CodeEditor/LineColumnPadding.hpp> + +namespace RadiumNBR::Gui { + +/// +/// @file QCodeEditorDesign.hpp +/// @author Nicolas Kogler +/// @date October 4th, 2016 +/// @class QCodeEditorDesign +/// @brief Specifies the visual appearance of the QCodeEditor. +/// +class NodeBasedRenderer_LIBRARY_API QCodeEditorDesign +{ + public: + /// + /// @fn Default constructor + /// @brief Initializes a new instance of QCodeEditorDesign. + /// + QCodeEditorDesign(); + + /// + /// @fn Constructor + /// @brief Loads a code editor design from a file. + /// @param path Path to the XML design file + /// + QCodeEditorDesign( const QString& path ); + + /// + /// @fn Copy constructor + /// @brief Copies one QCodeEditorDesign to another. + /// @param design Other design + /// + QCodeEditorDesign( const QCodeEditorDesign& design ); + + /// + /// @fn Destructor + /// @brief Frees all resources allocated by QCodeEditorDesign. + /// + ~QCodeEditorDesign(); + + /// + /// @fn editorBackColor : const + /// @brief Retrieves the background color of the code editor. + /// @returns the background color of the editor. + /// + const QColor& editorBackColor() const; + + /// + /// @fn editorTextColor : const + /// @brief Retrieves the text color of the code editor. + /// @returns the text color of the editor. + /// + const QColor& editorTextColor() const; + + /// + /// @fn editorBorderColor : const + /// @brief Retrieves the border color of the code editor. + /// @returns the border color of the editor. + /// + const QColor& editorBorderColor() const; + + /// + /// @fn lineColumnBackColor : const + /// @brief Retrieves the bg color of the line column. + /// @returns the bg color of the line column. + /// + const QColor& lineColumnBackColor() const; + + /// + /// @fn lineColumnSeparatorColor : const + /// @brief Retrieves the color of the separator between column and editor. + /// @returns the separator color between line widget and editor. + /// + const QColor& lineColumnSeparatorColor() const; + + /// + /// @fn lineColumnTextColor : const + /// @brief Retrieves the color of the line number texts. + /// @returns the color in which the line numbers are rendered. + /// + const QColor& lineColumnTextColor() const; + + /// + /// @fn activeLineColor : const + /// @brief Retrieves the color of the line number text that is currently active. + /// @returns the active line number text. + /// + const QColor& activeLineColor() const; + + /// + /// @fn intelliBoxBackColor : const + /// @brief Retrieves the bg color of the intellisense box. + /// @returns the bg color of the intellisense box. + /// + const QColor& intelliBoxBackColor() const; + + /// + /// @fn intelliBoxTextColor : const + /// @brief Retrieves the text color of the intellisense box. + /// @returns the text color of the intellisense box. + /// + const QColor& intelliBoxTextColor() const; + + /// + /// @fn intelliBoxBorderColor : const + /// @brief Retrieves the border color of the intellisense box. + /// @returns the border color of the intellisense box. + /// + const QColor& intelliBoxBorderColor() const; + + /// + /// @fn intelliBoxSelectionBackColor : const + /// @brief Retrieves the selection back color of the intellisense items. + /// @returns the selection back color of the intellisense items. + /// + const QColor& intelliBoxSelectionBackColor() const; + + /// + /// @fn intelliBoxSelectionBorderColor : const + /// @brief Retrieves the selection border color of the intellisense items. + /// @returns the selection border color of the intellisense items. + /// + const QColor& intelliBoxSelectionBorderColor() const; + + /// + /// @fn intelliBoxPressBackColor : const + /// @brief Retrieves the back color of the intellisense items on mouse-press. + /// @returns the back color of the intellisense items on mouse-press. + /// + const QColor& intelliBoxPressBackColor() const; + + /// + /// @fn intelliBoxPressBorderColor : const + /// @brief Retrieves the border color of the intellisense items on mouse-press. + /// @returns the Press border color of the intellisense items on mouse-press. + /// + const QColor& intelliBoxPressBorderColor() const; + + /// + /// @fn editorFont : const + /// @brief Retrieves the font of the default editor text. + /// @returns a pointer to the editor font. + /// + const QFont& editorFont() const; + + /// + /// @fn intelliBoxFont : const + /// @brief Retrieves the font of the intellisense box text. + /// @returns a pointer to the intellisense box font. + /// + const QFont& intelliBoxFont() const; + + /// + /// @fn editorBorder : const + /// @brief Retrieves the border-width in all directions. + /// @returns a reference to a QMargins instance. + /// + const QMargins& editorBorder() const; + + /// + /// @fn intelliBoxBorder : const + /// @brief Retrieves the border-width in all directions. + /// @returns a reference to a QMargins instance. + /// + const QMargins& intelliBoxBorder() const; + + /// + /// @fn lineColumnPadding : const + /// @brief Retrieves the padding in the right- and left direction. + /// @returns a ref to a QLineColumnPadding instance. + /// + const QLineColumnPadding& lineColumnPadding() const; + + /// + /// @fn popupSize : const + /// @brief Retrieves the size of the auto-completion menu. + /// @returns a ref to a QSize instance. + /// + const QSize& popupSize() const; + + /// + /// @fn isLineColumnVisible : const + /// @brief Determines whether the line column is visible. + /// @returns true if the line column is visible. + /// + bool isLineColumnVisible() const; + + /// + /// @fn hasFocusRect : const + /// @brief Determines whether the intelliBox items should have a focus rectangle. + /// @returns true if a dotted rectangle should be drawn around a focused item. + /// + bool hasFocusRect() const; + + /// + /// @fn startsWithOne : const + /// @brief Determines whether the line numbers start with 1. + /// @returns true if 1 is the first line. + /// + bool startsWithOne() const; + + /// + /// @fn setEditorBackColor + /// @brief Specifies the bg color of the code editor. + /// @param color Color to use as background + /// + void setEditorBackColor( const QColor& color ); + + /// + /// @fn setEditorTextColor + /// @brief Specifies the bg color of the code editor. + /// @param color Color to draw text in + /// + void setEditorTextColor( const QColor& color ); + + /// + /// @fn setEditorBorderColor + /// @brief Specifies the bg color of the code editor. + /// @param color Color to draw border in + /// + void setEditorBorderColor( const QColor& color ); + + /// + /// @fn setLineColumnBackColor + /// @brief Specifies the bg color of the line column. + /// @param color Color to use as background + /// + void setLineColumnBackColor( const QColor& color ); + + /// + /// @fn setLineColumnSeparatorColor + /// @brief Specifies the separator color of the line column. + /// @param color Color to draw separator in + /// + void setLineColumnSeparatorColor( const QColor& color ); + + /// + /// @fn setLineColumnTextColor + /// @brief Specifies the text color of the line column. + /// @param color Color to draw text in + /// + void setLineColumnTextColor( const QColor& color ); + + /// + /// @fn setActiveLineColor + /// @brief Specifies the active line color + /// @param color Color to draw active line text in + /// + void setActiveLineColor( const QColor& color ); + + /// + /// @fn setIntelliBoxBackColor + /// @brief Specifies the bg color of the intelliBox. + /// @param color Color to use as background + /// + void setIntelliBoxBackColor( const QColor& color ); + + /// + /// @fn setIntelliBoxTextColor + /// @brief Specifies the text color of the intelliBox. + /// @param color Color to draw text in + /// + void setIntelliBoxTextColor( const QColor& color ); + + /// + /// @fn setIntelliBoxBorderColor + /// @brief Specifies the border color of the intelliBox. + /// @param color Color to draw border in + /// + void setIntelliBoxBorderColor( const QColor& color ); + + /// + /// @fn setIntelliBoxSelectionBackColor + /// @brief Specifies the bg color of the intelliBox-selection. + /// @param color Color to use as selection background + /// + void setIntelliBoxSelectionBackColor( const QColor& color ); + + /// + /// @fn setIntelliBoxSelectionBorderColor + /// @brief Specifies the border color of the intelliBox-selection. + /// @param color Color to draw selection border in + /// + void setIntelliBoxSelectionBorderColor( const QColor& color ); + + /// + /// @fn setIntelliBoxPressBackColor + /// @brief Specifies the bg color of the items on mouse-press. + /// @param color Color to use on mouse-press + /// + void setIntelliBoxPressBackColor( const QColor& color ); + + /// + /// @fn setIntelliBoxPressBorderColor + /// @brief Specifies the border color of the items on mouse-press. + /// @param color Color to draw border in on mouse-press + /// + void setIntelliBoxPressBorderColor( const QColor& color ); + + /// + /// @fn setEditorFont + /// @brief Specifies the default font for editor-text. + /// @param font Pointer to font to use for default text + /// + void setEditorFont( const QFont& font ); + + /// + /// @fn setIntelliBoxFont + /// @brief Specifies the default font for intelliBox-text. + /// @param font Pointer to font to use for default text + /// + void setIntelliBoxFont( const QFont& font ); + + /// + /// @fn setEditorBorder + /// @brief Specifies the thickness of the editor border. + /// @param border QMargins specifying the thickness in all directions + /// + void setEditorBorder( const QMargins& border ); + + /// + /// @fn setIntelliBoxBorder + /// @brief Specifies the thickness of the intelliBox border. + /// @param border QMargins specifying the thickness in all directions + /// + void setIntelliBoxBorder( const QMargins& border ); + + /// + /// @fn setLineColumnPadding + /// @brief Specifies the line column padding. + /// @param padding Ref to instance of QLineColumnPadding + /// + void setLineColumnPadding( const QLineColumnPadding& padding ); + + /// + /// @fn setPopupSize + /// @brief Specifies the size of the auto-complete menu. + /// @param size Ref to instance of QSize + /// + void setPopupSize( const QSize& size ); + + /// + /// @fn setLineColumnVisible + /// @brief Determines the visibility of the line column. + /// @param visible True if should be drawn, otherwise false + /// + void setLineColumnVisible( bool visible ); + + /// + /// @fn showFocusRect + /// @brief Specifies whether to show a dotted rectangle around a focused intelliBox item. + /// @param show True if a dotted rectangle should be drawn. + /// + void showFocusRect( bool show ); + + /// + /// @fn setFirstLineOne + /// @brief Specifies whether to set the first line as one and not zero. + /// @param one True if line numbers should start with 1. + /// + void setFirstLineAsOne( bool one ); + + private: + // + // Private class members + // + QColor m_EditorBackColor; + QColor m_EditorTextColor; + QColor m_EditorBorderColor; + QColor m_LineColumnBackColor; + QColor m_LineColumnTextColor; + QColor m_LineColumnSeparatorColor; + QColor m_ActiveLineColor; + QColor m_IntelliBoxBackColor; + QColor m_IntelliBoxTextColor; + QColor m_IntelliBoxBorderColor; + QColor m_IntelliBoxSelectionBackColor; + QColor m_IntelliBoxSelectionBorderColor; + QColor m_IntelliBoxPressBackColor; + QColor m_IntelliBoxPressBorderColor; + QFont m_EditorFont; + QFont m_IntelliBoxFont; + QMargins m_EditorBorder; + QMargins m_IntelliBoxBorder; + QLineColumnPadding m_LineColumnPadding; + QSize m_PopupSize; + bool m_HasLineColumn; + bool m_ShowFocusRect; + bool m_FirstLineOne; +}; +} // namespace RadiumNBR::Gui diff --git a/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorEvents.cpp b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorEvents.cpp new file mode 100644 index 0000000..43e7e54 --- /dev/null +++ b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorEvents.cpp @@ -0,0 +1,295 @@ +// +// QCodeEditor - Widget to highlight and auto-complete code. +// Copyright (C) 2016 Nicolas Kogler (kogler.cml@hotmail.com) +// +// This file is part of QCodeEditor. +// +// QCodeEditor is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with QCodeEditor. If not, see <http://www.gnu.org/licenses/>. +// +// + +// +// Included headers +// +#include <QAbstractItemView> +#include <QCompleter> +#include <QPainter> +#include <QScrollBar> +#include <RadiumNBR/Gui/CodeEditor/CodeEditor.hpp> +#include <RadiumNBR/Gui/CodeEditor/CodeEditorLineWidget.hpp> + +namespace RadiumNBR::Gui { + +/// +/// @fn paintEvent +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditor::paintEvent( QPaintEvent* event ) { + // Paints the text edit and the line column + QPlainTextEdit::paintEvent( event ); + + // Using style-sheets as for now! + /*// Paints a border around the widget, if requested + if (!m_Design.editorBorder().isNull()) { + QPainter painter(viewport()); + + // Changes the pen-color of the painter + painter.setBrush(QBrush(m_Design.editorBorderColor())); + + // Draws a line in each direction requested + QMargins border = m_Design.editorBorder(); + if (border.top()) { + painter.fillRect(0, 0, width(), border.top(), Qt::SolidPattern); + } if (border.left()) { + painter.fillRect(0, 0, border.left(), height(), Qt::SolidPattern); + } if (border.bottom()) { + painter.fillRect(0, height()-border.bottom(), width(), border.bottom(), + Qt::SolidPattern); } if (border.right()) { painter.fillRect(width()-border.right(), 0, + border.right(), height(), Qt::SolidPattern); + } + }*/ +} + +/// +/// @fn keyReleaseEvent +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditor::keyReleaseEvent( QKeyEvent* event ) { + if ( event->key() == Qt::Key_Tab ) { return; } + + QPlainTextEdit::keyReleaseEvent( event ); +} + +/// +/// @fn keyPressEvent +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditor::keyPressEvent( QKeyEvent* event ) { + static QChar par( 0x2029 ); // Qt uses paragraph separators + static QString tab = QString( " " ).repeated( 4 ); + + // The code editor should not receive keys + // while the auto complete menu is open. + if ( m_AutoComplete->popup()->isVisible() ) + { + switch ( event->key() ) + { + case Qt::Key_Tab: + case Qt::Key_Enter: + case Qt::Key_Return: + case Qt::Key_Escape: + case Qt::Key_Backtab: + event->ignore(); + return; + default: + break; + } + } + + // Adds tabs in front of the selected block(s) + if ( event->key() == Qt::Key_Tab && !textCursor().selectedText().isEmpty() ) + { + // Retrieves the amount of lines within the selected text + QTextCursor cursor = textCursor(); + QString selected = cursor.selectedText(); + qint32 amountOfLines = selected.count( par ) + 1; + + // Does not do anything if only one line is selected + if ( amountOfLines == 1 ) { return; } + + // Selects the start of the current line and retrieves the position + int linePos, lineCopy; + cursor.setPosition( cursor.selectionStart() ); + cursor.movePosition( QTextCursor::StartOfLine ); + linePos = lineCopy = cursor.position(); + cursor.beginEditBlock(); + + // Inserts tabs for each selected line + for ( int i = 0; i < amountOfLines; ++i ) + { + cursor.setPosition( linePos ); + cursor.insertText( tab ); + cursor.movePosition( QTextCursor::Down ); + cursor.movePosition( QTextCursor::StartOfLine ); + linePos = cursor.position(); + } + + // Selects all the text + cursor.movePosition( QTextCursor::Down ); + cursor.movePosition( QTextCursor::EndOfLine ); + cursor.setPosition( lineCopy, QTextCursor::KeepAnchor ); + cursor.endEditBlock(); + setTextCursor( cursor ); + return; + } + + // Removes tabs in front of selected block(s) + if ( event->key() == Qt::Key_Backtab && !textCursor().selectedText().isEmpty() ) + { + // Retrieves the amount of lines within the selected text + QTextCursor cursor = textCursor(); + QString selected = cursor.selectedText(); + qint32 amountOfLines = selected.count( par ) + 1; + + // Does not do anything if only one line is selected + if ( amountOfLines == 1 ) { return; } + + // Retrieves the start of the selection + int start = 0, line, diff, copy; + cursor.setPosition( cursor.selectionStart() ); + cursor.movePosition( QTextCursor::StartOfLine ); + copy = cursor.position(); + + if ( selected.at( 0 ).isSpace() ) + { + cursor.movePosition( QTextCursor::NextWord ); + start = cursor.position(); + } + + cursor.clearSelection(); + cursor.beginEditBlock(); + + // Removes a tab from each line + for ( int i = 0; i < amountOfLines; ++i ) + { + cursor.setPosition( start ); + cursor.movePosition( QTextCursor::StartOfLine ); + line = cursor.position(); + cursor.setPosition( start ); + + if ( start == line ) + { + continue; // nothing to remove + } + + diff = qMin( 4, start - line ); + for ( int i = 0; i < diff; ++i ) + { + cursor.deletePreviousChar(); + } + + // Finds position of the first word in the next line + cursor.movePosition( QTextCursor::Down ); + cursor.movePosition( QTextCursor::StartOfLine ); + cursor.movePosition( QTextCursor::NextWord ); + cursor.movePosition( QTextCursor::StartOfWord ); + start = cursor.position(); + } + + // Selects all the text + cursor.movePosition( QTextCursor::Down ); + cursor.movePosition( QTextCursor::EndOfLine ); + cursor.setPosition( copy, QTextCursor::KeepAnchor ); + cursor.endEditBlock(); + setTextCursor( cursor ); + return; + } + + // Replaces a tab with four whitespaces + if ( event->key() == Qt::Key_Tab ) + { + QTextCursor cursor = textCursor(); + cursor.insertText( QString( " " ).repeated( 4 ) ); + setTextCursor( cursor ); + return; + } + + // Forwards the pressed key to the QPlainTextEdit + QPlainTextEdit::keyPressEvent( event ); + + // Retrieves the underlying word for further checks + QTextCursor cursor = textCursor(); + cursor.select( QTextCursor::WordUnderCursor ); + + // Close intellisense box if the current character is a + // whitespace and the backspace key was pressed. + if ( event->key() == Qt::Key_Backspace ) + { + if ( cursor.selectedText().trimmed().isEmpty() ) + { + m_AutoComplete->popup()->hide(); + return; + } + } + + // Does absolutely nothing if a single modifier key was pressed + if ( ( event->modifiers() & ( Qt::ControlModifier | Qt::ShiftModifier ) ) && + event->text().isEmpty() ) + { return; } + + // Does absolutely nothing if: + // the word is interrupted by an invalid character + // the word is too short to trigger a completion + static QString invalidChars( "~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-=" ); + if ( event->text().isEmpty() || cursor.selectedText().length() < m_CompletionTrigger || + invalidChars.contains( event->text().right( 1 ) ) ) + { + + // Hides the completion menu + m_AutoComplete->popup()->hide(); + return; + } + + // Filters the keyword list + QString prefix = QString( "^" ) + cursor.selectedText(); + m_RuleFilter->setFilterRegExp( QRegExp( prefix, Qt::CaseInsensitive ) ); + m_AutoComplete->popup()->setCurrentIndex( m_AutoComplete->completionModel()->index( 0, 0 ) ); + + // Determines whether any match has been found + if ( m_AutoComplete->popup()->model()->rowCount() == 0 ) + { + m_AutoComplete->popup()->hide(); + return; + } + + // Shows the auto-complete box, if not already + if ( !m_AutoComplete->popup()->isVisible() ) + { + // Sets the position of the popup menu and shows it + QRect rect = cursorRect(); + rect.moveTo( rect.x() + lineColumnWidth() - fontMetrics().horizontalAdvance( prefix ), + rect.y() + 4 ); + rect.setWidth( m_AutoComplete->popup()->sizeHintForColumn( 0 ) + + m_AutoComplete->popup()->verticalScrollBar()->sizeHint().width() ); + + m_AutoComplete->complete( rect ); + } +} + +/// +/// @fn resizeEvent +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditor::resizeEvent( QResizeEvent* event ) { + Q_UNUSED( event ); + + // Makes space for the line widget + if ( m_Design.isLineColumnVisible() ) + { + QRect content = contentsRect(); + setViewportMargins( lineColumnWidth(), 0, 0, 0 ); + m_LineWidget->setGeometry( + content.left(), content.top(), lineColumnWidth(), content.height() ); + } + else + { + setViewportMargins( 0, 0, 0, 0 ); + m_LineWidget->setGeometry( 0, 0, 0, 0 ); + } +} +} // namespace RadiumNBR::Gui diff --git a/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorHighlighter.cpp b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorHighlighter.cpp new file mode 100644 index 0000000..f0e6e9b --- /dev/null +++ b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorHighlighter.cpp @@ -0,0 +1,244 @@ +// +// QCodeEditor - Widget to highlight and auto-complete code. +// Copyright (C) 2016 Nicolas Kogler (kogler.cml@hotmail.com) +// +// This file is part of QCodeEditor. +// +// QCodeEditor is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with QCodeEditor. If not, see <http://www.gnu.org/licenses/>. +// +// + +// +// Included headers +// +#include <RadiumNBR/Gui/CodeEditor/CodeEditor.hpp> +#include <RadiumNBR/Gui/CodeEditor/CodeEditorHighlighter.hpp> + +#include <QUuid> + +namespace RadiumNBR::Gui { + +/// +/// @fn Constructor +/// @author Nicolas Kogler +/// @date October 6th, 2016 +/// +QCodeEditorHighlighter::QCodeEditorHighlighter( QCodeEditor* parent ) : + QSyntaxHighlighter( parent->document() ), + m_Rules( &parent->rules() ), + m_Design( &parent->design() ), + m_Parent( parent ) {} + +/// +/// @fn Destructor +/// @author Nicolas Kogler +/// @date October 6th, 2016 +/// +QCodeEditorHighlighter::~QCodeEditorHighlighter() {} + +/// +/// @fn updateFormats +/// @author Nicolas Kogler +/// @date October 8th, 2016 +/// +void QCodeEditorHighlighter::updateFormats() { + + m_Formats.clear(); + + // Pre-parses the rules' style into a list of QTextCharFormat + for ( const QSyntaxRule& rule : m_Parent->rules() ) + { + QTextCharFormat format; + + if ( rule.useFont() ) { format.setFont( rule.font() ); } + else + { format.setFont( m_Parent->font() ); } + if ( rule.foreColor().alpha() == 0 ) + { format.setForeground( QBrush( m_Parent->design().editorTextColor() ) ); } + else + { format.setForeground( rule.foreColor() ); } + + format.setBackground( QBrush( rule.backColor() ) ); + m_Formats.push_back( format ); + } +} + +/// +/// @fn setHighlighting +/// @author Nicolas Kogler +/// @date October 12th, 2016 +/// +void QCodeEditorHighlighter::setHighlighting( int start, + int length, + const QTextCharFormat& format ) { + setFormat( start, length, format ); +} + +/// +/// @fn highlightBlock +/// @author Nicolas Kogler +/// @date October 6th, 2016 +/// +void QCodeEditorHighlighter::highlightBlock( const QString& text ) { + if ( currentBlockUserData() != NULL ) + { + auto d = static_cast<QCodeEditorBlockData*>( currentBlockUserData() ); + QRegularExpression regex( d->re ); + if ( !regex.match( text ).hasMatch() ) { onRemove( d ); } + } + + int ruleIndex = 0; + setCurrentBlockState( -1 ); + + // Applies highlighting rules + const QList<QSyntaxRule>& rules = *m_Rules; + for ( const QSyntaxRule& rule : rules ) + { + const QTextCharFormat& format = m_Formats.at( ruleIndex ); + QRegularExpression regex( rule.regex() ); + QRegularExpressionMatch match; + + if ( rule.isGlobal() ) + { + + // Searches for more than one match + QRegularExpressionMatchIterator iter = regex.globalMatch( text ); + + if ( iter.hasNext() && !rule.closeRegex().isEmpty() && currentBlockState() == -1 ) + { setCurrentBlockState( ruleIndex ); } + + // Iterates through all other matches and highlights them + while ( iter.hasNext() ) + { + match = iter.next(); + setFormat( match.capturedStart(), match.capturedLength(), format ); + if ( !rule.id().isEmpty() ) + { + if ( currentBlockUserData() == NULL ) + { + setCurrentBlockUserData( new QCodeEditorBlockData( rule.regex() ) ); + emit onMatch( rule, match.captured(), currentBlock() ); + } + else + { + auto d = static_cast<QCodeEditorBlockData*>( currentBlockUserData() ); + if ( d->re == rule.regex() ) + { + // Already containing a match that has the exact regex: + // Remove it first, then add it again + emit onRemove( d ); + setCurrentBlockUserData( new QCodeEditorBlockData( rule.regex() ) ); + emit onMatch( rule, match.captured(), currentBlock() ); + } + } + } + } + } + else + { + + // Searches for only one match + match = regex.match( text ); + + if ( match.hasMatch() ) + { + if ( !rule.closeRegex().isEmpty() && currentBlockState() == -1 ) + { + // Before setting the multiline trigger, checks if + // the closing sequence is on the same line. + QRegularExpression mulEnd( rule.closeRegex() ); + if ( !mulEnd.match( text ).hasMatch() ) setCurrentBlockState( ruleIndex ); + } + if ( !rule.id().isEmpty() ) + { + if ( currentBlockUserData() == NULL ) + { + setCurrentBlockUserData( new QCodeEditorBlockData( rule.regex() ) ); + emit onMatch( rule, match.captured(), currentBlock() ); + } + else + { + auto d = static_cast<QCodeEditorBlockData*>( currentBlockUserData() ); + if ( d->re == rule.regex() ) + { + // Already containing a match that has the exact regex: + // Remove it first, then add it again + emit onRemove( d ); + setCurrentBlockUserData( new QCodeEditorBlockData( rule.regex() ) ); + emit onMatch( rule, match.captured(), currentBlock() ); + } + } + } + + setFormat( match.capturedStart(), match.capturedLength(), format ); + } + } + + ++ruleIndex; + } + + // If there has been a multi-line-rule match in the previous + // block, we need to check against its closing regex. + if ( previousBlockState() != -1 ) + { + const QSyntaxRule& rule = m_Rules->at( previousBlockState() ); + const QTextCharFormat format = m_Formats.at( previousBlockState() ); + QRegularExpression regex( rule.closeRegex() ); + QRegularExpressionMatch match = regex.match( text ); + + // If now has a match, ends the multi-line regex + if ( match.hasMatch() ) + { + setFormat( match.capturedStart(), match.capturedLength(), format ); + setCurrentBlockState( -1 ); + + if ( !rule.id().isEmpty() ) + { + if ( currentBlockUserData() == NULL ) + { + setCurrentBlockUserData( new QCodeEditorBlockData( rule.regex() ) ); + emit onMatch( rule, match.captured(), currentBlock() ); + } + else + { + auto d = static_cast<QCodeEditorBlockData*>( currentBlockUserData() ); + if ( d->re == rule.regex() ) + { + // Already containing a match that has the exact regex: + // Remove it first, then add it again + emit onRemove( d ); + setCurrentBlockUserData( new QCodeEditorBlockData( rule.regex() ) ); + emit onMatch( rule, match.captured(), currentBlock() ); + } + } + } + } + else + { + // Has no match, highlights the entire line and + // forwards the previous state to the next line. + setFormat( 0, text.length(), format ); + setCurrentBlockState( previousBlockState() ); + } + } + + // Provides custom highlighting logic + emit onHighlight( this ); +} + +/// +/// @brief Defines the global uuid list +/// +QList<QUuid> QCodeEditorBlockData::p; +} // namespace RadiumNBR::Gui diff --git a/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorHighlighter.hpp b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorHighlighter.hpp new file mode 100644 index 0000000..8d333f4 --- /dev/null +++ b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorHighlighter.hpp @@ -0,0 +1,142 @@ +#pragma once +#include <RadiumNBR/NodeBasedRendererMacro.hpp> + +// +// Included headers +// + +#include <RadiumNBR/Gui/CodeEditor/CodeEditorDesign.hpp> +#include <RadiumNBR/Gui/CodeEditor/SyntaxRule.hpp> + +#include <QObject> +#include <QSyntaxHighlighter> +#include <QTextBlockUserData> +#include <QTextDocument> + +namespace RadiumNBR::Gui { + +// +// Forward declarations +// +class QCodeEditor; +class QCodeEditorBlockData; + +/// +/// @file QCodeEditorHighlighter.hpp +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// @class QCodeEditorHighlighter +/// @brief Highlights keywords, functions, ... +/// +class NodeBasedRenderer_LIBRARY_API QCodeEditorHighlighter : public QSyntaxHighlighter +{ + Q_OBJECT + public: + /// + /// @fn Constructor + /// @brief Initializes a new instance of QCodeEditorHighlighter. + /// + QCodeEditorHighlighter( QCodeEditor* parent ); + + /// + /// @fn Destructor + /// @brief Frees all resources allocated by QCodeEditorHighlighter. + /// + ~QCodeEditorHighlighter(); + + /// + /// @fn updateFormats + /// @brief Updates the text char formats. + /// + /// Always call after setting new rules. + /// + void updateFormats(); + + /// + /// @fn setHighlighting + /// @brief Applies some visual styling to text in the current line. + /// @param start Start index to apply styling to + /// @param length Length of the text to style + /// @param format Format to apply + /// + void setHighlighting( int start, int length, const QTextCharFormat& format ); + + signals: + + /// + /// @fn onMatch : signal + /// @brief Will fire if a match is found. + /// + /// The signal will only fire if the rule containing the match + /// has a valid and unique string identifier (e.g <rule id="foo">) + /// + /// @param rule The rule that emitted this signal + /// @param sequence The matched sequence + /// @param block The current QTextBlock + /// + void onMatch( const QSyntaxRule& rule, QString sequence, QTextBlock block ); + + /// + /// @fn onHighlight : signal + /// @brief Will fire if a line was highlighted. + /// + /// Gives the possibility to add own highlighting logic afterwards. + /// + /// @param highlighter Pointer to this highlighter instance + /// + void onHighlight( QCodeEditorHighlighter* highlighter ); + + /// + /// @fn onRemove : signal + /// @brief Will fire if a line's highlighting was removed. + /// @param data Data containing the UUID. + /// + void onRemove( QCodeEditorBlockData* data ); + + protected: + /// + /// @fn highlightBlock + /// @brief Highlights the given text line. + /// @param text Current line to process + /// + void highlightBlock( const QString& text ) Q_DECL_OVERRIDE; + + private: + // + // Private class members + // + const QList<QSyntaxRule>* m_Rules; + const QCodeEditorDesign* m_Design; + const QCodeEditor* m_Parent; + QList<QTextCharFormat> m_Formats; +}; + +/// +/// @file QCodeEditorHighlighter.hpp +/// @author Nicolas Kogler +/// @date October 29th, 2016 +/// @class QCodeEditorBlockData +/// @brief Holds an unique identifier. +/// +class QCodeEditorBlockData : public QTextBlockUserData +{ + public: + /// @brief Tries to create a unique QUuid + QCodeEditorBlockData( QString r ) { + // Ensures that ID really is unique! + do + { id = QUuid::createUuid(); } while ( p.contains( id ) ); + + p.push_back( id ); + re = r; + } + + ~QCodeEditorBlockData() { p.removeOne( id ); } + + QUuid id; ///< Unique identifier for each match + QString re; ///< Causing regular expression + + private: + static QList<QUuid> p; ///< Holds all IDs to check uniqueness +}; +} // namespace RadiumNBR::Gui diff --git a/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorLineWidget.cpp b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorLineWidget.cpp new file mode 100644 index 0000000..a9cd320 --- /dev/null +++ b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorLineWidget.cpp @@ -0,0 +1,125 @@ +// +// QCodeEditor - Widget to highlight and auto-complete code. +// Copyright (C) 2016 Nicolas Kogler (kogler.cml@hotmail.com) +// +// This file is part of QCodeEditor. +// +// QCodeEditor is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with QCodeEditor. If not, see <http://www.gnu.org/licenses/>. +// +// + +// +// Included headers +// +#include <RadiumNBR/Gui/CodeEditor/CodeEditorLineWidget.hpp> + +#include <QPainter> +#include <QTextBlock> + +namespace RadiumNBR::Gui { + +/// +/// @fn Constructor +/// @author Nicolas Kogler +/// @date October 6th, 2016 +/// +QCodeEditorLineWidget::QCodeEditorLineWidget( QCodeEditor* parent ) : + QWidget( parent ), m_Parent( parent ), m_EditorView( parent->viewport() ) {} + +/// +/// @fn Destructor +/// @author Nicolas Kogler +/// @date October 6th, 2016 +/// +QCodeEditorLineWidget::~QCodeEditorLineWidget() {} + +/// +/// @fn sizeHint +/// @author Nicolas Kogler +/// @date October 6th, 2016 +/// +QSize QCodeEditorLineWidget::sizeHint() const { + if ( m_Parent->design().isLineColumnVisible() ) + { return QSize( m_Parent->lineColumnWidth(), 0 ); } + else + { + return QSize( 0, 0 ); // "Hide" line column + } +} + +/// +/// @fn paintEvent +/// @author Nicolas Kogler +/// @date October 6th, 2016 +/// +void QCodeEditorLineWidget::paintEvent( QPaintEvent* event ) { + + if ( !m_Parent->design().isLineColumnVisible() ) { return; } + + // Retrieves the content rectangle and creates the QPainter + const QCodeEditorDesign& design = m_Parent->design(); + const QRect& content = event->rect(); + QPainter painter( this ); + + // Paints the background and the separator + painter.fillRect( content, design.lineColumnBackColor() ); + painter.setPen( design.lineColumnSeparatorColor() ); + painter.drawLine( content.width() - 1, 0, content.width() - 1, height() - 1 ); + painter.setPen( design.lineColumnTextColor() ); + + // Retrieves the visible line numbers + QTextBlock firstLine = m_Parent->firstVisibleBlock(); + qint32 bnActive = m_Parent->textCursor().blockNumber() + ( ( design.startsWithOne() ) ? 1 : 0 ); + qint32 blockNumber = firstLine.blockNumber() + ( ( design.startsWithOne() ) ? 1 : 0 ); + qint32 blockTop = static_cast<int>( m_Parent->blockBoundingGeometry( firstLine ) + .translated( m_Parent->contentOffset() ) + .top() ); + qint32 blockBottom = + static_cast<int>( m_Parent->blockBoundingRect( firstLine ).height() ) + blockTop; + + // Paints all visible line numbers + while ( firstLine.isValid() && blockTop <= content.bottom() ) + { + if ( firstLine.isVisible() && blockBottom >= content.top() ) + { + if ( blockNumber == bnActive ) + { + painter.setPen( design.activeLineColor() ); + painter.drawText( 0, + blockTop, + width(), + fontMetrics().height(), + Qt::AlignCenter, + QString::number( blockNumber ) ); + painter.setPen( design.lineColumnTextColor() ); + } + else + { + painter.drawText( 0, + blockTop, + width(), + fontMetrics().height(), + Qt::AlignCenter, + QString::number( blockNumber ) ); + } + } + + // Jumps to the next line + blockNumber++; + firstLine = firstLine.next(); + blockTop = blockBottom; + blockBottom += static_cast<int>( m_Parent->blockBoundingRect( firstLine ).height() ); + } +} +} // namespace RadiumNBR::Gui diff --git a/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorLineWidget.hpp b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorLineWidget.hpp new file mode 100644 index 0000000..5e8f4c0 --- /dev/null +++ b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorLineWidget.hpp @@ -0,0 +1,56 @@ +#pragma once +#include <RadiumNBR/NodeBasedRendererMacro.hpp> + +// +// Included headers +// +#include <QWidget> +#include <RadiumNBR/Gui/CodeEditor/CodeEditor.hpp> + +namespace RadiumNBR::Gui { + +/// +/// @file QCodeEditorLineWidget.hpp +/// @author Nicolas Kogler +/// @date October 6th, 2016 +/// @class QCodeEditorLineWidget +/// @brief Paints the line column and the line numbers. +/// +class NodeBasedRenderer_LIBRARY_API QCodeEditorLineWidget : public QWidget +{ + public: + /// + /// @fn Constructor + /// @brief Initializes a new instance of QCodeEditorLineWidget. + /// + QCodeEditorLineWidget( QCodeEditor* parent ); + + /// + /// @fn Destructor + /// @brief Frees all resources allocated by QCodeEditorLineWidget. + /// + ~QCodeEditorLineWidget(); + + /// + /// @fn sizeHint + /// @brief Reserves space for the line widget. + /// @returns the desired size. + /// + QSize sizeHint() const Q_DECL_OVERRIDE; + + protected: + /// + /// @fn paintEvent + /// @brief Paints the line column and the line numbers. + /// @param event Pointer to a QPaintEvent instance + /// + void paintEvent( QPaintEvent* event ) Q_DECL_OVERRIDE; + + private: + // + // Private class members + // + QCodeEditor* m_Parent; + QWidget* m_EditorView; +}; +} // namespace RadiumNBR::Gui diff --git a/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorPopup.cpp b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorPopup.cpp new file mode 100644 index 0000000..9368ff1 --- /dev/null +++ b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorPopup.cpp @@ -0,0 +1,91 @@ +// +// QCodeEditor - Widget to highlight and auto-complete code. +// Copyright (C) 2016 Nicolas Kogler (kogler.cml@hotmail.com) +// +// This file is part of QCodeEditor. +// +// QCodeEditor is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with QCodeEditor. If not, see <http://www.gnu.org/licenses/>. +// +// + +// +// Included headers +// +#include <RadiumNBR/Gui/CodeEditor/CodeEditorPopup.hpp> +#include <RadiumNBR/Gui/CodeEditor/CodeEditorSheets.hpp> + +namespace RadiumNBR::Gui { + +/// +/// @fn Constructor +/// @author Nicolas Kogler +/// @date October 6th, 2016 +/// +QCodeEditorPopup::QCodeEditorPopup( QCodeEditor* parent ) : + QListView( parent ), m_Parent( parent ) { + + // Generates the stylesheets from the visual properties + m_StyleSheetNormal = QCodeEditorPopupSheets::hover( parent->design() ); + m_StyleSheetPress = QCodeEditorPopupSheets::press( parent->design() ); + + // Changes listview properties + setMouseTracking( true ); + setFrameShape( QFrame::NoFrame ); + setSelectionMode( QAbstractItemView::SingleSelection ); + setSelectionBehavior( QAbstractItemView::SelectItems ); + setVerticalScrollBarPolicy( Qt::ScrollBarAsNeeded ); + setFixedWidth( parent->design().popupSize().width() ); + setFixedHeight( parent->design().popupSize().height() ); + setStyleSheet( m_StyleSheetNormal ); + + // Applies several other properties + setFont( parent->design().intelliBoxFont() ); +} + +/// +/// @fn Destructor +/// @author Nicolas Kogler +/// @date October 6th, 2016 +/// +QCodeEditorPopup::~QCodeEditorPopup() {} + +/// +/// @fn viewportEvent +/// @author Nicolas Kogler +/// @date October 6th, 2016 +/// +bool QCodeEditorPopup::viewportEvent( QEvent* event ) { + if ( event->type() == QEvent::MouseMove ) + { + // Retrieves the item at the current mouse position + QMouseEvent* me = static_cast<QMouseEvent*>( event ); + QModelIndex index = indexAt( me->pos() ); + + // Selects the item at the mouse cursor position + selectionModel()->setCurrentIndex( index, QItemSelectionModel::ClearAndSelect ); + } + else if ( event->type() == QEvent::MouseButtonPress ) + { + // This is a little hack: There is no 'pressed' pseudo class + // to style the listview item on mouse-press, therefore we + // dynamically change the style-sheet. + if ( !selectedIndexes().isEmpty() ) { setStyleSheet( m_StyleSheetPress ); } + } + else if ( event->type() == QEvent::MouseButtonRelease ) + { setStyleSheet( m_StyleSheetNormal ); } + + // Processes the default event logic + return QListView::viewportEvent( event ); +} +} // namespace RadiumNBR::Gui diff --git a/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorPopup.hpp b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorPopup.hpp new file mode 100644 index 0000000..de1b940 --- /dev/null +++ b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorPopup.hpp @@ -0,0 +1,51 @@ +#pragma once +#include <RadiumNBR/NodeBasedRendererMacro.hpp> + +// +// Included headers +// +#include <QListView> +#include <RadiumNBR/Gui/CodeEditor/CodeEditor.hpp> + +namespace RadiumNBR::Gui { + +/// +/// @file QCodeEditorPopup.hpp +/// @author Nicolas Kogler +/// @date October 6th, 2016 +/// @class QCodeEditorPopup +/// @brief +/// +class NodeBasedRenderer_LIBRARY_API QCodeEditorPopup : public QListView +{ + public: + /// + /// @fn Constructor + /// @brief Initializes a new instance of QCodeEditorPopup. + /// + QCodeEditorPopup( QCodeEditor* parent ); + + /// + /// @fn Destructor + /// @brief Frees all resources allocated by QCodeEditorPopup. + /// + ~QCodeEditorPopup(); + + protected: + /// + /// @fn viewportEvent + /// @brief Intercepts events for the QCodeEditor viewport. + /// @param event Event of unknown type + /// @returns true to indicate we have handled the event ourselves. + /// + bool viewportEvent( QEvent* event ) Q_DECL_OVERRIDE; + + private: + // + // Private class members + // + QString m_StyleSheetNormal; ///< Holds the stylesheet on mouse-hover + QString m_StyleSheetPress; ///< Holds the stylesheet on mouse-press + QCodeEditor* m_Parent; ///< Holds the parent for design-property access +}; +} // namespace RadiumNBR::Gui diff --git a/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorResources.qrc b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorResources.qrc new file mode 100644 index 0000000..aec566a --- /dev/null +++ b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorResources.qrc @@ -0,0 +1,6 @@ +<RCC> + <qresource prefix="CodeEditor"> + <file>EditorDefaultDesign.xml</file> + <file>HighlightRules_cpp.xml</file> + </qresource> +</RCC> diff --git a/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorSheets.cpp b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorSheets.cpp new file mode 100644 index 0000000..cc39bed --- /dev/null +++ b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorSheets.cpp @@ -0,0 +1,98 @@ +// +// QCodeEditor - Widget to highlight and auto-complete code. +// Copyright (C) 2016 Nicolas Kogler (kogler.cml@hotmail.com) +// +// This file is part of QCodeEditor. +// +// QCodeEditor is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with QCodeEditor. If not, see <http://www.gnu.org/licenses/>. +// +// + +// +// Included headers +// +#include <RadiumNBR/Gui/CodeEditor/CodeEditorSheets.hpp> + +namespace RadiumNBR::Gui { + +/// +/// @fn border +/// @author Nicolas Kogler +/// @date October 6th, 2016 +/// +QString QCodeEditorSheets::border( const QCodeEditorDesign& design ) { + QString sheet( CSS_Editor_Widget ); + const QMargins& border = design.editorBorder(); + + // Replaces each variable with the respective design property + sheet.replace( "%t", QString::number( border.top() ) ); + sheet.replace( "%r", QString::number( border.right() ) ); + sheet.replace( "%b", QString::number( border.bottom() ) ); + sheet.replace( "%l", QString::number( border.left() ) ); + sheet.replace( "%c", QString::number( design.editorBorderColor().rgba(), 16 ) ); + + // Returns the modified sheet; can be applied immediately + return sheet; +} + +/// +/// @fn hover +/// @author Nicolas Kogler +/// @date October 6th, 2016 +/// +QString QCodeEditorPopupSheets::hover( const QCodeEditorDesign& design ) { + QString sheet( CSS_Popup_Widget ); + + // Replaces each variable with the respective design property + sheet.replace( "%border", QString::number( design.intelliBoxBorderColor().rgba(), 16 ) ); + sheet.replace( "%back", QString::number( design.intelliBoxBackColor().rgba(), 16 ) ); + sheet.replace( "%text", QString::number( design.intelliBoxTextColor().rgba(), 16 ) ); + sheet.replace( "%focus", design.hasFocusRect() ? "dotted" : "none" ); + sheet.replace( "%selbrd", + QString::number( design.intelliBoxSelectionBorderColor().rgba(), 16 ) ); + sheet.replace( "%selback", + QString::number( design.intelliBoxSelectionBackColor().rgba(), 16 ) ); + sheet.replace( "%t", QString::number( design.intelliBoxBorder().top() ) ); + sheet.replace( "%r", QString::number( design.intelliBoxBorder().right() ) ); + sheet.replace( "%b", QString::number( design.intelliBoxBorder().bottom() ) ); + sheet.replace( "%l", QString::number( design.intelliBoxBorder().left() ) ); + + // Returns the modified sheet; can be applied immediately + return sheet; +} + +/// +/// @fn press +/// @author Nicolas Kogler +/// @date October 6th, 2016 +/// +QString QCodeEditorPopupSheets::press( const QCodeEditorDesign& design ) { + QString sheet( CSS_Popup_Widget ); + + // Replaces each variable with the respective design property + sheet.replace( "%border", QString::number( design.intelliBoxBorderColor().rgba(), 16 ) ); + sheet.replace( "%back", QString::number( design.intelliBoxBackColor().rgba(), 16 ) ); + sheet.replace( "%text", QString::number( design.intelliBoxTextColor().rgba(), 16 ) ); + sheet.replace( "%focus", design.hasFocusRect() ? "dotted" : "none" ); + sheet.replace( "%selbrd", QString::number( design.intelliBoxPressBorderColor().rgba(), 16 ) ); + sheet.replace( "%selback", QString::number( design.intelliBoxPressBackColor().rgba(), 16 ) ); + sheet.replace( "%t", QString::number( design.intelliBoxBorder().top() ) ); + sheet.replace( "%r", QString::number( design.intelliBoxBorder().right() ) ); + sheet.replace( "%b", QString::number( design.intelliBoxBorder().bottom() ) ); + sheet.replace( "%l", QString::number( design.intelliBoxBorder().left() ) ); + + // Returns the modified sheet; can be applied immediately + return sheet; +} +} // namespace RadiumNBR::Gui diff --git a/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorSheets.hpp b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorSheets.hpp new file mode 100644 index 0000000..e0918b5 --- /dev/null +++ b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorSheets.hpp @@ -0,0 +1,91 @@ +#pragma once +#include <RadiumNBR/NodeBasedRendererMacro.hpp> + +#include <RadiumNBR/Gui/CodeEditor/CodeEditorDesign.hpp> + +namespace RadiumNBR::Gui { + +/// +/// @file QCodeEditorSheets.hpp +/// @author Nicolas Kogler +/// @date October 7th, 2016 +/// @def CSS_Editor_Widget +/// @brief Stylesheet for the editor widget. +/// +const char CSS_Editor_Widget[] = {"QPlainTextEdit {" + " border-top: %tpx solid #%c;" + " border-right: %rpx solid #%c;" + " border-bottom: %bpx solid #%c;" + " border-left: %lpx solid #%c;" + "}"}; + +/// +/// @file QCodeEditorSheets.hpp +/// @author Nicolas Kogler +/// @date October 6th, 2016 +/// @def CSS_Popup_Widget +/// @brief Stylesheet for the popup widget. +/// +const char CSS_Popup_Widget[] = {"QListView {" + " border-top: %tpx solid #%border;" + " border-left: %lpx solid #%border;" + " border-right: %rpx solid #%border;" + " border-bottom: %bpx solid #%border;" + " background-color: #%back;" + " color: #%text;" + " outline: %focus;" + "}" + "QListView::item:selected {" + " padding: -1px;" + " border: 1px solid #%selbrd;" + " background-color: #%selback;" + " color: #%text;" + "}" + "QListView::item:!selected:hover {" + " background: transparent;" + "}"}; + +/// +/// @file QCodeEditorSheets.hpp +/// @author Nicolas Kogler +/// @date October 6th, 2016 +/// @class QCodeEditorSheets +/// +class QCodeEditorSheets +{ + public: + /// + /// @fn border + /// @brief Retrieves the style-sheet for the border. + /// @param design Current code editor design + /// @returns the style-sheet for the border. + /// + static QString border( const QCodeEditorDesign& design ); +}; + +/// +/// @file QCodeEditorSheets.hpp +/// @author Nicolas Kogler +/// @date October 6th, 2016 +/// @class QCodeEditorPopupSheets +/// +class QCodeEditorPopupSheets +{ + public: + /// + /// @fn hover + /// @brief Retrieves the style-sheet for mouse-hover. + /// @param design Current code editor design + /// @returns the style-sheet for mouse-hovers. + /// + static QString hover( const QCodeEditorDesign& design ); + + /// + /// @fn press + /// @brief Retrieves the style-sheet for mouse-press. + /// @param design Current code editor design + /// @returns the style-sheet for mouse-presses. + /// + static QString press( const QCodeEditorDesign& design ); +}; +} // namespace RadiumNBR::Gui diff --git a/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorSlots.cpp b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorSlots.cpp new file mode 100644 index 0000000..94c587a --- /dev/null +++ b/src/libRender/RadiumNBR/Gui/CodeEditor/CodeEditorSlots.cpp @@ -0,0 +1,86 @@ +// +// QCodeEditor - Widget to highlight and auto-complete code. +// Copyright (C) 2016 Nicolas Kogler (kogler.cml@hotmail.com) +// +// This file is part of QCodeEditor. +// +// QCodeEditor is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with QCodeEditor. If not, see <http://www.gnu.org/licenses/>. +// +// + +// +// Included headers +// +#include <QCompleter> +#include <RadiumNBR/Gui/CodeEditor/CodeEditor.hpp> +#include <RadiumNBR/Gui/CodeEditor/CodeEditorLineWidget.hpp> + +namespace RadiumNBR::Gui { + +/// +/// @fn updateLineColumn +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditor::updateLineColumn( int lineCount ) { + Q_UNUSED( lineCount ); + setViewportMargins( lineColumnWidth(), 0, 0, 0 ); +} + +/// +/// @fn scrollLineColumn +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditor::scrollLineColumn( QRect view, int scroll ) { + // Scrolls the line widget to the current scrollbar value + if ( m_Design.isLineColumnVisible() ) + { + if ( scroll != 0 ) { m_LineWidget->scroll( 0, scroll ); } + else + { m_LineWidget->update( 0, view.y(), m_LineWidget->width(), view.height() ); } + + // Changes margins, line number digits may have changed + setViewportMargins( lineColumnWidth(), 0, 0, 0 ); + } + else + { setViewportMargins( 0, 0, 0, 0 ); } +} + +/// +/// @fn completeWord +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QCodeEditor::completeWord( const QString& word ) { + QTextCursor caretPos = textCursor(); + + // Inserts the missing characters at the current pos + caretPos.movePosition( QTextCursor::Left ); + caretPos.movePosition( QTextCursor::StartOfWord ); + caretPos.select( QTextCursor::WordUnderCursor ); + caretPos.removeSelectedText(); + caretPos.insertText( word ); + setTextCursor( caretPos ); +} + +/// +/// @fn textChanged +/// @author Nicolas Kogler +/// @date October 20th, 2016 +/// +void QCodeEditor::textChanged() { + emit lineChanged( textCursor().block() ); +} +} // namespace RadiumNBR::Gui diff --git a/src/libRender/RadiumNBR/Gui/CodeEditor/EditorDefaultDesign.xml b/src/libRender/RadiumNBR/Gui/CodeEditor/EditorDefaultDesign.xml new file mode 100644 index 0000000..0426505 --- /dev/null +++ b/src/libRender/RadiumNBR/Gui/CodeEditor/EditorDefaultDesign.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<design> + <!-- Order of declaration and character case does not matter --> + <editorbackcolor>#222222</editorbackcolor> + <editortextcolor>#eeeeee</editortextcolor> + <editorbordercolor>#b9b9b9</editorbordercolor> + <linecolumnbackcolor>#e9e9e9</linecolumnbackcolor> + <linecolumntextcolor>#6a9fc6</linecolumntextcolor> + <linecolumnseparatorcolor>#b9b9b9</linecolumnseparatorcolor> + <activelinecolor>#fc9100</activelinecolor> + <intelliboxbackcolor>#333333</intelliboxbackcolor> + <intelliboxtextcolor>#fafafa</intelliboxtextcolor> + <intelliboxbordercolor>#b9b9b9</intelliboxbordercolor> + <intelliboxselectionbackcolor>#c0dcf3</intelliboxselectionbackcolor> + <intelliboxselectionbordercolor>#90c8f6</intelliboxselectionbordercolor> + <intelliboxpressbackcolor>#90c8f6</intelliboxpressbackcolor> + <intelliboxpressbordercolor>#60b0f9</intelliboxpressbordercolor> + <editorborder>0 0 0 0</editorborder> <!-- left, top, right, bottom --> + <intelliboxborder>1 1 1 1</intelliboxborder> + <linecolumnpadding>16 16</linecolumnpadding> <!-- left, right --> + <popupsize>400 300</popupsize> <!-- width, height --> + <haslinecolumn>true</haslinecolumn> + <showfocusrect>false</showfocusrect> + <firstlineone>true</firstlineone> +</design> diff --git a/src/libRender/RadiumNBR/Gui/CodeEditor/HighlightRules_cpp.xml b/src/libRender/RadiumNBR/Gui/CodeEditor/HighlightRules_cpp.xml new file mode 100644 index 0000000..9f2c146 --- /dev/null +++ b/src/libRender/RadiumNBR/Gui/CodeEditor/HighlightRules_cpp.xml @@ -0,0 +1,76 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes" ?> +<rules> + <rule> + <!-- Styles binary, hexadecimal and decimal numbers --> + <regex>(\b\d+\b)|(\b0[xX][0-9a-fA-F]+\b)|(\b0[b][01]+\b)</regex> + <forecolor>#fc9100</forecolor> + </rule> + <rule> + <!-- Styles all C++ operators --> + <regex>(\B|\b)(::|\.|->|\[\]|\(\)|\+\+|-=|--|~|!|-|\+=|\+|&&|&=|&|\*=|\*|\/=|\/|%=|%|<<=|<<|>>=|>>|<=|<|>=|>|==|!=|\|\||\|=|\||\?|:|\^=|\^|\,)(\B|\b)</regex> + <font> + <bold>true</bold> + </font> + </rule> + <rule> + <!-- Styles strings and muli-line strings --> + <regex>(\"|<)((\\.)|[^\\"])*(\"|>)</regex> + <forecolor>#fc9100</forecolor> + </rule> + <rule global="false"> + <!-- Styles the C++ preprocessor diretives --> + <keywords start="true"> + #include #define #undef #if #ifdef #ifndef #error #pragma + </keywords> + <forecolor>#ca5179</forecolor> + </rule> + <rule> + <!-- Styles predefined compiler values like __FILE__ --> + <keywords> + defined __FILE__ __LINE__ __DATE__ __TIME__ __TIMESTAMP__ + </keywords> + <forecolor>#ca5179</forecolor> + </rule> + <rule id="macro" global="false"> + <!-- Styles definitions defined via #define --> + <regex>(\s*#define\s+\K)\S+(?=\s+\S+)</regex> + <forecolor>#795db2</forecolor> + </rule> + <rule> + <!-- Styles all C++ keywords --> + <keywords> + alignas alignof and and_eq asm auto bitand bitor bool break case catch char char16_t char32_t + class compl const constexpr const_cast continue decltype default delete do double dynamic_cast + else enum explicit export extern false float for friend goto if inline int long mutable namespace + new noexcept not not_eq nullptr operator or or_eq private protected public register reinterpret_cast + return short signed sizeof static static_assert static_cast struct switch template this thread_local + throw true try typedef typeid typename union unsigned using virtual void volatile wchar_t while + xor xor_eq override final + </keywords> + <forecolor>#2b8f49</forecolor> + </rule> + <rule global="false"> + <!-- Styles an enum or class type --> + <regex>((class\s+\K)|(enum\s+\K)|(namespace\s+\K))[A-Za-z]\w*</regex> + <forecolor>#3392ac</forecolor> + </rule> + <rule global="false"> + <!-- Styles multi-line comments --> + <startRegex>(\/\*.*\*\/)|(\/\*.*)</startRegex> + <closeRegex>.*\*\/</closeRegex> + <forecolor>#969698</forecolor> + </rule> + <rule global="false"> + <!-- Styles comments --> + <regex>(\/\*([\s\S]*?)\*\/)|(\/\/.*)</regex> + <forecolor>#969698</forecolor> + </rule> + <rule global="false"> + <!-- Styles doxygen keywords --> + <regex>(\/(\/|\*)(\/|!|\*)\s*\K)(\\|@)(addindex|addtogroup|anchor|arg|attention|authors|author|a|brief|bug|b|callergraph|callgraph|cite|class|code|cond|copybrief|copydetails|copydoc|copyright|c|date|def|defgroup|deprecated|details|diafile|dir|docbookonly|dontinclude|dot|dotfile|e|else|elseif|em|endcode|endcond|enddocbookonly|enddot|endhtmlonly|endif|endinternal|endlatexonly|endmsc|endparblock|enduml|enum|example|exception|extends|file|fn|headerfile|hidecallergraph|hidecallgraph|htmlonly|if|ifnot|image|implements|include|includedoc|ingroup|internal|interface|invariant|latexinclude|latexonly|mainpage|manonly|memberof|msc|mscfile|name|namespace|note|overload|package|page|par|paragraph|param|parblock|post|pre|private|property|protected|protocol|public|pure|p|ref|refitem|related|remark|remarks|result|return|returns|retval|section|see|short|since|skip|snippet|startuml|struct|throw|throws|todo|typedef|union|until|var|version|warning|xmlonly)\b</regex> + <forecolor>#969698</forecolor> + <font> + <bold>true</bold> + </font> + </rule> +</rules> diff --git a/src/libRender/RadiumNBR/Gui/CodeEditor/LineColumnPadding.cpp b/src/libRender/RadiumNBR/Gui/CodeEditor/LineColumnPadding.cpp new file mode 100644 index 0000000..112e7ce --- /dev/null +++ b/src/libRender/RadiumNBR/Gui/CodeEditor/LineColumnPadding.cpp @@ -0,0 +1,95 @@ +// +// QCodeEditor - Widget to highlight and auto-complete code. +// Copyright (C) 2016 Nicolas Kogler (kogler.cml@hotmail.com) +// +// This file is part of QCodeEditor. +// +// QCodeEditor is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with QCodeEditor. If not, see <http://www.gnu.org/licenses/>. +// +// + +// +// Included headers +// +#include <RadiumNBR/Gui/CodeEditor/LineColumnPadding.hpp> + +namespace RadiumNBR::Gui { + +/// +/// @fn Default constructor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +QLineColumnPadding::QLineColumnPadding() : m_Left( 16 ), m_Right( 16 ) {} + +/// +/// @fn Default constructor +/// @author Nicolas Kogler +/// @date October 19th, 2016 +/// +QLineColumnPadding::QLineColumnPadding( const QSize& size ) : + m_Left( static_cast<quint32>( size.width() ) ), + m_Right( static_cast<quint32>( size.height() ) ) {} + +/// +/// @fn Constructor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +QLineColumnPadding::QLineColumnPadding( quint32 left, quint32 right ) : + m_Left( left ), m_Right( right ) {} + +/// +/// @fn Destructor +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +QLineColumnPadding::~QLineColumnPadding() {} + +/// +/// @fn left +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +quint32 QLineColumnPadding::left() const { + return m_Left; +} + +/// +/// @fn right +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +quint32 QLineColumnPadding::right() const { + return m_Right; +} + +/// +/// @fn setLeft +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QLineColumnPadding::setLeft( quint32 left ) { + m_Left = left; +} + +/// +/// @fn setRight +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// +void QLineColumnPadding::setRight( quint32 right ) { + m_Right = right; +} +} // namespace RadiumNBR::Gui diff --git a/src/libRender/RadiumNBR/Gui/CodeEditor/LineColumnPadding.hpp b/src/libRender/RadiumNBR/Gui/CodeEditor/LineColumnPadding.hpp new file mode 100644 index 0000000..b8fa0ba --- /dev/null +++ b/src/libRender/RadiumNBR/Gui/CodeEditor/LineColumnPadding.hpp @@ -0,0 +1,80 @@ +#pragma once +#include <RadiumNBR/NodeBasedRendererMacro.hpp> + +#include <QtCore> + +namespace RadiumNBR::Gui { + +/// +/// @file QLineColumnPadding.hpp +/// @author Nicolas Kogler +/// @date October 5th, 2016 +/// @class QLineColumnPadding +/// @brief Defines a padding in the right- and left direction. +/// +class NodeBasedRenderer_LIBRARY_API QLineColumnPadding +{ + public: + /// + /// @fn Default constructor + /// @brief Initializes a new instance of QLineColumnPadding. + /// + QLineColumnPadding(); + + /// + /// @fn Constructor + /// @brief Constructs a padding out of a QSize. + /// @param size Size representing left and right padding + /// + QLineColumnPadding( const QSize& size ); + + /// + /// @fn Constructor + /// @brief Initializes a new instance of QLineColumnPadding. + /// @param left Initial left padding + /// @param right Initial right padding + /// + QLineColumnPadding( quint32 left, quint32 right ); + + /// + /// @fn Destructor + /// @brief Frees all resources allocated by QLineColumnPadding. + /// + ~QLineColumnPadding(); + + /// + /// @fn left : const + /// @brief Retrieves the padding in the left direction. + /// @returns the left padding. + /// + quint32 left() const; + + /// + /// @fn right : const + /// @brief Retrieves the padding in the right direction. + /// @returns the right padding. + /// + quint32 right() const; + + /// + /// @fn setLeft + /// @brief Specifies the left padding + /// @param left Left padding, in pixels + /// + void setLeft( quint32 left ); + + /// + /// @fn setRight + /// @brief Specifies the right padding + /// @param left Right padding, in pixels + /// + void setRight( quint32 right ); + + private: + // + // Private class members + // + quint32 m_Left; + quint32 m_Right; +}; +} // namespace RadiumNBR::Gui diff --git a/src/libRender/RadiumNBR/Gui/CodeEditor/SyntaxRule.cpp b/src/libRender/RadiumNBR/Gui/CodeEditor/SyntaxRule.cpp new file mode 100644 index 0000000..617f31a --- /dev/null +++ b/src/libRender/RadiumNBR/Gui/CodeEditor/SyntaxRule.cpp @@ -0,0 +1,358 @@ +// +// QCodeEditor - Widget to highlight and auto-complete code. +// Copyright (C) 2016 Nicolas Kogler (kogler.cml@hotmail.com) +// +// This file is part of QCodeEditor. +// +// QCodeEditor is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with QCodeEditor. If not, see <http://www.gnu.org/licenses/>. +// +// + +// +// Included headers +// +#include <QFont> +#include <QtXml/QtXml> +#include <RadiumNBR/Gui/CodeEditor/SyntaxRule.hpp> +#include <RadiumNBR/Gui/CodeEditor/XmlHelper.hpp> + +namespace RadiumNBR::Gui { + +/// +/// @fn Default constructor +/// @author Nicolas Kogler +/// @date October 4th, 2016 +/// +QSyntaxRule::QSyntaxRule() : + m_Regex( "" ), + m_BackColor( Qt::transparent ), + m_ForeColor( Qt::transparent ), + m_Id( "" ), + m_StartReg( "" ), + m_EndReg( "" ), + m_IsGlobal( true ), + m_UseFont( false ) {} + +/// +/// @fn Copy constructor +/// @author Nicolas Kogler +/// @date October 6th, 2016 +/// +QSyntaxRule::QSyntaxRule( const QSyntaxRule& rule ) : + m_Regex( rule.regex() ), + m_Font( rule.m_Font ), + m_BackColor( rule.backColor() ), + m_ForeColor( rule.foreColor() ), + m_Id( rule.id() ), + m_StartReg( rule.startRegex() ), + m_EndReg( rule.closeRegex() ), + m_IsGlobal( rule.isGlobal() ), + m_UseFont( rule.useFont() ) {} + +/// +/// @fn Destructor +/// @author Nicolas Kogler +/// @date October 4th, 2016 +/// +QSyntaxRule::~QSyntaxRule() {} + +/// +/// @fn regex +/// @author Nicolas Kogler +/// @date October 4th, 2016 +/// +const QString& QSyntaxRule::regex() const { + return m_Regex; +} + +/// +/// @fn font +/// @author Nicolas Kogler +/// @date October 4th, 2016 +/// +const QFont& QSyntaxRule::font() const { + return m_Font; +} + +/// +/// @fn backColor +/// @author Nicolas Kogler +/// @date October 4th, 2016 +/// +const QColor& QSyntaxRule::backColor() const { + return m_BackColor; +} + +/// +/// @fn backColor +/// @author Nicolas Kogler +/// @date October 4th, 2016 +/// +const QColor& QSyntaxRule::foreColor() const { + return m_ForeColor; +} + +/// +/// @fn id +/// @author Nicolas Kogler +/// @date October 8th, 2016 +/// +const QString& QSyntaxRule::id() const { + return m_Id; +} + +/// +/// @fn startRegex +/// @author Nicolas Kogler +/// @date October 9th, 2016 +/// +const QString& QSyntaxRule::startRegex() const { + return m_StartReg; +} + +/// +/// @fn closeRegex +/// @author Nicolas Kogler +/// @date October 9th, 2016 +/// +const QString& QSyntaxRule::closeRegex() const { + return m_EndReg; +} + +/// +/// @fn isGlobal +/// @author Nicolas Kogler +/// @date October 9th, 2016 +/// +bool QSyntaxRule::isGlobal() const { + return m_IsGlobal; +} + +/// +/// @fn useFont +/// @author Nicolas Kogler +/// @date October 7th, 2016 +/// +bool QSyntaxRule::useFont() const { + return m_UseFont; +} + +/// +/// @fn setRegex +/// @author Nicolas Kogler +/// @date October 4th, 2016 +/// +void QSyntaxRule::setRegex( const QString& regex ) { + m_Regex = regex; +} + +/// +/// @fn setKeywords +/// @author Nicolas Kogler +/// @date October 4th, 2016 +/// +void QSyntaxRule::setKeywords( const QStringList& keywords, bool lineBegin ) { + m_Regex.clear(); + + // Appends a regex that requires the given keywords either to be at the + // start of the line or to be the first non-whitespace on the line. + if ( lineBegin ) { m_Regex.append( "(^|\\s.)(" ); } + else + { m_Regex.append( "\\b(" ); } + + // Appends all the keywords as OR'ed expressions + foreach ( const QString& k, keywords ) + { m_Regex.append( k + "|" ); } + + // Adds the closing braces and a word boundary attribute + m_Regex.append( "(?!))\\b" ); +} + +/// +/// @fn setFont +/// @author Nicolas Kogler +/// @date October 6th, 2016 +/// +void QSyntaxRule::setFont( const QFont& font ) { + m_Font = font; + m_UseFont = true; +} + +/// +/// @fn setBackColor +/// @author Nicolas Kogler +/// @date October 4th, 2016 +/// +void QSyntaxRule::setBackColor( const QColor& backColor ) { + m_BackColor = backColor; +} + +/// +/// @fn setForeColor +/// @author Nicolas Kogler +/// @date October 4th, 2016 +/// +void QSyntaxRule::setForeColor( const QColor& foreColor ) { + m_ForeColor = foreColor; +} + +/// +/// @fn setId +/// @author Nicolas Kogler +/// @date October 8th, 2016 +/// +void QSyntaxRule::setId( const QString& id ) { + m_Id = id; +} + +/// +/// @fn setStartRegex +/// @author Nicolas Kogler +/// @date October 9th, 2016 +/// +void QSyntaxRule::setStartRegex( const QString& regex ) { + m_StartReg = regex; + m_Regex = regex; +} + +/// +/// @fn setCloseRegex +/// @author Nicolas Kogler +/// @date October 9th, 2016 +/// +void QSyntaxRule::setCloseRegex( const QString& regex ) { + m_EndReg = regex; +} + +/// +/// @fn setGlobal +/// @author Nicolas Kogler +/// @date October 9th, 2016 +/// +void QSyntaxRule::setGlobal( bool global ) { + m_IsGlobal = global; +} + +/// +/// @fn loadFromFile +/// @author Nicolas Kogler +/// @date October 7th, 2016 +/// +QList<QSyntaxRule> QSyntaxRules::loadFromFile( const char* path, const QCodeEditorDesign& design ) { + QList<QSyntaxRule> rules; + + // Attempts to open the XML file + QFile xmlFile( path ); + if ( !xmlFile.open( QIODevice::ReadOnly ) ) + { + qDebug( "Gui::QSyntaxRules: Cannot open XML file." ); + return rules; // check against <retval>.isEmpty(); + } + + // Loads the file into a XML reader and tries + // to find the <rules> tag. + QXmlStreamReader xmlReader( &xmlFile ); + if ( !xmlReader.readNextStartElement() || xmlReader.name() != "rules" ) + { + qDebug( "Gui::QSyntaxRules: There is no <rules> tag." ); + return rules; + } + + // Iterates through every rule until reader finished + while ( !xmlReader.hasError() ) + { + if ( !xmlReader.readNextStartElement() ) + { + break; // no more elements + } + else if ( xmlReader.name() != "rule" ) + { + break; // no more rule element + } + else if ( xmlReader.isComment() ) + { + continue; // comments are ignored + } + + QSyntaxRule rule; + QXmlStreamAttributes a = xmlReader.attributes(); + if ( !a.isEmpty() ) + { + auto first = a.at( 0 ); + if ( first.name() == "id" ) { rule.setId( first.value().toString() ); } + else if ( first.name() == "global" ) + { rule.setGlobal( ( first.value().toString() == "true" ) ? true : false ); } + if ( a.size() > 1 ) + { + auto second = a.at( 1 ); + if ( second.name() == "id" ) { rule.setId( second.value().toString() ); } + else if ( second.name() == "global" ) + { rule.setGlobal( ( second.value().toString() == "true" ) ? true : false ); } + } + } + + // Extracts the information out of the rule child + while ( !( xmlReader.isEndElement() && xmlReader.name() == "rule" ) ) + { + if ( !xmlReader.readNextStartElement() ) break; + if ( xmlReader.isComment() ) break; + + // Attempts to parse the rule + auto name = xmlReader.name().toString().toLower(); + if ( name == "regex" ) { rule.setRegex( xmlReader.readElementText() ); } + else if ( name == "startregex" ) + { rule.setStartRegex( xmlReader.readElementText() ); } + else if ( name == "closeregex" ) + { rule.setCloseRegex( xmlReader.readElementText() ); } + else if ( name == "keywords" ) + { + + // Attempts to read the attribute + bool firstWord = false; + auto attr = xmlReader.attributes(); + if ( !attr.isEmpty() && attr.at( 0 ).name() == "start" ) + { + if ( attr.at( 0 ).value() == "true" ) { firstWord = true; } + } + + rule.setKeywords( readKeywords( &xmlReader ), firstWord ); + } + else if ( name == "backcolor" ) + { rule.setBackColor( readColor( &xmlReader ) ); } + else if ( name == "forecolor" ) + { rule.setForeColor( readColor( &xmlReader ) ); } + else if ( name == "font" ) + { rule.setFont( readFont( &xmlReader, design.editorFont() ) ); } + else + { + QString s( "kgl::QSyntaxRule: Element '%0' is unknown." ); + qDebug( s.arg( name ).toStdString().c_str() ); + } + } + + // Determines if the required element (regex||keywords) was found + if ( rule.regex().isEmpty() && + ( rule.startRegex().isEmpty() && rule.closeRegex().isEmpty() ) ) + { + qDebug( "kgl::QSyntaxRule: Required element 'regex' or 'keywords' not found." ); + return rules; + } + else + { rules.push_back( rule ); } + } + + xmlFile.close(); + return rules; +} +} // namespace RadiumNBR::Gui diff --git a/src/libRender/RadiumNBR/Gui/CodeEditor/SyntaxRule.hpp b/src/libRender/RadiumNBR/Gui/CodeEditor/SyntaxRule.hpp new file mode 100644 index 0000000..cc7f954 --- /dev/null +++ b/src/libRender/RadiumNBR/Gui/CodeEditor/SyntaxRule.hpp @@ -0,0 +1,218 @@ +#pragma once +#include <RadiumNBR/NodeBasedRendererMacro.hpp> + +#include <QColor> +#include <RadiumNBR/Gui/CodeEditor/CodeEditorDesign.hpp> + +namespace RadiumNBR::Gui { + +/// +/// @file QSyntaxRule.hpp +/// @author Nicolas Kogler +/// @date October 4th, 2016 +/// @class QSyntaxRule +/// @brief Specifies the appearance of keywords or regular expressions. +/// +/// Under the hood, each regular expression is forwarded to a function +/// that searches for matches between the rule and the code editor's text. +/// If there are matches, they are coloured and transformed as specified. +/// Keywords will be directly converted to a regular expression. +/// +class NodeBasedRenderer_LIBRARY_API QSyntaxRule +{ + public: + /// + /// @fn Default constructor + /// @brief Initializes a new instance of QSyntaxRule. + /// + QSyntaxRule(); + + /// + /// @fn Copy constructor + /// @brief Copies one QSyntaxRule to another. + /// @param rule Other rule + /// + QSyntaxRule( const QSyntaxRule& rule ); + + /// + /// @fn Destructor + /// @brief Frees all resources allocated by QSyntaxRule. + /// + ~QSyntaxRule(); + + /// + /// @fn regex : const + /// @brief Retrieves the underlying regular expression. + /// + /// If keywords were specified instead of a regex, + /// this will be returned: "(keyword1|keyword2|...)" + /// + /// @returns a ref to the regex string being styled. + /// + const QString& regex() const; + + /// + /// @fn font : const + /// @brief Retrieves the font of the matching text. + /// @returns the font in which matches are rendered. + /// + const QFont& font() const; + + /// + /// @fn backColor : const + /// @brief Retrieves the background color of the matching text. + /// @returns the background color. + /// + const QColor& backColor() const; + + /// + /// @fn foreColor : const + /// @brief Retrieves the foreground color of the matching text. + /// @returns the foreground (text) color. + /// + const QColor& foreColor() const; + + /// + /// @fn id : const + /// @brief Retrieves the unique id for this rule. + /// @returns the rule string identifier. + /// + const QString& id() const; + + /// + /// @fn startRegex : const + /// @brief Retrieves the starting regex for this multiline rule. + /// @returns the staring regex. + /// + const QString& startRegex() const; + + /// + /// @fn closeRegex : const + /// @brief Retrieves the closing regex for this multiline rule. + /// @returns the staring regex. + /// + const QString& closeRegex() const; + + /// + /// @fn isGlobal : const + /// @brief Determines whether the regex search should be global. + /// @returns true to search globally. + /// + bool isGlobal() const; + + /// + /// @fn useFont : const + /// @brief Determines whether a custom font is used. + /// @returns true if a custom font is used. + /// + bool useFont() const; + + /// + /// @fn setRegex + /// @brief Specifies the regular expression for this rule. + /// @param regex Escaped regex sequence + /// + void setRegex( const QString& regex ); + + /// + /// @fn setKeywords + /// @brief Specifies the keywords. + /// + /// The keywords will be automatically translated + /// into a regular expression string. A usage example + /// for 'lineBegin=true' could be preprocessor directives. + /// + /// @param keywords List of keyword strings + /// @param lineBegin Should keywords only be at the beginning (also with whitespace) of a line? + /// + void setKeywords( const QStringList& keywords, bool lineBegin = false ); + + /// + /// @fn setFont + /// @brief Specifies the font in which matches are rendered. + /// + /// This class will NOT take ownership of the font + /// and will NOT delete it. Must be managed by the user. + /// If the font is NULL, the matches will be rendered + /// with the font of the parental QCodeEditor. + /// + /// @param font Pointer to font instance + /// + void setFont( const QFont& font ); + + /// + /// @fn setBackColor + /// @brief Specifies the background-color of the matches. + /// @param backColor Reference to QColor instance + /// + void setBackColor( const QColor& backColor ); + + /// + /// @fn setForeColor + /// @brief Specifies the foreground-color of the matches. + /// @param foreColor Reference to QColor instance + /// + void setForeColor( const QColor& foreColor ); + + /// + /// @fn setId + /// @brief Specifies the identifier for this rule. + /// @param id String identifier for this rule + /// + void setId( const QString& id ); + + /// + /// @fn setStartRegex + /// @brief Specifies the starting regular expression for this rule. + /// @param regex Escaped regex sequence + /// + void setStartRegex( const QString& regex ); + + /// + /// @fn setCloseRegex + /// @brief Specifies the closing regular expression for this rule. + /// @param regex Escaped regex sequence + /// + void setCloseRegex( const QString& regex ); + + /// + /// @fn setGlobal + /// @brief Specifies whether the regex search should be global. + /// @param global True to perform a global regex search + /// + void setGlobal( bool global ); + + private: + // + // Private class members + // + QString m_Regex; ///< Underlying regular expr + QFont m_Font; ///< Font to render matches in + QColor m_BackColor; ///< Background color of the matches + QColor m_ForeColor; ///< Foreground color of the matches + QString m_Id; ///< The rule name. NULL by default + QString m_StartReg; ///< Starting regex for multiline matches + QString m_EndReg; ///< Closing regex for multiline matches + bool m_IsGlobal; ///< Is regex search global? + bool m_UseFont; ///< Use the font or not? +}; + +/// +/// @file QSyntaxRule.hpp +/// @author Nicolas Kogler +/// @date October 7th, 2016 +/// @class QSyntaxRules +/// @brief Wraps a static loader function. +/// +class NodeBasedRenderer_LIBRARY_API QSyntaxRules +{ + public: + /// + /// @fn loadFromFile + /// @brief Loads a list of rules from a XML file. + /// @param path Absolute path or resource path + /// @param design Current editor design (need the default font) + /// + static QList<QSyntaxRule> loadFromFile( const char* path, const QCodeEditorDesign& design ); +}; +} // namespace RadiumNBR::Gui diff --git a/src/libRender/RadiumNBR/Gui/CodeEditor/XmlHelper.cpp b/src/libRender/RadiumNBR/Gui/CodeEditor/XmlHelper.cpp new file mode 100644 index 0000000..7cacadb --- /dev/null +++ b/src/libRender/RadiumNBR/Gui/CodeEditor/XmlHelper.cpp @@ -0,0 +1,252 @@ +// +// QCodeEditor - Widget to highlight and auto-complete code. +// Copyright (C) 2016 Nicolas Kogler (kogler.cml@hotmail.com) +// +// This file is part of QCodeEditor. +// +// QCodeEditor is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// 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 Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with QCodeEditor. If not, see <http://www.gnu.org/licenses/>. +// +// + +// +// Included headers +// +#include <QColor> +#include <QFont> +#include <QSize> +#include <RadiumNBR/Gui/CodeEditor/XmlHelper.hpp> + +namespace RadiumNBR::Gui { + +/// +/// @fn convertNumber +/// @author Nicolas Kogler +/// @date October 18th, 2016 +/// +inline bool convertNumber( const QString& str, int* out ) { + bool success; + *out = str.toInt( &success ); + return success; +} + +/// +/// @fn readFloat +/// @author Nicolas Kogler +/// @date October 19th, 2016 +inline float readFloat( QXmlStreamReader* reader ) { + QString str = reader->readElementText(); + bool success; + + // Attempts to convert float + float result = str.toFloat( &success ); + if ( !success ) { qDebug( "XmlHelper: Invalid float!" ); } + + return result; +} + +/// +/// @fn readBool +/// @author Nicolas Kogler +/// @date October 7th, 2016 +/// +bool readBool( QXmlStreamReader* reader ) { + auto string = reader->readElementText(); + if ( string.isEmpty() || string != "true" ) { return false; } + else + { return true; } +} + +/// +/// @fn readColor +/// @author Nicolas Kogler +/// @date October 7th, 2016 +/// +QColor readColor( QXmlStreamReader* reader ) { + auto string = reader->readElementText(); + if ( string.isEmpty() ) { return QColor( Qt::transparent ); } + + // Attempts to replace a hashtag with '0x' + if ( string.startsWith( '#' ) ) + { + string.remove( 0, 1 ); + string.insert( 0, "0x" ); + } + + // Tries to convert it to a number; + // if it failed, is a color name or rgba + bool success = false; + quint32 rgba = string.toUInt( &success, 16 ); + if ( success == true ) + { + if ( rgba <= 0xFFFFFF ) + { + // make it format '0xFF<rgb>' + return QColor( ( 0xffu << 24 ) | rgba ); + } + else + { return QColor( rgba ); } + } + else + { + if ( QColor::isValidColor( string ) ) { return QColor( string ); } + else + { + if ( string.startsWith( "rgba" ) ) + { + string.remove( 0, 4 ); + string.remove( QRegExp( "(\\(|\\))" ) ); + + QStringList s = string.split( ',', QString::SkipEmptyParts ); + int r, g, b, a; + if ( convertNumber( s.at( 0 ), &r ) && convertNumber( s.at( 1 ), &g ) && + convertNumber( s.at( 2 ), &b ) && convertNumber( s.at( 3 ), &a ) ) + { return QColor( r, g, b, a ); } + else + { return QColor( Qt::transparent ); } + } + else if ( string.startsWith( "rgb" ) ) + { + string.remove( 0, 4 ); + string.remove( QRegExp( "(\\(|\\))" ) ); + + QStringList s = string.split( ',', QString::SkipEmptyParts ); + int r, g, b; + if ( convertNumber( s.at( 0 ), &r ) && convertNumber( s.at( 1 ), &g ) && + convertNumber( s.at( 2 ), &b ) ) + { return QColor( r, g, b ); } + else + { + QString e( "XML: Value of element '%0' is invalid. Value: '%1'." ); + qDebug( e.arg( reader->name().toString(), string ).toStdString().c_str() ); + return QColor( Qt::transparent ); + } + } + } + } + + return QColor( Qt::transparent ); +} + +/// +/// @fn readKeywords +/// @author Nicolas Kogler +/// @date October 7th, 2016 +/// +QStringList readKeywords( QXmlStreamReader* reader ) { + + // Simplified fits our needs: Removes trailing and leading space + // and converts multiple spaces to a single space. We have multiple + // spaces as: "\n " -> results in "\n ". + QString string = reader->readElementText().simplified(); + + // Now we just have to remove all the newlines + string.remove( "\n" ); + + // A single whitespace is our separator for the keywords, + // thus split the string up accordingly. + return string.split( ' ' ); // safe to not include empty parts +} + +/// +/// @fn readMargin +/// @author Nicolas Kogler +/// @date October 18th, 2016 +/// +QMargins readMargin( QXmlStreamReader* reader ) { + QString str = reader->readElementText().simplified(); + QStringList li = str.split( ' ' ); + if ( li.size() != 4 ) + { + QString e( "XML: Value of element '%0' is invalid. Value: '%1'." ); + qDebug( e.arg( reader->name().toString(), str ).toStdString().c_str() ); + return QMargins(); + } + + int l, r, t, b; + if ( convertNumber( li.at( 0 ), &l ) && convertNumber( li.at( 1 ), &r ) && + convertNumber( li.at( 2 ), &t ) && convertNumber( li.at( 3 ), &b ) ) + { return QMargins( l, r, t, b ); } + else + { + QString e( "XML: Value of element '%0' is invalid. Value: '%1'." ); + qDebug( e.arg( reader->name().toString(), str ).toStdString().c_str() ); + return QMargins(); + } +} + +/// +/// @fn readSize +/// @author Nicolas Kogler +/// @date October 19th, 2016 +/// +QSize readSize( QXmlStreamReader* reader ) { + QString str = reader->readElementText().simplified(); + QStringList li = str.split( ' ' ); + if ( li.size() != 2 ) + { + QString e( "XML: Value of element '%0' is invalid. Value: '%1'." ); + qDebug( e.arg( reader->name().toString(), str ).toStdString().c_str() ); + return QSize(); + } + + int w, h; + if ( convertNumber( li.at( 0 ), &w ) && convertNumber( li.at( 1 ), &h ) ) + { return QSize( w, h ); } + else + { + QString e( "XML: Value of element '%0' is invalid. Value: '%1'." ); + qDebug( e.arg( reader->name().toString(), str ).toStdString().c_str() ); + return QSize(); + } +} + +/// +/// @fn readFont +/// @author Nicolas Kogler +/// @date October 19th, 2016 +/// +QFont readFont( QXmlStreamReader* reader, const QFont& def ) { + QString parent = reader->name().toString(); + if ( !reader->readNextStartElement() ) { return def; } + + // Determines whether default font should be used + QFont usedFont; + if ( reader->name() == "family" ) + { + usedFont.setFamily( reader->readElementText() ); + usedFont.setStyleHint( QFont::TypeWriter ); + reader->readNextStartElement(); + } + else + { usedFont = def; } + + // Reads the font properties + while ( !( reader->isEndElement() && reader->name() == parent ) ) + { + auto prop = reader->name(); + if ( prop == "strikethrough" ) { usedFont.setStrikeOut( readBool( reader ) ); } + else if ( prop == "underline" ) + { usedFont.setUnderline( readBool( reader ) ); } + else if ( prop == "italic" ) + { usedFont.setItalic( readBool( reader ) ); } + else if ( prop == "bold" ) + { usedFont.setBold( readBool( reader ) ); } + else if ( prop == "pointsize" ) + { usedFont.setPointSizeF( readFloat( reader ) ); } + reader->readNextStartElement(); + } + + return usedFont; +} +} // namespace RadiumNBR::Gui diff --git a/src/libRender/RadiumNBR/Gui/CodeEditor/XmlHelper.hpp b/src/libRender/RadiumNBR/Gui/CodeEditor/XmlHelper.hpp new file mode 100644 index 0000000..c50dd04 --- /dev/null +++ b/src/libRender/RadiumNBR/Gui/CodeEditor/XmlHelper.hpp @@ -0,0 +1,71 @@ +#pragma once +#include <RadiumNBR/NodeBasedRendererMacro.hpp> + +// +// Included headers +// +#include <QMargins> +#include <QXmlStreamReader> + +namespace RadiumNBR::Gui { + +/// +/// @file XmlHelper.hpp +/// @author Nicolas Kogler +/// @date October 18th, 2016 +/// @brief Declares several functions for parsing XML elements. +/// + +/// +/// @fn readBool +/// @brief Reads an XML element and converts it to a boolean. +/// @param reader Current XML reader +/// @returns true if element is string "true", otherwise false. +/// +extern bool readBool( QXmlStreamReader* reader ); + +/// +/// @fn readColor +/// @brief Reads an XML element and converts it to a color. +/// @param reader Current XML reader +/// @returns the color representing the inline text. +/// +/// Supported: +/// Hexadecimal notation (e.g. '#abcdef') +/// Color strings (e.g. 'red') +/// RGBA notation (e.g. 'rgba(r, g, b, a) +/// +extern QColor readColor( QXmlStreamReader* reader ); + +/// +/// @fn readKeywords +/// @brief Reads an XML element and converts it to a list of strings. +/// @param reader Current XML reader +/// @returns all the words separated by whitespace +/// +extern QStringList readKeywords( QXmlStreamReader* reader ); + +/// +/// @fn readMargin +/// @brief Reads a margin with 4 values. +/// @param reader Current XML reader +/// @returns a QMargins structure. +/// +extern QMargins readMargin( QXmlStreamReader* reader ); + +/// +/// @fn readSize +/// @brief Reads a size with 2 values. +/// @param reader Current XML reader +/// @returns a QSize structure. +extern QSize readSize( QXmlStreamReader* reader ); + +/// +/// @fn readFont +/// @brief Reads a font structure from the XML reader. +/// @param reader Current XML reader +/// @param def Default font, in case no family specified +/// @returns a QFont structure. +/// +extern QFont readFont( QXmlStreamReader* reader, const QFont& def ); +} // namespace RadiumNBR::Gui diff --git a/src/libRender/RadiumNBR/Gui/GlslEditor.cpp b/src/libRender/RadiumNBR/Gui/GlslEditor.cpp new file mode 100644 index 0000000..e98b6c6 --- /dev/null +++ b/src/libRender/RadiumNBR/Gui/GlslEditor.cpp @@ -0,0 +1,68 @@ +#include <RadiumNBR/Gui/GlslEditor.hpp> + +namespace RadiumNBR::Gui { + +GlslEditor::GlslEditor( QWidget* parent ) : QCodeEditor( parent ) { + + // Loads the highlighting rules from a resource file + QCodeEditorDesign design( ":/CodeEditor/EditorDefaultDesign.xml" ); + QList<QSyntaxRule> rules = + QSyntaxRules::loadFromFile( ":/CodeEditor/HighlightRules_cpp.xml", design ); + + // Creates a new code editor, sets the rules and design, adds it to the form + setDesign( design ); + setRules( rules ); + setKeywords( {"printf", "scanf", "atoi", "mbtoa", "strlen", "memcpy", "memset"} ); + + // Connects the highlighter's onMatch signal to our printMatch slot + connect( highlighter(), + SIGNAL( onMatch( QSyntaxRule, QString, QTextBlock ) ), + this, + SLOT( addMacro( QSyntaxRule, QString, QTextBlock ) ) ); + connect( highlighter(), + SIGNAL( onRemove( QCodeEditorBlockData* ) ), + this, + SLOT( removeMacro( QCodeEditorBlockData* ) ) ); +} + +GlslEditor::~GlslEditor() {} + +void GlslEditor::addMacro( const QSyntaxRule& rule, QString seq, QTextBlock block ) { + // Add the macro, if not already existing + if ( rule.id() == "macro" ) + { + foreach ( const QTextBlock& b, m_RuleMap.keys() ) + { + if ( b.userData() != NULL && block.userData() != NULL ) + { + auto* d1 = static_cast<QCodeEditorBlockData*>( block.userData() ); + auto* d2 = static_cast<QCodeEditorBlockData*>( b.userData() ); + if ( d1->id == d2->id ) { return; } + } + } + + QString def = seq.split( ' ' ).at( 0 ); + m_RuleMap.insert( block, rule ); + m_MacroMap.insert( block, def ); + addKeyword( def ); + + // TODO: Add highlighting rule for the new macro + } +} + +void GlslEditor::removeMacro( QCodeEditorBlockData* data ) { + foreach ( const QTextBlock& b, m_RuleMap.keys() ) + { + if ( b.userData() ) + { + auto* d = static_cast<QCodeEditorBlockData*>( b.userData() ); + if ( d->id == data->id ) + { + removeKeyword( m_MacroMap.value( b ) ); + m_RuleMap.remove( b ); + m_MacroMap.remove( b ); + } + } + } +} +} // namespace RadiumNBR::Gui diff --git a/src/libRender/RadiumNBR/Gui/GlslEditor.hpp b/src/libRender/RadiumNBR/Gui/GlslEditor.hpp new file mode 100644 index 0000000..37584f2 --- /dev/null +++ b/src/libRender/RadiumNBR/Gui/GlslEditor.hpp @@ -0,0 +1,54 @@ +#pragma once +#include <RadiumNBR/NodeBasedRendererMacro.hpp> + +#include <QMainWindow> +#include <QMap> +#include <RadiumNBR/Gui/CodeEditor/CodeEditor.hpp> +#include <RadiumNBR/Gui/CodeEditor/CodeEditorHighlighter.hpp> + +namespace RadiumNBR::Gui { + +/** + * Class that allow to edit glsl code. + * @todo fix the syntax highligt configuration (for now, it the one for C++) + * @todo add the Radium glsl interface in the keywords set for auto completion + * @todo (not really needed) : allow to configure the style ;) + */ +class NodeBasedRenderer_LIBRARY_API GlslEditor : public QCodeEditor +{ + Q_OBJECT + public: + /// + /// @fn Default constructor + /// @brief Initializes a new instance of QCodeEditor_Example. + /// + GlslEditor( QWidget* parent = 0 ); + + /// + /// @fn Destructor + /// @brief Frees resources used by QCodeEditor_Example. + /// + ~GlslEditor(); + + private slots: + + /// + /// @fn addMacro + /// @brief Adds the given macro. + /// + void addMacro( const QSyntaxRule& rule, QString seq, QTextBlock block ); + + /// + /// @fn removeMacro2 + /// @brief Removes a macro under certain conditions. + /// + void removeMacro( QCodeEditorBlockData* data ); + + private: + // + // Class members + // + QMap<QTextBlock, QSyntaxRule> m_RuleMap; + QMap<QTextBlock, QString> m_MacroMap; +}; +} // namespace RadiumNBR::Gui diff --git a/src/libRender/RadiumNBR/Gui/RendererPanel.cpp b/src/libRender/RadiumNBR/Gui/RendererPanel.cpp index 8cd9070..20eb93d 100644 --- a/src/libRender/RadiumNBR/Gui/RendererPanel.cpp +++ b/src/libRender/RadiumNBR/Gui/RendererPanel.cpp @@ -1,5 +1,7 @@ #include <RadiumNBR/Gui/RendererPanel.hpp> +#include <RadiumNBR/Gui/GlslEditor.hpp> + #include <QLabel> #include <QVBoxLayout> @@ -134,6 +136,21 @@ void RendererPanel::addFileInput( const std::string& name, m_currentLayout->addWidget( button ); } +void RendererPanel::addFileOuput( const std::string& name, + std::function<void( std::string )> callback, + const std::string& rootDirectory, + const std::string& filters ) { + auto button = new QPushButton( name.c_str(), this ); + auto fileDialog = [this, callback, rootDirectory, filters]() { + auto fltrs = QString::fromStdString( filters ); + auto filename = QFileDialog::getSaveFileName( + this, "Save file", QString::fromStdString( rootDirectory ), fltrs ); + if ( !filename.isEmpty() ) { callback( filename.toStdString() ); } + }; + connect( button, &QPushButton::clicked, fileDialog ); + m_currentLayout->addWidget( button ); +} + void RendererPanel::beginLayout( QBoxLayout::Direction dir ) { m_layouts.push( m_currentLayout ); m_currentLayout = new QBoxLayout( dir ); @@ -167,29 +184,33 @@ void RendererPanel::addSeparator() { void RendererPanel::addCodeEditor( const std::string& name, std::function<void( std::string )> callback, - const std::string& initialText ) { -// @todo : get a full featured code editor -// cf https://www.codeproject.com/Articles/1139741/QCodeEditor-Widget-for-Qt -// (code at https://www.codeproject.com/script/Articles/ViewDownloads.aspx?aid=1139741) - auto button = new QPushButton( name.c_str(), this ); + std::function<std::string()> initialText ) { + // @todo : get a full featured code editor + // cf https://www.codeproject.com/Articles/1139741/QCodeEditor-Widget-for-Qt + // (code at https://www.codeproject.com/script/Articles/ViewDownloads.aspx?aid=1139741) + auto button = new QPushButton( name.c_str(), this ); + QDialog* dialog = nullptr; auto editDialog = [this, callback, initialText, dialog]() mutable { if ( !dialog ) { dialog = new QDialog( this ); - auto buttonBox = new QDialogButtonBox( - QDialogButtonBox::Save | QDialogButtonBox::Cancel | QDialogButtonBox::Ok ); - auto textEditor = new QPlainTextEdit(); - textEditor->setPlainText( QString::fromStdString( initialText ) ); + auto buttonBox = + new QDialogButtonBox( QDialogButtonBox::Save | QDialogButtonBox::Close ); + auto textEditor = new GlslEditor( dialog ); + textEditor->setPlainText( QString::fromStdString( initialText() ) ); connect( buttonBox, &QDialogButtonBox::accepted, [callback, textEditor]() { + std::cout << "Accepted event received" << std::endl; callback( textEditor->toPlainText().toStdString() ); } ); - connect( buttonBox, &QDialogButtonBox::rejected, [callback, textEditor, initialText]() { - textEditor->setPlainText( QString::fromStdString( initialText ) ); - } ); + connect( + buttonBox, &QDialogButtonBox::rejected, [callback, &dialog, initialText]() mutable { + delete dialog; + dialog = nullptr; + } ); QVBoxLayout* mainLayout = new QVBoxLayout; mainLayout->addWidget( textEditor ); @@ -201,6 +222,17 @@ void RendererPanel::addCodeEditor( const std::string& name, dialog->activateWindow(); }; connect( button, &QPushButton::clicked, editDialog ); + + /* + GlslEditor *editor{nullptr}; + auto launchEditor= [this, callback, initialText, editor]() mutable { + if ( !editor ){ + editor = new GlslEditor(); + } + editor->show(); + }; + connect( button, &QPushButton::clicked, launchEditor ); + */ m_currentLayout->addWidget( button ); } diff --git a/src/libRender/RadiumNBR/Gui/RendererPanel.hpp b/src/libRender/RadiumNBR/Gui/RendererPanel.hpp index f5c465c..59962dc 100644 --- a/src/libRender/RadiumNBR/Gui/RendererPanel.hpp +++ b/src/libRender/RadiumNBR/Gui/RendererPanel.hpp @@ -124,6 +124,17 @@ class NodeBasedRenderer_LIBRARY_API RendererPanel : public QFrame const std::string& rootDirectory, const std::string& filters ); + /** Add a save file dialog to the ui. + * Allow the user to select a file to load according to given filters + * @param name The name of the file property to expose + * @param callback The function to call when the state changed + * @param rootDirectory The initial directory of the file dialog + * @param filters The filters to apply to filenames + */ + void addFileOuput( const std::string& name, + std::function<void( std::string )> callback, + const std::string& rootDirectory, + const std::string& filters ); /** * * @param name @@ -132,7 +143,7 @@ class NodeBasedRenderer_LIBRARY_API RendererPanel : public QFrame */ void addCodeEditor( const std::string& name, std::function<void( std::string )> callback, - const std::string& initialText ); + std::function<std::string()> initialText ); /**@}*/ private: diff --git a/src/libRender/RadiumNBR/Gui/VisualizationGui.cpp b/src/libRender/RadiumNBR/Gui/VisualizationGui.cpp index 47a694a..aa98c23 100644 --- a/src/libRender/RadiumNBR/Gui/VisualizationGui.cpp +++ b/src/libRender/RadiumNBR/Gui/VisualizationGui.cpp @@ -2,6 +2,8 @@ #include <RadiumNBR/NodeBasedRenderer.hpp> #include <RadiumNBR/Renderer/Visualization.hpp> +#include <fstream> + namespace RadiumNBR { using namespace Gui; @@ -48,22 +50,73 @@ buildControllerGui( NodeBasedRenderer* renderer, const std::function<void()>& ap false ); controlPanel->endLayout( true ); + controlPanel->beginLayout( QBoxLayout::LeftToRight ); + controlPanel->beginLayout( QBoxLayout::TopToBottom ); + auto visualizationFunction = controller.getAttribToColorFunc(); + + auto getAttribCode = [&controller]() { return controller.getAttribToColorFunc().first; }; + auto getColorCode = [&controller]() { return controller.getAttribToColorFunc().second; }; + controlPanel->addCodeEditor( - "Attribute function", + "Edit attribute function", [&controller, visualizationFunction, appUpdateCallback]( const std::string& s ) { controller.setAttribToColorFunc( s, visualizationFunction.second ); appUpdateCallback(); }, - visualizationFunction.first ); + getAttribCode ); controlPanel->addCodeEditor( - "Color function", + "Edit Color function", [&controller, visualizationFunction, appUpdateCallback]( const std::string& s ) { controller.setAttribToColorFunc( visualizationFunction.first, s ); appUpdateCallback(); }, - visualizationFunction.second ); + getColorCode ); + controlPanel->endLayout( false ); + + controlPanel->beginLayout( QBoxLayout::TopToBottom ); + auto loadAttribFuncClbk = + [&controller, visualizationFunction, appUpdateCallback]( const std::string& file ) { + if ( file.empty() ) { return; } + std::cout << "Loading attrib function from file " << file << std::endl; + std::ifstream inFile( file ); + std::stringstream strStream; + strStream << inFile.rdbuf(); // read the file + controller.setAttribToColorFunc( strStream.str(), visualizationFunction.second ); + appUpdateCallback(); + }; + auto loadColorFuncClbk = + [&controller, visualizationFunction, appUpdateCallback]( const std::string& file ) { + if ( file.empty() ) { return; } + std::cout << "Loading color function from file " << file << std::endl; + std::ifstream inFile( file ); + std::stringstream strStream; + strStream << inFile.rdbuf(); // read the file + controller.setAttribToColorFunc( visualizationFunction.first, strStream.str() ); + appUpdateCallback(); + }; + controlPanel->addFileInput( + "Load attribute func", loadAttribFuncClbk, "./", "Shaders (*.glsl)" ); + controlPanel->addFileInput( "Load color func", loadColorFuncClbk, "./", "Shaders (*.glsl)" ); + controlPanel->endLayout( false ); + + controlPanel->beginLayout( QBoxLayout::TopToBottom ); + auto saveAttribFuncClbk = [visualizationFunction, + appUpdateCallback]( const std::string& file ) { + std::ofstream outFile( file ); + outFile << visualizationFunction.first; + }; + auto saveColorFuncClbk = [visualizationFunction, appUpdateCallback]( const std::string& file ) { + std::ofstream outFile( file ); + outFile << visualizationFunction.second; + }; + controlPanel->addFileOuput( + "Save attribute func", saveAttribFuncClbk, "./", "Shaders (*.glsl)" ); + controlPanel->addFileOuput( "Save color func", saveColorFuncClbk, "./", "Shaders (*.glsl)" ); + controlPanel->endLayout( false ); + + controlPanel->endLayout( true ); return controlPanel; } -- GitLab