summaryrefslogtreecommitdiffstats
path: root/SQLiteStudio3/guiSQLiteStudio/sqleditor.h
blob: 4af89c155b688b9fdb73117c2db9d351782ca8b4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
#ifndef SQLEDITOR_H
#define SQLEDITOR_H

#include "common/strhash.h"
#include "guiSQLiteStudio_global.h"
#include "common/extactioncontainer.h"
#include "sqlitesyntaxhighlighter.h"
#include <QPlainTextEdit>
#include <QTextEdit>
#include <QFont>
#include <QHash>
#include <QMutex>
#include <QFuture>

class CompleterWindow;
class Parser;
class SqlEditor;
class SearchTextDialog;
class SearchTextLocator;
class LazyTrigger;
class Db;
class QTimer;

#ifdef Q_OS_OSX
#  define COMPLETE_REQ_KEY Qt::META
#else
#  define COMPLETE_REQ_KEY Qt::CTRL
#endif

CFG_KEY_LIST(SqlEditor, QObject::tr("SQL editor input field"),
    CFG_KEY_ENTRY(CUT,             QKeySequence::Cut,                 QObject::tr("Cut selected text"))
    CFG_KEY_ENTRY(COPY,            QKeySequence::Copy,                QObject::tr("Copy selected text"))
    CFG_KEY_ENTRY(PASTE,           QKeySequence::Paste,               QObject::tr("Paste from clipboard"))
    CFG_KEY_ENTRY(DELETE,          QKeySequence::Delete,              QObject::tr("Delete selected text"))
    CFG_KEY_ENTRY(SELECT_ALL,      QKeySequence::SelectAll,           QObject::tr("Select whole editor contents"))
    CFG_KEY_ENTRY(UNDO,            QKeySequence::Undo,                QObject::tr("Undo"))
    CFG_KEY_ENTRY(REDO,            QKeySequence::Redo,                QObject::tr("Redo"))
    CFG_KEY_ENTRY(SAVE_SQL_FILE,   QKeySequence::Save,                QObject::tr("Save contents into a file"))
    CFG_KEY_ENTRY(OPEN_SQL_FILE,   QKeySequence::Open,                QObject::tr("Load contents from a file"))
    CFG_KEY_ENTRY(FIND,            QKeySequence::Find,                QObject::tr("Find in text"))
    CFG_KEY_ENTRY(FIND_NEXT,       QKeySequence::FindNext,            QObject::tr("Find next"))
    CFG_KEY_ENTRY(FIND_PREV,       QKeySequence::FindPrevious,        QObject::tr("Find previous"))
    CFG_KEY_ENTRY(REPLACE,         QKeySequence::Replace,             QObject::tr("Replace in text"))
    CFG_KEY_ENTRY(DELETE_LINE,     Qt::CTRL + Qt::Key_D,              QObject::tr("Delete current line"))
    CFG_KEY_ENTRY(COMPLETE,        COMPLETE_REQ_KEY + Qt::Key_Space,  QObject::tr("Request code assistant"))
    CFG_KEY_ENTRY(FORMAT_SQL,      Qt::CTRL + Qt::Key_T,              QObject::tr("Format contents"))
    CFG_KEY_ENTRY(MOVE_BLOCK_DOWN, Qt::ALT + Qt::Key_Down,            QObject::tr("Move selected block of text one line down"))
    CFG_KEY_ENTRY(MOVE_BLOCK_UP,   Qt::ALT + Qt::Key_Up,              QObject::tr("Move selected block of text one line up"))
    CFG_KEY_ENTRY(COPY_BLOCK_DOWN, Qt::ALT + Qt::CTRL + Qt::Key_Down, QObject::tr("Copy selected block of text and paste it a line below"))
    CFG_KEY_ENTRY(COPY_BLOCK_UP,   Qt::ALT + Qt::CTRL + Qt::Key_Up,   QObject::tr("Copy selected block of text and paste it a line above"))
    CFG_KEY_ENTRY(TOGGLE_COMMENT,  Qt::CTRL + Qt::Key_Slash,          QObject::tr("Toggle comment"))
    CFG_KEY_ENTRY(INCR_FONT_SIZE,  Qt::CTRL + Qt::Key_Plus,           QObject::tr("Increase font size", "sql editor"))
    CFG_KEY_ENTRY(DECR_FONT_SIZE,  Qt::CTRL + Qt::Key_Minus,          QObject::tr("Decrease font size", "sql editor"))
)

class GUI_API_EXPORT SqlEditor : public QPlainTextEdit, public ExtActionContainer
{
    Q_OBJECT

    public:
        enum Action
        {
            COPY,
            PASTE,
            CUT,
            UNDO,
            REDO,
            DELETE,
            SELECT_ALL,
            FORMAT_SQL,
            OPEN_SQL_FILE,
            SAVE_SQL_FILE,
            SAVE_AS_SQL_FILE,
            DELETE_LINE,
            COMPLETE,
            MOVE_BLOCK_DOWN,
            MOVE_BLOCK_UP,
            COPY_BLOCK_DOWN,
            COPY_BLOCK_UP,
            FIND,
            FIND_NEXT,
            FIND_PREV,
            REPLACE,
            TOGGLE_COMMENT,
            WORD_WRAP,
            INCR_FONT_SIZE,
            DECR_FONT_SIZE
        };
        Q_ENUM(Action)

        enum ToolBar
        {
        };

        static void createStaticActions();
        static void staticInit();

        explicit SqlEditor(QWidget *parent = 0);
        ~SqlEditor();

        Db* getDb() const;
        void setDb(Db* value);
        void setAutoCompletion(bool enabled);
        QString getVirtualSqlExpression() const;
        void setVirtualSqlExpression(const QString& value);
        void setTriggerContext(const QString& table);
        bool haveErrors();
        bool isSyntaxChecked();
        bool getShowLineNumbers() const;
        void setShowLineNumbers(bool value);
        void checkSyntaxNow();
        void saveSelection();
        void restoreSelection();
        QToolBar* getToolBar(int toolbar) const;
        void setCurrentQueryHighlighting(bool enabled);
        bool getVirtualSqlCompleteSemicolon() const;
        void setVirtualSqlCompleteSemicolon(bool value);
        bool getHighlightingSyntax() const;
        void setOpenSaveActionsEnabled(bool value);

        static QHash<Action, QAction*> staticActions;
        static bool wrapWords;

        bool getAlwaysEnforceErrorsChecking() const;
        void setAlwaysEnforceErrorsChecking(bool newAlwaysEnforceErrorsChecking);

    protected:
        void setupDefShortcuts();
        void createActions();
        void keyPressEvent(QKeyEvent* e);
        void keyReleaseEvent(QKeyEvent* e);
        void focusOutEvent(QFocusEvent* e);
        void focusInEvent(QFocusEvent* e);
        void mouseMoveEvent(QMouseEvent* e);
        void mousePressEvent(QMouseEvent* e);
        void resizeEvent(QResizeEvent *e);
        void changeEvent(QEvent* e);
        void showEvent(QShowEvent* event);
        void dropEvent(QDropEvent* e);

    private:
        class LineNumberArea : public QWidget
        {
            public:
                explicit LineNumberArea(SqlEditor *editor);
                QSize sizeHint() const;

            protected:
                void paintEvent(QPaintEvent *event);

            private:
                SqlEditor *codeEditor = nullptr;
        };

        struct DbObject
        {
            DbObject(int from, int to, const QString& dbName);

            int from;
            int to;

            /**
             * @brief dbName
             * Attach name for the db that object belongs to.
             * If the object is database itself, then this variable is null.
             */
            QString dbName;
        };

        void setupMenu();
        void updateCompleterPosition();
        void init();
        void removeErrorMarkers();
        void deleteCurrentLine();
        void deleteSelectedLines();

        /**
         * @brief markErrorAt Mark error range.
         * @param start Start index of error.
         * @param end End index of error.
         * @param limitedDamage true if error is just invalid token, that didn't cause parser to fail.
         */
        void markErrorAt(int start, int end, bool limitedDamage = false);
        void deletePreviousChars(int length = 1);
        void checkForSyntaxErrors();
        void checkForValidObjects();
        void setObjectLinks(bool enabled);
        void addDbObject(int from, int to, const QString& dbName);
        void clearDbObjects();
        void lineNumberAreaPaintEvent(QPaintEvent* event);
        int lineNumberAreaWidth();
        void highlightParenthesis(QList<QTextEdit::ExtraSelection>& selections);
        void highlightCurrentQuery(QList<QTextEdit::ExtraSelection>& selections);
        void highlightCurrentLine(QList<QTextEdit::ExtraSelection>& selections);
        void highlightCurrentCursorContext(bool delayedCall = false);
        const TextBlockData::Parenthesis* matchParenthesis(QList<const TextBlockData::Parenthesis*> parList, const TextBlockData::Parenthesis* thePar);
        void markMatchedParenthesis(int pos1, int pos2, QList<QTextEdit::ExtraSelection>& selections);
        void doBackspace(int repeats = 1);
        void indentSelected(bool shiftPressed);
        void indentBlock(const QTextBlock& block);
        void unindentBlock(const QTextBlock& block);
        void indentNewLine();
        void showSearchDialog();
        int sqlIndex(int idx);
        void updateLineNumberArea();
        bool hasSelection() const;
        void replaceSelectedText(const QString& newText);
        QString getSelectedText() const;
        void openObject(const QString& database, const QString& name);

        /**
         * @brief getValidObjectForPosition
         * @param position Cursor text position determinated by Qt mouse event.
         * @param movedLeft true if Qt moved cursor left from click point, which means that user clicked closer to left border of character. Otherwise cursor was moved towards right.
         * @return Object identified under given text position, or null if there was no valid object under that position.
         */
        const DbObject* getValidObjectForPosition(int position, bool movedLeft);
        const DbObject* getValidObjectForPosition(const QPoint& point);
        void handleValidObjectCursor(const QPoint& point);
        bool handleValidObjectContextMenu(const QPoint& pos);
        void saveToFile(const QString& fileName);
        void toggleLineCommentForLine(const QTextBlock& block);

        SqliteSyntaxHighlighter* highlighter = nullptr;
        QMenu* contextMenu = nullptr;
        QMenu* validObjContextMenu = nullptr;
        Db* db = nullptr;
        CompleterWindow* completer = nullptr;
        LazyTrigger* autoCompleteTrigger = nullptr;
        bool autoCompletion = true;
        bool deletionKeyPressed = false;
        LazyTrigger* queryParserTrigger = nullptr;
        Parser* queryParser = nullptr;
        StrHash<QStringList> objectsInNamedDb;
        bool objectLinksEnabled = false;
        QList<DbObject> validDbObjects;
        QWidget* lineNumberArea = nullptr;
        SearchTextDialog* searchDialog = nullptr;
        SearchTextLocator* textLocator = nullptr;
        bool cursorMovingByLocator = false;
        bool syntaxValidated = false;
        bool showLineNumbers = true;
        int storedSelectionStart = 0;
        int storedSelectionEnd = 0;
        bool richFeaturesEnabled = true;
        bool alwaysEnforceErrorsChecking = false;
        bool highlightingSyntax = true;
        QBrush currentQueryBrush;
        QTimer* currentQueryTimer = nullptr;
        bool openSaveActionsEnabled = true;

        /**
         * @brief virtualSqlExpression
         * It has to be an SQL string containing exactly one argument %1 (as Qt string arguments).
         * It will be used in every syntax completion request as a template, as if user
         * wrote this entire SQL statement, plus his own code in place of %1 and then the completer is invoked.
         * User never sees this SQL expression, it's hidden from him.
         * The expression will also be used for syntax error checking the same was as for completer.
         *
         * This is useful for example when we want to have a context for completion in CHECK() constraint,
         * but user has only input edit for the CHECK expression itself, so for completer to work correctly
         * it needs to be lied that there is entire "CREATE TABLE...CHECK(" before the users code. In that
         * case you would set this variable to something like this: "CREATE TABLE x (c CHECK(%1))".
         * The SqlEditor is smart enough to do all the magic given the above expression.
         */
        QString virtualSqlExpression;
        int virtualSqlOffset = 0;
        int virtualSqlRightOffset = 0;
        bool virtualSqlCompleteSemicolon = false;
        QString createTriggerTable;
        QString loadedFile;
        QFutureWatcher<QHash<QString,QStringList>>* objectsInNamedDbWatcher = nullptr;
        void changeFontSize(int factor);

        static const int autoCompleterDelay = 300;
        static const int queryParserDelay = 500;

    private slots:
        void highlightSyntax();
        void customContextMenuRequested(const QPoint& pos);
        void updateUndoAction(bool enabled);
        void updateRedoAction(bool enabled);
        void updateCopyAction(bool enabled);
        void deleteSelected();
        void homePressed(Qt::KeyboardModifiers modifiers);
        void tabPressed(bool shiftPressed);
        void backspacePressed();
        void complete();
        void completeSelected();
//        void scheduleAutoCompletion();
        void checkForAutoCompletion();
        void completerTypedText(const QString& text);
        void completerBackspacePressed();
        void completerLeftPressed();
        void completerRightPressed();
        void parseContents();
        void scheduleQueryParserForSchemaRefresh();
        void scheduleQueryParser(bool force = false, bool skipCompleter = false);
        void updateLineNumberAreaWidth();
        void updateLineNumberArea(const QRect&rect, int dy);
        void cursorMoved();
        void checkContentSize();
        void formatSql();
        void saveToFile();
        void saveAsToFile();
        void loadFromFile();
        void deleteLine();
        void moveBlockDown(bool deleteOld = true);
        void moveBlockUp(bool deleteOld = true);
        void copyBlockDown();
        void copyBlockUp();
        void find();
        void findNext();
        void findPrevious();
        void replace();
        void found(int start, int end);
        void reachedEnd();
        void changeFont(const QVariant& font);
        void configModified();
        void toggleComment();
        void wordWrappingChanged(const QVariant& value);
        void currentCursorContextDelayedHighlight();
        void fontSizeChangeRequested(int delta);
        void incrFontSize();
        void decrFontSize();
        void moveCursorTo(int pos);

    public slots:
        void colorsConfigChanged();
        void refreshValidObjects();

    signals:
        void errorsChecked(bool haveErrors);
};


#endif // SQLEDITOR_H