2 * Copyright (c) 2015 Samsung Electronics Co., Ltd.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
19 #include <dali-toolkit/internal/text/text-controller.h>
22 #include <dali-toolkit/internal/text/character-set-conversion.h>
23 #include <dali-toolkit/internal/text/layouts/layout-engine.h>
24 #include <dali-toolkit/internal/text/layouts/layout-parameters.h>
25 #include <dali-toolkit/internal/text/logical-model.h>
26 #include <dali-toolkit/internal/text/multi-language-support.h>
27 #include <dali-toolkit/internal/text/script-run.h>
28 #include <dali-toolkit/internal/text/segmentation.h>
29 #include <dali-toolkit/internal/text/shaper.h>
30 #include <dali-toolkit/internal/text/text-view.h>
31 #include <dali-toolkit/internal/text/visual-model.h>
36 #include <dali/public-api/adaptor-framework/key.h>
37 #include <dali/public-api/text-abstraction/font-client.h>
43 const float MAX_FLOAT = std::numeric_limits<float>::max();
55 struct Controller::TextInput
57 // Used to queue input events until DoRelayout()
60 KEYBOARD_FOCUS_GAIN_EVENT,
61 KEYBOARD_FOCUS_LOST_EVENT,
77 Event( EventType eventType )
97 TextInput( LogicalModelPtr logicalModel,
98 VisualModelPtr visualModel,
99 DecoratorPtr decorator )
100 : mLogicalModel( logicalModel ),
101 mVisualModel( visualModel ),
102 mDecorator( decorator ),
108 * @brief Helper to move the cursor, grab handle etc.
110 bool ProcessInputEvents()
112 mDecoratorUpdated = false;
116 for( vector<TextInput::Event>::iterator iter = mEventQueue.begin(); iter != mEventQueue.end(); ++iter )
120 case KEYBOARD_FOCUS_GAIN_EVENT:
122 OnKeyboardFocus( true );
125 case KEYBOARD_FOCUS_LOST_EVENT:
127 OnKeyboardFocus( false );
140 case GRAB_HANDLE_EVENT:
142 OnGrabHandleEvent( *iter );
151 return mDecoratorUpdated;
154 void OnKeyboardFocus( bool hasFocus )
158 void OnKeyEvent( const Event& event )
160 int keyCode = event.p1.mInt;
162 // Handle state changes
163 if( Dali::DALI_KEY_ESCAPE == keyCode )
165 ChangeState( INACTIVE ); // Escape key ends edit mode
167 else if ( event.p2.mString )
169 // Some text may be selected, hiding keyboard causes an empty keystring to be sent, we don't want to delete highlight in this case
170 ChangeState( EDITING );
173 // Handle the actual key event
174 if( Dali::DALI_KEY_BACKSPACE == keyCode )
176 HandleBackspaceKey();
178 else if( Dali::DALI_KEY_CURSOR_LEFT == keyCode ||
179 Dali::DALI_KEY_CURSOR_RIGHT == keyCode ||
180 Dali::DALI_KEY_CURSOR_UP == keyCode ||
181 Dali::DALI_KEY_CURSOR_DOWN == keyCode )
183 HandleCursorKey( keyCode );
185 else if ( event.p2.mString )
187 HandleKeyString( event.p2.mString );
189 delete [] event.p2.mString;
193 void HandleBackspaceKey()
198 void HandleCursorKey( int keyCode )
203 void HandleKeyString( const char* keyString )
208 void OnTapEvent( const Event& event )
210 unsigned int tapCount = event.p1.mUint;
214 ChangeState( EDITING );
216 float xPosition = event.p2.mFloat;
217 float yPosition = event.p3.mFloat;
219 GetClosestCursorPosition( xPosition, yPosition, height );
220 mDecorator->SetPosition( PRIMARY_CURSOR, xPosition, yPosition, height );
222 mDecoratorUpdated = true;
224 else if( 2u == tapCount )
226 ChangeState( SELECTING );
230 void OnGrabHandleEvent( const Event& event )
232 unsigned int state = event.p1.mUint;
234 if( GRAB_HANDLE_PRESSED == state )
236 float xPosition = event.p2.mFloat;
237 float yPosition = event.p3.mFloat;
240 GetClosestCursorPosition( xPosition, yPosition, height );
242 mDecorator->SetPosition( PRIMARY_CURSOR, xPosition, yPosition, height );
243 mDecoratorUpdated = true;
247 void ChangeState( State newState )
249 if( mState != newState )
253 if( INACTIVE == mState )
255 mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
256 mDecorator->StopCursorBlink();
257 mDecorator->SetGrabHandleActive( false );
258 mDecorator->SetSelectionActive( false );
259 mDecoratorUpdated = true;
261 else if ( SELECTING == mState )
263 mDecorator->SetActiveCursor( ACTIVE_CURSOR_NONE );
264 mDecorator->StopCursorBlink();
265 mDecorator->SetGrabHandleActive( false );
266 mDecorator->SetSelectionActive( true );
267 mDecoratorUpdated = true;
269 else if( EDITING == mState )
271 mDecorator->SetActiveCursor( ACTIVE_CURSOR_PRIMARY );
272 mDecorator->StartCursorBlink();
273 mDecorator->SetGrabHandleActive( true );
274 mDecorator->SetSelectionActive( false );
275 mDecoratorUpdated = true;
280 void GetClosestCursorPosition( float& x, float& y, float& height )
282 // TODO - Look at LineRuns first
284 Text::Length numberOfGlyphs = mVisualModel->GetNumberOfGlyphs();
285 if( 0 == numberOfGlyphs )
290 Vector<GlyphInfo> glyphs;
291 glyphs.Resize( numberOfGlyphs );
292 mVisualModel->GetGlyphs( glyphs.Begin(), 0, numberOfGlyphs );
293 const GlyphInfo* const glyphsBuffer = glyphs.Begin();
295 Vector<Vector2> positions;
296 positions.Resize( numberOfGlyphs );
297 mVisualModel->GetGlyphPositions( positions.Begin(), 0, numberOfGlyphs );
298 const Vector2* const positionsBuffer = positions.Begin();
300 unsigned int closestGlyph = 0;
301 float closestDistance = MAX_FLOAT;
303 for( unsigned int i = 0, numberOfGLyphs = glyphs.Count(); i < numberOfGLyphs; ++i )
305 const GlyphInfo& glyphInfo = *( glyphsBuffer + i );
306 const Vector2& position = *( positionsBuffer + i );
307 float glyphX = position.x + glyphInfo.width*0.5f;
308 float glyphY = position.y + glyphInfo.height*0.5f;
310 float distanceToGlyph = fabsf( glyphX - x ) + fabsf( glyphY - y );
312 if( distanceToGlyph < closestDistance )
314 closestDistance = distanceToGlyph;
319 // TODO - Consider RTL languages
320 x = positions[closestGlyph].x + glyphs[closestGlyph].width;
324 TextAbstraction::FontClient::Get().GetFontMetrics( glyphs[closestGlyph].fontId, metrics );
325 height = metrics.height; // TODO - Fix for multi-line
328 LogicalModelPtr mLogicalModel;
329 VisualModelPtr mVisualModel;
330 DecoratorPtr mDecorator;
332 std::string mPlaceholderText;
335 * This is used to delay handling events until after the model has been updated.
336 * The number of updates to the model is minimized to improve performance.
338 vector<Event> mEventQueue; ///< The queue of touch events etc.
342 bool mDecoratorUpdated;
345 struct Controller::FontDefaults
348 : mDefaultPointSize(0.0f),
353 FontId GetFontId( TextAbstraction::FontClient& fontClient )
357 Dali::TextAbstraction::PointSize26Dot6 pointSize = mDefaultPointSize*64;
358 mFontId = fontClient.GetFontId( mDefaultFontFamily, mDefaultFontStyle, pointSize );
364 std::string mDefaultFontFamily;
365 std::string mDefaultFontStyle;
366 float mDefaultPointSize;
370 struct Controller::Impl
372 Impl( ControlInterface& controlInterface )
373 : mControlInterface( controlInterface ),
376 mFontDefaults( NULL ),
383 mOperationsPending( NO_OPERATION ),
384 mRecalculateNaturalSize( true )
386 mLogicalModel = LogicalModel::New();
387 mVisualModel = VisualModel::New();
389 mFontClient = TextAbstraction::FontClient::Get();
391 mView.SetVisualModel( mVisualModel );
399 ControlInterface& mControlInterface; ///< Reference to the text controller.
400 LogicalModelPtr mLogicalModel; ///< Pointer to the logical model.
401 VisualModelPtr mVisualModel; ///< Pointer to the visual model.
402 FontDefaults* mFontDefaults; ///< Avoid allocating this when the user does not specify a font.
403 Controller::TextInput* mTextInput; ///< Avoid allocating everything for text input until EnableTextInput().
404 TextAbstraction::FontClient mFontClient; ///< Handle to the font client.
405 View mView; ///< The view interface to the rendering back-end.
406 LayoutEngine mLayoutEngine; ///< The layout engine.
407 std::string mNewText; ///< Temporary stores the text set until the next relayout.
408 Size mControlSize; ///< The size of the control.
409 OperationsMask mOperationsPending; ///< Operations pending to be done to layout the text.
410 bool mRecalculateNaturalSize:1; ///< Whether the natural size needs to be recalculated.
413 ControllerPtr Controller::New( ControlInterface& controlInterface )
415 return ControllerPtr( new Controller( controlInterface ) );
418 void Controller::SetText( const std::string& text )
420 // Keep until size negotiation
421 mImpl->mNewText = text;
423 // All operations need to be done. (convert to utf32, get break info, ..., layout, ...)
424 mImpl->mOperationsPending = ALL_OPERATIONS;
426 // The natural size needs to be re-calculated.
427 mImpl->mRecalculateNaturalSize = true;
429 if( mImpl->mTextInput )
431 // Cancel previously queued events
432 mImpl->mTextInput->mEventQueue.clear();
434 // TODO - Hide selection decorations
438 void Controller::GetText( std::string& text ) const
440 if( !mImpl->mNewText.empty() )
442 text = mImpl->mNewText;
446 // TODO - Convert from UTF-32
450 void Controller::SetPlaceholderText( const std::string& text )
452 if( !mImpl->mTextInput )
454 mImpl->mTextInput->mPlaceholderText = text;
458 void Controller::GetPlaceholderText( std::string& text ) const
460 if( !mImpl->mTextInput )
462 text = mImpl->mTextInput->mPlaceholderText;
466 void Controller::SetDefaultFontFamily( const std::string& defaultFontFamily )
468 if( !mImpl->mFontDefaults )
470 mImpl->mFontDefaults = new Controller::FontDefaults();
473 mImpl->mFontDefaults->mDefaultFontFamily = defaultFontFamily;
474 mImpl->mFontDefaults->mFontId = 0u; // Remove old font ID
475 mImpl->mOperationsPending = ALL_OPERATIONS;
476 mImpl->mRecalculateNaturalSize = true;
479 const std::string& Controller::GetDefaultFontFamily() const
481 if( mImpl->mFontDefaults )
483 return mImpl->mFontDefaults->mDefaultFontFamily;
486 return Dali::String::EMPTY;
489 void Controller::SetDefaultFontStyle( const std::string& defaultFontStyle )
491 if( !mImpl->mFontDefaults )
493 mImpl->mFontDefaults = new Controller::FontDefaults();
496 mImpl->mFontDefaults->mDefaultFontStyle = defaultFontStyle;
497 mImpl->mFontDefaults->mFontId = 0u; // Remove old font ID
498 mImpl->mOperationsPending = ALL_OPERATIONS;
499 mImpl->mRecalculateNaturalSize = true;
502 const std::string& Controller::GetDefaultFontStyle() const
504 if( mImpl->mFontDefaults )
506 return mImpl->mFontDefaults->mDefaultFontStyle;
509 return Dali::String::EMPTY;
512 void Controller::SetDefaultPointSize( float pointSize )
514 if( !mImpl->mFontDefaults )
516 mImpl->mFontDefaults = new Controller::FontDefaults();
519 mImpl->mFontDefaults->mDefaultPointSize = pointSize;
520 mImpl->mFontDefaults->mFontId = 0u; // Remove old font ID
521 mImpl->mOperationsPending = ALL_OPERATIONS;
522 mImpl->mRecalculateNaturalSize = true;
525 float Controller::GetDefaultPointSize() const
527 if( mImpl->mFontDefaults )
529 return mImpl->mFontDefaults->mDefaultPointSize;
535 void Controller::EnableTextInput( DecoratorPtr decorator )
537 if( !mImpl->mTextInput )
539 mImpl->mTextInput = new TextInput( mImpl->mLogicalModel, mImpl->mVisualModel, decorator );
543 bool Controller::Relayout( const Vector2& size )
545 if( ( size.width < Math::MACHINE_EPSILON_1000 ) || ( size.height < Math::MACHINE_EPSILON_1000 ) )
547 // Not worth to relayout if width or height is equal to zero.
551 if( size != mImpl->mControlSize )
553 // Operations that need to be done if the size changes.
554 mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending |
560 mImpl->mControlSize = size;
564 bool updated = DoRelayout( mImpl->mControlSize,
565 mImpl->mOperationsPending,
568 // Do not re-do any operation until something changes.
569 mImpl->mOperationsPending = NO_OPERATION;
571 if( mImpl->mTextInput )
573 // Move the cursor, grab handle etc.
574 updated = mImpl->mTextInput->ProcessInputEvents() || updated;
580 bool Controller::DoRelayout( const Vector2& size,
581 OperationsMask operationsRequired,
584 bool viewUpdated( false );
586 // Calculate the operations to be done.
587 const OperationsMask operations = static_cast<OperationsMask>( mImpl->mOperationsPending & operationsRequired );
589 Vector<Character> utf32Characters;
590 Length characterCount = 0u;
591 if( CONVERT_TO_UTF32 & operations )
593 std::string& text = mImpl->mNewText;
595 // Convert text into UTF-32
596 utf32Characters.Resize( text.size() );
598 // This is a bit horrible but std::string returns a (signed) char*
599 const uint8_t* utf8 = reinterpret_cast<const uint8_t*>( text.c_str() );
601 // Transform a text array encoded in utf8 into an array encoded in utf32.
602 // It returns the actual number of characters.
603 characterCount = Utf8ToUtf32( utf8, text.size(), utf32Characters.Begin() );
604 utf32Characters.Resize( characterCount );
606 // Sets the text into the model.
607 mImpl->mLogicalModel->SetText( utf32Characters.Begin(), characterCount );
609 // Discard temporary text
613 Vector<LineBreakInfo> lineBreakInfo;
614 if( GET_LINE_BREAKS & operations )
616 // Retrieves the line break info. The line break info is used to split the text in 'paragraphs' to
617 // calculate the bidirectional info for each 'paragraph'.
618 // It's also used to layout the text (where it should be a new line) or to shape the text (text in different lines
619 // is not shaped together).
620 lineBreakInfo.Resize( characterCount, TextAbstraction::LINE_NO_BREAK );
622 SetLineBreakInfo( utf32Characters,
625 mImpl->mLogicalModel->SetLineBreakInfo( lineBreakInfo.Begin(), characterCount );
628 Vector<WordBreakInfo> wordBreakInfo;
629 if( GET_WORD_BREAKS & operations )
631 // Retrieves the word break info. The word break info is used to layout the text (where to wrap the text in lines).
632 wordBreakInfo.Resize( characterCount, TextAbstraction::WORD_NO_BREAK );
634 SetWordBreakInfo( utf32Characters,
637 mImpl->mLogicalModel->SetWordBreakInfo( wordBreakInfo.Begin(), characterCount );
640 const bool getScripts = GET_SCRIPTS & operations;
641 const bool validateFonts = VALIDATE_FONTS & operations;
643 Vector<ScriptRun> scripts;
644 Vector<FontRun> fonts;
646 if( mImpl->mFontDefaults )
648 // TODO - pass into ValidateFonts
651 if( getScripts || validateFonts )
653 // Validates the fonts assigned by the application or assigns default ones.
654 // It makes sure all the characters are going to be rendered by the correct font.
655 MultilanguageSupport multilanguageSupport = MultilanguageSupport::Get();
659 // Retrieves the scripts used in the text.
660 multilanguageSupport.SetScripts( utf32Characters,
664 // Sets the scripts into the model.
665 mImpl->mLogicalModel->SetScripts( scripts.Begin(), scripts.Count() );
670 // Validates the fonts. If there is a character with no assigned font it sets a default one.
671 // After this call, fonts are validated.
672 multilanguageSupport.ValidateFonts( utf32Characters,
676 // Sets the fonts into the model.
677 mImpl->mLogicalModel->SetFonts( fonts.Begin(), fonts.Count() );
681 Vector<GlyphInfo> glyphs;
682 Vector<CharacterIndex> glyphsToCharactersMap;
683 Vector<Length> charactersPerGlyph;
684 if( SHAPE_TEXT & operations )
687 ShapeText( utf32Characters,
692 glyphsToCharactersMap,
693 charactersPerGlyph );
696 if( GET_GLYPH_METRICS & operations )
698 mImpl->mFontClient.GetGlyphMetrics( glyphs.Begin(), glyphs.Count() );
701 Length numberOfGlyphs = glyphs.Count();
702 if( 0u != numberOfGlyphs )
704 // Sets the glyphs into the model.
705 mImpl->mVisualModel->SetGlyphs( glyphs.Begin(),
706 glyphsToCharactersMap.Begin(),
707 charactersPerGlyph.Begin(),
711 if( LAYOUT & operations )
713 if( 0u == numberOfGlyphs )
715 const Length numberOfCharacters = mImpl->mLogicalModel->GetNumberOfCharacters();
716 numberOfGlyphs = mImpl->mVisualModel->GetNumberOfGlyphs();
718 lineBreakInfo.Resize( numberOfCharacters );
719 wordBreakInfo.Resize( numberOfCharacters );
720 glyphs.Resize( numberOfGlyphs );
721 glyphsToCharactersMap.Resize( numberOfGlyphs );
722 charactersPerGlyph.Resize( numberOfGlyphs );
724 mImpl->mLogicalModel->GetLineBreakInfo( lineBreakInfo.Begin(),
726 numberOfCharacters );
728 mImpl->mLogicalModel->GetWordBreakInfo( wordBreakInfo.Begin(),
730 numberOfCharacters );
732 mImpl->mVisualModel->GetGlyphs( glyphs.Begin(),
736 mImpl->mVisualModel->GetGlyphToCharacterMap( glyphsToCharactersMap.Begin(),
740 mImpl->mVisualModel->GetCharactersPerGlyphMap( charactersPerGlyph.Begin(),
745 // Set the layout parameters.
746 LayoutParameters layoutParameters( size,
747 lineBreakInfo.Begin(),
748 wordBreakInfo.Begin(),
751 glyphsToCharactersMap.Begin(),
752 charactersPerGlyph.Begin() );
754 // Reserve space to set the positions of the glyphs.
755 Vector<Vector2> glyphPositions;
756 glyphPositions.Resize( numberOfGlyphs );
758 // Update the visual model
759 viewUpdated = mImpl->mLayoutEngine.LayoutText( layoutParameters,
763 // Sets the positions into the model.
764 if( UPDATE_POSITIONS & operations )
766 mImpl->mVisualModel->SetGlyphPositions( glyphPositions.Begin(),
770 // Sets the actual size.
771 if( UPDATE_ACTUAL_SIZE & operations )
773 mImpl->mVisualModel->SetActualSize( layoutSize );
778 layoutSize = mImpl->mVisualModel->GetActualSize();
784 Vector3 Controller::GetNaturalSize()
788 if( mImpl->mRecalculateNaturalSize )
790 // Operations that can be done only once until the text changes.
791 const OperationsMask onlyOnceOperations = static_cast<OperationsMask>( CONVERT_TO_UTF32 |
799 // Operations that need to be done if the size changes.
800 const OperationsMask sizeOperations = static_cast<OperationsMask>( LAYOUT |
803 DoRelayout( Size( MAX_FLOAT, MAX_FLOAT ),
804 static_cast<OperationsMask>( onlyOnceOperations |
806 naturalSize.GetVectorXY() );
808 // Do not do again the only once operations.
809 mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending & ~onlyOnceOperations );
811 // Do the size related operations again.
812 mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending | sizeOperations );
814 // Stores the natural size to avoid recalculate it again
815 // unless the text/style changes.
816 mImpl->mVisualModel->SetNaturalSize( naturalSize.GetVectorXY() );
818 mImpl->mRecalculateNaturalSize = false;
822 naturalSize = mImpl->mVisualModel->GetNaturalSize();
828 float Controller::GetHeightForWidth( float width )
831 if( width != mImpl->mControlSize.width )
833 // Operations that can be done only once until the text changes.
834 const OperationsMask onlyOnceOperations = static_cast<OperationsMask>( CONVERT_TO_UTF32 |
842 // Operations that need to be done if the size changes.
843 const OperationsMask sizeOperations = static_cast<OperationsMask>( LAYOUT |
846 DoRelayout( Size( width, MAX_FLOAT ),
847 static_cast<OperationsMask>( onlyOnceOperations |
851 // Do not do again the only once operations.
852 mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending & ~onlyOnceOperations );
854 // Do the size related operations again.
855 mImpl->mOperationsPending = static_cast<OperationsMask>( mImpl->mOperationsPending | sizeOperations );
859 layoutSize = mImpl->mVisualModel->GetActualSize();
862 return layoutSize.height;
865 View& Controller::GetView()
870 LayoutEngine& Controller::GetLayoutEngine()
872 return mImpl->mLayoutEngine;
875 void Controller::RequestRelayout()
877 mImpl->mControlInterface.RequestTextRelayout();
880 void Controller::KeyboardFocusGainEvent()
882 DALI_ASSERT_DEBUG( mImpl->mTextInput && "Unexpected KeyboardFocusGainEvent" );
884 if( mImpl->mTextInput )
886 TextInput::Event event( TextInput::KEYBOARD_FOCUS_GAIN_EVENT );
887 mImpl->mTextInput->mEventQueue.push_back( event );
893 void Controller::KeyboardFocusLostEvent()
895 DALI_ASSERT_DEBUG( mImpl->mTextInput && "Unexpected KeyboardFocusLostEvent" );
897 if( mImpl->mTextInput )
899 TextInput::Event event( TextInput::KEYBOARD_FOCUS_LOST_EVENT );
900 mImpl->mTextInput->mEventQueue.push_back( event );
906 bool Controller::KeyEvent( const Dali::KeyEvent& keyEvent )
908 DALI_ASSERT_DEBUG( mImpl->mTextInput && "Unexpected KeyEvent" );
910 if( mImpl->mTextInput )
912 TextInput::Event event( TextInput::KEY_EVENT );
913 event.p1.mInt = keyEvent.keyCode;
914 event.p2.mString = NULL;
916 const std::string& keyString = keyEvent.keyPressed;
917 if ( !keyString.empty() )
919 event.p2.mString = new char[keyString.size() + 1];
920 std::copy(keyString.begin(), keyString.end(), event.p2.mString);
921 event.p2.mString[keyString.size()] = '\0';
924 mImpl->mTextInput->mEventQueue.push_back( event );
932 void Controller::TapEvent( unsigned int tapCount, float x, float y )
934 DALI_ASSERT_DEBUG( mImpl->mTextInput && "Unexpected TapEvent" );
936 if( mImpl->mTextInput )
938 TextInput::Event event( TextInput::TAP_EVENT );
939 event.p1.mUint = tapCount;
942 mImpl->mTextInput->mEventQueue.push_back( event );
948 void Controller::GrabHandleEvent( GrabHandleState state, float x, float y )
950 DALI_ASSERT_DEBUG( mImpl->mTextInput && "Unexpected GrabHandleEvent" );
952 if( mImpl->mTextInput )
954 TextInput::Event event( TextInput::GRAB_HANDLE_EVENT );
955 event.p1.mUint = state;
958 mImpl->mTextInput->mEventQueue.push_back( event );
964 Controller::~Controller()
969 Controller::Controller( ControlInterface& controlInterface )
972 mImpl = new Controller::Impl( controlInterface );
977 } // namespace Toolkit