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 <sal/config.h>
24 #include <o3tl/string_view.hxx>
25 #include <tools/debug.hxx>
26 #include <tools/multisel.hxx>
28 #include <rtl/ustrbuf.hxx>
30 void MultiSelection::ImplClear()
32 // no selected indexes
37 std::size_t MultiSelection::ImplFindSubSelection( sal_Int32 nIndex
) const
39 // iterate through the sub selections
42 n
< aSels
.size() && nIndex
> aSels
[ n
].Max();
43 ++n
) {} /* empty loop */
47 void MultiSelection::ImplMergeSubSelections( sal_Int32 nPos1
, std::size_t nPos2
)
49 // didn't a sub selection at nPos2 exist?
50 if ( nPos2
>= aSels
.size() )
53 // did the sub selections touch each other?
54 if ( (aSels
[ nPos1
].Max() + 1) == aSels
[ nPos2
].Min() )
57 aSels
[ nPos1
].Max() = aSels
[ nPos2
].Max();
58 aSels
.erase( aSels
.begin() + nPos2
);
62 MultiSelection::MultiSelection():
71 void MultiSelection::Reset()
73 aTotRange
= Range(0, -1);
75 // clear the old sub selections
79 MultiSelection::MultiSelection( const MultiSelection
& rOrig
) :
80 aTotRange(rOrig
.aTotRange
),
81 nSelCount(rOrig
.nSelCount
),
82 bCurValid(rOrig
.bCurValid
)
86 nCurSubSel
= rOrig
.nCurSubSel
;
87 nCurIndex
= rOrig
.nCurIndex
;
95 // copy the sub selections
96 aSels
.insert( aSels
.end(), rOrig
.aSels
.begin(), rOrig
.aSels
.end() );
99 MultiSelection::MultiSelection( const Range
& rRange
):
108 MultiSelection::~MultiSelection()
112 MultiSelection
& MultiSelection::operator= ( const MultiSelection
& rOrig
)
114 aTotRange
= rOrig
.aTotRange
;
115 bCurValid
= rOrig
.bCurValid
;
118 nCurSubSel
= rOrig
.nCurSubSel
;
119 nCurIndex
= rOrig
.nCurIndex
;
122 // clear the old and copy the sub selections
124 aSels
.insert( aSels
.end(), rOrig
.aSels
.begin(), rOrig
.aSels
.end() );
125 nSelCount
= rOrig
.nSelCount
;
130 void MultiSelection::SelectAll( bool bSelect
)
135 aSels
.push_back( aTotRange
);
136 nSelCount
= aTotRange
.Len();
140 bool MultiSelection::Select( sal_Int32 nIndex
, bool bSelect
)
142 DBG_ASSERT( aTotRange
.Contains(nIndex
), "selected index out of range" );
145 if ( !aTotRange
.Contains(nIndex
) )
148 // find the virtual target position
149 std::size_t nSubSelPos
= ImplFindSubSelection( nIndex
);
153 // is it included in the found sub selection?
154 if ( nSubSelPos
< aSels
.size() && aSels
[ nSubSelPos
].Contains( nIndex
) )
155 // already selected, nothing to do
158 // it will become selected
161 // is it at the end of the previous sub selection
162 if ( nSubSelPos
> 0 &&
163 aSels
[ nSubSelPos
-1 ].Max() == (nIndex
-1) )
165 // expand the previous sub selection
166 aSels
[ nSubSelPos
-1 ].Max() = nIndex
;
168 // try to merge the previous sub selection
169 ImplMergeSubSelections( nSubSelPos
-1, nSubSelPos
);
171 // is it at the beginning of the found sub selection
172 else if ( nSubSelPos
< aSels
.size()
173 && aSels
[ nSubSelPos
].Min() == (nIndex
+1)
175 // expand the found sub selection
176 aSels
[ nSubSelPos
].Min() = nIndex
;
179 // create a new sub selection
180 if ( nSubSelPos
< aSels
.size() ) {
181 aSels
.insert( aSels
.begin() + nSubSelPos
, Range( nIndex
, nIndex
) );
183 aSels
.push_back( Range( nIndex
, nIndex
) );
185 if ( bCurValid
&& nCurSubSel
>= nSubSelPos
)
191 // is it excluded from the found sub selection?
192 if ( nSubSelPos
>= aSels
.size()
193 || !aSels
[ nSubSelPos
].Contains( nIndex
)
195 // not selected, nothing to do
199 // it will become deselected
202 // is it the only index in the found sub selection?
203 if ( aSels
[ nSubSelPos
].Len() == 1 )
205 // remove the complete sub selection
206 aSels
.erase( aSels
.begin() + nSubSelPos
);
210 // is it at the beginning of the found sub selection?
211 if ( aSels
[ nSubSelPos
].Min() == nIndex
)
212 ++aSels
[ nSubSelPos
].Min();
213 // is it at the end of the found sub selection?
214 else if ( aSels
[ nSubSelPos
].Max() == nIndex
)
215 --aSels
[ nSubSelPos
].Max();
216 // it is in the middle of the found sub selection?
219 // split the sub selection
220 if ( nSubSelPos
< aSels
.size() ) {
221 aSels
.insert( aSels
.begin() + nSubSelPos
, Range( aSels
[ nSubSelPos
].Min(), nIndex
-1 ) );
223 aSels
.push_back( Range( aSels
[ nSubSelPos
].Min(), nIndex
-1 ) );
225 aSels
[ nSubSelPos
+1 ].Min() = nIndex
+ 1;
232 void MultiSelection::Select( const Range
& rIndexRange
, bool bSelect
)
236 sal_Int32 nTmpMin
= rIndexRange
.Min();
237 sal_Int32 nTmpMax
= rIndexRange
.Max();
238 sal_Int32 nCurMin
= FirstSelected();
239 sal_Int32 nCurMax
= LastSelected();
240 DBG_ASSERT(aTotRange
.Contains(nTmpMax
), "selected index out of range" );
241 DBG_ASSERT(aTotRange
.Contains(nTmpMin
), "selected index out of range" );
243 // replace whole selection?
244 if( aSels
.empty() || (nTmpMin
<= nCurMin
&& nTmpMax
>= nCurMax
) )
249 aSels
.push_back( rIndexRange
);
250 nSelCount
= rIndexRange
.Len();
254 // expand on left side?
255 if( nTmpMax
< nCurMin
)
259 // extend first range?
260 if( nCurMin
> (nTmpMax
+1) )
262 aSels
.insert( aSels
.begin(), rIndexRange
);
263 nSelCount
+= rIndexRange
.Len();
267 auto & rRange
= aSels
.front();
269 rRange
.Min() = nTmpMin
;
270 nSelCount
+= ( nOld
- nTmpMin
);
276 // expand on right side?
277 else if( nTmpMin
> nCurMax
)
281 // extend last range?
282 if( nTmpMin
> (nCurMax
+1) )
284 aSels
.push_back( rIndexRange
);
285 nSelCount
+= rIndexRange
.Len();
289 auto & rRange
= aSels
.back();
291 rRange
.Max() = nTmpMax
;
292 nSelCount
+= ( nTmpMax
- nOld
);
299 // TODO here is potential for optimization
300 while( nTmpMin
<= nTmpMax
)
302 Select( nTmpMin
, bSelect
);
307 bool MultiSelection::IsSelected( sal_Int32 nIndex
) const
309 // find the virtual target position
310 std::size_t nSubSelPos
= ImplFindSubSelection( nIndex
);
312 return nSubSelPos
< aSels
.size() && aSels
[ nSubSelPos
].Contains(nIndex
);
315 void MultiSelection::Insert( sal_Int32 nIndex
, sal_Int32 nCount
)
317 // find the virtual target position
318 std::size_t nSubSelPos
= ImplFindSubSelection( nIndex
);
320 // did we need to shift the sub selections?
321 if ( nSubSelPos
< aSels
.size() )
322 { // did we insert an unselected into an existing sub selection?
323 if ( aSels
[ nSubSelPos
].Min() != nIndex
324 && aSels
[ nSubSelPos
].Contains(nIndex
)
325 ) { // split the sub selection
326 if ( nSubSelPos
< aSels
.size() ) {
327 aSels
.insert( aSels
.begin() + nSubSelPos
, Range( aSels
[ nSubSelPos
].Min(), nIndex
-1 ) );
329 aSels
.push_back( Range( aSels
[ nSubSelPos
].Min(), nIndex
-1 ) );
332 aSels
[ nSubSelPos
].Min() = nIndex
;
335 // shift the sub selections behind the inserting position
336 for ( std::size_t nPos
= nSubSelPos
; nPos
< aSels
.size(); ++nPos
)
338 aSels
[ nPos
].Min() += nCount
;
339 aSels
[ nPos
].Max() += nCount
;
344 aTotRange
.Max() += nCount
;
347 void MultiSelection::Remove( sal_Int32 nIndex
)
349 // find the virtual target position
350 std::size_t nSubSelPos
= ImplFindSubSelection( nIndex
);
352 // did we remove from an existing sub selection?
353 if ( nSubSelPos
< aSels
.size()
354 && aSels
[ nSubSelPos
].Contains(nIndex
)
356 // does this sub selection only contain the index to be deleted
357 if ( aSels
[ nSubSelPos
].Len() == 1 ) {
358 // completely remove the sub selection
359 aSels
.erase( aSels
.begin() + nSubSelPos
);
361 // shorten this sub selection
362 --( aSels
[ nSubSelPos
++ ].Max() );
365 // adjust the selected counter
369 // shift the sub selections behind the removed index
370 for ( std::size_t nPos
= nSubSelPos
; nPos
< aSels
.size(); ++nPos
)
372 --( aSels
[ nPos
].Min() );
373 --( aSels
[ nPos
].Max() );
377 aTotRange
.Max() -= 1;
380 sal_Int32
MultiSelection::FirstSelected()
384 bCurValid
= !aSels
.empty();
386 return SFX_ENDOFSELECTION
;
388 nCurIndex
= aSels
[ 0 ].Min();
392 sal_Int32
MultiSelection::LastSelected()
394 bCurValid
= !aSels
.empty();
397 return SFX_ENDOFSELECTION
;
399 nCurSubSel
= aSels
.size() - 1;
400 nCurIndex
= aSels
[ nCurSubSel
].Max();
404 sal_Int32
MultiSelection::NextSelected()
407 return SFX_ENDOFSELECTION
;
409 // is the next index in the current sub selection too?
410 if ( nCurIndex
< aSels
[ nCurSubSel
].Max() )
413 // are there further sub selections?
414 if ( ++nCurSubSel
>= aSels
.size() )
415 // we are at the end!
416 return SFX_ENDOFSELECTION
;
418 nCurIndex
= aSels
[ nCurSubSel
].Min();
422 void MultiSelection::SetTotalRange( const Range
& rTotRange
)
424 aTotRange
= rTotRange
;
426 // adjust lower boundary
427 Range
* pRange
= aSels
.empty() ? nullptr : &aSels
.front();
430 if( pRange
->Max() < aTotRange
.Min() )
432 aSels
.erase( aSels
.begin() );
434 else if( pRange
->Min() < aTotRange
.Min() )
436 pRange
->Min() = aTotRange
.Min();
442 pRange
= aSels
.empty() ? nullptr : &aSels
.front();
445 // adjust upper boundary
446 sal_Int32 nCount
= aSels
.size();
449 pRange
= &aSels
[ nCount
- 1 ];
450 if( pRange
->Min() > aTotRange
.Max() )
454 else if( pRange
->Max() > aTotRange
.Max() )
456 pRange
->Max() = aTotRange
.Max();
462 nCount
= aSels
.size();
465 // re-calculate selection count
467 for (Range
const & rSel
: aSels
)
468 nSelCount
+= rSel
.Len();
474 // StringRangeEnumerator
476 StringRangeEnumerator::StringRangeEnumerator( std::u16string_view i_rInput
,
477 sal_Int32 i_nMinNumber
,
478 sal_Int32 i_nMaxNumber
,
479 sal_Int32 i_nLogicalOffset
482 , mnMin( i_nMinNumber
)
483 , mnMax( i_nMaxNumber
)
484 , mnOffset( i_nLogicalOffset
)
485 , mbValidInput( false )
487 // Parse string only if boundaries are valid.
488 if( mnMin
>= 0 && mnMax
>= 0 && mnMin
<= mnMax
)
489 mbValidInput
= setRange( i_rInput
);
492 bool StringRangeEnumerator::checkValue( sal_Int32 i_nValue
, const o3tl::sorted_vector
< sal_Int32
>* i_pPossibleValues
) const
494 if( i_nValue
< 0 || i_nValue
< mnMin
|| i_nValue
> mnMax
)
496 if( i_pPossibleValues
&& i_pPossibleValues
->find( i_nValue
) == i_pPossibleValues
->end() )
501 bool StringRangeEnumerator::insertRange( sal_Int32 i_nFirst
, sal_Int32 i_nLast
, bool bSequence
)
503 bool bSuccess
= true;
506 // Check if the range is completely outside of possible pages range
507 if ((i_nFirst
< mnMin
&& i_nLast
< mnMin
) ||
508 (i_nFirst
> mnMax
&& i_nLast
> mnMax
))
510 if( i_nFirst
< mnMin
)
512 if( i_nFirst
> mnMax
)
514 if( i_nLast
< mnMin
)
516 if( i_nLast
> mnMax
)
518 if( checkValue( i_nFirst
) && checkValue( i_nLast
) )
520 maSequence
.push_back( Range( i_nFirst
, i_nLast
) );
521 sal_Int32 nNumber
= i_nLast
- i_nFirst
;
522 nNumber
= nNumber
< 0 ? -nNumber
: nNumber
;
523 mnCount
+= nNumber
+ 1;
530 if( checkValue( i_nFirst
) )
532 maSequence
.push_back( Range( i_nFirst
, i_nFirst
) );
535 else if( checkValue( i_nLast
) )
537 maSequence
.push_back( Range( i_nLast
, i_nLast
) );
547 void StringRangeEnumerator::insertJoinedRanges(
548 const std::vector
< sal_Int32
>& rNumbers
)
550 size_t nCount
= rNumbers
.size();
556 insertRange( rNumbers
[0], -1, false );
560 for( size_t i
= 0; i
< nCount
- 1; i
++ )
562 sal_Int32 nFirst
= rNumbers
[i
];
563 sal_Int32 nLast
= rNumbers
[i
+ 1];
566 if ( nFirst
> nLast
) nFirst
--;
567 else if( nFirst
< nLast
) nFirst
++;
570 insertRange( nFirst
, nLast
, nFirst
!= nLast
);
574 bool StringRangeEnumerator::setRange( std::u16string_view aNewRange
)
579 auto pInput
= aNewRange
.begin();
580 auto pInputEnd
= aNewRange
.end();
581 OUStringBuffer
aNumberBuf( 16 );
582 std::vector
< sal_Int32
> aNumbers
;
583 bool bSequence
= false;
584 while( pInput
!= pInputEnd
)
586 while( pInput
!= pInputEnd
&& *pInput
>= '0' && *pInput
<= '9' )
587 aNumberBuf
.append( *pInput
++ );
588 if( !aNumberBuf
.isEmpty() )
590 sal_Int32 nNumber
= o3tl::toInt32(aNumberBuf
) + mnOffset
;
591 aNumberBuf
.setLength(0);
592 aNumbers
.push_back( nNumber
);
595 if (pInput
== pInputEnd
)
600 if( aNumbers
.empty() )
602 // push out-of-range small value, to exclude ranges totally outside of possible range
603 aNumbers
.push_back( mnMin
-1 );
606 else if( *pInput
== ',' || *pInput
== ';' )
608 if( bSequence
&& !aNumbers
.empty() )
610 // push out-of-range large value, to exclude ranges totally outside of possible range
611 aNumbers
.push_back( mnMax
+1 );
613 insertJoinedRanges( aNumbers
);
618 else if( *pInput
!= ' ' )
619 return false; // parse error
623 // insert last entries
624 if( bSequence
&& !aNumbers
.empty() )
626 // push out-of-range large value, to exclude ranges totally outside of possible range
627 aNumbers
.push_back( mnMax
+1 );
629 insertJoinedRanges( aNumbers
);
634 bool StringRangeEnumerator::hasValue( sal_Int32 i_nValue
, const o3tl::sorted_vector
< sal_Int32
>* i_pPossibleValues
) const
636 if( i_pPossibleValues
&& i_pPossibleValues
->find( i_nValue
) == i_pPossibleValues
->end() )
638 size_t n
= maSequence
.size();
639 for( size_t i
= 0; i
< n
; ++i
)
641 const StringRangeEnumerator::Range
rRange( maSequence
[i
] );
642 if( rRange
.nFirst
< rRange
.nLast
)
644 if( i_nValue
>= rRange
.nFirst
&& i_nValue
<= rRange
.nLast
)
649 if( i_nValue
>= rRange
.nLast
&& i_nValue
<= rRange
.nFirst
)
656 StringRangeEnumerator::Iterator
& StringRangeEnumerator::Iterator::operator++()
658 if( nRangeIndex
>= 0 && nCurrent
>= 0 && pEnumerator
)
660 const StringRangeEnumerator::Range
& rRange( pEnumerator
->maSequence
[nRangeIndex
] );
661 bool bRangeChange
= false;
662 if( rRange
.nLast
< rRange
.nFirst
)
665 if( nCurrent
> rRange
.nLast
)
673 if( nCurrent
< rRange
.nLast
)
681 if( size_t(nRangeIndex
) == pEnumerator
->maSequence
.size() )
684 nRangeIndex
= nCurrent
= -1;
687 nCurrent
= pEnumerator
->maSequence
[nRangeIndex
].nFirst
;
689 if( nRangeIndex
!= -1 && nCurrent
!= -1 )
691 if( ! pEnumerator
->checkValue( nCurrent
, pPossibleValues
) )
699 bool StringRangeEnumerator::Iterator::operator==( const Iterator
& i_rCompare
) const
701 return i_rCompare
.pEnumerator
== pEnumerator
&& i_rCompare
.nRangeIndex
== nRangeIndex
&& i_rCompare
.nCurrent
== nCurrent
;
704 StringRangeEnumerator::Iterator
StringRangeEnumerator::begin( const o3tl::sorted_vector
< sal_Int32
>* i_pPossibleValues
) const
706 StringRangeEnumerator::Iterator
it( this,
708 maSequence
.empty() ? -1 : 0,
709 maSequence
.empty() ? -1 : maSequence
[0].nFirst
);
710 if( ! checkValue(*it
, i_pPossibleValues
) )
715 StringRangeEnumerator::Iterator
StringRangeEnumerator::end( const o3tl::sorted_vector
< sal_Int32
>* i_pPossibleValues
) const
717 return StringRangeEnumerator::Iterator( this, i_pPossibleValues
, -1, -1 );
720 bool StringRangeEnumerator::getRangesFromString( std::u16string_view i_rPageRange
,
721 std::vector
< sal_Int32
>& o_rPageVector
,
722 sal_Int32 i_nMinNumber
,
723 sal_Int32 i_nMaxNumber
,
724 sal_Int32 i_nLogicalOffset
,
725 o3tl::sorted_vector
< sal_Int32
> const * i_pPossibleValues
728 o_rPageVector
.clear();
730 StringRangeEnumerator
aEnum( i_rPageRange
, i_nMinNumber
, i_nMaxNumber
, i_nLogicalOffset
) ;
732 //Even if the input range wasn't completely valid, return what ranges could
733 //be extracted from the input.
734 o_rPageVector
.reserve( static_cast< size_t >( aEnum
.size() ) );
735 for( StringRangeEnumerator::Iterator it
= aEnum
.begin( i_pPossibleValues
);
736 it
!= aEnum
.end( i_pPossibleValues
); ++it
)
738 o_rPageVector
.push_back( *it
);
741 return aEnum
.mbValidInput
;
744 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */