src/hbwidgets/editors/hbdatetimevalidator_p.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Mon, 19 Apr 2010 14:02:13 +0300
changeset 0 16d8024aca5e
permissions -rw-r--r--
Revision: 201011 Kit: 201015

/****************************************************************************
**
** Copyright (C) 2008-2010 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (developer.feedback@nokia.com)
**
** This file is part of the HbWidgets module of the UI Extensions for Mobile.
**
** GNU Lesser General Public License Usage
** This file may be used under the terms of the GNU Lesser General Public
** License version 2.1 as published by the Free Software Foundation and
** appearing in the file LICENSE.LGPL included in the packaging of this file.
** Please review the following information to ensure the GNU Lesser General
** Public License version 2.1 requirements will be met:
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain additional
** rights.  These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** If you have questions regarding the use of this file, please contact
** Nokia at developer.feedback@nokia.com.
**
****************************************************************************/

//
//  W A R N I N G
//  -------------
//
// This file is not part of the Hb API.  It exists purely as an
// implementation detail.  This file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//

#include <QIntValidator>
#include <QRegExpValidator>
#include <QTextCursor>
#include <QRegExp>
#include <QtDebug>
#include <QTextDocument>
#include <QDate>

#include "hbdatetimevalidator_p_p.h"
#include "hbvalidator_p.h"

#define DEFAULT_YEAR_VALUE "2009"

const int KMinimumYear = -4713;
const int KMaximumYear = 11000000; // see more in QDate documentation

HbDateTimeValidatorPrivate::HbDateTimeValidatorPrivate() :
    HbValidatorPrivate()
{

}

HbDateTimeValidatorPrivate::~HbDateTimeValidatorPrivate()
{

}

class HbFixableIntQValidator : public QIntValidator
{
public:
    HbFixableIntQValidator( QObject * parent ) :
            QIntValidator ( parent ),
            mMaxLeght(10000)
    {
    }

    HbFixableIntQValidator ( int minimum, int maximum, QObject * parent ) :
            QIntValidator ( minimum, maximum, parent )
    {
        int topLenght = QString::number(top()).length();
        int bottomLenght = QString::number(bottom()).length();
        mMaxLeght = qMax(topLenght, bottomLenght);
    }

    void fixup ( QString & input ) const
    {
        QString newTextValue=input;
        newTextValue.remove(QRegExp((bottom()>=0)?"[^0-9]":"[^\\-0-9]"));
        bool ok;
        int value = newTextValue.toInt(&ok);
        if(ok) {
            value = qBound(bottom(), value, top());
            newTextValue.setNum(value);
            if(newTextValue!=input) {
                input = newTextValue;
            }
        }
    }

    State validate ( QString & input, int & pos ) const
    {
        if(input.length()>mMaxLeght) {
            return Invalid;
        }

        return QIntValidator::validate(input, pos);
    }

private:
    int mMaxLeght;
};

void HbDateTimeValidatorPrivate::addDateTimeField(const DataPositions& data)
{
    Q_Q(HbDateTimeValidator);
    QValidator *fieldValidator = NULL;
    QString defaultValue;

    switch(data.type) {
    case Year: {
            if(data.lenght() == 4) {
                fieldValidator = new HbFixableIntQValidator(KMinimumYear,
                                                            KMaximumYear,
                                                            q);
                defaultValue = "2009";
            } else {
                fieldValidator = new HbFixableIntQValidator(0,99,q);
                defaultValue = "09";
            }
        }
        break;
    case Month: {
            fieldValidator = new HbFixableIntQValidator(1,12,q);
            defaultValue = (data.lenght() == 1)?"1":"01";
        }
        break;
    case Day: {
            fieldValidator = new HbFixableIntQValidator(1,31,q);
            defaultValue = (data.lenght() == 1)?"1":"01";
        }
        break;
    case AmPm: {
            QRegExp regUxp("AM|PM", Qt::CaseInsensitive);
            fieldValidator = new QRegExpValidator(regUxp,q);
            defaultValue = "AM";
        }
        break;
    case Hour24: {
            fieldValidator = new HbFixableIntQValidator(0,23,q);
            defaultValue = (data.lenght() == 1)?"0":"00";
        }
        break;
    case Hour12: {
            fieldValidator = new HbFixableIntQValidator(1,12,q);
            defaultValue = "12";
        }
        break;
    case Minutes: {
            fieldValidator = new HbFixableIntQValidator(0,59,q);
            defaultValue = (data.lenght() == 1)?"0":"00";
        }
        break;
    case Seconds: {
            fieldValidator = new HbFixableIntQValidator(0,59,q);
            defaultValue = (data.lenght() == 1)?"0":"00";
        }
        break;
    case MilliSeconds: {
            fieldValidator = new HbFixableIntQValidator(0,999,q);
            defaultValue = (data.lenght() == 1)?"0":"000";
        }
        break;
    default:
        Q_ASSERT(false);
    }
    fieldTypes.append(data.type);
    addField(separatorToAdd,fieldValidator,defaultValue);
}

bool HbDateTimeValidatorPrivate::fixDate(QTextCursor *cursor, bool updateCursor)
{
    Q_ASSERT(!fieldTypes.isEmpty());

    QRegExp regExp = QRegExp(parseString,Qt::CaseInsensitive);

    Q_ASSERT(regExp.isValid());

    bool result = false;

    if(regExp.exactMatch(cursor->document()->toPlainText())) {
        QTextCursor fixingCursor(cursor->document());
        fixingCursor.beginEditBlock();

        // step one: fixing each field separately
        for(int i = fieldTypes.count()-1; i>=0; --i ) {
            int pos = regExp.pos(i+1);
            QString fieldTxt = regExp.cap(i+1);
            fixingCursor.setPosition(pos);
            fixingCursor.setPosition(pos + fieldTxt.length());

            int x = fieldTxt.length();
            if( QValidator::Acceptable != fields.at(i)->mValidator->validate(fieldTxt, x) ) {
                fields.at(i)->mValidator->fixup(fieldTxt);
                if(QValidator::Acceptable == fields.at(i)->mValidator->validate(fieldTxt, x)) {
                    // successful fix
                    fixingCursor.insertText(fieldTxt);
                    result = true;
                }
            }
        }

        // step two: fixing correlations between fields
        // Note: this functionality is specific for HbDateTimeValidator
        int yearCapture  = fieldTypes.indexOf(Year)+1;
        int monthCapture = fieldTypes.indexOf(Month)+1;
        int dayCapture   = fieldTypes.indexOf(Day)+1;

        if(dayCapture<=0 || monthCapture<=0) {
            fixingCursor.endEditBlock();
            return result; // nothing to fix no date
        }

        // match content again
        if(regExp.exactMatch(cursor->document()->toPlainText())) {
            int year=1900, month, day;

            if(yearCapture>0) {
                year = regExp.cap(yearCapture).toInt();
            }
            month = regExp.cap(monthCapture).toInt();

            int daySize =0;
            QString s = regExp.cap(dayCapture);
            daySize = s.length();
            day   = s.toInt();

            if( !QDate::isValid(year, month, day) ) {
                QDate dateFoFix(year,month,1);
                if(dateFoFix.daysInMonth()<day) {
                    dateFoFix.setDate(year,month,dateFoFix.daysInMonth());
                    int dayCapPos = regExp.pos(dayCapture);
                    fixingCursor.setPosition(dayCapPos);
                    fixingCursor.setPosition(dayCapPos+daySize, QTextCursor::KeepAnchor);

                    // updating date by changing document
                    // this will prevent some unexpected isues
                    fixingCursor.insertText(QString().setNum(dateFoFix.daysInMonth()));

                    if(updateCursor) {
                        fixingCursor.setPosition(dayCapPos);
                        fixingCursor.setPosition(dayCapPos+daySize, QTextCursor::KeepAnchor);
                        *cursor = fixingCursor;
                    }
                    result = true;
                }
            }
        }
        fixingCursor.endEditBlock();
    }

    return result;
}

/*!
    \class HbDateTimeValidator
    \brief HbDateTimeValidator provides validation services for QAbstractEdit derived editors.
*/

/*!
    Constructs a HbDateTimeValidator object with a /a parent
*/
HbDateTimeValidator::HbDateTimeValidator(QObject *parent) :
    HbValidator(*new HbDateTimeValidatorPrivate, parent)
{
    Q_D(HbDateTimeValidator);
    d->q_ptr = this;
}

HbDateTimeValidator::HbDateTimeValidator(HbDateTimeValidatorPrivate &dd, QObject *parent) :
    HbValidator(dd, parent)
{
    Q_D(HbDateTimeValidator);
    d->q_ptr = this;
}

/*!
    Destructor
*/
HbDateTimeValidator::~HbDateTimeValidator()
{
}

inline void appendIfFound(DataType type, QList<DataPositions>* where, QRegExp& what, const QString& from)
{
    int pos = from.indexOf(what);
    if(pos>=0) {
        int matchLenght = what.cap(0).length();
        Q_ASSERT( matchLenght>0 );
        Q_ASSERT( from.indexOf(what, pos+matchLenght)==-1 );
        
        where->append(
                DataPositions(type, pos, matchLenght)
                );
    }
}

void HbDateTimeValidator::setDisplayFormat(const QString &format)
{
    QList<DataPositions> positions;

    QRegExp regExp;
    QString newParseString = QRegExp::escape(format);
    const QString captureNumber = "([0-9]+)";

    regExp.setPattern("yy(yy)?");
    appendIfFound(Year, &positions, regExp, format);
    newParseString.replace(regExp, captureNumber);

    regExp.setPattern("M{1,3}");
    appendIfFound(Month, &positions, regExp, format);
    newParseString.replace(regExp, captureNumber);

    regExp.setPattern("d{1,3}");
    appendIfFound(Day, &positions, regExp, format);
    newParseString.replace(regExp, captureNumber);

    const QString captureAmPm("(AM|PM|am|pm)");
    regExp.setPattern("ap");
    appendIfFound(AmPm, &positions, regExp, format);
    newParseString.replace(regExp, captureAmPm);

    regExp.setPattern("AP");
    appendIfFound(AmPm, &positions, regExp, format);
    newParseString.replace(regExp, captureAmPm);

    DataType hourType = Hour24;
    if(!positions.isEmpty()) {
        if(positions.last().type==AmPm) {
            hourType = Hour12;
        }
    }

    regExp.setPattern("hh?");
    appendIfFound(hourType, &positions, regExp, format);
    newParseString.replace(regExp, captureNumber);

    regExp.setPattern("HH?");
    appendIfFound(hourType, &positions, regExp, format);
    newParseString.replace(regExp, captureNumber);

    regExp.setPattern("mm?");
    appendIfFound(Minutes, &positions, regExp, format);
    newParseString.replace(regExp, captureNumber);

    regExp.setPattern("ss?");
    appendIfFound(Seconds, &positions, regExp, format);
    newParseString.replace(regExp, captureNumber);

    regExp.setPattern("z(zz)?");
    appendIfFound(MilliSeconds, &positions, regExp, format);
    newParseString.replace(regExp, captureNumber);

    if(positions.size()==0) {
        qWarning() << "HbDateTimeValidator::setDisplayFormat: format: \""
                << format << "\" is invalid so it was ignored" ;
        return;
    }

    qSort(positions);

    Q_D(HbDateTimeValidator);
    d->format = format;
    // ignore trailing separators:
    if(positions.back().stop < format.length()) {
        newParseString.chop(QRegExp::escape(format.mid(positions.back().stop)).length());
        d->format.resize(positions.back().stop);
    }

    // ignore front separators:
    if(positions.front().start != 0) {
        newParseString.remove(0,positions.front().start);
    }

    d->parseString = newParseString;
    d->fieldTypes.clear();
    d->deleteAllFields();

    const int n=positions.size()-1;
    for(int i=0; i<n; ++i) {
        d->addDateTimeField(positions.at(i));
        int separatorBegin = positions.at(i).stop;
        int separatorLenght = positions.at(i+1).start - separatorBegin;
        if(separatorLenght>0) {
            setDefaultSeparator(format.mid(separatorBegin,separatorLenght));
        } else {
            setDefaultSeparator(" ");
        }
    }
    d->addDateTimeField(positions.at(n));
}

const QString HbDateTimeValidator::displayFormat() const
{
    Q_D(const HbDateTimeValidator);
    return d->format;
}

bool HbDateTimeValidator::fixDate(QTextCursor *cursor, bool updateCursor)
{
    return d_func()->fixDate(cursor, updateCursor);
}