blob: 77a168a99aab741502fb49b7176277d948ef2430 [file] [log] [blame]
/**************************************************************
*
* 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
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_sc.hxx"
#include "fieldwnd.hxx"
#include <tools/debug.hxx>
#include <vcl/decoview.hxx>
#include <vcl/help.hxx>
#include <vcl/svapp.hxx>
#include <vcl/virdev.hxx>
#include "pvlaydlg.hxx"
#include "AccessibleDataPilotControl.hxx"
#include "scresid.hxx"
#include "sc.hrc"
// ============================================================================
using namespace ::com::sun::star;
using ::rtl::OUString;
// ============================================================================
namespace {
/** Line width for insertion cursor in pixels. */
const long CURSOR_WIDTH = 3;
/** Number of tracking events before auto scrolling starts. */
const size_t INITIAL_TRACKING_DELAY = 20;
} // namespace
// ============================================================================
ScPivotFieldWindow::ScPivotWindowField::ScPivotWindowField( const ScDPLabelData& rLabelData ) :
maFuncData( rLabelData.mnCol, rLabelData.mnFuncMask ),
maFieldName( rLabelData.getDisplayName() )
{
}
ScPivotFieldWindow::ScPivotWindowField::ScPivotWindowField( ScPivotLayoutDlg& rDialog, const ScPivotField& rField, bool bDataWindow ) :
maFuncData( rField.nCol, rField.nFuncMask, rField.maFieldRef )
{
InitFieldName( rDialog, bDataWindow );
}
ScPivotFieldWindow::ScPivotWindowField::ScPivotWindowField( ScPivotLayoutDlg& rDialog, const ScPivotFuncData& rFuncData, bool bDataWindow ) :
maFuncData( rFuncData )
{
InitFieldName( rDialog, bDataWindow );
}
void ScPivotFieldWindow::ScPivotWindowField::InitFieldName( ScPivotLayoutDlg& rDialog, bool bDataWindow )
{
if( maFuncData.mnCol != PIVOT_DATA_FIELD )
{
ScDPLabelData* pLabelData = rDialog.GetLabelData( maFuncData.mnCol );
DBG_ASSERT( pLabelData, "ScPivotWindowField::InitFieldName - no label data found" );
if( pLabelData )
{
if( bDataWindow )
{
// write original nFuncMask to label data
pLabelData->mnFuncMask = maFuncData.mnFuncMask;
// GetFuncString() modifies nFuncMask (e.g. auto to sum or count)
maFieldName = rDialog.GetFuncString( maFuncData.mnFuncMask, pLabelData->mbIsValue );
}
else
maFieldName = OUString(); // #i118111# don't append to previous string
maFieldName += pLabelData->getDisplayName();
}
}
}
// ============================================================================
ScPivotFieldWindow::ScPivotFieldWindow( ScPivotLayoutDlg* pDialog, const ResId& rResId,
ScrollBar& rScrollBar, FixedText* pFtCaption, const OUString& rName,
ScPivotFieldType eFieldType, const sal_Char* pcHelpId, PointerStyle eDropPointer,
size_t nColCount, size_t nRowCount, long nFieldWidthFactor, long nSpaceSize ) :
Control( pDialog, rResId ),
mpDialog( pDialog ),
mpAccessible( 0 ),
mrScrollBar( rScrollBar ),
mpFtCaption( pFtCaption ),
maName( rName ),
meFieldType( eFieldType ),
meDropPointer( eDropPointer ),
mnColCount( nColCount ),
mnRowCount( nRowCount ),
mnFirstVisIndex( 0 ),
mnSelectIndex( 0 ),
mnInsCursorIndex( PIVOTFIELD_INVALID ),
mnOldFirstVisIndex( 0 ),
mnAutoScrollDelay( 0 ),
mbVertical( eFieldType == PIVOTFIELDTYPE_SELECT ),
mbIsTrackingSource( false )
{
SetHelpId( pcHelpId );
mnLineSize = mbVertical ? mnRowCount : mnColCount;
mnPageSize = mnColCount * mnRowCount;
// a single field is 36x12 appfont units
maFieldSize = LogicToPixel( Size( 36, 12 ), MapMode( MAP_APPFONT ) );
maFieldSize.Width() *= nFieldWidthFactor;
maSpaceSize = LogicToPixel( Size( nSpaceSize, nSpaceSize ), MapMode( MAP_APPFONT ) );
// set window size
long nWinWidth = static_cast< long >( mnColCount * maFieldSize.Width() + (mnColCount - 1) * maSpaceSize.Width() );
long nWinHeight = static_cast< long >( mnRowCount * maFieldSize.Height() + (mnRowCount - 1) * maSpaceSize.Height() );
SetSizePixel( Size( nWinWidth, nWinHeight ) );
// scroll bar
Point aScrollBarPos = GetPosPixel();
Size aScrollBarSize( nWinWidth, nWinHeight );
if( mbVertical )
{
aScrollBarPos.Y() += nWinHeight + maSpaceSize.Height();
aScrollBarSize.Height() = GetSettings().GetStyleSettings().GetScrollBarSize();
}
else
{
aScrollBarPos.X() += nWinWidth + maSpaceSize.Width();
aScrollBarSize.Width() = GetSettings().GetStyleSettings().GetScrollBarSize();
}
mrScrollBar.SetPosSizePixel( aScrollBarPos, aScrollBarSize );
mrScrollBar.SetLineSize( 1 );
mrScrollBar.SetPageSize( static_cast< long >( mbVertical ? mnColCount : mnRowCount ) );
mrScrollBar.SetVisibleSize( static_cast< long >( mbVertical ? mnColCount : mnRowCount ) );
mrScrollBar.SetScrollHdl( LINK( this, ScPivotFieldWindow, ScrollHdl ) );
mrScrollBar.SetEndScrollHdl( LINK( this, ScPivotFieldWindow, ScrollHdl ) );
}
ScPivotFieldWindow::~ScPivotFieldWindow()
{
::rtl::Reference< ScAccessibleDataPilotControl > xAcc = GetAccessibleControl();
if( xAcc.is() )
xAcc->dispose();
}
void ScPivotFieldWindow::ReadDataLabels( const ScDPLabelDataVector& rLabels )
{
maFields.clear();
maFields.reserve( rLabels.size() );
for( ScDPLabelDataVector::const_iterator aIt = rLabels.begin(), aEnd = rLabels.end(); aIt != aEnd; ++aIt )
{
ScPivotWindowField aField( *aIt );
if( aField.maFieldName.getLength() > 0 )
maFields.push_back( aField );
}
Invalidate();
}
void ScPivotFieldWindow::ReadPivotFields( const ScPivotFieldVector& rPivotFields )
{
maFields.clear();
maFields.reserve( rPivotFields.size() );
for( ScPivotFieldVector::const_iterator aIt = rPivotFields.begin(), aEnd = rPivotFields.end(); aIt != aEnd; ++aIt )
{
ScPivotWindowField aField( *mpDialog, *aIt, meFieldType == PIVOTFIELDTYPE_DATA );
if( aField.maFieldName.getLength() > 0 )
maFields.push_back( aField );
}
Invalidate();
}
void ScPivotFieldWindow::WriteFieldNames( ScDPNameVec& rFieldNames ) const
{
rFieldNames.clear();
rFieldNames.reserve( maFields.size() );
// do not use the names stored in maFields, but generate plain display names from label data
for( ScPivotWindowFieldVector::const_iterator aIt = maFields.begin(), aEnd = maFields.end(); aIt != aEnd; ++aIt )
{
if( ScDPLabelData* pLabelData = mpDialog->GetLabelData( aIt->maFuncData.mnCol ) )
{
OUString aDisplayName = pLabelData->getDisplayName();
if( aDisplayName.getLength() > 0 )
rFieldNames.push_back( aDisplayName );
}
}
}
void ScPivotFieldWindow::WritePivotFields( ScPivotFieldVector& rPivotFields ) const
{
rPivotFields.resize( maFields.size() );
ScPivotFieldVector::iterator aOutIt = rPivotFields.begin();
for( ScPivotWindowFieldVector::const_iterator aIt = maFields.begin(), aEnd = maFields.end(); aIt != aEnd; ++aIt, ++aOutIt )
{
aOutIt->nCol = aIt->maFuncData.mnCol;
aOutIt->nFuncMask = aIt->maFuncData.mnFuncMask;
aOutIt->maFieldRef = aIt->maFuncData.maFieldRef;
}
}
OUString ScPivotFieldWindow::GetDescription() const
{
switch( meFieldType )
{
case PIVOTFIELDTYPE_COL: return String( ScResId( STR_ACC_DATAPILOT_COL_DESCR ) );
case PIVOTFIELDTYPE_ROW: return String( ScResId( STR_ACC_DATAPILOT_ROW_DESCR ) );
case PIVOTFIELDTYPE_DATA: return String( ScResId( STR_ACC_DATAPILOT_DATA_DESCR ) );
case PIVOTFIELDTYPE_SELECT: return String( ScResId( STR_ACC_DATAPILOT_SEL_DESCR ) );
default:;
}
return OUString();
}
OUString ScPivotFieldWindow::GetFieldText( size_t nFieldIndex ) const
{
return (nFieldIndex < maFields.size()) ? maFields[ nFieldIndex ].maFieldName : OUString();
}
ScPivotFuncDataEntry ScPivotFieldWindow::FindFuncDataByCol( SCCOL nCol ) const
{
for( ScPivotWindowFieldVector::const_iterator aIt = maFields.begin(), aEnd = maFields.end(); aIt != aEnd; ++aIt )
if( aIt->maFuncData.mnCol == nCol )
return ScPivotFuncDataEntry( &aIt->maFuncData, aIt - maFields.begin() );
return ScPivotFuncDataEntry( 0, PIVOTFIELD_INVALID );
}
Point ScPivotFieldWindow::GetFieldPosition( size_t nFieldIndex ) const
{
long nRelIndex = static_cast< long >( nFieldIndex ) - mnFirstVisIndex;
long nCol = static_cast< long >( mbVertical ? (nRelIndex / mnRowCount) : (nRelIndex % mnColCount) );
long nRow = static_cast< long >( mbVertical ? (nRelIndex % mnRowCount) : (nRelIndex / mnColCount) );
return Point( nCol * (maFieldSize.Width() + maSpaceSize.Width()), nRow * (maFieldSize.Height() + maSpaceSize.Height()) );
}
size_t ScPivotFieldWindow::GetFieldIndex( const Point& rWindowPos ) const
{
if( (rWindowPos.X() >= 0) && (rWindowPos.Y() >= 0) )
{
long nGridWidth = maFieldSize.Width() + maSpaceSize.Width();
long nGridHeight = maFieldSize.Height() + maSpaceSize.Height();
size_t nCol = static_cast< size_t >( rWindowPos.X() / nGridWidth );
size_t nRow = static_cast< size_t >( rWindowPos.Y() / nGridHeight );
if( (nCol < mnColCount) && (nRow < mnRowCount) )
{
long nColOffset = rWindowPos.X() % nGridWidth;
long nRowOffset = rWindowPos.Y() % nGridHeight;
// check that passed position is not in the space between the fields
if( (nColOffset < maFieldSize.Width()) && (nRowOffset < maFieldSize.Height()) )
{
size_t nFieldIndex = mnFirstVisIndex + (mbVertical ? (nCol * mnRowCount + nRow) : (nRow * mnColCount + nCol));
return (nFieldIndex < maFields.size()) ? nFieldIndex : PIVOTFIELD_INVALID;
}
}
}
return PIVOTFIELD_INVALID;
}
size_t ScPivotFieldWindow::GetDropIndex( const Point& rWindowPos ) const
{
if( (rWindowPos.X() >= 0) && (rWindowPos.Y() >= 0) )
{
long nGridWidth = maFieldSize.Width() + maSpaceSize.Width();
long nGridHeight = maFieldSize.Height() + maSpaceSize.Height();
size_t nCol = static_cast< size_t >( rWindowPos.X() / nGridWidth );
size_t nRow = static_cast< size_t >( rWindowPos.Y() / nGridHeight );
if( (nCol < mnColCount) && (nRow < mnRowCount) )
{
size_t nFieldIndex = mnFirstVisIndex + (mbVertical ? (nCol * mnRowCount + nRow) : (nRow * mnColCount + nCol));
long nColOffset = rWindowPos.X() % nGridWidth;
long nRowOffset = rWindowPos.Y() % nGridHeight;
// take next field, if position is in right/lower third
if( (mnColCount == 1) ? (nRowOffset * 3 > nGridHeight * 2) : (nColOffset * 3 > nGridWidth * 2) )
++nFieldIndex;
return ::std::min( nFieldIndex, maFields.size() );
}
}
return maFields.size();
}
void ScPivotFieldWindow::GrabFocusAndSelect( size_t nSelectIndex )
{
if( !HasFocus() ) GrabFocus();
MoveSelection( nSelectIndex );
}
void ScPivotFieldWindow::SelectNextField()
{
MoveSelection( NEXT_FIELD );
}
void ScPivotFieldWindow::InsertField( size_t nInsertIndex, const ScPivotFuncData& rFuncData )
{
if( (meFieldType != PIVOTFIELDTYPE_SELECT) && (nInsertIndex <= maFields.size()) )
{
size_t nFieldIndex = FindFuncDataByCol( rFuncData.mnCol ).second;
if( nFieldIndex < maFields.size() )
{
// field exists already in this window, move it to the specified position
MoveField( nFieldIndex, nInsertIndex );
}
else
{
// insert the field into the vector and notify accessibility object
ScPivotWindowField aField( *mpDialog, rFuncData, meFieldType == PIVOTFIELDTYPE_DATA );
if( aField.maFieldName.getLength() > 0 )
{
InsertFieldUnchecked( nInsertIndex, aField );
// adjust selection and scroll position
MoveSelection( nInsertIndex );
Invalidate();
}
}
}
}
bool ScPivotFieldWindow::RemoveField( size_t nRemoveIndex )
{
if( (meFieldType != PIVOTFIELDTYPE_SELECT) && (nRemoveIndex < maFields.size()) )
{
// remove the field from the vector and notify accessibility object
RemoveFieldUnchecked( nRemoveIndex );
// adjust selection and scroll position, if last field is removed
if( !maFields.empty() )
MoveSelection( (mnSelectIndex < maFields.size()) ? mnSelectIndex : (maFields.size() - 1) );
Invalidate();
return true;
}
return false;
}
bool ScPivotFieldWindow::MoveField( size_t nFieldIndex, size_t nInsertIndex )
{
/* If field is moved behind current position, insertion index needs to be
adjusted, because the field is first removed from the vector. This is
done before nFieldIndex and nInsertIndex are checked for equality, to
catch the cases "move before ourselves" and "move bedind ourselves"
which are both no-ops. */
if( nFieldIndex < nInsertIndex )
--nInsertIndex;
if( (meFieldType != PIVOTFIELDTYPE_SELECT) && (nFieldIndex != nInsertIndex) && (nFieldIndex < maFields.size()) && (nInsertIndex < maFields.size()) )
{
// move the field in the vector and notify accessibility object
ScPivotWindowField aField = maFields[ nFieldIndex ];
RemoveFieldUnchecked( nFieldIndex );
InsertFieldUnchecked( nInsertIndex, aField );
// adjust selection and scroll position
MoveSelection( nInsertIndex );
Invalidate();
return true;
}
return false;
}
const ScPivotFuncData* ScPivotFieldWindow::GetSelectedFuncData() const
{
return (mnSelectIndex < maFields.size()) ? &maFields[ mnSelectIndex ].maFuncData : 0;
}
void ScPivotFieldWindow::ModifySelectedField( const ScPivotFuncData& rFuncData )
{
if( mnSelectIndex < maFields.size() )
{
maFields[ mnSelectIndex ].maFuncData = rFuncData;
maFields[ mnSelectIndex ].InitFieldName( *mpDialog, meFieldType == PIVOTFIELDTYPE_DATA );
Invalidate();
}
}
bool ScPivotFieldWindow::RemoveSelectedField()
{
return RemoveField( mnSelectIndex );
}
bool ScPivotFieldWindow::MoveSelectedField( size_t nInsertIndex )
{
return MoveField( mnSelectIndex, nInsertIndex );
}
void ScPivotFieldWindow::NotifyStartTracking()
{
// rescue old scrolling index, to be able to restore it when tracking is cancelled
mnOldFirstVisIndex = mnFirstVisIndex;
}
void ScPivotFieldWindow::NotifyTracking( const Point& rWindowPos )
{
size_t nFieldIndex = GetDropIndex( rWindowPos );
// insertion index changed: draw new cursor and exit
if( nFieldIndex != mnInsCursorIndex )
{
mnInsCursorIndex = nFieldIndex;
mnAutoScrollDelay = INITIAL_TRACKING_DELAY;
Invalidate();
return;
}
// insertion index unchanged: countdown for auto scrolling
if( mnAutoScrollDelay > 0 )
{
--mnAutoScrollDelay;
return;
}
// check if tracking happens on first or last field
long nScrollDelta = 0;
if( (mnInsCursorIndex > 0) && (mnInsCursorIndex == mnFirstVisIndex) )
nScrollDelta = -static_cast< long >( mnLineSize );
else if( (mnInsCursorIndex < maFields.size()) && (mnInsCursorIndex == mnFirstVisIndex + mnPageSize) )
nScrollDelta = static_cast< long >( mnLineSize );
if( nScrollDelta != 0 )
{
// update mnInsCursorIndex, so it will be drawn at the same position after scrolling
mnInsCursorIndex += nScrollDelta;
mnFirstVisIndex += nScrollDelta;
// delay auto scroll by line size, to slow down scrolling in column/page windows
mnAutoScrollDelay = mnLineSize - 1;
Invalidate();
}
}
void ScPivotFieldWindow::NotifyEndTracking( ScPivotFieldEndTracking eEndType )
{
if( eEndType != ENDTRACKING_DROP )
mnFirstVisIndex = mnOldFirstVisIndex;
if( eEndType != ENDTRACKING_SUSPEND )
{
mnOldFirstVisIndex = PIVOTFIELD_INVALID;
mbIsTrackingSource = false;
}
mnInsCursorIndex = PIVOTFIELD_INVALID;
Invalidate();
}
// protected ------------------------------------------------------------------
void ScPivotFieldWindow::Paint( const Rectangle& /*rRect*/ )
{
// prepare a virtual device for buffered painting
VirtualDevice aVirDev;
// #i97623# VirtualDevice is always LTR on construction while other windows derive direction from parent
aVirDev.EnableRTL( IsRTLEnabled() );
aVirDev.SetMapMode( MAP_PIXEL );
aVirDev.SetOutputSizePixel( GetSizePixel() );
Font aFont = GetFont();
aFont.SetTransparent( true );
aVirDev.SetFont( aFont );
// draw the background and all fields
DrawBackground( aVirDev );
for( size_t nFieldIndex = mnFirstVisIndex, nEndIndex = mnFirstVisIndex + mnPageSize; nFieldIndex < nEndIndex; ++nFieldIndex )
DrawField( aVirDev, nFieldIndex );
DrawInsertionCursor( aVirDev );
DrawBitmap( Point( 0, 0 ), aVirDev.GetBitmap( Point( 0, 0 ), GetSizePixel() ) );
// draw field text focus
if( HasFocus() && (mnSelectIndex < maFields.size()) && (mnFirstVisIndex <= mnSelectIndex) && (mnSelectIndex < mnFirstVisIndex + mnPageSize) )
{
long nFieldWidth = maFieldSize.Width();
long nSelectionWidth = Min( GetTextWidth( maFields[ mnSelectIndex ].maFieldName ) + 4, nFieldWidth - 6 );
Rectangle aSelection(
GetFieldPosition( mnSelectIndex ) + Point( (nFieldWidth - nSelectionWidth) / 2, 3 ),
Size( nSelectionWidth, maFieldSize.Height() - 6 ) );
InvertTracking( aSelection, SHOWTRACK_SMALL | SHOWTRACK_WINDOW );
}
// update scrollbar
size_t nFieldCount = maFields.size();
/* Already show the scrollbar if window is full but no fields are hidden
(yet). This gives the user the hint that it is now possible to add more
fields to the window. */
mrScrollBar.Show( nFieldCount >= mnPageSize );
mrScrollBar.Enable( nFieldCount > mnPageSize );
if( mrScrollBar.IsVisible() )
{
mrScrollBar.SetRange( Range( 0, static_cast< long >( (nFieldCount - 1) / mnLineSize + 1 ) ) );
mrScrollBar.SetThumbPos( static_cast< long >( mnFirstVisIndex / mnLineSize ) );
}
/* Exclude empty fields from tab chain, but do not disable them. They need
to be enabled because they still act as target for field movement via
keyboard shortcuts. */
WinBits nMask = ~(WB_TABSTOP | WB_NOTABSTOP);
SetStyle( (GetStyle() & nMask) | (IsEmpty() ? WB_NOTABSTOP : WB_TABSTOP) );
}
void ScPivotFieldWindow::StateChanged( StateChangedType nStateChange )
{
Control::StateChanged( nStateChange );
if( nStateChange == STATE_CHANGE_INITSHOW )
{
/* After the fixed text associated to this control has received its
unique mnemonic from VCL dialog initialization code, put this text
into the field windows.
#124828# Hiding the FixedTexts and clearing the tab stop style bits
has to be done after assigning the mnemonics, but Paint() is too
late, because the test tool may send key events to the dialog when
it isn't visible. Mnemonics are assigned in Dialog::StateChanged()
for STATE_CHANGE_INITSHOW, so this can be done immediately
afterwards. */
if( mpFtCaption )
{
SetText( mpFtCaption->GetText() );
mpFtCaption->Hide();
}
}
}
void ScPivotFieldWindow::DataChanged( const DataChangedEvent& rDCEvt )
{
Control::DataChanged( rDCEvt );
if( (rDCEvt.GetType() == DATACHANGED_SETTINGS) && (rDCEvt.GetFlags() & SETTINGS_STYLE) )
Invalidate();
}
void ScPivotFieldWindow::KeyInput( const KeyEvent& rKEvt )
{
bool bKeyEvaluated = false;
if( !maFields.empty() )
{
const KeyCode& rKeyCode = rKEvt.GetKeyCode();
sal_uInt16 nCode = rKeyCode.GetCode();
// do not move fields in selection window
if( rKeyCode.IsMod1() && (meFieldType != PIVOTFIELDTYPE_SELECT) )
{
bKeyEvaluated = true;
switch( nCode )
{
case KEY_UP: MoveSelectedField( mbVertical ? PREV_FIELD : PREV_LINE ); break;
case KEY_DOWN: MoveSelectedField( mbVertical ? NEXT_FIELD : NEXT_LINE ); break;
case KEY_LEFT: MoveSelectedField( mbVertical ? PREV_LINE : PREV_FIELD ); break;
case KEY_RIGHT: MoveSelectedField( mbVertical ? NEXT_LINE : NEXT_FIELD ); break;
case KEY_HOME: MoveSelectedField( FIRST_FIELD ); break;
case KEY_END: MoveSelectedField( LAST_FIELD ); break;
default: bKeyEvaluated = false;
}
}
else
{
bKeyEvaluated = true;
switch( nCode )
{
case KEY_UP: MoveSelection( mbVertical ? PREV_FIELD : PREV_LINE ); break;
case KEY_DOWN: MoveSelection( mbVertical ? NEXT_FIELD : NEXT_LINE ); break;
case KEY_LEFT: MoveSelection( mbVertical ? PREV_LINE : PREV_FIELD ); break;
case KEY_RIGHT: MoveSelection( mbVertical ? NEXT_LINE : NEXT_FIELD ); break;
case KEY_PAGEUP: MoveSelection( PREV_PAGE ); break;
case KEY_PAGEDOWN: MoveSelection( NEXT_PAGE ); break;
case KEY_HOME: MoveSelection( FIRST_FIELD ); break;
case KEY_END: MoveSelection( LAST_FIELD ); break;
// delete field per DEL key - dialog needs to change focus if window becomes empty
case KEY_DELETE: RemoveSelectedField(); mpDialog->NotifyFieldRemoved( *this ); break;
default: bKeyEvaluated = false;
}
}
}
if( !bKeyEvaluated )
Control::KeyInput( rKEvt );
}
void ScPivotFieldWindow::MouseButtonDown( const MouseEvent& rMEvt )
{
if( rMEvt.IsLeft() )
{
size_t nNewSelectIndex = GetFieldIndex( rMEvt.GetPosPixel() );
if( nNewSelectIndex < maFields.size() )
{
// grabbing after GetFieldIndex() will prevent to focus empty window
GrabFocusAndSelect( nNewSelectIndex );
if( rMEvt.GetClicks() == 1 )
{
// one click: start tracking
mbIsTrackingSource = true;
mnOldFirstVisIndex = mnFirstVisIndex;
mpDialog->NotifyStartTracking( *this );
}
else
{
// two clicks: open field options dialog
mpDialog->NotifyDoubleClick( *this );
}
}
}
}
void ScPivotFieldWindow::RequestHelp( const HelpEvent& rHEvt )
{
if( (rHEvt.GetMode() & HELPMODE_QUICK) != 0 )
{
// show a tooltip with full field name, if field text is clipped
size_t nFieldIndex = GetFieldIndex( rHEvt.GetMousePosPixel() - GetPosPixel() );
if( (nFieldIndex < maFields.size()) && maFields[ nFieldIndex ].mbClipped )
{
Rectangle aRect( rHEvt.GetMousePosPixel(), GetSizePixel() );
Help::ShowQuickHelp( this, aRect, maFields[ nFieldIndex ].maFieldName );
return;
}
}
Control::RequestHelp( rHEvt );
}
void ScPivotFieldWindow::GetFocus()
{
Control::GetFocus();
Invalidate();
::rtl::Reference< ScAccessibleDataPilotControl > xAcc = GetAccessibleControl();
if( xAcc.is() )
xAcc->GotFocus();
}
void ScPivotFieldWindow::LoseFocus()
{
Control::LoseFocus();
Invalidate();
::rtl::Reference< ScAccessibleDataPilotControl > xAcc = GetAccessibleControl();
if( xAcc.is() )
xAcc->LostFocus();
}
uno::Reference< accessibility::XAccessible > ScPivotFieldWindow::CreateAccessible()
{
mpAccessible = new ScAccessibleDataPilotControl( GetAccessibleParentWindow()->GetAccessible(), this );
uno::Reference< accessibility::XAccessible > xReturn( mpAccessible );
mpAccessible->Init();
mxAccessible = xReturn;
return xReturn;
}
// private --------------------------------------------------------------------
size_t ScPivotFieldWindow::RecalcVisibleIndex( size_t nSelectIndex ) const
{
// calculate a scrolling offset that shows the selected field
size_t nNewFirstVisIndex = mnFirstVisIndex;
if( nSelectIndex < nNewFirstVisIndex )
nNewFirstVisIndex = static_cast< size_t >( (nSelectIndex / mnLineSize) * mnLineSize );
else if( nSelectIndex >= nNewFirstVisIndex + mnPageSize )
nNewFirstVisIndex = static_cast< size_t >( (nSelectIndex / mnLineSize + 1) * mnLineSize ) - mnPageSize;
// check if there are complete empty lines in the bottom/right
size_t nMaxFirstVisIndex = (maFields.size() <= mnPageSize) ? 0 : (((maFields.size() - 1) / mnLineSize + 1) * mnLineSize - mnPageSize);
return ::std::min( nNewFirstVisIndex, nMaxFirstVisIndex );
}
void ScPivotFieldWindow::SetSelectionUnchecked( size_t nSelectIndex, size_t nFirstVisIndex )
{
if( !maFields.empty() && (nSelectIndex < maFields.size()) )
{
bool bScrollPosChanged = mnFirstVisIndex != nFirstVisIndex;
bool bSelectionChanged = mnSelectIndex != nSelectIndex;
sal_Int32 nOldSelected = static_cast< sal_Int32 >( mnSelectIndex );
mnFirstVisIndex = nFirstVisIndex;
mnSelectIndex = nSelectIndex;
if( bScrollPosChanged || bSelectionChanged )
Invalidate();
// TODO: accessibility action for changed scrolling position?
// notify accessibility object about changed selection
if( bSelectionChanged && HasFocus() )
{
::rtl::Reference< ScAccessibleDataPilotControl > xAcc = GetAccessibleControl();
if( xAcc.is() )
xAcc->FieldFocusChange( nOldSelected, static_cast< sal_Int32 >( mnSelectIndex ) );
}
}
}
void ScPivotFieldWindow::MoveSelection( size_t nSelectIndex )
{
if( nSelectIndex < maFields.size() )
SetSelectionUnchecked( nSelectIndex, RecalcVisibleIndex( nSelectIndex ) );
}
void ScPivotFieldWindow::MoveSelection( MoveType eMoveType )
{
if( maFields.empty() )
return;
size_t nLastIndex = maFields.size() - 1;
size_t nNewSelectIndex = mnSelectIndex;
switch( eMoveType )
{
case PREV_FIELD:
nNewSelectIndex = (nNewSelectIndex > 0) ? (nNewSelectIndex - 1) : 0;
break;
case NEXT_FIELD:
nNewSelectIndex = (nNewSelectIndex < nLastIndex) ? (nNewSelectIndex + 1) : nLastIndex;
break;
case PREV_LINE:
nNewSelectIndex = (nNewSelectIndex > mnLineSize) ? (nNewSelectIndex - mnLineSize) : 0;
break;
case NEXT_LINE:
nNewSelectIndex = (nNewSelectIndex + mnLineSize < nLastIndex) ? (nNewSelectIndex + mnLineSize) : nLastIndex;
break;
case PREV_PAGE:
nNewSelectIndex = (nNewSelectIndex > mnPageSize) ? (nNewSelectIndex - mnPageSize) : 0;
break;
case NEXT_PAGE:
nNewSelectIndex = (nNewSelectIndex + mnPageSize < nLastIndex) ? (nNewSelectIndex + mnPageSize) : nLastIndex;
break;
case FIRST_FIELD:
nNewSelectIndex = 0;
break;
case LAST_FIELD:
nNewSelectIndex = nLastIndex;
break;
}
// SetSelectionUnchecked() redraws the control and updates the scrollbar
SetSelectionUnchecked( nNewSelectIndex, RecalcVisibleIndex( nNewSelectIndex ) );
}
void ScPivotFieldWindow::MoveSelectedField( MoveType eMoveType )
{
if( mnSelectIndex < maFields.size() )
{
// find position to insert the field by changing the selection first
size_t nOldSelectIndex = mnSelectIndex;
MoveSelection( eMoveType );
MoveField( nOldSelectIndex, (nOldSelectIndex < mnSelectIndex) ? (mnSelectIndex + 1) : mnSelectIndex );
}
}
void ScPivotFieldWindow::InsertFieldUnchecked( size_t nInsertIndex, const ScPivotWindowField& rField )
{
maFields.insert( maFields.begin() + nInsertIndex, rField );
::rtl::Reference< ScAccessibleDataPilotControl > xAcc = GetAccessibleControl();
if( xAcc.is() )
xAcc->AddField( static_cast< sal_Int32 >( nInsertIndex ) );
}
void ScPivotFieldWindow::RemoveFieldUnchecked( size_t nRemoveIndex )
{
::rtl::Reference< ScAccessibleDataPilotControl > xAcc = GetAccessibleControl();
if( xAcc.is() )
xAcc->RemoveField( static_cast< sal_Int32 >( nRemoveIndex ) );
maFields.erase( maFields.begin() + nRemoveIndex );
}
void ScPivotFieldWindow::DrawBackground( OutputDevice& rDev )
{
Size aDevSize = rDev.GetOutputSizePixel();
const StyleSettings& rStyleSett = GetSettings().GetStyleSettings();
if( meFieldType == PIVOTFIELDTYPE_SELECT )
{
rDev.SetLineColor();
rDev.SetFillColor( rStyleSett.GetFaceColor() );
rDev.DrawRect( Rectangle( Point( 0, 0 ), aDevSize ) );
}
else
{
rDev.SetLineColor( rStyleSett.GetWindowTextColor() );
rDev.SetFillColor( rStyleSett.GetWindowColor() );
rDev.DrawRect( Rectangle( Point( 0, 0 ), aDevSize ) );
/* Draw the caption text. This needs some special handling, because we
support hard line breaks here. This part will draw each line of the
text for itself. */
rDev.SetTextColor( rStyleSett.GetWindowTextColor() );
xub_StrLen nTokenCnt = GetText().GetTokenCount( '\n' );
long nY = (aDevSize.Height() - nTokenCnt * rDev.GetTextHeight()) / 2;
for( xub_StrLen nToken = 0, nStringIx = 0; nToken < nTokenCnt; ++nToken )
{
String aLine = GetText().GetToken( 0, '\n', nStringIx );
Point aLinePos( (aDevSize.Width() - rDev.GetCtrlTextWidth( aLine )) / 2, nY );
rDev.DrawCtrlText( aLinePos, aLine );
nY += rDev.GetTextHeight();
}
}
}
void ScPivotFieldWindow::DrawField( OutputDevice& rDev, size_t nFieldIndex )
{
if( (nFieldIndex < maFields.size()) && (mnFirstVisIndex <= nFieldIndex) && (nFieldIndex < mnFirstVisIndex + mnPageSize) )
{
// draw the button
Point aFieldPos = GetFieldPosition( nFieldIndex );
bool bFocus = HasFocus() && (nFieldIndex == mnSelectIndex);
DecorationView aDecoView( &rDev );
aDecoView.DrawButton( Rectangle( aFieldPos, maFieldSize ), bFocus ? BUTTON_DRAW_DEFAULT : 0 );
// #i31600# if text is too long, cut and add ellipsis
const OUString& rFullText = maFields[ nFieldIndex ].maFieldName;
OUString aClippedText = rFullText;
long nLabelWidth = rDev.GetTextWidth( rFullText );
if( (maFields[ nFieldIndex ].mbClipped = nLabelWidth + 6 > maFieldSize.Width()) == true )
{
sal_Int32 nMinLen = 0;
sal_Int32 nMaxLen = rFullText.getLength();
bool bFits = false;
do
{
sal_Int32 nCurrLen = (nMinLen + nMaxLen) / 2;
aClippedText = rFullText.copy( 0, nCurrLen ) + OUString( RTL_CONSTASCII_USTRINGPARAM( "..." ) );
nLabelWidth = rDev.GetTextWidth( aClippedText );
bFits = nLabelWidth + 6 <= maFieldSize.Width();
(bFits ? nMinLen : nMaxLen) = nCurrLen;
}
while( !bFits || (nMinLen + 1 < nMaxLen) );
}
// draw the button text
Point aLabelOffset( (maFieldSize.Width() - nLabelWidth) / 2, ::std::max< long >( (maFieldSize.Height() - rDev.GetTextHeight()) / 2, 3 ) );
rDev.SetTextColor( GetSettings().GetStyleSettings().GetButtonTextColor() );
rDev.DrawText( aFieldPos + aLabelOffset, aClippedText );
}
}
void ScPivotFieldWindow::DrawInsertionCursor( OutputDevice& rDev )
{
if( (mnInsCursorIndex <= maFields.size()) && (mnFirstVisIndex <= mnInsCursorIndex) && (mnInsCursorIndex <= mnFirstVisIndex + mnPageSize) &&
(!mbIsTrackingSource || (mnInsCursorIndex < mnSelectIndex) || (mnInsCursorIndex > mnSelectIndex + 1)) )
{
Color aTextColor = GetSettings().GetStyleSettings().GetButtonTextColor();
rDev.SetLineColor( aTextColor );
rDev.SetFillColor( aTextColor );
bool bVerticalCursor = mnColCount > 1;
long nCursorLength = bVerticalCursor ? maFieldSize.Height() : maFieldSize.Width();
bool bEndOfLastField = mnInsCursorIndex == mnFirstVisIndex + mnPageSize;
Point aMainLinePos = GetFieldPosition( bEndOfLastField ? (mnInsCursorIndex - 1) : mnInsCursorIndex );
if( bEndOfLastField )
(bVerticalCursor ? aMainLinePos.X() : aMainLinePos.Y()) += ((bVerticalCursor ? maFieldSize.Width() : maFieldSize.Height()) - CURSOR_WIDTH);
else if( (bVerticalCursor ? aMainLinePos.X() : aMainLinePos.Y()) > 0 )
(bVerticalCursor ? aMainLinePos.X() : aMainLinePos.Y()) -= ((CURSOR_WIDTH + 1) / 2);
Size aMainLineSize( bVerticalCursor ? CURSOR_WIDTH : nCursorLength, bVerticalCursor ? nCursorLength : CURSOR_WIDTH );
rDev.DrawRect( Rectangle( aMainLinePos, aMainLineSize ) );
Point aSubLinePos = aMainLinePos;
(bVerticalCursor ? aSubLinePos.X() : aSubLinePos.Y()) -= CURSOR_WIDTH;
Size aSubLineSize( bVerticalCursor ? (3 * CURSOR_WIDTH) : CURSOR_WIDTH, bVerticalCursor ? CURSOR_WIDTH : (3 * CURSOR_WIDTH) );
rDev.DrawRect( Rectangle( aSubLinePos, aSubLineSize ) );
(bVerticalCursor ? aSubLinePos.Y() : aSubLinePos.X()) += (nCursorLength - CURSOR_WIDTH);
rDev.DrawRect( Rectangle( aSubLinePos, aSubLineSize ) );
}
}
::rtl::Reference< ScAccessibleDataPilotControl > ScPivotFieldWindow::GetAccessibleControl()
{
::rtl::Reference< ScAccessibleDataPilotControl > xAccImpl;
if( mpAccessible )
{
// try to resolve the weak reference mxAccessible
uno::Reference< accessibility::XAccessible > xAcc = mxAccessible;
if( xAcc.is() )
xAccImpl.set( mpAccessible ); // the rtl reference keeps the object alive
else
mpAccessible = 0; // object is dead, forget the pointer
}
return xAccImpl;
}
// handlers -------------------------------------------------------------------
IMPL_LINK( ScPivotFieldWindow, ScrollHdl, ScrollBar*, pScrollBar )
{
// scrollbar may return negative values, if it is too small
long nThumbPos = pScrollBar->GetThumbPos();
if( nThumbPos >= 0 )
{
size_t nNewFirstVisIndex = static_cast< size_t >( nThumbPos * mnLineSize );
// keep the selection index on same relative position inside row/column
size_t nSelectLineOffset = mnSelectIndex % mnLineSize;
size_t nNewSelectIndex = mnSelectIndex;
if( nNewSelectIndex < nNewFirstVisIndex )
nNewSelectIndex = nNewFirstVisIndex + nSelectLineOffset;
else if( nNewSelectIndex >= nNewFirstVisIndex + mnPageSize )
nNewSelectIndex = nNewFirstVisIndex + mnPageSize - mnLineSize + nSelectLineOffset;
nNewSelectIndex = ::std::min( nNewSelectIndex, maFields.size() - 1 );
SetSelectionUnchecked( nNewSelectIndex, nNewFirstVisIndex );
}
GrabFocus();
return 0;
}
// ============================================================================