xref: /aoo41x/main/sc/source/ui/dbgui/fieldwnd.cxx (revision 0f49733d)
1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_sc.hxx"
30 
31 #include "fieldwnd.hxx"
32 
33 #include <tools/debug.hxx>
34 #include <vcl/decoview.hxx>
35 #include <vcl/help.hxx>
36 #include <vcl/svapp.hxx>
37 #include <vcl/virdev.hxx>
38 
39 #include "pvlaydlg.hxx"
40 #include "AccessibleDataPilotControl.hxx"
41 #include "scresid.hxx"
42 #include "sc.hrc"
43 
44 // ============================================================================
45 
46 using namespace ::com::sun::star;
47 using ::rtl::OUString;
48 
49 // ============================================================================
50 
51 namespace {
52 
53 /** Line width for insertion cursor in pixels. */
54 const long CURSOR_WIDTH             = 3;
55 
56 /** Number of tracking events before auto scrolling starts. */
57 const size_t INITIAL_TRACKING_DELAY = 20;
58 
59 } // namespace
60 
61 // ============================================================================
62 
63 ScPivotFieldWindow::ScPivotWindowField::ScPivotWindowField( const ScDPLabelData& rLabelData ) :
64     maFuncData( rLabelData.mnCol, rLabelData.mnFuncMask ),
65     maFieldName( rLabelData.getDisplayName() )
66 {
67 }
68 
69 ScPivotFieldWindow::ScPivotWindowField::ScPivotWindowField( ScPivotLayoutDlg& rDialog, const ScPivotField& rField, bool bDataWindow ) :
70     maFuncData( rField.nCol, rField.nFuncMask, rField.maFieldRef )
71 {
72     InitFieldName( rDialog, bDataWindow );
73 }
74 
75 ScPivotFieldWindow::ScPivotWindowField::ScPivotWindowField( ScPivotLayoutDlg& rDialog, const ScPivotFuncData& rFuncData, bool bDataWindow ) :
76     maFuncData( rFuncData )
77 {
78     InitFieldName( rDialog, bDataWindow );
79 }
80 
81 void ScPivotFieldWindow::ScPivotWindowField::InitFieldName( ScPivotLayoutDlg& rDialog, bool bDataWindow )
82 {
83     if( maFuncData.mnCol != PIVOT_DATA_FIELD )
84     {
85         ScDPLabelData* pLabelData = rDialog.GetLabelData( maFuncData.mnCol );
86         DBG_ASSERT( pLabelData, "ScPivotWindowField::InitFieldName - no label data found" );
87         if( pLabelData )
88         {
89             if( bDataWindow )
90             {
91                 // write original nFuncMask to label data
92                 pLabelData->mnFuncMask = maFuncData.mnFuncMask;
93                 // GetFuncString() modifies nFuncMask (e.g. auto to sum or count)
94                 maFieldName = rDialog.GetFuncString( maFuncData.mnFuncMask, pLabelData->mbIsValue );
95             }
96             else
97                 maFieldName = OUString();   // #i118111# don't append to previous string
98             maFieldName += pLabelData->getDisplayName();
99         }
100     }
101 }
102 
103 // ============================================================================
104 
105 ScPivotFieldWindow::ScPivotFieldWindow( ScPivotLayoutDlg* pDialog, const ResId& rResId,
106         ScrollBar& rScrollBar, FixedText* pFtCaption, const OUString& rName,
107         ScPivotFieldType eFieldType, const sal_Char* pcHelpId, PointerStyle eDropPointer,
108         size_t nColCount, size_t nRowCount, long nFieldWidthFactor, long nSpaceSize ) :
109     Control( pDialog, rResId ),
110     mpDialog( pDialog ),
111     mpAccessible( 0 ),
112     mrScrollBar( rScrollBar ),
113     mpFtCaption( pFtCaption ),
114     maName( rName ),
115     meFieldType( eFieldType ),
116     meDropPointer( eDropPointer ),
117     mnColCount( nColCount ),
118     mnRowCount( nRowCount ),
119     mnFirstVisIndex( 0 ),
120     mnSelectIndex( 0 ),
121     mnInsCursorIndex( PIVOTFIELD_INVALID ),
122     mnOldFirstVisIndex( 0 ),
123     mnAutoScrollDelay( 0 ),
124     mbVertical( eFieldType == PIVOTFIELDTYPE_SELECT ),
125     mbIsTrackingSource( false )
126 {
127     SetHelpId( pcHelpId );
128 
129     mnLineSize = mbVertical ? mnRowCount : mnColCount;
130     mnPageSize = mnColCount * mnRowCount;
131 
132     // a single field is 36x12 appfont units
133     maFieldSize = LogicToPixel( Size( 36, 12 ), MapMode( MAP_APPFONT ) );
134     maFieldSize.Width() *= nFieldWidthFactor;
135     maSpaceSize = LogicToPixel( Size( nSpaceSize, nSpaceSize ), MapMode( MAP_APPFONT ) );
136 
137     // set window size
138     long nWinWidth  = static_cast< long >( mnColCount * maFieldSize.Width()  + (mnColCount - 1) * maSpaceSize.Width() );
139     long nWinHeight = static_cast< long >( mnRowCount * maFieldSize.Height() + (mnRowCount - 1) * maSpaceSize.Height() );
140     SetSizePixel( Size( nWinWidth, nWinHeight ) );
141 
142     // scroll bar
143     Point aScrollBarPos = GetPosPixel();
144     Size aScrollBarSize( nWinWidth, nWinHeight );
145     if( mbVertical )
146     {
147         aScrollBarPos.Y() += nWinHeight + maSpaceSize.Height();
148         aScrollBarSize.Height() = GetSettings().GetStyleSettings().GetScrollBarSize();
149     }
150     else
151     {
152         aScrollBarPos.X() += nWinWidth + maSpaceSize.Width();
153         aScrollBarSize.Width() = GetSettings().GetStyleSettings().GetScrollBarSize();
154     }
155     mrScrollBar.SetPosSizePixel( aScrollBarPos, aScrollBarSize );
156     mrScrollBar.SetLineSize( 1 );
157     mrScrollBar.SetPageSize( static_cast< long >( mbVertical ? mnColCount : mnRowCount ) );
158     mrScrollBar.SetVisibleSize( static_cast< long >( mbVertical ? mnColCount : mnRowCount ) );
159     mrScrollBar.SetScrollHdl( LINK( this, ScPivotFieldWindow, ScrollHdl ) );
160     mrScrollBar.SetEndScrollHdl( LINK( this, ScPivotFieldWindow, ScrollHdl ) );
161 }
162 
163 ScPivotFieldWindow::~ScPivotFieldWindow()
164 {
165     ::rtl::Reference< ScAccessibleDataPilotControl > xAcc = GetAccessibleControl();
166     if( xAcc.is() )
167         xAcc->dispose();
168 }
169 
170 void ScPivotFieldWindow::ReadDataLabels( const ScDPLabelDataVector& rLabels )
171 {
172     maFields.clear();
173     maFields.reserve( rLabels.size() );
174     for( ScDPLabelDataVector::const_iterator aIt = rLabels.begin(), aEnd = rLabels.end(); aIt != aEnd; ++aIt )
175     {
176         ScPivotWindowField aField( *aIt );
177         if( aField.maFieldName.getLength() > 0 )
178             maFields.push_back( aField );
179     }
180     Invalidate();
181 }
182 
183 void ScPivotFieldWindow::ReadPivotFields( const ScPivotFieldVector& rPivotFields )
184 {
185     maFields.clear();
186     maFields.reserve( rPivotFields.size() );
187     for( ScPivotFieldVector::const_iterator aIt = rPivotFields.begin(), aEnd = rPivotFields.end(); aIt != aEnd; ++aIt )
188     {
189         ScPivotWindowField aField( *mpDialog, *aIt, meFieldType == PIVOTFIELDTYPE_DATA );
190         if( aField.maFieldName.getLength() > 0 )
191             maFields.push_back( aField );
192     }
193     Invalidate();
194 }
195 
196 void ScPivotFieldWindow::WriteFieldNames( ScDPNameVec& rFieldNames ) const
197 {
198     rFieldNames.clear();
199     rFieldNames.reserve( maFields.size() );
200     // do not use the names stored in maFields, but generate plain display names from label data
201     for( ScPivotWindowFieldVector::const_iterator aIt = maFields.begin(), aEnd = maFields.end(); aIt != aEnd; ++aIt )
202     {
203         if( ScDPLabelData* pLabelData = mpDialog->GetLabelData( aIt->maFuncData.mnCol ) )
204         {
205             OUString aDisplayName = pLabelData->getDisplayName();
206             if( aDisplayName.getLength() > 0 )
207                 rFieldNames.push_back( aDisplayName );
208         }
209     }
210 }
211 
212 void ScPivotFieldWindow::WritePivotFields( ScPivotFieldVector& rPivotFields ) const
213 {
214     rPivotFields.resize( maFields.size() );
215     ScPivotFieldVector::iterator aOutIt = rPivotFields.begin();
216     for( ScPivotWindowFieldVector::const_iterator aIt = maFields.begin(), aEnd = maFields.end(); aIt != aEnd; ++aIt, ++aOutIt )
217     {
218         aOutIt->nCol = aIt->maFuncData.mnCol;
219         aOutIt->nFuncMask = aIt->maFuncData.mnFuncMask;
220         aOutIt->maFieldRef = aIt->maFuncData.maFieldRef;
221     }
222 }
223 
224 OUString ScPivotFieldWindow::GetDescription() const
225 {
226     switch( meFieldType )
227     {
228         case PIVOTFIELDTYPE_COL:      return String( ScResId( STR_ACC_DATAPILOT_COL_DESCR ) );
229         case PIVOTFIELDTYPE_ROW:      return String( ScResId( STR_ACC_DATAPILOT_ROW_DESCR ) );
230         case PIVOTFIELDTYPE_DATA:     return String( ScResId( STR_ACC_DATAPILOT_DATA_DESCR ) );
231         case PIVOTFIELDTYPE_SELECT:   return String( ScResId( STR_ACC_DATAPILOT_SEL_DESCR ) );
232         default:;
233     }
234     return OUString();
235 }
236 
237 OUString ScPivotFieldWindow::GetFieldText( size_t nFieldIndex ) const
238 {
239     return (nFieldIndex < maFields.size()) ? maFields[ nFieldIndex ].maFieldName : OUString();
240 }
241 
242 ScPivotFuncDataEntry ScPivotFieldWindow::FindFuncDataByCol( SCCOL nCol ) const
243 {
244     for( ScPivotWindowFieldVector::const_iterator aIt = maFields.begin(), aEnd = maFields.end(); aIt != aEnd; ++aIt )
245         if( aIt->maFuncData.mnCol == nCol )
246             return ScPivotFuncDataEntry( &aIt->maFuncData, aIt - maFields.begin() );
247     return ScPivotFuncDataEntry( 0, PIVOTFIELD_INVALID );
248 }
249 
250 Point ScPivotFieldWindow::GetFieldPosition( size_t nFieldIndex ) const
251 {
252     long nRelIndex = static_cast< long >( nFieldIndex ) - mnFirstVisIndex;
253     long nCol = static_cast< long >( mbVertical ? (nRelIndex / mnRowCount) : (nRelIndex % mnColCount) );
254     long nRow = static_cast< long >( mbVertical ? (nRelIndex % mnRowCount) : (nRelIndex / mnColCount) );
255     return Point( nCol * (maFieldSize.Width() + maSpaceSize.Width()), nRow * (maFieldSize.Height() + maSpaceSize.Height()) );
256 }
257 
258 size_t ScPivotFieldWindow::GetFieldIndex( const Point& rWindowPos ) const
259 {
260     if( (rWindowPos.X() >= 0) && (rWindowPos.Y() >= 0) )
261     {
262         long nGridWidth = maFieldSize.Width() + maSpaceSize.Width();
263         long nGridHeight = maFieldSize.Height() + maSpaceSize.Height();
264         size_t nCol = static_cast< size_t >( rWindowPos.X() / nGridWidth );
265         size_t nRow = static_cast< size_t >( rWindowPos.Y() / nGridHeight );
266         if( (nCol < mnColCount) && (nRow < mnRowCount) )
267         {
268             long nColOffset = rWindowPos.X() % nGridWidth;
269             long nRowOffset = rWindowPos.Y() % nGridHeight;
270             // check that passed position is not in the space between the fields
271             if( (nColOffset < maFieldSize.Width()) && (nRowOffset < maFieldSize.Height()) )
272             {
273                 size_t nFieldIndex = mnFirstVisIndex + (mbVertical ? (nCol * mnRowCount + nRow) : (nRow * mnColCount + nCol));
274                 return (nFieldIndex < maFields.size()) ? nFieldIndex : PIVOTFIELD_INVALID;
275             }
276         }
277     }
278     return PIVOTFIELD_INVALID;
279 }
280 
281 size_t ScPivotFieldWindow::GetDropIndex( const Point& rWindowPos ) const
282 {
283     if( (rWindowPos.X() >= 0) && (rWindowPos.Y() >= 0) )
284     {
285         long nGridWidth = maFieldSize.Width() + maSpaceSize.Width();
286         long nGridHeight = maFieldSize.Height() + maSpaceSize.Height();
287         size_t nCol = static_cast< size_t >( rWindowPos.X() / nGridWidth );
288         size_t nRow = static_cast< size_t >( rWindowPos.Y() / nGridHeight );
289         if( (nCol < mnColCount) && (nRow < mnRowCount) )
290         {
291             size_t nFieldIndex = mnFirstVisIndex + (mbVertical ? (nCol * mnRowCount + nRow) : (nRow * mnColCount + nCol));
292             long nColOffset = rWindowPos.X() % nGridWidth;
293             long nRowOffset = rWindowPos.Y() % nGridHeight;
294             // take next field, if position is in right/lower third
295             if( (mnColCount == 1) ? (nRowOffset * 3 > nGridHeight * 2) : (nColOffset * 3 > nGridWidth * 2) )
296                 ++nFieldIndex;
297             return ::std::min( nFieldIndex, maFields.size() );
298         }
299     }
300     return maFields.size();
301 }
302 
303 void ScPivotFieldWindow::GrabFocusAndSelect( size_t nSelectIndex )
304 {
305     if( !HasFocus() ) GrabFocus();
306     MoveSelection( nSelectIndex );
307 }
308 
309 void ScPivotFieldWindow::SelectNextField()
310 {
311     MoveSelection( NEXT_FIELD );
312 }
313 
314 void ScPivotFieldWindow::InsertField( size_t nInsertIndex, const ScPivotFuncData& rFuncData )
315 {
316     if( (meFieldType != PIVOTFIELDTYPE_SELECT) && (nInsertIndex <= maFields.size()) )
317     {
318         size_t nFieldIndex = FindFuncDataByCol( rFuncData.mnCol ).second;
319         if( nFieldIndex < maFields.size() )
320         {
321             // field exists already in this window, move it to the specified position
322             MoveField( nFieldIndex, nInsertIndex );
323         }
324         else
325         {
326             // insert the field into the vector and notify accessibility object
327             ScPivotWindowField aField( *mpDialog, rFuncData, meFieldType == PIVOTFIELDTYPE_DATA );
328             if( aField.maFieldName.getLength() > 0 )
329             {
330                 InsertFieldUnchecked( nInsertIndex, aField );
331                 // adjust selection and scroll position
332                 MoveSelection( nInsertIndex );
333                 Invalidate();
334             }
335         }
336     }
337 }
338 
339 bool ScPivotFieldWindow::RemoveField( size_t nRemoveIndex )
340 {
341     if( (meFieldType != PIVOTFIELDTYPE_SELECT) && (nRemoveIndex < maFields.size()) )
342     {
343         // remove the field from the vector and notify accessibility object
344         RemoveFieldUnchecked( nRemoveIndex );
345         // adjust selection and scroll position, if last field is removed
346         if( !maFields.empty() )
347             MoveSelection( (mnSelectIndex < maFields.size()) ? mnSelectIndex : (maFields.size() - 1) );
348         Invalidate();
349         return true;
350     }
351     return false;
352 }
353 
354 bool ScPivotFieldWindow::MoveField( size_t nFieldIndex, size_t nInsertIndex )
355 {
356     /*  If field is moved behind current position, insertion index needs to be
357         adjusted, because the field is first removed from the vector. This is
358         done before nFieldIndex and nInsertIndex are checked for equality, to
359         catch the cases "move before ourselves" and "move bedind ourselves"
360         which are both no-ops. */
361     if( nFieldIndex < nInsertIndex )
362         --nInsertIndex;
363 
364     if( (meFieldType != PIVOTFIELDTYPE_SELECT) && (nFieldIndex != nInsertIndex) && (nFieldIndex < maFields.size()) && (nInsertIndex < maFields.size()) )
365     {
366         // move the field in the vector and notify accessibility object
367         ScPivotWindowField aField = maFields[ nFieldIndex ];
368         RemoveFieldUnchecked( nFieldIndex );
369         InsertFieldUnchecked( nInsertIndex, aField );
370         // adjust selection and scroll position
371         MoveSelection( nInsertIndex );
372         Invalidate();
373         return true;
374     }
375     return false;
376 }
377 
378 const ScPivotFuncData* ScPivotFieldWindow::GetSelectedFuncData() const
379 {
380     return (mnSelectIndex < maFields.size()) ? &maFields[ mnSelectIndex ].maFuncData : 0;
381 }
382 
383 void ScPivotFieldWindow::ModifySelectedField( const ScPivotFuncData& rFuncData )
384 {
385     if( mnSelectIndex < maFields.size() )
386     {
387         maFields[ mnSelectIndex ].maFuncData = rFuncData;
388         maFields[ mnSelectIndex ].InitFieldName( *mpDialog, meFieldType == PIVOTFIELDTYPE_DATA );
389         Invalidate();
390     }
391 }
392 
393 bool ScPivotFieldWindow::RemoveSelectedField()
394 {
395     return RemoveField( mnSelectIndex );
396 }
397 
398 bool ScPivotFieldWindow::MoveSelectedField( size_t nInsertIndex )
399 {
400     return MoveField( mnSelectIndex, nInsertIndex );
401 }
402 
403 void ScPivotFieldWindow::NotifyStartTracking()
404 {
405     // rescue old scrolling index, to be able to restore it when tracking is cancelled
406     mnOldFirstVisIndex = mnFirstVisIndex;
407 }
408 
409 void ScPivotFieldWindow::NotifyTracking( const Point& rWindowPos )
410 {
411     size_t nFieldIndex = GetDropIndex( rWindowPos );
412 
413     // insertion index changed: draw new cursor and exit
414     if( nFieldIndex != mnInsCursorIndex )
415     {
416         mnInsCursorIndex = nFieldIndex;
417         mnAutoScrollDelay = INITIAL_TRACKING_DELAY;
418         Invalidate();
419         return;
420     }
421 
422     // insertion index unchanged: countdown for auto scrolling
423     if( mnAutoScrollDelay > 0 )
424     {
425         --mnAutoScrollDelay;
426         return;
427     }
428 
429     // check if tracking happens on first or last field
430     long nScrollDelta = 0;
431     if( (mnInsCursorIndex > 0) && (mnInsCursorIndex == mnFirstVisIndex) )
432         nScrollDelta = -static_cast< long >( mnLineSize );
433     else if( (mnInsCursorIndex < maFields.size()) && (mnInsCursorIndex == mnFirstVisIndex + mnPageSize) )
434         nScrollDelta = static_cast< long >( mnLineSize );
435     if( nScrollDelta != 0 )
436     {
437         // update mnInsCursorIndex, so it will be drawn at the same position after scrolling
438         mnInsCursorIndex += nScrollDelta;
439         mnFirstVisIndex += nScrollDelta;
440         // delay auto scroll by line size, to slow down scrolling in column/page windows
441         mnAutoScrollDelay = mnLineSize - 1;
442         Invalidate();
443     }
444 }
445 
446 void ScPivotFieldWindow::NotifyEndTracking( ScPivotFieldEndTracking eEndType )
447 {
448     if( eEndType != ENDTRACKING_DROP )
449         mnFirstVisIndex = mnOldFirstVisIndex;
450     if( eEndType != ENDTRACKING_SUSPEND )
451     {
452         mnOldFirstVisIndex = PIVOTFIELD_INVALID;
453         mbIsTrackingSource = false;
454     }
455     mnInsCursorIndex = PIVOTFIELD_INVALID;
456     Invalidate();
457 }
458 
459 // protected ------------------------------------------------------------------
460 
461 void ScPivotFieldWindow::Paint( const Rectangle& /*rRect*/ )
462 {
463     // prepare a virtual device for buffered painting
464     VirtualDevice aVirDev;
465     // #i97623# VirtualDevice is always LTR on construction while other windows derive direction from parent
466     aVirDev.EnableRTL( IsRTLEnabled() );
467     aVirDev.SetMapMode( MAP_PIXEL );
468     aVirDev.SetOutputSizePixel( GetSizePixel() );
469     Font aFont = GetFont();
470     aFont.SetTransparent( true );
471     aVirDev.SetFont( aFont );
472 
473     // draw the background and all fields
474     DrawBackground( aVirDev );
475     for( size_t nFieldIndex = mnFirstVisIndex, nEndIndex = mnFirstVisIndex + mnPageSize; nFieldIndex < nEndIndex; ++nFieldIndex )
476         DrawField( aVirDev, nFieldIndex );
477     DrawInsertionCursor( aVirDev );
478     DrawBitmap( Point( 0, 0 ), aVirDev.GetBitmap( Point( 0, 0 ), GetSizePixel() ) );
479 
480     // draw field text focus
481     if( HasFocus() && (mnSelectIndex < maFields.size()) && (mnFirstVisIndex <= mnSelectIndex) && (mnSelectIndex < mnFirstVisIndex + mnPageSize) )
482     {
483         long nFieldWidth = maFieldSize.Width();
484         long nSelectionWidth = Min( GetTextWidth( maFields[ mnSelectIndex ].maFieldName ) + 4, nFieldWidth - 6 );
485         Rectangle aSelection(
486             GetFieldPosition( mnSelectIndex ) + Point( (nFieldWidth - nSelectionWidth) / 2, 3 ),
487             Size( nSelectionWidth, maFieldSize.Height() - 6 ) );
488         InvertTracking( aSelection, SHOWTRACK_SMALL | SHOWTRACK_WINDOW );
489     }
490 
491     // update scrollbar
492     size_t nFieldCount = maFields.size();
493     /*  Already show the scrollbar if window is full but no fields are hidden
494         (yet). This gives the user the hint that it is now possible to add more
495         fields to the window. */
496     mrScrollBar.Show( nFieldCount >= mnPageSize );
497     mrScrollBar.Enable( nFieldCount > mnPageSize );
498     if( mrScrollBar.IsVisible() )
499     {
500         mrScrollBar.SetRange( Range( 0, static_cast< long >( (nFieldCount - 1) / mnLineSize + 1 ) ) );
501         mrScrollBar.SetThumbPos( static_cast< long >( mnFirstVisIndex / mnLineSize ) );
502     }
503 
504     /*  Exclude empty fields from tab chain, but do not disable them. They need
505         to be enabled because they still act as target for field movement via
506         keyboard shortcuts. */
507     WinBits nMask = ~(WB_TABSTOP | WB_NOTABSTOP);
508     SetStyle( (GetStyle() & nMask) | (IsEmpty() ? WB_NOTABSTOP : WB_TABSTOP) );
509 }
510 
511 void ScPivotFieldWindow::StateChanged( StateChangedType nStateChange )
512 {
513     Control::StateChanged( nStateChange );
514 
515     if( nStateChange == STATE_CHANGE_INITSHOW )
516     {
517         /*  After the fixed text associated to this control has received its
518             unique mnemonic from VCL dialog initialization code, put this text
519             into the field windows.
520             #124828# Hiding the FixedTexts and clearing the tab stop style bits
521             has to be done after assigning the mnemonics, but Paint() is too
522             late, because the test tool may send key events to the dialog when
523             it isn't visible. Mnemonics are assigned in Dialog::StateChanged()
524             for STATE_CHANGE_INITSHOW, so this can be done immediately
525             afterwards. */
526         if( mpFtCaption )
527         {
528             SetText( mpFtCaption->GetText() );
529             mpFtCaption->Hide();
530         }
531     }
532 }
533 
534 void ScPivotFieldWindow::DataChanged( const DataChangedEvent& rDCEvt )
535 {
536     Control::DataChanged( rDCEvt );
537     if( (rDCEvt.GetType() == DATACHANGED_SETTINGS) && (rDCEvt.GetFlags() & SETTINGS_STYLE) )
538         Invalidate();
539 }
540 
541 void ScPivotFieldWindow::KeyInput( const KeyEvent& rKEvt )
542 {
543     bool bKeyEvaluated = false;
544 
545     if( !maFields.empty() )
546     {
547         const KeyCode& rKeyCode = rKEvt.GetKeyCode();
548         sal_uInt16 nCode = rKeyCode.GetCode();
549 
550         // do not move fields in selection window
551         if( rKeyCode.IsMod1() && (meFieldType != PIVOTFIELDTYPE_SELECT) )
552         {
553             bKeyEvaluated = true;
554             switch( nCode )
555             {
556                 case KEY_UP:        MoveSelectedField( mbVertical ? PREV_FIELD : PREV_LINE );   break;
557                 case KEY_DOWN:      MoveSelectedField( mbVertical ? NEXT_FIELD : NEXT_LINE );   break;
558                 case KEY_LEFT:      MoveSelectedField( mbVertical ? PREV_LINE : PREV_FIELD );   break;
559                 case KEY_RIGHT:     MoveSelectedField( mbVertical ? NEXT_LINE : NEXT_FIELD );   break;
560                 case KEY_HOME:      MoveSelectedField( FIRST_FIELD );                           break;
561                 case KEY_END:       MoveSelectedField( LAST_FIELD );                            break;
562                 default:            bKeyEvaluated = false;
563             }
564         }
565         else
566         {
567             bKeyEvaluated = true;
568             switch( nCode )
569             {
570                 case KEY_UP:        MoveSelection( mbVertical ? PREV_FIELD : PREV_LINE );           break;
571                 case KEY_DOWN:      MoveSelection( mbVertical ? NEXT_FIELD : NEXT_LINE );           break;
572                 case KEY_LEFT:      MoveSelection( mbVertical ? PREV_LINE : PREV_FIELD );           break;
573                 case KEY_RIGHT:     MoveSelection( mbVertical ? NEXT_LINE : NEXT_FIELD );           break;
574                 case KEY_PAGEUP:    MoveSelection( PREV_PAGE );                                     break;
575                 case KEY_PAGEDOWN:  MoveSelection( NEXT_PAGE );                                     break;
576                 case KEY_HOME:      MoveSelection( FIRST_FIELD );                                   break;
577                 case KEY_END:       MoveSelection( LAST_FIELD );                                    break;
578                 // delete field per DEL key - dialog needs to change focus if window becomes empty
579                 case KEY_DELETE:    RemoveSelectedField(); mpDialog->NotifyFieldRemoved( *this );   break;
580                 default:            bKeyEvaluated = false;
581             }
582         }
583     }
584 
585     if( !bKeyEvaluated )
586         Control::KeyInput( rKEvt );
587 }
588 
589 void ScPivotFieldWindow::MouseButtonDown( const MouseEvent& rMEvt )
590 {
591     if( rMEvt.IsLeft() )
592     {
593         size_t nNewSelectIndex = GetFieldIndex( rMEvt.GetPosPixel() );
594         if( nNewSelectIndex < maFields.size() )
595         {
596             // grabbing after GetFieldIndex() will prevent to focus empty window
597             GrabFocusAndSelect( nNewSelectIndex );
598             if( rMEvt.GetClicks() == 1 )
599             {
600                 // one click: start tracking
601                 mbIsTrackingSource = true;
602                 mnOldFirstVisIndex = mnFirstVisIndex;
603                 mpDialog->NotifyStartTracking( *this );
604             }
605             else
606             {
607                 // two clicks: open field options dialog
608                 mpDialog->NotifyDoubleClick( *this );
609             }
610         }
611     }
612 }
613 
614 void ScPivotFieldWindow::RequestHelp( const HelpEvent& rHEvt )
615 {
616     if( (rHEvt.GetMode() & HELPMODE_QUICK) != 0 )
617     {
618         // show a tooltip with full field name, if field text is clipped
619         size_t nFieldIndex = GetFieldIndex( rHEvt.GetMousePosPixel() - GetPosPixel() );
620         if( (nFieldIndex < maFields.size()) && maFields[ nFieldIndex ].mbClipped )
621         {
622             Rectangle aRect( rHEvt.GetMousePosPixel(), GetSizePixel() );
623             Help::ShowQuickHelp( this, aRect, maFields[ nFieldIndex ].maFieldName );
624             return;
625         }
626     }
627     Control::RequestHelp( rHEvt );
628 }
629 
630 void ScPivotFieldWindow::GetFocus()
631 {
632     Control::GetFocus();
633     Invalidate();
634     ::rtl::Reference< ScAccessibleDataPilotControl > xAcc = GetAccessibleControl();
635     if( xAcc.is() )
636         xAcc->GotFocus();
637 }
638 
639 void ScPivotFieldWindow::LoseFocus()
640 {
641     Control::LoseFocus();
642     Invalidate();
643     ::rtl::Reference< ScAccessibleDataPilotControl > xAcc = GetAccessibleControl();
644     if( xAcc.is() )
645         xAcc->LostFocus();
646 }
647 
648 uno::Reference< accessibility::XAccessible > ScPivotFieldWindow::CreateAccessible()
649 {
650     mpAccessible = new ScAccessibleDataPilotControl( GetAccessibleParentWindow()->GetAccessible(), this );
651     uno::Reference< accessibility::XAccessible > xReturn( mpAccessible );
652     mpAccessible->Init();
653     mxAccessible = xReturn;
654     return xReturn;
655 }
656 
657 // private --------------------------------------------------------------------
658 
659 size_t ScPivotFieldWindow::RecalcVisibleIndex( size_t nSelectIndex ) const
660 {
661     // calculate a scrolling offset that shows the selected field
662     size_t nNewFirstVisIndex = mnFirstVisIndex;
663     if( nSelectIndex < nNewFirstVisIndex )
664         nNewFirstVisIndex = static_cast< size_t >( (nSelectIndex / mnLineSize) * mnLineSize );
665     else if( nSelectIndex >= nNewFirstVisIndex + mnPageSize )
666         nNewFirstVisIndex = static_cast< size_t >( (nSelectIndex / mnLineSize + 1) * mnLineSize ) - mnPageSize;
667     // check if there are complete empty lines in the bottom/right
668     size_t nMaxFirstVisIndex = (maFields.size() <= mnPageSize) ? 0 : (((maFields.size() - 1) / mnLineSize + 1) * mnLineSize - mnPageSize);
669     return ::std::min( nNewFirstVisIndex, nMaxFirstVisIndex );
670 }
671 
672 void ScPivotFieldWindow::SetSelectionUnchecked( size_t nSelectIndex, size_t nFirstVisIndex )
673 {
674     if( !maFields.empty() && (nSelectIndex < maFields.size()) )
675     {
676         bool bScrollPosChanged = mnFirstVisIndex != nFirstVisIndex;
677         bool bSelectionChanged = mnSelectIndex != nSelectIndex;
678 
679         sal_Int32 nOldSelected = static_cast< sal_Int32 >( mnSelectIndex );
680         mnFirstVisIndex = nFirstVisIndex;
681         mnSelectIndex = nSelectIndex;
682 
683         if( bScrollPosChanged || bSelectionChanged )
684             Invalidate();
685 
686         // TODO: accessibility action for changed scrolling position?
687 
688         // notify accessibility object about changed selection
689         if( bSelectionChanged && HasFocus() )
690         {
691             ::rtl::Reference< ScAccessibleDataPilotControl > xAcc = GetAccessibleControl();
692             if( xAcc.is() )
693                 xAcc->FieldFocusChange( nOldSelected, static_cast< sal_Int32 >( mnSelectIndex ) );
694         }
695     }
696 }
697 
698 void ScPivotFieldWindow::MoveSelection( size_t nSelectIndex )
699 {
700     if( nSelectIndex < maFields.size() )
701         SetSelectionUnchecked( nSelectIndex, RecalcVisibleIndex( nSelectIndex ) );
702 }
703 
704 void ScPivotFieldWindow::MoveSelection( MoveType eMoveType )
705 {
706     if( maFields.empty() )
707         return;
708 
709     size_t nLastIndex = maFields.size() - 1;
710     size_t nNewSelectIndex = mnSelectIndex;
711     switch( eMoveType )
712     {
713         case PREV_FIELD:
714             nNewSelectIndex = (nNewSelectIndex > 0) ? (nNewSelectIndex - 1) : 0;
715         break;
716         case NEXT_FIELD:
717             nNewSelectIndex = (nNewSelectIndex < nLastIndex) ? (nNewSelectIndex + 1) : nLastIndex;
718         break;
719         case PREV_LINE:
720             nNewSelectIndex = (nNewSelectIndex > mnLineSize) ? (nNewSelectIndex - mnLineSize) : 0;
721         break;
722         case NEXT_LINE:
723             nNewSelectIndex = (nNewSelectIndex + mnLineSize < nLastIndex) ? (nNewSelectIndex + mnLineSize) : nLastIndex;
724         break;
725         case PREV_PAGE:
726             nNewSelectIndex = (nNewSelectIndex > mnPageSize) ? (nNewSelectIndex - mnPageSize) : 0;
727         break;
728         case NEXT_PAGE:
729             nNewSelectIndex = (nNewSelectIndex + mnPageSize < nLastIndex) ? (nNewSelectIndex + mnPageSize) : nLastIndex;
730         break;
731         case FIRST_FIELD:
732             nNewSelectIndex = 0;
733         break;
734         case LAST_FIELD:
735             nNewSelectIndex = nLastIndex;
736         break;
737     }
738 
739     // SetSelectionUnchecked() redraws the control and updates the scrollbar
740     SetSelectionUnchecked( nNewSelectIndex, RecalcVisibleIndex( nNewSelectIndex ) );
741 }
742 
743 void ScPivotFieldWindow::MoveSelectedField( MoveType eMoveType )
744 {
745     if( mnSelectIndex < maFields.size() )
746     {
747         // find position to insert the field by changing the selection first
748         size_t nOldSelectIndex = mnSelectIndex;
749         MoveSelection( eMoveType );
750         MoveField( nOldSelectIndex, (nOldSelectIndex < mnSelectIndex) ? (mnSelectIndex + 1) : mnSelectIndex );
751     }
752 }
753 
754 void ScPivotFieldWindow::InsertFieldUnchecked( size_t nInsertIndex, const ScPivotWindowField& rField )
755 {
756     maFields.insert( maFields.begin() + nInsertIndex, rField );
757     ::rtl::Reference< ScAccessibleDataPilotControl > xAcc = GetAccessibleControl();
758     if( xAcc.is() )
759         xAcc->AddField( static_cast< sal_Int32 >( nInsertIndex ) );
760 }
761 
762 void ScPivotFieldWindow::RemoveFieldUnchecked( size_t nRemoveIndex )
763 {
764     ::rtl::Reference< ScAccessibleDataPilotControl > xAcc = GetAccessibleControl();
765     if( xAcc.is() )
766         xAcc->RemoveField( static_cast< sal_Int32 >( nRemoveIndex ) );
767     maFields.erase( maFields.begin() + nRemoveIndex );
768 }
769 
770 void ScPivotFieldWindow::DrawBackground( OutputDevice& rDev )
771 {
772     Size aDevSize = rDev.GetOutputSizePixel();
773     const StyleSettings& rStyleSett = GetSettings().GetStyleSettings();
774 
775     if( meFieldType == PIVOTFIELDTYPE_SELECT )
776     {
777         rDev.SetLineColor();
778         rDev.SetFillColor( rStyleSett.GetFaceColor() );
779         rDev.DrawRect( Rectangle( Point( 0, 0 ), aDevSize ) );
780     }
781     else
782     {
783         rDev.SetLineColor( rStyleSett.GetWindowTextColor() );
784         rDev.SetFillColor( rStyleSett.GetWindowColor() );
785         rDev.DrawRect( Rectangle( Point( 0, 0 ), aDevSize ) );
786 
787         /*  Draw the caption text. This needs some special handling, because we
788             support hard line breaks here. This part will draw each line of the
789             text for itself. */
790         rDev.SetTextColor( rStyleSett.GetWindowTextColor() );
791         xub_StrLen nTokenCnt = GetText().GetTokenCount( '\n' );
792         long nY = (aDevSize.Height() - nTokenCnt * rDev.GetTextHeight()) / 2;
793         for( xub_StrLen nToken = 0, nStringIx = 0; nToken < nTokenCnt; ++nToken )
794         {
795             String aLine = GetText().GetToken( 0, '\n', nStringIx );
796             Point aLinePos( (aDevSize.Width() - rDev.GetCtrlTextWidth( aLine )) / 2, nY );
797             rDev.DrawCtrlText( aLinePos, aLine );
798             nY += rDev.GetTextHeight();
799         }
800     }
801 }
802 
803 void ScPivotFieldWindow::DrawField( OutputDevice& rDev, size_t nFieldIndex )
804 {
805     if( (nFieldIndex < maFields.size()) && (mnFirstVisIndex <= nFieldIndex) && (nFieldIndex < mnFirstVisIndex + mnPageSize) )
806     {
807         // draw the button
808         Point aFieldPos = GetFieldPosition( nFieldIndex );
809         bool bFocus = HasFocus() && (nFieldIndex == mnSelectIndex);
810         DecorationView aDecoView( &rDev );
811         aDecoView.DrawButton( Rectangle( aFieldPos, maFieldSize ), bFocus ? BUTTON_DRAW_DEFAULT : 0 );
812 
813         // #i31600# if text is too long, cut and add ellipsis
814         const OUString& rFullText = maFields[ nFieldIndex ].maFieldName;
815         OUString aClippedText = rFullText;
816         long nLabelWidth = rDev.GetTextWidth( rFullText );
817         if( (maFields[ nFieldIndex ].mbClipped = nLabelWidth + 6 > maFieldSize.Width()) == true )
818         {
819             sal_Int32 nMinLen = 0;
820             sal_Int32 nMaxLen = rFullText.getLength();
821             bool bFits = false;
822             do
823             {
824                 sal_Int32 nCurrLen = (nMinLen + nMaxLen) / 2;
825                 aClippedText = rFullText.copy( 0, nCurrLen ) + OUString( RTL_CONSTASCII_USTRINGPARAM( "..." ) );
826                 nLabelWidth = rDev.GetTextWidth( aClippedText );
827                 bFits = nLabelWidth + 6 <= maFieldSize.Width();
828                 (bFits ? nMinLen : nMaxLen) = nCurrLen;
829             }
830             while( !bFits || (nMinLen + 1 < nMaxLen) );
831         }
832 
833         // draw the button text
834         Point aLabelOffset( (maFieldSize.Width() - nLabelWidth) / 2, ::std::max< long >( (maFieldSize.Height() - rDev.GetTextHeight()) / 2, 3 ) );
835         rDev.SetTextColor( GetSettings().GetStyleSettings().GetButtonTextColor() );
836         rDev.DrawText( aFieldPos + aLabelOffset, aClippedText );
837     }
838 }
839 
840 void ScPivotFieldWindow::DrawInsertionCursor( OutputDevice& rDev )
841 {
842     if( (mnInsCursorIndex <= maFields.size()) && (mnFirstVisIndex <= mnInsCursorIndex) && (mnInsCursorIndex <= mnFirstVisIndex + mnPageSize) &&
843         (!mbIsTrackingSource || (mnInsCursorIndex < mnSelectIndex) || (mnInsCursorIndex > mnSelectIndex + 1)) )
844     {
845         Color aTextColor = GetSettings().GetStyleSettings().GetButtonTextColor();
846         rDev.SetLineColor( aTextColor );
847         rDev.SetFillColor( aTextColor );
848 
849         bool bVerticalCursor = mnColCount > 1;
850         long nCursorLength = bVerticalCursor ? maFieldSize.Height() : maFieldSize.Width();
851 
852         bool bEndOfLastField = mnInsCursorIndex == mnFirstVisIndex + mnPageSize;
853         Point aMainLinePos = GetFieldPosition( bEndOfLastField ? (mnInsCursorIndex - 1) : mnInsCursorIndex );
854         if( bEndOfLastField )
855             (bVerticalCursor ? aMainLinePos.X() : aMainLinePos.Y()) += ((bVerticalCursor ? maFieldSize.Width() : maFieldSize.Height()) - CURSOR_WIDTH);
856         else if( (bVerticalCursor ? aMainLinePos.X() : aMainLinePos.Y()) > 0 )
857             (bVerticalCursor ? aMainLinePos.X() : aMainLinePos.Y()) -= ((CURSOR_WIDTH + 1) / 2);
858         Size aMainLineSize( bVerticalCursor ? CURSOR_WIDTH : nCursorLength, bVerticalCursor ? nCursorLength : CURSOR_WIDTH );
859         rDev.DrawRect( Rectangle( aMainLinePos, aMainLineSize ) );
860 
861         Point aSubLinePos = aMainLinePos;
862         (bVerticalCursor ? aSubLinePos.X() : aSubLinePos.Y()) -= CURSOR_WIDTH;
863         Size aSubLineSize( bVerticalCursor ? (3 * CURSOR_WIDTH) : CURSOR_WIDTH, bVerticalCursor ? CURSOR_WIDTH : (3 * CURSOR_WIDTH) );
864         rDev.DrawRect( Rectangle( aSubLinePos, aSubLineSize ) );
865 
866         (bVerticalCursor ? aSubLinePos.Y() : aSubLinePos.X()) += (nCursorLength - CURSOR_WIDTH);
867         rDev.DrawRect( Rectangle( aSubLinePos, aSubLineSize ) );
868     }
869 }
870 
871 ::rtl::Reference< ScAccessibleDataPilotControl > ScPivotFieldWindow::GetAccessibleControl()
872 {
873     ::rtl::Reference< ScAccessibleDataPilotControl > xAccImpl;
874     if( mpAccessible )
875     {
876         // try to resolve the weak reference mxAccessible
877         uno::Reference< accessibility::XAccessible > xAcc = mxAccessible;
878         if( xAcc.is() )
879             xAccImpl.set( mpAccessible );   // the rtl reference keeps the object alive
880         else
881             mpAccessible = 0;               // object is dead, forget the pointer
882     }
883     return xAccImpl;
884  }
885 
886 // handlers -------------------------------------------------------------------
887 
888 IMPL_LINK( ScPivotFieldWindow, ScrollHdl, ScrollBar*, pScrollBar )
889 {
890     // scrollbar may return negative values, if it is too small
891     long nThumbPos = pScrollBar->GetThumbPos();
892     if( nThumbPos >= 0 )
893     {
894         size_t nNewFirstVisIndex = static_cast< size_t >( nThumbPos * mnLineSize );
895         // keep the selection index on same relative position inside row/column
896         size_t nSelectLineOffset = mnSelectIndex % mnLineSize;
897         size_t nNewSelectIndex = mnSelectIndex;
898         if( nNewSelectIndex < nNewFirstVisIndex )
899             nNewSelectIndex = nNewFirstVisIndex + nSelectLineOffset;
900         else if( nNewSelectIndex >= nNewFirstVisIndex + mnPageSize )
901             nNewSelectIndex = nNewFirstVisIndex + mnPageSize - mnLineSize + nSelectLineOffset;
902         nNewSelectIndex = ::std::min( nNewSelectIndex, maFields.size() - 1 );
903         SetSelectionUnchecked( nNewSelectIndex, nNewFirstVisIndex );
904     }
905     GrabFocus();
906     return 0;
907 }
908 
909 // ============================================================================
910