sc ide: make several shortcuts local to code editor widget
[supercollider.git] / editors / sc-ide / widgets / multi_editor.cpp
blob48ac89a87c92bdb3d5837374070563163ea51e65
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/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 <QStyle>
38 #include <QHBoxLayout>
39 #include <QVBoxLayout>
40 #include <QShortcut>
41 #include <QMenu>
42 #include <QDebug>
44 #include <QDialog>
45 #include <QFileInfo>
46 #include <QHeaderView>
47 #include <QListView>
48 #include <QTreeWidget>
49 #include <QStandardItemModel>
52 namespace ScIDE {
54 class DocumentSelectPopUp : public QDialog
56 public:
57 explicit 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 void keyReleaseEvent (QKeyEvent * ke)
93 // adapted from qtcreator
94 if (ke->modifiers() == 0
95 /*HACK this is to overcome some event inconsistencies between platforms*/
96 || (ke->modifiers() == Qt::AltModifier
97 && (ke->key() == Qt::Key_Alt || ke->key() == -1))) {
98 ke->accept();
99 accept();
101 QDialog::keyPressEvent(ke);
104 void keyPressEvent(QKeyEvent * ke)
106 switch (ke->key()) {
107 case Qt::Key_Tab: {
108 int row = mListView->currentIndex().row() + 1;
109 if (!mModel->hasIndex(row, 0))
110 row = 0;
112 QModelIndex nextIndex = mModel->index(row, 0);
113 mListView->setCurrentIndex(nextIndex);
114 ke->accept();
115 return;
118 case Qt::Key_Backtab: {
119 int row = mListView->currentIndex().row() - 1;
120 if (!mModel->hasIndex(row, 0))
121 row = mModel->rowCount() - 1;
123 QModelIndex nextIndex = mModel->index(row, 0);
124 mListView->setCurrentIndex(nextIndex);
125 ke->accept();
126 return;
129 case Qt::Key_Escape:
130 reject();
131 return;
133 default:
137 QDialog::keyPressEvent(ke);
140 Document * currentDocument()
142 QStandardItem * currentItem = mModel->itemFromIndex(mListView->currentIndex());
144 return currentItem ? currentItem->data().value<Document*>()
145 : NULL;
148 void populateModel( const CodeEditorBox::History & history )
150 QList<Document*> displayDocuments;
151 foreach(CodeEditor *editor, history)
152 displayDocuments << editor->document();
154 QList<Document*> managerDocuments = Main::instance()->documentManager()->documents();
155 foreach(Document *document, managerDocuments)
156 if (!displayDocuments.contains(document))
157 displayDocuments << document;
159 foreach (Document * document, displayDocuments) {
160 QStandardItem * item = new QStandardItem(document->title());
161 item->setData(QVariant::fromValue(document));
162 mModel->appendRow(item);
166 QListView *mListView;
167 QStandardItemModel *mModel;
170 MultiEditor::MultiEditor( Main *main, QWidget * parent ) :
171 QWidget(parent),
172 mDocManager(main->documentManager()),
173 mSigMux(new SignalMultiplexer(this)),
174 mBoxSigMux(new SignalMultiplexer(this)),
175 mDocModifiedIcon( QApplication::style()->standardIcon(QStyle::SP_DialogSaveButton) )
177 mTabs = new QTabBar;
178 mTabs->setDocumentMode(true);
179 mTabs->setTabsClosable(true);
180 mTabs->setMovable(true);
181 mTabs->setUsesScrollButtons(true);
182 mTabs->setDrawBase(false);
184 CodeEditorBox *defaultBox = newBox();
186 mSplitter = new MultiSplitter();
187 mSplitter->addWidget(defaultBox);
189 QVBoxLayout *l = new QVBoxLayout;
190 l->setContentsMargins(0,0,0,0);
191 l->setSpacing(0);
192 l->addWidget(mTabs);
193 l->addWidget(mSplitter);
194 setLayout(l);
196 connect(mDocManager, SIGNAL(opened(Document*, int)),
197 this, SLOT(onOpen(Document*, int)));
198 connect(mDocManager, SIGNAL(closed(Document*)),
199 this, SLOT(onClose(Document*)));
200 connect(mDocManager, SIGNAL(saved(Document*)),
201 this, SLOT(update(Document*)));
202 connect(mDocManager, SIGNAL(showRequest(Document*,int)),
203 this, SLOT(show(Document*,int))),
205 connect(mTabs, SIGNAL(currentChanged(int)),
206 this, SLOT(onCurrentTabChanged(int)));
207 connect(mTabs, SIGNAL(tabCloseRequested(int)),
208 this, SLOT(onCloseRequest(int)));
210 mSigMux->connect(SIGNAL(modificationChanged(bool)),
211 this, SLOT(onModificationChanged(bool)));
213 mBoxSigMux->connect(SIGNAL(currentChanged(CodeEditor*)),
214 this, SLOT(onCurrentEditorChanged(CodeEditor*)));
216 connect(this, SIGNAL(currentDocumentChanged(Document*)), mDocManager, SLOT(activeDocumentChanged(Document*)));
218 createActions();
220 setCurrentBox( defaultBox ); // will updateActions();
222 applySettings(main->settings());
225 void MultiEditor::createActions()
227 Settings::Manager *settings = Main::instance()->settings();
228 settings->beginGroup("IDE/shortcuts");
230 QAction * act;
232 // Edit
234 mActions[Undo] = act = new QAction(
235 QIcon::fromTheme("edit-undo"), tr("&Undo"), this);
236 act->setShortcut(tr("Ctrl+Z", "Undo"));
237 act->setStatusTip(tr("Undo last editing action"));
238 mSigMux->connect(act, SIGNAL(triggered()), SLOT(undo()));
239 mSigMux->connect(SIGNAL(undoAvailable(bool)), act, SLOT(setEnabled(bool)));
241 mActions[Redo] = act = new QAction(
242 QIcon::fromTheme("edit-redo"), tr("Re&do"), this);
243 act->setShortcut(tr("Ctrl+Shift+Z", "Redo"));
244 act->setStatusTip(tr("Redo next editing action"));
245 mSigMux->connect(act, SIGNAL(triggered()), SLOT(redo()));
246 mSigMux->connect(SIGNAL(redoAvailable(bool)), act, SLOT(setEnabled(bool)));
248 mActions[Cut] = act = new QAction(
249 QIcon::fromTheme("edit-cut"), tr("Cu&t"), this);
250 act->setShortcut(tr("Ctrl+X", "Cut"));
251 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
252 act->setStatusTip(tr("Cut text to clipboard"));
253 mSigMux->connect(act, SIGNAL(triggered()), SLOT(cut()));
254 mSigMux->connect(SIGNAL(copyAvailable(bool)), act, SLOT(setEnabled(bool)));
256 mActions[Copy] = act = new QAction(
257 QIcon::fromTheme("edit-copy"), tr("&Copy"), this);
258 act->setShortcut(tr("Ctrl+C", "Copy"));
259 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
260 act->setStatusTip(tr("Copy text to clipboard"));
261 mSigMux->connect(act, SIGNAL(triggered()), SLOT(copy()));
262 mSigMux->connect(SIGNAL(copyAvailable(bool)), act, SLOT(setEnabled(bool)));
264 mActions[Paste] = act = new QAction(
265 QIcon::fromTheme("edit-paste"), tr("&Paste"), this);
266 act->setShortcut(tr("Ctrl+V", "Paste"));
267 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
268 act->setStatusTip(tr("Paste text from clipboard"));
269 mSigMux->connect(act, SIGNAL(triggered()), SLOT(paste()));
271 mActions[IndentLineOrRegion] = act = new QAction(
272 QIcon::fromTheme("format-indent-line"), tr("Indent Line or Region"), this);
273 act->setStatusTip(tr("Indent Line or Region"));
274 mSigMux->connect(act, SIGNAL(triggered()), SLOT(indent()));
276 mActions[TriggerAutoCompletion] = act = new QAction(tr("Trigger Autocompletion"), this);
277 act->setStatusTip(tr("Suggest possible completions of text at cursor"));
278 act->setShortcut(tr("Ctrl+Space", "Trigger Autocompletion"));
279 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
280 mSigMux->connect(act, SIGNAL(triggered()), SLOT(triggerAutoCompletion()));
282 mActions[TriggerMethodCallAid] = act = new QAction(tr("Trigger Method Call Aid"), this);
283 act->setStatusTip(tr("Show arguments for currently typed method call"));
284 act->setShortcut(tr("Alt+Space", "Trigger Method Call Aid"));
285 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
286 mSigMux->connect(act, SIGNAL(triggered()), SLOT(triggerMethodCallAid()));
288 mActions[ToggleComment] = act = new QAction(
289 QIcon::fromTheme("edit-comment"), tr("Toggle &Comment"), this);
290 act->setShortcut(tr("Ctrl+/", "Toggle Comment"));
291 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
292 act->setStatusTip(tr("Toggle Comment"));
293 mSigMux->connect(act, SIGNAL(triggered()), SLOT(toggleComment()));
295 mActions[ToggleOverwriteMode] = act = new QAction(
296 QIcon::fromTheme("edit-overwrite"), tr("Toggle &Overwrite Mode"), this);
297 act->setShortcut(tr("Insert", "Toggle Overwrite Mode"));
298 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
299 mSigMux->connect(act, SIGNAL(triggered()), SLOT(toggleOverwriteMode()));
301 mActions[CopyLineUp] = act = new QAction(
302 QIcon::fromTheme("edit-copylineup"), tr("Copy Line Up"), this);
303 act->setShortcut(tr("Ctrl+Alt+Up", "Copy Line Up"));
304 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
305 mSigMux->connect(act, SIGNAL(triggered()), SLOT(copyLineUp()));
307 mActions[CopyLineDown] = act = new QAction(
308 QIcon::fromTheme("edit-copylinedown"), tr("Copy Line Down"), this);
309 act->setShortcut(tr("Ctrl+Alt+Down", "Copy Line Up"));
310 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
311 mSigMux->connect(act, SIGNAL(triggered()), SLOT(copyLineDown()));
313 mActions[MoveLineUp] = act = new QAction(
314 QIcon::fromTheme("edit-movelineup"), tr("move Line Up"), this);
315 act->setShortcut(tr("Ctrl+Shift+Up", "Move Line Up"));
316 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
317 mSigMux->connect(act, SIGNAL(triggered()), SLOT(moveLineUp()));
319 mActions[MoveLineDown] = act = new QAction(
320 QIcon::fromTheme("edit-movelinedown"), tr("Move Line Down"), this);
321 act->setShortcut(tr("Ctrl+Shift+Down", "Move Line Up"));
322 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
323 mSigMux->connect(act, SIGNAL(triggered()), SLOT(moveLineDown()));
325 mActions[GotoPreviousBlock] = act = new QAction(
326 QIcon::fromTheme("edit-gotopreviousblock"), tr("Go to Previous Block"), this);
327 act->setShortcut(tr("Ctrl+[", "Go to Previous Block"));
328 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
329 mSigMux->connect(act, SIGNAL(triggered()), SLOT(gotoPreviousBlock()));
331 mActions[GotoNextBlock] = act = new QAction(
332 QIcon::fromTheme("edit-gotonextblock"), tr("Go to Next Block"), this);
333 act->setShortcut(tr("Ctrl+]", "Go to Next Block"));
334 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
335 mSigMux->connect(act, SIGNAL(triggered()), SLOT(gotoNextBlock()));
337 mActions[GotoPreviousEmptyLine] = act = new QAction( tr("Go to Previous Empty Line"), this);
338 act->setShortcut(tr("Ctrl+Up", "Go to Previous Empty Line"));
339 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
340 mSigMux->connect(act, SIGNAL(triggered()), SLOT(gotoPreviousEmptyLine()));
342 mActions[GotoNextEmptyLine] = act = new QAction( tr("Go to Next Empty Line"), this);
343 act->setShortcut(tr("Ctrl+Down", "Go to Next Empty Line"));
344 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
345 mSigMux->connect(act, SIGNAL(triggered()), SLOT(gotoNextEmptyLine()));
347 mActions[SelectRegion] = act = new QAction( tr("Select Region"), this);
348 act->setShortcut(tr("Ctrl+Shift+R", "Select Region"));
349 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
350 mSigMux->connect(act, SIGNAL(triggered()), SLOT(selectCurrentRegion()));
352 // View
354 mActions[EnlargeFont] = act = new QAction(
355 QIcon::fromTheme("zoom-in"), tr("&Enlarge Font"), this);
356 act->setShortcut(tr("Ctrl++", "Enlarge font"));
357 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
358 act->setStatusTip(tr("Increase displayed font size"));
359 mSigMux->connect(act, SIGNAL(triggered()), SLOT(zoomIn()));
361 mActions[ShrinkFont] = act = new QAction(
362 QIcon::fromTheme("zoom-out"), tr("&Shrink Font"), this);
363 act->setShortcut( tr("Ctrl+-", "Shrink font"));
364 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
365 act->setStatusTip(tr("Decrease displayed font size"));
366 mSigMux->connect(act, SIGNAL(triggered()), SLOT(zoomOut()));
368 mActions[ResetFontSize] = act = new QAction(
369 QIcon::fromTheme("zoom-reset"), tr("&Reset Font Size"), this);
370 act->setShortcut( tr("Ctrl+0", "Reset font"));
371 act->setStatusTip(tr("Reset displayed font size"));
372 mSigMux->connect(act, SIGNAL(triggered()), SLOT(resetFontSize()));
374 mActions[ShowWhitespace] = act = new QAction(tr("Show Spaces and Tabs"), this);
375 act->setCheckable(true);
376 mSigMux->connect(act, SIGNAL(triggered(bool)), SLOT(setShowWhitespace(bool)));
378 mActions[NextDocument] = act = new QAction(tr("Next Document"), this);
379 act->setShortcut( tr("Alt+Right", "Next Document"));
380 connect(act, SIGNAL(triggered()), this, SLOT(showNextDocument()));
382 mActions[PreviousDocument] = act = new QAction(tr("Previous Document"), this);
383 act->setShortcut( tr("Alt+Left", "Next Document"));
384 connect(act, SIGNAL(triggered()), this, SLOT(showPreviousDocument()));
386 mActions[SwitchDocument] = act = new QAction(tr("Switch Document"), this);
387 act->setShortcut( tr("Ctrl+Tab", "Switch Document"));
388 connect(act, SIGNAL(triggered()), this, SLOT(switchDocument()));
390 mActions[SplitHorizontally] = act = new QAction(tr("Split To Right"), this);
391 connect(act, SIGNAL(triggered()), this, SLOT(splitHorizontally()));
393 mActions[SplitVertically] = act = new QAction(tr("Split To Bottom"), this);
394 connect(act, SIGNAL(triggered()), this, SLOT(splitVertically()));
396 mActions[RemoveCurrentSplit] = act = new QAction(tr("Remove Current Split"), this);
397 connect(act, SIGNAL(triggered()), this, SLOT(removeCurrentSplit()));
399 mActions[RemoveAllSplits] = act = new QAction(tr("Remove All Splits"), this);
400 connect(act, SIGNAL(triggered()), this, SLOT(removeAllSplits()));
402 // Language
404 mActions[EvaluateCurrentDocument] = act = new QAction(
405 QIcon::fromTheme("media-playback-start"), tr("Evaluate &File"), this);
406 act->setStatusTip(tr("Evaluate current File"));
407 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
408 connect(act, SIGNAL(triggered()), this, SLOT(evaluateDocument()));
410 mActions[EvaluateRegion] = act = new QAction(
411 QIcon::fromTheme("media-playback-start"), tr("&Evaluate Selection, Line or Region"), this);
412 act->setShortcut(tr("Ctrl+Return", "Evaluate region"));
413 act->setStatusTip(tr("Evaluate current region"));
414 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
415 connect(act, SIGNAL(triggered()), this, SLOT(evaluateRegion()));
417 mActions[EvaluateLine] = act = new QAction(
418 QIcon::fromTheme("media-playback-startline"), tr("&Evaluate Line"), this);
419 act->setShortcut(tr("Shift+Ctrl+Return", "Evaluate line"));
420 act->setStatusTip(tr("Evaluate current line"));
421 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
422 connect(act, SIGNAL(triggered()), this, SLOT(evaluateLine()));
424 mActions[OpenDefinition] = act = new QAction(tr("Open Class/Method Definition"), this);
425 act->setShortcut(tr("Ctrl+I", "Open definition of selected class or method"));
426 act->setShortcutContext(Qt::WidgetWithChildrenShortcut);
427 connect(act, SIGNAL(triggered(bool)), this, SLOT(openDefinition()));
429 settings->endGroup(); // IDE/shortcuts
431 for (int i = 0; i < ActionRoleCount; ++i)
432 settings->addAction( mActions[i] );
434 // These actions are not added to any menu, so they have to be added
435 // at least to this widget, in order for the shortcuts to always respond:
436 addAction(mActions[TriggerAutoCompletion]);
437 addAction(mActions[TriggerMethodCallAid]);
438 addAction(mActions[SwitchDocument]);
440 // These actions have to be added because to the widget because they have
441 // Qt::WidgetWithChildrenShortcut context:
442 addAction(mActions[Cut]);
443 addAction(mActions[Copy]);
444 addAction(mActions[Paste]);
445 addAction(mActions[EnlargeFont]);
446 addAction(mActions[ShrinkFont]);
447 addAction(mActions[OpenDefinition]);
448 addAction(mActions[EvaluateCurrentDocument]);
449 addAction(mActions[EvaluateRegion]);
450 addAction(mActions[EvaluateLine]);
451 addAction(mActions[ToggleComment]);
452 addAction(mActions[ToggleOverwriteMode]);
453 addAction(mActions[CopyLineUp]);
454 addAction(mActions[CopyLineDown]);
455 addAction(mActions[MoveLineUp]);
456 addAction(mActions[MoveLineDown]);
457 addAction(mActions[GotoPreviousBlock]);
458 addAction(mActions[GotoNextBlock]);
459 addAction(mActions[GotoPreviousEmptyLine]);
460 addAction(mActions[GotoNextEmptyLine]);
461 addAction(mActions[SelectRegion]);
464 void MultiEditor::updateActions()
466 CodeEditor *editor = currentEditor();
467 QTextDocument *doc = editor ? editor->textDocument() : 0;
469 mActions[Undo]->setEnabled( doc && doc->isUndoAvailable() );
470 mActions[Redo]->setEnabled( doc && doc->isRedoAvailable() );
471 mActions[Copy]->setEnabled( editor && editor->textCursor().hasSelection() );
472 mActions[Cut]->setEnabled( mActions[Copy]->isEnabled() );
473 mActions[Paste]->setEnabled( editor );
474 mActions[ToggleComment]->setEnabled( editor );
475 mActions[ToggleOverwriteMode]->setEnabled( editor );
476 mActions[CopyLineUp]->setEnabled( editor );
477 mActions[CopyLineDown]->setEnabled( editor );
478 mActions[MoveLineUp]->setEnabled( editor );
479 mActions[MoveLineDown]->setEnabled( editor );
480 mActions[GotoPreviousBlock]->setEnabled( editor );
481 mActions[GotoNextBlock]->setEnabled( editor );
482 mActions[GotoPreviousEmptyLine]->setEnabled( editor );
483 mActions[GotoNextEmptyLine]->setEnabled( editor );
484 mActions[SelectRegion]->setEnabled( editor );
486 mActions[IndentLineOrRegion]->setEnabled( editor );
487 mActions[EnlargeFont]->setEnabled( editor );
488 mActions[ShrinkFont]->setEnabled( editor );
489 mActions[OpenDefinition]->setEnabled( editor );
490 mActions[EvaluateCurrentDocument]->setEnabled( editor );
491 mActions[EvaluateRegion]->setEnabled( editor );
492 mActions[EvaluateLine]->setEnabled( editor );
493 mActions[ResetFontSize]->setEnabled( editor );
494 mActions[ShowWhitespace]->setEnabled( editor );
495 mActions[ShowWhitespace]->setChecked( editor && editor->showWhitespace() );
498 void MultiEditor::applySettings( Settings::Manager *s )
500 s->beginGroup("IDE/editor");
501 mStepForwardEvaluation = s->value("stepForwardEvaluation").toBool();
502 s->endGroup();
505 static QVariantList saveBoxState( CodeEditorBox *box )
507 // save editors in reverse order - first one is last shown.
508 QVariantList boxData;
509 int idx = box->history().count();
510 while(idx--) {
511 CodeEditor *editor = box->history()[idx];
512 if (!editor->document()->filePath().isEmpty()) {
513 QVariantMap editorData;
514 editorData.insert("file", editor->document()->filePath());
515 editorData.insert("position", editor->textCursor().position());
516 boxData.append( editorData );
519 return boxData;
522 static QVariantMap saveSplitterState( QSplitter *splitter )
524 QVariantMap splitterData;
526 splitterData.insert( "state", splitter->saveState().toBase64() );
528 QVariantList childrenData;
530 int childCount = splitter->count();
531 for (int idx = 0; idx < childCount; idx++) {
532 QWidget *child = splitter->widget(idx);
534 CodeEditorBox *box = qobject_cast<CodeEditorBox*>(child);
535 if (box) {
536 QVariantList boxData = saveBoxState(box);
537 childrenData.append( QVariant(boxData) );
538 continue;
541 QSplitter *childSplitter = qobject_cast<QSplitter*>(child);
542 if (childSplitter) {
543 QVariantMap childSplitterData = saveSplitterState(childSplitter);
544 childrenData.append( QVariant(childSplitterData) );
548 splitterData.insert( "elements", childrenData );
550 return splitterData;
553 void MultiEditor::saveSession( Session *session )
555 session->remove( "editors" );
556 session->setValue( "editors", saveSplitterState(mSplitter) );
559 void MultiEditor::loadBoxState( CodeEditorBox *box, const QVariantList & data )
561 mCurrentEditorBox = box;
562 foreach( QVariant docVar, data )
564 QVariantMap docData = docVar.value<QVariantMap>();
565 QString docPath = docData.value("file").toString();
566 int docPos = docData.value("position").toInt();
567 Main::instance()->documentManager()->open( docPath, docPos );
571 void MultiEditor::loadSplitterState( QSplitter *splitter, const QVariantMap & data )
573 QByteArray state = QByteArray::fromBase64( data.value("state").value<QByteArray>() );
575 QVariantList childrenData = data.value("elements").value<QVariantList>();
576 foreach (const QVariant & childVar, childrenData) {
577 if (childVar.type() == QVariant::List) {
578 CodeEditorBox *childBox = newBox();
579 splitter->addWidget(childBox);
580 QVariantList childBoxData = childVar.value<QVariantList>();
581 loadBoxState( childBox, childBoxData );
583 else if (childVar.type() == QVariant::Map) {
584 QSplitter *childSplitter = new QSplitter;
585 splitter->addWidget(childSplitter);
586 QVariantMap childSplitterData = childVar.value<QVariantMap>();
587 loadSplitterState( childSplitter, childSplitterData );
591 if (!splitter->restoreState(state))
592 qWarning("MultiEditor: could not restore splitter state!");
595 void MultiEditor::switchSession( Session *session )
597 DocumentManager *docManager = Main::instance()->documentManager();
598 QList<Document*> docs = docManager->documents();
599 foreach (Document *doc, docs)
600 docManager->close(doc);
602 delete mSplitter;
603 mSplitter = new MultiSplitter();
605 CodeEditorBox *firstBox = 0;
607 if (session && session->contains("editors")) {
608 QVariantMap splitterData = session->value("editors").value<QVariantMap>();
609 loadSplitterState( mSplitter, splitterData );
611 if (mSplitter->count()) {
612 firstBox = mSplitter->findChild<CodeEditorBox>();
613 if (!firstBox) {
614 qWarning("Session seems to contain invalid editor split data!");
615 delete mSplitter;
616 mSplitter = new MultiSplitter();
621 if (!firstBox) {
622 firstBox = newBox();
623 mSplitter->addWidget( firstBox );
626 mCurrentEditorBox = 0; // ensure complete update
627 setCurrentBox( firstBox );
629 layout()->addWidget(mSplitter);
631 if (!session)
632 // create a document on new session
633 docManager->create();
635 firstBox->setFocus(Qt::OtherFocusReason); // ensure focus
638 void MultiEditor::setCurrent( Document *doc )
640 int tabIdx = tabForDocument(doc);
641 if (tabIdx != -1)
642 mTabs->setCurrentIndex(tabIdx);
645 void MultiEditor::showNextDocument()
647 int currentIndex = mTabs->currentIndex();
648 mTabs->setCurrentIndex( qMin(currentIndex + 1, mTabs->count() - 1) );
651 void MultiEditor::showPreviousDocument()
653 int currentIndex = mTabs->currentIndex();
654 mTabs->setCurrentIndex( qMax(0, currentIndex - 1) );
657 void MultiEditor::switchDocument()
659 CodeEditorBox *box = currentBox();
661 DocumentSelectPopUp * popup = new DocumentSelectPopUp(box->history(), this);
663 QRect popupRect(0,0,300,200);
664 popupRect.moveCenter(rect().center());
665 popup->resize(popupRect.size());
666 QPoint globalPosition = mapToGlobal(popupRect.topLeft());
668 Document * selectedDocument = popup->exec(globalPosition);
670 if (selectedDocument)
671 box->setDocument(selectedDocument);
674 void MultiEditor::onOpen( Document *doc, int pos )
676 QTextDocument *tdoc = doc->textDocument();
678 QIcon icon;
679 if(tdoc->isModified())
680 icon = mDocModifiedIcon;
682 int newTabIndex = mTabs->addTab( icon, doc->title() );
683 mTabs->setTabData( newTabIndex, QVariant::fromValue<Document*>(doc) );
685 currentBox()->setDocument(doc, pos);
686 currentBox()->setFocus(Qt::OtherFocusReason);
689 void MultiEditor::onClose( Document *doc )
691 int tabIdx = tabForDocument(doc);
692 if (tabIdx != -1)
693 mTabs->removeTab(tabIdx);
694 // TODO: each box should switch document according to their own history
697 void MultiEditor::show( Document *doc, int pos )
699 currentBox()->setDocument(doc, pos);
700 currentBox()->setFocus(Qt::OtherFocusReason);
703 void MultiEditor::update( Document *doc )
705 int tabIdx = tabForDocument(doc);
706 if (tabIdx != -1)
707 mTabs->setTabText(tabIdx, doc->title());
710 void MultiEditor::onCloseRequest( int index )
712 Document *doc = documentForTab(index);
713 if (doc)
714 MainWindow::close(doc);
717 void MultiEditor::onCurrentTabChanged( int index )
719 if (index == -1)
720 return;
722 Document *doc = documentForTab(index);
723 if (!doc)
724 return;
726 CodeEditorBox *curBox = currentBox();
727 curBox->setDocument(doc);
728 curBox->setFocus(Qt::OtherFocusReason);
731 void MultiEditor::onCurrentEditorChanged(CodeEditor *editor)
733 setCurrentEditor(editor);
736 void MultiEditor::onBoxActivated(CodeEditorBox *box)
738 setCurrentBox(box);
741 void MultiEditor::onModificationChanged( bool modified )
743 Q_ASSERT(currentEditor());
745 int tabIdx = tabForDocument( currentEditor()->document() );
746 if (tabIdx == -1)
747 return;
749 QIcon icon;
750 if(modified)
751 icon = mDocModifiedIcon;
752 mTabs->setTabIcon( tabIdx, icon );
755 void MultiEditor::evaluateRegion()
757 CodeEditor * editor = currentEditor();
758 if (!editor)
759 return;
761 QString text;
763 // Try current selection
764 QTextCursor cursor = editor->textCursor();
765 if (cursor.hasSelection())
766 text = cursor.selectedText();
767 else {
768 // If no selection, try current region
769 cursor = editor->currentRegion();
770 if (!cursor.isNull()) {
771 // if region is in a single line, evaluate complete line
773 QTextCursor selectionStart (cursor);
774 selectionStart.setPosition(cursor.selectionStart());
776 QTextCursor selectionEnd (cursor);
777 selectionEnd.setPosition(cursor.selectionEnd());
779 if (selectionStart.block() == selectionEnd.block())
780 cursor.select(QTextCursor::LineUnderCursor);
782 text = cursor.selectedText();
783 } else {
784 // If no current region, try current line
785 cursor = editor->textCursor();
786 text = cursor.block().text();
787 if( mStepForwardEvaluation ) {
788 QTextCursor newCursor = cursor;
789 newCursor.movePosition(QTextCursor::NextBlock);
790 newCursor.movePosition(QTextCursor::EndOfBlock);
791 editor->setTextCursor(newCursor);
793 // Adjust cursor for code blinking:
794 cursor.movePosition(QTextCursor::StartOfBlock);
795 cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
799 if (text.isEmpty())
800 return;
802 text.replace( QChar( 0x2029 ), QChar( '\n' ) );
804 Main::evaluateCode(text);
806 editor->blinkCode( cursor );
809 void MultiEditor::evaluateLine()
811 CodeEditor * editor = currentEditor();
812 if (!editor)
813 return;
815 QString text;
817 // Try current selection
818 QTextCursor cursor = editor->textCursor();
819 cursor.select(QTextCursor::LineUnderCursor);
820 text = cursor.selectedText();
822 if( mStepForwardEvaluation ) {
823 QTextCursor newCursor = cursor;
824 newCursor.movePosition(QTextCursor::NextBlock);
825 newCursor.movePosition(QTextCursor::EndOfBlock);
826 editor->setTextCursor(newCursor);
829 if (text.isEmpty())
830 return;
832 // Adjust cursor for code blinking:
833 cursor.movePosition(QTextCursor::StartOfBlock);
834 cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
836 text.replace( QChar( 0x2029 ), QChar( '\n' ) );
838 Main::evaluateCode(text);
840 editor->blinkCode( cursor );
843 void MultiEditor::evaluateDocument()
845 CodeEditor * editor = currentEditor();
846 if (!editor)
847 return;
849 QString documentText = editor->textDocument()->toPlainText();
850 Main::evaluateCode(documentText);
853 void MultiEditor::openDefinition(const QString &string)
855 QString definitionString = string.trimmed();
856 if (definitionString.isEmpty())
857 return;
859 LookupDialog dialog(this);
860 dialog.query(definitionString);
861 dialog.exec();
864 void MultiEditor::openDefinition()
866 CodeEditor * editor = currentEditor();
867 QTextCursor textCursor = editor->textCursor();
869 if (!textCursor.hasSelection())
870 textCursor.select(QTextCursor::WordUnderCursor);
872 openDefinition(textCursor.selectedText());
875 bool MultiEditor::openDocumentation(const QString &string)
877 QString symbol = string.trimmed();
878 if (symbol.isEmpty())
879 return false;
881 QString code = QString("HelpBrowser.openHelpFor(\"%1\")").arg(symbol);
882 Main::evaluateCode(code, true);
883 return true;
886 bool MultiEditor::openDocumentation()
888 CodeEditor * editor = currentEditor();
889 if (!editor)
890 return false;
892 QTextCursor textCursor = editor->textCursor();
894 if (!textCursor.hasSelection())
895 textCursor.select(QTextCursor::WordUnderCursor);
897 return openDocumentation(textCursor.selectedText());
900 Document * MultiEditor::documentForTab( int index )
902 return mTabs->tabData(index).value<Document*>();
905 int MultiEditor::tabForDocument( Document * doc )
907 int tabCount = mTabs->count();
908 for (int idx = 0; idx < tabCount; ++idx) {
909 Document *tabDoc = documentForTab(idx);
910 if (tabDoc && tabDoc == doc)
911 return idx;
913 return -1;
916 CodeEditorBox *MultiEditor::newBox()
918 CodeEditorBox *box = new CodeEditorBox();
920 connect(box, SIGNAL(activated(CodeEditorBox*)),
921 this, SLOT(onBoxActivated(CodeEditorBox*)));
923 return box;
926 void MultiEditor::setCurrentBox( CodeEditorBox * box )
928 if (mCurrentEditorBox == box)
929 return;
931 mCurrentEditorBox = box;
932 mBoxSigMux->setCurrentObject(box);
933 setCurrentEditor( box->currentEditor() );
935 mCurrentEditorBox->setActive();
938 void MultiEditor::setCurrentEditor( CodeEditor * editor )
940 if (editor) {
941 int tabIndex = tabForDocument(editor->document());
942 if (tabIndex != -1)
943 mTabs->setCurrentIndex(tabIndex);
946 mSigMux->setCurrentObject(editor);
947 updateActions();
949 Document *currentDocument = editor ? editor->document() : 0;
950 Main::scProcess()->setActiveDocument(currentDocument);
951 emit currentDocumentChanged(currentDocument);
954 CodeEditor *MultiEditor::currentEditor()
956 return currentBox()->currentEditor();
959 void MultiEditor::split( Qt::Orientation splitDirection )
961 CodeEditorBox *box = newBox();
962 CodeEditorBox *curBox = currentBox();
963 CodeEditor *curEditor = curBox->currentEditor();
965 if (curEditor)
966 box->setDocument(curEditor->document(), curEditor->textCursor().position());
968 mSplitter->insertWidget(box, curBox, splitDirection);
969 box->setFocus( Qt::OtherFocusReason );
972 void MultiEditor::removeCurrentSplit()
974 int boxCount = mSplitter->findChildren<CodeEditorBox*>().count();
975 if (boxCount < 2)
976 // Do not allow removing the one and only box.
977 return;
979 CodeEditorBox *box = currentBox();
980 mSplitter->removeWidget(box);
982 // switch current box to first box found:
983 box = mSplitter->findChild<CodeEditorBox>();
984 Q_ASSERT(box);
985 setCurrentBox(box);
986 box->setFocus( Qt::OtherFocusReason );
989 void MultiEditor::removeAllSplits()
991 CodeEditorBox *box = currentBox();
992 Q_ASSERT(box);
993 Q_ASSERT(mSplitter->count());
994 if (mSplitter->count() == 1 && mSplitter->widget(0) == box)
995 // Nothing to do.
996 return;
998 MultiSplitter *newSplitter = new MultiSplitter;
999 newSplitter->addWidget(box);
1001 delete mSplitter;
1002 mSplitter = newSplitter;
1003 layout()->addWidget(newSplitter);
1005 box->setFocus( Qt::OtherFocusReason );
1008 } // namespace ScIDE