tracktion-engine 3.0-10-g034fdde4aa5
Tracktion Engine — High level data model for audio applications

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_ProjectManager.cpp
Go to the documentation of this file.
1 /*
2 ,--. ,--. ,--. ,--.
3 ,-' '-.,--.--.,--,--.,---.| |,-.,-' '-.`--' ,---. ,--,--, Copyright 2024
4 '-. .-'| .--' ,-. | .--'| /'-. .-',--.| .-. || \ Tracktion Software
5 | | | | \ '-' \ `--.| \ \ | | | |' '-' '| || | Corporation
6 `---' `--' `--`--'`---'`--'`--' `---' `--' `---' `--''--' www.tracktion.com
7
8 Tracktion Engine uses a GPL/commercial licence - see LICENCE.md for details.
9*/
10
11namespace tracktion { inline namespace engine
12{
13
14ProjectManager::ProjectManager (Engine& e)
15 : engine (e)
16{
17}
18
19ProjectManager::~ProjectManager()
20{
22 folders = {};
23 jassert (openProjects.isEmpty());
24}
25
26//==============================================================================
27void ProjectManager::initialise()
28{
29 loadList();
30
31 auto& storage = engine.getPropertyStorage();
32
33 if (storage.getProperty (SettingID::findExamples, false))
34 {
35 storage.setProperty (SettingID::findExamples, juce::var());
36
37 auto examplesDir = storage.getAppPrefsFolder().getChildFile ("examples");
38
39 auto exampleProjects = examplesDir.findChildFiles (juce::File::findFiles, true,
40 juce::String ("*") + projectFileSuffix);
41
42 for (auto& f : exampleProjects)
43 addProjectToList (f, false, getActiveProjectsFolder(),
44 f.getFileName().containsIgnoreCase ("Spiralling") ? 0 : -1);
45
46 saveList();
47 }
48}
49
50//==============================================================================
51static void ensureAllItemsHaveIDs (const juce::ValueTree& folder)
52{
53 if (folder[IDs::uid].toString().isEmpty())
54 juce::ValueTree (folder).setProperty (IDs::uid, juce::String::toHexString (juce::Random().nextInt()), nullptr);
55
56 for (int i = 0; i < folder.getNumChildren(); ++i)
57 ensureAllItemsHaveIDs (folder.getChild(i));
58}
59
60void ProjectManager::loadList()
61{
62 const juce::ScopedLock sl (lock);
63
64 folders = {};
65
66 auto xml = engine.getPropertyStorage().getXmlProperty (SettingID::projectList);
67
68 if (xml != nullptr)
69 folders = juce::ValueTree::fromXml (*xml);
70
71 if (! folders.hasType (IDs::ROOT))
72 folders = juce::ValueTree (IDs::ROOT);
73
74 if (! getLibraryProjectsFolder().isValid()) folders.addChild (juce::ValueTree (IDs::LIBRARY), -1, nullptr);
75 if (! getActiveProjectsFolder().isValid()) folders.addChild (juce::ValueTree (IDs::ACTIVE), 0, nullptr);
76
77 jassert (getActiveProjectsFolder().isValid() && getLibraryProjectsFolder().isValid());
78
79 if (xml == nullptr) // import from T4 format:
80 {
81 xml = engine.getPropertyStorage().getXmlProperty (SettingID::projects);
82
83 if (xml != nullptr)
84 {
85 auto oldT4 = juce::ValueTree::fromXml (*xml);
86
87 {
88 auto v = oldT4.getChildWithProperty (IDs::name, "Library Projects");
89
90 for (int i = v.getNumChildren(); --i >= 0;)
91 {
92 auto c = v.getChild(i);
93 v.removeChild (c, nullptr);
94 getLibraryProjectsFolder().addChild (c, 0, nullptr);
95 }
96 }
97
98 {
99 auto v = oldT4.getChildWithProperty (IDs::name, "Active Projects");
100
101 for (int i = v.getNumChildren(); --i >= 0;)
102 {
103 auto c = v.getChild(i);
104 v.removeChild (c, nullptr);
105 getActiveProjectsFolder().addChild (c, 0, nullptr);
106 }
107 }
108 }
109 }
110
111 ensureAllItemsHaveIDs (folders);
112}
113
114static void stripProjectObjects (juce::ValueTree v)
115{
116 v.removeProperty (IDs::project, nullptr);
117
118 for (int i = 0; i < v.getNumChildren(); ++i)
119 stripProjectObjects (v.getChild(i));
120}
121
122void ProjectManager::saveList()
123{
124 const juce::ScopedLock sl (lock);
125
126 auto foldersCopy = folders.createCopy();
127 stripProjectObjects (foldersCopy);
128
129 std::unique_ptr<juce::XmlElement> xml (foldersCopy.createXml());
130 engine.getPropertyStorage().setXmlProperty (SettingID::projectList, *xml);
131}
132
133static void findProjects (ProjectManager& pm, const juce::ValueTree& folder,
135{
136 if (auto p = pm.getProjectFrom (folder))
137 list.add (p);
138
139 for (int i = 0; i < folder.getNumChildren(); ++i)
140 findProjects (pm, folder.getChild(i), list);
141}
142
143juce::ReferenceCountedArray<Project> ProjectManager::getAllProjects()
144{
146 findProjects (*this, folders, list);
147 return list;
148}
149
150juce::ReferenceCountedArray<Project> ProjectManager::getAllProjects (const juce::ValueTree& folder)
151{
153 findProjects (*this, folder, list);
154 return list;
155}
156
157juce::ValueTree ProjectManager::getActiveProjectsFolder() { return folders.getChildWithName (IDs::ACTIVE); }
158juce::ValueTree ProjectManager::getLibraryProjectsFolder() { return folders.getChildWithName (IDs::LIBRARY); }
159
160//==============================================================================
161Project::Ptr ProjectManager::findProjectWithId (const juce::ValueTree& folder, int pid)
162{
163 if (auto p = getProjectFrom (folder))
164 if (p->getProjectID() == pid)
165 return p;
166
167 for (int i = 0; i < folder.getNumChildren(); ++i)
168 if (auto p = findProjectWithId (folder.getChild (i), pid))
169 return p;
170
171 return {};
172}
173
174Project::Ptr ProjectManager::findProjectWithFile (const juce::ValueTree& folder,
175 const juce::File& f)
176{
177 if (auto p = getProjectFrom (folder))
178 if (p->getProjectFile() == f)
179 return p;
180
181 for (int i = 0; i < folder.getNumChildren(); ++i)
182 if (auto p = findProjectWithFile (folder.getChild (i), f))
183 return p;
184
185 return {};
186}
187
188Project::Ptr ProjectManager::getProjectFrom (const juce::ValueTree& v,
189 bool createIfNotFound)
190{
191 if (auto p = dynamic_cast<Project*> (v.getProperty (IDs::project).getObject()))
192 return p;
193
194 if (createIfNotFound && v.hasType (IDs::PROJECT))
195 {
196 const juce::File f (v[IDs::file]);
197
198 if (f.exists())
199 {
200 Project::Ptr p;
201
202 for (auto* proj : openProjects)
203 if (proj->getProjectFile() == f)
204 p = proj;
205
206 if (p == nullptr)
207 p = createNewProject (f);
208
209 if (p->isValid())
210 {
211 juce::ValueTree (v).setProperty (IDs::project, juce::var (p.get()), nullptr);
212 return p;
213 }
214 }
215 }
216
217 return {};
218}
219
220Project::Ptr ProjectManager::getProject (int pid)
221{
222 const juce::ScopedLock sl (lock);
223
224 for (auto* p : openProjects)
225 if (p->getProjectID() == pid)
226 return p;
227
228 return findProjectWithId (folders, pid);
229}
230
231Project::Ptr ProjectManager::getProject (const juce::File& f)
232{
233 const juce::ScopedLock sl (lock);
234
235 for (auto* p : openProjects)
236 if (p->getProjectFile() == f)
237 return p;
238
239 return findProjectWithFile (folders, f);
240}
241
242//==============================================================================
243Project::Ptr ProjectManager::addProjectToList (const juce::File& f,
244 bool shouldSaveList,
245 juce::ValueTree folderToAddTo,
246 int index)
247{
248 if (f.existsAsFile() && isTracktionProjectFile (f))
249 {
250 const juce::ScopedLock sl (lock);
251
252 if (auto existing = findProjectWithFile (folders, f))
253 return existing;
254
255 auto p = createNewProject (f);
256
257 if (p->isValid())
258 {
259 if (auto existing = findProjectWithId (folders, p->getProjectID()))
260 return existing;
261
262 auto v = createValueTree (IDs::PROJECT,
263 IDs::file, f.getFullPathName());
264
265 folderToAddTo.addChild (v, index, nullptr);
266 ensureAllItemsHaveIDs (folderToAddTo);
267
268 // NB: the object added may be a copy, so need to re-get this pointer
269 p = getProject (f);
270
271 if (shouldSaveList)
272 saveList();
273
274 return p;
275 }
276 }
277
278 return {};
279}
280
281static void removeProject (const juce::ValueTree& folder, const Project::Ptr& toRemove)
282{
283 if (toRemove == nullptr)
284 return;
285
286 if (auto p = toRemove->projectManager.getProjectFrom (folder))
287 {
288 if (p == toRemove)
289 {
290 folder.getParent().removeChild (folder, nullptr);
291 return;
292 }
293 }
294
295 for (int i = 0; i < folder.getNumChildren(); ++i)
296 removeProject (folder.getChild(i), toRemove);
297}
298
299void ProjectManager::removeProjectFromList (const juce::File& f)
300{
301 const juce::ScopedLock sl (lock);
302
303 if (auto p = getProject (f))
304 {
305 if (! engine.getUIBehaviour().closeAllEditsBelongingToProject (*p))
306 return;
307
308 p->deselect();
309 removeProject (folders, p);
310
311 saveList();
312
313 SelectionManager::deselectAllFromAllWindows();
314
315 engine.getUIBehaviour().updateAllProjectItemLists();
316
317 for (auto edit : engine.getUIBehaviour().getAllOpenEdits())
318 if (edit != nullptr)
319 edit->sendSourceFileUpdate();
320
321 addFileToRecentProjectsList (f);
322 }
323}
324
325void ProjectManager::clearProjects()
326{
327 const juce::ScopedLock sl (lock);
328
329 folders.removeAllChildren (nullptr);
330 openProjects.clear();
331}
332
333static bool getValueTreeFor (const juce::ValueTree& folder, const Project* proj,
334 juce::ValueTree& result, bool createIfNotFound = true)
335{
336 if (proj == nullptr)
337 return false;
338
339 if (auto p = proj->projectManager.getProjectFrom (folder, createIfNotFound))
340 {
341 if (p == proj)
342 {
343 result = folder;
344 return true;
345 }
346 }
347
348 for (int i = 0; i < folder.getNumChildren(); ++i)
349 if (getValueTreeFor (folder.getChild(i), proj, result, createIfNotFound))
350 return true;
351
352 return false;
353}
354
355juce::ValueTree ProjectManager::findFolderContaining (const Project& p) const
356{
357 juce::ValueTree result;
358
359 if (getValueTreeFor (folders, &p, result))
360 return result.getParent();
361
362 return result;
363}
364
365juce::ValueTree ProjectManager::getFolderItemFor (const Project& p) const
366{
367 juce::ValueTree result;
368
369 if (getValueTreeFor (folders, &p, result))
370 return result;
371
372 return {};
373}
374
375int ProjectManager::getFolderIndexFor (const Project& p) const
376{
377 juce::ValueTree result;
378
379 if (getValueTreeFor (folders, &p, result))
380 return result.getParent().indexOf (result);
381
382 return -1;
383}
384
385void ProjectManager::updateProjectFile (Project& p, const juce::File& f)
386{
387 juce::ValueTree result;
388
389 if (getValueTreeFor (folders, &p, result, false))
390 result.setProperty (IDs::file, f.getFullPathName(), nullptr);
391}
392
393ProjectItem::Ptr ProjectManager::getProjectItem (ProjectItemID id)
394{
395 if (auto p = getProject (id.getProjectID()))
396 return p->getProjectItemForID (id);
397
398 return {};
399}
400
401ProjectItem::Ptr ProjectManager::getProjectItem (const Edit& ed)
402{
403 return getProjectItem (ed.getProjectItemID());
404}
405
406Project::Ptr ProjectManager::getProject (const Edit& ed)
407{
408 return getProject (ed.getProjectItemID().getProjectID());
409}
410
411juce::File ProjectManager::findSourceFile (ProjectItemID id)
412{
413 if (auto i = getProjectItem (id))
414 return i->getSourceFile();
415
416 return {};
417}
418
419void ProjectManager::saveAllProjects()
420{
421 const juce::ScopedLock sl (lock);
422
423 for (auto p : getAllProjects (folders))
424 p->save();
425}
426
427Project::Ptr ProjectManager::createNewProject (const juce::File& projectFile)
428{
429 return new Project (engine, *this, projectFile);
430}
431
432Project::Ptr ProjectManager::createNewProject (const juce::File& projectFile, juce::ValueTree folderToAddTo)
433{
434 const juce::ScopedLock sl (lock);
435
436 auto newProj = createNewProject (projectFile);
437 newProj->createNewProjectId();
438 newProj->setName (projectFile.getFileName().upToLastOccurrenceOf (".", false, false));
439 newProj->setDescription (TRANS("Created") + ": " + juce::Time::getCurrentTime().toString (true, false));
440
441 if (newProj->save())
442 {
443 newProj = nullptr;
444 newProj = addProjectToList (projectFile, true, folderToAddTo);
445
446 if (newProj != nullptr)
447 {
448 if (newProj->getNumProjectItems() == 0)
449 {
450 if (auto newEditProjectItem = newProj->createNewEdit())
451 {
452 newEditProjectItem->setDescription ("(" + TRANS("Created as the default edit for this project") + ")");
453 newProj->save();
454 }
455 }
456
457 newProj->createDefaultFolders();
458 newProj->refreshFolderStructure();
459
460 engine.getUIBehaviour().selectProjectInFocusedWindow (newProj);
461 }
462 }
463
464 engine.getUIBehaviour().updateAllProjectItemLists();
465
466 saveList();
467 return newProj;
468}
469
470Project::Ptr ProjectManager::createNewProjectFromTemplate (const juce::String& name, const juce::File& lastPath,
471 const juce::File& archiveFile, juce::ValueTree folder)
472{
473 auto extractPath = lastPath.getNonexistentChildFile (juce::File::createLegalFileName (name), {});
474
475 if (! extractPath.createDirectory())
476 return {};
477
478 TracktionArchiveFile archive (engine, archiveFile);
479
480 Project::Ptr proj;
481 bool aborted = false;
482 juce::Array<juce::File> filesCreated;
483 archive.extractAllAsTask (extractPath, false, filesCreated, aborted);
484
485 if (! aborted)
486 {
487 for (auto& f : filesCreated)
488 {
489 if (isTracktionProjectFile (f))
490 {
491 const juce::ScopedLock sl (lock);
492
493 auto p = createNewProject (f);
494
495 auto oldID = p->getProjectID();
496 p->createNewProjectId();
497
498 {
499 auto newID = p->getProjectID();
500 p->redirectIDsFromProject (oldID, newID);
501 p->setName (name);
502 p->save();
503 }
504
505 auto newFileName = p->getProjectFile();
506 p = nullptr;
507
508 proj = addProjectToList (newFileName, true, folder);
509
510 if (proj != nullptr)
511 {
512 engine.getUIBehaviour().selectProjectInFocusedWindow (proj);
513 int editNum = 1;
514
515 for (int i = 0; i < proj->getNumProjectItems(); ++i)
516 {
517 auto mo = proj->getProjectItemAt (i);
518
519 if (mo->isEdit())
520 mo->setName (name + " " + TRANS("Edit") + " " + juce::String (editNum++),
521 ProjectItem::SetNameMode::forceRename);
522 }
523
524 proj->createDefaultFolders();
525 proj->refreshFolderStructure();
526 saveList();
527
528 #if JUCE_DEBUG
529 for (int i = 0; i < proj->getNumProjectItems(); ++i)
530 jassert (proj->getProjectItemAt (i));
531 #endif
532 }
533
534 break;
535 }
536 }
537 }
538
539 return proj;
540}
541
542Project::Ptr ProjectManager::createNewProjectInteractively (const juce::String& name,
543 const juce::File& lastPath,
544 juce::ValueTree folderToAddTo)
545{
546 if (name.isNotEmpty())
547 {
548 auto& ui = engine.getUIBehaviour();
549 auto fileName = juce::File::createLegalFileName (name);
550
551 auto projectFile = lastPath.getChildFile (fileName)
552 .getChildFile (fileName + projectFileSuffix);
553
554 if (projectFile.exists())
555 {
556 if (! ui.showOkCancelAlertBox (TRANS("Create project"),
557 TRANS("This file already exists - do you want to open it?"),
558 TRANS("Open")))
559 return {};
560 }
561 else
562 {
563 auto parentDir = projectFile.getParentDirectory();
564
565 if (! parentDir.exists())
566 parentDir.createDirectory();
567
568 if (parentDir.getNumberOfChildFiles (juce::File::findFiles)
569 + parentDir.getNumberOfChildFiles (juce::File::findDirectories) > 0)
570 {
571 auto r = ui.showYesNoCancelAlertBox (
572 TRANS("Create project"),
573 TRANS("The directory in which you're trying to create this project is not empty.")
574 + "\n\n"
575 + TRANS("It's sensible to keep each project in its own directory, so "
576 "would you like to create a new subdirectory for it called \"XZZX\"?")
577 .replace ("XZZX", projectFile.getFileNameWithoutExtension()),
578 TRANS("Create a new subdirectory"),
579 TRANS("Use this directory anyway"),
580 TRANS("Cancel"));
581
582 if (r == 0)
583 return {};
584
585 if (r == 1)
586 {
587 auto newDir = parentDir.getChildFile (projectFile.getFileNameWithoutExtension());
588
589 if (newDir.exists()
590 && newDir.getNumberOfChildFiles (juce::File::findDirectories)
591 + newDir.getNumberOfChildFiles (juce::File::findFiles) > 0)
592 {
593 ui.showWarningAlert (TRANS("Create project"),
594 TRANS("The directory already existed and wasn't empty, so the project couldn't be created."));
595
596 return {};
597 }
598
599 if (! newDir.createDirectory())
600 {
601 ui.showWarningAlert (TRANS("Create project"),
602 TRANS("Couldn't create the new directory")
603 + ":\n\n" + newDir.getFullPathName());
604
605 return {};
606 }
607
608 projectFile = newDir.getChildFile (projectFile.getFileName());
609 }
610 }
611
612 if (! projectFile.create())
613 {
614 ui.showWarningAlert (TRANS("Create project"),
615 TRANS("Couldn't write to the file")
616 + ":\n\n" + projectFile.getFullPathName());
617 return {};
618 }
619 }
620
621 return createNewProject (projectFile, folderToAddTo);
622 }
623
624 return {};
625}
626
627void ProjectManager::unpackArchiveAndAddToList (const juce::File& archiveFile, juce::ValueTree folder)
628{
629 TracktionArchiveFile archive (engine, archiveFile);
630
631 if (! archive.isValidArchive())
632 {
633 engine.getUIBehaviour().showWarningMessage (TRANS("This file wasn't a valid tracktion archive file"));
634 return;
635 }
636
637 auto text = TRANS("Choose a directory into which the archive \"XZZX\" should be unpacked")
638 .replace ("XZZX", archiveFile.getFileName()) + "...";
639
640 auto lastPath = engine.getPropertyStorage().getDefaultLoadSaveDirectory ("projectfile");
641
642 if (lastPath.existsAsFile())
643 lastPath = lastPath.getParentDirectory();
644
645 if (! lastPath.isDirectory())
646 lastPath = archiveFile;
647 #if JUCE_MODAL_LOOPS_PERMITTED
648 juce::FileChooser chooser (text, lastPath, "*");
649
650 if (chooser.browseForDirectory())
651 #endif
652 {
653 #if JUCE_MODAL_LOOPS_PERMITTED
654 auto destDir = chooser.getResult();
655 #else
656 auto destDir = lastPath;
657 #endif
658
659 if (! destDir.createDirectory())
660 {
661 engine.getUIBehaviour().showWarningMessage (TRANS("Couldn't create this target directory"));
662 return;
663 }
664
665 destDir = destDir.getNonexistentChildFile (archiveFile.getFileNameWithoutExtension(), {}, false);
666
667 if (! destDir.createDirectory())
668 {
669 engine.getUIBehaviour().showWarningMessage (TRANS("Couldn't create this target directory"));
670 return;
671 }
672
673 bool wasAborted;
675
676 if (archive.extractAllAsTask (destDir, false, newFiles, wasAborted))
677 {
678 if (! wasAborted)
679 {
680 for (int i = newFiles.size(); --i >= 0;)
681 if (! isTracktionProjectFile (newFiles.getReference (i)))
682 newFiles.remove (i);
683
684 if (newFiles.isEmpty())
685 {
686 engine.getUIBehaviour().showWarningMessage (TRANS("This archive unpacked ok, but it didn't contain any project files!"));
687 }
688 else
689 {
690 for (int i = newFiles.size(); --i >= 0;)
691 if (auto newProj = addProjectToList (newFiles.getReference (i), true, folder))
692 engine.getUIBehaviour().selectProjectInFocusedWindow (newProj);
693 }
694
695 for (auto& f : newFiles)
696 {
697 if (auto proj = getProject (f))
698 {
699 proj->createDefaultFolders();
700 proj->refreshFolderStructure();
701 }
702 }
703
704 saveList();
705 }
706 }
707 else
708 {
709 engine.getUIBehaviour().showWarningMessage (TRANS("Errors occurred whilst trying to unpack this archive"));
710 }
711 }
712}
713
714juce::StringArray ProjectManager::getRecentProjects (bool printableFormat)
715{
716 juce::StringArray files;
717 files.addTokens (engine.getPropertyStorage().getProperty (SettingID::recentProjects).toString(), ";", {});
718 files.trim();
719 files.removeEmptyStrings();
720
721 while (files.size() > 8)
722 files.remove (0);
723
724 for (int i = files.size(); --i >= 0;)
725 {
726 const juce::File f (files.getReference (i));
727
728 if (! f.existsAsFile())
729 {
730 files.remove (i);
731 }
732 else
733 {
734 if (auto p = getProject (f))
735 if (findFolderContaining (*p).isValid())
736 files.remove (i);
737 }
738 }
739
740 if (printableFormat)
741 for (auto& f : files)
742 f = juce::File (f).getFileNameWithoutExtension();
743
744 return files;
745}
746
747void ProjectManager::addFileToRecentProjectsList (const juce::File& f)
748{
749 auto files = getRecentProjects (false);
750
751 for (auto& file : files)
752 if (juce::File (file) == f)
753 return;
754
755 if (auto p = getProject (f))
756 if (findFolderContaining (*p).isValid())
757 return;
758
759 files.add (f.getFullPathName());
760
761 engine.getPropertyStorage().setProperty (SettingID::recentProjects, files.joinIntoString (";"));
762}
763
764void ProjectManager::createNewProjectFolder (juce::ValueTree parent, const juce::String& name)
765{
766 auto v = createValueTree (IDs::FOLDER,
767 IDs::name, name);
768
769 parent.addChild (v, 0, nullptr);
770 ensureAllItemsHaveIDs (parent);
771 saveList();
772 engine.getUIBehaviour().updateAllProjectItemLists();
773}
774
775void ProjectManager::deleteProjectFolder (juce::ValueTree folder)
776{
777 folder.getParent().removeChild (folder, nullptr);
778}
779
780}} // namespace tracktion { inline namespace engine
bool isEmpty() const noexcept
int size() const noexcept
void remove(int indexToRemove)
ElementType & getReference(int index) noexcept
bool isDirectory() const
bool existsAsFile() const
const String & getFullPathName() const noexcept
String getFileName() const
File getChildFile(StringRef relativeOrAbsolutePath) const
String getFileNameWithoutExtension() const
Result create() const
File getNonexistentChildFile(const String &prefix, const String &suffix, bool putNumbersInBrackets=true) const
File getParentDirectory() const
static String createLegalFileName(const String &fileNameToFix)
bool exists() const
Result createDirectory() const
bool isValid() const noexcept
ObjectClass * add(ObjectClass *newObject)
String joinIntoString(StringRef separatorString, int startIndex=0, int numberOfElements=-1) const
String & getReference(int index) noexcept
void removeEmptyStrings(bool removeWhitespaceStrings=true)
int size() const noexcept
void add(String stringToAdd)
void remove(int index)
int addTokens(StringRef stringToTokenise, bool preserveQuotedStrings)
static String toHexString(IntegerType number)
String upToLastOccurrenceOf(StringRef substringToFind, bool includeSubStringInResult, bool ignoreCase) const
static Time JUCE_CALLTYPE getCurrentTime() noexcept
void removeChild(const ValueTree &child, UndoManager *undoManager)
ValueTree getChild(int index) const
int getNumChildren() const noexcept
ValueTree & setProperty(const Identifier &name, const var &newValue, UndoManager *undoManager)
int indexOf(const ValueTree &child) const noexcept
void addChild(const ValueTree &child, int index, UndoManager *undoManager)
ValueTree getParent() const noexcept
static ValueTree fromXml(const XmlElement &xml)
The Tracktion Edit class!
ProjectItemID getProjectItemID() const noexcept
Returns the ProjectItemID of the Edit.
An ID representing one of the items in a Project.
int getProjectID() const
Returns the ID of the project this item belongs to.
A tracktion project.
#define TRANS(stringLiteral)
#define jassert(expression)
#define CRASH_TRACER
This macro adds the current location to a stack which gets logged if a crash happens.