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