Vuo 2.4.4
Loading...
Searching...
No Matches
VuoMetadataEditor.cc
Go to the documentation of this file.
1
10#include "VuoMetadataEditor.hh"
11#include "ui_VuoMetadataEditor.h"
12
13#include "VuoComposition.hh"
15#include "VuoEditor.hh"
17#include "VuoEditorWindow.hh"
18#include "VuoFileUtilities.hh"
19#include "VuoStringUtilities.hh"
20
21map<QString, QString> VuoMetadataEditor::descriptionForLicense;
22const QString VuoMetadataEditor::otherLicenseTitle = "Other";
23const QString VuoMetadataEditor::placeholderIconText = "Icon for Exported App";
24
28VuoMetadataEditor::VuoMetadataEditor(VuoEditorComposition *composition, QWidget *parent, Qt::WindowFlags flags, bool isCodeEditor) :
29 QDialog(parent, flags),
30 ui(new Ui::VuoMetadataEditor)
31{
32 this->composition = composition;
33
34 ui->setupUi(this);
35 ui->tabWidget->setCurrentIndex(0);
36 setFixedSize(size());
37
38 ui->descriptionDetailsLabel->setText("<style>a{color:#0099a9;}</style>"
39 + tr("What is the purpose of this composition? You can use <a href='%1'>Markdown</a> formatting.")
40 .arg("https://daringfireball.net/projects/markdown/basics"));
41
42 // @todo https://b33p.net/kosada/node/4946 Limit drop area to icon preview frame.
43 setAcceptDrops(true);
44
45 populateLicenseInfo();
46
47 // Dynamically update placeholder texts that are derived from other text field values.
48 connect(ui->name, &QLineEdit::textEdited, this, &VuoMetadataEditor::updatePlaceholderTexts);
49
50 // Update license text and link for the current menu selection.
51 connect(ui->licenseTitle, &QComboBox::currentTextChanged, this, &VuoMetadataEditor::updateLicenseFields);
52 connect(ui->licenseText, &QTextEdit::textChanged, this, &VuoMetadataEditor::recordCustomLicenseText);
53 connect(ui->licenseLink, &QLineEdit::textChanged, this, &VuoMetadataEditor::recordCustomLicenseText);
54
55 connect(ui->homepageLink, &QLineEdit::editingFinished, this, &VuoMetadataEditor::sanitizeURL);
56 connect(ui->documentationLink, &QLineEdit::editingFinished, this, &VuoMetadataEditor::sanitizeURL);
57
58 // If editing text code, hide fields that apply only to compositions.
59 ui->iconFrame->setVisible(! isCodeEditor);
60 ui->iconLabel->setVisible(! isCodeEditor);
61 ui->bundleIdentifier->setVisible(! isCodeEditor);
62 ui->bundleIdentifierLabel->setVisible(! isCodeEditor);
63 ui->bundleIdentifierDetailsLabel->setVisible(! isCodeEditor);
64 ui->fxPlugGroup->setVisible(! isCodeEditor);
65 ui->fxPlugGroupLabel->setVisible(! isCodeEditor);
66 ui->fxPlugGroupDetailsLabel->setVisible(! isCodeEditor);
67
68 // If editing a composition, hide fields that apply only to text code.
69 ui->categories->setVisible(isCodeEditor);
70 ui->categoriesLabel->setVisible(isCodeEditor);
71 ui->categoriesDetailsLabel->setVisible(isCodeEditor);
72}
73
78{
79 // Name
80 ui->name->setText(QString::fromStdString( composition->getBase()->getMetadata()->getCustomizedName() ));
81
82 // Version
83 ui->version->setText(composition->getBase()->getMetadata()->getVersion().c_str());
84
85 // Icon
86 iconURL = composition->getBase()->getMetadata()->getIconURL().c_str();
87 if (!iconURL.isEmpty())
88 {
89 string relativeDir, file, ext;
90 VuoFileUtilities::splitPath(iconURL.toUtf8().constData(), relativeDir, file, ext);
91 string iconFileName = file;
92 if (!ext.empty())
93 {
94 iconFileName += ".";
95 iconFileName += ext;
96 }
97
98 QDir compositionDir(QDir(composition->getBase()->getDirectory().c_str()).canonicalPath());
99 QString iconAbsolutePath = compositionDir.filePath(QDir(relativeDir.c_str()).filePath(iconFileName.c_str()));
100
101 QImageReader reader(iconAbsolutePath);
102 if (reader.canRead())
103 {
104 QIcon icon(iconAbsolutePath);
105 QPixmap pixmap = icon.pixmap(icon.actualSize(ui->iconLabel->size()-QSize(4,4)));
106 ui->iconLabel->setPixmap(pixmap);
107 }
108 else
109 ui->iconLabel->setText(placeholderIconText);
110 }
111 else // if (iconURL.isEmpty())
112 {
113 ui->iconLabel->setText(placeholderIconText);
114 }
115
116 // Description
117 ui->description->setText(composition->getBase()->getMetadata()->getDescription().c_str());
118 ui->description->setFocus();
119 ui->description->moveCursor(QTextCursor::End);
120
121 // Keywords
122 string keywords = VuoStringUtilities::join(composition->getBase()->getMetadata()->getKeywords(), ", ");
123 ui->keywords->setText(QString::fromStdString(keywords));
124 ui->keywords->moveCursor(QTextCursor::End);
125
126 // Copyright
127 ui->copyright->setText(composition->getBase()->getMetadata()->getCopyright().c_str());
128 ui->copyright->moveCursor(QTextCursor::End);
129
130 // License
131 QString license = composition->getBase()->getMetadata()->getLicense().c_str();
132 QPair<QString, QString> licenseFields = getLicenseTextAndLinkFromDescription(license);
133 auto licenseFieldsHTTPS = QPair<QString, QString>(licenseFields.first.replace( "http:", "https:"),
134 licenseFields.second.replace("http:", "https:"));
135
136 bool foundLicenseMatchInStandardOptions = false;
137 for (int i = 0; i < ui->licenseTitle->count() && !foundLicenseMatchInStandardOptions; ++i)
138 {
139 QString currentLicenseTitle = ui->licenseTitle->itemText(i);
140 QString currentLicenseDescription = descriptionForLicense[currentLicenseTitle];
141 QPair<QString, QString> currentLicenseFields = getLicenseTextAndLinkFromDescription(currentLicenseDescription);
142
143 if (((licenseFields.first == currentLicenseFields.first) && (licenseFields.second == currentLicenseFields.second))
144 || ((licenseFieldsHTTPS.first == currentLicenseFields.first) && (licenseFieldsHTTPS.second == currentLicenseFields.second)))
145 {
146 ui->licenseTitle->setCurrentText(currentLicenseTitle);
147 foundLicenseMatchInStandardOptions = true;
148 }
149 }
150 if (!foundLicenseMatchInStandardOptions)
151 {
152 descriptionForLicense[otherLicenseTitle] = license;
153 ui->licenseTitle->setCurrentText(otherLicenseTitle);
154 }
155
156 updateLicenseFields(ui->licenseTitle->currentText());
157
158 // Homepage URL
159 ui->homepageLink->setText(composition->getBase()->getMetadata()->getHomepageURL().c_str());
160
161 // Documentation URL
162 ui->documentationLink->setText(composition->getBase()->getMetadata()->getDocumentationURL().c_str());
163
164 // Bundle identifier
165 ui->bundleIdentifier->setText(composition->getBase()->getMetadata()->getBundleIdentifier().c_str());
166
167 // FxPlug group
168 ui->fxPlugGroup->setText(composition->getBase()->getMetadata()->getFxPlugGroup().c_str());
169
170 // Categories
171 string categories = VuoStringUtilities::join(composition->getBase()->getMetadata()->getCategories(), ", ");
172 ui->categories->setText(QString::fromStdString(categories));
173 ui->categories->moveCursor(QTextCursor::End);
174
175 updatePlaceholderTexts();
176
177 QDialog::show();
178}
179
180VuoMetadataEditor::~VuoMetadataEditor()
181{
182 delete ui;
183}
184
188void VuoMetadataEditor::updatePlaceholderTexts()
189{
190 QString name = QString::fromStdString( composition->getBase()->getMetadata()->getName() );
191 QString defaultName = QString::fromStdString( composition->getBase()->getMetadata()->getDefaultName() );
192 ui->name->setPlaceholderText( composition->formatCompositionFileNameForDisplay(defaultName) );
193 ui->version->setPlaceholderText("1.0");
194 ui->homepageLink->setPlaceholderText("http://");
195 ui->documentationLink->setPlaceholderText("http://");
196 ui->bundleIdentifier->setPlaceholderText( generateBundleIdentifierForCompositionName(name) );
197 ui->fxPlugGroup->setPlaceholderText(generateFxPlugGroupName());
198}
199
204{
205 return QString("com.")
206 .append(static_cast<VuoEditor *>(qApp)->getUserName().c_str())
207 .append(".")
208 .append(VuoEditorWindow::deriveBaseNodeClassNameFromDisplayName(name.toUtf8().constData()).c_str());
209}
210
215{
216 return QString(static_cast<VuoEditor *>(qApp)->getUserName().c_str())
217 .append("'s Plugins");
218}
219
224{
225 VuoCompositionMetadata *metadata = new VuoCompositionMetadata(*composition->getBase()->getMetadata());
226
227 metadata->setName( ui->name->text().toStdString() );
228 metadata->setVersion( ui->version->text().toStdString() );
229 metadata->setDescription( ui->description->toPlainText().toStdString() );
230 metadata->setKeywords( commaSeparatedListToStdVector(ui->keywords->toPlainText()) );
231 metadata->setCopyright( ui->copyright->toPlainText().toStdString() );
232 metadata->setLicense( getLicense().toStdString() );
233 metadata->setIconURL( iconURL.toStdString() );
234 metadata->setHomepageURL( ui->homepageLink->text().toStdString() );
235 metadata->setDocumentationURL( ui->documentationLink->text().toStdString() );
236 metadata->setBundleIdentifier( ui->bundleIdentifier->text().toStdString() );
237 metadata->setFxPlugGroup( ui->fxPlugGroup->text().toStdString() );
238 metadata->setCategories( commaSeparatedListToStdVector(ui->categories->toPlainText()) );
239
240 return metadata;
241}
242
246QString VuoMetadataEditor::getLicense()
247{
248 QString licenseText = ui->licenseText->toPlainText();
249 QString licenseLink = ui->licenseLink->text();
250
251 QString fullLicenseString = licenseText;
252 if (!licenseText.isEmpty() && !licenseLink.isEmpty())
253 fullLicenseString.append(" ");
254 if (!licenseLink.isEmpty())
255 {
256 fullLicenseString.append("For more information, see ");
257 fullLicenseString.append(licenseLink);
258 fullLicenseString.append(" .");
259 }
260
261 return fullLicenseString;
262}
263
267vector<string> VuoMetadataEditor::commaSeparatedListToStdVector(const QString &listText)
268{
269 vector<string> vec;
270
271 QStringList list = listText.split(',', QString::SkipEmptyParts);
272 for (const QString &item : list)
273 vec.push_back(item.trimmed().toStdString());
274
275 return vec;
276}
277
281void VuoMetadataEditor::dragEnterEvent(QDragEnterEvent *event)
282{
283 const QMimeData *mimeData = event->mimeData();
284
285 // Accept drags of icon (.icns or image) urls.
286 if (mimeData->hasFormat("text/uri-list"))
287 {
288 QList<QUrl> urls = mimeData->urls();
289 if (urls.size() == 1)
290 {
291 QString iconAbsolutePath = urls[0].path();
292 QImageReader reader(iconAbsolutePath);
293 if (reader.canRead())
294 {
295 event->accept();
296 return;
297 }
298 }
299 }
300
301 event->setDropAction(Qt::IgnoreAction);
302 event->accept();
303}
304
308void VuoMetadataEditor::dropEvent(QDropEvent *event)
309{
310 const QMimeData *mimeData = event->mimeData();
311 if (mimeData->hasFormat("text/uri-list"))
312 {
313 // Retrieve the composition directory so that file paths may be specified relative to it.
314 QDir compositionDir(QDir(composition->getBase()->getDirectory().c_str()).canonicalPath());
315 QList<QUrl> urls = mimeData->urls();
316
317 if (urls.size() == 1)
318 {
319 QString iconAbsolutePath = urls[0].path();
320 string dir, file, ext;
321 VuoFileUtilities::splitPath(iconAbsolutePath.toUtf8().constData(), dir, file, ext);
322
323 QImageReader reader(iconAbsolutePath);
324 if (reader.canRead())
325 {
326 iconURL = compositionDir.relativeFilePath(iconAbsolutePath);
327
328 QIcon icon(iconAbsolutePath);
329 QPixmap pixmap = icon.pixmap(icon.actualSize(ui->iconLabel->size()-QSize(4,4)));
330 ui->iconLabel->setPixmap(pixmap);
331
332 event->accept();
333 return;
334 }
335 }
336 }
337
338 event->ignore();
339}
340
345{
346 if (ui->iconLabel->hasFocus() && ((event->key() == Qt::Key_Backspace) ||
347 (event->key() == Qt::Key_Delete)))
348 {
349 this->iconURL = "";
350 ui->iconLabel->setText(placeholderIconText);
351 event->accept();
352 }
353 else
354 QDialog::keyPressEvent(event);
355}
356
357#undef BSD
358
362void VuoMetadataEditor::populateLicenseInfo()
363{
364 const QString CC_A = "Creative Commons: Attribution";
365 const QString CC_A_ND = "Creative Commons: Attribution + No Derivatives";
366 const QString CC_A_NC = "Creative Commons: Attribution + Noncommercial";
367 const QString CC_A_NC_ND = "Creative Commons: Attribution + Noncommercial + No Derivatives";
368 const QString CC_A_NC_SA = "Creative Commons: Attribution + Noncommercial + Share Alike";
369 const QString CC_A_SA = "Creative Commons: Attribution + Share Alike";
370 const QString MIT = "MIT";
371 const QString BSD = "BSD";
372 const QString PublicDomain = "Public Domain";
373
374 descriptionForLicense[CC_A] = "This composition may be modified and distributed under the terms of the Creative Commons: Attribution (CC BY) License. For more information, see https://creativecommons.org/licenses/by/4.0/ .";
375 descriptionForLicense[CC_A_ND] = "This composition may be distributed under the terms of the Creative Commons: Attribution + No Derivatives (CC BY-ND) License. For more information, see https://creativecommons.org/licenses/by-nd/4.0/ .";
376 descriptionForLicense[CC_A_NC] = "This composition may be modified and distributed under the terms of the Creative Commons: Attribution + Noncommercial (CC BY-NC) License. For more information, see https://creativecommons.org/licenses/by-nc/4.0/ .";
377 descriptionForLicense[CC_A_NC_ND] = "This composition may be distributed under the terms of the Creative Commons: Attribution + Noncommercial + No Derivatives (CC BY-NC-ND) License. For more information, see https://creativecommons.org/licenses/by-nc-nd/4.0/ .";
378 descriptionForLicense[CC_A_NC_SA] = "This composition may be modified and distributed under the terms of the Creative Commons: Attribution + Noncommercial + Share Alike (CC BY-NC-SA) License. For more information, see https://creativecommons.org/licenses/by-nc-sa/4.0/ .";
379 descriptionForLicense[CC_A_SA] = "This composition may be modified and distributed under the terms of the Creative Commons: Attribution + Share Alike (CC BY-SA) License. For more information, see https://creativecommons.org/licenses/by-sa/4.0/ .";
380 descriptionForLicense[MIT] = "This composition may be modified and distributed under the terms of the MIT License. For more information, see https://opensource.org/licenses/MIT .";
381 descriptionForLicense[BSD] = "This composition may be modified and distributed under the terms of the BSD License. For more information, see https://opensource.org/licenses/BSD-3-Clause .";
382 descriptionForLicense[PublicDomain] = "This composition is in the public domain and may be modified and distributed. For more information, see https://creativecommons.org/publicdomain/zero/1.0/ .";
383 descriptionForLicense[otherLicenseTitle] = "";
384
385 QList<QString> licenseOptions = QList<QString>() << CC_A << CC_A_ND << CC_A_NC << CC_A_NC_ND << CC_A_NC_SA << CC_A_SA << MIT << BSD << PublicDomain << otherLicenseTitle;
386 foreach (QString licenseOption, licenseOptions)
387 ui->licenseTitle->addItem(licenseOption);
388}
389
393QPair<QString, QString> VuoMetadataEditor::getLicenseTextAndLinkFromDescription(QString description)
394{
395 QRegExp descriptionTemplate("^((.*)\\s+)?For more information, see ([^\\s]*)\\s*\\.\\s*$");
396 if (!descriptionTemplate.exactMatch(description))
397 return QPair<QString, QString>(description, "");
398
399 QString text = description;
400 QString link = description;
401 text.replace(descriptionTemplate, "\\2");
402 link.replace(descriptionTemplate, "\\3");
403
404 return QPair<QString, QString>(text, link);
405}
406
410void VuoMetadataEditor::updateLicenseFields(QString licenseTitle)
411{
412 QString description = descriptionForLicense[licenseTitle];
413 QPair<QString, QString> licenseFields = getLicenseTextAndLinkFromDescription(description);
414 ui->licenseText->setText(licenseFields.first);
415 ui->licenseLink->setText(licenseFields.second);
416
417 bool otherLicenseSelected = (licenseTitle == otherLicenseTitle);
418 ui->licenseText->setReadOnly(!otherLicenseSelected);
419 ui->licenseLink->setReadOnly(!otherLicenseSelected);
420
421 if (otherLicenseSelected)
422 {
423 ui->licenseText->setFocus();
424 ui->licenseText->moveCursor(QTextCursor::End);
425 }
426}
427
433void VuoMetadataEditor::recordCustomLicenseText()
434{
435 if (ui->licenseTitle->currentText() == otherLicenseTitle)
436 descriptionForLicense[otherLicenseTitle] = getLicense();
437}
438
443void VuoMetadataEditor::sanitizeURL()
444{
445 VuoLineEdit *sender = static_cast<VuoLineEdit *>(QObject::sender());
446 if (sender && !sender->text().isEmpty() && !sender->text().contains("://"))
447 sender->setText(QString("http://").append(sender->text()));
448}