diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 09:06:44 +0000 |
commit | ed5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch) | |
tree | 7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /vcl/source/control/field2.cxx | |
parent | Initial commit. (diff) | |
download | libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip |
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vcl/source/control/field2.cxx')
-rw-r--r-- | vcl/source/control/field2.cxx | 3183 |
1 files changed, 3183 insertions, 0 deletions
diff --git a/vcl/source/control/field2.cxx b/vcl/source/control/field2.cxx new file mode 100644 index 000000000..b7a5c2246 --- /dev/null +++ b/vcl/source/control/field2.cxx @@ -0,0 +1,3183 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <algorithm> +#include <string_view> + +#include <tools/diagnose_ex.h> +#include <comphelper/processfactory.hxx> +#include <comphelper/string.hxx> +#include <o3tl/string_view.hxx> +#include <officecfg/Office/Common.hxx> +#include <vcl/svapp.hxx> +#include <vcl/event.hxx> +#include <vcl/toolkit/field.hxx> +#include <vcl/unohelp.hxx> +#include <vcl/settings.hxx> +#include <vcl/weldutils.hxx> + +#include <svdata.hxx> + +#include <com/sun/star/i18n/XCharacterClassification.hpp> +#include <com/sun/star/i18n/CalendarFieldIndex.hdl> + +#include <unotools/localedatawrapper.hxx> +#include <unotools/calendarwrapper.hxx> +#include <unotools/charclass.hxx> +#include <svl/numformat.hxx> +#include <svl/zforlist.hxx> + +using namespace ::com::sun::star; +using namespace ::comphelper; + +#define EDITMASK_LITERAL 'L' +#define EDITMASK_ALPHA 'a' +#define EDITMASK_UPPERALPHA 'A' +#define EDITMASK_ALPHANUM 'c' +#define EDITMASK_UPPERALPHANUM 'C' +#define EDITMASK_NUM 'N' +#define EDITMASK_NUMSPACE 'n' +#define EDITMASK_ALLCHAR 'x' +#define EDITMASK_UPPERALLCHAR 'X' + +uno::Reference< i18n::XCharacterClassification > const & ImplGetCharClass() +{ + ImplSVData *const pSVData = ImplGetSVData(); + assert(pSVData); + + if (!pSVData->m_xCharClass.is()) + { + pSVData->m_xCharClass = vcl::unohelper::CreateCharacterClassification(); + } + + return pSVData->m_xCharClass; +} + +static sal_Unicode* ImplAddString( sal_Unicode* pBuf, const OUString& rStr ) +{ + memcpy( pBuf, rStr.getStr(), rStr.getLength() * sizeof(sal_Unicode) ); + pBuf += rStr.getLength(); + return pBuf; +} + +static sal_Unicode* ImplAddNum( sal_Unicode* pBuf, sal_uLong nNumber, int nMinLen ) +{ + // fill temp buffer with digits + sal_Unicode aTempBuf[30]; + sal_Unicode* pTempBuf = aTempBuf; + do + { + *pTempBuf = static_cast<sal_Unicode>(nNumber % 10) + '0'; + pTempBuf++; + nNumber /= 10; + if ( nMinLen ) + nMinLen--; + } + while ( nNumber ); + + // fill with zeros up to the minimal length + while ( nMinLen > 0 ) + { + *pBuf = '0'; + pBuf++; + nMinLen--; + } + + // copy temp buffer to real buffer + do + { + pTempBuf--; + *pBuf = *pTempBuf; + pBuf++; + } + while ( pTempBuf != aTempBuf ); + + return pBuf; +} + +static sal_Unicode* ImplAddSNum( sal_Unicode* pBuf, sal_Int32 nNumber, int nMinLen ) +{ + if (nNumber < 0) + { + *pBuf++ = '-'; + nNumber = -nNumber; + } + return ImplAddNum( pBuf, nNumber, nMinLen); +} + +static sal_uInt16 ImplGetNum( const sal_Unicode*& rpBuf, bool& rbError ) +{ + if ( !*rpBuf ) + { + rbError = true; + return 0; + } + + sal_uInt16 nNumber = 0; + while( ( *rpBuf >= '0' ) && ( *rpBuf <= '9' ) ) + { + nNumber *= 10; + nNumber += *rpBuf - '0'; + rpBuf++; + } + + return nNumber; +} + +static void ImplSkipDelimiters( const sal_Unicode*& rpBuf ) +{ + while( ( *rpBuf == ',' ) || ( *rpBuf == '.' ) || ( *rpBuf == ';' ) || + ( *rpBuf == ':' ) || ( *rpBuf == '-' ) || ( *rpBuf == '/' ) ) + { + rpBuf++; + } +} + +static bool ImplIsPatternChar( sal_Unicode cChar, char cEditMask ) +{ + sal_Int32 nType = 0; + + try + { + OUString aCharStr(cChar); + nType = ImplGetCharClass()->getStringType( aCharStr, 0, aCharStr.getLength(), + Application::GetSettings().GetLanguageTag().getLocale() ); + } + catch (const css::uno::Exception&) + { + DBG_UNHANDLED_EXCEPTION("vcl.control"); + return false; + } + + if ( (cEditMask == EDITMASK_ALPHA) || (cEditMask == EDITMASK_UPPERALPHA) ) + { + if( !CharClass::isLetterType( nType ) ) + return false; + } + else if ( cEditMask == EDITMASK_NUM ) + { + if( !CharClass::isNumericType( nType ) ) + return false; + } + else if ( (cEditMask == EDITMASK_ALPHANUM) || (cEditMask == EDITMASK_UPPERALPHANUM) ) + { + if( !CharClass::isLetterNumericType( nType ) ) + return false; + } + else if ( (cEditMask == EDITMASK_ALLCHAR) || (cEditMask == EDITMASK_UPPERALLCHAR) ) + { + if ( cChar < 32 ) + return false; + } + else if ( cEditMask == EDITMASK_NUMSPACE ) + { + if ( !CharClass::isNumericType( nType ) && ( cChar != ' ' ) ) + return false; + } + else + return false; + + return true; +} + +static sal_Unicode ImplPatternChar( sal_Unicode cChar, char cEditMask ) +{ + if ( ImplIsPatternChar( cChar, cEditMask ) ) + { + if ( (cEditMask == EDITMASK_UPPERALPHA) || + (cEditMask == EDITMASK_UPPERALPHANUM) || + ( cEditMask == EDITMASK_UPPERALLCHAR ) ) + { + cChar = ImplGetCharClass()->toUpper(OUString(cChar), 0, 1, + Application::GetSettings().GetLanguageTag().getLocale())[0]; + } + return cChar; + } + else + return 0; +} + +static bool ImplCommaPointCharEqual( sal_Unicode c1, sal_Unicode c2 ) +{ + if ( c1 == c2 ) + return true; + else if ( ((c1 == '.') || (c1 == ',')) && + ((c2 == '.') || (c2 == ',')) ) + return true; + else + return false; +} + +static OUString ImplPatternReformat( const OUString& rStr, + const OString& rEditMask, + std::u16string_view rLiteralMask, + sal_uInt16 nFormatFlags ) +{ + if (rEditMask.isEmpty()) + return rStr; + + OUStringBuffer aOutStr(rLiteralMask); + sal_Unicode cTempChar; + sal_Unicode cChar; + sal_Unicode cLiteral; + char cMask; + sal_Int32 nStrIndex = 0; + sal_Int32 i = 0; + sal_Int32 n; + + while ( i < rEditMask.getLength() ) + { + if ( nStrIndex >= rStr.getLength() ) + break; + + cChar = rStr[nStrIndex]; + cLiteral = rLiteralMask[i]; + cMask = rEditMask[i]; + + // current position is a literal + if ( cMask == EDITMASK_LITERAL ) + { + // if it is a literal copy otherwise ignore because it might be the next valid + // character of the string + if ( ImplCommaPointCharEqual( cChar, cLiteral ) ) + nStrIndex++; + else + { + // Otherwise we check if it is an invalid character. This is the case if it does not + // fit in the pattern of the next non-literal character. + n = i+1; + while ( n < rEditMask.getLength() ) + { + if ( rEditMask[n] != EDITMASK_LITERAL ) + { + if ( !ImplIsPatternChar( cChar, rEditMask[n] ) ) + nStrIndex++; + break; + } + + n++; + } + } + } + else + { + // valid character at this position + cTempChar = ImplPatternChar( cChar, cMask ); + if ( cTempChar ) + { + // use this character + aOutStr[i] = cTempChar; + nStrIndex++; + } + else + { + // copy if it is a literal character + if ( cLiteral == cChar ) + nStrIndex++; + else + { + // If the invalid character might be the next literal character then we jump + // ahead to it, otherwise we ignore it. Do only if empty literals are allowed. + if ( nFormatFlags & PATTERN_FORMAT_EMPTYLITERALS ) + { + n = i; + while ( n < rEditMask.getLength() ) + { + if ( rEditMask[n] == EDITMASK_LITERAL ) + { + if ( ImplCommaPointCharEqual( cChar, rLiteralMask[n] ) ) + i = n+1; + + break; + } + + n++; + } + } + + nStrIndex++; + continue; + } + } + } + + i++; + } + + return aOutStr.makeStringAndClear(); +} + +static void ImplPatternMaxPos( const OUString& rStr, const OString& rEditMask, + sal_uInt16 nFormatFlags, bool bSameMask, + sal_Int32 nCursorPos, sal_Int32& rPos ) +{ + + // last position must not be longer than the contained string + sal_Int32 nMaxPos = rStr.getLength(); + + // if non empty literals are allowed ignore blanks at the end as well + if ( bSameMask && !(nFormatFlags & PATTERN_FORMAT_EMPTYLITERALS) ) + { + while ( nMaxPos ) + { + if ( (rEditMask[nMaxPos-1] != EDITMASK_LITERAL) && + (rStr[nMaxPos-1] != ' ') ) + break; + nMaxPos--; + } + + // if we are in front of a literal, continue search until first character after the literal + sal_Int32 nTempPos = nMaxPos; + while ( nTempPos < rEditMask.getLength() ) + { + if ( rEditMask[nTempPos] != EDITMASK_LITERAL ) + { + nMaxPos = nTempPos; + break; + } + nTempPos++; + } + } + + if ( rPos > nMaxPos ) + rPos = nMaxPos; + + // character should not move left + if ( rPos < nCursorPos ) + rPos = nCursorPos; +} + +static OUString ImplPatternProcessStrictModify(const OUString& rText, + const OString& rEditMask, + std::u16string_view rLiteralMask, + bool bSameMask) +{ + OUString aText(rText); + + // remove leading blanks + if (bSameMask && !rEditMask.isEmpty()) + { + sal_Int32 i = 0; + sal_Int32 nMaxLen = aText.getLength(); + while ( i < nMaxLen ) + { + if ( (rEditMask[i] != EDITMASK_LITERAL) && + (aText[i] != ' ') ) + break; + + i++; + } + // keep all literal characters + while ( i && (rEditMask[i] == EDITMASK_LITERAL) ) + i--; + aText = aText.copy( i ); + } + + return ImplPatternReformat(aText, rEditMask, rLiteralMask, 0); +} + +static void ImplPatternProcessStrictModify( Edit* pEdit, + const OString& rEditMask, + std::u16string_view rLiteralMask, + bool bSameMask ) +{ + OUString aText = pEdit->GetText(); + OUString aNewText = ImplPatternProcessStrictModify(aText, + rEditMask, + rLiteralMask, + bSameMask); + + if ( aNewText == aText ) + return; + + // adjust selection such that it remains at the end if it was there before + Selection aSel = pEdit->GetSelection(); + sal_Int64 nMaxSel = std::max( aSel.Min(), aSel.Max() ); + if ( nMaxSel >= aText.getLength() ) + { + sal_Int32 nMaxPos = aNewText.getLength(); + ImplPatternMaxPos(aNewText, rEditMask, 0, bSameMask, nMaxSel, nMaxPos); + if ( aSel.Min() == aSel.Max() ) + { + aSel.Min() = nMaxPos; + aSel.Max() = aSel.Min(); + } + else if ( aSel.Min() > aSel.Max() ) + aSel.Min() = nMaxPos; + else + aSel.Max() = nMaxPos; + } + pEdit->SetText( aNewText, aSel ); +} + +static void ImplPatternProcessStrictModify( weld::Entry& rEntry, + const OString& rEditMask, + std::u16string_view rLiteralMask, + bool bSameMask ) +{ + OUString aText = rEntry.get_text(); + OUString aNewText = ImplPatternProcessStrictModify(aText, + rEditMask, + rLiteralMask, + bSameMask); + + if (aNewText == aText) + return; + + // adjust selection such that it remains at the end if it was there before + int nStartPos, nEndPos; + rEntry.get_selection_bounds(nStartPos, nEndPos); + + int nMaxSel = std::max(nStartPos, nEndPos); + if (nMaxSel >= aText.getLength()) + { + sal_Int32 nMaxPos = aNewText.getLength(); + ImplPatternMaxPos(aNewText, rEditMask, 0, bSameMask, nMaxSel, nMaxPos); + if (nStartPos == nEndPos) + { + nStartPos = nMaxPos; + nEndPos = nMaxPos; + } + else if (nStartPos > nMaxPos) + nStartPos = nMaxPos; + else + nEndPos = nMaxPos; + } + rEntry.set_text(aNewText); + rEntry.select_region(nStartPos, nEndPos); +} + +static sal_Int32 ImplPatternLeftPos(std::string_view rEditMask, sal_Int32 nCursorPos) +{ + // search non-literal predecessor + sal_Int32 nNewPos = nCursorPos; + sal_Int32 nTempPos = nNewPos; + while ( nTempPos ) + { + if ( rEditMask[nTempPos-1] != EDITMASK_LITERAL ) + { + nNewPos = nTempPos-1; + break; + } + nTempPos--; + } + return nNewPos; +} + +static sal_Int32 ImplPatternRightPos( const OUString& rStr, const OString& rEditMask, + sal_uInt16 nFormatFlags, bool bSameMask, + sal_Int32 nCursorPos ) +{ + // search non-literal successor + sal_Int32 nNewPos = nCursorPos; + ; + for(sal_Int32 nTempPos = nNewPos+1; nTempPos < rEditMask.getLength(); ++nTempPos ) + { + if ( rEditMask[nTempPos] != EDITMASK_LITERAL ) + { + nNewPos = nTempPos; + break; + } + } + ImplPatternMaxPos( rStr, rEditMask, nFormatFlags, bSameMask, nCursorPos, nNewPos ); + return nNewPos; +} + +namespace +{ + class IEditImplementation + { + public: + virtual ~IEditImplementation() {} + + virtual OUString GetText() const = 0; + virtual void SetText(const OUString& rStr, const Selection& rSelection) = 0; + + virtual Selection GetSelection() const = 0; + virtual void SetSelection(const Selection& rSelection) = 0; + + virtual bool IsInsertMode() const = 0; + + virtual void SetModified() = 0; + }; +} + +static bool ImplPatternProcessKeyInput( IEditImplementation& rEdit, const KeyEvent& rKEvt, + const OString& rEditMask, + std::u16string_view rLiteralMask, + bool bStrictFormat, + bool bSameMask, + bool& rbInKeyInput ) +{ + if ( rEditMask.isEmpty() || !bStrictFormat ) + return false; + + sal_uInt16 nFormatFlags = 0; + Selection aOldSel = rEdit.GetSelection(); + vcl::KeyCode aCode = rKEvt.GetKeyCode(); + sal_Unicode cChar = rKEvt.GetCharCode(); + sal_uInt16 nKeyCode = aCode.GetCode(); + bool bShift = aCode.IsShift(); + sal_Int32 nCursorPos = static_cast<sal_Int32>(aOldSel.Max()); + sal_Int32 nNewPos; + sal_Int32 nTempPos; + + if ( nKeyCode && !aCode.IsMod1() && !aCode.IsMod2() ) + { + if ( nKeyCode == KEY_LEFT ) + { + Selection aSel( ImplPatternLeftPos( rEditMask, nCursorPos ) ); + if ( bShift ) + aSel.Min() = aOldSel.Min(); + rEdit.SetSelection( aSel ); + return true; + } + else if ( nKeyCode == KEY_RIGHT ) + { + // Use the start of selection as minimum; even a small position is allowed in case that + // all was selected by the focus + Selection aSel( aOldSel ); + aSel.Justify(); + nCursorPos = aSel.Min(); + aSel.Max() = ImplPatternRightPos( rEdit.GetText(), rEditMask, nFormatFlags, bSameMask, nCursorPos ); + if ( bShift ) + aSel.Min() = aOldSel.Min(); + else + aSel.Min() = aSel.Max(); + rEdit.SetSelection( aSel ); + return true; + } + else if ( nKeyCode == KEY_HOME ) + { + // Home is the position of the first non-literal character + nNewPos = 0; + while ( (nNewPos < rEditMask.getLength()) && + (rEditMask[nNewPos] == EDITMASK_LITERAL) ) + nNewPos++; + + // Home should not move to the right + if ( nCursorPos < nNewPos ) + nNewPos = nCursorPos; + Selection aSel( nNewPos ); + if ( bShift ) + aSel.Min() = aOldSel.Min(); + rEdit.SetSelection( aSel ); + return true; + } + else if ( nKeyCode == KEY_END ) + { + // End is position of last non-literal character + nNewPos = rEditMask.getLength(); + while ( nNewPos && + (rEditMask[nNewPos-1] == EDITMASK_LITERAL) ) + nNewPos--; + // Use the start of selection as minimum; even a small position is allowed in case that + // all was selected by the focus + Selection aSel( aOldSel ); + aSel.Justify(); + nCursorPos = static_cast<sal_Int32>(aSel.Min()); + ImplPatternMaxPos( rEdit.GetText(), rEditMask, nFormatFlags, bSameMask, nCursorPos, nNewPos ); + aSel.Max() = nNewPos; + if ( bShift ) + aSel.Min() = aOldSel.Min(); + else + aSel.Min() = aSel.Max(); + rEdit.SetSelection( aSel ); + return true; + } + else if ( (nKeyCode == KEY_BACKSPACE) || (nKeyCode == KEY_DELETE) ) + { + OUString aOldStr( rEdit.GetText() ); + OUStringBuffer aStr( aOldStr ); + Selection aSel = aOldSel; + + aSel.Justify(); + nNewPos = static_cast<sal_Int32>(aSel.Min()); + + // if selection then delete it + if ( aSel.Len() ) + { + if ( bSameMask ) + aStr.remove( static_cast<sal_Int32>(aSel.Min()), static_cast<sal_Int32>(aSel.Len()) ); + else + { + std::u16string_view aRep = rLiteralMask.substr( static_cast<sal_Int32>(aSel.Min()), static_cast<sal_Int32>(aSel.Len()) ); + aStr.remove( aSel.Min(), aRep.size() ); + aStr.insert( aSel.Min(), aRep ); + } + } + else + { + if ( nKeyCode == KEY_BACKSPACE ) + { + nTempPos = nNewPos; + nNewPos = ImplPatternLeftPos( rEditMask, nTempPos ); + } + else + nTempPos = ImplPatternRightPos( aStr.toString(), rEditMask, nFormatFlags, bSameMask, nNewPos ); + + if ( nNewPos != nTempPos ) + { + if ( bSameMask ) + { + if ( rEditMask[nNewPos] != EDITMASK_LITERAL ) + aStr.remove( nNewPos, 1 ); + } + else + { + aStr[nNewPos] = rLiteralMask[nNewPos]; + } + } + } + + OUString sStr = aStr.makeStringAndClear(); + if ( aOldStr != sStr ) + { + if ( bSameMask ) + sStr = ImplPatternReformat( sStr, rEditMask, rLiteralMask, nFormatFlags ); + rbInKeyInput = true; + rEdit.SetText( sStr, Selection( nNewPos ) ); + rEdit.SetModified(); + rbInKeyInput = false; + } + else + rEdit.SetSelection( Selection( nNewPos ) ); + + return true; + } + else if ( nKeyCode == KEY_INSERT ) + { + // you can only set InsertMode for a PatternField if the + // mask is equal at all input positions + if ( !bSameMask ) + { + return true; + } + } + } + + if ( rKEvt.GetKeyCode().IsMod2() || (cChar < 32) || (cChar == 127) ) + return false; + + Selection aSel = aOldSel; + aSel.Justify(); + nNewPos = aSel.Min(); + + if ( nNewPos < rEditMask.getLength() ) + { + sal_Unicode cPattChar = ImplPatternChar( cChar, rEditMask[nNewPos] ); + if ( cPattChar ) + cChar = cPattChar; + else + { + // If no valid character, check if the user wanted to jump to next literal. We do this + // only if we're after a character, so that literals that were skipped automatically + // do not influence the position anymore. + if ( nNewPos && + (rEditMask[nNewPos-1] != EDITMASK_LITERAL) && + !aSel.Len() ) + { + // search for next character not being a literal + nTempPos = nNewPos; + while ( nTempPos < rEditMask.getLength() ) + { + if ( rEditMask[nTempPos] == EDITMASK_LITERAL ) + { + // only valid if no literal present + if ( (rEditMask[nTempPos+1] != EDITMASK_LITERAL ) && + ImplCommaPointCharEqual( cChar, rLiteralMask[nTempPos] ) ) + { + nTempPos++; + ImplPatternMaxPos( rEdit.GetText(), rEditMask, nFormatFlags, bSameMask, nNewPos, nTempPos ); + if ( nTempPos > nNewPos ) + { + rEdit.SetSelection( Selection( nTempPos ) ); + return true; + } + } + break; + } + nTempPos++; + } + } + + cChar = 0; + } + } + else + cChar = 0; + if ( cChar ) + { + OUStringBuffer aStr(rEdit.GetText()); + bool bError = false; + if ( bSameMask && rEdit.IsInsertMode() ) + { + // crop spaces and literals at the end until current position + sal_Int32 n = aStr.getLength(); + while ( n && (n > nNewPos) ) + { + if ( (aStr[n-1] != ' ') && + ((n > rEditMask.getLength()) || (rEditMask[n-1] != EDITMASK_LITERAL)) ) + break; + + n--; + } + aStr.truncate( n ); + + if ( aSel.Len() ) + aStr.remove( aSel.Min(), aSel.Len() ); + + if ( aStr.getLength() < rEditMask.getLength() ) + { + // possibly extend string until cursor position + if ( aStr.getLength() < nNewPos ) + aStr.append( rLiteralMask.substr(aStr.getLength(), nNewPos-aStr.getLength()) ); + if ( nNewPos < aStr.getLength() ) + aStr.insert( cChar, nNewPos ); + else if ( nNewPos < rEditMask.getLength() ) + aStr.append(cChar); + aStr = ImplPatternReformat( aStr.toString(), rEditMask, rLiteralMask, nFormatFlags ); + } + else + bError = true; + } + else + { + if ( aSel.Len() ) + { + // delete selection + std::u16string_view aRep = rLiteralMask.substr( aSel.Min(), aSel.Len() ); + aStr.remove( aSel.Min(), aRep.size() ); + aStr.insert( aSel.Min(), aRep ); + } + + if ( nNewPos < aStr.getLength() ) + aStr[nNewPos] = cChar; + else if ( nNewPos < rEditMask.getLength() ) + aStr.append(cChar); + } + + if ( !bError ) + { + rbInKeyInput = true; + const OUString sStr = aStr.makeStringAndClear(); + Selection aNewSel( ImplPatternRightPos( sStr, rEditMask, nFormatFlags, bSameMask, nNewPos ) ); + rEdit.SetText( sStr, aNewSel ); + rEdit.SetModified(); + rbInKeyInput = false; + } + } + + return true; +} + +namespace +{ + bool ImplSetMask(const OString& rEditMask, OUString& rLiteralMask) + { + bool bSameMask = true; + + if (rEditMask.getLength() != rLiteralMask.getLength()) + { + OUStringBuffer aBuf(rLiteralMask); + if (rEditMask.getLength() < aBuf.getLength()) + aBuf.setLength(rEditMask.getLength()); + else + comphelper::string::padToLength(aBuf, rEditMask.getLength(), ' '); + rLiteralMask = aBuf.makeStringAndClear(); + } + + // Strict mode allows only the input mode if only equal characters are allowed as mask and if + // only spaces are specified which are not allowed by the mask + sal_Int32 i = 0; + char c = 0; + while ( i < rEditMask.getLength() ) + { + char cTemp = rEditMask[i]; + if ( cTemp != EDITMASK_LITERAL ) + { + if ( (cTemp == EDITMASK_ALLCHAR) || + (cTemp == EDITMASK_UPPERALLCHAR) || + (cTemp == EDITMASK_NUMSPACE) ) + { + bSameMask = false; + break; + } + if ( i < rLiteralMask.getLength() ) + { + if ( rLiteralMask[i] != ' ' ) + { + bSameMask = false; + break; + } + } + if ( !c ) + c = cTemp; + if ( cTemp != c ) + { + bSameMask = false; + break; + } + } + i++; + } + + return bSameMask; + } +} + +PatternFormatter::PatternFormatter(Edit* pEdit) + : FormatterBase(pEdit) +{ + mbSameMask = true; + mbInPattKeyInput = false; +} + +PatternFormatter::~PatternFormatter() +{ +} + +void PatternFormatter::SetMask( const OString& rEditMask, + const OUString& rLiteralMask ) +{ + m_aEditMask = rEditMask; + maLiteralMask = rLiteralMask; + mbSameMask = ImplSetMask(m_aEditMask, maLiteralMask); + ReformatAll(); +} + +namespace +{ + class EntryImplementation : public IEditImplementation + { + public: + EntryImplementation(weld::PatternFormatter& rFormatter) + : m_rFormatter(rFormatter) + , m_rEntry(rFormatter.get_widget()) + { + } + + virtual OUString GetText() const override + { + return m_rEntry.get_text(); + } + + virtual void SetText(const OUString& rStr, const Selection& rSelection) override + { + m_rEntry.set_text(rStr); + SetSelection(rSelection); + } + + virtual Selection GetSelection() const override + { + int nStartPos, nEndPos; + m_rEntry.get_selection_bounds(nStartPos, nEndPos); + return Selection(nStartPos, nEndPos); + } + + virtual void SetSelection(const Selection& rSelection) override + { + auto nMin = rSelection.Min(); + auto nMax = rSelection.Max(); + m_rEntry.select_region(nMin < 0 ? 0 : nMin, nMax == SELECTION_MAX ? -1 : nMax); + } + + virtual bool IsInsertMode() const override + { + return !m_rEntry.get_overwrite_mode(); + } + + virtual void SetModified() override + { + m_rFormatter.Modify(); + } + + private: + weld::PatternFormatter& m_rFormatter; + weld::Entry& m_rEntry; + }; +} + +namespace weld +{ + void PatternFormatter::SetStrictFormat(bool bStrict) + { + if (bStrict != m_bStrictFormat) + { + m_bStrictFormat = bStrict; + if (m_bStrictFormat) + ReformatAll(); + } + } + + void PatternFormatter::SetMask(const OString& rEditMask, + const OUString& rLiteralMask) + { + m_aEditMask = rEditMask; + m_aLiteralMask = rLiteralMask; + m_bSameMask = ImplSetMask(m_aEditMask, m_aLiteralMask); + ReformatAll(); + } + + void PatternFormatter::ReformatAll() + { + m_rEntry.set_text(ImplPatternReformat(m_rEntry.get_text(), m_aEditMask, m_aLiteralMask, 0/*nFormatFlags*/)); + if (!m_bSameMask && m_bStrictFormat && m_rEntry.get_editable()) + m_rEntry.set_overwrite_mode(true); + } + + void PatternFormatter::EntryGainFocus() + { + m_bReformat = false; + } + + void PatternFormatter::EntryLostFocus() + { + if (m_bReformat) + ReformatAll(); + } + + void PatternFormatter::Modify() + { + if (!m_bInPattKeyInput) + { + if (m_bStrictFormat) + ImplPatternProcessStrictModify(m_rEntry, m_aEditMask, m_aLiteralMask, m_bSameMask); + else + m_bReformat = true; + } + m_aModifyHdl.Call(m_rEntry); + } + + IMPL_LINK(PatternFormatter, KeyInputHdl, const KeyEvent&, rKEvt, bool) + { + if (m_aKeyPressHdl.Call(rKEvt)) + return true; + if (rKEvt.GetKeyCode().IsMod2()) + return false; + EntryImplementation aAdapt(*this); + return ImplPatternProcessKeyInput(aAdapt, rKEvt, m_aEditMask, m_aLiteralMask, + m_bStrictFormat, + m_bSameMask, m_bInPattKeyInput); + } +} + +void PatternFormatter::SetString( const OUString& rStr ) +{ + if ( GetField() ) + { + GetField()->SetText( rStr ); + MarkToBeReformatted( false ); + } +} + +OUString PatternFormatter::GetString() const +{ + if ( !GetField() ) + return OUString(); + else + return ImplPatternReformat( GetField()->GetText(), m_aEditMask, maLiteralMask, 0/*nFormatFlags*/ ); +} + +void PatternFormatter::Reformat() +{ + if ( GetField() ) + { + ImplSetText( ImplPatternReformat( GetField()->GetText(), m_aEditMask, maLiteralMask, 0/*nFormatFlags*/ ) ); + if ( !mbSameMask && IsStrictFormat() && !GetField()->IsReadOnly() ) + GetField()->SetInsertMode( false ); + } +} + +PatternField::PatternField(vcl::Window* pParent, WinBits nWinStyle) + : SpinField(pParent, nWinStyle) + , PatternFormatter(this) +{ + Reformat(); +} + +void PatternField::dispose() +{ + ClearField(); + SpinField::dispose(); +} + +namespace +{ + class EditImplementation : public IEditImplementation + { + public: + EditImplementation(Edit& rEdit) + : m_rEdit(rEdit) + { + } + + virtual OUString GetText() const override + { + return m_rEdit.GetText(); + } + + virtual void SetText(const OUString& rStr, const Selection& rSelection) override + { + m_rEdit.SetText(rStr, rSelection); + } + + virtual Selection GetSelection() const override + { + return m_rEdit.GetSelection(); + } + + virtual void SetSelection(const Selection& rSelection) override + { + m_rEdit.SetSelection(rSelection); + } + + virtual bool IsInsertMode() const override + { + return m_rEdit.IsInsertMode(); + } + + virtual void SetModified() override + { + m_rEdit.SetModifyFlag(); + m_rEdit.Modify(); + } + + private: + Edit& m_rEdit; + }; +} + +bool PatternField::PreNotify( NotifyEvent& rNEvt ) +{ + if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() ) + { + EditImplementation aAdapt(*GetField()); + if ( ImplPatternProcessKeyInput( aAdapt, *rNEvt.GetKeyEvent(), GetEditMask(), GetLiteralMask(), + IsStrictFormat(), + ImplIsSameMask(), ImplGetInPattKeyInput() ) ) + return true; + } + + return SpinField::PreNotify( rNEvt ); +} + +bool PatternField::EventNotify( NotifyEvent& rNEvt ) +{ + if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS ) + MarkToBeReformatted( false ); + else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS ) + { + if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) ) + Reformat(); + } + + return SpinField::EventNotify( rNEvt ); +} + +void PatternField::Modify() +{ + if ( !ImplGetInPattKeyInput() ) + { + if ( IsStrictFormat() ) + ImplPatternProcessStrictModify( GetField(), GetEditMask(), GetLiteralMask(), ImplIsSameMask() ); + else + MarkToBeReformatted( true ); + } + + SpinField::Modify(); +} + +PatternBox::PatternBox(vcl::Window* pParent, WinBits nWinStyle) + : ComboBox( pParent, nWinStyle ) + , PatternFormatter(this) +{ + Reformat(); +} + +void PatternBox::dispose() +{ + ClearField(); + ComboBox::dispose(); +} + +bool PatternBox::PreNotify( NotifyEvent& rNEvt ) +{ + if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() ) + { + EditImplementation aAdapt(*GetField()); + if ( ImplPatternProcessKeyInput( aAdapt, *rNEvt.GetKeyEvent(), GetEditMask(), GetLiteralMask(), + IsStrictFormat(), + ImplIsSameMask(), ImplGetInPattKeyInput() ) ) + return true; + } + + return ComboBox::PreNotify( rNEvt ); +} + +bool PatternBox::EventNotify( NotifyEvent& rNEvt ) +{ + if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS ) + MarkToBeReformatted( false ); + else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS ) + { + if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) ) + Reformat(); + } + + return ComboBox::EventNotify( rNEvt ); +} + +void PatternBox::Modify() +{ + if ( !ImplGetInPattKeyInput() ) + { + if ( IsStrictFormat() ) + ImplPatternProcessStrictModify( GetField(), GetEditMask(), GetLiteralMask(), ImplIsSameMask() ); + else + MarkToBeReformatted( true ); + } + + ComboBox::Modify(); +} + +void PatternBox::ReformatAll() +{ + OUString aStr; + SetUpdateMode( false ); + const sal_Int32 nEntryCount = GetEntryCount(); + for ( sal_Int32 i=0; i < nEntryCount; ++i ) + { + aStr = ImplPatternReformat( GetEntry( i ), GetEditMask(), GetLiteralMask(), 0/*nFormatFlags*/ ); + RemoveEntryAt(i); + InsertEntry( aStr, i ); + } + PatternFormatter::Reformat(); + SetUpdateMode( true ); +} + +static ExtDateFieldFormat ImplGetExtFormat( LongDateOrder eOld ) +{ + switch( eOld ) + { + case LongDateOrder::YDM: + case LongDateOrder::DMY: return ExtDateFieldFormat::ShortDDMMYY; + case LongDateOrder::MDY: return ExtDateFieldFormat::ShortMMDDYY; + case LongDateOrder::YMD: + default: return ExtDateFieldFormat::ShortYYMMDD; + } +} + +static sal_uInt16 ImplCutNumberFromString( OUString& rStr ) +{ + sal_Int32 i1 = 0; + while (i1 != rStr.getLength() && (rStr[i1] < '0' || rStr[i1] > '9')) { + ++i1; + } + sal_Int32 i2 = i1; + while (i2 != rStr.getLength() && rStr[i2] >= '0' && rStr[i2] <= '9') { + ++i2; + } + sal_Int32 nValue = o3tl::toInt32(rStr.subView(i1, i2-i1)); + rStr = rStr.copy(std::min(i2+1, rStr.getLength())); + return nValue; +} + +static bool ImplCutMonthName( OUString& rStr, std::u16string_view _rLookupMonthName ) +{ + sal_Int32 index = 0; + rStr = rStr.replaceFirst(_rLookupMonthName, "", &index); + return index >= 0; +} + +static sal_uInt16 ImplGetMonthFromCalendarItem( OUString& rStr, const uno::Sequence< i18n::CalendarItem2 >& rMonths ) +{ + const sal_uInt16 nMonths = rMonths.getLength(); + for (sal_uInt16 i=0; i < nMonths; ++i) + { + // long month name? + if ( ImplCutMonthName( rStr, rMonths[i].FullName ) ) + return i+1; + + // short month name? + if ( ImplCutMonthName( rStr, rMonths[i].AbbrevName ) ) + return i+1; + } + return 0; +} + +static sal_uInt16 ImplCutMonthFromString( OUString& rStr, OUString& rCalendarName, + const LocaleDataWrapper& rLocaleData, const CalendarWrapper& rCalendarWrapper ) +{ + const OUString aDefaultCalendarName( rCalendarWrapper.getUniqueID()); + rCalendarName = aDefaultCalendarName; + + // Search for a month name of the loaded default calendar. + const uno::Sequence< i18n::CalendarItem2 > aMonths = rCalendarWrapper.getMonths(); + sal_uInt16 nMonth = ImplGetMonthFromCalendarItem( rStr, aMonths); + if (nMonth > 0) + return nMonth; + + // And also possessive genitive and partitive month names. + const uno::Sequence< i18n::CalendarItem2 > aGenitiveMonths = rCalendarWrapper.getGenitiveMonths(); + if (aGenitiveMonths != aMonths) + { + nMonth = ImplGetMonthFromCalendarItem( rStr, aGenitiveMonths); + if (nMonth > 0) + return nMonth; + } + const uno::Sequence< i18n::CalendarItem2 > aPartitiveMonths = rCalendarWrapper.getPartitiveMonths(); + if (aPartitiveMonths != aMonths) + { + nMonth = ImplGetMonthFromCalendarItem( rStr, aPartitiveMonths); + if (nMonth > 0) + return nMonth; + } + + // Check if there are more calendars and try them if so, as the long date + // format is obtained from the number formatter this is possible (e.g. + // ar_DZ "[~hijri] ...") + const uno::Sequence< i18n::Calendar2 > aCalendars = rLocaleData.getAllCalendars(); + if (aCalendars.getLength() > 1) + { + for (const auto& rCalendar : aCalendars) + { + if (rCalendar.Name != aDefaultCalendarName) + { + rCalendarName = rCalendar.Name; + + nMonth = ImplGetMonthFromCalendarItem( rStr, rCalendar.Months); + if (nMonth > 0) + return nMonth; + + if (rCalendar.Months != rCalendar.GenitiveMonths) + { + nMonth = ImplGetMonthFromCalendarItem( rStr, rCalendar.GenitiveMonths); + if (nMonth > 0) + return nMonth; + } + + if (rCalendar.Months != rCalendar.PartitiveMonths) + { + nMonth = ImplGetMonthFromCalendarItem( rStr, rCalendar.PartitiveMonths); + if (nMonth > 0) + return nMonth; + } + + rCalendarName = aDefaultCalendarName; + } + } + } + + return ImplCutNumberFromString( rStr ); +} + +static OUString ImplGetDateSep( const LocaleDataWrapper& rLocaleDataWrapper, ExtDateFieldFormat eFormat ) +{ + if ( ( eFormat == ExtDateFieldFormat::ShortYYMMDD_DIN5008 ) || ( eFormat == ExtDateFieldFormat::ShortYYYYMMDD_DIN5008 ) ) + return "-"; + else + return rLocaleDataWrapper.getDateSep(); +} + +static bool ImplDateProcessKeyInput( const KeyEvent& rKEvt, ExtDateFieldFormat eFormat, + const LocaleDataWrapper& rLocaleDataWrapper ) +{ + sal_Unicode cChar = rKEvt.GetCharCode(); + sal_uInt16 nGroup = rKEvt.GetKeyCode().GetGroup(); + return !((nGroup == KEYGROUP_FKEYS) || + (nGroup == KEYGROUP_CURSOR) || + (nGroup == KEYGROUP_MISC)|| + ((cChar >= '0') && (cChar <= '9')) || + (cChar == ImplGetDateSep( rLocaleDataWrapper, eFormat )[0])); +} + +bool DateFormatter::TextToDate(const OUString& rStr, Date& rDate, ExtDateFieldFormat eDateOrder, + const LocaleDataWrapper& rLocaleDataWrapper, const CalendarWrapper& rCalendarWrapper) +{ + sal_uInt16 nDay = 0; + sal_uInt16 nMonth = 0; + sal_uInt16 nYear = 0; + bool bError = false; + OUString aStr( rStr ); + + if ( eDateOrder == ExtDateFieldFormat::SystemLong ) + { + OUString aCalendarName; + LongDateOrder eFormat = rLocaleDataWrapper.getLongDateOrder(); + switch( eFormat ) + { + case LongDateOrder::MDY: + nMonth = ImplCutMonthFromString( aStr, aCalendarName, rLocaleDataWrapper, rCalendarWrapper ); + nDay = ImplCutNumberFromString( aStr ); + nYear = ImplCutNumberFromString( aStr ); + break; + case LongDateOrder::DMY: + nDay = ImplCutNumberFromString( aStr ); + nMonth = ImplCutMonthFromString( aStr, aCalendarName, rLocaleDataWrapper, rCalendarWrapper ); + nYear = ImplCutNumberFromString( aStr ); + break; + case LongDateOrder::YDM: + nYear = ImplCutNumberFromString( aStr ); + nDay = ImplCutNumberFromString( aStr ); + nMonth = ImplCutMonthFromString( aStr, aCalendarName, rLocaleDataWrapper, rCalendarWrapper ); + break; + case LongDateOrder::YMD: + default: + nYear = ImplCutNumberFromString( aStr ); + nMonth = ImplCutMonthFromString( aStr, aCalendarName, rLocaleDataWrapper, rCalendarWrapper ); + nDay = ImplCutNumberFromString( aStr ); + break; + } + if (aCalendarName != "gregorian") + { + // Calendar widget is Gregorian, convert date. + // Need full date. + bError = !nDay || !nMonth || !nYear; + if (!bError) + { + CalendarWrapper aCW( rLocaleDataWrapper.getComponentContext()); + aCW.loadCalendar( aCalendarName, rLocaleDataWrapper.getLoadedLanguageTag().getLocale()); + aCW.setDateTime(0.5); // get rid of current time, set some day noon + aCW.setValue( i18n::CalendarFieldIndex::DAY_OF_MONTH, nDay); + aCW.setValue( i18n::CalendarFieldIndex::MONTH, nMonth - 1); + aCW.setValue( i18n::CalendarFieldIndex::YEAR, nYear); + bError = !aCW.isValid(); + if (!bError) + { + Date aDate = aCW.getEpochStart() + aCW.getDateTime(); + nYear = aDate.GetYear(); + nMonth = aDate.GetMonth(); + nDay = aDate.GetDay(); + } + } + } + } + else + { + bool bYear = true; + + // Check if year is present: + OUString aDateSep = ImplGetDateSep( rLocaleDataWrapper, eDateOrder ); + sal_Int32 nSepPos = aStr.indexOf( aDateSep ); + if ( nSepPos < 0 ) + return false; + nSepPos = aStr.indexOf( aDateSep, nSepPos+1 ); + if ( ( nSepPos < 0 ) || ( nSepPos == (aStr.getLength()-1) ) ) + { + bYear = false; + nYear = Date( Date::SYSTEM ).GetYearUnsigned(); + } + + const sal_Unicode* pBuf = aStr.getStr(); + ImplSkipDelimiters( pBuf ); + + switch ( eDateOrder ) + { + case ExtDateFieldFormat::ShortDDMMYY: + case ExtDateFieldFormat::ShortDDMMYYYY: + { + nDay = ImplGetNum( pBuf, bError ); + ImplSkipDelimiters( pBuf ); + nMonth = ImplGetNum( pBuf, bError ); + ImplSkipDelimiters( pBuf ); + if ( bYear ) + nYear = ImplGetNum( pBuf, bError ); + } + break; + case ExtDateFieldFormat::ShortMMDDYY: + case ExtDateFieldFormat::ShortMMDDYYYY: + { + nMonth = ImplGetNum( pBuf, bError ); + ImplSkipDelimiters( pBuf ); + nDay = ImplGetNum( pBuf, bError ); + ImplSkipDelimiters( pBuf ); + if ( bYear ) + nYear = ImplGetNum( pBuf, bError ); + } + break; + case ExtDateFieldFormat::ShortYYMMDD: + case ExtDateFieldFormat::ShortYYYYMMDD: + case ExtDateFieldFormat::ShortYYMMDD_DIN5008: + case ExtDateFieldFormat::ShortYYYYMMDD_DIN5008: + { + if ( bYear ) + nYear = ImplGetNum( pBuf, bError ); + ImplSkipDelimiters( pBuf ); + nMonth = ImplGetNum( pBuf, bError ); + ImplSkipDelimiters( pBuf ); + nDay = ImplGetNum( pBuf, bError ); + } + break; + + default: + { + OSL_FAIL( "DateOrder???" ); + } + } + } + + if ( bError || !nDay || !nMonth ) + return false; + + Date aNewDate( nDay, nMonth, nYear ); + DateFormatter::ExpandCentury( aNewDate, officecfg::Office::Common::DateFormat::TwoDigitYear::get() ); + if ( aNewDate.IsValidDate() ) + { + rDate = aNewDate; + return true; + } + return false; +} + +void DateFormatter::ImplDateReformat( const OUString& rStr, OUString& rOutStr ) +{ + Date aDate( Date::EMPTY ); + if (!TextToDate(rStr, aDate, GetExtDateFormat(true), ImplGetLocaleDataWrapper(), GetCalendarWrapper())) + return; + + Date aTempDate = aDate; + if ( aTempDate > GetMax() ) + aTempDate = GetMax(); + else if ( aTempDate < GetMin() ) + aTempDate = GetMin(); + + rOutStr = ImplGetDateAsText( aTempDate ); +} + +namespace +{ + ExtDateFieldFormat ResolveSystemFormat(ExtDateFieldFormat eDateFormat, const LocaleDataWrapper& rLocaleData) + { + if (eDateFormat <= ExtDateFieldFormat::SystemShortYYYY) + { + bool bShowCentury = (eDateFormat == ExtDateFieldFormat::SystemShortYYYY); + switch (rLocaleData.getDateOrder()) + { + case DateOrder::DMY: + eDateFormat = bShowCentury ? ExtDateFieldFormat::ShortDDMMYYYY : ExtDateFieldFormat::ShortDDMMYY; + break; + case DateOrder::MDY: + eDateFormat = bShowCentury ? ExtDateFieldFormat::ShortMMDDYYYY : ExtDateFieldFormat::ShortMMDDYY; + break; + default: + eDateFormat = bShowCentury ? ExtDateFieldFormat::ShortYYYYMMDD : ExtDateFieldFormat::ShortYYMMDD; + } + } + return eDateFormat; + } +} + +OUString DateFormatter::FormatDate(const Date& rDate, ExtDateFieldFormat eExtFormat, + const LocaleDataWrapper& rLocaleData, + const Formatter::StaticFormatter& rStaticFormatter) +{ + bool bShowCentury = false; + switch (eExtFormat) + { + case ExtDateFieldFormat::SystemShortYYYY: + case ExtDateFieldFormat::SystemLong: + case ExtDateFieldFormat::ShortDDMMYYYY: + case ExtDateFieldFormat::ShortMMDDYYYY: + case ExtDateFieldFormat::ShortYYYYMMDD: + case ExtDateFieldFormat::ShortYYYYMMDD_DIN5008: + { + bShowCentury = true; + } + break; + default: + { + bShowCentury = false; + } + } + + if ( !bShowCentury ) + { + // Check if I have to use force showing the century + sal_uInt16 nTwoDigitYearStart = officecfg::Office::Common::DateFormat::TwoDigitYear::get(); + sal_uInt16 nYear = rDate.GetYearUnsigned(); + + // If year is not in double digit range + if ( (nYear < nTwoDigitYearStart) || (nYear >= nTwoDigitYearStart+100) ) + bShowCentury = true; + } + + sal_Unicode aBuf[128]; + sal_Unicode* pBuf = aBuf; + + eExtFormat = ResolveSystemFormat(eExtFormat, rLocaleData); + + OUString aDateSep = ImplGetDateSep( rLocaleData, eExtFormat ); + sal_uInt16 nDay = rDate.GetDay(); + sal_uInt16 nMonth = rDate.GetMonth(); + sal_Int16 nYear = rDate.GetYear(); + sal_uInt16 nYearLen = bShowCentury ? 4 : 2; + + if ( !bShowCentury ) + nYear %= 100; + + switch (eExtFormat) + { + case ExtDateFieldFormat::SystemLong: + { + SvNumberFormatter* pFormatter = rStaticFormatter; + const LanguageTag aFormatterLang( pFormatter->GetLanguageTag()); + const sal_uInt32 nIndex = pFormatter->GetFormatIndex( NF_DATE_SYSTEM_LONG, + rLocaleData.getLanguageTag().getLanguageType(false)); + OUString aStr; + const Color* pCol; + pFormatter->GetOutputString( rDate - pFormatter->GetNullDate(), nIndex, aStr, &pCol); + // Reset to what other uses may expect. + pFormatter->ChangeIntl( aFormatterLang.getLanguageType(false)); + return aStr; + } + case ExtDateFieldFormat::ShortDDMMYY: + case ExtDateFieldFormat::ShortDDMMYYYY: + { + pBuf = ImplAddNum( pBuf, nDay, 2 ); + pBuf = ImplAddString( pBuf, aDateSep ); + pBuf = ImplAddNum( pBuf, nMonth, 2 ); + pBuf = ImplAddString( pBuf, aDateSep ); + pBuf = ImplAddSNum( pBuf, nYear, nYearLen ); + } + break; + case ExtDateFieldFormat::ShortMMDDYY: + case ExtDateFieldFormat::ShortMMDDYYYY: + { + pBuf = ImplAddNum( pBuf, nMonth, 2 ); + pBuf = ImplAddString( pBuf, aDateSep ); + pBuf = ImplAddNum( pBuf, nDay, 2 ); + pBuf = ImplAddString( pBuf, aDateSep ); + pBuf = ImplAddSNum( pBuf, nYear, nYearLen ); + } + break; + case ExtDateFieldFormat::ShortYYMMDD: + case ExtDateFieldFormat::ShortYYYYMMDD: + case ExtDateFieldFormat::ShortYYMMDD_DIN5008: + case ExtDateFieldFormat::ShortYYYYMMDD_DIN5008: + { + pBuf = ImplAddSNum( pBuf, nYear, nYearLen ); + pBuf = ImplAddString( pBuf, aDateSep ); + pBuf = ImplAddNum( pBuf, nMonth, 2 ); + pBuf = ImplAddString( pBuf, aDateSep ); + pBuf = ImplAddNum( pBuf, nDay, 2 ); + } + break; + default: + { + OSL_FAIL( "DateOrder???" ); + } + } + + return OUString(aBuf, pBuf-aBuf); +} + +OUString DateFormatter::ImplGetDateAsText( const Date& rDate ) const +{ + return DateFormatter::FormatDate(rDate, GetExtDateFormat(), ImplGetLocaleDataWrapper(), maStaticFormatter); +} + +static void ImplDateIncrementDay( Date& rDate, bool bUp ) +{ + DateFormatter::ExpandCentury( rDate ); + rDate.AddDays( bUp ? 1 : -1 ); +} + +static void ImplDateIncrementMonth( Date& rDate, bool bUp ) +{ + DateFormatter::ExpandCentury( rDate ); + rDate.AddMonths( bUp ? 1 : -1 ); +} + +static void ImplDateIncrementYear( Date& rDate, bool bUp ) +{ + DateFormatter::ExpandCentury( rDate ); + rDate.AddYears( bUp ? 1 : -1 ); +} + +bool DateFormatter::ImplAllowMalformedInput() const +{ + return !IsEnforceValidValue(); +} + +int DateFormatter::GetDateArea(ExtDateFieldFormat& eFormat, std::u16string_view rText, int nCursor, const LocaleDataWrapper& rLocaleDataWrapper) +{ + sal_Int8 nDateArea = 0; + + if ( eFormat == ExtDateFieldFormat::SystemLong ) + { + eFormat = ImplGetExtFormat(rLocaleDataWrapper.getLongDateOrder()); + nDateArea = 1; + } + else + { + // search area + size_t nPos = 0; + OUString aDateSep = ImplGetDateSep(rLocaleDataWrapper, eFormat); + for ( sal_Int8 i = 1; i <= 3; i++ ) + { + nPos = rText.find( aDateSep, nPos ); + if (nPos == std::u16string_view::npos || static_cast<sal_Int32>(nPos) >= nCursor) + { + nDateArea = i; + break; + } + else + nPos++; + } + } + + return nDateArea; +} + +void DateField::ImplDateSpinArea( bool bUp ) +{ + // increment days if all is selected + if ( !GetField() ) + return; + + Date aDate( GetDate() ); + Selection aSelection = GetField()->GetSelection(); + aSelection.Justify(); + OUString aText( GetText() ); + if ( static_cast<sal_Int32>(aSelection.Len()) == aText.getLength() ) + ImplDateIncrementDay( aDate, bUp ); + else + { + ExtDateFieldFormat eFormat = GetExtDateFormat( true ); + sal_Int8 nDateArea = GetDateArea(eFormat, aText, aSelection.Max(), ImplGetLocaleDataWrapper()); + + switch( eFormat ) + { + case ExtDateFieldFormat::ShortMMDDYY: + case ExtDateFieldFormat::ShortMMDDYYYY: + switch( nDateArea ) + { + case 1: ImplDateIncrementMonth( aDate, bUp ); + break; + case 2: ImplDateIncrementDay( aDate, bUp ); + break; + case 3: ImplDateIncrementYear( aDate, bUp ); + break; + } + break; + case ExtDateFieldFormat::ShortDDMMYY: + case ExtDateFieldFormat::ShortDDMMYYYY: + switch( nDateArea ) + { + case 1: ImplDateIncrementDay( aDate, bUp ); + break; + case 2: ImplDateIncrementMonth( aDate, bUp ); + break; + case 3: ImplDateIncrementYear( aDate, bUp ); + break; + } + break; + case ExtDateFieldFormat::ShortYYMMDD: + case ExtDateFieldFormat::ShortYYYYMMDD: + case ExtDateFieldFormat::ShortYYMMDD_DIN5008: + case ExtDateFieldFormat::ShortYYYYMMDD_DIN5008: + switch( nDateArea ) + { + case 1: ImplDateIncrementYear( aDate, bUp ); + break; + case 2: ImplDateIncrementMonth( aDate, bUp ); + break; + case 3: ImplDateIncrementDay( aDate, bUp ); + break; + } + break; + default: + OSL_FAIL( "invalid conversion" ); + break; + } + } + + ImplNewFieldValue( aDate ); +} + +DateFormatter::DateFormatter(Edit* pEdit) + : FormatterBase(pEdit) + , maFieldDate(0) + , maLastDate(0) + , maMin(1, 1, 1900) + , maMax(31, 12, 2200) + , mbLongFormat(false) + , mbShowDateCentury(true) + , mnExtDateFormat(ExtDateFieldFormat::SystemShort) + , mbEnforceValidValue(true) +{ +} + +DateFormatter::~DateFormatter() +{ +} + +CalendarWrapper& DateFormatter::GetCalendarWrapper() const +{ + if (!mxCalendarWrapper) + { + const_cast<DateFormatter*>(this)->mxCalendarWrapper.reset( new CalendarWrapper( comphelper::getProcessComponentContext() ) ); + mxCalendarWrapper->loadDefaultCalendar( GetLocale() ); + } + + return *mxCalendarWrapper; +} + +void DateFormatter::SetExtDateFormat( ExtDateFieldFormat eFormat ) +{ + mnExtDateFormat = eFormat; + ReformatAll(); +} + +ExtDateFieldFormat DateFormatter::GetExtDateFormat( bool bResolveSystemFormat ) const +{ + ExtDateFieldFormat eDateFormat = mnExtDateFormat; + + if (bResolveSystemFormat) + eDateFormat = ResolveSystemFormat(eDateFormat, ImplGetLocaleDataWrapper()); + + return eDateFormat; +} + +void DateFormatter::ReformatAll() +{ + Reformat(); +} + +void DateFormatter::SetMin( const Date& rNewMin ) +{ + maMin = rNewMin; + if ( !IsEmptyFieldValue() ) + ReformatAll(); +} + +void DateFormatter::SetMax( const Date& rNewMax ) +{ + maMax = rNewMax; + if ( !IsEmptyFieldValue() ) + ReformatAll(); +} + +void DateFormatter::SetLongFormat( bool bLong ) +{ + mbLongFormat = bLong; + + // #91913# Remove LongFormat and DateShowCentury - redundant + if ( bLong ) + { + SetExtDateFormat( ExtDateFieldFormat::SystemLong ); + } + else + { + if( mnExtDateFormat == ExtDateFieldFormat::SystemLong ) + SetExtDateFormat( ExtDateFieldFormat::SystemShort ); + } + + ReformatAll(); +} + +namespace +{ + ExtDateFieldFormat ChangeDateCentury(ExtDateFieldFormat eExtDateFormat, bool bShowDateCentury) + { + // #91913# Remove LongFormat and DateShowCentury - redundant + if (bShowDateCentury) + { + switch (eExtDateFormat) + { + case ExtDateFieldFormat::SystemShort: + case ExtDateFieldFormat::SystemShortYY: + eExtDateFormat = ExtDateFieldFormat::SystemShortYYYY; break; + case ExtDateFieldFormat::ShortDDMMYY: + eExtDateFormat = ExtDateFieldFormat::ShortDDMMYYYY; break; + case ExtDateFieldFormat::ShortMMDDYY: + eExtDateFormat = ExtDateFieldFormat::ShortMMDDYYYY; break; + case ExtDateFieldFormat::ShortYYMMDD: + eExtDateFormat = ExtDateFieldFormat::ShortYYYYMMDD; break; + case ExtDateFieldFormat::ShortYYMMDD_DIN5008: + eExtDateFormat = ExtDateFieldFormat::ShortYYYYMMDD_DIN5008; break; + default: + ; + } + } + else + { + switch (eExtDateFormat) + { + case ExtDateFieldFormat::SystemShort: + case ExtDateFieldFormat::SystemShortYYYY: + eExtDateFormat = ExtDateFieldFormat::SystemShortYY; break; + case ExtDateFieldFormat::ShortDDMMYYYY: + eExtDateFormat = ExtDateFieldFormat::ShortDDMMYY; break; + case ExtDateFieldFormat::ShortMMDDYYYY: + eExtDateFormat = ExtDateFieldFormat::ShortMMDDYY; break; + case ExtDateFieldFormat::ShortYYYYMMDD: + eExtDateFormat = ExtDateFieldFormat::ShortYYMMDD; break; + case ExtDateFieldFormat::ShortYYYYMMDD_DIN5008: + eExtDateFormat = ExtDateFieldFormat::ShortYYMMDD_DIN5008; break; + default: + ; + } + } + + return eExtDateFormat; + } +} + +void DateFormatter::SetShowDateCentury( bool bShowDateCentury ) +{ + mbShowDateCentury = bShowDateCentury; + + SetExtDateFormat(ChangeDateCentury(GetExtDateFormat(), bShowDateCentury)); + + ReformatAll(); +} + +void DateFormatter::SetDate( const Date& rNewDate ) +{ + ImplSetUserDate( rNewDate ); + maFieldDate = maLastDate; + maLastDate = GetDate(); +} + +void DateFormatter::ImplSetUserDate( const Date& rNewDate, Selection const * pNewSelection ) +{ + Date aNewDate = rNewDate; + if ( aNewDate > maMax ) + aNewDate = maMax; + else if ( aNewDate < maMin ) + aNewDate = maMin; + maLastDate = aNewDate; + + if ( GetField() ) + ImplSetText( ImplGetDateAsText( aNewDate ), pNewSelection ); +} + +void DateFormatter::ImplNewFieldValue( const Date& rDate ) +{ + if ( !GetField() ) + return; + + Selection aSelection = GetField()->GetSelection(); + aSelection.Justify(); + OUString aText = GetField()->GetText(); + + // If selected until the end then keep it that way + if ( static_cast<sal_Int32>(aSelection.Max()) == aText.getLength() ) + { + if ( !aSelection.Len() ) + aSelection.Min() = SELECTION_MAX; + aSelection.Max() = SELECTION_MAX; + } + + Date aOldLastDate = maLastDate; + ImplSetUserDate( rDate, &aSelection ); + maLastDate = aOldLastDate; + + // Modify at Edit is only set at KeyInput + if ( GetField()->GetText() != aText ) + { + GetField()->SetModifyFlag(); + GetField()->Modify(); + } +} + +Date DateFormatter::GetDate() const +{ + Date aDate( Date::EMPTY ); + + if ( GetField() ) + { + if (TextToDate(GetField()->GetText(), aDate, GetExtDateFormat(true), ImplGetLocaleDataWrapper(), GetCalendarWrapper())) + { + if ( aDate > maMax ) + aDate = maMax; + else if ( aDate < maMin ) + aDate = maMin; + } + else + { + // !!! We should find out why dates are treated differently than other fields (see + // also bug: 52384) + + if ( !ImplAllowMalformedInput() ) + { + if ( maLastDate.GetDate() ) + aDate = maLastDate; + else if ( !IsEmptyFieldValueEnabled() ) + aDate = Date( Date::SYSTEM ); + } + else + aDate = Date( Date::EMPTY ); // set invalid date + } + } + + return aDate; +} + +void DateFormatter::SetEmptyDate() +{ + FormatterBase::SetEmptyFieldValue(); +} + +bool DateFormatter::IsEmptyDate() const +{ + bool bEmpty = FormatterBase::IsEmptyFieldValue(); + + if ( GetField() && MustBeReformatted() && IsEmptyFieldValueEnabled() ) + { + if ( GetField()->GetText().isEmpty() ) + { + bEmpty = true; + } + else if ( !maLastDate.GetDate() ) + { + Date aDate( Date::EMPTY ); + bEmpty = !TextToDate(GetField()->GetText(), aDate, GetExtDateFormat(true), ImplGetLocaleDataWrapper(), GetCalendarWrapper()); + } + } + return bEmpty; +} + +void DateFormatter::Reformat() +{ + if ( !GetField() ) + return; + + if ( GetField()->GetText().isEmpty() && ImplGetEmptyFieldValue() ) + return; + + OUString aStr; + ImplDateReformat( GetField()->GetText(), aStr ); + + if ( !aStr.isEmpty() ) + { + ImplSetText( aStr ); + (void)TextToDate(aStr, maLastDate, GetExtDateFormat(true), ImplGetLocaleDataWrapper(), GetCalendarWrapper()); + } + else + { + if ( maLastDate.GetDate() ) + SetDate( maLastDate ); + else if ( !IsEmptyFieldValueEnabled() ) + SetDate( Date( Date::SYSTEM ) ); + else + { + ImplSetText( OUString() ); + SetEmptyFieldValueData( true ); + } + } +} + +void DateFormatter::ExpandCentury( Date& rDate ) +{ + ExpandCentury(rDate, officecfg::Office::Common::DateFormat::TwoDigitYear::get()); +} + +void DateFormatter::ExpandCentury( Date& rDate, sal_uInt16 nTwoDigitYearStart ) +{ + sal_Int16 nDateYear = rDate.GetYear(); + if ( 0 <= nDateYear && nDateYear < 100 ) + { + sal_uInt16 nCentury = nTwoDigitYearStart / 100; + if ( nDateYear < (nTwoDigitYearStart % 100) ) + nCentury++; + rDate.SetYear( nDateYear + (nCentury*100) ); + } +} + +DateField::DateField( vcl::Window* pParent, WinBits nWinStyle ) : + SpinField( pParent, nWinStyle ), + DateFormatter(this), + maFirst( GetMin() ), + maLast( GetMax() ) +{ + SetText( ImplGetLocaleDataWrapper().getDate( ImplGetFieldDate() ) ); + Reformat(); + ResetLastDate(); +} + +void DateField::dispose() +{ + ClearField(); + SpinField::dispose(); +} + +bool DateField::PreNotify( NotifyEvent& rNEvt ) +{ + if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && IsStrictFormat() && + ( GetExtDateFormat() != ExtDateFieldFormat::SystemLong ) && + !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() ) + { + if ( ImplDateProcessKeyInput( *rNEvt.GetKeyEvent(), GetExtDateFormat( true ), ImplGetLocaleDataWrapper() ) ) + return true; + } + + return SpinField::PreNotify( rNEvt ); +} + +bool DateField::EventNotify( NotifyEvent& rNEvt ) +{ + if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS ) + MarkToBeReformatted( false ); + else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS ) + { + if ( MustBeReformatted() ) + { + // !!! We should find out why dates are treated differently than other fields (see + // also bug: 52384) + + bool bTextLen = !GetText().isEmpty(); + if ( bTextLen || !IsEmptyFieldValueEnabled() ) + { + if ( !ImplAllowMalformedInput() ) + Reformat(); + else + { + Date aDate( 0, 0, 0 ); + if (TextToDate(GetText(), aDate, GetExtDateFormat(true), ImplGetLocaleDataWrapper(), GetCalendarWrapper())) + // even with strict text analysis, our text is a valid date -> do a complete + // reformat + Reformat(); + } + } + else + { + ResetLastDate(); + SetEmptyFieldValueData( true ); + } + } + } + + return SpinField::EventNotify( rNEvt ); +} + +void DateField::DataChanged( const DataChangedEvent& rDCEvt ) +{ + SpinField::DataChanged( rDCEvt ); + + if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & (AllSettingsFlags::LOCALE|AllSettingsFlags::MISC)) ) + { + if (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) + ImplResetLocaleDataWrapper(); + ReformatAll(); + } +} + +void DateField::Modify() +{ + MarkToBeReformatted( true ); + SpinField::Modify(); +} + +void DateField::Up() +{ + ImplDateSpinArea( true ); + SpinField::Up(); +} + +void DateField::Down() +{ + ImplDateSpinArea( false ); + SpinField::Down(); +} + +void DateField::First() +{ + ImplNewFieldValue( maFirst ); + SpinField::First(); +} + +void DateField::Last() +{ + ImplNewFieldValue( maLast ); + SpinField::Last(); +} + +DateBox::DateBox(vcl::Window* pParent, WinBits nWinStyle) + : ComboBox( pParent, nWinStyle ) + , DateFormatter(this) +{ + SetText( ImplGetLocaleDataWrapper().getDate( ImplGetFieldDate() ) ); + Reformat(); +} + +void DateBox::dispose() +{ + ClearField(); + ComboBox::dispose(); +} + +bool DateBox::PreNotify( NotifyEvent& rNEvt ) +{ + if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && IsStrictFormat() && + ( GetExtDateFormat() != ExtDateFieldFormat::SystemLong ) && + !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() ) + { + if ( ImplDateProcessKeyInput( *rNEvt.GetKeyEvent(), GetExtDateFormat( true ), ImplGetLocaleDataWrapper() ) ) + return true; + } + + return ComboBox::PreNotify( rNEvt ); +} + +void DateBox::DataChanged( const DataChangedEvent& rDCEvt ) +{ + ComboBox::DataChanged( rDCEvt ); + + if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) ) + { + ImplResetLocaleDataWrapper(); + ReformatAll(); + } +} + +bool DateBox::EventNotify( NotifyEvent& rNEvt ) +{ + if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS ) + MarkToBeReformatted( false ); + else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS ) + { + if ( MustBeReformatted() ) + { + bool bTextLen = !GetText().isEmpty(); + if ( bTextLen || !IsEmptyFieldValueEnabled() ) + Reformat(); + else + { + ResetLastDate(); + SetEmptyFieldValueData( true ); + } + } + } + + return ComboBox::EventNotify( rNEvt ); +} + +void DateBox::Modify() +{ + MarkToBeReformatted( true ); + ComboBox::Modify(); +} + +void DateBox::ReformatAll() +{ + OUString aStr; + SetUpdateMode( false ); + const sal_Int32 nEntryCount = GetEntryCount(); + for ( sal_Int32 i=0; i < nEntryCount; ++i ) + { + ImplDateReformat( GetEntry( i ), aStr ); + RemoveEntryAt(i); + InsertEntry( aStr, i ); + } + DateFormatter::Reformat(); + SetUpdateMode( true ); +} + +namespace weld +{ + CalendarWrapper& DateFormatter::GetCalendarWrapper() const + { + if (!m_xCalendarWrapper) + { + m_xCalendarWrapper.reset(new CalendarWrapper(comphelper::getProcessComponentContext())); + m_xCalendarWrapper->loadDefaultCalendar(Application::GetSettings().GetLanguageTag().getLocale()); + } + return *m_xCalendarWrapper; + } + + void DateFormatter::SetShowDateCentury(bool bShowDateCentury) + { + m_eFormat = ChangeDateCentury(m_eFormat, bShowDateCentury); + + ReFormat(); + } + + void DateFormatter::SetDate(const Date& rDate) + { + auto nDate = rDate.GetDate(); + bool bForceOutput = GetEntryText().isEmpty() && rDate == GetDate(); + if (bForceOutput) + { + ImplSetValue(nDate, true); + return; + } + SetValue(nDate); + } + + Date DateFormatter::GetDate() + { + return Date(GetValue()); + } + + void DateFormatter::SetMin(const Date& rNewMin) + { + SetMinValue(rNewMin.GetDate()); + } + + void DateFormatter::SetMax(const Date& rNewMax) + { + SetMaxValue(rNewMax.GetDate()); + } + + OUString DateFormatter::FormatNumber(int nValue) const + { + const LocaleDataWrapper& rLocaleData = Application::GetSettings().GetLocaleDataWrapper(); + return ::DateFormatter::FormatDate(Date(nValue), m_eFormat, rLocaleData, m_aStaticFormatter); + } + + IMPL_LINK_NOARG(DateFormatter, FormatOutputHdl, LinkParamNone*, bool) + { + OUString sText = FormatNumber(GetValue()); + ImplSetTextImpl(sText, nullptr); + return true; + } + + IMPL_LINK(DateFormatter, ParseInputHdl, sal_Int64*, result, TriState) + { + const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper(); + + Date aResult(Date::EMPTY); + bool bRet = ::DateFormatter::TextToDate(GetEntryText(), aResult, ResolveSystemFormat(m_eFormat, rLocaleDataWrapper), + rLocaleDataWrapper, GetCalendarWrapper()); + if (bRet) + *result = aResult.GetDate(); + + return bRet ? TRISTATE_TRUE : TRISTATE_FALSE; + } +} + +static bool ImplTimeProcessKeyInput( const KeyEvent& rKEvt, + bool bStrictFormat, bool bDuration, + TimeFieldFormat eFormat, + const LocaleDataWrapper& rLocaleDataWrapper ) +{ + sal_Unicode cChar = rKEvt.GetCharCode(); + + if ( !bStrictFormat ) + return false; + else + { + sal_uInt16 nGroup = rKEvt.GetKeyCode().GetGroup(); + if ( (nGroup == KEYGROUP_FKEYS) || (nGroup == KEYGROUP_CURSOR) || + (nGroup == KEYGROUP_MISC) || + ((cChar >= '0') && (cChar <= '9')) || + rLocaleDataWrapper.getTimeSep() == OUStringChar(cChar) || + (rLocaleDataWrapper.getTimeAM().indexOf(cChar) != -1) || + (rLocaleDataWrapper.getTimePM().indexOf(cChar) != -1) || + // Accept AM/PM: + (cChar == 'a') || (cChar == 'A') || (cChar == 'm') || (cChar == 'M') || (cChar == 'p') || (cChar == 'P') || + ((eFormat == TimeFieldFormat::F_SEC_CS) && rLocaleDataWrapper.getTime100SecSep() == OUStringChar(cChar)) || + (bDuration && (cChar == '-')) ) + return false; + else + return true; + } +} + +static bool ImplIsOnlyDigits( const OUString& _rStr ) +{ + const sal_Unicode* _pChr = _rStr.getStr(); + for ( sal_Int32 i = 0; i < _rStr.getLength(); ++i, ++_pChr ) + { + if ( *_pChr < '0' || *_pChr > '9' ) + return false; + } + return true; +} + +static bool ImplIsValidTimePortion( bool _bSkipInvalidCharacters, const OUString& _rStr ) +{ + if ( !_bSkipInvalidCharacters ) + { + if ( ( _rStr.getLength() > 2 ) || _rStr.isEmpty() || !ImplIsOnlyDigits( _rStr ) ) + return false; + } + return true; +} + +static bool ImplCutTimePortion( OUStringBuffer& _rStr, sal_Int32 _nSepPos, bool _bSkipInvalidCharacters, short* _pPortion ) +{ + OUString sPortion(_rStr.getStr(), _nSepPos ); + + if (_nSepPos < _rStr.getLength()) + _rStr.remove(0, _nSepPos + 1); + else + _rStr.truncate(); + + if ( !ImplIsValidTimePortion( _bSkipInvalidCharacters, sPortion ) ) + return false; + *_pPortion = static_cast<short>(sPortion.toInt32()); + return true; +} + +bool TimeFormatter::TextToTime(std::u16string_view rStr, tools::Time& rTime, + TimeFieldFormat eFormat, + bool bDuration, const LocaleDataWrapper& rLocaleDataWrapper, bool _bSkipInvalidCharacters) +{ + OUStringBuffer aStr(rStr); + short nHour = 0; + short nMinute = 0; + short nSecond = 0; + sal_Int64 nNanoSec = 0; + tools::Time aTime( 0, 0, 0 ); + + if ( rStr.empty() ) + return false; + + // Search for separators + if (!rLocaleDataWrapper.getTimeSep().isEmpty()) + { + OUStringBuffer aSepStr(",.;:/"); + if ( !bDuration ) + aSepStr.append('-'); + + // Replace characters above by the separator character + for (sal_Int32 i = 0; i < aSepStr.getLength(); ++i) + { + if (rLocaleDataWrapper.getTimeSep() == OUStringChar(aSepStr[i])) + continue; + for ( sal_Int32 j = 0; j < aStr.getLength(); j++ ) + { + if (aStr[j] == aSepStr[i]) + aStr[j] = rLocaleDataWrapper.getTimeSep()[0]; + } + } + } + + bool bNegative = false; + sal_Int32 nSepPos = aStr.indexOf( rLocaleDataWrapper.getTimeSep() ); + if ( aStr[0] == '-' ) + bNegative = true; + if ( eFormat != TimeFieldFormat::F_SEC_CS ) + { + if ( nSepPos < 0 ) + nSepPos = aStr.getLength(); + if ( !ImplCutTimePortion( aStr, nSepPos, _bSkipInvalidCharacters, &nHour ) ) + return false; + + nSepPos = aStr.indexOf( rLocaleDataWrapper.getTimeSep() ); + if ( !aStr.isEmpty() && aStr[0] == '-' ) + bNegative = true; + if ( nSepPos >= 0 ) + { + if ( !ImplCutTimePortion( aStr, nSepPos, _bSkipInvalidCharacters, &nMinute ) ) + return false; + + nSepPos = aStr.indexOf( rLocaleDataWrapper.getTimeSep() ); + if ( !aStr.isEmpty() && aStr[0] == '-' ) + bNegative = true; + if ( nSepPos >= 0 ) + { + if ( !ImplCutTimePortion( aStr, nSepPos, _bSkipInvalidCharacters, &nSecond ) ) + return false; + if ( !aStr.isEmpty() && aStr[0] == '-' ) + bNegative = true; + nNanoSec = o3tl::toInt64(aStr); + } + else + nSecond = static_cast<short>(o3tl::toInt32(aStr)); + } + else + nMinute = static_cast<short>(o3tl::toInt32(aStr)); + } + else if ( nSepPos < 0 ) + { + nSecond = static_cast<short>(o3tl::toInt32(aStr)); + nMinute += nSecond / 60; + nSecond %= 60; + nHour += nMinute / 60; + nMinute %= 60; + } + else + { + nSecond = static_cast<short>(o3tl::toInt32(aStr.subView( 0, nSepPos ))); + aStr.remove( 0, nSepPos+1 ); + + nSepPos = aStr.indexOf( rLocaleDataWrapper.getTimeSep() ); + if ( !aStr.isEmpty() && aStr[0] == '-' ) + bNegative = true; + if ( nSepPos >= 0 ) + { + nMinute = nSecond; + nSecond = static_cast<short>(o3tl::toInt32(aStr.subView( 0, nSepPos ))); + aStr.remove( 0, nSepPos+1 ); + + nSepPos = aStr.indexOf( rLocaleDataWrapper.getTimeSep() ); + if ( !aStr.isEmpty() && aStr[0] == '-' ) + bNegative = true; + if ( nSepPos >= 0 ) + { + nHour = nMinute; + nMinute = nSecond; + nSecond = static_cast<short>(o3tl::toInt32(aStr.subView( 0, nSepPos ))); + aStr.remove( 0, nSepPos+1 ); + } + else + { + nHour += nMinute / 60; + nMinute %= 60; + } + } + else + { + nMinute += nSecond / 60; + nSecond %= 60; + nHour += nMinute / 60; + nMinute %= 60; + } + nNanoSec = o3tl::toInt64(aStr); + } + + if ( nNanoSec ) + { + assert(aStr.getLength() >= 1); + + sal_Int32 nLen = 1; // at least one digit, otherwise nNanoSec==0 + + while ( aStr.getLength() > nLen && aStr[nLen] >= '0' && aStr[nLen] <= '9' ) + nLen++; + + while ( nLen < 9) + { + nNanoSec *= 10; + ++nLen; + } + while ( nLen > 9 ) + { + // round if negative? + nNanoSec = (nNanoSec + 5) / 10; + --nLen; + } + } + + assert(nNanoSec > -1000000000 && nNanoSec < 1000000000); + if ( (nMinute > 59) || (nSecond > 59) || (nNanoSec > 1000000000) ) + return false; + + if ( eFormat == TimeFieldFormat::F_NONE ) + nSecond = nNanoSec = 0; + else if ( eFormat == TimeFieldFormat::F_SEC ) + nNanoSec = 0; + + if ( !bDuration ) + { + if ( bNegative || (nHour < 0) || (nMinute < 0) || + (nSecond < 0) || (nNanoSec < 0) ) + return false; + + OUString aUpperCaseStr = aStr.toString().toAsciiUpperCase(); + OUString aAMlocalised(rLocaleDataWrapper.getTimeAM().toAsciiUpperCase()); + OUString aPMlocalised(rLocaleDataWrapper.getTimePM().toAsciiUpperCase()); + + if ( (nHour < 12) && ( ( aUpperCaseStr.indexOf( "PM" ) >= 0 ) || ( aUpperCaseStr.indexOf( aPMlocalised ) >= 0 ) ) ) + nHour += 12; + + if ( (nHour == 12) && ( ( aUpperCaseStr.indexOf( "AM" ) >= 0 ) || ( aUpperCaseStr.indexOf( aAMlocalised ) >= 0 ) ) ) + nHour = 0; + + aTime = tools::Time( static_cast<sal_uInt16>(nHour), static_cast<sal_uInt16>(nMinute), static_cast<sal_uInt16>(nSecond), + static_cast<sal_uInt32>(nNanoSec) ); + } + else + { + assert( !bNegative || (nHour < 0) || (nMinute < 0) || + (nSecond < 0) || (nNanoSec < 0) ); + if ( bNegative || (nHour < 0) || (nMinute < 0) || + (nSecond < 0) || (nNanoSec < 0) ) + { + // LEM TODO: this looks weird... I think buggy when parsing "05:-02:18" + bNegative = true; + nHour = nHour < 0 ? -nHour : nHour; + nMinute = nMinute < 0 ? -nMinute : nMinute; + nSecond = nSecond < 0 ? -nSecond : nSecond; + nNanoSec = nNanoSec < 0 ? -nNanoSec : nNanoSec; + } + + aTime = tools::Time( static_cast<sal_uInt16>(nHour), static_cast<sal_uInt16>(nMinute), static_cast<sal_uInt16>(nSecond), + static_cast<sal_uInt32>(nNanoSec) ); + if ( bNegative ) + aTime = -aTime; + } + + rTime = aTime; + + return true; +} + +void TimeFormatter::ImplTimeReformat( std::u16string_view rStr, OUString& rOutStr ) +{ + tools::Time aTime( 0, 0, 0 ); + if ( !TextToTime( rStr, aTime, GetFormat(), IsDuration(), ImplGetLocaleDataWrapper() ) ) + return; + + tools::Time aTempTime = aTime; + if ( aTempTime > GetMax() ) + aTempTime = GetMax() ; + else if ( aTempTime < GetMin() ) + aTempTime = GetMin(); + + bool bSecond = false; + bool b100Sec = false; + if ( meFormat != TimeFieldFormat::F_NONE ) + bSecond = true; + + if ( meFormat == TimeFieldFormat::F_SEC_CS ) + { + sal_uLong n = aTempTime.GetHour() * 3600L; + n += aTempTime.GetMin() * 60L; + n += aTempTime.GetSec(); + rOutStr = OUString::number( n ); + rOutStr += ImplGetLocaleDataWrapper().getTime100SecSep(); + std::ostringstream ostr; + ostr.fill('0'); + ostr.width(9); + ostr << aTempTime.GetNanoSec(); + rOutStr += OUString::createFromAscii(ostr.str().c_str()); + } + else if ( mbDuration ) + rOutStr = ImplGetLocaleDataWrapper().getDuration( aTempTime, bSecond, b100Sec ); + else + { + rOutStr = ImplGetLocaleDataWrapper().getTime( aTempTime, bSecond, b100Sec ); + if ( GetTimeFormat() == TimeFormat::Hour12 ) + { + if ( aTempTime.GetHour() > 12 ) + { + tools::Time aT( aTempTime ); + aT.SetHour( aT.GetHour() % 12 ); + rOutStr = ImplGetLocaleDataWrapper().getTime( aT, bSecond, b100Sec ); + } + // Don't use LocaleDataWrapper, we want AM/PM + if ( aTempTime.GetHour() < 12 ) + rOutStr += "AM"; // ImplGetLocaleDataWrapper().getTimeAM(); + else + rOutStr += "PM"; // ImplGetLocaleDataWrapper().getTimePM(); + } + } +} + +bool TimeFormatter::ImplAllowMalformedInput() const +{ + return !IsEnforceValidValue(); +} + +int TimeFormatter::GetTimeArea(TimeFieldFormat eFormat, std::u16string_view rText, int nCursor, + const LocaleDataWrapper& rLocaleDataWrapper) +{ + int nTimeArea = 0; + + // Area search + if (eFormat != TimeFieldFormat::F_SEC_CS) + { + //Which area is the cursor in of HH:MM:SS.TT + for ( size_t i = 1, nPos = 0; i <= 4; i++ ) + { + size_t nPos1 = rText.find(rLocaleDataWrapper.getTimeSep(), nPos); + size_t nPos2 = rText.find(rLocaleDataWrapper.getTime100SecSep(), nPos); + //which ever comes first, bearing in mind that one might not be there + if (nPos1 != std::u16string_view::npos && nPos2 != std::u16string_view::npos) + nPos = std::min(nPos1, nPos2); + else if (nPos1 != std::u16string_view::npos) + nPos = nPos1; + else + nPos = nPos2; + if (nPos == std::u16string_view::npos || static_cast<sal_Int32>(nPos) >= nCursor) + { + nTimeArea = i; + break; + } + else + nPos++; + } + } + else + { + size_t nPos = rText.find(rLocaleDataWrapper.getTime100SecSep()); + if (nPos == std::u16string_view::npos || static_cast<sal_Int32>(nPos) >= nCursor) + nTimeArea = 3; + else + nTimeArea = 4; + } + + return nTimeArea; +} + +tools::Time TimeFormatter::SpinTime(bool bUp, const tools::Time& rTime, TimeFieldFormat eFormat, + bool bDuration, std::u16string_view rText, int nCursor, + const LocaleDataWrapper& rLocaleDataWrapper) +{ + tools::Time aTime(rTime); + + int nTimeArea = GetTimeArea(eFormat, rText, nCursor, rLocaleDataWrapper); + + if ( nTimeArea ) + { + tools::Time aAddTime( 0, 0, 0 ); + if ( nTimeArea == 1 ) + aAddTime = tools::Time( 1, 0 ); + else if ( nTimeArea == 2 ) + aAddTime = tools::Time( 0, 1 ); + else if ( nTimeArea == 3 ) + aAddTime = tools::Time( 0, 0, 1 ); + else if ( nTimeArea == 4 ) + aAddTime = tools::Time( 0, 0, 0, 1 ); + + if ( !bUp ) + aAddTime = -aAddTime; + + aTime += aAddTime; + if (!bDuration) + { + tools::Time aAbsMaxTime( 23, 59, 59, 999999999 ); + if ( aTime > aAbsMaxTime ) + aTime = aAbsMaxTime; + tools::Time aAbsMinTime( 0, 0 ); + if ( aTime < aAbsMinTime ) + aTime = aAbsMinTime; + } + } + + return aTime; +} + +void TimeField::ImplTimeSpinArea( bool bUp ) +{ + if ( GetField() ) + { + tools::Time aTime( GetTime() ); + OUString aText( GetText() ); + Selection aSelection( GetField()->GetSelection() ); + + aTime = TimeFormatter::SpinTime(bUp, aTime, GetFormat(), IsDuration(), aText, aSelection.Max(), ImplGetLocaleDataWrapper()); + + ImplNewFieldValue( aTime ); + } +} + +TimeFormatter::TimeFormatter(Edit* pEdit) + : FormatterBase(pEdit) + , maLastTime(0, 0) + , maMin(0, 0) + , maMax(23, 59, 59, 999999999) + , meFormat(TimeFieldFormat::F_NONE) + , mnTimeFormat(TimeFormat::Hour24) // Should become an ExtTimeFieldFormat in next implementation, merge with mbDuration and meFormat + , mbDuration(false) + , mbEnforceValidValue(true) + , maFieldTime(0, 0) +{ +} + +TimeFormatter::~TimeFormatter() +{ +} + +void TimeFormatter::ReformatAll() +{ + Reformat(); +} + +void TimeFormatter::SetMin( const tools::Time& rNewMin ) +{ + maMin = rNewMin; + if ( !IsEmptyFieldValue() ) + ReformatAll(); +} + +void TimeFormatter::SetMax( const tools::Time& rNewMax ) +{ + maMax = rNewMax; + if ( !IsEmptyFieldValue() ) + ReformatAll(); +} + +void TimeFormatter::SetTimeFormat( TimeFormat eNewFormat ) +{ + mnTimeFormat = eNewFormat; +} + + +void TimeFormatter::SetFormat( TimeFieldFormat eNewFormat ) +{ + meFormat = eNewFormat; + ReformatAll(); +} + +void TimeFormatter::SetDuration( bool bNewDuration ) +{ + mbDuration = bNewDuration; + ReformatAll(); +} + +void TimeFormatter::SetTime( const tools::Time& rNewTime ) +{ + SetUserTime( rNewTime ); + maFieldTime = maLastTime; + SetEmptyFieldValueData( false ); +} + +void TimeFormatter::ImplNewFieldValue( const tools::Time& rTime ) +{ + if ( !GetField() ) + return; + + Selection aSelection = GetField()->GetSelection(); + aSelection.Justify(); + OUString aText = GetField()->GetText(); + + // If selected until the end then keep it that way + if ( static_cast<sal_Int32>(aSelection.Max()) == aText.getLength() ) + { + if ( !aSelection.Len() ) + aSelection.Min() = SELECTION_MAX; + aSelection.Max() = SELECTION_MAX; + } + + tools::Time aOldLastTime = maLastTime; + ImplSetUserTime( rTime, &aSelection ); + maLastTime = aOldLastTime; + + // Modify at Edit is only set at KeyInput + if ( GetField()->GetText() != aText ) + { + GetField()->SetModifyFlag(); + GetField()->Modify(); + } +} + +OUString TimeFormatter::FormatTime(const tools::Time& rNewTime, TimeFieldFormat eFormat, TimeFormat eHourFormat, bool bDuration, const LocaleDataWrapper& rLocaleData) +{ + OUString aStr; + bool bSec = false; + bool b100Sec = false; + if ( eFormat != TimeFieldFormat::F_NONE ) + bSec = true; + if ( eFormat == TimeFieldFormat::F_SEC_CS ) + b100Sec = true; + if ( eFormat == TimeFieldFormat::F_SEC_CS ) + { + sal_uLong n = rNewTime.GetHour() * 3600L; + n += rNewTime.GetMin() * 60L; + n += rNewTime.GetSec(); + aStr = OUString::number( n ) + rLocaleData.getTime100SecSep(); + std::ostringstream ostr; + ostr.fill('0'); + ostr.width(9); + ostr << rNewTime.GetNanoSec(); + aStr += OUString::createFromAscii(ostr.str().c_str()); + } + else if ( bDuration ) + { + aStr = rLocaleData.getDuration( rNewTime, bSec, b100Sec ); + } + else + { + aStr = rLocaleData.getTime( rNewTime, bSec, b100Sec ); + if ( eHourFormat == TimeFormat::Hour12 ) + { + if ( rNewTime.GetHour() > 12 ) + { + tools::Time aT( rNewTime ); + aT.SetHour( aT.GetHour() % 12 ); + aStr = rLocaleData.getTime( aT, bSec, b100Sec ); + } + // Don't use LocaleDataWrapper, we want AM/PM + if ( rNewTime.GetHour() < 12 ) + aStr += "AM"; // rLocaleData.getTimeAM(); + else + aStr += "PM"; // rLocaleData.getTimePM(); + } + } + + return aStr; +} + +void TimeFormatter::ImplSetUserTime( const tools::Time& rNewTime, Selection const * pNewSelection ) +{ + tools::Time aNewTime = rNewTime; + if ( aNewTime > GetMax() ) + aNewTime = GetMax(); + else if ( aNewTime < GetMin() ) + aNewTime = GetMin(); + maLastTime = aNewTime; + + if ( GetField() ) + { + OUString aStr = TimeFormatter::FormatTime(aNewTime, meFormat, GetTimeFormat(), mbDuration, ImplGetLocaleDataWrapper()); + ImplSetText( aStr, pNewSelection ); + } +} + +void TimeFormatter::SetUserTime( const tools::Time& rNewTime ) +{ + ImplSetUserTime( rNewTime ); +} + +tools::Time TimeFormatter::GetTime() const +{ + tools::Time aTime( 0, 0, 0 ); + + if ( GetField() ) + { + bool bAllowMalformed = ImplAllowMalformedInput(); + if ( TextToTime( GetField()->GetText(), aTime, GetFormat(), IsDuration(), ImplGetLocaleDataWrapper(), !bAllowMalformed ) ) + { + if ( aTime > GetMax() ) + aTime = GetMax(); + else if ( aTime < GetMin() ) + aTime = GetMin(); + } + else + { + if ( bAllowMalformed ) + aTime = tools::Time( 99, 99, 99 ); // set invalid time + else + aTime = maLastTime; + } + } + + return aTime; +} + +void TimeFormatter::Reformat() +{ + if ( !GetField() ) + return; + + if ( GetField()->GetText().isEmpty() && ImplGetEmptyFieldValue() ) + return; + + OUString aStr; + ImplTimeReformat( GetField()->GetText(), aStr ); + + if ( !aStr.isEmpty() ) + { + ImplSetText( aStr ); + (void)TextToTime(aStr, maLastTime, GetFormat(), IsDuration(), ImplGetLocaleDataWrapper()); + } + else + SetTime( maLastTime ); +} + +TimeField::TimeField( vcl::Window* pParent, WinBits nWinStyle ) : + SpinField( pParent, nWinStyle ), + TimeFormatter(this), + maFirst( GetMin() ), + maLast( GetMax() ) +{ + SetText( ImplGetLocaleDataWrapper().getTime( maFieldTime, false ) ); + Reformat(); +} + +void TimeField::dispose() +{ + ClearField(); + SpinField::dispose(); +} + +bool TimeField::PreNotify( NotifyEvent& rNEvt ) +{ + if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() ) + { + if ( ImplTimeProcessKeyInput( *rNEvt.GetKeyEvent(), IsStrictFormat(), IsDuration(), GetFormat(), ImplGetLocaleDataWrapper() ) ) + return true; + } + + return SpinField::PreNotify( rNEvt ); +} + +bool TimeField::EventNotify( NotifyEvent& rNEvt ) +{ + if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS ) + MarkToBeReformatted( false ); + else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS ) + { + if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) ) + { + if ( !ImplAllowMalformedInput() ) + Reformat(); + else + { + tools::Time aTime( 0, 0, 0 ); + if ( TextToTime( GetText(), aTime, GetFormat(), IsDuration(), ImplGetLocaleDataWrapper(), false ) ) + // even with strict text analysis, our text is a valid time -> do a complete + // reformat + Reformat(); + } + } + } + + return SpinField::EventNotify( rNEvt ); +} + +void TimeField::DataChanged( const DataChangedEvent& rDCEvt ) +{ + SpinField::DataChanged( rDCEvt ); + + if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) ) + { + ImplResetLocaleDataWrapper(); + ReformatAll(); + } +} + +void TimeField::Modify() +{ + MarkToBeReformatted( true ); + SpinField::Modify(); +} + +void TimeField::Up() +{ + ImplTimeSpinArea( true ); + SpinField::Up(); +} + +void TimeField::Down() +{ + ImplTimeSpinArea( false ); + SpinField::Down(); +} + +void TimeField::First() +{ + ImplNewFieldValue( maFirst ); + SpinField::First(); +} + +void TimeField::Last() +{ + ImplNewFieldValue( maLast ); + SpinField::Last(); +} + +void TimeField::SetExtFormat( ExtTimeFieldFormat eFormat ) +{ + switch ( eFormat ) + { + case ExtTimeFieldFormat::Short24H: + { + SetTimeFormat( TimeFormat::Hour24 ); + SetDuration( false ); + SetFormat( TimeFieldFormat::F_NONE ); + } + break; + case ExtTimeFieldFormat::Long24H: + { + SetTimeFormat( TimeFormat::Hour24 ); + SetDuration( false ); + SetFormat( TimeFieldFormat::F_SEC ); + } + break; + case ExtTimeFieldFormat::Short12H: + { + SetTimeFormat( TimeFormat::Hour12 ); + SetDuration( false ); + SetFormat( TimeFieldFormat::F_NONE ); + } + break; + case ExtTimeFieldFormat::Long12H: + { + SetTimeFormat( TimeFormat::Hour12 ); + SetDuration( false ); + SetFormat( TimeFieldFormat::F_SEC ); + } + break; + case ExtTimeFieldFormat::ShortDuration: + { + SetDuration( true ); + SetFormat( TimeFieldFormat::F_NONE ); + } + break; + case ExtTimeFieldFormat::LongDuration: + { + SetDuration( true ); + SetFormat( TimeFieldFormat::F_SEC ); + } + break; + default: OSL_FAIL( "ExtTimeFieldFormat unknown!" ); + } + + if ( GetField() && !GetField()->GetText().isEmpty() ) + SetUserTime( GetTime() ); + ReformatAll(); +} + +TimeBox::TimeBox(vcl::Window* pParent, WinBits nWinStyle) + : ComboBox(pParent, nWinStyle) + , TimeFormatter(this) +{ + SetText( ImplGetLocaleDataWrapper().getTime( maFieldTime, false ) ); + Reformat(); +} + +void TimeBox::dispose() +{ + ClearField(); + ComboBox::dispose(); +} + +bool TimeBox::PreNotify( NotifyEvent& rNEvt ) +{ + if ( (rNEvt.GetType() == MouseNotifyEvent::KEYINPUT) && !rNEvt.GetKeyEvent()->GetKeyCode().IsMod2() ) + { + if ( ImplTimeProcessKeyInput( *rNEvt.GetKeyEvent(), IsStrictFormat(), IsDuration(), GetFormat(), ImplGetLocaleDataWrapper() ) ) + return true; + } + + return ComboBox::PreNotify( rNEvt ); +} + +bool TimeBox::EventNotify( NotifyEvent& rNEvt ) +{ + if ( rNEvt.GetType() == MouseNotifyEvent::GETFOCUS ) + MarkToBeReformatted( false ); + else if ( rNEvt.GetType() == MouseNotifyEvent::LOSEFOCUS ) + { + if ( MustBeReformatted() && (!GetText().isEmpty() || !IsEmptyFieldValueEnabled()) ) + Reformat(); + } + + return ComboBox::EventNotify( rNEvt ); +} + +void TimeBox::DataChanged( const DataChangedEvent& rDCEvt ) +{ + ComboBox::DataChanged( rDCEvt ); + + if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && (rDCEvt.GetFlags() & AllSettingsFlags::LOCALE) ) + { + ImplResetLocaleDataWrapper(); + ReformatAll(); + } +} + +void TimeBox::Modify() +{ + MarkToBeReformatted( true ); + ComboBox::Modify(); +} + +void TimeBox::ReformatAll() +{ + OUString aStr; + SetUpdateMode( false ); + const sal_Int32 nEntryCount = GetEntryCount(); + for ( sal_Int32 i=0; i < nEntryCount; ++i ) + { + ImplTimeReformat( GetEntry( i ), aStr ); + RemoveEntryAt(i); + InsertEntry( aStr, i ); + } + TimeFormatter::Reformat(); + SetUpdateMode( true ); +} + +namespace weld +{ + tools::Time TimeFormatter::ConvertValue(int nValue) + { + tools::Time aTime(0); + aTime.MakeTimeFromMS(nValue); + return aTime; + } + + int TimeFormatter::ConvertValue(const tools::Time& rTime) + { + return rTime.GetMSFromTime(); + } + + void TimeFormatter::SetTime(const tools::Time& rTime) + { + auto nTime = ConvertValue(rTime); + bool bForceOutput = GetEntryText().isEmpty() && rTime == GetTime(); + if (bForceOutput) + { + ImplSetValue(nTime, true); + return; + } + SetValue(nTime); + } + + tools::Time TimeFormatter::GetTime() + { + return ConvertValue(GetValue()); + } + + void TimeFormatter::SetMin(const tools::Time& rNewMin) + { + SetMinValue(ConvertValue(rNewMin)); + } + + void TimeFormatter::SetMax(const tools::Time& rNewMax) + { + SetMaxValue(ConvertValue(rNewMax)); + } + + OUString TimeFormatter::FormatNumber(int nValue) const + { + const LocaleDataWrapper& rLocaleData = Application::GetSettings().GetLocaleDataWrapper(); + return ::TimeFormatter::FormatTime(ConvertValue(nValue), m_eFormat, m_eTimeFormat, m_bDuration, rLocaleData); + } + + IMPL_LINK_NOARG(TimeFormatter, FormatOutputHdl, LinkParamNone*, bool) + { + OUString sText = FormatNumber(GetValue()); + ImplSetTextImpl(sText, nullptr); + return true; + } + + IMPL_LINK(TimeFormatter, ParseInputHdl, sal_Int64*, result, TriState) + { + const LocaleDataWrapper& rLocaleDataWrapper = Application::GetSettings().GetLocaleDataWrapper(); + + tools::Time aResult(0); + bool bRet = ::TimeFormatter::TextToTime(GetEntryText(), aResult, m_eFormat, m_bDuration, rLocaleDataWrapper); + if (bRet) + *result = ConvertValue(aResult); + + return bRet ? TRISTATE_TRUE : TRISTATE_FALSE; + } + + IMPL_LINK(TimeFormatter, CursorChangedHdl, weld::Entry&, rEntry, void) + { + int nStartPos, nEndPos; + rEntry.get_selection_bounds(nStartPos, nEndPos); + + const LocaleDataWrapper& rLocaleData = Application::GetSettings().GetLocaleDataWrapper(); + const int nTimeArea = ::TimeFormatter::GetTimeArea(m_eFormat, GetEntryText(), nEndPos, rLocaleData); + + int nIncrements = 1; + + if (nTimeArea == 1) + nIncrements = 1000 * 60 * 60; + else if (nTimeArea == 2) + nIncrements = 1000 * 60; + else if (nTimeArea == 3) + nIncrements = 1000; + + SetSpinSize(nIncrements); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |