11namespace tracktion {
inline namespace engine
14static const char* slashEscapeSeq =
"[[slash]]";
29 values = other.values;
42 const int index = keys.
indexOf (key);
46 values.
set (index, value);
58 const int index = keys.
indexOf (key);
75 return values [keys.
indexOf (key)];
93 void remove (
int index)
111 return keys [values.
indexOf (value)];
118 for (
int i = values.
size(); --i >= 0;)
119 if (values[i] == value)
125 bool operator== (
const StringMap& other)
const
127 return keys == other.keys && values == other.values;
135 for (
int i = 0; i < keys.
size(); ++i)
137 s << keys[i].replace (
"|", slashEscapeSeq)
139 << values[i].replace (
"|", slashEscapeSeq);
141 if (i < keys.
size() - 1)
162 if (c ==
'|' || c == 0)
178 if (c ==
'|' || c == 0)
184 if (key.contains (slashEscapeSeq))
185 key = key.replace (slashEscapeSeq,
"|");
189 if (val.contains (slashEscapeSeq))
190 val = val.replace (slashEscapeSeq,
"|");
213 ProjectItem::Category category_,
225 setCategory (category_);
239 : engine (e), itemID (id)
241 objectName = readStringAutoDetectingUTF (*in);
242 type = readStringAutoDetectingUTF (*in);
243 description = readStringAutoDetectingUTF (*in);
244 file = readStringAutoDetectingUTF (*in);
248ProjectItem::~ProjectItem()
250 notifyListenersOfDeletion();
262void ProjectItem::sendChange()
266 if (
auto pp = getProject())
272 if (isEdit())
return TRANS (
"Edit");
273 if (isWave() || isMidi())
return TRANS (
"Audio File");
274 if (isVideo())
return TRANS (
"Video File");
276 return TRANS(
"Project item of type 'XXX'").replace (
"XXX", type);
281 if (isNowSelected && getLength() == 0)
288 if (
auto pp = getProject())
290 auto localName = name;
293 if (localName.containsChar (
'/'))
295 if (localName.startsWithChar (
'/'))
296 localName =
"C:" + localName.replaceCharacter (
'/',
'\\');
298 localName = localName.replaceCharacter (
'/',
'\\');
301 if (localName.containsChar (
'\\'))
302 localName = localName.replaceCharacter (
'\\',
'/');
305 auto projectDir = pp->getDefaultDirectory();
306 return projectDir.getChildFile (localName);
317 auto f = getRelativeFile (file);
319 if (f.existsAsFile())
328 if (! substitute.existsAsFile())
331 if (substitute.existsAsFile())
332 sourceFile = substitute;
339bool ProjectItem::isForFile (
const juce::File& f)
342 return f == sourceFile;
347void ProjectItem::setSourceFile (
const juce::File& f, FileMode mode)
349 if (
auto pp = getProject())
351 auto projectDir = pp->getDefaultDirectory();
354 if (mode == FileMode::relativeIfWithinProject)
356 else if (mode == FileMode::relative)
373bool ProjectItem::isAbsolutePath()
const
378void ProjectItem::convertToRelativePath()
380 auto source = getSourceFile();
382 if (source.existsAsFile() && isAbsolutePath())
383 setSourceFile (source, FileMode::relative);
386void ProjectItem::convertToAbsolutePath()
388 auto source = getSourceFile();
390 if (source.existsAsFile() && ! isAbsolutePath())
391 setSourceFile (source, FileMode::absolute);
394void ProjectItem::handleAsyncUpdate()
397 EditFileOperations::updateEditFiles();
399 for (
auto edit :
engine.getActiveEdits().getEdits())
401 edit->sendSourceFileUpdate();
411 if (standardised.containsChar (
'/'))
421 if (
auto p = getProject())
426 return dir.getChildFile (
"preview_" + getID().toStringSuitableForFilename()).withFileExtension (
"ogg");
436 if (
auto p = getProject())
441 return dir.getChildFile (
"preview_" + getID().toStringSuitableForFilename()).withFileExtension (
"png");
453bool ProjectItem::hasBeenDeleted()
const
455 if (
auto p = getProject())
456 return p->getProjectItemForID (getID()) ==
nullptr;
466void ProjectItem::setName (
const juce::String& n, SetNameMode mode)
470 if (
auto pp = getProject())
475 auto src = getSourceFile();
477 bool shouldRename =
false;
479 auto mrm = (ProjectItem::RenameMode)
static_cast<int> (
engine.
getPropertyStorage().getProperty (SettingID::renameMode, (
int) RenameMode::local));
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());
490 .withFileExtension (src.getFileExtension());
504void ProjectItem::timerCallback()
508 auto src = getSourceFile();
509 auto dst = getNonExistentSiblingWithIncrementedNumberSuffix (newDstFile,
false);
514 afm.releaseFile (AudioFile (
engine, src));
516 if (! dst.existsAsFile() && src.moveFileTo (dst))
518 afm.checkFileForChanges (AudioFile (
engine, dst));
519 afm.checkFileForChanges (AudioFile (
engine, src));
523 SelectionManager::refreshAllPropertyPanels();
529ProjectItem::Category ProjectItem::getCategory()
const
531 auto cat = (Category) getNamedProperty (
"MediaObjectCategory").getIntValue();
533 if (cat == Category::none && isEdit())
534 return Category::edit;
539void ProjectItem::setCategory (ProjectItem::Category cat)
541 setNamedProperty (
"MediaObjectCategory",
juce::String ((
int) cat));
555void ProjectItem::setDescription (
const juce::String& newDesc)
557 if (newDesc != getDescription())
571 objectName = other.objectName;
572 description = other.description;
581 return map.get (name);
589 if (
auto pp = getProject())
591 if (! pp->isReadOnly())
598 if (map.get (name) != value)
600 map.set (name, value);
601 description = getDescription() +
'|' + map.toString();
611 auto m = getNamedProperty (
"marks");
619 marks.
add (TimePosition::fromSeconds (t.getDoubleValue()));
627 if (
auto pp = getProject())
629 if (! pp->isReadOnly())
633 for (
auto& p : points)
634 m << p.inSeconds() <<
" ";
636 setNamedProperty (
"marks", m.trim());
639 pp->cancelAnyPendingUpdates();
644bool ProjectItem::convertEditFile()
646 auto f = getSourceFile();
653 if (newFile.existsAsFile())
655 juce::String m (
TRANS(
"There appears to already be a converted Edit in the project folder."));
659 TRANS(
"Use Existing"),
660 TRANS(
"Create New")))
662 setSourceFile (newFile);
666 newFile.copyFileTo (newFile.getNonexistentSibling());
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."));
680 setSourceFile (newFile);
686double ProjectItem::getLength()
const
691void ProjectItem::setLength (
double l)
693 if (std::abs (length - l) > 0.001)
702 if (
auto p = getProject())
737 if (len > 0.001 && len != getLength())
744 double startTime,
double lengthToCopy)
746 actualFileCreated = destFile;
750 actualFileCreated = destFile;
751 return AudioFileUtils::copySectionToNewFile (
engine,
752 getSourceFile(), actualFileCreated,
753 { TimePosition::fromSeconds (startTime), TimeDuration::fromSeconds (lengthToCopy) }) > 0;
762bool ProjectItem::deleteSourceFile()
765 auto f = getSourceFile();
775 for (
int attempts = 3; --attempts >= 0;)
777 afm.releaseFile (af);
783 juce::Thread::sleep (800);
786 afm.checkFileForChangesAsync (af);
794 info <<
" (read only)";
797 info <<
" (directory)";
801 TRACKTION_LOG_ERROR (info);
811 if (getID().getProjectID() == oldID)
819 for (
auto& item :
exp->getReferencedItems())
820 if (item.itemID.getProjectID() == oldID)
821 exp->reassignReferencedItem (item, item.itemID.withNewProjectID (newID), 0.0);
831 if (
auto pp = getProject())
834 auto newName = getName().
trim();
836 if (newName.fromLastOccurrenceOf (
"(",
false,
false).containsOnly (
"0123456789()")
837 && newName.retainCharacters (
"0123456789()").length() > 0)
839 while (newName.length() > 1
844 if (! newName.endsWithIgnoreCase (
TRANS(
"Copy")))
845 newName = newName +
" - " +
TRANS(
"Copy");
847 auto nameStem = newName;
852 bool alreadyThere =
false;
854 for (
int i = pp->getNumProjectItems(); --i >= 0;)
856 if (pp->getProjectItemAt (i)->getName().equalsIgnoreCase (newName))
864 newName = nameStem +
"(" +
juce::String (index++) +
")";
869 auto source = getSourceFile();
870 auto fileExtension = source.getFileExtension();
872 if (fileExtension.isEmpty())
874 fileExtension = af->getFileExtensions()[0];
876 auto newFile = source.getParentDirectory()
877 .getNonexistentChildFile (source.getFileNameWithoutExtension(),
878 fileExtension,
true);
880 if (pp->isReadOnly())
884 else if (source.copyFileTo (newFile))
886 return pp->createNewItem (newFile, getType(), newName, description, getCategory(),
true);
899 while (! t.isEmpty())
901 if (! (t.isLetterOrDigit()))
912 if (t.getAddress() > start.getAddress() + 1)
919 tokenise (objectName, toks);
920 tokenise (getDescription(), toks);
void add(const ElementType &newElement)
void triggerAsyncUpdate()
Time getLastModificationTime() const
bool hasWriteAccess() const
bool existsAsFile() const
bool copyFileTo(const File &targetLocation) 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
static String createLegalFileName(const String &fileNameToFix)
bool isAChildOf(const File &potentialParentDirectory) const
bool hasFileExtension(StringRef extensionToTest) const
Result createDirectory() 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)
int addTokens(StringRef stringToTokenise, bool preserveQuotedStrings)
CharPointerType getCharPointer() const noexcept
String upToFirstOccurrenceOf(StringRef substringToEndWith, bool includeSubStringInResult, bool ignoreCase) 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.
#define JUCE_LEAK_DETECTOR(OwnerClass)
#define TRANS(stringLiteral)
std::unique_ptr< Edit > loadEditForExamining(ProjectManager &pm, ProjectItemID itemID, Edit::EditRole role)
Uses the ProjectManager to find an Edit file and open it.