From 79bf147d8977edea631efb83f15b236b94a27197 Mon Sep 17 00:00:00 2001 From: jules Date: Sat, 1 Oct 2011 18:26:53 +0100 Subject: [PATCH] Couple of minor additions to file browser comps. --- .../juce_DirectoryContentsDisplayComponent.h | 4 + .../juce_DirectoryContentsList.cpp | 11 + .../filebrowser/juce_DirectoryContentsList.h | 3 + .../filebrowser/juce_FileBrowserComponent.cpp | 12 + .../filebrowser/juce_FileBrowserComponent.h | 11 +- .../filebrowser/juce_FileListComponent.cpp | 14 + .../filebrowser/juce_FileListComponent.h | 4 + .../filebrowser/juce_FileTreeComponent.cpp | 16 ++ .../filebrowser/juce_FileTreeComponent.h | 4 + .../juce_gui_basics/widgets/juce_TreeView.cpp | 252 +++++++++--------- .../juce_gui_basics/widgets/juce_TreeView.h | 11 +- 11 files changed, 208 insertions(+), 134 deletions(-) diff --git a/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsDisplayComponent.h b/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsDisplayComponent.h index 0f231b7c80..5d0cf4a173 100644 --- a/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsDisplayComponent.h +++ b/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsDisplayComponent.h @@ -65,6 +65,10 @@ public: /** Scrolls this view to the top. */ virtual void scrollToTop() = 0; + /** If the specified file is in the list, it will become the only selected item + (and if the file isn't in the list, all other items will be deselected). */ + virtual void setSelectedFile (const File&) = 0; + //============================================================================== /** Adds a listener to be told when files are selected or clicked. @see removeListener diff --git a/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsList.cpp b/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsList.cpp index 9dff89ff81..282dcccfce 100644 --- a/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsList.cpp +++ b/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsList.cpp @@ -150,6 +150,17 @@ File DirectoryContentsList::getFile (const int index) const return File::nonexistent; } +bool DirectoryContentsList::contains (const File& targetFile) const +{ + const ScopedLock sl (fileListLock); + + for (int i = files.size(); --i >= 0;) + if (root.getChildFile (files.getUnchecked(i)->filename) == targetFile) + return true; + + return false; +} + bool DirectoryContentsList::isStillLoading() const { return fileFindHandle != nullptr; diff --git a/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsList.h b/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsList.h index e861f0475c..48367be540 100644 --- a/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsList.h +++ b/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsList.h @@ -179,6 +179,9 @@ public: */ const FileFilter* getFilter() const { return fileFilter; } + /** Returns true if the list contains the specified file. */ + bool contains (const File&) const; + //============================================================================== /** @internal */ int useTimeSlice(); diff --git a/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp b/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp index 4f2508e9f1..e71e483823 100644 --- a/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp +++ b/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.cpp @@ -257,6 +257,13 @@ void FileBrowserComponent::setRoot (const File& newRootDirectory) } } +void FileBrowserComponent::setFileName (const String& newName) +{ + filenameBox.setText (newName, true); + + fileListComponent->setSelectedFile (currentRoot.getChildFile (newName)); +} + void FileBrowserComponent::resetRecentPaths() { currentPathBox.clear(); @@ -299,6 +306,11 @@ String FileBrowserComponent::getActionVerb() const return isSaveMode() ? TRANS("Save") : TRANS("Open"); } +void FileBrowserComponent::setFilenameBoxLabel (const String& name) +{ + fileLabel.setText (name, false); +} + FilePreviewComponent* FileBrowserComponent::getPreviewComponent() const noexcept { return previewComp; diff --git a/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.h b/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.h index 590da9a358..0478b37cb1 100644 --- a/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.h +++ b/modules/juce_gui_basics/filebrowser/juce_FileBrowserComponent.h @@ -136,6 +136,9 @@ public: /** Changes the directory that's being shown in the listbox. */ void setRoot (const File& newRootDirectory); + /** Changes the name that is currently shown in the filename box. */ + void setFileName (const String& newName); + /** Equivalent to pressing the "up" button to browse the parent directory. */ void goUp(); @@ -156,15 +159,19 @@ public: */ bool isSaveMode() const noexcept; + /** Sets the label that will be displayed next to the filename entry box. + By default this is just "file", but you might want to change it to something more + appropriate for your app. + */ + void setFilenameBoxLabel (const String& name); + //============================================================================== /** Adds a listener to be told when the user selects and clicks on files. - @see removeListener */ void addListener (FileBrowserListener* listener); /** Removes a listener. - @see addListener */ void removeListener (FileBrowserListener* listener); diff --git a/modules/juce_gui_basics/filebrowser/juce_FileListComponent.cpp b/modules/juce_gui_basics/filebrowser/juce_FileListComponent.cpp index d4544e831d..482d7a9dbc 100644 --- a/modules/juce_gui_basics/filebrowser/juce_FileListComponent.cpp +++ b/modules/juce_gui_basics/filebrowser/juce_FileListComponent.cpp @@ -62,6 +62,20 @@ void FileListComponent::scrollToTop() getVerticalScrollBar()->setCurrentRangeStart (0); } +void FileListComponent::setSelectedFile (const File& f) +{ + for (int i = fileList.getNumFiles(); --i >= 0;) + { + if (fileList.getFile(i) == f) + { + selectRow (i); + return; + } + } + + deselectAllRows(); +} + //============================================================================== void FileListComponent::changeListenerCallback (ChangeBroadcaster*) { diff --git a/modules/juce_gui_basics/filebrowser/juce_FileListComponent.h b/modules/juce_gui_basics/filebrowser/juce_FileListComponent.h index 54256530cc..9d58b7ccf1 100644 --- a/modules/juce_gui_basics/filebrowser/juce_FileListComponent.h +++ b/modules/juce_gui_basics/filebrowser/juce_FileListComponent.h @@ -76,6 +76,10 @@ public: /** Scrolls to the top of the list. */ void scrollToTop(); + /** If the specified file is in the list, it will become the only selected item + (and if the file isn't in the list, all other items will be deselected). */ + void setSelectedFile (const File&); + //============================================================================== /** @internal */ void changeListenerCallback (ChangeBroadcaster*); diff --git a/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.cpp b/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.cpp index b8c0b2cf01..2ae3ba3332 100644 --- a/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.cpp +++ b/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.cpp @@ -247,4 +247,20 @@ void FileTreeComponent::setDragAndDropDescription (const String& description) dragAndDropDescription = description; } +void FileTreeComponent::setSelectedFile (const File& target) +{ + for (int i = getNumSelectedItems(); --i >= 0;) + { + FileListTreeItem* t = dynamic_cast (getSelectedItem (i)); + + if (t != nullptr && t->file == target) + { + t->setSelected (true, true); + return; + } + } + + clearSelectedItems(); +} + END_JUCE_NAMESPACE diff --git a/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.h b/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.h index 6c256e7030..534bd50500 100644 --- a/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.h +++ b/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.h @@ -72,6 +72,10 @@ public: /** Scrolls the list to the top. */ void scrollToTop(); + /** If the specified file is in the list, it will become the only selected item + (and if the file isn't in the list, all other items will be deselected). */ + void setSelectedFile (const File&); + /** Setting a name for this allows tree items to be dragged. The string that you pass in here will be returned by the getDragSourceDescription() diff --git a/modules/juce_gui_basics/widgets/juce_TreeView.cpp b/modules/juce_gui_basics/widgets/juce_TreeView.cpp index 7541c00149..5c31c139d2 100644 --- a/modules/juce_gui_basics/widgets/juce_TreeView.cpp +++ b/modules/juce_gui_basics/widgets/juce_TreeView.cpp @@ -27,7 +27,8 @@ BEGIN_JUCE_NAMESPACE //============================================================================== class TreeViewContentComponent : public Component, - public TooltipClient + public TooltipClient, + public AsyncUpdater { public: TreeViewContentComponent (TreeView& owner_) @@ -47,30 +48,30 @@ public: Rectangle pos; TreeViewItem* const item = findItemAt (e.y, pos); - if (item == nullptr) - return; - - // (if the open/close buttons are hidden, we'll treat clicks to the left of the item - // as selection clicks) - if (e.x < pos.getX() && owner.openCloseButtonsVisible) + if (item != nullptr) { - if (e.x >= pos.getX() - owner.getIndentSize()) - item->setOpen (! item->isOpen()); + // (if the open/close buttons are hidden, we'll treat clicks to the left of the item + // as selection clicks) + if (e.x < pos.getX() && owner.openCloseButtonsVisible) + { + if (e.x >= pos.getX() - owner.getIndentSize()) + item->setOpen (! item->isOpen()); - // (clicks to the left of an open/close button are ignored) - } - else - { - // mouse-down inside the body of the item.. - if (! owner.isMultiSelectEnabled()) - item->setSelected (true, true); - else if (item->isSelected()) - needSelectionOnMouseUp = ! e.mods.isPopupMenu(); + // (clicks to the left of an open/close button are ignored) + } else - selectBasedOnModifiers (item, e.mods); + { + // mouse-down inside the body of the item.. + if (! owner.isMultiSelectEnabled()) + item->setSelected (true, true); + else if (item->isSelected()) + needSelectionOnMouseUp = ! e.mods.isPopupMenu(); + else + selectBasedOnModifiers (item, e.mods); - if (e.x >= pos.getX()) - item->itemClicked (e.withNewPosition (e.getPosition() - pos.getPosition())); + if (e.x >= pos.getX()) + item->itemClicked (e.withNewPosition (e.getPosition() - pos.getPosition())); + } } } @@ -141,21 +142,14 @@ public: } } - void mouseMove (const MouseEvent& e) - { - updateButtonUnderMouse (e); - } - - void mouseExit (const MouseEvent& e) - { - updateButtonUnderMouse (e); - } + void mouseMove (const MouseEvent& e) { updateButtonUnderMouse (e); } + void mouseExit (const MouseEvent& e) { updateButtonUnderMouse (e); } void paint (Graphics& g) { if (owner.rootItem != nullptr) { - owner.handleAsyncUpdate(); + owner.recalculateIfNeeded(); if (! owner.rootItemVisible) g.setOrigin (0, -owner.rootItem->itemHeight); @@ -166,22 +160,20 @@ public: TreeViewItem* findItemAt (int y, Rectangle& itemPosition) const { - if (owner.rootItem != nullptr) - { - owner.handleAsyncUpdate(); + if (owner.rootItem == nullptr) + return nullptr; - if (! owner.rootItemVisible) - y += owner.rootItem->itemHeight; + owner.recalculateIfNeeded(); - TreeViewItem* const ti = owner.rootItem->findItemRecursively (y); + if (! owner.rootItemVisible) + y += owner.rootItem->itemHeight; - if (ti != nullptr) - itemPosition = ti->getItemPosition (false); + TreeViewItem* const ti = owner.rootItem->findItemRecursively (y); - return ti; - } + if (ti != nullptr) + itemPosition = ti->getItemPosition (false); - return nullptr; + return ti; } void updateComponents() @@ -257,42 +249,6 @@ public: } } - void updateButtonUnderMouse (const MouseEvent& e) - { - TreeViewItem* newItem = nullptr; - - if (owner.openCloseButtonsVisible) - { - Rectangle pos; - TreeViewItem* item = findItemAt (e.y, pos); - - if (item != nullptr && e.x < pos.getX() && e.x >= pos.getX() - owner.getIndentSize()) - { - newItem = item; - - if (! newItem->mightContainSubItems()) - newItem = nullptr; - } - } - - if (buttonUnderMouse != newItem) - { - if (buttonUnderMouse != nullptr && containsItem (buttonUnderMouse)) - { - const Rectangle r (buttonUnderMouse->getItemPosition (false)); - repaint (0, r.getY(), r.getX(), buttonUnderMouse->getItemHeight()); - } - - buttonUnderMouse = newItem; - - if (buttonUnderMouse != nullptr) - { - const Rectangle r (buttonUnderMouse->getItemPosition (false)); - repaint (0, r.getY(), r.getX(), buttonUnderMouse->getItemHeight()); - } - } - } - bool isMouseOverButton (TreeViewItem* const item) const noexcept { return item == buttonUnderMouse; @@ -392,6 +348,41 @@ private: return nullptr; } + void updateButtonUnderMouse (const MouseEvent& e) + { + TreeViewItem* newItem = nullptr; + + if (owner.openCloseButtonsVisible) + { + Rectangle pos; + TreeViewItem* item = findItemAt (e.y, pos); + + if (item != nullptr && e.x < pos.getX() && e.x >= pos.getX() - owner.getIndentSize()) + { + newItem = item; + + if (! newItem->mightContainSubItems()) + newItem = nullptr; + } + } + + if (buttonUnderMouse != newItem) + { + repaintButtonUnderMouse(); + buttonUnderMouse = newItem; + repaintButtonUnderMouse(); + } + } + + void repaintButtonUnderMouse() + { + if (buttonUnderMouse != nullptr && containsItem (buttonUnderMouse)) + { + const Rectangle r (buttonUnderMouse->getItemPosition (false)); + repaint (0, r.getY(), r.getX(), buttonUnderMouse->getItemHeight()); + } + } + static bool isMouseDraggingInChildCompOf (Component* const comp) { for (int i = Desktop::getInstance().getNumMouseSources(); --i >= 0;) @@ -410,6 +401,11 @@ private: return false; } + void handleAsyncUpdate() + { + owner.recalculateIfNeeded(); + } + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TreeViewContentComponent); }; @@ -421,7 +417,8 @@ public: void updateComponents (const bool triggerResize = false) { - TreeViewContentComponent* const tvc = static_cast (getViewedComponent()); + TreeViewContentComponent* const tvc = getContentComp(); + if (tvc != nullptr) { if (triggerResize) @@ -440,8 +437,12 @@ public: updateComponents (hasScrolledSideways); } + TreeViewContentComponent* getContentComp() const noexcept + { + return static_cast (getViewedComponent()); + } + private: - //============================================================================== int lastX; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TreeViewport); @@ -451,6 +452,7 @@ private: //============================================================================== TreeView::TreeView (const String& name) : Component (name), + viewport (new TreeViewport()), rootItem (nullptr), indentSize (24), defaultOpenness (false), @@ -459,7 +461,7 @@ TreeView::TreeView (const String& name) multiSelectEnabled (false), openCloseButtonsVisible (true) { - addAndMakeVisible (viewport = new TreeViewport()); + addAndMakeVisible (viewport); viewport->setViewedComponent (new TreeViewContentComponent (*this)); viewport->setWantsKeyboardFocus (false); setWantsKeyboardFocus (true); @@ -492,7 +494,7 @@ void TreeView::setRootItem (TreeViewItem* const newRootItem) newRootItem->setOwnerView (this); needsRecalculating = true; - handleAsyncUpdate(); + recalculateIfNeeded(); if (rootItem != nullptr && (defaultOpenness || ! rootItemVisible)) { @@ -602,7 +604,7 @@ TreeViewItem* TreeView::getItemOnRow (int index) const TreeViewItem* TreeView::getItemAt (int y) const noexcept { - TreeViewContentComponent* const tc = static_cast (viewport->getViewedComponent()); + TreeViewContentComponent* const tc = viewport->getContentComp(); Rectangle pos; return tc->findItemAt (tc->getLocalPoint (this, Point (0, y)).getY(), pos); } @@ -683,7 +685,7 @@ void TreeView::resized() viewport->setBounds (getLocalBounds()); itemsChanged(); - handleAsyncUpdate(); + recalculateIfNeeded(); } void TreeView::enablementChanged() @@ -691,11 +693,8 @@ void TreeView::enablementChanged() repaint(); } -void TreeView::moveSelectedRow (int delta) +void TreeView::moveSelectedRow (const int delta) { - if (delta == 0) - return; - int rowSelected = 0; TreeViewItem* const firstSelected = getSelectedItem (0); @@ -741,7 +740,7 @@ void TreeView::scrollToKeepItemVisible (TreeViewItem* item) { if (item != nullptr && item->ownerView == this) { - handleAsyncUpdate(); + recalculateIfNeeded(); item = item->getDeepestOpenParentItem(); @@ -779,7 +778,8 @@ bool TreeView::keyPressed (const KeyPress& key) if (key.isKeyCode (KeyPress::pageUpKey)) rowsOnScreen = -rowsOnScreen; - moveSelectedRow (rowsOnScreen); + if (rowsOnScreen != 0) + moveSelectedRow (rowsOnScreen); } } else if (key.isKeyCode (KeyPress::homeKey)) @@ -845,10 +845,10 @@ void TreeView::itemsChanged() noexcept { needsRecalculating = true; repaint(); - triggerAsyncUpdate(); + viewport->getContentComp()->triggerAsyncUpdate(); } -void TreeView::handleAsyncUpdate() +void TreeView::recalculateIfNeeded() { if (needsRecalculating) { @@ -1339,7 +1339,7 @@ void TreeViewItem::itemDropped (const DragAndDropTarget::SourceDetails& /*dragSo { } -const Rectangle TreeViewItem::getItemPosition (const bool relativeToTreeViewTopLeft) const noexcept +Rectangle TreeViewItem::getItemPosition (const bool relativeToTreeViewTopLeft) const noexcept { const int indentX = getIndentX(); int width = itemWidth; @@ -1449,6 +1449,20 @@ void TreeViewItem::setDrawsInLeftMargin (bool canDrawInLeftMargin) noexcept drawsInLeftMargin = canDrawInLeftMargin; } +namespace TreeViewHelpers +{ + int calculateDepth (const TreeViewItem* item, const bool rootIsVisible) noexcept + { + jassert (item != nullptr); + int depth = rootIsVisible ? 0 : -1; + + for (const TreeViewItem* p = item->getParentItem(); p != nullptr; p = p->getParentItem()) + ++depth; + + return depth; + } +} + void TreeViewItem::paintRecursively (Graphics& g, int width) { jassert (ownerView != nullptr); @@ -1470,49 +1484,36 @@ void TreeViewItem::paintRecursively (Graphics& g, int width) g.setColour (ownerView->findColour (TreeView::linesColourId)); const float halfH = itemHeight * 0.5f; - int depth = 0; - TreeViewItem* p = parentItem; - - while (p != nullptr) - { - ++depth; - p = p->parentItem; - } - - if (! ownerView->rootItemVisible) - --depth; - const int indentWidth = ownerView->getIndentSize(); + const int depth = TreeViewHelpers::calculateDepth (this, ownerView->rootItemVisible); if (depth >= 0 && ownerView->openCloseButtonsVisible) { float x = (depth + 0.5f) * indentWidth; - if (depth >= 0) - { - if (parentItem != nullptr && parentItem->drawLinesInside) - g.drawLine (x, 0, x, isLastOfSiblings() ? halfH : (float) itemHeight); - + if (parentItem != nullptr && parentItem->drawLinesInside) + g.drawLine (x, 0, x, isLastOfSiblings() ? halfH : (float) itemHeight); - if ((parentItem != nullptr && parentItem->drawLinesInside) - || (parentItem == nullptr && drawLinesInside)) - g.drawLine (x, halfH, x + indentWidth / 2, halfH); - } - - p = parentItem; - int d = depth; + if ((parentItem != nullptr && parentItem->drawLinesInside) + || (parentItem == nullptr && drawLinesInside)) + g.drawLine (x, halfH, x + indentWidth / 2, halfH); - while (p != nullptr && --d >= 0) { - x -= (float) indentWidth; + TreeViewItem* p = parentItem; + int d = depth; - if ((p->parentItem == nullptr || p->parentItem->drawLinesInside) - && ! p->isLastOfSiblings()) + while (p != nullptr && --d >= 0) { - g.drawLine (x, 0, x, (float) itemHeight); - } + x -= (float) indentWidth; - p = p->parentItem; + if ((p->parentItem == nullptr || p->parentItem->drawLinesInside) + && ! p->isLastOfSiblings()) + { + g.drawLine (x, 0, x, (float) itemHeight); + } + + p = p->parentItem; + } } if (mightContainSubItems()) @@ -1523,8 +1524,7 @@ void TreeViewItem::paintRecursively (Graphics& g, int width) g.reduceClipRegion (0, 0, indentWidth, itemHeight); paintOpenCloseButton (g, indentWidth, itemHeight, - static_cast (ownerView->viewport->getViewedComponent()) - ->isMouseOverButton (this)); + ownerView->viewport->getContentComp()->isMouseOverButton (this)); } } diff --git a/modules/juce_gui_basics/widgets/juce_TreeView.h b/modules/juce_gui_basics/widgets/juce_TreeView.h index 624983c192..6172f59c2d 100644 --- a/modules/juce_gui_basics/widgets/juce_TreeView.h +++ b/modules/juce_gui_basics/widgets/juce_TreeView.h @@ -135,7 +135,7 @@ public: the tree. If false, it is relative to the top-left of the topmost item in the tree (so this would be unaffected by scrolling the view). */ - const Rectangle getItemPosition (bool relativeToTreeViewTopLeft) const noexcept; + Rectangle getItemPosition (bool relativeToTreeViewTopLeft) const noexcept; /** Sends a signal to the treeview to make it refresh itself. @@ -545,8 +545,7 @@ private: class JUCE_API TreeView : public Component, public SettableTooltipClient, public FileDragAndDropTarget, - public DragAndDropTarget, - private AsyncUpdater + public DragAndDropTarget { public: //============================================================================== @@ -813,10 +812,10 @@ private: bool openCloseButtonsVisible : 1; void itemsChanged() noexcept; - void handleAsyncUpdate(); + void recalculateIfNeeded(); void moveSelectedRow (int delta); - void updateButtonUnderMouse (const MouseEvent& e); - void showDragHighlight (TreeViewItem* item, int insertIndex, int x, int y) noexcept; + void updateButtonUnderMouse (const MouseEvent&); + void showDragHighlight (TreeViewItem*, int insertIndex, int x, int y) noexcept; void hideDragHighlight() noexcept; void handleDrag (const StringArray& files, const SourceDetails&); void handleDrop (const StringArray& files, const SourceDetails&);