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 #include "vbalistformat.hxx"
20 #include <vbahelper/vbahelper.hxx>
21 #include <ooo/vba/word/WdListApplyTo.hpp>
22 #include <ooo/vba/word/WdDefaultListBehavior.hpp>
23 #include <com/sun/star/awt/FontDescriptor.hpp>
24 #include <com/sun/star/container/XEnumerationAccess.hpp>
25 #include <com/sun/star/container/XEnumeration.hpp>
26 #include <com/sun/star/document/XUndoManagerSupplier.hpp>
27 #include <com/sun/star/beans/XMultiPropertySet.hpp>
28 #include <com/sun/star/beans/XPropertySet.hpp>
29 #include <com/sun/star/style/TabStop.hpp>
30 #include <com/sun/star/text/PositionAndSpaceMode.hpp>
31 #include <com/sun/star/text/XTextTable.hpp>
32 #include <com/sun/star/util/Color.hpp>
33 #include <comphelper/sequence.hxx>
34 #include <comphelper/sequenceashashmap.hxx>
35 #include <comphelper/scopeguard.hxx>
36 #include <editeng/numitem.hxx>
37 #include "vbalisttemplate.hxx"
38 
39 #include <vector>
40 
41 using namespace ::ooo::vba;
42 using namespace ::com::sun::star;
43 
SwVbaListFormat(const uno::Reference<ooo::vba::XHelperInterface> & rParent,const uno::Reference<uno::XComponentContext> & rContext,const uno::Reference<text::XTextRange> & xTextRange)44 SwVbaListFormat::SwVbaListFormat( const uno::Reference< ooo::vba::XHelperInterface >& rParent, const uno::Reference< uno::XComponentContext >& rContext, const uno::Reference< text::XTextRange >& xTextRange ) : SwVbaListFormat_BASE( rParent, rContext ), mxTextRange( xTextRange )
45 {
46 }
47 
~SwVbaListFormat()48 SwVbaListFormat::~SwVbaListFormat()
49 {
50 }
51 
ApplyListTemplate(const css::uno::Reference<word::XListTemplate> & ListTemplate,const css::uno::Any & ContinuePreviousList,const css::uno::Any & ApplyTo,const css::uno::Any & DefaultListBehavior)52 void SAL_CALL SwVbaListFormat::ApplyListTemplate( const css::uno::Reference< word::XListTemplate >& ListTemplate, const css::uno::Any& ContinuePreviousList, const css::uno::Any& ApplyTo, const css::uno::Any& DefaultListBehavior )
53 {
54     bool bContinuePreviousList = true;
55     if( ContinuePreviousList.hasValue() )
56         ContinuePreviousList >>= bContinuePreviousList;
57 
58     // "applyto" must be current selection
59     sal_Int32 bApplyTo = word::WdListApplyTo::wdListApplyToSelection;
60     if( ApplyTo.hasValue() )
61         ApplyTo >>= bApplyTo;
62     if( bApplyTo != word::WdListApplyTo::wdListApplyToSelection )
63         throw uno::RuntimeException();
64 
65     // default behaviour must be wdWord8ListBehavior
66     sal_Int32 nDefaultListBehavior = word::WdDefaultListBehavior::wdWord8ListBehavior;
67     if( DefaultListBehavior.hasValue() )
68         DefaultListBehavior >>= nDefaultListBehavior;
69     if( nDefaultListBehavior != word::WdDefaultListBehavior::wdWord8ListBehavior )
70         throw uno::RuntimeException();
71 
72     uno::Reference< container::XEnumerationAccess > xEnumAccess( mxTextRange, uno::UNO_QUERY_THROW );
73     uno::Reference< container::XEnumeration > xEnum = xEnumAccess->createEnumeration();
74     if (!xEnum->hasMoreElements())
75         return;
76 
77     SwVbaListTemplate& rListTemplate = dynamic_cast<SwVbaListTemplate&>(*ListTemplate);
78 
79     bool isFirstElement = true;
80     do
81     {
82         uno::Reference< beans::XPropertySet > xProps( xEnum->nextElement(), uno::UNO_QUERY_THROW );
83         if( isFirstElement )
84         {
85             bool isNumberingRestart = !bContinuePreviousList;
86             xProps->setPropertyValue("ParaIsNumberingRestart", uno::makeAny( isNumberingRestart ) );
87             if( isNumberingRestart )
88             {
89                 xProps->setPropertyValue("NumberingStartValue", uno::makeAny( sal_Int16(1) ) );
90             }
91             isFirstElement = false;
92         }
93         else
94         {
95             xProps->setPropertyValue("ParaIsNumberingRestart", uno::makeAny( false ) );
96         }
97         rListTemplate.applyListTemplate( xProps );
98     }
99     while( xEnum->hasMoreElements() );
100 }
101 
102 template <class Ref>
addParagraphsToList(const Ref & a,std::vector<css::uno::Reference<css::beans::XPropertySet>> & rList)103 static void addParagraphsToList(const Ref& a,
104                                 std::vector<css::uno::Reference<css::beans::XPropertySet>>& rList)
105 {
106     if (css::uno::Reference<css::lang::XServiceInfo> xInfo{ a, css::uno::UNO_QUERY })
107     {
108         if (xInfo->supportsService("com.sun.star.text.Paragraph"))
109         {
110             rList.emplace_back(xInfo, css::uno::UNO_QUERY_THROW);
111         }
112         else if (xInfo->supportsService("com.sun.star.text.TextTable"))
113         {
114             css::uno::Reference<css::text::XTextTable> xTable(xInfo, css::uno::UNO_QUERY_THROW);
115             const auto aNames = xTable->getCellNames();
116             for (const auto& rName : aNames)
117             {
118                 addParagraphsToList(xTable->getCellByName(rName), rList);
119             }
120         }
121     }
122     if (css::uno::Reference<css::container::XEnumerationAccess> xEnumAccess{ a,
123                                                                              css::uno::UNO_QUERY })
124     {
125         auto xEnum = xEnumAccess->createEnumeration();
126         while (xEnum->hasMoreElements())
127             addParagraphsToList(xEnum->nextElement(), rList);
128     }
129 }
130 
ConvertNumbersToText()131 void SAL_CALL SwVbaListFormat::ConvertNumbersToText(  )
132 {
133     css::uno::Reference<css::frame::XModel> xModel(getThisWordDoc(mxContext));
134     css::uno::Reference<css::document::XUndoManagerSupplier> xUndoSupplier(
135         xModel, css::uno::UNO_QUERY_THROW);
136     css::uno::Reference<css::document::XUndoManager> xUndoManager(xUndoSupplier->getUndoManager());
137     xUndoManager->enterUndoContext("ConvertNumbersToText");
138     xModel->lockControllers();
139     comphelper::ScopeGuard g([xModel, xUndoManager]() {
140         xModel->unlockControllers();
141         xUndoManager->leaveUndoContext();
142     });
143 
144     std::vector<css::uno::Reference<css::beans::XPropertySet>> aParagraphs;
145     addParagraphsToList(mxTextRange, aParagraphs);
146 
147     // in reverse order, to get proper label strings
148     for (auto it = aParagraphs.rbegin(); it != aParagraphs.rend(); ++it)
149     {
150         if (bool bNumber; ((*it)->getPropertyValue("NumberingIsNumber") >>= bNumber) && bNumber)
151         {
152             css::uno::Reference<css::text::XTextRange> xRange(*it, css::uno::UNO_QUERY_THROW);
153             OUString sLabelString;
154             (*it)->getPropertyValue("ListLabelString") >>= sLabelString;
155             // sal_Int16 nAdjust = SAL_MAX_INT16; // TODO?
156             sal_Int16 nNumberingType = SAL_MAX_INT16; // css::style::NumberingType
157             sal_Int16 nPositionAndSpaceMode = SAL_MAX_INT16;
158             sal_Int16 nLabelFollowedBy = SAL_MAX_INT16;
159             sal_Int32 nListtabStopPosition = SAL_MAX_INT32;
160             sal_Int32 nFirstLineIndent = SAL_MAX_INT32;
161             sal_Int32 nIndentAt = SAL_MAX_INT32;
162             sal_Int32 nLeftMargin = SAL_MAX_INT32;
163             sal_Int32 nSymbolTextDistance = SAL_MAX_INT32;
164             sal_Int32 nFirstLineOffset = SAL_MAX_INT32;
165             OUString sCharStyleName, sBulletChar;
166             css::awt::FontDescriptor aBulletFont;
167             bool bHasFont;
168             css::util::Color aBulletColor = css::util::Color(COL_AUTO);
169             bool bHasColor;
170 
171             {
172                 sal_uInt16 nLevel = SAL_MAX_UINT16;
173                 (*it)->getPropertyValue("NumberingLevel") >>= nLevel;
174                 css::uno::Reference<css::container::XIndexAccess> xNumberingRules;
175                 (*it)->getPropertyValue("NumberingRules") >>= xNumberingRules;
176                 comphelper::SequenceAsHashMap aLevelRule(xNumberingRules->getByIndex(nLevel));
177 
178                 // See offapi/com/sun/star/text/NumberingLevel.idl
179                 aLevelRule["CharStyleName"] >>= sCharStyleName;
180                 aLevelRule["NumberingType"] >>= nNumberingType;
181                 // TODO: aLevelRule["Adjust"] >>= nAdjust; // HoriOrientation::LEFT/RIGHT/CENTER
182                 aLevelRule["PositionAndSpaceMode"] >>= nPositionAndSpaceMode;
183 
184                 // for css::text::PositionAndSpaceMode::LABEL_ALIGNMENT
185                 aLevelRule["LabelFollowedBy"] >>= nLabelFollowedBy;
186                 aLevelRule["ListtabStopPosition"] >>= nListtabStopPosition;
187                 aLevelRule["FirstLineIndent"] >>= nFirstLineIndent;
188                 aLevelRule["IndentAt"] >>= nIndentAt;
189 
190                 // for css::text::PositionAndSpaceMode::LABEL_WIDTH_AND_POSITION
191                 aLevelRule["LeftMargin"] >>= nLeftMargin;
192                 aLevelRule["SymbolTextDistance"] >>= nSymbolTextDistance;
193                 aLevelRule["FirstLineOffset"] >>= nFirstLineOffset;
194 
195                 aLevelRule["BulletChar"] >>= sBulletChar;
196                 bHasFont = (aLevelRule["BulletFont"] >>= aBulletFont);
197                 bHasColor = (aLevelRule["BulletColor"] >>= aBulletColor);
198             }
199 
200             if (nNumberingType != css::style::NumberingType::BITMAP) // TODO
201             {
202                 if (nPositionAndSpaceMode
203                     == css::text::PositionAndSpaceMode::LABEL_WIDTH_AND_POSITION)
204                 {
205                     nIndentAt = nLeftMargin;
206                     nFirstLineIndent = nFirstLineOffset;
207                     nListtabStopPosition = nSymbolTextDistance;
208                     nLabelFollowedBy = SvxNumberFormat::LabelFollowedBy::LISTTAB;
209                 }
210 
211                 switch (nLabelFollowedBy)
212                 {
213                     case SvxNumberFormat::LabelFollowedBy::LISTTAB:
214                         sLabelString += "\t";
215                         break;
216                     case SvxNumberFormat::LabelFollowedBy::SPACE:
217                         sLabelString += " ";
218                         break;
219                     case SvxNumberFormat::LabelFollowedBy::NEWLINE:
220                         sLabelString += "\n";
221                         break;
222                 }
223 
224                 css::uno::Reference<css::text::XTextRange> xNumberText(xRange->getStart());
225                 xNumberText->setString(sLabelString);
226                 css::uno::Reference<css::beans::XPropertySet> xNumberProps(
227                     xNumberText, css::uno::UNO_QUERY_THROW);
228                 if (!sCharStyleName.isEmpty())
229                     xNumberProps->setPropertyValue("CharStyleName", css::uno::Any(sCharStyleName));
230 
231                 if (nNumberingType == css::style::NumberingType::CHAR_SPECIAL)
232                 {
233                     css::uno::Reference<css::text::XTextRange> xBulletText(xNumberText->getStart());
234                     xBulletText->setString(sBulletChar);
235 
236                     std::unordered_map<OUString, css::uno::Any> aNameValues;
237                     if (bHasFont)
238                     {
239                         aNameValues.insert({
240                             { "CharFontName", css::uno::Any(aBulletFont.Name) },
241                             { "CharFontStyleName", css::uno::Any(aBulletFont.StyleName) },
242                             { "CharFontFamily", css::uno::Any(aBulletFont.Family) },
243                             { "CharFontCharSet", css::uno::Any(aBulletFont.CharSet) },
244                             { "CharWeight", css::uno::Any(aBulletFont.Weight) },
245                             { "CharUnderline", css::uno::Any(aBulletFont.Underline) },
246                             { "CharStrikeout", css::uno::Any(aBulletFont.Strikeout) },
247                             { "CharAutoKerning", css::uno::Any(aBulletFont.Kerning) },
248                             { "CharFontPitch", css::uno::Any(aBulletFont.Pitch) },
249                             { "CharWordMode", css::uno::Any(aBulletFont.WordLineMode) },
250                             { "CharRotation", css::uno::Any(static_cast<sal_Int16>(
251                                                   std::round(aBulletFont.Orientation * 10))) },
252                             });
253                         if (aBulletFont.Height)
254                             aNameValues["CharHeight"] <<= aBulletFont.Height;
255                     }
256                     if (bHasColor)
257                     {
258                         aNameValues["CharColor"] <<= aBulletColor;
259                     }
260 
261                     if (css::uno::Reference<css::beans::XMultiPropertySet> xBulletMultiProps{
262                             xBulletText, css::uno::UNO_QUERY })
263                     {
264                         xBulletMultiProps->setPropertyValues(
265                             comphelper::mapKeysToSequence(aNameValues),
266                             comphelper::mapValuesToSequence(aNameValues));
267                     }
268                     else
269                     {
270                         css::uno::Reference<css::beans::XPropertySet> xBulletProps(
271                             xBulletText, css::uno::UNO_QUERY_THROW);
272                         for (const auto& [rName, rVal] : aNameValues)
273                             xBulletProps->setPropertyValue(rName, rVal);
274                     }
275                 }
276                 else
277                 {
278                     // TODO: css::style::NumberingType::BITMAP
279                 }
280 
281                 (*it)->setPropertyValue("ParaLeftMargin", css::uno::Any(nIndentAt));
282                 (*it)->setPropertyValue("ParaFirstLineIndent", css::uno::Any(nFirstLineIndent));
283                 if (nLabelFollowedBy == SvxNumberFormat::LabelFollowedBy::LISTTAB)
284                 {
285                     css::uno::Sequence<css::style::TabStop> stops;
286                     (*it)->getPropertyValue("ParaTabStops") >>= stops;
287                     css::style::TabStop tabStop{};
288                     tabStop.Position = nListtabStopPosition;
289                     tabStop.Alignment = com::sun::star::style::TabAlign::TabAlign_LEFT;
290                     tabStop.FillChar = ' ';
291                     (*it)->setPropertyValue(
292                         "ParaTabStops",
293                         css::uno::Any(comphelper::combineSequences({ tabStop }, stops)));
294                     // FIXME: What if added tap stop is greater than already defined ones?
295                 }
296             }
297             else
298             {
299                 continue; // for now, keep such lists as is
300             }
301             // In case of higher outline levels, each assignment of empty value just sets level 1
302             while ((*it)->getPropertyValue("NumberingRules") != css::uno::Any())
303                 (*it)->setPropertyValue("NumberingRules", css::uno::Any());
304         }
305     }
306 }
307 
308 OUString
getServiceImplName()309 SwVbaListFormat::getServiceImplName()
310 {
311     return "SwVbaListFormat";
312 }
313 
314 uno::Sequence< OUString >
getServiceNames()315 SwVbaListFormat::getServiceNames()
316 {
317     static uno::Sequence< OUString > const aServiceNames
318     {
319         "ooo.vba.word.ListFormat"
320     };
321     return aServiceNames;
322 }
323 
324 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
325