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 OUString SEP_PATH
= u
"Office.Calc/Dialogs/CSVImport"_ustr
;
36 constexpr OUString FIXED_WIDTH_LIST
= u
"FixedWidthList"_ustr
;
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(OUString::number(rSplits
[i
]) + ";");
80 OUString sFixedWidthLists
= sSplits
.makeStringAndClear();
81 Sequence
<Any
> aValues
;
83 Sequence
<OUString
> aNames
{ FIXED_WIDTH_LIST
};
84 ScLinkConfigItem
aItem( SEP_PATH
);
86 aValues
= aItem
.GetProperties( aNames
);
87 pProperties
= aValues
.getArray();
88 pProperties
[0] <<= sFixedWidthLists
;
90 aItem
.PutProperties(aNames
, aValues
);
93 ScCsvRuler::ScCsvRuler(const ScCsvLayoutData
& rData
, ScCsvTableBox
* pTableBox
)
95 , mpTableBox(pTableBox
)
105 void ScCsvRuler::SetDrawingArea(weld::DrawingArea
* pDrawingArea
)
107 ScCsvControl::SetDrawingArea(pDrawingArea
);
111 Size
aSize(1, GetTextHeight() + mnSplitSize
+ 2);
112 pDrawingArea
->set_size_request(aSize
.Width(), aSize
.Height());
113 SetOutputSizePixel(aSize
);
115 EnableRTL( false ); // RTL
119 OutputDevice
& rRefDevice
= pDrawingArea
->get_ref_device();
120 maBackgrDev
->SetFont( rRefDevice
.GetFont() );
121 maRulerDev
->SetFont( rRefDevice
.GetFont() );
122 load_FixedWidthList( maSplits
);
125 ScCsvRuler::~ScCsvRuler()
127 save_FixedWidthList( maSplits
);
130 // common ruler handling ------------------------------------------------------
132 void ScCsvRuler::ApplyLayout( const ScCsvLayoutData
& rOldData
)
134 ScCsvDiff nDiff
= GetLayoutData().GetDiff( rOldData
) & (ScCsvDiff::HorizontalMask
| ScCsvDiff::RulerCursor
);
135 if( nDiff
== ScCsvDiff::Equal
) return;
138 if( nDiff
& ScCsvDiff::HorizontalMask
)
141 if( GetRulerCursorPos() >= GetPosCount() )
142 MoveCursor( GetPosCount() - 1 );
144 if( nDiff
& ScCsvDiff::RulerCursor
)
146 ImplInvertCursor( rOldData
.mnPosCursor
);
147 ImplInvertCursor( GetRulerCursorPos() );
151 if( nDiff
& ScCsvDiff::PosOffset
)
152 AccSendVisibleEvent();
155 void ScCsvRuler::InitColors()
157 const StyleSettings
& rSett
= Application::GetSettings().GetStyleSettings();
158 maBackColor
= rSett
.GetFaceColor();
159 maActiveColor
= rSett
.GetFieldColor();
160 maTextColor
= rSett
.GetLabelTextColor();
161 maSplitColor
= maBackColor
.IsDark() ? maTextColor
: COL_LIGHTRED
;
165 void ScCsvRuler::UpdateSplitSize()
167 mnSplitSize
= (GetCharWidth() * 3 / 5) | 1; // make an odd number
170 void ScCsvRuler::InitSizeData()
172 maWinSize
= GetOutputSizePixel();
176 sal_Int32 nActiveWidth
= std::min( GetWidth() - GetHdrWidth(), GetPosCount() * GetCharWidth() );
177 sal_Int32 nActiveHeight
= GetTextHeight();
179 maActiveRect
.SetPos( Point( GetFirstX(), (GetHeight() - nActiveHeight
- 1) / 2 ) );
180 maActiveRect
.SetSize( Size( nActiveWidth
, nActiveHeight
) );
182 maBackgrDev
->SetOutputSizePixel( maWinSize
);
183 maRulerDev
->SetOutputSizePixel( maWinSize
);
188 void ScCsvRuler::MoveCursor( sal_Int32 nPos
, bool bScroll
)
192 Execute( CSVCMD_MAKEPOSVISIBLE
, nPos
);
193 Execute( CSVCMD_MOVERULERCURSOR
, IsVisibleSplitPos( nPos
) ? nPos
: CSV_POS_INVALID
);
198 void ScCsvRuler::MoveCursorRel( ScMoveMode eDir
)
200 if( GetRulerCursorPos() == CSV_POS_INVALID
)
209 MoveCursor( GetPosCount() - 1 );
212 if( GetRulerCursorPos() > 1 )
213 MoveCursor( GetRulerCursorPos() - 1 );
216 if( GetRulerCursorPos() < GetPosCount() - 1 )
217 MoveCursor( GetRulerCursorPos() + 1 );
221 // added to avoid warnings
226 void ScCsvRuler::MoveCursorToSplit( ScMoveMode eDir
)
228 if( GetRulerCursorPos() == CSV_POS_INVALID
)
231 sal_uInt32 nIndex
= CSV_VEC_NOTFOUND
;
234 case MOVE_FIRST
: nIndex
= maSplits
.LowerBound( 0 ); break;
235 case MOVE_LAST
: nIndex
= maSplits
.UpperBound( GetPosCount() ); break;
236 case MOVE_PREV
: nIndex
= maSplits
.UpperBound( GetRulerCursorPos() - 1 ); break;
237 case MOVE_NEXT
: nIndex
= maSplits
.LowerBound( GetRulerCursorPos() + 1 ); break;
240 // added to avoid warnings
243 sal_Int32 nPos
= maSplits
[ nIndex
];
244 if( nPos
!= CSV_POS_INVALID
)
248 void ScCsvRuler::ScrollVertRel( ScMoveMode eDir
)
250 sal_Int32 nLine
= GetFirstVisLine();
253 case MOVE_PREV
: --nLine
; break;
254 case MOVE_NEXT
: ++nLine
; break;
255 case MOVE_PREVPAGE
: nLine
-= GetVisLineCount() - 1; break;
256 case MOVE_NEXTPAGE
: nLine
+= GetVisLineCount() - 1; break;
259 // added to avoid warnings
262 Execute( CSVCMD_SETLINEOFFSET
, nLine
);
265 // split handling -------------------------------------------------------------
267 sal_Int32
ScCsvRuler::GetNoScrollPos( sal_Int32 nPos
) const
269 sal_Int32 nNewPos
= nPos
;
270 if( nNewPos
!= CSV_POS_INVALID
)
272 if( nNewPos
< GetFirstVisPos() + CSV_SCROLL_DIST
)
274 sal_Int32 nScroll
= (GetFirstVisPos() > 0) ? CSV_SCROLL_DIST
: 0;
275 nNewPos
= std::max( nPos
, GetFirstVisPos() + nScroll
);
277 else if( nNewPos
> GetLastVisPos() - CSV_SCROLL_DIST
- 1 )
279 sal_Int32 nScroll
= (GetFirstVisPos() < GetMaxPosOffset()) ? CSV_SCROLL_DIST
: 0;
280 nNewPos
= std::min( nNewPos
, GetLastVisPos() - nScroll
- sal_Int32( 1 ) );
286 void ScCsvRuler::InsertSplit( sal_Int32 nPos
)
288 if( maSplits
.Insert( nPos
) )
290 ImplDrawSplit( nPos
);
295 void ScCsvRuler::RemoveSplit( sal_Int32 nPos
)
297 if( maSplits
.Remove( nPos
) )
299 ImplEraseSplit( nPos
);
304 void ScCsvRuler::MoveSplit( sal_Int32 nPos
, sal_Int32 nNewPos
)
306 bool bRemove
= maSplits
.Remove( nPos
);
307 bool bInsert
= maSplits
.Insert( nNewPos
);
308 if( bRemove
|| bInsert
)
310 ImplEraseSplit( nPos
);
311 ImplDrawSplit( nNewPos
);
316 void ScCsvRuler::RemoveAllSplits()
322 sal_Int32
ScCsvRuler::FindEmptyPos( sal_Int32 nPos
, ScMoveMode eDir
) const
324 sal_Int32 nNewPos
= nPos
;
325 if( nNewPos
!= CSV_POS_INVALID
)
330 nNewPos
= std::min( nPos
, FindEmptyPos( 0, MOVE_NEXT
) );
333 nNewPos
= std::max( nPos
, FindEmptyPos( GetPosCount(), MOVE_PREV
) );
336 while( HasSplit( --nNewPos
) ) ;
339 while( HasSplit( ++nNewPos
) ) ;
343 // added to avoid warnings
347 return IsValidSplitPos( nNewPos
) ? nNewPos
: CSV_POS_INVALID
;
350 void ScCsvRuler::MoveCurrSplit( sal_Int32 nNewPos
)
353 Execute( CSVCMD_MOVESPLIT
, GetRulerCursorPos(), nNewPos
);
354 MoveCursor( nNewPos
);
358 void ScCsvRuler::MoveCurrSplitRel( ScMoveMode eDir
)
360 if( HasSplit( GetRulerCursorPos() ) )
362 sal_Int32 nNewPos
= FindEmptyPos( GetRulerCursorPos(), eDir
);
363 if( nNewPos
!= CSV_POS_INVALID
)
364 MoveCurrSplit( nNewPos
);
368 // event handling -------------------------------------------------------------
370 void ScCsvRuler::Resize()
372 ScCsvControl::Resize();
377 void ScCsvRuler::GetFocus()
379 ScCsvControl::GetFocus();
381 if( GetRulerCursorPos() == CSV_POS_INVALID
)
382 MoveCursor( GetNoScrollPos( mnPosCursorLast
) );
386 void ScCsvRuler::LoseFocus()
388 ScCsvControl::LoseFocus();
389 mnPosCursorLast
= GetRulerCursorPos();
390 MoveCursor( CSV_POS_INVALID
);
393 void ScCsvRuler::StyleUpdated()
398 ScCsvControl::StyleUpdated();
401 bool ScCsvRuler::MouseButtonDown( const MouseEvent
& rMEvt
)
408 sal_Int32 nPos
= GetPosFromX( rMEvt
.GetPosPixel().X() );
409 if( IsVisibleSplitPos( nPos
) )
410 StartMouseTracking( nPos
);
411 ImplSetMousePointer( nPos
);
417 bool ScCsvRuler::MouseButtonUp( const MouseEvent
& )
427 bool ScCsvRuler::MouseMove( const MouseEvent
& rMEvt
)
429 if( !rMEvt
.IsModifierChanged() )
431 sal_Int32 nPos
= GetPosFromX( rMEvt
.GetPosPixel().X() );
434 // on mouse tracking: keep position valid
435 nPos
= std::clamp( nPos
, sal_Int32(1), GetPosCount() - 1 );
436 MoveMouseTracking( nPos
);
440 tools::Rectangle
aRect( Point(), maWinSize
);
441 if( !IsVisibleSplitPos( nPos
) || !aRect
.Contains( rMEvt
.GetPosPixel() ) )
442 // if focused, keep old cursor position for key input
443 nPos
= HasFocus() ? GetRulerCursorPos() : CSV_POS_INVALID
;
444 MoveCursor( nPos
, false );
446 ImplSetMousePointer( nPos
);
451 bool ScCsvRuler::KeyInput( const KeyEvent
& rKEvt
)
453 const vcl::KeyCode
& rKCode
= rKEvt
.GetKeyCode();
454 sal_uInt16 nCode
= rKCode
.GetCode();
455 bool bNoMod
= !rKCode
.GetModifier();
456 bool bShift
= (rKCode
.GetModifier() == KEY_SHIFT
);
457 bool bJump
= (rKCode
.GetModifier() == KEY_MOD1
);
458 bool bMove
= (rKCode
.GetModifier() == (KEY_MOD1
| KEY_SHIFT
));
460 ScMoveMode eHDir
= GetHorzDirection( nCode
, true );
461 ScMoveMode eVDir
= GetVertDirection( nCode
, false );
465 if( eHDir
!= MOVE_NONE
)
466 MoveCursorRel( eHDir
);
467 else if( eVDir
!= MOVE_NONE
)
468 ScrollVertRel( eVDir
);
471 case KEY_SPACE
: Execute( CSVCMD_TOGGLESPLIT
, GetRulerCursorPos() ); break;
472 case KEY_INSERT
: Execute( CSVCMD_INSERTSPLIT
, GetRulerCursorPos() ); break;
473 case KEY_DELETE
: Execute( CSVCMD_REMOVESPLIT
, GetRulerCursorPos() ); break;
476 else if( bJump
&& (eHDir
!= MOVE_NONE
) )
477 MoveCursorToSplit( eHDir
);
478 else if( bMove
&& (eHDir
!= MOVE_NONE
) )
479 MoveCurrSplitRel( eHDir
);
480 else if( bShift
&& (nCode
== KEY_DELETE
) )
481 Execute( CSVCMD_REMOVEALLSPLITS
);
483 return rKCode
.GetGroup() == KEYGROUP_CURSOR
;
486 void ScCsvRuler::StartMouseTracking( sal_Int32 nPos
)
488 mnPosMTStart
= mnPosMTCurr
= nPos
;
489 mbPosMTMoved
= false;
490 maOldSplits
= maSplits
;
491 Execute( CSVCMD_INSERTSPLIT
, nPos
);
492 if( HasSplit( nPos
) )
496 void ScCsvRuler::MoveMouseTracking( sal_Int32 nPos
)
498 if( mnPosMTCurr
!= nPos
)
502 if( (mnPosMTCurr
!= mnPosMTStart
) && maOldSplits
.HasSplit( mnPosMTCurr
) )
503 Execute( CSVCMD_INSERTSPLIT
, nPos
);
505 Execute( CSVCMD_MOVESPLIT
, mnPosMTCurr
, nPos
);
512 void ScCsvRuler::EndMouseTracking()
514 // remove on simple click on an existing split
515 if( (mnPosMTCurr
== mnPosMTStart
) && maOldSplits
.HasSplit( mnPosMTCurr
) && !mbPosMTMoved
)
516 Execute( CSVCMD_REMOVESPLIT
, mnPosMTCurr
);
517 mnPosMTStart
= CSV_POS_INVALID
;
520 // painting -------------------------------------------------------------------
522 void ScCsvRuler::Paint( vcl::RenderContext
& rRenderContext
, const tools::Rectangle
& )
524 ImplRedraw(rRenderContext
);
527 void ScCsvRuler::ImplRedraw(vcl::RenderContext
& rRenderContext
)
537 rRenderContext
.DrawOutDev( Point(), maWinSize
, Point(), maWinSize
, *maRulerDev
);
541 tools::Rectangle
ScCsvRuler::GetFocusRect()
543 /* Draws directly tracking rectangle to the column with the specified index. */
545 return tools::Rectangle(0, 0, GetWidth() - 1, GetHeight() - 2);
546 return weld::CustomWidgetController::GetFocusRect();
549 void ScCsvRuler::ImplDrawArea( sal_Int32 nPosX
, sal_Int32 nWidth
)
551 maBackgrDev
->SetLineColor();
552 tools::Rectangle
aRect( Point( nPosX
, 0 ), Size( nWidth
, GetHeight() ) );
553 maBackgrDev
->SetFillColor( maBackColor
);
554 maBackgrDev
->DrawRect( aRect
);
556 aRect
= maActiveRect
;
557 aRect
.SetLeft( std::max( GetFirstX(), nPosX
) );
558 aRect
.SetRight( std::min( std::min( GetX( GetPosCount() ), GetLastX() ), nPosX
+ nWidth
- sal_Int32( 1 ) ) );
559 if( aRect
.Left() <= aRect
.Right() )
561 maBackgrDev
->SetFillColor( maActiveColor
);
562 maBackgrDev
->DrawRect( aRect
);
565 maBackgrDev
->SetLineColor( maTextColor
);
566 sal_Int32 nY
= GetHeight() - 1;
567 maBackgrDev
->DrawLine( Point( nPosX
, nY
), Point( nPosX
+ nWidth
- 1, nY
) );
570 void ScCsvRuler::ImplDrawBackgrDev()
572 ImplDrawArea( 0, GetWidth() );
575 maBackgrDev
->SetLineColor( maTextColor
);
576 maBackgrDev
->SetFillColor();
579 sal_Int32 nFirstPos
= std::max( GetPosFromX( 0 ) - 1, sal_Int32(0) );
580 sal_Int32 nLastPos
= GetPosFromX( GetWidth() );
581 sal_Int32 nY
= (maActiveRect
.Top() + maActiveRect
.Bottom()) / 2;
582 for( nPos
= nFirstPos
; nPos
<= nLastPos
; ++nPos
)
584 sal_Int32 nX
= GetX( nPos
);
586 maBackgrDev
->DrawPixel( Point( nX
, nY
) );
588 maBackgrDev
->DrawLine( Point( nX
, nY
- 1 ), Point( nX
, nY
+ 1 ) );
592 maBackgrDev
->SetTextColor( maTextColor
);
593 maBackgrDev
->SetTextFillColor();
594 for( nPos
= ((nFirstPos
+ 9) / 10) * 10; nPos
<= nLastPos
; nPos
+= 10 )
596 OUString
aText( OUString::number( nPos
) );
597 sal_Int32 nTextWidth
= maBackgrDev
->GetTextWidth( aText
);
598 sal_Int32 nTextX
= GetX( nPos
) - nTextWidth
/ 2;
599 ImplDrawArea( nTextX
- 1, nTextWidth
+ 2 );
600 maBackgrDev
->DrawText( Point( nTextX
, maActiveRect
.Top() ), aText
);
604 void ScCsvRuler::ImplDrawSplit( sal_Int32 nPos
)
606 if( IsVisibleSplitPos( nPos
) )
608 Point
aPos( GetX( nPos
) - mnSplitSize
/ 2, GetHeight() - mnSplitSize
- 2 );
609 Size
aSize( mnSplitSize
, mnSplitSize
);
610 maRulerDev
->SetLineColor( maTextColor
);
611 maRulerDev
->SetFillColor( maSplitColor
);
612 maRulerDev
->DrawEllipse( tools::Rectangle( aPos
, aSize
) );
613 maRulerDev
->DrawPixel( Point( GetX( nPos
), GetHeight() - 2 ) );
617 void ScCsvRuler::ImplEraseSplit( sal_Int32 nPos
)
619 if( IsVisibleSplitPos( nPos
) )
621 ImplInvertCursor( GetRulerCursorPos() );
622 Point
aPos( GetX( nPos
) - mnSplitSize
/ 2, 0 );
623 Size
aSize( mnSplitSize
, GetHeight() );
624 maRulerDev
->DrawOutDev( aPos
, aSize
, aPos
, aSize
, *maBackgrDev
);
625 ImplInvertCursor( GetRulerCursorPos() );
629 void ScCsvRuler::ImplDrawRulerDev()
631 maRulerDev
->DrawOutDev( Point(), maWinSize
, Point(), maWinSize
, *maBackgrDev
);
632 ImplInvertCursor( GetRulerCursorPos() );
634 sal_uInt32 nFirst
= maSplits
.LowerBound( GetFirstVisPos() );
635 sal_uInt32 nLast
= maSplits
.UpperBound( GetLastVisPos() );
636 if( (nFirst
!= CSV_VEC_NOTFOUND
) && (nLast
!= CSV_VEC_NOTFOUND
) )
637 for( sal_uInt32 nIndex
= nFirst
; nIndex
<= nLast
; ++nIndex
)
638 ImplDrawSplit( GetSplitPos( nIndex
) );
641 void ScCsvRuler::ImplInvertCursor( sal_Int32 nPos
)
643 if( IsVisibleSplitPos( nPos
) )
645 ImplInvertRect( *maRulerDev
, tools::Rectangle( Point( GetX( nPos
) - 1, 0 ), Size( 3, GetHeight() - 1 ) ) );
646 if( HasSplit( nPos
) )
647 ImplDrawSplit( nPos
);
651 void ScCsvRuler::ImplSetMousePointer( sal_Int32 nPos
)
653 SetPointer( HasSplit( nPos
) ? PointerStyle::HSplit
: PointerStyle::Arrow
);
656 // accessibility ==============================================================
658 css::uno::Reference
<css::accessibility::XAccessible
> ScCsvRuler::CreateAccessible()
660 rtl::Reference
<ScAccessibleCsvRuler
> xRef(new ScAccessibleCsvRuler(*this));
665 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */