Drag&Drop from a toolbar


#1

Hi,

basically what I try to do now is a basic GUI designer (it does not replicate Introjucer functionallity, those are different GUI elements). I decided to have a toolbar with possible elements and let the user drag the items he wants from the toolbar. My problem is that the dragging works only within the toolbar but when the items get's out, it is no longer visible until I move the mouse within the toolbar area again.

 

Well, this may not be toolbar related. It may actually be just that I don't use the Drag&Drop functionality in a correct way. In the docs, it is written, that the topmost component (the parent of the things to drag) should inherit DragAndDropContainer and the component to recieve the dropped items should inherit DragAndDropTarget. When the drag event occurs the dragged item should call the DragAndDropContainer's startDragging(). I tried to understand how the Demo application uses DragAndDrop so that I can implement it in the same way. The WidgetsDemo uses Drag&Drop but the it does not call the startDragging() method from anywhere. Fulltext search in the file is a no match.

 

Since I couldn't figure out the usage from the demo I did just what the documentation says. My top level content component inherits a DragAndDropContainer and it has two child objects. The toolbar and then something I called LayoutDesignComponent and it just inherits Component and DragAndDropTarget.

 

On the toolbar I have some buttons whith overloaded mouseDrag() method, which calls findParentDragContainerFor() which should return the top level content component (obviously it does) and call startDragging() on this object. The thing is - as stated on top of this post - the item being dragged is visible only until it leaves the toolbar. Also itemDropped() is never called.

 

Toolbar itself has some dragging functionality implemented to customize it's layout, so I hope it does not collide with what I try to do here. Is there any reason the above described hierarchy should not work?

 

This post is possibly longer than it should be, I just tried to be as descriptive as possible...

 

Thanks


#2

Not only the parent of your 'things to drag' should be a DragAndDropContainer but also where you want the drop your components. Most of the times i only have one DragAndDropContainer, my main window.


#3

But you kind of contradict yourself there. You say that not only the parrent should be DragAndDropContainer, right after that you say you have just one DragAndDropContainer instance. Can you make it a bit clearer?

 

In the Widgets demo, there's DragAndDropDemoTarget which inherits DragAndDropTarget but not DragAndDropContainer as I'd expect.

 

Thanks


#4

Anybody?


#5

A guess into the blue: did you implement DragAndDropTarget::isInterestedInDragSource() (obviously you have, because it's pure virtual) and does it return true for the started drag?

http://www.juce.com/doc/classDragAndDropTarget#a53853ec7aac70f5590a590b84c3f4f12

You can decide either by description or by dynamic_cast...


#6

For now, I just return true. So this should not be a problem. Demo does the same by the way...


#7

Another guess, the component where you want to drop,

  • does this inherit Component AND DragAndDropTarget?
  • is it really a child of the DragAndDropContainer, i.e. do you call addAndMakeVisible for the target in the container?
  • is the space where you look at really occupied by the component?
  • do you some fancy things with the mouse events (i.e. using a MouseListener or implementing mouseMove, mouseEnter etc.)? if so try to comment this out or call the super classes method after you did your customization

I'm no juce expert, but these are the places where I would look at...

HTH


#8

does this inherit Component AND DragAndDropTarget?

class LayoutDesignComponent : public Component, public DragAndDropTarget{

is it really a child of the DragAndDropContainer, i.e. do you call addAndMakeVisible for the target in the container?

addAndMakeVisible(toolbar);
addAndMakeVisible(designComponent);

is the space where you look at really occupied by the component?

void MainContentComponent::resized()
{
    const int toolbarThickness = 40;

    if (toolbar.isVertical())
        toolbar.setBounds(getLocalBounds().removeFromLeft(toolbarThickness));
    else
        toolbar.setBounds(getLocalBounds().removeFromTop(toolbarThickness));    

    designComponent.setBounds(0,toolbarThickness,getWidth(), getHeight() - toolbarThickness);
}

do you some fancy things with the mouse events

No I don't. It is a simple app I just created. So simple I could easilly post the headers actually. So here they are:

 

LayoutDesignComponent.h (stripped includes and include guards to make it shorter)

 

class LayoutDesignComponent : public Component, public DragAndDropTarget
{
public:
    //==============================================================================
    LayoutDesignComponent();
    ~LayoutDesignComponent();

    void paint(Graphics&);
    void resized();

    virtual bool     isInterestedInDragSource(const SourceDetails &dragSourceDetails) {
        return true;
    }
    virtual void     itemDropped(const SourceDetails &dragSourceDetails) {
        int x;
        x = 5; //I have a breakpoint here so that I know this method gets called, it does not.
    }

private:    
};

 

MainComponent.h

 

class MainContentComponent   : public Component, public DragAndDropContainer
{
public:
    //==============================================================================
    MainContentComponent();
    ~MainContentComponent();

    void paint (Graphics&);
    void resized();

private:
    Toolbar toolbar;
    LayoutDesignComponent designComponent;

    class ElementsToolbarItemFactory : public ToolbarItemFactory
    {
        class DraggableToolbarButton : public ToolbarButton {
        public:
            using ToolbarButton::ToolbarButton;
            virtual void mouseDrag(const MouseEvent &event) override {
                findParentDragContainerFor(this)->startDragging(var(42), this); //for now...
            }
        };
    public:
        ElementsToolbarItemFactory() {}

        enum ElementsToolbarItemIds
        {
            elem_slider = 1,
            elem_edit = 2,
            elem_spinedit = 3,            
        };

        void getAllToolbarItemIds(Array<int>& ids) override
        {

            ids.add(elem_slider);
            ids.add(elem_edit);
            ids.add(elem_spinedit);                       
        }

        void getDefaultItemSet(Array<int>& ids) override
        {            
            ids.add(elem_slider);
            ids.add(elem_edit);
            ids.add(elem_spinedit);
        }

        ToolbarItemComponent* createItem(int itemId) override
        {
            switch (itemId)
            {
            case elem_slider:           return createButtonFromZipFileSVG(itemId, "new", "fader.svg");
            case elem_edit:                return createButtonFromZipFileSVG(itemId, "open", "edit.svg");
            case elem_spinedit:         return createButtonFromZipFileSVG(itemId, "save", "spinedit.svg");
            default:                break;
            }

            return nullptr;
        }

    private:
        StringArray iconNames;
        OwnedArray<Drawable> iconsFromZipFile;

        ToolbarButton* createButtonFromZipFileSVG(const int itemId, const String& text, const String& filename)
        {
            if (iconsFromZipFile.size() == 0)
            {
                File file = File::getCurrentWorkingDirectory().getParentDirectory().getParentDirectory().getChildFile("Resources/icons.zip");

                ZipFile icons(file);

                for (int i = 0; i < icons.getNumEntries(); ++i)
                {
                    ScopedPointer<InputStream> svgFileStream(icons.createStreamForEntry(i));

                    if (svgFileStream != nullptr)
                    {
                        iconNames.add(icons.getEntry(i)->filename);
                        iconsFromZipFile.add(Drawable::createFromImageDataStream(*svgFileStream));
                    }
                }
            }

            Drawable* image = iconsFromZipFile[iconNames.indexOf(filename)]->createCopy();
            //return new ToolbarButton(itemId, text, image, 0);
            return new DraggableToolbarButton(itemId, text, image, 0);
        }
        
    };

    ElementsToolbarItemFactory factory;

    //==============================================================================
    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent)
};

 

Maybe if I could understand whay I can't find any startDragging method call in the WidgetsDemo, I could also see, what's wrong here.

 

And thanks...


#9

I looked up the workflow in the demo, it uses functionality implemented in the ListBox:

https://github.com/julianstorer/JUCE/blob/master/modules/juce_gui_basics/widgets/juce_ListBox.cpp

The drag is started by the ListBox::RowComponent::mouseDrag() line 103

It calls on the ListBox (aka owner) owner.startDragAndDrop (e, rowsToDrag, dragDescription, true)  in line 123.

This in line 932 ff. figures the DragContainer and calls "startDragging".

There is also an Image set for the drag, so eventually that's what is missing in your case...

HTH


#10

Maybe try first not using the Toolbar but any different Component. Dragging from a Toolbar might be more complicated, because the ToolbarEditingMode could interfere with your desired dragging... But again, just guessing... I used a ListBox and had no problems...


#11

Yes, I just did that (see bellow) even before I read your post. Also I noticed that my isInterestedInDragSource() is not called at all, I placed a breakpoint there and it never hits even when I drag the mouse over the target. This means probably means it finds the wrong DragAndDropContainer.

 

So when I replaced the toolbar with Component object with simple TextButtons, it works as expected. I think that when I call findParentDragContainerFor(this) with this being a toolbar button it finds the Toolbar component, which is also a DragAndDropContainer and there is this sentence in the docs for find findParentDragContainerFor():

 

This will search up this component's parent hierarchy looking for the first parent component which is a DragAndDropContainer.

Guess I'll have to set the pointer to the top most component as some attribute in the ToolbarButton (in my case DraggableToolbarButton). I'll try that and let youd know. Thank's a lot for your help.

 

 

 


#12

Yes, that was it. I had to make the factory know the top-level component, so that it can pass it to the buttons it creates. Calling findParentDragContainerFor() is not the solution here as it returns the Toolbar which would not let me drag the items to it's siblings. It would only work for it's children.

Thanks once more.