Vuo  2.1.0
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"
16 #include "VuoEditorComposition.hh"
17 #include "VuoEditorWindow.hh"
18 #include "VuoFileUtilities.hh"
19 #include "VuoStringUtilities.hh"
20 
21 map<QString, QString> VuoMetadataEditor::descriptionForLicense;
22 const QString VuoMetadataEditor::otherLicenseTitle = "Other";
23 const QString VuoMetadataEditor::placeholderIconText = "Icon for Exported App";
24 
28 VuoMetadataEditor::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 
180 VuoMetadataEditor::~VuoMetadataEditor()
181 {
182  delete ui;
183 }
184 
188 void 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 
246 QString 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 
267 vector<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 
281 void 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 
308 void 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 
344 void VuoMetadataEditor::keyPressEvent(QKeyEvent *event)
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 
362 void 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 
393 QPair<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 
410 void 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 
433 void VuoMetadataEditor::recordCustomLicenseText()
434 {
435  if (ui->licenseTitle->currentText() == otherLicenseTitle)
436  descriptionForLicense[otherLicenseTitle] = getLicense();
437 }
438 
443 void 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 }