I noticed a different behaviour between Windows and OS X, and I can’t get to do what I want on Mac… All I want is simply to have a VST/AU plugin open a dialog window on top of the main GUI window so it’s visible.
Works fine on Windows, but on Mac it appears (and remains) behind the plugin’s window, which is quite inconvenient…
I added of few lines of code to Juce’s DemoEditorComponent.cpp/.h in order to exhibit the behaviour difference between Mac and PC. Extra code is tagged by // EXTRA STUFF // comments. Adding this code and recompiling Juce’s VST (ou AU) plugin demo project will produce a plugin that behaves differently on Win/OSX.
Is it a bug in Juce? If not, what should I do to get on OSX the same behaviour as on Windows?
Here’s the code. To make the dialog pop up, click on an empty area of the plugin’s GUI.
DemoEditorComponent.cpp
/*
==============================================================================
This file is part of the JUCE library - "Jules' Utility Class Extensions"
Copyright 2004-7 by Raw Material Software ltd.
------------------------------------------------------------------------------
JUCE can be redistributed and/or modified under the terms of the
GNU General Public License, as published by the Free Software Foundation;
either version 2 of the License, or (at your option) any later version.
JUCE is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with JUCE; if not, visit www.gnu.org/licenses or write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA
------------------------------------------------------------------------------
If you'd like to release a closed-source product which uses JUCE, commercial
licenses are also available: visit www.rawmaterialsoftware.com/juce for
more information.
==============================================================================
*/
#include "../../../../juce.h"
#include "DemoEditorComponent.h"
// EXTRA STUFF //
class TestDlg: public Component,
public ButtonListener
{
public:
/// Constructors
TestDlg( );
~TestDlg();
void buttonClicked (Button* buttonThatWasClicked);
TextButton* OkButton;
};
TestDlg::TestDlg( )
{
setSize(500, 300);
setAlwaysOnTop(true); // useless...
toFront(true); // useless...
addAndMakeVisible (OkButton = new TextButton (T("button")));
OkButton->setButtonText (T("Ok"));
OkButton->addButtonListener (this);
OkButton->setBounds (200, 250, 80, 24);
}
TestDlg::~TestDlg()
{
deleteAndZero (OkButton);
}
// listeners
void TestDlg::buttonClicked (Button* buttonThatWasClicked)
{
Component *p = getParentComponent();
p->exitModalState(1);
}
void DemoEditorComponent::mouseDown (const MouseEvent & e)
{
TestDlg *pTestDlg;
pTestDlg = new TestDlg();
DialogWindow::showModalDialog ("Test", pTestDlg, this, Colours::white, true);
delete pTestDlg;
}
// /EXTRA STUFF //
//==============================================================================
// quick-and-dirty function to format a timecode string
static const String timeToTimecodeString (const double seconds)
{
const double absSecs = fabs (seconds);
const tchar* const sign = (seconds < 0) ? T("-") : T("");
const int hours = (int) (absSecs / (60.0 * 60.0));
const int mins = ((int) (absSecs / 60.0)) % 60;
const int secs = ((int) absSecs) % 60;
return String::formatted (T("%s%02d:%02d:%02d:%03d"),
sign, hours, mins, secs,
roundDoubleToInt (absSecs * 1000) % 1000);
}
// quick-and-dirty function to format a bars/beats string
static const String ppqToBarsBeatsString (const double ppq,
const double lastBarPPQ,
const int numerator,
const int denominator)
{
if (numerator == 0 || denominator == 0)
return T("1|1|0");
const int ppqPerBar = (numerator * 4 / denominator);
const double beats = (fmod (ppq, ppqPerBar) / ppqPerBar) * numerator;
const int bar = ((int) ppq) / ppqPerBar + 1;
const int beat = ((int) beats) + 1;
const int ticks = ((int) (fmod (beats, 1.0) * 960.0));
String s;
s << bar << T('|') << beat << T('|') << ticks;
return s;
}
//==============================================================================
DemoEditorComponent::DemoEditorComponent (DemoJuceFilter* const ownerFilter)
: AudioProcessorEditor (ownerFilter)
{
// create our gain slider..
addAndMakeVisible (gainSlider = new Slider (T("gain")));
gainSlider->addListener (this);
gainSlider->setRange (0.0, 1.0, 0.01);
gainSlider->setTooltip (T("changes the volume of the audio that runs through the plugin.."));
// get the gain parameter from the filter and use it to set up our slider
gainSlider->setValue (ownerFilter->getParameter (0), false);
// create and add the midi keyboard component..
addAndMakeVisible (midiKeyboard
= new MidiKeyboardComponent (ownerFilter->keyboardState,
MidiKeyboardComponent::horizontalKeyboard));
// add a label that will display the current timecode and status..
addAndMakeVisible (infoLabel = new Label (String::empty, String::empty));
// add the triangular resizer component for the bottom-right of the UI
addAndMakeVisible (resizer = new ResizableCornerComponent (this, &resizeLimits));
resizeLimits.setSizeLimits (150, 150, 800, 300);
// set our component's initial size to be the last one that was stored in the filter's settings
setSize (ownerFilter->lastUIWidth,
ownerFilter->lastUIHeight);
// register ourselves with the filter - it will use its ChangeBroadcaster base
// class to tell us when something has changed, and this will call our changeListenerCallback()
// method.
ownerFilter->addChangeListener (this);
}
DemoEditorComponent::~DemoEditorComponent()
{
getFilter()->removeChangeListener (this);
deleteAllChildren();
}
//==============================================================================
void DemoEditorComponent::paint (Graphics& g)
{
// just clear the window
g.fillAll (Colour::greyLevel (0.9f));
}
void DemoEditorComponent::resized()
{
gainSlider->setBounds (10, 10, 200, 22);
infoLabel->setBounds (10, 35, 450, 20);
const int keyboardHeight = 70;
midiKeyboard->setBounds (4, getHeight() - keyboardHeight - 4,
getWidth() - 8, keyboardHeight);
resizer->setBounds (getWidth() - 16, getHeight() - 16, 16, 16);
// if we've been resized, tell the filter so that it can store the new size
// in its settings
getFilter()->lastUIWidth = getWidth();
getFilter()->lastUIHeight = getHeight();
}
//==============================================================================
void DemoEditorComponent::changeListenerCallback (void* source)
{
// this is the filter telling us that it's changed, so we'll update our
// display of the time, midi message, etc.
updateParametersFromFilter();
}
void DemoEditorComponent::sliderValueChanged (Slider*)
{
getFilter()->setParameterNotifyingHost (0, (float) gainSlider->getValue());
}
//==============================================================================
void DemoEditorComponent::updateParametersFromFilter()
{
DemoJuceFilter* const filter = getFilter();
// we use this lock to make sure the processBlock() method isn't writing to the
// lastMidiMessage variable while we're trying to read it, but be extra-careful to
// only hold the lock for a minimum amount of time..
filter->getCallbackLock().enter();
// take a local copy of the info we need while we've got the lock..
const AudioPlayHead::CurrentPositionInfo positionInfo (filter->lastPosInfo);
const float newGain = filter->getParameter (0);
// ..release the lock ASAP
filter->getCallbackLock().exit();
// ..and after releasing the lock, we're free to do the time-consuming UI stuff..
String infoText;
infoText << String (positionInfo.bpm, 2) << T(" bpm, ")
<< positionInfo.timeSigNumerator << T("/") << positionInfo.timeSigDenominator
<< T(" - ") << timeToTimecodeString (positionInfo.timeInSeconds)
<< T(" - ") << ppqToBarsBeatsString (positionInfo.ppqPosition,
positionInfo.ppqPositionOfLastBarStart,
positionInfo.timeSigNumerator,
positionInfo.timeSigDenominator);
if (positionInfo.isPlaying)
infoText << T(" (playing)");
infoLabel->setText (infoText, false);
/* Update our slider.
(note that it's important here to tell the slider not to send a change
message, because that would cause it to call the filter with a parameter
change message again, and the values would drift out.
*/
gainSlider->setValue (newGain, false);
setSize (filter->lastUIWidth,
filter->lastUIHeight);
}
DemoEditorComponent.h
/*
==============================================================================
This file is part of the JUCE library - "Jules' Utility Class Extensions"
Copyright 2004-7 by Raw Material Software ltd.
------------------------------------------------------------------------------
JUCE can be redistributed and/or modified under the terms of the
GNU General Public License, as published by the Free Software Foundation;
either version 2 of the License, or (at your option) any later version.
JUCE is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with JUCE; if not, visit www.gnu.org/licenses or write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA
------------------------------------------------------------------------------
If you'd like to release a closed-source product which uses JUCE, commercial
licenses are also available: visit www.rawmaterialsoftware.com/juce for
more information.
==============================================================================
*/
#ifndef DEMOJUCEPLUGINEDITOR_H
#define DEMOJUCEPLUGINEDITOR_H
#include "DemoJuceFilter.h"
//==============================================================================
/**
This is the Component that our filter will use as its UI.
One or more of these is created by the DemoJuceFilter::createEditor() method,
and they will be deleted at some later time by the wrapper code.
To demonstrate the correct way of connecting a filter to its UI, this
class is a ChangeListener, and our demo filter is a ChangeBroadcaster. The
editor component registers with the filter when it's created and deregisters
when it's destroyed. When the filter's parameters are changed, it broadcasts
a message and this editor responds by updating its display.
*/
class DemoEditorComponent : public AudioProcessorEditor,
public ChangeListener,
public SliderListener
{
public:
/** Constructor.
When created, this will register itself with the filter for changes. It's
safe to assume that the filter won't be deleted before this object is.
*/
DemoEditorComponent (DemoJuceFilter* const ownerFilter);
/** Destructor. */
~DemoEditorComponent();
//==============================================================================
/** Our demo filter is a ChangeBroadcaster, and will call us back when one of
its parameters changes.
*/
void changeListenerCallback (void* source);
void sliderValueChanged (Slider*);
// EXTRA STUFF //
void DemoEditorComponent::mouseDown (const MouseEvent & e);
// /EXTRA STUFF //
//==============================================================================
/** Standard Juce paint callback. */
void paint (Graphics& g);
/** Standard Juce resize callback. */
void resized();
private:
//==============================================================================
Slider* gainSlider;
MidiKeyboardComponent* midiKeyboard;
Label* infoLabel;
ResizableCornerComponent* resizer;
ComponentBoundsConstrainer resizeLimits;
TooltipWindow tooltipWindow;
void updateParametersFromFilter();
// handy wrapper method to avoid having to cast the filter to a DemoJuceFilter
// every time we need it..
DemoJuceFilter* getFilter() const throw() { return (DemoJuceFilter*) getAudioProcessor(); }
};
#endif