Photoshop plug-ins with JUCE


#1

I’ve been trying to make a Frankenstein Photoshop plugin – basically using JUCE to do the UI rather than Win32/Cocoa/Carbon so I can write my UI once.

After the namespace conflicts (‘keyWidth’ and ‘keySource’) and a really bizarre one with int32 and uint32, I’m at the point where I can make the HelloWorld dialog come up from within the Dissolve example in CS5. (Mac, 64-bit.)

But the @$#%^! thing eats my menu bar on the way out. I’m living in someone else’s program (e.g. Photoshop) and can’t touch the menubar. Is there any way (using the HelloWorld example) to suppress it from modifying the menu bar?

Should I be using a different example to make this work in an already existing DLL?
(I know about the hDLLInstance bit for the Win32 version…)

Has someone already done this?


If I can get this to work on Mac/64, and then to work on Mac/32, PC/64 and PC/32 (what appears to be straightforward), we’ll probably be licensing the code for our products and not using GPL. It is really an amazing suite of tools and I can live with the spelling differences between American and British English. :slight_smile:
(h/t George Bernard Shaw)


#2

Excellent stuff!

The main thing you need to do is to make sure that JUCEApplication::isStandaloneApp() returns false, because that’ll tell things like the menu bar code to run in ‘plugin’ mode. You’re not using the START_JUCE_APPLICATION macro anywhere, are you?


#3

Ah, yes. The START_JUCE_APPLICATION macro.

Well, I can’t because I don’t have a main() as such because I’m inside of of somebody else’s DLL. :slight_smile:
So I took your macro and unwrapped it like this:

static JUCE_NAMESPACE::JUCEApplication* juce_CreateApplication() { return new JUCEHelloWorldApplication(); } // Juce Application for my window ... //where I would invoke my dialog... JUCE_NAMESPACE::JUCEApplication::createInstance = &juce_CreateApplication; //instantiate the window String cmd; //don't need any parameters for this test JUCE_NAMESPACE::JUCEApplication::main (cmd); ...

But I don’t know how to override isStandaloneApp(). I tried this:

and it didn’t make any difference.

I’m a C/Assembly-guy, not really that deep into C++ and multiple inheritance, so I’m kinda drowning here. My previous dialogs were all Carbon (actually upgraded Classic) and Win32. Maybe it’s time for some remedial C++ … any good references?


#4

Disregard my previous post.

Clearly my mistake was to use the content of the START_JUCE_APPLICATION() macro.

Which begs the question, where/how do I start with a DLL? I see the createInstance variable and it is now obvious that it should be null.


#5

I’ve no idea how photoshop plugins work, but presumably they have some sort of initialisation callback, and that’s where you’d need to call initialiseJuce_GUI() - see the juce_Initialisation.h file for info about those functions.


#6

I’ve no idea how photoshop plugins work, but presumably they have some sort of initialisation callback, and that’s where you’d need to call initialiseJuce_GUI() - see the juce_Initialisation.h file for info about those functions.


#7

I tried that. They actually re-use a single entry point with a different selector parameter:

[code]//-------------------------------------------------------------------------------
//
// PluginMain
//
// All calls to the plug in module come through this routine.
//
// Inputs:
// const int16 selector Host provides selector indicating what
// command to do.
//
// Inputs and Outputs:
// FilterRecord *filterRecord Host provides a pointer to parameter block
// containing pertinent data and callbacks.
// See PIFilter.h
//
// intptr_t *data Use this to store a handle or pointer to our global
// data structure, which is maintained by the
// host between calls to the plug in.
//
// Outputs:
// int16 *result Returns error result. Some errors are handled
// by the host, some are silent, and some you
// must handle. See PIGeneral.h.
//
//-------------------------------------------------------------------------------
DLLExport MACPASCAL void PluginMain(const int16 selector,
FilterRecordPtr filterRecord,
intptr_t * data,
int16 * result)
{
// update our global parameters
gFilterRecord = filterRecord;
gDataHandle = data;
gResult = result;

if (selector == filterSelectorAbout)
{
	sSPBasic = ((AboutRecord*)gFilterRecord)->sSPBasic;
}
else
{
	sSPBasic = gFilterRecord->sSPBasic;

	if (gFilterRecord->bigDocumentData != NULL)
		gFilterRecord->bigDocumentData->PluginUsing32BitCoordinates = true;
}

// do the command according to the selector
switch (selector)
{
	case filterSelectorAbout:
		DoAbout();
		break;
	case filterSelectorParameters:
		DoParameters();
		break;
	case filterSelectorPrepare:
		DoPrepare();
		break;
	case filterSelectorStart:
		DoStart();
		break;
	case filterSelectorContinue:
		DoContinue();
		break;
	case filterSelectorFinish:
		DoFinish();
		break;
	default:
		break;
}

// unlock our handles used by gData and gParams
if (selector != filterSelectorAbout)
	UnlockHandles();

}[/code]

So I put initialiseJuce_GUI(); in the DoPrepare() call (which gets called pretty early – I might change that so the doAbout call can work, but that is for later) and shutdownJuce_GUI(); in the DoFinish() call. That works. Does nothing. Now at some point I need to call the HelloWorldComponent. That’s where I’m stuck. That’s why I dissected your START macro and used the pieces in my DoStart() function (from there I can call back into Photoshop to get image data and update it), but it all went sour after that.


#8

I don’t understand the problem… The host will surely give some kind of callback to indicate “open the UI” or something, at which point you’d just create your window.


#9

If your “just open the UI window” is any analog to the START_APPLICATION macro, then I’m confused.

You’re saying I just need to invoke this? JUCEHelloWorldApplication(); ?? Hmm. No dialog comes up.

How do I “just create your window” ??


#10

You’re not running an app! A plugin doesn’t run the event loop, so there’s no point looking at any of my start-up code, which is all designed to start and run an app. If you want something as a reference, look at the audio plugin wrappers, or the browser plugin wrappers. (But it’s complicated stuff!)

[code]class MyWindow : public Component (or whatever)
{
…etc

void someKindOfCallbackFromTheHost()
{
new MyWindow();
}[/code]


#11

Actually, I DO need an event loop.

Within Photoshop, a plug-in essentially takes over (it’s modal) and is provided with a large set of callbacks to use the main program for a number of purposes:

• Fetch or write back image data
• Color space transforms
• Push a progress bar or test for the esc key (this has the side-effect of being used to update Photoshop’s window interface)
• Scripting
• any number of other services.

Under Carbon, I’d have the classic modalDialog() event loop. Under Win32, I’d have a DialogBoxParam-style resource-driven window, and although a full-blown window can be done, I’m lazy.

What you presume is a call-back from the Parent really isn’t. Photoshop basically calls you three times (doPrepare, doStart, doFinish) and you perform all of the work at the doStart stage using call-backs INTO Photoshop. It’s an old API. (1989) They send you parameters for your dialog, and a flag as to whether that dialog appears or not, you send the updated parameters back.

The most recent example you provided: new *MyWindow; sadly doesn’t do anything (I’m using the HelloWorldComponent). The App example does put up and drive a dialog nicely, but it has the unfortunate side-effect of munging the menu bars.

I’ve been trying to understand (I believe you Brits would say “Suss”) your audio plugins to see if they provide anything useful for me, but as far as I can tell, the heavy lifting is performed within the main app.

I could be entirely out-to-lunch with regards to how your event loop works, but my goal here was to not have to write three versions of code: Objective-C (required under 64-bit), Carbon (within Photoshop, required under32-bit), and Win32 for these plug-ins. And your package seems uniquely suited for that. It looks fantastic, is full-featured, and the amalgamated build approach makes integration much easier! (except for the namespace issues)

If I was writing an application this would be fantastic. The problem is integrating it into someone else’s SDK where the plug-in model requires that we run independently and then return back to the parent, preferably without having modified anything other than the image.


#12

Ok, gotcha, I guess I was assuming all plugins ran in vaguely the same way!

Well, if you need to run a modal loop, it’s easy, you’d just create your window, and call its Component::runModalLoop() method.


#13

Hi Jules,

JUCE version : 1_53
OS : MAC 10.6.3

I also tried creating JUCE DocumentWindow inside Photoshop plugin on MAC and Windows. On Windows everything works fine but on MAC the JUCE dialog goes behind Photoshop menu even if I call runModalLoop() on the DocumentWindow.

Can you please help me fix this issue?

Thanks,
Dheeraj


#14

You could try setAlwaysOnTop (true) ?


#15

Yes I tried that but the window will stay on top always even if other applications have focus. So I cannot use setAlwaysOnTop (true).


#16

You say it goes behind the menu… but isn’t that what you’d expect with a menu?


#17

The JUCE window goes behind the side tool bars as well. But Photoshop’s modal dialog stays on top of the toolbar. So the JUCE modal window also should stay on top ideally.

Thanks,
Dheeraj


#18

Well, I don’t really know and can’t test it myself… You’d have to do a bit of digging around yourself to figure that one out.

So in a photoshop plugin does the plugin create its own desktop window, rather than just adding a sub-view to a window that’s provided by the host?


#19

I understand. Thanks for the replies.

The photoshop plugin creates its own desktop window.


#20

I’m not familiar with Photoshop either, but if its an NSPanel, you could look at some NSPanel functions. Specifically setFloatingLevel (You want it to floating above everything, but only in that app…)

(See NSPanel on developer.apple.com)

You might want to see what kindOfClass the ComponentPeer is though and try some other things…

Your implementation file would need to be a .mm not a .cpp file, then you could do something like this…(untested, just typed in, so probably some errors!) (In a component in the document window, after it’s all been added and visible. I’d probably make a makeFloatingWindow function that is called when i’m sure the window is visible. (It won’t work without a heavyweight window, so throwing it in your constructor isn’t going to work)

ComponentPeer *componentPeer=getPeer();
	jassert(componentPeer);
	NSView* const peer = (NSView*) (componentPeer->getNativeHandle());
jassert(peer);
NSWindow *window=[peer window];
jassert(window);
if ([window isKindOfClass:[NSPanel class]])
{
 // hey i'm a panel! I can then set the floating level
 [(NSPanel*) window:setFloatingPanel:YES];

}
else if ([window isKindOfClass:[NSWindow class]])
{
[window setLevel:NSFloatingWindowLevel];
}
else
{
//What type of window am I???
jasserfalse;
}

There are a bunch of different levels defined.
See NSWindow Window Levels

[quote]#define NSNormalWindowLevel kCGNormalWindowLevel
#define NSFloatingWindowLevel kCGFloatingWindowLevel
#define NSSubmenuWindowLevel kCGTornOffMenuWindowLevel
#define NSTornOffMenuWindowLevel kCGTornOffMenuWindowLevel
#define NSMainMenuWindowLevel kCGMainMenuWindowLevel
#define NSStatusWindowLevel kCGStatusWindowLevel
#define NSModalPanelWindowLevel kCGModalPanelWindowLevel
#define NSPopUpMenuWindowLevel kCGPopUpMenuWindowLevel
#define NSScreenSaverWindowLevel kCGScreenSaverWindowLevel
#define NSDockWindowLevel kCGDockWindowLevel[/quote]

Let us know how you get on! Good luck

EDIT: Juce uses NSFloatingWindowLevel when you setAlwaysOnTop to be true… how about playing with some of the others? Like NSStatusWindowLevel?