mainwindow.cpp 28.7 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, const char *folder)
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
    connect(searchWidget, SIGNAL(searchProject(QString)), this, SLOT(searchProject(QString)));
jhammen's avatar
jhammen committed
64
65
    connect(searchWidget, SIGNAL(replaceNext(QString, QString)), this,
            SLOT(replaceNext(QString, QString)));
jhammen's avatar
jhammen committed
66
    connect(searchWidget, SIGNAL(clearSearch()), this, SLOT(clearSearch()));
jhammen's avatar
jhammen committed
67
68
    connect(searchWidget, SIGNAL(navigateResult(EditorLocation &)), this,
            SLOT(handleLink(EditorLocation &)));
jhammen's avatar
jhammen committed
69
70

    projectList.load();
71
72
73
74
    if(folder) {  // from CLI
        QDir dir(folder);
        projectList.select(dir.canonicalPath());
    }
75
    if (projectList.currentExists()) {
jhammen's avatar
jhammen committed
76
77
78
79
80
81
82
83
        loadProject();
    } else {
        on_actionOpenProject_triggered();
    }

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

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

86
87
    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
88
89
}

90
MainWindow::~MainWindow() { delete ui; }
jhammen's avatar
jhammen committed
91
92
93

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

94
void MainWindow::closeEvent(QCloseEvent *event) {
jhammen's avatar
jhammen committed
95
    // save settings
96
    projectList.save();
jhammen's avatar
jhammen committed
97
    // close tabs
98
    if (!saveAllTabs()) {
jhammen's avatar
jhammen committed
99
100
101
        event->ignore();
    }
    // kill running scripts
102
    if (!killAllScripts()) {
jhammen's avatar
jhammen committed
103
104
105
106
107
108
        event->ignore();
    }
}

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

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

123
124
125
void MainWindow::closeEditorTab(int index) {
    EditorWidget *editor = qobject_cast<EditorWidget *>(ui->tabWidget->widget(index));
    if (saveOrAbandon(editor)) {
126
        projectList.currentProject().closeFile(editor->path());
jhammen's avatar
jhammen committed
127
128
129
130
        delete editor;
    }
}

131
132
void MainWindow::closeOutputTab(int index) {
    OutputWidget *output = qobject_cast<OutputWidget *>(ui->outputTabWidget->widget(index));
jhammen's avatar
jhammen committed
133
    QString name = ui->outputTabWidget->tabText(index);
134
    if (killOrDetach(output, name)) {
jhammen's avatar
jhammen committed
135
136
137
138
        delete output;
    }
}

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

155
bool MainWindow::createOrOpenEditor(NamedFile &file) {
jhammen's avatar
jhammen committed
156
157
158
159
160
161
    EditorWidget *editor = findEditor(file.path);
    if (editor) {
        ui->tabWidget->setCurrentWidget(editor);
    } else {
        // new editor for this file
        editor = new EditorWidget(file);
162
163
164
165
166
167
168
169
170
171
172
        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);
173
174
        } else {
            return false;
175
        }
jhammen's avatar
jhammen committed
176
177
178
    }
    selectedFolder = nullptr;
    enableFileActions(editor);
179
    return true;
jhammen's avatar
jhammen committed
180
181
}

182
bool MainWindow::createOrOpenEditor(const QString &path) {
jhammen's avatar
jhammen committed
183
184
    QFileInfo fileInfo(path);
    NamedFile file(fileInfo.fileName(), fileInfo.filePath());
185
    return createOrOpenEditor(file);
jhammen's avatar
jhammen committed
186
187
}

188
189
EditorWidget *MainWindow::currentEditor() {
    return static_cast<EditorWidget *>(ui->tabWidget->currentWidget());
jhammen's avatar
jhammen committed
190
191
}

192
193
void MainWindow::folderTreeClicked(QTreeWidgetItem *item, int) {
    NamedFile &file = static_cast<FolderTreeItem *>(item)->file();
jhammen's avatar
jhammen committed
194
    QFileInfo fileInfo(file.path);
195
    if (fileInfo.isDir()) {
jhammen's avatar
jhammen committed
196
197
        selectedFolder = &(file.path);
        enableFileActions(nullptr);
198
    } else if (fileInfo.isFile()) {
jhammen's avatar
jhammen committed
199
        createOrOpenEditor(file);
200
201
    } else {
        QMessageBox::information(this, "Unknown Entry", file.path);
jhammen's avatar
jhammen committed
202
203
204
    }
}

205
void MainWindow::handleLink(const QUrl &url) {
jhammen's avatar
jhammen committed
206
207
    QString path(url.path());
    createOrOpenEditor(path);
208
    if (url.hasQuery()) {
jhammen's avatar
jhammen committed
209
210
211
212
213
214
215
        int column = QString(url.query()).toInt();
        currentEditor()->focusLine(url.port(), column);
    } else {
        currentEditor()->focusLine(url.port());
    }
}

jhammen's avatar
jhammen committed
216
217
218
219
220
void MainWindow::handleLink(EditorLocation &loc) {
    createOrOpenEditor(loc.file().path);
    currentEditor()->focusLocation(loc);
}

221
void MainWindow::markDirty(bool value) {
jhammen's avatar
jhammen committed
222
    QString tabText(currentEditor()->name());
223
224
225
    if (value) {
        tabText.append("*");
    }
jhammen's avatar
jhammen committed
226
227
228
229
230
231
    int index = ui->tabWidget->currentIndex();
    ui->tabWidget->setTabText(index, tabText);
    ui->actionSaveFile->setEnabled(value);
    setWindowModified(value);
}

jhammen's avatar
jhammen committed
232
233
234
void MainWindow::searchCurrent(const QString &term) {
    EditorWidget *editor = currentEditor();
    if (editor) {
235
        searchWidget->addResults(editor->path(), editor->name(), editor->findAll(term, true), true);
jhammen's avatar
jhammen committed
236
237
238
    }
}

jhammen's avatar
jhammen committed
239
240
241
242
243
244
void MainWindow::searchNext(const QString &term) {
    if (currentEditor()) {
        currentEditor()->findNext(term);
    }
}

jhammen's avatar
jhammen committed
245
void MainWindow::replaceNext(const QString &term, const QString &replace) {
246
247
248
249
    EditorWidget *editor = currentEditor();
    if (editor) {
        editor->replaceNext(term, replace);
        searchWidget->updateResults(editor->path(), editor->findAll(term, true));
jhammen's avatar
jhammen committed
250
251
252
    }
}

jhammen's avatar
jhammen committed
253
254
255
256
257
258
259
260
261
262
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()) {
263
                    searchWidget->addResults(editor->path(), editor->name(), results, true);
jhammen's avatar
jhammen committed
264
265
266
267
                }
            } else {
                QList<EditorLink> results = searchFile(term, fileInfo.filePath());
                if (results.size()) {
268
269
                    searchWidget->addResults(fileInfo.filePath(), fileInfo.fileName(), results,
                                             false);
jhammen's avatar
jhammen committed
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
                }
            }
        } 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
294

295
void MainWindow::showTime(Position pos) {
jhammen's avatar
jhammen committed
296
297
298
    QTime qtime(0, 0, 0);
    qtime = qtime.addMSecs(pos.msec);
    timeWidget.setText(qtime.toString("mm:ss.zzz"));
299
    bbtWidget.setText(QString("%1|%2|%3").arg(pos.bar, 2).arg(pos.beat, 2).arg(pos.tick, 4));
jhammen's avatar
jhammen committed
300
301
302
    transportPosition = pos.frame;
}

303
304
void MainWindow::tabChanged(int i) {
    EditorWidget *editor = qobject_cast<EditorWidget *>(ui->tabWidget->widget(i));
305
    Project &project = projectList.currentProject();
306
    if (editor == nullptr) { // all tabs closed
jhammen's avatar
jhammen committed
307
308
309
        ui->folderTree->clearSelection();
        setWindowTitle(QCoreApplication::applicationName());
        enableFileActions(nullptr);
310
        project.activeFile(nullptr);
jhammen's avatar
jhammen committed
311
312
    } else {
        QTreeWidgetItem *item = findTreeItem(editor->path());
313
        if (item) {
jhammen's avatar
jhammen committed
314
315
316
317
318
319
320
            ui->folderTree->setCurrentItem(item);
        } else {
            ui->folderTree->clearSelection();
        }
        setWindowTitle(QString("%1[*]").arg(editor->name()));
        setWindowModified(editor->isDirty());
        enableFileActions(editor);
321
        project.activeFile(editor->path());
jhammen's avatar
jhammen committed
322
323
324
    }
}

325
326
void MainWindow::closeOtherEditors() {
    if (QAction *action = qobject_cast<QAction *>(sender())) {
327
        int index = action->data().toInt();
328
329
330
331
        for (int i = ui->tabWidget->count() - 1; i >= 0; i--) {
            if (i != index) {
                closeEditorTab(i);
            }
332
333
334
335
        }
    }
}

336
337
338
339
340
341
342
343
344
345
346
void MainWindow::closeOtherOutputs() {
    if (QAction *action = qobject_cast<QAction *>(sender())) {
        int index = action->data().toInt();
        for (int i = ui->outputTabWidget->count() - 1; i >= 0; i--) {
            if (i != index) {
                closeOutputTab(i);
            }
        }
    }
}

jhammen's avatar
jhammen committed
347
348
// ------------ auto slots

349
void MainWindow::on_actionAbout_triggered() {
jhammen's avatar
jhammen committed
350
    QString lang = QLocale::languageToString(QLocale().language());
351
352
    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
353
354
355
    QMessageBox::about(this, "Bipscript-IDE", message);
}

356
void MainWindow::on_actionAPIDocumentation_triggered() {
jhammen's avatar
jhammen committed
357
358
    QStringList supported = {"en", "es"};
    QString lang = QLocale().name().left(2);
359
360
361
    if (supported.contains(lang)) {
        lang = supported[0];
    }
jhammen's avatar
jhammen committed
362
363
364
365
    QString link = QString("http://www.bipscript.org/%1/api/").arg(lang);
    QDesktopServices::openUrl(QUrl(link));
}

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

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

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

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

374
void MainWindow::on_actionNewFile_triggered() {
jhammen's avatar
jhammen committed
375
376
377
378
    QString message("New file in ");
    message.append(*selectedFolder);
    bool ok;
    QString name = QInputDialog::getText(this, "New File", message, QLineEdit::Normal, "", &ok);
379
    if (name.length() > 0) {
jhammen's avatar
jhammen committed
380
381
382
383
        // create file
        QFileInfo fileInfo(*selectedFolder + "/" + name);
        QString path(fileInfo.filePath());
        QFile file(path);
384
        if (file.exists()) {
jhammen's avatar
jhammen committed
385
386
387
388
389
390
391
392
393
394
395
396
397
398
            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);
    }
}

399
void MainWindow::on_actionDeleteFile_triggered() {
jhammen's avatar
jhammen committed
400
401
402
403
404
405
406
    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();
407
    if (choice == QMessageBox::Ok) {
jhammen's avatar
jhammen committed
408
        // remove from tree
409
        FolderTreeItem *item = static_cast<FolderTreeItem *>(findTreeItem(path));
jhammen's avatar
jhammen committed
410
        item->parent()->removeChild(item);
411
412
        // remove from project
        projectList.currentProject().closeFile(path);
jhammen's avatar
jhammen committed
413
414
415
416
417
418
419
        // remove editor if any
        delete findEditor(path);
        // remove file from filesystem
        QFile::remove(path);
    }
}

420
void MainWindow::on_actionExamples_triggered() {
jhammen's avatar
jhammen committed
421
422
    QRegExp versionExp = QRegExp("v([0-9\\.]+)");
    QString versionString = QString(BIPSCRIPT_IDE_VERSION);
423
    if (versionExp.indexIn(versionString) == 0) {
jhammen's avatar
jhammen committed
424
425
426
427
428
429
430
431
432
433
        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);
    }
}

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

436
void MainWindow::on_actionFastForward_triggered() {
jhammen's avatar
jhammen committed
437
438
439
440
    transportPosition = transportPosition + audioConfig.framesPerSecond;
    audioEngine.reposition(transportPosition);
}

jhammen's avatar
jhammen committed
441
442
443
444
445
void MainWindow::on_actionFind_in_Project_triggered() {
    searchWidget->newProjectSearch();
    ui->leftTabWidget->setCurrentWidget(searchWidget);
}

jhammen's avatar
jhammen committed
446
447
448
449
450
/**
 * @brief MainWindow::on_actionRun_triggered
 *
 * action to run script
 */
451
void MainWindow::on_actionRun_triggered() {
jhammen's avatar
jhammen committed
452
    // check if possible
453
    if (!ScriptRun::possible()) {
jhammen's avatar
jhammen committed
454
455
456
457
458
459
460
461
462
463
464
465
        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)));
466
467
468
469
    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
470
471
472
473
474
    // show output area if hidden
    ui->outputDockWidget->show();
    run->start();
}

475
void MainWindow::enableTransportControls(bool enabled) {
jhammen's avatar
jhammen committed
476
477
478
479
480
481
    ui->actionStartStopTransport->setEnabled(enabled);
    ui->actionRewind->setEnabled(enabled);
    ui->actionRestart->setEnabled(enabled);
    ui->actionFastForward->setEnabled(enabled);
}

482
483
void MainWindow::on_actionJackConnect_toggled(bool connected) {
    if (connected) {
jhammen's avatar
jhammen committed
484
485
486
        audioConfig = audioEngine.connect();
        atimer = audioEngine.timerService();
        connect(atimer, SIGNAL(transport(Position)), this, SLOT(showTime(Position)));
487
488
        connect(atimer, SIGNAL(rolling(bool)), ui->actionStartStopTransport,
                SLOT(setChecked(bool)));
jhammen's avatar
jhammen committed
489
490
491
492
493
494
495
496
497
498
        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());
}

499
void MainWindow::on_actionOpenFile_triggered() {
jhammen's avatar
jhammen committed
500
501
502
    QFileDialog fileDialog(this);
    fileDialog.setFileMode(QFileDialog::AnyFile);
    fileDialog.setDirectory(projectList.commonRoot());
503
    if (fileDialog.exec()) {
jhammen's avatar
jhammen committed
504
505
506
507
508
        QString path = fileDialog.selectedFiles()[0];
        createOrOpenEditor(path);
    }
}

509
510
void MainWindow::on_actionOpenProject_triggered() {
    if (saveAllTabs()) {
jhammen's avatar
jhammen committed
511
512
513
514
515
516
        ProjectsDialog projectsDialog(this);
        connect(&projectsDialog, SIGNAL(projectChanged()), this, SLOT(changeProject()));
        projectsDialog.exec();
    }
}

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

519
void MainWindow::on_actionProjectOptions_triggered() {
520
    Project &project = projectList.currentProject();
jhammen's avatar
jhammen committed
521
522
523
524
    QString value = project.executable();
    QString message("Executable Path");
    bool ok;
    QString name = QInputDialog::getText(this, "Options", message, QLineEdit::Normal, value, &ok);
525
    if (name.length() > 0) {
jhammen's avatar
jhammen committed
526
527
528
529
530
531
        // TODO: test existance etc.
        project.executable(name);
        ScriptRun::setExecutable(project.executable());
    }
}

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

534
535
void MainWindow::on_actionRenameFile_triggered() {
    if (saveOrAbandon(currentEditor())) {
jhammen's avatar
jhammen committed
536
537
538
        QString message("New name");
        QString oldPath = currentEditor()->path();
        bool ok;
539
540
541
        QString path =
            QInputDialog::getText(this, "Rename", message, QLineEdit::Normal, oldPath, &ok);
        if (path.length() > 0) {
jhammen's avatar
jhammen committed
542
            QFileInfo fileInfo(path);
543
            if (fileInfo.exists()) {
jhammen's avatar
jhammen committed
544
545
546
547
548
                QString message = QString("Cannot move file, target exists:\n%1").arg(path);
                QMessageBox::warning(this, "File exists", message);
                return;
            }
            QFile oldFile(oldPath);
549
            if (!oldFile.rename(fileInfo.filePath())) {
jhammen's avatar
jhammen committed
550
551
552
553
554
555
556
557
558
559
                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);
560
            if (item) {
jhammen's avatar
jhammen committed
561
562
                item->parent()->removeChild(item);
            }
563
564
565
566
567
            ui->folderTree->clearSelection();
            // update project
            Project &project = projectList.currentProject();
            project.closeFile(oldPath);
            project.openFile(path);
jhammen's avatar
jhammen committed
568
569
            // add new to tree if possible
            QTreeWidgetItem *parent = findTreeItem(fileInfo.path());
570
571
572
            if (parent) {
                FolderTreeItem *newItem =
                    new FolderTreeItem(parent, fileInfo.fileName(), fileInfo.filePath());
jhammen's avatar
jhammen committed
573
574
575
576
577
578
                ui->folderTree->setCurrentItem(newItem);
            }
            // update editor + tab
            currentEditor()->moveFile(file);
            int index = ui->tabWidget->currentIndex();
            QString tabText(currentEditor()->name());
579
580
581
            if (currentEditor()->isDirty()) {
                tabText.append("*");
            }
jhammen's avatar
jhammen committed
582
583
584
585
586
587
588
            ui->tabWidget->setTabText(index, tabText);
            // update main window title
            setWindowTitle(QString("%1[*]").arg(file.name));
        }
    }
}

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

591
void MainWindow::on_actionRewind_triggered() {
jhammen's avatar
jhammen committed
592
593
594
595
596
    int newpos = static_cast<int>(transportPosition - audioConfig.framesPerSecond);
    transportPosition = static_cast<uint>(qMax(newpos, 0));
    audioEngine.reposition(transportPosition);
}

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

jhammen's avatar
jhammen committed
599
600
601
602
603
void MainWindow::on_actionFocusFind_triggered() {
    searchWidget->newFileSearch();
    ui->leftTabWidget->setCurrentWidget(searchWidget);
}

604
605
void MainWindow::on_actionStartStopTransport_toggled(bool start) {
    if (start) {
jhammen's avatar
jhammen committed
606
607
608
609
610
611
612
        audioEngine.transportStart();
    } else {
        audioEngine.transportStop();
    }
    ui->actionJackConnect->setEnabled(!start);
}

613
void MainWindow::on_actionTransportLock_toggled(bool locked) {
jhammen's avatar
jhammen committed
614
615
616
617
618
619
620
    bool enabled = !locked && ui->actionJackConnect->isChecked();
    ui->actionStartStopTransport->setEnabled(enabled);
    ui->actionRewind->setEnabled(enabled);
    ui->actionRestart->setEnabled(enabled);
    ui->actionFastForward->setEnabled(enabled);
}

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

623
void MainWindow::on_tabWidget_customContextMenuRequested(const QPoint &pos) {
624
625
626
627
628
629
630
    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
631
632
// ------------ private methods

633
void MainWindow::enableFileActions(EditorWidget *editor) {
jhammen's avatar
jhammen committed
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
    // 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());
}

651
652
653
654
655
656
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
657
658
659
660
    }
    return nullptr;
}

661
QTreeWidgetItem *MainWindow::findTreeItem(const QString &path) {
jhammen's avatar
jhammen committed
662
663
    QTreeWidgetItemIterator it(ui->folderTree);
    while (*it) {
664
665
666
667
        FolderTreeItem *item = static_cast<FolderTreeItem *>(*it);
        if (item->file().path == path) {
            return item;
        }
jhammen's avatar
jhammen committed
668
669
670
671
672
        ++it;
    }
    return nullptr;
}

673
void MainWindow::loadProject() {
674
    Project &project = projectList.currentProject();
jhammen's avatar
jhammen committed
675
676
677
678
679
    ScriptRun::setExecutable(project.executable());
    for (int i = 0; i < project.folderCount(); ++i) {
        NamedFile &folder = project.folder(i);
        ui->folderTree->addTopLevelItem(toTreeFolder(folder));
    }
680
681
682
683
684
    foreach (const QString &path, project.openFiles()) {
        if (!createOrOpenEditor(path)) {
            project.closeFile(path);
        }
    }
685
686
687
688
    EditorWidget *editor = findEditor(project.activeFile());
    if (editor) {
        ui->tabWidget->setCurrentWidget(editor);
    }
jhammen's avatar
jhammen committed
689
690
    searchWidget->clear();
    ui->leftTabWidget->setCurrentWidget(ui->folderTree);
jhammen's avatar
jhammen committed
691
692
}

693
694
695
696
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
697
            QMessageBox::StandardButton choice = unsavedChangesDialog(editor->path());
698
            if (choice == QMessageBox::Cancel) {
jhammen's avatar
jhammen committed
699
                return false;
700
            } else if (choice == QMessageBox::Save) {
jhammen's avatar
jhammen committed
701
702
703
704
705
706
707
                editor->save();
            }
        }
    }
    return true;
}

708
709
710
711
bool MainWindow::saveOrAbandon(EditorWidget *editor) {
    if (editor->isDirty()) {
        QMessageBox::StandardButton choice = unsavedChangesDialog(editor->path());
        if (choice == QMessageBox::Cancel) {
jhammen's avatar
jhammen committed
712
            return false;
713
714
        } else if (choice == QMessageBox::Save) {
            if (!editor->save()) {
jhammen's avatar
jhammen committed
715
716
717
718
719
720
721
                return false;
            }
        }
    }
    return true;
}

jhammen's avatar
jhammen committed
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
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;
}

749
750
751
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
752
        QString name = ui->outputTabWidget->tabText(i);
753
        if (!killOrDetach(output, name)) {
jhammen's avatar
jhammen committed
754
755
756
757
758
759
            return false;
        }
    }
    return true;
}

760
761
bool MainWindow::killOrDetach(OutputWidget *output, const QString &name) {
    if (output->pid() != 0) {
jhammen's avatar
jhammen committed
762
763
        QString id = QString("%1 pid %2").arg(name).arg(output->pid());
        QString mesg = "%1 is still running.\nDo you wish to kill the process?";
764
765
766
767
        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
768
            return false;
769
        } else if (choice == QMessageBox::Yes) {
jhammen's avatar
jhammen committed
770
771
            output->killScript(true);
        }
772
773
    }
    return true;
jhammen's avatar
jhammen committed
774
775
}

776
FolderTreeItem *MainWindow::toTreeFolder(NamedFile &folder) {
jhammen's avatar
jhammen committed
777
778
779
780
    FolderTreeItem *folderItem = new FolderTreeItem(nullptr, folder.name, folder.path);
    QDir dir(folder.path);
    dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);
    foreach (QFileInfo fileInfo, dir.entryInfoList()) {
781
782
783
784
        if (fileInfo.isFile()) {
            folderItem->addChild(
                new FolderTreeItem(folderItem, fileInfo.fileName(), fileInfo.filePath()));
        } else if (fileInfo.isDir()) {
jhammen's avatar
jhammen committed
785
786
787
788
789
790
791
            NamedFile childFolder(fileInfo.baseName(), fileInfo.filePath());
            folderItem->addChild(toTreeFolder(childFolder));
        }
    }
    return folderItem;
}

792
793
794
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
795
796
797
                                QMessageBox::Save | QMessageBox::No | QMessageBox::Cancel);
}

798
void MainWindow::on_actionWebsite_triggered() {
jhammen's avatar
jhammen committed
799
800
801
    QString link = "http://www.bipscript.org";
    QDesktopServices::openUrl(QUrl(link));
}
802
803
804
805
806
807
808
809

void MainWindow::on_outputTabWidget_customContextMenuRequested(const QPoint &pos) {
    QMenu menu;
    QAction *action = menu.addAction(tr("Close Other Tabs"), this, SLOT(closeOtherOutputs()));
    action->setData(ui->outputTabWidget->tabBar()->tabAt(pos));
    action->setEnabled(ui->outputTabWidget->count() > 1);
    menu.exec(QCursor::pos());
}