Coverage Report

Created: 2026-09-28 10:59

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libreoffice/svx/source/tbxctrls/PaletteManager.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 <svx/PaletteManager.hxx>
21
22
#include <comphelper/propertyvalue.hxx>
23
#include <tools/urlobj.hxx>
24
#include <tools/json_writer.hxx>
25
#include <osl/file.hxx>
26
#include <unotools/pathoptions.hxx>
27
#include <sfx2/objsh.hxx>
28
#include <svx/drawitem.hxx>
29
#include <svx/strings.hrc>
30
#include <svx/svxids.hrc>
31
#include <svx/dialmgr.hxx>
32
33
#include <tbxcolorupdate.hxx>
34
#include <vcl/svapp.hxx>
35
#include <vcl/settings.hxx>
36
#include <comphelper/sequence.hxx>
37
#include <officecfg/Office/Common.hxx>
38
#include <com/sun/star/frame/XDispatchProvider.hpp>
39
#include <com/sun/star/frame/XDispatch.hpp>
40
#include <com/sun/star/frame/Desktop.hpp>
41
#include <com/sun/star/util/XURLTransformer.hpp>
42
#include <com/sun/star/util/URLTransformer.hpp>
43
#include <docmodel/color/ComplexColor.hxx>
44
#include <docmodel/uno/UnoComplexColor.hxx>
45
46
#include <palettes.hxx>
47
48
#include <memory>
49
#include <array>
50
#include <stack>
51
52
static OUString lcl_convertUINameToFileName(OUString& rStr);
53
54
PaletteManager::PaletteManager() :
55
0
    mnMaxRecentColors(Application::GetSettings().GetStyleSettings().GetColorValueSetColumnCount()),
56
0
    mnNumOfPalettes(3),
57
0
    mnCurrentPalette(0),
58
0
    mnColorCount(0),
59
0
    mpBtnUpdater(nullptr),
60
0
    maColorSelectFunction(PaletteManager::DispatchColorCommand)
61
62
0
{
63
0
    SfxObjectShell* pDocSh = SfxObjectShell::Current();
64
0
    if(pDocSh)
65
0
    {
66
0
        if (const SvxColorListItem* pItem = pDocSh->GetItem(SID_COLOR_TABLE))
67
0
            mpColorList = pItem->GetColorList();
68
0
    }
69
0
    if(!mpColorList.is())
70
0
        mpColorList = XColorList::CreateStdColorList();
71
0
    LoadPalettes();
72
0
    mnNumOfPalettes += m_Palettes.size();
73
0
}
74
75
PaletteManager::~PaletteManager()
76
0
{
77
0
}
78
79
void PaletteManager::LoadPalettes()
80
0
{
81
0
    m_Palettes.clear();
82
0
    OUString aPalPaths = SvtPathOptions().GetPalettePath();
83
84
0
    std::stack<OUString> aDirs;
85
0
    sal_Int32 nIndex = 0;
86
0
    do
87
0
    {
88
0
        aDirs.push(aPalPaths.getToken(0, ';', nIndex));
89
0
    }
90
0
    while (nIndex >= 0);
91
92
0
    std::set<OUString> aNames;
93
    //try all entries in palette path list, user first, then
94
    //system, ignoring duplicate file names
95
0
    while (!aDirs.empty())
96
0
    {
97
0
        OUString aPalPath = aDirs.top();
98
0
        aDirs.pop();
99
100
0
        osl::Directory aDir(aPalPath);
101
0
        osl::DirectoryItem aDirItem;
102
0
        osl::FileStatus aFileStat( osl_FileStatus_Mask_FileName |
103
0
                                   osl_FileStatus_Mask_FileURL  |
104
0
                                   osl_FileStatus_Mask_Type     );
105
0
        if( aDir.open() == osl::FileBase::E_None )
106
0
        {
107
0
            while( aDir.getNextItem(aDirItem) == osl::FileBase::E_None )
108
0
            {
109
0
                aDirItem.getFileStatus(aFileStat);
110
0
                if(aFileStat.isRegular() || aFileStat.isLink())
111
0
                {
112
0
                    OUString aFName = aFileStat.getFileName();
113
0
                    INetURLObject aURLObj( aFileStat.getFileURL() );
114
0
                    OUString aFNameWithoutExt = aURLObj.GetBase();
115
0
                    if (aNames.find(aFName) == aNames.end())
116
0
                    {
117
0
                        std::unique_ptr<Palette> pPalette;
118
0
                        if( aFName.endsWithIgnoreAsciiCase(".gpl") )
119
0
                            pPalette.reset(new PaletteGPL(aFileStat.getFileURL(), aFNameWithoutExt));
120
0
                        else if( aFName.endsWithIgnoreAsciiCase(".soc") )
121
0
                        {
122
0
                            if (aFNameWithoutExt == "standard")
123
0
                                aFNameWithoutExt = SvxResId(RID_SVXSTR_COLOR_PALETTE_STANDARD);
124
0
                            else if (aFNameWithoutExt == "tonal")
125
0
                                aFNameWithoutExt = SvxResId(RID_SVXSTR_COLOR_PALETTE_TONAL);
126
0
                            else if (aFNameWithoutExt == "html")
127
0
                                aFNameWithoutExt = SvxResId(RID_SVXSTR_COLOR_PALETTE_HTML);
128
0
                            else if (aFNameWithoutExt == "chart-palettes")
129
0
                                aFNameWithoutExt = SvxResId(RID_SVXSTR_COLOR_PALETTE_CHARTPALETTES);
130
0
                            else if (aFNameWithoutExt == "compatibility")
131
0
                                aFNameWithoutExt = SvxResId(RID_SVXSTR_COLOR_PALETTE_COMPATIBILITY);
132
0
                            else if (aFNameWithoutExt == "material")
133
0
                                aFNameWithoutExt = SvxResId(RID_SVXSTR_COLOR_PALETTE_MATERIAL);
134
0
                            else if (aFNameWithoutExt == "libreoffice")
135
0
                                aFNameWithoutExt = "LibreOffice";
136
0
                            else if (aFNameWithoutExt == "freecolour-hlc")
137
0
                                aFNameWithoutExt = SvxResId(RID_SVXSTR_COLOR_PALETTE_FREECOLOURHLC);
138
0
                            pPalette.reset(new PaletteSOC(aFileStat.getFileURL(), aFNameWithoutExt));
139
0
                        }
140
0
                        else if ( aFName.endsWithIgnoreAsciiCase(".ase") )
141
0
                            pPalette.reset(new PaletteASE(aFileStat.getFileURL(), aFNameWithoutExt));
142
143
0
                        if( pPalette && pPalette->IsValid() )
144
0
                            m_Palettes.push_back( std::move(pPalette) );
145
146
0
                        aNames.insert(aFNameWithoutExt);
147
0
                    }
148
0
                }
149
0
            }
150
0
        }
151
0
    }
152
0
}
153
154
bool PaletteManager::IsThemePaletteSelected() const
155
0
{
156
0
    return mnCurrentPalette == mnNumOfPalettes - 2;
157
0
}
158
159
bool PaletteManager::GetThemeAndEffectIndex(sal_uInt16 nItemIndex, sal_uInt16& rThemeIndex, sal_uInt16& rEffectIndex)
160
0
{
161
    // Each column is the same color with different effects.
162
0
    rThemeIndex = nItemIndex % 12;
163
164
0
    rEffectIndex = nItemIndex / 12;
165
0
    if (rEffectIndex > 5)
166
0
        return false;
167
0
    return true;
168
0
}
169
170
bool PaletteManager::GetLumModOff(sal_uInt16 nThemeIndex, sal_uInt16 nEffect, sal_Int16& rLumMod, sal_Int16& rLumOff)
171
0
{
172
0
    if (!moThemePaletteCollection)
173
0
        return false;
174
175
0
    auto const& aThemeColorData = moThemePaletteCollection->maColors[nThemeIndex];
176
177
0
    rLumMod = aThemeColorData.getLumMod(nEffect);
178
0
    rLumOff = aThemeColorData.getLumOff(nEffect);
179
180
0
    return true;
181
0
}
182
183
void PaletteManager::ReloadColorSet(IColorSet& rColorSet)
184
0
{
185
0
    moThemePaletteCollection.reset();
186
0
    if( mnCurrentPalette == 0)
187
0
    {
188
0
        rColorSet.clear();
189
0
        css::uno::Sequence< sal_Int32 > CustomColorList( officecfg::Office::Common::UserColors::CustomColor::get() );
190
0
        css::uno::Sequence< OUString > CustomColorNameList( officecfg::Office::Common::UserColors::CustomColorName::get() );
191
0
        for (int i = 0; i < CustomColorList.getLength(); ++i)
192
0
        {
193
0
            Color aColor(ColorTransparency, CustomColorList[i]);
194
0
            rColorSet.insert(i, aColor, CustomColorNameList[i]);
195
0
        }
196
0
    }
197
0
    else if (IsThemePaletteSelected())
198
0
    {
199
0
        SfxObjectShell* pObjectShell = SfxObjectShell::Current();
200
0
        if (pObjectShell)
201
0
        {
202
0
            auto pColorSet = pObjectShell->GetThemeColors();
203
0
            mnColorCount = 12;
204
0
            rColorSet.clear();
205
206
0
            if (!pColorSet)
207
0
                return;
208
209
0
            svx::ThemeColorPaletteManager aThemeColorManager(pColorSet);
210
0
            moThemePaletteCollection = aThemeColorManager.generate();
211
212
0
            int nItemIndex = 0;
213
            // Each row is one effect type (no effect + each type).
214
0
            for (size_t nEffect : {0, 1, 2, 3, 4, 5})
215
0
            {
216
                // Each column is one color type.
217
0
                for (auto const& rColorData : moThemePaletteCollection->maColors)
218
0
                {
219
0
                    auto const& rEffect = rColorData.maEffects[nEffect];
220
0
                    rColorSet.insert(nItemIndex++, rEffect.maColor, rEffect.maColorName);
221
0
                }
222
0
            }
223
0
        }
224
0
    }
225
0
    else if( mnCurrentPalette == mnNumOfPalettes - 1 )
226
0
    {
227
        // Add doc colors to palette
228
0
        SfxObjectShell* pDocSh = SfxObjectShell::Current();
229
0
        if (pDocSh)
230
0
        {
231
0
            std::set<Color> aColors = pDocSh->GetDocColors();
232
0
            mnColorCount = aColors.size();
233
0
            rColorSet.clear();
234
0
            const OUString sNamePrefix = SvxResId(RID_SVXSTR_DOC_COLOR_PREFIX) + u" ";
235
0
            int nIndex = 0;
236
0
            for(const auto& rColor : aColors)
237
0
            {
238
0
                rColorSet.insert(nIndex, rColor, sNamePrefix + OUString::number(nIndex));
239
0
                nIndex++;
240
0
            }
241
0
        }
242
0
    }
243
0
    else
244
0
    {
245
0
        m_Palettes[mnCurrentPalette - 1]->LoadColorSet( rColorSet );
246
0
        mnColorCount = rColorSet.getItemCount();
247
0
    }
248
0
}
249
250
void PaletteManager::ReloadRecentColorSet(IColorSet& rColorSet)
251
0
{
252
0
    maRecentColors.clear();
253
0
    rColorSet.clear();
254
0
    css::uno::Sequence< sal_Int32 > Colorlist(officecfg::Office::Common::UserColors::RecentColor::get());
255
0
    css::uno::Sequence< OUString > ColorNamelist(officecfg::Office::Common::UserColors::RecentColorName::get());
256
0
    const bool bHasColorNames = Colorlist.getLength() == ColorNamelist.getLength();
257
0
    for (int i = 0; i < Colorlist.getLength(); ++i)
258
0
    {
259
0
        Color aColor(ColorTransparency, Colorlist[i]);
260
0
        OUString sColorName = bHasColorNames ? ColorNamelist[i] : ("#" + aColor.AsRGBHexString().toAsciiUpperCase());
261
0
        maRecentColors.emplace_back(aColor, sColorName);
262
0
        rColorSet.insert(i, aColor, sColorName);
263
0
    }
264
0
}
265
266
std::vector<OUString> PaletteManager::GetPaletteList()
267
0
{
268
0
    std::vector<OUString> aPaletteNames
269
0
    {
270
0
        SvxResId( RID_SVXSTR_CUSTOM_PAL )
271
0
    };
272
0
    for (auto const& it : m_Palettes)
273
0
    {
274
0
        aPaletteNames.push_back( (*it).GetName() );
275
0
    }
276
0
    aPaletteNames.push_back(SvxResId(RID_SVXSTR_THEME_COLORS));
277
0
    aPaletteNames.push_back( SvxResId ( RID_SVXSTR_DOC_COLORS ) );
278
279
0
    return aPaletteNames;
280
0
}
281
282
void PaletteManager::SetPalette(sal_Int32 nPos, bool bPosOnly)
283
0
{
284
0
    mnCurrentPalette = nPos;
285
0
    if (bPosOnly)
286
0
        return;
287
0
    if( nPos != mnNumOfPalettes - 1 && nPos != 0)
288
0
    {
289
0
        mpColorList = XPropertyList::AsColorList(
290
0
                            XPropertyList::CreatePropertyListFromURL(
291
0
                            XPropertyListType::Color, GetSelectedPalettePath()));
292
0
        auto name = GetPaletteName(); // may change pColorList
293
0
        OUString fileName = lcl_convertUINameToFileName(name);
294
0
        mpColorList->SetName(fileName);
295
0
        if(mpColorList->Load())
296
0
        {
297
0
            SfxObjectShell* pShell = SfxObjectShell::Current();
298
0
            if (pShell != nullptr)
299
0
            {
300
0
                SvxColorListItem aColorItem(mpColorList, SID_COLOR_TABLE);
301
0
                pShell->PutItem( aColorItem );
302
0
            }
303
0
        }
304
0
    }
305
0
    OUString aPaletteName(officecfg::Office::Common::UserColors::PaletteName::get());
306
0
    if (aPaletteName != GetPaletteName())
307
0
    {
308
0
        std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create());
309
0
        officecfg::Office::Common::UserColors::PaletteName::set(GetPaletteName(), batch);
310
0
        batch->commit();
311
0
    }
312
0
}
313
314
sal_Int32 PaletteManager::GetPalette() const
315
0
{
316
0
    return mnCurrentPalette;
317
0
}
318
319
OUString PaletteManager::GetPaletteName()
320
0
{
321
0
    std::vector<OUString> aNames(GetPaletteList());
322
0
    if(mnCurrentPalette != mnNumOfPalettes - 1 && mnCurrentPalette != 0)
323
0
    {
324
0
        SfxObjectShell* pDocSh = SfxObjectShell::Current();
325
0
        if(pDocSh)
326
0
        {
327
0
            if (const SvxColorListItem* pItem = pDocSh->GetItem(SID_COLOR_TABLE))
328
0
                mpColorList = pItem->GetColorList();
329
0
        }
330
0
    }
331
0
    return aNames[mnCurrentPalette];
332
0
}
333
334
const OUString & PaletteManager::GetSelectedPalettePath()
335
0
{
336
0
    auto nPalettesIndex = mnCurrentPalette - 1;
337
0
    if (nPalettesIndex >= 0 && o3tl::make_unsigned(nPalettesIndex) < m_Palettes.size())
338
0
        return m_Palettes[nPalettesIndex]->GetPath();
339
0
    else
340
0
        return EMPTY_OUSTRING;
341
0
}
342
343
void PaletteManager::AddRecentColor(const Color& rRecentColor, const OUString& rName, bool bFront)
344
0
{
345
0
    auto itColor = std::find_if(maRecentColors.begin(),
346
0
                                maRecentColors.end(),
347
0
                                [rRecentColor] (const NamedColor &aColor) { return aColor.m_aColor == rRecentColor; });
348
    // if recent color to be added is already in list, remove it
349
0
    if( itColor != maRecentColors.end() )
350
0
        maRecentColors.erase( itColor );
351
352
0
    if (maRecentColors.size() == mnMaxRecentColors)
353
0
        maRecentColors.pop_back();
354
0
    if (bFront)
355
0
        maRecentColors.emplace_front(rRecentColor, rName);
356
0
    else
357
0
        maRecentColors.emplace_back(rRecentColor, rName);
358
0
    css::uno::Sequence< sal_Int32 > aColorList(maRecentColors.size());
359
0
    auto aColorListRange = asNonConstRange(aColorList);
360
0
    css::uno::Sequence< OUString > aColorNameList(maRecentColors.size());
361
0
    auto aColorNameListRange = asNonConstRange(aColorNameList);
362
0
    for (size_t i = 0; i < maRecentColors.size(); ++i)
363
0
    {
364
0
        aColorListRange[i] = static_cast<sal_Int32>(maRecentColors[i].m_aColor);
365
0
        aColorNameListRange[i] = maRecentColors[i].m_aName;
366
0
    }
367
0
    std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create());
368
0
    officecfg::Office::Common::UserColors::RecentColor::set(aColorList, batch);
369
0
    officecfg::Office::Common::UserColors::RecentColorName::set(aColorNameList, batch);
370
0
    batch->commit();
371
0
}
372
373
void PaletteManager::SetSplitButtonColor(const NamedColor& rColor)
374
0
{
375
0
    if (mpBtnUpdater)
376
0
        mpBtnUpdater->SetRecentColor(rColor);
377
0
}
378
379
void PaletteManager::SetBtnUpdater(svx::ToolboxButtonColorUpdaterBase* pBtnUpdater)
380
0
{
381
0
    mpBtnUpdater = pBtnUpdater;
382
0
}
383
384
void PaletteManager::SetColorSelectFunction(const ColorSelectFunction& aColorSelectFunction)
385
0
{
386
0
    maColorSelectFunction = aColorSelectFunction;
387
0
}
388
389
void PaletteManager::PopupColorPicker(weld::Window* pParent, const OUString& aCommand, const Color& rInitialColor)
390
0
{
391
    // The calling object goes away during aColorDlg.Execute(), so we must copy this
392
0
    OUString aCommandCopy = aCommand;
393
0
    m_pColorDlg = std::make_unique<ColorDialog>(pParent, vcl::ColorPickerMode::Modify);
394
0
    m_pColorDlg->SetColor(rInitialColor);
395
0
    std::shared_ptr<PaletteManager> xSelf(shared_from_this());
396
0
    m_pColorDlg->ExecuteAsync([xSelf=std::move(xSelf),
397
0
                               aCommandCopy=std::move(aCommandCopy)] (sal_Int32 nResult) {
398
0
        if (nResult == RET_OK)
399
0
        {
400
0
            Color aLastColor = xSelf->m_pColorDlg->GetColor();
401
0
            OUString sColorName = "#" + aLastColor.AsRGBHexString().toAsciiUpperCase();
402
0
            NamedColor aNamedColor(aLastColor, sColorName);
403
0
            xSelf->SetSplitButtonColor(aNamedColor);
404
0
            xSelf->AddRecentColor(aLastColor, sColorName);
405
0
            xSelf->maColorSelectFunction(aCommandCopy, aNamedColor);
406
0
        }
407
0
    });
408
0
}
409
410
void PaletteManager::DispatchColorCommand(const OUString& aCommand, const NamedColor& rColor)
411
0
{
412
0
    using namespace css;
413
0
    using namespace css::uno;
414
0
    using namespace css::frame;
415
0
    using namespace css::beans;
416
0
    using namespace css::util;
417
418
0
    const Reference<XComponentContext>& xContext(comphelper::getProcessComponentContext());
419
0
    Reference<XDesktop2> xDesktop = Desktop::create(xContext);
420
0
    Reference<XFrame> xFrame(xDesktop->getCurrentFrame());
421
0
    Reference<XDispatchProvider> xDispatchProvider(xFrame, UNO_QUERY);
422
0
    if (!xDispatchProvider.is())
423
0
        return;
424
425
0
    INetURLObject aObj( aCommand );
426
427
0
    std::vector<PropertyValue> aArgs{
428
0
        comphelper::makePropertyValue(aObj.GetURLPath()+ ".Color", sal_Int32(rColor.m_aColor)),
429
0
    };
430
431
0
    if (rColor.m_nThemeIndex != -1)
432
0
    {
433
0
        model::ComplexColor aComplexColor;
434
0
        aComplexColor.setThemeColor(model::convertToThemeColorType(rColor.m_nThemeIndex));
435
0
        if (rColor.m_nLumMod != 10000)
436
0
            aComplexColor.addTransformation({model::TransformationType::LumMod, rColor.m_nLumMod});
437
0
        if (rColor.m_nLumMod != 0)
438
0
            aComplexColor.addTransformation({model::TransformationType::LumOff, rColor.m_nLumOff});
439
440
0
        uno::Any aAny;
441
0
        aAny <<= model::color::createXComplexColor(aComplexColor);
442
443
0
        aArgs.push_back(comphelper::makePropertyValue(aObj.GetURLPath() + ".ComplexColor", aAny));
444
0
    }
445
446
0
    URL aTargetURL;
447
0
    aTargetURL.Complete = aCommand;
448
0
    Reference<XURLTransformer> xURLTransformer(URLTransformer::create(comphelper::getProcessComponentContext()));
449
0
    xURLTransformer->parseStrict(aTargetURL);
450
451
0
    Reference<XDispatch> xDispatch = xDispatchProvider->queryDispatch(aTargetURL, OUString(), 0);
452
0
    if (xDispatch.is())
453
0
    {
454
0
        xDispatch->dispatch(aTargetURL, comphelper::containerToSequence(aArgs));
455
0
        if (xFrame->getContainerWindow().is())
456
0
            xFrame->getContainerWindow()->setFocus();
457
0
    }
458
0
}
459
460
// tdf#171670 for soc palettes names, UI names are stored instead of file names
461
// so let's convert back to file names
462
static OUString lcl_convertUINameToFileName(OUString& rStr)
463
0
{
464
0
    if (rStr == SvxResId(RID_SVXSTR_COLOR_PALETTE_STANDARD))
465
0
       return u"standard"_ustr;
466
0
    else if (rStr == SvxResId(RID_SVXSTR_COLOR_PALETTE_TONAL))
467
0
        return u"tonal"_ustr;
468
0
    else if (rStr == SvxResId(RID_SVXSTR_COLOR_PALETTE_HTML))
469
0
        return u"html"_ustr;
470
0
    else if (rStr == SvxResId(RID_SVXSTR_COLOR_PALETTE_CHARTPALETTES))
471
0
        return u"chart-palettes"_ustr;
472
0
    else if (rStr == SvxResId(RID_SVXSTR_COLOR_PALETTE_COMPATIBILITY))
473
0
        return u"compatibility"_ustr;
474
0
    else if (rStr == SvxResId(RID_SVXSTR_COLOR_PALETTE_MATERIAL))
475
0
        return u"material"_ustr;
476
0
    else if (rStr == "LibreOffice")
477
0
        return u"libreoffice"_ustr;
478
0
    else if (rStr == SvxResId(RID_SVXSTR_COLOR_PALETTE_FREECOLOURHLC))
479
0
       return u"freecolour-hlc"_ustr;
480
0
    return rStr;
481
0
}
482
483
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */