TreeView requests

We need to highlight the items when the user moves his mouse over them, also for the “PlusMinusBoxes”.
I also modified the behavior so that the PlusMinusBoxes react at mouse down events instead of mouse up.

// juce_TreeView.h:276

     virtual void paintItem (Graphics& g, int width, int height);
->
     virtual void paintItem (Graphics& g, int width, int height, bool isMouseOver);
// juce_TreeView.h:348

     void paintRecursively (Graphics& g, int width);
->
     void paintRecursively (Graphics& g, int width, TreeViewItem* itemUnderMouse, bool isMouseOverPlusMinusBox);
// juce_TreeView.cpp:44

static const bool triggerPlusMinusBoxesOnMouseDown = true;

//==============================================================================
class TreeViewContentComponent  : public Component
{
public:
    TreeViewContentComponent (TreeView* const owner_)
        : owner (owner_),
          isDragging (false)
    {
        setRepaintsOnMouseActivity(true); // <-
    }

    // [...]

    void mouseDown (const MouseEvent& e)
    {

        // [...]

            MouseEvent e2 (e);
            e2.x -= pos.getX();
            e2.y -= pos.getY();
            item->itemClicked (e2);
        }
        else if (item != 0                                                    // <- from here
                  && triggerPlusMinusBoxesOnMouseDown 
                  && e.x >= pos.getX() - owner->getIndentSize()
                  && e.x < pos.getX())
        {
            item->setOpen (! item->isOpen());
        }
    }

    // [...]

    void mouseUp (const MouseEvent& e)
    {
        // [...]
             if (needSelectionOnMouseUp)
            {
                selectBasedOnModifiers (item, e.mods);
            }
            else if (! triggerPlusMinusBoxesOnMouseDown  // <-
                      && e.mouseWasClicked())
            {
                if (e.x >= pos.getX() - owner->getIndentSize()
                     && e.x < pos.getX())
                {
                    item->setOpen (! item->isOpen());
                }
            }

    // [...]

    virtual void mouseMove(const MouseEvent& e)
    {
      repaint();
    }
// juce_TreeView.cpp:677

void TreeViewContentComponent::paint (Graphics& g)
{

        // [...]

        if (! owner->rootItemVisible)
        {
            const int indentWidth = owner->getIndentSize();

            g.setOrigin (-indentWidth, -owner->rootItem->itemHeight);
            w += indentWidth;
        }

        TreeViewItem* itemUnderMouse = 0;
        bool isMouseOverPlusMinusBox = false;
        {
            int x, y;
            getMouseXYRelative(x, y);
            Rectangle pos;
            itemUnderMouse = findItemAt (y, pos);
            if (itemUnderMouse != 0 && x >= pos.getX() - owner->getIndentSize()
                 && x < pos.getX())
            {
                isMouseOverPlusMinusBox = true;
            }
        }

        owner->rootItem->paintRecursively (g, w, itemUnderMouse, isMouseOverPlusMinusBox);
    }

// [...]

void TreeViewItem::paintItem (Graphics&, int, int, bool)
{
}

// [...]

void TreeViewItem::paintRecursively (Graphics& g, int width, TreeViewItem* itemUnderMouse, bool isMouseOverPlusMinusBox)
{
    jassert (ownerView != 0);
    if (ownerView == 0)
        return;

    // [...]

        if (mightContainSubItems())
        {
            ownerView->getLookAndFeel()
                .drawTreeviewPlusMinusBox (g,
                                           depth * indentWidth, 0,
                                           indentWidth, itemHeight,
                                           ! isOpen(), 
                                           itemUnderMouse == this && isMouseOverPlusMinusBox); // <-
        }
    }

    {
        g.saveState();
        g.setOrigin (indent, 0);

        if (g.reduceClipRegion (0, 0, itemW, itemHeight))
            paintItem (g, itemW, itemHeight, itemUnderMouse == this); // <-

        g.restoreState();
    }

    // [...]

            if (relY + ti->totalHeight >= clip.getY())
            {
                g.saveState();
                g.setOrigin (0, relY);

                if (g.reduceClipRegion (0, 0, width, ti->totalHeight))
                    ti->paintRecursively (g, width, itemUnderMouse, isMouseOverPlusMinusBox);  // <-
// juce_LookAndFeel.h:270

    virtual void drawTreeviewPlusMinusBox (Graphics& g, int x, int y, int w, int h, bool isPlus, bool isMouseOver);

Ok, the only drawback there is any existing code that uses TreeViewItem::paintItem will silently break… I’m doing some treeview work today anyway, so will see what I can do…

great, thanks !

Hey Jules, while you’re messing around with the TreeView, any chance of:

  1. adding a paintPlusMinus() method in the actual TreeViewItem class. There are times where it might be useful to override the plus/minus glyph defined in LAF on an item by item basis.

  2. conversely, any chance of placing the code to draw the selection rectangle into LookAndFeel. In this case, I specifically need to be able to extend the selection back past the item’s indent (either to cover the plus/minus area, or even all the way back to the left edge of the treeview).

For examples of why, consider OSX’s tree panels (Finder, or iTunes). By turning plus/minus into a triangle, and running the selection bar all the way across the tree, you could easily emulate OSX’s simple navigation panel.

Ok, I’ve just checked in some changes. I’ve done the plus/minus box stuff, but couldn’t really see how to change the row-highlighting code. At the moment it’s all drawn in the item’s paint routine, so it can’t draw beyond the left-hand boundary. To alter that would involve restructuring quite a bit…

Thanks Jules, but as I asked you, would it be possible to have also the mouseover flag TreeViewItem::paintItem() ?

I need to draw a line somewhere about how much component-like behaviour goes into the item objects, and that feels like a bit too much. It’d mean every treeview out there would need rewriting, and also that all rows would need to be repainted every time the mouse moves, when in 99% of cases that’s not necessary. You can always just use a custom row component to do the job.

No problem, I’ll use a custom component then.