IME pop-up on text input


#1

In Windows 7, if I switch my keyboard layout to Japanese Hiragana or Katakana and enter characters in say a TextEditor, I can’t enter text directly in the component.
Instead, input first goes into a little IME pop-up window and then when pressing enter shows in the TextEditor (displaying the text works fine).
While this is functional, it is rather ugly.

This is probably the same issue as this topic: http://www.rawmaterialsoftware.com/viewtopic.php?f=3&t=5533
Most Windows applications I’ve tried handle this correctly, some do not however.

I realize this is not a very high priority issue and possibly quite tricky, but it would be great is someone had a solution!


#2

I’ve looked into this a bit myself;

The basic problem is that many non-western languages use alphabets whose characters exceed the number of keys on a keyboard.
So in order to input these characters Input Method Editors (IMEs) are used that map sequences of keystrokes to characters.

For instance one way of entering Japanese text is to use English phonetic spelling (“romaji”) which then automatically gets translated to Japanese syllabic characters as the user types (composition). E.g. 3 key strokes “tsu” gets translated to “つ” if the IME is set to Hiragana or “ツ” if the IME is set to Katakana. The syllabic transcription can then be translated to Chinese-derived Kanji characters, at which point the user has to resolve ambiguous homophones (one word in Hiragana or Katakana can be written in different ways in Kanji depending on meaning; there may also be ambiguity wrt. word boundaries as there are no spaces used).

On Windows this is handled through the IMM (Input Method Manager) API (or the newer Text Services Framework API). If an app is not IME-aware IME messages are simply passed on to DefWndProc() and the IME takes care of all the GUI stuff needed (this is what happens in Juce currently). There are two main default windows in IMEs; the IME Composition Window (for unambiguous as-you-type translation) and the IME Candidates Window (for showing a list of candidate translations when there is ambiguity).
The default Composition Window in particular is not so nice from a user experience point of view (the Candidates Window is okay though).

In Juce I tried implementing WM_IME_SETCONTEXT, WM_IME_STARTCOMPOSITION, WM_IME_ENDCOMPOSITION and WM_IME_COMPOSITION to emulate the user typing in the correct characters on a huge keyboard that has all the keys needed (sending WM_CHAR, WM_KEYDOWN, WM_KEYUP). This method replaces the default Composition Window. The default Candidates Window is still shown, just repositioned.

It seems that this could be made to work. Some features like underlines are not shown with this hack.

I could investigate a but further and ask a Japanese person to have a look if it functions correctly. Not so sure for other languages.


#3

I have very little experience of these extended input methods, so any ideas you’ve got about how best to implement them would be much appreciated!


#4

I’ve made a little example implementation. As far as I can tell it works like it is supposed to.
It would be great if you could have a look at it and maybe even implement this in JUCE!
Here is a good link if you need additional references: http://stackoverflow.com/questions/434048/how-do-you-use-ime

In order to draw all information provided by the IME’s Composition Window, we need to extend the TextInputTarget interface a little.
I propose to add something like the following functions:

    virtual void setCaretPosition (int newIndex) = 0;
    virtual int getCaretPosition() const = 0;

//    virtual const Rectangle<int> getCaretRectangle() = 0;

    virtual void setHighlightedRegionWithoutMovingCaret (const Range<int>& newRange) = 0;
    virtual const Rectangle<int> getHighlightedRegionRectangle() const = 0;

    virtual void replaceTextInRange (const Range<int>& range, const String& newText) = 0;

    virtual void setUnderlineRegions (const Array<Range<int> >& regions) = 0;

I’ll provide my implementations of these functions in TextEditor as well, but note that I just hacked them to get something working, not considering all of TextEditor’s capabilities:

void TextEditor::replaceTextInRange (const Range<int>& range, const String& newText)
{
    String text = getText();
    text = text.replaceSection (range.getStart(), range.getLength(), newText);
    setText (text, true); // ?
}

void TextEditor::setUnderlineRegions(const Array<Range<int> >& regions)
{
    underlineRegions = regions;
    repaint();
}

void TextEditor::setHighlightedRegionWithoutMovingCaret (const Range<int>& newRange)
{
    selection = newRange;
    repaint();
}

const Rectangle<int> TextEditor::getHighlightedRegionRectangle() const
{
    // XXX: this bit is especially wrong; just hack to get something going
    Range<int> sel = getHighlightedRegion();
    String preText = getTextInRange (Range<int> (0, sel.getStart()));
    String selText = getTextInRange (sel);
    const Font font = getFont();
    int b = font.getStringWidth (preText);
    int w = font.getStringWidth (selText);
    return Rectangle<int> (borderSize.getLeft() + textHolder->getX() + leftIndent + b - viewport->getX(),
        borderSize.getTop() + textHolder->getY() - viewport->getY(),
        w, roundToInt (font.getHeight()) + 2*topIndent + 8); // 8 needed because rest of code is incorrect?
}

Btw, I think the implementation of TextEditor::getCaretRectangle() may be incorrect.

Also change TextEditor::paintOverChildren() to draw underlines and clear underlines on TextEditor::focusLost():

void TextEditor::paintOverChildren (Graphics& g)
{
    const Font font = getFont();//g.getCurrentFont();
    for (int i = 0; i < underlineRegions.size(); ++i)
    {
        Range<int> region = underlineRegions[i];

        if (region.intersects (getHighlightedRegion()))
            continue; // do not underline highlighted region

        String preText = getTextInRange (Range<int> (0, region.getStart()));
        String ulText = getTextInRange (region);
        int b = font.getStringWidth (preText);
        int w = font.getStringWidth (ulText);

        float ulX0 = float (borderSize.getLeft() + textHolder->getX() + leftIndent + b);
        float ulX1 = float (ulX0 + w);
        float ulY =  float (borderSize.getTop() + textHolder->getY() + topIndent + roundToInt (cursorY) + roundToInt (cursorHeight));
        Line<float> ul (ulX0, ulY, ulX1, ulY);
        float dashes[2];
        dashes[0] = 4.0f;
        dashes[1] = 4.0f;
        g.setColour (Colours::white);
        g.drawDashedLine (ul, dashes, 2, 1.0f, 0);
    }
    ...
}

void TextEditor::focusLost (FocusChangeType)
{
    ...
    underlineRegions.clear();
    ...
}

Finally, you need to handle the WM_IME_* messages:

// NOTE:
// Here all Win32 IME handling is contained in a single class to facilitate 
// showing the code changes.
// If this code were to be merged in the JUCE source tree, it would perhaps 
// be better suited to go into juce_win32_Windowing.cpp directly.

#pragma comment(lib, "imm32.lib")

class Win32ImeHandler
{
public:
	Win32ImeHandler();
	LRESULT handleImeWndProc(ComponentPeer &owner, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, bool &handled);

private:
	void initialize();

	// Get either the current 'in-progress' composition string or the 'finalized' composition string:
	String getCompositionString(HIMC hImc, bool wantFinalized);

	// Get caret position while doing composition:
	// curImeCompString is current IME composition string, not currently displayed 
	// composition string (although they may be identical)
	// returned position is relative to beginning of TextInputTarget, not composition string
	int getCompositionCaretPos(HIMC hImc, LPARAM lParam, const String &curImeCompString);

	// Get selected/highlighted range while doing composition:
	// returned range is relative to beginning of TextInputTarget, not composition string
	Range<int> getCompositionSelection(HIMC hImc, LPARAM lParam);

	// Get underline/clause ranges while doing composition:
	Array<Range<int> > getCompositionUnderlines(HIMC hImc, LPARAM lParam);
	Array<Range<int> > getDefaultCompositionUnderlines(const String &compString, const Range<int> &selection);

	// Move Candidate Window to correct position on screen:
	void moveCandidateWindowToLeftAlignWithSelection(ComponentPeer &owner, TextInputTarget *target, HIMC hImc);

private:
	// Begin and end positions of composition in number of characters relative to the 
	// beginning of the TextInputTarget. These values refer to the *currently displayed* 
	// composition string, not the composition string currently in the IME.
	int bPosComposition;
	int ePosComposition;

	// Flag to see if there was a composition that didn't end yet, used to 
	// check in WM_IME_ENDCOMPOSITION for end of composition by cancellation.
	bool hasPendingComposition;
};

// -------------------------------------------------------------------------------------------------

Win32ImeHandler::Win32ImeHandler()
{
	initialize();
}

LRESULT Win32ImeHandler::handleImeWndProc(ComponentPeer &owner, HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, bool &handled)
{
	switch (msg)
	{
	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	case WM_IME_SETCONTEXT:
		{
			bool windowActive = (wParam == TRUE);

			// Complete pending composition when losing focus:
			// Note:
			// Here findCurrentTextInputTarget() already returns NULL, so we can't update 
			// the TextInputTarget's state here; so each concrete TextInputTarget should handle this on its own 
			// when losing focus.
			if (hasPendingComposition && !windowActive)
			{
				hasPendingComposition = false; // clear pending composition, so triggering WM_IME_ENDCOMPOSITION on ImmNotifyIME() does not consider this a canceled composition

				HIMC hImc = ImmGetContext(hWnd);
				if (hImc)
				{
					ImmNotifyIME(hImc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
					ImmReleaseContext(hWnd, hImc);
				}
			}

			// We handle the IME Composition Window ourselves (but let IME Candidates Window be 
			// handled by IME through DefWindowProc()), so we clear the ISC_SHOWUICOMPOSITIONWINDOW flag:
			lParam &= ~ISC_SHOWUICOMPOSITIONWINDOW;

			// Call DefWindowProc() ourselves, instead of simply setting handled to false and returning, 
			// because modified lParam is local variable:
			handled = true;
			return DefWindowProc(hWnd, msg, wParam, lParam);
		}
		break;

	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	case WM_IME_STARTCOMPOSITION:
		{
			initialize();

			// Delete current selection in TextInputTarget (overwrite):
			TextInputTarget *target = owner.findCurrentTextInputTarget();
			if (target)
			{
				target->replaceTextInRange(target->getHighlightedRegion(), String::empty);
			}

			handled = true;
			return 0;
		}
		break;

	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	case WM_IME_ENDCOMPOSITION:
		{
			// Check whether WM_IME_ENDCOMPOSITION was send because user canceled operation 
			// (e.g. by pressing escape button) and if so handle properly in TextInputTarget:
			if (hasPendingComposition)
			{
				// Notify TextInputTarget:
				TextInputTarget *target = owner.findCurrentTextInputTarget();
				if (target)
				{
					// Remove composition string:
					target->replaceTextInRange(Range<int>(bPosComposition, ePosComposition), String::empty);
					ePosComposition = bPosComposition;

					// Set caret to end of composition string:
					target->setCaretPosition(ePosComposition);

					// Set selection to no selection:
					target->setHighlightedRegionWithoutMovingCaret(Range<int>::emptyRange(ePosComposition));

					// Disable underlines:
					Array<Range<int> > underlines;
					target->setUnderlineRegions(underlines);
				}

				// Notify IME:
				HIMC hImc = ImmGetContext(hWnd);

				if (hImc)
				{
					// Close Candidate Window if needed:
					ImmNotifyIME(hImc, NI_CLOSECANDIDATE, 0, 0);

					ImmReleaseContext(hWnd, hImc);
				}
			}

			initialize();

			// Let DefWindowProc() handle this message so the Candidate Window is closed 
			// when you press enter, escape, or click on a candidate in the list.
			handled = false;
			return 0; // unused
		}
		break;

	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	case WM_IME_COMPOSITION:
		{
			// Get TextInputTarget:
			TextInputTarget *target = owner.findCurrentTextInputTarget();
			if (!target)
			{
				handled = true; // ?
				return 0;
			}

			// Get IMM context:
			HIMC hImc = ImmGetContext(hWnd);
			if (!hImc)
			{
				handled = true; // ?
				return 0;
			}

			if (bPosComposition == -1 || ePosComposition == -1)
				bPosComposition = ePosComposition = target->getCaretPosition();

			// Handle result of composition (usually when the user presses enter, or clicks on an item 
			// in the Candidate Window):
			if (lParam & GCS_RESULTSTR)
			{
				// Get 'finalized' composition string:
				String compString = getCompositionString(hImc, true);

				// Replace currently displayed composition string with new composition string:
				target->replaceTextInRange(Range<int>(bPosComposition, ePosComposition), compString);
				ePosComposition = bPosComposition + compString.length(); // update begin/end positions

				// Set caret to end of composition string:
				target->setCaretPosition(ePosComposition);

				// Set selection to no selection:
				target->setHighlightedRegionWithoutMovingCaret(Range<int>::emptyRange(ePosComposition));

				// Disable underlines:
				Array<Range<int> > underlines;
				target->setUnderlineRegions(underlines);

				//initialize();
				hasPendingComposition = false;
			}

			// Handle not-yet-finalized composition:
			if (lParam & GCS_COMPSTR)
			{
				// Get 'in-progress' composition string:
				String compString = getCompositionString(hImc, false);

				// Replace currently displayed composition string with new composition string:
				target->replaceTextInRange(Range<int>(bPosComposition, ePosComposition), compString);
				ePosComposition = bPosComposition + compString.length(); // update begin/end positions
				
				// Update caret position:
				int caretPos = getCompositionCaretPos(hImc, lParam, compString);
				target->setCaretPosition(caretPos);

				// Update selected range:
				Range<int> selection = getCompositionSelection(hImc, lParam);
				target->setHighlightedRegionWithoutMovingCaret(selection);

				// XXX: when there's a selection, this means the 
				// candidates window was opened; while going through 
				// candidates the caret should be at the end of the composition string, 
				// but getCompositionCaretPos() return begin of the composition string for some reason
				// XXX: this is a work-around.. probably there's something else wrong
				if (!selection.isEmpty())
					target->setCaretPosition(ePosComposition);

				// Update underlines:
				Array<Range<int> > underlines = getCompositionUnderlines(hImc, lParam);
				if (underlines.size() == 0)
					underlines = getDefaultCompositionUnderlines(compString, selection);
				target->setUnderlineRegions(underlines);

				// Mark that we are currently compositing:
				hasPendingComposition = true;
			}

			// Move Candidate Window position so that the highlighted part of the 
			// displayed composition string and the candidates are left aligned into
			// a single column (of course fonts will probably not match):
			// Note: Do *after* updating selection.
			moveCandidateWindowToLeftAlignWithSelection(owner, target, hImc);

			// Release IMM context:
			ImmReleaseContext(hWnd, hImc);

			handled = true;
			return 0;
		}
		break;
	}

	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	handled = false;
	return 0; // unused
}

// -------------------------------------------------------------------------------------------------

void Win32ImeHandler::initialize()
{
	// initialize to invalid
	bPosComposition = -1;
	ePosComposition = -1;

	hasPendingComposition = false;
}

String Win32ImeHandler::getCompositionString(HIMC hImc, bool wantFinalized)
{
	if (!hImc)
		return String::empty;

	DWORD dwIndex = GCS_COMPSTR;
	if (wantFinalized)
		dwIndex = GCS_RESULTSTR;

	// Get string length:
	int lenCompStrBytes = ImmGetCompositionString(hImc, dwIndex, NULL, 0); // or negative on error
	if (lenCompStrBytes < 0)
		return String::empty;

	int lenCompStrChars = lenCompStrBytes/sizeof(TCHAR);

	// Get string data:
	TCHAR *tmp = new TCHAR[lenCompStrChars+1];
	if (lenCompStrBytes > 0)
		ImmGetCompositionString(hImc, dwIndex, tmp, lenCompStrBytes);
	tmp[lenCompStrChars] = 0; // null terminate string

	// Convert to JUCE string:
	String compStr(CharPointer_UTF16(tmp), lenCompStrChars);
	delete[] tmp;
	tmp = NULL;

	return compStr;
}

int Win32ImeHandler::getCompositionCaretPos(HIMC hImc, LPARAM lParam, const String &curImeCompString)
{
	if (!hImc)
		return -1;

	if (lParam & CS_NOMOVECARET)
	{
		return bPosComposition; // do not move caret (by request of IME), bPosComposition = original caret position
	}
	else if (lParam & GCS_CURSORPOS)
	{
		int localCaretPos = ImmGetCompositionString(hImc, GCS_CURSORPOS, NULL, 0);
		if (localCaretPos >= 0)
			return bPosComposition + localCaretPos;
		else
			return bPosComposition; // error
	}
	else
	{
		return bPosComposition + curImeCompString.length();
	}

}

Range<int> Win32ImeHandler::getCompositionSelection(HIMC hImc, LPARAM lParam)
{
	if (!hImc)
		return Range<int>::emptyRange(-1);

	int bLocalSel = 0;
	int eLocalSel = 0;

	if (lParam & GCS_COMPATTR)
	{
		// Get size of attributes array:
		int lenAttributesBytes = ImmGetCompositionString(hImc, GCS_COMPATTR, NULL, 0);

		if (lenAttributesBytes > 0)
		{
			int lenAttributesChars = int(lenAttributesBytes/sizeof(char));

			// Get attributes (8 bit flag per character):
			char *attributes = new char[lenAttributesChars];
			ImmGetCompositionString(hImc, GCS_COMPATTR, attributes, lenAttributesBytes);

			// Find first character with ATTR_TARGET_* flag set:
			bLocalSel = -1;
			for (int i = 0; i < lenAttributesChars; ++i)
			{
				bool isCharSelected = (attributes[i] == ATTR_TARGET_CONVERTED || attributes[i] == ATTR_TARGET_NOTCONVERTED);
				if (isCharSelected)
				{
					bLocalSel = i;
					break;
				}
			}
			if (bLocalSel == -1)
				bLocalSel = lenAttributesChars;

			// Find first character with ATTR_TARGET_* flag not set:
			eLocalSel = -1;
			for (int i = bLocalSel; i < lenAttributesChars; ++i)
			{
				bool isCharSelected = (attributes[i] == ATTR_TARGET_CONVERTED || attributes[i] == ATTR_TARGET_NOTCONVERTED);
				if (!isCharSelected)
				{
					eLocalSel = i;
					break;
				}
			}
			if (eLocalSel == -1)
				eLocalSel = lenAttributesChars;

			delete[] attributes;
			attributes = NULL;
		}
	}

	return Range<int>(bPosComposition + bLocalSel, bPosComposition + eLocalSel);
}

Array<Range<int> > Win32ImeHandler::getCompositionUnderlines(HIMC hImc, LPARAM lParam)
{
	Array<Range<int> > result;

	if (!hImc)
		return result;

	if (lParam & GCS_COMPCLAUSE)
	{
		int lenClauseDataBytes = ImmGetCompositionString(hImc, GCS_COMPCLAUSE, NULL, 0);
		if (lenClauseDataBytes <= 0)
			return result;

		int lenClauseDataItems = lenClauseDataBytes/sizeof(uint32);

		uint32 *clauseData = new uint32[lenClauseDataItems];

		ImmGetCompositionString(hImc, GCS_COMPCLAUSE, clauseData, lenClauseDataBytes);
		for (int i = 0; i < lenClauseDataItems - 1; ++i)
		{
			Range<int> underline(bPosComposition + clauseData[i], bPosComposition + clauseData[i+1]);
			result.add(underline);
		}

		delete[] clauseData;
		clauseData = NULL;
	}

	return result;
}

Array<Range<int> > Win32ImeHandler::getDefaultCompositionUnderlines(const String &compString, const Range<int> &selection)
{
	Array<Range<int> > result;

	Range<int> ul1 = Range<int>(0, selection.getStart());
	Range<int> ul2 = Range<int>(selection.getStart(), selection.getEnd());
	Range<int> ul3 = Range<int>(selection.getEnd(), compString.length());

	if (!ul1.isEmpty())
		result.add(ul1);
	if (!ul2.isEmpty())
		result.add(ul2);
	if (!ul3.isEmpty())
		result.add(ul3);

	return result;
}


#pragma warning(push)
#pragma warning(disable:4189) // why get this warning??

void Win32ImeHandler::moveCandidateWindowToLeftAlignWithSelection(ComponentPeer &owner, TextInputTarget *target, HIMC hImc)
{
	Component *ownerComp = owner.getComponent();
	Component *targetComp = ownerComp->getCurrentlyFocusedComponent();
	if (targetComp != 0)
	{
		const int hMargin = 2; // ?
		Rectangle<int> selRect = target->getHighlightedRegionRectangle();
		Rectangle<int> compRect = targetComp->getBounds();
		selRect.translate(compRect.getX(), compRect.getY()+hMargin);

		CANDIDATEFORM posCandidateWindow = {0, CFS_CANDIDATEPOS, {selRect.getX(), selRect.getBottom()}, {0, 0, 0, 0}};
		ImmSetCandidateWindow(hImc, &posCandidateWindow);
	}
}

#pragma warning(pop)

One thing that’s still missing is that TextEditor::focusLost() should somehow call ImmNotifyIME(hImc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0);, to close any open candidate list window, etc.; I have no idea how to do this in JUCE however…

Hope this helps!


#5

Thanks very much for doing the research on this one! I’m going to take a look at this today and see what I can make of it…


#6

Just having a look at this, but I don’t understand the underlining stuff at all - why should parts of the text be underlined??


#7

Many IMEs do not just translate a sequence of keystrokes to a character, but require further interaction with the user.

For Japanese it works something like this:

  1. You enter a phrase in kana or romaji (automatically converted to kana). This starts a new IME input sequence.
  2. You press space bar to switch between different ways of writing the different words (clauses) in the phrase. Pressing space twice opens the Candidate Window.
  3. You can move back and forth between different clauses using left/right cursor keys.
  4. You can change current clause end boundary by holding shift and using left/right cursor keys.
  5. You can commit the IME input sequence by pressing enter (or cancel by presing escape).

So in the end we need to inform the user about:

  • Which part of the displayed string is the current IME input sequence (underline = IME input sequence, no underline is already commited text).
  • Which clause of the current IME input sequence we are currently editing (thick underline or highlighted text = currently edited clause, normal underline = other clauses).

Made a small video that hopefully shows what I mean:
http://www.mediafire.com/?6498kwsl534jgis (200kb)

See this link: http://www.autopenhosting.org/unicode/type-Japanese.html
Or add a IME keyboard layout in Windows and try in any app.

About the code above; there are at least two issues:

  1. The Candidate Window isn’t always positioned properly.
  2. Commiting the composition when a TextInputTarget loses focus is rather crucial, especially when there’s more than one text input component.

#8

I started trying to make some sense out of this, and have checked-in some untested code in. I’ve got to move onto some other stuff now, but you might want to have a look at what I’ve done…


#9

I had just noticed the check-in before seeing this post.
Great, thanks!

Looks like a good step in the right direction at the very least.
I will continue testing and tell you if I find any problems.

What I’ve noticed so far:

  • In JUCE Demo, Widgets, misc widgets; entering text in the “combo box item 1” widget, then clicking on the “single-line text box” does not cause cancelPendingTextInput() to be triggered (also, I’d name the function confirm/commit/complete rather than cancel).

  • However, going from the “single-line text box” to “combo box item 1” or the password box does work. Going from password box to “single-line text box” also works.

  • For the password box opening the Candidate Window shows the password as plain text. I’m not sure what’s the proper way to handle this.

  • For small fonts the dashed line dash/gap lengths are not very proportional; maybe simply using a solid line would be the easiest solution.


#10

Ok, thanks. I’m surprised it works, TBH, I literally checked in the code without having tried it at all!

I guess the combo box is probably just deleting the editor while it still has focus, so might need a dismissal call in its destructor as well as focusLost().

For the dashes, it might work better as an image fill, let me try that…


#11

hexano…as the OP on the other thread…thank you!


#12

Hello,
I am new to JUCE and I am encountering a leaking memory In Juce Demo. Let me explain In steps

     Demo ->Widgets -> misc Wigets
              Then I dragged the floating component(Light Weight Window)out the Main Window and I selected Other demos.
     Then closed the main Window and I was hit with a memory leak (Couldn't delete all the windows).
              Can u help me in deleting this Window??

#13

[quote=“Suman”]Hello,
I am new to JUCE and I am encountering a leaking memory In Juce Demo. Let me explain In steps

     Demo ->Widgets -> misc Wigets
              Then I dragged the floating component(Light Weight Window)out the Main Window and I selected Other demos.
     Then closed the main Window and I was hit with a memory leak (Couldn't delete all the windows).
              Can u help me in deleting this Window??[/quote]

Ah, don’t worry about that. To keep the demo code simple the widgets panel just calls deleteAllChildren() to clean up its child components. So when that component has been dragged onto the desktop, it obviously is no longer a child component, and won’t get deleted.

Note that I wrote that demo code many years ago and it could certainly use a bit of improvement… Using deleteAllChildren() is NOT the recommended way to manage the lifetime of your components! Use modern C++ object ownership patterns instead in your own code!


#14

Thank you ! It really worked… :smiley: