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