2938 lines
94 KiB
C++
2938 lines
94 KiB
C++
/* -*- 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 <utility>
|
|
#include <vcl/builder.hxx>
|
|
#include <vcl/event.hxx>
|
|
#include <vcl/cursor.hxx>
|
|
#include <vcl/menu.hxx>
|
|
#include <vcl/toolkit/edit.hxx>
|
|
#include <vcl/weld.hxx>
|
|
#include <vcl/specialchars.hxx>
|
|
#include <vcl/svapp.hxx>
|
|
#include <vcl/settings.hxx>
|
|
#include <vcl/transfer.hxx>
|
|
#include <vcl/uitest/uiobject.hxx>
|
|
#include <vcl/ptrstyle.hxx>
|
|
|
|
#include <window.h>
|
|
#include <svdata.hxx>
|
|
#include <strings.hrc>
|
|
|
|
#include <com/sun/star/i18n/BreakIterator.hpp>
|
|
#include <com/sun/star/i18n/CharacterIteratorMode.hpp>
|
|
#include <com/sun/star/i18n/WordType.hpp>
|
|
#include <com/sun/star/datatransfer/XTransferable.hpp>
|
|
#include <com/sun/star/datatransfer/clipboard/XClipboard.hpp>
|
|
|
|
#include <com/sun/star/datatransfer/dnd/DNDConstants.hpp>
|
|
#include <com/sun/star/datatransfer/dnd/XDragGestureRecognizer.hpp>
|
|
#include <com/sun/star/datatransfer/dnd/XDropTarget.hpp>
|
|
|
|
#include <com/sun/star/i18n/InputSequenceChecker.hpp>
|
|
#include <com/sun/star/i18n/InputSequenceCheckMode.hpp>
|
|
#include <com/sun/star/i18n/ScriptType.hpp>
|
|
|
|
#include <com/sun/star/uno/Any.hxx>
|
|
|
|
#include <comphelper/processfactory.hxx>
|
|
#include <comphelper/string.hxx>
|
|
|
|
#include <sot/exchange.hxx>
|
|
#include <sot/formats.hxx>
|
|
#include <sal/macros.h>
|
|
#include <sal/log.hxx>
|
|
|
|
#include <i18nlangtag/languagetag.hxx>
|
|
#include <vcl/unohelp2.hxx>
|
|
#include <o3tl/safeint.hxx>
|
|
#include <o3tl/string_view.hxx>
|
|
#include <officecfg/Office/Common.hxx>
|
|
#include <tools/json_writer.hxx>
|
|
|
|
#include <algorithm>
|
|
#include <memory>
|
|
#include <string_view>
|
|
|
|
using namespace ::com::sun::star;
|
|
using namespace ::com::sun::star::uno;
|
|
using namespace ::com::sun::star::lang;
|
|
|
|
// - Redo
|
|
// - if Tracking-Cancel recreate DefaultSelection
|
|
|
|
static FncGetSpecialChars pImplFncGetSpecialChars = nullptr;
|
|
|
|
#define EDIT_ALIGN_LEFT 1
|
|
#define EDIT_ALIGN_CENTER 2
|
|
#define EDIT_ALIGN_RIGHT 3
|
|
|
|
#define EDIT_DEL_LEFT 1
|
|
#define EDIT_DEL_RIGHT 2
|
|
|
|
#define EDIT_DELMODE_SIMPLE 11
|
|
#define EDIT_DELMODE_RESTOFWORD 12
|
|
#define EDIT_DELMODE_RESTOFCONTENT 13
|
|
|
|
struct DDInfo
|
|
{
|
|
vcl::Cursor aCursor;
|
|
Selection aDndStartSel;
|
|
sal_Int32 nDropPos;
|
|
bool bStarterOfDD;
|
|
bool bDroppedInMe;
|
|
bool bVisCursor;
|
|
bool bIsStringSupported;
|
|
|
|
DDInfo()
|
|
{
|
|
aCursor.SetStyle( CURSOR_SHADOW );
|
|
nDropPos = 0;
|
|
bStarterOfDD = false;
|
|
bDroppedInMe = false;
|
|
bVisCursor = false;
|
|
bIsStringSupported = false;
|
|
}
|
|
};
|
|
|
|
struct Impl_IMEInfos
|
|
{
|
|
OUString aOldTextAfterStartPos;
|
|
std::unique_ptr<ExtTextInputAttr[]>
|
|
pAttribs;
|
|
sal_Int32 nPos;
|
|
sal_Int32 nLen;
|
|
bool bCursor;
|
|
bool bWasCursorOverwrite;
|
|
|
|
Impl_IMEInfos(sal_Int32 nPos, OUString aOldTextAfterStartPos);
|
|
|
|
void CopyAttribs(const ExtTextInputAttr* pA, sal_Int32 nL);
|
|
void DestroyAttribs();
|
|
};
|
|
|
|
Impl_IMEInfos::Impl_IMEInfos(sal_Int32 nP, OUString _aOldTextAfterStartPos)
|
|
: aOldTextAfterStartPos(std::move(_aOldTextAfterStartPos)),
|
|
nPos(nP),
|
|
nLen(0),
|
|
bCursor(true),
|
|
bWasCursorOverwrite(false)
|
|
{
|
|
}
|
|
|
|
void Impl_IMEInfos::CopyAttribs(const ExtTextInputAttr* pA, sal_Int32 nL)
|
|
{
|
|
nLen = nL;
|
|
pAttribs.reset(new ExtTextInputAttr[ nL ]);
|
|
memcpy( pAttribs.get(), pA, nL*sizeof(ExtTextInputAttr) );
|
|
}
|
|
|
|
void Impl_IMEInfos::DestroyAttribs()
|
|
{
|
|
pAttribs.reset();
|
|
nLen = 0;
|
|
}
|
|
|
|
Edit::Edit( WindowType nType )
|
|
: Control( nType )
|
|
{
|
|
ImplInitEditData();
|
|
}
|
|
|
|
Edit::Edit( vcl::Window* pParent, WinBits nStyle )
|
|
: Control( WindowType::EDIT )
|
|
{
|
|
ImplInitEditData();
|
|
ImplInit( pParent, nStyle );
|
|
}
|
|
|
|
void Edit::SetWidthInChars(sal_Int32 nWidthInChars)
|
|
{
|
|
if (mnWidthInChars != nWidthInChars)
|
|
{
|
|
mnWidthInChars = nWidthInChars;
|
|
queue_resize();
|
|
}
|
|
}
|
|
|
|
void Edit::setMaxWidthChars(sal_Int32 nWidth)
|
|
{
|
|
if (nWidth != mnMaxWidthChars)
|
|
{
|
|
mnMaxWidthChars = nWidth;
|
|
queue_resize();
|
|
}
|
|
}
|
|
|
|
bool Edit::set_property(const OUString &rKey, const OUString &rValue)
|
|
{
|
|
if (rKey == "width-chars")
|
|
SetWidthInChars(rValue.toInt32());
|
|
else if (rKey == "max-width-chars")
|
|
setMaxWidthChars(rValue.toInt32());
|
|
else if (rKey == "max-length")
|
|
{
|
|
sal_Int32 nTextLen = rValue.toInt32();
|
|
SetMaxTextLen(nTextLen == 0 ? EDIT_NOLIMIT : nTextLen);
|
|
}
|
|
else if (rKey == "editable")
|
|
{
|
|
SetReadOnly(!toBool(rValue));
|
|
}
|
|
else if (rKey == "overwrite-mode")
|
|
{
|
|
SetInsertMode(!toBool(rValue));
|
|
}
|
|
else if (rKey == "visibility")
|
|
{
|
|
mbPassword = false;
|
|
if (!toBool(rValue))
|
|
mbPassword = true;
|
|
}
|
|
else if (rKey == "placeholder-text")
|
|
SetPlaceholderText(rValue);
|
|
else if (rKey == "shadow-type")
|
|
{
|
|
if (GetStyle() & WB_BORDER)
|
|
SetBorderStyle(rValue == "none" ? WindowBorderStyle::MONO : WindowBorderStyle::NORMAL);
|
|
}
|
|
else
|
|
return Control::set_property(rKey, rValue);
|
|
return true;
|
|
}
|
|
|
|
Edit::~Edit()
|
|
{
|
|
disposeOnce();
|
|
}
|
|
|
|
void Edit::dispose()
|
|
{
|
|
mpUIBuilder.reset();
|
|
mpDDInfo.reset();
|
|
|
|
vcl::Cursor* pCursor = GetCursor();
|
|
if ( pCursor )
|
|
{
|
|
SetCursor( nullptr );
|
|
delete pCursor;
|
|
}
|
|
|
|
mpIMEInfos.reset();
|
|
|
|
if ( mxDnDListener.is() )
|
|
{
|
|
if ( GetDragGestureRecognizer().is() )
|
|
{
|
|
GetDragGestureRecognizer()->removeDragGestureListener( mxDnDListener );
|
|
}
|
|
if ( GetDropTarget().is() )
|
|
{
|
|
GetDropTarget()->removeDropTargetListener( mxDnDListener );
|
|
}
|
|
|
|
mxDnDListener->disposing( lang::EventObject() ); // #95154# #96585# Empty Source means it's the Client
|
|
mxDnDListener.clear();
|
|
}
|
|
|
|
SetType(WindowType::WINDOW);
|
|
|
|
mpSubEdit.disposeAndClear();
|
|
Control::dispose();
|
|
}
|
|
|
|
void Edit::ImplInitEditData()
|
|
{
|
|
mpSubEdit = VclPtr<Edit>();
|
|
mpFilterText = nullptr;
|
|
mnXOffset = 0;
|
|
mnAlign = EDIT_ALIGN_LEFT;
|
|
mnMaxTextLen = EDIT_NOLIMIT;
|
|
mnWidthInChars = -1;
|
|
mnMaxWidthChars = -1;
|
|
mbInternModified = false;
|
|
mbReadOnly = false;
|
|
mbInsertMode = true;
|
|
mbClickedInSelection = false;
|
|
mbActivePopup = false;
|
|
mbIsSubEdit = false;
|
|
mbForceControlBackground = false;
|
|
mbPassword = false;
|
|
mpDDInfo = nullptr;
|
|
mpIMEInfos = nullptr;
|
|
mcEchoChar = 0;
|
|
|
|
// no default mirroring for Edit controls
|
|
// note: controls that use a subedit will revert this (SpinField, ComboBox)
|
|
EnableRTL( false );
|
|
|
|
mxDnDListener = new vcl::unohelper::DragAndDropWrapper( this );
|
|
}
|
|
|
|
bool Edit::ImplUseNativeBorder(vcl::RenderContext const & rRenderContext, WinBits nStyle) const
|
|
{
|
|
bool bRet = rRenderContext.IsNativeControlSupported(ImplGetNativeControlType(),
|
|
ControlPart::HasBackgroundTexture)
|
|
&& ((nStyle & WB_BORDER) && !(nStyle & WB_NOBORDER));
|
|
if (!bRet && mbIsSubEdit)
|
|
{
|
|
vcl::Window* pWindow = GetParent();
|
|
nStyle = pWindow->GetStyle();
|
|
bRet = pWindow->IsNativeControlSupported(ImplGetNativeControlType(),
|
|
ControlPart::HasBackgroundTexture)
|
|
&& ((nStyle & WB_BORDER) && !(nStyle & WB_NOBORDER));
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
void Edit::ImplInit(vcl::Window* pParent, WinBits nStyle)
|
|
{
|
|
nStyle = ImplInitStyle(nStyle);
|
|
|
|
if (!(nStyle & (WB_CENTER | WB_RIGHT)))
|
|
nStyle |= WB_LEFT;
|
|
|
|
Control::ImplInit(pParent, nStyle, nullptr);
|
|
|
|
mbReadOnly = (nStyle & WB_READONLY) != 0;
|
|
|
|
mnAlign = EDIT_ALIGN_LEFT;
|
|
|
|
// hack: right align until keyinput and cursor travelling works
|
|
if( IsRTLEnabled() )
|
|
mnAlign = EDIT_ALIGN_RIGHT;
|
|
|
|
if ( nStyle & WB_RIGHT )
|
|
mnAlign = EDIT_ALIGN_RIGHT;
|
|
else if ( nStyle & WB_CENTER )
|
|
mnAlign = EDIT_ALIGN_CENTER;
|
|
|
|
SetCursor( new vcl::Cursor );
|
|
|
|
SetPointer( PointerStyle::Text );
|
|
ApplySettings(*GetOutDev());
|
|
|
|
uno::Reference< datatransfer::dnd::XDragGestureRecognizer > xDGR = GetDragGestureRecognizer();
|
|
if ( xDGR.is() )
|
|
{
|
|
xDGR->addDragGestureListener( mxDnDListener );
|
|
GetDropTarget()->addDropTargetListener( mxDnDListener );
|
|
GetDropTarget()->setActive( true );
|
|
GetDropTarget()->setDefaultActions( datatransfer::dnd::DNDConstants::ACTION_COPY_OR_MOVE );
|
|
}
|
|
}
|
|
|
|
WinBits Edit::ImplInitStyle( WinBits nStyle )
|
|
{
|
|
if ( !(nStyle & WB_NOTABSTOP) )
|
|
nStyle |= WB_TABSTOP;
|
|
if ( !(nStyle & WB_NOGROUP) )
|
|
nStyle |= WB_GROUP;
|
|
|
|
return nStyle;
|
|
}
|
|
|
|
bool Edit::IsCharInput( const KeyEvent& rKeyEvent )
|
|
{
|
|
// In the future we must use new Unicode functions for this
|
|
sal_Unicode cCharCode = rKeyEvent.GetCharCode();
|
|
return ((cCharCode >= 32) && (cCharCode != 127) &&
|
|
!rKeyEvent.GetKeyCode().IsMod3() &&
|
|
!rKeyEvent.GetKeyCode().IsMod2() &&
|
|
!rKeyEvent.GetKeyCode().IsMod1() );
|
|
}
|
|
|
|
void Edit::ApplySettings(vcl::RenderContext& rRenderContext)
|
|
{
|
|
Control::ApplySettings(rRenderContext);
|
|
|
|
const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
|
|
|
|
const vcl::Font& aFont = rStyleSettings.GetFieldFont();
|
|
ApplyControlFont(rRenderContext, aFont);
|
|
|
|
ImplClearLayoutData();
|
|
|
|
Color aTextColor = rStyleSettings.GetFieldTextColor();
|
|
ApplyControlForeground(rRenderContext, aTextColor);
|
|
|
|
if (IsControlBackground())
|
|
{
|
|
rRenderContext.SetBackground(GetControlBackground());
|
|
rRenderContext.SetFillColor(GetControlBackground());
|
|
|
|
if (ImplUseNativeBorder(rRenderContext, GetStyle()))
|
|
{
|
|
// indicates that no non-native drawing of background should take place
|
|
mpWindowImpl->mnNativeBackground = ControlPart::Entire;
|
|
}
|
|
}
|
|
else if (ImplUseNativeBorder(rRenderContext, GetStyle()))
|
|
{
|
|
// Transparent background
|
|
rRenderContext.SetBackground();
|
|
rRenderContext.SetFillColor();
|
|
}
|
|
else
|
|
{
|
|
rRenderContext.SetBackground(rStyleSettings.GetFieldColor());
|
|
rRenderContext.SetFillColor(rStyleSettings.GetFieldColor());
|
|
}
|
|
}
|
|
|
|
tools::Long Edit::ImplGetExtraXOffset() const
|
|
{
|
|
// MT 09/2002: nExtraOffsetX should become a member, instead of checking every time,
|
|
// but I need an incompatible update for this...
|
|
// #94095# Use extra offset only when edit has a border
|
|
tools::Long nExtraOffset = 0;
|
|
if( ( GetStyle() & WB_BORDER ) || ( mbIsSubEdit && ( GetParent()->GetStyle() & WB_BORDER ) ) )
|
|
nExtraOffset = 2;
|
|
|
|
return nExtraOffset;
|
|
}
|
|
|
|
tools::Long Edit::ImplGetExtraYOffset() const
|
|
{
|
|
tools::Long nExtraOffset = 0;
|
|
ControlType eCtrlType = ImplGetNativeControlType();
|
|
if (eCtrlType != ControlType::EditboxNoBorder)
|
|
{
|
|
// add some space between text entry and border
|
|
nExtraOffset = 2;
|
|
}
|
|
return nExtraOffset;
|
|
}
|
|
|
|
OUString Edit::ImplGetText() const
|
|
{
|
|
if ( mcEchoChar || mbPassword )
|
|
{
|
|
sal_Unicode cEchoChar;
|
|
if ( mcEchoChar )
|
|
cEchoChar = mcEchoChar;
|
|
else
|
|
cEchoChar = u'\x2022';
|
|
OUStringBuffer aText(maText.getLength());
|
|
comphelper::string::padToLength(aText, maText.getLength(), cEchoChar);
|
|
return aText.makeStringAndClear();
|
|
}
|
|
else
|
|
return maText.toString();
|
|
}
|
|
|
|
void Edit::ImplInvalidateOrRepaint()
|
|
{
|
|
if( IsPaintTransparent() )
|
|
{
|
|
Invalidate();
|
|
// FIXME: this is currently only on macOS
|
|
if( ImplGetSVData()->maNWFData.mbNoFocusRects )
|
|
PaintImmediately();
|
|
}
|
|
else
|
|
Invalidate();
|
|
}
|
|
|
|
tools::Long Edit::ImplGetTextYPosition() const
|
|
{
|
|
if ( GetStyle() & WB_TOP )
|
|
return ImplGetExtraXOffset();
|
|
else if ( GetStyle() & WB_BOTTOM )
|
|
return GetOutputSizePixel().Height() - GetTextHeight() - ImplGetExtraXOffset();
|
|
return ( GetOutputSizePixel().Height() - GetTextHeight() ) / 2;
|
|
}
|
|
|
|
void Edit::ImplRepaint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRectangle)
|
|
{
|
|
if (!IsReallyVisible())
|
|
return;
|
|
|
|
ApplySettings(rRenderContext);
|
|
|
|
const OUString aText = ImplGetText();
|
|
const sal_Int32 nLen = aText.getLength();
|
|
|
|
KernArray aDX;
|
|
if (nLen)
|
|
GetOutDev()->GetCaretPositions(aText, aDX, 0, nLen);
|
|
|
|
tools::Long nTH = GetTextHeight();
|
|
Point aPos(mnXOffset, ImplGetTextYPosition());
|
|
|
|
vcl::Cursor* pCursor = GetCursor();
|
|
bool bVisCursor = pCursor && pCursor->IsVisible();
|
|
if (pCursor)
|
|
pCursor->Hide();
|
|
|
|
ImplClearBackground(rRenderContext, rRectangle, 0, GetOutputSizePixel().Width()-1);
|
|
|
|
bool bPaintPlaceholderText = aText.isEmpty() && !maPlaceholderText.isEmpty();
|
|
|
|
const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings();
|
|
|
|
if (!IsEnabled() || bPaintPlaceholderText)
|
|
rRenderContext.SetTextColor(rStyleSettings.GetDisableColor());
|
|
|
|
// Set background color of the normal text
|
|
if (mbForceControlBackground && IsControlBackground())
|
|
{
|
|
// check if we need to set ControlBackground even in NWF case
|
|
rRenderContext.Push(vcl::PushFlags::FILLCOLOR | vcl::PushFlags::LINECOLOR);
|
|
rRenderContext.SetLineColor();
|
|
rRenderContext.SetFillColor(GetControlBackground());
|
|
rRenderContext.DrawRect(tools::Rectangle(aPos, Size(GetOutputSizePixel().Width() - 2 * mnXOffset, GetOutputSizePixel().Height())));
|
|
rRenderContext.Pop();
|
|
|
|
rRenderContext.SetTextFillColor(GetControlBackground());
|
|
}
|
|
else if (IsPaintTransparent() || ImplUseNativeBorder(rRenderContext, GetStyle()))
|
|
rRenderContext.SetTextFillColor();
|
|
else
|
|
rRenderContext.SetTextFillColor(IsControlBackground() ? GetControlBackground() : rStyleSettings.GetFieldColor());
|
|
|
|
ImplPaintBorder(rRenderContext);
|
|
|
|
bool bDrawSelection = maSelection.Len() && (HasFocus() || (GetStyle() & WB_NOHIDESELECTION) || mbActivePopup);
|
|
|
|
aPos.setX( mnXOffset + ImplGetExtraXOffset() );
|
|
if (bPaintPlaceholderText)
|
|
{
|
|
rRenderContext.DrawText(aPos, maPlaceholderText);
|
|
}
|
|
else if (!bDrawSelection && !mpIMEInfos)
|
|
{
|
|
rRenderContext.DrawText(aPos, aText, 0, nLen);
|
|
}
|
|
else
|
|
{
|
|
// save graphics state
|
|
rRenderContext.Push();
|
|
// first calculate highlighted and non highlighted clip regions
|
|
vcl::Region aHighlightClipRegion;
|
|
vcl::Region aNormalClipRegion;
|
|
Selection aTmpSel(maSelection);
|
|
aTmpSel.Normalize();
|
|
// selection is highlighted
|
|
for(sal_Int32 i = 0; i < nLen; ++i)
|
|
{
|
|
tools::Rectangle aRect(aPos, Size(10, nTH));
|
|
aRect.SetLeft( aDX[2 * i] + mnXOffset + ImplGetExtraXOffset() );
|
|
aRect.SetRight( aDX[2 * i + 1] + mnXOffset + ImplGetExtraXOffset() );
|
|
aRect.Normalize();
|
|
bool bHighlight = false;
|
|
if (i >= aTmpSel.Min() && i < aTmpSel.Max())
|
|
bHighlight = true;
|
|
|
|
if (mpIMEInfos && mpIMEInfos->pAttribs &&
|
|
i >= mpIMEInfos->nPos && i < (mpIMEInfos->nPos+mpIMEInfos->nLen) &&
|
|
(mpIMEInfos->pAttribs[i - mpIMEInfos->nPos] & ExtTextInputAttr::Highlight))
|
|
{
|
|
bHighlight = true;
|
|
}
|
|
|
|
if (bHighlight)
|
|
aHighlightClipRegion.Union(aRect);
|
|
else
|
|
aNormalClipRegion.Union(aRect);
|
|
}
|
|
// draw normal text
|
|
Color aNormalTextColor = rRenderContext.GetTextColor();
|
|
rRenderContext.SetClipRegion(aNormalClipRegion);
|
|
|
|
if (IsPaintTransparent())
|
|
rRenderContext.SetTextFillColor();
|
|
else
|
|
{
|
|
// Set background color when part of the text is selected
|
|
if (ImplUseNativeBorder(rRenderContext, GetStyle()))
|
|
{
|
|
if( mbForceControlBackground && IsControlBackground() )
|
|
rRenderContext.SetTextFillColor(GetControlBackground());
|
|
else
|
|
rRenderContext.SetTextFillColor();
|
|
}
|
|
else
|
|
{
|
|
rRenderContext.SetTextFillColor(IsControlBackground() ? GetControlBackground() : rStyleSettings.GetFieldColor());
|
|
}
|
|
}
|
|
rRenderContext.DrawText(aPos, aText, 0, nLen);
|
|
|
|
// draw highlighted text
|
|
rRenderContext.SetClipRegion(aHighlightClipRegion);
|
|
rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor());
|
|
rRenderContext.SetTextFillColor(rStyleSettings.GetHighlightColor());
|
|
rRenderContext.DrawText(aPos, aText, 0, nLen);
|
|
|
|
// if IME info exists loop over portions and output different font attributes
|
|
if (mpIMEInfos && mpIMEInfos->pAttribs)
|
|
{
|
|
for(int n = 0; n < 2; n++)
|
|
{
|
|
vcl::Region aRegion;
|
|
if (n == 0)
|
|
{
|
|
rRenderContext.SetTextColor(aNormalTextColor);
|
|
if (IsPaintTransparent())
|
|
rRenderContext.SetTextFillColor();
|
|
else
|
|
rRenderContext.SetTextFillColor(IsControlBackground() ? GetControlBackground() : rStyleSettings.GetFieldColor());
|
|
aRegion = aNormalClipRegion;
|
|
}
|
|
else
|
|
{
|
|
rRenderContext.SetTextColor(rStyleSettings.GetHighlightTextColor());
|
|
rRenderContext.SetTextFillColor(rStyleSettings.GetHighlightColor());
|
|
aRegion = aHighlightClipRegion;
|
|
}
|
|
|
|
for(int i = 0; i < mpIMEInfos->nLen; )
|
|
{
|
|
ExtTextInputAttr nAttr = mpIMEInfos->pAttribs[i];
|
|
vcl::Region aClip;
|
|
int nIndex = i;
|
|
while (nIndex < mpIMEInfos->nLen && mpIMEInfos->pAttribs[nIndex] == nAttr) // #112631# check nIndex before using it
|
|
{
|
|
tools::Rectangle aRect( aPos, Size( 10, nTH ) );
|
|
aRect.SetLeft( aDX[2 * (nIndex + mpIMEInfos->nPos)] + mnXOffset + ImplGetExtraXOffset() );
|
|
aRect.SetRight( aDX[2 * (nIndex + mpIMEInfos->nPos) + 1] + mnXOffset + ImplGetExtraXOffset() );
|
|
aRect.Normalize();
|
|
aClip.Union(aRect);
|
|
nIndex++;
|
|
}
|
|
i = nIndex;
|
|
aClip.Intersect(aRegion);
|
|
if (!aClip.IsEmpty() && nAttr != ExtTextInputAttr::NONE)
|
|
{
|
|
vcl::Font aFont = rRenderContext.GetFont();
|
|
if (nAttr & ExtTextInputAttr::Underline)
|
|
aFont.SetUnderline(LINESTYLE_SINGLE);
|
|
else if (nAttr & ExtTextInputAttr::DoubleUnderline)
|
|
aFont.SetUnderline(LINESTYLE_DOUBLE);
|
|
else if (nAttr & ExtTextInputAttr::BoldUnderline)
|
|
aFont.SetUnderline( LINESTYLE_BOLD);
|
|
else if (nAttr & ExtTextInputAttr::DottedUnderline)
|
|
aFont.SetUnderline( LINESTYLE_DOTTED);
|
|
else if (nAttr & ExtTextInputAttr::DashDotUnderline)
|
|
aFont.SetUnderline( LINESTYLE_DASHDOT);
|
|
else if (nAttr & ExtTextInputAttr::GrayWaveline)
|
|
{
|
|
aFont.SetUnderline(LINESTYLE_WAVE);
|
|
rRenderContext.SetTextLineColor(COL_LIGHTGRAY);
|
|
}
|
|
rRenderContext.SetFont(aFont);
|
|
|
|
if (nAttr & ExtTextInputAttr::RedText)
|
|
rRenderContext.SetTextColor(COL_RED);
|
|
else if (nAttr & ExtTextInputAttr::HalfToneText)
|
|
rRenderContext.SetTextColor(COL_LIGHTGRAY);
|
|
|
|
rRenderContext.SetClipRegion(aClip);
|
|
rRenderContext.DrawText(aPos, aText, 0, nLen);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// restore graphics state
|
|
rRenderContext.Pop();
|
|
}
|
|
|
|
if (bVisCursor && (!mpIMEInfos || mpIMEInfos->bCursor))
|
|
pCursor->Show();
|
|
}
|
|
|
|
void Edit::ImplDelete( const Selection& rSelection, sal_uInt8 nDirection, sal_uInt8 nMode )
|
|
{
|
|
const sal_Int32 nTextLen = ImplGetText().getLength();
|
|
|
|
// deleting possible?
|
|
if ( !rSelection.Len() &&
|
|
(((rSelection.Min() == 0) && (nDirection == EDIT_DEL_LEFT)) ||
|
|
((rSelection.Max() == nTextLen) && (nDirection == EDIT_DEL_RIGHT))) )
|
|
return;
|
|
|
|
ImplClearLayoutData();
|
|
|
|
Selection aSelection( rSelection );
|
|
aSelection.Normalize();
|
|
|
|
if ( !aSelection.Len() )
|
|
{
|
|
uno::Reference < i18n::XBreakIterator > xBI = ImplGetBreakIterator();
|
|
if ( nDirection == EDIT_DEL_LEFT )
|
|
{
|
|
if ( nMode == EDIT_DELMODE_RESTOFWORD )
|
|
{
|
|
const OUString sText = maText.toString();
|
|
i18n::Boundary aBoundary = xBI->getWordBoundary( sText, aSelection.Min(),
|
|
GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
|
|
auto startPos = aBoundary.startPos;
|
|
if ( startPos == aSelection.Min() )
|
|
{
|
|
aBoundary = xBI->previousWord( sText, aSelection.Min(),
|
|
GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES );
|
|
startPos = std::max(aBoundary.startPos, sal_Int32(0));
|
|
}
|
|
aSelection.Min() = startPos;
|
|
}
|
|
else if ( nMode == EDIT_DELMODE_RESTOFCONTENT )
|
|
{
|
|
aSelection.Min() = 0;
|
|
}
|
|
else
|
|
{
|
|
sal_Int32 nCount = 1;
|
|
aSelection.Min() = xBI->previousCharacters( maText.toString(), aSelection.Min(),
|
|
GetSettings().GetLanguageTag().getLocale(), i18n::CharacterIteratorMode::SKIPCHARACTER, nCount, nCount );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( nMode == EDIT_DELMODE_RESTOFWORD )
|
|
{
|
|
i18n::Boundary aBoundary = xBI->nextWord( maText.toString(), aSelection.Max(),
|
|
GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES );
|
|
aSelection.Max() = aBoundary.startPos;
|
|
}
|
|
else if ( nMode == EDIT_DELMODE_RESTOFCONTENT )
|
|
{
|
|
aSelection.Max() = nTextLen;
|
|
}
|
|
else
|
|
{
|
|
sal_Int32 nCount = 1;
|
|
aSelection.Max() = xBI->nextCharacters( maText.toString(), aSelection.Max(),
|
|
GetSettings().GetLanguageTag().getLocale(), i18n::CharacterIteratorMode::SKIPCHARACTER, nCount, nCount );
|
|
}
|
|
}
|
|
}
|
|
|
|
const auto nSelectionMin = aSelection.Min();
|
|
maText.remove( nSelectionMin, aSelection.Len() );
|
|
maSelection.Min() = nSelectionMin;
|
|
maSelection.Max() = nSelectionMin;
|
|
ImplAlignAndPaint();
|
|
mbInternModified = true;
|
|
}
|
|
|
|
OUString Edit::ImplGetValidString( const OUString& rString )
|
|
{
|
|
OUString aValidString = rString.replaceAll("\n", "").replaceAll("\r", "");
|
|
aValidString = aValidString.replace('\t', ' ');
|
|
return aValidString;
|
|
}
|
|
|
|
uno::Reference <i18n::XBreakIterator> const& Edit::ImplGetBreakIterator()
|
|
{
|
|
if (!mxBreakIterator)
|
|
mxBreakIterator = i18n::BreakIterator::create(::comphelper::getProcessComponentContext());
|
|
return mxBreakIterator;
|
|
}
|
|
|
|
uno::Reference <i18n::XExtendedInputSequenceChecker> const& Edit::ImplGetInputSequenceChecker()
|
|
{
|
|
if (!mxISC.is())
|
|
mxISC = i18n::InputSequenceChecker::create(::comphelper::getProcessComponentContext());
|
|
return mxISC;
|
|
}
|
|
|
|
void Edit::ShowTruncationWarning(weld::Widget* pParent)
|
|
{
|
|
std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent, VclMessageType::Warning,
|
|
VclButtonsType::Ok, VclResId(SV_EDIT_WARNING_STR)));
|
|
xBox->run();
|
|
}
|
|
|
|
bool Edit::ImplTruncateToMaxLen( OUString& rStr, sal_Int32 nSelectionLen ) const
|
|
{
|
|
bool bWasTruncated = false;
|
|
if (maText.getLength() - nSelectionLen > mnMaxTextLen - rStr.getLength())
|
|
{
|
|
sal_Int32 nErasePos = mnMaxTextLen - maText.getLength() + nSelectionLen;
|
|
rStr = rStr.copy( 0, nErasePos );
|
|
bWasTruncated = true;
|
|
}
|
|
return bWasTruncated;
|
|
}
|
|
|
|
void Edit::ImplInsertText( const OUString& rStr, const Selection* pNewSel, bool bIsUserInput )
|
|
{
|
|
Selection aSelection( maSelection );
|
|
aSelection.Normalize();
|
|
|
|
OUString aNewText( ImplGetValidString( rStr ) );
|
|
|
|
// as below, if there's no selection, but we're in overwrite mode and not beyond
|
|
// the end of the existing text then that's like a selection of 1
|
|
auto nSelectionLen = aSelection.Len();
|
|
if (!nSelectionLen && !mbInsertMode && aSelection.Max() < maText.getLength())
|
|
nSelectionLen = 1;
|
|
ImplTruncateToMaxLen( aNewText, nSelectionLen );
|
|
|
|
ImplClearLayoutData();
|
|
|
|
if ( aSelection.Len() )
|
|
maText.remove( aSelection.Min(), aSelection.Len() );
|
|
else if (!mbInsertMode && aSelection.Max() < maText.getLength())
|
|
maText.remove( aSelection.Max(), 1 );
|
|
|
|
// take care of input-sequence-checking now
|
|
if (bIsUserInput && !rStr.isEmpty())
|
|
{
|
|
SAL_WARN_IF( rStr.getLength() != 1, "vcl", "unexpected string length. User input is expected to provide 1 char only!" );
|
|
|
|
// determine if input-sequence-checking should be applied or not
|
|
|
|
uno::Reference < i18n::XBreakIterator > xBI = ImplGetBreakIterator();
|
|
bool bIsInputSequenceChecking = rStr.getLength() == 1 &&
|
|
officecfg::Office::Common::I18N::CTL::CTLFont::get() &&
|
|
officecfg::Office::Common::I18N::CTL::CTLSequenceChecking::get() &&
|
|
aSelection.Min() > 0 && /* first char needs not to be checked */
|
|
xBI.is() && i18n::ScriptType::COMPLEX == xBI->getScriptType( rStr, 0 );
|
|
|
|
if (bIsInputSequenceChecking)
|
|
{
|
|
uno::Reference < i18n::XExtendedInputSequenceChecker > xISC = ImplGetInputSequenceChecker();
|
|
if (xISC.is())
|
|
{
|
|
sal_Unicode cChar = rStr[0];
|
|
sal_Int32 nTmpPos = aSelection.Min();
|
|
sal_Int16 nCheckMode = officecfg::Office::Common::I18N::CTL::CTLSequenceCheckingRestricted::get()?
|
|
i18n::InputSequenceCheckMode::STRICT : i18n::InputSequenceCheckMode::BASIC;
|
|
|
|
// the text that needs to be checked is only the one
|
|
// before the current cursor position
|
|
const OUString aOldText( maText.subView(0, nTmpPos) );
|
|
OUString aTmpText( aOldText );
|
|
if (officecfg::Office::Common::I18N::CTL::CTLSequenceCheckingTypeAndReplace::get())
|
|
{
|
|
xISC->correctInputSequence( aTmpText, nTmpPos - 1, cChar, nCheckMode );
|
|
|
|
// find position of first character that has changed
|
|
sal_Int32 nOldLen = aOldText.getLength();
|
|
sal_Int32 nTmpLen = aTmpText.getLength();
|
|
const sal_Unicode *pOldTxt = aOldText.getStr();
|
|
const sal_Unicode *pTmpTxt = aTmpText.getStr();
|
|
sal_Int32 nChgPos = 0;
|
|
while ( nChgPos < nOldLen && nChgPos < nTmpLen &&
|
|
pOldTxt[nChgPos] == pTmpTxt[nChgPos] )
|
|
++nChgPos;
|
|
|
|
const OUString aChgText( aTmpText.copy( nChgPos ) );
|
|
|
|
// remove text from first pos to be changed to current pos
|
|
maText.remove( nChgPos, nTmpPos - nChgPos );
|
|
|
|
if (!aChgText.isEmpty())
|
|
{
|
|
aNewText = aChgText;
|
|
aSelection.Min() = nChgPos; // position for new text to be inserted
|
|
}
|
|
else
|
|
aNewText.clear();
|
|
}
|
|
else
|
|
{
|
|
// should the character be ignored (i.e. not get inserted) ?
|
|
if (!xISC->checkInputSequence( aOldText, nTmpPos - 1, cChar, nCheckMode ))
|
|
aNewText.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
// at this point now we will insert the non-empty text 'normally' some lines below...
|
|
}
|
|
|
|
if ( !aNewText.isEmpty() )
|
|
maText.insert( aSelection.Min(), aNewText );
|
|
|
|
if ( !pNewSel )
|
|
{
|
|
maSelection.Min() = aSelection.Min() + aNewText.getLength();
|
|
maSelection.Max() = maSelection.Min();
|
|
}
|
|
else
|
|
{
|
|
maSelection = *pNewSel;
|
|
if ( maSelection.Min() > maText.getLength() )
|
|
maSelection.Min() = maText.getLength();
|
|
if ( maSelection.Max() > maText.getLength() )
|
|
maSelection.Max() = maText.getLength();
|
|
}
|
|
|
|
ImplAlignAndPaint();
|
|
mbInternModified = true;
|
|
}
|
|
|
|
void Edit::ImplSetText( const OUString& rText, const Selection* pNewSelection )
|
|
{
|
|
// we delete text by "selecting" the old text completely then calling InsertText; this is flicker free
|
|
if ( ( rText.getLength() > mnMaxTextLen ) ||
|
|
( std::u16string_view(rText) == std::u16string_view(maText)
|
|
&& (!pNewSelection || (*pNewSelection == maSelection)) ) )
|
|
return;
|
|
|
|
ImplClearLayoutData();
|
|
maSelection.Min() = 0;
|
|
maSelection.Max() = maText.getLength();
|
|
if ( mnXOffset || HasPaintEvent() )
|
|
{
|
|
mnXOffset = 0;
|
|
maText = ImplGetValidString( rText );
|
|
|
|
// #i54929# recalculate mnXOffset before ImplSetSelection,
|
|
// else cursor ends up in wrong position
|
|
ImplAlign();
|
|
|
|
if ( pNewSelection )
|
|
ImplSetSelection( *pNewSelection, false );
|
|
|
|
if ( mnXOffset && !pNewSelection )
|
|
maSelection.Max() = 0;
|
|
|
|
Invalidate();
|
|
}
|
|
else
|
|
ImplInsertText( rText, pNewSelection );
|
|
|
|
CallEventListeners( VclEventId::EditModify );
|
|
}
|
|
|
|
ControlType Edit::ImplGetNativeControlType() const
|
|
{
|
|
ControlType nCtrl = ControlType::Generic;
|
|
const vcl::Window* pControl = mbIsSubEdit ? GetParent() : this;
|
|
|
|
switch (pControl->GetType())
|
|
{
|
|
case WindowType::COMBOBOX:
|
|
case WindowType::PATTERNBOX:
|
|
case WindowType::NUMERICBOX:
|
|
case WindowType::METRICBOX:
|
|
case WindowType::CURRENCYBOX:
|
|
case WindowType::DATEBOX:
|
|
case WindowType::TIMEBOX:
|
|
case WindowType::LONGCURRENCYBOX:
|
|
nCtrl = ControlType::Combobox;
|
|
break;
|
|
|
|
case WindowType::MULTILINEEDIT:
|
|
if ( GetWindow( GetWindowType::Border ) != this )
|
|
nCtrl = ControlType::MultilineEditbox;
|
|
else
|
|
nCtrl = ControlType::EditboxNoBorder;
|
|
break;
|
|
|
|
case WindowType::EDIT:
|
|
case WindowType::PATTERNFIELD:
|
|
case WindowType::METRICFIELD:
|
|
case WindowType::CURRENCYFIELD:
|
|
case WindowType::DATEFIELD:
|
|
case WindowType::TIMEFIELD:
|
|
case WindowType::SPINFIELD:
|
|
case WindowType::FORMATTEDFIELD:
|
|
if (pControl->GetStyle() & WB_SPIN)
|
|
nCtrl = ControlType::Spinbox;
|
|
else
|
|
{
|
|
if (GetWindow(GetWindowType::Border) != this)
|
|
nCtrl = ControlType::Editbox;
|
|
else
|
|
nCtrl = ControlType::EditboxNoBorder;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
nCtrl = ControlType::Editbox;
|
|
}
|
|
return nCtrl;
|
|
}
|
|
|
|
void Edit::ImplClearBackground(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRectangle, tools::Long nXStart, tools::Long nXEnd )
|
|
{
|
|
/*
|
|
* note: at this point the cursor must be switched off already
|
|
*/
|
|
tools::Rectangle aRect(Point(), GetOutputSizePixel());
|
|
aRect.SetLeft( nXStart );
|
|
aRect.SetRight( nXEnd );
|
|
|
|
if( !(ImplUseNativeBorder(rRenderContext, GetStyle()) || IsPaintTransparent()))
|
|
rRenderContext.Erase(aRect);
|
|
else if (SupportsDoubleBuffering() && mbIsSubEdit)
|
|
{
|
|
// ImplPaintBorder() is a NOP, we have a native border, and this is a sub-edit of a control.
|
|
// That means we have to draw the parent native widget to paint the edit area to clear our background.
|
|
vcl::PaintBufferGuard g(ImplGetWindowImpl()->mpFrameData, GetParent());
|
|
GetParent()->Paint(rRenderContext, rRectangle);
|
|
}
|
|
}
|
|
|
|
void Edit::ImplPaintBorder(vcl::RenderContext const & rRenderContext)
|
|
{
|
|
// this is not needed when double-buffering
|
|
if (SupportsDoubleBuffering())
|
|
return;
|
|
|
|
if (!(ImplUseNativeBorder(rRenderContext, GetStyle()) || IsPaintTransparent()))
|
|
return;
|
|
|
|
// draw the inner part by painting the whole control using its border window
|
|
vcl::Window* pBorder = GetWindow(GetWindowType::Border);
|
|
if (pBorder == this)
|
|
{
|
|
// we have no border, use parent
|
|
vcl::Window* pControl = mbIsSubEdit ? GetParent() : this;
|
|
pBorder = pControl->GetWindow(GetWindowType::Border);
|
|
if (pBorder == this)
|
|
pBorder = GetParent();
|
|
}
|
|
|
|
if (!pBorder)
|
|
return;
|
|
|
|
// set proper clipping region to not overdraw the whole control
|
|
vcl::Region aClipRgn = GetPaintRegion();
|
|
if (!aClipRgn.IsNull())
|
|
{
|
|
// transform clipping region to border window's coordinate system
|
|
if (IsRTLEnabled() != pBorder->IsRTLEnabled() && AllSettings::GetLayoutRTL())
|
|
{
|
|
// need to mirror in case border is not RTL but edit is (or vice versa)
|
|
|
|
// mirror
|
|
tools::Rectangle aBounds(aClipRgn.GetBoundRect());
|
|
int xNew = GetOutputSizePixel().Width() - aBounds.GetWidth() - aBounds.Left();
|
|
aClipRgn.Move(xNew - aBounds.Left(), 0);
|
|
|
|
// move offset of border window
|
|
Point aBorderOffs = pBorder->ScreenToOutputPixel(OutputToScreenPixel(Point()));
|
|
aClipRgn.Move(aBorderOffs.X(), aBorderOffs.Y());
|
|
}
|
|
else
|
|
{
|
|
// normal case
|
|
Point aBorderOffs = pBorder->ScreenToOutputPixel(OutputToScreenPixel(Point()));
|
|
aClipRgn.Move(aBorderOffs.X(), aBorderOffs.Y());
|
|
}
|
|
|
|
vcl::Region oldRgn(pBorder->GetOutDev()->GetClipRegion());
|
|
pBorder->GetOutDev()->SetClipRegion(aClipRgn);
|
|
|
|
pBorder->Paint(*pBorder->GetOutDev(), tools::Rectangle());
|
|
|
|
pBorder->GetOutDev()->SetClipRegion(oldRgn);
|
|
}
|
|
else
|
|
{
|
|
pBorder->Paint(*pBorder->GetOutDev(), tools::Rectangle());
|
|
}
|
|
}
|
|
|
|
void Edit::ImplShowCursor( bool bOnlyIfVisible )
|
|
{
|
|
if ( !IsUpdateMode() || ( bOnlyIfVisible && !IsReallyVisible() ) )
|
|
return;
|
|
|
|
vcl::Cursor* pCursor = GetCursor();
|
|
OUString aText = ImplGetText();
|
|
|
|
tools::Long nTextPos = 0;
|
|
|
|
if( !aText.isEmpty() )
|
|
{
|
|
KernArray aDX;
|
|
GetOutDev()->GetCaretPositions(aText, aDX, 0, aText.getLength());
|
|
|
|
if( maSelection.Max() < aText.getLength() )
|
|
nTextPos = aDX[ 2*maSelection.Max() ];
|
|
else
|
|
nTextPos = aDX[ 2*aText.getLength()-1 ];
|
|
}
|
|
|
|
tools::Long nCursorWidth = 0;
|
|
if ( !mbInsertMode && !maSelection.Len() && (maSelection.Max() < aText.getLength()) )
|
|
nCursorWidth = GetTextWidth(aText, maSelection.Max(), 1);
|
|
tools::Long nCursorPosX = nTextPos + mnXOffset + ImplGetExtraXOffset();
|
|
|
|
// cursor should land in visible area
|
|
const Size aOutSize = GetOutputSizePixel();
|
|
if ( (nCursorPosX < 0) || (nCursorPosX >= aOutSize.Width()) )
|
|
{
|
|
tools::Long nOldXOffset = mnXOffset;
|
|
|
|
if ( nCursorPosX < 0 )
|
|
{
|
|
mnXOffset = - nTextPos;
|
|
tools::Long nMaxX = 0;
|
|
mnXOffset += aOutSize.Width() / 5;
|
|
if ( mnXOffset > nMaxX )
|
|
mnXOffset = nMaxX;
|
|
}
|
|
else
|
|
{
|
|
mnXOffset = (aOutSize.Width()-ImplGetExtraXOffset()) - nTextPos;
|
|
// Something more?
|
|
if ( (aOutSize.Width()-ImplGetExtraXOffset()) < nTextPos )
|
|
{
|
|
tools::Long nMaxNegX = (aOutSize.Width()-ImplGetExtraXOffset()) - GetTextWidth( aText );
|
|
mnXOffset -= aOutSize.Width() / 5;
|
|
if ( mnXOffset < nMaxNegX ) // both negative...
|
|
mnXOffset = nMaxNegX;
|
|
}
|
|
}
|
|
|
|
nCursorPosX = nTextPos + mnXOffset + ImplGetExtraXOffset();
|
|
if ( nCursorPosX == aOutSize.Width() ) // then invisible...
|
|
nCursorPosX--;
|
|
|
|
if ( mnXOffset != nOldXOffset )
|
|
ImplInvalidateOrRepaint();
|
|
}
|
|
|
|
const tools::Long nTextHeight = GetTextHeight();
|
|
const tools::Long nCursorPosY = ImplGetTextYPosition();
|
|
if (pCursor)
|
|
{
|
|
pCursor->SetPos( Point( nCursorPosX, nCursorPosY ) );
|
|
pCursor->SetSize( Size( nCursorWidth, nTextHeight ) );
|
|
pCursor->Show();
|
|
}
|
|
}
|
|
|
|
void Edit::ImplAlign()
|
|
{
|
|
if (mnAlign == EDIT_ALIGN_LEFT && !mnXOffset)
|
|
{
|
|
// short circuit common case and avoid slow GetTextWidth() calc
|
|
return;
|
|
}
|
|
|
|
tools::Long nTextWidth = GetTextWidth( ImplGetText() );
|
|
tools::Long nOutWidth = GetOutputSizePixel().Width();
|
|
|
|
if ( mnAlign == EDIT_ALIGN_LEFT )
|
|
{
|
|
if (nTextWidth < nOutWidth)
|
|
mnXOffset = 0;
|
|
}
|
|
else if ( mnAlign == EDIT_ALIGN_RIGHT )
|
|
{
|
|
tools::Long nMinXOffset = nOutWidth - nTextWidth - 1 - ImplGetExtraXOffset();
|
|
bool bRTL = IsRTLEnabled();
|
|
if( mbIsSubEdit && GetParent() )
|
|
bRTL = GetParent()->IsRTLEnabled();
|
|
if( bRTL )
|
|
{
|
|
if( nTextWidth < nOutWidth )
|
|
mnXOffset = nMinXOffset;
|
|
}
|
|
else
|
|
{
|
|
if( nTextWidth < nOutWidth )
|
|
mnXOffset = nMinXOffset;
|
|
else if ( mnXOffset < nMinXOffset )
|
|
mnXOffset = nMinXOffset;
|
|
}
|
|
}
|
|
else if( mnAlign == EDIT_ALIGN_CENTER )
|
|
{
|
|
// would be nicer with check while scrolling but then it's not centred in scrolled state
|
|
mnXOffset = (nOutWidth - nTextWidth) / 2;
|
|
}
|
|
}
|
|
|
|
void Edit::ImplAlignAndPaint()
|
|
{
|
|
ImplAlign();
|
|
ImplInvalidateOrRepaint();
|
|
ImplShowCursor();
|
|
}
|
|
|
|
sal_Int32 Edit::ImplGetCharPos( const Point& rWindowPos ) const
|
|
{
|
|
sal_Int32 nIndex = EDIT_NOLIMIT;
|
|
OUString aText = ImplGetText();
|
|
|
|
if (aText.isEmpty())
|
|
return nIndex;
|
|
|
|
KernArray aDX;
|
|
GetOutDev()->GetCaretPositions(aText, aDX, 0, aText.getLength());
|
|
tools::Long nX = rWindowPos.X() - mnXOffset - ImplGetExtraXOffset();
|
|
for (sal_Int32 i = 0; i < aText.getLength(); aText.iterateCodePoints(&i))
|
|
{
|
|
if( (aDX[2*i] >= nX && aDX[2*i+1] <= nX) ||
|
|
(aDX[2*i+1] >= nX && aDX[2*i] <= nX))
|
|
{
|
|
nIndex = i;
|
|
if( aDX[2*i] < aDX[2*i+1] )
|
|
{
|
|
if( nX > (aDX[2*i]+aDX[2*i+1])/2 )
|
|
aText.iterateCodePoints(&nIndex);
|
|
}
|
|
else
|
|
{
|
|
if( nX < (aDX[2*i]+aDX[2*i+1])/2 )
|
|
aText.iterateCodePoints(&nIndex);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if( nIndex == EDIT_NOLIMIT )
|
|
{
|
|
nIndex = 0;
|
|
sal_Int32 nFinalIndex = 0;
|
|
tools::Long nDiff = std::abs( aDX[0]-nX );
|
|
sal_Int32 i = 0;
|
|
if (!aText.isEmpty())
|
|
{
|
|
aText.iterateCodePoints(&i); //skip the first character
|
|
}
|
|
while (i < aText.getLength())
|
|
{
|
|
tools::Long nNewDiff = std::abs( aDX[2*i]-nX );
|
|
|
|
if( nNewDiff < nDiff )
|
|
{
|
|
nIndex = i;
|
|
nDiff = nNewDiff;
|
|
}
|
|
|
|
nFinalIndex = i;
|
|
|
|
aText.iterateCodePoints(&i);
|
|
}
|
|
if (nIndex == nFinalIndex && std::abs( aDX[2*nIndex+1] - nX ) < nDiff)
|
|
nIndex = EDIT_NOLIMIT;
|
|
}
|
|
|
|
return nIndex;
|
|
}
|
|
|
|
void Edit::ImplSetCursorPos( sal_Int32 nChar, bool bSelect )
|
|
{
|
|
Selection aSelection( maSelection );
|
|
aSelection.Max() = nChar;
|
|
if ( !bSelect )
|
|
aSelection.Min() = aSelection.Max();
|
|
ImplSetSelection( aSelection );
|
|
}
|
|
|
|
void Edit::ImplCopyToSelectionClipboard()
|
|
{
|
|
if ( GetSelection().Len() )
|
|
{
|
|
css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetSystemPrimarySelection());
|
|
ImplCopy( aSelection );
|
|
}
|
|
}
|
|
|
|
void Edit::ImplCopy( uno::Reference< datatransfer::clipboard::XClipboard > const & rxClipboard )
|
|
{
|
|
vcl::unohelper::TextDataObject::CopyStringTo( GetSelected(), rxClipboard );
|
|
}
|
|
|
|
void Edit::ImplPaste( uno::Reference< datatransfer::clipboard::XClipboard > const & rxClipboard )
|
|
{
|
|
if ( !rxClipboard.is() )
|
|
return;
|
|
|
|
uno::Reference< datatransfer::XTransferable > xDataObj;
|
|
|
|
try
|
|
{
|
|
SolarMutexReleaser aReleaser;
|
|
xDataObj = rxClipboard->getContents();
|
|
}
|
|
catch( const css::uno::Exception& )
|
|
{
|
|
}
|
|
|
|
if ( !xDataObj.is() )
|
|
return;
|
|
|
|
datatransfer::DataFlavor aFlavor;
|
|
SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
|
|
try
|
|
{
|
|
uno::Any aData = xDataObj->getTransferData( aFlavor );
|
|
OUString aText;
|
|
aData >>= aText;
|
|
|
|
// tdf#127588 - extend selection to the entire field or paste the text
|
|
// from the clipboard to the current position if there is no selection
|
|
if (mnMaxTextLen < EDIT_NOLIMIT && maSelection.Len() == 0)
|
|
{
|
|
const sal_Int32 aTextLen = aText.getLength();
|
|
if (aTextLen == mnMaxTextLen)
|
|
{
|
|
maSelection.Min() = 0;
|
|
maSelection.Max() = mnMaxTextLen;
|
|
} else
|
|
maSelection.Max() = std::min<sal_Int32>(maSelection.Min() + aTextLen, mnMaxTextLen);
|
|
}
|
|
|
|
Selection aSelection(maSelection);
|
|
aSelection.Normalize();
|
|
if (ImplTruncateToMaxLen(aText, aSelection.Len()))
|
|
ShowTruncationWarning(GetFrameWeld());
|
|
|
|
ReplaceSelected( aText );
|
|
}
|
|
catch( const css::uno::Exception& )
|
|
{
|
|
}
|
|
}
|
|
|
|
void Edit::MouseButtonDown( const MouseEvent& rMEvt )
|
|
{
|
|
if ( mpSubEdit )
|
|
{
|
|
Control::MouseButtonDown( rMEvt );
|
|
return;
|
|
}
|
|
|
|
sal_Int32 nCharPos = ImplGetCharPos( rMEvt.GetPosPixel() );
|
|
Selection aSelection( maSelection );
|
|
aSelection.Normalize();
|
|
|
|
if ( rMEvt.GetClicks() < 4 )
|
|
{
|
|
mbClickedInSelection = false;
|
|
if ( rMEvt.GetClicks() == 3 )
|
|
{
|
|
ImplSetSelection( Selection( 0, EDIT_NOLIMIT) );
|
|
ImplCopyToSelectionClipboard();
|
|
|
|
}
|
|
else if ( rMEvt.GetClicks() == 2 )
|
|
{
|
|
uno::Reference < i18n::XBreakIterator > xBI = ImplGetBreakIterator();
|
|
i18n::Boundary aBoundary = xBI->getWordBoundary( maText.toString(), aSelection.Max(),
|
|
GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
|
|
ImplSetSelection( Selection( aBoundary.startPos, aBoundary.endPos ) );
|
|
ImplCopyToSelectionClipboard();
|
|
}
|
|
else if ( !rMEvt.IsShift() && HasFocus() && aSelection.Contains( nCharPos ) )
|
|
mbClickedInSelection = true;
|
|
else if ( rMEvt.IsLeft() )
|
|
ImplSetCursorPos( nCharPos, rMEvt.IsShift() );
|
|
|
|
if ( !mbClickedInSelection && rMEvt.IsLeft() && ( rMEvt.GetClicks() == 1 ) )
|
|
StartTracking( StartTrackingFlags::ScrollRepeat );
|
|
}
|
|
|
|
GrabFocus();
|
|
}
|
|
|
|
void Edit::MouseButtonUp( const MouseEvent& rMEvt )
|
|
{
|
|
if ( mbClickedInSelection && rMEvt.IsLeft() )
|
|
{
|
|
sal_Int32 nCharPos = ImplGetCharPos( rMEvt.GetPosPixel() );
|
|
ImplSetCursorPos( nCharPos, false );
|
|
mbClickedInSelection = false;
|
|
}
|
|
else if ( rMEvt.IsMiddle() && !mbReadOnly &&
|
|
( GetSettings().GetMouseSettings().GetMiddleButtonAction() == MouseMiddleButtonAction::PasteSelection ) )
|
|
{
|
|
css::uno::Reference<css::datatransfer::clipboard::XClipboard> aSelection(GetSystemPrimarySelection());
|
|
ImplPaste( aSelection );
|
|
Modify();
|
|
}
|
|
}
|
|
|
|
void Edit::Tracking( const TrackingEvent& rTEvt )
|
|
{
|
|
if ( rTEvt.IsTrackingEnded() )
|
|
{
|
|
if ( mbClickedInSelection )
|
|
{
|
|
sal_Int32 nCharPos = ImplGetCharPos( rTEvt.GetMouseEvent().GetPosPixel() );
|
|
ImplSetCursorPos( nCharPos, false );
|
|
mbClickedInSelection = false;
|
|
}
|
|
else if ( rTEvt.GetMouseEvent().IsLeft() )
|
|
{
|
|
ImplCopyToSelectionClipboard();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( !mbClickedInSelection )
|
|
{
|
|
sal_Int32 nCharPos = ImplGetCharPos( rTEvt.GetMouseEvent().GetPosPixel() );
|
|
ImplSetCursorPos( nCharPos, true );
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Edit::ImplHandleKeyEvent( const KeyEvent& rKEvt )
|
|
{
|
|
bool bDone = false;
|
|
sal_uInt16 nCode = rKEvt.GetKeyCode().GetCode();
|
|
KeyFuncType eFunc = rKEvt.GetKeyCode().GetFunction();
|
|
|
|
mbInternModified = false;
|
|
|
|
if ( eFunc != KeyFuncType::DONTKNOW )
|
|
{
|
|
switch ( eFunc )
|
|
{
|
|
case KeyFuncType::CUT:
|
|
{
|
|
if ( !mbReadOnly && maSelection.Len() && !mbPassword )
|
|
{
|
|
Cut();
|
|
Modify();
|
|
bDone = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KeyFuncType::COPY:
|
|
{
|
|
if ( !mbPassword )
|
|
{
|
|
Copy();
|
|
bDone = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KeyFuncType::PASTE:
|
|
{
|
|
if ( !mbReadOnly )
|
|
{
|
|
Paste();
|
|
bDone = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KeyFuncType::UNDO:
|
|
{
|
|
if ( !mbReadOnly )
|
|
{
|
|
Undo();
|
|
bDone = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
eFunc = KeyFuncType::DONTKNOW;
|
|
}
|
|
}
|
|
|
|
if ( !bDone && rKEvt.GetKeyCode().IsMod1() && !rKEvt.GetKeyCode().IsMod2() )
|
|
{
|
|
if ( nCode == KEY_A )
|
|
{
|
|
ImplSetSelection( Selection( 0, maText.getLength() ) );
|
|
bDone = true;
|
|
}
|
|
else if ( rKEvt.GetKeyCode().IsShift() && (nCode == KEY_S) )
|
|
{
|
|
if ( pImplFncGetSpecialChars )
|
|
{
|
|
Selection aSaveSel = GetSelection(); // if someone changes the selection in Get/LoseFocus, e.g. URL bar
|
|
OUString aChars = pImplFncGetSpecialChars( GetFrameWeld(), GetFont() );
|
|
SetSelection( aSaveSel );
|
|
if ( !aChars.isEmpty() )
|
|
{
|
|
ImplInsertText( aChars );
|
|
Modify();
|
|
}
|
|
bDone = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( eFunc == KeyFuncType::DONTKNOW && ! bDone )
|
|
{
|
|
switch ( nCode )
|
|
{
|
|
case css::awt::Key::SELECT_ALL:
|
|
{
|
|
ImplSetSelection( Selection( 0, maText.getLength() ) );
|
|
bDone = true;
|
|
}
|
|
break;
|
|
|
|
case KEY_LEFT:
|
|
case KEY_RIGHT:
|
|
case KEY_HOME:
|
|
case KEY_END:
|
|
case css::awt::Key::MOVE_WORD_FORWARD:
|
|
case css::awt::Key::SELECT_WORD_FORWARD:
|
|
case css::awt::Key::MOVE_WORD_BACKWARD:
|
|
case css::awt::Key::SELECT_WORD_BACKWARD:
|
|
case css::awt::Key::MOVE_TO_BEGIN_OF_LINE:
|
|
case css::awt::Key::MOVE_TO_END_OF_LINE:
|
|
case css::awt::Key::SELECT_TO_BEGIN_OF_LINE:
|
|
case css::awt::Key::SELECT_TO_END_OF_LINE:
|
|
case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH:
|
|
case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH:
|
|
case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH:
|
|
case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH:
|
|
case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT:
|
|
case css::awt::Key::MOVE_TO_END_OF_DOCUMENT:
|
|
case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT:
|
|
case css::awt::Key::SELECT_TO_END_OF_DOCUMENT:
|
|
{
|
|
if ( !rKEvt.GetKeyCode().IsMod2() )
|
|
{
|
|
ImplClearLayoutData();
|
|
uno::Reference < i18n::XBreakIterator > xBI = ImplGetBreakIterator();
|
|
|
|
Selection aSel( maSelection );
|
|
bool bWord = rKEvt.GetKeyCode().IsMod1();
|
|
bool bSelect = rKEvt.GetKeyCode().IsShift();
|
|
bool bGoLeft = (nCode == KEY_LEFT);
|
|
bool bGoRight = (nCode == KEY_RIGHT);
|
|
bool bGoHome = (nCode == KEY_HOME);
|
|
bool bGoEnd = (nCode == KEY_END);
|
|
|
|
switch( nCode )
|
|
{
|
|
case css::awt::Key::MOVE_WORD_FORWARD:
|
|
bGoRight = bWord = true;break;
|
|
case css::awt::Key::SELECT_WORD_FORWARD:
|
|
bGoRight = bSelect = bWord = true;break;
|
|
case css::awt::Key::MOVE_WORD_BACKWARD:
|
|
bGoLeft = bWord = true;break;
|
|
case css::awt::Key::SELECT_WORD_BACKWARD:
|
|
bGoLeft = bSelect = bWord = true;break;
|
|
case css::awt::Key::SELECT_TO_BEGIN_OF_LINE:
|
|
case css::awt::Key::SELECT_TO_BEGIN_OF_PARAGRAPH:
|
|
case css::awt::Key::SELECT_TO_BEGIN_OF_DOCUMENT:
|
|
bSelect = true;
|
|
[[fallthrough]];
|
|
case css::awt::Key::MOVE_TO_BEGIN_OF_LINE:
|
|
case css::awt::Key::MOVE_TO_BEGIN_OF_PARAGRAPH:
|
|
case css::awt::Key::MOVE_TO_BEGIN_OF_DOCUMENT:
|
|
bGoHome = true;break;
|
|
case css::awt::Key::SELECT_TO_END_OF_LINE:
|
|
case css::awt::Key::SELECT_TO_END_OF_PARAGRAPH:
|
|
case css::awt::Key::SELECT_TO_END_OF_DOCUMENT:
|
|
bSelect = true;
|
|
[[fallthrough]];
|
|
case css::awt::Key::MOVE_TO_END_OF_LINE:
|
|
case css::awt::Key::MOVE_TO_END_OF_PARAGRAPH:
|
|
case css::awt::Key::MOVE_TO_END_OF_DOCUMENT:
|
|
bGoEnd = true;break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// range is checked in ImplSetSelection ...
|
|
if ( bGoLeft && aSel.Max() )
|
|
{
|
|
if ( bWord )
|
|
{
|
|
const OUString sText = maText.toString();
|
|
i18n::Boundary aBoundary = xBI->getWordBoundary( sText, aSel.Max(),
|
|
GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES, true );
|
|
if ( aBoundary.startPos == aSel.Max() )
|
|
aBoundary = xBI->previousWord( sText, aSel.Max(),
|
|
GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES );
|
|
aSel.Max() = aBoundary.startPos;
|
|
}
|
|
else
|
|
{
|
|
sal_Int32 nCount = 1;
|
|
aSel.Max() = xBI->previousCharacters( maText.toString(), aSel.Max(),
|
|
GetSettings().GetLanguageTag().getLocale(), i18n::CharacterIteratorMode::SKIPCHARACTER, nCount, nCount );
|
|
}
|
|
}
|
|
else if ( bGoRight && ( aSel.Max() < maText.getLength() ) )
|
|
{
|
|
if ( bWord )
|
|
{
|
|
i18n::Boundary aBoundary = xBI->nextWord( maText.toString(), aSel.Max(),
|
|
GetSettings().GetLanguageTag().getLocale(), i18n::WordType::ANYWORD_IGNOREWHITESPACES );
|
|
aSel.Max() = aBoundary.startPos;
|
|
}
|
|
else
|
|
{
|
|
sal_Int32 nCount = 1;
|
|
aSel.Max() = xBI->nextCharacters( maText.toString(), aSel.Max(),
|
|
GetSettings().GetLanguageTag().getLocale(), i18n::CharacterIteratorMode::SKIPCHARACTER, nCount, nCount );
|
|
}
|
|
}
|
|
else if ( bGoHome )
|
|
{
|
|
aSel.Max() = 0;
|
|
}
|
|
else if ( bGoEnd )
|
|
{
|
|
aSel.Max() = EDIT_NOLIMIT;
|
|
}
|
|
|
|
if ( !bSelect )
|
|
aSel.Min() = aSel.Max();
|
|
|
|
if ( aSel != GetSelection() )
|
|
{
|
|
ImplSetSelection( aSel );
|
|
ImplCopyToSelectionClipboard();
|
|
}
|
|
|
|
if (bGoEnd && maAutocompleteHdl.IsSet() && !rKEvt.GetKeyCode().GetModifier())
|
|
{
|
|
if ( (maSelection.Min() == maSelection.Max()) && (maSelection.Min() == maText.getLength()) )
|
|
{
|
|
maAutocompleteHdl.Call(*this);
|
|
}
|
|
}
|
|
|
|
bDone = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case css::awt::Key::DELETE_WORD_BACKWARD:
|
|
case css::awt::Key::DELETE_WORD_FORWARD:
|
|
case css::awt::Key::DELETE_TO_BEGIN_OF_LINE:
|
|
case css::awt::Key::DELETE_TO_END_OF_LINE:
|
|
case KEY_BACKSPACE:
|
|
case KEY_DELETE:
|
|
{
|
|
if ( !mbReadOnly && !rKEvt.GetKeyCode().IsMod2() )
|
|
{
|
|
sal_uInt8 nDel = (nCode == KEY_DELETE) ? EDIT_DEL_RIGHT : EDIT_DEL_LEFT;
|
|
sal_uInt8 nMode = rKEvt.GetKeyCode().IsMod1() ? EDIT_DELMODE_RESTOFWORD : EDIT_DELMODE_SIMPLE;
|
|
if ( (nMode == EDIT_DELMODE_RESTOFWORD) && rKEvt.GetKeyCode().IsShift() )
|
|
nMode = EDIT_DELMODE_RESTOFCONTENT;
|
|
switch( nCode )
|
|
{
|
|
case css::awt::Key::DELETE_WORD_BACKWARD:
|
|
nDel = EDIT_DEL_LEFT;
|
|
nMode = EDIT_DELMODE_RESTOFWORD;
|
|
break;
|
|
case css::awt::Key::DELETE_WORD_FORWARD:
|
|
nDel = EDIT_DEL_RIGHT;
|
|
nMode = EDIT_DELMODE_RESTOFWORD;
|
|
break;
|
|
case css::awt::Key::DELETE_TO_BEGIN_OF_LINE:
|
|
nDel = EDIT_DEL_LEFT;
|
|
nMode = EDIT_DELMODE_RESTOFCONTENT;
|
|
break;
|
|
case css::awt::Key::DELETE_TO_END_OF_LINE:
|
|
nDel = EDIT_DEL_RIGHT;
|
|
nMode = EDIT_DELMODE_RESTOFCONTENT;
|
|
break;
|
|
default: break;
|
|
}
|
|
sal_Int32 nOldLen = maText.getLength();
|
|
ImplDelete( maSelection, nDel, nMode );
|
|
if ( maText.getLength() != nOldLen )
|
|
Modify();
|
|
bDone = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KEY_INSERT:
|
|
{
|
|
if ( !mpIMEInfos && !mbReadOnly && !rKEvt.GetKeyCode().IsMod2() )
|
|
{
|
|
SetInsertMode( !mbInsertMode );
|
|
bDone = true;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KEY_RETURN:
|
|
if (maActivateHdl.IsSet() && !rKEvt.GetKeyCode().GetModifier())
|
|
bDone = maActivateHdl.Call(*this);
|
|
break;
|
|
|
|
default:
|
|
{
|
|
if ( IsCharInput( rKEvt ) )
|
|
{
|
|
bDone = true; // read characters also when in ReadOnly
|
|
if ( !mbReadOnly )
|
|
{
|
|
ImplInsertText(OUString(rKEvt.GetCharCode()), nullptr, true);
|
|
if (maAutocompleteHdl.IsSet())
|
|
{
|
|
if ( (maSelection.Min() == maSelection.Max()) && (maSelection.Min() == maText.getLength()) )
|
|
{
|
|
maAutocompleteHdl.Call(*this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( mbInternModified )
|
|
Modify();
|
|
|
|
return bDone;
|
|
}
|
|
|
|
void Edit::KeyInput( const KeyEvent& rKEvt )
|
|
{
|
|
if ( mpSubEdit || !ImplHandleKeyEvent( rKEvt ) )
|
|
Control::KeyInput( rKEvt );
|
|
}
|
|
|
|
void Edit::FillLayoutData() const
|
|
{
|
|
mxLayoutData.emplace();
|
|
const_cast<Edit*>(this)->Invalidate();
|
|
}
|
|
|
|
void Edit::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRectangle)
|
|
{
|
|
if (!mpSubEdit)
|
|
ImplRepaint(rRenderContext, rRectangle);
|
|
}
|
|
|
|
void Edit::Resize()
|
|
{
|
|
if ( !mpSubEdit && IsReallyVisible() )
|
|
{
|
|
Control::Resize();
|
|
// because of vertical centering...
|
|
mnXOffset = 0;
|
|
ImplAlign();
|
|
Invalidate();
|
|
ImplShowCursor();
|
|
}
|
|
}
|
|
|
|
void Edit::Draw( OutputDevice* pDev, const Point& rPos, SystemTextColorFlags nFlags )
|
|
{
|
|
ApplySettings(*pDev);
|
|
|
|
Point aPos = pDev->LogicToPixel( rPos );
|
|
Size aSize = GetSizePixel();
|
|
vcl::Font aFont = GetDrawPixelFont( pDev );
|
|
|
|
pDev->Push();
|
|
pDev->SetMapMode();
|
|
pDev->SetFont( aFont );
|
|
pDev->SetTextFillColor();
|
|
|
|
// Border/Background
|
|
pDev->SetLineColor();
|
|
pDev->SetFillColor();
|
|
bool bBorder = (GetStyle() & WB_BORDER);
|
|
bool bBackground = IsControlBackground();
|
|
if ( bBorder || bBackground )
|
|
{
|
|
tools::Rectangle aRect( aPos, aSize );
|
|
if ( bBorder )
|
|
{
|
|
ImplDrawFrame( pDev, aRect );
|
|
}
|
|
if ( bBackground )
|
|
{
|
|
pDev->SetFillColor( GetControlBackground() );
|
|
pDev->DrawRect( aRect );
|
|
}
|
|
}
|
|
|
|
// Content
|
|
if ( nFlags & SystemTextColorFlags::Mono )
|
|
pDev->SetTextColor( COL_BLACK );
|
|
else
|
|
{
|
|
if ( !IsEnabled() )
|
|
{
|
|
const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
|
|
pDev->SetTextColor( rStyleSettings.GetDisableColor() );
|
|
}
|
|
else
|
|
{
|
|
pDev->SetTextColor( GetTextColor() );
|
|
}
|
|
}
|
|
|
|
const tools::Long nOnePixel = GetDrawPixel( pDev, 1 );
|
|
const tools::Long nOffX = 3*nOnePixel;
|
|
DrawTextFlags nTextStyle = DrawTextFlags::VCenter;
|
|
tools::Rectangle aTextRect( aPos, aSize );
|
|
|
|
if ( GetStyle() & WB_CENTER )
|
|
nTextStyle |= DrawTextFlags::Center;
|
|
else if ( GetStyle() & WB_RIGHT )
|
|
nTextStyle |= DrawTextFlags::Right;
|
|
else
|
|
nTextStyle |= DrawTextFlags::Left;
|
|
|
|
aTextRect.AdjustLeft(nOffX );
|
|
aTextRect.AdjustRight( -nOffX );
|
|
|
|
OUString aText = ImplGetText();
|
|
tools::Long nTextHeight = pDev->GetTextHeight();
|
|
tools::Long nTextWidth = pDev->GetTextWidth( aText );
|
|
tools::Long nOffY = (aSize.Height() - nTextHeight) / 2;
|
|
|
|
// Clipping?
|
|
if ( (nOffY < 0) ||
|
|
((nOffY+nTextHeight) > aSize.Height()) ||
|
|
((nOffX+nTextWidth) > aSize.Width()) )
|
|
{
|
|
tools::Rectangle aClip( aPos, aSize );
|
|
if ( nTextHeight > aSize.Height() )
|
|
aClip.AdjustBottom(nTextHeight-aSize.Height()+1 ); // prevent HP printers from 'optimizing'
|
|
pDev->IntersectClipRegion( aClip );
|
|
}
|
|
|
|
pDev->DrawText( aTextRect, aText, nTextStyle );
|
|
pDev->Pop();
|
|
|
|
if ( GetSubEdit() )
|
|
{
|
|
Size aOrigSize(GetSubEdit()->GetSizePixel());
|
|
GetSubEdit()->SetSizePixel(GetSizePixel());
|
|
GetSubEdit()->Draw(pDev, rPos, nFlags);
|
|
GetSubEdit()->SetSizePixel(aOrigSize);
|
|
}
|
|
}
|
|
|
|
void Edit::ImplInvalidateOutermostBorder( vcl::Window* pWin )
|
|
{
|
|
// allow control to show focused state
|
|
vcl::Window *pInvalWin = pWin;
|
|
for (;;)
|
|
{
|
|
vcl::Window* pBorder = pInvalWin->GetWindow( GetWindowType::Border );
|
|
if (pBorder == pInvalWin || !pBorder ||
|
|
pInvalWin->ImplGetFrame() != pBorder->ImplGetFrame() )
|
|
break;
|
|
pInvalWin = pBorder;
|
|
}
|
|
|
|
pInvalWin->Invalidate( InvalidateFlags::Children | InvalidateFlags::Update );
|
|
}
|
|
|
|
void Edit::GetFocus()
|
|
{
|
|
Control::GetFocus();
|
|
|
|
// tdf#164127 for an Edit in the UNO control property browser, above call to Control::GetFocus
|
|
// can result in it getting disposed - return early in that case
|
|
if (isDisposed())
|
|
return;
|
|
|
|
if ( mpSubEdit )
|
|
mpSubEdit->ImplGrabFocus( GetGetFocusFlags() );
|
|
else if ( !mbActivePopup )
|
|
{
|
|
maUndoText = maText.toString();
|
|
SelectionOptions nSelOptions = GetSettings().GetStyleSettings().GetSelectionOptions();
|
|
if ( !( GetStyle() & (WB_NOHIDESELECTION|WB_READONLY) )
|
|
&& ( GetGetFocusFlags() & (GetFocusFlags::Init|GetFocusFlags::Tab|GetFocusFlags::CURSOR|GetFocusFlags::Mnemonic) ) )
|
|
{
|
|
if ( nSelOptions & SelectionOptions::ShowFirst )
|
|
{
|
|
maSelection.Min() = maText.getLength();
|
|
maSelection.Max() = 0;
|
|
}
|
|
else
|
|
{
|
|
maSelection.Min() = 0;
|
|
maSelection.Max() = maText.getLength();
|
|
}
|
|
if ( mbIsSubEdit )
|
|
static_cast<Edit*>(GetParent())->CallEventListeners( VclEventId::EditSelectionChanged );
|
|
else
|
|
CallEventListeners( VclEventId::EditSelectionChanged );
|
|
}
|
|
|
|
ImplShowCursor();
|
|
|
|
if (IsNativeWidgetEnabled() &&
|
|
IsNativeControlSupported( ControlType::Editbox, ControlPart::Entire ))
|
|
{
|
|
ImplInvalidateOutermostBorder( mbIsSubEdit ? GetParent() : this );
|
|
}
|
|
else if ( maSelection.Len() )
|
|
{
|
|
// paint the selection
|
|
if ( !HasPaintEvent() )
|
|
ImplInvalidateOrRepaint();
|
|
else
|
|
Invalidate();
|
|
}
|
|
|
|
SetInputContext( InputContext( GetFont(), !IsReadOnly() ? InputContextFlags::Text|InputContextFlags::ExtText : InputContextFlags::NONE ) );
|
|
}
|
|
}
|
|
|
|
void Edit::LoseFocus()
|
|
{
|
|
if ( !mpSubEdit )
|
|
{
|
|
if (IsNativeWidgetEnabled() &&
|
|
IsNativeControlSupported(ControlType::Editbox, ControlPart::Entire))
|
|
{
|
|
ImplInvalidateOutermostBorder( mbIsSubEdit ? GetParent() : this );
|
|
}
|
|
|
|
if ( !mbActivePopup && !( GetStyle() & WB_NOHIDESELECTION ) && maSelection.Len() )
|
|
ImplInvalidateOrRepaint(); // paint the selection
|
|
}
|
|
|
|
Control::LoseFocus();
|
|
}
|
|
|
|
bool Edit::PreNotify(NotifyEvent& rNEvt)
|
|
{
|
|
if (rNEvt.GetType() == NotifyEventType::MOUSEMOVE)
|
|
{
|
|
const MouseEvent* pMouseEvt = rNEvt.GetMouseEvent();
|
|
if (pMouseEvt && !pMouseEvt->GetButtons() && !pMouseEvt->IsSynthetic() && !pMouseEvt->IsModifierChanged())
|
|
{
|
|
// trigger redraw if mouse over state has changed
|
|
if (pMouseEvt->IsLeaveWindow() || pMouseEvt->IsEnterWindow())
|
|
{
|
|
if (IsNativeWidgetEnabled() &&
|
|
IsNativeControlSupported(ControlType::Editbox, ControlPart::Entire))
|
|
{
|
|
ImplInvalidateOutermostBorder(this);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return Control::PreNotify(rNEvt);
|
|
}
|
|
|
|
void Edit::Command( const CommandEvent& rCEvt )
|
|
{
|
|
if ( rCEvt.GetCommand() == CommandEventId::ContextMenu )
|
|
{
|
|
VclPtr<PopupMenu> pPopup = Edit::CreatePopupMenu();
|
|
|
|
bool bEnableCut = true;
|
|
bool bEnableCopy = true;
|
|
bool bEnableDelete = true;
|
|
bool bEnablePaste = true;
|
|
bool bEnableSpecialChar = true;
|
|
|
|
if ( !maSelection.Len() )
|
|
{
|
|
bEnableCut = false;
|
|
bEnableCopy = false;
|
|
bEnableDelete = false;
|
|
}
|
|
|
|
if ( IsReadOnly() )
|
|
{
|
|
bEnableCut = false;
|
|
bEnablePaste = false;
|
|
bEnableDelete = false;
|
|
bEnableSpecialChar = false;
|
|
}
|
|
else
|
|
{
|
|
// only paste if text available in clipboard
|
|
bool bData = false;
|
|
uno::Reference< datatransfer::clipboard::XClipboard > xClipboard = GetClipboard();
|
|
|
|
if ( xClipboard.is() )
|
|
{
|
|
uno::Reference< datatransfer::XTransferable > xDataObj;
|
|
{
|
|
SolarMutexReleaser aReleaser;
|
|
xDataObj = xClipboard->getContents();
|
|
}
|
|
if ( xDataObj.is() )
|
|
{
|
|
datatransfer::DataFlavor aFlavor;
|
|
SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
|
|
bData = xDataObj->isDataFlavorSupported( aFlavor );
|
|
}
|
|
}
|
|
bEnablePaste = bData;
|
|
}
|
|
|
|
pPopup->EnableItem(pPopup->GetItemId(u"cut"), bEnableCut);
|
|
pPopup->EnableItem(pPopup->GetItemId(u"copy"), bEnableCopy);
|
|
pPopup->EnableItem(pPopup->GetItemId(u"delete"), bEnableDelete);
|
|
pPopup->EnableItem(pPopup->GetItemId(u"paste"), bEnablePaste);
|
|
pPopup->SetItemText(pPopup->GetItemId(u"specialchar"),
|
|
BuilderUtils::convertMnemonicMarkup(VclResId(STR_SPECIAL_CHARACTER_MENU_ENTRY)));
|
|
pPopup->EnableItem(pPopup->GetItemId(u"specialchar"), bEnableSpecialChar);
|
|
pPopup->EnableItem(
|
|
pPopup->GetItemId(u"undo"),
|
|
std::u16string_view(maUndoText) != std::u16string_view(maText));
|
|
bool bAllSelected = maSelection.Min() == 0 && maSelection.Max() == maText.getLength();
|
|
pPopup->EnableItem(pPopup->GetItemId(u"selectall"), !bAllSelected);
|
|
pPopup->ShowItem(pPopup->GetItemId(u"specialchar"), pImplFncGetSpecialChars != nullptr);
|
|
|
|
mbActivePopup = true;
|
|
Selection aSaveSel = GetSelection(); // if someone changes selection in Get/LoseFocus, e.g. URL bar
|
|
Point aPos = rCEvt.GetMousePosPixel();
|
|
if ( !rCEvt.IsMouseEvent() )
|
|
{
|
|
// Show menu eventually centered in selection
|
|
Size aSize = GetOutputSizePixel();
|
|
aPos = Point( aSize.Width()/2, aSize.Height()/2 );
|
|
}
|
|
sal_uInt16 n = pPopup->Execute( this, aPos );
|
|
SetSelection( aSaveSel );
|
|
OUString sCommand = pPopup->GetItemIdent(n);
|
|
if (sCommand == "undo")
|
|
{
|
|
Undo();
|
|
Modify();
|
|
}
|
|
else if (sCommand == "cut")
|
|
{
|
|
Cut();
|
|
Modify();
|
|
}
|
|
else if (sCommand == "copy")
|
|
{
|
|
Copy();
|
|
}
|
|
else if (sCommand == "paste")
|
|
{
|
|
Paste();
|
|
Modify();
|
|
}
|
|
else if (sCommand == "delete")
|
|
{
|
|
DeleteSelected();
|
|
Modify();
|
|
}
|
|
else if (sCommand == "selectall")
|
|
{
|
|
ImplSetSelection( Selection( 0, maText.getLength() ) );
|
|
}
|
|
else if (sCommand == "specialchar" && pImplFncGetSpecialChars)
|
|
{
|
|
OUString aChars = pImplFncGetSpecialChars(GetFrameWeld(), GetFont());
|
|
if (!isDisposed()) // destroyed while the insert special character dialog was still open
|
|
{
|
|
SetSelection( aSaveSel );
|
|
if (!aChars.isEmpty())
|
|
{
|
|
ImplInsertText( aChars );
|
|
Modify();
|
|
}
|
|
}
|
|
}
|
|
pPopup.clear();
|
|
mbActivePopup = false;
|
|
}
|
|
else if ( rCEvt.GetCommand() == CommandEventId::StartExtTextInput )
|
|
{
|
|
DeleteSelected();
|
|
sal_Int32 nPos = maSelection.Max();
|
|
mpIMEInfos.reset(new Impl_IMEInfos( nPos, maText.copy(nPos).makeStringAndClear() ));
|
|
mpIMEInfos->bWasCursorOverwrite = !IsInsertMode();
|
|
}
|
|
else if ( rCEvt.GetCommand() == CommandEventId::EndExtTextInput )
|
|
{
|
|
bool bInsertMode = !mpIMEInfos->bWasCursorOverwrite;
|
|
mpIMEInfos.reset();
|
|
|
|
SetInsertMode(bInsertMode);
|
|
Modify();
|
|
|
|
Invalidate();
|
|
|
|
// #i25161# call auto complete handler for ext text commit also
|
|
if (maAutocompleteHdl.IsSet())
|
|
{
|
|
if ( (maSelection.Min() == maSelection.Max()) && (maSelection.Min() == maText.getLength()) )
|
|
{
|
|
maAutocompleteHdl.Call(*this);
|
|
}
|
|
}
|
|
}
|
|
else if ( rCEvt.GetCommand() == CommandEventId::ExtTextInput )
|
|
{
|
|
const CommandExtTextInputData* pData = rCEvt.GetExtTextInputData();
|
|
|
|
maText.remove( mpIMEInfos->nPos, mpIMEInfos->nLen );
|
|
maText.insert( mpIMEInfos->nPos, pData->GetText() );
|
|
if ( mpIMEInfos->bWasCursorOverwrite )
|
|
{
|
|
const sal_Int32 nOldIMETextLen = mpIMEInfos->nLen;
|
|
const sal_Int32 nNewIMETextLen = pData->GetText().getLength();
|
|
if ( ( nOldIMETextLen > nNewIMETextLen ) &&
|
|
( nNewIMETextLen < mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
|
|
{
|
|
// restore old characters
|
|
const sal_Int32 nRestore = nOldIMETextLen - nNewIMETextLen;
|
|
maText.insert( mpIMEInfos->nPos + nNewIMETextLen, mpIMEInfos->aOldTextAfterStartPos.subView( nNewIMETextLen, nRestore ) );
|
|
}
|
|
else if ( ( nOldIMETextLen < nNewIMETextLen ) &&
|
|
( nOldIMETextLen < mpIMEInfos->aOldTextAfterStartPos.getLength() ) )
|
|
{
|
|
const sal_Int32 nOverwrite = ( nNewIMETextLen > mpIMEInfos->aOldTextAfterStartPos.getLength()
|
|
? mpIMEInfos->aOldTextAfterStartPos.getLength() : nNewIMETextLen ) - nOldIMETextLen;
|
|
maText.remove( mpIMEInfos->nPos + nNewIMETextLen, nOverwrite );
|
|
}
|
|
}
|
|
|
|
if ( pData->GetTextAttr() )
|
|
{
|
|
mpIMEInfos->CopyAttribs( pData->GetTextAttr(), pData->GetText().getLength() );
|
|
mpIMEInfos->bCursor = pData->IsCursorVisible();
|
|
}
|
|
else
|
|
{
|
|
mpIMEInfos->DestroyAttribs();
|
|
}
|
|
|
|
ImplAlignAndPaint();
|
|
sal_Int32 nCursorPos = mpIMEInfos->nPos + pData->GetCursorPos();
|
|
SetSelection( Selection( nCursorPos, nCursorPos ) );
|
|
SetInsertMode( !pData->IsCursorOverwrite() );
|
|
|
|
if ( pData->IsCursorVisible() )
|
|
GetCursor()->Show();
|
|
else
|
|
GetCursor()->Hide();
|
|
}
|
|
else if ( rCEvt.GetCommand() == CommandEventId::CursorPos )
|
|
{
|
|
if ( mpIMEInfos )
|
|
{
|
|
sal_Int32 nCursorPos = GetSelection().Max();
|
|
SetCursorRect( nullptr, GetTextWidth( maText.toString(), nCursorPos, mpIMEInfos->nPos+mpIMEInfos->nLen-nCursorPos ) );
|
|
}
|
|
else
|
|
{
|
|
SetCursorRect();
|
|
}
|
|
}
|
|
else if ( rCEvt.GetCommand() == CommandEventId::SelectionChange )
|
|
{
|
|
const CommandSelectionChangeData *pData = rCEvt.GetSelectionChangeData();
|
|
Selection aSelection( pData->GetStart(), pData->GetEnd() );
|
|
SetSelection(aSelection);
|
|
}
|
|
else if ( rCEvt.GetCommand() == CommandEventId::QueryCharPosition )
|
|
{
|
|
if (mpIMEInfos && mpIMEInfos->nLen > 0)
|
|
{
|
|
OUString aText = ImplGetText();
|
|
KernArray aDX;
|
|
GetOutDev()->GetCaretPositions(aText, aDX, 0, aText.getLength());
|
|
|
|
tools::Long nTH = GetTextHeight();
|
|
Point aPos( mnXOffset, ImplGetTextYPosition() );
|
|
|
|
std::vector<tools::Rectangle> aRects(mpIMEInfos->nLen);
|
|
for ( int nIndex = 0; nIndex < mpIMEInfos->nLen; ++nIndex )
|
|
{
|
|
tools::Rectangle aRect( aPos, Size( 10, nTH ) );
|
|
aRect.SetLeft( aDX[2*(nIndex+mpIMEInfos->nPos)] + mnXOffset + ImplGetExtraXOffset() );
|
|
aRects[ nIndex ] = aRect;
|
|
}
|
|
SetCompositionCharRect(aRects.data(), mpIMEInfos->nLen);
|
|
}
|
|
}
|
|
else
|
|
Control::Command( rCEvt );
|
|
}
|
|
|
|
void Edit::StateChanged( StateChangedType nType )
|
|
{
|
|
if (nType == StateChangedType::InitShow)
|
|
{
|
|
if (!mpSubEdit)
|
|
{
|
|
mnXOffset = 0; // if GrabFocus before while size was still wrong
|
|
ImplAlign();
|
|
if (!mpSubEdit)
|
|
ImplShowCursor(false);
|
|
Invalidate();
|
|
}
|
|
}
|
|
else if (nType == StateChangedType::Enable)
|
|
{
|
|
if (!mpSubEdit)
|
|
{
|
|
// change text color only
|
|
ImplInvalidateOrRepaint();
|
|
}
|
|
}
|
|
else if (nType == StateChangedType::Style || nType == StateChangedType::Mirroring)
|
|
{
|
|
WinBits nStyle = GetStyle();
|
|
if (nType == StateChangedType::Style)
|
|
{
|
|
nStyle = ImplInitStyle(GetStyle());
|
|
SetStyle(nStyle);
|
|
}
|
|
|
|
sal_uInt16 nOldAlign = mnAlign;
|
|
mnAlign = EDIT_ALIGN_LEFT;
|
|
|
|
// hack: right align until keyinput and cursor travelling works
|
|
// edits are always RTL disabled
|
|
// however the parent edits contain the correct setting
|
|
if (mbIsSubEdit && GetParent()->IsRTLEnabled())
|
|
{
|
|
if (GetParent()->GetStyle() & WB_LEFT)
|
|
mnAlign = EDIT_ALIGN_RIGHT;
|
|
if (nType == StateChangedType::Mirroring)
|
|
GetOutDev()->SetLayoutMode(vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::TextOriginLeft);
|
|
}
|
|
else if (mbIsSubEdit && !GetParent()->IsRTLEnabled())
|
|
{
|
|
if (nType == StateChangedType::Mirroring)
|
|
GetOutDev()->SetLayoutMode(vcl::text::ComplexTextLayoutFlags::TextOriginLeft);
|
|
}
|
|
|
|
if (nStyle & WB_RIGHT)
|
|
mnAlign = EDIT_ALIGN_RIGHT;
|
|
else if (nStyle & WB_CENTER)
|
|
mnAlign = EDIT_ALIGN_CENTER;
|
|
if (!maText.isEmpty() && (mnAlign != nOldAlign))
|
|
{
|
|
ImplAlign();
|
|
Invalidate();
|
|
}
|
|
|
|
}
|
|
else if ((nType == StateChangedType::Zoom) || (nType == StateChangedType::ControlFont))
|
|
{
|
|
if (!mpSubEdit)
|
|
{
|
|
ApplySettings(*GetOutDev());
|
|
ImplShowCursor();
|
|
Invalidate();
|
|
}
|
|
}
|
|
else if ((nType == StateChangedType::ControlForeground) || (nType == StateChangedType::ControlBackground))
|
|
{
|
|
if (!mpSubEdit)
|
|
{
|
|
ApplySettings(*GetOutDev());
|
|
Invalidate();
|
|
}
|
|
}
|
|
|
|
Control::StateChanged(nType);
|
|
}
|
|
|
|
void Edit::DataChanged( const DataChangedEvent& rDCEvt )
|
|
{
|
|
if ( (rDCEvt.GetType() == DataChangedEventType::FONTS) ||
|
|
(rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) ||
|
|
((rDCEvt.GetType() == DataChangedEventType::SETTINGS) &&
|
|
(rDCEvt.GetFlags() & AllSettingsFlags::STYLE)) )
|
|
{
|
|
if ( !mpSubEdit )
|
|
{
|
|
ApplySettings(*GetOutDev());
|
|
ImplShowCursor();
|
|
Invalidate();
|
|
}
|
|
}
|
|
|
|
Control::DataChanged( rDCEvt );
|
|
}
|
|
|
|
void Edit::ImplShowDDCursor()
|
|
{
|
|
if (!mpDDInfo->bVisCursor)
|
|
{
|
|
tools::Long nTextWidth = GetTextWidth( maText.toString(), 0, mpDDInfo->nDropPos );
|
|
tools::Long nTextHeight = GetTextHeight();
|
|
tools::Rectangle aCursorRect( Point( nTextWidth + mnXOffset, (GetOutDev()->GetOutputSize().Height()-nTextHeight)/2 ), Size( 2, nTextHeight ) );
|
|
mpDDInfo->aCursor.SetWindow( this );
|
|
mpDDInfo->aCursor.SetPos( aCursorRect.TopLeft() );
|
|
mpDDInfo->aCursor.SetSize( aCursorRect.GetSize() );
|
|
mpDDInfo->aCursor.Show();
|
|
mpDDInfo->bVisCursor = true;
|
|
}
|
|
}
|
|
|
|
void Edit::ImplHideDDCursor()
|
|
{
|
|
if ( mpDDInfo && mpDDInfo->bVisCursor )
|
|
{
|
|
mpDDInfo->aCursor.Hide();
|
|
mpDDInfo->bVisCursor = false;
|
|
}
|
|
}
|
|
|
|
TextFilter::TextFilter(OUString _aForbiddenChars)
|
|
: sForbiddenChars(std::move(_aForbiddenChars))
|
|
{
|
|
}
|
|
|
|
TextFilter::~TextFilter()
|
|
{
|
|
}
|
|
|
|
OUString TextFilter::filter(const OUString &rText)
|
|
{
|
|
OUString sTemp(rText);
|
|
for (sal_Int32 i = 0; i < sForbiddenChars.getLength(); ++i)
|
|
{
|
|
sTemp = sTemp.replaceAll(OUStringChar(sForbiddenChars[i]), "");
|
|
}
|
|
return sTemp;
|
|
}
|
|
|
|
void Edit::filterText()
|
|
{
|
|
Selection aSel = GetSelection();
|
|
const OUString sOrig = GetText();
|
|
const OUString sNew = mpFilterText->filter(GetText());
|
|
if (sOrig != sNew)
|
|
{
|
|
sal_Int32 nDiff = sOrig.getLength() - sNew.getLength();
|
|
if (nDiff)
|
|
{
|
|
aSel.setMin(aSel.getMin() - nDiff);
|
|
aSel.setMax(aSel.getMin());
|
|
}
|
|
SetText(sNew);
|
|
SetSelection(aSel);
|
|
}
|
|
}
|
|
|
|
void Edit::Modify()
|
|
{
|
|
if (mpFilterText)
|
|
filterText();
|
|
|
|
if ( mbIsSubEdit )
|
|
{
|
|
static_cast<Edit*>(GetParent())->Modify();
|
|
}
|
|
else
|
|
{
|
|
if ( ImplCallEventListenersAndHandler( VclEventId::EditModify, [this] () { maModifyHdl.Call(*this); } ) )
|
|
// have been destroyed while calling into the handlers
|
|
return;
|
|
|
|
// #i13677# notify edit listeners about caret position change
|
|
CallEventListeners( VclEventId::EditCaretChanged );
|
|
// FIXME: this is currently only on macOS
|
|
// check for other platforms that need similar handling
|
|
if( ImplGetSVData()->maNWFData.mbNoFocusRects &&
|
|
IsNativeWidgetEnabled() &&
|
|
IsNativeControlSupported( ControlType::Editbox, ControlPart::Entire ) )
|
|
{
|
|
ImplInvalidateOutermostBorder( this );
|
|
}
|
|
}
|
|
}
|
|
|
|
void Edit::SetEchoChar( sal_Unicode c )
|
|
{
|
|
mcEchoChar = c;
|
|
if ( mpSubEdit )
|
|
mpSubEdit->SetEchoChar( c );
|
|
}
|
|
|
|
void Edit::SetReadOnly( bool bReadOnly )
|
|
{
|
|
if ( mbReadOnly != bReadOnly )
|
|
{
|
|
mbReadOnly = bReadOnly;
|
|
if ( mpSubEdit )
|
|
mpSubEdit->SetReadOnly( bReadOnly );
|
|
|
|
CompatStateChanged( StateChangedType::ReadOnly );
|
|
}
|
|
}
|
|
|
|
void Edit::SetInsertMode( bool bInsert )
|
|
{
|
|
if ( bInsert != mbInsertMode )
|
|
{
|
|
mbInsertMode = bInsert;
|
|
if ( mpSubEdit )
|
|
mpSubEdit->SetInsertMode( bInsert );
|
|
else
|
|
ImplShowCursor();
|
|
}
|
|
}
|
|
|
|
bool Edit::IsInsertMode() const
|
|
{
|
|
if ( mpSubEdit )
|
|
return mpSubEdit->IsInsertMode();
|
|
else
|
|
return mbInsertMode;
|
|
}
|
|
|
|
void Edit::SetMaxTextLen(sal_Int32 nMaxLen)
|
|
{
|
|
mnMaxTextLen = nMaxLen > 0 ? nMaxLen : EDIT_NOLIMIT;
|
|
|
|
if ( mpSubEdit )
|
|
mpSubEdit->SetMaxTextLen( mnMaxTextLen );
|
|
else
|
|
{
|
|
if ( maText.getLength() > mnMaxTextLen )
|
|
ImplDelete( Selection( mnMaxTextLen, maText.getLength() ), EDIT_DEL_RIGHT, EDIT_DELMODE_SIMPLE );
|
|
}
|
|
}
|
|
|
|
void Edit::SetSelection( const Selection& rSelection )
|
|
{
|
|
// If the selection was changed from outside, e.g. by MouseButtonDown, don't call Tracking()
|
|
// directly afterwards which would change the selection again
|
|
if ( IsTracking() )
|
|
EndTracking();
|
|
else if ( mpSubEdit && mpSubEdit->IsTracking() )
|
|
mpSubEdit->EndTracking();
|
|
|
|
ImplSetSelection( rSelection );
|
|
}
|
|
|
|
void Edit::ImplSetSelection( const Selection& rSelection, bool bPaint )
|
|
{
|
|
if ( mpSubEdit )
|
|
mpSubEdit->ImplSetSelection( rSelection );
|
|
else
|
|
{
|
|
if ( rSelection != maSelection )
|
|
{
|
|
Selection aOld( maSelection );
|
|
Selection aNew( rSelection );
|
|
|
|
if ( aNew.Min() > maText.getLength() )
|
|
aNew.Min() = maText.getLength();
|
|
if ( aNew.Max() > maText.getLength() )
|
|
aNew.Max() = maText.getLength();
|
|
if ( aNew.Min() < 0 )
|
|
aNew.Min() = 0;
|
|
if ( aNew.Max() < 0 )
|
|
aNew.Max() = 0;
|
|
|
|
if ( aNew != maSelection )
|
|
{
|
|
ImplClearLayoutData();
|
|
Selection aTemp = maSelection;
|
|
maSelection = aNew;
|
|
|
|
if ( bPaint && ( aOld.Len() || aNew.Len() || IsPaintTransparent() ) )
|
|
ImplInvalidateOrRepaint();
|
|
ImplShowCursor();
|
|
|
|
bool bCaret = false, bSelection = false;
|
|
tools::Long nB=aNew.Max(), nA=aNew.Min(),oB=aTemp.Max(), oA=aTemp.Min();
|
|
tools::Long nGap = nB-nA, oGap = oB-oA;
|
|
if (nB != oB)
|
|
bCaret = true;
|
|
if (nGap != 0 || oGap != 0)
|
|
bSelection = true;
|
|
|
|
if (bSelection)
|
|
{
|
|
if ( mbIsSubEdit )
|
|
static_cast<Edit*>(GetParent())->CallEventListeners( VclEventId::EditSelectionChanged );
|
|
else
|
|
CallEventListeners( VclEventId::EditSelectionChanged );
|
|
}
|
|
|
|
if (bCaret)
|
|
{
|
|
if ( mbIsSubEdit )
|
|
static_cast<Edit*>(GetParent())->CallEventListeners( VclEventId::EditCaretChanged );
|
|
else
|
|
CallEventListeners( VclEventId::EditCaretChanged );
|
|
}
|
|
|
|
// #103511# notify combobox listeners of deselection
|
|
if( !maSelection && GetParent() && GetParent()->GetType() == WindowType::COMBOBOX )
|
|
static_cast<Edit*>(GetParent())->CallEventListeners( VclEventId::ComboboxDeselect );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const Selection& Edit::GetSelection() const
|
|
{
|
|
if ( mpSubEdit )
|
|
return mpSubEdit->GetSelection();
|
|
else
|
|
return maSelection;
|
|
}
|
|
|
|
void Edit::ReplaceSelected( const OUString& rStr )
|
|
{
|
|
if ( mpSubEdit )
|
|
mpSubEdit->ReplaceSelected( rStr );
|
|
else
|
|
ImplInsertText( rStr );
|
|
}
|
|
|
|
void Edit::DeleteSelected()
|
|
{
|
|
if ( mpSubEdit )
|
|
mpSubEdit->DeleteSelected();
|
|
else
|
|
{
|
|
if ( maSelection.Len() )
|
|
ImplDelete( maSelection, EDIT_DEL_RIGHT, EDIT_DELMODE_SIMPLE );
|
|
}
|
|
}
|
|
|
|
OUString Edit::GetSelected() const
|
|
{
|
|
if ( mpSubEdit )
|
|
return mpSubEdit->GetSelected();
|
|
else
|
|
{
|
|
Selection aSelection( maSelection );
|
|
aSelection.Normalize();
|
|
return OUString( maText.getStr() + aSelection.Min(), aSelection.Len() );
|
|
}
|
|
}
|
|
|
|
void Edit::Cut()
|
|
{
|
|
if ( !mbPassword )
|
|
{
|
|
Copy();
|
|
ReplaceSelected( OUString() );
|
|
}
|
|
}
|
|
|
|
void Edit::Copy()
|
|
{
|
|
if ( !mbPassword )
|
|
{
|
|
css::uno::Reference<css::datatransfer::clipboard::XClipboard> aClipboard(GetClipboard());
|
|
ImplCopy( aClipboard );
|
|
}
|
|
}
|
|
|
|
void Edit::Paste()
|
|
{
|
|
css::uno::Reference<css::datatransfer::clipboard::XClipboard> aClipboard(GetClipboard());
|
|
ImplPaste( aClipboard );
|
|
}
|
|
|
|
void Edit::Undo()
|
|
{
|
|
if ( mpSubEdit )
|
|
mpSubEdit->Undo();
|
|
else
|
|
{
|
|
const OUString aText( maText.toString() );
|
|
ImplDelete( Selection( 0, aText.getLength() ), EDIT_DEL_RIGHT, EDIT_DELMODE_SIMPLE );
|
|
ImplInsertText( maUndoText );
|
|
ImplSetSelection( Selection( 0, maUndoText.getLength() ) );
|
|
maUndoText = aText;
|
|
}
|
|
}
|
|
|
|
void Edit::SetText( const OUString& rStr )
|
|
{
|
|
if ( mpSubEdit )
|
|
mpSubEdit->SetText( rStr ); // not directly ImplSetText if SetText overridden
|
|
else
|
|
{
|
|
Selection aNewSel( 0, 0 ); // prevent scrolling
|
|
ImplSetText( rStr, &aNewSel );
|
|
}
|
|
}
|
|
|
|
void Edit::SetText( const OUString& rStr, const Selection& rSelection )
|
|
{
|
|
if ( mpSubEdit )
|
|
mpSubEdit->SetText( rStr, rSelection );
|
|
else
|
|
ImplSetText( rStr, &rSelection );
|
|
}
|
|
|
|
OUString Edit::GetText() const
|
|
{
|
|
if ( mpSubEdit )
|
|
return mpSubEdit->GetText();
|
|
else
|
|
return maText.toString();
|
|
}
|
|
|
|
void Edit::SetCursorAtLast(){
|
|
ImplSetCursorPos( GetText().getLength(), false );
|
|
}
|
|
|
|
void Edit::SetPlaceholderText( const OUString& rStr )
|
|
{
|
|
if ( mpSubEdit )
|
|
mpSubEdit->SetPlaceholderText( rStr );
|
|
else if ( maPlaceholderText != rStr )
|
|
{
|
|
maPlaceholderText = rStr;
|
|
if ( GetText().isEmpty() )
|
|
Invalidate();
|
|
}
|
|
}
|
|
|
|
void Edit::SetModifyFlag()
|
|
{
|
|
}
|
|
|
|
void Edit::SetSubEdit(Edit* pEdit)
|
|
{
|
|
mpSubEdit.disposeAndClear();
|
|
mpSubEdit.set(pEdit);
|
|
|
|
if (mpSubEdit)
|
|
{
|
|
SetPointer(PointerStyle::Arrow); // Only SubEdit has the BEAM...
|
|
mpSubEdit->mbIsSubEdit = true;
|
|
|
|
mpSubEdit->SetReadOnly(mbReadOnly);
|
|
mpSubEdit->maAutocompleteHdl = maAutocompleteHdl;
|
|
}
|
|
}
|
|
|
|
Size Edit::CalcMinimumSizeForText(const OUString &rString) const
|
|
{
|
|
ControlType eCtrlType = ImplGetNativeControlType();
|
|
|
|
Size aSize;
|
|
if (mnWidthInChars != -1)
|
|
{
|
|
//CalcSize calls CalcWindowSize, but we will call that also in this
|
|
//function, so undo the first one with CalcOutputSize
|
|
aSize = CalcOutputSize(CalcSize(mnWidthInChars));
|
|
}
|
|
else
|
|
{
|
|
OUString aString;
|
|
if (mnMaxWidthChars != -1 && mnMaxWidthChars < rString.getLength())
|
|
aString = rString.copy(0, mnMaxWidthChars);
|
|
else
|
|
aString = rString;
|
|
|
|
aSize.setHeight( GetTextHeight() );
|
|
aSize.setWidth( GetTextWidth(aString) );
|
|
aSize.AdjustWidth(ImplGetExtraXOffset() * 2 );
|
|
|
|
// do not create edit fields in which one cannot enter anything
|
|
// a default minimum width should exist for at least 3 characters
|
|
|
|
//CalcSize calls CalcWindowSize, but we will call that also in this
|
|
//function, so undo the first one with CalcOutputSize
|
|
Size aMinSize(CalcOutputSize(CalcSize(3)));
|
|
if (aSize.Width() < aMinSize.Width())
|
|
aSize.setWidth( aMinSize.Width() );
|
|
}
|
|
|
|
aSize.AdjustHeight(ImplGetExtraYOffset() * 2 );
|
|
|
|
aSize = CalcWindowSize( aSize );
|
|
|
|
// ask NWF what if it has an opinion, too
|
|
ImplControlValue aControlValue;
|
|
tools::Rectangle aRect( Point( 0, 0 ), aSize );
|
|
tools::Rectangle aContent, aBound;
|
|
if (GetNativeControlRegion(eCtrlType, ControlPart::Entire, aRect, ControlState::NONE,
|
|
aControlValue, aBound, aContent))
|
|
{
|
|
if (aBound.GetHeight() > aSize.Height())
|
|
aSize.setHeight( aBound.GetHeight() );
|
|
}
|
|
return aSize;
|
|
}
|
|
|
|
Size Edit::CalcMinimumSize() const
|
|
{
|
|
return CalcMinimumSizeForText(GetText());
|
|
}
|
|
|
|
Size Edit::GetOptimalSize() const
|
|
{
|
|
return CalcMinimumSize();
|
|
}
|
|
|
|
Size Edit::CalcSize(sal_Int32 nChars) const
|
|
{
|
|
// width for N characters, independent from content.
|
|
// works only correct for fixed fonts, average otherwise
|
|
float fUnitWidth = std::max(approximate_char_width(), approximate_digit_width());
|
|
Size aSz(fUnitWidth * nChars, GetTextHeight());
|
|
aSz.AdjustWidth(ImplGetExtraXOffset() * 2 );
|
|
aSz = CalcWindowSize( aSz );
|
|
return aSz;
|
|
}
|
|
|
|
sal_Int32 Edit::GetMaxVisChars() const
|
|
{
|
|
const vcl::Window* pW = mpSubEdit ? mpSubEdit : this;
|
|
sal_Int32 nOutWidth = pW->GetOutputSizePixel().Width();
|
|
float fUnitWidth = std::max(approximate_char_width(), approximate_digit_width());
|
|
return nOutWidth / fUnitWidth;
|
|
}
|
|
|
|
namespace vcl
|
|
{
|
|
void SetGetSpecialCharsFunction( FncGetSpecialChars fn )
|
|
{
|
|
pImplFncGetSpecialChars = fn;
|
|
}
|
|
|
|
FncGetSpecialChars GetGetSpecialCharsFunction()
|
|
{
|
|
return pImplFncGetSpecialChars;
|
|
}
|
|
}
|
|
|
|
VclPtr<PopupMenu> Edit::CreatePopupMenu()
|
|
{
|
|
if (!mpUIBuilder)
|
|
mpUIBuilder.reset(new VclBuilder(nullptr, AllSettings::GetUIRootDir(), u"vcl/ui/editmenu.ui"_ustr, u""_ustr));
|
|
VclPtr<PopupMenu> pPopup = mpUIBuilder->get_menu(u"menu");
|
|
const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings();
|
|
if (rStyleSettings.GetHideDisabledMenuItems())
|
|
pPopup->SetMenuFlags( MenuFlags::HideDisabledEntries );
|
|
else
|
|
pPopup->SetMenuFlags ( MenuFlags::AlwaysShowDisabledEntries );
|
|
if (rStyleSettings.GetContextMenuShortcuts())
|
|
{
|
|
pPopup->SetAccelKey(pPopup->GetItemId(u"undo"), vcl::KeyCode( KeyFuncType::UNDO));
|
|
pPopup->SetAccelKey(pPopup->GetItemId(u"cut"), vcl::KeyCode( KeyFuncType::CUT));
|
|
pPopup->SetAccelKey(pPopup->GetItemId(u"copy"), vcl::KeyCode( KeyFuncType::COPY));
|
|
pPopup->SetAccelKey(pPopup->GetItemId(u"paste"), vcl::KeyCode( KeyFuncType::PASTE));
|
|
pPopup->SetAccelKey(pPopup->GetItemId(u"delete"), vcl::KeyCode( KeyFuncType::DELETE));
|
|
pPopup->SetAccelKey(pPopup->GetItemId(u"selectall"), vcl::KeyCode( KEY_A, false, true, false, false));
|
|
pPopup->SetAccelKey(pPopup->GetItemId(u"specialchar"), vcl::KeyCode( KEY_S, true, true, false, false));
|
|
}
|
|
return pPopup;
|
|
}
|
|
|
|
// css::datatransfer::dnd::XDragGestureListener
|
|
void Edit::dragGestureRecognized( const css::datatransfer::dnd::DragGestureEvent& rDGE )
|
|
{
|
|
SolarMutexGuard aVclGuard;
|
|
|
|
if ( !(!IsTracking() && maSelection.Len() &&
|
|
!mbPassword && (!mpDDInfo || !mpDDInfo->bStarterOfDD)) ) // no repeated D&D
|
|
return;
|
|
|
|
Selection aSel( maSelection );
|
|
aSel.Normalize();
|
|
|
|
// only if mouse in the selection...
|
|
Point aMousePos( rDGE.DragOriginX, rDGE.DragOriginY );
|
|
sal_Int32 nCharPos = ImplGetCharPos( aMousePos );
|
|
if ( (nCharPos < aSel.Min()) || (nCharPos >= aSel.Max()) )
|
|
return;
|
|
|
|
if ( !mpDDInfo )
|
|
mpDDInfo.reset(new DDInfo);
|
|
|
|
mpDDInfo->bStarterOfDD = true;
|
|
mpDDInfo->aDndStartSel = aSel;
|
|
|
|
if ( IsTracking() )
|
|
EndTracking(); // before D&D disable tracking
|
|
|
|
rtl::Reference<vcl::unohelper::TextDataObject> pDataObj = new vcl::unohelper::TextDataObject( GetSelected() );
|
|
sal_Int8 nActions = datatransfer::dnd::DNDConstants::ACTION_COPY;
|
|
if ( !IsReadOnly() )
|
|
nActions |= datatransfer::dnd::DNDConstants::ACTION_MOVE;
|
|
rDGE.DragSource->startDrag( rDGE, nActions, 0 /*cursor*/, 0 /*image*/, pDataObj, mxDnDListener );
|
|
if ( GetCursor() )
|
|
GetCursor()->Hide();
|
|
}
|
|
|
|
// css::datatransfer::dnd::XDragSourceListener
|
|
void Edit::dragDropEnd( const css::datatransfer::dnd::DragSourceDropEvent& rDSDE )
|
|
{
|
|
SolarMutexGuard aVclGuard;
|
|
|
|
if (rDSDE.DropSuccess && (rDSDE.DropAction & datatransfer::dnd::DNDConstants::ACTION_MOVE) && mpDDInfo)
|
|
{
|
|
Selection aSel( mpDDInfo->aDndStartSel );
|
|
if ( mpDDInfo->bDroppedInMe )
|
|
{
|
|
if ( aSel.Max() > mpDDInfo->nDropPos )
|
|
{
|
|
tools::Long nLen = aSel.Len();
|
|
aSel.Min() += nLen;
|
|
aSel.Max() += nLen;
|
|
}
|
|
}
|
|
ImplDelete( aSel, EDIT_DEL_RIGHT, EDIT_DELMODE_SIMPLE );
|
|
Modify();
|
|
}
|
|
|
|
ImplHideDDCursor();
|
|
mpDDInfo.reset();
|
|
}
|
|
|
|
// css::datatransfer::dnd::XDropTargetListener
|
|
void Edit::drop( const css::datatransfer::dnd::DropTargetDropEvent& rDTDE )
|
|
{
|
|
SolarMutexGuard aVclGuard;
|
|
|
|
bool bChanges = false;
|
|
if ( !mbReadOnly && mpDDInfo )
|
|
{
|
|
ImplHideDDCursor();
|
|
|
|
Selection aSel( maSelection );
|
|
aSel.Normalize();
|
|
|
|
if ( aSel.Len() && !mpDDInfo->bStarterOfDD )
|
|
ImplDelete( aSel, EDIT_DEL_RIGHT, EDIT_DELMODE_SIMPLE );
|
|
|
|
mpDDInfo->bDroppedInMe = true;
|
|
|
|
aSel.Min() = mpDDInfo->nDropPos;
|
|
aSel.Max() = mpDDInfo->nDropPos;
|
|
ImplSetSelection( aSel );
|
|
|
|
uno::Reference< datatransfer::XTransferable > xDataObj = rDTDE.Transferable;
|
|
if ( xDataObj.is() )
|
|
{
|
|
datatransfer::DataFlavor aFlavor;
|
|
SotExchange::GetFormatDataFlavor( SotClipboardFormatId::STRING, aFlavor );
|
|
if ( xDataObj->isDataFlavorSupported( aFlavor ) )
|
|
{
|
|
uno::Any aData = xDataObj->getTransferData( aFlavor );
|
|
OUString aText;
|
|
aData >>= aText;
|
|
ImplInsertText( aText );
|
|
bChanges = true;
|
|
Modify();
|
|
}
|
|
}
|
|
|
|
if ( !mpDDInfo->bStarterOfDD )
|
|
{
|
|
mpDDInfo.reset();
|
|
}
|
|
}
|
|
|
|
rDTDE.Context->dropComplete( bChanges );
|
|
}
|
|
|
|
void Edit::dragEnter( const css::datatransfer::dnd::DropTargetDragEnterEvent& rDTDE )
|
|
{
|
|
if ( !mpDDInfo )
|
|
{
|
|
mpDDInfo.reset(new DDInfo);
|
|
}
|
|
// search for string data type
|
|
const Sequence< css::datatransfer::DataFlavor >& rFlavors( rDTDE.SupportedDataFlavors );
|
|
mpDDInfo->bIsStringSupported = std::any_of(rFlavors.begin(), rFlavors.end(),
|
|
[](const css::datatransfer::DataFlavor& rFlavor) {
|
|
sal_Int32 nIndex = 0;
|
|
const std::u16string_view aMimetype = o3tl::getToken(rFlavor.MimeType, 0, ';', nIndex );
|
|
return aMimetype == u"text/plain";
|
|
});
|
|
}
|
|
|
|
void Edit::dragExit( const css::datatransfer::dnd::DropTargetEvent& )
|
|
{
|
|
SolarMutexGuard aVclGuard;
|
|
|
|
ImplHideDDCursor();
|
|
}
|
|
|
|
void Edit::dragOver( const css::datatransfer::dnd::DropTargetDragEvent& rDTDE )
|
|
{
|
|
SolarMutexGuard aVclGuard;
|
|
|
|
Point aMousePos( rDTDE.LocationX, rDTDE.LocationY );
|
|
|
|
sal_Int32 nPrevDropPos = mpDDInfo->nDropPos;
|
|
mpDDInfo->nDropPos = ImplGetCharPos( aMousePos );
|
|
|
|
/*
|
|
Size aOutSize = GetOutputSizePixel();
|
|
if ( ( aMousePos.X() < 0 ) || ( aMousePos.X() > aOutSize.Width() ) )
|
|
{
|
|
// Scroll?
|
|
// No, I will not receive events in this case...
|
|
}
|
|
*/
|
|
|
|
Selection aSel( maSelection );
|
|
aSel.Normalize();
|
|
|
|
// Don't accept drop in selection or read-only field...
|
|
if ( IsReadOnly() || aSel.Contains( mpDDInfo->nDropPos ) || ! mpDDInfo->bIsStringSupported )
|
|
{
|
|
ImplHideDDCursor();
|
|
rDTDE.Context->rejectDrag();
|
|
}
|
|
else
|
|
{
|
|
// draw the old cursor away...
|
|
if ( !mpDDInfo->bVisCursor || ( nPrevDropPos != mpDDInfo->nDropPos ) )
|
|
{
|
|
ImplHideDDCursor();
|
|
ImplShowDDCursor();
|
|
}
|
|
rDTDE.Context->acceptDrag( rDTDE.DropAction );
|
|
}
|
|
}
|
|
|
|
OUString Edit::GetSurroundingText() const
|
|
{
|
|
if (mpSubEdit)
|
|
return mpSubEdit->GetSurroundingText();
|
|
return maText.toString();
|
|
}
|
|
|
|
Selection Edit::GetSurroundingTextSelection() const
|
|
{
|
|
return GetSelection();
|
|
}
|
|
|
|
bool Edit::DeleteSurroundingText(const Selection& rSelection)
|
|
{
|
|
SetSelection(rSelection);
|
|
DeleteSelected();
|
|
// maybe we should update mpIMEInfos here
|
|
return true;
|
|
}
|
|
|
|
FactoryFunction Edit::GetUITestFactory() const
|
|
{
|
|
return EditUIObject::create;
|
|
}
|
|
|
|
|
|
void Edit::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
|
|
{
|
|
Control::DumpAsPropertyTree(rJsonWriter);
|
|
|
|
if (!maPlaceholderText.isEmpty())
|
|
rJsonWriter.put("placeholder", maPlaceholderText);
|
|
|
|
if (IsPassword())
|
|
rJsonWriter.put("password", true);
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|