ACMX 2.136.0
Dual-Backend Real-Time GPU Video Synthesis
Loading...
Searching...
No Matches
ACMX2/interface/playlist.cpp
Go to the documentation of this file.
1#include "playlist.hpp"
2#include "custom_style.hpp"
3#include <QFileDialog>
4#include <QFileInfo>
5#include <QInputDialog>
6#include <QMessageBox>
7#include <QSettings>
8#include <QTextStream>
9#include <algorithm>
10#include <random>
11
12PlaylistDialog::PlaylistDialog(const QStringList &shaderNames, QWidget *parent)
13 : QDialog(parent) {
14 setWindowTitle("Shader Playlist Settings");
15 setMinimumSize(600, 600);
16 setupUI();
17 loadShaders(shaderNames);
18}
19
21 QVBoxLayout *mainLayout = new QVBoxLayout(this);
22
23 enableCheckBox = new QCheckBox("Enable Shader Playlist", this);
24 mainLayout->addWidget(enableCheckBox);
25
26 QLabel *infoLabel = new QLabel(
27 "Build a playlist tree of shaders. Create named nodes, then add shaders to each node.\n"
28 "When enabled, the playlist file is passed to acmx2. Press P to toggle; Up/Down to navigate.",
29 this);
30 infoLabel->setWordWrap(true);
31 mainLayout->addWidget(infoLabel);
32
33 QGroupBox *shaderGroup = new QGroupBox("Playlist Shader Selection", this);
34 QVBoxLayout *shaderMainLayout = new QVBoxLayout(shaderGroup);
35
36 QHBoxLayout *searchLayout = new QHBoxLayout();
37 QLabel *searchLabel = new QLabel("Search:", this);
38 searchLineEdit = new QLineEdit(this);
39 searchLineEdit->setPlaceholderText("Type to search shaders...");
40 searchLineEdit->setClearButtonEnabled(true);
41 searchLayout->addWidget(searchLabel);
42 searchLayout->addWidget(searchLineEdit, 1);
43 shaderMainLayout->addLayout(searchLayout);
44
45 QHBoxLayout *comboLayout = new QHBoxLayout();
46 QLabel *availableLabel = new QLabel("Available Shaders:", this);
47 shaderComboBox = new QComboBox(this);
48 shaderComboBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
49 shaderComboBox->setMaxVisibleItems(20);
50
51 shaderModel = new QStandardItemModel(this);
52 proxyModel = new QSortFilterProxyModel(this);
53 proxyModel->setSourceModel(shaderModel);
54 proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
55 proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
56 shaderComboBox->setModel(proxyModel);
57
58 comboLayout->addWidget(availableLabel);
59 comboLayout->addWidget(shaderComboBox, 1);
60 shaderMainLayout->addLayout(comboLayout);
61
62 QHBoxLayout *nodeButtonLayout = new QHBoxLayout();
63 addNodeButton = new QPushButton("+ Node", this);
64 renameNodeButton = new QPushButton("Rename Node", this);
65 removeNodeButton = new QPushButton("- Node", this);
66 nodeButtonLayout->addWidget(addNodeButton);
67 nodeButtonLayout->addWidget(renameNodeButton);
68 nodeButtonLayout->addWidget(removeNodeButton);
69 nodeButtonLayout->addStretch();
70 shaderMainLayout->addLayout(nodeButtonLayout);
71
72 QHBoxLayout *buttonLayout = new QHBoxLayout();
73 addButton = new QPushButton("Add Shader", this);
74 removeButton = new QPushButton("Remove", this);
75 upButton = new QPushButton("↑ Up", this);
76 downButton = new QPushButton("↓ Down", this);
77 clearButton = new QPushButton("Clear All", this);
78 shuffleButton = new QPushButton("Shuffle", this);
79 buttonLayout->addWidget(addButton);
80 buttonLayout->addWidget(removeButton);
81 buttonLayout->addWidget(upButton);
82 buttonLayout->addWidget(downButton);
83 buttonLayout->addWidget(clearButton);
84 buttonLayout->addWidget(shuffleButton);
85 shaderMainLayout->addLayout(buttonLayout);
86
87 QLabel *selectedLabel = new QLabel("Playlist Tree (press P in acmx2 to toggle, Up/Down to navigate):", this);
88 shaderMainLayout->addWidget(selectedLabel);
89 playlistTree = new QTreeWidget(this);
90 playlistTree->setHeaderLabels({"Shader / Node"});
91 playlistTree->setMinimumHeight(250);
92 playlistTree->setDragDropMode(QAbstractItemView::InternalMove);
93 playlistTree->setSelectionMode(QAbstractItemView::SingleSelection);
94 shaderMainLayout->addWidget(playlistTree);
95
96 QHBoxLayout *fileButtonLayout = new QHBoxLayout();
97 saveButton = new QPushButton("Save Playlist...", this);
98 loadButton = new QPushButton("Load Playlist...", this);
99 concatButton = new QPushButton("Concat Playlist...", this);
100 fileButtonLayout->addWidget(saveButton);
101 fileButtonLayout->addWidget(loadButton);
102 fileButtonLayout->addWidget(concatButton);
103 fileButtonLayout->addStretch();
104 shaderMainLayout->addLayout(fileButtonLayout);
105
106 QHBoxLayout *autopilotLayout = new QHBoxLayout();
107 QLabel *autopilotLabel = new QLabel(
108 "Autopilot frames (minimum 4; switch to a random shader after this many frames, toggle with J):",
109 this);
110 autopilotLabel->setWordWrap(true);
111 autopilotFramesSpinBox = new QSpinBox(this);
112 autopilotFramesSpinBox->setRange(4, 1000000);
113 autopilotFramesSpinBox->setSingleStep(30);
114 autopilotFramesSpinBox->setValue(4);
115 autopilotFramesSpinBox->setSuffix(" frames");
116 autopilotRandomCheckBox = new QCheckBox("Random", this);
117 autopilotRandomCheckBox->setToolTip("Use --autopilot-random instead of --autopilot-frames");
118 autopilotLayout->addWidget(autopilotLabel, 1);
119 autopilotLayout->addWidget(autopilotFramesSpinBox);
120 autopilotLayout->addWidget(autopilotRandomCheckBox);
121 shaderMainLayout->addLayout(autopilotLayout);
122
123 mainLayout->addWidget(shaderGroup);
124
125 QHBoxLayout *dialogButtonLayout = new QHBoxLayout();
126 okButton = new QPushButton("OK", this);
127 cancelButton = new QPushButton("Cancel", this);
128 dialogButtonLayout->addStretch();
129 dialogButtonLayout->addWidget(okButton);
130 dialogButtonLayout->addWidget(cancelButton);
131 mainLayout->addLayout(dialogButtonLayout);
132
133 connect(addNodeButton, &QPushButton::clicked, this, &PlaylistDialog::addNode);
134 connect(renameNodeButton, &QPushButton::clicked, this, &PlaylistDialog::renameNode);
135 connect(removeNodeButton, &QPushButton::clicked, this, &PlaylistDialog::removeNode);
136 connect(addButton, &QPushButton::clicked, this, &PlaylistDialog::addShader);
137 connect(removeButton, &QPushButton::clicked, this, &PlaylistDialog::removeShader);
138 connect(upButton, &QPushButton::clicked, this, &PlaylistDialog::moveUp);
139 connect(downButton, &QPushButton::clicked, this, &PlaylistDialog::moveDown);
140 connect(clearButton, &QPushButton::clicked, this, &PlaylistDialog::clearAll);
141 connect(shuffleButton, &QPushButton::clicked, this, &PlaylistDialog::shufflePlaylist);
142 connect(concatButton, &QPushButton::clicked, this, &PlaylistDialog::concatPlaylist);
143 connect(saveButton, &QPushButton::clicked, this, &PlaylistDialog::savePlaylist);
144 connect(loadButton, &QPushButton::clicked, this, &PlaylistDialog::loadPlaylist);
145 connect(okButton, &QPushButton::clicked, this, &QDialog::accept);
146 connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject);
147 connect(searchLineEdit, &QLineEdit::textChanged, this, &PlaylistDialog::filterSearchChanged);
148
149 connect(enableCheckBox, &QCheckBox::toggled, this, [this](bool checked) {
150 shaderComboBox->setEnabled(checked);
151 playlistTree->setEnabled(checked);
152 searchLineEdit->setEnabled(checked);
153 addNodeButton->setEnabled(checked);
154 renameNodeButton->setEnabled(checked);
155 removeNodeButton->setEnabled(checked);
156 addButton->setEnabled(checked);
157 removeButton->setEnabled(checked);
158 upButton->setEnabled(checked);
159 downButton->setEnabled(checked);
160 clearButton->setEnabled(checked);
161 shuffleButton->setEnabled(checked);
162 concatButton->setEnabled(checked);
163 saveButton->setEnabled(checked);
164 loadButton->setEnabled(checked);
166 autopilotFramesSpinBox->setEnabled(checked);
168 autopilotRandomCheckBox->setEnabled(checked);
169 });
170
171 enableCheckBox->setChecked(false);
172 shaderComboBox->setEnabled(false);
173 playlistTree->setEnabled(false);
174 searchLineEdit->setEnabled(false);
175 addNodeButton->setEnabled(false);
176 renameNodeButton->setEnabled(false);
177 removeNodeButton->setEnabled(false);
178 addButton->setEnabled(false);
179 removeButton->setEnabled(false);
180 upButton->setEnabled(false);
181 downButton->setEnabled(false);
182 clearButton->setEnabled(false);
183 shuffleButton->setEnabled(false);
184 concatButton->setEnabled(false);
185 saveButton->setEnabled(false);
186 loadButton->setEnabled(false);
187 autopilotFramesSpinBox->setEnabled(false);
188 autopilotRandomCheckBox->setEnabled(false);
189
191}
192
193void PlaylistDialog::loadShaders(const QStringList &shaderNames) {
194 QStringList selectedNames = getSelectedShaderNames();
195
196 shaderNamesList.clear();
197 shaderNameToIndex.clear();
198 shaderModel->clear();
199
200 for (int i = 0; i < shaderNames.size(); ++i) {
201 QString name = shaderNames[i];
202 shaderNamesList.append(name);
203 shaderNameToIndex[name] = i;
204 QStandardItem *item = new QStandardItem(name);
205 item->setData(i, Qt::UserRole);
206 shaderModel->appendRow(item);
207 }
208
209 if (!selectedNames.isEmpty()) {
210 setSelectedShaderNames(selectedNames);
211 }
212}
213
214void PlaylistDialog::filterSearchChanged(const QString &text) {
215 proxyModel->setFilterFixedString(text);
216 if (proxyModel->rowCount() > 0) {
217 shaderComboBox->setCurrentIndex(0);
218 }
219}
220
221QTreeWidgetItem *PlaylistDialog::currentNodeItem() const {
222 QTreeWidgetItem *current = playlistTree->currentItem();
223 if (!current)
224 return nullptr;
225 if (!current->parent())
226 return current;
227 return current->parent();
228}
229
231 bool ok = false;
232 QString name = QInputDialog::getText(this, "New Playlist Node", "Node name:", QLineEdit::Normal, QString(), &ok);
233 if (!ok || name.trimmed().isEmpty())
234 return;
235
236 auto *nodeItem = new QTreeWidgetItem(playlistTree);
237 nodeItem->setText(0, name.trimmed());
238 nodeItem->setFlags(nodeItem->flags() | Qt::ItemIsEditable);
239 nodeItem->setExpanded(true);
240 playlistTree->setCurrentItem(nodeItem);
241}
242
244 QTreeWidgetItem *node = currentNodeItem();
245 if (!node) {
246 QMessageBox::information(this, "No Node Selected", "Select a playlist node to rename.");
247 return;
248 }
249
250 bool ok = false;
251 QString name = QInputDialog::getText(this, "Rename Node", "New name:", QLineEdit::Normal, node->text(0), &ok);
252 if (ok && !name.trimmed().isEmpty()) {
253 node->setText(0, name.trimmed());
254 }
255}
256
258 QTreeWidgetItem *node = currentNodeItem();
259 if (!node) {
260 QMessageBox::information(this, "No Node Selected", "Select a playlist node to remove.");
261 return;
262 }
263
264 if (node->childCount() > 0) {
265 auto reply = QMessageBox::question(this, "Remove Node",
266 "Node \"" + node->text(0) + "\" has " + QString::number(node->childCount()) +
267 " shader(s). Remove it and all its shaders?",
268 QMessageBox::Yes | QMessageBox::No);
269 if (reply != QMessageBox::Yes)
270 return;
271 }
272
273 delete node;
274}
275
277 if (shaderComboBox->currentIndex() < 0)
278 return;
279
280 QTreeWidgetItem *node = currentNodeItem();
281 if (!node) {
282 if (playlistTree->topLevelItemCount() == 0) {
283 auto *nodeItem = new QTreeWidgetItem(playlistTree);
284 nodeItem->setText(0, "Default");
285 nodeItem->setFlags(nodeItem->flags() | Qt::ItemIsEditable);
286 nodeItem->setExpanded(true);
287 node = nodeItem;
288 } else {
289 QMessageBox::information(this, "No Node Selected", "Select a playlist node to add the shader to.");
290 return;
291 }
292 }
293
294 QString shaderName = shaderComboBox->currentText();
295 auto *item = new QTreeWidgetItem(node);
296 item->setText(0, shaderName);
297 if (shaderNameToIndex.contains(shaderName)) {
298 item->setData(0, Qt::UserRole, shaderNameToIndex[shaderName]);
299 }
300 node->setExpanded(true);
301}
302
304 QTreeWidgetItem *current = playlistTree->currentItem();
305 if (!current)
306 return;
307 if (!current->parent()) {
308 removeNode();
309 return;
310 }
311 delete current;
312}
313
315 QTreeWidgetItem *current = playlistTree->currentItem();
316 if (!current)
317 return;
318
319 QTreeWidgetItem *parent = current->parent();
320 if (parent) {
321 int idx = parent->indexOfChild(current);
322 if (idx > 0) {
323 parent->takeChild(idx);
324 parent->insertChild(idx - 1, current);
325 playlistTree->setCurrentItem(current);
326 }
327 } else {
328 int idx = playlistTree->indexOfTopLevelItem(current);
329 if (idx > 0) {
330 playlistTree->takeTopLevelItem(idx);
331 playlistTree->insertTopLevelItem(idx - 1, current);
332 playlistTree->setCurrentItem(current);
333 }
334 }
335}
336
338 QTreeWidgetItem *current = playlistTree->currentItem();
339 if (!current)
340 return;
341
342 QTreeWidgetItem *parent = current->parent();
343 if (parent) {
344 int idx = parent->indexOfChild(current);
345 if (idx < parent->childCount() - 1) {
346 parent->takeChild(idx);
347 parent->insertChild(idx + 1, current);
348 playlistTree->setCurrentItem(current);
349 }
350 } else {
351 int idx = playlistTree->indexOfTopLevelItem(current);
352 if (idx < playlistTree->topLevelItemCount() - 1) {
353 playlistTree->takeTopLevelItem(idx);
354 playlistTree->insertTopLevelItem(idx + 1, current);
355 playlistTree->setCurrentItem(current);
356 }
357 }
358}
359
361 playlistTree->clear();
362}
363
365 int totalShaders = 0;
366 for (int i = 0; i < playlistTree->topLevelItemCount(); ++i) {
367 totalShaders += playlistTree->topLevelItem(i)->childCount();
368 }
369 if (totalShaders == 0) {
370 QMessageBox::information(this, "Empty Playlist", "Add shaders to the playlist before shuffling.");
371 return;
372 }
373
374 static thread_local std::mt19937 rng{std::random_device{}()};
375
376 for (int i = 0; i < playlistTree->topLevelItemCount(); ++i) {
377 QTreeWidgetItem *node = playlistTree->topLevelItem(i);
378 const int n = node->childCount();
379 if (n < 2)
380 continue;
381
382 QList<QTreeWidgetItem *> children;
383 children.reserve(n);
384 while (node->childCount() > 0) {
385 children.append(node->takeChild(0));
386 }
387 std::shuffle(children.begin(), children.end(), rng);
388 for (QTreeWidgetItem *child : children) {
389 node->addChild(child);
390 }
391 node->setExpanded(true);
392 }
393}
394
396 QSettings appSettings("LostSideDead");
397 QString lastDir = appSettings.value("lastPlaylistDir", "").toString();
398 QString filePath = QFileDialog::getOpenFileName(this, "Concat Playlist", lastDir,
399 "Text Files (*.txt);;All Files (*)");
400 if (filePath.isEmpty())
401 return;
402
403 appSettings.setValue("lastPlaylistDir", QFileInfo(filePath).absolutePath());
404
405 QFile file(filePath);
406 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
407 QMessageBox::critical(this, "Error", "Could not open playlist file: " + filePath);
408 return;
409 }
410
411 QTextStream in(&file);
412 int loadedCount = 0;
413 int skippedCount = 0;
414 int nodesAdded = 0;
415 QTreeWidgetItem *currentNode = nullptr;
416
417 while (!in.atEnd()) {
418 QString line = in.readLine().trimmed();
419 if (line.isEmpty())
420 continue;
421
422 if (line.startsWith('[') && line.endsWith(']')) {
423 QString nodeName = line.mid(1, line.length() - 2);
424 currentNode = new QTreeWidgetItem(playlistTree);
425 currentNode->setText(0, nodeName);
426 currentNode->setFlags(currentNode->flags() | Qt::ItemIsEditable);
427 currentNode->setExpanded(true);
428 ++nodesAdded;
429 } else {
430 if (!currentNode) {
431 QString nodeName = QFileInfo(filePath).baseName();
432 if (nodeName.isEmpty())
433 nodeName = "Concat";
434 currentNode = new QTreeWidgetItem(playlistTree);
435 currentNode->setText(0, nodeName);
436 currentNode->setFlags(currentNode->flags() | Qt::ItemIsEditable);
437 currentNode->setExpanded(true);
438 ++nodesAdded;
439 }
440 if (shaderNameToIndex.contains(line)) {
441 auto *item = new QTreeWidgetItem(currentNode);
442 item->setText(0, line);
443 item->setData(0, Qt::UserRole, shaderNameToIndex[line]);
444 ++loadedCount;
445 } else {
446 ++skippedCount;
447 }
448 }
449 }
450 file.close();
451
452 QString msg = "Concatenated " + QString::number(loadedCount) + " shader(s) into " +
453 QString::number(nodesAdded) + " node(s).";
454 if (skippedCount > 0)
455 msg += "\n" + QString::number(skippedCount) + " shader(s) not found and skipped.";
456 QMessageBox::information(this, "Playlist Concatenated", msg);
457}
458
460 int shaderCount = 0;
461 for (int i = 0; i < playlistTree->topLevelItemCount(); ++i) {
462 shaderCount += playlistTree->topLevelItem(i)->childCount();
463 }
464 if (shaderCount == 0) {
465 QMessageBox::information(this, "Empty Playlist", "Add shaders to the playlist before saving.");
466 return;
467 }
468
469 QSettings appSettings("LostSideDead");
470 QString lastDir = appSettings.value("lastPlaylistDir", "").toString();
471 QString filePath = QFileDialog::getSaveFileName(this, "Save Playlist", lastDir, "Text Files (*.txt);;All Files (*)");
472 if (filePath.isEmpty())
473 return;
474
475 appSettings.setValue("lastPlaylistDir", QFileInfo(filePath).absolutePath());
476
477 QFile file(filePath);
478 if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
479 QMessageBox::critical(this, "Error", "Could not save playlist file: " + filePath);
480 return;
481 }
482
483 QTextStream out(&file);
484 for (int i = 0; i < playlistTree->topLevelItemCount(); ++i) {
485 QTreeWidgetItem *node = playlistTree->topLevelItem(i);
486 out << "[" << node->text(0) << "]\n";
487 for (int j = 0; j < node->childCount(); ++j) {
488 out << node->child(j)->text(0) << "\n";
489 }
490 }
491 file.close();
492 playlistFilePath = filePath;
493 QMessageBox::information(this, "Saved", "Playlist saved to: " + filePath);
494}
495
497 QSettings appSettings("LostSideDead");
498 QString lastDir = appSettings.value("lastPlaylistDir", "").toString();
499 QString filePath = QFileDialog::getOpenFileName(this, "Load Playlist", lastDir, "Text Files (*.txt);;All Files (*)");
500 if (filePath.isEmpty())
501 return;
502
503 appSettings.setValue("lastPlaylistDir", QFileInfo(filePath).absolutePath());
504
505 QFile file(filePath);
506 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
507 QMessageBox::critical(this, "Error", "Could not open playlist file: " + filePath);
508 return;
509 }
510
511 playlistTree->clear();
512 QTextStream in(&file);
513 int loadedCount = 0;
514 int skippedCount = 0;
515 QTreeWidgetItem *currentNode = nullptr;
516
517 while (!in.atEnd()) {
518 QString line = in.readLine().trimmed();
519 if (line.isEmpty())
520 continue;
521
522 if (line.startsWith('[') && line.endsWith(']')) {
523 QString nodeName = line.mid(1, line.length() - 2);
524 currentNode = new QTreeWidgetItem(playlistTree);
525 currentNode->setText(0, nodeName);
526 currentNode->setFlags(currentNode->flags() | Qt::ItemIsEditable);
527 currentNode->setExpanded(true);
528 } else {
529 if (!currentNode) {
530 currentNode = new QTreeWidgetItem(playlistTree);
531 currentNode->setText(0, "Default");
532 currentNode->setFlags(currentNode->flags() | Qt::ItemIsEditable);
533 currentNode->setExpanded(true);
534 }
535 if (shaderNameToIndex.contains(line)) {
536 auto *item = new QTreeWidgetItem(currentNode);
537 item->setText(0, line);
538 item->setData(0, Qt::UserRole, shaderNameToIndex[line]);
539 ++loadedCount;
540 } else {
541 ++skippedCount;
542 }
543 }
544 }
545 file.close();
546 playlistFilePath = filePath;
547
548 QString msg = "Loaded " + QString::number(loadedCount) + " shader(s).";
549 if (skippedCount > 0)
550 msg += "\n" + QString::number(skippedCount) + " shader(s) not found and skipped.";
551 QMessageBox::information(this, "Playlist Loaded", msg);
552}
553
555 if (!enableCheckBox->isChecked())
556 return false;
557 for (int i = 0; i < playlistTree->topLevelItemCount(); ++i) {
558 if (playlistTree->topLevelItem(i)->childCount() > 0)
559 return true;
560 }
561 return false;
562}
563
565 QStringList names;
566 for (int i = 0; i < playlistTree->topLevelItemCount(); ++i) {
567 QTreeWidgetItem *node = playlistTree->topLevelItem(i);
568 for (int j = 0; j < node->childCount(); ++j) {
569 names.append(node->child(j)->text(0));
570 }
571 }
572 return names;
573}
574
575QList<QPair<QString, QStringList>> PlaylistDialog::getPlaylistTree() const {
576 QList<QPair<QString, QStringList>> tree;
577 for (int i = 0; i < playlistTree->topLevelItemCount(); ++i) {
578 QTreeWidgetItem *node = playlistTree->topLevelItem(i);
579 QStringList shaders;
580 for (int j = 0; j < node->childCount(); ++j) {
581 shaders.append(node->child(j)->text(0));
582 }
583 tree.append({node->text(0), shaders});
584 }
585 return tree;
586}
587
589 return playlistFilePath;
590}
591
595
597 return autopilotRandomCheckBox ? autopilotRandomCheckBox->isChecked() : false;
598}
599
602 if (frames < 4)
603 frames = 4;
604 autopilotFramesSpinBox->setValue(frames);
605 }
606}
607
610 autopilotRandomCheckBox->setChecked(enabled);
611 }
612}
613
614void PlaylistDialog::setEnabled(bool enabled) {
615 enableCheckBox->setChecked(enabled);
616}
617
618void PlaylistDialog::setSelectedShaderNames(const QStringList &names) {
619 playlistTree->clear();
620 if (names.isEmpty())
621 return;
622
623 auto *node = new QTreeWidgetItem(playlistTree);
624 node->setText(0, "Default");
625 node->setFlags(node->flags() | Qt::ItemIsEditable);
626 node->setExpanded(true);
627
628 for (const QString &name : names) {
629 if (shaderNameToIndex.contains(name)) {
630 auto *item = new QTreeWidgetItem(node);
631 item->setText(0, name);
632 item->setData(0, Qt::UserRole, shaderNameToIndex[name]);
633 }
634 }
635}
636
637void PlaylistDialog::setPlaylistTree(const QList<QPair<QString, QStringList>> &tree) {
638 playlistTree->clear();
639 for (const auto &[nodeName, shaders] : tree) {
640 auto *node = new QTreeWidgetItem(playlistTree);
641 node->setText(0, nodeName);
642 node->setFlags(node->flags() | Qt::ItemIsEditable);
643 node->setExpanded(true);
644 for (const QString &name : shaders) {
645 if (shaderNameToIndex.contains(name)) {
646 auto *item = new QTreeWidgetItem(node);
647 item->setText(0, name);
648 item->setData(0, Qt::UserRole, shaderNameToIndex[name]);
649 }
650 }
651 }
652}
653
654void PlaylistDialog::setPlaylistFile(const QString &path) {
655 playlistFilePath = path;
656 if (!path.isEmpty()) {
657 QFile file(path);
658 if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
659 QTextStream in(&file);
660 bool hasNodes = false;
661 while (!in.atEnd()) {
662 QString line = in.readLine().trimmed();
663 if (line.startsWith('[') && line.endsWith(']')) {
664 hasNodes = true;
665 break;
666 }
667 }
668 file.close();
669
670 if (hasNodes && file.open(QIODevice::ReadOnly | QIODevice::Text)) {
671 QTextStream in2(&file);
672 playlistTree->clear();
673 QTreeWidgetItem *currentNode = nullptr;
674 while (!in2.atEnd()) {
675 QString line = in2.readLine().trimmed();
676 if (line.isEmpty())
677 continue;
678 if (line.startsWith('[') && line.endsWith(']')) {
679 QString nodeName = line.mid(1, line.length() - 2);
680 currentNode = new QTreeWidgetItem(playlistTree);
681 currentNode->setText(0, nodeName);
682 currentNode->setFlags(currentNode->flags() | Qt::ItemIsEditable);
683 currentNode->setExpanded(true);
684 } else if (currentNode && shaderNameToIndex.contains(line)) {
685 auto *item = new QTreeWidgetItem(currentNode);
686 item->setText(0, line);
687 item->setData(0, Qt::UserRole, shaderNameToIndex[line]);
688 }
689 }
690 file.close();
691 }
692 }
693 }
694}
695
696void PlaylistDialog::updateShaderList(const QStringList &shaderNames) {
697 loadShaders(shaderNames);
698}
Dialog for building nested shader playlists.
void loadShaders(const QStringList &shaderNames)
int getAutopilotFrames() const
Return frames-per-shader threshold for autopilot mode (minimum 4).
void setPlaylistTree(const QList< QPair< QString, QStringList > > &tree)
PlaylistDialog(const QStringList &shaderNames, QWidget *parent=nullptr)
QStringList getSelectedShaderNames() const
Return flattened selection of shaders in current playlist.
void setEnabled(bool enabled)
bool isPlaylistEnabled() const
Return whether playlist mode is enabled.
void setPlaylistFile(const QString &path)
QSortFilterProxyModel * proxyModel
void setSelectedShaderNames(const QStringList &names)
QString getPlaylistFile() const
Return current playlist file path.
void setAutopilotRandom(bool enabled)
Enable or disable random autopilot timeout mode.
QTreeWidgetItem * currentNodeItem() const
void updateShaderList(const QStringList &shaderNames)
bool isAutopilotRandom() const
Return true when random autopilot timeout mode is enabled.
QList< QPair< QString, QStringList > > getPlaylistTree() const
Return tree representation as node-name and shader-list pairs.
void filterSearchChanged(const QString &text)
void setAutopilotFrames(int frames)
Set frames-per-shader threshold for autopilot mode.
QMap< QString, int > shaderNameToIndex
QStandardItemModel * shaderModel
void applyCustomStyleIfEnabled(QWidget *widget)