1 #ifndef SQLEDITOR_H
2 #define SQLEDITOR_H
3 
4 #include "guiSQLiteStudio_global.h"
5 #include "common/extactioncontainer.h"
6 #include "db/db.h"
7 #include "sqlitesyntaxhighlighter.h"
8 #include <QPlainTextEdit>
9 #include <QTextEdit>
10 #include <QFont>
11 #include <QHash>
12 #include <QMutex>
13 #include <QFuture>
14 
15 class CompleterWindow;
16 class Parser;
17 class SqlEditor;
18 class SearchTextDialog;
19 class SearchTextLocator;
20 class LazyTrigger;
21 
22 #ifdef Q_OS_OSX
23 #  define COMPLETE_REQ_KEY Qt::META
24 #else
25 #  define COMPLETE_REQ_KEY Qt::CTRL
26 #endif
27 
28 CFG_KEY_LIST(SqlEditor, QObject::tr("SQL editor input field"),
29     CFG_KEY_ENTRY(CUT,             QKeySequence::Cut,                 QObject::tr("Cut selected text"))
30     CFG_KEY_ENTRY(COPY,            QKeySequence::Copy,                QObject::tr("Copy selected text"))
31     CFG_KEY_ENTRY(PASTE,           QKeySequence::Paste,               QObject::tr("Paste from clipboard"))
32     CFG_KEY_ENTRY(DELETE,          QKeySequence::Delete,              QObject::tr("Delete selected text"))
33     CFG_KEY_ENTRY(SELECT_ALL,      QKeySequence::SelectAll,           QObject::tr("Select whole editor contents"))
34     CFG_KEY_ENTRY(UNDO,            QKeySequence::Undo,                QObject::tr("Undo"))
35     CFG_KEY_ENTRY(REDO,            QKeySequence::Redo,                QObject::tr("Redo"))
36     CFG_KEY_ENTRY(SAVE_SQL_FILE,   QKeySequence::Save,                QObject::tr("Save contents into a file"))
37     CFG_KEY_ENTRY(OPEN_SQL_FILE,   QKeySequence::Open,                QObject::tr("Load contents from a file"))
38     CFG_KEY_ENTRY(FIND,            QKeySequence::Find,                QObject::tr("Find in text"))
39     CFG_KEY_ENTRY(FIND_NEXT,       QKeySequence::FindNext,            QObject::tr("Find next"))
40     CFG_KEY_ENTRY(FIND_PREV,       QKeySequence::FindPrevious,        QObject::tr("Find previous"))
41     CFG_KEY_ENTRY(REPLACE,         QKeySequence::Replace,             QObject::tr("Replace in text"))
42     CFG_KEY_ENTRY(DELETE_LINE,     Qt::CTRL + Qt::Key_D,              QObject::tr("Delete current line"))
43     CFG_KEY_ENTRY(COMPLETE,        COMPLETE_REQ_KEY + Qt::Key_Space,          QObject::tr("Request code assistant"))
44     CFG_KEY_ENTRY(FORMAT_SQL,      Qt::CTRL + Qt::Key_T,              QObject::tr("Format contents"))
45     CFG_KEY_ENTRY(MOVE_BLOCK_DOWN, Qt::ALT + Qt::Key_Down,            QObject::tr("Move selected block of text one line down"))
46     CFG_KEY_ENTRY(MOVE_BLOCK_UP,   Qt::ALT + Qt::Key_Up,              QObject::tr("Move selected block of text one line up"))
47     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"))
48     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"))
49     CFG_KEY_ENTRY(TOGGLE_COMMENT,  Qt::CTRL + Qt::Key_Slash,          QObject::tr("Toggle comment"))
50 )
51 
52 class GUI_API_EXPORT SqlEditor : public QPlainTextEdit, public ExtActionContainer
53 {
54         Q_OBJECT
55         Q_ENUMS(Action)
56 
57     public:
58         enum Action
59         {
60             COPY,
61             PASTE,
62             CUT,
63             UNDO,
64             REDO,
65             DELETE,
66             SELECT_ALL,
67             FORMAT_SQL,
68             OPEN_SQL_FILE,
69             SAVE_SQL_FILE,
70             SAVE_AS_SQL_FILE,
71             DELETE_LINE,
72             COMPLETE,
73             MOVE_BLOCK_DOWN,
74             MOVE_BLOCK_UP,
75             COPY_BLOCK_DOWN,
76             COPY_BLOCK_UP,
77             FIND,
78             FIND_NEXT,
79             FIND_PREV,
80             REPLACE,
81             TOGGLE_COMMENT
82         };
83 
84         enum ToolBar
85         {
86         };
87 
88         explicit SqlEditor(QWidget *parent = 0);
89         ~SqlEditor();
90 
91         Db* getDb() const;
92         void setDb(Db* value);
93         void setAutoCompletion(bool enabled);
94         QString getVirtualSqlExpression() const;
95         void setVirtualSqlExpression(const QString& value);
96         void setTriggerContext(const QString& table);
97         bool haveErrors();
98         bool isSyntaxChecked();
99         bool getShowLineNumbers() const;
100         void setShowLineNumbers(bool value);
101         void checkSyntaxNow();
102         void saveSelection();
103         void restoreSelection();
104         QToolBar* getToolBar(int toolbar) const;
105 
106         bool getVirtualSqlCompleteSemicolon() const;
107         void setVirtualSqlCompleteSemicolon(bool value);
108 
109     protected:
110         void setupDefShortcuts();
111         void createActions();
112         void keyPressEvent(QKeyEvent* e);
113         void keyReleaseEvent(QKeyEvent* e);
114         void focusOutEvent(QFocusEvent* e);
115         void focusInEvent(QFocusEvent* e);
116         void mouseMoveEvent(QMouseEvent* e);
117         void mousePressEvent(QMouseEvent* e);
118         void resizeEvent(QResizeEvent *e);
119         void changeEvent(QEvent*e);
120 
121     private:
122         class LineNumberArea : public QWidget
123         {
124             public:
125                 explicit LineNumberArea(SqlEditor *editor);
126                 QSize sizeHint() const;
127 
128             protected:
129                 void paintEvent(QPaintEvent *event);
130 
131             private:
132                 SqlEditor *codeEditor = nullptr;
133         };
134 
135         struct DbObject
136         {
137             DbObject(int from, int to, const QString& dbName);
138 
139             int from;
140             int to;
141 
142             /**
143              * @brief dbName
144              * Attach name for the db that object belongs to.
145              * If the object is database itself, then this variable is null.
146              */
147             QString dbName;
148         };
149 
150         void setupMenu();
151         void updateCompleterPosition();
152         void init();
153         void removeErrorMarkers();
154         void deleteCurrentLine();
155         void deleteSelectedLines();
156 
157         /**
158          * @brief markErrorAt Mark error range.
159          * @param start Start index of error.
160          * @param end End index of error.
161          * @param limitedDamage true if error is just invalid token, that didn't cause parser to fail.
162          */
163         void markErrorAt(int start, int end, bool limitedDamage = false);
164         void deletePreviousChars(int length = 1);
165         void refreshValidObjects();
166         void checkForSyntaxErrors();
167         void checkForValidObjects();
168         void setObjectLinks(bool enabled);
169         void addDbObject(int from, int to, const QString& dbName);
170         void clearDbObjects();
171         void lineNumberAreaPaintEvent(QPaintEvent* event);
172         int lineNumberAreaWidth();
173         void highlightParenthesis(QList<QTextEdit::ExtraSelection>& selections);
174         void highlightCurrentLine(QList<QTextEdit::ExtraSelection>& selections);
175         void highlightCurrentCursorContext();
176         const TextBlockData::Parenthesis* matchParenthesis(QList<const TextBlockData::Parenthesis*> parList, const TextBlockData::Parenthesis* thePar);
177         void markMatchedParenthesis(int pos1, int pos2, QList<QTextEdit::ExtraSelection>& selections);
178         void doBackspace(int repeats = 1);
179         void indentSelected(bool shiftPressed);
180         void indentBlock(const QTextBlock& block);
181         void unindentBlock(const QTextBlock& block);
182         void indentNewLine();
183         void showSearchDialog();
184         int sqlIndex(int idx);
185         void updateLineNumberArea();
186         bool hasSelection() const;
187         void replaceSelectedText(const QString& newText);
188         QString getSelectedText() const;
189         void openObject(const QString& database, const QString& name);
190 
191         /**
192          * @brief getValidObjectForPosition
193          * @param position Cursor text position determinated by Qt mouse event.
194          * @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.
195          * @return Object identified under given text position, or null if there was no valid object under that position.
196          */
197         const DbObject* getValidObjectForPosition(int position, bool movedLeft);
198         const DbObject* getValidObjectForPosition(const QPoint& point);
199         void handleValidObjectCursor(const QPoint& point);
200         bool handleValidObjectContextMenu(const QPoint& pos);
201         void saveToFile(const QString& fileName);
202         void toggleLineCommentForLine(const QTextBlock& block);
203 
204         SqliteSyntaxHighlighter* highlighter = nullptr;
205         QMenu* contextMenu = nullptr;
206         QMenu* validObjContextMenu = nullptr;
207         Db* db = nullptr;
208         CompleterWindow* completer = nullptr;
209         LazyTrigger* autoCompleteTrigger = nullptr;
210         bool autoCompletion = true;
211         bool deletionKeyPressed = false;
212         LazyTrigger* queryParserTrigger = nullptr;
213         Parser* queryParser = nullptr;
214         QHash<QString,QStringList> objectsInNamedDb;
215         QMutex objectsInNamedDbMutex;
216         bool objectLinksEnabled = false;
217         QList<DbObject> validDbObjects;
218         QWidget* lineNumberArea = nullptr;
219         SearchTextDialog* searchDialog = nullptr;
220         SearchTextLocator* textLocator = nullptr;
221         bool cursorMovingByLocator = false;
222         bool syntaxValidated = false;
223         bool showLineNumbers = true;
224         int storedSelectionStart = 0;
225         int storedSelectionEnd = 0;
226         bool richFeaturesEnabled = true;
227 
228         /**
229          * @brief virtualSqlExpression
230          * It has to be an SQL string containing exactly one argument %1 (as Qt string arguments).
231          * It will be used in every syntax completion request as a template, as if user
232          * wrote this entire SQL statement, plus his own code in place of %1 and then the completer is invoked.
233          * User never sees this SQL expression, it's hidden from him.
234          * The expression will also be used for syntax error checking the same was as for completer.
235          *
236          * This is useful for example when we want to have a context for completion in CHECK() constraint,
237          * but user has only input edit for the CHECK expression itself, so for completer to work correctly
238          * it needs to be lied that there is entire "CREATE TABLE...CHECK(" before the users code. In that
239          * case you would set this variable to something like this: "CREATE TABLE x (c CHECK(%1))".
240          * The SqlEditor is smart enough to do all the magic given the above expression.
241          */
242         QString virtualSqlExpression;
243         int virtualSqlOffset = 0;
244         int virtualSqlRightOffset = 0;
245         bool virtualSqlCompleteSemicolon = false;
246         QString createTriggerTable;
247         QString loadedFile;
248         QFuture<void> objectsInNamedDbFuture;
249 
250         static const int autoCompleterDelay = 300;
251         static const int queryParserDelay = 500;
252 
253     private slots:
254         void customContextMenuRequested(const QPoint& pos);
255         void updateUndoAction(bool enabled);
256         void updateRedoAction(bool enabled);
257         void updateCopyAction(bool enabled);
258         void deleteSelected();
259         void homePressed(Qt::KeyboardModifiers modifiers);
260         void tabPressed(bool shiftPressed);
261         void backspacePressed();
262         void complete();
263         void completeSelected();
264 //        void scheduleAutoCompletion();
265         void checkForAutoCompletion();
266         void completerTypedText(const QString& text);
267         void completerBackspacePressed();
268         void completerLeftPressed();
269         void completerRightPressed();
270         void parseContents();
271         void scheduleQueryParser(bool force = false);
272         void updateLineNumberAreaWidth();
273         void updateLineNumberArea(const QRect&rect, int dy);
274         void cursorMoved();
275         void checkContentSize();
276         void formatSql();
277         void saveToFile();
278         void saveAsToFile();
279         void loadFromFile();
280         void deleteLine();
281         void moveBlockDown(bool deleteOld = true);
282         void moveBlockUp(bool deleteOld = true);
283         void copyBlockDown();
284         void copyBlockUp();
285         void find();
286         void findNext();
287         void findPrevious();
288         void replace();
289         void found(int start, int end);
290         void reachedEnd();
291         void changeFont(const QVariant& font);
292         void configModified();
293         void toggleComment();
294 
295     signals:
296         void errorsChecked(bool haveErrors);
297 };
298 
299 
300 #endif // SQLEDITOR_H
301