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