[Solved] Paint method of Component paints in parentComponent

Hi Fellow Jucers,

I am having a issue with a paint method. What I am trying to do is the write a custom class for a combination of a slider and a combobox. A piece of the graphics is done in LaF for the slider. The rest of the component is done inside the paint method of the class. The class itself derives from component.

To paint the component I use the paintEntireComponent inside the paint method of the PluginEditor class. The problem is that a part of the component paints also at the top of the screen. Far outside of the component bounds. Its like a copy but then at the wrong place. anyone have an idea why this happens? And why only to a part of the paint method inside ComboSlider.

the code:
Comboslider.h:

#pragma once
#include "../JuceLibraryCode/JuceHeader.h"
#include "SynthLookAndFeel.h"
#include "SynthColourScheme.h"

class ComboSlider : public juce::Component
{
public:
ComboSlider();
~ComboSlider();

void paint(juce::Graphics& g) override;
void resized() override;

//----------------------------------------------------------------
//Combobox settings-----------------------------------------------
//----------------------------------------------------------------

void mouseDown(const juce::MouseEvent& event) override;

void setText(juce::String text);
juce::String getItemTextIfChanged(int);

int getItemID();

void switchMenuState();
bool getMenuState();

//----------------------------------------------------------------
//Slider settings-------------------------------------------------
//----------------------------------------------------------------

void setSliderRange(juce::Range<double> _sliderRange, double _sliderInterval);
void setDefaultSliderValue(int sliderDefaultValue);
void setSliderValue(double currentSliderValue);

float getSliderValue();

void addListener (juce::Slider::Listener *listener);
juce::Slider* getSlider();

private:
juce::Slider slider;
synthLookAndFeel_ComboSlider comboSliderLookAndFeel;
juce::PopupMenu popupMenu;

juce::String text;

SynthColourScheme colorScheme;

int currentItemID = 0;
bool menuActive = false;

double currentSliderValue;
};

ComboSlider.cpp:

    #include "ComboSlider.h"


ComboSlider::ComboSlider() {
    setSize(200, 200);
    
    slider.setSliderStyle(juce::Slider::SliderStyle::LinearHorizontal);
    slider.setTextBoxStyle(juce::Slider::TextEntryBoxPosition::NoTextBox, true, 0, 0);
    slider.setLookAndFeel(&comboSliderLookAndFeel);
    addAndMakeVisible(slider);
    
    popupMenu.setLookAndFeel(&comboSliderLookAndFeel);
    popupMenu.addItem(1, "Filter 1");
    popupMenu.addItem(2, "Filter 2");
    popupMenu.addItem(3, "Osc1");
    
}

ComboSlider::~ComboSlider()  {
    slider.setLookAndFeel(nullptr);
}

void ComboSlider::paint(juce::Graphics& g){
    
    
    //drawBackground of the component
    juce::Rectangle<float> background;
    background.setBounds(getX(), getY(), getWidth(), getHeight());
    g.setColour(colorScheme.getComboboxBackgroundColor());
    g.fillRoundedRectangle(background, 3.0f);
    
    //draw outerline of the component
    juce::Rectangle<float> backgroundLine;
    backgroundLine.setBounds(getX(), getY(), getWidth(), getHeight());
    g.setColour(colorScheme.getComboboxDropHandleLineColor());
    g.drawRect(backgroundLine);
        
    //drawDropDownHandler
    juce::Rectangle<int> arrowZone (this->getWidth() - 23, 0, 20, this->getHeight());
    juce::Path path;
    path.startNewSubPath ((float) arrowZone.getX() + 4.0f, (float) arrowZone.getCentreY() - 3.0f);
    path.lineTo ((float) arrowZone.getCentreX()-1.0f, (float) arrowZone.getCentreY() + 2.0f);
    path.lineTo ((float) arrowZone.getRight() - 6.0f, (float) arrowZone.getCentreY() - 3.0f);
    path.closeSubPath();
   
    //choose colors of the combobox handler. Depending on if the popup menu is active or not.
    if (!menuActive){
        g.setColour (colorScheme.getComboboxDropHandleColor());
    } else {
           g.setColour(juce::Colours::lightgrey);
    }
    
    g.strokePath (path, juce::PathStrokeType (2.0f));;
    g.fillPath(path);
    
    g.setOpacity(0.4);
    g.drawLine(this->getWidth()-(this->getHeight()/2)-12, 0, this->getWidth()-(this->getHeight()/2)-12, this->getHeight());
}

void ComboSlider::resized(){
    int x = -12;
    int y = 0;
    int width = getWidth()-(getHeight()/2);
    int height = 30;
    //slider->setBounds(-12, 0, getWidth()-(getHeight()/2), 30);
    std::cout << "X: " << x << std::endl;
    std::cout << "Y: " << y << std::endl;
    std::cout << "Width: " << width << std::endl;
    std::cout << "Height: " << height << std::endl;
    
    slider.setBounds(x, y, width,  height);
    
}

void ComboSlider::mouseDown(const juce::MouseEvent &event) {
    switchMenuState();
    repaint();
    popupMenu.showMenuAsync(juce::PopupMenu::Options().withTargetComponent(this).withMinimumWidth(getWidth()),   [&, this](int result) {
        
        repaint();
        std::cout << "text" << std::endl;
        if (result != 0){
            
            comboSliderLookAndFeel.setText(getItemTextIfChanged(result));
            
        }
        switchMenuState();
        repaint();
    });
}

void ComboSlider::setText(juce::String text) {
    this->text = text;
    std::cout << text << std::endl;
    
    comboSliderLookAndFeel.setText(text);
}

juce::String ComboSlider::getItemTextIfChanged(int index){
    for (juce::PopupMenu::MenuItemIterator iterator (popupMenu, true); iterator.next();)
    {
        auto& item = iterator.getItem();
        if (item.itemID == index ){
            return item.text;
            currentItemID = item.itemID;
        }
    }
    return "";
}

void ComboSlider::switchMenuState(){
    if (!menuActive) {
        menuActive = true;
    } else{
        menuActive = false;
    }
}

//----------------------------------------------------------------
//Slider settings-------------------------------------------------
//----------------------------------------------------------------

void ComboSlider::setSliderRange(juce::Range<double> _sliderRange, double _sliderInterval) {
    slider.setRange(_sliderRange, _sliderInterval);
}

void ComboSlider::setDefaultSliderValue(int sliderDefaultValue) {

}

void ComboSlider::addListener(juce::Slider::Listener *listener) {
    slider.addListener(listener);
}

juce::Slider* ComboSlider::getSlider() {
    return &slider;
}

PluginEditor.cpp

#include "PluginProcessor.h"
#include "PluginEditor.h"

//==============================================================================
SynthesizerAudioProcessorEditor::SynthesizerAudioProcessorEditor (SynthesizerAudioProcessor& p)
: AudioProcessorEditor (&p), audioProcessor (p)
{
// Make sure that before the constructor has finished, you've set the
// editor's size to whatever you need it to be.
setSize (1000, 800);
addAndMakeVisible(box);
}

SynthesizerAudioProcessorEditor::~SynthesizerAudioProcessorEditor()
{
box.setLookAndFeel(nullptr);
}

//==============================================================================
void SynthesizerAudioProcessorEditor::paint (juce::Graphics& g)
{
// (Our component is opaque, so we must completely fill the background with a solid colour)
g.fillAll (colourScheme.getBackgroundColor());

g.setColour (juce::Colours::black);
g.setFont (15.0f);
g.drawText("Search Path: " + juce::File::getSpecialLocation(
    juce::File::SpecialLocationType::userDesktopDirectory)
    .getChildFile("Oval.png").getFullPathName(),
    getLocalBounds().removeFromBottom(70), juce::Justification::centred, true);

box.paintEntireComponent(g, false);
}

void SynthesizerAudioProcessorEditor::resized()
{
// This is generally where you'll want to lay out the positions of any
// subcomponents in your editor..
box.setBounds(200, 200, 200, 30);

}

The answer is in the docs to paintEntireComponent():

You should very rarely have to use this method, it’s simply there in case you need to draw a component with a custom graphics context for some reason, e.g. for creating a snapshot of the component.

By making the box a child using addAndMakeVisible() the paint happens automatically.

The reason why it paints in the wrong places is, that the coordinate system of the parent component is used by using the parents Graphics instance.

Thanks for your answer. That makes sense. The only issue I am running into is that only a part of the paint method is being painted. See the screenshot.

You will have to check all your coordinates, that’s most likely the culprit.
To check which area is actually available for painting, you can add a call

g.fillAll (juce::Colours::red);

To get the area that is available use getLocalBounds() or in the lookandfeel it is often passed on as parameter argument.

Thanks for your reply. I figured it out. By using getX and getY the painting goes outside the component. By just using 0 and 0 it works again. Thank you for helping me out.