25bd5d8adb
subrepo: subdir: "deps/juce" merged: "b13f9084e" upstream: origin: "https://github.com/essej/JUCE.git" branch: "sono6good" commit: "b13f9084e" git-subrepo: version: "0.4.3" origin: "https://github.com/ingydotnet/git-subrepo.git" commit: "2f68596"
582 lines
22 KiB
C++
582 lines
22 KiB
C++
/*
|
|
==============================================================================
|
|
|
|
This file is part of the JUCE library.
|
|
Copyright (c) 2020 - Raw Material Software Limited
|
|
|
|
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 6 End-User License
|
|
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
|
|
|
|
End User License Agreement: www.juce.com/juce-6-licence
|
|
Privacy Policy: www.juce.com/juce-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.
|
|
|
|
==============================================================================
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include "../../Application/jucer_CommonHeaders.h"
|
|
#include "../../Application/jucer_Application.h"
|
|
|
|
//==============================================================================
|
|
class MessagesPopupWindow : public Component,
|
|
private ComponentMovementWatcher
|
|
{
|
|
public:
|
|
MessagesPopupWindow (Component& target, Component& parent, Project& project)
|
|
: ComponentMovementWatcher (&parent),
|
|
targetComponent (target),
|
|
parentComponent (parent),
|
|
messagesListComponent (*this, project)
|
|
{
|
|
parentComponent.addAndMakeVisible (this);
|
|
setAlwaysOnTop (true);
|
|
|
|
addAndMakeVisible (viewport);
|
|
viewport.setScrollBarsShown (true, false);
|
|
viewport.setViewedComponent (&messagesListComponent, false);
|
|
viewport.setWantsKeyboardFocus (false);
|
|
|
|
setOpaque (true);
|
|
}
|
|
|
|
void paint (Graphics& g) override
|
|
{
|
|
g.fillAll (findColour (secondaryBackgroundColourId));
|
|
}
|
|
|
|
void resized() override
|
|
{
|
|
viewport.setBounds (getLocalBounds());
|
|
}
|
|
|
|
bool isListShowing() const
|
|
{
|
|
return messagesListComponent.getRequiredHeight() > 0;
|
|
}
|
|
|
|
void updateBounds (bool animate)
|
|
{
|
|
auto targetBounds = parentComponent.getLocalArea (&targetComponent, targetComponent.getLocalBounds());
|
|
|
|
auto height = jmin (messagesListComponent.getRequiredHeight(), maxHeight);
|
|
auto yPos = jmax (indent, targetBounds.getY() - height);
|
|
|
|
Rectangle<int> bounds (targetBounds.getX(), yPos,
|
|
jmin (width, parentComponent.getWidth() - targetBounds.getX() - indent), targetBounds.getY() - yPos);
|
|
|
|
auto& animator = Desktop::getInstance().getAnimator();
|
|
|
|
if (animate)
|
|
{
|
|
setBounds (bounds.withY (targetBounds.getY()));
|
|
animator.animateComponent (this, bounds, 1.0f, 150, false, 1.0, 1.0);
|
|
}
|
|
else
|
|
{
|
|
if (animator.isAnimating (this))
|
|
animator.cancelAnimation (this, false);
|
|
|
|
setBounds (bounds);
|
|
}
|
|
|
|
messagesListComponent.resized();
|
|
}
|
|
|
|
private:
|
|
//==============================================================================
|
|
class MessagesListComponent : public Component,
|
|
private ValueTree::Listener,
|
|
private AsyncUpdater
|
|
{
|
|
public:
|
|
MessagesListComponent (MessagesPopupWindow& o, Project& currentProject)
|
|
: owner (o),
|
|
project (currentProject)
|
|
{
|
|
messagesTree = project.getProjectMessages();
|
|
messagesTree.addListener (this);
|
|
|
|
setOpaque (true);
|
|
|
|
messagesChanged();
|
|
}
|
|
|
|
void resized() override
|
|
{
|
|
auto bounds = getLocalBounds();
|
|
auto numMessages = messages.size();
|
|
|
|
for (size_t i = 0; i < numMessages; ++i)
|
|
{
|
|
messages[i]->setBounds (bounds.removeFromTop (messageHeight));
|
|
|
|
if (numMessages > 1 && i != (numMessages - 1))
|
|
bounds.removeFromTop (messageSpacing);
|
|
}
|
|
}
|
|
|
|
void paint (Graphics& g) override
|
|
{
|
|
g.fillAll (findColour (backgroundColourId).contrasting (0.2f));
|
|
}
|
|
|
|
int getRequiredHeight() const
|
|
{
|
|
auto numMessages = (int) messages.size();
|
|
|
|
if (numMessages > 0)
|
|
return (numMessages * messageHeight) + ((numMessages - 1) * messageSpacing);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void updateSize (int parentWidth)
|
|
{
|
|
setSize (parentWidth, getRequiredHeight());
|
|
}
|
|
|
|
private:
|
|
static constexpr int messageHeight = 65;
|
|
static constexpr int messageSpacing = 2;
|
|
|
|
//==============================================================================
|
|
struct MessageComponent : public Component
|
|
{
|
|
MessageComponent (MessagesListComponent& listComponent,
|
|
const Identifier& messageToDisplay,
|
|
std::vector<ProjectMessages::MessageAction> messageActions)
|
|
: message (messageToDisplay)
|
|
{
|
|
for (auto& action : messageActions)
|
|
{
|
|
auto button = std::make_unique<TextButton> (action.first);
|
|
addAndMakeVisible (*button);
|
|
button->onClick = action.second;
|
|
|
|
buttons.push_back (std::move (button));
|
|
}
|
|
|
|
icon = (ProjectMessages::getTypeForMessage (message) == ProjectMessages::Ids::warning ? getIcons().warning : getIcons().info);
|
|
|
|
messageTitleLabel.setText (ProjectMessages::getTitleForMessage (message), dontSendNotification);
|
|
messageTitleLabel.setFont (Font (11.0f).boldened());
|
|
addAndMakeVisible (messageTitleLabel);
|
|
|
|
messageDescriptionLabel.setText (ProjectMessages::getDescriptionForMessage (message), dontSendNotification);
|
|
messageDescriptionLabel.setFont (Font (11.0f));
|
|
messageDescriptionLabel.setJustificationType (Justification::topLeft);
|
|
addAndMakeVisible (messageDescriptionLabel);
|
|
|
|
dismissButton.setShape (getLookAndFeel().getCrossShape (1.0f), false, true, false);
|
|
addAndMakeVisible (dismissButton);
|
|
|
|
dismissButton.onClick = [this, &listComponent]
|
|
{
|
|
listComponent.messagesTree.getChildWithName (ProjectMessages::getTypeForMessage (message))
|
|
.getChildWithName (message)
|
|
.setProperty (ProjectMessages::Ids::isVisible, false, nullptr);
|
|
};
|
|
}
|
|
|
|
void paint (Graphics& g) override
|
|
{
|
|
g.fillAll (findColour (secondaryBackgroundColourId).contrasting (0.1f));
|
|
|
|
auto bounds = getLocalBounds().reduced (5);
|
|
|
|
g.setColour (findColour (defaultIconColourId));
|
|
g.fillPath (icon, icon.getTransformToScaleToFit (bounds.removeFromTop (messageTitleHeight)
|
|
.removeFromLeft (messageTitleHeight).toFloat(), true));
|
|
}
|
|
|
|
void resized() override
|
|
{
|
|
auto bounds = getLocalBounds().reduced (5);
|
|
|
|
auto topSlice = bounds.removeFromTop (messageTitleHeight);
|
|
|
|
topSlice.removeFromLeft (messageTitleHeight + 5);
|
|
topSlice.removeFromRight (5);
|
|
|
|
dismissButton.setBounds (topSlice.removeFromRight (messageTitleHeight));
|
|
messageTitleLabel.setBounds (topSlice);
|
|
bounds.removeFromTop (5);
|
|
|
|
auto numButtons = (int) buttons.size();
|
|
|
|
if (numButtons > 0)
|
|
{
|
|
auto buttonBounds = bounds.removeFromBottom (buttonHeight);
|
|
|
|
auto buttonWidth = roundToInt ((float) buttonBounds.getWidth() / 3.5f);
|
|
auto requiredWidth = (numButtons * buttonWidth) + ((numButtons - 1) * buttonSpacing);
|
|
buttonBounds.reduce ((buttonBounds.getWidth() - requiredWidth) / 2, 0);
|
|
|
|
for (auto& b : buttons)
|
|
{
|
|
b->setBounds (buttonBounds.removeFromLeft (buttonWidth));
|
|
buttonBounds.removeFromLeft (buttonSpacing);
|
|
}
|
|
|
|
bounds.removeFromBottom (5);
|
|
}
|
|
|
|
messageDescriptionLabel.setBounds (bounds);
|
|
}
|
|
|
|
static constexpr int messageTitleHeight = 11;
|
|
static constexpr int buttonHeight = messageHeight / 4;
|
|
static constexpr int buttonSpacing = 5;
|
|
|
|
Identifier message;
|
|
|
|
Path icon;
|
|
Label messageTitleLabel, messageDescriptionLabel;
|
|
std::vector<std::unique_ptr<TextButton>> buttons;
|
|
ShapeButton dismissButton { {},
|
|
findColour (treeIconColourId),
|
|
findColour (treeIconColourId).overlaidWith (findColour (defaultHighlightedTextColourId).withAlpha (0.2f)),
|
|
findColour (treeIconColourId).overlaidWith (findColour (defaultHighlightedTextColourId).withAlpha (0.4f)) };
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessageComponent)
|
|
};
|
|
|
|
//==============================================================================
|
|
void valueTreePropertyChanged (ValueTree&, const Identifier&) override { messagesChanged(); }
|
|
void valueTreeChildAdded (ValueTree&, ValueTree&) override { messagesChanged(); }
|
|
void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override { messagesChanged(); }
|
|
void valueTreeChildOrderChanged (ValueTree&, int, int) override { messagesChanged(); }
|
|
void valueTreeParentChanged (ValueTree&) override { messagesChanged(); }
|
|
void valueTreeRedirected (ValueTree&) override { messagesChanged(); }
|
|
|
|
void handleAsyncUpdate() override
|
|
{
|
|
messagesChanged();
|
|
}
|
|
|
|
void messagesChanged()
|
|
{
|
|
auto listWasShowing = (getHeight() > 0);
|
|
|
|
auto warningsTree = messagesTree.getChildWithName (ProjectMessages::Ids::warning);
|
|
auto notificationsTree = messagesTree.getChildWithName (ProjectMessages::Ids::notification);
|
|
|
|
auto removePredicate = [warningsTree, notificationsTree] (std::unique_ptr<MessageComponent>& messageComponent)
|
|
{
|
|
for (int i = 0; i < warningsTree.getNumChildren(); ++i)
|
|
{
|
|
auto child = warningsTree.getChild (i);
|
|
|
|
if (child.getType() == messageComponent->message
|
|
&& child.getProperty (ProjectMessages::Ids::isVisible))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < notificationsTree.getNumChildren(); ++i)
|
|
{
|
|
auto child = notificationsTree.getChild (i);
|
|
|
|
if (child.getType() == messageComponent->message
|
|
&& child.getProperty (ProjectMessages::Ids::isVisible))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
messages.erase (std::remove_if (std::begin (messages), std::end (messages), removePredicate),
|
|
std::end (messages));
|
|
|
|
for (int i = 0; i < warningsTree.getNumChildren(); ++i)
|
|
{
|
|
auto child = warningsTree.getChild (i);
|
|
|
|
if (! child.getProperty (ProjectMessages::Ids::isVisible))
|
|
continue;
|
|
|
|
if (std::find_if (std::begin (messages), std::end (messages),
|
|
[child] (const std::unique_ptr<MessageComponent>& messageComponent) { return messageComponent->message == child.getType(); })
|
|
== std::end (messages))
|
|
{
|
|
messages.push_back (std::make_unique<MessageComponent> (*this, child.getType(), project.getMessageActions (child.getType())));
|
|
addAndMakeVisible (*messages.back());
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < notificationsTree.getNumChildren(); ++i)
|
|
{
|
|
auto child = notificationsTree.getChild (i);
|
|
|
|
if (! child.getProperty (ProjectMessages::Ids::isVisible))
|
|
continue;
|
|
|
|
if (std::find_if (std::begin (messages), std::end (messages),
|
|
[child] (const std::unique_ptr<MessageComponent>& messageComponent) { return messageComponent->message == child.getType(); })
|
|
== std::end (messages))
|
|
{
|
|
messages.push_back (std::make_unique<MessageComponent> (*this, child.getType(), project.getMessageActions (child.getType())));
|
|
addAndMakeVisible (*messages.back());
|
|
}
|
|
}
|
|
|
|
auto isNowShowing = (messages.size() > 0);
|
|
|
|
owner.updateBounds (isNowShowing != listWasShowing);
|
|
updateSize (owner.getWidth());
|
|
}
|
|
|
|
//==============================================================================
|
|
MessagesPopupWindow& owner;
|
|
Project& project;
|
|
|
|
ValueTree messagesTree;
|
|
std::vector<std::unique_ptr<MessageComponent>> messages;
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessagesListComponent)
|
|
};
|
|
|
|
//==============================================================================
|
|
void componentMovedOrResized (bool, bool) override
|
|
{
|
|
if (isListShowing())
|
|
updateBounds (false);
|
|
}
|
|
|
|
using ComponentMovementWatcher::componentMovedOrResized;
|
|
|
|
void componentPeerChanged() override
|
|
{
|
|
if (isListShowing())
|
|
updateBounds (false);
|
|
}
|
|
|
|
void componentVisibilityChanged() override
|
|
{
|
|
if (isListShowing())
|
|
updateBounds (false);
|
|
}
|
|
|
|
using ComponentMovementWatcher::componentVisibilityChanged;
|
|
|
|
//==============================================================================
|
|
static constexpr int maxHeight = 500, width = 350, indent = 20;
|
|
|
|
Component& targetComponent;
|
|
Component& parentComponent;
|
|
|
|
Viewport viewport;
|
|
MessagesListComponent messagesListComponent;
|
|
|
|
//==============================================================================
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MessagesPopupWindow)
|
|
};
|
|
|
|
//==============================================================================
|
|
class ProjectMessagesComponent : public Component
|
|
{
|
|
public:
|
|
ProjectMessagesComponent()
|
|
{
|
|
setFocusContainerType (FocusContainerType::focusContainer);
|
|
setTitle ("Project Messages");
|
|
|
|
addAndMakeVisible (warningsComponent);
|
|
addAndMakeVisible (notificationsComponent);
|
|
|
|
warningsComponent.addMouseListener (this, true);
|
|
notificationsComponent.addMouseListener (this, true);
|
|
|
|
setOpaque (true);
|
|
}
|
|
|
|
//==============================================================================
|
|
void resized() override
|
|
{
|
|
auto b = getLocalBounds();
|
|
|
|
warningsComponent.setBounds (b.removeFromLeft (b.getWidth() / 2).reduced (5));
|
|
notificationsComponent.setBounds (b.reduced (5));
|
|
}
|
|
|
|
void paint (Graphics& g) override
|
|
{
|
|
auto backgroundColour = findColour (backgroundColourId);
|
|
|
|
if (isMouseDown || isMouseOver)
|
|
backgroundColour = backgroundColour.overlaidWith (findColour (defaultHighlightColourId)
|
|
.withAlpha (isMouseDown ? 1.0f : 0.8f));
|
|
|
|
g.fillAll (backgroundColour);
|
|
}
|
|
|
|
//==============================================================================
|
|
void mouseEnter (const MouseEvent&) override
|
|
{
|
|
isMouseOver = true;
|
|
repaint();
|
|
}
|
|
|
|
void mouseExit (const MouseEvent&) override
|
|
{
|
|
isMouseOver = false;
|
|
repaint();
|
|
}
|
|
|
|
void mouseDown (const MouseEvent&) override
|
|
{
|
|
isMouseDown = true;
|
|
repaint();
|
|
}
|
|
|
|
void mouseUp (const MouseEvent&) override
|
|
{
|
|
isMouseDown = false;
|
|
repaint();
|
|
|
|
showOrHideMessagesWindow();
|
|
}
|
|
|
|
std::unique_ptr<AccessibilityHandler> createAccessibilityHandler() override
|
|
{
|
|
return std::make_unique<AccessibilityHandler> (*this,
|
|
AccessibilityRole::button,
|
|
AccessibilityActions().addAction (AccessibilityActionType::press,
|
|
[this] { showOrHideMessagesWindow(); }));
|
|
}
|
|
|
|
//==============================================================================
|
|
void setProject (Project* newProject)
|
|
{
|
|
if (currentProject != newProject)
|
|
{
|
|
currentProject = newProject;
|
|
|
|
if (currentProject != nullptr)
|
|
{
|
|
if (auto* projectWindow = ProjucerApplication::getApp().mainWindowList.getMainWindowForFile (currentProject->getFile()))
|
|
messagesWindow = std::make_unique<MessagesPopupWindow> (*this, *projectWindow, *currentProject);
|
|
|
|
auto projectMessagesTree = currentProject->getProjectMessages();
|
|
|
|
warningsComponent.setTree (projectMessagesTree.getChildWithName (ProjectMessages::Ids::warning));
|
|
notificationsComponent.setTree (projectMessagesTree.getChildWithName (ProjectMessages::Ids::notification));
|
|
}
|
|
else
|
|
{
|
|
warningsComponent.setTree ({});
|
|
notificationsComponent.setTree ({});
|
|
}
|
|
}
|
|
}
|
|
|
|
void numMessagesChanged()
|
|
{
|
|
const auto total = warningsComponent.getNumMessages()
|
|
+ notificationsComponent.getNumMessages();
|
|
|
|
setHelpText (String (total) + (total == 1 ? " message" : " messages"));
|
|
}
|
|
|
|
void showOrHideMessagesWindow()
|
|
{
|
|
if (messagesWindow != nullptr)
|
|
showOrHideAllMessages (! messagesWindow->isListShowing());
|
|
}
|
|
|
|
private:
|
|
//==============================================================================
|
|
struct MessageCountComponent : public Component,
|
|
private ValueTree::Listener
|
|
{
|
|
MessageCountComponent (ProjectMessagesComponent& o, Path pathToUse)
|
|
: owner (o),
|
|
path (pathToUse)
|
|
{
|
|
setInterceptsMouseClicks (false, false);
|
|
}
|
|
|
|
void paint (Graphics& g) override
|
|
{
|
|
auto b = getLocalBounds().toFloat();
|
|
|
|
g.setColour (findColour ((owner.isMouseDown || owner.isMouseOver) ? defaultHighlightedTextColourId : treeIconColourId));
|
|
g.fillPath (path, path.getTransformToScaleToFit (b.removeFromLeft (b.getWidth() / 2.0f), true));
|
|
|
|
b.removeFromLeft (5);
|
|
g.drawFittedText (String (numMessages), b.getSmallestIntegerContainer(), Justification::centredLeft, 1);
|
|
}
|
|
|
|
void setTree (ValueTree tree)
|
|
{
|
|
messagesTree = tree;
|
|
|
|
if (messagesTree.isValid())
|
|
messagesTree.addListener (this);
|
|
|
|
updateNumMessages();
|
|
}
|
|
|
|
void updateNumMessages()
|
|
{
|
|
numMessages = messagesTree.getNumChildren();
|
|
owner.numMessagesChanged();
|
|
repaint();
|
|
}
|
|
|
|
int getNumMessages() const noexcept { return numMessages; }
|
|
|
|
private:
|
|
void valueTreeChildAdded (ValueTree&, ValueTree&) override { updateNumMessages(); }
|
|
void valueTreeChildRemoved (ValueTree&, ValueTree&, int) override { updateNumMessages(); }
|
|
|
|
ProjectMessagesComponent& owner;
|
|
ValueTree messagesTree;
|
|
|
|
Path path;
|
|
int numMessages = 0;
|
|
};
|
|
|
|
void showOrHideAllMessages (bool shouldBeVisible)
|
|
{
|
|
if (currentProject != nullptr)
|
|
{
|
|
auto messagesTree = currentProject->getProjectMessages();
|
|
|
|
auto setVisible = [shouldBeVisible] (ValueTree subTree)
|
|
{
|
|
for (int i = 0; i < subTree.getNumChildren(); ++i)
|
|
subTree.getChild (i).setProperty (ProjectMessages::Ids::isVisible, shouldBeVisible, nullptr);
|
|
};
|
|
|
|
setVisible (messagesTree.getChildWithName (ProjectMessages::Ids::warning));
|
|
setVisible (messagesTree.getChildWithName (ProjectMessages::Ids::notification));
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
Project* currentProject = nullptr;
|
|
bool isMouseOver = false, isMouseDown = false;
|
|
|
|
MessageCountComponent warningsComponent { *this, getIcons().warning },
|
|
notificationsComponent { *this, getIcons().info };
|
|
|
|
std::unique_ptr<MessagesPopupWindow> messagesWindow;
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectMessagesComponent)
|
|
};
|