1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <csvruler.hxx>
21 #include <AccessibleCsvControl.hxx>
23 #include <optutil.hxx>
24 #include <com/sun/star/uno/Any.hxx>
25 #include <com/sun/star/uno/Sequence.hxx>
26 #include <vcl/event.hxx>
27 #include <vcl/settings.hxx>
28 #include <vcl/ptrstyle.hxx>
29 #include <vcl/svapp.hxx>
30 #include <vcl/virdev.hxx>
31 #include <o3tl/string_view.hxx>
33 using namespace com::sun::star::uno
;
35 constexpr OUStringLiteral SEP_PATH
= u
"Office.Calc/Dialogs/CSVImport";
36 constexpr OUStringLiteral FIXED_WIDTH_LIST
= u
"FixedWidthList";
38 static void load_FixedWidthList(ScCsvSplits
&rSplits
)
41 const Any
*pProperties
;
42 Sequence
<OUString
> aNames
{ FIXED_WIDTH_LIST
};
43 ScLinkConfigItem
aItem( SEP_PATH
);
45 aValues
= aItem
.GetProperties( aNames
);
46 pProperties
= aValues
.getConstArray();
48 if( !pProperties
[0].hasValue() )
53 OUString sFixedWidthLists
;
54 pProperties
[0] >>= sFixedWidthLists
;
59 const sal_Int32 n
= o3tl::toInt32(o3tl::getToken(sFixedWidthLists
, 0, ';', nIdx
));
62 // String ends with a semi-colon so there
63 // is no useful 'int' after the last one.
64 // This also works in case of empty string
70 static void save_FixedWidthList(const ScCsvSplits
& rSplits
)
72 OUStringBuffer sSplits
;
73 // Create a semi-colon separated string to save the splits
74 sal_uInt32 n
= rSplits
.Count();
75 for (sal_uInt32 i
= 0; i
< n
; ++i
)
77 sSplits
.append(rSplits
[i
]);
81 OUString sFixedWidthLists
= sSplits
.makeStringAndClear();
82 Sequence
<Any
> aValues
;
84 Sequence
<OUString
> aNames
{ FIXED_WIDTH_LIST
};
85 ScLinkConfigItem
aItem( SEP_PATH
);
87 aValues
= aItem
.GetProperties( aNames
);
88 pProperties
= aValues
.getArray();
89 pProperties
[0] <<= sFixedWidthLists
;
91 aItem
.PutProperties(aNames
, aValues
);
94 ScCsvRuler::ScCsvRuler(const ScCsvLayoutData
& rData
, ScCsvTableBox
* pTableBox
)
96 , mpTableBox(pTableBox
)
100 , mbPosMTMoved(false)
106 void ScCsvRuler::SetDrawingArea(weld::DrawingArea
* pDrawingArea
)
108 ScCsvControl::SetDrawingArea(pDrawingArea
);
112 Size
aSize(1, GetTextHeight() + mnSplitSize
+ 2);
113 pDrawingArea
->set_size_request(aSize
.Width(), aSize
.Height());
114 SetOutputSizePixel(aSize
);
116 EnableRTL( false ); // RTL
120 OutputDevice
& rRefDevice
= pDrawingArea
->get_ref_device();
121 maBackgrDev
->SetFont( rRefDevice
.GetFont() );
122 maRulerDev
->SetFont( rRefDevice
.GetFont() );
123 load_FixedWidthList( maSplits
);
126 ScCsvRuler::~ScCsvRuler()
128 save_FixedWidthList( maSplits
);
131 // common ruler handling ------------------------------------------------------
133 void ScCsvRuler::ApplyLayout( const ScCsvLayoutData
& rOldData
)
135 ScCsvDiff nDiff
= GetLayoutData().GetDiff( rOldData
) & (ScCsvDiff::HorizontalMask
| ScCsvDiff::RulerCursor
);
136 if( nDiff
== ScCsvDiff::Equal
) return;
139 if( nDiff
& ScCsvDiff::HorizontalMask
)
142 if( GetRulerCursorPos() >= GetPosCount() )
143 MoveCursor( GetPosCount() - 1 );
145 if( nDiff
& ScCsvDiff::RulerCursor
)
147 ImplInvertCursor( rOldData
.mnPosCursor
);
148 ImplInvertCursor( GetRulerCursorPos() );
152 if( nDiff
& ScCsvDiff::PosOffset
)
153 AccSendVisibleEvent();
156 void ScCsvRuler::InitColors()
158 const StyleSettings
& rSett
= Application::GetSettings().GetStyleSettings();
159 maBackColor
= rSett
.GetFaceColor();
160 maActiveColor
= rSett
.GetWindowColor();
161 maTextColor
= rSett
.GetLabelTextColor();
162 maSplitColor
= maBackColor
.IsDark() ? maTextColor
: COL_LIGHTRED
;
166 void ScCsvRuler::UpdateSplitSize()
168 mnSplitSize
= (GetCharWidth() * 3 / 5) | 1; // make an odd number
171 void ScCsvRuler::InitSizeData()
173 maWinSize
= GetOutputSizePixel();
177 sal_Int32 nActiveWidth
= std::min( GetWidth() - GetHdrWidth(), GetPosCount() * GetCharWidth() );
178 sal_Int32 nActiveHeight
= GetTextHeight();
180 maActiveRect
.SetPos( Point( GetFirstX(), (GetHeight() - nActiveHeight
- 1) / 2 ) );
181 maActiveRect
.SetSize( Size( nActiveWidth
, nActiveHeight
) );
183 maBackgrDev
->SetOutputSizePixel( maWinSize
);
184 maRulerDev
->SetOutputSizePixel( maWinSize
);
189 void ScCsvRuler::MoveCursor( sal_Int32 nPos
, bool bScroll
)
193 Execute( CSVCMD_MAKEPOSVISIBLE
, nPos
);
194 Execute( CSVCMD_MOVERULERCURSOR
, IsVisibleSplitPos( nPos
) ? nPos
: CSV_POS_INVALID
);
199 void ScCsvRuler::MoveCursorRel( ScMoveMode eDir
)
201 if( GetRulerCursorPos() == CSV_POS_INVALID
)
210 MoveCursor( GetPosCount() - 1 );
213 if( GetRulerCursorPos() > 1 )
214 MoveCursor( GetRulerCursorPos() - 1 );
217 if( GetRulerCursorPos() < GetPosCount() - 1 )
218 MoveCursor( GetRulerCursorPos() + 1 );
222 // added to avoid warnings
227 void ScCsvRuler::MoveCursorToSplit( ScMoveMode eDir
)
229 if( GetRulerCursorPos() == CSV_POS_INVALID
)
232 sal_uInt32 nIndex
= CSV_VEC_NOTFOUND
;
235 case MOVE_FIRST
: nIndex
= maSplits
.LowerBound( 0 ); break;
236 case MOVE_LAST
: nIndex
= maSplits
.UpperBound( GetPosCount() ); break;
237 case MOVE_PREV
: nIndex
= maSplits
.UpperBound( GetRulerCursorPos() - 1 ); break;
238 case MOVE_NEXT
: nIndex
= maSplits
.LowerBound( GetRulerCursorPos() + 1 ); break;
241 // added to avoid warnings
244 sal_Int32 nPos
= maSplits
[ nIndex
];
245 if( nPos
!= CSV_POS_INVALID
)
249 void ScCsvRuler::ScrollVertRel( ScMoveMode eDir
)
251 sal_Int32 nLine
= GetFirstVisLine();
254 case MOVE_PREV
: --nLine
; break;
255 case MOVE_NEXT
: ++nLine
; break;
256 case MOVE_PREVPAGE
: nLine
-= GetVisLineCount() - 1; break;
257 case MOVE_NEXTPAGE
: nLine
+= GetVisLineCount() - 1; break;
260 // added to avoid warnings
263 Execute( CSVCMD_SETLINEOFFSET
, nLine
);
266 // split handling -------------------------------------------------------------
268 sal_Int32
ScCsvRuler::GetNoScrollPos( sal_Int32 nPos
) const
270 sal_Int32 nNewPos
= nPos
;
271 if( nNewPos
!= CSV_POS_INVALID
)
273 if( nNewPos
< GetFirstVisPos() + CSV_SCROLL_DIST
)
275 sal_Int32 nScroll
= (GetFirstVisPos() > 0) ? CSV_SCROLL_DIST
: 0;
276 nNewPos
= std::max( nPos
, GetFirstVisPos() + nScroll
);
278 else if( nNewPos
> GetLastVisPos() - CSV_SCROLL_DIST
- 1 )
280 sal_Int32 nScroll
= (GetFirstVisPos() < GetMaxPosOffset()) ? CSV_SCROLL_DIST
: 0;
281 nNewPos
= std::min( nNewPos
, GetLastVisPos() - nScroll
- sal_Int32( 1 ) );
287 void ScCsvRuler::InsertSplit( sal_Int32 nPos
)
289 if( maSplits
.Insert( nPos
) )
291 ImplDrawSplit( nPos
);
296 void ScCsvRuler::RemoveSplit( sal_Int32 nPos
)
298 if( maSplits
.Remove( nPos
) )
300 ImplEraseSplit( nPos
);
305 void ScCsvRuler::MoveSplit( sal_Int32 nPos
, sal_Int32 nNewPos
)
307 bool bRemove
= maSplits
.Remove( nPos
);
308 bool bInsert
= maSplits
.Insert( nNewPos
);
309 if( bRemove
|| bInsert
)
311 ImplEraseSplit( nPos
);
312 ImplDrawSplit( nNewPos
);
317 void ScCsvRuler::RemoveAllSplits()
323 sal_Int32
ScCsvRuler::FindEmptyPos( sal_Int32 nPos
, ScMoveMode eDir
) const
325 sal_Int32 nNewPos
= nPos
;
326 if( nNewPos
!= CSV_POS_INVALID
)
331 nNewPos
= std::min( nPos
, FindEmptyPos( 0, MOVE_NEXT
) );
334 nNewPos
= std::max( nPos
, FindEmptyPos( GetPosCount(), MOVE_PREV
) );
337 while( HasSplit( --nNewPos
) ) ;
340 while( HasSplit( ++nNewPos
) ) ;
344 // added to avoid warnings
348 return IsValidSplitPos( nNewPos
) ? nNewPos
: CSV_POS_INVALID
;
351 void ScCsvRuler::MoveCurrSplit( sal_Int32 nNewPos
)
354 Execute( CSVCMD_MOVESPLIT
, GetRulerCursorPos(), nNewPos
);
355 MoveCursor( nNewPos
);
359 void ScCsvRuler::MoveCurrSplitRel( ScMoveMode eDir
)
361 if( HasSplit( GetRulerCursorPos() ) )
363 sal_Int32 nNewPos
= FindEmptyPos( GetRulerCursorPos(), eDir
);
364 if( nNewPos
!= CSV_POS_INVALID
)
365 MoveCurrSplit( nNewPos
);
369 // event handling -------------------------------------------------------------
371 void ScCsvRuler::Resize()
373 ScCsvControl::Resize();
378 void ScCsvRuler::GetFocus()
380 ScCsvControl::GetFocus();
382 if( GetRulerCursorPos() == CSV_POS_INVALID
)
383 MoveCursor( GetNoScrollPos( mnPosCursorLast
) );
387 void ScCsvRuler::LoseFocus()
389 ScCsvControl::LoseFocus();
390 mnPosCursorLast
= GetRulerCursorPos();
391 MoveCursor( CSV_POS_INVALID
);
394 void ScCsvRuler::StyleUpdated()
399 ScCsvControl::StyleUpdated();
402 bool ScCsvRuler::MouseButtonDown( const MouseEvent
& rMEvt
)
409 sal_Int32 nPos
= GetPosFromX( rMEvt
.GetPosPixel().X() );
410 if( IsVisibleSplitPos( nPos
) )
411 StartMouseTracking( nPos
);
412 ImplSetMousePointer( nPos
);
418 bool ScCsvRuler::MouseButtonUp( const MouseEvent
& )
428 bool ScCsvRuler::MouseMove( const MouseEvent
& rMEvt
)
430 if( !rMEvt
.IsModifierChanged() )
432 sal_Int32 nPos
= GetPosFromX( rMEvt
.GetPosPixel().X() );
435 // on mouse tracking: keep position valid
436 nPos
= std::clamp( nPos
, sal_Int32(1), GetPosCount() - 1 );
437 MoveMouseTracking( nPos
);
441 tools::Rectangle
aRect( Point(), maWinSize
);
442 if( !IsVisibleSplitPos( nPos
) || !aRect
.Contains( rMEvt
.GetPosPixel() ) )
443 // if focused, keep old cursor position for key input
444 nPos
= HasFocus() ? GetRulerCursorPos() : CSV_POS_INVALID
;
445 MoveCursor( nPos
, false );
447 ImplSetMousePointer( nPos
);
452 bool ScCsvRuler::KeyInput( const KeyEvent
& rKEvt
)
454 const vcl::KeyCode
& rKCode
= rKEvt
.GetKeyCode();
455 sal_uInt16 nCode
= rKCode
.GetCode();
456 bool bNoMod
= !rKCode
.GetModifier();
457 bool bShift
= (rKCode
.GetModifier() == KEY_SHIFT
);
458 bool bJump
= (rKCode
.GetModifier() == KEY_MOD1
);
459 bool bMove
= (rKCode
.GetModifier() == (KEY_MOD1
| KEY_SHIFT
));
461 ScMoveMode eHDir
= GetHorzDirection( nCode
, true );
462 ScMoveMode eVDir
= GetVertDirection( nCode
, false );
466 if( eHDir
!= MOVE_NONE
)
467 MoveCursorRel( eHDir
);
468 else if( eVDir
!= MOVE_NONE
)
469 ScrollVertRel( eVDir
);
472 case KEY_SPACE
: Execute( CSVCMD_TOGGLESPLIT
, GetRulerCursorPos() ); break;
473 case KEY_INSERT
: Execute( CSVCMD_INSERTSPLIT
, GetRulerCursorPos() ); break;
474 case KEY_DELETE
: Execute( CSVCMD_REMOVESPLIT
, GetRulerCursorPos() ); break;
477 else if( bJump
&& (eHDir
!= MOVE_NONE
) )
478 MoveCursorToSplit( eHDir
);
479 else if( bMove
&& (eHDir
!= MOVE_NONE
) )
480 MoveCurrSplitRel( eHDir
);
481 else if( bShift
&& (nCode
== KEY_DELETE
) )
482 Execute( CSVCMD_REMOVEALLSPLITS
);
484 return rKCode
.GetGroup() == KEYGROUP_CURSOR
;
487 void ScCsvRuler::StartMouseTracking( sal_Int32 nPos
)
489 mnPosMTStart
= mnPosMTCurr
= nPos
;
490 mbPosMTMoved
= false;
491 maOldSplits
= maSplits
;
492 Execute( CSVCMD_INSERTSPLIT
, nPos
);
493 if( HasSplit( nPos
) )
497 void ScCsvRuler::MoveMouseTracking( sal_Int32 nPos
)
499 if( mnPosMTCurr
!= nPos
)
503 if( (mnPosMTCurr
!= mnPosMTStart
) && maOldSplits
.HasSplit( mnPosMTCurr
) )
504 Execute( CSVCMD_INSERTSPLIT
, nPos
);
506 Execute( CSVCMD_MOVESPLIT
, mnPosMTCurr
, nPos
);
513 void ScCsvRuler::EndMouseTracking()
515 // remove on simple click on an existing split
516 if( (mnPosMTCurr
== mnPosMTStart
) && maOldSplits
.HasSplit( mnPosMTCurr
) && !mbPosMTMoved
)
517 Execute( CSVCMD_REMOVESPLIT
, mnPosMTCurr
);
518 mnPosMTStart
= CSV_POS_INVALID
;
521 // painting -------------------------------------------------------------------
523 void ScCsvRuler::Paint( vcl::RenderContext
& rRenderContext
, const tools::Rectangle
& )
525 ImplRedraw(rRenderContext
);
528 void ScCsvRuler::ImplRedraw(vcl::RenderContext
& rRenderContext
)
538 rRenderContext
.DrawOutDev( Point(), maWinSize
, Point(), maWinSize
, *maRulerDev
);
542 tools::Rectangle
ScCsvRuler::GetFocusRect()
544 /* Draws directly tracking rectangle to the column with the specified index. */
546 return tools::Rectangle(0, 0, GetWidth() - 1, GetHeight() - 2);
547 return weld::CustomWidgetController::GetFocusRect();
550 void ScCsvRuler::ImplDrawArea( sal_Int32 nPosX
, sal_Int32 nWidth
)
552 maBackgrDev
->SetLineColor();
553 tools::Rectangle
aRect( Point( nPosX
, 0 ), Size( nWidth
, GetHeight() ) );
554 maBackgrDev
->SetFillColor( maBackColor
);
555 maBackgrDev
->DrawRect( aRect
);
557 aRect
= maActiveRect
;
558 aRect
.SetLeft( std::max( GetFirstX(), nPosX
) );
559 aRect
.SetRight( std::min( std::min( GetX( GetPosCount() ), GetLastX() ), nPosX
+ nWidth
- sal_Int32( 1 ) ) );
560 if( aRect
.Left() <= aRect
.Right() )
562 maBackgrDev
->SetFillColor( maActiveColor
);
563 maBackgrDev
->DrawRect( aRect
);
566 maBackgrDev
->SetLineColor( maTextColor
);
567 sal_Int32 nY
= GetHeight() - 1;
568 maBackgrDev
->DrawLine( Point( nPosX
, nY
), Point( nPosX
+ nWidth
- 1, nY
) );
571 void ScCsvRuler::ImplDrawBackgrDev()
573 ImplDrawArea( 0, GetWidth() );
576 maBackgrDev
->SetLineColor( maTextColor
);
577 maBackgrDev
->SetFillColor();
580 sal_Int32 nFirstPos
= std::max( GetPosFromX( 0 ) - 1, sal_Int32(0) );
581 sal_Int32 nLastPos
= GetPosFromX( GetWidth() );
582 sal_Int32 nY
= (maActiveRect
.Top() + maActiveRect
.Bottom()) / 2;
583 for( nPos
= nFirstPos
; nPos
<= nLastPos
; ++nPos
)
585 sal_Int32 nX
= GetX( nPos
);
587 maBackgrDev
->DrawPixel( Point( nX
, nY
) );
589 maBackgrDev
->DrawLine( Point( nX
, nY
- 1 ), Point( nX
, nY
+ 1 ) );
593 maBackgrDev
->SetTextColor( maTextColor
);
594 maBackgrDev
->SetTextFillColor();
595 for( nPos
= ((nFirstPos
+ 9) / 10) * 10; nPos
<= nLastPos
; nPos
+= 10 )
597 OUString
aText( OUString::number( nPos
) );
598 sal_Int32 nTextWidth
= maBackgrDev
->GetTextWidth( aText
);
599 sal_Int32 nTextX
= GetX( nPos
) - nTextWidth
/ 2;
600 ImplDrawArea( nTextX
- 1, nTextWidth
+ 2 );
601 maBackgrDev
->DrawText( Point( nTextX
, maActiveRect
.Top() ), aText
);
605 void ScCsvRuler::ImplDrawSplit( sal_Int32 nPos
)
607 if( IsVisibleSplitPos( nPos
) )
609 Point
aPos( GetX( nPos
) - mnSplitSize
/ 2, GetHeight() - mnSplitSize
- 2 );
610 Size
aSize( mnSplitSize
, mnSplitSize
);
611 maRulerDev
->SetLineColor( maTextColor
);
612 maRulerDev
->SetFillColor( maSplitColor
);
613 maRulerDev
->DrawEllipse( tools::Rectangle( aPos
, aSize
) );
614 maRulerDev
->DrawPixel( Point( GetX( nPos
), GetHeight() - 2 ) );
618 void ScCsvRuler::ImplEraseSplit( sal_Int32 nPos
)
620 if( IsVisibleSplitPos( nPos
) )
622 ImplInvertCursor( GetRulerCursorPos() );
623 Point
aPos( GetX( nPos
) - mnSplitSize
/ 2, 0 );
624 Size
aSize( mnSplitSize
, GetHeight() );
625 maRulerDev
->DrawOutDev( aPos
, aSize
, aPos
, aSize
, *maBackgrDev
);
626 ImplInvertCursor( GetRulerCursorPos() );
630 void ScCsvRuler::ImplDrawRulerDev()
632 maRulerDev
->DrawOutDev( Point(), maWinSize
, Point(), maWinSize
, *maBackgrDev
);
633 ImplInvertCursor( GetRulerCursorPos() );
635 sal_uInt32 nFirst
= maSplits
.LowerBound( GetFirstVisPos() );
636 sal_uInt32 nLast
= maSplits
.UpperBound( GetLastVisPos() );
637 if( (nFirst
!= CSV_VEC_NOTFOUND
) && (nLast
!= CSV_VEC_NOTFOUND
) )
638 for( sal_uInt32 nIndex
= nFirst
; nIndex
<= nLast
; ++nIndex
)
639 ImplDrawSplit( GetSplitPos( nIndex
) );
642 void ScCsvRuler::ImplInvertCursor( sal_Int32 nPos
)
644 if( IsVisibleSplitPos( nPos
) )
646 ImplInvertRect( *maRulerDev
, tools::Rectangle( Point( GetX( nPos
) - 1, 0 ), Size( 3, GetHeight() - 1 ) ) );
647 if( HasSplit( nPos
) )
648 ImplDrawSplit( nPos
);
652 void ScCsvRuler::ImplSetMousePointer( sal_Int32 nPos
)
654 SetPointer( HasSplit( nPos
) ? PointerStyle::HSplit
: PointerStyle::Arrow
);
657 // accessibility ==============================================================
659 css::uno::Reference
<css::accessibility::XAccessible
> ScCsvRuler::CreateAccessible()
661 rtl::Reference
<ScAccessibleCsvRuler
> xRef(new ScAccessibleCsvRuler(*this));
666 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */