/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ /* * 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 .
*/
if (comphelper::LibreOfficeKit::isActive())
{ // In the future we might send the payload as well.
SfxLokHelper::notifyAllViews(LOK_CALLBACK_CLIPBOARD_CHANGED, ""_ostr);
}
}
void SAL_CALL SfxClipboardChangeListener::disposing( const lang::EventObject& /*rEventObject*/ )
{ // Either clipboard or ViewShell is going to be destroyed -> no interest in listening anymore
uno::Reference< lang::XComponent > xCtrl( m_xCtrl );
uno::Reference< datatransfer::clipboard::XClipboardNotifier > xNotify( m_xClpbrdNtfr );
uno::Reference< datatransfer::clipboard::XClipboardListener > xThis( static_cast< datatransfer::clipboard::XClipboardListener* >( this )); if ( xCtrl.is() )
xCtrl->removeEventListener( uno::Reference < lang::XEventListener > ( static_cast < lang::XEventListener* >( this ))); if ( xNotify.is() )
xNotify->removeClipboardListener( xThis );
// Make asynchronous call to avoid locking SolarMutex which is the // root for many deadlocks, especially in conjunction with the "Windows" // based single thread apartment clipboard code!
AsyncExecuteInfo* pInfo = new AsyncExecuteInfo( ASYNCEXECUTE_CMD_DISPOSING, this ); if (!Application::PostUserEvent( LINK( nullptr, SfxClipboardChangeListener, AsyncExecuteHdl_Impl ), pInfo )) delete pInfo;
}
void SAL_CALL SfxClipboardChangeListener::changedContents( const datatransfer::clipboard::ClipboardEvent& )
{ // Make asynchronous call to avoid locking SolarMutex which is the // root for many deadlocks, especially in conjunction with the "Windows" // based single thread apartment clipboard code!
AsyncExecuteInfo* pInfo = new AsyncExecuteInfo( ASYNCEXECUTE_CMD_CHANGEDCONTENTS, this); if (!Application::PostUserEvent( LINK( nullptr, SfxClipboardChangeListener, AsyncExecuteHdl_Impl ), pInfo )) delete pInfo;
}
// Put in rAncestorList all ancestors of xTable up to xAncestorTable or // up to the first not-a-table ancestor if xAncestorTable is not an ancestor. // xTable is included in the list, xAncestorTable is not included. // The list is ordered from the ancient ancestor to xTable. // Return true if xAncestorTable is an ancestor of xTable. bool getAncestorList(XAccessibleTableList& rAncestorList, const uno::Reference<accessibility::XAccessibleTable>& xTable, const uno::Reference<accessibility::XAccessibleTable>& xAncestorTable = uno::Reference<accessibility::XAccessibleTable>())
{
uno::Reference<accessibility::XAccessibleTable> xCurrentTable = xTable; while (xCurrentTable.is() && xCurrentTable != xAncestorTable)
{
rAncestorList.push_front(xCurrentTable);
if (stateSet == 0) return"INVALID";
::sal_Int64 state = 1;
std::string s; for (int i = 0; i < 35; ++i)
{ if (stateSet & state)
{
s += states[i];
s += "|";
}
state <<= 1;
} return s;
}
// notifyEditingInSelectionState // Used for notifying when editing becomes active/disabled for a shape // bParagraph: should we append currently focused paragraph ? // The problem is that the initially focused paragraph could not be the one user has clicked on, // when there are more than a single paragraph. // So in some case sending the focused paragraph could be misleading. void LOKDocumentFocusListener::notifyEditingInSelectionState(bool bParagraph)
{
aboutView("LOKDocumentFocusListener::notifyEditingInSelectionState", this, m_pViewShell);
/// notifyFocusedParagraphChanged // // Notify content, caret position and text selection start/end for the focused paragraph // in current view. // For focused we don't mean to be necessarily the currently focused accessibility node. // It's enough that the caret is present in the paragraph (position != -1). // In fact each view has its own accessibility node per each text paragraph. // Anyway there can be only one focused accessibility node at time. // So when text changes are performed in one view, both accessibility nodes emit // a text changed event, anyway only the accessibility node belonging to the view // where the text change has occurred is the focused one. // // force: when true update the clipboard content even if client is composing. // // Usually when editing on the client involves composing the clipboard area updating // is skipped until the composition is over. // On the contrary the composition would be aborted, making dictation not possible. // Anyway when the text change has been performed by another view we are in due // to update the clipboard content even if the user is in the middle of a composition. void LOKDocumentFocusListener::notifyFocusedParagraphChanged(bool force)
{
aboutView("LOKDocumentFocusListener::notifyFocusedParagraphChanged", this, m_pViewShell);
std::string aPayload;
paragraphPropertiesToJson(aPayload, force); if (m_pViewShell)
{
aboutParagraph("LOKDocumentFocusListener::notifyFocusedParagraphChanged",
m_sFocusedParagraph, m_nCaretPosition,
m_nSelectionStart, m_nSelectionEnd, m_nListPrefixLength, force);
void LOKDocumentFocusListener::disposing( const lang::EventObject& aEvent )
{ // Unref the object here, but do not remove as listener since the object // might no longer be in a state that safely allows this. if( aEvent.Source.is() )
m_aRefList.erase(aEvent.Source);
bool bNotify = false; // If caret is present inside the paragraph (pos != -1), it means that paragraph has focus in the current view.
sal_Int32 nCaretPosition = xAccText->getCaretPosition(); if (nCaretPosition >= 0)
{
OUString sText = xAccText->getText();
m_nCaretPosition = nCaretPosition;
m_nSelectionStart = xAccText->getSelectionStart();
m_nSelectionEnd = xAccText->getSelectionEnd();
m_nListPrefixLength = getListPrefixSize(xAccText);
// Inside a text shape when there is no selection, selection-start and selection-end are // set to current caret position instead of -1. Moreover, inside a text shape pressing // delete or backspace with an empty selection really deletes text and not only the empty // selection as it occurs in a text paragraph in Writer. // So whenever selection-start == selection-end, and we are inside a shape we need // to set these parameters to -1 in order to have the client to handle delete and // backspace properly. if (m_nSelectionStart == m_nSelectionEnd && m_nSelectionStart != -1)
{
uno::Reference<accessibility::XAccessibleContext> xContext(xAccText, uno::UNO_QUERY);
sal_Int16 nParentRole = getParentRole(xContext); if (nParentRole == accessibility::AccessibleRole::SHAPE ||
nParentRole == accessibility::AccessibleRole::TEXT_FRAME) // spreadsheet cell editing
m_nSelectionStart = m_nSelectionEnd = -1;
}
// In case only caret position or text selection are different we can rely on specific events. if (m_sFocusedParagraph != sText)
{
m_sFocusedParagraph = sText;
bNotify = true;
}
} else
{
SAL_WARN("lok.a11y", "LOKDocumentFocusListener::updateParagraphInfo: skipped since no caret is present");
}
// For a presentation document when an accessible event of type SELECTION_CHANGED_XXX occurs // the selected (or unselected) object is put in NewValue, instead for a text document // the selected object is put in Source. // The following function helps to retrieve the selected object independently on where it has been put.
uno::Reference< accessibility::XAccessible >
LOKDocumentFocusListener::getSelectedObject(const accessibility::AccessibleEventObject& aEvent) const
{
uno::Reference< accessibility::XAccessible > xSelectedObject; if (isText(m_nDocumentType))
{
xSelectedObject.set(aEvent.Source, uno::UNO_QUERY);
} else
{
aEvent.NewValue >>= xSelectedObject;
} return xSelectedObject;
}
void LOKDocumentFocusListener::onShapeSelectionChanged( const uno::Reference<accessibility::XAccessible>& xSelectedObject, const OUString& sAction)
{ // when a shape is selected or unselected we could need to notify that text content editing // is no more active, that allows on the client side to prevent default input.
resetParagraphInfo(); if (m_bIsEditingInSelection)
{
m_bIsEditingInSelection = false;
notifyEditingInSelectionState();
}
notifySelectionChanged(xSelectedObject, sAction);
}
if (m_xLastTable.is())
{ if (xTable != m_xLastTable)
{ // do we get in one or more nested tables ? // check if xTable is a descendant of m_xLastTable
XAccessibleTableList newTableAncestorList; bool isLastAncestorOfNew = getAncestorList(newTableAncestorList, xTable, m_xLastTable); bool isNewAncestorOfLast = false; if (!isLastAncestorOfNew)
{ // do we get out of one or more nested tables ? // check if m_xLastTable is a descendant of xTable
XAccessibleTableList lastTableAncestorList;
isNewAncestorOfLast = getAncestorList(lastTableAncestorList, m_xLastTable, xTable); // we have to notify "out of table" for all m_xLastTable ancestors up to xTable // or the first not-a-table ancestor
nOutCount = lastTableAncestorList.size();
} if (isLastAncestorOfNew || !isNewAncestorOfLast)
{ // we have to notify row/col count for all xTable ancestors starting from the ancestor // which is a child of m_xLastTable (isLastAncestorOfNew) or the first not-a-table ancestor for (constauto& ancestor: newTableAncestorList)
{
TableSizeType aTableSize{ancestor->getAccessibleRowCount(),
ancestor->getAccessibleColumnCount()};
aInList.push_back(aTableSize);
}
}
}
} else
{ // cursor was not inside any table and gets inside one or more tables // we have to notify row/col count for all xTable ancestors starting from first not-a-table ancestor
XAccessibleTableList newTableAncestorList;
getAncestorList(newTableAncestorList, xTable); for (constauto& ancestor: newTableAncestorList)
{
TableSizeType aTableSize{ancestor->getAccessibleRowCount(),
ancestor->getAccessibleColumnCount()};
aInList.push_back(aTableSize);
}
}
// we have to notify current row/col of xTable and related row/col span
sal_Int32 nRow = xTable->getAccessibleRow(nChildIndex);
sal_Int32 nCol = xTable->getAccessibleColumn(nChildIndex);
sal_Int32 nRowSpan = xTable->getAccessibleRowExtentAt(nRow, nCol);
sal_Int32 nColSpan = xTable->getAccessibleColumnExtentAt(nRow, nCol);
// check validity
uno::Reference< XAccessible > xAccessibleObject = getAccessible(aEvent); if (!xAccessibleObject.is()) return;
uno::Reference<XAccessibleContext> xContext(aEvent.Source, uno::UNO_QUERY); if (!xContext) return;
sal_Int16 nRole = xContext->getAccessibleRole();
if (nRole == AccessibleRole::PARAGRAPH)
{
uno::Reference<XAccessibleText> xAccText(xAccessibleObject, uno::UNO_QUERY); if (!xAccText.is()) return;
switch (nState)
{ case AccessibleStateType::ACTIVE:
{ if (!m_bIsEditingInSelection && hasToBeActiveForEditing(getParentRole(xContext)))
{
m_bIsEditingInSelection = true;
} break;
} case AccessibleStateType::FOCUSED:
{ if (m_bIsEditingInSelection && m_xSelectedObject.is())
{
updateParagraphInfo(xAccText, true, "STATE_CHANGED: FOCUSED");
notifyEditingInSelectionState(getAccessibleSiblingCount(xContext) == 0);
notifyFocusedParagraphChanged(true); // we clear selected object so when editing is over but shape is // still selected, the selection event is notified the same to the client
m_xSelectedObject.clear(); return;
} if (isText(m_nDocumentType))
{ // check if we are inside a table: in case notify table and current cell info bool isInsideTable = false;
uno::Reference<XAccessibleTable> xTable;
sal_Int64 nChildIndex = 0;
lookForParentTable(xContext, xTable, nChildIndex); if (xTable.is())
{
onFocusedParagraphInWriterTable(xTable, nChildIndex, xAccText);
isInsideTable = true;
} // paragraph is not inside any table if (!isInsideTable)
{ if (m_xLastTable.is())
{ // we get out one or more tables // we have to notify "out of table" for all m_xLastTable ancestors // up to the first not-a-table ancestor
XAccessibleTableList lastTableAncestorList;
getAncestorList(lastTableAncestorList, m_xLastTable);
sal_Int32 nOutCount = lastTableAncestorList.size(); // no more inside a table
m_xLastTable.clear(); // notify
std::vector<TableSizeType> aInList;
updateParagraphInfo(xAccText, false, "STATE_CHANGED: FOCUSED");
notifyFocusedCellChanged(nOutCount, aInList, -1, -1, 1, 1);
} else
{
updateAndNotifyParagraph(xAccText, false, "STATE_CHANGED: FOCUSED");
}
}
} elseif (isSpreadsheet(m_nDocumentType))
{ if (m_bIsEditingCell)
{ if (!hasState(aEvent, AccessibleStateType::ACTIVE))
{
SAL_WARN("lok.a11y", "LOKDocumentFocusListener::notifyEvent: FOCUSED: " "cell not ACTIVE for editing yet"); return;
} elseif (m_xSelectedObject.is())
{
updateParagraphInfo(xAccText, true, "STATE_CHANGED: ACTIVE");
notifyEditingInSelectionState(getAccessibleSiblingCount(xContext) == 0);
notifyFocusedParagraphChanged(true);
m_xSelectedObject.clear(); return;
}
if (nNewPos >= 0)
{
SAL_INFO("lok.a11y", "LOKDocumentFocusListener::notifyEvent: CARET_CHANGED: " "new pos: " << nNewPos << ", nOldPos: " << nOldPos);
uno::Reference<XAccessibleText> xAccText(getAccessible(aEvent), uno::UNO_QUERY); if (xAccText.is())
{
m_nCaretPosition = nNewPos; // Let's say we are in the following case: 'Hello wor|ld', // where '|' is the cursor position for the current view. // Suppose that in another view it's typed <enter> soon before 'world'. // Now the new paragraph content and caret position is: 'wor|ld'. // Anyway no new paragraph focused event is emitted for current view. // Only a new caret position event is emitted. // So we could need to notify a new focused paragraph changed message. if (!isFocused(aEvent))
{ if (updateParagraphInfo(xAccText, false, "CARET_CHANGED"))
notifyFocusedParagraphChanged(true);
} else
{
notifyCaretChanged();
}
aboutParagraph("LOKDocumentFocusListener::notifyEvent: CARET_CHANGED", xAccText);
}
} break;
} case AccessibleEventId::TEXT_CHANGED:
{
TextSegment aDeletedText;
TextSegment aInsertedText;
// When the change has been performed in another view we need to force // paragraph content updating on the client, even if current editing involves composing. // We make a guess that if the paragraph accessibility node is not focused, // it means that the text change has been performed in another view.
updateAndNotifyParagraph(xAccText, !isFocused(aEvent), "TEXT_CHANGED");
break;
} case AccessibleEventId::TEXT_SELECTION_CHANGED:
{ if (!isFocused(aEvent))
{
SAL_WARN("lok.a11y", "LOKDocumentFocusListener::notifyEvent: TEXT_SELECTION_CHANGED: " "skip non focused paragraph"); return;
}
uno::Reference<XAccessibleText> xAccText(getAccessible(aEvent), uno::UNO_QUERY); if (xAccText.is())
{ // We send a message to client also when start/end are -1, in this way the client knows // if a text selection object exists or not. That's needed because of the odd behavior // occurring when <backspace>/<delete> are hit and a text selection is empty, // but it still exists. // Such keys delete the empty selection instead of the previous/next char.
updateParagraphInfo(xAccText, false, "TEXT_SELECTION_CHANGED");
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.