mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
840 lines
28 KiB
C++
840 lines
28 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE library.
|
|
Copyright (c) 2017 - ROLI Ltd.
|
|
|
|
JUCE is an open source library subject to commercial or open-source
|
|
licensing.
|
|
|
|
By using JUCE, you agree to the terms of both the JUCE 5 End-User License
|
|
Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
|
|
27th April 2017).
|
|
|
|
End User License Agreement: www.juce.com/juce-5-licence
|
|
Privacy Policy: www.juce.com/juce-5-privacy-policy
|
|
|
|
Or: You may also use this code under the terms of the GPL v3 (see
|
|
www.gnu.org/licenses).
|
|
|
|
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
|
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
|
DISCLAIMED.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
|
|
//==============================================================================
|
|
class FileTreeItemBase : public JucerTreeViewBase,
|
|
public ValueTree::Listener
|
|
{
|
|
public:
|
|
FileTreeItemBase (const Project::Item& projectItem)
|
|
: item (projectItem), isFileMissing (false)
|
|
{
|
|
item.state.addListener (this);
|
|
}
|
|
|
|
~FileTreeItemBase() override
|
|
{
|
|
item.state.removeListener (this);
|
|
}
|
|
|
|
//==============================================================================
|
|
virtual bool acceptsFileDrop (const StringArray& files) const = 0;
|
|
virtual bool acceptsDragItems (const OwnedArray<Project::Item>& selectedNodes) = 0;
|
|
|
|
//==============================================================================
|
|
String getDisplayName() const override { return item.getName(); }
|
|
String getRenamingName() const override { return getDisplayName(); }
|
|
void setName (const String& newName) override { item.getNameValue() = newName; }
|
|
bool isMissing() const override { return isFileMissing; }
|
|
virtual File getFile() const { return item.getFile(); }
|
|
|
|
void deleteItem() override { item.removeItemFromProject(); }
|
|
|
|
void deleteAllSelectedItems() override
|
|
{
|
|
auto* tree = getOwnerView();
|
|
Array<File> filesToTrash;
|
|
Array<Project::Item> itemsToRemove;
|
|
|
|
for (int i = 0; i < tree->getNumSelectedItems(); ++i)
|
|
{
|
|
if (auto* p = dynamic_cast<FileTreeItemBase*> (tree->getSelectedItem (i)))
|
|
{
|
|
itemsToRemove.add (p->item);
|
|
|
|
if (p->item.isGroup())
|
|
{
|
|
for (int j = 0; j < p->item.getNumChildren(); ++j)
|
|
{
|
|
auto associatedFile = p->item.getChild (j).getFile();
|
|
|
|
if (associatedFile.existsAsFile())
|
|
filesToTrash.addIfNotAlreadyThere (associatedFile);
|
|
}
|
|
}
|
|
else if (p->getFile().existsAsFile())
|
|
{
|
|
filesToTrash.addIfNotAlreadyThere (p->getFile());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (filesToTrash.size() > 0)
|
|
{
|
|
String fileList;
|
|
auto maxFilesToList = 10;
|
|
for (auto i = jmin (maxFilesToList, filesToTrash.size()); --i >= 0;)
|
|
fileList << filesToTrash.getUnchecked(i).getFullPathName() << "\n";
|
|
|
|
if (filesToTrash.size() > maxFilesToList)
|
|
fileList << "\n...plus " << (filesToTrash.size() - maxFilesToList) << " more files...";
|
|
|
|
auto r = AlertWindow::showYesNoCancelBox (AlertWindow::NoIcon, "Delete Project Items",
|
|
"As well as removing the selected item(s) from the project, do you also want to move their files to the trash:\n\n"
|
|
+ fileList,
|
|
"Just remove references",
|
|
"Also move files to Trash",
|
|
"Cancel",
|
|
tree->getTopLevelComponent());
|
|
|
|
if (r == 0)
|
|
return;
|
|
|
|
if (r != 2)
|
|
filesToTrash.clear();
|
|
}
|
|
|
|
if (auto* treeRootItem = dynamic_cast<FileTreeItemBase*> (tree->getRootItem()))
|
|
{
|
|
auto& om = ProjucerApplication::getApp().openDocumentManager;
|
|
|
|
for (auto i = filesToTrash.size(); --i >= 0;)
|
|
{
|
|
auto f = filesToTrash.getUnchecked(i);
|
|
|
|
om.closeFile (f, false);
|
|
|
|
if (! f.moveToTrash())
|
|
{
|
|
// xxx
|
|
}
|
|
}
|
|
|
|
for (auto i = itemsToRemove.size(); --i >= 0;)
|
|
{
|
|
if (auto itemToRemove = treeRootItem->findTreeViewItem (itemsToRemove.getUnchecked (i)))
|
|
{
|
|
if (auto* pcc = treeRootItem->getProjectContentComponent())
|
|
{
|
|
if (auto* fileInfoComp = dynamic_cast<FileGroupInformationComponent*> (pcc->getEditorComponentContent()))
|
|
if (fileInfoComp->getGroupPath() == itemToRemove->getFile().getFullPathName())
|
|
pcc->hideEditor();
|
|
}
|
|
|
|
om.closeFile (itemToRemove->getFile(), false);
|
|
itemToRemove->deleteItem();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
jassertfalse;
|
|
}
|
|
}
|
|
|
|
virtual void revealInFinder() const
|
|
{
|
|
getFile().revealToUser();
|
|
}
|
|
|
|
virtual void browseToAddExistingFiles()
|
|
{
|
|
auto location = item.isGroup() ? item.determineGroupFolder() : getFile();
|
|
FileChooser fc ("Add Files to Jucer Project", location, {});
|
|
|
|
if (fc.browseForMultipleFilesOrDirectories())
|
|
{
|
|
StringArray files;
|
|
|
|
for (int i = 0; i < fc.getResults().size(); ++i)
|
|
files.add (fc.getResults().getReference(i).getFullPathName());
|
|
|
|
addFilesRetainingSortOrder (files);
|
|
}
|
|
}
|
|
|
|
virtual void checkFileStatus() // (recursive)
|
|
{
|
|
auto file = getFile();
|
|
auto nowMissing = (file != File() && ! file.exists());
|
|
|
|
if (nowMissing != isFileMissing)
|
|
{
|
|
isFileMissing = nowMissing;
|
|
repaintItem();
|
|
}
|
|
}
|
|
|
|
virtual void addFilesAtIndex (const StringArray& files, int insertIndex)
|
|
{
|
|
if (auto* p = getParentProjectItem())
|
|
p->addFilesAtIndex (files, insertIndex);
|
|
}
|
|
|
|
virtual void addFilesRetainingSortOrder (const StringArray& files)
|
|
{
|
|
if (auto* p = getParentProjectItem())
|
|
p->addFilesRetainingSortOrder (files);
|
|
}
|
|
|
|
virtual void moveSelectedItemsTo (OwnedArray <Project::Item>&, int /*insertIndex*/)
|
|
{
|
|
jassertfalse;
|
|
}
|
|
|
|
void showMultiSelectionPopupMenu() override
|
|
{
|
|
PopupMenu m;
|
|
m.addItem (1, "Delete");
|
|
|
|
m.showMenuAsync (PopupMenu::Options(),
|
|
ModalCallbackFunction::create (treeViewMultiSelectItemChosen, this));
|
|
}
|
|
|
|
static void treeViewMultiSelectItemChosen (int resultCode, FileTreeItemBase* item)
|
|
{
|
|
switch (resultCode)
|
|
{
|
|
case 1: item->deleteAllSelectedItems(); break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
virtual FileTreeItemBase* findTreeViewItem (const Project::Item& itemToFind)
|
|
{
|
|
if (item == itemToFind)
|
|
return this;
|
|
|
|
auto wasOpen = isOpen();
|
|
setOpen (true);
|
|
|
|
for (auto i = getNumSubItems(); --i >= 0;)
|
|
{
|
|
if (auto* pg = dynamic_cast<FileTreeItemBase*> (getSubItem(i)))
|
|
if (auto* found = pg->findTreeViewItem (itemToFind))
|
|
return found;
|
|
}
|
|
|
|
setOpen (wasOpen);
|
|
return nullptr;
|
|
}
|
|
|
|
//==============================================================================
|
|
void valueTreePropertyChanged (ValueTree& tree, const Identifier&) override
|
|
{
|
|
if (tree == item.state)
|
|
repaintItem();
|
|
}
|
|
|
|
void valueTreeChildAdded (ValueTree& parentTree, ValueTree&) override { treeChildrenChanged (parentTree); }
|
|
void valueTreeChildRemoved (ValueTree& parentTree, ValueTree&, int) override { treeChildrenChanged (parentTree); }
|
|
void valueTreeChildOrderChanged (ValueTree& parentTree, int, int) override { treeChildrenChanged (parentTree); }
|
|
|
|
//==============================================================================
|
|
bool mightContainSubItems() override { return item.getNumChildren() > 0; }
|
|
String getUniqueName() const override { jassert (item.getID().isNotEmpty()); return item.getID(); }
|
|
bool canBeSelected() const override { return true; }
|
|
String getTooltip() override { return {}; }
|
|
File getDraggableFile() const override { return getFile(); }
|
|
|
|
var getDragSourceDescription() override
|
|
{
|
|
cancelDelayedSelectionTimer();
|
|
return projectItemDragType;
|
|
}
|
|
|
|
void addSubItems() override
|
|
{
|
|
for (int i = 0; i < item.getNumChildren(); ++i)
|
|
if (auto* p = createSubItem (item.getChild(i)))
|
|
addSubItem (p);
|
|
}
|
|
|
|
void itemOpennessChanged (bool isNowOpen) override
|
|
{
|
|
if (isNowOpen)
|
|
refreshSubItems();
|
|
}
|
|
|
|
//==============================================================================
|
|
bool isInterestedInFileDrag (const StringArray& files) override
|
|
{
|
|
return acceptsFileDrop (files);
|
|
}
|
|
|
|
void filesDropped (const StringArray& files, int insertIndex) override
|
|
{
|
|
if (files.size() == 1 && File (files[0]).hasFileExtension (Project::projectFileExtension))
|
|
ProjucerApplication::getApp().openFile (files[0]);
|
|
else
|
|
addFilesAtIndex (files, insertIndex);
|
|
}
|
|
|
|
bool isInterestedInDragSource (const DragAndDropTarget::SourceDetails& dragSourceDetails) override
|
|
{
|
|
OwnedArray<Project::Item> selectedNodes;
|
|
getSelectedProjectItemsBeingDragged (dragSourceDetails, selectedNodes);
|
|
|
|
return selectedNodes.size() > 0 && acceptsDragItems (selectedNodes);
|
|
}
|
|
|
|
void itemDropped (const DragAndDropTarget::SourceDetails& dragSourceDetails, int insertIndex) override
|
|
{
|
|
OwnedArray<Project::Item> selectedNodes;
|
|
getSelectedProjectItemsBeingDragged (dragSourceDetails, selectedNodes);
|
|
|
|
if (selectedNodes.size() > 0)
|
|
{
|
|
auto* tree = getOwnerView();
|
|
std::unique_ptr<XmlElement> oldOpenness (tree->getOpennessState (false));
|
|
|
|
moveSelectedItemsTo (selectedNodes, insertIndex);
|
|
|
|
if (oldOpenness != nullptr)
|
|
tree->restoreOpennessState (*oldOpenness, false);
|
|
}
|
|
}
|
|
|
|
int getMillisecsAllowedForDragGesture() override
|
|
{
|
|
// for images, give the user longer to start dragging before assuming they're
|
|
// clicking to select it for previewing..
|
|
return item.isImageFile() ? 250 : JucerTreeViewBase::getMillisecsAllowedForDragGesture();
|
|
}
|
|
|
|
static void getSelectedProjectItemsBeingDragged (const DragAndDropTarget::SourceDetails& dragSourceDetails,
|
|
OwnedArray<Project::Item>& selectedNodes)
|
|
{
|
|
if (dragSourceDetails.description == projectItemDragType)
|
|
{
|
|
auto* tree = dynamic_cast<TreeView*> (dragSourceDetails.sourceComponent.get());
|
|
|
|
if (tree == nullptr)
|
|
tree = dragSourceDetails.sourceComponent->findParentComponentOfClass<TreeView>();
|
|
|
|
if (tree != nullptr)
|
|
{
|
|
auto numSelected = tree->getNumSelectedItems();
|
|
|
|
for (int i = 0; i < numSelected; ++i)
|
|
if (auto* p = dynamic_cast<FileTreeItemBase*> (tree->getSelectedItem (i)))
|
|
selectedNodes.add (new Project::Item (p->item));
|
|
}
|
|
}
|
|
}
|
|
|
|
FileTreeItemBase* getParentProjectItem() const
|
|
{
|
|
return dynamic_cast<FileTreeItemBase*> (getParentItem());
|
|
}
|
|
|
|
//==============================================================================
|
|
Project::Item item;
|
|
|
|
protected:
|
|
bool isFileMissing;
|
|
|
|
virtual FileTreeItemBase* createSubItem (const Project::Item& node) = 0;
|
|
|
|
Icon getIcon() const override
|
|
{
|
|
auto colour = getOwnerView()->findColour (isSelected() ? defaultHighlightedTextColourId
|
|
: treeIconColourId);
|
|
|
|
return item.getIcon (isOpen()).withColour (colour);
|
|
}
|
|
|
|
bool isIconCrossedOut() const override { return item.isIconCrossedOut(); }
|
|
|
|
void treeChildrenChanged (const ValueTree& parentTree)
|
|
{
|
|
if (parentTree == item.state)
|
|
{
|
|
refreshSubItems();
|
|
treeHasChanged();
|
|
setOpen (true);
|
|
}
|
|
}
|
|
|
|
void triggerAsyncRename (const Project::Item& itemToRename)
|
|
{
|
|
struct RenameMessage : public CallbackMessage
|
|
{
|
|
RenameMessage (TreeView* const t, const Project::Item& i)
|
|
: tree (t), itemToRename (i) {}
|
|
|
|
void messageCallback() override
|
|
{
|
|
if (tree != nullptr)
|
|
if (auto* root = dynamic_cast<FileTreeItemBase*> (tree->getRootItem()))
|
|
if (auto* found = root->findTreeViewItem (itemToRename))
|
|
found->showRenameBox();
|
|
}
|
|
|
|
private:
|
|
Component::SafePointer<TreeView> tree;
|
|
Project::Item itemToRename;
|
|
};
|
|
|
|
(new RenameMessage (getOwnerView(), itemToRename))->post();
|
|
}
|
|
|
|
static void moveItems (OwnedArray<Project::Item>& selectedNodes, Project::Item destNode, int insertIndex)
|
|
{
|
|
for (auto i = selectedNodes.size(); --i >= 0;)
|
|
{
|
|
auto* n = selectedNodes.getUnchecked(i);
|
|
|
|
if (destNode == *n || destNode.state.isAChildOf (n->state)) // Check for recursion.
|
|
return;
|
|
|
|
if (! destNode.canContain (*n))
|
|
selectedNodes.remove (i);
|
|
}
|
|
|
|
// Don't include any nodes that are children of other selected nodes..
|
|
for (auto i = selectedNodes.size(); --i >= 0;)
|
|
{
|
|
auto* n = selectedNodes.getUnchecked(i);
|
|
|
|
for (auto j = selectedNodes.size(); --j >= 0;)
|
|
{
|
|
if (j != i && n->state.isAChildOf (selectedNodes.getUnchecked(j)->state))
|
|
{
|
|
selectedNodes.remove (i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove and re-insert them one at a time..
|
|
for (int i = 0; i < selectedNodes.size(); ++i)
|
|
{
|
|
auto* selectedNode = selectedNodes.getUnchecked(i);
|
|
|
|
if (selectedNode->state.getParent() == destNode.state
|
|
&& indexOfNode (destNode.state, selectedNode->state) < insertIndex)
|
|
--insertIndex;
|
|
|
|
selectedNode->removeItemFromProject();
|
|
destNode.addChild (*selectedNode, insertIndex++);
|
|
}
|
|
}
|
|
|
|
static int indexOfNode (const ValueTree& parent, const ValueTree& child)
|
|
{
|
|
for (auto i = parent.getNumChildren(); --i >= 0;)
|
|
if (parent.getChild (i) == child)
|
|
return i;
|
|
|
|
return -1;
|
|
}
|
|
};
|
|
|
|
//==============================================================================
|
|
class SourceFileItem : public FileTreeItemBase
|
|
{
|
|
public:
|
|
SourceFileItem (const Project::Item& projectItem)
|
|
: FileTreeItemBase (projectItem)
|
|
{
|
|
}
|
|
|
|
bool acceptsFileDrop (const StringArray&) const override { return false; }
|
|
bool acceptsDragItems (const OwnedArray <Project::Item>&) override { return false; }
|
|
|
|
String getDisplayName() const override
|
|
{
|
|
return getFile().getFileName();
|
|
}
|
|
|
|
void paintItem (Graphics& g, int width, int height) override
|
|
{
|
|
JucerTreeViewBase::paintItem (g, width, height);
|
|
|
|
if (item.needsSaving())
|
|
{
|
|
auto bounds = g.getClipBounds().withY (0).withHeight (height);
|
|
|
|
g.setFont (getFont());
|
|
g.setColour (getContentColour (false));
|
|
|
|
g.drawFittedText ("*", bounds.removeFromLeft (height), Justification::centred, 1);
|
|
}
|
|
}
|
|
|
|
static File findCorrespondingHeaderOrCpp (const File& f)
|
|
{
|
|
if (f.hasFileExtension (sourceFileExtensions)) return f.withFileExtension (".h");
|
|
if (f.hasFileExtension (headerFileExtensions)) return f.withFileExtension (".cpp");
|
|
|
|
return {};
|
|
}
|
|
|
|
void setName (const String& newName) override
|
|
{
|
|
if (newName != File::createLegalFileName (newName))
|
|
{
|
|
AlertWindow::showMessageBox (AlertWindow::WarningIcon, "File Rename",
|
|
"That filename contained some illegal characters!");
|
|
triggerAsyncRename (item);
|
|
return;
|
|
}
|
|
|
|
auto oldFile = getFile();
|
|
auto newFile = oldFile.getSiblingFile (newName);
|
|
auto correspondingFile = findCorrespondingHeaderOrCpp (oldFile);
|
|
|
|
if (correspondingFile.exists() && newFile.hasFileExtension (oldFile.getFileExtension()))
|
|
{
|
|
auto correspondingItem = item.project.getMainGroup().findItemForFile (correspondingFile);
|
|
|
|
if (correspondingItem.isValid())
|
|
{
|
|
if (AlertWindow::showOkCancelBox (AlertWindow::NoIcon, "File Rename",
|
|
"Do you also want to rename the corresponding file \"" + correspondingFile.getFileName()
|
|
+ "\" to match?"))
|
|
{
|
|
if (! item.renameFile (newFile))
|
|
{
|
|
AlertWindow::showMessageBox (AlertWindow::WarningIcon, "File Rename",
|
|
"Failed to rename \"" + oldFile.getFullPathName() + "\"!\n\nCheck your file permissions!");
|
|
return;
|
|
}
|
|
|
|
if (! correspondingItem.renameFile (newFile.withFileExtension (correspondingFile.getFileExtension())))
|
|
{
|
|
AlertWindow::showMessageBox (AlertWindow::WarningIcon, "File Rename",
|
|
"Failed to rename \"" + correspondingFile.getFullPathName() + "\"!\n\nCheck your file permissions!");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (! item.renameFile (newFile))
|
|
{
|
|
AlertWindow::showMessageBox (AlertWindow::WarningIcon, "File Rename",
|
|
"Failed to rename the file!\n\nCheck your file permissions!");
|
|
}
|
|
}
|
|
|
|
FileTreeItemBase* createSubItem (const Project::Item&) override
|
|
{
|
|
jassertfalse;
|
|
return nullptr;
|
|
}
|
|
|
|
void showDocument() override
|
|
{
|
|
auto f = getFile();
|
|
|
|
if (f.exists())
|
|
if (auto* pcc = getProjectContentComponent())
|
|
pcc->showEditorForFile (f, false);
|
|
}
|
|
|
|
void showPopupMenu() override
|
|
{
|
|
PopupMenu m;
|
|
|
|
m.addItem (1, "Open in external editor");
|
|
m.addItem (2,
|
|
#if JUCE_MAC
|
|
"Reveal in Finder");
|
|
#else
|
|
"Reveal in Explorer");
|
|
#endif
|
|
|
|
m.addItem (4, "Rename File...");
|
|
m.addSeparator();
|
|
|
|
if (auto* group = dynamic_cast<GroupItem*> (getParentItem()))
|
|
{
|
|
if (group->isRoot())
|
|
{
|
|
m.addItem (5, "Binary Resource", true, item.shouldBeAddedToBinaryResources());
|
|
m.addItem (6, "Xcode Resource", true, item.shouldBeAddedToXcodeResources());
|
|
m.addItem (7, "Compile", true, item.shouldBeCompiled());
|
|
m.addSeparator();
|
|
}
|
|
}
|
|
|
|
m.addItem (3, "Delete");
|
|
|
|
launchPopupMenu (m);
|
|
}
|
|
|
|
void showAddMenu() override
|
|
{
|
|
if (auto* group = dynamic_cast<GroupItem*> (getParentItem()))
|
|
group->showAddMenu();
|
|
}
|
|
|
|
void handlePopupMenuResult (int resultCode) override
|
|
{
|
|
switch (resultCode)
|
|
{
|
|
case 1: getFile().startAsProcess(); break;
|
|
case 2: revealInFinder(); break;
|
|
case 3: deleteAllSelectedItems(); break;
|
|
case 4: triggerAsyncRename (item); break;
|
|
case 5: item.getShouldAddToBinaryResourcesValue().setValue (! item.shouldBeAddedToBinaryResources()); break;
|
|
case 6: item.getShouldAddToXcodeResourcesValue().setValue (! item.shouldBeAddedToXcodeResources()); break;
|
|
case 7: item.getShouldCompileValue().setValue (! item.shouldBeCompiled()); break;
|
|
|
|
default:
|
|
if (auto* parentGroup = dynamic_cast<GroupItem*> (getParentProjectItem()))
|
|
parentGroup->processCreateFileMenuItem (resultCode);
|
|
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
|
|
//==============================================================================
|
|
class GroupItem : public FileTreeItemBase
|
|
{
|
|
public:
|
|
GroupItem (const Project::Item& projectItem, const String& filter = {})
|
|
: FileTreeItemBase (projectItem),
|
|
searchFilter (filter)
|
|
{
|
|
}
|
|
|
|
bool isRoot() const override { return item.isMainGroup(); }
|
|
bool acceptsFileDrop (const StringArray&) const override { return true; }
|
|
|
|
void addNewGroup()
|
|
{
|
|
auto newGroup = item.addNewSubGroup ("New Group", 0);
|
|
triggerAsyncRename (newGroup);
|
|
}
|
|
|
|
bool acceptsDragItems (const OwnedArray<Project::Item>& selectedNodes) override
|
|
{
|
|
for (auto i = selectedNodes.size(); --i >= 0;)
|
|
if (item.canContain (*selectedNodes.getUnchecked(i)))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
void addFilesAtIndex (const StringArray& files, int insertIndex) override
|
|
{
|
|
for (auto f : files)
|
|
{
|
|
if (item.addFileAtIndex (f, insertIndex, true))
|
|
++insertIndex;
|
|
}
|
|
}
|
|
|
|
void addFilesRetainingSortOrder (const StringArray& files) override
|
|
{
|
|
for (auto i = files.size(); --i >= 0;)
|
|
item.addFileRetainingSortOrder (files[i], true);
|
|
}
|
|
|
|
void moveSelectedItemsTo (OwnedArray<Project::Item>& selectedNodes, int insertIndex) override
|
|
{
|
|
moveItems (selectedNodes, item, insertIndex);
|
|
}
|
|
|
|
void checkFileStatus() override
|
|
{
|
|
for (int i = 0; i < getNumSubItems(); ++i)
|
|
if (auto* p = dynamic_cast<FileTreeItemBase*> (getSubItem(i)))
|
|
p->checkFileStatus();
|
|
}
|
|
|
|
bool isGroupEmpty (const Project::Item& group) // recursive
|
|
{
|
|
for (int i = 0; i < group.getNumChildren(); ++i)
|
|
{
|
|
auto child = group.getChild (i);
|
|
|
|
if ((child.isGroup() && ! isGroupEmpty (child))
|
|
|| (child.isFile() && child.getName().containsIgnoreCase (searchFilter)))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
FileTreeItemBase* createSubItem (const Project::Item& child) override
|
|
{
|
|
if (child.isGroup())
|
|
{
|
|
if (searchFilter.isNotEmpty() && isGroupEmpty (child))
|
|
return nullptr;
|
|
|
|
return new GroupItem (child, searchFilter);
|
|
}
|
|
|
|
if (child.isFile())
|
|
{
|
|
if (child.getName().containsIgnoreCase (searchFilter))
|
|
return new SourceFileItem (child);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
jassertfalse;
|
|
return nullptr;
|
|
}
|
|
|
|
void showDocument() override
|
|
{
|
|
if (auto* pcc = getProjectContentComponent())
|
|
pcc->setEditorComponent (new FileGroupInformationComponent (item), nullptr);
|
|
}
|
|
|
|
static void openAllGroups (TreeViewItem* root)
|
|
{
|
|
for (int i = 0; i < root->getNumSubItems(); ++i)
|
|
if (auto* sub = root->getSubItem (i))
|
|
openOrCloseAllSubGroups (*sub, true);
|
|
}
|
|
|
|
static void closeAllGroups (TreeViewItem* root)
|
|
{
|
|
for (int i = 0; i < root->getNumSubItems(); ++i)
|
|
if (auto* sub = root->getSubItem (i))
|
|
openOrCloseAllSubGroups (*sub, false);
|
|
}
|
|
|
|
static void openOrCloseAllSubGroups (TreeViewItem& item, bool shouldOpen)
|
|
{
|
|
item.setOpen (shouldOpen);
|
|
|
|
for (auto i = item.getNumSubItems(); --i >= 0;)
|
|
if (auto* sub = item.getSubItem (i))
|
|
openOrCloseAllSubGroups (*sub, shouldOpen);
|
|
}
|
|
|
|
static void setFilesToCompile (Project::Item item, const bool shouldCompile)
|
|
{
|
|
if (item.isFile() && (item.getFile().hasFileExtension (fileTypesToCompileByDefault)))
|
|
item.getShouldCompileValue() = shouldCompile;
|
|
|
|
for (auto i = item.getNumChildren(); --i >= 0;)
|
|
setFilesToCompile (item.getChild (i), shouldCompile);
|
|
}
|
|
|
|
void showPopupMenu() override
|
|
{
|
|
PopupMenu m;
|
|
addCreateFileMenuItems (m);
|
|
|
|
m.addSeparator();
|
|
|
|
m.addItem (1, "Collapse all Groups");
|
|
m.addItem (2, "Expand all Groups");
|
|
|
|
if (! isRoot())
|
|
{
|
|
if (isOpen())
|
|
m.addItem (3, "Collapse all Sub-groups");
|
|
else
|
|
m.addItem (4, "Expand all Sub-groups");
|
|
}
|
|
|
|
m.addSeparator();
|
|
m.addItem (5, "Enable compiling of all enclosed files");
|
|
m.addItem (6, "Disable compiling of all enclosed files");
|
|
|
|
m.addSeparator();
|
|
m.addItem (7, "Sort Items Alphabetically");
|
|
m.addItem (8, "Sort Items Alphabetically (Groups first)");
|
|
m.addSeparator();
|
|
|
|
if (! isRoot())
|
|
{
|
|
m.addItem (9, "Rename...");
|
|
m.addItem (10, "Delete");
|
|
}
|
|
|
|
launchPopupMenu (m);
|
|
}
|
|
|
|
void showAddMenu() override
|
|
{
|
|
PopupMenu m;
|
|
addCreateFileMenuItems (m);
|
|
|
|
launchPopupMenu (m);
|
|
}
|
|
|
|
void handlePopupMenuResult (int resultCode) override
|
|
{
|
|
switch (resultCode)
|
|
{
|
|
case 1: closeAllGroups (getOwnerView()->getRootItem()); break;
|
|
case 2: openAllGroups (getOwnerView()->getRootItem()); break;
|
|
case 3: openOrCloseAllSubGroups (*this, false); break;
|
|
case 4: openOrCloseAllSubGroups (*this, true); break;
|
|
case 5: setFilesToCompile (item, true); break;
|
|
case 6: setFilesToCompile (item, false); break;
|
|
case 7: item.sortAlphabetically (false, false); break;
|
|
case 8: item.sortAlphabetically (true, false); break;
|
|
case 9: triggerAsyncRename (item); break;
|
|
case 10: deleteAllSelectedItems(); break;
|
|
default: processCreateFileMenuItem (resultCode); break;
|
|
}
|
|
}
|
|
|
|
void addCreateFileMenuItems (PopupMenu& m)
|
|
{
|
|
m.addItem (1001, "Add New Group");
|
|
m.addItem (1002, "Add Existing Files...");
|
|
|
|
m.addSeparator();
|
|
NewFileWizard().addWizardsToMenu (m);
|
|
}
|
|
|
|
void processCreateFileMenuItem (int menuID)
|
|
{
|
|
switch (menuID)
|
|
{
|
|
case 1001: addNewGroup(); break;
|
|
case 1002: browseToAddExistingFiles(); break;
|
|
|
|
default:
|
|
jassert (getProject() != nullptr);
|
|
NewFileWizard().runWizardFromMenu (menuID, *getProject(), item);
|
|
break;
|
|
}
|
|
}
|
|
|
|
Project* getProject()
|
|
{
|
|
if (auto* tv = getOwnerView())
|
|
if (auto* pcc = tv->findParentComponentOfClass<ProjectContentComponent>())
|
|
return pcc->getProject();
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void setSearchFilter (const String& filter) override
|
|
{
|
|
searchFilter = filter;
|
|
refreshSubItems();
|
|
}
|
|
|
|
String searchFilter;
|
|
};
|