/** * ScriptTextEditor.h * Copyright (c) 2013 ROBLOX Corp. All Rights Reserved. */ #pragma once //this will make sure that we can use qt with boost (else moc compiler errors) #ifndef QT_NO_KEYWORDS #define QT_NO_KEYWORDS #endif // Standard C/C++ Headers #include // Qt Headers #include #include #include #include #include #include #include // Roblox Headers #include "rbx/BaldPtr.h" #include "rbx/signal.h" #include "reflection/Property.h" #include "script/ScriptAnalyzer.h" // Roblox Studio Headers #include "FindDialog.h" #include "RobloxContextualHelp.h" #include "rbx/DenseHash.h" #include "boost/optional/optional.hpp" class RobloxScriptDoc; class RobloxMainWindow; class ScriptSyntaxHighlighter; class ScriptSideWidget; class ScriptTextEditor; /*****************************************************************************/ // ScriptEditorUtils /*****************************************************************************/ class ScriptEditorUtils { public: static QTextBlock findFoldBoundary(QTextBlock endBlock, bool bUp); static int noOfStartingTabs(QTextBlock block); }; /*****************************************************************************/ // ScriptTraversalThread /*****************************************************************************/ class ScriptTraversalThread : public QThread { Q_OBJECT public: ScriptTraversalThread(ScriptTextEditor * parent, RobloxScriptDoc * document); virtual ~ScriptTraversalThread(); void requestRun(); private: void run(); protected: virtual void doWork() = 0; QMutex mutex; QWaitCondition condition; ScriptTextEditor* m_pScriptEditor; RobloxScriptDoc* m_pDocument; bool restart; bool abort; }; /*****************************************************************************/ // CheckSyntaxThread /*****************************************************************************/ class CheckSyntaxThread : public ScriptTraversalThread { Q_OBJECT public: CheckSyntaxThread(ScriptTextEditor *parent, RobloxScriptDoc *document) : ScriptTraversalThread(parent, document) {} private: void doWork(); }; /*****************************************************************************/ // FindThread /*****************************************************************************/ class FindThread : public ScriptTraversalThread { Q_OBJECT public: FindThread(ScriptTextEditor *parent, RobloxScriptDoc *document) : ScriptTraversalThread(parent, document) , m_goToNextItem(true) , m_redrawRequested(false) {} void setData(const QRegExp& regExp,QTextDocument::FindFlags flags); void setAutoFindNext(bool value) { m_goToNextItem = value; } private: QRegExp toSearch; QTextDocument::FindFlags searchFlags; void doWork(); bool findInBlockNoCursor( const QString& text, int startOfBlock, const QRegExp& expression, int offset, QTextDocument::FindFlags options, int& start, int& end); int m_searchStartBlockLine; int m_searchLastBlockLine; bool m_goToNextItem; bool m_redrawRequested; }; /*****************************************************************************/ // ScriptTextEditor /*****************************************************************************/ class ScriptTextEditor : public QPlainTextEdit, public IFindListener { Q_OBJECT public: ScriptTextEditor(RobloxScriptDoc *pScriptDoc,RobloxMainWindow *pParentWidget); ~ScriptTextEditor(); Q_INVOKABLE void setErrorLine(int errorLine, QString errorMessage); Q_INVOKABLE void setAnalysisResult(const RBX::ScriptAnalyzer::Result& analysisResults); Q_INVOKABLE void startFind(); void clearFoundItems(); void updateFindSelection(); virtual void focusOutEvent(QFocusEvent * evt); virtual void focusInEvent(QFocusEvent * evt); void setViewportMargins(int left, int top, int right, int bottom); QTextBlock firstVisibleBlock() const; QPointF contentOffset() const; QRectF blockBoundingGeometry(const QTextBlock &block) const; void ensureVisible(const QTextBlock& block); void updateFolds(); // action handling bool doHandleAction(const QString& actionID); bool actionState(const QString& actionID,bool& enableState,bool& checkedState); QMenu* getContextualMenu() { return m_ContextMenu; } void moveCursorToLine(int line); void selectText(int startPosition, int endPosition); void selectText(const RBX::ScriptAnalyzer::Location& location, bool trySettingBlocksVisible = true); bool addToExtraSelections(const QTextEdit::ExtraSelection& extraSelection, bool prepend = false); void removeFromExtraSelections(QList extraSelections); virtual void keyPressEvent(QKeyEvent *e); virtual void mousePressEvent(QMouseEvent *e); void updateEmbeddedFindPosition(); void addFoundItem(int start, int end); // Wiki Search static void setupWikiLookup(); static void cleanupWikiLookup() { wikiLookup.clear(); } static bool isWordCharacter(const QChar& c); int numberOfFoundItems() { return m_foundItems.size(); } Q_SIGNALS: void toggleBreakpoint(int lineNumber); bool showToolTip(const QPoint& pos, const QString& wordUnderCursor); void updateContextualMenu(QMenu* pMenu, QPoint pos); void helpTopicChanged(const QString& topic); protected: virtual void resizeEvent(QResizeEvent* event); virtual void contextMenuEvent(QContextMenuEvent* event); virtual bool event(QEvent *e); virtual void wheelEvent(QWheelEvent* event); /** * IFindListener */ virtual bool find(bool loop,const QRegExp& regExp,QTextDocument::FindFlags flags); virtual void replace(const QString& text,const QRegExp& regExp,QTextDocument::FindFlags flags); virtual QString getSelectedText(); virtual bool hasSelection(); virtual void goToStart(QTextDocument::FindFlags flags); virtual void moveNext(bool foward); virtual void replaceNext(const QRegExp& rx, const QString& text); virtual void replaceAll(const QRegExp& rx, const QString& text); void homeEvent(QKeyEvent* e); void endEvent(QKeyEvent* e); public Q_SLOTS: void onCheckSyntax(); private Q_SLOTS: void checkSyntax(); void intellesenseDoubleClick(QListWidgetItem* listItem); void updateCursorVisibility(); void selectionChangedSearch(); void onSearchTimeout(); void onCheckSyntaxTimerTimeOut(); void find(); void onFind(); void findNext(bool moveCursorToBeginningOfSelection = false); private: // actions void commentSelection(); void uncommentSelection(); void toggleCommentSelection(); void updateSelection(bool* isBlockUnfolded = NULL); void goToError(); void expandAllFolds(); void collapseAllFolds(); void goToLine(); void findPrevious(); void replace(); void zoom(int delta, bool setDeltaAsValue = false); void replaceText(QString text, int cursorPos); void processTooltip(QHelpEvent* evt); bool tabSelection(bool bPrepend); void autoIndent(); void replaceSelection(int startPos,int endPos,const QString& text); void expandAllFolds(bool expand); void onPropertyChanged(const RBX::Reflection::PropertyDescriptor* pDescriptor); bool isPasteEnabled(); int getDocFoldState(int blockNumber = 0); void autoCompleteBlock(const QString& text, QString& indent); bool findInBlock( const QTextBlock& block, const QRegExp& expression, int offset, QTextDocument::FindFlags options, QTextCursor& cursor ); QTextCursor findInDoc( const QTextDocument& doc, const QRegExp& expr, const QTextCursor& from, QTextDocument::FindFlags options ); QString constructStyleSheet(); QString getVariableAtPos(const QPoint& location); int variableStartPos(const QString &text, int startPos); int variableEndPos(const QString &text, int endPos); void updateViewAndCursor(bool resizeRequired); bool appendExtraSelection(const QTextCharFormat& format, int startPos, int endPos, bool atEof, QList& appendList); void populateValuesFromLocation(const RBX::ScriptAnalyzer::Location& location, int& startPos, int& endPos, bool& atEof); int getStartPosition(const RBX::ScriptAnalyzer::Location& location); int getEndPosition(const RBX::ScriptAnalyzer::Location& location); int m_visibleErrorLine; //!< line that the error is being displayed on int m_errorLine; //!< actual error line QString m_errorMessage; struct ScriptMessage { int code; QString text; int begin; int end; }; RBX::BaldPtr m_pScriptDoc; RBX::BaldPtr m_pSyntaxHighlighter; RBX::BaldPtr m_pSideWidget; RBX::BaldPtr m_pCheckSyntaxThread; FindThread* m_pFindThread; RBX::BaldPtr m_pMainWindow; RBX::BaldPtr m_ContextMenu; rbx::signals::scoped_connection m_PropertyChangedConnection; QTextCharFormat m_ErrorFormat; QTextCharFormat m_WarningFormat; QTimer* m_pCheckSyntaxTimer; int m_originalCursorPosition; QTimer* m_pWikiSearchTimer; QString currentWikiSearchSelection; QList m_AnalysisSelections; QMutex m_selectionMutex; QTextCharFormat m_findSelectionFormat; std::map m_extraSelectionsToAdd; std::multimap m_extraSelectionsToRemove; QList m_currentSelections; QList m_currentFoundItems; boost::optional > m_analysisResult; std::map m_foundItems; typedef std::pair Range; struct RangeCompare { bool operator()(const Range& lRange, const Range& rRange) const { return lRange.second < rRange.first; } }; typedef std::multimap MessageCollection; MessageCollection m_scriptMessageForRange; static void updateWikiLookup(const char*, std::string); typedef RBX::DenseHashMap, RBX::Reflection::StringHashPredicate, RBX::Reflection::StringEqualPredicate> WikiLookup; static WikiLookup wikiLookup; };