MLTE text object


#1

I’m trying to put up a MLTE text object as a child window of the main juce window, so I can have multi language text entry without having to worry about fonts (MLTE uses font fallback). I’ve managed to do this on Win (with an EDIT control), but as I’m no hardcore lowlevel maccie, I have no idea of how to do it on mac. I’ve tried creating a TXNObject and attach it to the main window (got handle via ComponentPeer), but its not showing… :frowning:

Tips, anyone?

Forgot, this is on juce 1.46 (i.e. Carbon)


#2

Yeek, I wouldn’t try it in carbon. In the new stuff, there’s now an NSViewComponent that you can use to embed an NSView (a bit like the ActiveXComponent in windows), so I’d imagine that there’s some kind of NSView editor thingy that you could put in there quite easily.


#3

Hmm… was a bit afraid of that… wanted to avoid the obj-C stuff as long as I could… :cry:


#4

Not to hijack… but if JuceNSView implemented NSTextInput protocol, do you think we’d get multilanguage text input(for free)? Right now the character input palette does nothing and entering in katakana just spits english into the textfield. (Presumably because it just grabs the keydown)

Robiwan: As a test i put an NSTableView into an NSViewComponent and it was relatively painless(I think the NSTableView wasa royal pain as it needs to be in a scrollview as well. Hmmm… actually an NSTextView needs a scrollview as well I think.) I’d start off dropping in an NSTextField

in a .mm file(for cocoa access) and the latest from svn…

void juceTest()
{
AlertWindow *aw=new AlertWindow(L"Test",L"Testing NSView stuff",AlertWindow::NoIcon);
			NSViewComponent *aComponent=new NSViewComponent();
			aComponent->setBounds(0,0,600,400);
			NSTextField *statusText = [[NSTextField alloc] initWithFrame: NSMakeRect(0, 0, 600, 400)];
			[statusText setEditable: YES];
			[statusText setStringValue: @"Current Status"];
			[statusText setDrawsBackground:YES];
			[statusText setBordered:YES];
			[statusText setFont:[NSFont fontWithName:[[statusText font]fontName] size:10]];
			
			aComponent->setView(statusText);
			aComponent->setVisible(true);
			aw->addButton(L"OK",0,KeyPress (KeyPress::returnKey, 0, 0),0);
			aw->addCustomComponent(aComponent);

			aw->runModalLoop();
			deleteAndZero(aw);
}

Entering in katakana works fine…

I’m interested in your Windows experiment…I’m not very proficient in windows unfortunately, can you give me the main Create function to get me in the right direction?


#5

Something like this:

NativeEditField.h:


#ifndef __JUCE_NATIVEEDITFIELD_JUCEHEADER__
#define __JUCE_NATIVEEDITFIELD_JUCEHEADER__

#include "../juce_Component.h"

class NativeEditFieldImpl;
class NativeEditField;

class INativeEditFieldCallback
{
public:
	virtual ~INativeEditFieldCallback() {}
	virtual void INativeEditFieldCallback_changed(NativeEditField& editField) = 0;
	virtual void INativeEditFieldCallback_gotFocus(NativeEditField& editField) = 0;
	virtual void INativeEditFieldCallback_lostFocus(NativeEditField& editField) = 0;
};

class JUCE_API NativeEditField : public Component
{
public:
	NativeEditField();
	virtual ~NativeEditField();
	void resized();
	void moved();
	String getText();
	void setText(const String& newText);
	void setColours(const Colour& bgColor, const Colour& txtColor);
	void addListener(INativeEditFieldCallback * listener);
	void removeListener(INativeEditFieldCallback * listener);
	void selectText(const int start, const int end);

private:
	void internalGotFocus();
	void internalLostFocus();
	void internalChanged();
	NativeEditFieldImpl * m_impl;
	NativeEditFieldImpl * getImpl();
	Colour m_bgColor;
	Colour m_txtColor;
	Array<INativeEditFieldCallback*> m_listeners;
	friend class NativeEditFieldImpl;
};

#endif   // __JUCE_NATIVEEDITFIELD_JUCEHEADER__

NativeEditField.cpp:

class NativeEditFieldImpl
{
public:
	NativeEditFieldImpl(NativeEditField& owner) : m_owner(owner), m_bgBrush(0), m_edit(0), m_msgWnd(0)
	{
	}
	~NativeEditFieldImpl()
	{
		if (m_edit)
		{
			DestroyWindow(m_edit);
			m_edit = 0;
		}
		if (m_msgWnd)
		{
			DestroyWindow(m_msgWnd);
			m_msgWnd = 0;
		}
		if (m_bgBrush)
		{
			DeleteObject(m_bgBrush);
			m_bgBrush = 0;
		}
	}

	void setColours(const Colour& bgColor, const Colour& txtColor)
	{
		if (m_bgBrush)
		{
			DeleteObject(m_bgBrush);
		}

		m_bgColor = RGB(bgColor.getRed(), bgColor.getGreen(), bgColor.getBlue());
		m_bgBrush = CreateSolidBrush(m_bgColor);
		m_textColor = RGB(txtColor.getRed(), txtColor.getGreen(), txtColor.getBlue());
	}

	Rectangle getMyBounds()
	{
		Rectangle bounds = m_owner.getBounds();
		int x = 0, y = 0;
		m_owner.relativePositionToOtherComponent(m_owner.getTopLevelComponent(), x, y);
		bounds.setPosition(x, y);
		return bounds;
	}
	void create()
	{
		if (m_edit == 0 && m_msgWnd == 0 &&	m_owner.getPeer() != 0)
		{
			const Rectangle& bounds = getMyBounds();

			HWND hwnd = (HWND) m_owner.getPeer()->getNativeHandle();

			m_msgWnd = CreateWindow(JUCE_T("STATIC"), NULL, WS_CHILD|WS_VISIBLE|WS_CLIPCHILDREN, 
				bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), hwnd, NULL, NULL, NULL);
			assert(m_msgWnd != 0);
			SetWindowLongPtr(m_msgWnd, GWL_WNDPROC, (LONG) NativeEditFieldImpl::wndProc);

			m_edit = CreateWindow(JUCE_T("EDIT"), NULL, WS_CHILD|WS_VISIBLE|ES_AUTOHSCROLL, 
				0, 0, bounds.getWidth(), bounds.getHeight(), m_msgWnd, NULL, NULL, NULL);
			HFONT hFont = (HFONT)GetStockObject(DEFAULT_GUI_FONT);
			SendMessage(m_edit, WM_SETFONT, (WPARAM)hFont, NULL);
			setText(m_initial);

			// Set this last
			SetWindowLongPtr(m_msgWnd, GWL_USERDATA, (LONG) this);
		}

	}
	void resized()
	{
		if (m_msgWnd)
		{
			const Rectangle& bounds = getMyBounds();
			SetWindowPos(m_msgWnd, NULL, 0, 0, bounds.getWidth(), bounds.getHeight(), SWP_NOZORDER|SWP_NOMOVE);
		}
	}
	void moved()
	{
		if (m_msgWnd)
		{
			const Rectangle& bounds = getMyBounds();
			SetWindowPos(m_msgWnd, NULL, bounds.getX(), bounds.getY(), 0, 0, SWP_NOZORDER|SWP_NOSIZE);
		}
	}
	String getText() const
	{
		if (m_edit != 0)
		{
			WCHAR buffer[512];
			GetWindowText(m_edit, buffer, 512);
			return buffer;
		}

		return m_initial;
	}
	void setText(const String& newText)
	{
		if (m_edit != 0)
		{
			SetWindowText(m_edit, newText);
		}
		else
		{
			m_initial = newText;
		}
	}
	void selectText(const int start, const int end)
	{
		if (m_edit)
		{
			SendMessage(m_edit, EM_SETSEL, start, end);
		}
	}

	static LRESULT CALLBACK wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
	{
		switch(message)
		{
		case WM_CTLCOLOREDIT:
			{
				NativeEditFieldImpl *ptr = (NativeEditFieldImpl*)GetWindowLongPtr(hwnd, GWL_USERDATA);
				if (ptr != 0)
				{
					HDC hdc = (HDC)wParam;
					// Set the text background color.
					SetBkColor(hdc, ptr->m_bgColor);
					// Set the text foreground color.
					SetTextColor(hdc, ptr->m_textColor);
					// Return the control background brush.
					return (LRESULT)ptr->m_bgBrush;
				}
			}
			break;
		case WM_COMMAND:
			{
				NativeEditFieldImpl* ptr = (NativeEditFieldImpl*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
				if (ptr != 0)
				{
					unsigned notificationCode = HIWORD(wParam);
					switch(notificationCode)
					{
					case EN_SETFOCUS:
						ptr->m_owner.internalGotFocus();
						break;
					case EN_CHANGE:
						ptr->m_owner.internalChanged();
						break;
					case EN_KILLFOCUS:
						ptr->m_owner.internalLostFocus();
						break;
					}
				}
			}
			break;
		default:
			break;
		}
		return DefWindowProc(hwnd, message, wParam, lParam);
	}

private:
	HWND m_edit;
	HWND m_msgWnd;
	HBRUSH m_bgBrush;
	COLORREF m_textColor;
	COLORREF m_bgColor;
	String m_initial;
	NativeEditField& m_owner;
	friend NativeEditField;
};

NativeEditField::NativeEditField()
: m_impl(0)
{

}

NativeEditField::~NativeEditField()
{
	if (m_impl != 0)
	{
		delete m_impl;
		m_impl = 0;
	}
}

void NativeEditField::setColours(const Colour& bgColor, const Colour& txtColor)
{
	getImpl()->setColours(bgColor, txtColor);
}

void NativeEditField::resized()
{
	getImpl()->resized();
}

void NativeEditField::moved()
{
	getImpl()->moved();
}

String NativeEditField::getText() 
{
	return getImpl()->getText();
}

void NativeEditField::setText(const String& newText)
{
	getImpl()->setText(newText);
}

void NativeEditField::selectText(const int start, const int end)
{
	getImpl()->selectText(start, end);
}

void NativeEditField::addListener(INativeEditFieldCallback * listener)
{
	m_listeners.addIfNotAlreadyThere(listener);
}

void NativeEditField::removeListener(INativeEditFieldCallback * listener)
{
	m_listeners.removeValue(listener);
}

void NativeEditField::internalGotFocus()
{
	for (unsigned i = 0; i < m_listeners.size(); ++i)
	{
		INativeEditFieldCallback * cb = m_listeners[i];
		cb->INativeEditFieldCallback_gotFocus(*this);
	}
}

void NativeEditField::internalLostFocus()
{
	for (unsigned i = 0; i < m_listeners.size(); ++i)
	{
		INativeEditFieldCallback * cb = m_listeners[i];
		cb->INativeEditFieldCallback_lostFocus(*this);
	}
}

void NativeEditField::internalChanged()
{
	for (unsigned i = 0; i < m_listeners.size(); ++i)
	{
		INativeEditFieldCallback * cb = m_listeners[i];
		cb->INativeEditFieldCallback_changed(*this);
	}
}

NativeEditFieldImpl * NativeEditField::getImpl()
{
	assert(getParentComponent() != 0);
	if (m_impl == 0)
	{
		m_impl = new NativeEditFieldImpl(*this);
		m_impl->create();
	}
	return m_impl;
}

#6

Wow! Thanks for that Robiwan , very kind… I was hoping for a function declaration, but this is just awesome.

I’m using Julian’s Web Browser template to implement NSTextField myself. (having it lose and gain focus where appropriate, send cocoa action and target messages to a delegate that notifies the component listeners, very similar to what you have done. Once I have it a little more complete, I’ll post it here for you, no need in both of us doubling duties…

Cheers

Justin


#7

No probs. Although I spotted a bug, the getImpl should read:

NativeEditFieldImpl * NativeEditField::getImpl() 
{ 
   assert(getParentComponent() != 0); 
   if (m_impl == 0) 
   { 
      m_impl = new NativeEditFieldImpl(*this); 
   } 
   m_impl->create(); 
   return m_impl; 
} 

#8

I’ve managed to get the NSTextField showing, but quite stomped on how to implement the callback notification. You’ve anything to share ? :wink:


#9

Here’s what I have so far…

.h

class SMUnicodeLabel;


class UnicodeListener
{
public:
	virtual ~UnicodeListener(){}
	virtual void returnKeyPressed(SMUnicodeLabel &label)=0;
};

class SMUnicodeLabelInternal;
class SMUnicodeLabel : public Component
{
public:
	SMUnicodeLabel (const String &_label);
	~SMUnicodeLabel ();
	void resized();
	void addListener (UnicodeListener* const newListener) throw();
	void removeListener (UnicodeListener* const listenerToRemove) throw();
	const String getText();
	
	/** if we let juce handle focus then all hell break's lose */
	void setFocus();
	/** Internal
		*/
	void handleActionMessage();
private:
	/* data */

	SMUnicodeLabelInternal *textField; // On Mac this is an NSTextField
	SortedSet <void*> listeners;
};

#10

.mm

@interface CustomTextField : NSTextField
{
	
}
@end

@implementation CustomTextField
//Some NSResponder stuff

- (BOOL)performKeyEquivalent:(NSEvent *)theEvent
{
	NSString *keyString;
	unichar keyChar;
	
	keyString = [theEvent charactersIgnoringModifiers];
	keyChar = [keyString characterAtIndex:0];
	switch (keyChar)
	{
		case 'a':
			[self selectText:self];
			break;
		case 'v':
			//[super paste:self];//NSTextView
			break;
		default:
			return [super performKeyEquivalent:theEvent];
	}
	return true;
	
}
@end
// ==========================================================
@interface SMTextFieldDelegate : NSObject
{
	SMUnicodeLabel *ownerComponent;
}
- (SMTextFieldDelegate*) initWithOwner: (SMUnicodeLabel*) ownerComponent;
- (void)actionMessage:(id)sender;
@end
/***************************************************************************/
@implementation SMTextFieldDelegate
- (SMTextFieldDelegate*) initWithOwner: (SMUnicodeLabel*) ownerComponent_
{
	[super init];
	ownerComponent=ownerComponent_;
	return self;
}
/***************************************************************************/
- (void)actionMessage:(id)sender
{
	ownerComponent->handleActionMessage();
}
@end
// ==========================================================
class SMUnicodeLabelInternal : public NSViewComponent
{
public:
	SMUnicodeLabelInternal (SMUnicodeLabel *owner)
	{
		textField=[[CustomTextField alloc]initWithFrame:NSMakeRect(0,0,100,100)];
		setView(textField);
		setWantsKeyboardFocus(true);
		textFieldDelegate=[[SMTextFieldDelegate alloc]initWithOwner:owner];
		[textField setTarget:textFieldDelegate];
		[textField setEditable:YES];
		[textField setDrawsBackground:NO];
		//[textField setBordered:NO];
		[textField setAction:@selector(actionMessage:)];
		
	}
	virtual ~SMUnicodeLabelInternal ()
	{
		[textFieldDelegate release];
		setView (0);
	}
    void setFocus()
	{
		// Will set first responder
		[textField selectText:nil];
	}
	
	const String getText()
	{
		return nsStringToJuce([textField stringValue]);
	}
	
	
private:
	CustomTextField *textField;
	SMTextFieldDelegate *textFieldDelegate;
};
/***************************************************************************/
/** This allows me to use a textview in Mac OSX so I can have unicode safe stuff

/***************************************************************************/
SMUnicodeLabel::SMUnicodeLabel (const String &_label): Component(_label), listeners(2)
{
	//setOpaque(true); // if this is true, I need to do the painting
	addAndMakeVisible(textField=new SMUnicodeLabelInternal(this));
	setWantsKeyboardFocus(false);
	 setMouseCursor(MouseCursor::IBeamCursor);
}
/***************************************************************************/
SMUnicodeLabel::~SMUnicodeLabel()
{
	deleteAndZero(textField);
}
/***************************************************************************/
void SMUnicodeLabel::resized()
{
    textField->setSize (getWidth(), getHeight());
}
/***************************************************************************/
const String SMUnicodeLabel::getText()
{
	return textField->getText();	
}
/***************************************************************************/
void SMUnicodeLabel::setFocus()
{

	textField->setFocus();
}
/***************************************************************************/
void SMUnicodeLabel::addListener (UnicodeListener* const newListener) throw()
{
    jassert (newListener != 0)
	
    if (newListener != 0)
        listeners.add (newListener);
}
/***************************************************************************/
void SMUnicodeLabel::removeListener (UnicodeListener* const listenerToRemove) throw()
{
    listeners.removeValue (listenerToRemove);
}
/***************************************************************************/
void SMUnicodeLabel::handleActionMessage()
{
	const ComponentDeletionWatcher deletionChecker (this);
	
    for (int i = listeners.size(); --i >= 0;)
    {
        UnicodeListener* const tl = (UnicodeListener*) listeners [i];
		
        if (tl != 0)
        {
           
			tl->returnKeyPressed (*this);
						
            if (i > 0 && deletionChecker.hasBeenDeleted())
                return;
        }
    }
}

Hope that helps a little… you can see I started implementing copy and paste… but the NSResponder is working properly and setFocus is working(two things that gave me the most headaches)

Let me know if you have any questions


#11

You’ll notice lots of small things not implemented yet(setText, bounds) but that’s easy to add of course…

To use add and make visiible, set size, and addListener (inherit UnicodeListener)

you’ll get called with…

void returnKeyPressed (SMUnicodeLabel& editor)
{

}

I hope that’s enough…i’ll happily whip together a little project with it if you need…


#12

Thnx! That’ll get me started for sure! :slight_smile:


#13

Ok, got it to work, sort of… I’ve overridden the textDidChange method of NSTextField to get the change notification (as I didn’t get the target action notification to fire (?) ), also did an override of becomeFirstResponder for a “focus gained” signal. The insertion point (caret) color is default black whereas I need white, so in textBeginEditing I get the currentEditor and set the caret to white color (unfortunately I need to actually begin type before that happens, no “editorAvailable” mechanism available…).


#14

Awesome! The action should only be fired when the return key is hit. (Just thought I’d note that in case you start getting double actions!)

I might be misunderstanding, but have you looked at NSWindow’s fieldEditor:createFlag:forObject: method?

Every NSWindow has an NSText * object. When NSTextField goes into edit mode, this is used. You might be able to get an instance/create it and set your colors then?

Justin


#15

[quote=“justin”]I might be misunderstanding, but have you looked at NSWindow’s fieldEditor:createFlag:forObject: method?

Every NSWindow has an NSText * object. When NSTextField goes into edit mode, this is used. You might be able to get an instance/create it and set your colors then?[/quote]

Oh no I haven’t :slight_smile: The documentation is not that easy to browse and I have none to zero experience of Cocoa/Obj-C… Thnx for the info, I’ll try that out!

Edit: I tried it out, had to put the code in becomeFirstResponder, but yeah it works nicely. Thanks very much!