1 /*
2 	Copyright (C) 2009 Andres Cabrera
3 	mantaraya36@gmail.com
4 
5 	This file is part of CsoundQt.
6 
7 	CsoundQt is free software; you can redistribute it
8 	and/or modify it under the terms of the GNU Lesser General Public
9 	License as published by the Free Software Foundation; either
10 	version 2.1 of the License, or (at your option) any later version.
11 
12 	CsoundQt is distributed in the hope that it will be useful,
13 	but WITHOUT ANY WARRANTY; without even the implied warranty of
14 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 	GNU Lesser General Public License for more details.
16 
17 	You should have received a copy of the GNU Lesser General Public
18 	License along with Csound; if not, write to the Free Software
19 	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
20 	02111-1307 USA
21 */
22 
23 #include "eventsheet.h"
24 #include "liveeventframe.h"
25 
26 #include <QMenu>
27 #include <QDir>
28 #include <QContextMenuEvent>
29 #include <QLabel>
30 #include <QVBoxLayout>
31 #include <QLineEdit>
32 #include <QDialog>
33 #include <QDoubleSpinBox>
34 #include <QApplication>
35 #include <QClipboard>
36 
37 #include <QFile>
38 #include <QMessageBox>
39 
40 // For rand() function
41 #include <cstdlib>
42 // Only for debug
43 #include <QtCore>
44 
45 class OneValueDialog: public QDialog
46 {
47 public:
OneValueDialog(QWidget * parent,QString label,double defaultValue=0.0)48 	OneValueDialog(QWidget *parent, QString label, double defaultValue = 0.0):
49 		QDialog(parent)
50 	{
51 		l = new QVBoxLayout(this);
52 		lab= new QLabel(label, this);
53 		box = new QDoubleSpinBox(this);
54 		box->setRange(-999999, 999999);
55 		box->setDecimals(8);
56 		box->setValue(defaultValue);
57 		box->selectAll();
58 		l->addWidget(lab);
59 		l->addWidget(box);
60 		connect(box, SIGNAL(editingFinished()), this, SLOT(accept ()) );
61 	}
62 
value()63 	double value() { return box->value();}
64 
65 	QVBoxLayout *l;
66 	QLabel *lab;
67 	QDoubleSpinBox *box;
68 };
69 
70 class ThreeValueDialog: public QDialog
71 {
72 public:
ThreeValueDialog(QWidget * parent,QStringList labels,QVector<double> defaultValues)73 	ThreeValueDialog(QWidget *parent, QStringList labels, QVector<double> defaultValues):
74 		QDialog(parent)
75 	{
76 		l = new QVBoxLayout(this);
77 		lab1= new QLabel(labels[0], this);
78 		box1 = new QDoubleSpinBox(this);
79 		box1->setRange(-999999, 999999);
80 		box1->setDecimals(8);
81 		box1->setValue(defaultValues[0]);
82 		box1->selectAll();
83 		lab2= new QLabel(labels[1], this);
84 		box2 = new QDoubleSpinBox(this);
85 		box2->setRange(-999999, 999999);
86 		box2->setDecimals(8);
87 		box2->setValue(defaultValues[1]);
88 		l->addWidget(lab1);
89 		l->addWidget(box1);
90 		l->addWidget(lab2);
91 		l->addWidget(box2);
92 		if (labels[2] != "") {
93 			lab3= new QLabel(labels[2], this);
94 			box3 = new QDoubleSpinBox(this);
95 			box3->setRange(-999999, 999999);
96 			box3->setDecimals(8);
97 			box3->setValue(defaultValues[2]);
98 			l->addWidget(lab3);
99 			l->addWidget(box3);
100 		}
101 		//    connect(box1, SIGNAL(editingFinished()), this, SLOT(accept ()) );
102 		//    connect(box2, SIGNAL(editingFinished()), this, SLOT(accept ()) );
103 		connect(box3, SIGNAL(editingFinished()), this, SLOT(accept ()) );
104 	}
105 
value1()106 	double value1() { return box1->value();}
value2()107 	double value2() { return box2->value();}
value3()108 	double value3() { return box3->value();} // Be careful, this is not checked!
109 	QVBoxLayout *l;
110 	QLabel *lab1;
111 	QDoubleSpinBox *box1;
112 	QLabel *lab2;
113 	QDoubleSpinBox *box2;
114 	QLabel *lab3;
115 	QDoubleSpinBox *box3;
116 
117 protected:
keyPressEvent(QKeyEvent * event)118 	virtual void keyPressEvent (QKeyEvent * event) {
119 		if (event->key() == Qt::Key_Escape) {
120 			this->reject();
121 		}
122 		else if (event->key() == Qt::Key_Return) {
123 			if (box1->hasFocus())
124 				box2->setFocus(Qt::TabFocusReason);
125 			else if (box2->hasFocus())
126 				box3->setFocus(Qt::TabFocusReason);
127 		}
128 		else {
129 			QDialog::keyPressEvent(event);  // Otherwise propagate event
130 		}
131 	}
132 };
133 
EventSheet(QWidget * parent)134 EventSheet::EventSheet(QWidget *parent) : QTableWidget(parent)
135 {
136 	//  qDebug() << "EventSheet::EventSheet";
137 	this->setRowCount(10);
138 	this->setColumnCount(6);
139 	columnNames << tr("Event") << "p1 (instr)" << "p2 (start)" << "p3 (dur)" << "p4" << "p5";
140 	this->setHorizontalHeaderLabels(columnNames);
141 	this->setColumnWidth(0, 50);
142 	this->setColumnWidth(1, 70);
143 	this->setColumnWidth(2, 70);
144 	this->setColumnWidth(3, 70);
145 	this->setColumnWidth(4, 50);
146 	this->setColumnWidth(5, 50);
147 
148 	m_name = "Events";
149 	m_stopScript = false;
150 	m_looping = false;
151 	createActions();
152 	connect(this, SIGNAL(itemSelectionChanged()), this, SLOT(newSelection()));
153 	// a bit of a hack to ensure that manual changes to the sheet are stored in the
154 	// undo history. This seems better than calling markHistory() when a cell
155 	// changes because large operations like add or subractract will produce
156 	// many steps in the history
157 //	connect(this, SIGNAL(cellDoubleClicked(int, int)), this, SLOT(cellDoubleClickedSlot(int, int)));
158 	connect(this, SIGNAL(cellChanged (int, int)), this, SLOT(cellChangedSlot(int, int)));
159 
160 	loopTimer.setSingleShot(true);
161 	connect(&loopTimer, SIGNAL(timeout()), this, SLOT(sendEvents()));
162 
163 	builtinScripts << ":/python/sort_by_start.py" << ":/python/produce_score.py"<< ":/python/fill_text.py";
164 	converterScripts << ":/python/Conversion/cps2mid.py" << ":/python/Conversion/mid2cps.py" << ":/python/Conversion/cps2pch.py"
165 					 << ":/python/Conversion/pch2cps.py" ;
166 	testScripts <<  ":/python/Tests/python_test.py" << ":/python/Tests/tk_test.py";
167 
168 	noHistoryChange = 0;
169 }
170 
~EventSheet()171 EventSheet::~EventSheet()
172 {
173 }
174 
getPlainText(bool scaleTempo)175 QString EventSheet::getPlainText(bool scaleTempo)
176 {
177 	QString t = "";
178 	for (int i = 0; i < this->rowCount(); i++) {
179 		t += getLine(i, scaleTempo) + "\n";  // Don't scale by default
180 	}
181 	t.chop(1);
182 	//  qDebug() << " EventSheet::getPlainText   " << t;
183 	return t;
184 }
185 
getSelection(bool cut)186 QString EventSheet::getSelection(bool cut)
187 {
188 	QModelIndexList list = this->selectedIndexes();
189 	QList<int> selectedRows;
190 	QList<int> selectedColumns;
191 	for (int i = 0; i < list.size(); i++) { // Get list of selected rows
192 		if (!selectedRows.contains(list[i].row()) ) {
193 			selectedRows.append(list[i].row());
194 		}
195 		if (!selectedColumns.contains(list[i].column()) ) {
196 			selectedColumns.append(list[i].column());
197 		}
198 	}
199 	QString text = "";
200 	for (int i = 0; i < selectedRows.size(); i++) {
201 		QString line = "";
202 		for (int j = 0; j < selectedColumns.size(); j++) {
203 			QTableWidgetItem * item = this->item(selectedRows[i], selectedColumns[j]);
204 			if (item != 0) { // Item is not empty
205 				line += item->data(Qt::DisplayRole).toString();
206 				QString space = item->data(Qt::UserRole).toString();
207 				if (!space.isEmpty())
208 					line += space;
209 				else
210 					line += " ";
211 				if (cut)
212 					delete item;
213 			}
214 		}
215 		text += line + "\n";
216 	}
217 	text.chop(1); // remove last line break
218 	return text;
219 }
220 
getLine(int number,bool scaleTempo,bool storeNumber,bool preprocess,double startOffset)221 QString EventSheet::getLine(int number, bool scaleTempo, bool storeNumber, bool preprocess, double startOffset)
222 {
223 	QString line = "";
224 	bool instrEvent = false;
225 	bool comment = false;
226 	for (int i = 0; i < this->columnCount(); i++) {
227 		QTableWidgetItem * item = this->item(number, i);
228 		if (item != 0) { // Item is not empty
229 			if (i == 0 && (item->data(Qt::DisplayRole).toString() == "i") ) {
230 				instrEvent = true;  // Check if event is intrument note to allow scale by tempo
231 			}
232 			if (instrEvent && i == 1 && storeNumber) { // append current instrument number to active note list
233 				bool ok = false;
234 				double instrNum = item->data(Qt::DisplayRole).toDouble(&ok);
235 				if (ok && !activeInstruments.contains(instrNum))
236 					activeInstruments.append(instrNum);
237 			}
238 			if (preprocess && item->data(Qt::DisplayRole).toString() == ".") { // Carry value from above
239 				QString cellText = ".";
240 				int row = number;
241 				while (row >= 0 && cellText == "." && cellText != "") {
242 					item = this->item(row, i);
243 					if (item != 0 ) {
244 						cellText = item->data(Qt::DisplayRole).toString();
245 					}
246 					else {
247 						cellText = "";
248 					}
249 					row--;
250 				}
251 			}
252 			if (scaleTempo && instrEvent && (i == 2 || i == 3) ) { // Scale tempo only for pfields p2 and p3
253 				bool ok = false;
254 				double value = item->data(Qt::DisplayRole).toDouble(&ok);
255 				if (ok) {
256 					if (i == 2) { // Add start offset to p2 before scaling
257 						value += startOffset;
258 					}
259 					value = value * (60.0/m_tempo);
260 					line += QString::number(value, 'f', 8);;
261 				}
262 				else {
263 					line += item->data(Qt::DisplayRole).toString();
264 				}
265 			}
266 			else {  // All other p-fields that don't require tempo scaling
267 				QString cellText = item->data(Qt::DisplayRole).toString();
268 				if (comment) {
269 					bool dataRemaining = false;  // Check if remaining cells have data
270 					for (int j = i + 1; j < this->columnCount(); j++) {
271 						QTableWidgetItem * checkItem = this->item(number, j);
272 						if (checkItem != 0 && checkItem->data(Qt::DisplayRole).toString() != "") {
273 							dataRemaining = true;
274 							break;
275 						}
276 					}
277 					if (dataRemaining || cellText != "")
278 						line += ";" + cellText;
279 				}
280 				else if (cellText.startsWith(';')) {
281 					comment = true; // is a comment from now on
282 					line += cellText;
283 				}
284 				else {
285 					line += cellText;
286 				}
287 			}
288 			// Then add white space separation
289 			QString space = item->data(Qt::UserRole).toString();
290 			if (!space.isEmpty()) { // Separataion is stored in UserRole of items
291 				line += space;
292 			}
293 			else {
294 				line += " ";
295 			}
296 		}
297 		else if (comment) { // empty cell part of a comment
298 			// Do nothing
299 		}
300 	}
301 	return line;
302 }
303 
getData()304 QList< QList<QVariant> > EventSheet::getData()
305 {
306 	// TODO this can be made a lot more efficient
307 	QList< QList<QVariant> > data;
308 	for (int i = 0; i < this->rowCount(); i++) {
309 		QList<QVariant> row;
310 		for (int j = 0; j < this->columnCount(); j++) {
311 			QTableWidgetItem * item = this->item(i, j);
312 			if (item != 0) { // Item is not empty
313 				row.append(item->data(Qt::DisplayRole));
314 			}
315 			else {
316 				row.append(QVariant());
317 			}
318 		}
319 		data << row;
320 	}
321 	return data;
322 }
323 
setFromText(QString text,int rowOffset,int columnOffset,int numRows,int numColumns,bool noHistoryMark)324 void EventSheet::setFromText(QString text, int rowOffset, int columnOffset, int numRows, int numColumns, bool noHistoryMark)
325 {
326 	// Separataion is stored in UserRole of items
327 	// remember to treat comments and formulas properly
328 	QStringList lines = text.split("\n");
329 	int nRows = 0; // Number of actual rows to process
330 	// numRows = 0 : don't remove rows, only add if necessary. numRows = -1 limit rows to the ones in text
331 	nRows = numRows <= 0 ? lines.size() : numRows;
332 	if (this->rowCount() < nRows + rowOffset || numRows == -1) {
333 		this->setRowCount(nRows + rowOffset);
334 	}
335 	for (int i = 0; i < nRows; i++) {
336 		//    if (nRows != 0 && i >= nRows) {  // Only paste up to a certain number of rows if not 0
337 		//      break;
338 		//    }
339 		QString line = "";
340 		if (i < lines.size()) {
341 			line = lines[i].trimmed(); //Remove whitespace from start and end
342 		}
343 		this->blockSignals(true); // To avoid triggering changed signal which goes to undo for each cell
344 		QList<QPair<QString, QString> > fields = parseLine(line);
345 		int nColumns = numColumns == 0 ? fields.size() : numColumns;
346 		nColumns = (numColumns == -1 && nColumns <  this->columnCount()) ?  this->columnCount() : nColumns;
347 		while (this->columnCount() < nColumns + columnOffset) {
348 			appendColumn();
349 		}
350 		int j;
351 		for (j = 0; j < nColumns; j++) {
352 			QTableWidgetItem * item = this->item(i + rowOffset, j + columnOffset);
353 			if (item == 0) {
354 				item = new QTableWidgetItem();
355 				noHistoryChange = (noHistoryMark ? 1: 0);
356 				this->setItem(i + rowOffset, j + columnOffset, item);
357 			}
358 			if (j < fields.size()) {
359 				noHistoryChange = (noHistoryMark ? 1: 0);
360 				item->setData(Qt::DisplayRole, fields[j].first);
361 				noHistoryChange = (noHistoryMark ? 1: 0);
362 				item->setData(Qt::UserRole, fields[j].second);
363 			}
364 			else {
365 				noHistoryChange = (noHistoryMark ? 1: 0);
366 				item->setData(Qt::DisplayRole, "");
367 				noHistoryChange = (noHistoryMark ? 1: 0);
368 				item->setData(Qt::UserRole, "");
369 			}
370 		}
371 		while (j < numColumns && j + columnOffset < this->columnCount()) {
372 			this->removeCellWidget(i + rowOffset, j + columnOffset);
373 			j++;
374 		}
375 	}
376 	// column names mus be reset here as the names have been cleared
377 	this->setHorizontalHeaderLabels(columnNames);
378 	this->blockSignals(false);
379 	if (!noHistoryMark) {
380 		emit ( cellChanged(rowOffset, columnOffset) );
381 	}
382 	if (this->rowCount() == 0)
383 		this->setRowCount(1);
384 }
385 
setCell(int row,int column,QVariant value)386 void EventSheet::setCell(int row, int column, QVariant value)
387 {
388 	QTableWidgetItem * item = this->item(row, column);
389 	if (item == 0) {
390 		item = new QTableWidgetItem();
391 		this->setItem(row, column, item);
392 	}
393 	item->setData(Qt::DisplayRole, value);
394 }
395 
setDebug(bool debug)396 void EventSheet::setDebug(bool debug)
397 {
398 	m_debug = debug;
399 }
400 
getSelectedRowsRange()401 QPair<int, int> EventSheet::getSelectedRowsRange()
402 {
403 	QModelIndexList selection = this->selectedIndexes();
404 	int min = 9999, max = -1;
405 	for (int i = 0; i < selection.size(); i++) {
406 		if (selection[i].row() > max) {
407 			max = selection[i].row();
408 		}
409 		if (selection[i].row() < min) {
410 			min = selection[i].row();
411 		}
412 	}
413 	qDebug() << "EventSheet::getSelectedRowsRange " << min << " "<< max;
414 	return QPair<int, int>(min,max);
415 }
416 
setTempo(double value)417 void EventSheet::setTempo(double value)
418 {
419 	//  qDebug() << "EventSheet::setTempo " << value;
420 	m_tempo = value;
421 }
422 
setLoopLength(double value)423 void EventSheet::setLoopLength(double value)
424 {
425 	//  qDebug() << "EventSheet::setLoopLength " << value;
426 	m_loopLength = value;
427 }
428 
sendEvents()429 void EventSheet::sendEvents()
430 {
431 	QModelIndexList list;
432 	QPair<int, int> rowsRange;
433 	if (m_looping && sender() != sendEventsAct) {
434 		double time = 1000.0 * m_loopLength * 60.0 /m_tempo;
435 		loopTimer.start(time);
436 		qDebug() << " EventSheet::sendEvents() " << time;
437 		rowsRange.first = m_loopStart;
438 		rowsRange.second = m_loopEnd;
439 	}
440 	else {
441 		rowsRange = getSelectedRowsRange();
442 	}
443 	for (int i = rowsRange.first; i <= rowsRange.second; i++) {
444 		//    double number = 0.0;
445 		emit sendEvent(getLine(i, true, true, true));  // With tempo scaling
446 	}
447 }
448 
sendAllEvents()449 void EventSheet::sendAllEvents()
450 {
451 	for (int i = 0; i < this->rowCount(); i++) {
452 		//    qDebug() << "EventSheet::sendAllEvents() " << i;
453 		emit sendEvent(getLine(i, true, true, true));  // With tempo scaling
454 	}
455 }
456 
sendEventsOffset()457 void EventSheet::sendEventsOffset()
458 {
459 	QModelIndexList list;
460 	QList<int> selectedRows;
461 	list = this->selectedIndexes();
462 	for (int i = 0; i < list.size(); i++) {
463 		if (!selectedRows.contains(list[i].row()) ) {
464 			selectedRows.append(list[i].row());
465 		}
466 	}
467 	double minTime = 999999999999999.0;
468 	bool hasMin = false;
469 	for (int i = 0; i < selectedRows.size(); i++) {
470 		QTableWidgetItem * item = this->item(selectedRows[i], 2);
471 		if (item != 0 && item->data(Qt::DisplayRole).canConvert(QVariant::Double)) {
472 			bool ok = false;
473 			double n = item->data(Qt::DisplayRole).toDouble(&ok);
474 			if (ok && n < minTime) {
475 				minTime = n;
476 				hasMin = true;
477 			}
478 		}
479 	}
480 	if (!hasMin)
481 		minTime = 0.0;
482 	for (int i = 0; i < selectedRows.size(); i++) {
483 		//    double number = 0.0;
484 		emit sendEvent(getLine(selectedRows[i], true, true, true, -minTime));  // With tempo scaling
485 	}
486 }
487 
loopEvents()488 void EventSheet::loopEvents()
489 {
490 	QPair<int, int> rowsRange = getSelectedRowsRange();
491 	markLoop(rowsRange.first,rowsRange.second);
492 	setLoopActive(true);
493 	emit setLoopEnabledFromSheet(true);
494 }
495 
setLoopActive(bool loop)496 void EventSheet::setLoopActive(bool loop)
497 {
498 	//  qDebug() << "EventSheet::setLoopActive " << loop;
499 	if (loop) {
500 		if (!m_looping) {
501 			m_looping = true;
502 			markLoop(m_loopStart, m_loopEnd);
503 			sendEvents();
504 		}
505 	}
506 	else {
507 		m_looping = false;
508 		markLoop(m_loopStart, m_loopEnd);
509 	}
510 }
511 
markLoop(double start,double end)512 void EventSheet::markLoop(double start, double end)
513 {
514 	// TODO move looping to eventframe class
515 	m_loopStart = (int) start;
516 	m_loopEnd = (int) end;
517 	for (int i = 0; i < rowCount(); i++) {
518 		for (int j = 0; j < 4; j++) {
519 			QTableWidgetItem * item = this->item(i, j);
520 			if (item == 0) {
521 				item = new QTableWidgetItem();
522 				this->setItem(i, j, item );
523 			}
524 			if (i < m_loopStart || i > m_loopEnd) {
525 				item->setBackground(QBrush());
526 			}
527 			else {
528 				if (m_looping) {
529 					item->setBackground(QBrush(Qt::green));
530 				}
531 				else {
532 					item->setBackground(QBrush(QColor(200,255,200)));
533 				}
534 			}
535 		}
536 	}
537 }
538 
setLoopRange()539 void EventSheet::setLoopRange()
540 {
541 	qDebug() << "EventSheet::setLoopRange()";
542 	QPair<int, int> rowsRange = getSelectedRowsRange();
543 	double start = rowsRange.first;
544 	double end = rowsRange.second;
545 	emit setLoopRangeFromSheet(start,end);
546 }
547 
stopAllEvents()548 void EventSheet::stopAllEvents()
549 {
550 	loopTimer.stop();
551 	m_looping = false;
552 	markLoop();
553 	while (!activeInstruments.isEmpty()) {
554 		QString event = "i -";
555 		event += QString::number(activeInstruments.takeFirst(), 'f', 10);
556 		event += " 0 1";
557 		emit sendEvent(event);
558 	}
559 }
560 
del()561 void EventSheet::del()
562 {
563 	QModelIndexList list = this->selectedIndexes();
564 	for (int i = 0; i < list.size(); i++) {
565 		QTableWidgetItem * item = this->item(list[i].row(), list[i].column());
566 		if (item != 0) { // Item is not empty
567 			delete item;
568 		}
569 	}
570 	markHistory();
571 }
572 
cut()573 void EventSheet::cut()
574 {
575 	copy(true);
576 	markHistory();
577 }
578 
copy(bool cut)579 void EventSheet::copy(bool cut)
580 {
581 	qApp->clipboard()->setText(getSelection(cut));
582 }
583 
paste()584 void EventSheet::paste()
585 {
586 	//  qDebug() << "EventSheet::paste() text = " << qApp->clipboard()->text();
587 	QModelIndexList list = this->selectedIndexes();
588 	QList<int> selectedRows;
589 	QList<int> selectedColumns;
590 	int rowCount, columnCount;
591 	int lowestRow = 0x0fffffff, lowestColumn = 0x0fffffff;
592 	for (int i = 0; i < list.size(); i++) { // Get list of selected rows
593 		if (!selectedRows.contains(list[i].row()) ) {
594 			selectedRows.append(list[i].row());
595 			if (list[i].row() < lowestRow) {
596 				lowestRow = list[i].row();
597 			}
598 		}
599 		if (!selectedColumns.contains(list[i].column()) ) {
600 			selectedColumns.append(list[i].column());
601 			if (list[i].column() < lowestColumn) {
602 				lowestColumn = list[i].column();
603 			}
604 		}
605 	}
606 	if (lowestRow == 0x0fffffff) { // If there is no selection
607 		lowestRow = 0;
608 	}
609 	if (lowestColumn == 0x0fffffff) { // If there is no selection
610 		lowestColumn = 0;
611 	}
612 	rowCount = selectedRows.size();
613 	columnCount = selectedColumns.size();
614 	if (rowCount <= 1 && columnCount <= 1) {
615 		rowCount = columnCount = 0;
616 	}
617 	// TODO comments that should be pasted on multiple cells are not.
618 	setFromText(qApp->clipboard()->text(), lowestRow, lowestColumn, rowCount, columnCount, true);
619 	markHistory();
620 }
621 
undo()622 void EventSheet::undo()
623 {
624 	qDebug() << "EventSheet::undo() " << historyIndex;
625 	if (historyIndex > 0) {
626 		historyIndex--;
627 		setFromText(history[historyIndex], 0,0,rowCount(),columnCount(),true);
628 	}
629 }
630 
redo()631 void EventSheet::redo()
632 {
633 	qDebug() << "EventSheet::redo() " << historyIndex << history.size();
634 	if (historyIndex < history.size() - 1) {
635 		historyIndex++;
636 		setFromText(history[historyIndex], 0,0,rowCount(),columnCount(),true);
637 	}
638 }
639 
markHistory()640 void EventSheet::markHistory()
641 {
642 	QString text = getPlainText();
643 	if (history.isEmpty()) {
644 		history << "";
645 		historyIndex = 0;
646 	}
647 	if (history[historyIndex] != text) {
648 		if (! history[historyIndex].isEmpty())
649 			historyIndex++;
650 		//    if (historyIndex >= QCS_MAX_UNDO) {
651 		//      history.pop_front();
652 		//      historyIndex--;
653 		//    }
654 		if (history.size() != historyIndex + 1)
655 			history.resize(historyIndex + 1);
656 		history[historyIndex] = text;
657 		//    qDebug() << "EventSheet::markHistory "<< historyIndex << " ....."  << text;
658 	}
659 }
660 
clearHistory()661 void EventSheet::clearHistory()
662 {
663 	QString text = getPlainText();
664 	//  qDebug() << "EventSheet::clearHistory() " << text;
665 	history.clear();
666 	historyIndex = 0;
667 }
668 
setScriptDirectory(QString dir)669 void EventSheet::setScriptDirectory(QString dir)
670 {
671 	scriptDir = dir;
672 	if (!scriptDir.endsWith("/") && !scriptDir.isEmpty())
673 		scriptDir += "/";
674 }
675 
subtract()676 void EventSheet::subtract()
677 {
678 	OneValueDialog d(this, tr("Subtract"));
679 	d.exec();
680 	if (d.result() == QDialog::Accepted) {
681 		this->add(- d.value());
682 	}
683 }
684 
add()685 void EventSheet::add()
686 {
687 	OneValueDialog d(this, tr("Add"));
688 	d.exec();
689 	if (d.result() == QDialog::Accepted) {
690 		this->add(d.value());
691 	}
692 }
693 
multiply()694 void EventSheet::multiply()
695 {
696 	OneValueDialog d(this, tr("Multiply by"));
697 	d.exec();
698 	if (d.result() == QDialog::Accepted) {
699 		this->multiply(d.value());
700 	}
701 }
702 
divide()703 void EventSheet::divide()
704 {
705 	OneValueDialog d(this, tr("Divide by"));
706 	d.exec();
707 	if (d.result() == QDialog::Accepted) {
708 		this->divide(d.value());
709 	}
710 }
711 
randomize()712 void EventSheet::randomize()
713 {
714 	QStringList labels;
715 	labels << tr("Minimum") << tr("Maximum") << tr("Mode: 0=decimals 1=Integers only");
716 	QVector<double> defaultValues;
717 	defaultValues << 0.0 << 1.0 << 0.0;
718 	ThreeValueDialog d(this, labels, defaultValues);
719 	d.box3->setDecimals(0);
720 	d.exec();
721 	if (d.result() == QDialog::Accepted) {
722 		this->randomize(d.value1(), d.value2(), d.value3());
723 	}
724 }
725 
reverse()726 void EventSheet::reverse()
727 {
728 	QModelIndexList list = this->selectedIndexes();
729 	if (list.size() < 2)
730 		return;
731 	QList<int> selectedColumns;
732 	for (int i = 0; i < list.size(); i++) {
733 		if (!selectedColumns.contains(list[i].column()) ) {
734 			selectedColumns.append(list[i].column());
735 		}
736 	}
737 	int numRows = list.size() / selectedColumns.size();
738 	if (numRows < 2)
739 		return;
740 
741 	QVector<QVariant> elements;
742 	elements.resize(numRows);
743 	for (int i = 0; i < selectedColumns.size(); i++) {
744 		for (int j = 0; j < numRows; j++) {
745 			QTableWidgetItem * item = this->item(list[(i*numRows) + j].row(), list[(i*numRows) + j].column());
746 			if (item != 0) {
747 				elements[numRows - j - 1] = item->data(Qt::DisplayRole);
748 			}
749 			else {
750 				elements[numRows - j - 1] = QVariant();
751 			}
752 		}
753 		for (int j = 0; j < numRows; j++) {
754 			QTableWidgetItem * item = this->item(list[(i*numRows) + j].row(), list[(i*numRows) + j].column());
755 			if (item == 0) {
756 				item = new QTableWidgetItem();
757 				this->setItem(list[(i*numRows) + j].row(), list[(i*numRows) + j].column(), item );
758 			}
759 			item->setData(Qt::DisplayRole, elements[j] );
760 		}
761 	}
762 }
763 
shuffle()764 void EventSheet::shuffle()
765 {
766 	OneValueDialog d(this, tr("Iterations"));
767 	d.box->setValue(10.0);
768 	d.box->setDecimals(0);
769 	d.box->selectAll();
770 	d.exec();
771 	if (d.result() == QDialog::Accepted) {
772 		this->shuffle(d.value());
773 	}
774 }
775 
776 //void EventSheet::mirror()
777 //{
778 //
779 //}
780 
rotate()781 void EventSheet::rotate()
782 {
783 	OneValueDialog d(this, tr("Rotate by"));
784 	d.box->setValue(1.0);
785 	d.box->setDecimals(0);
786 	d.exec();
787 	if (d.result() == QDialog::Accepted) {
788 		this->rotate(d.value());
789 	}
790 }
791 
fill()792 void EventSheet::fill()
793 {
794 	QStringList labels;
795 	labels << tr("From") << tr("To") << tr("Slope (1=Linear)");
796 	QVector<double> defaultValues;
797 	defaultValues << 1.0 << 5.0 << 1.0;
798 	ThreeValueDialog d(this, labels, defaultValues);
799 	connect(d.box1, SIGNAL(valueChanged (double)),
800 			d.box2, SLOT(setValue(double)));
801 	d.exec();
802 	if (d.result() == QDialog::Accepted) {
803 		this->fill(d.value1(), d.value2(), d.value3());
804 	}
805 }
806 
runScript()807 void EventSheet::runScript()
808 {
809 	runScript(static_cast<QAction *>(sender())->data().toString());
810 }
811 
generateDataText(QString outFileName)812 QString EventSheet::generateDataText(QString outFileName)
813 {
814 	QModelIndexList list = this->selectedIndexes();
815 	int minRow = 999999, minCol = 999999, maxRow = -1, maxCol = -1;
816 	for (int i = 0; i < list.size(); i++) { // First traverse to find size
817 		if (list[i].row() > maxRow) {
818 			maxRow = list[i].row();
819 		}
820 		if (list[i].row() < minRow) {
821 			minRow = list[i].row();
822 		}
823 		if (list[i].column() > maxCol) {
824 			maxCol = list[i].column();
825 		}
826 		if (list[i].column() < minCol) {
827 			minCol = list[i].column();
828 		}
829 	}
830 	//  QString data = "[ ";
831 	//  for (int i = minRow; i <= maxRow; i++) {
832 	//    data += "[ ";
833 	//    for (int j = minCol; j <= maxCol; j++) {
834 	//      QTableWidgetItem * item = this->item(i, j);
835 	//      if ( item == 0) {
836 	//        data += "''";
837 	//      }
838 	//      else {
839 	//        bool ok;
840 	//        item->data(Qt::DisplayRole).toString().toDouble(&ok);
841 	//        if (ok) {
842 	//          data += item->data(Qt::DisplayRole).toString();
843 	//        }
844 	//        else {
845 	//          data += "'" + item->data(Qt::DisplayRole).toString() + "'";
846 	//        }
847 	//      }
848 	//      data += ", ";
849 	//    }
850 	//    data.chop(2);
851 	//    data += " ],\n";
852 	//  }
853 	//  data.chop(2);
854 	//  data += " ]";
855 
856 	QString data_all = "[ ";
857 	for (int i = 0; i < this->rowCount(); i++) {
858 		data_all += "[ ";
859 		for (int j = 0; j < this->columnCount() ; j++) {
860 			QTableWidgetItem * item = this->item(i, j);
861 			if ( item == 0) {
862 				data_all += "''";
863 			}
864 			else {
865 				bool ok;
866 				item->data(Qt::DisplayRole).toString().toDouble(&ok);
867 				//        qDebug() << item->data(Qt::DisplayRole).typeName() << item;
868 				if (ok) {
869 					data_all += item->data(Qt::DisplayRole).toString();
870 				}
871 				else {
872 					data_all += "'" + item->data(Qt::DisplayRole).toString() + "'";
873 				}
874 			}
875 			data_all += ", ";
876 		}
877 		data_all.chop(2);
878 		data_all += " ],\n";
879 	}
880 	data_all.chop(2);
881 	data_all += " ]";
882 
883 	QString text;
884 	text += "row = " + QString::number(minRow) + "\n";
885 	text += "col = " + QString::number(minCol) + "\n";
886 	text += "num_rows = " + QString::number(maxRow - minRow + 1) + "\n";
887 	text += "num_cols = " + QString::number(maxCol - minCol + 1) + "\n";
888 	text += "total_rows = " + QString::number(this->rowCount()) + "\n";
889 	text += "total_cols = " + QString::number(this->columnCount ()) + "\n";
890 
891 	//  text += "data = " + data + "\n";
892 	text += "data_all = " + data_all + "\n";
893 	text += "out_filename = '" + outFileName + "'\n";
894 	return text;
895 }
896 
runScript(QString name)897 void EventSheet::runScript(QString name)
898 {
899 	//  qDebug() << "EventSheet::runScript " << name;
900 
901 	QString outFileName = "qutesheet_out_data.txt";
902 	QDir oldDir = QDir::current();
903 	QDir tempDir(QDir::tempPath());
904 	QString subDir = "QCS-" + QString::number(qrand());
905 	while (!tempDir.mkdir(subDir))
906 		subDir = "QCS-" + QString(qrand());
907 	tempDir.cd(subDir);
908 	QDir::setCurrent(tempDir.absolutePath());
909 	QFile module(tempDir.absolutePath() + QDir::separator() + "qutesheet.py");
910 	module.open(QFile::WriteOnly | QIODevice::Text);
911 	QFile file(":/python/qutesheet.py");
912 	file.open(QIODevice::ReadOnly);
913 	QTextStream moduleStream(&module);
914 	moduleStream << file.readAll();
915 	file.close();
916 	module.close();
917 	qDebug() << "EventSheet::runScript module " << module.fileName();
918 
919 	QFile script(tempDir.absolutePath() + QDir::separator() + name.mid(name.lastIndexOf("/") + 1));
920 	script.open(QFile::WriteOnly | QIODevice::Text);
921 	QFile file2(name);
922 	file2.open(QIODevice::ReadOnly);
923 	QTextStream scriptStream(&script);
924 	scriptStream << file2.readAll();
925 	file2.close();
926 	script.close();
927 
928 	QFile dataFile(tempDir.absolutePath() + QDir::separator() + "qutesheet_data.py");
929 	dataFile.open(QFile::WriteOnly | QIODevice::Text);
930 	QTextStream dataStream(&dataFile);
931 	dataStream << generateDataText(outFileName);
932 	dataFile.close();
933 
934 	QFile outFile(tempDir.absolutePath() + QDir::separator() + outFileName);
935 
936 	QProcess p;
937 	p.start("python " + name.mid(name.lastIndexOf("/") + 1));
938 
939 	while (!p.waitForFinished (10) && !m_stopScript) {
940 		qApp->processEvents();
941 	}
942 	m_stopScript = false;
943 	QByteArray sout = p.readAllStandardOutput();
944 	QByteArray serr = p.readAllStandardError();
945 	qDebug() << "EventSheet::runScript Error -----\n" << serr;
946 	QDir::setCurrent(oldDir.absolutePath());
947 	if (p.exitCode() != 0) {
948 		if (m_debug) {
949 			QMessageBox::critical(this, name.mid(name.lastIndexOf("/") + 1) ,
950 								  QString(serr),
951 								  QMessageBox::Ok);
952 		}
953 		else {
954 			QMessageBox::critical(this, name.mid(name.lastIndexOf("/") + 1) ,
955 								  tr("Error running script"),
956 								  QMessageBox::Ok);
957 		}
958 	}
959 	else {
960 		if (m_debug && !sout.isEmpty()) {
961 			QMessageBox::information(this, name.mid(name.lastIndexOf("/") + 1) + " Output" ,
962 									 QString(sout),
963 									 QMessageBox::Ok);
964 		}
965 		qDebug() << sout;
966 		outFile.open(QIODevice::ReadWrite);
967 		QString text = outFile.readAll();
968 		QStringList lines = text.split("\n");
969 		if (lines.size() > 0 && lines[0].startsWith("__@ ")) {
970 			QStringList position = lines[0].split(" ");
971 			position.pop_front();
972 			lines.pop_front();
973 			QString pasteText = lines.join("\n");
974 			pasteText.chop(1);
975 			setFromText(pasteText, position[0].toInt(), position[1].toInt(),
976 						position[2].toInt(), position[3].toInt(), true);
977 			markHistory();
978 		}
979 		else {
980 			qDebug() << "EventSheet::runScript invalid out file format";
981 		}
982 	}
983 	module.remove();
984 	script.remove();
985 	dataFile.remove();
986 	outFile.remove();
987 	tempDir.rmpath(tempDir.absolutePath());
988 }
989 
insertColumnHere()990 void EventSheet::insertColumnHere()
991 {
992 	// TODO implement
993 }
994 
insertRowHere()995 void EventSheet::insertRowHere()
996 {
997 	// TODO implement
998 }
999 
appendColumn()1000 void EventSheet::appendColumn()
1001 {
1002 	this->insertColumn(this->columnCount());
1003 	columnNames << QString("p%1").arg(this->columnCount() - 1);
1004 	this->setHorizontalHeaderLabels(columnNames);
1005 	this->setColumnWidth(this->columnCount() - 1, 50);
1006 }
1007 
appendRow()1008 void EventSheet::appendRow()
1009 {
1010 	//  qDebug() << "EventSheet::appendRow()";
1011 	this->insertRow(this->rowCount());
1012 }
1013 
appendColumns()1014 void EventSheet::appendColumns()
1015 {
1016 	OneValueDialog d(this, tr("Add columns:"), 5);
1017 	d.box->setDecimals(0);
1018 	d.exec();
1019 	if (d.result() == QDialog::Accepted) {
1020 		for (int i = 0; i < d.value() && i < 256; i++) {
1021 			appendColumn();
1022 		}
1023 	}
1024 }
1025 
appendRows()1026 void EventSheet::appendRows()
1027 {
1028 	OneValueDialog d(this, tr("Add Rows:"), 10);
1029 	d.box->setDecimals(0);
1030 	d.exec();
1031 	if (d.result() == QDialog::Accepted) {
1032 		for (int i = 0; i < d.value() && i < 256; i++) {
1033 			appendRow();
1034 		}
1035 	}
1036 }
1037 
deleteColumn()1038 void EventSheet::deleteColumn()
1039 {
1040 	// TODO: remove multiple columns
1041 	this->removeColumn(this->columnCount() - 1);
1042 	columnNames.takeLast();
1043 }
1044 
deleteRows()1045 void EventSheet::deleteRows()
1046 {
1047 	// TODO: remove multiple rows
1048 	QModelIndexList list = this->selectedIndexes();
1049 	QList<int> selectedRows;
1050 	for (int i = 0; i < list.size(); i++) {
1051 		if (!selectedRows.contains(list[i].row()) ) {
1052 			selectedRows.append(list[i].row());
1053 		}
1054 	}
1055 	qSort(selectedRows);
1056 	for (int i = selectedRows.size() - 1; i >=0; i--) {
1057 		this->removeRow(selectedRows[i]);
1058 	}
1059 }
1060 
contextMenuEvent(QContextMenuEvent * event)1061 void EventSheet::contextMenuEvent (QContextMenuEvent * event)
1062 {
1063 	//  qDebug() << "EventSheet::contextMenuEvent";
1064 
1065 	QMenu menu;
1066 	menu.addAction(sendEventsAct);
1067 	menu.addAction(sendEventsOffsetAct);
1068 	menu.addAction(loopSelectionAct);
1069 	menu.addAction(markLoopAct);
1070 	menu.addAction(stopAllEventsAct);
1071 	menu.addSeparator();
1072 	menu.addAction(subtractAct);
1073 	menu.addAction(addAct);
1074 	menu.addAction(multiplyAct);
1075 	menu.addAction(divideAct);
1076 	menu.addAction(randomizeAct);
1077 	menu.addAction(reverseAct);
1078 	menu.addAction(shuffleAct);
1079 	//  menu.addAction(mirrorAct);
1080 	menu.addAction(rotateAct);
1081 	menu.addAction(fillAct);
1082 	menu.addSeparator();
1083 	QMenu *scriptMenu = menu.addMenu(tr("Python Scripts"));
1084 	QMenu *converterMenu = scriptMenu->addMenu(tr("Conversion"));
1085 	for (int i = 0; i < converterScripts.size(); i++) {
1086 		QAction *a = converterMenu->addAction(converterScripts[i].mid(converterScripts[i].lastIndexOf("/") + 1),
1087 											  this, SLOT(runScript() ));
1088 		a->setData(converterScripts[i]);
1089 	}
1090 	QMenu *testMenu = scriptMenu->addMenu(tr("Tests"));
1091 	for (int i = 0; i < testScripts.size(); i++) {
1092 		QAction *a = testMenu->addAction(testScripts[i].mid(testScripts[i].lastIndexOf("/") + 1),
1093 										 this, SLOT(runScript() ));
1094 		a->setData(testScripts[i]);
1095 	}
1096 	for (int i = 0; i < builtinScripts.size(); i++) {
1097 		QAction *a = scriptMenu->addAction(builtinScripts[i].mid(builtinScripts[i].lastIndexOf("/") + 1),
1098 										   this, SLOT(runScript() ));
1099 		a->setData(builtinScripts[i]);
1100 	}
1101 	if (!scriptDir.isEmpty()) {
1102 		scriptMenu->addSeparator();
1103 		addDirectoryToMenu(scriptMenu, scriptDir);
1104 	}
1105 	menu.addAction(stopScriptAct);
1106 	menu.addSeparator();
1107 	//  menu.addAction(insertColumnHereAct);
1108 	//  menu.addAction(insertRowHereAct);
1109 	menu.addAction(appendColumnAct);
1110 	menu.addAction(appendRowAct);
1111 	menu.addAction(appendColumnsAct);
1112 	menu.addAction(appendRowsAct);
1113 	menu.addAction(deleteColumnAct);
1114 	menu.addAction(deleteRowAct);
1115 	menu.exec(event->globalPos());
1116 }
1117 
addDirectoryToMenu(QMenu * m,QString dir,int depth)1118 void EventSheet::addDirectoryToMenu(QMenu *m, QString dir, int depth)
1119 {
1120 	if (depth > 4)
1121 		return;
1122 	QDir d(dir);
1123 	QStringList filters;
1124 	filters << "*.py";
1125 	d.setNameFilters(filters);
1126 	QStringList scripts = d.entryList(QDir::Files,QDir::Name);
1127 	QStringList directories = d.entryList(QDir::AllDirs,QDir::Name);
1128 	if (scripts.size() > 0 || directories.size() > 2) {
1129 		QMenu *newMenu = m;
1130 		if (dir != scriptDir)
1131 			newMenu = m->addMenu(dir.mid(dir.lastIndexOf("/")+ 1));
1132 		for (int i = 0; i < directories.size(); i++) {
1133 			if (directories[i] != "." && directories[i] != "..")
1134 				addDirectoryToMenu(newMenu, dir + "/" + directories[i], depth++);
1135 		}
1136 		for (int i = 0; i < scripts.size(); i++) {
1137 			QAction *a = newMenu->addAction(scripts[i], this, SLOT(runScript() ));
1138 			a->setData(dir + "/" + scripts[i]);
1139 		}
1140 	}
1141 }
1142 
keyPressEvent(QKeyEvent * event)1143 void EventSheet::keyPressEvent (QKeyEvent * event) {
1144 	if (event->matches(QKeySequence::Delete)) {
1145 		this->del();
1146 	}
1147 	else if (event->matches(QKeySequence::Cut)) {
1148 		this->cut();
1149 	}
1150 	else if (event->matches(QKeySequence::Copy)) {
1151 		this->copy();
1152 	}
1153 	else if (event->matches(QKeySequence::Paste)) {
1154 		this->paste();
1155 	}
1156 	else if (event->matches(QKeySequence::Undo)
1157 			 || (event->key() == Qt::Key_Z && event->modifiers() == Qt::ControlModifier) ){
1158 		this->undo();
1159 	}
1160 	else if (event->matches(QKeySequence::Redo)
1161 			 || (event->key() == Qt::Key_Z
1162 				 && event->modifiers() == (Qt::ControlModifier | Qt::ShiftModifier)) ) {
1163 		this->redo();
1164 	}
1165 	else if (event->matches(QKeySequence::InsertLineSeparator)) {
1166 		this->sendEvents();
1167 	}
1168 	else {
1169 		//    qDebug() << "EventSheet::keyPressEvent  " << event->key();
1170 		//    event->ignore();
1171 		QTableWidget::keyPressEvent(event);  // Propagate any other events
1172 	}
1173 }
1174 
add(double value)1175 void EventSheet::add(double value)
1176 {
1177 	QModelIndexList list = this->selectedIndexes();
1178 	for (int i = 0; i < list.size(); i++) {
1179 		QTableWidgetItem * item = this->item(list[i].row(), list[i].column());
1180 		if (item != 0 && item->data(Qt::DisplayRole).canConvert(QVariant::Double)) {
1181 			bool ok = false;
1182 			double n = item->data(Qt::DisplayRole).toDouble(&ok);
1183 			if (ok) {
1184 				noHistoryChange = 1;
1185 				item->setData(Qt::DisplayRole,
1186 							  QVariant(n + value));
1187 			}
1188 		}
1189 	}
1190 	noHistoryChange = 0;
1191 	markHistory();
1192 }
1193 
multiply(double value)1194 void EventSheet::multiply(double value)
1195 {
1196 	QModelIndexList list = this->selectedIndexes();
1197 	for (int i = 0; i < list.size(); i++) {
1198 		QTableWidgetItem * item = this->item(list[i].row(), list[i].column());
1199 		if (item != 0 && item->data(Qt::DisplayRole).canConvert(QVariant::Double)) {
1200 			bool ok = false;
1201 			double n = item->data(Qt::DisplayRole).toDouble(&ok);
1202 			if (ok) {
1203 				noHistoryChange = 1;
1204 				item->setData(Qt::DisplayRole,
1205 							  QVariant(n * value));
1206 			}
1207 		}
1208 	}
1209 	noHistoryChange = 0;
1210 	markHistory();
1211 }
1212 
divide(double value)1213 void EventSheet::divide(double value)
1214 {
1215 	QModelIndexList list = this->selectedIndexes();
1216 	for (int i = 0; i < list.size(); i++) {
1217 		QTableWidgetItem * item = this->item(list[i].row(), list[i].column());
1218 		if (item != 0 && item->data(Qt::DisplayRole).canConvert(QVariant::Double)) {
1219 			bool ok = false;
1220 			double n = item->data(Qt::DisplayRole).toDouble(&ok);
1221 			if (ok) {
1222 				noHistoryChange = 1;
1223 				item->setData(Qt::DisplayRole,
1224 							  QVariant(n / value));
1225 			}
1226 		}
1227 	}
1228 	noHistoryChange = 0;
1229 	markHistory();
1230 }
1231 
randomize(double min,double max,int mode)1232 void EventSheet::randomize(double min, double max, int mode)
1233 {
1234 	// Mode 0 =
1235 	// Mode 1 = integers only
1236 	QModelIndexList list = this->selectedIndexes();
1237 	QTime midnight(0, 0, 0);
1238 	qsrand(midnight.secsTo(QTime::currentTime()));
1239 	for (int i = 0; i < list.size(); i++) {
1240 		QTableWidgetItem * item = this->item(list[i].row(), list[i].column());
1241 		if (item == 0) {
1242 			item = new QTableWidgetItem();
1243 			noHistoryChange = 1;
1244 			this->setItem(list[i].row(), list[i].column(), item);
1245 		}
1246 		double value = 0.0;
1247 		if (mode == 0) {
1248 			value = min + ((double) qrand() / (double) RAND_MAX) * (max - min);
1249 		}
1250 		else /*if (mode == 1)*/ {  // Integers only
1251 			value = min + (qrand() % (int) (max - min + 1)); // Include max value as a possibility
1252 		}
1253 		noHistoryChange = 1;
1254 		item->setData(Qt::DisplayRole,
1255 					  QVariant(value));
1256 	}
1257 	noHistoryChange = 0;
1258 	markHistory();
1259 }
1260 
shuffle(int iterations)1261 void EventSheet::shuffle(int iterations)
1262 {
1263 	QTime midnight(0, 0, 0);
1264 	qsrand(midnight.secsTo(QTime::currentTime()));
1265 	QModelIndexList list = this->selectedIndexes();
1266 	if (list.size() < 3)
1267 		return;
1268 	for (int i = 0; i < iterations; i++) { // First traverse to copy values
1269 		int num1 = qrand() % list.size();
1270 		QTableWidgetItem * item1 = this->item(list[num1].row(), list[num1].column());
1271 		int num2 = qrand() % list.size();
1272 		while (num2 == num1) {
1273 			num2 = qrand() % list.size();
1274 		}
1275 		QTableWidgetItem * item2 = this->item(list[num2].row(), list[num2].column());
1276 		QVariant value1 = QVariant();
1277 		if (item1 != 0) {
1278 			value1 = item1->data(Qt::DisplayRole);
1279 		}
1280 		else {
1281 			item1 = new QTableWidgetItem();
1282 			noHistoryChange = 1;
1283 			this->setItem(list[i].row(), list[i].column(), item1);
1284 		}
1285 		if (item2 == 0) {
1286 			item2 = new QTableWidgetItem();
1287 			noHistoryChange = 1;
1288 			this->setItem(list[i].row(), list[i].column(), item2);
1289 		}
1290 		noHistoryChange = 1;
1291 		item1->setData(Qt::DisplayRole,item2->data(Qt::DisplayRole));
1292 		noHistoryChange = 1;
1293 		item2->setData(Qt::DisplayRole,value1);
1294 	}
1295 	noHistoryChange = 0;
1296 	markHistory();
1297 }
1298 
rotate(int amount)1299 void EventSheet::rotate(int amount)
1300 {
1301 	QModelIndexList list = this->selectedIndexes();
1302 	QList<QVariant> oldValues;
1303 	for (int i = 0; i < list.size(); i++) { // First traverse to copy values
1304 		QTableWidgetItem * item = this->item(list[i].row(), list[i].column());
1305 		if (item != 0) {
1306 			oldValues.append(item->data(Qt::DisplayRole));
1307 		}
1308 		else {
1309 			oldValues.append(QVariant());
1310 		}
1311 	}
1312 	for (int i = 0; i < list.size(); i++) { // Then put in rotated values
1313 		QTableWidgetItem * item = this->item(list[i].row(), list[i].column());
1314 		if (item == 0) {
1315 			item = new QTableWidgetItem();
1316 			noHistoryChange = 1;
1317 			this->setItem(list[i].row(), list[i].column(), item);
1318 		}
1319 		int index = (i - amount + list.size())%list.size();
1320 		noHistoryChange = 1;
1321 		item->setData(Qt::DisplayRole,oldValues[index]);
1322 	}
1323 	noHistoryChange = 0;
1324 	markHistory();
1325 }
1326 
fill(double start,double end,double slope)1327 void EventSheet::fill(double start, double end, double slope)
1328 {
1329 	QModelIndexList list = this->selectedIndexes();
1330 	double inc = (end - start) / (list.size() - 1.0);
1331 	double value = start;
1332 	//  double listSize = (double) list.size();
1333 	for (int i = 0; i < list.size(); i++) {
1334 		QTableWidgetItem * item = this->item(list[i].row(), list[i].column());
1335 		if (item == 0) {
1336 			item = new QTableWidgetItem();
1337 			noHistoryChange = 1;
1338 			this->setItem(list[i].row(), list[i].column(), item);
1339 		}
1340 		noHistoryChange = 1;
1341 		item->setData(Qt::DisplayRole,
1342 					  QVariant(value));
1343 		if (slope == 1.0) {
1344 			value += inc;
1345 		}
1346 		else if (slope < 1.0 && slope >= 0.0) {
1347 			value = start;
1348 			value += (end-start) * (exp(((i + 1.0) / (list.size() - 1.0)) * log(slope))-1.0) / (slope-1.0);
1349 		}
1350 		else if (slope > 1.0) {
1351 			value = start;
1352 			value += (end-start) * (exp(((i + 1.0) / (list.size() - 1.0)) * log(slope))-1.0) / (slope-1.0);
1353 		}
1354 	}
1355 	noHistoryChange = 0;
1356 	markHistory();
1357 }
1358 
createActions()1359 void EventSheet::createActions()
1360 {
1361 	// For some reason, the shortcuts set here have no effect and need to be
1362 	// decoded in keyPressEvent... (at least for linux)
1363 	//  cutAct = new QAction(/*QIcon(":/a.png"),*/ tr("Cut"), this);
1364 	////  loopEventsAct->setIconText(tr("Loop Events"));
1365 	//  cutAct->setShortcut(QKeySequence(QKeySequence::Cut));
1366 	//  connect(cutAct, SIGNAL(triggered()), this, SLOT(cut()));
1367 	//
1368 	//  copyAct = new QAction(/*QIcon(":/a.png"),*/ tr("Copy"), this);
1369 	////  loopEventsAct->setIconText(tr("Loop Events"));
1370 	//  copyAct->setShortcut(QKeySequence(QKeySequence::Copy));
1371 	//  connect(copyAct, SIGNAL(triggered()), this, SLOT(copy()));
1372 	//
1373 	//  pasteAct = new QAction(/*QIcon(":/a.png"),*/ tr("Paste"), this);
1374 	////  loopEventsAct->setIconText(tr("Loop Events"));
1375 	//  pasteAct->setShortcut(QKeySequence(QKeySequence::Paste));
1376 	//  connect(pasteAct, SIGNAL(triggered()), this, SLOT(paste()));
1377 
1378 	sendEventsAct = new QAction(/*QIcon(":/a.png"),*/ tr("&Send Events"), this);
1379 	sendEventsAct->setStatusTip(tr("Send Events to Csound"));
1380 	sendEventsAct->setIconText(tr("Send Events"));
1381 	sendEventsAct->setShortcut(QKeySequence(QKeySequence::InsertLineSeparator));
1382 	connect(sendEventsAct, SIGNAL(triggered()), this, SLOT(sendEvents()));
1383 
1384 	sendEventsOffsetAct = new QAction(/*QIcon(":/a.png"),*/ tr("&Send Events without offset"), this);
1385 	sendEventsOffsetAct->setStatusTip(tr("Send Events to Csound without offset"));
1386 	sendEventsOffsetAct->setIconText(tr("Send Events no offset"));
1387 	connect(sendEventsOffsetAct, SIGNAL(triggered()), this, SLOT(sendEventsOffset()));
1388 
1389 	loopSelectionAct = new QAction(/*QIcon(":/a.png"),*/ tr("&Loop Selection"), this);
1390 	loopSelectionAct->setStatusTip(tr("Mark loop to current selection and start looping"));
1391 	loopSelectionAct->setIconText(tr("Loop Events"));
1392 	connect(loopSelectionAct, SIGNAL(triggered()), this, SLOT(loopEvents()));
1393 
1394 	enableLoopAct = new QAction(/*QIcon(":/a.png"),*/ tr("Loop Active"), this);
1395 	enableLoopAct->setStatusTip(tr("Activate Loop"));
1396 	enableLoopAct->setCheckable(true);
1397 	enableLoopAct->setChecked(false);
1398 	connect(enableLoopAct, SIGNAL(toggled(bool)), this, SLOT(setLoopActive(bool)));
1399 
1400 	markLoopAct = new QAction(/*QIcon(":/a.png"),*/ tr("Mark Loop"), this);
1401 	markLoopAct->setStatusTip(tr("Set Loop to selection, without starting loop"));
1402 	connect(markLoopAct, SIGNAL(triggered()), this, SLOT(setLoopRange()));
1403 
1404 	stopAllEventsAct = new QAction(/*QIcon(":/a.png"),*/ tr("&Stop Events"), this);
1405 	stopAllEventsAct->setStatusTip(tr("Stop all running and pending events"));
1406 	stopAllEventsAct->setIconText(tr("Stop Events"));
1407 	connect(stopAllEventsAct, SIGNAL(triggered()), this, SLOT(stopAllEvents()));
1408 
1409 	subtractAct = new QAction(/*QIcon(":/a.png"),*/ tr("&Subtract"), this);
1410 	subtractAct->setStatusTip(tr("Subtract a value from the selected cells"));
1411 	subtractAct->setIconText(tr("Subtract"));
1412 	connect(subtractAct, SIGNAL(triggered()), this, SLOT(subtract()));
1413 
1414 	addAct = new QAction(/*QIcon(":/a.png"),*/ tr("&Add"), this);
1415 	addAct->setStatusTip(tr("Add a value to the selected cells"));
1416 	addAct->setIconText(tr("Add"));
1417 	connect(addAct, SIGNAL(triggered()), this, SLOT(add()));
1418 
1419 	multiplyAct = new QAction(/*QIcon(":/a.png"),*/ tr("&Multiply"), this);
1420 	multiplyAct->setStatusTip(tr("Multiply the selected cells by a value"));
1421 	multiplyAct->setIconText(tr("Multiply"));
1422 	connect(multiplyAct, SIGNAL(triggered()), this, SLOT(multiply()));
1423 
1424 	divideAct = new QAction(/*QIcon(":/a.png"),*/ tr("&Divide"), this);
1425 	divideAct->setStatusTip(tr("Divide the selected cells by a value"));
1426 	divideAct->setIconText(tr("Divide"));
1427 	connect(divideAct, SIGNAL(triggered()), this, SLOT(divide()));
1428 
1429 	randomizeAct = new QAction(/*QIcon(":/a.png"),*/ tr("&Randomize"), this);
1430 	randomizeAct->setStatusTip(tr("Randomize the selected cells"));
1431 	randomizeAct->setIconText(tr("Randomize"));
1432 	connect(randomizeAct, SIGNAL(triggered()), this, SLOT(randomize()));
1433 
1434 	reverseAct = new QAction(/*QIcon(":/a.png"),*/ tr("&Reverse"), this);
1435 	reverseAct->setStatusTip(tr("Reverse the selected cells by column"));
1436 	reverseAct->setIconText(tr("Reverse"));
1437 	connect(reverseAct, SIGNAL(triggered()), this, SLOT(reverse()));
1438 
1439 	shuffleAct = new QAction(/*QIcon(":/a.png"),*/ tr("&Shuffle"), this);
1440 	shuffleAct->setStatusTip(tr("Shuffle the selected cells"));
1441 	shuffleAct->setIconText(tr("Shuffle"));
1442 	connect(shuffleAct, SIGNAL(triggered()), this, SLOT(shuffle()));
1443 
1444 	//  mirrorAct = new QAction(/*QIcon(":/a.png"),*/ tr("&Mirror"), this);
1445 	//  mirrorAct->setStatusTip(tr("Mirror the selected cells"));
1446 	//  mirrorAct->setIconText(tr("Mirror"));
1447 	//  connect(mirrorAct, SIGNAL(triggered()), this, SLOT(mirror()));
1448 
1449 	rotateAct = new QAction(/*QIcon(":/a.png"),*/ tr("&Rotate"), this);
1450 	rotateAct->setStatusTip(tr("Rotate the selected cells"));
1451 	rotateAct->setIconText(tr("Rotate"));
1452 	connect(rotateAct, SIGNAL(triggered()), this, SLOT(rotate()));
1453 
1454 	fillAct = new QAction(/*QIcon(":/a.png"),*/ tr("&Fill Cells"), this);
1455 	fillAct->setStatusTip(tr("Fill selected cells"));
1456 	fillAct->setIconText(tr("Fill"));
1457 	connect(fillAct, SIGNAL(triggered()), this, SLOT(fill()));
1458 
1459 	insertColumnHereAct = new QAction(/*QIcon(":/a.png"),*/ tr("&Insert Column"), this);
1460 	insertColumnHereAct->setStatusTip(tr("Insert a column at the current position"));
1461 	insertColumnHereAct->setIconText(tr("Insert Column"));
1462 	connect(insertColumnHereAct, SIGNAL(triggered()), this, SLOT(insertColumnHere()));
1463 
1464 	insertRowHereAct = new QAction(/*QIcon(":/a.png"),*/ tr("Insert Row"), this);
1465 	insertRowHereAct->setStatusTip(tr("Insert a row at the current position"));
1466 	insertRowHereAct->setIconText(tr("Insert Row"));
1467 	connect(insertRowHereAct, SIGNAL(triggered()), this, SLOT(insertRowHere()));
1468 
1469 	appendColumnAct = new QAction(/*QIcon(":/a.png"),*/ tr("Append Column"), this);
1470 	appendColumnAct->setStatusTip(tr("Append a column to the sheet"));
1471 	appendColumnAct->setIconText(tr("Append Column"));
1472 	connect(appendColumnAct, SIGNAL(triggered()), this, SLOT(appendColumn()));
1473 
1474 	appendRowAct = new QAction(/*QIcon(":/a.png"),*/ tr("&Append Row"), this);
1475 	appendRowAct->setStatusTip(tr("Append a row to the sheet"));
1476 	appendRowAct->setIconText(tr("Append Row"));
1477 	connect(appendRowAct, SIGNAL(triggered()), this, SLOT(appendRow()));
1478 
1479 	appendColumnsAct = new QAction(/*QIcon(":/a.png"),*/ tr("Append Columns..."), this);
1480 	appendColumnsAct->setStatusTip(tr("Append columns to the sheet"));
1481 	appendColumnsAct->setIconText(tr("Append Columns..."));
1482 	connect(appendColumnsAct, SIGNAL(triggered()), this, SLOT(appendColumns()));
1483 
1484 	appendRowsAct = new QAction(/*QIcon(":/a.png"),*/ tr("Append Rows..."), this);
1485 	appendRowsAct->setStatusTip(tr("Append rows to the sheet"));
1486 	appendRowsAct->setIconText(tr("Append Rows..."));
1487 	connect(appendRowsAct, SIGNAL(triggered()), this, SLOT(appendRows()));
1488 
1489 	deleteColumnAct = new QAction(/*QIcon(":/a.png"),*/ tr("Delete Last Column"), this);
1490 	deleteColumnAct->setStatusTip(tr("Delete Last Column"));
1491 	deleteColumnAct->setIconText(tr("Delete Last Column"));
1492 	connect(deleteColumnAct, SIGNAL(triggered()), this, SLOT(deleteColumn()));
1493 
1494 	deleteRowAct = new QAction(/*QIcon(":/a.png"),*/ tr("Delete Selected Rows"), this);
1495 	deleteRowAct->setStatusTip(tr("Delete Rows"));
1496 	deleteRowAct->setIconText(tr("Delete Rows"));
1497 	connect(deleteRowAct, SIGNAL(triggered()), this, SLOT(deleteRows()));
1498 
1499 	stopScriptAct = new QAction(/*QIcon(":/a.png"),*/ tr("Stop running script"), this);
1500 	//  stopScriptAct->setStatusTip(tr("Delete Rows"));
1501 	//  stopScriptAct->setIconText(tr("Delete Rows"));
1502 	connect(stopScriptAct, SIGNAL(triggered()), this, SLOT(stopScript()));
1503 }
1504 
parseLine(QString line)1505 QList<QPair<QString, QString> > EventSheet::parseLine(QString line)
1506 {
1507 	QList<QPair<QString, QString> > list;
1508 	QPair<QString, QString> field;
1509 
1510 	int count = 0;
1511 	bool formula = false;
1512 	bool string = false;
1513 	bool isp = true; // Assume starting on a pfield, not white space
1514 	QString pvalue = "";
1515 	QString spacing = "";
1516 	if (!line.isEmpty() && (line[0] == 'i' || line[0] == 'f') ) { // A space is not necessary between these and the first p-field
1517 		field.first = QString(line[0]);
1518 		field.second = QString();
1519 		count++;
1520 		isp = false; // consider p-field done with first character.
1521 	}
1522 	while (count < line.size()) {  // More characters left
1523 		if (isp == true || formula || string) { // Processing p-field
1524 			if (line[count] == '"') { // string takes precedence over formulas and comments
1525 				string = !string;
1526 			}
1527 			else if (line[count] == '[') { //Start of formula
1528 				formula = true;  // This should never happen as this character should always be after whitespace....
1529 			}
1530 			else if (line[count] == ']') { //End of formula
1531 				formula = false;
1532 			}
1533 			else if (line[count] == ';') { // comment
1534 				if (count > 0) {
1535 					// First add current pfield, in case there is previous data
1536 					field.first = pvalue;
1537 					field.second = spacing;
1538 					list.append(field);
1539 				}
1540 				// Now add comment
1541 				count++;
1542 				QString comment = line.mid(count);
1543 				QStringList parts = comment.split(";");
1544 				for (int i = 0; i < parts.size(); i++) {
1545 					if (i==0) {  // Only put the ; character visible on the first column
1546 						field.first = ";" + parts[i];
1547 					}
1548 					else {
1549 						field.first = parts[i];
1550 					}
1551 					field.second = "";
1552 					list.append(field);
1553 				}
1554 				isp = false;  // last p-field has been processed here
1555 				break; // Nothing more todo for this line
1556 			}  // End of comment processing
1557 			// ----
1558 			if ( (line[count] == ' ' || line[count] == '\t') && !formula && !string) { // White space so p-field has finished
1559 				spacing = line[count];
1560 				isp = false;
1561 			}
1562 			else { // A character or formula so continue p-field processing
1563 				pvalue.append(line[count]);
1564 				field.first = pvalue;
1565 			}
1566 		}
1567 		else { // Processing white space
1568 			if (line[count] == '"') { // string
1569 				string = !string;
1570 			}
1571 			else if (line[count] == '[') { //Start of formula
1572 				formula = true;
1573 			}
1574 			else if (line[count] == ';') { // comment
1575 				field.first = pvalue;
1576 				field.second = spacing;
1577 				list.append(field);  //Should only append when pcount is incremented
1578 				count++;
1579 				QString comment = line.mid(count);
1580 				QStringList parts = comment.split(";");
1581 				for (int i = 0; i < parts.size(); i++) {
1582 					if (i==0) {  // Only put the ; character visible on the first column
1583 						field.first = ";" + parts[i];
1584 					}
1585 					else {
1586 						field.first = parts[i];
1587 					}
1588 					field.second = "";
1589 					list.append(field);
1590 				}
1591 				isp = false;  // last p-field has been processed here
1592 				break; // Nothing more todo for this line
1593 			}
1594 			// ---
1595 			if (line[count] != ' ' && line[count] != '\t') { // Not White space so new p-field has started
1596 				field.second = spacing;
1597 				list.append(field);  //Should only append when pcount is incremented
1598 				isp = true;
1599 				pvalue = line[count];
1600 				field.first = pvalue;
1601 				field.second = "";
1602 				spacing = "";
1603 			}
1604 			else { // Continue p-field processing
1605 				spacing.append(line[count]);
1606 			}
1607 		}
1608 		count++;
1609 	}
1610 	// Process final p-field
1611 	if (isp == true) {
1612 		field.first = pvalue;
1613 		field.second = "";
1614 		list.append(field);
1615 	}
1616 	return list;
1617 }
1618 
newSelection()1619 void EventSheet::newSelection()
1620 {
1621 	QModelIndexList list = this->selectedIndexes();
1622 	if (list.size() > 1) {
1623 		this->setDragDropMode(QAbstractItemView::DragDrop); // Allow dragging items
1624 	}
1625 	else {
1626 		this->setDragDropMode(QAbstractItemView::NoDragDrop); // Allow extending selection
1627 	}
1628 }
1629 
cellDoubleClickedSlot(int,int)1630 void EventSheet::cellDoubleClickedSlot(int /*row*/, int /*column*/)
1631 {
1632 	markHistory();
1633 }
1634 
cellChangedSlot(int row,int column)1635 void EventSheet::cellChangedSlot(int row, int column)
1636 {
1637     if (this->item(row, column) != 0 && this->item(row, column)->data(Qt::DisplayRole).toString() != 0) {
1638 		while (column > 0) {
1639 			column--;
1640 			QTableWidgetItem * item = this->item(row, column);
1641 			//      qDebug() << "EventSheet::cellChangedSlot " << column;
1642             if (item == 0 || item->data(Qt::DisplayRole).toString() == "") {
1643 				this->setItem(row, column, this->takeItem(row, column + 1));
1644 			}
1645 			else {
1646 				break;
1647 			}
1648 		}
1649 	}
1650 	if (noHistoryChange == 0) {
1651 		markHistory();
1652 	}
1653 	else {
1654 		noHistoryChange = 0;
1655 	}
1656 	emit modified();
1657 }
1658 
stopScript()1659 void EventSheet::stopScript()
1660 {
1661 	m_stopScript = true;
1662 }
1663