summaryrefslogtreecommitdiff
path: root/core/launcher/inputmethods.cpp
authorzecke <zecke>2003-08-28 14:34:45 (UTC)
committer zecke <zecke>2003-08-28 14:34:45 (UTC)
commit3f829e499bac92d52a9bbb1ad6b31bb74734287f (patch) (side-by-side diff)
tree7d757842147e15076308d3bad0c852f36f8076e7 /core/launcher/inputmethods.cpp
parentd9f3423c2e2aca46ab10f08152450b3ebf5221cf (diff)
downloadopie-3f829e499bac92d52a9bbb1ad6b31bb74734287f.zip
opie-3f829e499bac92d52a9bbb1ad6b31bb74734287f.tar.gz
opie-3f829e499bac92d52a9bbb1ad6b31bb74734287f.tar.bz2
Use QLibrary directly ( make it compile with Opie )
Save the last used InputMethod
Diffstat (limited to 'core/launcher/inputmethods.cpp') (more/less context) (ignore whitespace changes)
-rw-r--r--core/launcher/inputmethods.cpp533
1 files changed, 389 insertions, 144 deletions
diff --git a/core/launcher/inputmethods.cpp b/core/launcher/inputmethods.cpp
index 09b9a83..f0d8294 100644
--- a/core/launcher/inputmethods.cpp
+++ b/core/launcher/inputmethods.cpp
@@ -1,7 +1,7 @@
/**********************************************************************
-** Copyright (C) 2000 Trolltech AS. All rights reserved.
+** Copyright (C) 2000-2002 Trolltech AS. All rights reserved.
**
-** This file is part of Qtopia Environment.
+** This file is part of the Qtopia Environment.
**
** This file may be distributed and/or modified under the terms of the
** GNU General Public License version 2 as published by the Free Software
@@ -21,33 +21,37 @@
#define QTOPIA_INTERNAL_LANGLIST
#include "inputmethods.h"
-#include <qpe/config.h>
-#include <qpe/qpeapplication.h>
-#include <qpe/inputmethodinterface.h>
-#include <qpe/qlibrary.h>
-#include <qpe/global.h>
+#include <qtopia/config.h>
+#include <qtopia/qpeapplication.h>
+#include <qtopia/inputmethodinterface.h>
+#include <qtopia/global.h>
#include <qpopupmenu.h>
#include <qpushbutton.h>
#include <qtoolbutton.h>
+#include <qwidgetstack.h>
#include <qwidget.h>
#include <qlayout.h>
#include <qtimer.h>
#include <qdir.h>
#include <stdlib.h>
#include <qtranslator.h>
+#include <qtl.h>
#ifdef Q_WS_QWS
#include <qwindowsystem_qws.h>
#include <qwsevent_qws.h>
+#include <qcopchannel_qws.h>
#endif
-#ifdef SINGLE_APP
-#include "handwritingimpl.h"
-#include "keyboardimpl.h"
-#include "pickboardimpl.h"
+/* ### SingleFloppy if someone is interested? */
+#if 0
+#ifdef QT_NO_COMPONENT
+#include "../plugins/inputmethods/handwriting/handwritingimpl.h"
+#include "../plugins/inputmethods/keyboard/keyboardimpl.h"
+#include "../3rdparty/plugins/inputmethods/pickboard/pickboardimpl.h"
+#endif
#endif
-
/* XPM */
static const char * tri_xpm[]={
@@ -64,55 +68,103 @@ static const char * tri_xpm[]={
".........",
"........."};
-static const int inputWidgetStyle = QWidget::WStyle_Customize |
- QWidget::WStyle_Tool |
- QWidget::WStyle_StaysOnTop |
- QWidget::WGroupLeader;
+static const int inputWidgetStyle = QWidget::WStyle_Customize |
+ QWidget::WStyle_Tool |
+ QWidget::WStyle_StaysOnTop |
+ QWidget::WGroupLeader;
-InputMethods::InputMethods( QWidget *parent ) :
- QWidget( parent, "InputMethods", WStyle_Tool | WStyle_Customize )
+
+int InputMethod::operator <(const InputMethod& o) const
+{
+ return name() < o.name();
+}
+int InputMethod::operator >(const InputMethod& o) const
+{
+ return name() > o.name();
+}
+int InputMethod::operator <=(const InputMethod& o) const
{
- method = NULL;
+ return name() <= o.name();
+}
+
+
+/*
+ Slightly hacky: We use WStyle_Tool as a flag to say "this widget
+ belongs to the IM system, so clicking it should not cause a reset".
+ */
+class IMToolButton : public QToolButton
+{
+public:
+ IMToolButton::IMToolButton( QWidget *parent ) : QToolButton( parent )
+ { setWFlags( WStyle_Tool ); }
+};
- setBackgroundMode ( PaletteBackground );
+InputMethods::InputMethods( QWidget *parent ) :
+ QWidget( parent, "InputMethods", WStyle_Tool | WStyle_Customize ),
+ mkeyboard(0), imethod(0)
+{
+ setBackgroundMode( PaletteBackground );
QHBoxLayout *hbox = new QHBoxLayout( this );
- kbdButton = new QToolButton( this );
+ kbdButton = new IMToolButton( this);
kbdButton->setFocusPolicy(NoFocus);
kbdButton->setToggleButton( TRUE );
- kbdButton->setFixedHeight( 17 );
+ if (parent->sizeHint().height() > 0)
+ kbdButton->setFixedHeight( parent->sizeHint().height() );
kbdButton->setFixedWidth( 32 );
kbdButton->setAutoRaise( TRUE );
kbdButton->setUsesBigPixmap( TRUE );
hbox->addWidget( kbdButton );
connect( kbdButton, SIGNAL(toggled(bool)), this, SLOT(showKbd(bool)) );
- kbdChoice = new QToolButton( this );
+ kbdChoice = new IMToolButton( this );
kbdChoice->setFocusPolicy(NoFocus);
kbdChoice->setPixmap( QPixmap( (const char **)tri_xpm ) );
- kbdChoice->setFixedHeight( 17 );
- kbdChoice->setFixedWidth( 12 );
+ if (parent->sizeHint().height() > 0)
+ kbdChoice->setFixedHeight( parent->sizeHint().height() );
+ kbdChoice->setFixedWidth( 13 );
kbdChoice->setAutoRaise( TRUE );
hbox->addWidget( kbdChoice );
connect( kbdChoice, SIGNAL(clicked()), this, SLOT(chooseKbd()) );
connect( (QPEApplication*)qApp, SIGNAL(clientMoused()),
- this, SLOT(resetStates()) );
+ this, SLOT(resetStates()) );
+
+
+ imButton = new QWidgetStack( this ); // later a widget stack
+ imButton->setFocusPolicy(NoFocus);
+ if (parent->sizeHint().height() > 0)
+ imButton->setFixedHeight( parent->sizeHint().height() );
+ hbox->addWidget(imButton);
+
+ imChoice = new QToolButton( this );
+ imChoice->setFocusPolicy(NoFocus);
+ imChoice->setPixmap( QPixmap( (const char **)tri_xpm ) );
+ if (parent->sizeHint().height() > 0)
+ imChoice->setFixedHeight( parent->sizeHint().height() );
+ imChoice->setFixedWidth( 13 );
+ imChoice->setAutoRaise( TRUE );
+ hbox->addWidget( imChoice );
+ connect( imChoice, SIGNAL(clicked()), this, SLOT(chooseIm()) );
loadInputMethods();
+
+ QCopChannel *channel = new QCopChannel( "QPE/IME", this );
+ connect( channel, SIGNAL(received(const QCString&, const QByteArray&)),
+ this, SLOT(qcopReceive(const QCString&, const QByteArray&)) );
}
InputMethods::~InputMethods()
{
-#ifndef SINGLE_APP
- QValueList<InputMethod>::Iterator mit;
- for ( mit = inputMethodList.begin(); mit != inputMethodList.end(); ++mit ) {
- (void) (*mit).interface->release();
- (*mit).library->unload();
- delete (*mit).library;
- }
-#endif
+ Config cfg("qpe");
+ cfg.setGroup("InputMethod");
+ if (imethod)
+ cfg.writeEntry("im", imethod->name() );
+ if (mkeyboard)
+ cfg.writeEntry("current", mkeyboard->name() );
+
+ unloadInputMethods();
}
void InputMethods::hideInputMethod()
@@ -131,122 +183,207 @@ void InputMethods::showInputMethod(const QString& name)
QValueList<InputMethod>::Iterator it;
InputMethod *im = 0;
for ( it = inputMethodList.begin(); it != inputMethodList.end(); ++it, i++ ) {
- if ( (*it).interface->name() == name ) {
- im = &(*it);
- break;
- }
+ QString lname = (*it).libName.mid((*it).libName.findRev('/') + 1);
+ if ( (*it).name() == name || lname == name ) {
+ im = &(*it);
+ break;
+ }
}
if ( im )
- chooseMethod(im);
+ chooseKeyboard(im);
}
void InputMethods::resetStates()
{
- if ( method )
- method->interface->resetState();
+ if ( mkeyboard && !mkeyboard->newIM )
+ mkeyboard->interface->resetState();
}
QRect InputMethods::inputRect() const
{
- if ( !method || !method->widget->isVisible() )
- return QRect();
+ if ( !mkeyboard || !mkeyboard->widget || !mkeyboard->widget->isVisible() )
+ return QRect();
else
- return method->widget->geometry();
+ return mkeyboard->widget->geometry();
+}
+
+void InputMethods::unloadInputMethods()
+{
+ unloadMethod( inputMethodList );
+ unloadMethod( inputModifierList );
+ inputMethodList.clear();
+ inputModifierList.clear();
+
+}
+
+void InputMethods::unloadMethod( QValueList<InputMethod>& list ) {
+ QValueList<InputMethod>::Iterator it;
+
+ for (it = list.begin(); it != list.end(); ++it )
+ (*it).releaseInterface();
+
+}
+
+
+QStringList InputMethods::plugins()const {
+ QString path = QPEApplication::qpeDir() + "/plugins/inputmethods";
+ QDir dir( path, "lib*.so" );
+ return dir.entryList();
+}
+
+void InputMethods::installTranslator( const QString& type ) {
+ QStringList langs = Global::languageList();
+ QStringList::ConstIterator lit;
+ for ( lit= langs.begin(); lit!=langs.end(); ++lit) {
+ QString lang = *lit;
+ QTranslator * trans = new QTranslator(qApp);
+
+ QString tfn = QPEApplication::qpeDir()+"/i18n/"+lang+"/"+type+".qm";
+
+ if ( trans->load( tfn ))
+ qApp->installTranslator( trans );
+ else
+ delete trans;
+ }
+}
+
+void InputMethods::setPreferedHandlers() {
+ Config cfg("qpe");
+ cfg.setGroup("InputMethod");
+ QString current = cfg.readEntry("current");
+ QString im = cfg.readEntry("im");
+
+ QValueList<InputMethod>::Iterator it;
+ if (!inputModifierList.isEmpty() && !im.isEmpty() ) {
+ for (it = inputModifierList.begin(); it != inputModifierList.end(); ++it )
+ if ( (*it).name() == im ) {
+ imethod = &(*it); break;
+ }
+
+ }
+ if (!inputMethodList.isEmpty() && !current.isEmpty() ) {
+ for (it = inputMethodList.begin(); it != inputMethodList.end(); ++it )
+ if ( (*it).name() == current ) {
+ qWarning("preferred keyboard is %s", current.latin1() );
+ mkeyboard = &(*it);
+ kbdButton->setPixmap( *mkeyboard->icon() );
+ break;
+ }
+ }
+
}
void InputMethods::loadInputMethods()
{
-#ifndef SINGLE_APP
+#ifndef QT_NO_COMPONENT
hideInputMethod();
- method = 0;
+ mkeyboard = 0;
- QValueList<InputMethod>::Iterator mit;
- for ( mit = inputMethodList.begin(); mit != inputMethodList.end(); ++mit ) {
- (*mit).interface->release();
- (*mit).library->unload();
- delete (*mit).library;
- }
- inputMethodList.clear();
+ unloadInputMethods();
QString path = QPEApplication::qpeDir() + "/plugins/inputmethods";
- QDir dir( path, "lib*.so" );
- QStringList list = dir.entryList();
+ QStringList list = plugins();
QStringList::Iterator it;
for ( it = list.begin(); it != list.end(); ++it ) {
- InputMethodInterface *iface = 0;
- QLibrary *lib = new QLibrary( path + "/" + *it );
- if ( lib->queryInterface( IID_InputMethod, (QUnknownInterface**)&iface ) == QS_OK ) {
- InputMethod input;
- input.library = lib;
- input.interface = iface;
- input.widget = input.interface->inputMethod( 0, inputWidgetStyle );
- input.interface->onKeyPress( this, SLOT(sendKey(ushort,ushort,ushort,bool,bool)) );
- inputMethodList.append( input );
-
- QString type = (*it).left( (*it).find(".") );
- QStringList langs = Global::languageList();
- for (QStringList::ConstIterator lit = langs.begin(); lit!=langs.end(); ++lit) {
- QString lang = *lit;
- QTranslator * trans = new QTranslator(qApp);
- QString tfn = QPEApplication::qpeDir()+"/i18n/"+lang+"/"+type+".qm";
- if ( trans->load( tfn ))
- qApp->installTranslator( trans );
- else
- delete trans;
- }
- } else {
- delete lib;
- }
- }
-#else
- InputMethod input;
- input.interface = new HandwritingImpl();
- input.widget = input.interface->inputMethod( 0, inputWidgetStyle );
- input.interface->onKeyPress( this, SLOT(sendKey(ushort,ushort,ushort,bool,bool)) );
- inputMethodList.append( input );
- input.interface = new KeyboardImpl();
- input.widget = input.interface->inputMethod( 0, inputWidgetStyle );
- input.interface->onKeyPress( this, SLOT(sendKey(ushort,ushort,ushort,bool,bool)) );
- inputMethodList.append( input );
- input.interface = new PickboardImpl();
- input.widget = input.interface->inputMethod( 0, inputWidgetStyle );
- input.interface->onKeyPress( this, SLOT(sendKey(ushort,ushort,ushort,bool,bool)) );
- inputMethodList.append( input );
-#endif
- if ( !inputMethodList.isEmpty() ) {
- method = &inputMethodList[0];
- Config cfg("qpe");
- cfg.setGroup("InputMethod");
- QString curMethod = cfg.readEntry("current","");
- int i = 0;
- QValueList<InputMethod>::Iterator it;
- for ( it = inputMethodList.begin(); it != inputMethodList.end(); ++it, i++ ) {
- if((*it).interface->name() == curMethod) {
- method = &inputMethodList[i];
- }
+ InputMethodInterface *iface = 0;
+ ExtInputMethodInterface *eface = 0;
+ QLibrary *lib = new QLibrary( path + "/" + *it );
+
+ if ( lib->queryInterface( IID_InputMethod, (QUnknownInterface**)&iface ) == QS_OK ) {
+ InputMethod input;
+ input.newIM = FALSE;
+ input.library = lib;
+ input.libName = *it;
+ input.interface = iface;
+ input.widget = input.interface->inputMethod( 0, inputWidgetStyle );
+ input.interface->onKeyPress( this, SLOT(sendKey(ushort,ushort,ushort,bool,bool)) );
+ inputMethodList.append( input );
+ } else if ( lib->queryInterface( IID_ExtInputMethod, (QUnknownInterface**)&eface ) == QS_OK ) {
+ InputMethod input;
+ input.newIM = TRUE;
+ input.library = lib;
+ input.libName = *it;
+ input.extInterface = eface;
+ input.widget = input.extInterface->keyboardWidget( 0, inputWidgetStyle );
+ // may be either a simple, or advanced.
+ if (input.widget) {
+ //qDebug("its a keyboard");
+ inputMethodList.append( input );
+ } else {
+ //qDebug("its a real im");
+ input.widget = input.extInterface->statusWidget( 0, 0 );
+ if (input.widget) {
+ //qDebug("blah");
+ inputModifierList.append( input );
+ imButton->addWidget(input.widget, inputModifierList.count());
+ }
+ }
+ }else{
+ delete lib;
+ lib = 0l;
}
- kbdButton->setPixmap( *method->interface->icon() );
+ installTranslator( (*it).left( (*it).find(".") ) );
}
- if ( !inputMethodList.isEmpty() )
- kbdButton->show();
+ qHeapSort( inputMethodList );
+#endif /* killed BUILT in cause they would not compile */
+
+ QWSServer::setCurrentInputMethod( 0 );
+
+ /* set the prefered IM + handler */
+ setPreferedHandlers();
+ if ( !inputModifierList.isEmpty() ) {
+ if (!imethod)
+ imethod = &inputModifierList[0];
+ imButton->raiseWidget(imethod->widget);
+ QWSServer::setCurrentInputMethod( imethod->extInterface->inputMethod() );
+ } else {
+ imethod = 0;
+ }
+
+ // we need to update keyboards afterwards, as some of them may not be compatible with
+ // the current input method
+ updateKeyboards(imethod);
+
+ if ( !inputModifierList.isEmpty() )
+ imButton->show();
else
- kbdButton->hide();
- if ( inputMethodList.count() > 1 )
- kbdChoice->show();
+ imButton->hide();
+
+ if ( inputModifierList.count() > 1 )
+ imChoice->show();
else
- kbdChoice->hide();
+ imChoice->hide();
}
void InputMethods::chooseKbd()
{
QPopupMenu pop( this );
+ pop.setFocusPolicy( NoFocus ); //don't reset IM
+
+ QString imname;
+ if (imethod)
+ imname = imethod->libName.mid(imethod->libName.findRev('/') + 1);
int i = 0;
+ int firstDepKbd = 0;
+
QValueList<InputMethod>::Iterator it;
for ( it = inputMethodList.begin(); it != inputMethodList.end(); ++it, i++ ) {
- pop.insertItem( (*it).interface->name(), i );
- if ( method == &(*it) )
- pop.setItemChecked( i, TRUE );
+ // add empty new items, all old items.
+ if (!(*it).newIM || (*it).extInterface->compatible().count() == 0 ) {
+ pop.insertItem( (*it).name(), i, firstDepKbd);
+ if ( mkeyboard == &(*it) )
+ pop.setItemChecked( i, TRUE );
+
+ firstDepKbd++;
+ } else if ( (*it).extInterface->compatible().contains(imname)) {
+ // check if we need to insert a sep.
+ if (firstDepKbd == i)
+ pop.insertSeparator();
+ pop.insertItem( (*it).name(), i, -1);
+ if ( mkeyboard == &(*it) )
+ pop.setItemChecked( i, TRUE );
+ }
}
QPoint pt = mapToGlobal(kbdChoice->geometry().topRight());
@@ -255,48 +392,156 @@ void InputMethods::chooseKbd()
pt.rx() -= s.width();
i = pop.exec( pt );
if ( i == -1 )
- return;
+ return;
InputMethod *im = &inputMethodList[i];
+ chooseKeyboard(im);
+}
+
+void InputMethods::chooseIm()
+{
+ QPopupMenu pop( this );
+
+ int i = 0;
+ QValueList<InputMethod>::Iterator it;
+ for ( it = inputModifierList.begin(); it != inputModifierList.end(); ++it, i++ ) {
+ pop.insertItem( (*it).name(), i );
+ if ( imethod == &(*it) )
+ pop.setItemChecked( i, TRUE );
+ }
+
+ QPoint pt = mapToGlobal(imChoice->geometry().topRight());
+ QSize s = pop.sizeHint();
+ pt.ry() -= s.height();
+ pt.rx() -= s.width();
+ i = pop.exec( pt );
+ if ( i == -1 )
+ return;
+ InputMethod *im = &inputModifierList[i];
+
chooseMethod(im);
}
+void InputMethods::chooseKeyboard(InputMethod* im)
+{
+ if ( im != mkeyboard ) {
+ if ( mkeyboard && mkeyboard->widget->isVisible() )
+ mkeyboard->widget->hide();
+ mkeyboard = im;
+ kbdButton->setPixmap( *mkeyboard->icon() );
+ }
+ if ( !kbdButton->isOn() )
+ kbdButton->setOn( TRUE );
+ else
+ showKbd( TRUE );
+}
+
+static bool keyboardCompatible(InputMethod *keyb, const QString &imname )
+{
+ if ( !keyb || !keyb->newIM || !keyb->extInterface->compatible().count() )
+ return TRUE;
+
+ if ( keyb->extInterface->compatible().contains(imname) )
+ return TRUE;
+
+ return FALSE;
+}
+
+// Updates the display of the soft keyboards available to the current input method
+void InputMethods::updateKeyboards(InputMethod *im )
+{
+ uint count;
+
+ if ( im ) {
+ QString imname = im->libName.mid(im->libName.findRev('/') + 1);
+
+ if ( mkeyboard && !keyboardCompatible(mkeyboard, imname) ) {
+ kbdButton->setOn( FALSE );
+ showKbd( FALSE );
+ mkeyboard = 0;
+ }
+
+ count = 0;
+
+ QValueList<InputMethod>::Iterator it;
+ for ( it = inputMethodList.begin(); it != inputMethodList.end(); ++it ) {
+ if ( keyboardCompatible( &(*it), imname ) ) {
+ if ( !mkeyboard ) {
+ mkeyboard = &(*it);
+ kbdButton->setPixmap( *mkeyboard->icon() );
+ }
+
+ count++;
+ }
+ }
+ } else {
+ count = inputMethodList.count();
+ if ( count && !mkeyboard ) {
+ mkeyboard = &inputMethodList[0];
+ kbdButton->setPixmap( *mkeyboard->icon() );
+ } else if (!count){
+ mkeyboard = 0; //might be redundant
+ }
+ }
+
+ if ( count > 1 )
+ kbdChoice->show();
+ else
+ kbdChoice->hide();
+
+ if ( count )
+ kbdButton->show();
+ else
+ kbdButton->hide();
+}
+
void InputMethods::chooseMethod(InputMethod* im)
{
- if ( im != method ) {
- if ( method && method->widget->isVisible() )
- method->widget->hide();
- method = im;
+ if ( im != imethod ) {
+ updateKeyboards( im );
+
Config cfg("qpe");
cfg.setGroup("InputMethod");
- cfg.writeEntry("current", method->interface->name());
- kbdButton->setPixmap( *method->interface->icon() );
+ if (im )
+ cfg.writeEntry("im", im->name() );
+ if (mkeyboard)
+ cfg.writeEntry("current", mkeyboard->name() );
+
+ QWSServer::setCurrentInputMethod( 0 );
+ imethod = im;
+ if ( imethod && imethod->newIM )
+ QWSServer::setCurrentInputMethod( imethod->extInterface->inputMethod() );
+ else
+ QWSServer::setCurrentInputMethod( 0 );
+
+ if ( im )
+ imButton->raiseWidget(im->widget);
+ else
+ imButton->hide(); //### good UI? make sure it is shown again!
}
- if ( !kbdButton->isOn() )
- kbdButton->setOn( TRUE );
- else
- showKbd( TRUE );
+}
+
+void InputMethods::qcopReceive( const QCString &msg, const QByteArray &data )
+{
+ if ( imethod && imethod->newIM )
+ imethod->extInterface->qcopReceive( msg, data );
}
void InputMethods::showKbd( bool on )
{
- if ( !method )
- return;
+ if ( !mkeyboard )
+ return;
if ( on ) {
- method->interface->resetState();
- // HACK... Make the texteditor fit with all input methods
- // Input methods should also never use more than about 40% of the screen
- int height = QMIN( method->widget->sizeHint().height(), 134 );
- #ifdef QT_QWS_SIMPAD
- method->widget->resize( qApp->desktop()->width() / 2, height ); // make it half the width on the SIMpad
- #else
- method->widget->resize( qApp->desktop()->width(), height );
- #endif
- method->widget->move( 0, mapToGlobal( QPoint() ).y() - height );
- method->widget->show();
+ mkeyboard->resetState();
+ // HACK... Make the texteditor fit with all input methods
+ // Input methods should also never use more than about 40% of the screen
+ int height = QMIN( mkeyboard->widget->sizeHint().height(), 134 );
+ mkeyboard->widget->resize( qApp->desktop()->width(), height );
+ mkeyboard->widget->move( 0, mapToGlobal( QPoint() ).y() - height );
+ mkeyboard->widget->show();
} else {
- method->widget->hide();
+ mkeyboard->widget->hide();
}
emit inputToggled( on );
@@ -304,13 +549,13 @@ void InputMethods::showKbd( bool on )
bool InputMethods::shown() const
{
- return method && method->widget->isVisible();
+ return mkeyboard && mkeyboard->widget->isVisible();
}
QString InputMethods::currentShown() const
{
- return method && method->widget->isVisible()
- ? method->interface->name() : QString::null;
+ return mkeyboard && mkeyboard->widget->isVisible()
+ ? mkeyboard->name() : QString::null;
}
void InputMethods::sendKey( ushort unicode, ushort scancode, ushort mod, bool press, bool repeat )