123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807 |
- /*
- ==============================================================================
- This file is part of the JUCE library.
- Copyright (c) 2017 - ROLI Ltd.
- JUCE is an open source library subject to commercial or open-source
- licensing.
- By using JUCE, you agree to the terms of both the JUCE 5 End-User License
- Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
- 27th April 2017).
- End User License Agreement: www.juce.com/juce-5-licence
- Privacy Policy: www.juce.com/juce-5-privacy-policy
- Or: You may also use this code under the terms of the GPL v3 (see
- www.gnu.org/licenses).
- JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
- EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
- DISCLAIMED.
- ==============================================================================
- */
- namespace juce
- {
- const char* const Toolbar::toolbarDragDescriptor = "_toolbarItem_";
- //==============================================================================
- class Toolbar::Spacer : public ToolbarItemComponent
- {
- public:
- Spacer (int itemID, float sizeToUse, bool shouldDrawBar)
- : ToolbarItemComponent (itemID, {}, false),
- fixedSize (sizeToUse),
- drawBar (shouldDrawBar)
- {
- setWantsKeyboardFocus (false);
- }
- bool getToolbarItemSizes (int toolbarThickness, bool /*isToolbarVertical*/,
- int& preferredSize, int& minSize, int& maxSize) override
- {
- if (fixedSize <= 0)
- {
- preferredSize = toolbarThickness * 2;
- minSize = 4;
- maxSize = 32768;
- }
- else
- {
- maxSize = roundToInt (toolbarThickness * fixedSize);
- minSize = drawBar ? maxSize : jmin (4, maxSize);
- preferredSize = maxSize;
- if (getEditingMode() == editableOnPalette)
- preferredSize = maxSize = toolbarThickness / (drawBar ? 3 : 2);
- }
- return true;
- }
- void paintButtonArea (Graphics&, int, int, bool, bool) override
- {
- }
- void contentAreaChanged (const Rectangle<int>&) override
- {
- }
- int getResizeOrder() const noexcept
- {
- return fixedSize <= 0 ? 0 : 1;
- }
- void paint (Graphics& g) override
- {
- auto w = getWidth();
- auto h = getHeight();
- if (drawBar)
- {
- g.setColour (findColour (Toolbar::separatorColourId, true));
- auto thickness = 0.2f;
- if (isToolbarVertical())
- g.fillRect (w * 0.1f, h * (0.5f - thickness * 0.5f), w * 0.8f, h * thickness);
- else
- g.fillRect (w * (0.5f - thickness * 0.5f), h * 0.1f, w * thickness, h * 0.8f);
- }
- if (getEditingMode() != normalMode && ! drawBar)
- {
- g.setColour (findColour (Toolbar::separatorColourId, true));
- auto indentX = jmin (2, (w - 3) / 2);
- auto indentY = jmin (2, (h - 3) / 2);
- g.drawRect (indentX, indentY, w - indentX * 2, h - indentY * 2, 1);
- if (fixedSize <= 0)
- {
- float x1, y1, x2, y2, x3, y3, x4, y4, hw, hl;
- if (isToolbarVertical())
- {
- x1 = w * 0.5f;
- y1 = h * 0.4f;
- x2 = x1;
- y2 = indentX * 2.0f;
- x3 = x1;
- y3 = h * 0.6f;
- x4 = x1;
- y4 = h - y2;
- hw = w * 0.15f;
- hl = w * 0.2f;
- }
- else
- {
- x1 = w * 0.4f;
- y1 = h * 0.5f;
- x2 = indentX * 2.0f;
- y2 = y1;
- x3 = w * 0.6f;
- y3 = y1;
- x4 = w - x2;
- y4 = y1;
- hw = h * 0.15f;
- hl = h * 0.2f;
- }
- Path p;
- p.addArrow ({ x1, y1, x2, y2 }, 1.5f, hw, hl);
- p.addArrow ({ x3, y3, x4, y4 }, 1.5f, hw, hl);
- g.fillPath (p);
- }
- }
- }
- private:
- const float fixedSize;
- const bool drawBar;
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Spacer)
- };
- //==============================================================================
- class Toolbar::MissingItemsComponent : public PopupMenu::CustomComponent
- {
- public:
- MissingItemsComponent (Toolbar& bar, int h)
- : PopupMenu::CustomComponent (true),
- owner (&bar),
- height (h)
- {
- for (int i = bar.items.size(); --i >= 0;)
- {
- auto* tc = bar.items.getUnchecked(i);
- if (dynamic_cast<Spacer*> (tc) == nullptr && ! tc->isVisible())
- {
- oldIndexes.insert (0, i);
- addAndMakeVisible (tc, 0);
- }
- }
- layout (400);
- }
- ~MissingItemsComponent() override
- {
- if (owner != nullptr)
- {
- for (int i = 0; i < getNumChildComponents(); ++i)
- {
- if (auto* tc = dynamic_cast<ToolbarItemComponent*> (getChildComponent (i)))
- {
- tc->setVisible (false);
- auto index = oldIndexes.removeAndReturn (i);
- owner->addChildComponent (tc, index);
- --i;
- }
- }
- owner->resized();
- }
- }
- void layout (const int preferredWidth)
- {
- const int indent = 8;
- auto x = indent;
- auto y = indent;
- int maxX = 0;
- for (auto* c : getChildren())
- {
- if (auto* tc = dynamic_cast<ToolbarItemComponent*> (c))
- {
- int preferredSize = 1, minSize = 1, maxSize = 1;
- if (tc->getToolbarItemSizes (height, false, preferredSize, minSize, maxSize))
- {
- if (x + preferredSize > preferredWidth && x > indent)
- {
- x = indent;
- y += height;
- }
- tc->setBounds (x, y, preferredSize, height);
- x += preferredSize;
- maxX = jmax (maxX, x);
- }
- }
- }
- setSize (maxX + 8, y + height + 8);
- }
- void getIdealSize (int& idealWidth, int& idealHeight) override
- {
- idealWidth = getWidth();
- idealHeight = getHeight();
- }
- private:
- Component::SafePointer<Toolbar> owner;
- const int height;
- Array<int> oldIndexes;
- JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MissingItemsComponent)
- };
- //==============================================================================
- Toolbar::Toolbar()
- {
- missingItemsButton.reset (getLookAndFeel().createToolbarMissingItemsButton (*this));
- addChildComponent (missingItemsButton.get());
- missingItemsButton->setAlwaysOnTop (true);
- missingItemsButton->onClick = [this] { showMissingItems(); };
- }
- Toolbar::~Toolbar()
- {
- items.clear();
- }
- void Toolbar::setVertical (const bool shouldBeVertical)
- {
- if (vertical != shouldBeVertical)
- {
- vertical = shouldBeVertical;
- resized();
- }
- }
- void Toolbar::clear()
- {
- items.clear();
- resized();
- }
- ToolbarItemComponent* Toolbar::createItem (ToolbarItemFactory& factory, const int itemId)
- {
- if (itemId == ToolbarItemFactory::separatorBarId) return new Spacer (itemId, 0.1f, true);
- if (itemId == ToolbarItemFactory::spacerId) return new Spacer (itemId, 0.5f, false);
- if (itemId == ToolbarItemFactory::flexibleSpacerId) return new Spacer (itemId, 0.0f, false);
- return factory.createItem (itemId);
- }
- void Toolbar::addItemInternal (ToolbarItemFactory& factory,
- const int itemId,
- const int insertIndex)
- {
- // An ID can't be zero - this might indicate a mistake somewhere?
- jassert (itemId != 0);
- if (auto* tc = createItem (factory, itemId))
- {
- #if JUCE_DEBUG
- Array<int> allowedIds;
- factory.getAllToolbarItemIds (allowedIds);
- // If your factory can create an item for a given ID, it must also return
- // that ID from its getAllToolbarItemIds() method!
- jassert (allowedIds.contains (itemId));
- #endif
- items.insert (insertIndex, tc);
- addAndMakeVisible (tc, insertIndex);
- }
- }
- void Toolbar::addItem (ToolbarItemFactory& factory, int itemId, int insertIndex)
- {
- addItemInternal (factory, itemId, insertIndex);
- resized();
- }
- void Toolbar::addDefaultItems (ToolbarItemFactory& factoryToUse)
- {
- Array<int> ids;
- factoryToUse.getDefaultItemSet (ids);
- clear();
- for (auto i : ids)
- addItemInternal (factoryToUse, i, -1);
- resized();
- }
- void Toolbar::removeToolbarItem (const int itemIndex)
- {
- items.remove (itemIndex);
- resized();
- }
- ToolbarItemComponent* Toolbar::removeAndReturnItem (const int itemIndex)
- {
- if (auto* tc = items.removeAndReturn (itemIndex))
- {
- removeChildComponent (tc);
- resized();
- return tc;
- }
- return nullptr;
- }
- int Toolbar::getNumItems() const noexcept
- {
- return items.size();
- }
- int Toolbar::getItemId (const int itemIndex) const noexcept
- {
- if (auto* tc = getItemComponent (itemIndex))
- return tc->getItemId();
- return 0;
- }
- ToolbarItemComponent* Toolbar::getItemComponent (const int itemIndex) const noexcept
- {
- return items[itemIndex];
- }
- ToolbarItemComponent* Toolbar::getNextActiveComponent (int index, const int delta) const
- {
- for (;;)
- {
- index += delta;
- if (auto* tc = getItemComponent (index))
- {
- if (tc->isActive)
- return tc;
- }
- else
- {
- return nullptr;
- }
- }
- }
- void Toolbar::setStyle (const ToolbarItemStyle& newStyle)
- {
- if (toolbarStyle != newStyle)
- {
- toolbarStyle = newStyle;
- updateAllItemPositions (false);
- }
- }
- String Toolbar::toString() const
- {
- String s ("TB:");
- for (int i = 0; i < getNumItems(); ++i)
- s << getItemId(i) << ' ';
- return s.trimEnd();
- }
- bool Toolbar::restoreFromString (ToolbarItemFactory& factoryToUse,
- const String& savedVersion)
- {
- if (! savedVersion.startsWith ("TB:"))
- return false;
- StringArray tokens;
- tokens.addTokens (savedVersion.substring (3), false);
- clear();
- for (auto& t : tokens)
- addItemInternal (factoryToUse, t.getIntValue(), -1);
- resized();
- return true;
- }
- void Toolbar::paint (Graphics& g)
- {
- getLookAndFeel().paintToolbarBackground (g, getWidth(), getHeight(), *this);
- }
- int Toolbar::getThickness() const noexcept
- {
- return vertical ? getWidth() : getHeight();
- }
- int Toolbar::getLength() const noexcept
- {
- return vertical ? getHeight() : getWidth();
- }
- void Toolbar::setEditingActive (const bool active)
- {
- if (isEditingActive != active)
- {
- isEditingActive = active;
- updateAllItemPositions (false);
- }
- }
- //==============================================================================
- void Toolbar::resized()
- {
- updateAllItemPositions (false);
- }
- void Toolbar::updateAllItemPositions (bool animate)
- {
- if (getWidth() > 0 && getHeight() > 0)
- {
- StretchableObjectResizer resizer;
- for (auto* tc : items)
- {
- tc->setEditingMode (isEditingActive ? ToolbarItemComponent::editableOnToolbar
- : ToolbarItemComponent::normalMode);
- tc->setStyle (toolbarStyle);
- auto* spacer = dynamic_cast<Spacer*> (tc);
- int preferredSize = 1, minSize = 1, maxSize = 1;
- if (tc->getToolbarItemSizes (getThickness(), isVertical(),
- preferredSize, minSize, maxSize))
- {
- tc->isActive = true;
- resizer.addItem (preferredSize, minSize, maxSize,
- spacer != nullptr ? spacer->getResizeOrder() : 2);
- }
- else
- {
- tc->isActive = false;
- tc->setVisible (false);
- }
- }
- resizer.resizeToFit (getLength());
- int totalLength = 0;
- for (int i = 0; i < resizer.getNumItems(); ++i)
- totalLength += (int) resizer.getItemSize (i);
- const bool itemsOffTheEnd = totalLength > getLength();
- auto extrasButtonSize = getThickness() / 2;
- missingItemsButton->setSize (extrasButtonSize, extrasButtonSize);
- missingItemsButton->setVisible (itemsOffTheEnd);
- missingItemsButton->setEnabled (! isEditingActive);
- if (vertical)
- missingItemsButton->setCentrePosition (getWidth() / 2,
- getHeight() - 4 - extrasButtonSize / 2);
- else
- missingItemsButton->setCentrePosition (getWidth() - 4 - extrasButtonSize / 2,
- getHeight() / 2);
- auto maxLength = itemsOffTheEnd ? (vertical ? missingItemsButton->getY()
- : missingItemsButton->getX()) - 4
- : getLength();
- int pos = 0, activeIndex = 0;
- for (auto* tc : items)
- {
- if (tc->isActive)
- {
- auto size = (int) resizer.getItemSize (activeIndex++);
- Rectangle<int> newBounds;
- if (vertical)
- newBounds.setBounds (0, pos, getWidth(), size);
- else
- newBounds.setBounds (pos, 0, size, getHeight());
- auto& animator = Desktop::getInstance().getAnimator();
- if (animate)
- {
- animator.animateComponent (tc, newBounds, 1.0f, 200, false, 3.0, 0.0);
- }
- else
- {
- animator.cancelAnimation (tc, false);
- tc->setBounds (newBounds);
- }
- pos += size;
- tc->setVisible (pos <= maxLength
- && ((! tc->isBeingDragged)
- || tc->getEditingMode() == ToolbarItemComponent::editableOnPalette));
- }
- }
- }
- }
- //==============================================================================
- void Toolbar::showMissingItems()
- {
- jassert (missingItemsButton->isShowing());
- if (missingItemsButton->isShowing())
- {
- PopupMenu m;
- auto comp = std::make_unique<MissingItemsComponent> (*this, getThickness());
- m.addCustomItem (1, std::move (comp));
- m.showMenuAsync (PopupMenu::Options().withTargetComponent (missingItemsButton.get()));
- }
- }
- //==============================================================================
- bool Toolbar::isInterestedInDragSource (const SourceDetails& dragSourceDetails)
- {
- return dragSourceDetails.description == toolbarDragDescriptor && isEditingActive;
- }
- void Toolbar::itemDragMove (const SourceDetails& dragSourceDetails)
- {
- if (auto* tc = dynamic_cast<ToolbarItemComponent*> (dragSourceDetails.sourceComponent.get()))
- {
- if (! items.contains (tc))
- {
- if (tc->getEditingMode() == ToolbarItemComponent::editableOnPalette)
- {
- if (auto* palette = tc->findParentComponentOfClass<ToolbarItemPalette>())
- palette->replaceComponent (*tc);
- }
- else
- {
- jassert (tc->getEditingMode() == ToolbarItemComponent::editableOnToolbar);
- }
- items.add (tc);
- addChildComponent (tc);
- updateAllItemPositions (true);
- }
- auto& animator = Desktop::getInstance().getAnimator();
- for (int i = getNumItems(); --i >= 0;)
- {
- auto currentIndex = items.indexOf (tc);
- auto newIndex = currentIndex;
- auto dragObjectLeft = vertical ? (dragSourceDetails.localPosition.getY() - tc->dragOffsetY)
- : (dragSourceDetails.localPosition.getX() - tc->dragOffsetX);
- auto dragObjectRight = dragObjectLeft + (vertical ? tc->getHeight() : tc->getWidth());
- auto current = animator.getComponentDestination (getChildComponent (newIndex));
- if (auto* prev = getNextActiveComponent (newIndex, -1))
- {
- auto previousPos = animator.getComponentDestination (prev);
- if (std::abs (dragObjectLeft - (vertical ? previousPos.getY() : previousPos.getX()))
- < std::abs (dragObjectRight - (vertical ? current.getBottom() : current.getRight())))
- {
- newIndex = getIndexOfChildComponent (prev);
- }
- }
- if (auto* next = getNextActiveComponent (newIndex, 1))
- {
- auto nextPos = animator.getComponentDestination (next);
- if (std::abs (dragObjectLeft - (vertical ? current.getY() : current.getX()))
- > std::abs (dragObjectRight - (vertical ? nextPos.getBottom() : nextPos.getRight())))
- {
- newIndex = getIndexOfChildComponent (next) + 1;
- }
- }
- if (newIndex == currentIndex)
- break;
- items.removeObject (tc, false);
- removeChildComponent (tc);
- addChildComponent (tc, newIndex);
- items.insert (newIndex, tc);
- updateAllItemPositions (true);
- }
- }
- }
- void Toolbar::itemDragExit (const SourceDetails& dragSourceDetails)
- {
- if (auto* tc = dynamic_cast<ToolbarItemComponent*> (dragSourceDetails.sourceComponent.get()))
- {
- if (isParentOf (tc))
- {
- items.removeObject (tc, false);
- removeChildComponent (tc);
- updateAllItemPositions (true);
- }
- }
- }
- void Toolbar::itemDropped (const SourceDetails& dragSourceDetails)
- {
- if (auto* tc = dynamic_cast<ToolbarItemComponent*> (dragSourceDetails.sourceComponent.get()))
- tc->setState (Button::buttonNormal);
- }
- void Toolbar::mouseDown (const MouseEvent&) {}
- //==============================================================================
- class Toolbar::CustomisationDialog : public DialogWindow
- {
- public:
- CustomisationDialog (ToolbarItemFactory& factory, Toolbar& bar, int optionFlags)
- : DialogWindow (TRANS("Add/remove items from toolbar"), Colours::white, true, true),
- toolbar (bar)
- {
- setContentOwned (new CustomiserPanel (factory, toolbar, optionFlags), true);
- setResizable (true, true);
- setResizeLimits (400, 300, 1500, 1000);
- positionNearBar();
- }
- ~CustomisationDialog() override
- {
- toolbar.setEditingActive (false);
- }
- void closeButtonPressed() override
- {
- setVisible (false);
- }
- bool canModalEventBeSentToComponent (const Component* comp) override
- {
- return toolbar.isParentOf (comp)
- || dynamic_cast<const ToolbarItemComponent::ItemDragAndDropOverlayComponent*> (comp) != nullptr;
- }
- void positionNearBar()
- {
- auto screenSize = toolbar.getParentMonitorArea();
- auto pos = toolbar.getScreenPosition();
- const int gap = 8;
- if (toolbar.isVertical())
- {
- if (pos.x > screenSize.getCentreX())
- pos.x -= getWidth() - gap;
- else
- pos.x += toolbar.getWidth() + gap;
- }
- else
- {
- pos.x += (toolbar.getWidth() - getWidth()) / 2;
- if (pos.y > screenSize.getCentreY())
- pos.y -= getHeight() - gap;
- else
- pos.y += toolbar.getHeight() + gap;
- }
- setTopLeftPosition (pos);
- }
- private:
- Toolbar& toolbar;
- class CustomiserPanel : public Component
- {
- public:
- CustomiserPanel (ToolbarItemFactory& tbf, Toolbar& bar, int optionFlags)
- : factory (tbf), toolbar (bar), palette (tbf, bar),
- instructions ({}, TRANS ("You can drag the items above and drop them onto a toolbar to add them.")
- + "\n\n"
- + TRANS ("Items on the toolbar can also be dragged around to change their order, or dragged off the edge to delete them.")),
- defaultButton (TRANS ("Restore to default set of items"))
- {
- addAndMakeVisible (palette);
- if ((optionFlags & (Toolbar::allowIconsOnlyChoice
- | Toolbar::allowIconsWithTextChoice
- | Toolbar::allowTextOnlyChoice)) != 0)
- {
- addAndMakeVisible (styleBox);
- styleBox.setEditableText (false);
- if ((optionFlags & Toolbar::allowIconsOnlyChoice) != 0) styleBox.addItem (TRANS("Show icons only"), 1);
- if ((optionFlags & Toolbar::allowIconsWithTextChoice) != 0) styleBox.addItem (TRANS("Show icons and descriptions"), 2);
- if ((optionFlags & Toolbar::allowTextOnlyChoice) != 0) styleBox.addItem (TRANS("Show descriptions only"), 3);
- int selectedStyle = 0;
- switch (bar.getStyle())
- {
- case Toolbar::iconsOnly: selectedStyle = 1; break;
- case Toolbar::iconsWithText: selectedStyle = 2; break;
- case Toolbar::textOnly: selectedStyle = 3; break;
- }
- styleBox.setSelectedId (selectedStyle);
- styleBox.onChange = [this] { updateStyle(); };
- }
- if ((optionFlags & Toolbar::showResetToDefaultsButton) != 0)
- {
- addAndMakeVisible (defaultButton);
- defaultButton.onClick = [this] { toolbar.addDefaultItems (factory); };
- }
- addAndMakeVisible (instructions);
- instructions.setFont (Font (13.0f));
- setSize (500, 300);
- }
- void updateStyle()
- {
- switch (styleBox.getSelectedId())
- {
- case 1: toolbar.setStyle (Toolbar::iconsOnly); break;
- case 2: toolbar.setStyle (Toolbar::iconsWithText); break;
- case 3: toolbar.setStyle (Toolbar::textOnly); break;
- }
- palette.resized(); // to make it update the styles
- }
- void paint (Graphics& g) override
- {
- Colour background;
- if (auto* dw = findParentComponentOfClass<DialogWindow>())
- background = dw->getBackgroundColour();
- g.setColour (background.contrasting().withAlpha (0.3f));
- g.fillRect (palette.getX(), palette.getBottom() - 1, palette.getWidth(), 1);
- }
- void resized() override
- {
- palette.setBounds (0, 0, getWidth(), getHeight() - 120);
- styleBox.setBounds (10, getHeight() - 110, 200, 22);
- defaultButton.changeWidthToFitText (22);
- defaultButton.setTopLeftPosition (240, getHeight() - 110);
- instructions.setBounds (10, getHeight() - 80, getWidth() - 20, 80);
- }
- private:
- ToolbarItemFactory& factory;
- Toolbar& toolbar;
- ToolbarItemPalette palette;
- Label instructions;
- ComboBox styleBox;
- TextButton defaultButton;
- };
- };
- void Toolbar::showCustomisationDialog (ToolbarItemFactory& factory, const int optionFlags)
- {
- setEditingActive (true);
- (new CustomisationDialog (factory, *this, optionFlags))
- ->enterModalState (true, nullptr, true);
- }
- } // namespace juce
|