1 /*******************************************************************
2 
3 Part of the Fritzing project - http://fritzing.org
4 Copyright (c) 2007-2015 Fachhochschule Potsdam - http://fh-potsdam.de
5 
6 Fritzing is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10 
11 Fritzing is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with Fritzing.  If not, see <http://www.gnu.org/licenses/>.
18 
19 ********************************************************************
20 
21 $Revision: 6904 $:
22 $Author: irascibl@gmail.com $:
23 $Date: 2013-02-26 16:26:03 +0100 (Di, 26. Feb 2013) $
24 
25 ********************************************************************/
26 
27 /////////////////////////////////////////////
28 //
29 // TODO
30 //
31 //  integrate dirty
32 //  remove old program window
33 //  enable all buttons, and give error messages (i.e. where is IDE)
34 
35 
36 #include "programwindow.h"
37 #include "highlighter.h"
38 #include "syntaxer.h"
39 #include "programtab.h"
40 #include "platformarduino.h"
41 #include "platformpicaxe.h"
42 
43 #include "../debugdialog.h"
44 #include "../waitpushundostack.h"
45 #include "../utils/folderutils.h"
46 
47 #include <QFileInfoList>
48 #include <QFileInfo>
49 #include <QRegExp>
50 #include <QSettings>
51 #include <QFontMetrics>
52 #include <QTextStream>
53 #include <QLayout>
54 #include <QMenu>
55 #include <QMenuBar>
56 #include <QApplication>
57 #include <QKeyEvent>
58 #include <QCloseEvent>
59 #include <QPrinter>
60 #include <QPrintDialog>
61 #include <QtSerialPort/qserialportinfo.h>
62 #include <QtSerialPort/qserialport.h>
63 
64 ///////////////////////////////////////////////
65 
PTabWidget(QWidget * parent)66 PTabWidget::PTabWidget(QWidget * parent) : QTabWidget(parent) {
67         m_lastTabIndex = -1;
68 
69         connect(tabBar(), SIGNAL(currentChanged(int)), this, SLOT(tabChanged(int)));
70 }
71 
tabBar()72 QTabBar * PTabWidget::tabBar() {
73 	return QTabWidget::tabBar();
74 }
75 
tabChanged(int index)76 void PTabWidget::tabChanged(int index) {
77     // Hide the close button on the old tab
78     if (m_lastTabIndex >= 0) {
79         QAbstractButton *tabButton = qobject_cast<QAbstractButton *>(tabBar()->tabButton(m_lastTabIndex, QTabBar::LeftSide));
80         if (!tabButton) {
81 			tabButton = qobject_cast<QAbstractButton *>(tabBar()->tabButton(m_lastTabIndex, QTabBar::RightSide));
82         }
83 
84         if (tabButton) {
85             tabButton->hide();
86         }
87     }
88 
89     m_lastTabIndex = index;
90 
91     // Show the close button on the new tab
92     if (m_lastTabIndex >= 0) {
93         QAbstractButton *tabButton = qobject_cast<QAbstractButton *>(tabBar()->tabButton(m_lastTabIndex, QTabBar::LeftSide));
94         if (!tabButton) {
95             tabButton = qobject_cast<QAbstractButton *>(tabBar()->tabButton(m_lastTabIndex, QTabBar::RightSide));
96         }
97         if (tabButton) {
98             tabButton->show();
99         }
100     }
101 }
102 
103 ///////////////////////////////////////////////
104 
105 static int UntitledIndex = 1;
106 QList<Platform *> ProgramWindow::m_platforms;
107 QString ProgramWindow::NoBoardName;
108 
ProgramWindow(QWidget * parent)109 ProgramWindow::ProgramWindow(QWidget *parent)
110     : FritzingWindow("", untitledFileCount(), "", parent)
111 {
112     QFile styleSheet(":/resources/styles/programwindow.qss");
113 
114     this->setObjectName("programmingWindow");
115     if (!styleSheet.open(QIODevice::ReadOnly)) {
116         qWarning("Unable to open :/resources/styles/programwindow.qss");
117     } else {
118         QString ss = styleSheet.readAll();
119 #ifdef Q_OS_MAC
120                 int paneLoc = 4;
121                 int tabBarLoc = 0;
122 #else
123                 int paneLoc = -1;
124                 int tabBarLoc = 5;
125 #endif
126                 ss = ss.arg(paneLoc).arg(tabBarLoc);
127         this->setStyleSheet(ss);
128     }
129 
130     if (m_platforms.count() == 0) {
131         initPlatforms();
132 	}
133 
134     if (NoBoardName.isEmpty()) {
135         NoBoardName = tr("No boards available");
136     }
137 
138 	m_savingProgramTab = NULL;
139 	UntitledIndex--;						// incremented by FritzingWindow
140 	ProgramWindow::setTitle();				// set to something weird by FritzingWindow
141 }
142 
~ProgramWindow()143 ProgramWindow::~ProgramWindow()
144 {
145 }
146 
setup()147 void ProgramWindow::setup()
148 {
149     if (parentWidget() == NULL) {
150         resize(500,700);
151         setAttribute(Qt::WA_DeleteOnClose, true);
152     }
153 
154     QFrame * mainFrame =  new QFrame(this);
155 
156 	QFrame * headerFrame = createHeader();
157 	QFrame * centerFrame = createCenter();
158 
159 	layout()->setMargin(0);
160 	layout()->setSpacing(0);
161 
162 	QGridLayout *layout = new QGridLayout(mainFrame);
163 	layout->setMargin(0);
164 	layout->setSpacing(0);
165 	layout->addWidget(headerFrame,0,0);
166 	layout->addWidget(centerFrame,1,0);
167 
168 	setCentralWidget(mainFrame);
169 
170     setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
171 
172 	QSettings settings;
173 	if (!settings.value("programwindow/state").isNull()) {
174 		restoreState(settings.value("programwindow/state").toByteArray());
175 	}
176 	if (!settings.value("programwindow/geometry").isNull()) {
177 		restoreGeometry(settings.value("programwindow/geometry").toByteArray());
178 	}
179 
180 	installEventFilter(this);
181 }
182 
initMenus(QMenuBar * menubar)183 void ProgramWindow::initMenus(QMenuBar * menubar) {
184     QAction *currentAction;
185 
186     m_editMenu = menubar->addMenu(tr("&Edit"));
187 
188     m_undoAction = new QAction(tr("Undo"), this);
189     m_undoAction->setShortcuts(QKeySequence::Undo);
190     m_undoAction->setEnabled(false);
191     connect(m_undoAction, SIGNAL(triggered()), this, SLOT(undo()));
192     m_editMenu->addAction(m_undoAction);
193 
194     m_redoAction = new QAction(tr("Redo"), this);
195     m_redoAction->setShortcuts(QKeySequence::Redo);
196     m_redoAction->setEnabled(false);
197     connect(m_redoAction, SIGNAL(triggered()), this, SLOT(redo()));
198     m_editMenu->addAction(m_redoAction);
199 
200     m_editMenu->addSeparator();
201 
202     m_cutAction = new QAction(tr("&Cut"), this);
203     m_cutAction->setShortcut(QKeySequence::Cut);
204     m_cutAction->setStatusTip(tr("Cut selection"));
205     m_cutAction->setEnabled(false);
206     connect(m_cutAction, SIGNAL(triggered()), this, SLOT(cut()));
207     m_editMenu->addAction(m_cutAction);
208 
209     m_copyAction = new QAction(tr("&Copy"), this);
210     m_copyAction->setShortcut(QKeySequence::Copy);
211     m_copyAction->setStatusTip(tr("Copy selection"));
212     m_copyAction->setEnabled(false);
213     connect(m_copyAction, SIGNAL(triggered()), this, SLOT(copy()));
214     m_editMenu->addAction(m_copyAction);
215 
216     m_pasteAction = new QAction(tr("&Paste"), this);
217     m_pasteAction->setShortcut(QKeySequence::Paste);
218     m_pasteAction->setStatusTip(tr("Paste clipboard contents"));
219     // TODO: Check clipboard status and disable appropriately here
220     connect(m_pasteAction, SIGNAL(triggered()), this, SLOT(paste()));
221     m_editMenu->addAction(m_pasteAction);
222 
223     m_editMenu->addSeparator();
224 
225     m_selectAction = new QAction(tr("&Select All"), this);
226     m_selectAction->setShortcut(QKeySequence::SelectAll);
227     m_selectAction->setStatusTip(tr("Select all text"));
228     connect(m_selectAction, SIGNAL(triggered()), this, SLOT(selectAll()));
229     m_editMenu->addAction(m_selectAction);
230 
231     m_editMenu->addSeparator();
232 
233     m_preferencesAction = new QAction(tr("&Preferences..."), this);
234     m_preferencesAction->setStatusTip(tr("Show the application's about box"));
235     connect(m_preferencesAction, SIGNAL(triggered()), QApplication::instance(), SLOT(preferences()));
236     m_editMenu->addAction(m_preferencesAction);
237 
238     m_programMenu = menubar->addMenu(tr("&Code"));
239 
240     m_newAction = new QAction(tr("&New Tab"), this);
241     m_newAction->setShortcut(QKeySequence::AddTab);
242     m_newAction->setStatusTip(tr("Create a new program tab"));
243     connect(m_newAction, SIGNAL(triggered()), this, SLOT(addTab()));
244     m_programMenu->addAction(m_newAction);
245 
246     m_openAction = new QAction(tr("&Import Code..."), this);
247     m_openAction->setShortcut(tr("Alt+Ctrl+I"));
248     m_openAction->setStatusTip(tr("Import a program from a file"));
249     connect(m_openAction, SIGNAL(triggered()), this, SLOT(loadProgramFile()));
250     m_programMenu->addAction(m_openAction);
251 
252     m_saveAction = new QAction(tr("&Save Tab"), this);
253     m_saveAction->setShortcut(tr("Alt+Ctrl+S"));
254     m_saveAction->setStatusTip(tr("Save the current program tab"));
255     connect(m_saveAction, SIGNAL(triggered()), this, SLOT(saveCurrentTab()));
256     m_programMenu->addAction(m_saveAction);
257 
258     currentAction = new QAction(tr("&Rename Tab"), this);
259     currentAction->setShortcut(tr("Alt+Ctrl+R"));
260     currentAction->setStatusTip(tr("Rename the current program tab"));
261     connect(currentAction, SIGNAL(triggered()), this, SLOT(rename()));
262     m_programMenu->addAction(currentAction);
263 
264     currentAction = new QAction(tr("Close Tab"), this);
265     currentAction->setShortcut(tr("Alt+Ctrl+W"));
266     currentAction->setStatusTip(tr("Remove the current program tab from the sketch"));
267     connect(currentAction, SIGNAL(triggered()), this, SLOT(closeCurrentTab()));
268     m_programMenu->addAction(currentAction);
269 
270     m_programMenu->addSeparator();
271 
272     m_platformMenu = new QMenu(tr("Platform"), this);
273     m_programMenu->addMenu(m_platformMenu);
274     QSettings settings;
275     QString currentPlatform = settings.value("programwindow/platform", "").toString();
276     QList<Platform *> platforms = getAvailablePlatforms();
277     m_platformActionGroup = new QActionGroup(this);
278     foreach (Platform * platform, platforms) {
279         currentAction = new QAction(platform->getName(), this);
280         currentAction->setCheckable(true);
281         m_platformActions.insert(platform, currentAction);
282         m_platformActionGroup->addAction(currentAction);
283         m_platformMenu->addAction(currentAction);
284         if (!currentPlatform.isEmpty()) {
285             if (platform->getName().compare(currentPlatform) == 0) {
286 				currentAction->setChecked(true);
287 			}
288 		}
289     }
290     connect(m_platformMenu, SIGNAL(triggered(QAction*)), this, SLOT(setPlatform(QAction*)));
291 
292     m_boardMenu = new QMenu(tr("Board"), this);
293     m_programMenu->addMenu(m_boardMenu);
294     m_boardActionGroup = new QActionGroup(this);
295     updateBoards();
296     connect(m_boardMenu, SIGNAL(triggered(QAction*)), this, SLOT(setBoard(QAction*)));
297 
298     m_serialPortMenu = new QMenu(tr("Port"), this);
299     m_programMenu->addMenu(m_serialPortMenu);
300     m_serialPortActionGroup = new QActionGroup(this);
301     updateSerialPorts();
302     connect(m_serialPortMenu, SIGNAL(triggered(QAction*)), this, SLOT(setPort(QAction*)));
303     connect(m_serialPortMenu, SIGNAL(aboutToShow()), this, SLOT(updateSerialPorts()), Qt::DirectConnection);
304 
305     m_programMenu->addSeparator();
306 
307     m_monitorAction = new QAction(tr("Serial Monitor"), this);
308     m_monitorAction->setShortcut(tr("Ctrl+M"));
309     m_monitorAction->setStatusTip(tr("Monitor the serial port communication"));
310     m_monitorAction->setEnabled(false);
311     connect(m_monitorAction, SIGNAL(triggered()), this, SLOT(serialMonitor()));
312     m_programMenu->addAction(m_monitorAction);
313 
314     m_programAction = new QAction(tr("Upload"), this);
315     m_programAction->setShortcut(tr("Ctrl+U"));
316     m_programAction->setStatusTip(tr("Upload the current program onto a microcontroller"));
317     m_programAction->setEnabled(false);
318     connect(m_programAction, SIGNAL(triggered()), this, SLOT(sendProgram()));
319     m_programMenu->addAction(m_programAction);
320 
321     m_viewMenu = menubar->addMenu(tr("&View"));
322     foreach (QAction * action, m_viewMenuActions) {
323         m_viewMenu->addAction(action);
324     }
325 
326     addTab(); // the initial ProgramTab must be created after all actions are set up
327 }
328 
showMenus(bool show)329 void ProgramWindow::showMenus(bool show) {
330     if (m_editMenu) {
331         m_editMenu->menuAction()->setVisible(show);
332         m_editMenu->setEnabled(show);
333         m_undoAction->setEnabled(show);
334         m_redoAction->setEnabled(show);
335         m_cutAction->setEnabled(show);
336         m_copyAction->setEnabled(show);
337         m_pasteAction->setEnabled(show);
338         m_selectAction->setEnabled(show);
339     }
340     if (m_programMenu) {
341         m_programMenu->menuAction()->setVisible(show);
342         m_programMenu->setEnabled(show);
343     }
344     if (m_viewMenu) {
345         m_viewMenu->menuAction()->setVisible(show);
346         m_viewMenu->setEnabled(show);
347     }
348 }
349 
createViewMenuActions(QList<QAction * > & actions)350 void ProgramWindow::createViewMenuActions(QList<QAction *> & actions) {
351     m_viewMenuActions = actions;
352 }
353 
linkFiles(const QList<LinkedFile * > & linkedFiles,const QString & alternativePath)354 void ProgramWindow::linkFiles(const QList<LinkedFile *> & linkedFiles, const QString & alternativePath) {
355     if (linkedFiles.isEmpty()) return;
356 
357     bool firstTime = true;
358     foreach (LinkedFile * linkedFile, linkedFiles) {
359         ProgramTab * programTab = NULL;
360         if (firstTime) {
361             firstTime = false;
362             programTab = indexWidget(0);
363         }
364         else {
365             programTab = addTab();
366         }
367         QDir dir(alternativePath);
368         QFileInfo fileInfo(linkedFile->linkedFilename);
369         programTab->loadProgramFile(linkedFile->linkedFilename, dir.absoluteFilePath(fileInfo.fileName()), false);
370 		if ((linkedFile->fileFlags & LinkedFile::InBundleFlag) && ((linkedFile->fileFlags & LinkedFile::ReadOnlyFlag) == 0)) {
371 			if (linkedFile->fileFlags & LinkedFile::SameMachineFlag) {
372 				programTab->appendToConsole(tr("File '%1' was restored from the .fzz file; the local copy was not found.").arg(fileInfo.fileName()));
373 			}
374 			else {
375 				programTab->appendToConsole(tr("File '%1' was restored from the .fzz file; save a local copy to work with an external editor.").arg(fileInfo.fileName()));
376 			}
377 		}
378         if (hasPlatform(linkedFile->platform)) {
379             programTab->setPlatform(linkedFile->platform, false);
380 		}
381 		else {
382             linkedFile->platform.clear();
383 		}
384     }
385 }
386 
createHeader()387 QFrame * ProgramWindow::createHeader() {
388 	QFrame * headerFrame = new QFrame();
389 	headerFrame->setSizePolicy(QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Fixed));
390 	headerFrame->setObjectName("header");
391 
392 	return headerFrame;
393 }
394 
createCenter()395 QFrame * ProgramWindow::createCenter() {
396 
397     QFrame * centerFrame = new QFrame(this);
398 	centerFrame->setObjectName("center");
399 
400 	m_tabWidget = new PTabWidget(centerFrame);
401 	m_tabWidget->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding);
402 	m_tabWidget->setMovable(true);
403     m_tabWidget->setTabsClosable(true);
404 	m_tabWidget->setUsesScrollButtons(false);
405 	m_tabWidget->setElideMode(Qt::ElideLeft);
406      connect(m_tabWidget, SIGNAL(tabCloseRequested(int)), this, SLOT(closeTab(int)));
407 
408     //addTab();
409 
410 	QGridLayout *tabLayout = new QGridLayout(m_tabWidget);
411 	tabLayout->setMargin(0);
412 	tabLayout->setSpacing(0);
413 
414 	QGridLayout *mainLayout = new QGridLayout(centerFrame);
415 	mainLayout->setMargin(0);
416 	mainLayout->setSpacing(0);
417 	mainLayout->addWidget(m_tabWidget,0,0,1,1);
418 
419 	return centerFrame;
420 }
421 
cleanUp()422 void ProgramWindow::cleanUp() {
423 }
424 
425 /**
426  * eventFilter here is used to catch the keyboard shortcuts that trigger the close event
427  * for ProgramWindow. If there are more than one tab widget the standard close shortcut
428  * should only close the current tab instead of closing the whole window. Every other
429  * case is ignored and handled by closeEvent() like normal.
430  */
eventFilter(QObject * object,QEvent * event)431 bool ProgramWindow::eventFilter(QObject * object, QEvent * event) {
432         if (object == this && event->type() == QEvent::ShortcutOverride) {
433                 QKeyEvent *keyEvent = dynamic_cast<QKeyEvent*>(event);
434                 if(keyEvent && keyEvent->matches(QKeySequence::Close) && m_tabWidget->count() > 1 ) {
435                         return true;
436                 }
437         }
438         return QMainWindow::eventFilter(object, event);
439 }
440 
441 /**
442  * Reimplement closeEvent to save any modified documents before closing.
443  */
closeEvent(QCloseEvent * event)444 void ProgramWindow::closeEvent(QCloseEvent *event) {
445 	bool discard;
446 	if(beforeClosing(true, discard)) {
447 		cleanUp();
448 		QMainWindow::closeEvent(event);
449 		emit closed();
450 	} else {
451 		event->ignore();
452 	}
453 
454 	QSettings settings;
455 	settings.setValue("programwindow/state",saveState());
456 	settings.setValue("programwindow/geometry",saveGeometry());
457 }
458 
untitledFileName()459 const QString ProgramWindow::untitledFileName() {
460 	return "Untitled";
461 }
462 
fileExtension()463 const QString ProgramWindow::fileExtension() {
464 	return "";
465 }
466 
defaultSaveFolder()467 const QString ProgramWindow::defaultSaveFolder() {
468 	return FolderUtils::openSaveFolder();
469 }
470 
event(QEvent * e)471 bool ProgramWindow::event(QEvent * e) {
472 	switch (e->type()) {
473 		case QEvent::WindowActivate:
474 			emit changeActivationSignal(true, this);
475 			break;
476 		case QEvent::WindowDeactivate:
477 			emit changeActivationSignal(false, this);
478 			break;
479 		default:
480 			break;
481 	}
482 	return FritzingWindow::event(e);
483 }
484 
untitledFileCount()485 int & ProgramWindow::untitledFileCount() {
486 	return UntitledIndex;
487 }
488 
setTitle()489 void ProgramWindow::setTitle() {
490     setWindowTitle(tr("Code Window"));
491 }
492 
setTitle(const QString & filename)493 void ProgramWindow::setTitle(const QString & filename) {
494         setWindowTitle(tr("Code Window - %1").arg(filename));
495 }
496 
497 /**
498  * Create and open a new tab within the PTabWidget child.
499  * This function handled connecting all the appropriate signals
500  * and setting an appropriate filename.
501  */
addTab()502 ProgramTab * ProgramWindow::addTab() {
503     QString name = (UntitledIndex == 1) ? untitledFileName() : tr("%1%2").arg(untitledFileName()).arg(UntitledIndex);
504     ProgramTab * programTab = new ProgramTab(name, m_tabWidget);
505     connect(programTab, SIGNAL(wantToSave(int)), this, SLOT(tabSave(int)));
506     connect(programTab, SIGNAL(wantToSaveAs(int)), this, SLOT(tabSaveAs(int)));
507     connect(programTab, SIGNAL(wantToRename(int)), this, SLOT(tabRename(int)));
508     connect(programTab, SIGNAL(wantToDelete(int, bool)), this, SLOT(tabDelete(int, bool)), Qt::DirectConnection);
509     connect(programTab,
510         SIGNAL(programWindowUpdateRequest(bool, bool, bool, bool, bool, bool, Platform *, const QString &, const QString &, const QString &)),
511 		this,
512         SLOT(updateMenu(bool, bool, bool, bool, bool, bool, Platform *, const QString &, const QString &, const QString &)));
513 	int ix = m_tabWidget->addTab(programTab, name);
514     m_tabWidget->setCurrentIndex(ix);
515     programTab->initMenus();
516 	UntitledIndex++;
517 
518 	return programTab;
519 }
520 
521 /**
522  * A function for closing the current displayed tab.
523  * I'm using a ProgramWindow method instead of calling deleteTab()
524  * directly as I believe it's more apropos.
525  */
closeCurrentTab()526 void ProgramWindow::closeCurrentTab() {
527         closeTab(m_tabWidget->currentIndex());
528 }
529 
closeTab(int index)530 void ProgramWindow::closeTab(int index) {
531         ProgramTab * pTab = indexWidget(index);
532         if (pTab) {
533             emit linkToProgramFile(pTab->filename(), NULL, false, true);
534                 pTab->deleteTab();
535         }
536 }
537 
538 /**
539  * This slot is for updating the tab-dependent menu items.
540  *  - program
541  *  - undo/redo
542  *  - cut/copy
543  */
updateMenu(bool programEnable,bool undoEnable,bool redoEnable,bool cutEnable,bool copyEnable,bool pasteEnable,Platform * platform,const QString & port,const QString & board,const QString & filename)544 void ProgramWindow::updateMenu(bool programEnable, bool undoEnable, bool redoEnable, bool cutEnable, bool copyEnable, bool pasteEnable,
545                               Platform* platform, const QString & port, const QString & board, const QString & filename)
546 {
547 	ProgramTab * programTab = currentWidget();
548 	m_saveAction->setEnabled(programTab->isModified());
549     m_monitorAction->setEnabled(!port.isEmpty());
550     m_programAction->setEnabled(programEnable);
551     m_undoAction->setEnabled(undoEnable);
552     m_redoAction->setEnabled(redoEnable);
553     m_cutAction->setEnabled(cutEnable);
554     m_copyAction->setEnabled(copyEnable);
555     m_pasteAction->setEnabled(pasteEnable);
556     QAction *lang = m_platformActions.value(platform);
557     if (lang) {
558         lang->setChecked(true);
559     }
560     QAction *portAction = m_portActions.value(port);
561     if (portAction) {
562         portAction->setChecked(true);
563     }
564     QAction *boardAction = m_boardActions.value(board);
565     if (boardAction) {
566         boardAction->setChecked(true);
567     }
568 
569     setTitle(filename);
570 }
571 
setPlatform(QAction * action)572 void ProgramWindow::setPlatform(QAction* action) {
573     currentWidget()->setPlatform(action->text());
574 }
575 
setPort(QAction * action)576 void ProgramWindow::setPort(QAction* action) {
577     currentWidget()->setPort(action->text());
578 }
579 
setBoard(QAction * action)580 void ProgramWindow::setBoard(QAction* action) {
581     currentWidget()->setBoard(action->text());
582 }
583 
beforeClosing(bool showCancel,bool & discard)584 bool ProgramWindow::beforeClosing(bool showCancel, bool & discard) {
585 	discard = false;
586 	for (int i = 0; i < m_tabWidget->count(); i++) {
587 		if (!beforeClosingTab(i, showCancel)) {
588 			return false;
589 		}
590 	}
591 
592 	return true;
593 }
594 
beforeClosingTab(int index,bool showCancel)595 bool ProgramWindow::beforeClosingTab(int index, bool showCancel)
596 {
597 	ProgramTab * programTab = indexWidget(index);
598 	if (programTab == NULL) return true;
599 
600 	if (!programTab->isModified()) return true;
601 
602 	QMessageBox::StandardButton reply = beforeClosingMessage(programTab->filename(), showCancel);
603 	if (reply == QMessageBox::Save) {
604 		return prepSave(programTab, false);
605 	}
606 
607 	if (reply == QMessageBox::Discard) {
608 		return true;
609 	}
610 
611 	return false;
612 }
613 
print()614 void ProgramWindow::print() {
615 #ifndef QT_NO_PRINTER
616     QPrinter printer(QPrinter::HighResolution);
617     QPrintDialog printDialog(&printer, this);
618     if (printDialog.exec() == QDialog::Accepted) {
619         currentWidget()->print(printer);
620     }
621 #endif
622 }
623 
624 
625 // overrides MainWindow::saveAsAux
saveAsAux(const QString & fileName)626 bool ProgramWindow::saveAsAux(const QString & fileName) {
627 	if (!m_savingProgramTab) return false;
628 
629     bool result = m_savingProgramTab->save(fileName);
630 	m_savingProgramTab = NULL;
631 	return result;
632 }
633 
tabDelete(int index,bool deleteFile)634 void ProgramWindow::tabDelete(int index, bool deleteFile) {
635 	ProgramTab * programTab = indexWidget(index);
636     QString fname = programTab->filename();
637 	m_tabWidget->removeTab(index);
638 	if (m_tabWidget->count() == 0) {
639 		addTab();
640 	}
641 
642 	if (deleteFile) {
643 		QFile file(fname);
644 		file.remove();
645 	}
646 }
647 
saveAll()648 void ProgramWindow::saveAll() {
649     for (int i= 0; i < m_tabWidget->count(); i++)
650         tabSave(i);
651 }
652 
saveCurrentTab()653 void ProgramWindow::saveCurrentTab() {
654     tabSave(m_tabWidget->currentIndex());
655 }
656 
tabSave(int index)657 void ProgramWindow::tabSave(int index) {
658 	ProgramTab * programTab =indexWidget(index);
659 	if (programTab == NULL) return;
660 
661     prepSave(programTab, false);
662 }
663 
tabSaveAs(int index)664 void ProgramWindow::tabSaveAs(int index) {
665 	ProgramTab * programTab = indexWidget(index);
666     if (programTab == NULL) return;
667 
668     prepSave(programTab, true);
669 }
670 
tabRename(int index)671 void ProgramWindow::tabRename(int index) {
672     ProgramTab * programTab = indexWidget(index);
673     if (programTab == NULL) return;
674 
675     QString oldFileName = programTab->filename();
676     if (prepSave(programTab, true)) {
677 		if (programTab->filename() != oldFileName) {
678 			QFile oldFile(oldFileName);
679 			if (oldFile.exists()) {
680 				oldFile.remove();
681                 emit linkToProgramFile(oldFileName, NULL, false, true);
682 			}
683 		}
684     }
685 }
686 
duplicateTab()687 void ProgramWindow::duplicateTab() {
688         ProgramTab * oldTab = currentWidget();
689         if (oldTab == NULL) return;
690 
691         ProgramTab * newTab = addTab();
692 
693         newTab->setText(oldTab->text());
694 }
695 
tabBeforeClosing(int index,bool & ok)696 void ProgramWindow::tabBeforeClosing(int index, bool & ok) {
697 	ok = beforeClosingTab(index, true);
698 }
699 
prepSave(ProgramTab * programTab,bool saveAsFlag)700 bool ProgramWindow::prepSave(ProgramTab * programTab, bool saveAsFlag)
701 {
702 	m_savingProgramTab = programTab;				// need this for the saveAsAux call
703     if (!programTab->isModified()) return false;
704 
705 	bool result = (saveAsFlag)
706 		? saveAs(programTab->filename(), programTab->readOnly())
707 		: save(programTab->filename(), programTab->readOnly());
708 
709     if (result) {
710 		programTab->setClean();
711         emit linkToProgramFile(programTab->filename(), programTab->platform(), true, true);
712 	}
713 	return result;
714 }
715 
initPlatforms()716 void ProgramWindow::initPlatforms() {
717     QDir dir(FolderUtils::getApplicationSubFolderPath("translations"));
718     Highlighter::loadStyles(dir.absolutePath().append("/syntax/styles.xml"));
719 
720     m_platforms << new PlatformArduino() << new PlatformPicaxe();
721 }
722 
getAvailablePlatforms()723 QList<Platform *> ProgramWindow::getAvailablePlatforms() {
724     return m_platforms;
725 }
726 
getPlatformByName(const QString & platformName)727 Platform * ProgramWindow::getPlatformByName(const QString & platformName) {
728     foreach (Platform * platform, getAvailablePlatforms()) {
729         if (platform->getName().compare(platformName, Qt::CaseInsensitive) == 0)
730             return platform;
731     }
732     return NULL;
733 }
734 
hasPlatform(const QString & platformName)735 bool ProgramWindow::hasPlatform(const QString & platformName) {
736     return getPlatformByName(platformName) != NULL;
737 }
738 
getBoards()739 const QMap<QString, QString> ProgramWindow::getBoards() {
740     if (currentWidget() && currentWidget()->platform())
741         return currentWidget()->platform()->getBoards();
742 
743     QMap<QString, QString> boards;
744     boards.insert(NoBoardName, NoBoardName);
745     return boards;
746 }
747 
addBoard(const QString & name,const QString & definition)748 QAction * ProgramWindow::addBoard(const QString & name, const QString & definition)
749 {
750     QAction * currentAction = new QAction(name, this);
751     currentAction->setCheckable(true);
752     currentAction->setData(definition);
753     m_boardActions.insert(name, currentAction);
754     m_boardMenu->addAction(currentAction);
755     m_boardActionGroup->addAction(currentAction);
756     return currentAction;
757 }
758 
updateBoards()759 void ProgramWindow::updateBoards() {
760     QMap<QString, QString> boards = getBoards();
761 
762     m_boardActions.clear();
763     foreach (QAction * action, m_boardActionGroup->actions())
764         m_boardActionGroup->removeAction(action);
765     m_boardMenu->clear();
766 
767     QMapIterator<QString, QString> i(boards);
768      while (i.hasNext()) {
769          i.next();
770          addBoard(i.key(), i.value());
771      }
772 }
773 
loadProgramFile()774 void ProgramWindow::loadProgramFile() {
775     DebugDialog::debug("loading program file");
776 	currentWidget()->loadProgramFile();
777 
778 }
779 
loadProgramFileNew()780 void ProgramWindow::loadProgramFileNew() {
781 	ProgramTab * programTab = addTab();
782 	if (programTab) {
783 		if (!programTab->loadProgramFile()) {
784 			delete programTab;
785 		}
786 	}
787 }
788 
rename()789 void ProgramWindow::rename() {
790 	 currentWidget()->rename();
791 }
792 
undo()793 void ProgramWindow::undo() {
794 	 currentWidget()->undo();
795 }
796 
redo()797 void ProgramWindow::redo() {
798 	 currentWidget()->redo();
799 }
800 
cut()801 void ProgramWindow::cut() {
802 	 currentWidget()->cut();
803 }
804 
copy()805 void ProgramWindow::copy() {
806 	 currentWidget()->copy();
807 }
808 
paste()809 void ProgramWindow::paste() {
810 	 currentWidget()->paste();
811 }
812 
selectAll()813 void ProgramWindow::selectAll() {
814 	 currentWidget()->selectAll();
815 }
816 
serialMonitor()817 void ProgramWindow::serialMonitor() {
818      currentWidget()->serialMonitor();
819 }
820 
sendProgram()821 void ProgramWindow::sendProgram() {
822 	 currentWidget()->sendProgram();
823 }
824 
currentWidget()825 ProgramTab * ProgramWindow::currentWidget() {
826 	return qobject_cast<ProgramTab *>(m_tabWidget->currentWidget());
827 }
828 
indexWidget(int index)829 ProgramTab * ProgramWindow::indexWidget(int index) {
830 	return qobject_cast<ProgramTab *>(m_tabWidget->widget(index));
831 }
832 
alreadyHasProgram(const QString & filename)833 bool ProgramWindow::alreadyHasProgram(const QString & filename) {
834     DebugDialog::debug("already has program");
835 	for (int i = 0; i < m_tabWidget->count(); i++) {
836 		ProgramTab * tab = indexWidget(i);
837 		if (tab->filename() == filename) {
838 			m_tabWidget->setCurrentIndex(i);
839 			return true;
840 		}
841 	}
842 
843 	return false;
844 }
845 
getExtensionString()846 QString ProgramWindow::getExtensionString() {
847 	ProgramTab * pt = currentWidget();
848 	if (pt == NULL) return "";
849 
850 	return pt->extensionString();
851 }
852 
getExtensions()853 QStringList ProgramWindow::getExtensions() {
854 	ProgramTab * pt = currentWidget();
855 	if (pt == NULL) return QStringList();
856 
857 	return pt->extensions();
858 }
859 
getSerialPorts()860 QList<QSerialPortInfo> ProgramWindow::getSerialPorts() {
861     QList<QSerialPortInfo> ports;
862     ports = QSerialPortInfo::availablePorts();
863 
864     /*
865     // on the pc, handy for testing the UI when there are no serial ports
866     ports.removeOne("COM0");
867     ports.removeOne("COM1");
868     ports.removeOne("COM2");
869     ports.removeOne("COM3");
870     */
871 
872     return ports;
873 }
874 
updateSerialPorts()875 void ProgramWindow::updateSerialPorts() {
876     QList<QSerialPortInfo> ports = getSerialPorts();
877 
878     m_portActions.clear();
879     foreach (QAction * action, m_serialPortActionGroup->actions())
880         m_serialPortActionGroup->removeAction(action);
881     m_serialPortMenu->clear();
882 
883     foreach (QSerialPortInfo port, ports) {
884         addPort(port);
885     }
886 }
887 
addPort(QSerialPortInfo port)888 QAction * ProgramWindow::addPort(QSerialPortInfo port)
889 {
890     QAction * currentAction = new QAction(port.portName(), this);
891     currentAction->setCheckable(true);
892     currentAction->setData(port.systemLocation());
893     m_portActions.insert(port.portName(), currentAction);
894     m_serialPortMenu->addAction(currentAction);
895     m_serialPortActionGroup->addAction(currentAction);
896     return currentAction;
897 }
898 
hasPort(const QString & portName)899 bool ProgramWindow::hasPort(const QString & portName) {
900     foreach (QSerialPortInfo port, getSerialPorts()) {
901         if (port.portName().compare(portName) == 0)
902             return true;
903     }
904     return false;
905 }
906 
updateLink(const QString & filename,Platform * platform,bool addlink,bool strong)907 void ProgramWindow::updateLink(const QString & filename, Platform * platform, bool addlink, bool strong)
908 {
909     DebugDialog::debug("updating link");
910     emit linkToProgramFile(filename, platform, addlink, strong);
911 }
912 
portProcessFinished(int exitCode,QProcess::ExitStatus exitStatus)913 void ProgramWindow::portProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) {
914 	DebugDialog::debug(QString("process finished %1 %2").arg(exitCode).arg(exitStatus));
915 
916 	// parse the text and update the combo box
917 
918 	sender()->deleteLater();
919 }
920 
portProcessReadyRead()921 void ProgramWindow::portProcessReadyRead() {
922 	m_ports.clear();
923 
924 	QByteArray byteArray = qobject_cast<QProcess *>(sender())->readAllStandardOutput();
925     QTextStream textStream(byteArray, QIODevice::ReadOnly);
926     while (true) {
927         QString line = textStream.readLine();
928         if (line.isNull()) break;
929 
930         if (!line.contains("tty")) continue;
931         if (!line.contains("serial", Qt::CaseInsensitive)) continue;
932 
933         QStringList candidates = line.split(" ");
934         foreach (QString candidate, candidates) {
935             if (candidate.contains("tty")) {
936                 m_ports.append(candidate);
937                 break;
938             }
939         }
940     }
941 }
942