/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: */ |