Merge pull request #506 from andrewcsmith/patch-2
[supercollider.git] / editors / sc-ide / widgets / multi_editor.cpp
blob9017fae5f959c38f05424c160fe239b229cf8b85
1 /*
2 SuperCollider Qt IDE
3 Copyright (c) 2012 Jakob Leben & Tim Blechmann
4 http://www.audiosynth.com
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.
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.
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
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"
33 #include "yaml-cpp/node.h"
34 #include "yaml-cpp/parser.h"
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>
52 namespace ScIDE {
54 class DocumentSelectPopUp : public QDialog
56 public:
57 DocumentSelectPopUp(const CodeEditorBox::History & history, QWidget * parent):
58 QDialog(parent, Qt::Popup)
60 mModel = new QStandardItemModel(this);
61 populateModel(history);
63 mListView = new QListView();
64 mListView->setModel(mModel);
65 mListView->setFrameShape(QFrame::NoFrame);
67 QHBoxLayout *layout = new QHBoxLayout(this);
68 layout->addWidget(mListView);
69 layout->setContentsMargins(1,1,1,1);
71 connect(mListView, SIGNAL(activated(QModelIndex)), this, SLOT(accept()));
73 mListView->setFocus(Qt::OtherFocusReason);
75 QModelIndex nextIndex = mModel->index(1, 0);
76 mListView->setCurrentIndex(nextIndex);
78 mListView->setEditTriggers(QAbstractItemView::NoEditTriggers);
81 Document * exec( const QPoint & pos )
83 move(pos);
84 if (QDialog::exec())
85 return currentDocument();
86 else
87 return 0;
90 private:
91 bool event(QEvent * event)
93 if (event->type() == QEvent::ShortcutOverride) {
94 event->accept();
95 return true;
97 return QWidget::event(event);
100 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
106 && (ke->key() == Qt::Key_Alt || ke->key() == -1))) {
107 ke->accept();
108 accept();
110 QDialog::keyReleaseEvent(ke);
113 void keyPressEvent(QKeyEvent * ke)
115 switch (ke->key()) {
116 case Qt::Key_Down:
117 case Qt::Key_Tab:
118 cycleDown();
119 ke->accept();
120 return;
122 case Qt::Key_Up:
123 case Qt::Key_Backtab:
124 cycleUp();
125 ke->accept();
126 return;
128 case Qt::Key_Escape:
129 reject();
130 return;
132 default:
136 QDialog::keyPressEvent(ke);
139 void paintEvent( QPaintEvent * )
141 QPainter painter(this);
142 painter.setBrush(Qt::NoBrush);
143 painter.setPen(palette().color(QPalette::Dark));
144 painter.drawRect(rect().adjusted(0,0,-1,-1));
147 void cycleDown()
149 int row = mListView->currentIndex().row() + 1;
150 if (!mModel->hasIndex(row, 0))
151 row = 0;
153 QModelIndex nextIndex = mModel->index(row, 0);
154 mListView->setCurrentIndex(nextIndex);
157 void cycleUp()
159 int row = mListView->currentIndex().row() - 1;
160 if (!mModel->hasIndex(row, 0))
161 row = mModel->rowCount() - 1;
163 QModelIndex nextIndex = mModel->index(row, 0);
164 mListView->setCurrentIndex(nextIndex);
167 Document * currentDocument()
169 QStandardItem * currentItem = mModel->itemFromIndex(mListView->currentIndex());
170 return currentItem ? currentItem->data().value<Document*>()
171 : NULL;
174 void populateModel( const CodeEditorBox::History & history )
176 QList<Document*> displayDocuments;
177 foreach(ScCodeEditor *editor, history)
178 displayDocuments << editor->document();
180 QList<Document*> managerDocuments = Main::documentManager()->documents();
181 foreach(Document *document, managerDocuments)
182 if (!displayDocuments.contains(document))
183 displayDocuments << document;
185 foreach (Document * document, displayDocuments) {
186 QStandardItem * item = new QStandardItem(document->title());
187 item->setData(QVariant::fromValue(document));
188 mModel->appendRow(item);
192 QListView *mListView;
193 QStandardItemModel *mModel;
196 MultiEditor::MultiEditor( Main *main, QWidget * parent ) :
197 QWidget(parent),
198 mSigMux(new SignalMultiplexer(this)),
199 mBoxSigMux(new SignalMultiplexer(this)),
200 mDocModifiedIcon( QApplication::style()->standardIcon(QStyle::SP_DialogSaveButton) )
202 mTabs = new QTabBar;
203 mTabs->setDocumentMode(true);
204 mTabs->setTabsClosable(true);
205 mTabs->setMovable(true);
206 mTabs->setUsesScrollButtons(true);
207 mTabs->setDrawBase(false);
209 CodeEditorBox *defaultBox = newBox();
211 mSplitter = new MultiSplitter();
212 mSplitter->addWidget(defaultBox);
214 QVBoxLayout *l = new QVBoxLayout;
215 l->setContentsMargins(0,0,0,0);
216 l->setSpacing(0);
217 l->addWidget(mTabs);
218 l->addWidget(mSplitter);
219 setLayout(l);
221 makeSignalConnections();
223 mSigMux->connect(SIGNAL(modificationChanged(bool)),
224 this, SLOT(onModificationChanged(bool)));
226 mBoxSigMux->connect(SIGNAL(currentChanged(ScCodeEditor*)),
227 this, SLOT(onCurrentEditorChanged(ScCodeEditor*)));
229 createActions();
231 setCurrentBox( defaultBox ); // will updateActions();
233 applySettings(main->settings());
236 void MultiEditor::makeSignalConnections()
238 DocumentManager *docManager = Main::documentManager();
240 connect(docManager, SIGNAL(opened(Document*, int, int)),
241 this, SLOT(onOpen(Document*, int, int)));
242 connect(docManager, SIGNAL(closed(Document*)),
243 this, SLOT(onClose(Document*)));
244 connect(docManager, SIGNAL(saved(Document*)),
245 this, SLOT(update(Document*)));
246 connect(docManager, SIGNAL(showRequest(Document*, int, int)),
247 this, SLOT(show(Document*, int, int)));
249 connect(mTabs, SIGNAL(currentChanged(int)),
250 this, SLOT(onCurrentTabChanged(int)));
251 connect(mTabs, SIGNAL(tabCloseRequested(int)),
252 this, SLOT(onCloseRequest(int)));
255 void MultiEditor::breakSignalConnections()
257 DocumentManager *docManager = Main::documentManager();
258 docManager->disconnect(this);
259 mTabs->disconnect(this);
262 void MultiEditor::createActions()
264 Settings::Manager *settings = Main::settings();
265 settings->beginGroup("IDE/shortcuts");
267 QAction * act;
269 // Edit
271 mActions[Undo] = act = new QAction(
272 QIcon::fromTheme("edit-undo"), tr("&Undo"), this);
273 act->setShortcut(tr("Ctrl+Z", "Undo"));
274 act->setStatusTip(tr("Undo last editing action"));
275 mSigMux->connect(act, SIGNAL(triggered()), SLOT(undo()));
276 mSigMux->connect(SIGNAL(undoAvailable(bool)), act, SLOT(setEnabled(bool)));
278 mActions[Redo] = act = new QAction(
279 QIcon::fromTheme("edit-redo"), tr("Re&do"), this);
280 act->setShortcut(tr("Ctrl+Shift+Z", "Redo"));
281 act->setStatusTip(tr("Redo next editing action"));
282 mSigMux->connect(act, SIGNAL(triggered()), SLOT(redo()));
283 mSigMux->connect(SIGNAL(redoAvailable(bool)), act, SLOT(setEnabled(bool)));
285 mActions[Cut] = act = new QAction(
286 QIcon::fromTheme("edit-cut"), tr("Cu&t"), this);
287 act->setShortcut(tr("Ctrl+X", "Cut"));
288 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
289 act->setStatusTip(tr("Cut text to clipboard"));
290 mSigMux->connect(act, SIGNAL(triggered()), SLOT(cut()));
291 mSigMux->connect(SIGNAL(copyAvailable(bool)), act, SLOT(setEnabled(bool)));
293 mActions[Copy] = act = new QAction(
294 QIcon::fromTheme("edit-copy"), tr("&Copy"), this);
295 act->setShortcut(tr("Ctrl+C", "Copy"));
296 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
297 act->setStatusTip(tr("Copy text to clipboard"));
298 mSigMux->connect(act, SIGNAL(triggered()), SLOT(copy()));
299 mSigMux->connect(SIGNAL(copyAvailable(bool)), act, SLOT(setEnabled(bool)));
301 mActions[Paste] = act = new QAction(
302 QIcon::fromTheme("edit-paste"), tr("&Paste"), this);
303 act->setShortcut(tr("Ctrl+V", "Paste"));
304 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
305 act->setStatusTip(tr("Paste text from clipboard"));
306 mSigMux->connect(act, SIGNAL(triggered()), SLOT(paste()));
308 mActions[IndentLineOrRegion] = act = new QAction(
309 QIcon::fromTheme("format-indent-line"), tr("Indent Line or Region"), this);
310 act->setStatusTip(tr("Indent Line or Region"));
311 mSigMux->connect(act, SIGNAL(triggered()), SLOT(indent()));
313 mActions[TriggerAutoCompletion] = act = new QAction(tr("Trigger Autocompletion"), this);
314 act->setStatusTip(tr("Suggest possible completions of text at cursor"));
315 act->setShortcut(tr("Ctrl+Space", "Trigger Autocompletion"));
316 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
317 mSigMux->connect(act, SIGNAL(triggered()), SLOT(triggerAutoCompletion()));
319 mActions[TriggerMethodCallAid] = act = new QAction(tr("Trigger Method Call Aid"), this);
320 act->setStatusTip(tr("Show arguments for currently typed method call"));
321 act->setShortcut(tr("Ctrl+Shift+Space", "Trigger Method Call Aid"));
322 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
323 mSigMux->connect(act, SIGNAL(triggered()), SLOT(triggerMethodCallAid()));
325 mActions[ToggleComment] = act = new QAction(
326 QIcon::fromTheme("edit-comment"), tr("Toggle &Comment"), this);
327 act->setShortcut(tr("Ctrl+/", "Toggle Comment"));
328 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
329 act->setStatusTip(tr("Toggle Comment"));
330 mSigMux->connect(act, SIGNAL(triggered()), SLOT(toggleComment()));
332 mActions[ToggleOverwriteMode] = act = new QAction(
333 QIcon::fromTheme("edit-overwrite"), tr("Toggle &Overwrite Mode"), this);
334 act->setShortcut(tr("Insert", "Toggle Overwrite Mode"));
335 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
336 mSigMux->connect(act, SIGNAL(triggered()), SLOT(toggleOverwriteMode()));
338 mActions[CopyLineUp] = act = new QAction(
339 QIcon::fromTheme("edit-copylineup"), tr("Copy Line Up"), this);
340 act->setShortcut(tr("Ctrl+Alt+Up", "Copy Line Up"));
341 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
342 mSigMux->connect(act, SIGNAL(triggered()), SLOT(copyLineUp()));
344 mActions[CopyLineDown] = act = new QAction(
345 QIcon::fromTheme("edit-copylinedown"), tr("Copy Line Down"), this);
346 act->setShortcut(tr("Ctrl+Alt+Down", "Copy Line Up"));
347 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
348 mSigMux->connect(act, SIGNAL(triggered()), SLOT(copyLineDown()));
350 mActions[MoveLineUp] = act = new QAction(
351 QIcon::fromTheme("edit-movelineup"), tr("move Line Up"), this);
352 act->setShortcut(tr("Ctrl+Shift+Up", "Move Line Up"));
353 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
354 mSigMux->connect(act, SIGNAL(triggered()), SLOT(moveLineUp()));
356 mActions[MoveLineDown] = act = new QAction(
357 QIcon::fromTheme("edit-movelinedown"), tr("Move Line Down"), this);
358 act->setShortcut(tr("Ctrl+Shift+Down", "Move Line Up"));
359 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
360 mSigMux->connect(act, SIGNAL(triggered()), SLOT(moveLineDown()));
362 mActions[GotoPreviousBlock] = act = new QAction(
363 QIcon::fromTheme("edit-gotopreviousblock"), tr("Go to Previous Block"), this);
364 act->setShortcut(tr("Ctrl+[", "Go to Previous Block"));
365 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
366 mSigMux->connect(act, SIGNAL(triggered()), SLOT(gotoPreviousBlock()));
368 mActions[GotoNextBlock] = act = new QAction(
369 QIcon::fromTheme("edit-gotonextblock"), tr("Go to Next Block"), this);
370 act->setShortcut(tr("Ctrl+]", "Go to Next Block"));
371 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
372 mSigMux->connect(act, SIGNAL(triggered()), SLOT(gotoNextBlock()));
374 mActions[GotoPreviousRegion] = act = new QAction(
375 QIcon::fromTheme("edit-gotopreviousregion"), tr("Go to Previous Region"), this);
376 act->setShortcut(tr("Alt+[", "Go to Previous Region"));
377 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
378 mSigMux->connect(act, SIGNAL(triggered()), SLOT(gotoPreviousRegion()));
380 mActions[GotoNextRegion] = act = new QAction(
381 QIcon::fromTheme("edit-gotonextregion"), tr("Go to Next Region"), this);
382 act->setShortcut(tr("Alt+]", "Go to Next Region"));
383 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
384 mSigMux->connect(act, SIGNAL(triggered()), SLOT(gotoNextRegion()));
386 mActions[GotoPreviousEmptyLine] = act = new QAction( tr("Go to Previous Empty Line"), this);
387 act->setShortcut(tr("Ctrl+Up", "Go to Previous Empty Line"));
388 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
389 mSigMux->connect(act, SIGNAL(triggered()), SLOT(gotoPreviousEmptyLine()));
391 mActions[GotoNextEmptyLine] = act = new QAction( tr("Go to Next Empty Line"), this);
392 act->setShortcut(tr("Ctrl+Down", "Go to Next Empty Line"));
393 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
394 mSigMux->connect(act, SIGNAL(triggered()), SLOT(gotoNextEmptyLine()));
396 mActions[SelectRegion] = act = new QAction( tr("Select Region"), this);
397 act->setShortcut(tr("Ctrl+Shift+R", "Select Region"));
398 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
399 mSigMux->connect(act, SIGNAL(triggered()), SLOT(selectCurrentRegion()));
401 // View
403 mActions[EnlargeFont] = act = new QAction(
404 QIcon::fromTheme("zoom-in"), tr("&Enlarge Font"), this);
405 act->setShortcut(tr("Ctrl++", "Enlarge font"));
406 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
407 act->setStatusTip(tr("Increase displayed font size"));
408 mSigMux->connect(act, SIGNAL(triggered()), SLOT(zoomIn()));
410 mActions[ShrinkFont] = act = new QAction(
411 QIcon::fromTheme("zoom-out"), tr("&Shrink Font"), this);
412 act->setShortcut( tr("Ctrl+-", "Shrink font"));
413 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
414 act->setStatusTip(tr("Decrease displayed font size"));
415 mSigMux->connect(act, SIGNAL(triggered()), SLOT(zoomOut()));
417 mActions[ResetFontSize] = act = new QAction(
418 QIcon::fromTheme("zoom-reset"), tr("&Reset Font Size"), this);
419 act->setShortcut( tr("Ctrl+0", "Reset font"));
420 act->setStatusTip(tr("Reset displayed font size"));
421 mSigMux->connect(act, SIGNAL(triggered()), SLOT(resetFontSize()));
423 mActions[ShowWhitespace] = act = new QAction(tr("Show Spaces and Tabs"), this);
424 act->setCheckable(true);
425 mSigMux->connect(act, SIGNAL(triggered(bool)), SLOT(setShowWhitespace(bool)));
427 mActions[NextDocument] = act = new QAction(tr("Next Document"), this);
428 act->setShortcut( tr("Alt+Right", "Next Document"));
429 connect(act, SIGNAL(triggered()), this, SLOT(showNextDocument()));
431 mActions[PreviousDocument] = act = new QAction(tr("Previous Document"), this);
432 act->setShortcut( tr("Alt+Left", "Next Document"));
433 connect(act, SIGNAL(triggered()), this, SLOT(showPreviousDocument()));
435 mActions[SwitchDocument] = act = new QAction(tr("Switch Document"), this);
436 act->setShortcut( tr("Ctrl+Tab", "Switch Document"));
437 connect(act, SIGNAL(triggered()), this, SLOT(switchDocument()));
439 mActions[SplitHorizontally] = act = new QAction(tr("Split To Right"), this);
440 act->setShortcut( tr("Ctrl+P, 3", "Split To Right"));
441 connect(act, SIGNAL(triggered()), this, SLOT(splitHorizontally()));
443 mActions[SplitVertically] = act = new QAction(tr("Split To Bottom"), this);
444 act->setShortcut( tr("Ctrl+P, 2", "Split To Bottom"));
445 connect(act, SIGNAL(triggered()), this, SLOT(splitVertically()));
447 mActions[RemoveCurrentSplit] = act = new QAction(tr("Remove Current Split"), this);
448 act->setShortcut( tr("Ctrl+P, 1", "Remove Current Split"));
449 connect(act, SIGNAL(triggered()), this, SLOT(removeCurrentSplit()));
451 mActions[RemoveAllSplits] = act = new QAction(tr("Remove All Splits"), this);
452 act->setShortcut( tr("Ctrl+P, 0", "Remove All Splits"));
453 connect(act, SIGNAL(triggered()), this, SLOT(removeAllSplits()));
455 // Language
457 mActions[EvaluateCurrentDocument] = act = new QAction(
458 QIcon::fromTheme("media-playback-start"), tr("Evaluate &File"), this);
459 act->setStatusTip(tr("Evaluate current File"));
460 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
461 connect(act, SIGNAL(triggered()), this, SLOT(evaluateDocument()));
463 mActions[EvaluateRegion] = act = new QAction(
464 QIcon::fromTheme("media-playback-start"), tr("&Evaluate Selection, Line or Region"), this);
465 act->setShortcut(tr("Ctrl+Return", "Evaluate region"));
466 act->setStatusTip(tr("Evaluate current region"));
467 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
468 connect(act, SIGNAL(triggered()), this, SLOT(evaluateRegion()));
470 mActions[EvaluateLine] = act = new QAction(
471 QIcon::fromTheme("media-playback-startline"), tr("&Evaluate Line"), this);
472 act->setShortcut(tr("Shift+Ctrl+Return", "Evaluate line"));
473 act->setStatusTip(tr("Evaluate current line"));
474 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
475 connect(act, SIGNAL(triggered()), this, SLOT(evaluateLine()));
477 settings->endGroup(); // IDE/shortcuts
479 for (int i = 0; i < ActionRoleCount; ++i)
480 settings->addAction( mActions[i] );
482 // These actions are not added to any menu, so they have to be added
483 // at least to this widget, in order for the shortcuts to always respond:
484 addAction(mActions[TriggerAutoCompletion]);
485 addAction(mActions[TriggerMethodCallAid]);
486 addAction(mActions[SwitchDocument]);
488 // These actions have to be added because to the widget because they have
489 // Qt::WidgetWithChildrenShortcut context:
490 addAction(mActions[Cut]);
491 addAction(mActions[Copy]);
492 addAction(mActions[Paste]);
493 addAction(mActions[EnlargeFont]);
494 addAction(mActions[ShrinkFont]);
495 addAction(mActions[EvaluateCurrentDocument]);
496 addAction(mActions[EvaluateRegion]);
497 addAction(mActions[EvaluateLine]);
498 addAction(mActions[ToggleComment]);
499 addAction(mActions[ToggleOverwriteMode]);
500 addAction(mActions[CopyLineUp]);
501 addAction(mActions[CopyLineDown]);
502 addAction(mActions[MoveLineUp]);
503 addAction(mActions[MoveLineDown]);
504 addAction(mActions[GotoPreviousBlock]);
505 addAction(mActions[GotoNextBlock]);
506 addAction(mActions[GotoPreviousRegion]);
507 addAction(mActions[GotoNextRegion]);
508 addAction(mActions[GotoPreviousEmptyLine]);
509 addAction(mActions[GotoNextEmptyLine]);
510 addAction(mActions[SelectRegion]);
513 void MultiEditor::updateActions()
515 ScCodeEditor *editor = currentEditor();
516 QTextDocument *doc = editor ? editor->textDocument() : 0;
518 mActions[Undo]->setEnabled( doc && doc->isUndoAvailable() );
519 mActions[Redo]->setEnabled( doc && doc->isRedoAvailable() );
520 mActions[Copy]->setEnabled( editor && editor->textCursor().hasSelection() );
521 mActions[Cut]->setEnabled( mActions[Copy]->isEnabled() );
522 mActions[Paste]->setEnabled( editor );
523 mActions[ToggleComment]->setEnabled( editor );
524 mActions[ToggleOverwriteMode]->setEnabled( editor );
525 mActions[CopyLineUp]->setEnabled( editor );
526 mActions[CopyLineDown]->setEnabled( editor );
527 mActions[MoveLineUp]->setEnabled( editor );
528 mActions[MoveLineDown]->setEnabled( editor );
529 mActions[GotoPreviousBlock]->setEnabled( editor );
530 mActions[GotoNextBlock]->setEnabled( editor );
531 mActions[GotoPreviousRegion]->setEnabled( editor );
532 mActions[GotoNextRegion]->setEnabled( editor );
533 mActions[GotoPreviousEmptyLine]->setEnabled( editor );
534 mActions[GotoNextEmptyLine]->setEnabled( editor );
535 mActions[SelectRegion]->setEnabled( editor );
537 mActions[IndentLineOrRegion]->setEnabled( editor );
538 mActions[EnlargeFont]->setEnabled( editor );
539 mActions[ShrinkFont]->setEnabled( editor );
540 mActions[EvaluateCurrentDocument]->setEnabled( editor );
541 mActions[EvaluateRegion]->setEnabled( editor );
542 mActions[EvaluateLine]->setEnabled( editor );
543 mActions[ResetFontSize]->setEnabled( editor );
544 mActions[ShowWhitespace]->setEnabled( editor );
545 mActions[ShowWhitespace]->setChecked( editor && editor->showWhitespace() );
548 void MultiEditor::applySettings( Settings::Manager *s )
550 s->beginGroup("IDE/editor");
551 mStepForwardEvaluation = s->value("stepForwardEvaluation").toBool();
552 s->endGroup();
555 static QVariantList saveBoxState( CodeEditorBox *box, const QList<Document*> & documentList )
557 // save editors in reverse order - first one is last shown.
558 QVariantList boxData;
559 int idx = box->history().count();
560 while(idx--) {
561 ScCodeEditor *editor = box->history()[idx];
562 if (!editor->document()->filePath().isEmpty()) {
563 int documentIndex = documentList.indexOf( editor->document() );
564 Q_ASSERT(documentIndex >= 0);
565 QVariantMap editorData;
566 editorData.insert("documentIndex", documentIndex);
567 editorData.insert("position", editor->textCursor().position());
568 boxData.append( editorData );
571 return boxData;
574 static QVariantMap saveSplitterState( QSplitter *splitter, const QList<Document*> & documentList )
576 QVariantMap splitterData;
578 splitterData.insert( "state", splitter->saveState().toBase64() );
580 QVariantList childrenData;
582 int childCount = splitter->count();
583 for (int idx = 0; idx < childCount; idx++) {
584 QWidget *child = splitter->widget(idx);
586 CodeEditorBox *box = qobject_cast<CodeEditorBox*>(child);
587 if (box) {
588 QVariantList boxData = saveBoxState(box, documentList);
589 childrenData.append( QVariant(boxData) );
590 continue;
593 QSplitter *childSplitter = qobject_cast<QSplitter*>(child);
594 if (childSplitter) {
595 QVariantMap childSplitterData = saveSplitterState(childSplitter, documentList);
596 childrenData.append( QVariant(childSplitterData) );
600 splitterData.insert( "elements", childrenData );
602 return splitterData;
605 void MultiEditor::saveSession( Session *session )
607 QList<Document*> documentList;
609 QVariantList tabsData;
610 int tabCount = mTabs->count();
611 for (int tabIdx = 0; tabIdx < tabCount; ++tabIdx) {
612 Document *doc = documentForTab(tabIdx);
613 documentList << doc;
614 tabsData << doc->filePath();
617 session->setValue( "documents", QVariant::fromValue(tabsData) );
619 session->remove( "editors" );
620 session->setValue( "editors", saveSplitterState(mSplitter, documentList) );
623 void MultiEditor::loadBoxState( CodeEditorBox *box,
624 const QVariantList & data, const QList<Document*> & documentList )
626 int docCount = documentList.count();
627 foreach( QVariant docVar, data )
629 QVariantMap docData = docVar.value<QVariantMap>();
630 int docIndex = docData.value("documentIndex").toInt();
631 int docPos = docData.value("position").toInt();
632 if (docIndex >= 0 && docIndex < docCount)
633 box->setDocument( documentList[docIndex], docPos );
637 void MultiEditor::loadSplitterState( QSplitter *splitter,
638 const QVariantMap & data, const QList<Document*> & documentList )
640 QByteArray state = QByteArray::fromBase64( data.value("state").value<QByteArray>() );
642 QVariantList childrenData = data.value("elements").value<QVariantList>();
643 foreach (const QVariant & childVar, childrenData) {
644 if (childVar.type() == QVariant::List) {
645 CodeEditorBox *childBox = newBox();
646 splitter->addWidget(childBox);
647 QVariantList childBoxData = childVar.value<QVariantList>();
648 loadBoxState( childBox, childBoxData, documentList );
650 else if (childVar.type() == QVariant::Map) {
651 QSplitter *childSplitter = new QSplitter;
652 splitter->addWidget(childSplitter);
653 QVariantMap childSplitterData = childVar.value<QVariantMap>();
654 loadSplitterState( childSplitter, childSplitterData, documentList );
658 if (!splitter->restoreState(state))
659 qWarning("MultiEditor: could not restore splitter state!");
662 void MultiEditor::switchSession( Session *session )
664 ///// Going offline...
666 breakSignalConnections();
668 DocumentManager *docManager = Main::documentManager();
670 QList<Document*> documentList = docManager->documents();
672 // close all docs
673 foreach (Document *doc, documentList)
674 docManager->close(doc);
676 // remove all tabs
677 while (mTabs->count())
678 mTabs->removeTab(0);
680 // remove all editors
681 delete mSplitter;
683 documentList.clear();
685 mSplitter = new MultiSplitter();
687 CodeEditorBox *firstBox = 0;
689 if (session)
691 // open documents saved in the session
692 QVariantList docDataList = session->value("documents").value<QVariantList>();
693 foreach( const QVariant & docData, docDataList ) {
694 QString filePath = docData.toString();
695 Document * doc = docManager->open(filePath, -1, 0, false);
696 documentList << doc;
699 // restore tabs
700 foreach ( Document * doc, documentList ) {
701 if (!doc)
702 continue;
703 int newTabIndex = mTabs->addTab( doc->title() );
704 mTabs->setTabData( newTabIndex, QVariant::fromValue<Document*>(doc) );
707 // restore editors
708 if (session->contains("editors")) {
709 QVariantMap splitterData = session->value("editors").value<QVariantMap>();
710 loadSplitterState( mSplitter, splitterData, documentList );
712 if (mSplitter->count()) {
713 firstBox = mSplitter->findChild<CodeEditorBox>();
714 if (!firstBox) {
715 qWarning("Session seems to contain invalid editor split data!");
716 delete mSplitter;
717 mSplitter = new MultiSplitter();
723 if (!firstBox) {
724 // Restoring the session didn't result in any editor box, so create one:
725 firstBox = newBox();
726 mSplitter->addWidget( firstBox );
729 layout()->addWidget(mSplitter);
731 makeSignalConnections();
733 ///// Back online.
735 mCurrentEditorBox = 0; // ensure complete update
736 setCurrentBox( firstBox );
738 if (!session)
739 // create a document on new session
740 docManager->create();
742 firstBox->setFocus(Qt::OtherFocusReason); // ensure focus
745 void MultiEditor::setCurrent( Document *doc )
747 int tabIdx = tabForDocument(doc);
748 if (tabIdx != -1)
749 mTabs->setCurrentIndex(tabIdx);
752 void MultiEditor::showNextDocument()
754 int currentIndex = mTabs->currentIndex();
755 mTabs->setCurrentIndex( qMin(currentIndex + 1, mTabs->count() - 1) );
758 void MultiEditor::showPreviousDocument()
760 int currentIndex = mTabs->currentIndex();
761 mTabs->setCurrentIndex( qMax(0, currentIndex - 1) );
764 void MultiEditor::switchDocument()
766 CodeEditorBox *box = currentBox();
768 DocumentSelectPopUp * popup = new DocumentSelectPopUp(box->history(), this);
770 QRect popupRect(0,0,300,200);
771 popupRect.moveCenter(rect().center());
772 popup->resize(popupRect.size());
773 QPoint globalPosition = mapToGlobal(popupRect.topLeft());
775 Document * selectedDocument = popup->exec(globalPosition);
777 if (selectedDocument)
778 box->setDocument(selectedDocument);
781 void MultiEditor::onOpen( Document *doc, int initialCursorPosition, int selectionLength )
783 QTextDocument *tdoc = doc->textDocument();
785 QIcon icon;
786 if(tdoc->isModified())
787 icon = mDocModifiedIcon;
789 int newTabIndex = mTabs->addTab( icon, doc->title() );
790 mTabs->setTabData( newTabIndex, QVariant::fromValue<Document*>(doc) );
792 currentBox()->setDocument(doc, initialCursorPosition, selectionLength);
793 currentBox()->setFocus(Qt::OtherFocusReason);
796 void MultiEditor::onClose( Document *doc )
798 int tabIdx = tabForDocument(doc);
799 if (tabIdx != -1)
800 mTabs->removeTab(tabIdx);
801 // TODO: each box should switch document according to their own history
804 void MultiEditor::show( Document *doc, int pos, int selectionLength )
806 currentBox()->setDocument(doc, pos, selectionLength);
807 currentBox()->setFocus(Qt::OtherFocusReason);
810 void MultiEditor::update( Document *doc )
812 int tabIdx = tabForDocument(doc);
813 if (tabIdx != -1)
814 mTabs->setTabText(tabIdx, doc->title());
817 void MultiEditor::onCloseRequest( int index )
819 Document *doc = documentForTab(index);
820 if (doc)
821 MainWindow::close(doc);
824 void MultiEditor::onCurrentTabChanged( int index )
826 if (index == -1)
827 return;
829 Document *doc = documentForTab(index);
830 if (!doc)
831 return;
833 CodeEditorBox *curBox = currentBox();
834 curBox->setDocument(doc);
835 curBox->setFocus(Qt::OtherFocusReason);
838 void MultiEditor::onCurrentEditorChanged(ScCodeEditor *editor)
840 setCurrentEditor(editor);
843 void MultiEditor::onBoxActivated(CodeEditorBox *box)
845 setCurrentBox(box);
848 void MultiEditor::onModificationChanged( bool modified )
850 Q_ASSERT(currentEditor());
852 int tabIdx = tabForDocument( currentEditor()->document() );
853 if (tabIdx == -1)
854 return;
856 QIcon icon;
857 if(modified)
858 icon = mDocModifiedIcon;
859 mTabs->setTabIcon( tabIdx, icon );
862 void MultiEditor::evaluateRegion()
864 ScCodeEditor * editor = currentEditor();
865 if (!editor)
866 return;
868 QString text;
870 // Try current selection
871 QTextCursor cursor = editor->textCursor();
872 if (cursor.hasSelection())
873 text = cursor.selectedText();
874 else {
875 // If no selection, try current region
876 cursor = editor->currentRegion();
877 if (!cursor.isNull()) {
878 text = cursor.selectedText();
879 } else {
880 // If no current region, try current line
881 cursor = editor->textCursor();
882 text = cursor.block().text();
883 if( mStepForwardEvaluation ) {
884 QTextCursor newCursor = cursor;
885 newCursor.movePosition(QTextCursor::NextBlock);
886 editor->setTextCursor(newCursor);
888 // Adjust cursor for code blinking:
889 cursor.movePosition(QTextCursor::StartOfBlock);
890 cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
894 if (text.isEmpty())
895 return;
897 text.replace( QChar( 0x2029 ), QChar( '\n' ) );
899 Main::evaluateCode(text);
901 editor->blinkCode( cursor );
904 void MultiEditor::evaluateLine()
906 ScCodeEditor * editor = currentEditor();
907 if (!editor)
908 return;
910 QString text;
912 // Try current selection
913 QTextCursor cursor = editor->textCursor();
914 cursor.select(QTextCursor::LineUnderCursor);
915 text = cursor.selectedText();
917 if( mStepForwardEvaluation ) {
918 QTextCursor newCursor = cursor;
919 newCursor.movePosition(QTextCursor::NextBlock);
920 editor->setTextCursor(newCursor);
923 if (text.isEmpty())
924 return;
926 // Adjust cursor for code blinking:
927 cursor.movePosition(QTextCursor::StartOfBlock);
928 cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
930 text.replace( QChar( 0x2029 ), QChar( '\n' ) );
932 Main::evaluateCode(text);
934 editor->blinkCode( cursor );
937 void MultiEditor::evaluateDocument()
939 ScCodeEditor * editor = currentEditor();
940 if (!editor)
941 return;
943 QString documentText = editor->textDocument()->toPlainText();
944 Main::evaluateCode(documentText);
947 Document * MultiEditor::documentForTab( int index )
949 return mTabs->tabData(index).value<Document*>();
952 int MultiEditor::tabForDocument( Document * doc )
954 int tabCount = mTabs->count();
955 for (int idx = 0; idx < tabCount; ++idx) {
956 Document *tabDoc = documentForTab(idx);
957 if (tabDoc && tabDoc == doc)
958 return idx;
960 return -1;
963 CodeEditorBox *MultiEditor::newBox()
965 CodeEditorBox *box = new CodeEditorBox();
967 connect(box, SIGNAL(activated(CodeEditorBox*)),
968 this, SLOT(onBoxActivated(CodeEditorBox*)));
970 return box;
973 void MultiEditor::setCurrentBox( CodeEditorBox * box )
975 if (mCurrentEditorBox == box)
976 return;
978 mCurrentEditorBox = box;
979 mBoxSigMux->setCurrentObject(box);
980 setCurrentEditor( box->currentEditor() );
982 mCurrentEditorBox->setActive();
985 void MultiEditor::setCurrentEditor( ScCodeEditor * editor )
987 if (editor) {
988 int tabIndex = tabForDocument(editor->document());
989 if (tabIndex != -1)
990 mTabs->setCurrentIndex(tabIndex);
993 mSigMux->setCurrentObject(editor);
994 updateActions();
996 Document *currentDocument = editor ? editor->document() : 0;
997 Main::scProcess()->setActiveDocument(currentDocument);
998 emit currentDocumentChanged(currentDocument);
1001 ScCodeEditor *MultiEditor::currentEditor()
1003 return currentBox()->currentEditor();
1006 void MultiEditor::split( Qt::Orientation splitDirection )
1008 CodeEditorBox *box = newBox();
1009 CodeEditorBox *curBox = currentBox();
1010 ScCodeEditor *curEditor = curBox->currentEditor();
1012 if (curEditor)
1013 box->setDocument(curEditor->document(), curEditor->textCursor().position());
1015 mSplitter->insertWidget(box, curBox, splitDirection);
1016 box->setFocus( Qt::OtherFocusReason );
1019 void MultiEditor::removeCurrentSplit()
1021 int boxCount = mSplitter->findChildren<CodeEditorBox*>().count();
1022 if (boxCount < 2)
1023 // Do not allow removing the one and only box.
1024 return;
1026 CodeEditorBox *box = currentBox();
1027 mSplitter->removeWidget(box);
1029 // switch current box to first box found:
1030 box = mSplitter->findChild<CodeEditorBox>();
1031 Q_ASSERT(box);
1032 setCurrentBox(box);
1033 box->setFocus( Qt::OtherFocusReason );
1036 void MultiEditor::removeAllSplits()
1038 CodeEditorBox *box = currentBox();
1039 Q_ASSERT(box);
1040 Q_ASSERT(mSplitter->count());
1041 if (mSplitter->count() == 1 && mSplitter->widget(0) == box)
1042 // Nothing to do.
1043 return;
1045 MultiSplitter *newSplitter = new MultiSplitter;
1046 newSplitter->addWidget(box);
1048 delete mSplitter;
1049 mSplitter = newSplitter;
1050 layout()->addWidget(newSplitter);
1052 box->setFocus( Qt::OtherFocusReason );
1055 } // namespace ScIDE