mainwindow.cpp 22.8 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
34
35
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent), audioEngine(AudioEngine::instance()), transportPosition(0),
      ui(new Ui::MainWindow), timeWidget(this), bbtWidget(this) {
jhammen's avatar
jhammen committed
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
    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
53
54
55
    // output doc starts hidden
    ui->outputDockWidget->setHidden(true);

jhammen's avatar
jhammen committed
56
57
58
59
    ui->folderTree->setColumnCount(1);

    ProjectList &projectList = ProjectList::getProjectList();
    projectList.load();
60
    if (projectList.currentExists()) {
jhammen's avatar
jhammen committed
61
62
63
64
65
66
67
68
        loadProject();
    } else {
        on_actionOpenProject_triggered();
    }

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

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

71
72
    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
73
74
}

75
MainWindow::~MainWindow() { delete ui; }
jhammen's avatar
jhammen committed
76
77
78

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

79
void MainWindow::closeEvent(QCloseEvent *event) {
jhammen's avatar
jhammen committed
80
81
82
    // save settings
    ProjectList::getProjectList().save();
    // close tabs
83
    if (!saveAllTabs()) {
jhammen's avatar
jhammen committed
84
85
86
        event->ignore();
    }
    // kill running scripts
87
    if (!killAllScripts()) {
jhammen's avatar
jhammen committed
88
89
90
91
92
93
        event->ignore();
    }
}

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

94
void MainWindow::changeProject() {
jhammen's avatar
jhammen committed
95
96
97
    ui->folderTree->clear();
    loadProject();
    // close all edit tabs not still applicable
98
    for (int i = ui->tabWidget->count() - 1; i >= 0; i--) {
jhammen's avatar
jhammen committed
99
        QWidget *widget = ui->tabWidget->widget(i);
100
101
        EditorWidget *editor = static_cast<EditorWidget *>(widget);
        if (findTreeItem(editor->path()) == nullptr) {
jhammen's avatar
jhammen committed
102
103
104
105
106
107
            ui->tabWidget->removeTab(i);
        }
    }
    selectedFolder = nullptr;
}

108
109
110
void MainWindow::closeEditorTab(int index) {
    EditorWidget *editor = qobject_cast<EditorWidget *>(ui->tabWidget->widget(index));
    if (saveOrAbandon(editor)) {
jhammen's avatar
jhammen committed
111
112
113
114
        delete editor;
    }
}

115
116
void MainWindow::closeOutputTab(int index) {
    OutputWidget *output = qobject_cast<OutputWidget *>(ui->outputTabWidget->widget(index));
jhammen's avatar
jhammen committed
117
    QString name = ui->outputTabWidget->tabText(index);
118
    if (killOrDetach(output, name)) {
jhammen's avatar
jhammen committed
119
120
121
122
        delete output;
    }
}

123
void MainWindow::codeError(const QString &path, int line) {
jhammen's avatar
jhammen committed
124
    EditorWidget *editor = findEditor(path);
125
    if (editor) {
jhammen's avatar
jhammen committed
126
127
128
        editor->addError(line);
    }
}
129
void MainWindow::engineDisconnect() {
jhammen's avatar
jhammen committed
130
131
132
133
134
135
136
137
138
    ui->actionStartStopTransport->setChecked(false);
    enableTransportControls(false);
    audioEngine.disconnect();
    timeWidget.setText("00:00.000");
    bbtWidget.setText("--|--|----");
    ui->actionJackConnect->setChecked(false);
    ui->actionJackConnect->setEnabled(true);
}

139
void MainWindow::createOrOpenEditor(NamedFile &file) {
jhammen's avatar
jhammen committed
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
    EditorWidget *editor = findEditor(file.path);
    if (editor) {
        ui->tabWidget->setCurrentWidget(editor);
    } else {
        // new editor for this file
        editor = new EditorWidget(file);
        ui->tabWidget->addTab(editor, file.name);
        ui->tabWidget->setCurrentWidget(editor);
        editor->load();
        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)));
    }
    selectedFolder = nullptr;
    enableFileActions(editor);
}

159
void MainWindow::createOrOpenEditor(QString &path) {
jhammen's avatar
jhammen committed
160
161
162
163
164
    QFileInfo fileInfo(path);
    NamedFile file(fileInfo.fileName(), fileInfo.filePath());
    createOrOpenEditor(file);
}

165
166
EditorWidget *MainWindow::currentEditor() {
    return static_cast<EditorWidget *>(ui->tabWidget->currentWidget());
jhammen's avatar
jhammen committed
167
168
}

169
170
void MainWindow::folderTreeClicked(QTreeWidgetItem *item, int) {
    NamedFile &file = static_cast<FolderTreeItem *>(item)->file();
jhammen's avatar
jhammen committed
171
    QFileInfo fileInfo(file.path);
172
    if (fileInfo.isDir()) {
jhammen's avatar
jhammen committed
173
174
        selectedFolder = &(file.path);
        enableFileActions(nullptr);
175
    } else if (fileInfo.isFile()) {
jhammen's avatar
jhammen committed
176
        createOrOpenEditor(file);
177
178
    } else {
        QMessageBox::information(this, "Unknown Entry", file.path);
jhammen's avatar
jhammen committed
179
180
181
    }
}

182
void MainWindow::handleLink(const QUrl &url) {
jhammen's avatar
jhammen committed
183
184
    QString path(url.path());
    createOrOpenEditor(path);
185
    if (url.hasQuery()) {
jhammen's avatar
jhammen committed
186
187
188
189
190
191
192
        int column = QString(url.query()).toInt();
        currentEditor()->focusLine(url.port(), column);
    } else {
        currentEditor()->focusLine(url.port());
    }
}

193
void MainWindow::markDirty(bool value) {
jhammen's avatar
jhammen committed
194
    QString tabText(currentEditor()->name());
195
196
197
    if (value) {
        tabText.append("*");
    }
jhammen's avatar
jhammen committed
198
199
200
201
202
203
    int index = ui->tabWidget->currentIndex();
    ui->tabWidget->setTabText(index, tabText);
    ui->actionSaveFile->setEnabled(value);
    setWindowModified(value);
}

204
void MainWindow::showTime(Position pos) {
jhammen's avatar
jhammen committed
205
206
207
    QTime qtime(0, 0, 0);
    qtime = qtime.addMSecs(pos.msec);
    timeWidget.setText(qtime.toString("mm:ss.zzz"));
208
    bbtWidget.setText(QString("%1|%2|%3").arg(pos.bar, 2).arg(pos.beat, 2).arg(pos.tick, 4));
jhammen's avatar
jhammen committed
209
210
211
    transportPosition = pos.frame;
}

212
213
214
void MainWindow::tabChanged(int i) {
    EditorWidget *editor = qobject_cast<EditorWidget *>(ui->tabWidget->widget(i));
    if (editor == nullptr) { // all tabs closed
jhammen's avatar
jhammen committed
215
216
217
218
219
        ui->folderTree->clearSelection();
        setWindowTitle(QCoreApplication::applicationName());
        enableFileActions(nullptr);
    } else {
        QTreeWidgetItem *item = findTreeItem(editor->path());
220
        if (item) {
jhammen's avatar
jhammen committed
221
222
223
224
225
226
227
228
229
230
            ui->folderTree->setCurrentItem(item);
        } else {
            ui->folderTree->clearSelection();
        }
        setWindowTitle(QString("%1[*]").arg(editor->name()));
        setWindowModified(editor->isDirty());
        enableFileActions(editor);
    }
}

231
232
void MainWindow::closeOtherEditors() {
    if (QAction *action = qobject_cast<QAction *>(sender())) {
233
        int index = action->data().toInt();
234
235
236
237
        for (int i = ui->tabWidget->count() - 1; i >= 0; i--) {
            if (i != index) {
                closeEditorTab(i);
            }
238
239
240
241
        }
    }
}

jhammen's avatar
jhammen committed
242
243
// ------------ auto slots

244
void MainWindow::on_actionAbout_triggered() {
jhammen's avatar
jhammen committed
245
    QString lang = QLocale::languageToString(QLocale().language());
246
247
    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
248
249
250
    QMessageBox::about(this, "Bipscript-IDE", message);
}

251
void MainWindow::on_actionAPIDocumentation_triggered() {
jhammen's avatar
jhammen committed
252
253
    QStringList supported = {"en", "es"};
    QString lang = QLocale().name().left(2);
254
255
256
    if (supported.contains(lang)) {
        lang = supported[0];
    }
jhammen's avatar
jhammen committed
257
258
259
260
    QString link = QString("http://www.bipscript.org/%1/api/").arg(lang);
    QDesktopServices::openUrl(QUrl(link));
}

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

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

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

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

269
void MainWindow::on_actionNewFile_triggered() {
jhammen's avatar
jhammen committed
270
271
272
273
    QString message("New file in ");
    message.append(*selectedFolder);
    bool ok;
    QString name = QInputDialog::getText(this, "New File", message, QLineEdit::Normal, "", &ok);
274
    if (name.length() > 0) {
jhammen's avatar
jhammen committed
275
276
277
278
        // create file
        QFileInfo fileInfo(*selectedFolder + "/" + name);
        QString path(fileInfo.filePath());
        QFile file(path);
279
        if (file.exists()) {
jhammen's avatar
jhammen committed
280
281
282
283
284
285
286
287
288
289
290
291
292
293
            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);
    }
}

294
void MainWindow::on_actionDeleteFile_triggered() {
jhammen's avatar
jhammen committed
295
296
297
298
299
300
301
    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();
302
    if (choice == QMessageBox::Ok) {
jhammen's avatar
jhammen committed
303
        // remove from tree
304
        FolderTreeItem *item = static_cast<FolderTreeItem *>(findTreeItem(path));
jhammen's avatar
jhammen committed
305
306
307
308
309
310
311
312
        item->parent()->removeChild(item);
        // remove editor if any
        delete findEditor(path);
        // remove file from filesystem
        QFile::remove(path);
    }
}

313
void MainWindow::on_actionExamples_triggered() {
jhammen's avatar
jhammen committed
314
315
    QRegExp versionExp = QRegExp("v([0-9\\.]+)");
    QString versionString = QString(BIPSCRIPT_IDE_VERSION);
316
    if (versionExp.indexIn(versionString) == 0) {
jhammen's avatar
jhammen committed
317
318
319
320
321
322
323
324
325
326
        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);
    }
}

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

329
void MainWindow::on_actionFastForward_triggered() {
jhammen's avatar
jhammen committed
330
331
332
333
334
335
336
337
338
    transportPosition = transportPosition + audioConfig.framesPerSecond;
    audioEngine.reposition(transportPosition);
}

/**
 * @brief MainWindow::on_actionRun_triggered
 *
 * action to run script
 */
339
void MainWindow::on_actionRun_triggered() {
jhammen's avatar
jhammen committed
340
    // check if possible
341
    if (!ScriptRun::possible()) {
jhammen's avatar
jhammen committed
342
343
344
345
346
347
348
349
350
351
352
353
        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)));
354
355
356
357
    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
358
359
360
361
362
    // show output area if hidden
    ui->outputDockWidget->show();
    run->start();
}

363
void MainWindow::enableTransportControls(bool enabled) {
jhammen's avatar
jhammen committed
364
365
366
367
368
369
    ui->actionStartStopTransport->setEnabled(enabled);
    ui->actionRewind->setEnabled(enabled);
    ui->actionRestart->setEnabled(enabled);
    ui->actionFastForward->setEnabled(enabled);
}

370
371
void MainWindow::on_actionJackConnect_toggled(bool connected) {
    if (connected) {
jhammen's avatar
jhammen committed
372
373
374
        audioConfig = audioEngine.connect();
        atimer = audioEngine.timerService();
        connect(atimer, SIGNAL(transport(Position)), this, SLOT(showTime(Position)));
375
376
        connect(atimer, SIGNAL(rolling(bool)), ui->actionStartStopTransport,
                SLOT(setChecked(bool)));
jhammen's avatar
jhammen committed
377
378
379
380
381
382
383
384
385
386
        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());
}

387
void MainWindow::on_actionOpenFile_triggered() {
jhammen's avatar
jhammen committed
388
389
390
391
    ProjectList &projectList = ProjectList::getProjectList();
    QFileDialog fileDialog(this);
    fileDialog.setFileMode(QFileDialog::AnyFile);
    fileDialog.setDirectory(projectList.commonRoot());
392
    if (fileDialog.exec()) {
jhammen's avatar
jhammen committed
393
394
395
396
397
        QString path = fileDialog.selectedFiles()[0];
        createOrOpenEditor(path);
    }
}

398
399
void MainWindow::on_actionOpenProject_triggered() {
    if (saveAllTabs()) {
jhammen's avatar
jhammen committed
400
401
402
403
404
405
        ProjectsDialog projectsDialog(this);
        connect(&projectsDialog, SIGNAL(projectChanged()), this, SLOT(changeProject()));
        projectsDialog.exec();
    }
}

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

408
void MainWindow::on_actionProjectOptions_triggered() {
jhammen's avatar
jhammen committed
409
410
411
412
413
414
    ProjectList &projects = ProjectList::getProjectList();
    Project &project = projects.currentProject();
    QString value = project.executable();
    QString message("Executable Path");
    bool ok;
    QString name = QInputDialog::getText(this, "Options", message, QLineEdit::Normal, value, &ok);
415
    if (name.length() > 0) {
jhammen's avatar
jhammen committed
416
417
418
419
420
421
        // TODO: test existance etc.
        project.executable(name);
        ScriptRun::setExecutable(project.executable());
    }
}

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

424
425
void MainWindow::on_actionRenameFile_triggered() {
    if (saveOrAbandon(currentEditor())) {
jhammen's avatar
jhammen committed
426
427
428
        QString message("New name");
        QString oldPath = currentEditor()->path();
        bool ok;
429
430
431
        QString path =
            QInputDialog::getText(this, "Rename", message, QLineEdit::Normal, oldPath, &ok);
        if (path.length() > 0) {
jhammen's avatar
jhammen committed
432
            QFileInfo fileInfo(path);
433
            if (fileInfo.exists()) {
jhammen's avatar
jhammen committed
434
435
436
437
438
                QString message = QString("Cannot move file, target exists:\n%1").arg(path);
                QMessageBox::warning(this, "File exists", message);
                return;
            }
            QFile oldFile(oldPath);
439
            if (!oldFile.rename(fileInfo.filePath())) {
jhammen's avatar
jhammen committed
440
441
442
443
444
445
446
447
448
449
                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);
450
            if (item) {
jhammen's avatar
jhammen committed
451
452
453
454
455
                item->parent()->removeChild(item);
            }
            // add new to tree if possible
            QTreeWidgetItem *parent = findTreeItem(fileInfo.path());
            ui->folderTree->clearSelection();
456
457
458
            if (parent) {
                FolderTreeItem *newItem =
                    new FolderTreeItem(parent, fileInfo.fileName(), fileInfo.filePath());
jhammen's avatar
jhammen committed
459
460
461
462
463
464
                ui->folderTree->setCurrentItem(newItem);
            }
            // update editor + tab
            currentEditor()->moveFile(file);
            int index = ui->tabWidget->currentIndex();
            QString tabText(currentEditor()->name());
465
466
467
            if (currentEditor()->isDirty()) {
                tabText.append("*");
            }
jhammen's avatar
jhammen committed
468
469
470
471
472
473
474
            ui->tabWidget->setTabText(index, tabText);
            // update main window title
            setWindowTitle(QString("%1[*]").arg(file.name));
        }
    }
}

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

477
void MainWindow::on_actionRewind_triggered() {
jhammen's avatar
jhammen committed
478
479
480
481
482
    int newpos = static_cast<int>(transportPosition - audioConfig.framesPerSecond);
    transportPosition = static_cast<uint>(qMax(newpos, 0));
    audioEngine.reposition(transportPosition);
}

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

485
486
void MainWindow::on_actionStartStopTransport_toggled(bool start) {
    if (start) {
jhammen's avatar
jhammen committed
487
488
489
490
491
492
493
        audioEngine.transportStart();
    } else {
        audioEngine.transportStop();
    }
    ui->actionJackConnect->setEnabled(!start);
}

494
void MainWindow::on_actionTransportLock_toggled(bool locked) {
jhammen's avatar
jhammen committed
495
496
497
498
499
500
501
    bool enabled = !locked && ui->actionJackConnect->isChecked();
    ui->actionStartStopTransport->setEnabled(enabled);
    ui->actionRewind->setEnabled(enabled);
    ui->actionRestart->setEnabled(enabled);
    ui->actionFastForward->setEnabled(enabled);
}

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

504
void MainWindow::on_tabWidget_customContextMenuRequested(const QPoint &pos) {
505
506
507
508
509
510
511
    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
512
513
// ------------ private methods

514
void MainWindow::enableFileActions(EditorWidget *editor) {
jhammen's avatar
jhammen committed
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
    // 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());
}

532
533
534
535
536
537
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
538
539
540
541
    }
    return nullptr;
}

542
QTreeWidgetItem *MainWindow::findTreeItem(const QString &path) {
jhammen's avatar
jhammen committed
543
544
    QTreeWidgetItemIterator it(ui->folderTree);
    while (*it) {
545
546
547
548
        FolderTreeItem *item = static_cast<FolderTreeItem *>(*it);
        if (item->file().path == path) {
            return item;
        }
jhammen's avatar
jhammen committed
549
550
551
552
553
        ++it;
    }
    return nullptr;
}

554
void MainWindow::loadProject() {
jhammen's avatar
jhammen committed
555
556
557
558
559
560
561
562
563
    ProjectList &projects = ProjectList::getProjectList();
    Project &project = projects.currentProject();
    ScriptRun::setExecutable(project.executable());
    for (int i = 0; i < project.folderCount(); ++i) {
        NamedFile &folder = project.folder(i);
        ui->folderTree->addTopLevelItem(toTreeFolder(folder));
    }
}

564
565
566
567
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
568
            QMessageBox::StandardButton choice = unsavedChangesDialog(editor->path());
569
            if (choice == QMessageBox::Cancel) {
jhammen's avatar
jhammen committed
570
                return false;
571
            } else if (choice == QMessageBox::Save) {
jhammen's avatar
jhammen committed
572
573
574
575
576
577
578
                editor->save();
            }
        }
    }
    return true;
}

579
580
581
582
bool MainWindow::saveOrAbandon(EditorWidget *editor) {
    if (editor->isDirty()) {
        QMessageBox::StandardButton choice = unsavedChangesDialog(editor->path());
        if (choice == QMessageBox::Cancel) {
jhammen's avatar
jhammen committed
583
            return false;
584
585
        } else if (choice == QMessageBox::Save) {
            if (!editor->save()) {
jhammen's avatar
jhammen committed
586
587
588
589
590
591
592
                return false;
            }
        }
    }
    return true;
}

593
594
595
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
596
        QString name = ui->outputTabWidget->tabText(i);
597
        if (!killOrDetach(output, name)) {
jhammen's avatar
jhammen committed
598
599
600
601
602
603
            return false;
        }
    }
    return true;
}

604
605
bool MainWindow::killOrDetach(OutputWidget *output, const QString &name) {
    if (output->pid() != 0) {
jhammen's avatar
jhammen committed
606
607
        QString id = QString("%1 pid %2").arg(name).arg(output->pid());
        QString mesg = "%1 is still running.\nDo you wish to kill the process?";
608
609
610
611
        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
612
            return false;
613
        } else if (choice == QMessageBox::Yes) {
jhammen's avatar
jhammen committed
614
615
            output->killScript(true);
        }
616
617
    }
    return true;
jhammen's avatar
jhammen committed
618
619
}

620
FolderTreeItem *MainWindow::toTreeFolder(NamedFile &folder) {
jhammen's avatar
jhammen committed
621
622
623
624
    FolderTreeItem *folderItem = new FolderTreeItem(nullptr, folder.name, folder.path);
    QDir dir(folder.path);
    dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);
    foreach (QFileInfo fileInfo, dir.entryInfoList()) {
625
626
627
628
        if (fileInfo.isFile()) {
            folderItem->addChild(
                new FolderTreeItem(folderItem, fileInfo.fileName(), fileInfo.filePath()));
        } else if (fileInfo.isDir()) {
jhammen's avatar
jhammen committed
629
630
631
632
633
634
635
            NamedFile childFolder(fileInfo.baseName(), fileInfo.filePath());
            folderItem->addChild(toTreeFolder(childFolder));
        }
    }
    return folderItem;
}

636
637
638
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
639
640
641
                                QMessageBox::Save | QMessageBox::No | QMessageBox::Cancel);
}

642
void MainWindow::on_actionWebsite_triggered() {
jhammen's avatar
jhammen committed
643
644
645
    QString link = "http://www.bipscript.org";
    QDesktopServices::openUrl(QUrl(link));
}