Coverage Report

Created: 2026-08-14 10:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/sfx2/source/control/thumbnailview.cxx
Line
Count
Source
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
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/.
8
 */
9
10
#include <config_wasm_strip.h>
11
12
#include <sfx2/thumbnailview.hxx>
13
#include <sfx2/thumbnailviewitem.hxx>
14
15
#include <algorithm>
16
#include <utility>
17
18
#include "thumbnailviewacc.hxx"
19
#include "thumbnailviewitemacc.hxx"
20
21
#include <basegfx/color/bcolortools.hxx>
22
#include <comphelper/processfactory.hxx>
23
#include <comphelper/propertyvalue.hxx>
24
#include <drawinglayer/attribute/fontattribute.hxx>
25
#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx>
26
#include <drawinglayer/primitive2d/Primitive2DContainer.hxx>
27
#include <drawinglayer/primitive2d/textlayoutdevice.hxx>
28
#include <drawinglayer/processor2d/baseprocessor2d.hxx>
29
#include <drawinglayer/processor2d/processor2dtools.hxx>
30
#include <o3tl/safeint.hxx>
31
#include <rtl/ustring.hxx>
32
#include <sal/log.hxx>
33
#include <svtools/optionsdrawinglayer.hxx>
34
#include <tools/stream.hxx>
35
#include <comphelper/diagnose_ex.hxx>
36
#include <unotools/ucbstreamhelper.hxx>
37
#include <vcl/svapp.hxx>
38
#include <vcl/settings.hxx>
39
#include <vcl/event.hxx>
40
#include <vcl/filter/PngImageReader.hxx>
41
#include <vcl/graph.hxx>
42
#include <vcl/graphicfilter.hxx>
43
#include <vcl/weld/weldutils.hxx>
44
45
#include <com/sun/star/accessibility/AccessibleEventId.hpp>
46
#include <com/sun/star/embed/ElementModes.hpp>
47
#include <com/sun/star/embed/StorageFactory.hpp>
48
#include <com/sun/star/embed/StorageFormats.hpp>
49
#include <com/sun/star/embed/XHierarchicalStorageAccess.hpp>
50
#include <com/sun/star/embed/XRelationshipAccess.hpp>
51
#include <com/sun/star/embed/XStorage.hpp>
52
#include <com/sun/star/packages/zip/ZipFileAccess.hpp>
53
54
#include <memory>
55
#if !ENABLE_WASM_STRIP_RECENT
56
#include "recentdocsviewitem.hxx"
57
#endif
58
59
using namespace basegfx;
60
using namespace drawinglayer::attribute;
61
using namespace drawinglayer::primitive2d;
62
63
constexpr int gnFineness = 5;
64
65
bool ThumbnailView::renameItem(ThumbnailViewItem&, const OUString&)
66
0
{
67
    // Do nothing by default
68
0
    return false;
69
0
}
70
71
static css::uno::Reference<css::embed::XHierarchicalStorageAccess>
72
getStorageAccess(const OUString& URL, sal_Int32 format)
73
0
{
74
0
    auto xFactory = css::embed::StorageFactory::create(comphelper::getProcessComponentContext());
75
0
    css::uno::Sequence descriptor{ comphelper::makePropertyValue(u"StorageFormat"_ustr, format) };
76
0
    css::uno::Sequence args{ css::uno::Any(URL), css::uno::Any(css::embed::ElementModes::READ),
77
0
                             css::uno::Any(descriptor) };
78
0
    return xFactory->createInstanceWithArguments(args)
79
0
        .queryThrow<css::embed::XHierarchicalStorageAccess>();
80
0
}
81
82
static css::uno::Reference<css::io::XInputStream>
83
getHierarchicalStream(const css::uno::Reference<css::embed::XHierarchicalStorageAccess>& xStorage,
84
                      const OUString& name)
85
0
{
86
0
    auto xStream
87
0
        = xStorage->openStreamElementByHierarchicalName(name, css::embed::ElementModes::READ);
88
0
    return xStream->getInputStream();
89
0
}
90
91
static css::uno::Reference<css::io::XInputStream>
92
getFirstHierarchicalStream(const OUString& URL, sal_Int32 format,
93
                           std::initializer_list<OUString> names)
94
0
{
95
0
    auto xStorage(getStorageAccess(URL, format));
96
0
    for (const auto& name : names)
97
0
    {
98
0
        try
99
0
        {
100
0
            return getHierarchicalStream(xStorage, name);
101
0
        }
102
0
        catch (const css::uno::Exception&)
103
0
        {
104
0
            TOOLS_WARN_EXCEPTION("sfx", "caught exception while trying to access " << name << " of "
105
0
                                                                                   << URL);
106
0
        }
107
0
    }
108
0
    return {};
109
0
}
110
111
static css::uno::Reference<css::io::XInputStream>
112
getFirstStreamByRelType(const OUString& URL, std::initializer_list<OUString> types)
113
0
{
114
0
    auto xStorage(getStorageAccess(URL, css::embed::StorageFormats::OFOPXML));
115
0
    if (auto xRelationshipAccess = xStorage.query<css::embed::XRelationshipAccess>())
116
0
    {
117
0
        for (const auto& type : types)
118
0
        {
119
0
            auto rels = xRelationshipAccess->getRelationshipsByType(type);
120
0
            if (rels.hasElements())
121
0
            {
122
                // ISO/IEC 29500-1:2016(E) 15.2.16 Thumbnail Part: "Packages shall not contain
123
                // more than one thumbnail relationship associated with the package as a whole"
124
0
                for (const auto& [tag, value] : rels[0])
125
0
                {
126
0
                    if (tag == "Id")
127
0
                    {
128
0
                        return getHierarchicalStream(xStorage,
129
0
                                                     xRelationshipAccess->getTargetByID(value));
130
0
                    }
131
0
                }
132
0
            }
133
0
        }
134
0
    }
135
0
    return {};
136
0
}
137
138
Bitmap ThumbnailView::readThumbnail(const OUString &msURL)
139
0
{
140
0
    using namespace ::com::sun::star;
141
0
    using namespace ::com::sun::star::uno;
142
143
    // Load the thumbnail from a template document.
144
0
    uno::Reference<io::XInputStream> xIStream;
145
146
0
    try
147
0
    {
148
        // An (older) implementation had a bug - The storage
149
        // name was "Thumbnail" instead of "Thumbnails".  The
150
        // old name is still used as fallback but this code can
151
        // be removed soon.
152
0
        xIStream = getFirstHierarchicalStream(
153
0
            msURL, embed::StorageFormats::PACKAGE,
154
0
            { u"Thumbnails/thumbnail.png"_ustr, u"Thumbnail/thumbnail.png"_ustr });
155
0
    }
156
0
    catch (const uno::Exception&)
157
0
    {
158
0
        TOOLS_WARN_EXCEPTION("sfx",
159
0
            "caught exception while trying to access thumbnail of "
160
0
            << msURL);
161
0
    }
162
163
0
    if (!xIStream.is())
164
0
    {
165
        // OOXML?
166
0
        try
167
0
        {
168
            // Check both Transitional and Strict relationships
169
0
            xIStream = getFirstStreamByRelType(
170
0
                msURL,
171
0
                { u"http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail"_ustr,
172
0
                  u"http://purl.oclc.org/ooxml/officeDocument/relationships/metadata/thumbnail"_ustr });
173
0
        }
174
0
        catch (const uno::Exception&)
175
0
        {
176
            // Not an OOXML; fine
177
0
        }
178
0
    }
179
180
    // Extract the image from the stream.
181
0
    Bitmap aThumbnail;
182
0
    if (auto pStream = utl::UcbStreamHelper::CreateStream(xIStream, /*CloseStream=*/true))
183
0
    {
184
0
        Graphic aGraphic = GraphicFilter::GetGraphicFilter().ImportUnloadedGraphic(*pStream);
185
0
        aThumbnail = aGraphic.GetBitmap();
186
0
    }
187
188
    // Note that the preview is returned without scaling it to the desired
189
    // width.  This gives the caller the chance to take advantage of a
190
    // possibly larger resolution then was asked for.
191
0
    return aThumbnail;
192
0
}
193
194
ThumbnailView::ThumbnailView(std::unique_ptr<weld::ScrolledWindow> xWindow)
195
0
    : mnThumbnailHeight(0)
196
0
    , mnDisplayHeight(0)
197
0
    , mnVItemSpace(-1)
198
0
    , mbAllowVScrollBar(xWindow->get_vpolicy() != VclPolicyType::NEVER)
199
0
    , mbSelectOnFocus(true)
200
0
    , mpItemAttrs(new ThumbnailItemAttributes)
201
0
    , mxScrolledWindow(std::move(xWindow))
202
0
{
203
0
    ImplInit();
204
0
    mxScrolledWindow->connect_vadjustment_value_changed(LINK(this, ThumbnailView, ImplScrollHdl));
205
0
}
206
207
ThumbnailView::~ThumbnailView()
208
0
{
209
0
    ImplDeleteItems();
210
211
0
    if (mxAccessible.is())
212
0
        mxAccessible->dispose();
213
214
0
    mpItemAttrs.reset();
215
0
}
216
217
bool ThumbnailView::MouseMove(const MouseEvent& rMEvt)
218
0
{
219
0
    size_t nItemCount = mFilteredItemList.size();
220
0
    Point aPoint = rMEvt.GetPosPixel();
221
222
0
    for (size_t i = 0; i < nItemCount; i++)
223
0
    {
224
0
        ThumbnailViewItem *pItem = mFilteredItemList[i];
225
0
        ::tools::Rectangle aToInvalidate(pItem->updateHighlight(pItem->mbVisible && !rMEvt.IsLeaveWindow(), aPoint));
226
0
        if (!aToInvalidate.IsEmpty() && IsReallyVisible())
227
0
            Invalidate(aToInvalidate);
228
0
    }
229
230
0
    return true;
231
0
}
232
233
OUString ThumbnailView::RequestHelp(tools::Rectangle& rHelpRect)
234
0
{
235
0
    if (!mbShowTooltips)
236
0
        return OUString();
237
238
0
    Point aPos = rHelpRect.TopLeft();
239
0
    size_t nItemCount = mFilteredItemList.size();
240
0
    for (size_t i = 0; i < nItemCount; i++)
241
0
    {
242
0
        ThumbnailViewItem *pItem = mFilteredItemList[i];
243
0
        if (!pItem->mbVisible)
244
0
            continue;
245
0
        const tools::Rectangle& rDrawArea = pItem->getDrawArea();
246
0
        if (rDrawArea.Contains(aPos))
247
0
        {
248
0
            rHelpRect = rDrawArea;
249
0
            return pItem->getHelpText();
250
0
        }
251
0
    }
252
253
0
    return OUString();
254
0
}
255
256
void ThumbnailView::AppendItem(std::unique_ptr<ThumbnailViewItem> pItem)
257
0
{
258
0
    if (maFilterFunc(pItem.get()))
259
0
    {
260
        // Save current start,end range, iterator might get invalidated
261
0
        size_t nSelStartPos = 0;
262
0
        ThumbnailViewItem *pSelStartItem = nullptr;
263
264
0
        if (mpStartSelRange != mFilteredItemList.end())
265
0
        {
266
0
            pSelStartItem = *mpStartSelRange;
267
0
            nSelStartPos = mpStartSelRange - mFilteredItemList.begin();
268
0
        }
269
270
0
        mFilteredItemList.push_back(pItem.get());
271
0
        mpStartSelRange = pSelStartItem != nullptr ? mFilteredItemList.begin() + nSelStartPos : mFilteredItemList.end();
272
0
    }
273
274
0
    mItemList.push_back(std::move(pItem));
275
0
}
276
277
void ThumbnailView::ImplInit()
278
0
{
279
0
    mnItemWidth = 0;
280
0
    mnItemHeight = 0;
281
0
    mnItemPadding = 0;
282
0
    mnVisLines = 0;
283
0
    mnLines = 0;
284
0
    mnFirstLine = 0;
285
0
    mnCols = 0;
286
0
    mnPinnedSeparatorY = 0;
287
0
    mnPinnedSeparatorMargin = 0;
288
0
    mbScroll = false;
289
0
    mbHasVisibleItems = false;
290
0
    mbHasPinnedSeparator = false;
291
0
    mbShowTooltips = false;
292
0
    mbDrawMnemonics = false;
293
0
    mbAllowMultiSelection = true;
294
0
    maFilterFunc = ViewFilterAll();
295
296
0
    mpStartSelRange = mFilteredItemList.end();
297
298
0
    mfHighlightTransparence = SvtOptionsDrawinglayer::GetTransparentSelectionPercent() * 0.01;
299
0
    mpItemAttrs->nMaxTextLength = 0;
300
301
0
    UpdateColors(Application::GetSettings().GetStyleSettings());
302
0
    updateItemAttrsFromColors();
303
0
}
304
305
void ThumbnailView::UpdateColors(const StyleSettings& rSettings)
306
0
{
307
0
    maFillColor = rSettings.GetFieldColor();
308
0
    maTextColor = rSettings.GetWindowTextColor();
309
0
    maHighlightColor = rSettings.GetHighlightColor();
310
0
    maHighlightTextColor = rSettings.GetHighlightTextColor();
311
0
}
312
313
void ThumbnailView::updateItemAttrsFromColors()
314
0
{
315
0
    mpItemAttrs->aFillColor = maFillColor.getBColor();
316
0
    mpItemAttrs->aTextColor = maTextColor.getBColor();
317
0
    mpItemAttrs->aHighlightColor = maHighlightColor.getBColor();
318
0
    mpItemAttrs->aHighlightTextColor = maHighlightTextColor.getBColor();
319
0
    mpItemAttrs->fHighlightTransparence = mfHighlightTransparence;
320
0
}
321
322
void ThumbnailView::ImplDeleteItems()
323
0
{
324
0
    const size_t n = mItemList.size();
325
326
0
    for ( size_t i = 0; i < n; ++i )
327
0
    {
328
0
        ThumbnailViewItem *const pItem = mItemList[i].get();
329
330
        // deselect all current selected items and fire events
331
0
        if (pItem->isSelected())
332
0
        {
333
0
            pItem->setSelection(false);
334
0
            maItemStateHdl.Call(pItem);
335
336
            // fire accessible event???
337
0
        }
338
339
0
        rtl::Reference<ThumbnailViewItemAcc> xItemAcc = pItem->GetAccessible(false);
340
0
        if (xItemAcc.is())
341
0
        {
342
0
            css::uno::Any aOldAny, aNewAny;
343
0
            aOldAny <<= css::uno::Reference<css::accessibility::XAccessible>(pItem->GetAccessible());
344
0
            ImplFireAccessibleEvent( css::accessibility::AccessibleEventId::CHILD, aOldAny, aNewAny );
345
346
0
            xItemAcc->dispose();
347
0
        }
348
349
0
        mItemList[i].reset();
350
0
    }
351
352
0
    mItemList.clear();
353
0
    mFilteredItemList.clear();
354
355
0
    mpStartSelRange = mFilteredItemList.end();
356
0
}
357
358
void ThumbnailView::DrawItem(ThumbnailViewItem const *pItem)
359
0
{
360
0
    if (pItem->isVisible())
361
0
    {
362
0
        ::tools::Rectangle aRect = pItem->getDrawArea();
363
364
0
        if (!aRect.IsEmpty())
365
0
            Invalidate(aRect);
366
0
    }
367
0
}
368
369
void ThumbnailView::OnItemDblClicked (ThumbnailViewItem*)
370
0
{
371
0
}
372
373
rtl::Reference<comphelper::OAccessible> ThumbnailView::CreateAccessible()
374
0
{
375
0
    mxAccessible.set(new ThumbnailViewAcc(this));
376
0
    return mxAccessible;
377
0
}
378
379
const rtl::Reference< ThumbnailViewAcc > & ThumbnailView::getAccessible() const
380
0
{
381
0
    return mxAccessible;
382
0
}
383
384
void ThumbnailView::CalculateItemPositions(bool bScrollBarUsed)
385
0
{
386
0
    if (!mnItemHeight || !mnItemWidth)
387
0
        return;
388
389
0
    Size        aWinSize = GetOutputSizePixel();
390
0
    size_t      nItemCount = mFilteredItemList.size();
391
392
    // calculate window scroll ratio
393
0
    float nScrollRatio;
394
0
    if (bScrollBarUsed)
395
0
    {
396
0
        nScrollRatio = static_cast<float>(mxScrolledWindow->vadjustment_get_value()) /
397
0
                       static_cast<float>(mxScrolledWindow->vadjustment_get_upper() -
398
0
                                          mxScrolledWindow->vadjustment_get_page_size());
399
0
    }
400
0
    else
401
0
        nScrollRatio = 0;
402
403
    // calculate ScrollBar width
404
0
    tools::Long nScrBarWidth = mbAllowVScrollBar ? mxScrolledWindow->get_scroll_thickness() : 0;
405
406
    // calculate maximum number of visible columns
407
0
    mnCols = static_cast<sal_uInt16>((aWinSize.Width()-nScrBarWidth) / mnItemWidth);
408
409
0
    if (!mnCols)
410
0
        mnCols = 1;
411
412
    // calculate maximum number of visible rows
413
0
    mnVisLines = static_cast<sal_uInt16>(aWinSize.Height() / mnItemHeight);
414
415
    // calculate empty space
416
0
    tools::Long nHSpace = aWinSize.Width()-nScrBarWidth - mnCols*mnItemWidth;
417
0
    tools::Long nVSpace = aWinSize.Height() - mnVisLines*mnItemHeight;
418
0
    tools::Long nHItemSpace = nHSpace / (mnCols+1);
419
0
    tools::Long nVItemSpace = mnVItemSpace;
420
0
    if (nVItemSpace == -1) // auto, split up extra space to use as vertical spacing
421
0
        nVItemSpace = nVSpace / (mnVisLines+1);
422
423
    // tdf#162510 - calculate maximum number of rows
424
0
    size_t nItemCountPinned = 0;
425
0
#if !ENABLE_WASM_STRIP_RECENT
426
0
    bool bPinnedItems = true;
427
0
    for (size_t i = 0; bPinnedItems && i < nItemCount; ++i)
428
0
    {
429
0
        ThumbnailViewItem& rItem = *mFilteredItemList[i];
430
0
        if (auto const pRecentDocsItem = dynamic_cast<RecentDocsViewItem*>(&rItem))
431
0
        {
432
0
            if (pRecentDocsItem->isPinned())
433
0
                ++nItemCountPinned;
434
0
            else
435
0
                bPinnedItems = false;
436
0
        }
437
0
    }
438
0
#endif
439
440
    // calculate maximum number of rows
441
    // Floor( (M+N-1)/N )==Ceiling( M/N )
442
0
    mnLines = (static_cast<tools::Long>(nItemCount - nItemCountPinned) + mnCols - 1) / mnCols;
443
    // tdf#162510 - add pinned items to number of lines
444
0
    mnLines += (static_cast<tools::Long>(nItemCountPinned) + mnCols - 1) / mnCols;
445
446
0
    if ( !mnLines )
447
0
        mnLines = 1;
448
449
0
    if ( mnLines <= mnVisLines )
450
0
        mnFirstLine = 0;
451
0
    else if ( mnFirstLine > o3tl::make_unsigned(mnLines-mnVisLines) )
452
0
        mnFirstLine = static_cast<sal_uInt16>(mnLines-mnVisLines);
453
454
0
    mbHasVisibleItems = true;
455
456
0
    tools::Long nFullSteps = (mnLines > mnVisLines) ? mnLines - mnVisLines + 1 : 1;
457
458
0
    tools::Long nItemHeightOffset = mnItemHeight + nVItemSpace;
459
0
    tools::Long nHiddenLines = static_cast<tools::Long>((nFullSteps - 1) * nScrollRatio);
460
461
    // calculate offsets
462
0
    tools::Long nStartX = nHItemSpace;
463
0
    tools::Long nStartY = nVItemSpace;
464
465
    // calculate and draw items
466
0
    tools::Long x = nStartX;
467
0
    tools::Long y = nStartY - ((nFullSteps - 1) * nScrollRatio - nHiddenLines) * nItemHeightOffset;
468
469
    // draw items
470
    // Unless we are scrolling (via scrollbar) we just use the precalculated
471
    // mnFirstLine -- our nHiddenLines calculation takes into account only
472
    // what the user has done with the scrollbar but not any changes of selection
473
    // using the keyboard, meaning we could accidentally hide the selected item
474
    // if we believe the scrollbar (fdo#72287).
475
0
    size_t nFirstItem = (bScrollBarUsed ? nHiddenLines : mnFirstLine) * mnCols;
476
0
    size_t nLastItem = nFirstItem + (mnVisLines + 1) * mnCols;
477
478
    // tdf#162510 - helper for in order to handle accessibility events
479
0
    auto handleAccessibleEvent = [&](ThumbnailViewItem& rItem, bool bIsVisible)
480
0
    {
481
0
        if (ImplHasAccessibleListeners())
482
0
        {
483
0
            css::uno::Any aOldAny, aNewAny;
484
0
            if (bIsVisible)
485
0
                aNewAny <<= css::uno::Reference<css::accessibility::XAccessible>(rItem.GetAccessible());
486
0
            else
487
0
                aOldAny <<= css::uno::Reference<css::accessibility::XAccessible>(rItem.GetAccessible());
488
0
            ImplFireAccessibleEvent(css::accessibility::AccessibleEventId::CHILD, aOldAny, aNewAny);
489
0
        }
490
0
    };
491
492
    // tdf#162510 - helper to set visibility and update layout
493
0
    auto updateItemLayout = [&](ThumbnailViewItem& rItem, bool bIsVisible, size_t& nVisibleCount)
494
0
    {
495
0
        if (bIsVisible != rItem.isVisible())
496
0
        {
497
0
            handleAccessibleEvent(rItem, bIsVisible);
498
0
            rItem.show(bIsVisible);
499
0
            maItemStateHdl.Call(&rItem);
500
0
        }
501
502
0
        if (bIsVisible)
503
0
        {
504
0
            rItem.setDrawArea(::tools::Rectangle(Point(x, y), Size(mnItemWidth, mnItemHeight)));
505
0
            rItem.calculateItemsPosition(mnThumbnailHeight, mnItemPadding,
506
0
                                         mpItemAttrs->nMaxTextLength, mpItemAttrs.get());
507
508
0
            if ((nVisibleCount + 1) % mnCols)
509
0
                x += mnItemWidth + nHItemSpace;
510
0
            else
511
0
            {
512
0
                x = nStartX;
513
0
                y += mnItemHeight + nVItemSpace;
514
0
            }
515
0
            ++nVisibleCount;
516
0
        }
517
0
    };
518
519
0
    size_t nCurCountVisible = 0;
520
0
#if !ENABLE_WASM_STRIP_RECENT
521
    // tdf#162510 - process pinned items
522
0
    for (size_t i = 0; i < nItemCountPinned; i++)
523
0
        updateItemLayout(*mFilteredItemList[i], nFirstItem <= i && i < nLastItem, nCurCountVisible);
524
525
    // tdf#162510 - start a new line only if the entire line is not filled with pinned items
526
0
    if (nCurCountVisible && nCurCountVisible % mnCols)
527
0
    {
528
0
        x = nStartX;
529
0
        y += mnItemHeight + nVItemSpace;
530
0
    }
531
532
    // tdf#38742 - calculate the position for the separator line between pinned/unpinned items
533
0
    mbHasPinnedSeparator = nCurCountVisible && nItemCountPinned < nItemCount;
534
0
    if (mbHasPinnedSeparator)
535
0
    {
536
        // separator line is drawn at the center above the first unpinned row
537
0
        mnPinnedSeparatorY = y - nVItemSpace / 2;
538
        // separator line is aligned with the first and last document thumbnails in the row
539
0
        mnPinnedSeparatorMargin = nStartX;
540
0
    }
541
542
    // tdf#164102 - adjust first item only if there are any pinned items
543
0
    if (const auto nRemainingPinnedSlots = nItemCountPinned % mnCols)
544
0
    {
545
        // tdf#162510 - adjust first item to take into account the new line after pinned items
546
0
        const auto nFirstItemAdjustment = mnCols - nRemainingPinnedSlots;
547
0
        if (nFirstItemAdjustment <= nFirstItem)
548
0
            nFirstItem -= nFirstItemAdjustment;
549
0
    }
550
551
0
#endif
552
553
    // If want also draw parts of items in the last line,
554
    // then we add one more line if parts of this line are visible
555
0
    nCurCountVisible = 0;
556
0
    for (size_t i = nItemCountPinned; i < nItemCount; i++)
557
0
        updateItemLayout(*mFilteredItemList[i], nFirstItem <= i && i < nLastItem, nCurCountVisible);
558
559
    // check if scroll is needed
560
0
    mbScroll = mnLines > mnVisLines;
561
562
0
    mxScrolledWindow->vadjustment_set_upper(mnLines * gnFineness);
563
0
    mxScrolledWindow->vadjustment_set_page_size(mnVisLines * gnFineness);
564
0
    if (!bScrollBarUsed)
565
0
        mxScrolledWindow->vadjustment_set_value(static_cast<tools::Long>(mnFirstLine)*gnFineness);
566
0
    tools::Long nPageSize = mnVisLines;
567
0
    if ( nPageSize < 1 )
568
0
        nPageSize = 1;
569
0
    mxScrolledWindow->vadjustment_set_page_increment(nPageSize*gnFineness);
570
0
    if (mbAllowVScrollBar)
571
0
        mxScrolledWindow->set_vpolicy(mbScroll ? VclPolicyType::ALWAYS : VclPolicyType::NEVER);
572
0
}
573
574
size_t ThumbnailView::ImplGetItem( const Point& rPos ) const
575
0
{
576
0
    if ( !mbHasVisibleItems )
577
0
    {
578
0
        return THUMBNAILVIEW_ITEM_NOTFOUND;
579
0
    }
580
581
0
    for (size_t i = 0; i < mFilteredItemList.size(); ++i)
582
0
    {
583
0
        if (mFilteredItemList[i]->isVisible() && mFilteredItemList[i]->getDrawArea().Contains(rPos))
584
0
            return i;
585
0
    }
586
587
0
    return THUMBNAILVIEW_ITEM_NOTFOUND;
588
0
}
589
590
ThumbnailViewItem* ThumbnailView::ImplGetItem( size_t nPos )
591
0
{
592
0
    return ( nPos < mFilteredItemList.size() ) ? mFilteredItemList[nPos] : nullptr;
593
0
}
594
595
sal_uInt16 ThumbnailView::ImplGetVisibleItemCount() const
596
0
{
597
0
    sal_uInt16 nRet = 0;
598
0
    const size_t nItemCount = mItemList.size();
599
600
0
    for ( size_t n = 0; n < nItemCount; ++n )
601
0
    {
602
0
        if ( mItemList[n]->isVisible() )
603
0
            ++nRet;
604
0
    }
605
606
0
    return nRet;
607
0
}
608
609
ThumbnailViewItem* ThumbnailView::ImplGetVisibleItem( sal_uInt16 nVisiblePos )
610
0
{
611
0
    const size_t nItemCount = mItemList.size();
612
613
0
    for ( size_t n = 0; n < nItemCount; ++n )
614
0
    {
615
0
        ThumbnailViewItem *const pItem = mItemList[n].get();
616
617
0
        if ( pItem->isVisible() && !nVisiblePos-- )
618
0
            return pItem;
619
0
    }
620
621
0
    return nullptr;
622
0
}
623
624
void ThumbnailView::ImplFireAccessibleEvent( short nEventId, const css::uno::Any& rOldValue, const css::uno::Any& rNewValue )
625
0
{
626
0
    if( mxAccessible )
627
0
        mxAccessible->FireAccessibleEvent( nEventId, rOldValue, rNewValue );
628
0
}
629
630
bool ThumbnailView::ImplHasAccessibleListeners() const
631
0
{
632
0
    return mxAccessible && mxAccessible->HasAccessibleListeners();
633
0
}
634
635
IMPL_LINK_NOARG(ThumbnailView, ImplScrollHdl, weld::ScrolledWindow&, void)
636
0
{
637
0
    CalculateItemPositions(true);
638
0
    if (IsReallyVisible())
639
0
        Invalidate();
640
0
}
641
642
bool ThumbnailView::KeyInput( const KeyEvent& rKEvt )
643
0
{
644
0
    bool bHandled = true;
645
646
    // Get the last selected item in the list
647
0
    size_t nLastPos = 0;
648
0
    bool bFoundLast = false;
649
0
    for ( tools::Long i = mFilteredItemList.size() - 1; !bFoundLast && i >= 0; --i )
650
0
    {
651
0
        ThumbnailViewItem* pItem = mFilteredItemList[i];
652
0
        if ( pItem->isSelected() )
653
0
        {
654
0
            nLastPos = i;
655
0
            bFoundLast = true;
656
0
        }
657
0
    }
658
659
0
    bool bValidRange = false;
660
0
    bool bHasSelRange = mpStartSelRange != mFilteredItemList.end();
661
0
    size_t nNextPos = nLastPos;
662
0
    vcl::KeyCode aKeyCode = rKEvt.GetKeyCode();
663
0
    ThumbnailViewItem* pNext = nullptr;
664
665
0
    if (aKeyCode.IsShift() && bHasSelRange)
666
0
    {
667
        //If the last element selected is the start range position
668
        //search for the first selected item
669
0
        size_t nSelPos = mpStartSelRange - mFilteredItemList.begin();
670
671
0
        if (nLastPos == nSelPos)
672
0
        {
673
0
            while (nLastPos && mFilteredItemList[nLastPos-1]->isSelected())
674
0
                --nLastPos;
675
0
        }
676
0
    }
677
678
0
    switch ( aKeyCode.GetCode() )
679
0
    {
680
0
        case KEY_RIGHT:
681
0
            if (!mFilteredItemList.empty())
682
0
            {
683
0
                if ( bFoundLast && nLastPos + 1 < mFilteredItemList.size() )
684
0
                {
685
0
                    bValidRange = true;
686
0
                    nNextPos = nLastPos + 1;
687
0
                }
688
689
0
                pNext = mFilteredItemList[nNextPos];
690
0
            }
691
0
            break;
692
0
        case KEY_LEFT:
693
0
            if (!mFilteredItemList.empty())
694
0
            {
695
0
                if ( nLastPos > 0 )
696
0
                {
697
0
                    bValidRange = true;
698
0
                    nNextPos = nLastPos - 1;
699
0
                }
700
701
0
                pNext = mFilteredItemList[nNextPos];
702
0
            }
703
0
            break;
704
0
        case KEY_DOWN:
705
0
            if (!mFilteredItemList.empty())
706
0
            {
707
0
                if ( bFoundLast )
708
0
                {
709
                    //If we are in the second last row just go the one in
710
                    //the row below, if there's not row below just go to the
711
                    //last item but for the last row don't do anything.
712
0
                    if ( nLastPos + mnCols < mFilteredItemList.size( ) )
713
0
                    {
714
0
                        bValidRange = true;
715
0
                        nNextPos = nLastPos + mnCols;
716
0
                    }
717
0
                    else
718
0
                    {
719
0
                        int curRow = nLastPos/mnCols;
720
721
0
                        if (curRow < mnLines-1)
722
0
                            nNextPos = mFilteredItemList.size()-1;
723
0
                    }
724
0
                }
725
726
0
                pNext = mFilteredItemList[nNextPos];
727
0
            }
728
0
            break;
729
0
        case KEY_UP:
730
0
            if (!mFilteredItemList.empty())
731
0
            {
732
0
                if ( nLastPos >= mnCols )
733
0
                {
734
0
                    bValidRange = true;
735
0
                    nNextPos = nLastPos - mnCols;
736
0
                }
737
738
0
                pNext = mFilteredItemList[nNextPos];
739
0
            }
740
0
            break;
741
0
        case KEY_RETURN:
742
0
            {
743
0
                if ( bFoundLast )
744
0
                    OnItemDblClicked( mFilteredItemList[nLastPos] );
745
0
            }
746
0
            [[fallthrough]];
747
0
        default:
748
0
            bHandled = CustomWidgetController::KeyInput(rKEvt);
749
0
    }
750
751
0
    if ( pNext )
752
0
    {
753
0
        if (aKeyCode.IsShift() && bValidRange && mbAllowMultiSelection)
754
0
        {
755
0
            std::pair<size_t,size_t> aRange;
756
0
            size_t nSelPos = mpStartSelRange - mFilteredItemList.begin();
757
758
0
            if (nLastPos < nSelPos)
759
0
            {
760
0
                if (nNextPos > nLastPos)
761
0
                {
762
0
                    if ( nNextPos > nSelPos)
763
0
                        aRange = std::make_pair(nLastPos,nNextPos);
764
0
                    else
765
0
                        aRange = std::make_pair(nLastPos,nNextPos-1);
766
0
                }
767
0
                else
768
0
                {
769
0
                    assert(nLastPos > 0);
770
0
                    aRange = std::make_pair(nNextPos,nLastPos-1);
771
0
                }
772
0
            }
773
0
            else if (nLastPos == nSelPos)
774
0
            {
775
0
                if (nNextPos > nLastPos)
776
0
                    aRange = std::make_pair(nLastPos+1,nNextPos);
777
0
                else
778
0
                {
779
0
                    assert(nLastPos > 0);
780
0
                    aRange = std::make_pair(nNextPos,nLastPos-1);
781
0
                }
782
0
            }
783
0
            else
784
0
            {
785
0
                if (nNextPos > nLastPos)
786
0
                    aRange = std::make_pair(nLastPos+1,nNextPos);
787
0
                else
788
0
                {
789
0
                    if ( nNextPos < nSelPos)
790
0
                        aRange = std::make_pair(nNextPos,nLastPos);
791
0
                    else
792
0
                        aRange = std::make_pair(nNextPos+1,nLastPos);
793
0
                }
794
0
            }
795
796
0
            for (size_t i = aRange.first; i <= aRange.second; ++i)
797
0
            {
798
0
                if (i != nSelPos)
799
0
                {
800
0
                    ThumbnailViewItem *pCurItem = mFilteredItemList[i];
801
802
0
                    pCurItem->setSelection(!pCurItem->isSelected());
803
804
0
                    DrawItem(pCurItem);
805
806
0
                    maItemStateHdl.Call(pCurItem);
807
0
                }
808
0
            }
809
0
        }
810
0
        else if (!aKeyCode.IsShift())
811
0
        {
812
0
            deselectItems();
813
0
            SelectItem(pNext->mnId);
814
815
            //Mark it as the selection range start position
816
0
            mpStartSelRange = mFilteredItemList.begin() + nNextPos;
817
0
        }
818
819
0
        MakeItemVisible(pNext->mnId);
820
0
    }
821
0
    return bHandled;
822
0
}
823
824
void ThumbnailView::MakeItemVisible( sal_uInt16 nItemId )
825
0
{
826
    // Get the item row
827
0
    auto it = std::ranges::find_if(mFilteredItemList,
828
0
        [nItemId](const ThumbnailViewItem* pItem) { return pItem->mnId == nItemId; });
829
0
    size_t nPos = (it != mFilteredItemList.end())
830
0
        ? std::distance(mFilteredItemList.begin(), it) : 0;
831
0
    sal_uInt16 nRow = mnCols ? nPos / mnCols : 0;
832
833
    // Move the visible rows as little as possible to include that one
834
0
    if ( nRow < mnFirstLine )
835
0
        mnFirstLine = nRow;
836
0
    else if ( nRow > mnFirstLine + mnVisLines )
837
0
        mnFirstLine = nRow - mnVisLines;
838
839
0
    CalculateItemPositions();
840
0
    Invalidate();
841
0
}
842
843
bool ThumbnailView::MouseButtonDown( const MouseEvent& rMEvt )
844
0
{
845
0
    GrabFocus();
846
847
0
    if (!rMEvt.IsLeft())
848
0
    {
849
0
        return CustomWidgetController::MouseButtonDown( rMEvt );
850
0
    }
851
852
0
    size_t nPos = ImplGetItem(rMEvt.GetPosPixel());
853
0
    ThumbnailViewItem* pItem = ImplGetItem(nPos);
854
855
0
    if ( !pItem )
856
0
    {
857
0
        deselectItems();
858
0
        return CustomWidgetController::MouseButtonDown( rMEvt );
859
0
    }
860
861
0
    if ( rMEvt.GetClicks() == 2 )
862
0
    {
863
0
        OnItemDblClicked(pItem);
864
0
        return true;
865
0
    }
866
867
0
    if(rMEvt.GetClicks() == 1)
868
0
    {
869
0
        if (rMEvt.IsMod1())
870
0
        {
871
            //Keep selected item group state and just invert current desired one state
872
0
            pItem->setSelection(!pItem->isSelected());
873
874
            //This one becomes the selection range start position if it changes its state to selected otherwise resets it
875
0
            mpStartSelRange = pItem->isSelected() ? mFilteredItemList.begin() + nPos : mFilteredItemList.end();
876
0
        }
877
0
        else if (rMEvt.IsShift() && mpStartSelRange != mFilteredItemList.end())
878
0
        {
879
0
            std::pair<size_t,size_t> aNewRange;
880
0
            aNewRange.first = mpStartSelRange - mFilteredItemList.begin();
881
0
            aNewRange.second = nPos;
882
883
0
            if (aNewRange.first > aNewRange.second)
884
0
                std::swap(aNewRange.first,aNewRange.second);
885
886
            //Deselect the ones outside of it
887
0
            for (size_t i = 0, n = mFilteredItemList.size(); i < n; ++i)
888
0
            {
889
0
                ThumbnailViewItem *pCurItem  = mFilteredItemList[i];
890
891
0
                if (pCurItem->isSelected() && (i < aNewRange.first || i > aNewRange.second))
892
0
                {
893
0
                    pCurItem->setSelection(false);
894
895
0
                    DrawItem(pCurItem);
896
897
0
                    maItemStateHdl.Call(pCurItem);
898
0
                }
899
0
            }
900
901
0
            size_t nSelPos = mpStartSelRange - mFilteredItemList.begin();
902
903
            //Select the items between start range and the selected item
904
0
            if (nSelPos != nPos)
905
0
            {
906
0
                int dir = nSelPos < nPos ? 1 : -1;
907
0
                size_t nCurPos = nSelPos + dir;
908
909
0
                while (nCurPos != nPos)
910
0
                {
911
0
                    ThumbnailViewItem *pCurItem  = mFilteredItemList[nCurPos];
912
913
0
                    if (!pCurItem->isSelected())
914
0
                    {
915
0
                        pCurItem->setSelection(true);
916
917
0
                        DrawItem(pCurItem);
918
919
0
                        maItemStateHdl.Call(pCurItem);
920
0
                    }
921
922
0
                    nCurPos += dir;
923
0
                }
924
0
            }
925
926
0
            pItem->setSelection(true);
927
0
        }
928
0
        else
929
0
        {
930
            //If we got a group of selected items deselect the rest and only keep the desired one
931
            //mark items as not selected to not fire unnecessary change state events.
932
0
            pItem->setSelection(false);
933
0
            deselectItems();
934
0
            pItem->setSelection(true);
935
936
            //Mark as initial selection range position and reset end one
937
0
            mpStartSelRange = mFilteredItemList.begin() + nPos;
938
0
        }
939
940
0
        if (!pItem->isHighlighted())
941
0
            DrawItem(pItem);
942
943
0
        maItemStateHdl.Call(pItem);
944
945
        //fire accessible event??
946
0
    }
947
0
    return true;
948
0
}
949
950
void ThumbnailView::SetDrawingArea(weld::DrawingArea* pDrawingArea)
951
0
{
952
0
    CustomWidgetController::SetDrawingArea(pDrawingArea);
953
954
0
    OutputDevice& rDevice = pDrawingArea->get_ref_device();
955
0
    weld::SetPointFont(rDevice, pDrawingArea->get_font());
956
0
    mpItemAttrs->aFontAttr = getFontAttributeFromVclFont(mpItemAttrs->aFontSize, rDevice.GetFont(), false, true);
957
958
0
    SetOutputSizePixel(pDrawingArea->get_preferred_size());
959
0
}
960
961
void ThumbnailView::Paint(vcl::RenderContext& rRenderContext, const ::tools::Rectangle& /*rRect*/)
962
0
{
963
0
    auto popIt = rRenderContext.ScopedPush(vcl::PushFlags::ALL);
964
965
    // Re-read settings colors to handle system theme changes on-the-fly
966
0
    UpdateColors(rRenderContext.GetSettings().GetStyleSettings());
967
0
    updateItemAttrsFromColors();
968
969
0
    rRenderContext.SetTextFillColor();
970
0
    rRenderContext.SetBackground(maFillColor);
971
972
0
    size_t nItemCount = mItemList.size();
973
974
    // Draw background
975
0
    drawinglayer::primitive2d::Primitive2DContainer aSeq(1);
976
0
    aSeq[0] = drawinglayer::primitive2d::Primitive2DReference(
977
0
            new PolyPolygonColorPrimitive2D(
978
0
                    B2DPolyPolygon( ::tools::Polygon(::tools::Rectangle(Point(), GetOutputSizePixel()), 0, 0).getB2DPolygon()),
979
0
                    maFillColor.getBColor()));
980
981
    // Create the processor and process the primitives
982
0
    const drawinglayer::geometry::ViewInformation2D aNewViewInfos;
983
984
0
    std::unique_ptr<drawinglayer::processor2d::BaseProcessor2D> pProcessor(
985
0
        drawinglayer::processor2d::createProcessor2DFromOutputDevice(rRenderContext, aNewViewInfos));
986
0
    pProcessor->process(aSeq);
987
988
    // draw items
989
0
    for (size_t i = 0; i < nItemCount; i++)
990
0
    {
991
0
        ThumbnailViewItem *const pItem = mItemList[i].get();
992
0
        if (!pItem->isVisible())
993
0
            continue;
994
0
        pItem->Paint(pProcessor.get(), *mpItemAttrs);
995
0
    }
996
997
    // tdf#38742 - draw a separator line between the pinned/unpinned items using left/right margin
998
0
    if (mbHasPinnedSeparator)
999
0
    {
1000
0
        rRenderContext.SetLineColor(
1001
0
            rRenderContext.GetSettings().GetStyleSettings().GetSeparatorColor());
1002
0
        rRenderContext.DrawLine(
1003
0
            Point(mnPinnedSeparatorMargin, mnPinnedSeparatorY),
1004
0
            Point(GetOutputSizePixel().Width() - mnPinnedSeparatorMargin, mnPinnedSeparatorY));
1005
0
    }
1006
0
}
1007
1008
void ThumbnailView::GetFocus()
1009
0
{
1010
0
    if (mbSelectOnFocus)
1011
0
    {
1012
        // Select the first item if nothing selected
1013
0
        int nSelected = -1;
1014
0
        for (size_t i = 0, n = mItemList.size(); i < n && nSelected == -1; ++i)
1015
0
        {
1016
0
            if (mItemList[i]->isSelected())
1017
0
                nSelected = i;
1018
0
        }
1019
1020
0
        if (nSelected == -1 && !mItemList.empty())
1021
0
        {
1022
0
            ThumbnailViewItem* pFirst = nullptr;
1023
0
            if (!mFilteredItemList.empty()) {
1024
0
                pFirst = mFilteredItemList[0];
1025
0
            } else {
1026
0
                pFirst = mItemList[0].get();
1027
0
            }
1028
1029
0
            SelectItem(pFirst->mnId);
1030
0
        }
1031
0
    }
1032
1033
    // Tell the accessible object that we got the focus.
1034
0
    if( mxAccessible )
1035
0
        mxAccessible->GetFocus();
1036
1037
0
    CustomWidgetController::GetFocus();
1038
0
}
1039
1040
void ThumbnailView::LoseFocus()
1041
0
{
1042
0
    CustomWidgetController::LoseFocus();
1043
1044
    // Tell the accessible object that we lost the focus.
1045
0
    if( mxAccessible )
1046
0
        mxAccessible->LoseFocus();
1047
0
}
1048
1049
void ThumbnailView::Resize()
1050
0
{
1051
0
    CustomWidgetController::Resize();
1052
0
    CalculateItemPositions();
1053
1054
0
    if (IsReallyVisible())
1055
0
        Invalidate();
1056
0
}
1057
1058
void ThumbnailView::RemoveItem( sal_uInt16 nItemId )
1059
0
{
1060
0
    size_t nPos = GetItemPos( nItemId );
1061
1062
0
    if ( nPos == THUMBNAILVIEW_ITEM_NOTFOUND )
1063
0
        return;
1064
1065
0
    if ( nPos < mFilteredItemList.size() ) {
1066
1067
        // keep it alive until after we have deleted it from the filter item list
1068
0
        std::unique_ptr<ThumbnailViewItem> xKeepAliveViewItem;
1069
1070
        // delete item from the thumbnail list
1071
0
        for (auto it = mItemList.begin(); it != mItemList.end(); ++it)
1072
0
        {
1073
0
            if ((*it)->mnId == nItemId)
1074
0
            {
1075
0
                xKeepAliveViewItem = std::move(*it);
1076
0
                mItemList.erase(it);
1077
0
                break;
1078
0
            }
1079
0
        }
1080
1081
        // delete item from the filter item list
1082
0
        ThumbnailValueItemList::iterator it = mFilteredItemList.begin();
1083
0
        ::std::advance( it, nPos );
1084
1085
0
        if ((*it)->isSelected())
1086
0
        {
1087
0
            (*it)->setSelection(false);
1088
0
            maItemStateHdl.Call(*it);
1089
0
        }
1090
1091
0
        mFilteredItemList.erase( it );
1092
0
        mpStartSelRange = mFilteredItemList.end();
1093
0
    }
1094
1095
0
    CalculateItemPositions();
1096
1097
0
    if (IsReallyVisible())
1098
0
        Invalidate();
1099
0
}
1100
1101
void ThumbnailView::Clear()
1102
0
{
1103
0
    ImplDeleteItems();
1104
1105
    // reset variables
1106
0
    mnFirstLine     = 0;
1107
1108
0
    CalculateItemPositions();
1109
1110
0
    if (IsReallyVisible())
1111
0
        Invalidate();
1112
0
}
1113
1114
void ThumbnailView::updateItems (std::vector<std::unique_ptr<ThumbnailViewItem>> items)
1115
0
{
1116
0
    ImplDeleteItems();
1117
1118
    // reset variables
1119
0
    mnFirstLine     = 0;
1120
1121
0
    mItemList = std::move(items);
1122
1123
0
    filterItems(maFilterFunc);
1124
0
}
1125
1126
size_t ThumbnailView::GetItemPos( sal_uInt16 nItemId ) const
1127
0
{
1128
0
    for ( size_t i = 0, n = mFilteredItemList.size(); i < n; ++i ) {
1129
0
        if ( mFilteredItemList[i]->mnId == nItemId ) {
1130
0
            return i;
1131
0
        }
1132
0
    }
1133
0
    return THUMBNAILVIEW_ITEM_NOTFOUND;
1134
0
}
1135
1136
sal_uInt16 ThumbnailView::GetItemId( size_t nPos ) const
1137
0
{
1138
0
    return ( nPos < mFilteredItemList.size() ) ? mFilteredItemList[nPos]->mnId : 0 ;
1139
0
}
1140
1141
sal_uInt16 ThumbnailView::GetItemId( const Point& rPos ) const
1142
0
{
1143
0
    size_t nItemPos = ImplGetItem( rPos );
1144
0
    if ( nItemPos != THUMBNAILVIEW_ITEM_NOTFOUND )
1145
0
        return GetItemId( nItemPos );
1146
1147
0
    return 0;
1148
0
}
1149
1150
void ThumbnailView::setItemMaxTextLength(sal_uInt32 nLength)
1151
0
{
1152
0
    mpItemAttrs->nMaxTextLength = nLength;
1153
0
}
1154
1155
void ThumbnailView::setItemDimensions(tools::Long itemWidth, tools::Long thumbnailHeight, tools::Long displayHeight, int itemPadding)
1156
0
{
1157
0
    mnItemWidth = itemWidth + 2*itemPadding;
1158
0
    mnThumbnailHeight = thumbnailHeight;
1159
0
    mnDisplayHeight = displayHeight;
1160
0
    mnItemPadding = itemPadding;
1161
0
    mnItemHeight = mnDisplayHeight + mnThumbnailHeight + 2*itemPadding;
1162
0
}
1163
1164
void ThumbnailView::SelectItem( sal_uInt16 nItemId )
1165
0
{
1166
0
    size_t nItemPos = GetItemPos( nItemId );
1167
0
    if ( nItemPos == THUMBNAILVIEW_ITEM_NOTFOUND )
1168
0
        return;
1169
1170
0
    ThumbnailViewItem* pItem = mFilteredItemList[nItemPos];
1171
0
    if (pItem->isSelected())
1172
0
        return;
1173
1174
0
    pItem->setSelection(true);
1175
0
    maItemStateHdl.Call(pItem);
1176
1177
0
    if (IsReallyVisible())
1178
0
        Invalidate();
1179
1180
0
    bool bNewOut = IsReallyVisible();
1181
1182
    // if necessary scroll to the visible area
1183
0
    if (mbScroll && nItemId && mnCols)
1184
0
    {
1185
0
        sal_uInt16 nNewLine = static_cast<sal_uInt16>(nItemPos / mnCols);
1186
0
        if ( nNewLine < mnFirstLine )
1187
0
        {
1188
0
            mnFirstLine = nNewLine;
1189
0
        }
1190
0
        else if ( mnVisLines != 0 && nNewLine > o3tl::make_unsigned(mnFirstLine+mnVisLines-1) )
1191
0
        {
1192
0
            mnFirstLine = static_cast<sal_uInt16>(nNewLine-mnVisLines+1);
1193
0
        }
1194
0
    }
1195
1196
0
    if ( bNewOut )
1197
0
    {
1198
0
        if (IsReallyVisible())
1199
0
            Invalidate();
1200
0
    }
1201
1202
0
    if( !ImplHasAccessibleListeners() )
1203
0
        return;
1204
1205
    // focus event (select)
1206
0
    const rtl::Reference<ThumbnailViewItemAcc>& pItemAcc = pItem->GetAccessible();
1207
1208
0
    if( pItemAcc )
1209
0
    {
1210
0
        css::uno::Any aOldAny, aNewAny;
1211
0
        aNewAny <<= css::uno::Reference<css::accessibility::XAccessible>( pItemAcc );
1212
0
        ImplFireAccessibleEvent( css::accessibility::AccessibleEventId::ACTIVE_DESCENDANT_CHANGED, aOldAny, aNewAny );
1213
0
    }
1214
1215
    // selection event
1216
0
    css::uno::Any aOldAny, aNewAny;
1217
0
    ImplFireAccessibleEvent( css::accessibility::AccessibleEventId::SELECTION_CHANGED, aOldAny, aNewAny );
1218
0
}
1219
1220
bool ThumbnailView::IsItemSelected( sal_uInt16 nItemId ) const
1221
0
{
1222
0
    size_t nItemPos = GetItemPos( nItemId );
1223
0
    if ( nItemPos == THUMBNAILVIEW_ITEM_NOTFOUND )
1224
0
        return false;
1225
1226
0
    ThumbnailViewItem* pItem = mFilteredItemList[nItemPos];
1227
0
    return pItem->isSelected();
1228
0
}
1229
1230
void ThumbnailView::deselectItems()
1231
0
{
1232
0
    for (std::unique_ptr<ThumbnailViewItem>& p : mItemList)
1233
0
    {
1234
0
        if (p->isSelected())
1235
0
        {
1236
0
            p->setSelection(false);
1237
1238
0
            maItemStateHdl.Call(p.get());
1239
0
        }
1240
0
    }
1241
1242
0
    if (IsReallyVisible())
1243
0
        Invalidate();
1244
0
}
1245
1246
void ThumbnailView::ShowTooltips( bool bShowTooltips )
1247
0
{
1248
0
    mbShowTooltips = bShowTooltips;
1249
0
}
1250
1251
void ThumbnailView::DrawMnemonics( bool bDrawMnemonics )
1252
0
{
1253
0
    mbDrawMnemonics = bDrawMnemonics;
1254
0
}
1255
1256
void ThumbnailView::filterItems(const std::function<bool (const ThumbnailViewItem*)> &func)
1257
0
{
1258
0
    mnFirstLine = 0;        // start at the top of the list instead of the current position
1259
0
    maFilterFunc = func;
1260
1261
0
    size_t nSelPos = 0;
1262
0
    bool bHasSelRange = false;
1263
0
    ThumbnailViewItem *curSel = mpStartSelRange != mFilteredItemList.end() ? *mpStartSelRange : nullptr;
1264
1265
0
    mFilteredItemList.clear();
1266
1267
0
    for (size_t i = 0, n = mItemList.size(); i < n; ++i)
1268
0
    {
1269
0
        ThumbnailViewItem *const pItem = mItemList[i].get();
1270
1271
0
        if (maFilterFunc(pItem))
1272
0
        {
1273
0
            if (curSel == pItem)
1274
0
            {
1275
0
                nSelPos = i;
1276
0
                bHasSelRange = true;
1277
0
            }
1278
1279
0
            mFilteredItemList.push_back(pItem);
1280
0
        }
1281
0
        else
1282
0
        {
1283
0
            if( pItem->isVisible())
1284
0
            {
1285
0
                if ( ImplHasAccessibleListeners() )
1286
0
                {
1287
0
                    css::uno::Any aOldAny, aNewAny;
1288
1289
0
                    aOldAny <<= css::uno::Reference<css::accessibility::XAccessible>(pItem->GetAccessible());
1290
0
                    ImplFireAccessibleEvent( css::accessibility::AccessibleEventId::CHILD, aOldAny, aNewAny );
1291
0
                }
1292
1293
0
                pItem->show(false);
1294
0
                pItem->setSelection(false);
1295
1296
0
                maItemStateHdl.Call(pItem);
1297
0
            }
1298
0
        }
1299
0
    }
1300
1301
0
    mpStartSelRange = bHasSelRange ? mFilteredItemList.begin()  + nSelPos : mFilteredItemList.end();
1302
0
    CalculateItemPositions();
1303
1304
0
    Invalidate();
1305
0
}
1306
1307
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */