Coverage Report

Created: 2025-12-31 10:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/vcl/source/treelist/iconview.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
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <vcl/filter/PngImageWriter.hxx>
21
#include <vcl/toolkit/treelistentry.hxx>
22
#include <vcl/toolkit/viewdataentry.hxx>
23
#include <iconview.hxx>
24
#include "iconviewimpl.hxx"
25
#include <vcl/uitest/uiobject.hxx>
26
#include <tools/json_writer.hxx>
27
#include <vcl/toolkit/svlbitm.hxx>
28
#include <tools/stream.hxx>
29
#include <vcl/cvtgrf.hxx>
30
#include <comphelper/base64.hxx>
31
#include <comphelper/propertyvalue.hxx>
32
33
namespace
34
{
35
const int separatorHeight = 10;
36
const int nSpacing = 5; // 5 pixels from top, from bottom, between icon and label
37
}
38
39
IconView::IconView(vcl::Window* pParent, WinBits nBits)
40
0
    : SvTreeListBox(pParent, nBits)
41
0
{
42
0
    m_nColumnCount = 1;
43
0
    mbCenterAndClipText = true;
44
0
    SetEntryWidth(100);
45
46
0
    pImpl.reset(new IconViewImpl(this, GetModel(), GetStyle()));
47
0
}
Unexecuted instantiation: IconView::IconView(vcl::Window*, long)
Unexecuted instantiation: IconView::IconView(vcl::Window*, long)
48
49
Size IconView::GetEntrySize(const SvTreeListEntry& entry) const
50
0
{
51
0
    if (entry.IsSeparator())
52
0
        return { GetEntryWidth() * GetColumnCount(), separatorHeight };
53
0
    return { GetEntryWidth(), GetEntryHeight() };
54
0
}
55
56
void IconView::SetFixedColumnCount(short nColumnCount)
57
0
{
58
0
    m_nFixedColumnCount = nColumnCount;
59
0
    m_nColumnCount = nColumnCount;
60
0
}
61
62
void IconView::UpdateEntrySize(const Image& pImage)
63
0
{
64
0
    int spacing = nSpacing * 2;
65
0
    SetEntryHeight(pImage.GetSizePixel().getHeight() + spacing);
66
0
    SetEntryWidth(pImage.GetSizePixel().getWidth() + spacing);
67
0
}
68
69
bool IconView::HasSeparatorEntry() const
70
0
{
71
0
    for (sal_uInt32 i = 0; i < GetEntryCount(); i++)
72
0
    {
73
0
        SvTreeListEntry* pEntry = GetEntry(i);
74
0
        if (pEntry && pEntry->IsSeparator())
75
0
            return true;
76
0
    }
77
78
0
    return false;
79
0
}
80
81
void IconView::CalcEntryHeight(SvTreeListEntry const* pEntry)
82
0
{
83
0
    int nHeight = nSpacing * 2;
84
0
    SvViewDataEntry* pViewData = GetViewDataEntry(pEntry);
85
0
    const size_t nCount = pEntry->ItemCount();
86
0
    bool bHasIcon = false;
87
0
    for (size_t nCur = 0; nCur < nCount; ++nCur)
88
0
    {
89
0
        nHeight += SvLBoxItem::GetHeight(pViewData, nCur);
90
91
0
        if (!bHasIcon && pEntry->GetItem(nCur).GetType() == SvLBoxItemType::ContextBmp)
92
0
            bHasIcon = true;
93
0
    }
94
95
0
    if (bHasIcon && nCount > 1)
96
0
        nHeight += nSpacing; // between icon and label
97
98
0
    if (nHeight > nEntryHeight)
99
0
    {
100
0
        nEntryHeight = nHeight;
101
0
        Control::SetFont(GetFont());
102
0
        pImpl->SetEntryHeight();
103
0
    }
104
0
}
105
106
void IconView::Resize()
107
0
{
108
0
    Size aBoxSize = Control::GetOutputSizePixel();
109
110
0
    if (!aBoxSize.Width())
111
0
        return;
112
113
0
    if (m_nFixedColumnCount == -1)
114
0
        m_nColumnCount = nEntryWidth ? aBoxSize.Width() / nEntryWidth : 1;
115
116
0
    SvTreeListBox::Resize();
117
0
}
118
119
Size IconView::GetOptimalSize() const
120
0
{
121
0
    if (m_nFixedColumnCount != -1 && !HasSeparatorEntry())
122
0
    {
123
        // if a fixed amount of columns has been set and only icons (no separators) are used,
124
        // calculate size needed for those
125
0
        const short nRowCount = std::ceil(double(GetEntryCount()) / GetColumnCount());
126
0
        Size aSize(GetColumnCount() * nEntryWidth, nRowCount * nEntryHeight);
127
0
        if (GetStyle() & WB_VSCROLL)
128
0
            aSize.AdjustWidth(GetSettings().GetStyleSettings().GetScrollBarSize());
129
130
0
        return aSize;
131
0
    }
132
133
0
    return SvTreeListBox::GetOptimalSize();
134
0
}
135
136
tools::Rectangle IconView::GetFocusRect(const SvTreeListEntry* pEntry, tools::Long)
137
0
{
138
0
    return { GetEntryPosition(pEntry), GetEntrySize(*pEntry) };
139
0
}
140
141
void IconView::PaintEntry(SvTreeListEntry& rEntry, tools::Long nX, tools::Long nY,
142
                          vcl::RenderContext& rRenderContext)
143
0
{
144
0
    pImpl->UpdateContextBmpWidthMax(&rEntry);
145
146
0
    const Size entrySize = GetEntrySize(rEntry);
147
0
    short nTempEntryHeight = entrySize.Height();
148
0
    short nTempEntryWidth = entrySize.Width();
149
150
0
    Point aEntryPos(nX, nY);
151
152
0
    auto popIt = rRenderContext.ScopedPush(vcl::PushFlags::FILLCOLOR | vcl::PushFlags::LINECOLOR
153
0
                                           | vcl::PushFlags::FONT);
154
0
    const Color aBackupColor = rRenderContext.GetFillColor();
155
156
0
    const StyleSettings& rSettings = rRenderContext.GetSettings().GetStyleSettings();
157
158
0
    const Size aOutputSize = GetOutputSizePixel();
159
0
    if (aOutputSize.getHeight() < nTempEntryHeight)
160
0
        nTempEntryHeight = aOutputSize.getHeight();
161
162
0
    const SvViewDataEntry* pViewDataEntry = GetViewDataEntry(&rEntry);
163
164
0
    if (pViewDataEntry->IsHighlighted())
165
0
    {
166
0
        vcl::Font aHighlightFont(rRenderContext.GetFont());
167
0
        const Color aHighlightTextColor(rSettings.GetHighlightTextColor());
168
0
        aHighlightFont.SetColor(aHighlightTextColor);
169
170
        // set font color to highlight
171
0
        rRenderContext.SetTextColor(aHighlightTextColor);
172
0
        rRenderContext.SetFont(aHighlightFont);
173
0
    }
174
175
0
    bool bFillColorSet = false;
176
    // draw background
177
0
    if (!(nTreeFlags & SvTreeFlags::USESEL))
178
0
    {
179
        // set background pattern/color
180
0
        Wallpaper aWallpaper = rRenderContext.GetBackground();
181
182
0
        if (pViewDataEntry->IsHighlighted())
183
0
        {
184
0
            Color aNewWallColor = rSettings.GetHighlightColor();
185
            // if the face color is bright then the deactivate color is also bright
186
            // -> so you can't see any deactivate selection
187
0
            const WinBits nWindowStyle = GetStyle();
188
0
            const bool bHideSelection = (nWindowStyle & WB_HIDESELECTION) != 0 && !HasFocus();
189
0
            if (bHideSelection && !rSettings.GetFaceColor().IsBright()
190
0
                && aWallpaper.GetColor().IsBright() != rSettings.GetDeactiveColor().IsBright())
191
0
            {
192
0
                aNewWallColor = rSettings.GetDeactiveColor();
193
0
            }
194
0
            aWallpaper.SetColor(aNewWallColor);
195
0
        }
196
197
0
        Color aBackgroundColor = aWallpaper.GetColor();
198
0
        if (aBackgroundColor != COL_TRANSPARENT)
199
0
        {
200
0
            rRenderContext.SetFillColor(aBackgroundColor);
201
0
            bFillColorSet = true;
202
            // this case may occur for smaller horizontal resizes
203
0
            if (nTempEntryWidth > 1)
204
0
                rRenderContext.DrawRect({ aEntryPos, Size(nTempEntryWidth, nTempEntryHeight) });
205
0
        }
206
0
    }
207
208
0
    const size_t nItemCount = rEntry.ItemCount();
209
0
    size_t nIconItem = nItemCount;
210
211
0
    int nLabelHeight = 0;
212
0
    std::vector<size_t> aTextItems;
213
214
0
    for (size_t nCurItem = 0; nCurItem < nItemCount; ++nCurItem)
215
0
    {
216
0
        SvLBoxItem& rItem = rEntry.GetItem(nCurItem);
217
0
        SvLBoxItemType nItemType = rItem.GetType();
218
219
0
        if (nItemType == SvLBoxItemType::ContextBmp)
220
0
        {
221
0
            nIconItem = nCurItem;
222
0
            continue;
223
0
        }
224
225
0
        aTextItems.push_back(nCurItem);
226
0
        auto nItemHeight = SvLBoxItem::GetHeight(pViewDataEntry, nCurItem);
227
0
        nLabelHeight += nItemHeight;
228
0
    }
229
230
0
    int nLabelYPos = nY + nTempEntryHeight - nLabelHeight - nSpacing; // padding from bottom
231
0
    for (auto nCurItem : aTextItems)
232
0
    {
233
0
        aEntryPos.setY(nLabelYPos);
234
235
0
        auto nItemHeight = SvLBoxItem::GetHeight(pViewDataEntry, nCurItem);
236
0
        nLabelYPos += nItemHeight;
237
238
0
        rEntry.GetItem(nCurItem).Paint(aEntryPos, *this, rRenderContext, pViewDataEntry, rEntry);
239
0
    }
240
241
0
    if (bFillColorSet)
242
0
        rRenderContext.SetFillColor(aBackupColor);
243
244
    // draw icon
245
0
    if (nIconItem < nItemCount)
246
0
    {
247
0
        SvLBoxItem& rItem = rEntry.GetItem(nIconItem);
248
0
        auto nItemWidth = rItem.GetWidth(this, pViewDataEntry, nIconItem);
249
0
        auto nItemHeight = SvLBoxItem::GetHeight(pViewDataEntry, nIconItem);
250
251
0
        aEntryPos.setY(nY);
252
253
        // center horizontally
254
0
        aEntryPos.AdjustX((nTempEntryWidth - nItemWidth) / 2);
255
        // center vertically
256
0
        int nImageAreaHeight = nTempEntryHeight - nSpacing * 2; // spacings from top, from bottom
257
0
        if (nLabelHeight > 0)
258
0
        {
259
0
            nImageAreaHeight -= nLabelHeight + nSpacing; // spacing between icon and label
260
0
        }
261
0
        aEntryPos.AdjustY((nImageAreaHeight - nItemHeight) / 2 + nSpacing);
262
263
0
        rItem.Paint(aEntryPos, *this, rRenderContext, pViewDataEntry, rEntry);
264
0
    }
265
0
}
266
267
0
FactoryFunction IconView::GetUITestFactory() const { return IconViewUIObject::create; }
268
269
static OString extractPngString(const SvLBoxContextBmp* pBmpItem)
270
0
{
271
0
    Bitmap aImage = pBmpItem->GetBitmap1().GetBitmap();
272
0
    SvMemoryStream aOStm(65535, 65535);
273
    // Use fastest compression "1"
274
0
    css::uno::Sequence<css::beans::PropertyValue> aFilterData{
275
0
        comphelper::makePropertyValue(u"Compression"_ustr, sal_Int32(1)),
276
0
    };
277
0
    vcl::PngImageWriter aPNGWriter(aOStm);
278
0
    aPNGWriter.setParameters(aFilterData);
279
0
    if (aPNGWriter.write(aImage))
280
0
    {
281
0
        css::uno::Sequence<sal_Int8> aSeq(static_cast<sal_Int8 const*>(aOStm.GetData()),
282
0
                                          aOStm.Tell());
283
0
        OStringBuffer aBuffer("data:image/png;base64,");
284
0
        ::comphelper::Base64::encode(aBuffer, aSeq);
285
0
        return aBuffer.makeStringAndClear();
286
0
    }
287
288
0
    return ""_ostr;
289
0
}
290
291
OUString IconView::renderEntry(int pos, int /*dpix*/, int /*dpiy*/) const
292
0
{
293
    // TODO: support various DPI
294
0
    SvTreeListEntry* pEntry = GetEntry(pos);
295
0
    if (!pEntry)
296
0
        return "";
297
298
0
    OUString sResult;
299
0
    const bool bHandled
300
0
        = maDumpImageHdl.IsSet() && maDumpImageHdl.Call(encoded_image_query(sResult, pEntry));
301
302
0
    if (!bHandled)
303
0
    {
304
0
        if (const SvLBoxItem* pIt = pEntry->GetFirstItem(SvLBoxItemType::ContextBmp))
305
0
        {
306
0
            const SvLBoxContextBmp* pBmpItem = static_cast<const SvLBoxContextBmp*>(pIt);
307
0
            if (pBmpItem)
308
0
                return OStringToOUString(extractPngString(pBmpItem), RTL_TEXTENCODING_ASCII_US);
309
0
        }
310
0
    }
311
312
0
    return sResult;
313
0
}
314
315
void IconView::DumpEntryAndSiblings(tools::JsonWriter& rJsonWriter, SvTreeListEntry* pEntry)
316
0
{
317
0
    while (pEntry)
318
0
    {
319
0
        auto aNode = rJsonWriter.startStruct();
320
321
        // simple listbox value
322
0
        const SvLBoxItem* pIt = pEntry->GetFirstItem(SvLBoxItemType::String);
323
0
        if (pIt)
324
0
            rJsonWriter.put("text", static_cast<const SvLBoxString*>(pIt)->GetText());
325
326
0
        pIt = pEntry->GetFirstItem(SvLBoxItemType::ContextBmp);
327
0
        if (pIt)
328
0
        {
329
0
            const SvLBoxContextBmp* pBmpItem = static_cast<const SvLBoxContextBmp*>(pIt);
330
0
            if (pBmpItem)
331
0
                rJsonWriter.put("ondemand", true);
332
0
        }
333
334
0
        if (const OUString tooltip = GetEntryTooltip(pEntry); !tooltip.isEmpty())
335
0
            rJsonWriter.put("tooltip", tooltip);
336
337
0
        if (IsSelected(pEntry))
338
0
            rJsonWriter.put("selected", true);
339
340
0
        if (pEntry->IsSeparator())
341
0
            rJsonWriter.put("separator", true);
342
343
0
        rJsonWriter.put("row", GetModel()->GetAbsPos(pEntry));
344
345
0
        pEntry = pEntry->NextSibling();
346
0
    }
347
0
}
348
349
void IconView::DumpAsPropertyTree(tools::JsonWriter& rJsonWriter)
350
0
{
351
0
    SvTreeListBox::DumpAsPropertyTree(rJsonWriter);
352
0
    rJsonWriter.put("type", "iconview");
353
0
    rJsonWriter.put("singleclickactivate", GetActivateOnSingleClick());
354
0
    rJsonWriter.put("textWithIconEnabled", IsTextColumnEnabled());
355
0
    auto aNode = rJsonWriter.startArray("entries");
356
0
    DumpEntryAndSiblings(rJsonWriter, First());
357
0
}
358
359
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */