1 /*
2     SuperCollider Qt IDE
3     Copyright (c) 2012 Jakob Leben & Tim Blechmann
4     http://www.audiosynth.com
5 
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10 
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA
19 */
20 
21 #include "multi_editor.hpp"
22 #include "editor_box.hpp"
23 #include "main_window.hpp"
24 #include "lookup_dialog.hpp"
25 #include "code_editor/sc_editor.hpp"
26 #include "util/multi_splitter.hpp"
27 #include "../core/doc_manager.hpp"
28 #include "../core/sig_mux.hpp"
29 #include "../core/main.hpp"
30 #include "../core/sc_process.hpp"
31 #include "../core/session_manager.hpp"
32 
33 #include <yaml-cpp/node/node.h>
34 #include <yaml-cpp/parser.h>
35 
36 #include <QApplication>
37 #include <QDebug>
38 #include <QDialog>
39 #include <QFileInfo>
40 #include <QHBoxLayout>
41 #include <QHeaderView>
42 #include <QListView>
43 #include <QMenu>
44 #include <QPainter>
45 #include <QStandardItemModel>
46 #include <QShortcut>
47 #include <QStyle>
48 #include <QTreeWidget>
49 #include <QVBoxLayout>
50 
51 
52 namespace ScIDE {
53 
54 class DocumentSelectPopUp : public QDialog {
55 public:
DocumentSelectPopUp(const CodeEditorBox::History & history,QWidget * parent)56     DocumentSelectPopUp(const CodeEditorBox::History& history, QWidget* parent):
57 #ifndef Q_OS_MAC
58         QDialog(parent, Qt::Popup | Qt::FramelessWindowHint)
59 #else
60         QDialog(parent, Qt::Dialog | Qt::FramelessWindowHint)
61 #endif
62     {
63         mModel = new QStandardItemModel(this);
64         populateModel(history);
65 
66         mListView = new QListView();
67         mListView->setModel(mModel);
68         mListView->setFrameShape(QFrame::NoFrame);
69 
70         QHBoxLayout* layout = new QHBoxLayout(this);
71         layout->addWidget(mListView);
72         layout->setContentsMargins(1, 1, 1, 1);
73 
74         connect(mListView, SIGNAL(activated(QModelIndex)), this, SLOT(accept()));
75 
76         mListView->setFocus(Qt::OtherFocusReason);
77 
78         QModelIndex nextIndex = mModel->index(1, 0);
79         mListView->setCurrentIndex(nextIndex);
80 
81         mListView->setEditTriggers(QAbstractItemView::NoEditTriggers);
82     }
83 
exec(const QPoint & pos)84     Document* exec(const QPoint& pos) {
85         move(pos);
86         if (QDialog::exec())
87             return currentDocument();
88         else
89             return 0;
90     }
91 
92 private:
event(QEvent * event)93     bool event(QEvent* event) {
94         if (event->type() == QEvent::ShortcutOverride) {
95             event->accept();
96             return true;
97         }
98         return QWidget::event(event);
99     }
100 
keyReleaseEvent(QKeyEvent * ke)101     void keyReleaseEvent(QKeyEvent* ke) {
102         // adapted from qtcreator
103         if (ke->modifiers() == 0
104             /*HACK this is to overcome some event inconsistencies between platforms*/
105             || (ke->modifiers() == Qt::AltModifier && (ke->key() == Qt::Key_Alt || ke->key() == -1))) {
106             ke->accept();
107             accept();
108         }
109         QDialog::keyReleaseEvent(ke);
110     }
111 
keyPressEvent(QKeyEvent * ke)112     void keyPressEvent(QKeyEvent* ke) {
113         switch (ke->key()) {
114         case Qt::Key_Down:
115         case Qt::Key_Tab:
116             cycleDown();
117             ke->accept();
118             return;
119 
120         case Qt::Key_Up:
121         case Qt::Key_Backtab:
122             cycleUp();
123             ke->accept();
124             return;
125 
126         case Qt::Key_Escape:
127             reject();
128             return;
129 
130         default:;
131         }
132 
133         QDialog::keyPressEvent(ke);
134     }
135 
paintEvent(QPaintEvent *)136     void paintEvent(QPaintEvent*) {
137         QPainter painter(this);
138         painter.setBrush(Qt::NoBrush);
139         painter.setPen(palette().color(QPalette::Dark));
140         painter.drawRect(rect().adjusted(0, 0, -1, -1));
141     }
142 
cycleDown()143     void cycleDown() {
144         int row = mListView->currentIndex().row() + 1;
145         if (!mModel->hasIndex(row, 0))
146             row = 0;
147 
148         QModelIndex nextIndex = mModel->index(row, 0);
149         mListView->setCurrentIndex(nextIndex);
150     }
151 
cycleUp()152     void cycleUp() {
153         int row = mListView->currentIndex().row() - 1;
154         if (!mModel->hasIndex(row, 0))
155             row = mModel->rowCount() - 1;
156 
157         QModelIndex nextIndex = mModel->index(row, 0);
158         mListView->setCurrentIndex(nextIndex);
159     }
160 
currentDocument()161     Document* currentDocument() {
162         QStandardItem* currentItem = mModel->itemFromIndex(mListView->currentIndex());
163         return currentItem ? currentItem->data().value<Document*>() : NULL;
164     }
165 
populateModel(const CodeEditorBox::History & history)166     void populateModel(const CodeEditorBox::History& history) {
167         QList<Document*> displayDocuments;
168         foreach (GenericCodeEditor* editor, history)
169             displayDocuments << editor->document();
170 
171         QList<Document*> managerDocuments = Main::documentManager()->documents();
172         foreach (Document* document, managerDocuments)
173             if (!displayDocuments.contains(document))
174                 displayDocuments << document;
175 
176         foreach (Document* document, displayDocuments) {
177             QStandardItem* item = new QStandardItem(document->title());
178             item->setData(QVariant::fromValue(document));
179             mModel->appendRow(item);
180         }
181     }
182 
183     QListView* mListView;
184     QStandardItemModel* mModel;
185 };
186 
EditorTabBar(QWidget * parent)187 EditorTabBar::EditorTabBar(QWidget* parent): QTabBar(parent) {
188     setDocumentMode(true);
189     setTabsClosable(true);
190     setMovable(true);
191     setUsesScrollButtons(true);
192     setDrawBase(false);
193     setElideMode(Qt::ElideNone);
194 }
195 
mousePressEvent(QMouseEvent * event)196 void EditorTabBar::mousePressEvent(QMouseEvent* event) {
197     if (event->button() == Qt::RightButton) {
198         showContextMenu(event);
199         event->accept();
200         return;
201     } else if (event->button() == Qt::MiddleButton) {
202         mTabUnderCursor = tabAt(event->pos());
203         onCloseTab();
204         event->accept();
205         return;
206     }
207 
208     QTabBar::mousePressEvent(event);
209 }
210 
211 
mouseDoubleClickEvent(QMouseEvent * event)212 void EditorTabBar::mouseDoubleClickEvent(QMouseEvent* event) {
213     if (event->button() == Qt::LeftButton) {
214         if (tabAt(event->pos()) == -1) { // no tab under cursor
215             MainWindow::instance()->newDocument();
216             event->accept();
217             return;
218         }
219     }
220 
221     QTabBar::mouseDoubleClickEvent(event);
222 }
223 
showContextMenu(QMouseEvent * event)224 void EditorTabBar::showContextMenu(QMouseEvent* event) {
225     mTabUnderCursor = tabAt(event->pos());
226 
227     QMenu* menu = new QMenu(this);
228     // Cannot have a close tab action if we are not over a tab
229     if (mTabUnderCursor == -1) {
230         menu->addAction(tr("Close All Tabs"), this, SLOT(onCloseOtherTabs()));
231     } else {
232         menu->addAction(tr("Close"), this, SLOT(onCloseTab()));
233         menu->addAction(tr("Close Other Tabs"), this, SLOT(onCloseOtherTabs()));
234         menu->addAction(tr("Close Tabs to the Right"), this, SLOT(onCloseTabsToTheRight()));
235     }
236 
237     menu->popup(event->screenPos().toPoint());
238 }
239 
onCloseTab()240 void EditorTabBar::onCloseTab() {
241     Document* doc = tabData(mTabUnderCursor).value<Document*>();
242     assert(doc);
243 
244     MainWindow::close(doc);
245 }
246 
onCloseOtherTabs()247 void EditorTabBar::onCloseOtherTabs() {
248     QVector<Document*> docsToClose;
249 
250     for (int currentTab = 0; currentTab != count(); ++currentTab) {
251         if (currentTab != mTabUnderCursor)
252             docsToClose.append(tabData(currentTab).value<Document*>());
253     }
254 
255     for (Document* doc : docsToClose)
256         MainWindow::close(doc);
257 }
258 
onCloseTabsToTheRight()259 void EditorTabBar::onCloseTabsToTheRight() {
260     QVector<Document*> docsToClose;
261 
262     for (int currentTab = mTabUnderCursor + 1; currentTab != count(); ++currentTab)
263         docsToClose.append(tabData(currentTab).value<Document*>());
264 
265     for (Document* doc : docsToClose)
266         MainWindow::close(doc);
267 }
268 
269 
MultiEditor(Main * main,QWidget * parent)270 MultiEditor::MultiEditor(Main* main, QWidget* parent):
271     QWidget(parent),
272     mEditorSigMux(new SignalMultiplexer(this)),
273     mBoxSigMux(new SignalMultiplexer(this)),
274 #ifdef __APPLE__
275     mDocModifiedIcon(QApplication::style()->standardIcon(QStyle::SP_DriveHDIcon))
276 #else
277     mDocModifiedIcon(QIcon::fromTheme("document-save"))
278 #endif
279 {
280     mTabs = new EditorTabBar;
281 
282     mSplitter = new MultiSplitter(this);
283     CodeEditorBox* defaultBox = newBox(mSplitter);
284 
285     mSplitter->addWidget(defaultBox);
286 
287     multiEditorLayout = new QVBoxLayout;
288     multiEditorLayout->setContentsMargins(0, 0, 0, 0);
289     multiEditorLayout->setSpacing(0);
290     multiEditorLayout->addWidget(mTabs);
291     multiEditorLayout->addWidget(mSplitter);
292     setLayout(multiEditorLayout);
293 
294     makeSignalConnections();
295 
296     connect(main, SIGNAL(applySettingsRequest(Settings::Manager*)), this, SLOT(applySettings(Settings::Manager*)));
297 
298     createActions();
299 
300     setCurrentBox(defaultBox); // will updateActions();
301 
302     applySettings(main->settings());
303 }
304 
makeSignalConnections()305 void MultiEditor::makeSignalConnections() {
306     DocumentManager* docManager = Main::documentManager();
307 
308     connect(docManager, SIGNAL(opened(Document*, int, int)), this, SLOT(onOpen(Document*, int, int)));
309     connect(docManager, SIGNAL(closed(Document*)), this, SLOT(onClose(Document*)));
310     connect(docManager, SIGNAL(saved(Document*)), this, SLOT(update(Document*)));
311     connect(docManager, SIGNAL(showRequest(Document*, int, int)), this, SLOT(show(Document*, int, int)));
312     connect(docManager, SIGNAL(titleChanged(Document*)), this, SLOT(update(Document*)));
313 
314     connect(mTabs, SIGNAL(currentChanged(int)), this, SLOT(onCurrentTabChanged(int)));
315     connect(mTabs, SIGNAL(tabCloseRequested(int)), this, SLOT(onCloseRequest(int)));
316     connect(mTabs, SIGNAL(tabMoved(int, int)), this, SLOT(updateDocOrder(int, int)));
317 
318     mBoxSigMux->connect(SIGNAL(currentChanged(GenericCodeEditor*)), this,
319                         SLOT(onCurrentEditorChanged(GenericCodeEditor*)));
320 }
321 
updateDocOrder(int from,int to)322 void MultiEditor::updateDocOrder(int from, int to) { Q_EMIT(updateDockletOrder(from, to)); }
323 
breakSignalConnections()324 void MultiEditor::breakSignalConnections() {
325     DocumentManager* docManager = Main::documentManager();
326     docManager->disconnect(this);
327     mTabs->disconnect(this);
328     mBoxSigMux->disconnect(this);
329 }
330 
createActions()331 void MultiEditor::createActions() {
332     Settings::Manager* settings = Main::settings();
333 
334     QAction* action;
335     const QString editorCategory(tr("Text Editor"));
336 
337     // Edit
338 
339     mActions[Undo] = action = new QAction(QIcon::fromTheme("edit-undo"), tr("&Undo"), this);
340     action->setShortcut(tr("Ctrl+Z", "Undo"));
341     action->setStatusTip(tr("Undo last editing action"));
342     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(undo()));
343     mEditorSigMux->connect(SIGNAL(undoAvailable(bool)), action, SLOT(setEnabled(bool)));
344     settings->addAction(action, "editor-undo", editorCategory);
345 
346     mActions[Redo] = action = new QAction(QIcon::fromTheme("edit-redo"), tr("Re&do"), this);
347     action->setShortcut(tr("Ctrl+Shift+Z", "Redo"));
348     action->setStatusTip(tr("Redo next editing action"));
349     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(redo()));
350     mEditorSigMux->connect(SIGNAL(redoAvailable(bool)), action, SLOT(setEnabled(bool)));
351     settings->addAction(action, "editor-redo", editorCategory);
352 
353     mActions[Cut] = action = new QAction(QIcon::fromTheme("edit-cut"), tr("Cu&t"), this);
354     action->setShortcut(tr("Ctrl+X", "Cut"));
355     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
356     action->setStatusTip(tr("Cut text to clipboard"));
357     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(cut()));
358     mEditorSigMux->connect(SIGNAL(copyAvailable(bool)), action, SLOT(setEnabled(bool)));
359     settings->addAction(action, "editor-cut", editorCategory);
360 
361     mActions[Copy] = action = new QAction(QIcon::fromTheme("edit-copy"), tr("&Copy"), this);
362     action->setShortcut(tr("Ctrl+C", "Copy"));
363     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
364     action->setStatusTip(tr("Copy text to clipboard"));
365     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(copy()));
366     mEditorSigMux->connect(SIGNAL(copyAvailable(bool)), action, SLOT(setEnabled(bool)));
367     settings->addAction(action, "editor-copy", editorCategory);
368 
369     mActions[Paste] = action = new QAction(QIcon::fromTheme("edit-paste"), tr("&Paste"), this);
370     action->setShortcut(tr("Ctrl+V", "Paste"));
371     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
372     action->setStatusTip(tr("Paste text from clipboard"));
373     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(paste()));
374     settings->addAction(action, "editor-paste", editorCategory);
375 
376     mActions[IndentLineOrRegion] = action =
377         new QAction(QIcon::fromTheme("format-indent-line"), tr("Autoindent Line or Region"), this);
378     action->setStatusTip(tr("Autoindent Line or Region"));
379     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(indent()), SignalMultiplexer::ConnectionOptional);
380     settings->addAction(action, "editor-indent-auto", editorCategory);
381 
382     mActions[TriggerAutoCompletion] = action = new QAction(tr("Trigger Autocompletion"), this);
383     action->setStatusTip(tr("Suggest possible completions of text at cursor"));
384     action->setShortcut(tr("Ctrl+Space", "Trigger Autocompletion"));
385     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
386     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(triggerAutoCompletion()),
387                            SignalMultiplexer::ConnectionOptional);
388     settings->addAction(action, "editor-autocompletion", editorCategory);
389 
390     mActions[TriggerMethodCallAid] = action = new QAction(tr("Trigger Method Call Aid"), this);
391     action->setStatusTip(tr("Show arguments for currently typed method call"));
392     action->setShortcut(tr("Ctrl+Shift+Space", "Trigger Method Call Aid"));
393     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
394     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(triggerMethodCallAid()),
395                            SignalMultiplexer::ConnectionOptional);
396     settings->addAction(action, "editor-method-call-assist", editorCategory);
397 
398     mActions[ToggleComment] = action = new QAction(QIcon::fromTheme("edit-comment"), tr("Toggle &Comment"), this);
399     action->setShortcut(tr("Ctrl+/", "Toggle Comment"));
400     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
401     action->setStatusTip(tr("Toggle Comment"));
402     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(toggleComment()), SignalMultiplexer::ConnectionOptional);
403     settings->addAction(action, "editor-toggle-comment", editorCategory);
404 
405     mActions[ToggleOverwriteMode] = action =
406         new QAction(QIcon::fromTheme("edit-overwrite"), tr("Toggle &Overwrite Mode"), this);
407     action->setShortcut(tr("Insert", "Toggle Overwrite Mode"));
408     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
409     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(toggleOverwriteMode()));
410     settings->addAction(action, "editor-toggle-overwrite", editorCategory);
411 
412     mActions[CopyLineUp] = action = new QAction(QIcon::fromTheme("edit-copylineup"), tr("Copy Line Up"), this);
413     action->setShortcut(tr("Ctrl+Alt+Up", "Copy Line Up"));
414     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
415     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(copyLineUp()));
416     settings->addAction(action, "editor-copy-line-up", editorCategory);
417 
418     mActions[CopyLineDown] = action = new QAction(QIcon::fromTheme("edit-copylinedown"), tr("Copy Line Down"), this);
419     action->setShortcut(tr("Ctrl+Alt+Down", "Copy Line Up"));
420     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
421     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(copyLineDown()));
422     settings->addAction(action, "editor-copy-line-down", editorCategory);
423 
424     mActions[MoveLineUp] = action = new QAction(QIcon::fromTheme("edit-movelineup"), tr("Move Line Up"), this);
425     action->setShortcut(tr("Ctrl+Shift+Up", "Move Line Up"));
426     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
427     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(moveLineUp()));
428     settings->addAction(action, "editor-move-line-up", editorCategory);
429 
430     mActions[MoveLineDown] = action = new QAction(QIcon::fromTheme("edit-movelinedown"), tr("Move Line Down"), this);
431     action->setShortcut(tr("Ctrl+Shift+Down", "Move Line Up"));
432     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
433     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(moveLineDown()));
434     settings->addAction(action, "editor-move-line-down", editorCategory);
435 
436     mActions[DeleteWord] = action = new QAction(QIcon::fromTheme("edit-deleteword"), tr("Delete Word"), this);
437 #ifdef Q_OS_MAC
438     action->setShortcut(tr("Meta+W", "Delete Word"));
439 #endif
440     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
441     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(deleteWord()));
442     settings->addAction(action, "delete-word", editorCategory);
443 
444     mActions[GotoPreviousBlock] = action =
445         new QAction(QIcon::fromTheme("edit-gotopreviousblock"), tr("Go to Previous Block"), this);
446     action->setShortcut(tr("Ctrl+[", "Go to Previous Block"));
447     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
448     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(gotoPreviousBlock()),
449                            SignalMultiplexer::ConnectionOptional);
450     settings->addAction(action, "editor-go-to-prev-block", editorCategory);
451 
452     mActions[GotoNextBlock] = action =
453         new QAction(QIcon::fromTheme("edit-gotonextblock"), tr("Go to Next Block"), this);
454     action->setShortcut(tr("Ctrl+]", "Go to Next Block"));
455     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
456     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(gotoNextBlock()), SignalMultiplexer::ConnectionOptional);
457     settings->addAction(action, "editor-go-to-next-block", editorCategory);
458 
459     mActions[SelectEnclosingBlock] = action = new QAction(tr("Select Enclosing Block"), this);
460     action->setShortcut(tr("Ctrl+Shift+B", "Select Enclosing Block"));
461     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
462     action->setStatusTip(tr("Select everything between brackets that contain cursor"));
463     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(selectBlockAroundCursor()),
464                            SignalMultiplexer::ConnectionOptional);
465     settings->addAction(action, "editor-select-enclosing-block", editorCategory);
466 
467     mActions[GotoPreviousRegion] = action =
468         new QAction(QIcon::fromTheme("edit-gotopreviousregion"), tr("Go to Previous Region"), this);
469     action->setShortcut(tr("Ctrl+Alt+[", "Go to Previous Region"));
470     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
471     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(gotoPreviousRegion()),
472                            SignalMultiplexer::ConnectionOptional);
473     settings->addAction(action, "editor-go-to-prev-region", editorCategory);
474 
475     mActions[GotoNextRegion] = action =
476         new QAction(QIcon::fromTheme("edit-gotonextregion"), tr("Go to Next Region"), this);
477     action->setShortcut(tr("Ctrl+Alt+]", "Go to Next Region"));
478     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
479     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(gotoNextRegion()), SignalMultiplexer::ConnectionOptional);
480     settings->addAction(action, "editor-go-to-next-region", editorCategory);
481 
482     mActions[GotoPreviousEmptyLine] = action = new QAction(tr("Go to Previous Empty Line"), this);
483     action->setShortcut(tr("Ctrl+Up", "Go to Previous Empty Line"));
484     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
485     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(gotoPreviousEmptyLine()));
486     settings->addAction(action, "editor-go-to-prev-empty-line", editorCategory);
487 
488     mActions[GotoNextEmptyLine] = action = new QAction(tr("Go to Next Empty Line"), this);
489     action->setShortcut(tr("Ctrl+Down", "Go to Next Empty Line"));
490     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
491     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(gotoNextEmptyLine()));
492     settings->addAction(action, "editor-go-to-next-empty-line", editorCategory);
493 
494     mActions[SelectRegion] = action = new QAction(tr("Select Region"), this);
495     action->setShortcut(tr("Ctrl+Shift+R", "Select Region"));
496     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
497     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(selectCurrentRegion()),
498                            SignalMultiplexer::ConnectionOptional);
499     settings->addAction(action, "editor-select-region", editorCategory);
500 
501     // View
502 
503     mActions[DocClose] = action = new QAction(QIcon::fromTheme("window-close"), tr("&Close"), this);
504     action->setShortcut(tr("Ctrl+W", "Close document"));
505     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
506     action->setStatusTip(tr("Close the current document"));
507     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(closeDocument()));
508     settings->addAction(action, "ide-document-close", editorCategory);
509 
510     mActions[EnlargeFont] = action = new QAction(QIcon::fromTheme("zoom-in"), tr("&Enlarge Font"), this);
511     action->setShortcut(tr("Ctrl++", "Enlarge font"));
512     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
513     action->setStatusTip(tr("Increase displayed font size"));
514     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(zoomIn()));
515     settings->addAction(action, "editor-enlarge-font", editorCategory);
516 
517     mActions[ShrinkFont] = action = new QAction(QIcon::fromTheme("zoom-out"), tr("&Shrink Font"), this);
518     action->setShortcut(tr("Ctrl+-", "Shrink font"));
519     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
520     action->setStatusTip(tr("Decrease displayed font size"));
521     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(zoomOut()));
522     settings->addAction(action, "editor-shrink-font", editorCategory);
523 
524     mActions[ResetFontSize] = action = new QAction(QIcon::fromTheme("zoom-reset"), tr("&Reset Font Size"), this);
525     action->setShortcut(tr("Ctrl+0", "Reset font"));
526     action->setStatusTip(tr("Reset displayed font size"));
527     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(resetFontSize()));
528     settings->addAction(action, "editor-reset-font-size", editorCategory);
529 
530     mActions[ShowWhitespace] = action = new QAction(tr("Show Spaces and Tabs"), this);
531     action->setCheckable(true);
532     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
533 #ifdef Q_OS_MAC
534     action->setShortcut(QKeySequence(Qt::META | Qt::Key_E, Qt::META | Qt::Key_V));
535 #else
536     action->setShortcut(QKeySequence(Qt::ALT | Qt::Key_E, Qt::ALT | Qt::Key_V));
537 #endif
538     connect(action, SIGNAL(triggered(bool)), this, SLOT(setShowWhitespace(bool)));
539     settings->addAction(action, "editor-toggle-show-whitespace", editorCategory);
540 
541     mActions[ShowLinenumber] = action = new QAction(tr("Show Line Number"), this);
542     action->setCheckable(true);
543     action->setShortcut(tr("Ctrl+Alt+#", "Show Line Number"));
544     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
545     connect(action, SIGNAL(triggered(bool)), this, SLOT(setShowLinenumber(bool)));
546     settings->addAction(action, "editor-toggle-show-line-number", editorCategory);
547 
548     mActions[ShowAutocompleteHelp] = action = new QAction(tr("Show Autocomplete Help"), this);
549     action->setCheckable(true);
550     connect(action, SIGNAL(triggered(bool)), this, SLOT(setShowAutocompleteHelp(bool)));
551     settings->addAction(action, "editor-toggle-show-autocomplete-help", editorCategory);
552 
553     mActions[IndentWithSpaces] = action = new QAction(tr("Use Spaces for Indentation"), this);
554     action->setCheckable(true);
555     action->setStatusTip(tr("Indent with spaces instead of tabs"));
556     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
557     mEditorSigMux->connect(action, SIGNAL(triggered(bool)), SLOT(setSpaceIndent(bool)),
558                            SignalMultiplexer::ConnectionOptional);
559     settings->addAction(action, "editor-toggle-space-indent", editorCategory);
560 
561     mActions[NextDocument] = action = new QAction(tr("Next Document"), this);
562 #ifndef Q_OS_MAC
563     action->setShortcut(tr("Alt+Right", "Next Document"));
564 #else
565     action->setShortcut(tr("Ctrl+Alt+Right", "Next Document"));
566 #endif
567     connect(action, SIGNAL(triggered()), this, SLOT(showNextDocument()));
568     settings->addAction(action, "editor-document-next", editorCategory);
569 
570     mActions[PreviousDocument] = action = new QAction(tr("Previous Document"), this);
571 #ifndef Q_OS_MAC
572     action->setShortcut(tr("Alt+Left", "Previous Document"));
573 #else
574     action->setShortcut(tr("Ctrl+Alt+Left", "Previous Document"));
575 #endif
576     connect(action, SIGNAL(triggered()), this, SLOT(showPreviousDocument()));
577     settings->addAction(action, "editor-document-previous", editorCategory);
578 
579     mActions[SwitchDocument] = action = new QAction(tr("Switch Document"), this);
580 #ifndef Q_OS_MAC
581     action->setShortcut(tr("Ctrl+Tab", "Switch Document"));
582 #else
583     action->setShortcut(tr("Alt+Tab", "Switch Document"));
584 #endif
585     connect(action, SIGNAL(triggered()), this, SLOT(switchDocument()));
586     settings->addAction(action, "editor-document-switch", editorCategory);
587 
588     mActions[SplitHorizontally] = action = new QAction(tr("Split To Right"), this);
589     // action->setShortcut( tr("Ctrl+P, 3", "Split To Right"));
590     connect(action, SIGNAL(triggered()), this, SLOT(splitHorizontally()));
591     settings->addAction(action, "editor-split-right", editorCategory);
592 
593     mActions[SplitVertically] = action = new QAction(tr("Split To Bottom"), this);
594     // action->setShortcut( tr("Ctrl+P, 2", "Split To Bottom"));
595     connect(action, SIGNAL(triggered()), this, SLOT(splitVertically()));
596     settings->addAction(action, "editor-split-bottom", editorCategory);
597 
598     mActions[RemoveCurrentSplit] = action = new QAction(tr("Remove Current Split"), this);
599     // action->setShortcut( tr("Ctrl+P, 1", "Remove Current Split"));
600     connect(action, SIGNAL(triggered()), this, SLOT(removeCurrentSplit()));
601     settings->addAction(action, "editor-split-remove", editorCategory);
602 
603     mActions[RemoveAllSplits] = action = new QAction(tr("Remove All Splits"), this);
604     // action->setShortcut( tr("Ctrl+P, 0", "Remove All Splits"));
605     connect(action, SIGNAL(triggered()), this, SLOT(removeAllSplits()));
606     settings->addAction(action, "editor-split-remove-all", editorCategory);
607 
608     // Language
609 
610     mActions[EvaluateCurrentDocument] = action =
611         new QAction(QIcon::fromTheme("media-playback-start"), tr("Evaluate &File"), this);
612     action->setStatusTip(tr("Evaluate current File"));
613     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
614     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(evaluateDocument()),
615                            SignalMultiplexer::ConnectionOptional);
616     settings->addAction(action, "editor-eval-file", editorCategory);
617 
618     mActions[EvaluateRegion] = action =
619         new QAction(QIcon::fromTheme("media-playback-start"), tr("&Evaluate Selection, Line or Region"), this);
620     action->setShortcut(tr("Ctrl+Return", "Evaluate region"));
621     action->setStatusTip(tr("Evaluate current region"));
622     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
623     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(evaluateRegion()), SignalMultiplexer::ConnectionOptional);
624     settings->addAction(action, "editor-eval-smart", editorCategory);
625 
626     mActions[EvaluateLine] = action =
627         new QAction(QIcon::fromTheme("media-playback-start"), tr("&Evaluate Selection or Line"), this);
628     action->setShortcut(tr("Shift+Return", "Evaluate selection/line"));
629     action->setStatusTip(tr("Evaluate current selection/line"));
630     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
631     mEditorSigMux->connect(action, SIGNAL(triggered()), SLOT(evaluateLine()), SignalMultiplexer::ConnectionOptional);
632     settings->addAction(action, "editor-eval-line", editorCategory);
633 
634     // These actions are not added to any menu, so they have to be added
635     // at least to this widget, in order for the shortcuts to always respond:
636     addAction(mActions[TriggerAutoCompletion]);
637     addAction(mActions[TriggerMethodCallAid]);
638     addAction(mActions[SwitchDocument]);
639 
640     // These actions have to be added because to the widget because they have
641     // Qt::WidgetWithChildrenShortcut context:
642     addAction(mActions[Cut]);
643     addAction(mActions[Copy]);
644     addAction(mActions[Paste]);
645     addAction(mActions[DocClose]);
646     addAction(mActions[EnlargeFont]);
647     addAction(mActions[ShrinkFont]);
648     addAction(mActions[ShowWhitespace]);
649     addAction(mActions[ShowLinenumber]);
650     addAction(mActions[IndentWithSpaces]);
651     addAction(mActions[EvaluateCurrentDocument]);
652     addAction(mActions[EvaluateRegion]);
653     addAction(mActions[EvaluateLine]);
654     addAction(mActions[ToggleComment]);
655     addAction(mActions[ToggleOverwriteMode]);
656     addAction(mActions[CopyLineUp]);
657     addAction(mActions[CopyLineDown]);
658     addAction(mActions[MoveLineUp]);
659     addAction(mActions[MoveLineDown]);
660     addAction(mActions[DeleteWord]);
661     addAction(mActions[GotoPreviousBlock]);
662     addAction(mActions[GotoNextBlock]);
663     addAction(mActions[SelectEnclosingBlock]);
664     addAction(mActions[GotoPreviousRegion]);
665     addAction(mActions[GotoNextRegion]);
666     addAction(mActions[GotoPreviousEmptyLine]);
667     addAction(mActions[GotoNextEmptyLine]);
668     addAction(mActions[SelectRegion]);
669 }
670 
updateActions()671 void MultiEditor::updateActions() {
672     GenericCodeEditor* editor = currentEditor();
673     ScCodeEditor* scEditor = qobject_cast<ScCodeEditor*>(editor);
674     QTextDocument* doc = editor ? editor->textDocument() : 0;
675 
676     mActions[Undo]->setEnabled(doc && doc->isUndoAvailable());
677     mActions[Redo]->setEnabled(doc && doc->isRedoAvailable());
678     mActions[Copy]->setEnabled(editor && editor->textCursor().hasSelection());
679     mActions[Cut]->setEnabled(mActions[Copy]->isEnabled());
680     mActions[Paste]->setEnabled(editor);
681     mActions[ToggleOverwriteMode]->setEnabled(editor);
682     mActions[CopyLineUp]->setEnabled(editor);
683     mActions[CopyLineDown]->setEnabled(editor);
684     mActions[MoveLineUp]->setEnabled(editor);
685     mActions[MoveLineDown]->setEnabled(editor);
686     mActions[DeleteWord]->setEnabled(editor);
687     mActions[GotoPreviousEmptyLine]->setEnabled(editor);
688     mActions[GotoNextEmptyLine]->setEnabled(editor);
689     mActions[DocClose]->setEnabled(editor);
690     mActions[EnlargeFont]->setEnabled(editor);
691     mActions[ShrinkFont]->setEnabled(editor);
692     mActions[ResetFontSize]->setEnabled(editor);
693     mActions[IndentWithSpaces]->setEnabled(scEditor);
694     mActions[IndentWithSpaces]->setChecked(scEditor && scEditor->spaceIndent());
695 
696     // ScLang-specific actions
697     bool editorIsScCodeEditor = qobject_cast<ScCodeEditor*>(editor); // NOOP at the moment, but
698     mActions[ToggleComment]->setEnabled(editor && editorIsScCodeEditor);
699     mActions[GotoPreviousBlock]->setEnabled(editor && editorIsScCodeEditor);
700     mActions[GotoNextBlock]->setEnabled(editor && editorIsScCodeEditor);
701     mActions[SelectEnclosingBlock]->setEnabled(editor && editorIsScCodeEditor);
702     mActions[GotoPreviousRegion]->setEnabled(editor && editorIsScCodeEditor);
703     mActions[GotoNextRegion]->setEnabled(editor && editorIsScCodeEditor);
704     mActions[SelectRegion]->setEnabled(editor && editorIsScCodeEditor);
705     mActions[IndentLineOrRegion]->setEnabled(editor && editorIsScCodeEditor);
706 
707     mActions[EvaluateCurrentDocument]->setEnabled(editor && editorIsScCodeEditor);
708     mActions[EvaluateRegion]->setEnabled(editor && editorIsScCodeEditor);
709     mActions[EvaluateLine]->setEnabled(editor && editorIsScCodeEditor);
710 }
711 
applySettings(Settings::Manager * settings)712 void MultiEditor::applySettings(Settings::Manager* settings) {
713     bool show_whitespace = settings->value("IDE/editor/showWhitespace").toBool();
714     bool show_linenumber = settings->value("IDE/editor/showLinenumber").toBool();
715     bool show_autocompletehelp = settings->value("IDE/editor/showAutocompleteHelp").toBool();
716     mActions[ShowWhitespace]->setChecked(show_whitespace);
717     mActions[ShowLinenumber]->setChecked(show_linenumber);
718     mActions[ShowAutocompleteHelp]->setChecked(show_autocompletehelp);
719 
720     setMainComboBoxOption();
721 
722     int boxCount = mSplitter->findChildren<CodeEditorBox*>().count();
723     if (boxCount > 1) {
724         activateComboBoxWhenSplitting();
725     }
726 }
727 
activateComboBoxWhenSplitting()728 void MultiEditor::activateComboBoxWhenSplitting() {
729     emit splitViewActivated();
730     bool comboBoxInUse = Main::settings()->value("IDE/editor/useComboBox").toBool();
731     if (!comboBoxInUse) {
732         bool comboBoxWhenSplitting = Main::settings()->value("IDE/editor/useComboBoxWhenSplitting").toBool();
733         showEditorTabs(comboBoxWhenSplitting);
734     }
735 }
736 
setMainComboBoxOption()737 void MultiEditor::setMainComboBoxOption() {
738     bool comboBoxInUse = Main::settings()->value("IDE/editor/useComboBox").toBool();
739     showEditorTabs(comboBoxInUse);
740 }
741 
showEditorTabs(bool condition)742 void MultiEditor::showEditorTabs(bool condition) {
743     if (condition)
744         mTabs->hide();
745     else
746         mTabs->show();
747 }
748 
saveBoxState(CodeEditorBox * box,const QList<Document * > & documentList)749 static QVariantList saveBoxState(CodeEditorBox* box, const QList<Document*>& documentList) {
750     // save editors in reverse order - first one is last shown.
751     QVariantList boxData;
752     int idx = box->history().count();
753     while (idx--) {
754         GenericCodeEditor* editor = box->history()[idx];
755         if (!editor->document()->filePath().isEmpty()) {
756             int documentIndex = documentList.indexOf(editor->document());
757             Q_ASSERT(documentIndex >= 0);
758             QVariantMap editorData;
759             editorData.insert("documentIndex", documentIndex);
760             editorData.insert("position", editor->textCursor().position());
761             boxData.append(editorData);
762         }
763     }
764     return boxData;
765 }
766 
saveSplitterState(QSplitter * splitter,const QList<Document * > & documentList)767 static QVariantMap saveSplitterState(QSplitter* splitter, const QList<Document*>& documentList) {
768     QVariantMap splitterData;
769 
770     splitterData.insert("state", splitter->saveState().toBase64());
771 
772     QVariantList childrenData;
773 
774     int childCount = splitter->count();
775     for (int idx = 0; idx < childCount; idx++) {
776         QWidget* child = splitter->widget(idx);
777 
778         CodeEditorBox* box = qobject_cast<CodeEditorBox*>(child);
779         if (box) {
780             QVariantList boxData = saveBoxState(box, documentList);
781             childrenData.append(QVariant(boxData));
782             continue;
783         }
784 
785         QSplitter* childSplitter = qobject_cast<QSplitter*>(child);
786         if (childSplitter) {
787             QVariantMap childSplitterData = saveSplitterState(childSplitter, documentList);
788             childrenData.append(QVariant(childSplitterData));
789         }
790     }
791 
792     splitterData.insert("elements", childrenData);
793 
794     return splitterData;
795 }
796 
saveSession(Session * session)797 void MultiEditor::saveSession(Session* session) {
798     QList<Document*> documentList;
799 
800     QVariantList tabsData;
801     int tabCount = mTabs->count();
802     for (int tabIdx = 0; tabIdx < tabCount; ++tabIdx) {
803         Document* doc = documentForTab(tabIdx);
804         if (doc) {
805             documentList << doc;
806             tabsData << doc->filePath();
807         }
808     }
809 
810     session->setValue("documents", QVariant::fromValue(tabsData));
811 
812     session->remove("editors");
813     session->setValue("editors", saveSplitterState(mSplitter, documentList));
814 }
815 
loadBoxState(CodeEditorBox * box,const QVariantList & data,const QList<Document * > & documentList)816 void MultiEditor::loadBoxState(CodeEditorBox* box, const QVariantList& data, const QList<Document*>& documentList) {
817     int docCount = documentList.count();
818     foreach (QVariant docVar, data) {
819         QVariantMap docData = docVar.value<QVariantMap>();
820         int docIndex = docData.value("documentIndex").toInt();
821         int docPos = docData.value("position").toInt();
822         if (docIndex >= 0 && docIndex < docCount)
823             box->setDocument(documentList[docIndex], docPos);
824     }
825 }
826 
loadSplitterState(MultiSplitter * splitter,const QVariantMap & data,const QList<Document * > & documentList)827 void MultiEditor::loadSplitterState(MultiSplitter* splitter, const QVariantMap& data,
828                                     const QList<Document*>& documentList) {
829     QByteArray state = QByteArray::fromBase64(data.value("state").value<QByteArray>());
830 
831     QVariantList childrenData = data.value("elements").value<QVariantList>();
832     foreach (const QVariant& childVar, childrenData) {
833         if (childVar.type() == QVariant::List) {
834             CodeEditorBox* childBox = newBox(splitter);
835             splitter->addWidget(childBox);
836             QVariantList childBoxData = childVar.value<QVariantList>();
837             loadBoxState(childBox, childBoxData, documentList);
838         } else if (childVar.type() == QVariant::Map) {
839             MultiSplitter* childSplitter = new MultiSplitter(this);
840             splitter->addWidget(childSplitter);
841             QVariantMap childSplitterData = childVar.value<QVariantMap>();
842             loadSplitterState(childSplitter, childSplitterData, documentList);
843         }
844     }
845 
846     if (!splitter->restoreState(state))
847         qWarning("MultiEditor: could not restore splitter state!");
848 }
849 
switchSession(Session * session)850 void MultiEditor::switchSession(Session* session) {
851     ///// Going offline...
852 
853     breakSignalConnections();
854 
855     DocumentManager* docManager = Main::documentManager();
856 
857     QList<Document*> documentList = docManager->documents();
858 
859     // close all docs
860     foreach (Document* doc, documentList)
861         docManager->close(doc);
862 
863     // remove all tabs
864     while (mTabs->count())
865         mTabs->removeTab(0);
866 
867     // remove all editors
868     delete mSplitter;
869 
870     documentList.clear();
871 
872     mSplitter = new MultiSplitter(this);
873 
874     CodeEditorBox* firstBox = 0;
875 
876     if (session) {
877         // open documents saved in the session
878         QVariantList docDataList = session->value("documents").value<QVariantList>();
879         foreach (const QVariant& docData, docDataList) {
880             QString filePath = docData.toString();
881             Document* doc = docManager->open(filePath, -1, 0, true);
882             documentList << doc;
883         }
884 
885         // restore tabs
886         for (int i = 0; i < documentList.size(); ++i)
887             insertTab(documentList[i], i);
888 
889         // restore editors
890         if (session->contains("editors")) {
891             QVariantMap splitterData = session->value("editors").value<QVariantMap>();
892             loadSplitterState(mSplitter, splitterData, documentList);
893 
894             if (mSplitter->count()) {
895                 firstBox = mSplitter->findChild<CodeEditorBox>();
896                 if (!firstBox) {
897                     qWarning("Session seems to contain invalid editor split data!");
898                     delete mSplitter;
899                     mSplitter = new MultiSplitter(this);
900                 }
901             }
902         }
903     }
904 
905     if (!firstBox) {
906         // Restoring the session didn't result in any editor box, so create one:
907         firstBox = newBox(mSplitter);
908         mSplitter->addWidget(firstBox);
909     }
910 
911     layout()->addWidget(mSplitter);
912 
913     makeSignalConnections();
914 
915     ///// Back online.
916 
917     mCurrentEditorBox = 0; // ensure complete update
918     setCurrentBox(firstBox);
919 
920     if (!session)
921         // create a document on new session
922         docManager->create();
923 
924     firstBox->setFocus(Qt::OtherFocusReason); // ensure focus
925 
926     setMainComboBoxOption();
927     if (mSplitter->count() > 1)
928         activateComboBoxWhenSplitting();
929     else
930         emit splitViewDeactivated();
931 }
932 
insertTab(Document * doc,int insertIndex)933 int MultiEditor::insertTab(Document* doc, int insertIndex) {
934     if (!doc)
935         return -1;
936 
937     int tabIdx = tabForDocument(doc);
938     if (tabIdx != -1)
939         return tabIdx;
940 
941     QTextDocument* tdoc = doc->textDocument();
942 
943     QIcon icon;
944     if (tdoc->isModified())
945         icon = mDocModifiedIcon;
946 
947     insertIndex = insertIndex < 0 ? mTabs->currentIndex() + 1 : insertIndex;
948     tabIdx = mTabs->insertTab(insertIndex, icon, doc->title());
949     mTabs->setTabData(tabIdx, QVariant::fromValue<Document*>(doc));
950 
951     connect(tdoc, &QTextDocument::modificationChanged, [this, doc](bool) { onDocModified(doc); });
952 
953     return tabIdx;
954 }
955 
setCurrent(Document * doc)956 void MultiEditor::setCurrent(Document* doc) {
957     int tabIdx = tabForDocument(doc);
958     if (tabIdx != -1)
959         mTabs->setCurrentIndex(tabIdx);
960 }
961 
updateTabsOrder(QList<Document * > docOrder)962 void MultiEditor::updateTabsOrder(QList<Document*> docOrder) {
963     mTabs->blockSignals(true);
964     for (int idx = 0; idx < docOrder.count(); idx++) {
965         if (docOrder.at(idx) != documentForTab(idx)) {
966             Document* doc = docOrder.at(idx);
967             int tabIdx = tabForDocument(doc);
968             mTabs->moveTab(tabIdx, idx);
969         }
970     }
971     mTabs->blockSignals(false);
972 }
973 
showNextDocument()974 void MultiEditor::showNextDocument() {
975     int currentIndex = mTabs->currentIndex();
976     mTabs->setCurrentIndex(qMin(currentIndex + 1, mTabs->count() - 1));
977 }
978 
showPreviousDocument()979 void MultiEditor::showPreviousDocument() {
980     int currentIndex = mTabs->currentIndex();
981     mTabs->setCurrentIndex(qMax(0, currentIndex - 1));
982 }
983 
switchDocument()984 void MultiEditor::switchDocument() {
985     CodeEditorBox* box = currentBox();
986 
987     DocumentSelectPopUp* popup = new DocumentSelectPopUp(box->history(), this);
988 
989     QRect popupRect(0, 0, 300, 200);
990     popupRect.moveCenter(rect().center());
991     popup->resize(popupRect.size());
992     QPoint globalPosition = mapToGlobal(popupRect.topLeft());
993 
994     Document* selectedDocument = popup->exec(globalPosition);
995 
996     if (selectedDocument)
997         box->setDocument(selectedDocument);
998 }
999 
onOpen(Document * doc,int initialCursorPosition,int selectionLength)1000 void MultiEditor::onOpen(Document* doc, int initialCursorPosition, int selectionLength) {
1001     insertTab(doc);
1002 
1003     currentBox()->setDocument(doc, initialCursorPosition, selectionLength);
1004     currentBox()->setFocus(Qt::OtherFocusReason);
1005 }
1006 
onClose(Document * doc)1007 void MultiEditor::onClose(Document* doc) {
1008     int tabIdx = tabForDocument(doc);
1009     if (tabIdx != -1)
1010         mTabs->removeTab(tabIdx);
1011     // TODO: each box should switch document according to their own history
1012 }
1013 
onDocModified(Document * doc)1014 void MultiEditor::onDocModified(Document* doc) {
1015     if (!doc)
1016         return;
1017 
1018     int tabIdx = tabForDocument(doc);
1019     if (tabIdx == -1)
1020         return;
1021 
1022     bool isModified = doc->textDocument()->isModified();
1023     QIcon icon;
1024     if (isModified)
1025         icon = mDocModifiedIcon;
1026 
1027     Main::evaluateCodeIfCompiled(
1028         QStringLiteral("Document.findByQUuid(\'%1\').prSetEdited(%2)").arg(doc->id().constData()).arg(isModified),
1029         true);
1030 
1031     mTabs->setTabIcon(tabIdx, icon);
1032 }
1033 
show(Document * doc,int pos,int selectionLength)1034 void MultiEditor::show(Document* doc, int pos, int selectionLength) {
1035     currentBox()->setDocument(doc, pos, selectionLength);
1036     currentBox()->setFocus(Qt::OtherFocusReason);
1037 }
1038 
update(Document * doc)1039 void MultiEditor::update(Document* doc) {
1040     int tabIdx = tabForDocument(doc);
1041     if (tabIdx != -1)
1042         mTabs->setTabText(tabIdx, doc->title());
1043 
1044     // update thisProcess.nowExecutingPath
1045     GenericCodeEditor* editor = currentEditor();
1046     if (editor->document() == doc)
1047         Main::documentManager()->setActiveDocument(doc);
1048 }
1049 
onCloseRequest(int index)1050 void MultiEditor::onCloseRequest(int index) {
1051     Document* doc = documentForTab(index);
1052     if (doc)
1053         MainWindow::close(doc);
1054 }
1055 
onCurrentTabChanged(int index)1056 void MultiEditor::onCurrentTabChanged(int index) {
1057     if (index == -1)
1058         return;
1059 
1060     Document* doc = documentForTab(index);
1061     if (!doc)
1062         return;
1063 
1064     CodeEditorBox* curBox = currentBox();
1065     curBox->setDocument(doc);
1066     curBox->setFocus(Qt::OtherFocusReason);
1067 }
1068 
onCurrentEditorChanged(GenericCodeEditor * editor)1069 void MultiEditor::onCurrentEditorChanged(GenericCodeEditor* editor) { setCurrentEditor(editor); }
1070 
onBoxActivated(CodeEditorBox * box)1071 void MultiEditor::onBoxActivated(CodeEditorBox* box) { setCurrentBox(box); }
1072 
documentForTab(int index)1073 Document* MultiEditor::documentForTab(int index) {
1074     QVariant doc = mTabs->tabData(index);
1075     if (doc.isValid() && !doc.isNull())
1076         return doc.value<Document*>();
1077     else
1078         return NULL;
1079 }
1080 
tabForDocument(Document * doc)1081 int MultiEditor::tabForDocument(Document* doc) {
1082     int tabCount = mTabs->count();
1083     for (int idx = 0; idx < tabCount; ++idx) {
1084         Document* tabDoc = documentForTab(idx);
1085         if (tabDoc && tabDoc == doc)
1086             return idx;
1087     }
1088     return -1;
1089 }
1090 
newBox(MultiSplitter * currSplitter)1091 CodeEditorBox* MultiEditor::newBox(MultiSplitter* currSplitter) {
1092     CodeEditorBox* box = new CodeEditorBox(currSplitter);
1093 
1094     connect(box, SIGNAL(activated(CodeEditorBox*)), this, SLOT(onBoxActivated(CodeEditorBox*)));
1095 
1096     return box;
1097 }
1098 
setCurrentBox(CodeEditorBox * box)1099 void MultiEditor::setCurrentBox(CodeEditorBox* box) {
1100     if (mCurrentEditorBox == box)
1101         return;
1102 
1103     mCurrentEditorBox = box;
1104     mBoxSigMux->setCurrentObject(box);
1105     setCurrentEditor(box->currentEditor());
1106 
1107     mCurrentEditorBox->setActive();
1108 }
1109 
setCurrentEditor(GenericCodeEditor * editor)1110 void MultiEditor::setCurrentEditor(GenericCodeEditor* editor) {
1111     if (editor) {
1112         int tabIndex = tabForDocument(editor->document());
1113         if (tabIndex != -1)
1114             mTabs->setCurrentIndex(tabIndex);
1115     }
1116 
1117     mEditorSigMux->setCurrentObject(editor);
1118     updateActions();
1119 
1120     Document* currentDocument = editor ? editor->document() : 0;
1121     Main::documentManager()->setActiveDocument(currentDocument);
1122     emit currentDocumentChanged(currentDocument);
1123 }
1124 
currentEditor()1125 GenericCodeEditor* MultiEditor::currentEditor() { return currentBox()->currentEditor(); }
1126 
split(Qt::Orientation splitDirection)1127 void MultiEditor::split(Qt::Orientation splitDirection) {
1128     CodeEditorBox* box = newBox(mSplitter);
1129     CodeEditorBox* curBox = currentBox();
1130     GenericCodeEditor* curEditor = curBox->currentEditor();
1131 
1132     if (curEditor)
1133         box->setDocument(curEditor->document(), curEditor->textCursor().position());
1134 
1135     mSplitter->insertWidget(box, curBox, splitDirection);
1136     box->setFocus(Qt::OtherFocusReason);
1137 
1138     activateComboBoxWhenSplitting();
1139 }
1140 
removeCurrentSplit()1141 void MultiEditor::removeCurrentSplit() {
1142     int boxCount = mSplitter->findChildren<CodeEditorBox*>().count();
1143     if (boxCount < 2) {
1144         // Do not allow removing the one and only box.
1145         return;
1146     }
1147 
1148     CodeEditorBox* box = currentBox();
1149     mSplitter->removeWidget(box);
1150 
1151     // switch current box to first box found:
1152     box = mSplitter->findChild<CodeEditorBox>();
1153     Q_ASSERT(box);
1154     setCurrentBox(box);
1155     box->setFocus(Qt::OtherFocusReason);
1156 
1157     if (boxCount == 2) {
1158         emit splitViewDeactivated();
1159         setMainComboBoxOption();
1160     } else if (boxCount > 2) {
1161         activateComboBoxWhenSplitting();
1162     }
1163 }
1164 
removeAllSplits()1165 void MultiEditor::removeAllSplits() {
1166     CodeEditorBox* box = currentBox();
1167     Q_ASSERT(box);
1168     Q_ASSERT(mSplitter->count());
1169     if (mSplitter->count() == 1 && mSplitter->widget(0) == box)
1170         // Nothing to do.
1171         return;
1172 
1173     breakSignalConnections();
1174 
1175     MultiSplitter* newSplitter = new MultiSplitter(this);
1176 
1177     CodeEditorBox* nBox = newBox(newSplitter);
1178     newSplitter->addWidget(nBox);
1179 
1180     GenericCodeEditor* curEditor = box->currentEditor();
1181     if (curEditor)
1182         nBox->setDocument(curEditor->document(), curEditor->textCursor().position());
1183 
1184     delete mSplitter;
1185     mSplitter = newSplitter;
1186     layout()->addWidget(mSplitter);
1187 
1188     emit splitViewDeactivated();
1189     setMainComboBoxOption();
1190 
1191     mCurrentEditorBox = 0; // ensure complete update
1192     setCurrentBox(nBox);
1193 
1194     makeSignalConnections(); // ensure signal connections
1195 
1196     nBox->setFocus(Qt::OtherFocusReason);
1197 }
1198 
setShowWhitespace(bool showWhitespace)1199 void MultiEditor::setShowWhitespace(bool showWhitespace) {
1200     Main::settings()->setValue("IDE/editor/showWhitespace", showWhitespace);
1201     Main::instance()->applySettings();
1202 }
1203 
setShowLinenumber(bool showLinenumber)1204 void MultiEditor::setShowLinenumber(bool showLinenumber) {
1205     Main::settings()->setValue("IDE/editor/showLinenumber", showLinenumber);
1206     Main::instance()->applySettings();
1207 }
1208 
setShowAutocompleteHelp(bool showAutocompleteHelp)1209 void MultiEditor::setShowAutocompleteHelp(bool showAutocompleteHelp) {
1210     Main::settings()->setValue("IDE/editor/showAutocompleteHelp", showAutocompleteHelp);
1211 }
1212 
1213 } // namespace ScIDE
1214