mainwindow.cpp 27.1 KB
Newer Older
jhammen's avatar
jhammen committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*
 * This file is part of Bipscript-IDE.
 *
 * Bipscript-IDE is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Bipscript-IDE is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Bipscript-IDE.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "mainwindow.h"

#include "editorwidget.h"
#include "projectsdialog.h"
21
22
#include "scriptrun.h"
#include "ui_mainwindow.h"
jhammen's avatar
jhammen committed
23
24
25
#include "version.h"

#include <QDebug>
26
27
#include <QDesktopServices>
#include <QDir>
jhammen's avatar
jhammen committed
28
29
30
#include <QFileDialog>
#include <QInputDialog>
#include <QLabel>
31
#include <QTime>
jhammen's avatar
jhammen committed
32

33
MainWindow::MainWindow(QWidget *parent)
34
35
36
    : QMainWindow(parent), projectList(ProjectList::instance()),
      audioEngine(AudioEngine::instance()), transportPosition(0), ui(new Ui::MainWindow),
      timeWidget(this), bbtWidget(this) {
jhammen's avatar
jhammen committed
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
    ui->setupUi(this);

    // time in MM:SS + BBT + engine config
    QString labelStyle("QLabel {font-family: monospace; color: green }");
    timeWidget.setFrameStyle(QFrame::Panel | QFrame::Sunken);
    timeWidget.setStyleSheet(labelStyle);
    timeWidget.setText("00:00.000");
    ui->transportToolBar->addWidget(&timeWidget);
    bbtWidget.setFrameStyle(QFrame::Panel | QFrame::Sunken);
    bbtWidget.setStyleSheet(labelStyle);
    bbtWidget.setText("--|--|----");
    ui->transportToolBar->addWidget(&bbtWidget);
    engineWidget.setFrameStyle(QFrame::Panel | QFrame::Sunken);
    engineWidget.setStyleSheet("QLabel {font-family: monospace; color: blue }");
    engineWidget.setText("----- Hz");
    ui->transportToolBar->addWidget(&engineWidget);

jhammen's avatar
jhammen committed
54
55
56
    // output doc starts hidden
    ui->outputDockWidget->setHidden(true);

jhammen's avatar
jhammen committed
57
    ui->folderTree->setColumnCount(1);
jhammen's avatar
jhammen committed
58
59
60
61
    //
    searchWidget = new SearchWidget();
    ui->leftTabWidget->addTab(searchWidget, "Search");
    connect(searchWidget, SIGNAL(searchCurrent(QString)), this, SLOT(searchCurrent(QString)));
jhammen's avatar
jhammen committed
62
    connect(searchWidget, SIGNAL(searchNext(QString)), this, SLOT(searchNext(QString)));
jhammen's avatar
jhammen committed
63
64
    connect(searchWidget, SIGNAL(searchProject(QString)), this, SLOT(searchProject(QString)));
    connect(searchWidget, SIGNAL(clearSearch()), this, SLOT(clearSearch()));
jhammen's avatar
jhammen committed
65
66
    connect(searchWidget, SIGNAL(navigateResult(EditorLocation &)), this,
            SLOT(handleLink(EditorLocation &)));
jhammen's avatar
jhammen committed
67
68

    projectList.load();
69
    if (projectList.currentExists()) {
jhammen's avatar
jhammen committed
70
71
72
73
74
75
76
77
        loadProject();
    } else {
        on_actionOpenProject_triggered();
    }

    connect(ui->folderTree, SIGNAL(itemClicked(QTreeWidgetItem *, int)), this,
            SLOT(folderTreeClicked(QTreeWidgetItem *, int)));

78
    connect(ui->tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int)));
jhammen's avatar
jhammen committed
79

80
81
    connect(ui->tabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(closeEditorTab(int)));
    connect(ui->outputTabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(closeOutputTab(int)));
jhammen's avatar
jhammen committed
82
83
}

84
MainWindow::~MainWindow() { delete ui; }
jhammen's avatar
jhammen committed
85
86
87

// ------------ superclass methods

88
void MainWindow::closeEvent(QCloseEvent *event) {
jhammen's avatar
jhammen committed
89
    // save settings
90
    projectList.save();
jhammen's avatar
jhammen committed
91
    // close tabs
92
    if (!saveAllTabs()) {
jhammen's avatar
jhammen committed
93
94
95
        event->ignore();
    }
    // kill running scripts
96
    if (!killAllScripts()) {
jhammen's avatar
jhammen committed
97
98
99
100
101
102
        event->ignore();
    }
}

// ------------ slots

103
void MainWindow::changeProject() {
jhammen's avatar
jhammen committed
104
105
106
    ui->folderTree->clear();
    loadProject();
    // close all edit tabs not still applicable
107
    for (int i = ui->tabWidget->count() - 1; i >= 0; i--) {
jhammen's avatar
jhammen committed
108
        QWidget *widget = ui->tabWidget->widget(i);
109
110
        EditorWidget *editor = static_cast<EditorWidget *>(widget);
        if (findTreeItem(editor->path()) == nullptr) {
jhammen's avatar
jhammen committed
111
112
113
114
115
116
            ui->tabWidget->removeTab(i);
        }
    }
    selectedFolder = nullptr;
}

117
118
119
void MainWindow::closeEditorTab(int index) {
    EditorWidget *editor = qobject_cast<EditorWidget *>(ui->tabWidget->widget(index));
    if (saveOrAbandon(editor)) {
120
        projectList.currentProject().closeFile(editor->path());
jhammen's avatar
jhammen committed
121
122
123
124
        delete editor;
    }
}

125
126
void MainWindow::closeOutputTab(int index) {
    OutputWidget *output = qobject_cast<OutputWidget *>(ui->outputTabWidget->widget(index));
jhammen's avatar
jhammen committed
127
    QString name = ui->outputTabWidget->tabText(index);
128
    if (killOrDetach(output, name)) {
jhammen's avatar
jhammen committed
129
130
131
132
        delete output;
    }
}

133
void MainWindow::codeError(const QString &path, int line) {
jhammen's avatar
jhammen committed
134
    EditorWidget *editor = findEditor(path);
135
    if (editor) {
jhammen's avatar
jhammen committed
136
137
138
        editor->addError(line);
    }
}
139
void MainWindow::engineDisconnect() {
jhammen's avatar
jhammen committed
140
141
142
143
144
145
146
147
148
    ui->actionStartStopTransport->setChecked(false);
    enableTransportControls(false);
    audioEngine.disconnect();
    timeWidget.setText("00:00.000");
    bbtWidget.setText("--|--|----");
    ui->actionJackConnect->setChecked(false);
    ui->actionJackConnect->setEnabled(true);
}

149
void MainWindow::createOrOpenEditor(NamedFile &file) {
jhammen's avatar
jhammen committed
150
151
152
153
154
155
    EditorWidget *editor = findEditor(file.path);
    if (editor) {
        ui->tabWidget->setCurrentWidget(editor);
    } else {
        // new editor for this file
        editor = new EditorWidget(file);
156
157
158
159
160
161
162
163
164
165
166
167
        if (editor->load()) {
            ui->tabWidget->addTab(editor, file.name);
            ui->tabWidget->setCurrentWidget(editor);
            connect(editor->document(), SIGNAL(modificationChanged(bool)), this,
                    SLOT(markDirty(bool)));
            connect(editor, SIGNAL(undoAvailable(bool)), ui->actionUndo, SLOT(setEnabled(bool)));
            connect(editor, SIGNAL(redoAvailable(bool)), ui->actionRedo, SLOT(setEnabled(bool)));
            connect(editor, SIGNAL(copyAvailable(bool)), ui->actionCut, SLOT(setEnabled(bool)));
            connect(editor, SIGNAL(copyAvailable(bool)), ui->actionCopy, SLOT(setEnabled(bool)));
            // add to project list as open file
            projectList.currentProject().openFile(file.path);
        }
jhammen's avatar
jhammen committed
168
169
170
171
172
    }
    selectedFolder = nullptr;
    enableFileActions(editor);
}

173
void MainWindow::createOrOpenEditor(const QString &path) {
jhammen's avatar
jhammen committed
174
175
176
177
178
    QFileInfo fileInfo(path);
    NamedFile file(fileInfo.fileName(), fileInfo.filePath());
    createOrOpenEditor(file);
}

179
180
EditorWidget *MainWindow::currentEditor() {
    return static_cast<EditorWidget *>(ui->tabWidget->currentWidget());
jhammen's avatar
jhammen committed
181
182
}

183
184
void MainWindow::folderTreeClicked(QTreeWidgetItem *item, int) {
    NamedFile &file = static_cast<FolderTreeItem *>(item)->file();
jhammen's avatar
jhammen committed
185
    QFileInfo fileInfo(file.path);
186
    if (fileInfo.isDir()) {
jhammen's avatar
jhammen committed
187
188
        selectedFolder = &(file.path);
        enableFileActions(nullptr);
189
    } else if (fileInfo.isFile()) {
jhammen's avatar
jhammen committed
190
        createOrOpenEditor(file);
191
192
    } else {
        QMessageBox::information(this, "Unknown Entry", file.path);
jhammen's avatar
jhammen committed
193
194
195
    }
}

196
void MainWindow::handleLink(const QUrl &url) {
jhammen's avatar
jhammen committed
197
198
    QString path(url.path());
    createOrOpenEditor(path);
199
    if (url.hasQuery()) {
jhammen's avatar
jhammen committed
200
201
202
203
204
205
206
        int column = QString(url.query()).toInt();
        currentEditor()->focusLine(url.port(), column);
    } else {
        currentEditor()->focusLine(url.port());
    }
}

jhammen's avatar
jhammen committed
207
208
209
210
211
void MainWindow::handleLink(EditorLocation &loc) {
    createOrOpenEditor(loc.file().path);
    currentEditor()->focusLocation(loc);
}

212
void MainWindow::markDirty(bool value) {
jhammen's avatar
jhammen committed
213
    QString tabText(currentEditor()->name());
214
215
216
    if (value) {
        tabText.append("*");
    }
jhammen's avatar
jhammen committed
217
218
219
220
221
222
    int index = ui->tabWidget->currentIndex();
    ui->tabWidget->setTabText(index, tabText);
    ui->actionSaveFile->setEnabled(value);
    setWindowModified(value);
}

jhammen's avatar
jhammen committed
223
224
225
void MainWindow::searchCurrent(const QString &term) {
    EditorWidget *editor = currentEditor();
    if (editor) {
jhammen's avatar
jhammen committed
226
        searchWidget->addResults(editor->name(), editor->findAll(term, true), true);
jhammen's avatar
jhammen committed
227
228
229
    }
}

jhammen's avatar
jhammen committed
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
void MainWindow::searchNext(const QString &term) {
    if (currentEditor()) {
        currentEditor()->findNext(term);
    }
}

void MainWindow::searchFolder(const QString &term, NamedFile &folder) {
    QDir dir(folder.path);
    dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);

    foreach (QFileInfo fileInfo, dir.entryInfoList()) {
        if (fileInfo.isFile()) {
            EditorWidget *editor = findEditor(fileInfo.filePath());
            if (editor) {
                QList<EditorLink> results = editor->findAll(term, false);
                if (results.size()) {
                    searchWidget->addResults(editor->name(), results, true);
                }
            } else {
                QList<EditorLink> results = searchFile(term, fileInfo.filePath());
                if (results.size()) {
                    searchWidget->addResults(fileInfo.fileName(), results, false);
                }
            }
        } else if (fileInfo.isDir()) {
            NamedFile childFolder(fileInfo.baseName(), fileInfo.filePath());
            searchFolder(term, childFolder);
        }
    }
}

void MainWindow::searchProject(const QString &term) {
    Project &project = projectList.currentProject();
    for (int i = 0; i < project.folderCount(); ++i) {
        NamedFile &folder = project.folder(i);
        searchFolder(term, folder);
    }
}

void MainWindow::clearSearch() {
    for (int i = 0; i < ui->tabWidget->count(); i++) {
        EditorWidget *editor = qobject_cast<EditorWidget *>(ui->tabWidget->widget(i));
        QList<QTextEdit::ExtraSelection> empty;
        editor->setExtraSelections(empty);
    }
}
jhammen's avatar
jhammen committed
276

277
void MainWindow::showTime(Position pos) {
jhammen's avatar
jhammen committed
278
279
280
    QTime qtime(0, 0, 0);
    qtime = qtime.addMSecs(pos.msec);
    timeWidget.setText(qtime.toString("mm:ss.zzz"));
281
    bbtWidget.setText(QString("%1|%2|%3").arg(pos.bar, 2).arg(pos.beat, 2).arg(pos.tick, 4));
jhammen's avatar
jhammen committed
282
283
284
    transportPosition = pos.frame;
}

285
286
void MainWindow::tabChanged(int i) {
    EditorWidget *editor = qobject_cast<EditorWidget *>(ui->tabWidget->widget(i));
287
    Project &project = projectList.currentProject();
288
    if (editor == nullptr) { // all tabs closed
jhammen's avatar
jhammen committed
289
290
291
        ui->folderTree->clearSelection();
        setWindowTitle(QCoreApplication::applicationName());
        enableFileActions(nullptr);
292
        project.activeFile(nullptr);
jhammen's avatar
jhammen committed
293
294
    } else {
        QTreeWidgetItem *item = findTreeItem(editor->path());
295
        if (item) {
jhammen's avatar
jhammen committed
296
297
298
299
300
301
302
            ui->folderTree->setCurrentItem(item);
        } else {
            ui->folderTree->clearSelection();
        }
        setWindowTitle(QString("%1[*]").arg(editor->name()));
        setWindowModified(editor->isDirty());
        enableFileActions(editor);
303
        project.activeFile(editor->path());
jhammen's avatar
jhammen committed
304
305
306
    }
}

307
308
void MainWindow::closeOtherEditors() {
    if (QAction *action = qobject_cast<QAction *>(sender())) {
309
        int index = action->data().toInt();
310
311
312
313
        for (int i = ui->tabWidget->count() - 1; i >= 0; i--) {
            if (i != index) {
                closeEditorTab(i);
            }
314
315
316
317
        }
    }
}

jhammen's avatar
jhammen committed
318
319
// ------------ auto slots

320
void MainWindow::on_actionAbout_triggered() {
jhammen's avatar
jhammen committed
321
    QString lang = QLocale::languageToString(QLocale().language());
322
323
    QString message =
        tr("Bipscript IDE %1\n%2\nQT %3").arg(BIPSCRIPT_IDE_VERSION).arg(lang).arg(QT_VERSION_STR);
jhammen's avatar
jhammen committed
324
325
326
    QMessageBox::about(this, "Bipscript-IDE", message);
}

327
void MainWindow::on_actionAPIDocumentation_triggered() {
jhammen's avatar
jhammen committed
328
329
    QStringList supported = {"en", "es"};
    QString lang = QLocale().name().left(2);
330
331
332
    if (supported.contains(lang)) {
        lang = supported[0];
    }
jhammen's avatar
jhammen committed
333
334
335
336
    QString link = QString("http://www.bipscript.org/%1/api/").arg(lang);
    QDesktopServices::openUrl(QUrl(link));
}

337
void MainWindow::on_actionCloseFile_triggered() { closeEditorTab(ui->tabWidget->currentIndex()); }
jhammen's avatar
jhammen committed
338

339
void MainWindow::on_actionComment_triggered() { currentEditor()->commentSelection(); }
jhammen's avatar
jhammen committed
340

341
void MainWindow::on_actionCopy_triggered() { currentEditor()->copy(); }
jhammen's avatar
jhammen committed
342

343
void MainWindow::on_actionCut_triggered() { currentEditor()->cut(); }
jhammen's avatar
jhammen committed
344

345
void MainWindow::on_actionNewFile_triggered() {
jhammen's avatar
jhammen committed
346
347
348
349
    QString message("New file in ");
    message.append(*selectedFolder);
    bool ok;
    QString name = QInputDialog::getText(this, "New File", message, QLineEdit::Normal, "", &ok);
350
    if (name.length() > 0) {
jhammen's avatar
jhammen committed
351
352
353
354
        // create file
        QFileInfo fileInfo(*selectedFolder + "/" + name);
        QString path(fileInfo.filePath());
        QFile file(path);
355
        if (file.exists()) {
jhammen's avatar
jhammen committed
356
357
358
359
360
361
362
363
364
365
366
367
368
369
            QMessageBox::warning(this, "File Exists", path + " already exists");
        } else {
            file.open(QIODevice::WriteOnly);
            file.close();
            // open in tree
            ui->folderTree->clear();
            loadProject();
        }
        QTreeWidgetItem *item = findTreeItem(path);
        ui->folderTree->setCurrentItem(item);
        createOrOpenEditor(path);
    }
}

370
void MainWindow::on_actionDeleteFile_triggered() {
jhammen's avatar
jhammen committed
371
372
373
374
375
376
377
    QMessageBox msgBox;
    QString path(currentEditor()->path());
    msgBox.setText("Delete " + path);
    msgBox.setInformativeText("Do you want to delete " + currentEditor()->name() + "?");
    msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
    msgBox.setDefaultButton(QMessageBox::Cancel);
    int choice = msgBox.exec();
378
    if (choice == QMessageBox::Ok) {
jhammen's avatar
jhammen committed
379
        // remove from tree
380
        FolderTreeItem *item = static_cast<FolderTreeItem *>(findTreeItem(path));
jhammen's avatar
jhammen committed
381
382
383
384
385
386
387
388
        item->parent()->removeChild(item);
        // remove editor if any
        delete findEditor(path);
        // remove file from filesystem
        QFile::remove(path);
    }
}

389
void MainWindow::on_actionExamples_triggered() {
jhammen's avatar
jhammen committed
390
391
    QRegExp versionExp = QRegExp("v([0-9\\.]+)");
    QString versionString = QString(BIPSCRIPT_IDE_VERSION);
392
    if (versionExp.indexIn(versionString) == 0) {
jhammen's avatar
jhammen committed
393
394
395
396
397
398
399
400
401
402
        QString version = versionExp.cap(1);
        QString link("https://github.com/bipscript/examples/releases/tag/v");
        link.append(version);
        QDesktopServices::openUrl(QUrl(link));
    } else {
        QString message = tr("No examples for version %1").arg(versionString);
        QMessageBox::warning(this, tr("Unknown Version"), message, QMessageBox::Ok);
    }
}

403
void MainWindow::on_actionExit_triggered() { QApplication::closeAllWindows(); }
jhammen's avatar
jhammen committed
404

405
void MainWindow::on_actionFastForward_triggered() {
jhammen's avatar
jhammen committed
406
407
408
409
    transportPosition = transportPosition + audioConfig.framesPerSecond;
    audioEngine.reposition(transportPosition);
}

jhammen's avatar
jhammen committed
410
411
412
413
414
void MainWindow::on_actionFind_in_Project_triggered() {
    searchWidget->newProjectSearch();
    ui->leftTabWidget->setCurrentWidget(searchWidget);
}

jhammen's avatar
jhammen committed
415
416
417
418
419
/**
 * @brief MainWindow::on_actionRun_triggered
 *
 * action to run script
 */
420
void MainWindow::on_actionRun_triggered() {
jhammen's avatar
jhammen committed
421
    // check if possible
422
    if (!ScriptRun::possible()) {
jhammen's avatar
jhammen committed
423
424
425
426
427
428
429
430
431
432
433
434
        QMessageBox::warning(this, "Executable", "Cannot launch bipscript executable");
        return;
    }
    ScriptRun *run = new ScriptRun(currentEditor()->path());
    // add output widget for this run
    OutputWidget *out = new OutputWidget();
    ui->outputTabWidget->addTab(out, currentEditor()->name());
    ui->outputTabWidget->setCurrentWidget(out);
    connect(run, SIGNAL(started(int)), out, SLOT(running(int)));
    connect(run, SIGNAL(readStdout(QString)), out, SLOT(write(QString)));
    connect(run, SIGNAL(readStderr(QString)), out, SLOT(writeError(QString)));
    connect(run, SIGNAL(error(QString)), out, SLOT(writeError(QString)));
435
436
437
438
    connect(run, SIGNAL(exited(int)), out, SLOT(completed(int)));
    connect(out, SIGNAL(codeError(const QString &, int)), this,
            SLOT(codeError(const QString &, int)));
    connect(out, SIGNAL(linkClicked(const QUrl &)), this, SLOT(handleLink(const QUrl &)));
jhammen's avatar
jhammen committed
439
440
441
442
443
    // show output area if hidden
    ui->outputDockWidget->show();
    run->start();
}

444
void MainWindow::enableTransportControls(bool enabled) {
jhammen's avatar
jhammen committed
445
446
447
448
449
450
    ui->actionStartStopTransport->setEnabled(enabled);
    ui->actionRewind->setEnabled(enabled);
    ui->actionRestart->setEnabled(enabled);
    ui->actionFastForward->setEnabled(enabled);
}

451
452
void MainWindow::on_actionJackConnect_toggled(bool connected) {
    if (connected) {
jhammen's avatar
jhammen committed
453
454
455
        audioConfig = audioEngine.connect();
        atimer = audioEngine.timerService();
        connect(atimer, SIGNAL(transport(Position)), this, SLOT(showTime(Position)));
456
457
        connect(atimer, SIGNAL(rolling(bool)), ui->actionStartStopTransport,
                SLOT(setChecked(bool)));
jhammen's avatar
jhammen committed
458
459
460
461
462
463
464
465
466
467
        connect(atimer, SIGNAL(finished()), this, SLOT(engineDisconnect()));
        engineWidget.setText(QString("%1 Hz").arg(audioConfig.framesPerSecond));
    } else {
        ui->actionJackConnect->setEnabled(false);
        atimer->stop();
        engineWidget.setText("----- Hz");
    }
    enableTransportControls(connected && !ui->actionTransportLock->isChecked());
}

468
void MainWindow::on_actionOpenFile_triggered() {
jhammen's avatar
jhammen committed
469
470
471
    QFileDialog fileDialog(this);
    fileDialog.setFileMode(QFileDialog::AnyFile);
    fileDialog.setDirectory(projectList.commonRoot());
472
    if (fileDialog.exec()) {
jhammen's avatar
jhammen committed
473
474
475
476
477
        QString path = fileDialog.selectedFiles()[0];
        createOrOpenEditor(path);
    }
}

478
479
void MainWindow::on_actionOpenProject_triggered() {
    if (saveAllTabs()) {
jhammen's avatar
jhammen committed
480
481
482
483
484
485
        ProjectsDialog projectsDialog(this);
        connect(&projectsDialog, SIGNAL(projectChanged()), this, SLOT(changeProject()));
        projectsDialog.exec();
    }
}

486
void MainWindow::on_actionPaste_triggered() { currentEditor()->paste(); }
jhammen's avatar
jhammen committed
487

488
void MainWindow::on_actionProjectOptions_triggered() {
489
    Project &project = projectList.currentProject();
jhammen's avatar
jhammen committed
490
491
492
493
    QString value = project.executable();
    QString message("Executable Path");
    bool ok;
    QString name = QInputDialog::getText(this, "Options", message, QLineEdit::Normal, value, &ok);
494
    if (name.length() > 0) {
jhammen's avatar
jhammen committed
495
496
497
498
499
500
        // TODO: test existance etc.
        project.executable(name);
        ScriptRun::setExecutable(project.executable());
    }
}

501
void MainWindow::on_actionRedo_triggered() { currentEditor()->redo(); }
jhammen's avatar
jhammen committed
502

503
504
void MainWindow::on_actionRenameFile_triggered() {
    if (saveOrAbandon(currentEditor())) {
jhammen's avatar
jhammen committed
505
506
507
        QString message("New name");
        QString oldPath = currentEditor()->path();
        bool ok;
508
509
510
        QString path =
            QInputDialog::getText(this, "Rename", message, QLineEdit::Normal, oldPath, &ok);
        if (path.length() > 0) {
jhammen's avatar
jhammen committed
511
            QFileInfo fileInfo(path);
512
            if (fileInfo.exists()) {
jhammen's avatar
jhammen committed
513
514
515
516
517
                QString message = QString("Cannot move file, target exists:\n%1").arg(path);
                QMessageBox::warning(this, "File exists", message);
                return;
            }
            QFile oldFile(oldPath);
518
            if (!oldFile.rename(fileInfo.filePath())) {
jhammen's avatar
jhammen committed
519
520
521
522
523
524
525
526
527
528
                QString message = QString("Error on move\n %1 ->\n %2").arg(oldPath).arg(path);
                QMessageBox::warning(this, "File Move Error", message);
                return;
            }
            // success
            NamedFile file;
            file.name = fileInfo.fileName();
            file.path = fileInfo.filePath();
            // remove old from tree
            QTreeWidgetItem *item = findTreeItem(oldPath);
529
            if (item) {
jhammen's avatar
jhammen committed
530
531
532
533
534
                item->parent()->removeChild(item);
            }
            // add new to tree if possible
            QTreeWidgetItem *parent = findTreeItem(fileInfo.path());
            ui->folderTree->clearSelection();
535
536
537
            if (parent) {
                FolderTreeItem *newItem =
                    new FolderTreeItem(parent, fileInfo.fileName(), fileInfo.filePath());
jhammen's avatar
jhammen committed
538
539
540
541
542
543
                ui->folderTree->setCurrentItem(newItem);
            }
            // update editor + tab
            currentEditor()->moveFile(file);
            int index = ui->tabWidget->currentIndex();
            QString tabText(currentEditor()->name());
544
545
546
            if (currentEditor()->isDirty()) {
                tabText.append("*");
            }
jhammen's avatar
jhammen committed
547
548
549
550
551
552
553
            ui->tabWidget->setTabText(index, tabText);
            // update main window title
            setWindowTitle(QString("%1[*]").arg(file.name));
        }
    }
}

554
void MainWindow::on_actionRestart_triggered() { audioEngine.reposition(0); }
jhammen's avatar
jhammen committed
555

556
void MainWindow::on_actionRewind_triggered() {
jhammen's avatar
jhammen committed
557
558
559
560
561
    int newpos = static_cast<int>(transportPosition - audioConfig.framesPerSecond);
    transportPosition = static_cast<uint>(qMax(newpos, 0));
    audioEngine.reposition(transportPosition);
}

562
void MainWindow::on_actionSaveFile_triggered() { currentEditor()->save(); }
jhammen's avatar
jhammen committed
563

jhammen's avatar
jhammen committed
564
565
566
567
568
void MainWindow::on_actionFocusFind_triggered() {
    searchWidget->newFileSearch();
    ui->leftTabWidget->setCurrentWidget(searchWidget);
}

569
570
void MainWindow::on_actionStartStopTransport_toggled(bool start) {
    if (start) {
jhammen's avatar
jhammen committed
571
572
573
574
575
576
577
        audioEngine.transportStart();
    } else {
        audioEngine.transportStop();
    }
    ui->actionJackConnect->setEnabled(!start);
}

578
void MainWindow::on_actionTransportLock_toggled(bool locked) {
jhammen's avatar
jhammen committed
579
580
581
582
583
584
585
    bool enabled = !locked && ui->actionJackConnect->isChecked();
    ui->actionStartStopTransport->setEnabled(enabled);
    ui->actionRewind->setEnabled(enabled);
    ui->actionRestart->setEnabled(enabled);
    ui->actionFastForward->setEnabled(enabled);
}

586
void MainWindow::on_actionUndo_triggered() { currentEditor()->undo(); }
jhammen's avatar
jhammen committed
587

588
void MainWindow::on_tabWidget_customContextMenuRequested(const QPoint &pos) {
589
590
591
592
593
594
595
    QMenu menu;
    QAction *action = menu.addAction(tr("Close Other Tabs"), this, SLOT(closeOtherEditors()));
    action->setData(ui->tabWidget->tabBar()->tabAt(pos));
    action->setEnabled(ui->tabWidget->count() > 1);
    menu.exec(QCursor::pos());
}

jhammen's avatar
jhammen committed
596
597
// ------------ private methods

598
void MainWindow::enableFileActions(EditorWidget *editor) {
jhammen's avatar
jhammen committed
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
    // requires file selected
    ui->actionDeleteFile->setEnabled(editor);
    ui->actionRun->setEnabled(editor);
    ui->actionPaste->setEnabled(editor);
    ui->actionCloseFile->setEnabled(editor);
    ui->actionComment->setEnabled(editor);
    ui->actionRenameFile->setEnabled(editor);
    // requires directory selected
    ui->actionNewFile->setEnabled(selectedFolder);
    // requires state from the editor
    ui->actionSaveFile->setEnabled(editor && editor->isDirty());
    ui->actionUndo->setEnabled(editor && editor->isUndoAvailable());
    ui->actionRedo->setEnabled(editor && editor->isRedoAvailable());
    ui->actionCut->setEnabled(editor && editor->hasSelection());
    ui->actionCopy->setEnabled(editor && editor->hasSelection());
}

616
617
618
619
620
621
EditorWidget *MainWindow::findEditor(const QString &path) {
    for (int i = 0; i < ui->tabWidget->count(); i++) {
        EditorWidget *editor = qobject_cast<EditorWidget *>(ui->tabWidget->widget(i));
        if (editor->path() == path) {
            return editor;
        }
jhammen's avatar
jhammen committed
622
623
624
625
    }
    return nullptr;
}

626
QTreeWidgetItem *MainWindow::findTreeItem(const QString &path) {
jhammen's avatar
jhammen committed
627
628
    QTreeWidgetItemIterator it(ui->folderTree);
    while (*it) {
629
630
631
632
        FolderTreeItem *item = static_cast<FolderTreeItem *>(*it);
        if (item->file().path == path) {
            return item;
        }
jhammen's avatar
jhammen committed
633
634
635
636
637
        ++it;
    }
    return nullptr;
}

638
void MainWindow::loadProject() {
639
    Project &project = projectList.currentProject();
jhammen's avatar
jhammen committed
640
641
642
643
644
    ScriptRun::setExecutable(project.executable());
    for (int i = 0; i < project.folderCount(); ++i) {
        NamedFile &folder = project.folder(i);
        ui->folderTree->addTopLevelItem(toTreeFolder(folder));
    }
645
646
647
648
649
    foreach (const QString &path, project.openFiles()) { createOrOpenEditor(path); }
    EditorWidget *editor = findEditor(project.activeFile());
    if (editor) {
        ui->tabWidget->setCurrentWidget(editor);
    }
jhammen's avatar
jhammen committed
650
651
    searchWidget->clear();
    ui->leftTabWidget->setCurrentWidget(ui->folderTree);
jhammen's avatar
jhammen committed
652
653
}

654
655
656
657
bool MainWindow::saveAllTabs() {
    for (int i = 0; i < ui->tabWidget->count(); i++) {
        EditorWidget *editor = static_cast<EditorWidget *>(ui->tabWidget->widget(i));
        if (editor->isDirty()) {
jhammen's avatar
jhammen committed
658
            QMessageBox::StandardButton choice = unsavedChangesDialog(editor->path());
659
            if (choice == QMessageBox::Cancel) {
jhammen's avatar
jhammen committed
660
                return false;
661
            } else if (choice == QMessageBox::Save) {
jhammen's avatar
jhammen committed
662
663
664
665
666
667
668
                editor->save();
            }
        }
    }
    return true;
}

669
670
671
672
bool MainWindow::saveOrAbandon(EditorWidget *editor) {
    if (editor->isDirty()) {
        QMessageBox::StandardButton choice = unsavedChangesDialog(editor->path());
        if (choice == QMessageBox::Cancel) {
jhammen's avatar
jhammen committed
673
            return false;
674
675
        } else if (choice == QMessageBox::Save) {
            if (!editor->save()) {
jhammen's avatar
jhammen committed
676
677
678
679
680
681
682
                return false;
            }
        }
    }
    return true;
}

jhammen's avatar
jhammen committed
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
QList<EditorLink> MainWindow::searchFile(const QString &term, const QString &path) {
    QList<EditorLink> ret;
    QFile file(path);
    if (file.open(QFile::ReadOnly | QFile::Text)) {
        QTextStream in(&file);
        int lineindex = 0;
        while (!in.atEnd()) {
            QString line = in.readLine();
            int index = line.indexOf(term, 0, Qt::CaseInsensitive);
            while (index != -1) {
                NamedFile nfile(file.fileName(), path);
                int anchor = lineindex + index;
                // find snippet
                int space = line.indexOf(QRegExp("\\s+"), index);
                int end = (space == -1 ? line.size() : space);
                QString text = line.mid(index, end - index);
                // create and add editor link
                EditorLink link(text, nfile, anchor, anchor + term.size());
                ret.append(link);
                index = line.indexOf(term, index + 1, Qt::CaseInsensitive);
            }
            lineindex += line.length() + 1;
        }
    }
    return ret;
}

710
711
712
bool MainWindow::killAllScripts() {
    for (int i = 0; i < ui->outputTabWidget->count(); i++) {
        OutputWidget *output = qobject_cast<OutputWidget *>(ui->outputTabWidget->widget(i));
jhammen's avatar
jhammen committed
713
        QString name = ui->outputTabWidget->tabText(i);
714
        if (!killOrDetach(output, name)) {
jhammen's avatar
jhammen committed
715
716
717
718
719
720
            return false;
        }
    }
    return true;
}

721
722
bool MainWindow::killOrDetach(OutputWidget *output, const QString &name) {
    if (output->pid() != 0) {
jhammen's avatar
jhammen committed
723
724
        QString id = QString("%1 pid %2").arg(name).arg(output->pid());
        QString mesg = "%1 is still running.\nDo you wish to kill the process?";
725
726
727
728
        QMessageBox::StandardButton choice =
            QMessageBox::warning(this, tr("Running Process"), mesg.arg(id),
                                 QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
        if (choice == QMessageBox::Cancel) {
jhammen's avatar
jhammen committed
729
            return false;
730
        } else if (choice == QMessageBox::Yes) {
jhammen's avatar
jhammen committed
731
732
            output->killScript(true);
        }
733
734
    }
    return true;
jhammen's avatar
jhammen committed
735
736
}

737
FolderTreeItem *MainWindow::toTreeFolder(NamedFile &folder) {
jhammen's avatar
jhammen committed
738
739
740
741
    FolderTreeItem *folderItem = new FolderTreeItem(nullptr, folder.name, folder.path);
    QDir dir(folder.path);
    dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);
    foreach (QFileInfo fileInfo, dir.entryInfoList()) {
742
743
744
745
        if (fileInfo.isFile()) {
            folderItem->addChild(
                new FolderTreeItem(folderItem, fileInfo.fileName(), fileInfo.filePath()));
        } else if (fileInfo.isDir()) {
jhammen's avatar
jhammen committed
746
747
748
749
750
751
752
            NamedFile childFolder(fileInfo.baseName(), fileInfo.filePath());
            folderItem->addChild(toTreeFolder(childFolder));
        }
    }
    return folderItem;
}

753
754
755
QMessageBox::StandardButton MainWindow::unsavedChangesDialog(const QString &path) {
    return QMessageBox::warning(this, tr("Unsaved Changes"),
                                tr("%1 has unsaved changes.\nDo you wish to save?").arg(path),
jhammen's avatar
jhammen committed
756
757
758
                                QMessageBox::Save | QMessageBox::No | QMessageBox::Cancel);
}

759
void MainWindow::on_actionWebsite_triggered() {
jhammen's avatar
jhammen committed
760
761
762
    QString link = "http://www.bipscript.org";
    QDesktopServices::openUrl(QUrl(link));
}