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

« « « Anklang Documentation
Loading...
Searching...
No Matches
tracktion_ProjectItem.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
14static const char* slashEscapeSeq = "[[slash]]";
15
17{
18 StringMap() {}
19
20 StringMap (const StringMap& other)
21 : keys (other.keys),
22 values (other.values)
23 {
24 }
25
26 StringMap& operator= (const StringMap& other)
27 {
28 keys = other.keys;
29 values = other.values;
30 return *this;
31 }
32
33 //==============================================================================
34 void set (const juce::String& key, const juce::String& value)
35 {
36 if (value.isEmpty())
37 {
38 remove (key);
39 }
40 else
41 {
42 const int index = keys.indexOf (key);
43
44 if (index >= 0)
45 {
46 values.set (index, value);
47 }
48 else
49 {
50 keys.add (key);
51 values.add (value);
52 }
53 }
54 }
55
56 void remove (const juce::String& key)
57 {
58 const int index = keys.indexOf (key);
59
60 if (index >= 0)
61 {
62 keys.remove (index);
63 values.remove (index);
64 }
65 }
66
67 void clear()
68 {
69 keys.clear();
70 values.clear();
71 }
72
73 juce::String get (const juce::String& key) const
74 {
75 return values [keys.indexOf (key)];
76 }
77
78 int size() const
79 {
80 return keys.size();
81 }
82
83 juce::String getKeyAt (int index) const
84 {
85 return keys[index];
86 }
87
88 juce::String getValueAt (int index) const
89 {
90 return values[index];
91 }
92
93 void remove (int index)
94 {
95 keys.remove (index);
96 values.remove (index);
97 }
98
99 bool containsKey (const juce::String& key) const
100 {
101 return keys.contains (key);
102 }
103
104 bool containsValue (const juce::String& value) const
105 {
106 return values.contains (value);
107 }
108
109 juce::String getKeyForValue (const juce::String& value) const
110 {
111 return keys [values.indexOf (value)];
112 }
113
114 juce::StringArray getKeysForValue (const juce::String& value) const
115 {
117
118 for (int i = values.size(); --i >= 0;)
119 if (values[i] == value)
120 s.add (keys[i]);
121
122 return s;
123 }
124
125 bool operator== (const StringMap& other) const
126 {
127 return keys == other.keys && values == other.values;
128 }
129
130 //==============================================================================
131 juce::String toString() const
132 {
133 juce::String s;
134
135 for (int i = 0; i < keys.size(); ++i)
136 {
137 s << keys[i].replace ("|", slashEscapeSeq)
138 << '|'
139 << values[i].replace ("|", slashEscapeSeq);
140
141 if (i < keys.size() - 1)
142 s << '|';
143 }
144
145 return s;
146 }
147
148 static StringMap recreateFromString (const juce::String& stringVersion)
149 {
150 StringMap sm;
151 auto n = stringVersion.getCharPointer();
152
153 for (;;)
154 {
155 int len = 0;
156 juce::juce_wchar c = 0;
157
158 for (;;)
159 {
160 c = n[len];
161
162 if (c == '|' || c == 0)
163 break;
164
165 ++len;
166 }
167
168 if (c == 0)
169 break;
170
171 juce::String key (n, (size_t) len);
172 n += len + 1;
173 len = 0;
174
175 for (;;)
176 {
177 c = n[len];
178 if (c == '|' || c == 0)
179 break;
180
181 ++len;
182 }
183
184 if (key.contains (slashEscapeSeq))
185 key = key.replace (slashEscapeSeq, "|");
186
187 juce::String val (n, (size_t) len);
188
189 if (val.contains (slashEscapeSeq))
190 val = val.replace (slashEscapeSeq, "|");
191
192 sm.set (key, val);
193
194 if (c == 0)
195 break;
196
197 n += len + 1;
198 }
199
200 return sm;
201 }
202
203 juce::StringArray keys, values;
204
206};
207
208
209//==============================================================================
211 const juce::String& name_, const juce::String& type_,
212 const juce::String& desc_, const juce::String& file_,
213 ProjectItem::Category category_,
214 double length_, ProjectItemID id)
215 : Selectable(),
217 engine (e),
218 itemID (id),
219 type (type_),
220 objectName (name_),
221 description (desc_),
222 file (file_),
223 length (length_)
224{
225 setCategory (category_);
226}
227
228static juce::String readStringAutoDetectingUTF (juce::InputStream& in)
229{
231
232 while (char byte = in.readByte())
233 mo << byte;
234
235 return mo.toString();
236}
237
239 : engine (e), itemID (id)
240{
241 objectName = readStringAutoDetectingUTF (*in);
242 type = readStringAutoDetectingUTF (*in);
243 description = readStringAutoDetectingUTF (*in);
244 file = readStringAutoDetectingUTF (*in);
245 length = in->readDouble();
246}
247
248ProjectItem::~ProjectItem()
249{
250 notifyListenersOfDeletion();
251}
252
253void ProjectItem::writeToStream (juce::OutputStream& out) const
254{
255 out.writeString (objectName);
256 out.writeString (type);
257 out.writeString (description);
258 out.writeString (file);
259 out.writeDouble (length);
260}
261
262void ProjectItem::sendChange()
263{
264 changed();
265
266 if (auto pp = getProject())
267 pp->changed();
268}
269
271{
272 if (isEdit()) return TRANS ("Edit");
273 if (isWave() || isMidi()) return TRANS ("Audio File");
274 if (isVideo()) return TRANS ("Video File");
275
276 return TRANS("Project item of type 'XXX'").replace ("XXX", type);
277}
278
279void ProjectItem::selectionStatusChanged (bool isNowSelected)
280{
281 if (isNowSelected && getLength() == 0)
282 verifyLength();
283}
284
285//==============================================================================
286juce::File ProjectItem::getRelativeFile (const juce::String& name) const
287{
288 if (auto pp = getProject())
289 {
290 auto localName = name;
291
292 #if JUCE_WINDOWS
293 if (localName.containsChar ('/'))
294 {
295 if (localName.startsWithChar ('/'))
296 localName = "C:" + localName.replaceCharacter ('/', '\\');
297 else
298 localName = localName.replaceCharacter ('/', '\\');
299 }
300 #else
301 if (localName.containsChar ('\\'))
302 localName = localName.replaceCharacter ('\\', '/');
303 #endif
304
305 auto projectDir = pp->getDefaultDirectory();
306 return projectDir.getChildFile (localName);
307 }
308
310 return {};
311}
312
313juce::File ProjectItem::getSourceFile()
314{
315 if (sourceFile == juce::File())
316 {
317 auto f = getRelativeFile (file);
318
319 if (f.existsAsFile())
320 {
321 sourceFile = f;
322 }
323 else if (engine.getAudioFileFormatManager().canOpen (f))
324 {
325 // if not found, look for a compressed version to use..
326 auto substitute = f.withFileExtension ("flac");
327
328 if (! substitute.existsAsFile())
329 substitute = f.withFileExtension ("ogg");
330
331 if (substitute.existsAsFile())
332 sourceFile = substitute;
333 }
334 }
335
336 return sourceFile;
337}
338
339bool ProjectItem::isForFile (const juce::File& f)
340{
341 if (sourceFile != juce::File())
342 return f == sourceFile;
343
344 return file.endsWithIgnoreCase (f.getFileName()) && getSourceFile() == f;
345}
346
347void ProjectItem::setSourceFile (const juce::File& f, FileMode mode)
348{
349 if (auto pp = getProject())
350 {
351 auto projectDir = pp->getDefaultDirectory();
352 bool relative = false;
353
354 if (mode == FileMode::relativeIfWithinProject)
355 relative = f.isAChildOf (projectDir);
356 else if (mode == FileMode::relative)
357 relative = true;
358
359 if (relative)
360 file = f.getRelativePathFrom (projectDir);
361 else
362 file = f.getFullPathName();
363
364 sourceFile = juce::File();
365
366 changed();
367 pp->changed();
368
370 }
371}
372
373bool ProjectItem::isAbsolutePath() const
374{
375 return file.isNotEmpty() && juce::File::isAbsolutePath (getRawFileName());
376}
377
378void ProjectItem::convertToRelativePath()
379{
380 auto source = getSourceFile();
381
382 if (source.existsAsFile() && isAbsolutePath())
383 setSourceFile (source, FileMode::relative);
384}
385
386void ProjectItem::convertToAbsolutePath()
387{
388 auto source = getSourceFile();
389
390 if (source.existsAsFile() && ! isAbsolutePath())
391 setSourceFile (source, FileMode::absolute);
392}
393
394void ProjectItem::handleAsyncUpdate()
395{
396 if (isEdit())
397 EditFileOperations::updateEditFiles();
398
399 for (auto edit : engine.getActiveEdits().getEdits())
400 if (edit != nullptr)
401 edit->sendSourceFileUpdate();
402}
403
404juce::String ProjectItem::getFileName() const
405{
406 if (file.isEmpty())
407 return {};
408
409 auto standardised = file.replaceCharacter ('\\', '/');
410
411 if (standardised.containsChar ('/'))
412 return standardised.fromLastOccurrenceOf ("/", false, false);
413
414 return standardised;
415}
416
418{
419 jassert (isEdit());
420
421 if (auto p = getProject())
422 {
423 auto dir = engine.getTemporaryFileManager().getTempDirectory().getChildFile ("previews");
424 dir.createDirectory();
425
426 return dir.getChildFile ("preview_" + getID().toStringSuitableForFilename()).withFileExtension ("ogg");
427 }
428
429 return {};
430}
431
433{
434 jassert (isEdit());
435
436 if (auto p = getProject())
437 {
438 auto dir = engine.getTemporaryFileManager().getTempDirectory().getChildFile ("previews");
439 dir.createDirectory();
440
441 return dir.getChildFile ("preview_" + getID().toStringSuitableForFilename()).withFileExtension ("png");
442 }
443
444 return {};
445}
446
447//==============================================================================
448Project::Ptr ProjectItem::getProject() const
449{
450 return engine.getProjectManager().getProject (getID().getProjectID());
451}
452
453bool ProjectItem::hasBeenDeleted() const
454{
455 if (auto p = getProject())
456 return p->getProjectItemForID (getID()) == nullptr;
457
458 return true;
459}
460
461const juce::String& ProjectItem::getName() const
462{
463 return objectName;
464}
465
466void ProjectItem::setName (const juce::String& n, SetNameMode mode)
467{
468 if (objectName != n)
469 {
470 if (auto pp = getProject())
471 {
472 objectName = n.substring (0, 256);
473
474 // also rename the file if it is in the project folder
475 auto src = getSourceFile();
476
477 bool shouldRename = false;
478
479 auto mrm = (ProjectItem::RenameMode) static_cast<int> (engine.getPropertyStorage().getProperty (SettingID::renameMode, (int) RenameMode::local));
480
481 if (mode == SetNameMode::forceNoRename) shouldRename = false;
482 else if (mode == SetNameMode::forceRename) shouldRename = true;
483 else if (mrm == RenameMode::always) shouldRename = true;
484 else if (mrm == RenameMode::never) shouldRename = false;
485 else if (mrm == RenameMode::local) shouldRename = src.isAChildOf (pp->getDefaultDirectory());
486
487 if (shouldRename)
488 {
489 newDstFile = src.getParentDirectory().getChildFile (juce::File::createLegalFileName (n))
490 .withFileExtension (src.getFileExtension());
491
492 startTimer (1);
493 }
494 else
495 {
496 engine.getUIBehaviour().editNamesMayHaveChanged();
497 }
498
499 sendChange();
500 }
501 }
502}
503
504void ProjectItem::timerCallback()
505{
506 stopTimer();
507
508 auto src = getSourceFile();
509 auto dst = getNonExistentSiblingWithIncrementedNumberSuffix (newDstFile, false);
510
512
513 auto& afm = engine.getAudioFileManager();
514 afm.releaseFile (AudioFile (engine, src));
515
516 if (! dst.existsAsFile() && src.moveFileTo (dst))
517 {
518 afm.checkFileForChanges (AudioFile (engine, dst));
519 afm.checkFileForChanges (AudioFile (engine, src));
520
521 setSourceFile (dst);
522
523 SelectionManager::refreshAllPropertyPanels();
524 engine.getUIBehaviour().editNamesMayHaveChanged();
525 sendChange();
526 }
527}
528
529ProjectItem::Category ProjectItem::getCategory() const
530{
531 auto cat = (Category) getNamedProperty ("MediaObjectCategory").getIntValue();
532
533 if (cat == Category::none && isEdit())
534 return Category::edit;
535
536 return cat;
537}
538
539void ProjectItem::setCategory (ProjectItem::Category cat)
540{
541 setNamedProperty ("MediaObjectCategory", juce::String ((int) cat));
542 sendChange();
543}
544
545const juce::String& ProjectItem::getType() const
546{
547 return type;
548}
549
550juce::String ProjectItem::getDescription() const
551{
552 return description.upToFirstOccurrenceOf ("|", false, false);
553}
554
555void ProjectItem::setDescription (const juce::String& newDesc)
556{
557 if (newDesc != getDescription())
558 {
559 if (description.containsChar ('|'))
560 description = newDesc.removeCharacters ("|")
561 + description.fromFirstOccurrenceOf ("|", true, false);
562 else
563 description = newDesc.removeCharacters ("|");
564
565 sendChange();
566 }
567}
568
570{
571 objectName = other.objectName;
572 description = other.description;
573}
574
575juce::String ProjectItem::getNamedProperty (const juce::String& name) const
576{
577 if (description.containsChar ('|'))
578 {
579 const StringMap map (StringMap::recreateFromString (description.fromFirstOccurrenceOf ("|", false, false)));
580
581 return map.get (name);
582 }
583
584 return {};
585}
586
587void ProjectItem::setNamedProperty (const juce::String& name, const juce::String& value)
588{
589 if (auto pp = getProject())
590 {
591 if (! pp->isReadOnly())
592 {
593 StringMap map;
594
595 if (description.containsChar ('|'))
596 map = StringMap::recreateFromString (description.fromFirstOccurrenceOf ("|", false, false));
597
598 if (map.get (name) != value)
599 {
600 map.set (name, value);
601 description = getDescription() + '|' + map.toString();
602 sendChange();
603 }
604 }
605 }
606}
607
609{
611 auto m = getNamedProperty ("marks");
612
613 if (m.isNotEmpty())
614 {
616 toks.addTokens (m, true);
617
618 for (auto& t : toks)
619 marks.add (TimePosition::fromSeconds (t.getDoubleValue()));
620 }
621
622 return marks;
623}
624
625void ProjectItem::setMarkedPoints (const juce::Array<TimePosition>& points)
626{
627 if (auto pp = getProject())
628 {
629 if (! pp->isReadOnly())
630 {
631 juce::String m;
632
633 for (auto& p : points)
634 m << p.inSeconds() << " ";
635
636 setNamedProperty ("marks", m.trim());
637
638 // stop the ui getting chuggy when moving points
639 pp->cancelAnyPendingUpdates();
640 }
641 }
642}
643
644bool ProjectItem::convertEditFile()
645{
646 auto f = getSourceFile();
647
648 if (f.hasFileExtension (editFileSuffix))
649 return true;
650
651 auto newFile = f.withFileExtension (editFileSuffix);
652
653 if (newFile.existsAsFile())
654 {
655 juce::String m (TRANS("There appears to already be a converted Edit in the project folder."));
656 m << juce::newLine << TRANS("Do you want to use this, or create a new conversion?");
657
658 if (engine.getUIBehaviour().showOkCancelAlertBox (TRANS("Converted Edit Already Exists"), m,
659 TRANS("Use Existing"),
660 TRANS("Create New")))
661 {
662 setSourceFile (newFile);
663 return true;
664 }
665
666 newFile.copyFileTo (newFile.getNonexistentSibling());
667 }
668
669 if (f.existsAsFile() && f != newFile)
670 {
671 if (! f.copyFileTo (newFile))
672 {
673 engine.getUIBehaviour().showWarningAlert (TRANS("Unable to Open Edit"),
674 TRANS("The selected Edit file could not be converted to the current project format.")
676 + TRANS("Please ensure you can write to the Edit directory and try again."));
677 return false;
678 }
679
680 setSourceFile (newFile);
681 }
682
683 return true;
684}
685
686double ProjectItem::getLength() const
687{
688 return length;
689}
690
691void ProjectItem::setLength (double l)
692{
693 if (std::abs (length - l) > 0.001)
694 {
695 length = l;
696 sendChange();
697 }
698}
699
701{
702 if (auto p = getProject())
703 return p->getName();
704
705 return {};
706}
707
708//==============================================================================
710{
711 double len = 0.0;
712
713 if (isWave())
714 {
715 len = AudioFile (engine, getSourceFile()).getLength();
716 }
717 else if (isMidi())
718 {
719 juce::FileInputStream in (getSourceFile());
720
721 if (in.openedOk())
722 {
724 mf.readFrom (in);
726
727 len = mf.getLastTimestamp();
728 }
729 }
730 else if (isEdit())
731 {
732 // don't do this - too slow..
733 //Edit tempEdit (this);
734 //len = tempEdit.getLength();
735 }
736
737 if (len > 0.001 && len != getLength())
738 setLength (len);
739}
740
741//==============================================================================
743 juce::File& actualFileCreated,
744 double startTime, double lengthToCopy)
745{
746 actualFileCreated = destFile;
747
748 if (isWave())
749 {
750 actualFileCreated = destFile;
751 return AudioFileUtils::copySectionToNewFile (engine,
752 getSourceFile(), actualFileCreated,
753 { TimePosition::fromSeconds (startTime), TimeDuration::fromSeconds (lengthToCopy) }) > 0;
754 }
755
756 if (isEdit())
757 return getSourceFile().copyFileTo (destFile);
758
759 return false;
760}
761
762bool ProjectItem::deleteSourceFile()
763{
764 bool ok = true;
765 auto f = getSourceFile();
766
767 if (f.existsAsFile())
768 {
769 // extra steps in case it's in use by a strip or something..
771 auto& afm = engine.getAudioFileManager();
772
773 AudioFile af (engine, f);
774
775 for (int attempts = 3; --attempts >= 0;)
776 {
777 afm.releaseFile (af);
778 ok = f.moveToTrash();
779
780 if (ok)
781 break;
782
783 juce::Thread::sleep (800);
784 }
785
786 afm.checkFileForChangesAsync (af);
787
788 if (! ok)
789 {
790 auto info = f.getFullPathName() + " "
792
793 if (! f.hasWriteAccess())
794 info << " (read only)";
795
796 if (f.isDirectory())
797 info << " (directory)";
798
799 info << " modified: " << f.getLastModificationTime().toString (true, true);
800
801 TRACKTION_LOG_ERROR (info);
802 }
803 }
804
805 return ok;
806}
807
808//==============================================================================
809void ProjectItem::changeProjectId (int oldID, int newID)
810{
811 if (getID().getProjectID() == oldID)
812 itemID = ProjectItemID (getID().getItemID(), newID);
813
814 if (isEdit())
815 {
816 auto ed = loadEditForExamining (engine.getProjectManager(), getID());
817
818 for (auto exp : Exportable::addAllExportables (*ed))
819 for (auto& item : exp->getReferencedItems())
820 if (item.itemID.getProjectID() == oldID)
821 exp->reassignReferencedItem (item, item.itemID.withNewProjectID (newID), 0.0);
822
823 EditFileOperations (*ed).save (false, true, false);
824 }
825}
826
827
828//==============================================================================
830{
831 if (auto pp = getProject())
832 {
833 // create a new unique name
834 auto newName = getName().trim();
835
836 if (newName.fromLastOccurrenceOf ("(", false, false).containsOnly ("0123456789()")
837 && newName.retainCharacters ("0123456789()").length() > 0)
838 {
839 while (newName.length() > 1
840 && juce::String ("0123456789()").containsChar (newName.getLastCharacter()))
841 newName = newName.dropLastCharacters (1);
842 }
843
844 if (! newName.endsWithIgnoreCase (TRANS("Copy")))
845 newName = newName + " - " + TRANS("Copy");
846
847 auto nameStem = newName;
848 int index = 2;
849
850 for (;;)
851 {
852 bool alreadyThere = false;
853
854 for (int i = pp->getNumProjectItems(); --i >= 0;)
855 {
856 if (pp->getProjectItemAt (i)->getName().equalsIgnoreCase (newName))
857 {
858 alreadyThere = true;
859 break;
860 }
861 }
862
863 if (alreadyThere)
864 newName = nameStem + "(" + juce::String (index++) + ")";
865 else
866 break;
867 }
868
869 auto source = getSourceFile();
870 auto fileExtension = source.getFileExtension();
871
872 if (fileExtension.isEmpty())
873 if (auto af = engine.getAudioFileFormatManager().getFormatFromFileName (source))
874 fileExtension = af->getFileExtensions()[0];
875
876 auto newFile = source.getParentDirectory()
877 .getNonexistentChildFile (source.getFileNameWithoutExtension(),
878 fileExtension, true);
879
880 if (pp->isReadOnly())
881 {
882 engine.getUIBehaviour().showWarningMessage (TRANS("Can't create a new item because this project is read-only"));
883 }
884 else if (source.copyFileTo (newFile))
885 {
886 return pp->createNewItem (newFile, getType(), newName, description, getCategory(), true);
887 }
888 }
889
890 return {};
891}
892
893//==============================================================================
894static void tokenise (const juce::String& s, juce::StringArray& toks)
895{
896 auto t = s.getCharPointer();
897 auto start = t;
898
899 while (! t.isEmpty())
900 {
901 if (! (t.isLetterOrDigit()))
902 {
903 if (t > start + 1)
904 toks.add (juce::String (start, t));
905
906 start = t;
907 }
908
909 ++t;
910 }
911
912 if (t.getAddress() > start.getAddress() + 1)
913 toks.add (juce::String (start, t));
914}
915
917{
919 tokenise (objectName, toks);
920 tokenise (getDescription(), toks);
921 return toks;
922}
923
924}} // namespace tracktion { inline namespace engine
void add(const ElementType &newElement)
bool openedOk() const noexcept
bool isDirectory() const
Time getLastModificationTime() const
bool hasWriteAccess() const
bool existsAsFile() const
bool copyFileTo(const File &targetLocation) const
int64 getSize() const
const String & getFullPathName() const noexcept
String getFileName() const
File getChildFile(StringRef relativeOrAbsolutePath) const
static bool isAbsolutePath(StringRef path)
String getRelativePathFrom(const File &directoryToBeRelativeTo) const
static String descriptionOfSizeInBytes(int64 bytes)
File withFileExtension(StringRef newExtension) const
bool moveToTrash() const
static String createLegalFileName(const String &fileNameToFix)
bool isAChildOf(const File &potentialParentDirectory) const
bool hasFileExtension(StringRef extensionToTest) const
Result createDirectory() const
virtual double readDouble()
virtual char readByte()
String toString() const
void convertTimestampTicksToSeconds()
double getLastTimestamp() const
bool readFrom(InputStream &sourceStream, bool createMatchingNoteOffs=true, int *midiFileType=nullptr)
virtual bool writeDouble(double value)
virtual bool writeString(const String &text)
int indexOf(StringRef stringToLookFor, bool ignoreCase=false, int startIndex=0) const
bool contains(StringRef stringToLookFor, bool ignoreCase=false) const
int size() const noexcept
void add(String stringToAdd)
void set(int index, String newString)
void remove(int index)
int addTokens(StringRef stringToTokenise, bool preserveQuotedStrings)
CharPointerType getCharPointer() const noexcept
String upToFirstOccurrenceOf(StringRef substringToEndWith, bool includeSubStringInResult, bool ignoreCase) const
String trim() const
bool isEmpty() const noexcept
bool containsChar(juce_wchar character) const noexcept
String removeCharacters(StringRef charactersToRemove) const
bool endsWithIgnoreCase(StringRef text) const noexcept
String dropLastCharacters(int numberToDrop) const
String replaceCharacter(juce_wchar characterToReplace, juce_wchar characterToInsertInstead) const
String substring(int startIndex, int endIndex) const
String fromLastOccurrenceOf(StringRef substringToFind, bool includeSubStringInResult, bool ignoreCase) const
bool isNotEmpty() const noexcept
String fromFirstOccurrenceOf(StringRef substringToStartFrom, bool includeSubStringInResult, bool ignoreCase) const
String toString(bool includeDate, bool includeTime, bool includeSeconds=true, bool use24HourClock=false) const
void stopTimer() noexcept
void startTimer(int intervalInMilliseconds) noexcept
Contains methods for saving an Edit to a file.
The Engine is the central class for all tracktion sessions.
PropertyStorage & getPropertyStorage() const
Returns the PropertyStorage user settings customisable XML file.
AudioFileFormatManager & getAudioFileFormatManager() const
Returns the AudioFileFormatManager that maintains a list of available audio file formats.
UIBehaviour & getUIBehaviour() const
Returns the UIBehaviour class.
AudioFileManager & getAudioFileManager() const
Returns the AudioFileManager instance.
ProjectManager & getProjectManager() const
Returns the ProjectManager instance.
TemporaryFileManager & getTemporaryFileManager() const
Returns the TemporaryFileManager allowing to handle the default app and user temporary folders.
static juce::Array< Exportable * > addAllExportables(Edit &)
Returns all the Exportables contained in an Edit.
An ID representing one of the items in a Project.
Represents a file-based resource that is used in a project.
void changeProjectId(int oldID, int newID)
used when moving to another project.
bool copySectionToNewFile(const juce::File &destFile, juce::File &actualFileCreated, double startTime, double length)
the actual file created may differ, e.g.
juce::File getEditPreviewFile() const
Returns the file that should be used as a preview for this Edit.
void verifyLength()
Updates the stored length value in this object.
juce::File getEditThumbnailFile() const
Returns the file that should be used as a visual preview for this Edit.
juce::String getProjectName() const
name of the project it's inside.
void selectionStatusChanged(bool isNowSelected) override
Can be overridden to tell this object that it has just been selected or deselected.
juce::StringArray getSearchTokens() const
Returns a list of search strings for this object, by chopping up the name and description into words.
juce::Array< TimePosition > getMarkedPoints() const
optional set of interesting time markers, for wave files
Ptr createCopy()
Creates a copy of this object and returns the copy.
void copyAllPropertiesFrom(const ProjectItem &other)
copies the full description, categories, properties, etc.
juce::String getSelectableDescription() override
Subclasses must return a description of what they are.
Engine & engine
The Engine instance this belongs to.
ProjectItem(Engine &, ProjectItemID, juce::InputStream *)
Loads a ProjectItem from a stream that was saved using writeToStream().
Base class for things that can be selected, and whose properties can appear in the properties panel.
virtual void changed()
This should be called to send a change notification to any SelectableListeners that are registered wi...
static void stopAllTransports(Engine &, bool discardRecordings, bool clearDevices)
Stops all TransportControl[s] in the Engine playing.
virtual bool showOkCancelAlertBox(const juce::String &title, const juce::String &message, const juce::String &ok={}, const juce::String &cancel={})
Should display a dismissable alert window.
virtual void showWarningAlert(const juce::String &title, const juce::String &message)
Should display a dismissable alert window.
virtual void showWarningMessage(const juce::String &message)
Should display a temporary warning message.
exp
#define JUCE_LEAK_DETECTOR(OwnerClass)
#define TRANS(stringLiteral)
#define jassert(expression)
#define jassertfalse
NewLine newLine
wchar_t juce_wchar
std::unique_ptr< Edit > loadEditForExamining(ProjectManager &pm, ProjectItemID itemID, Edit::EditRole role)
Uses the ProjectManager to find an Edit file and open it.
T relative(T... args)