diff --git a/juce_amalgamated.cpp b/juce_amalgamated.cpp index 3670537dec..8e5fac562f 100644 --- a/juce_amalgamated.cpp +++ b/juce_amalgamated.cpp @@ -54764,7 +54764,8 @@ public: Image* dragImage = Component::createComponentSnapshot (pos, true); dragImage->multiplyAllAlphas (0.6f); - dragContainer->startDragging (dragDescription, owner, dragImage, true); + Point imageOffset ((float) (pos.getX() - e.x), (float) (pos.getY() - e.y)); + dragContainer->startDragging (dragDescription, owner, dragImage, true, &imageOffset); } else { @@ -55015,6 +55016,8 @@ private: TreeView::TreeView (const String& componentName) : Component (componentName), rootItem (0), + dragInsertPointHighlight (0), + dragTargetGroupHighlight (0), indentSize (24), defaultOpenness (false), needsRecalculating (true), @@ -55160,13 +55163,22 @@ TreeViewItem* TreeView::getItemOnRow (int index) const return 0; } +TreeViewItem* TreeView::getItemAt (int y) const throw() +{ + TreeViewContentComponent* const tc = (TreeViewContentComponent*) viewport->getViewedComponent(); + int x; + relativePositionToOtherComponent (tc, x, y); + Rectangle pos; + return tc->findItemAt (y, pos); +} + XmlElement* TreeView::getOpennessState (const bool alsoIncludeScrollPosition) const { XmlElement* e = 0; if (rootItem != 0) { - e = rootItem->createXmlOpenness(); + e = rootItem->getOpennessState(); if (e != 0 && alsoIncludeScrollPosition) e->setAttribute (T("scrollPos"), viewport->getViewPositionY()); @@ -55179,7 +55191,7 @@ void TreeView::restoreOpennessState (const XmlElement& newState) { if (rootItem != 0) { - rootItem->restoreFromXml (newState); + rootItem->restoreOpennessState (newState); if (newState.hasAttribute (T("scrollPos"))) viewport->setViewPosition (viewport->getViewPositionX(), @@ -55388,6 +55400,243 @@ void TreeView::handleAsyncUpdate() } } +class TreeViewDragInsertPointHighlight : public Component +{ +public: + TreeViewDragInsertPointHighlight() + : lastItem (0) + { + setSize (100, 12); + setAlwaysOnTop (true); + setInterceptsMouseClicks (false, false); + } + + ~TreeViewDragInsertPointHighlight() {} + + void setTargetPosition (TreeViewItem* const item, int insertIndex, const int x, const int y, const int width) throw() + { + lastItem = item; + lastIndex = insertIndex; + const int offset = getHeight() / 2; + setBounds (x - offset, y - offset, width - (x - offset), getHeight()); + } + + void paint (Graphics& g) + { + Path p; + const float h = (float) getHeight(); + p.addEllipse (2.0f, 2.0f, h - 4.0f, h - 4.0f); + p.startNewSubPath (h - 2.0f, h / 2.0f); + p.lineTo ((float) getWidth(), h / 2.0f); + + g.setColour (findColour (TreeView::dragAndDropIndicatorColourId, true)); + g.strokePath (p, PathStrokeType (2.0f)); + } + + TreeViewItem* lastItem; + int lastIndex; +}; + +class TreeViewDragTargetGroupHighlight : public Component +{ +public: + TreeViewDragTargetGroupHighlight() + { + setAlwaysOnTop (true); + setInterceptsMouseClicks (false, false); + } + + ~TreeViewDragTargetGroupHighlight() {} + + void setTargetPosition (TreeViewItem* const item) throw() + { + Rectangle r (item->getItemPosition (true)); + r.setHeight (item->getItemHeight()); + setBounds (r); + } + + void paint (Graphics& g) + { + g.setColour (findColour (TreeView::dragAndDropIndicatorColourId, true)); + g.drawRoundedRectangle (1.0f, 1.0f, getWidth() - 2.0f, getHeight() - 2.0f, 3.0f, 2.0f); + } +}; + +void TreeView::showDragHighlight (TreeViewItem* item, int insertIndex, int x, int y) throw() +{ + beginDragAutoRepeat (1000 / 30); + + if (dragInsertPointHighlight == 0) + { + addAndMakeVisible (dragInsertPointHighlight = new TreeViewDragInsertPointHighlight()); + addAndMakeVisible (dragTargetGroupHighlight = new TreeViewDragTargetGroupHighlight()); + } + + ((TreeViewDragInsertPointHighlight*) dragInsertPointHighlight) + ->setTargetPosition (item, insertIndex, x, y, viewport->getViewWidth()); + + ((TreeViewDragTargetGroupHighlight*) dragTargetGroupHighlight) + ->setTargetPosition (item); +} + +void TreeView::hideDragHighlight() throw() +{ + deleteAndZero (dragInsertPointHighlight); + deleteAndZero (dragTargetGroupHighlight); +} + +TreeViewItem* TreeView::getInsertPosition (int& x, int& y, int& insertIndex, + const StringArray& files, const String& sourceDescription, + Component* sourceComponent) const throw() +{ + insertIndex = 0; + TreeViewItem* item = getItemAt (y); + + if (item == 0 || item->parentItem == 0) + return 0; + + Rectangle itemPos (item->getItemPosition (true)); + insertIndex = item->getIndexInParent(); + const int oldY = y; + y = itemPos.getY(); + + if (item->getNumSubItems() == 0 || ! item->isOpen()) + { + if (files.size() > 0 ? item->isInterestedInFileDrag (files) + : item->isInterestedInDragSource (sourceDescription, sourceComponent)) + { + // Check if we're trying to drag into an empty group item.. + if (oldY > itemPos.getY() + itemPos.getHeight() / 4 + && oldY < itemPos.getBottom() - itemPos.getHeight() / 4) + { + insertIndex = 0; + x = itemPos.getX() + getIndentSize(); + y = itemPos.getBottom(); + return item; + } + } + } + + if (oldY > itemPos.getCentreY()) + { + y += item->getItemHeight(); + + while (item->isLastOfSiblings() && item->parentItem != 0 + && item->parentItem->parentItem != 0) + { + if (x > itemPos.getX()) + break; + + item = item->parentItem; + itemPos = item->getItemPosition (true); + insertIndex = item->getIndexInParent(); + } + + ++insertIndex; + } + + x = itemPos.getX(); + return item->parentItem; +} + +void TreeView::handleDrag (const StringArray& files, const String& sourceDescription, Component* sourceComponent, int x, int y) +{ + const bool scrolled = viewport->autoScroll (x, y, 20, 10); + + int insertIndex; + TreeViewItem* const item = getInsertPosition (x, y, insertIndex, files, sourceDescription, sourceComponent); + + if (item != 0) + { + if (scrolled || dragInsertPointHighlight == 0 + || ((TreeViewDragInsertPointHighlight*) dragInsertPointHighlight)->lastItem != item + || ((TreeViewDragInsertPointHighlight*) dragInsertPointHighlight)->lastIndex != insertIndex) + { + if (files.size() > 0 ? item->isInterestedInFileDrag (files) + : item->isInterestedInDragSource (sourceDescription, sourceComponent)) + showDragHighlight (item, insertIndex, x, y); + else + hideDragHighlight(); + } + } + else + { + hideDragHighlight(); + } +} + +void TreeView::handleDrop (const StringArray& files, const String& sourceDescription, Component* sourceComponent, int x, int y) +{ + hideDragHighlight(); + + int insertIndex; + TreeViewItem* const item = getInsertPosition (x, y, insertIndex, files, sourceDescription, sourceComponent); + + if (item != 0) + { + if (files.size() > 0) + { + if (item->isInterestedInFileDrag (files)) + item->filesDropped (files, insertIndex); + } + else + { + if (item->isInterestedInDragSource (sourceDescription, sourceComponent)) + item->itemDropped (sourceDescription, sourceComponent, insertIndex); + } + } +} + +bool TreeView::isInterestedInFileDrag (const StringArray& files) +{ + return true; +} + +void TreeView::fileDragEnter (const StringArray& files, int x, int y) +{ + fileDragMove (files, x, y); +} + +void TreeView::fileDragMove (const StringArray& files, int x, int y) +{ + handleDrag (files, String::empty, 0, x, y); +} + +void TreeView::fileDragExit (const StringArray& files) +{ + hideDragHighlight(); +} + +void TreeView::filesDropped (const StringArray& files, int x, int y) +{ + handleDrop (files, String::empty, 0, x, y); +} + +bool TreeView::isInterestedInDragSource (const String& sourceDescription, Component* sourceComponent) +{ + return true; +} + +void TreeView::itemDragEnter (const String& sourceDescription, Component* sourceComponent, int x, int y) +{ + itemDragMove (sourceDescription, sourceComponent, x, y); +} + +void TreeView::itemDragMove (const String& sourceDescription, Component* sourceComponent, int x, int y) +{ + handleDrag (StringArray(), sourceDescription, sourceComponent, x, y); +} + +void TreeView::itemDragExit (const String& sourceDescription, Component* sourceComponent) +{ + hideDragHighlight(); +} + +void TreeView::itemDropped (const String& sourceDescription, Component* sourceComponent, int x, int y) +{ + handleDrop (StringArray(), sourceDescription, sourceComponent, x, y); +} + void TreeViewContentComponent::paint (Graphics& g) { if (owner->rootItem != 0) @@ -55615,6 +55864,24 @@ const String TreeViewItem::getDragSourceDescription() return String::empty; } +bool TreeViewItem::isInterestedInFileDrag (const StringArray& files) +{ + return false; +} + +void TreeViewItem::filesDropped (const StringArray& files, int insertIndex) +{ +} + +bool TreeViewItem::isInterestedInDragSource (const String& sourceDescription, Component* sourceComponent) +{ + return false; +} + +void TreeViewItem::itemDropped (const String& sourceDescription, Component* sourceComponent, int insertIndex) +{ +} + const Rectangle TreeViewItem::getItemPosition (const bool relativeToTreeViewTopLeft) const throw() { const int indentX = getIndentX(); @@ -55840,6 +56107,14 @@ bool TreeViewItem::isLastOfSiblings() const throw() || parentItem->subItems.getLast() == this; } +int TreeViewItem::getIndexInParent() const throw() +{ + if (parentItem == 0) + return 0; + + return parentItem->subItems.indexOf (this); +} + TreeViewItem* TreeViewItem::getTopLevelItem() throw() { return (parentItem == 0) ? this @@ -55904,7 +56179,7 @@ TreeViewItem* TreeViewItem::findItemRecursively (int y) throw() { TreeViewItem* const ti = subItems.getUnchecked(i); - if (ti->totalHeight >= y) + if (y < ti->totalHeight) return ti->findItemRecursively (y); y -= ti->totalHeight; @@ -56003,7 +56278,7 @@ TreeViewItem* TreeViewItem::getNextVisibleItem (const bool recurse) const throw( return 0; } -void TreeViewItem::restoreFromXml (const XmlElement& e) +void TreeViewItem::restoreOpennessState (const XmlElement& e) throw() { if (e.hasTagName (T("CLOSED"))) { @@ -56023,7 +56298,7 @@ void TreeViewItem::restoreFromXml (const XmlElement& e) if (ti->getUniqueName() == id) { - ti->restoreFromXml (*n); + ti->restoreOpennessState (*n); break; } } @@ -56031,38 +56306,35 @@ void TreeViewItem::restoreFromXml (const XmlElement& e) } } -XmlElement* TreeViewItem::createXmlOpenness() const +XmlElement* TreeViewItem::getOpennessState() const throw() { - if (openness != opennessDefault) + const String name (getUniqueName()); + + if (name.isNotEmpty()) { - const String name (getUniqueName()); + XmlElement* e; - if (name.isNotEmpty()) + if (isOpen()) { - XmlElement* e; + e = new XmlElement (T("OPEN")); - if (isOpen()) - { - e = new XmlElement (T("OPEN")); - - for (int i = 0; i < subItems.size(); ++i) - e->addChildElement (subItems.getUnchecked(i)->createXmlOpenness()); - } - else - { - e = new XmlElement (T("CLOSED")); - } - - e->setAttribute (T("id"), name); - - return e; + for (int i = 0; i < subItems.size(); ++i) + e->addChildElement (subItems.getUnchecked(i)->getOpennessState()); } else { - // trying to save the openness for an element that has no name - this won't - // work because it needs the names to identify what to open. - jassertfalse + e = new XmlElement (T("CLOSED")); } + + e->setAttribute (T("id"), name); + + return e; + } + else + { + // trying to save the openness for an element that has no name - this won't + // work because it needs the names to identify what to open. + jassertfalse } return 0; @@ -63034,6 +63306,44 @@ void Viewport::setViewPositionProportionately (const double x, jmax (0, roundDoubleToInt (y * (contentComp->getHeight() - getHeight())))); } +bool Viewport::autoScroll (int mouseX, int mouseY, int activeBorderThickness, int maximumSpeed) +{ + if (contentComp != 0) + { + int dx = 0, dy = 0; + + if (mouseX < activeBorderThickness) + dx = activeBorderThickness - mouseX; + else if (mouseX >= contentHolder->getWidth() - activeBorderThickness) + dx = (contentHolder->getWidth() - activeBorderThickness) - mouseX; + + if (dx < 0) + dx = jmax (dx, -maximumSpeed, contentHolder->getWidth() - contentComp->getRight()); + else + dx = jmin (dx, maximumSpeed, -contentComp->getX()); + + if (mouseY < activeBorderThickness) + dy = activeBorderThickness - mouseY; + else if (mouseY >= contentHolder->getHeight() - activeBorderThickness) + dy = (contentHolder->getHeight() - activeBorderThickness) - mouseY; + + if (dy < 0) + dy = jmax (dy, -maximumSpeed, contentHolder->getHeight() - contentComp->getBottom()); + else + dy = jmin (dy, maximumSpeed, -contentComp->getY()); + + if (dx != 0 || dy != 0) + { + contentComp->setTopLeftPosition (contentComp->getX() + dx, + contentComp->getY() + dy); + + return true; + } + } + + return false; +} + void Viewport::componentMovedOrResized (Component&, bool, bool) { updateVisibleRegion(); @@ -63297,6 +63607,7 @@ LookAndFeel::LookAndFeel() TreeView::linesColourId, 0x4c000000, TreeView::backgroundColourId, 0x00000000, + TreeView::dragAndDropIndicatorColourId, 0x80ff0000, PopupMenu::backgroundColourId, 0xffffffff, PopupMenu::textColourId, 0xff000000, @@ -69156,7 +69467,7 @@ private: ComponentDeletionWatcher* currentlyOverWatcher; String dragDesc; - int xOff, yOff; + const int imageX, imageY; bool hasCheckedForExternalDrag, drawImage; DragImageComponent (const DragImageComponent&); @@ -69166,13 +69477,16 @@ public: DragImageComponent (Image* const im, const String& desc, Component* const s, - DragAndDropContainer* const o) + DragAndDropContainer* const o, + const int imageX_, const int imageY_) : image (im), source (s), owner (o), currentlyOver (0), currentlyOverWatcher (0), dragDesc (desc), + imageX (imageX_), + imageY (imageY_), hasCheckedForExternalDrag (false), drawImage (true) { @@ -69188,13 +69502,6 @@ public: mouseDragSourceWatcher = new ComponentDeletionWatcher (mouseDragSource); mouseDragSource->addMouseListener (this, false); - int mx, my; - Desktop::getLastMouseDownPosition (mx, my); - source->globalPositionToRelative (mx, my); - - xOff = jlimit (0, im->getWidth(), mx); - yOff = jlimit (0, im->getHeight(), my); - startTimer (200); setInterceptsMouseClicks (false, false); @@ -69341,13 +69648,13 @@ public: // a modal loop and deletes this object before it returns) const String dragDescLocal (dragDesc); - int newX = x - xOff; - int newY = y - yOff; + int newX = x + imageX; + int newY = y + imageY; if (getParentComponent() != 0) getParentComponent()->globalPositionToRelative (newX, newY); - if (newX != getX() || newY != getY()) + //if (newX != getX() || newY != getY()) { setTopLeftPosition (newX, newY); @@ -69450,19 +69757,18 @@ DragAndDropContainer::DragAndDropContainer() DragAndDropContainer::~DragAndDropContainer() { - if (dragImageComponent != 0) - delete dragImageComponent; + delete dragImageComponent; } void DragAndDropContainer::startDragging (const String& sourceDescription, Component* sourceComponent, Image* im, - const bool allowDraggingToExternalWindows) + const bool allowDraggingToExternalWindows, + const Point* imageOffsetFromMouse) { if (dragImageComponent != 0) { - if (im != 0) - delete im; + delete im; } else { @@ -69472,6 +69778,7 @@ void DragAndDropContainer::startDragging (const String& sourceDescription, { int mx, my; Desktop::getLastMouseDownPosition (mx, my); + int imageX = 0, imageY = 0; if (im == 0) { @@ -69516,13 +69823,27 @@ void DragAndDropContainer::startDragging (const String& sourceDescription, } } } + + imageX = -cx; + imageY = -cy; + } + else + { + if (imageOffsetFromMouse == 0) + { + imageX = im->getWidth() / -2; + imageY = im->getHeight() / -2; + } + else + { + imageX = (int) imageOffsetFromMouse->getX(); + imageY = (int) imageOffsetFromMouse->getY(); + } } DragImageComponent* const dic - = new DragImageComponent (im, - sourceDescription, - sourceComponent, - this); + = new DragImageComponent (im, sourceDescription, sourceComponent, + this, imageX, imageY); dragImageComponent = dic; currentDragDesc = sourceDescription; @@ -69548,8 +69869,7 @@ void DragAndDropContainer::startDragging (const String& sourceDescription, // is also a Component. jassertfalse - if (im != 0) - delete im; + delete im; } } } diff --git a/juce_amalgamated.h b/juce_amalgamated.h index 2f56d4a3db..34d881fc62 100644 --- a/juce_amalgamated.h +++ b/juce_amalgamated.h @@ -31567,6 +31567,23 @@ public: void setViewPositionProportionately (const double proportionX, const double proportionY); + /** If the specified position is at the edges of the viewport, this method scrolls + the viewport to bring that position nearer to the centre. + + Call this if you're dragging an object inside a viewport and want to make it scroll + when the user approaches an edge. You might also find Component::beginDragAutoRepeat() + useful when auto-scrolling. + + @param mouseX the x position, relative to the Viewport's top-left + @param mouseY the y position, relative to the Viewport's top-left + @param distanceFromEdge specifies how close to an edge the position needs to be + before the viewport should scroll in that direction + @param maximumSpeed the maximum number of pixels that the viewport is allowed + to scroll by. + @returns true if the viewport was scrolled + */ + bool autoScroll (int mouseX, int mouseY, int distanceFromEdge, int maximumSpeed); + /** Returns the position within the child component of the top-left of its visible area. @see getViewWidth, setViewPosition */ @@ -42491,11 +42508,16 @@ public: @param allowDraggingToOtherJuceWindows if true, the dragged component will appear as a desktop window, and can be dragged to DragAndDropTargets that are the children of components other than this one. + @param imageOffsetFromMouse if an image has been passed-in, this specifies the offset + at which the image should be drawn from the mouse. If it isn't + specified, then the image will be centred around the mouse. If + an image hasn't been passed-in, this will be ignored. */ void startDragging (const String& sourceDescription, Component* sourceComponent, Image* dragImage = 0, - const bool allowDraggingToOtherJuceWindows = false); + const bool allowDraggingToOtherJuceWindows = false, + const Point* imageOffsetFromMouse = 0); /** Returns true if something is currently being dragged. */ bool isDragAndDropActive() const; @@ -43549,6 +43571,88 @@ private: #ifndef __JUCE_TREEVIEW_JUCEHEADER__ #define __JUCE_TREEVIEW_JUCEHEADER__ +/********* Start of inlined file: juce_FileDragAndDropTarget.h *********/ +#ifndef __JUCE_FILEDRAGANDDROPTARGET_JUCEHEADER__ +#define __JUCE_FILEDRAGANDDROPTARGET_JUCEHEADER__ + +/** + Components derived from this class can have files dropped onto them by an external application. + + @see DragAndDropContainer +*/ +class JUCE_API FileDragAndDropTarget +{ +public: + /** Destructor. */ + virtual ~FileDragAndDropTarget() {} + + /** Callback to check whether this target is interested in the set of files being offered. + + Note that this will be called repeatedly when the user is dragging the mouse around over your + component, so don't do anything time-consuming in here, like opening the files to have a look + inside them! + + @param files the set of (absolute) pathnames of the files that the user is dragging + @returns true if this component wants to receive the other callbacks regarging this + type of object; if it returns false, no other callbacks will be made. + */ + virtual bool isInterestedInFileDrag (const StringArray& files) = 0; + + /** Callback to indicate that some files are being dragged over this component. + + This gets called when the user moves the mouse into this component while dragging. + + Use this callback as a trigger to make your component repaint itself to give the + user feedback about whether the files can be dropped here or not. + + @param files the set of (absolute) pathnames of the files that the user is dragging + @param x the mouse x position, relative to this component + @param y the mouse y position, relative to this component + */ + virtual void fileDragEnter (const StringArray& files, int x, int y); + + /** Callback to indicate that the user is dragging some files over this component. + + This gets called when the user moves the mouse over this component while dragging. + Normally overriding itemDragEnter() and itemDragExit() are enough, but + this lets you know what happens in-between. + + @param files the set of (absolute) pathnames of the files that the user is dragging + @param x the mouse x position, relative to this component + @param y the mouse y position, relative to this component + */ + virtual void fileDragMove (const StringArray& files, int x, int y); + + /** Callback to indicate that the mouse has moved away from this component. + + This gets called when the user moves the mouse out of this component while dragging + the files. + + If you've used fileDragEnter() to repaint your component and give feedback, use this + as a signal to repaint it in its normal state. + + @param files the set of (absolute) pathnames of the files that the user is dragging + */ + virtual void fileDragExit (const StringArray& files); + + /** Callback to indicate that the user has dropped the files onto this component. + + When the user drops the files, this get called, and you can use the files in whatever + way is appropriate. + + Note that after this is called, the fileDragExit method may not be called, so you should + clean up in here if there's anything you need to do when the drag finishes. + + @param files the set of (absolute) pathnames of the files that the user is dragging + @param x the mouse x position, relative to this component + @param y the mouse y position, relative to this component + */ + virtual void filesDropped (const StringArray& files, int x, int y) = 0; +}; + +#endif // __JUCE_FILEDRAGANDDROPTARGET_JUCEHEADER__ +/********* End of inlined file: juce_FileDragAndDropTarget.h *********/ + class TreeView; /** @@ -43860,10 +43964,52 @@ public: If you need more complex drag-and-drop behaviour, you can use custom components for the items, and use those to trigger the drag. + To accept drag-and-drop in your tree, see isInterestedInDragSource(), + isInterestedInFileDrag(), etc. + @see DragAndDropContainer::startDragging */ virtual const String getDragSourceDescription(); + /** If you want your item to be able to have files drag-and-dropped onto it, implement this + method and return true. + + If you return true and allow some files to be dropped, you'll also need to implement the + filesDropped() method to do something with them. + + Note that this will be called often, so make your implementation very quick! There's + certainly no time to try opening the files and having a think about what's inside them! + + For responding to internal drag-and-drop of other types of object, see isInterestedInDragSource(). + @see FileDragAndDropTarget::isInterestedInFileDrag, isInterestedInDragSource + */ + virtual bool isInterestedInFileDrag (const StringArray& files); + + /** When files are dropped into this item, this callback is invoked. + + For this to work, you'll need to have also implemented isInterestedInFileDrag(). + The insertIndex value indicates where in the list of sub-items the files were dropped. + @see FileDragAndDropTarget::filesDropped, isInterestedInFileDrag + */ + virtual void filesDropped (const StringArray& files, int insertIndex); + + /** If you want your item to act as a DragAndDropTarget, implement this method and return true. + + If you implement this method, you'll also need to implement itemDropped() in order to handle + the items when they are dropped. + To respond to drag-and-drop of files from external applications, see isInterestedInFileDrag(). + @see DragAndDropTarget::isInterestedInDragSource, itemDropped + */ + virtual bool isInterestedInDragSource (const String& sourceDescription, Component* sourceComponent); + + /** When a things are dropped into this item, this callback is invoked. + + For this to work, you need to have also implemented isInterestedInDragSource(). + The insertIndex value indicates where in the list of sub-items the new items should be placed. + @see isInterestedInDragSource, DragAndDropTarget::itemDropped + */ + virtual void itemDropped (const String& sourceDescription, Component* sourceComponent, int insertIndex); + /** Sets a flag to indicate that the item wants to be allowed to draw all the way across to the left edge of the treeview. @@ -43879,6 +44025,42 @@ public: */ void setDrawsInLeftMargin (bool canDrawInLeftMargin) throw(); + /** Saves the current state of open/closed nodes so it can be restored later. + + This takes a snapshot of which sub-nodes have been explicitly opened or closed, + and records it as XML. To identify node objects it uses the + TreeViewItem::getUniqueName() method to create named paths. This + means that the same state of open/closed nodes can be restored to a + completely different instance of the tree, as long as it contains nodes + whose unique names are the same. + + You'd normally want to use TreeView::getOpennessState() rather than call it + for a specific item, but this can be handy if you need to briefly save the state + for a section of the tree. + + The caller is responsible for deleting the object that is returned. + @see TreeView::getOpennessState, restoreOpennessState + */ + XmlElement* getOpennessState() const throw(); + + /** Restores the openness of this item and all its sub-items from a saved state. + + See TreeView::restoreOpennessState for more details. + + You'd normally want to use TreeView::restoreOpennessState() rather than call it + for a specific item, but this can be handy if you need to briefly save the state + for a section of the tree. + + @see TreeView::restoreOpennessState, getOpennessState + */ + void restoreOpennessState (const XmlElement& xml) throw(); + + /** Returns the index of this item in its parent's sub-items. */ + int getIndexInParent() const throw(); + + /** Returns true if this item is the last of its parent's sub-itens. */ + bool isLastOfSiblings() const throw(); + juce_UseDebuggingNewOperator private: @@ -43900,12 +44082,9 @@ private: int getIndentX() const throw(); void setOwnerView (TreeView* const newOwner) throw(); void paintRecursively (Graphics& g, int width); + TreeViewItem* getTopLevelItem() throw(); TreeViewItem* findItemRecursively (int y) throw(); TreeViewItem* getDeepestOpenParentItem() throw(); - void restoreFromXml (const XmlElement& e); - XmlElement* createXmlOpenness() const; - bool isLastOfSiblings() const throw(); - TreeViewItem* getTopLevelItem() throw(); int getNumRows() const throw(); TreeViewItem* getItemOnRow (int index) throw(); void deselectAllRecursively(); @@ -43925,6 +44104,8 @@ private: */ class JUCE_API TreeView : public Component, public SettableTooltipClient, + public FileDragAndDropTarget, + public DragAndDropTarget, private AsyncUpdater { public: @@ -44057,6 +44238,11 @@ public: */ TreeViewItem* getItemOnRow (int index) const; + /** Returns the item that contains a given y position. + The y is relative to the top of the TreeView component. + */ + TreeViewItem* getItemAt (int yPosition) const throw(); + /** Tries to scroll the tree so that this item is on-screen somewhere. */ void scrollToKeepItemVisible (TreeViewItem* item); @@ -44111,8 +44297,9 @@ public: */ enum ColourIds { - backgroundColourId = 0x1000500, /**< A background colour to fill the component with. */ - linesColourId = 0x1000501 /**< The colour to draw the lines with.*/ + backgroundColourId = 0x1000500, /**< A background colour to fill the component with. */ + linesColourId = 0x1000501, /**< The colour to draw the lines with.*/ + dragAndDropIndicatorColourId = 0x1000502 /**< The colour to use for the drag-and-drop target position indicator. */ }; /** @internal */ @@ -44125,6 +44312,26 @@ public: void colourChanged(); /** @internal */ void enablementChanged(); + /** @internal */ + bool isInterestedInFileDrag (const StringArray& files); + /** @internal */ + void fileDragEnter (const StringArray& files, int x, int y); + /** @internal */ + void fileDragMove (const StringArray& files, int x, int y); + /** @internal */ + void fileDragExit (const StringArray& files); + /** @internal */ + void filesDropped (const StringArray& files, int x, int y); + /** @internal */ + bool isInterestedInDragSource (const String& sourceDescription, Component* sourceComponent); + /** @internal */ + void itemDragEnter (const String& sourceDescription, Component* sourceComponent, int x, int y); + /** @internal */ + void itemDragMove (const String& sourceDescription, Component* sourceComponent, int x, int y); + /** @internal */ + void itemDragExit (const String& sourceDescription, Component* sourceComponent); + /** @internal */ + void itemDropped (const String& sourceDescription, Component* sourceComponent, int x, int y); juce_UseDebuggingNewOperator @@ -44134,6 +44341,8 @@ private: Viewport* viewport; CriticalSection nodeAlterationLock; TreeViewItem* rootItem; + Component* dragInsertPointHighlight; + Component* dragTargetGroupHighlight; int indentSize; bool defaultOpenness : 1; bool needsRecalculating : 1; @@ -44145,6 +44354,13 @@ private: void handleAsyncUpdate(); void moveSelectedRow (int delta); void updateButtonUnderMouse (const MouseEvent& e); + void showDragHighlight (TreeViewItem* item, int insertIndex, int x, int y) throw(); + void hideDragHighlight() throw(); + void handleDrag (const StringArray& files, const String& sourceDescription, Component* sourceComponent, int x, int y); + void handleDrop (const StringArray& files, const String& sourceDescription, Component* sourceComponent, int x, int y); + TreeViewItem* getInsertPosition (int& x, int& y, int& insertIndex, + const StringArray& files, const String& sourceDescription, + Component* sourceComponent) const throw(); TreeView (const TreeView&); const TreeView& operator= (const TreeView&); @@ -45490,88 +45706,6 @@ private: #endif #ifndef __JUCE_FILEDRAGANDDROPTARGET_JUCEHEADER__ -/********* Start of inlined file: juce_FileDragAndDropTarget.h *********/ -#ifndef __JUCE_FILEDRAGANDDROPTARGET_JUCEHEADER__ -#define __JUCE_FILEDRAGANDDROPTARGET_JUCEHEADER__ - -/** - Components derived from this class can have files dropped onto them by an external application. - - @see DragAndDropContainer -*/ -class JUCE_API FileDragAndDropTarget -{ -public: - /** Destructor. */ - virtual ~FileDragAndDropTarget() {} - - /** Callback to check whether this target is interested in the set of files being offered. - - Note that this will be called repeatedly when the user is dragging the mouse around over your - component, so don't do anything time-consuming in here, like opening the files to have a look - inside them! - - @param files the set of (absolute) pathnames of the files that the user is dragging - @returns true if this component wants to receive the other callbacks regarging this - type of object; if it returns false, no other callbacks will be made. - */ - virtual bool isInterestedInFileDrag (const StringArray& files) = 0; - - /** Callback to indicate that some files are being dragged over this component. - - This gets called when the user moves the mouse into this component while dragging. - - Use this callback as a trigger to make your component repaint itself to give the - user feedback about whether the files can be dropped here or not. - - @param files the set of (absolute) pathnames of the files that the user is dragging - @param x the mouse x position, relative to this component - @param y the mouse y position, relative to this component - */ - virtual void fileDragEnter (const StringArray& files, int x, int y); - - /** Callback to indicate that the user is dragging some files over this component. - - This gets called when the user moves the mouse over this component while dragging. - Normally overriding itemDragEnter() and itemDragExit() are enough, but - this lets you know what happens in-between. - - @param files the set of (absolute) pathnames of the files that the user is dragging - @param x the mouse x position, relative to this component - @param y the mouse y position, relative to this component - */ - virtual void fileDragMove (const StringArray& files, int x, int y); - - /** Callback to indicate that the mouse has moved away from this component. - - This gets called when the user moves the mouse out of this component while dragging - the files. - - If you've used fileDragEnter() to repaint your component and give feedback, use this - as a signal to repaint it in its normal state. - - @param files the set of (absolute) pathnames of the files that the user is dragging - */ - virtual void fileDragExit (const StringArray& files); - - /** Callback to indicate that the user has dropped the files onto this component. - - When the user drops the files, this get called, and you can use the files in whatever - way is appropriate. - - Note that after this is called, the fileDragExit method may not be called, so you should - clean up in here if there's anything you need to do when the drag finishes. - - @param files the set of (absolute) pathnames of the files that the user is dragging - @param x the mouse x position, relative to this component - @param y the mouse y position, relative to this component - */ - virtual void filesDropped (const StringArray& files, int x, int y) = 0; -}; - -#endif // __JUCE_FILEDRAGANDDROPTARGET_JUCEHEADER__ -/********* End of inlined file: juce_FileDragAndDropTarget.h *********/ - #endif #ifndef __JUCE_LASSOCOMPONENT_JUCEHEADER__ diff --git a/src/gui/components/controls/juce_TreeView.cpp b/src/gui/components/controls/juce_TreeView.cpp index f2f215dc24..59322f0584 100644 --- a/src/gui/components/controls/juce_TreeView.cpp +++ b/src/gui/components/controls/juce_TreeView.cpp @@ -147,7 +147,8 @@ public: Image* dragImage = Component::createComponentSnapshot (pos, true); dragImage->multiplyAllAlphas (0.6f); - dragContainer->startDragging (dragDescription, owner, dragImage, true); + Point imageOffset ((float) (pos.getX() - e.x), (float) (pos.getY() - e.y)); + dragContainer->startDragging (dragDescription, owner, dragImage, true, &imageOffset); } else { @@ -403,6 +404,8 @@ private: TreeView::TreeView (const String& componentName) : Component (componentName), rootItem (0), + dragInsertPointHighlight (0), + dragTargetGroupHighlight (0), indentSize (24), defaultOpenness (false), needsRecalculating (true), @@ -550,6 +553,15 @@ TreeViewItem* TreeView::getItemOnRow (int index) const return 0; } +TreeViewItem* TreeView::getItemAt (int y) const throw() +{ + TreeViewContentComponent* const tc = (TreeViewContentComponent*) viewport->getViewedComponent(); + int x; + relativePositionToOtherComponent (tc, x, y); + Rectangle pos; + return tc->findItemAt (y, pos); +} + //============================================================================== XmlElement* TreeView::getOpennessState (const bool alsoIncludeScrollPosition) const { @@ -557,7 +569,7 @@ XmlElement* TreeView::getOpennessState (const bool alsoIncludeScrollPosition) co if (rootItem != 0) { - e = rootItem->createXmlOpenness(); + e = rootItem->getOpennessState(); if (e != 0 && alsoIncludeScrollPosition) e->setAttribute (T("scrollPos"), viewport->getViewPositionY()); @@ -570,7 +582,7 @@ void TreeView::restoreOpennessState (const XmlElement& newState) { if (rootItem != 0) { - rootItem->restoreFromXml (newState); + rootItem->restoreOpennessState (newState); if (newState.hasAttribute (T("scrollPos"))) viewport->setViewPosition (viewport->getViewPositionX(), @@ -780,6 +792,245 @@ void TreeView::handleAsyncUpdate() } } +//============================================================================== +class TreeViewDragInsertPointHighlight : public Component +{ +public: + TreeViewDragInsertPointHighlight() + : lastItem (0) + { + setSize (100, 12); + setAlwaysOnTop (true); + setInterceptsMouseClicks (false, false); + } + + ~TreeViewDragInsertPointHighlight() {} + + void setTargetPosition (TreeViewItem* const item, int insertIndex, const int x, const int y, const int width) throw() + { + lastItem = item; + lastIndex = insertIndex; + const int offset = getHeight() / 2; + setBounds (x - offset, y - offset, width - (x - offset), getHeight()); + } + + void paint (Graphics& g) + { + Path p; + const float h = (float) getHeight(); + p.addEllipse (2.0f, 2.0f, h - 4.0f, h - 4.0f); + p.startNewSubPath (h - 2.0f, h / 2.0f); + p.lineTo ((float) getWidth(), h / 2.0f); + + g.setColour (findColour (TreeView::dragAndDropIndicatorColourId, true)); + g.strokePath (p, PathStrokeType (2.0f)); + } + + TreeViewItem* lastItem; + int lastIndex; +}; + +class TreeViewDragTargetGroupHighlight : public Component +{ +public: + TreeViewDragTargetGroupHighlight() + { + setAlwaysOnTop (true); + setInterceptsMouseClicks (false, false); + } + + ~TreeViewDragTargetGroupHighlight() {} + + void setTargetPosition (TreeViewItem* const item) throw() + { + Rectangle r (item->getItemPosition (true)); + r.setHeight (item->getItemHeight()); + setBounds (r); + } + + void paint (Graphics& g) + { + g.setColour (findColour (TreeView::dragAndDropIndicatorColourId, true)); + g.drawRoundedRectangle (1.0f, 1.0f, getWidth() - 2.0f, getHeight() - 2.0f, 3.0f, 2.0f); + } +}; + +//============================================================================== +void TreeView::showDragHighlight (TreeViewItem* item, int insertIndex, int x, int y) throw() +{ + beginDragAutoRepeat (1000 / 30); + + if (dragInsertPointHighlight == 0) + { + addAndMakeVisible (dragInsertPointHighlight = new TreeViewDragInsertPointHighlight()); + addAndMakeVisible (dragTargetGroupHighlight = new TreeViewDragTargetGroupHighlight()); + } + + ((TreeViewDragInsertPointHighlight*) dragInsertPointHighlight) + ->setTargetPosition (item, insertIndex, x, y, viewport->getViewWidth()); + + ((TreeViewDragTargetGroupHighlight*) dragTargetGroupHighlight) + ->setTargetPosition (item); +} + +void TreeView::hideDragHighlight() throw() +{ + deleteAndZero (dragInsertPointHighlight); + deleteAndZero (dragTargetGroupHighlight); +} + +TreeViewItem* TreeView::getInsertPosition (int& x, int& y, int& insertIndex, + const StringArray& files, const String& sourceDescription, + Component* sourceComponent) const throw() +{ + insertIndex = 0; + TreeViewItem* item = getItemAt (y); + + if (item == 0 || item->parentItem == 0) + return 0; + + Rectangle itemPos (item->getItemPosition (true)); + insertIndex = item->getIndexInParent(); + const int oldY = y; + y = itemPos.getY(); + + if (item->getNumSubItems() == 0 || ! item->isOpen()) + { + if (files.size() > 0 ? item->isInterestedInFileDrag (files) + : item->isInterestedInDragSource (sourceDescription, sourceComponent)) + { + // Check if we're trying to drag into an empty group item.. + if (oldY > itemPos.getY() + itemPos.getHeight() / 4 + && oldY < itemPos.getBottom() - itemPos.getHeight() / 4) + { + insertIndex = 0; + x = itemPos.getX() + getIndentSize(); + y = itemPos.getBottom(); + return item; + } + } + } + + if (oldY > itemPos.getCentreY()) + { + y += item->getItemHeight(); + + while (item->isLastOfSiblings() && item->parentItem != 0 + && item->parentItem->parentItem != 0) + { + if (x > itemPos.getX()) + break; + + item = item->parentItem; + itemPos = item->getItemPosition (true); + insertIndex = item->getIndexInParent(); + } + + ++insertIndex; + } + + x = itemPos.getX(); + return item->parentItem; +} + +void TreeView::handleDrag (const StringArray& files, const String& sourceDescription, Component* sourceComponent, int x, int y) +{ + const bool scrolled = viewport->autoScroll (x, y, 20, 10); + + int insertIndex; + TreeViewItem* const item = getInsertPosition (x, y, insertIndex, files, sourceDescription, sourceComponent); + + if (item != 0) + { + if (scrolled || dragInsertPointHighlight == 0 + || ((TreeViewDragInsertPointHighlight*) dragInsertPointHighlight)->lastItem != item + || ((TreeViewDragInsertPointHighlight*) dragInsertPointHighlight)->lastIndex != insertIndex) + { + if (files.size() > 0 ? item->isInterestedInFileDrag (files) + : item->isInterestedInDragSource (sourceDescription, sourceComponent)) + showDragHighlight (item, insertIndex, x, y); + else + hideDragHighlight(); + } + } + else + { + hideDragHighlight(); + } +} + +void TreeView::handleDrop (const StringArray& files, const String& sourceDescription, Component* sourceComponent, int x, int y) +{ + hideDragHighlight(); + + int insertIndex; + TreeViewItem* const item = getInsertPosition (x, y, insertIndex, files, sourceDescription, sourceComponent); + + if (item != 0) + { + if (files.size() > 0) + { + if (item->isInterestedInFileDrag (files)) + item->filesDropped (files, insertIndex); + } + else + { + if (item->isInterestedInDragSource (sourceDescription, sourceComponent)) + item->itemDropped (sourceDescription, sourceComponent, insertIndex); + } + } +} + +//============================================================================== +bool TreeView::isInterestedInFileDrag (const StringArray& files) +{ + return true; +} + +void TreeView::fileDragEnter (const StringArray& files, int x, int y) +{ + fileDragMove (files, x, y); +} + +void TreeView::fileDragMove (const StringArray& files, int x, int y) +{ + handleDrag (files, String::empty, 0, x, y); +} + +void TreeView::fileDragExit (const StringArray& files) +{ + hideDragHighlight(); +} + +void TreeView::filesDropped (const StringArray& files, int x, int y) +{ + handleDrop (files, String::empty, 0, x, y); +} + +bool TreeView::isInterestedInDragSource (const String& sourceDescription, Component* sourceComponent) +{ + return true; +} + +void TreeView::itemDragEnter (const String& sourceDescription, Component* sourceComponent, int x, int y) +{ + itemDragMove (sourceDescription, sourceComponent, x, y); +} + +void TreeView::itemDragMove (const String& sourceDescription, Component* sourceComponent, int x, int y) +{ + handleDrag (StringArray(), sourceDescription, sourceComponent, x, y); +} + +void TreeView::itemDragExit (const String& sourceDescription, Component* sourceComponent) +{ + hideDragHighlight(); +} + +void TreeView::itemDropped (const String& sourceDescription, Component* sourceComponent, int x, int y) +{ + handleDrop (StringArray(), sourceDescription, sourceComponent, x, y); +} //============================================================================== void TreeViewContentComponent::paint (Graphics& g) @@ -1010,6 +1261,24 @@ const String TreeViewItem::getDragSourceDescription() return String::empty; } +bool TreeViewItem::isInterestedInFileDrag (const StringArray& files) +{ + return false; +} + +void TreeViewItem::filesDropped (const StringArray& files, int insertIndex) +{ +} + +bool TreeViewItem::isInterestedInDragSource (const String& sourceDescription, Component* sourceComponent) +{ + return false; +} + +void TreeViewItem::itemDropped (const String& sourceDescription, Component* sourceComponent, int insertIndex) +{ +} + const Rectangle TreeViewItem::getItemPosition (const bool relativeToTreeViewTopLeft) const throw() { const int indentX = getIndentX(); @@ -1236,6 +1505,14 @@ bool TreeViewItem::isLastOfSiblings() const throw() || parentItem->subItems.getLast() == this; } +int TreeViewItem::getIndexInParent() const throw() +{ + if (parentItem == 0) + return 0; + + return parentItem->subItems.indexOf (this); +} + TreeViewItem* TreeViewItem::getTopLevelItem() throw() { return (parentItem == 0) ? this @@ -1300,7 +1577,7 @@ TreeViewItem* TreeViewItem::findItemRecursively (int y) throw() { TreeViewItem* const ti = subItems.getUnchecked(i); - if (ti->totalHeight >= y) + if (y < ti->totalHeight) return ti->findItemRecursively (y); y -= ti->totalHeight; @@ -1399,7 +1676,7 @@ TreeViewItem* TreeViewItem::getNextVisibleItem (const bool recurse) const throw( return 0; } -void TreeViewItem::restoreFromXml (const XmlElement& e) +void TreeViewItem::restoreOpennessState (const XmlElement& e) throw() { if (e.hasTagName (T("CLOSED"))) { @@ -1419,7 +1696,7 @@ void TreeViewItem::restoreFromXml (const XmlElement& e) if (ti->getUniqueName() == id) { - ti->restoreFromXml (*n); + ti->restoreOpennessState (*n); break; } } @@ -1427,38 +1704,35 @@ void TreeViewItem::restoreFromXml (const XmlElement& e) } } -XmlElement* TreeViewItem::createXmlOpenness() const +XmlElement* TreeViewItem::getOpennessState() const throw() { - if (openness != opennessDefault) + const String name (getUniqueName()); + + if (name.isNotEmpty()) { - const String name (getUniqueName()); + XmlElement* e; - if (name.isNotEmpty()) + if (isOpen()) { - XmlElement* e; + e = new XmlElement (T("OPEN")); - if (isOpen()) - { - e = new XmlElement (T("OPEN")); - - for (int i = 0; i < subItems.size(); ++i) - e->addChildElement (subItems.getUnchecked(i)->createXmlOpenness()); - } - else - { - e = new XmlElement (T("CLOSED")); - } - - e->setAttribute (T("id"), name); - - return e; + for (int i = 0; i < subItems.size(); ++i) + e->addChildElement (subItems.getUnchecked(i)->getOpennessState()); } else { - // trying to save the openness for an element that has no name - this won't - // work because it needs the names to identify what to open. - jassertfalse + e = new XmlElement (T("CLOSED")); } + + e->setAttribute (T("id"), name); + + return e; + } + else + { + // trying to save the openness for an element that has no name - this won't + // work because it needs the names to identify what to open. + jassertfalse } return 0; diff --git a/src/gui/components/controls/juce_TreeView.h b/src/gui/components/controls/juce_TreeView.h index 01ec12579c..03fa4eb6f5 100644 --- a/src/gui/components/controls/juce_TreeView.h +++ b/src/gui/components/controls/juce_TreeView.h @@ -29,8 +29,11 @@ #include "../layout/juce_Viewport.h" #include "../../../text/juce_XmlElement.h" #include "../../../events/juce_AsyncUpdater.h" +#include "../mouse/juce_FileDragAndDropTarget.h" +#include "../mouse/juce_DragAndDropTarget.h" class TreeView; + //============================================================================== /** An item in a treeview. @@ -336,6 +339,7 @@ public: */ virtual const String getTooltip(); + //============================================================================== /** To allow items from your treeview to be dragged-and-dropped, implement this method. If this returns a non-empty name then when the user drags an item, the treeview will @@ -346,10 +350,53 @@ public: If you need more complex drag-and-drop behaviour, you can use custom components for the items, and use those to trigger the drag. + To accept drag-and-drop in your tree, see isInterestedInDragSource(), + isInterestedInFileDrag(), etc. + @see DragAndDropContainer::startDragging */ virtual const String getDragSourceDescription(); + /** If you want your item to be able to have files drag-and-dropped onto it, implement this + method and return true. + + If you return true and allow some files to be dropped, you'll also need to implement the + filesDropped() method to do something with them. + + Note that this will be called often, so make your implementation very quick! There's + certainly no time to try opening the files and having a think about what's inside them! + + For responding to internal drag-and-drop of other types of object, see isInterestedInDragSource(). + @see FileDragAndDropTarget::isInterestedInFileDrag, isInterestedInDragSource + */ + virtual bool isInterestedInFileDrag (const StringArray& files); + + /** When files are dropped into this item, this callback is invoked. + + For this to work, you'll need to have also implemented isInterestedInFileDrag(). + The insertIndex value indicates where in the list of sub-items the files were dropped. + @see FileDragAndDropTarget::filesDropped, isInterestedInFileDrag + */ + virtual void filesDropped (const StringArray& files, int insertIndex); + + /** If you want your item to act as a DragAndDropTarget, implement this method and return true. + + If you implement this method, you'll also need to implement itemDropped() in order to handle + the items when they are dropped. + To respond to drag-and-drop of files from external applications, see isInterestedInFileDrag(). + @see DragAndDropTarget::isInterestedInDragSource, itemDropped + */ + virtual bool isInterestedInDragSource (const String& sourceDescription, Component* sourceComponent); + + /** When a things are dropped into this item, this callback is invoked. + + For this to work, you need to have also implemented isInterestedInDragSource(). + The insertIndex value indicates where in the list of sub-items the new items should be placed. + @see isInterestedInDragSource, DragAndDropTarget::itemDropped + */ + virtual void itemDropped (const String& sourceDescription, Component* sourceComponent, int insertIndex); + + //============================================================================== /** Sets a flag to indicate that the item wants to be allowed to draw all the way across to the left edge of the treeview. @@ -365,6 +412,44 @@ public: */ void setDrawsInLeftMargin (bool canDrawInLeftMargin) throw(); + //============================================================================== + /** Saves the current state of open/closed nodes so it can be restored later. + + This takes a snapshot of which sub-nodes have been explicitly opened or closed, + and records it as XML. To identify node objects it uses the + TreeViewItem::getUniqueName() method to create named paths. This + means that the same state of open/closed nodes can be restored to a + completely different instance of the tree, as long as it contains nodes + whose unique names are the same. + + You'd normally want to use TreeView::getOpennessState() rather than call it + for a specific item, but this can be handy if you need to briefly save the state + for a section of the tree. + + The caller is responsible for deleting the object that is returned. + @see TreeView::getOpennessState, restoreOpennessState + */ + XmlElement* getOpennessState() const throw(); + + /** Restores the openness of this item and all its sub-items from a saved state. + + See TreeView::restoreOpennessState for more details. + + You'd normally want to use TreeView::restoreOpennessState() rather than call it + for a specific item, but this can be handy if you need to briefly save the state + for a section of the tree. + + @see TreeView::restoreOpennessState, getOpennessState + */ + void restoreOpennessState (const XmlElement& xml) throw(); + + //============================================================================== + /** Returns the index of this item in its parent's sub-items. */ + int getIndexInParent() const throw(); + + /** Returns true if this item is the last of its parent's sub-itens. */ + bool isLastOfSiblings() const throw(); + //============================================================================== juce_UseDebuggingNewOperator @@ -387,12 +472,9 @@ private: int getIndentX() const throw(); void setOwnerView (TreeView* const newOwner) throw(); void paintRecursively (Graphics& g, int width); + TreeViewItem* getTopLevelItem() throw(); TreeViewItem* findItemRecursively (int y) throw(); TreeViewItem* getDeepestOpenParentItem() throw(); - void restoreFromXml (const XmlElement& e); - XmlElement* createXmlOpenness() const; - bool isLastOfSiblings() const throw(); - TreeViewItem* getTopLevelItem() throw(); int getNumRows() const throw(); TreeViewItem* getItemOnRow (int index) throw(); void deselectAllRecursively(); @@ -414,6 +496,8 @@ private: */ class JUCE_API TreeView : public Component, public SettableTooltipClient, + public FileDragAndDropTarget, + public DragAndDropTarget, private AsyncUpdater { public: @@ -549,6 +633,11 @@ public: */ TreeViewItem* getItemOnRow (int index) const; + /** Returns the item that contains a given y position. + The y is relative to the top of the TreeView component. + */ + TreeViewItem* getItemAt (int yPosition) const throw(); + /** Tries to scroll the tree so that this item is on-screen somewhere. */ void scrollToKeepItemVisible (TreeViewItem* item); @@ -605,8 +694,9 @@ public: */ enum ColourIds { - backgroundColourId = 0x1000500, /**< A background colour to fill the component with. */ - linesColourId = 0x1000501 /**< The colour to draw the lines with.*/ + backgroundColourId = 0x1000500, /**< A background colour to fill the component with. */ + linesColourId = 0x1000501, /**< The colour to draw the lines with.*/ + dragAndDropIndicatorColourId = 0x1000502 /**< The colour to use for the drag-and-drop target position indicator. */ }; //============================================================================== @@ -620,6 +710,26 @@ public: void colourChanged(); /** @internal */ void enablementChanged(); + /** @internal */ + bool isInterestedInFileDrag (const StringArray& files); + /** @internal */ + void fileDragEnter (const StringArray& files, int x, int y); + /** @internal */ + void fileDragMove (const StringArray& files, int x, int y); + /** @internal */ + void fileDragExit (const StringArray& files); + /** @internal */ + void filesDropped (const StringArray& files, int x, int y); + /** @internal */ + bool isInterestedInDragSource (const String& sourceDescription, Component* sourceComponent); + /** @internal */ + void itemDragEnter (const String& sourceDescription, Component* sourceComponent, int x, int y); + /** @internal */ + void itemDragMove (const String& sourceDescription, Component* sourceComponent, int x, int y); + /** @internal */ + void itemDragExit (const String& sourceDescription, Component* sourceComponent); + /** @internal */ + void itemDropped (const String& sourceDescription, Component* sourceComponent, int x, int y); juce_UseDebuggingNewOperator @@ -629,6 +739,8 @@ private: Viewport* viewport; CriticalSection nodeAlterationLock; TreeViewItem* rootItem; + Component* dragInsertPointHighlight; + Component* dragTargetGroupHighlight; int indentSize; bool defaultOpenness : 1; bool needsRecalculating : 1; @@ -640,6 +752,13 @@ private: void handleAsyncUpdate(); void moveSelectedRow (int delta); void updateButtonUnderMouse (const MouseEvent& e); + void showDragHighlight (TreeViewItem* item, int insertIndex, int x, int y) throw(); + void hideDragHighlight() throw(); + void handleDrag (const StringArray& files, const String& sourceDescription, Component* sourceComponent, int x, int y); + void handleDrop (const StringArray& files, const String& sourceDescription, Component* sourceComponent, int x, int y); + TreeViewItem* getInsertPosition (int& x, int& y, int& insertIndex, + const StringArray& files, const String& sourceDescription, + Component* sourceComponent) const throw(); TreeView (const TreeView&); const TreeView& operator= (const TreeView&); diff --git a/src/gui/components/layout/juce_Viewport.cpp b/src/gui/components/layout/juce_Viewport.cpp index 9670d1734c..8830eb04f3 100644 --- a/src/gui/components/layout/juce_Viewport.cpp +++ b/src/gui/components/layout/juce_Viewport.cpp @@ -124,6 +124,44 @@ void Viewport::setViewPositionProportionately (const double x, jmax (0, roundDoubleToInt (y * (contentComp->getHeight() - getHeight())))); } +bool Viewport::autoScroll (int mouseX, int mouseY, int activeBorderThickness, int maximumSpeed) +{ + if (contentComp != 0) + { + int dx = 0, dy = 0; + + if (mouseX < activeBorderThickness) + dx = activeBorderThickness - mouseX; + else if (mouseX >= contentHolder->getWidth() - activeBorderThickness) + dx = (contentHolder->getWidth() - activeBorderThickness) - mouseX; + + if (dx < 0) + dx = jmax (dx, -maximumSpeed, contentHolder->getWidth() - contentComp->getRight()); + else + dx = jmin (dx, maximumSpeed, -contentComp->getX()); + + if (mouseY < activeBorderThickness) + dy = activeBorderThickness - mouseY; + else if (mouseY >= contentHolder->getHeight() - activeBorderThickness) + dy = (contentHolder->getHeight() - activeBorderThickness) - mouseY; + + if (dy < 0) + dy = jmax (dy, -maximumSpeed, contentHolder->getHeight() - contentComp->getBottom()); + else + dy = jmin (dy, maximumSpeed, -contentComp->getY()); + + if (dx != 0 || dy != 0) + { + contentComp->setTopLeftPosition (contentComp->getX() + dx, + contentComp->getY() + dy); + + return true; + } + } + + return false; +} + void Viewport::componentMovedOrResized (Component&, bool, bool) { updateVisibleRegion(); diff --git a/src/gui/components/layout/juce_Viewport.h b/src/gui/components/layout/juce_Viewport.h index ef24ce6329..01fe2fae70 100644 --- a/src/gui/components/layout/juce_Viewport.h +++ b/src/gui/components/layout/juce_Viewport.h @@ -104,6 +104,23 @@ public: void setViewPositionProportionately (const double proportionX, const double proportionY); + /** If the specified position is at the edges of the viewport, this method scrolls + the viewport to bring that position nearer to the centre. + + Call this if you're dragging an object inside a viewport and want to make it scroll + when the user approaches an edge. You might also find Component::beginDragAutoRepeat() + useful when auto-scrolling. + + @param mouseX the x position, relative to the Viewport's top-left + @param mouseY the y position, relative to the Viewport's top-left + @param distanceFromEdge specifies how close to an edge the position needs to be + before the viewport should scroll in that direction + @param maximumSpeed the maximum number of pixels that the viewport is allowed + to scroll by. + @returns true if the viewport was scrolled + */ + bool autoScroll (int mouseX, int mouseY, int distanceFromEdge, int maximumSpeed); + /** Returns the position within the child component of the top-left of its visible area. @see getViewWidth, setViewPosition */ diff --git a/src/gui/components/lookandfeel/juce_LookAndFeel.cpp b/src/gui/components/lookandfeel/juce_LookAndFeel.cpp index 647075f525..badb73e5ef 100644 --- a/src/gui/components/lookandfeel/juce_LookAndFeel.cpp +++ b/src/gui/components/lookandfeel/juce_LookAndFeel.cpp @@ -140,6 +140,7 @@ LookAndFeel::LookAndFeel() TreeView::linesColourId, 0x4c000000, TreeView::backgroundColourId, 0x00000000, + TreeView::dragAndDropIndicatorColourId, 0x80ff0000, PopupMenu::backgroundColourId, 0xffffffff, PopupMenu::textColourId, 0xff000000, diff --git a/src/gui/components/mouse/juce_DragAndDropContainer.cpp b/src/gui/components/mouse/juce_DragAndDropContainer.cpp index a4ee0dff3d..4a29a08e2e 100644 --- a/src/gui/components/mouse/juce_DragAndDropContainer.cpp +++ b/src/gui/components/mouse/juce_DragAndDropContainer.cpp @@ -55,7 +55,7 @@ private: ComponentDeletionWatcher* currentlyOverWatcher; String dragDesc; - int xOff, yOff; + const int imageX, imageY; bool hasCheckedForExternalDrag, drawImage; DragImageComponent (const DragImageComponent&); @@ -65,13 +65,16 @@ public: DragImageComponent (Image* const im, const String& desc, Component* const s, - DragAndDropContainer* const o) + DragAndDropContainer* const o, + const int imageX_, const int imageY_) : image (im), source (s), owner (o), currentlyOver (0), currentlyOverWatcher (0), dragDesc (desc), + imageX (imageX_), + imageY (imageY_), hasCheckedForExternalDrag (false), drawImage (true) { @@ -87,13 +90,6 @@ public: mouseDragSourceWatcher = new ComponentDeletionWatcher (mouseDragSource); mouseDragSource->addMouseListener (this, false); - int mx, my; - Desktop::getLastMouseDownPosition (mx, my); - source->globalPositionToRelative (mx, my); - - xOff = jlimit (0, im->getWidth(), mx); - yOff = jlimit (0, im->getHeight(), my); - startTimer (200); setInterceptsMouseClicks (false, false); @@ -240,13 +236,13 @@ public: // a modal loop and deletes this object before it returns) const String dragDescLocal (dragDesc); - int newX = x - xOff; - int newY = y - yOff; + int newX = x + imageX; + int newY = y + imageY; if (getParentComponent() != 0) getParentComponent()->globalPositionToRelative (newX, newY); - if (newX != getX() || newY != getY()) + //if (newX != getX() || newY != getY()) { setTopLeftPosition (newX, newY); @@ -351,19 +347,18 @@ DragAndDropContainer::DragAndDropContainer() DragAndDropContainer::~DragAndDropContainer() { - if (dragImageComponent != 0) - delete dragImageComponent; + delete dragImageComponent; } void DragAndDropContainer::startDragging (const String& sourceDescription, Component* sourceComponent, Image* im, - const bool allowDraggingToExternalWindows) + const bool allowDraggingToExternalWindows, + const Point* imageOffsetFromMouse) { if (dragImageComponent != 0) { - if (im != 0) - delete im; + delete im; } else { @@ -373,6 +368,7 @@ void DragAndDropContainer::startDragging (const String& sourceDescription, { int mx, my; Desktop::getLastMouseDownPosition (mx, my); + int imageX = 0, imageY = 0; if (im == 0) { @@ -417,13 +413,27 @@ void DragAndDropContainer::startDragging (const String& sourceDescription, } } } + + imageX = -cx; + imageY = -cy; + } + else + { + if (imageOffsetFromMouse == 0) + { + imageX = im->getWidth() / -2; + imageY = im->getHeight() / -2; + } + else + { + imageX = (int) imageOffsetFromMouse->getX(); + imageY = (int) imageOffsetFromMouse->getY(); + } } DragImageComponent* const dic - = new DragImageComponent (im, - sourceDescription, - sourceComponent, - this); + = new DragImageComponent (im, sourceDescription, sourceComponent, + this, imageX, imageY); dragImageComponent = dic; currentDragDesc = sourceDescription; @@ -449,8 +459,7 @@ void DragAndDropContainer::startDragging (const String& sourceDescription, // is also a Component. jassertfalse - if (im != 0) - delete im; + delete im; } } } diff --git a/src/gui/components/mouse/juce_DragAndDropContainer.h b/src/gui/components/mouse/juce_DragAndDropContainer.h index e0c0a2d38e..647dfb0ae5 100644 --- a/src/gui/components/mouse/juce_DragAndDropContainer.h +++ b/src/gui/components/mouse/juce_DragAndDropContainer.h @@ -85,11 +85,16 @@ public: @param allowDraggingToOtherJuceWindows if true, the dragged component will appear as a desktop window, and can be dragged to DragAndDropTargets that are the children of components other than this one. + @param imageOffsetFromMouse if an image has been passed-in, this specifies the offset + at which the image should be drawn from the mouse. If it isn't + specified, then the image will be centred around the mouse. If + an image hasn't been passed-in, this will be ignored. */ void startDragging (const String& sourceDescription, Component* sourceComponent, Image* dragImage = 0, - const bool allowDraggingToOtherJuceWindows = false); + const bool allowDraggingToOtherJuceWindows = false, + const Point* imageOffsetFromMouse = 0); /** Returns true if something is currently being dragged. */ bool isDragAndDropActive() const;