1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <sal/config.h>
24 #include <com/sun/star/configuration/ReadWriteAccess.hpp>
25 #include <com/sun/star/beans/PropertyAttribute.hpp>
26 #include <tools/debug.hxx>
27 #include <editeng/editids.hrc>
28 #include <svtools/colorcfg.hxx>
29 #include <svtools/extcolorcfg.hxx>
30 #include <svx/colorbox.hxx>
31 #include <unotools/moduleoptions.hxx>
32 #include <vcl/svapp.hxx>
33 #include <vcl/weld.hxx>
34 #include <svx/svxdlg.hxx>
36 #include <dialmgr.hxx>
37 #include "optcolor.hxx"
38 #include <svtools/restartdialog.hxx>
39 #include <strings.hrc>
40 #include <svtools/miscopt.hxx>
41 #include <officecfg/Office/Common.hxx>
42 #include <officecfg/Office/ExtendedColorScheme.hxx>
43 #include <officecfg/Office/UI.hxx>
44 using namespace ::com::sun::star
;
45 using namespace ::svtools
;
50 // list of default groups
75 // the groups are in the same order as in enum Group above
76 { Group_General
, u
"general"_ustr
},
77 { Group_Writer
, u
"writer"_ustr
},
78 { Group_Html
, u
"html"_ustr
},
79 { Group_Calc
, u
"calc"_ustr
},
80 { Group_Draw
, u
"draw"_ustr
},
81 { Group_Comment
, u
"comment"_ustr
},
82 { Group_Basic
, u
"basic"_ustr
},
83 { Group_Sql
, u
"sql"_ustr
},
86 // color config entry data (see ColorConfigWindow_Impl::Entry below)
91 //checkbox (or simple text)
98 std::u16string_view sPropName
;
103 u"" SAL_STRINGIFY(Name) ""_ustr, u"" SAL_STRINGIFY(Name##_lb) ""_ustr, false
105 #define IDS_CB(Name) \
106 u"" SAL_STRINGIFY(Name) ""_ustr, u"" SAL_STRINGIFY(Name##_lb) ""_ustr, true
108 // The list of these entries (enum ColorConfigEntry) are in colorcfg.hxx.
110 { Group_General
, IDS(doccolor
), std::u16string_view(u
"/DocColor") },
111 { Group_General
, IDS(docboundaries
), std::u16string_view(u
"/DocBoundaries") },
112 { Group_General
, IDS(appback
), std::u16string_view(u
"/AppBackground") },
113 { Group_General
, IDS(tblboundaries
), std::u16string_view(u
"/TableBoundaries") },
114 { Group_General
, IDS(font
), std::u16string_view(u
"/FontColor") },
115 { Group_General
, IDS_CB(unvisitedlinks
), std::u16string_view(u
"/Links") },
116 { Group_General
, IDS_CB(visitedlinks
), std::u16string_view(u
"/LinksVisited") },
117 { Group_General
, IDS(autospellcheck
), std::u16string_view(u
"/Spell") },
118 { Group_General
, IDS(grammarcheck
), std::u16string_view(u
"/Grammar") },
119 { Group_General
, IDS(smarttags
), std::u16string_view(u
"/SmartTags") },
120 { Group_General
, IDS_CB(shadows
), std::u16string_view(u
"/Shadow") },
122 { Group_Writer
, IDS(writergrid
), std::u16string_view(u
"/WriterTextGrid") },
123 { Group_Writer
, IDS_CB(field
), std::u16string_view(u
"/WriterFieldShadings") },
124 { Group_Writer
, IDS_CB(index
), std::u16string_view(u
"/WriterIdxShadings") },
125 { Group_Writer
, IDS(direct
), std::u16string_view(u
"/WriterDirectCursor") },
126 { Group_Writer
, IDS(script
), std::u16string_view(u
"/WriterScriptIndicator") },
127 { Group_Writer
, IDS(section
), std::u16string_view(u
"/WriterSectionBoundaries") },
128 { Group_Writer
, IDS(hdft
), std::u16string_view(u
"/WriterHeaderFooterMark") },
129 { Group_Writer
, IDS(pagebreak
), std::u16string_view(u
"/WriterPageBreaks") },
130 { Group_Writer
, IDS(nonprintchars
), std::u16string_view(u
"/WriterNonPrintChars") },
132 { Group_Html
, IDS(sgml
), std::u16string_view(u
"/HTMLSGML") },
133 { Group_Html
, IDS(htmlcomment
), std::u16string_view(u
"/HTMLComment") },
134 { Group_Html
, IDS(htmlkeyword
), std::u16string_view(u
"/HTMLKeyword") },
135 { Group_Html
, IDS(unknown
), std::u16string_view(u
"/HTMLUnknown") },
137 { Group_Calc
, IDS(calcgrid
), std::u16string_view(u
"/CalcGrid") },
138 { Group_Calc
, IDS(calccellfocus
), std::u16string_view(u
"/CalcCellFocus") },
139 { Group_Calc
, IDS(brk
), std::u16string_view(u
"/CalcPageBreak") },
140 { Group_Calc
, IDS(brkmanual
), std::u16string_view(u
"/CalcPageBreakManual") },
141 { Group_Calc
, IDS(brkauto
), std::u16string_view(u
"/CalcPageBreakAutomatic") },
142 { Group_Calc
, IDS_CB(hiddencolrow
), std::u16string_view(u
"/CalcHiddenColRow") },
143 { Group_Calc
, IDS_CB(textoverflow
), std::u16string_view(u
"/CalcTextOverflow") },
144 { Group_Calc
, IDS(comments
), std::u16string_view(u
"/CalcComments") },
145 { Group_Calc
, IDS(det
), std::u16string_view(u
"/CalcDetective") },
146 { Group_Calc
, IDS(deterror
), std::u16string_view(u
"/CalcDetectiveError") },
147 { Group_Calc
, IDS(ref
), std::u16string_view(u
"/CalcReference") },
148 { Group_Calc
, IDS(notes
), std::u16string_view(u
"/CalcNotesBackground") },
149 { Group_Calc
, IDS(values
), std::u16string_view(u
"/CalcValue") },
150 { Group_Calc
, IDS(formulas
), std::u16string_view(u
"/CalcFormula") },
151 { Group_Calc
, IDS(text
), std::u16string_view(u
"/CalcText") },
152 { Group_Calc
, IDS(protectedcells
), std::u16string_view(u
"/CalcProtectedBackground") },
154 { Group_Draw
, IDS(drawgrid
), std::u16string_view(u
"/DrawGrid") },
156 { Group_Comment
, IDS(author1
), std::u16string_view(u
"/Author1") },
157 { Group_Comment
, IDS(author2
), std::u16string_view(u
"/Author2") },
158 { Group_Comment
, IDS(author3
), std::u16string_view(u
"/Author3") },
159 { Group_Comment
, IDS(author4
), std::u16string_view(u
"/Author4") },
160 { Group_Comment
, IDS(author5
), std::u16string_view(u
"/Author5") },
161 { Group_Comment
, IDS(author6
), std::u16string_view(u
"/Author6") },
162 { Group_Comment
, IDS(author7
), std::u16string_view(u
"/Author7") },
163 { Group_Comment
, IDS(author8
), std::u16string_view(u
"/Author8") },
164 { Group_Comment
, IDS(author9
), std::u16string_view(u
"/Author9") },
166 { Group_Basic
, IDS(basiceditor
), std::u16string_view(u
"/BASICEditor") },
167 { Group_Basic
, IDS(basicid
), std::u16string_view(u
"/BASICIdentifier") },
168 { Group_Basic
, IDS(basiccomment
), std::u16string_view(u
"/BASICComment") },
169 { Group_Basic
, IDS(basicnumber
), std::u16string_view(u
"/BASICNumber") },
170 { Group_Basic
, IDS(basicstring
), std::u16string_view(u
"/BASICString") },
171 { Group_Basic
, IDS(basicop
), std::u16string_view(u
"/BASICOperator") },
172 { Group_Basic
, IDS(basickeyword
), std::u16string_view(u
"/BASICKeyword") },
173 { Group_Basic
, IDS(error
), std::u16string_view(u
"/BASICError") },
175 { Group_Sql
, IDS(sqlid
), std::u16string_view(u
"/SQLIdentifier") },
176 { Group_Sql
, IDS(sqlnumber
), std::u16string_view(u
"/SQLNumber") },
177 { Group_Sql
, IDS(sqlstring
), std::u16string_view(u
"/SQLString") },
178 { Group_Sql
, IDS(sqlop
), std::u16string_view(u
"/SQLOperator") },
179 { Group_Sql
, IDS(sqlkeyword
), std::u16string_view(u
"/SQLKeyword") },
180 { Group_Sql
, IDS(sqlparam
), std::u16string_view(u
"/SQLParameter") },
181 { Group_Sql
, IDS(sqlcomment
), std::u16string_view(u
"/SQLComment") }
186 // Maps the names of default color schemes to the corresponding TranslateId
187 const std::map
<OUString
, OUString
> &getColorSchemes()
189 static std::map
<OUString
, OUString
> const vColorSchemes
= {
190 {"COLOR_SCHEME_LIBREOFFICE_AUTOMATIC", CuiResId(RID_COLOR_SCHEME_LIBREOFFICE_AUTOMATIC
)},
192 return vColorSchemes
;
195 // If the color scheme name has a translated string, then return the translation
196 // Or else simply return the input string
197 // For non-translatable color schemes, the ID and the name are the same
198 OUString
lcl_SchemeIdToTranslatedName(const OUString
& sSchemeId
)
200 auto it
= getColorSchemes().find(sSchemeId
);
201 if (it
!= getColorSchemes().end())
206 // Given a translated color scheme name, return the scheme ID used in the UI.xcu file
207 // For non-translatable color schemes, the ID and the name are the same
208 OUString
lcl_TranslatedNameToSchemeId(const OUString
& sName
)
210 for (auto it
= getColorSchemes().begin(); it
!= getColorSchemes().end(); ++it
)
211 if (it
->second
== sName
)
216 // ColorConfigWindow_Impl
218 class ColorConfigWindow_Impl
221 explicit ColorConfigWindow_Impl(weld::Window
* pTopLevel
, weld::Container
* pParent
);
224 void SetLinks(Link
<weld::Toggleable
&,void> const&,
225 Link
<ColorListBox
&,void> const&,
226 Link
<weld::Widget
&,void> const&,
227 weld::ScrolledWindow
& rScroll
);
228 void Update(EditableColorConfig
const*, EditableExtendedColorConfig
const*);
229 void UpdateEntries();
230 void ClickHdl(EditableColorConfig
*, const weld::Toggleable
&);
231 void ColorHdl(EditableColorConfig
*, EditableExtendedColorConfig
*, const ColorListBox
*);
233 weld::Widget
& GetWidget1()
238 weld::Widget
& GetWidget2()
243 weld::Widget
& GetBody()
248 int GetLabelIndent() const
250 return m_nCheckBoxLabelOffset
;
254 // Chapter -- horizontal group separator stripe with text
258 std::unique_ptr
<weld::Label
> m_xText
;
260 Chapter(weld::Builder
& rBuilder
, const OUString
& pLabelWidget
, bool bShow
);
261 void SetText(const OUString
& rLabel
) { m_xText
->set_label(rLabel
); }
264 // Entry -- a color config entry:
265 // text (checkbox) + color list box
268 Entry(weld::Window
* pTopLevel
, weld::Builder
& rBuilder
, const OUString
& pTextWidget
, const OUString
& pColorWidget
,
269 const Color
& rColor
, int nCheckBoxLabelOffset
, const ColorListBox
* pCache
, bool bCheckBox
, bool bShow
);
270 void SetText(const OUString
& rLabel
) { dynamic_cast<weld::Label
&>(*m_xText
).set_label(rLabel
); }
271 int get_height_request() const
273 return std::max(m_xText
->get_preferred_size().Height(),
274 m_xColorList
->get_widget().get_preferred_size().Height());
278 void SetLinks(Link
<weld::Toggleable
&,void> const&,
279 Link
<ColorListBox
&,void> const&,
280 Link
<weld::Widget
&,void> const&);
281 void Update (ColorConfigValue
const&, std::u16string_view
,
282 css::uno::Reference
<css::configuration::XReadWriteAccess
> const&);
283 void Update (ExtendedColorConfigValue
const&, std::u16string_view
,
284 css::uno::Reference
<css::configuration::XReadWriteAccess
> const&);
285 void ColorChanged (ColorConfigValue
&);
286 void ColorChanged (ExtendedColorConfigValue
&);
288 bool Is(const weld::Toggleable
* pBox
) const { return m_xText
.get() == pBox
; }
289 bool Is(const ColorListBox
* pBox
) const { return m_xColorList
.get() == pBox
; }
291 // checkbox (CheckBox) or simple text (FixedText)
292 std::unique_ptr
<weld::Widget
> m_xText
;
294 std::unique_ptr
<ColorListBox
> m_xColorList
;
296 Color m_aDefaultColor
;
299 css::uno::Reference
<css::configuration::XReadWriteAccess
> m_xReadWriteAccess
;
302 weld::Window
* m_pTopLevel
;
303 int m_nCheckBoxLabelOffset
;
304 std::unique_ptr
<weld::Builder
> m_xBuilder
;
305 std::unique_ptr
<weld::Box
> m_xBox
;
306 std::unique_ptr
<weld::Widget
> m_xWidget1
;
307 std::unique_ptr
<weld::Widget
> m_xWidget2
;
309 std::vector
<std::unique_ptr
<weld::Builder
>> vExtBuilders
;
310 std::vector
<std::unique_ptr
<weld::Container
>> vExtContainers
;
311 // vChapters -- groups (group headers)
312 std::vector
<std::shared_ptr
<Chapter
> > vChapters
;
313 // vEntries -- color options
314 std::vector
<std::shared_ptr
<Entry
> > vEntries
;
317 SvtModuleOptions aModuleOptions
;
320 void CreateEntries();
324 bool IsGroupVisible (Group
) const;
329 // ColorConfigWindow_Impl::Chapter
331 // ctor for default groups
332 // rParent: parent window (ColorConfigWindow_Impl)
333 // eGroup: which group is this?
334 ColorConfigWindow_Impl::Chapter::Chapter(weld::Builder
& rBuilder
, const OUString
& pLabelWidget
, bool bShow
)
335 : m_xText(rBuilder
.weld_label(pLabelWidget
))
341 // ColorConfigWindow_Impl::Entry
342 ColorConfigWindow_Impl::Entry::Entry(weld::Window
* pTopLevel
, weld::Builder
& rBuilder
,
343 const OUString
& pTextWidget
, const OUString
& pColorWidget
,
344 const Color
& rColor
, int nCheckBoxLabelOffset
,
345 const ColorListBox
* pCache
, bool bCheckBox
, bool bShow
)
346 : m_xColorList(new ColorListBox(rBuilder
.weld_menu_button(pColorWidget
),
347 [pTopLevel
]{ return pTopLevel
; }, pCache
))
348 , m_aDefaultColor(rColor
)
351 m_xText
= rBuilder
.weld_check_button(pTextWidget
);
353 m_xText
= rBuilder
.weld_label(pTextWidget
);
356 m_xColorList
->SetSlotId(SID_ATTR_CHAR_COLOR
);
357 m_xColorList
->SetAutoDisplayColor(m_aDefaultColor
);
361 m_xText
->set_margin_start(m_xText
->get_margin_start() +
362 nCheckBoxLabelOffset
);
369 void ColorConfigWindow_Impl::Entry::Hide()
372 m_xColorList
->hide();
376 void ColorConfigWindow_Impl::Entry::SetLinks(Link
<weld::Toggleable
&,void> const& rCheckLink
,
377 Link
<ColorListBox
&,void> const& rColorLink
,
378 Link
<weld::Widget
&,void> const& rGetFocusLink
)
380 m_xColorList
->SetSelectHdl(rColorLink
);
381 m_xColorList
->connect_focus_in(rGetFocusLink
);
382 if (weld::Toggleable
* pCheckBox
= dynamic_cast<weld::Toggleable
*>(m_xText
.get()))
384 pCheckBox
->connect_toggled(rCheckLink
);
385 pCheckBox
->connect_focus_in(rGetFocusLink
);
389 // updates a default color config entry
390 void ColorConfigWindow_Impl::Entry::Update(ColorConfigValue
const& rValue
, std::u16string_view rConfigPath
,
391 css::uno::Reference
<css::configuration::XReadWriteAccess
> const& xReadWriteAccess
)
393 Color
aColor(rValue
.nColor
);
394 m_xColorList
->SelectEntry(aColor
);
396 bool bReadOnly
= false;
397 OUString aConfigPath
= OUString::Concat(rConfigPath
) + "/Color";
398 if (xReadWriteAccess
->hasPropertyByHierarchicalName(aConfigPath
))
400 css::beans::Property aProperty
= xReadWriteAccess
->getPropertyByHierarchicalName(aConfigPath
);
401 bReadOnly
= (aProperty
.Attributes
& css::beans::PropertyAttribute::READONLY
) != 0;
403 m_xColorList
->set_sensitive(!bReadOnly
);
405 if (weld::Toggleable
* pCheckBox
= dynamic_cast<weld::Toggleable
*>(m_xText
.get()))
408 pCheckBox
->set_active(rValue
.bIsVisible
);
410 aConfigPath
= OUString::Concat(rConfigPath
) + "/IsVisible";
411 if (xReadWriteAccess
->hasPropertyByHierarchicalName(aConfigPath
))
413 css::beans::Property aProperty
= xReadWriteAccess
->getPropertyByHierarchicalName(aConfigPath
);
414 bReadOnly
= (aProperty
.Attributes
& css::beans::PropertyAttribute::READONLY
) != 0;
416 pCheckBox
->set_sensitive(!bReadOnly
);
420 // updates an extended color config entry
421 void ColorConfigWindow_Impl::Entry::Update(ExtendedColorConfigValue
const& rValue
, std::u16string_view rConfigPath
,
422 css::uno::Reference
<css::configuration::XReadWriteAccess
> const& xReadWriteAccess
)
424 Color
aColor(rValue
.getColor());
425 if (rValue
.getColor() == rValue
.getDefaultColor())
426 m_xColorList
->SelectEntry(COL_AUTO
);
428 m_xColorList
->SelectEntry(aColor
);
430 bool bReadOnly
= false;
431 OUString aConfigPath
= OUString::Concat(rConfigPath
) + rValue
.getName() + "/Color";
432 if (xReadWriteAccess
->hasPropertyByHierarchicalName(aConfigPath
))
434 css::beans::Property aProperty
= xReadWriteAccess
->getPropertyByHierarchicalName(aConfigPath
);
435 bReadOnly
= (aProperty
.Attributes
& css::beans::PropertyAttribute::READONLY
) != 0;
437 m_xColorList
->set_sensitive(!bReadOnly
);
440 // color of a default entry has changed
441 void ColorConfigWindow_Impl::Entry::ColorChanged(ColorConfigValue
& rValue
)
443 Color aColor
= m_xColorList
->GetSelectEntryColor();
444 rValue
.nColor
= aColor
;
447 // color of an extended entry has changed
448 void ColorConfigWindow_Impl::Entry::ColorChanged(ExtendedColorConfigValue
& rValue
)
450 Color aColor
= m_xColorList
->GetSelectEntryColor();
451 rValue
.setColor(aColor
);
452 if (aColor
== COL_AUTO
)
454 rValue
.setColor(rValue
.getDefaultColor());
458 // ColorConfigWindow_Impl
459 ColorConfigWindow_Impl::ColorConfigWindow_Impl(weld::Window
* pTopLevel
, weld::Container
* pParent
)
460 : m_pTopLevel(pTopLevel
)
461 , m_xBuilder(Application::CreateBuilder(pParent
, u
"cui/ui/colorconfigwin.ui"_ustr
))
462 , m_xBox(m_xBuilder
->weld_box(u
"ColorConfigWindow"_ustr
))
463 , m_xWidget1(m_xBuilder
->weld_widget(u
"docboundaries"_ustr
))
464 , m_xWidget2(m_xBuilder
->weld_widget(u
"docboundaries_lb"_ustr
))
466 const css::uno::Reference
< css::uno::XComponentContext
>& xContext(::comphelper::getProcessComponentContext());
467 m_xReadWriteAccess
= css::configuration::ReadWriteAccess::create(xContext
, u
"*"_ustr
);
472 void ColorConfigWindow_Impl::CreateEntries()
474 std::bitset
<nGroupCount
> aModulesInstalled
;
475 // creating group headers
476 vChapters
.reserve(nGroupCount
);
477 for (unsigned i
= 0; i
!= nGroupCount
; ++i
)
479 aModulesInstalled
[i
] = IsGroupVisible(vGroupInfo
[i
].eGroup
);
480 vChapters
.push_back(std::make_shared
<Chapter
>(*m_xBuilder
, vGroupInfo
[i
].pGroup
, aModulesInstalled
[i
]));
483 // Here we want to get the amount to add to the position of a FixedText to
484 // get it to align its contents with that of a CheckBox
486 OUString
sSampleText(u
"XXXXXX"_ustr
);
487 std::unique_ptr
<weld::CheckButton
> xCheckBox(m_xBuilder
->weld_check_button(u
"unvisitedlinks"_ustr
));
488 std::unique_ptr
<weld::Label
> xFixedText(m_xBuilder
->weld_label(u
"doccolor"_ustr
));
489 OUString
sOrigCheck(xCheckBox
->get_label());
490 OUString
sOrigFixed(xFixedText
->get_label());
491 xCheckBox
->set_label(sSampleText
);
492 xFixedText
->set_label(sSampleText
);
493 Size
aCheckSize(xCheckBox
->get_preferred_size());
494 Size
aFixedSize(xFixedText
->get_preferred_size());
495 xCheckBox
->set_label(sOrigCheck
);
496 xFixedText
->set_label(sOrigFixed
);
497 m_nCheckBoxLabelOffset
= aCheckSize
.Width() - aFixedSize
.Width();
500 const ColorListBox
* pCache
= nullptr;
503 vEntries
.reserve(ColorConfigEntryCount
- THEME_APPLICATION_COLORS_COUNT
);
504 for (size_t i
= 0; i
< std::size(vEntryInfo
); ++i
)
506 vEntries
.push_back(std::make_shared
<Entry
>(m_pTopLevel
, *m_xBuilder
,
507 vEntryInfo
[i
].pText
, vEntryInfo
[i
].pColor
,
508 ColorConfig::GetDefaultColor(static_cast<ColorConfigEntry
>(i
)),
509 m_nCheckBoxLabelOffset
, pCache
,
510 vEntryInfo
[i
].bCheckBox
,
511 aModulesInstalled
[vEntryInfo
[i
].eGroup
]));
513 pCache
= vEntries
.back()->m_xColorList
.get();
517 ExtendedColorConfig aExtConfig
;
518 unsigned const nExtGroupCount
= aExtConfig
.GetComponentCount();
522 for (unsigned j
= 0; j
!= nExtGroupCount
; ++j
)
524 vExtBuilders
.emplace_back(Application::CreateBuilder(m_xBox
.get(), u
"cui/ui/chapterfragment.ui"_ustr
));
525 vExtContainers
.emplace_back(vExtBuilders
.back()->weld_frame(u
"ChapterFragment"_ustr
));
527 OUString
const sComponentName
= aExtConfig
.GetComponentName(j
);
528 vChapters
.push_back(std::make_shared
<Chapter
>(
529 *vExtBuilders
.back(), "chapter", true));
530 vChapters
.back()->SetText(aExtConfig
.GetComponentDisplayName(sComponentName
));
532 vExtContainers
.emplace_back(vExtBuilders
.back()->weld_box(u
"contents"_ustr
));
533 weld::Container
* pChapterBox
= vExtContainers
.back().get();
535 unsigned nColorCount
= aExtConfig
.GetComponentColorCount(sComponentName
);
536 for (unsigned i
= 0; i
!= nColorCount
; ++i
)
538 vExtBuilders
.emplace_back(Application::CreateBuilder(pChapterBox
, u
"cui/ui/colorfragment.ui"_ustr
));
539 vExtContainers
.emplace_back(vExtBuilders
.back()->weld_container(u
"ColorFragment"_ustr
));
541 ExtendedColorConfigValue
const aColorEntry
=
542 aExtConfig
.GetComponentColorConfigValue(sComponentName
, i
);
543 vEntries
.push_back(std::make_shared
<Entry
>(m_pTopLevel
, *vExtBuilders
.back(),
544 "label", "button", aColorEntry
.getDefaultColor(),
545 m_nCheckBoxLabelOffset
, pCache
, false, true));
546 vEntries
.back()->SetText(aColorEntry
.getDisplayName());
552 void ColorConfigWindow_Impl::SetLinks(Link
<weld::Toggleable
&,void> const& aCheckLink
,
553 Link
<ColorListBox
&,void> const& aColorLink
,
554 Link
<weld::Widget
&,void> const& rGetFocusLink
,
555 weld::ScrolledWindow
& rScroll
)
557 if (vEntries
.empty())
559 for (auto const & i
: vEntries
)
560 i
->SetLinks(aCheckLink
, aColorLink
, rGetFocusLink
);
561 // 6 is the spacing set on ColorConfigWindow
562 rScroll
.vadjustment_set_step_increment(vEntries
[0]->get_height_request() + 6);
566 void ColorConfigWindow_Impl::Update (
567 EditableColorConfig
const* pConfig
,
568 EditableExtendedColorConfig
const* pExtConfig
)
570 // updating default entries
571 std::optional
<OUString
> aUIColorSchemeName
= officecfg::Office::UI::ColorScheme::CurrentColorScheme::get();
572 OUString aUIColorSchemePath
= officecfg::Office::UI::ColorScheme::ColorSchemes::path() + u
"/" + aUIColorSchemeName
.value();
574 for (unsigned i
= 0; i
!= ColorConfigEntryCount
- THEME_APPLICATION_COLORS_COUNT
; ++i
)
576 OUString sPath
= aUIColorSchemePath
+ vEntryInfo
[i
].sPropName
;
577 ColorConfigEntry
const aColorEntry
= static_cast<ColorConfigEntry
>(i
);
579 pConfig
->GetColorValue(aColorEntry
),
585 // updating extended entries
586 decltype(vEntries
)::size_type i
= ColorConfigEntryCount
- THEME_APPLICATION_COLORS_COUNT
;
587 unsigned const nExtCount
= pExtConfig
->GetComponentCount();
588 for (unsigned j
= 0; j
!= nExtCount
; ++j
)
590 OUString sComponentName
= pExtConfig
->GetComponentName(j
);
591 aUIColorSchemePath
= officecfg::Office::ExtendedColorScheme::ExtendedColorScheme::ColorSchemes::path() + u
"/" +
592 aUIColorSchemeName
.value() + u
"/" + sComponentName
+ u
"/Entries/";
593 unsigned const nColorCount
= pExtConfig
->GetComponentColorCount(sComponentName
);
594 for (unsigned k
= 0; i
!= vEntries
.size() && k
!= nColorCount
; ++i
, ++k
)
596 pExtConfig
->GetComponentColorConfigValue(sComponentName
, k
),
603 void ColorConfigWindow_Impl::UpdateEntries()
605 for (unsigned i
= 0; i
!= ColorConfigEntryCount
- THEME_APPLICATION_COLORS_COUNT
; ++i
)
607 ColorConfigEntry
const aEntry
= static_cast<ColorConfigEntry
>(i
);
608 Color aColor
= ColorConfig::GetDefaultColor(aEntry
);
609 vEntries
[i
]->m_xColorList
->SetAutoDisplayColor(aColor
);
614 void ColorConfigWindow_Impl::ClickHdl(EditableColorConfig
* pConfig
, const weld::Toggleable
& rBox
)
616 for (unsigned i
= 0; i
!= ColorConfigEntryCount
- THEME_APPLICATION_COLORS_COUNT
; ++i
)
618 if (vEntries
[i
]->Is(&rBox
))
620 ColorConfigEntry
const aEntry
= static_cast<ColorConfigEntry
>(i
);
621 ColorConfigValue aValue
= pConfig
->GetColorValue(aEntry
);
622 aValue
.bIsVisible
= rBox
.get_active();
623 pConfig
->SetColorValue(aEntry
, aValue
);
630 void ColorConfigWindow_Impl::ColorHdl(
631 EditableColorConfig
* pConfig
, EditableExtendedColorConfig
* pExtConfig
,
632 const ColorListBox
* pBox
)
637 for ( ; i
!= ColorConfigEntryCount
- THEME_APPLICATION_COLORS_COUNT
; ++i
)
639 if (pBox
&& vEntries
[i
]->Is(pBox
))
641 ColorConfigEntry
const aColorEntry
= static_cast<ColorConfigEntry
>(i
);
642 ColorConfigValue aValue
= pConfig
->GetColorValue(aColorEntry
);
643 vEntries
[i
]->ColorChanged(aValue
);
644 pConfig
->SetColorValue(aColorEntry
, aValue
);
650 unsigned const nExtCount
= pExtConfig
->GetComponentCount();
651 i
= ColorConfigEntryCount
- THEME_APPLICATION_COLORS_COUNT
;
652 for (unsigned j
= 0; j
!= nExtCount
; ++j
)
654 OUString sComponentName
= pExtConfig
->GetComponentName(j
);
655 unsigned const nColorCount
= pExtConfig
->GetComponentColorCount(sComponentName
);
656 unsigned const nCount
= vEntries
.size();
657 for (unsigned k
= 0; i
!= nCount
&& k
!= nColorCount
; ++i
, ++k
)
659 if (pBox
&& vEntries
[i
]->Is(pBox
))
661 ExtendedColorConfigValue aValue
=
662 pExtConfig
->GetComponentColorConfigValue(sComponentName
, k
);
663 vEntries
[i
]->ColorChanged(aValue
);
664 pExtConfig
->SetColorValue(sComponentName
, aValue
);
673 bool ColorConfigWindow_Impl::IsGroupVisible (Group eGroup
) const
679 return aModuleOptions
.IsWriterInstalled();
681 return aModuleOptions
.IsCalcInstalled();
684 aModuleOptions
.IsDrawInstalled() ||
685 aModuleOptions
.IsImpressInstalled();
687 return aModuleOptions
.IsDataBaseInstalled();
693 class ColorConfigCtrl_Impl
695 std::unique_ptr
<weld::ScrolledWindow
> m_xVScroll
;
696 std::unique_ptr
<weld::Container
> m_xBody
;
697 std::unique_ptr
<ColorConfigWindow_Impl
> m_xScrollWindow
;
699 EditableColorConfig
* pColorConfig
;
700 EditableExtendedColorConfig
* pExtColorConfig
;
702 DECL_LINK(ClickHdl
, weld::Toggleable
&, void);
703 DECL_LINK(ColorHdl
, ColorListBox
&, void);
704 DECL_LINK(ControlFocusHdl
, weld::Widget
&, void);
707 explicit ColorConfigCtrl_Impl(weld::Window
* pTopLevel
, weld::Builder
& rbuilder
);
709 void SetConfig (EditableColorConfig
& rConfig
) { pColorConfig
= &rConfig
; }
710 void SetExtendedConfig (EditableExtendedColorConfig
& rConfig
) { pExtColorConfig
= &rConfig
; }
712 void UpdateEntries();
713 tools::Long
GetScrollPosition() const
715 return m_xVScroll
->vadjustment_get_value();
717 void SetScrollPosition(tools::Long nSet
)
719 m_xVScroll
->vadjustment_set_value(nSet
);
721 weld::Widget
& GetWidget1()
723 return m_xScrollWindow
->GetWidget1();
725 weld::Widget
& GetWidget2()
727 return m_xScrollWindow
->GetWidget2();
729 int GetLabelIndent() const
731 return m_xScrollWindow
->GetLabelIndent();
735 ColorConfigCtrl_Impl::ColorConfigCtrl_Impl(weld::Window
* pTopLevel
, weld::Builder
& rBuilder
)
736 : m_xVScroll(rBuilder
.weld_scrolled_window(u
"scroll"_ustr
))
737 , m_xBody(rBuilder
.weld_container(u
"colorconfig"_ustr
))
738 , m_xScrollWindow(std::make_unique
<ColorConfigWindow_Impl
>(pTopLevel
, m_xBody
.get()))
739 , pColorConfig(nullptr)
740 , pExtColorConfig(nullptr)
742 m_xBody
->set_stack_background();
744 Link
<weld::Toggleable
&,void> aCheckLink
= LINK(this, ColorConfigCtrl_Impl
, ClickHdl
);
745 Link
<ColorListBox
&,void> aColorLink
= LINK(this, ColorConfigCtrl_Impl
, ColorHdl
);
746 Link
<weld::Widget
&,void> const aGetFocusLink
= LINK(this, ColorConfigCtrl_Impl
, ControlFocusHdl
);
747 m_xScrollWindow
->SetLinks(aCheckLink
, aColorLink
, aGetFocusLink
, *m_xVScroll
);
750 void ColorConfigCtrl_Impl::Update ()
752 DBG_ASSERT(pColorConfig
, "Configuration not set");
753 m_xScrollWindow
->Update(pColorConfig
, pExtColorConfig
);
756 void ColorConfigCtrl_Impl::UpdateEntries()
758 m_xScrollWindow
->UpdateEntries();
761 IMPL_LINK(ColorConfigCtrl_Impl
, ClickHdl
, weld::Toggleable
&, rBox
, void)
763 DBG_ASSERT(pColorConfig
, "Configuration not set");
764 m_xScrollWindow
->ClickHdl(pColorConfig
, rBox
);
767 // a color list has changed
768 IMPL_LINK(ColorConfigCtrl_Impl
, ColorHdl
, ColorListBox
&, rBox
, void)
770 DBG_ASSERT(pColorConfig
, "Configuration not set" );
771 m_xScrollWindow
->ColorHdl(pColorConfig
, pExtColorConfig
, &rBox
);
774 IMPL_LINK(ColorConfigCtrl_Impl
, ControlFocusHdl
, weld::Widget
&, rCtrl
, void)
776 // determine whether a control is completely visible
777 // and make it visible
778 unsigned const nWinHeight
= m_xVScroll
->vadjustment_get_page_size();
781 auto nThumbPos
= m_xVScroll
->vadjustment_get_value();
782 int const nWinTop
= nThumbPos
;
783 int const nWinBottom
= nWinTop
+ nWinHeight
;
785 int x
, nCtrlPosY
, width
, nHeight
;
786 rCtrl
.get_extents_relative_to(m_xScrollWindow
->GetBody(), x
, nCtrlPosY
, width
, nHeight
);
788 int const nSelectedItemTop
= nCtrlPosY
;
789 int const nSelectedItemBottom
= nCtrlPosY
+ nHeight
;
790 bool const shouldScrollDown
= nSelectedItemBottom
>= nWinBottom
;
791 bool const shouldScrollUp
= nSelectedItemTop
<= nWinTop
;
792 bool const isNeedToScroll
= shouldScrollDown
|| shouldScrollUp
|| nCtrlPosY
< 0;
797 if (shouldScrollDown
)
799 int nOffset
= nSelectedItemBottom
- nWinBottom
;
800 nThumbPos
+= nOffset
+ 2;
804 int nOffset
= nWinTop
- nSelectedItemTop
;
805 nThumbPos
-= nOffset
+ 2;
809 m_xVScroll
->vadjustment_set_value(nThumbPos
);
812 // SvxColorOptionsTabPage
813 SvxColorOptionsTabPage::SvxColorOptionsTabPage(weld::Container
* pPage
, weld::DialogController
* pController
, const SfxItemSet
& rCoreSet
)
814 : SfxTabPage(pPage
, pController
, u
"cui/ui/optappearancepage.ui"_ustr
, u
"OptAppearancePage"_ustr
, &rCoreSet
)
815 , bFillItemSetCalled(false)
816 , m_bShowRestartDialog(false)
817 , m_nSizeAllocEventId(nullptr)
818 , m_xAutoColorLB(m_xBuilder
->weld_combo_box(u
"autocolorlb"_ustr
))
819 , m_xAutoColorImg(m_xBuilder
->weld_widget(u
"lockautocolorlb"_ustr
))
820 , m_xColorSchemeLB(m_xBuilder
->weld_combo_box(u
"colorschemelb"_ustr
))
821 , m_xColorSchemeImg(m_xBuilder
->weld_widget(u
"lockcolorschemelb"_ustr
))
822 , m_xSaveSchemePB(m_xBuilder
->weld_button(u
"save"_ustr
))
823 , m_xDeleteSchemePB(m_xBuilder
->weld_button(u
"delete"_ustr
))
824 , m_xColorConfigCT(new ColorConfigCtrl_Impl(pController
->getDialog(), *m_xBuilder
))
825 , m_xTable(m_xBuilder
->weld_widget(u
"table"_ustr
))
826 , m_xOnFT(m_xBuilder
->weld_label(u
"on"_ustr
))
827 , m_xColorFT(m_xBuilder
->weld_label(u
"colorsetting"_ustr
))
828 , m_rWidget1(m_xColorConfigCT
->GetWidget1())
829 , m_rWidget2(m_xColorConfigCT
->GetWidget2())
831 m_xColorSchemeLB
->make_sorted();
832 m_xColorSchemeLB
->connect_changed(LINK(this, SvxColorOptionsTabPage
, SchemeChangedHdl_Impl
));
833 m_xAutoColorLB
->connect_changed(LINK(this, SvxColorOptionsTabPage
, onAutoColorChanged
));
834 Link
<weld::Button
&,void> aLk
= LINK(this, SvxColorOptionsTabPage
, SaveDeleteHdl_Impl
);
835 m_xSaveSchemePB
->connect_clicked(aLk
);
836 m_xDeleteSchemePB
->connect_clicked(aLk
);
838 m_rWidget1
.connect_size_allocate(LINK(this, SvxColorOptionsTabPage
, AdjustHeaderBar
));
839 m_rWidget2
.connect_size_allocate(LINK(this, SvxColorOptionsTabPage
, AdjustHeaderBar
));
842 SvxColorOptionsTabPage::~SvxColorOptionsTabPage()
846 //when the dialog is cancelled but the color scheme ListBox has been changed these
847 //changes need to be undone
848 if (!bFillItemSetCalled
&& m_xColorSchemeLB
->get_value_changed_from_saved())
850 OUString sOldScheme
= m_xColorSchemeLB
->get_saved_value();
851 if(!sOldScheme
.isEmpty())
853 pColorConfig
->SetCurrentSchemeName(sOldScheme
);
854 pExtColorConfig
->SetCurrentSchemeName(sOldScheme
);
857 pColorConfig
->ClearModified();
858 pColorConfig
->EnableBroadcast();
859 pColorConfig
.reset();
861 pExtColorConfig
->ClearModified();
862 pExtColorConfig
->EnableBroadcast();
863 pExtColorConfig
.reset();
865 m_xColorConfigCT
.reset();
866 if (m_nSizeAllocEventId
)
867 Application::RemoveUserEvent(m_nSizeAllocEventId
);
869 if (m_bShowRestartDialog
)
871 ::svtools::executeRestartDialog(comphelper::getProcessComponentContext(), GetFrameWeld(),
872 svtools::RESTART_REASON_THEME_CHANGE
);
876 std::unique_ptr
<SfxTabPage
> SvxColorOptionsTabPage::Create(weld::Container
* pPage
, weld::DialogController
* pController
, const SfxItemSet
* rAttrSet
)
878 return std::make_unique
<SvxColorOptionsTabPage
>(pPage
, pController
, *rAttrSet
);
881 OUString
SvxColorOptionsTabPage::GetAllStrings()
883 // buttons are excluded
884 OUString sAllStrings
;
885 OUString labels
[] = { u
"label2"_ustr
, u
"label3"_ustr
, u
"autocolor"_ustr
, u
"uielements"_ustr
, u
"colorsetting"_ustr
};
887 for (const auto& label
: labels
)
889 if (const auto pString
= m_xBuilder
->weld_label(label
))
890 sAllStrings
+= pString
->get_label() + " ";
893 return sAllStrings
.replaceAll("_", "");
896 bool SvxColorOptionsTabPage::FillItemSet( SfxItemSet
* )
898 bFillItemSetCalled
= true;
899 if (m_xColorSchemeLB
->get_value_changed_from_saved())
901 pColorConfig
->SetModified();
902 pExtColorConfig
->SetModified();
904 if (pColorConfig
->IsModified())
905 pColorConfig
->Commit();
906 if (pExtColorConfig
->IsModified())
907 pExtColorConfig
->Commit();
911 void SvxColorOptionsTabPage::Reset( const SfxItemSet
* )
915 pColorConfig
->ClearModified();
916 pColorConfig
->DisableBroadcast();
918 pColorConfig
.reset(new EditableColorConfig
);
919 m_xColorConfigCT
->SetConfig(*pColorConfig
);
923 pExtColorConfig
->ClearModified();
924 pExtColorConfig
->DisableBroadcast();
926 pExtColorConfig
.reset(new EditableExtendedColorConfig
);
927 m_xColorConfigCT
->SetExtendedConfig(*pExtColorConfig
);
929 m_xAutoColorLB
->set_active( MiscSettings::GetAppColorMode() );
931 bool bReadOnly
= officecfg::Office::ExtendedColorScheme::ExtendedColorScheme::CurrentColorScheme::isReadOnly() ||
932 officecfg::Office::UI::ColorScheme::CurrentColorScheme::isReadOnly();
933 m_xAutoColorLB
->set_sensitive(!bReadOnly
);
934 m_xSaveSchemePB
->set_sensitive(!bReadOnly
);
935 m_xDeleteSchemePB
->set_sensitive(!bReadOnly
);
936 m_xAutoColorImg
->set_visible(bReadOnly
);
938 OUString sUser
= GetUserData();
939 //has to be called always to speed up accessibility tools
940 m_xColorConfigCT
->SetScrollPosition(sUser
.toInt32());
941 m_xColorSchemeLB
->clear();
942 const uno::Sequence
< OUString
> aSchemes
= pColorConfig
->GetSchemeNames();
943 for(const OUString
& s
: aSchemes
)
944 m_xColorSchemeLB
->append_text(lcl_SchemeIdToTranslatedName(s
));
946 m_xColorSchemeLB
->set_active_text(lcl_SchemeIdToTranslatedName(pColorConfig
->GetCurrentSchemeName()));
947 m_xColorSchemeLB
->set_sensitive(!officecfg::Office::Common::Misc::ApplicationAppearance::isReadOnly());
948 m_xColorSchemeImg
->set_visible(officecfg::Office::Common::Misc::ApplicationAppearance::isReadOnly());
949 m_xColorSchemeLB
->save_value();
951 m_xDeleteSchemePB
->set_sensitive( aSchemes
.getLength() > 1 &&
952 !officecfg::Office::ExtendedColorScheme::ExtendedColorScheme::CurrentColorScheme::isReadOnly() );
956 DeactivateRC
SvxColorOptionsTabPage::DeactivatePage( SfxItemSet
* pSet_
)
959 FillItemSet( pSet_
);
960 return DeactivateRC::LeavePage
;
963 void SvxColorOptionsTabPage::UpdateColorConfig()
965 //update the color config control
966 m_xColorConfigCT
->Update();
969 IMPL_LINK_NOARG(SvxColorOptionsTabPage
, onAutoColorChanged
, weld::ComboBox
&, void)
971 MiscSettings::SetAppColorMode( m_xAutoColorLB
->get_active() );
973 m_xColorConfigCT
->UpdateEntries();
975 pColorConfig
->LoadScheme(lcl_TranslatedNameToSchemeId(m_xColorSchemeLB
->get_active_text()));
976 pExtColorConfig
->LoadScheme(lcl_TranslatedNameToSchemeId(m_xColorSchemeLB
->get_active_text()));
980 IMPL_LINK(SvxColorOptionsTabPage
, SchemeChangedHdl_Impl
, weld::ComboBox
&, rBox
, void)
982 pColorConfig
->LoadScheme(lcl_TranslatedNameToSchemeId(rBox
.get_active_text()));
983 pExtColorConfig
->LoadScheme(lcl_TranslatedNameToSchemeId(rBox
.get_active_text()));
986 // show restart dialog only when LibreOffice Theme is enabled and
987 // the theme was changed.
988 if (officecfg::Office::Common::Misc::LibreOfficeTheme::get()
989 && rBox
.get_value_changed_from_saved())
990 m_bShowRestartDialog
= true;
993 IMPL_LINK(SvxColorOptionsTabPage
, SaveDeleteHdl_Impl
, weld::Button
&, rButton
, void)
995 if (m_xSaveSchemePB
.get() == &rButton
)
999 SvxAbstractDialogFactory
* pFact
= SvxAbstractDialogFactory::Create();
1000 ScopedVclPtr
<AbstractSvxNameDialog
> aNameDlg(pFact
->CreateSvxNameDialog(GetFrameWeld(),
1001 sName
, CuiResId(RID_CUISTR_COLOR_CONFIG_SAVE2
) ));
1002 aNameDlg
->SetCheckNameHdl( LINK(this, SvxColorOptionsTabPage
, CheckNameHdl_Impl
));
1003 aNameDlg
->SetText(CuiResId(RID_CUISTR_COLOR_CONFIG_SAVE1
));
1004 aNameDlg
->SetHelpId(HID_OPTIONS_COLORCONFIG_SAVE_SCHEME
);
1005 aNameDlg
->SetCheckNameHdl( LINK(this, SvxColorOptionsTabPage
, CheckNameHdl_Impl
));
1006 if(RET_OK
== aNameDlg
->Execute())
1008 sName
= aNameDlg
->GetName();
1009 pColorConfig
->AddScheme(sName
);
1010 pExtColorConfig
->AddScheme(sName
);
1011 m_xColorSchemeLB
->append_text(sName
);
1012 m_xColorSchemeLB
->set_active_text(sName
);
1013 SchemeChangedHdl_Impl(*m_xColorSchemeLB
);
1018 DBG_ASSERT(m_xColorSchemeLB
->get_count() > 1, "don't delete the last scheme");
1019 std::unique_ptr
<weld::MessageDialog
> xQuery(Application::CreateMessageDialog(GetFrameWeld(),
1020 VclMessageType::Question
, VclButtonsType::YesNo
,
1021 CuiResId(RID_CUISTR_COLOR_CONFIG_DELETE
)));
1022 xQuery
->set_title(CuiResId(RID_CUISTR_COLOR_CONFIG_DELETE_TITLE
));
1023 if (RET_YES
== xQuery
->run())
1025 OUString
sDeleteScheme(m_xColorSchemeLB
->get_active_text());
1026 m_xColorSchemeLB
->remove(m_xColorSchemeLB
->get_active());
1027 m_xColorSchemeLB
->set_active(0);
1028 SchemeChangedHdl_Impl(*m_xColorSchemeLB
);
1029 //first select the new scheme and then delete the old one
1030 pColorConfig
->DeleteScheme(sDeleteScheme
);
1031 pExtColorConfig
->DeleteScheme(sDeleteScheme
);
1034 m_xDeleteSchemePB
->set_sensitive(m_xColorSchemeLB
->get_count() > 1);
1037 IMPL_LINK(SvxColorOptionsTabPage
, CheckNameHdl_Impl
, AbstractSvxNameDialog
&, rDialog
, bool )
1039 OUString sName
= rDialog
.GetName();
1040 return !sName
.isEmpty() && m_xColorSchemeLB
->find_text(sName
) == -1;
1043 void SvxColorOptionsTabPage::FillUserData()
1045 SetUserData(OUString::number(m_xColorConfigCT
->GetScrollPosition()));
1048 IMPL_LINK_NOARG(SvxColorOptionsTabPage
, AdjustHeaderBar
, const Size
&, void)
1050 if (m_nSizeAllocEventId
)
1052 m_nSizeAllocEventId
= Application::PostUserEvent(LINK(this, SvxColorOptionsTabPage
, PostAdjustHeaderBar
));
1055 IMPL_LINK_NOARG(SvxColorOptionsTabPage
, PostAdjustHeaderBar
, void*, void)
1057 m_nSizeAllocEventId
= nullptr;
1059 // horizontal positions
1060 int nX1
, nX2
, nX3
, y
, width
, height
;
1061 if (!m_rWidget1
.get_extents_relative_to(*m_xTable
, nX1
, y
, width
, height
))
1063 if (!m_rWidget2
.get_extents_relative_to(*m_xTable
, nX2
, y
, width
, height
))
1065 if (!m_xTable
->get_extents_relative_to(*m_xTable
, nX3
, y
, width
, height
))
1068 // 6 is the column-spacing of the parent grid of these labels
1069 auto nTextWidth1
= nX1
+ m_xColorConfigCT
->GetLabelIndent() - 6;
1070 m_xOnFT
->set_size_request(nTextWidth1
, -1);
1071 auto nTextWidth3
= width
- nX2
;
1072 m_xColorFT
->set_size_request(nTextWidth3
, -1);
1075 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */