src/hbplugins/inputmethods/touchinput/hbinputprediction12keyhandler.cpp
changeset 0 16d8024aca5e
child 1 f7ac710697a9
equal deleted inserted replaced
-1:000000000000 0:16d8024aca5e
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2008-2010 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (developer.feedback@nokia.com)
       
     6 **
       
     7 ** This file is part of the HbPlugins module of the UI Extensions for Mobile.
       
     8 **
       
     9 ** GNU Lesser General Public License Usage
       
    10 ** This file may be used under the terms of the GNU Lesser General Public
       
    11 ** License version 2.1 as published by the Free Software Foundation and
       
    12 ** appearing in the file LICENSE.LGPL included in the packaging of this file.
       
    13 ** Please review the following information to ensure the GNU Lesser General
       
    14 ** Public License version 2.1 requirements will be met:
       
    15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
       
    16 **
       
    17 ** In addition, as a special exception, Nokia gives you certain additional
       
    18 ** rights.  These rights are described in the Nokia Qt LGPL Exception
       
    19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
       
    20 **
       
    21 ** If you have questions regarding the use of this file, please contact
       
    22 ** Nokia at developer.feedback@nokia.com.
       
    23 **
       
    24 ****************************************************************************/
       
    25 #include <QTimer>
       
    26 #include <QGraphicsScene>
       
    27 
       
    28 #include <hbinputmethod.h>
       
    29 #include <hbinputkeymapfactory.h>
       
    30 #include <hbinputpredictionengine.h>
       
    31 #include <hbinputsettingproxy.h>
       
    32 #include <hbinputvkbhost.h>
       
    33 #include <hbinputdialog.h>
       
    34 #include <hbaction.h>
       
    35 #include "virtual12key.h"
       
    36 
       
    37 #include "hbinputprediction12keyhandler.h"
       
    38 #include "hbinputpredictionhandler_p.h"
       
    39 #include "hbinputabstractbase.h"
       
    40 
       
    41 #define HbDeltaHeight 3.0
       
    42 #define MAXUDBWORDSIZE 64
       
    43 
       
    44 class HbInputPrediction12KeyHandlerPrivate: public HbInputPredictionHandlerPrivate
       
    45 {
       
    46     Q_DECLARE_PUBLIC(HbInputPrediction12KeyHandler)
       
    47 
       
    48 public:
       
    49     HbInputPrediction12KeyHandlerPrivate();
       
    50     ~HbInputPrediction12KeyHandlerPrivate();
       
    51 
       
    52     bool buttonReleased(const QKeyEvent *keyEvent);
       
    53     bool buttonPressed(const QKeyEvent *keyEvent);
       
    54     void _q_timeout();
       
    55     void launchSpellDialog(QString customWord);
       
    56     void getSpellDialogPositionAndSize(QPointF & pos,QSizeF & size,QRectF & geom);
       
    57     void cancelButtonPress();
       
    58 public:
       
    59     int mLastKey;
       
    60     bool mButtonDown;
       
    61     QChar mCurrentChar;
       
    62     bool mLongPressHappened;
       
    63     bool mShiftKeyDoubleTap;
       
    64 };
       
    65 
       
    66 HbInputPrediction12KeyHandlerPrivate::HbInputPrediction12KeyHandlerPrivate()
       
    67 :mLastKey(0),
       
    68 mButtonDown(false),
       
    69 mCurrentChar(0),
       
    70 mLongPressHappened(false),
       
    71 mShiftKeyDoubleTap(false)
       
    72 {
       
    73 }
       
    74 
       
    75 HbInputPrediction12KeyHandlerPrivate::~HbInputPrediction12KeyHandlerPrivate()
       
    76 {
       
    77 }
       
    78 
       
    79 void HbInputPrediction12KeyHandlerPrivate::_q_timeout()
       
    80 {
       
    81     qDebug("HbInputPrediction12KeyHandlerPrivate::_q_timeout()");
       
    82     Q_Q(HbInputPrediction12KeyHandler);
       
    83 
       
    84     // let's stop the timer first.
       
    85     mTimer->stop();
       
    86 
       
    87     //Long key press number key is applicable to all keys
       
    88     if (mButtonDown) {	
       
    89         if (mLastKey == Qt::Key_Asterisk) {
       
    90 			//Remove the "?" mark if present
       
    91 			if(!mCanContinuePrediction && (*mCandidates)[mBestGuessLocation].endsWith('?')) {	
       
    92                 (*mCandidates)[mBestGuessLocation].chop(1);
       
    93                 updateEditor();
       
    94                 mCanContinuePrediction = true;
       
    95 			}
       
    96             mInputMethod->switchMode(mLastKey);
       
    97         } else if (mLastKey == Qt::Key_Shift) {
       
    98             mInputMethod->switchMode(Qt::Key_Shift);
       
    99             mLongPressHappened = true;
       
   100         } else if (mLastKey == Qt::Key_Control) {
       
   101             mInputMethod->selectSpecialCharacterTableMode();
       
   102         } else {
       
   103             //With a long key press of a key, numbers are supposed to be entered.
       
   104             //When the existing input (along with the short key press input of the
       
   105             //existing key) results in tail (i.e. autocompletion), we need to accept
       
   106             //the short key press as well as the tail. In case of ? delete the questionmark and add the number value.          
       
   107             //mTailShowing = false;            
       
   108             // Delete "?" entered
       
   109             if (!mCanContinuePrediction) {
       
   110                 deleteOneCharacter();
       
   111             }
       
   112 			if (mLastKey != Qt::Key_Delete) {
       
   113 				q->commitFirstMappedNumber(mLastKey);
       
   114 			}
       
   115             mLongPressHappened = true;
       
   116         }
       
   117     }
       
   118 }
       
   119 
       
   120 bool HbInputPrediction12KeyHandlerPrivate::buttonPressed(const QKeyEvent *keyEvent)
       
   121 {
       
   122     mLongPressHappened = false;
       
   123     HbInputFocusObject *focusObject = 0;
       
   124     focusObject = mInputMethod->focusObject();
       
   125     if (!focusObject) {
       
   126         return false;
       
   127     }
       
   128 
       
   129     int buttonId = keyEvent->key();
       
   130 
       
   131     
       
   132     if (buttonId == Qt::Key_Control) {
       
   133         mLastKey = buttonId;
       
   134         mButtonDown = true;
       
   135         mTimer->start(HbLongPressTimerTimeout);
       
   136         return true;
       
   137     } else if (buttonId == Qt::Key_Shift) {		
       
   138     // if we get a second consequtive shift key press, 
       
   139     // we want to handle it in buttonRelease
       
   140         if (mTimer->isActive() && (mLastKey == buttonId)){
       
   141             mShiftKeyDoubleTap = true;
       
   142         }
       
   143         if (!mTimer->isActive()) {            		
       
   144             mTimer->start(HbLongPressTimerTimeout);
       
   145         }
       
   146         mLastKey = buttonId;
       
   147         mButtonDown = true;
       
   148         return true;
       
   149     }
       
   150 
       
   151     mLastKey = buttonId;
       
   152     mButtonDown = true;
       
   153     
       
   154     // custom button should not start timer.
       
   155     if ((buttonId & CUSTOM_INPUT_MASK) != CUSTOM_INPUT_MASK) {
       
   156         mTimer->start(HbLongPressTimerTimeout);
       
   157     }
       
   158     return false;
       
   159 }
       
   160 
       
   161 /*!
       
   162 Handles the key release events from the VKB. Launches the SCT with key release event of
       
   163 asterisk.
       
   164 */
       
   165 bool HbInputPrediction12KeyHandlerPrivate::buttonReleased(const QKeyEvent *keyEvent)
       
   166 {
       
   167     Q_Q(HbInputPrediction12KeyHandler);
       
   168     
       
   169     if(!mButtonDown || mLongPressHappened){
       
   170         return false;
       
   171     }
       
   172 
       
   173     // since button is released we can set buttonDown back to false.
       
   174     mButtonDown = false;
       
   175     int buttonId = keyEvent->key(); 
       
   176     // it was a long press on sct swith button. so just return form here.
       
   177     if (!mTimer->isActive() && buttonId == Qt::Key_Control) {
       
   178         return true;
       
   179     }
       
   180 
       
   181     // Sym key is handled in this class it self, so not passing it to 
       
   182     // the base mode handlers.	
       
   183     if ( buttonId == Qt::Key_Control) {
       
   184         //Same SYM key is used for launching candidate list (long key press)
       
   185         //and also for SCT. So, do not launch SCT if candidate list is already launched.
       
   186         mInputMethod->switchMode(buttonId);
       
   187         return true;
       
   188     } 
       
   189     /* Behavior of Short Press of Asterisk Key when in inline editing state 
       
   190 		- Should launch Candidate List if we can continue with prediction i.e. "?" is not displayed
       
   191 		- Should launch Spell Query Dialog if we cannot continue with prediction 
       
   192 	- Behavior of Short Press of Asterisk Key when not in inline editing state 
       
   193 		- Should launch SCT
       
   194 	*/
       
   195     else if (buttonId == Qt::Key_Asterisk ) {
       
   196 		if(!mCanContinuePrediction && (*mCandidates)[mBestGuessLocation].endsWith('?')) {			
       
   197             //Remove the "?" mark
       
   198             (*mCandidates)[mBestGuessLocation].chop(1);
       
   199             updateEditor();
       
   200             q->processCustomWord((*mCandidates)[mBestGuessLocation]);
       
   201             mCanContinuePrediction = true;
       
   202 		}
       
   203 		else
       
   204 			mInputMethod->starKeySelected();
       
   205         return true;
       
   206     }	
       
   207     else if (buttonId == Qt::Key_Return) {
       
   208         mInputMethod->closeKeypad();
       
   209         return true;
       
   210     }
       
   211     if (buttonId == Qt::Key_Shift) {
       
   212         // single tap of shift key toggles prediction status in case insensitive languages
       
   213         if (!HbInputSettingProxy::instance()->globalInputLanguage().isCaseSensitiveLanguage()) {
       
   214             HbInputSettingProxy::instance()->togglePrediction();
       
   215         } else {
       
   216             if (mShiftKeyDoubleTap) {
       
   217                 mTimer->stop(); 
       
   218                 mShiftKeyDoubleTap = false;	
       
   219                 //mShowTail = false;      
       
   220                 if (HbInputSettingProxy::instance()->globalInputLanguage()== mInputMethod->inputState().language()) {
       
   221                     // in latin variants , double tap of shift key toggles the prediction status	
       
   222                     // revert back to the old case as this is a double tap 
       
   223                     // (the case was changed on the single tap)
       
   224                     updateTextCase();				 
       
   225                     HbInputSettingProxy::instance()->togglePrediction();
       
   226                 } else {
       
   227                     // if the global language is different from the input mode language, we should 
       
   228                     // go back to the root state
       
   229                     // e.g. double tap of hash/shift key is used to change 
       
   230                     // to chinese input mode from latin input mode
       
   231                     HbInputState rootState;
       
   232                     mInputMethod->editorRootState(rootState);
       
   233                     mInputMethod->activateState(rootState); 		
       
   234                 }
       
   235             } else {
       
   236                 updateTextCase();
       
   237                 if( !mTimer->isActive()){
       
   238                     mTimer->start();
       
   239                 }
       
   240             }
       
   241         }
       
   242         return true;
       
   243     }
       
   244 
       
   245     // text input happens on button release		
       
   246     if (q->HbInputPredictionHandler::filterEvent(keyEvent)) {
       
   247         return true;
       
   248     }	
       
   249     return false;
       
   250 }
       
   251 
       
   252 void HbInputPrediction12KeyHandlerPrivate::launchSpellDialog(QString editorText)
       
   253 {
       
   254     HbInputFocusObject *focusObject = mInputMethod->focusObject();
       
   255     if (!focusObject) {
       
   256         return;
       
   257     }
       
   258         QPointer<QObject> focusedQObject = focusObject->object();
       
   259 	// store the current focused editor 
       
   260 			 
       
   261     HbTextCase currentTextCase = focusObject->editorInterface().textCase();
       
   262     mEngine->clear();
       
   263     mCanContinuePrediction = true;
       
   264     // close the keypad before showing the spell dialog
       
   265 	HbVkbHost *vkbHost = focusObject->editorInterface().vkbHost();
       
   266     if (vkbHost && vkbHost->keypadStatus() != HbVkbHost::HbVkbStatusClosed) {
       
   267         vkbHost->closeKeypad(true);
       
   268     }
       
   269     // create the spell dialog
       
   270     HbInputDialog *spellDialog = new HbInputDialog();
       
   271     spellDialog->setInputMode(HbInputDialog::TextInput);
       
   272     spellDialog->setPromptText("");
       
   273     spellDialog->setValue(QVariant(editorText));
       
   274     QSizeF  dialogSize = spellDialog->size();
       
   275     QPointF dialogPos = spellDialog->pos();
       
   276     QRectF geom = spellDialog->geometry();
       
   277     
       
   278     //set the spell dialog position
       
   279     getSpellDialogPositionAndSize(dialogPos,dialogSize, geom);
       
   280     geom.setHeight(dialogSize.height());
       
   281     geom.setWidth(dialogSize.width());
       
   282     spellDialog->setGeometry(geom);
       
   283     spellDialog->setPos(dialogPos);
       
   284 
       
   285     // change the focus to spell dialog editor
       
   286     HbLineEdit *spellEdit = spellDialog->lineEdit();
       
   287 
       
   288     if (spellEdit) {
       
   289         spellEdit->setFocus();
       
   290         spellEdit->clearFocus();
       
   291         spellEdit->setFocus();
       
   292         spellEdit->setMaxLength(MAXUDBWORDSIZE);
       
   293         spellEdit->setSmileysEnabled(false);
       
   294         HbEditorInterface eInt(spellEdit);
       
   295 		spellEdit->setInputMethodHints(spellEdit->inputMethodHints() | Qt::ImhNoPredictiveText);
       
   296         eInt.setTextCase(currentTextCase);
       
   297     }
       
   298     // execute the spell dialog
       
   299     HbAction *act = spellDialog->exec();
       
   300  
       
   301         //create new focus object and set the focus back to main editor
       
   302         HbInputFocusObject *newFocusObject = new HbInputFocusObject(focusedQObject);
       
   303 	   
       
   304 		
       
   305         newFocusObject->releaseFocus();
       
   306         newFocusObject->setFocus();
       
   307 
       
   308 	    HbAbstractEdit *abstractEdit = qobject_cast<HbAbstractEdit*>(focusedQObject);
       
   309         if(abstractEdit) {
       
   310             abstractEdit->setCursorPosition(abstractEdit->cursorPosition());
       
   311         }
       
   312 
       
   313         mInputMethod->setFocusObject(newFocusObject);
       
   314         mInputMethod->focusObject()->editorInterface().setTextCase(currentTextCase);
       
   315 
       
   316     if (act->text() == spellDialog->primaryAction()->text()) {
       
   317         commit(spellDialog->value().toString() , true, true);
       
   318     } else if (act->text() == spellDialog->secondaryAction()->text()) {
       
   319     //update the editor with pre-edit text
       
   320         mEngine->setWord(editorText);
       
   321         bool used = false;	 
       
   322         mEngine->updateCandidates(mBestGuessLocation, used);
       
   323 		mShowTail = false;
       
   324 		updateEditor();
       
   325 	}  
       
   326     delete spellDialog;
       
   327 }
       
   328 
       
   329 void HbInputPrediction12KeyHandlerPrivate::getSpellDialogPositionAndSize(QPointF & pos,QSizeF & size, QRectF &geom)
       
   330 {
       
   331     QRectF cursorRect = mInputMethod->focusObject()->microFocus(); // from the top of the screen
       
   332     pos = QPointF(cursorRect.bottomLeft().x(),cursorRect.bottomLeft().y());
       
   333     qreal heightOfTitlebar = 80.0; // Using magic number for now...
       
   334     qreal screenHeight = (qreal)HbDeviceProfile::current().logicalSize().height();
       
   335     if( ((screenHeight - cursorRect.bottomLeft().y()) > (cursorRect.y() - heightOfTitlebar))
       
   336         || ((screenHeight - cursorRect.bottomLeft().y() + HbDeltaHeight ) > geom.height()) ) {
       
   337         // this means there is amore space below inline text than at the top or we can fit spell Dialog
       
   338         // below inline text
       
   339         pos.setY(cursorRect.bottomLeft().y() + HbDeltaHeight);
       
   340         size.setHeight(screenHeight - pos.y());
       
   341     } else {
       
   342         // this means there is amore space above inline text than below it
       
   343         pos.setY(cursorRect.y() - geom.height() - HbDeltaHeight);
       
   344         if (pos.y() < heightOfTitlebar) {
       
   345             // this means that spell dialog can not be fit in from top of inline text, we need to trim it
       
   346             pos.setY(heightOfTitlebar);
       
   347         }
       
   348         size.setHeight(cursorRect.y() - heightOfTitlebar - HbDeltaHeight);
       
   349     }
       
   350     if ( size.height() > geom.height()) {
       
   351         size.setHeight(geom.height());
       
   352     }
       
   353     if ((pos.x() + size.width()) > (qreal)HbDeviceProfile::current().logicalSize().width()) {
       
   354         // can not fit spell dialog to the right side of inline edit text.
       
   355         pos.setX((qreal)HbDeviceProfile::current().logicalSize().width()- size.width());
       
   356     }
       
   357 }
       
   358 
       
   359 void HbInputPrediction12KeyHandlerPrivate::cancelButtonPress()
       
   360 {
       
   361     mTimer->stop();
       
   362     mButtonDown = false;
       
   363     mLastKey = 0;
       
   364     mShiftKeyDoubleTap = false;
       
   365 }
       
   366 
       
   367 HbInputPrediction12KeyHandler::HbInputPrediction12KeyHandler(HbInputAbstractMethod *inputMethod)
       
   368     :HbInputPredictionHandler(* new HbInputPrediction12KeyHandlerPrivate, inputMethod)
       
   369 {
       
   370     Q_D(HbInputPrediction12KeyHandler);
       
   371     d->q_ptr = this;
       
   372 }
       
   373 
       
   374 HbInputPrediction12KeyHandler::~HbInputPrediction12KeyHandler()
       
   375 {
       
   376 }
       
   377 
       
   378 /*!
       
   379     this function lists different modes.
       
   380 */
       
   381 void HbInputPrediction12KeyHandler::listInputModes(QVector<HbInputModeProperties>& modes) const
       
   382 {
       
   383     Q_UNUSED(modes); 
       
   384 }
       
   385 
       
   386 /*!
       
   387     filterEvent to handler keypress/release events.
       
   388 */
       
   389 bool HbInputPrediction12KeyHandler::filterEvent(const QKeyEvent * event)
       
   390 {
       
   391     Q_D(HbInputPrediction12KeyHandler);
       
   392     HbInputFocusObject *focusObject = 0;
       
   393     focusObject = d->mInputMethod->focusObject();
       
   394 
       
   395     //If there was a handling for empty candidate-list, i.e. the engine did not predict
       
   396     //any meaningful word for the input sequence.
       
   397     if(!d->mCanContinuePrediction) {
       
   398         int eventKey = event->key();
       
   399         switch(eventKey) {
       
   400         case Qt::Key_0:        
       
   401         case Qt::Key_Space: {
       
   402             if(d->mCandidates->size() && focusObject) {
       
   403                 //Remove the "?" mark
       
   404                 (*d->mCandidates)[d->mBestGuessLocation].chop(1);
       
   405                 d->updateEditor();
       
   406                 d->mCanContinuePrediction = true;
       
   407                 }
       
   408             }
       
   409             break;
       
   410         case Qt::Key_Shift: {
       
   411             if(event->type() == QEvent::KeyRelease && d->mShiftKeyDoubleTap) {
       
   412                 //Remove the "?" mark
       
   413                 deleteOneCharacter();
       
   414                 }
       
   415             }
       
   416         //For the following set of keys, it does not matter.
       
   417         case Qt::Key_Backspace:
       
   418         case Qt::Key_Delete:
       
   419         case Qt::Key_Return:
       
   420         case Qt::Key_Enter:
       
   421 		case Qt::Key_Asterisk:
       
   422             break;
       
   423         /* Behavior for other keys i.e. from key1 to key9 - 
       
   424         To start the long press timer as we need to handle long press functionality i.e Enter corresponding number mapped to a key */
       
   425         case Qt::Key_1:
       
   426         case Qt::Key_2:
       
   427         case Qt::Key_3:
       
   428         case Qt::Key_4:
       
   429         case Qt::Key_5:
       
   430         case Qt::Key_6:
       
   431         case Qt::Key_7:
       
   432         case Qt::Key_8:
       
   433         case Qt::Key_9: {
       
   434             if (event->type() == QEvent::KeyRelease) {
       
   435                 d->mButtonDown = false;
       
   436             } else {
       
   437                 d->mButtonDown = true;			
       
   438                 d->mLastKey = event->key();		
       
   439                 // start Long Press timer as corresponding number mapped to a key should be allowed to enter
       
   440                 d->mTimer->start(HbLongPressTimerTimeout);					
       
   441             }
       
   442             return true;
       
   443         }
       
   444         //The default behavior for any other key press is just to consume the key event and
       
   445         //not to do anything.
       
   446         default: {
       
   447             return true;
       
   448             }
       
   449         }
       
   450     }
       
   451 
       
   452     // prediction mode can't handle Qt::Key_0, so we will emit a passFilterEvent
       
   453     // this signal must be connected to by the plugin to a modehandler.
       
   454     // which can handle it.
       
   455     if (event->key() == Qt::Key_0) {
       
   456         if (d->mLastKey != Qt::Key_0) {
       
   457         actionHandler(HbInputModeHandler::HbInputModeActionCommit);
       
   458         }
       
   459         emit passFilterEvent(event);
       
   460         d->mLastKey = Qt::Key_0;
       
   461         return true;
       
   462     } else {
       
   463         if (d->mLastKey == Qt::Key_0) {
       
   464             emit passActionHandler(HbInputModeActionCommit);
       
   465         }
       
   466         if (event->type() == QEvent::KeyRelease) {
       
   467             return d->buttonReleased(event);
       
   468         } else {
       
   469             return d->buttonPressed(event);
       
   470         }
       
   471     }
       
   472 }
       
   473 
       
   474 /*!
       
   475 Action handler 
       
   476 */
       
   477 bool HbInputPrediction12KeyHandler::actionHandler(HbInputModeAction action)
       
   478 {
       
   479     Q_D(HbInputPrediction12KeyHandler);
       
   480     bool ret = true;
       
   481     switch (action) {
       
   482         case HbInputModeActionReset:
       
   483             HbInputPredictionHandler::actionHandler(HbInputModeActionReset);
       
   484             d->mTimer->stop();
       
   485             break;
       
   486         case HbInputModeActionCancelButtonPress:
       
   487             d->cancelButtonPress();
       
   488             break;
       
   489         case HbInputModeActionFocusRecieved:
       
   490             HbInputPredictionHandler::actionHandler(HbInputModeActionSetCandidateList);
       
   491             HbInputPredictionHandler::actionHandler(HbInputModeActionSetKeypad);
       
   492             d->mTimer->stop();
       
   493             break;
       
   494         default:
       
   495             ret = HbInputPredictionHandler::actionHandler(action);
       
   496             break;
       
   497     }
       
   498     
       
   499     return ret;
       
   500 }
       
   501 
       
   502 void HbInputPrediction12KeyHandler::mouseHandler(int cursorPosition, QMouseEvent* mouseEvent)
       
   503 {
       
   504     Q_D(HbInputPrediction12KeyHandler);
       
   505     if(d->mLastKey == Qt::Key_0) {
       
   506         // zero key handling is done in the basic handler
       
   507         emit passActionHandler(HbInputModeActionCommit);
       
   508     } else {
       
   509         HbInputPredictionHandler::mouseHandler(cursorPosition,mouseEvent);
       
   510     }
       
   511 }
       
   512 
       
   513 
       
   514 /*!
       
   515 Returns true if preidciton engine is available and initialized.
       
   516 */
       
   517 bool HbInputPrediction12KeyHandler::isActive() const
       
   518 { 
       
   519     Q_D(const HbInputPrediction12KeyHandler);
       
   520     return d->mEngine != 0;
       
   521 }
       
   522 
       
   523 void HbInputPrediction12KeyHandler::processCustomWord(QString customWord)
       
   524 {
       
   525     Q_D(HbInputPrediction12KeyHandler);
       
   526     if (customWord.size()) {
       
   527         d->launchSpellDialog(customWord);
       
   528     }
       
   529     return;	  
       
   530 }
       
   531 //EOF