1 /*
2 	Copyright (C) 2008, 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 "qutewidget.h"
24 #include "widgetlayout.h"
25 
QuteWidget(QWidget * parent)26 QuteWidget::QuteWidget(QWidget *parent):
27 	QWidget(parent), dialog(NULL)
28 {
29 	propertiesAct = new QAction(tr("&Properties"), this);
30 	propertiesAct->setStatusTip(tr("Open widget properties"));
31 	connect(propertiesAct, SIGNAL(triggered()), this, SLOT(openProperties()));
32 
33 	addChn_kAct = new QAction(tr("Add chn_k to csd"),this);
34 	addChn_kAct->setStatusTip(tr("Add chn_k definitionto ;;channels section in editor"));
35 	connect(addChn_kAct, SIGNAL(triggered()), this, SLOT(addChn_k()));
36 
37 	m_value = 0.0;
38 	m_value2 = 0.0;
39 	m_stringValue = "";
40 	m_valueChanged = false;
41 	m_value2Changed = false;
42 	m_locked = false;
43     m_description = "";
44     // used by all widgets which need access to the api (TableDisplay)
45     // TODO: adapt Scope and Graph to use this instead of implementing their own
46     m_csoundUserData = nullptr;
47 
48 	this->setMinimumSize(2,2);
49 	this->setMouseTracking(true); // Necessary to pass mouse tracking to widget panel for _MouseX channels
50 
51 	setProperty("QCS_x", 0);
52 	setProperty("QCS_y", 0);
53     //setProperty("width", 20);
54     //setProperty("height", 20);
55     setProperty("QCS_width", 20);
56     setProperty("QCS_height", 20);
57     setProperty("QCS_uuid", QUuid::createUuid().toString());
58 	setProperty("QCS_visible", true);
59 	setProperty("QCS_midichan", 0);
60 	setProperty("QCS_midicc", -3);
61     setProperty("QCS_description", "");
62 }
63 
~QuteWidget()64 QuteWidget::~QuteWidget()
65 {
66 }
67 
setWidgetGeometry(int x,int y,int w,int h)68 void QuteWidget::setWidgetGeometry(int x, int y, int w, int h)
69 {
70 	//  qDebug() << "QuteWidget::setWidgetGeometry" <<x<<y<<w<<h;
71 	Q_ASSERT(w > 0 && h > 0);
72 	//	Q_ASSERT(x > 0 && y > 0 and w > 0 && h > 0);
73 	this->setGeometry(QRect(x,y,w,h));
74 	m_widget->blockSignals(true);
75 	m_widget->setGeometry(QRect(0,0,w,h));
76 	m_widget->blockSignals(false);
77 	//  this->markChanged();  // It's better not to have geometry changes trigger markChanged as geometry changes can occur for various reasons (e.g. when calling applyInternalProperties)
78 }
79 
setValue(double value)80 void QuteWidget::setValue(double value)
81 {
82 #ifdef  USE_WIDGET_MUTEX
83 	widgetLock.lockForWrite();
84 #endif
85 	m_value = value;
86 	m_valueChanged = true;
87 #ifdef  USE_WIDGET_MUTEX
88 	widgetLock.unlock();
89 #endif
90 }
91 
setValue2(double value)92 void QuteWidget::setValue2(double value)
93 {
94 #ifdef  USE_WIDGET_MUTEX
95 	widgetLock.lockForWrite();
96 #endif
97 	m_value2 = value;
98 	m_value2Changed = true;
99 #ifdef  USE_WIDGET_MUTEX
100 	widgetLock.unlock();
101 #endif
102 }
103 
setValue(QString value)104 void QuteWidget::setValue(QString value)
105 {
106 #ifdef  USE_WIDGET_MUTEX
107 	widgetLock.lockForWrite();
108 #endif
109 	m_stringValue = value;
110 	m_valueChanged = true;
111 #ifdef  USE_WIDGET_MUTEX
112 	widgetLock.unlock();
113 #endif
114 }
115 
setMidiValue(int)116 void QuteWidget::setMidiValue(int /* value */)
117 {
118     qDebug() << "Not available for this widget." << this;
119 }
120 
setMidiValue2(int)121 void QuteWidget::setMidiValue2(int /* value */)
122 {
123     qDebug() << "Not available for this widget." << this;
124 }
125 
widgetMessage(QString path,QString text)126 void QuteWidget::widgetMessage(QString path, QString text)
127 {
128     qDebug() << text;
129 	if (property(path.toLocal8Bit()).isValid()) {
130 		setProperty(path.toLocal8Bit(), text);
131 		//    applyInternalProperties();
132 	}
133 }
134 
widgetMessage(QString path,double value)135 void QuteWidget::widgetMessage(QString path, double value)
136 {
137     qDebug() << value;
138 	if (property(path.toLocal8Bit()).isValid()) {
139 		setProperty(path.toLocal8Bit(), value);
140 		//    applyInternalProperties();
141 	}
142 }
143 
getChannelName()144 QString QuteWidget::getChannelName()
145 {
146 	//  widgetLock.lockForRead();
147 	QString name = m_channel;
148 	//  widgetLock.unlock();
149 	return name;
150 }
151 
getChannel2Name()152 QString QuteWidget::getChannel2Name()
153 {
154 	//  widgetLock.lockForRead();
155 	QString name = m_channel2;
156 	//  widgetLock.unlock();
157 	return name;
158 }
159 
getCabbageLine()160 QString QuteWidget::getCabbageLine()
161 {
162 	//Widgets return empty strings when not supported
163 	return QString("");
164 }
165 
createXmlWriter(QXmlStreamWriter & s)166 void QuteWidget::createXmlWriter(QXmlStreamWriter &s)
167 {
168 	s.setAutoFormatting(true);
169 	s.writeStartElement("bsbObject");
170 	s.writeAttribute("type", getWidgetType());
171 
172 	s.writeAttribute("version", QCS_CURRENT_XML_VERSION);  // Only for compatibility with blue (absolute values)
173 
174 	s.writeTextElement("objectName", m_channel);
175 	s.writeTextElement("x", QString::number(x()));
176 	s.writeTextElement("y", QString::number(y()));
177 	s.writeTextElement("width", QString::number(width()));
178 	s.writeTextElement("height", QString::number(height()));
179 	s.writeTextElement("uuid", property("QCS_uuid").toString());
180 	s.writeTextElement("visible", property("QCS_visible").toBool() ? "true":"false");
181 	s.writeTextElement("midichan", QString::number(property("QCS_midichan").toInt()));
182 	s.writeTextElement("midicc", QString::number(property("QCS_midicc").toInt()));
183     s.writeTextElement("description", m_description);
184 }
185 
getValue()186 double QuteWidget::getValue()
187 {
188     // When reimplementing this, remember to use the widget mutex to protect data,
189     // as this can be called from many different places
190 #ifdef  USE_WIDGET_MUTEX
191 	widgetLock.lockForRead();
192 #endif
193 	double value = m_value;
194 #ifdef  USE_WIDGET_MUTEX
195 	widgetLock.unlock();
196 #endif
197 	return value;
198 }
199 
getValue2()200 double QuteWidget::getValue2()
201 {
202 	// When reimplementing this, remember to use the widget mutex to protect data, as this can be called from many different places
203 #ifdef  USE_WIDGET_MUTEX
204 	widgetLock.lockForRead();
205 #endif
206 	double value = m_value2;
207 #ifdef  USE_WIDGET_MUTEX
208 	widgetLock.unlock();
209 #endif
210 	return value;
211 }
212 
getStringValue()213 QString QuteWidget::getStringValue()
214 {
215 	// When reimplementing this, remember to use the widget mutex to protect data, as this can be called from many different places
216 #ifdef  USE_WIDGET_MUTEX
217 	widgetLock.lockForRead();
218 #endif
219 	QString value = m_stringValue;
220 #ifdef  USE_WIDGET_MUTEX
221 	widgetLock.unlock();
222 #endif
223 	return value;
224 }
225 
getDescription()226 QString QuteWidget::getDescription()
227 {
228     return m_description;
229 }
230 
getCsladspaLine()231 QString QuteWidget::getCsladspaLine()
232 {
233 	//Widgets return empty strings when not supported
234 	return QString("");
235 }
236 
getQml()237 QString QuteWidget::getQml()
238 {
239     //Widgets return empty strings when not supported
240 	return QString();
241 }
242 
getUuid()243 QString QuteWidget::getUuid()
244 {
245 	if (property("QCS_uuid").isValid())
246 		return property("QCS_uuid").toString();
247 	else
248 		return QString();
249 }
250 
applyInternalProperties()251 void QuteWidget::applyInternalProperties()
252 {
253 	//  qDebug() << "QuteWidget::applyInternalProperties()";
254 #ifdef  USE_WIDGET_MUTEX
255 	widgetLock.lockForRead();
256 #endif
257 	int x,y,width, height;
258 	x = property("QCS_x").toInt();
259 	y = property("QCS_y").toInt();
260 	width = property("QCS_width").toInt();
261 	height = property("QCS_height").toInt();
262 	setWidgetGeometry(x,y,width, height);
263 	m_channel = property("QCS_objectName").toString();
264 	m_channel2 = property("QCS_objectName2").toString();
265 	m_midicc = property("QCS_midicc").toInt();
266 	m_midichan = property("QCS_midichan").toInt();
267 	setVisible(property("QCS_visible").toBool());
268 	m_valueChanged = true;
269     m_description = property("QCS_description").toString();
270 #ifdef  USE_WIDGET_MUTEX
271 	widgetLock.unlock();
272 #endif
273 }
274 
markChanged()275 void QuteWidget::markChanged()
276 {
277 	emit widgetChanged(this);
278 }
279 
canFocus(bool can)280 void QuteWidget::canFocus(bool can)
281 {
282 	if (can) {
283 		this->setFocusPolicy(Qt::StrongFocus);
284 		m_widget->setFocusPolicy(Qt::StrongFocus);
285 	}
286 	else {
287 		this->setFocusPolicy(Qt::NoFocus);
288 		m_widget->setFocusPolicy(Qt::NoFocus);
289 	}
290 }
291 
updateDialogWindow(int cc,int channel)292 void QuteWidget::updateDialogWindow(int cc, int channel) // to update values from midi Learn window to widget properties' dialog
293 {
294 
295 	if (!dialog) {
296 		qDebug() << "Dialog window not careated";
297 		return;
298 	}
299 
300 	if (dialog->isVisible() && acceptsMidi()) {
301 		midiccSpinBox->setValue(cc);
302 		midichanSpinBox->setValue(channel);
303 	}
304 }
305 
contextMenuEvent(QContextMenuEvent * event)306 void QuteWidget::contextMenuEvent(QContextMenuEvent *event)
307 {
308 	popUpMenu(event->globalPos());
309 }
310 
popUpMenu(QPoint pos)311 void QuteWidget::popUpMenu(QPoint pos)
312 {
313     if (m_locked) {
314 		return;
315 	}
316 	QMenu menu(this);
317 	menu.addAction(propertiesAct);
318 	menu.addSeparator();
319 
320 	if (!m_channel.isEmpty() || !m_channel2.isEmpty()) {
321 		menu.addAction(addChn_kAct);
322 		menu.addSeparator();
323 	}
324 
325 	if (acceptsMidi()) {
326         menu.addAction(tr("MIDI learn"), this, SLOT(openMidiDialog()) );
327 		menu.addSeparator();
328 	}
329 
330 	QList<QAction *> actionList = getParentActionList();
331 
332 	for (int i = 0; i < actionList.size(); i++) {
333         auto action = actionList[i];
334         if(action == nullptr)
335             menu.addSeparator();
336         else
337             menu.addAction(action);
338 	}
339 
340 	menu.addSeparator();
341 
342     WidgetLayout *layout = static_cast<WidgetLayout *>(this->parentWidget());
343     layout->setCurrentPosition(layout->mapFromGlobal(pos));
344 
345     menu.addAction(layout->storePresetAct);
346 	menu.addAction(layout->newPresetAct);
347 	menu.addAction(layout->recallPresetAct);
348 
349     menu.addSeparator();
350 
351     QMenu presetMenu(tr("Presets"), &menu);
352 
353 	QList<int> list = layout->getPresetNums();
354 	for (int i = 0; i < list.size(); i++) {
355 		QAction *act = new QAction(layout->getPresetName(list[i]), &menu);
356 		act->setData(i);
357 		connect(act, SIGNAL(triggered()), layout, SLOT(loadPresetFromAction()));
358 		presetMenu.addAction(act);
359 	}
360 
361     /*
362     menu.addSeparator();
363 
364     QMenu createMenu(tr("Create New", "Menu name in widget right-click menu"),&menu);
365     createMenu.addAction(layout->createSliderAct);
366     createMenu.addAction(layout->createLabelAct);
367     createMenu.addAction(layout->createDisplayAct);
368     createMenu.addAction(layout->createScrollNumberAct);
369     createMenu.addAction(layout->createLineEditAct);
370     createMenu.addAction(layout->createSpinBoxAct);
371     createMenu.addAction(layout->createButtonAct);
372     createMenu.addAction(layout->createKnobAct);
373     createMenu.addAction(layout->createCheckBoxAct);
374     createMenu.addAction(layout->createMenuAct);
375     createMenu.addAction(layout->createMeterAct);
376     createMenu.addAction(layout->createConsoleAct);
377     createMenu.addAction(layout->createGraphAct);
378     createMenu.addAction(layout->createScopeAct);
379 
380     menu.addMenu(&createMenu);
381     */
382 	menu.exec(pos);
383 }
384 
openProperties()385 void QuteWidget::openProperties()
386 {
387 	createPropertiesDialog();
388 
389 	connect(acceptButton, SIGNAL(released()), dialog, SLOT(accept()));
390 	connect(dialog, SIGNAL(accepted()), this, SLOT(apply()));
391 	connect(applyButton, SIGNAL(released()), this, SLOT(apply()));
392 	connect(cancelButton, SIGNAL(released()), dialog, SLOT(close()));
393 	if (acceptsMidi()) {
394 		connect(midiLearnButton, SIGNAL(released()),this, SLOT(openMidiDialog()));
395 	}
396 	dialog->exec();
397 	if (dialog->result() != QDialog::Accepted) {
398 		qDebug() << "QuteWidget::openProperties() dialog not accepted";
399 	}
400 	//  dialog->deleteLater();
401 	parentWidget()->setFocus(Qt::OtherFocusReason); // For some reason focus is grabbed away from the layout, but this doesn't solve the problem...
402 }
403 
404 
deleteWidget()405 void QuteWidget::deleteWidget()
406 {
407 	//   qDebug("QuteWidget::deleteWidget()");
408 	emit(deleteThisWidget(this));
409 }
410 
openMidiDialog()411 void QuteWidget::openMidiDialog()
412 {
413 	//createPropertiesDialog(); <- tryout for midi learn from context menu
414 	emit showMidiLearn(this);
415 }
416 
addChn_k()417 void QuteWidget::addChn_k()
418 {
419 	//qDebug()<<Q_FUNC_INFO << m_channel << m_channel2;
420 	if (!m_channel.isEmpty()) {
421 		emit addChn_kSignal(m_channel);
422 	}
423 	if (!m_channel2.isEmpty()) {
424 		emit addChn_kSignal(m_channel2);
425 	}
426 }
427 
createPropertiesDialog()428 void QuteWidget::createPropertiesDialog()
429 {
430     qDebug() << "QuteWidget::createPropertiesDialog()---Dynamic Properties:\n"
431              << dynamicPropertyNames ();
432     int footerRow = 20;
433 	dialog = new QDialog(this);
434     dialog->resize(360, 300);
435 	//  dialog->setModal(true);
436 	layout = new QGridLayout(dialog);
437     QLabel *label;
438 
439     label = new QLabel("X =", dialog);
440     layout->addWidget(label, 0, 0, Qt::AlignRight|Qt::AlignVCenter);
441 
442     xSpinBox = new QSpinBox(dialog);
443 	xSpinBox->setMaximum(9999);
444 	layout->addWidget(xSpinBox, 0, 1, Qt::AlignLeft|Qt::AlignVCenter);
445 
446     label = new QLabel("Y =", dialog);
447     layout->addWidget(label, 0, 2, Qt::AlignRight|Qt::AlignVCenter);
448 
449     ySpinBox = new QSpinBox(dialog);
450 	ySpinBox->setMaximum(9999);
451 	layout->addWidget(ySpinBox, 0, 3, Qt::AlignLeft|Qt::AlignVCenter);
452 
453     label = new QLabel(tr("Width ="), dialog);
454     layout->addWidget(label, 1, 0, Qt::AlignRight|Qt::AlignVCenter);
455 
456     wSpinBox = new QSpinBox(dialog);
457 	wSpinBox->setMaximum(9999);
458 	layout->addWidget(wSpinBox, 1, 1, Qt::AlignLeft|Qt::AlignVCenter);
459 
460     label = new QLabel(tr("Height ="), dialog);
461     layout->addWidget(label, 1, 2, Qt::AlignRight|Qt::AlignVCenter);
462 
463     hSpinBox = new QSpinBox(dialog);
464 	hSpinBox->setMaximum(9999);
465 	layout->addWidget(hSpinBox, 1, 3, Qt::AlignLeft|Qt::AlignVCenter);
466 
467     channelLabel = new QLabel(tr("Channel ="), dialog);
468     layout->addWidget(channelLabel, 3, 0, Qt::AlignRight|Qt::AlignVCenter);
469 
470     nameLineEdit = new QLineEdit(dialog);
471 	nameLineEdit->setFocus(Qt::OtherFocusReason);
472 	nameLineEdit->selectAll();
473     layout->addWidget(nameLineEdit, 3, 1, 1, 3, Qt::AlignLeft|Qt::AlignVCenter);
474 
475     label = new QLabel(tr("Description ="), dialog);
476     layout->addWidget(label, footerRow-2, 0, Qt::AlignRight|Qt::AlignVCenter);
477 
478     descriptionLineEdit = new QLineEdit(dialog);
479     descriptionLineEdit->setMinimumWidth(300);
480     layout->addWidget(descriptionLineEdit, footerRow-2, 1, 1, 4, Qt::AlignLeft|Qt::AlignVCenter);
481 
482 
483     if (acceptsMidi()) { // only when MIDI-enabled widgets
484         int midiRow = footerRow - 1;
485         label = new QLabel("MIDI CC =", dialog);
486         layout->addWidget(label, midiRow, 0, Qt::AlignRight|Qt::AlignVCenter);
487 
488         midiccSpinBox = new QSpinBox(dialog);
489 		midiccSpinBox->setRange(0,119);
490         layout->addWidget(midiccSpinBox, midiRow, 1, Qt::AlignLeft|Qt::AlignVCenter);
491 
492         label = new QLabel("MIDI Channel =", dialog);
493         layout->addWidget(label, midiRow, 2, Qt::AlignRight|Qt::AlignVCenter);
494 
495         midichanSpinBox = new QSpinBox(dialog);
496 		midichanSpinBox->setRange(0,127);
497         layout->addWidget(midichanSpinBox, midiRow,3, Qt::AlignLeft|Qt::AlignVCenter);
498 
499         midiLearnButton = new QPushButton(tr("MIDI learn"));
500         layout->addWidget(midiLearnButton, midiRow, 4, Qt::AlignLeft|Qt::AlignVCenter);
501 	}
502 	acceptButton = new QPushButton(tr("Ok"));
503 	acceptButton->setDefault(true);
504     layout->addWidget(acceptButton, footerRow, 3, Qt::AlignCenter|Qt::AlignVCenter);
505 
506     applyButton = new QPushButton(tr("Apply"));
507     layout->addWidget(applyButton, footerRow, 1, Qt::AlignCenter|Qt::AlignVCenter);
508 	cancelButton = new QPushButton(tr("Cancel"));
509     layout->addWidget(cancelButton, footerRow, 2, Qt::AlignCenter|Qt::AlignVCenter);
510 
511 #ifdef  USE_WIDGET_MUTEX
512 	widgetLock.lockForRead();
513 #endif
514 	xSpinBox->setValue(this->x());
515 	ySpinBox->setValue(this->y());
516 	wSpinBox->setValue(this->width());
517 	hSpinBox->setValue(this->height());
518 	nameLineEdit->setText(getChannelName());
519     descriptionLineEdit->setText(getDescription());
520 	if (acceptsMidi()) {
521         midiccSpinBox->setValue(this->m_midicc);
522         midichanSpinBox->setValue(this->m_midichan);
523     }
524 #ifdef  USE_WIDGET_MUTEX
525 	widgetLock.unlock();
526 #endif
527 }
528 
applyProperties()529 void QuteWidget::applyProperties()
530 {
531 //	qDebug();
532 #ifdef  USE_WIDGET_MUTEX
533 	widgetLock.lockForRead();
534 #endif
535 	setProperty("QCS_objectName", nameLineEdit->text());
536 	setProperty("QCS_x", xSpinBox->value());
537 	setProperty("QCS_y",ySpinBox->value());
538 	setProperty("QCS_width", wSpinBox->value());
539 	setProperty("QCS_height", hSpinBox->value());
540 	if (acceptsMidi()) {
541 		setProperty("QCS_midicc", midiccSpinBox->value());
542 		setProperty("QCS_midichan", midichanSpinBox->value());
543 	}
544     setProperty("QCS_description", descriptionLineEdit->text());
545 #ifdef  USE_WIDGET_MUTEX
546 	widgetLock.unlock();
547 #endif
548 	applyInternalProperties();
549 	//  setChannelName(nameLineEdit->text());
550 	//  setWidgetGeometry(xSpinBox->value(), ySpinBox->value(), wSpinBox->value(), hSpinBox->value());
551 
552 	//  this->setMouseTracking(true); // Necessary to pass mouse tracking to widget panel for _MouseX channels
553 	emit(widgetChanged(this));
554 	emit propertiesAccepted();
555 	parentWidget()->setFocus(Qt::PopupFocusReason); // For some reason focus is grabbed away from the layout
556 	m_valueChanged = true;
557 }
558 
getParentActionList()559 QList<QAction *> QuteWidget::getParentActionList()
560 {
561 	QList<QAction *> actionList;
562 	// A bit of a kludge... Must get the Widget Panel, which is the parent to the widget which
563 	// holds the actual QuteWidgets
564 	WidgetLayout *layout = static_cast<WidgetLayout *>(this->parentWidget());
565     actionList.append(layout->alignLeftAct);
566 	actionList.append(layout->alignRightAct);
567 	actionList.append(layout->alignTopAct);
568 	actionList.append(layout->alignBottomAct);
569     actionList.append(nullptr);
570     actionList.append(layout->alignCenterHorizontalAct);
571 	actionList.append(layout->alignCenterVerticalAct);
572     actionList.append(nullptr);
573     actionList.append(layout->sendToBackAct);
574     actionList.append(layout->sendToFrontAct);
575     actionList.append(nullptr);
576     actionList.append(layout->distributeHorizontalAct);
577     actionList.append(layout->distributeVerticalAct);
578     actionList.append(nullptr);
579     actionList.append(layout->copyAct);
580     actionList.append(layout->pasteAct);
581     actionList.append(layout->cutAct);
582     actionList.append(layout->deleteAct);
583     actionList.append(layout->duplicateAct);
584 
585     // FIXME put edit action in menu
586 	//  actionList.append(layout->editAct);
587 	return actionList;
588 }
589 
apply()590 void QuteWidget::apply()
591 {
592 	applyProperties();
593 }
594