Line data Source code
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/dialmgr.hxx>
21 : #include <svx/dialogs.hrc>
22 : #include <i18nlangtag/mslangid.hxx>
23 : #include <svtools/valueset.hxx>
24 : #include <svl/languageoptions.hxx>
25 : #include <helpid.hrc>
26 : #include <editeng/numitem.hxx>
27 : #include <svl/eitem.hxx>
28 : #include <vcl/svapp.hxx>
29 : #include <svx/gallery.hxx>
30 : #include <svl/urihelper.hxx>
31 : #include <editeng/brushitem.hxx>
32 : #include <svl/intitem.hxx>
33 : #include <sfx2/objsh.hxx>
34 : #include <vcl/graph.hxx>
35 : #include <vcl/msgbox.hxx>
36 : #include <vcl/settings.hxx>
37 : #include <vcl/builderfactory.hxx>
38 : #include <editeng/flstitem.hxx>
39 : #include <svx/dlgutil.hxx>
40 : #include <svx/xtable.hxx>
41 : #include <svx/drawitem.hxx>
42 : #include <svx/numvset.hxx>
43 : #include <sfx2/htmlmode.hxx>
44 : #include <unotools/pathoptions.hxx>
45 : #include <svtools/ctrltool.hxx>
46 : #include <editeng/unolingu.hxx>
47 : #include <com/sun/star/style/NumberingType.hpp>
48 : #include <com/sun/star/lang/XMultiServiceFactory.hpp>
49 : #include <com/sun/star/container/XIndexAccess.hpp>
50 : #include <com/sun/star/text/XDefaultNumberingProvider.hpp>
51 : #include <com/sun/star/text/XNumberingFormatter.hpp>
52 : #include <com/sun/star/beans/PropertyValue.hpp>
53 : #include <comphelper/processfactory.hxx>
54 : #include <com/sun/star/text/XNumberingTypeInfo.hpp>
55 :
56 : #include <algorithm>
57 : #include <sfx2/opengrf.hxx>
58 :
59 : using namespace com::sun::star::uno;
60 : using namespace com::sun::star::beans;
61 : using namespace com::sun::star::lang;
62 : using namespace com::sun::star::i18n;
63 : using namespace com::sun::star::text;
64 : using namespace com::sun::star::container;
65 : using namespace com::sun::star::style;
66 :
67 : #define NUM_PAGETYPE_BULLET 0
68 : #define NUM_PAGETYPE_SINGLENUM 1
69 : #define NUM_PAGETYPE_NUM 2
70 : #define NUM_PAGETYPE_BMP 3
71 :
72 : static const sal_Char cNumberingType[] = "NumberingType";
73 : static const sal_Char cValue[] = "Value";
74 : static const sal_Char cParentNumbering[] = "ParentNumbering";
75 : static const sal_Char cPrefix[] = "Prefix";
76 : static const sal_Char cSuffix[] = "Suffix";
77 : static const sal_Char cBulletChar[] = "BulletChar";
78 : static const sal_Char cBulletFontName[] = "BulletFontName";
79 :
80 : // The selection of bullets from the star symbol
81 : static const sal_Unicode aBulletTypes[] =
82 : {
83 : 0x2022,
84 : 0x25cf,
85 : 0xe00c,
86 : 0xe00a,
87 : 0x2794,
88 : 0x27a2,
89 : 0x2717,
90 : 0x2714
91 : };
92 :
93 0 : static vcl::Font& lcl_GetDefaultBulletFont()
94 : {
95 : static bool bInit = false;
96 0 : static vcl::Font aDefBulletFont( "StarSymbol", "", Size( 0, 14 ) );
97 0 : if(!bInit)
98 : {
99 0 : aDefBulletFont.SetCharSet( RTL_TEXTENCODING_SYMBOL );
100 0 : aDefBulletFont.SetFamily( FAMILY_DONTKNOW );
101 0 : aDefBulletFont.SetPitch( PITCH_DONTKNOW );
102 0 : aDefBulletFont.SetWeight( WEIGHT_DONTKNOW );
103 0 : aDefBulletFont.SetTransparent( true );
104 0 : bInit = true;
105 : }
106 0 : return aDefBulletFont;
107 : }
108 :
109 0 : static void lcl_PaintLevel(OutputDevice* pVDev, sal_Int16 nNumberingType,
110 : const OUString& rBulletChar, const OUString& rText, const OUString& rFontName,
111 : Point& rLeft, vcl::Font& rRuleFont, const vcl::Font& rTextFont)
112 : {
113 :
114 0 : if(NumberingType::CHAR_SPECIAL == nNumberingType )
115 : {
116 0 : rRuleFont.SetStyleName(rFontName);
117 0 : pVDev->SetFont(rRuleFont);
118 0 : pVDev->DrawText(rLeft, rBulletChar);
119 0 : rLeft.X() += pVDev->GetTextWidth(rBulletChar);
120 : }
121 : else
122 : {
123 0 : pVDev->SetFont(rTextFont);
124 0 : pVDev->DrawText(rLeft, rText);
125 0 : rLeft.X() += pVDev->GetTextWidth(rText);
126 : }
127 0 : }
128 0 : void SvxNumValueSet::UserDraw( const UserDrawEvent& rUDEvt )
129 : {
130 : static const sal_uInt16 aLinesArr[] =
131 : {
132 : 15, 10,
133 : 20, 30,
134 : 25, 50,
135 : 30, 70,
136 : 35, 90, // up to here line positions
137 : 05, 10, // character positions
138 : 10, 30,
139 : 15, 50,
140 : 20, 70,
141 : 25, 90,
142 : };
143 :
144 0 : const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings();
145 0 : const Color aBackColor = rStyleSettings.GetFieldColor();
146 0 : const Color aTextColor = rStyleSettings.GetFieldTextColor();
147 :
148 0 : OutputDevice* pDev = rUDEvt.GetDevice();
149 0 : Rectangle aRect = rUDEvt.GetRect();
150 0 : sal_uInt16 nItemId = rUDEvt.GetItemId();
151 0 : long nRectWidth = aRect.GetWidth();
152 0 : long nRectHeight = aRect.GetHeight();
153 0 : Size aRectSize(nRectWidth, aRect.GetHeight());
154 0 : Point aBLPos = aRect.TopLeft();
155 0 : vcl::Font aOldFont = pDev->GetFont();
156 0 : Color aOldColor = pDev->GetLineColor();
157 0 : pDev->SetLineColor(aTextColor);
158 : vcl::Font aFont(OutputDevice::GetDefaultFont(
159 0 : DefaultFontType::UI_SANS, MsLangId::getSystemLanguage(), GetDefaultFontFlags::OnlyOne));
160 :
161 0 : Size aSize = aFont.GetSize();
162 :
163 0 : vcl::Font aRuleFont( lcl_GetDefaultBulletFont() );
164 0 : aSize.Height() = nRectHeight/6;
165 0 : aRuleFont.SetSize(aSize);
166 0 : aRuleFont.SetColor(aTextColor);
167 0 : aRuleFont.SetFillColor(aBackColor);
168 0 : if(nPageType == NUM_PAGETYPE_BULLET)
169 0 : aFont = aRuleFont;
170 0 : else if(nPageType == NUM_PAGETYPE_NUM)
171 : {
172 0 : aSize.Height() = nRectHeight/8;
173 : }
174 0 : aFont.SetColor(aTextColor);
175 0 : aFont.SetFillColor(aBackColor);
176 0 : aFont.SetSize( aSize );
177 0 : pDev->SetFont(aFont);
178 :
179 0 : if(!pVDev)
180 : {
181 : // The lines are only one time in the virtual device, only the outline
182 : // page is currently done
183 0 : pVDev = VclPtr<VirtualDevice>::Create(*pDev);
184 0 : pVDev->SetMapMode(pDev->GetMapMode());
185 0 : pVDev->EnableRTL( IsRTLEnabled() );
186 0 : pVDev->SetOutputSize( aRectSize );
187 0 : aOrgRect = aRect;
188 0 : pVDev->SetFillColor( aBackColor );
189 :
190 0 : if(aBackColor == aLineColor)
191 0 : aLineColor.Invert();
192 0 : pVDev->SetLineColor(aLineColor);
193 : // Draw line only once
194 0 : if(nPageType != NUM_PAGETYPE_NUM)
195 : {
196 0 : Point aStart(aBLPos.X() + nRectWidth *25 / 100,0);
197 0 : Point aEnd(aBLPos.X() + nRectWidth * 9 / 10,0);
198 0 : for( sal_uInt16 i = 11; i < 100; i += 33)
199 : {
200 0 : aStart.Y() = aEnd.Y() = aBLPos.Y() + nRectHeight * i / 100;
201 0 : pVDev->DrawLine(aStart, aEnd);
202 0 : aStart.Y() = aEnd.Y() = aBLPos.Y() + nRectHeight * (i + 11) / 100;
203 0 : pVDev->DrawLine(aStart, aEnd);
204 : }
205 : }
206 : }
207 : pDev->DrawOutDev( aRect.TopLeft(), aRectSize,
208 0 : aOrgRect.TopLeft(), aRectSize,
209 0 : *pVDev );
210 : // Now comes the text
211 0 : const OUString sValue(cValue);
212 0 : if( NUM_PAGETYPE_SINGLENUM == nPageType ||
213 0 : NUM_PAGETYPE_BULLET == nPageType )
214 : {
215 0 : Point aStart(aBLPos.X() + nRectWidth / 9,0);
216 0 : for( sal_uInt16 i = 0; i < 3; i++ )
217 : {
218 0 : sal_uInt16 nY = 11 + i * 33;
219 0 : aStart.Y() = aBLPos.Y() + nRectHeight * nY / 100;
220 0 : OUString sText;
221 0 : if(nPageType == NUM_PAGETYPE_BULLET)
222 : {
223 0 : sText = OUString( aBulletTypes[nItemId - 1] );
224 0 : aStart.Y() -= pDev->GetTextHeight()/2;
225 0 : aStart.X() = aBLPos.X() + 5;
226 : }
227 : else
228 : {
229 0 : if(xFormatter.is() && aNumSettings.getLength() > nItemId - 1)
230 : {
231 0 : Sequence<PropertyValue> aLevel = aNumSettings.getConstArray()[nItemId - 1];
232 : try
233 : {
234 0 : aLevel.realloc(aLevel.getLength() + 1);
235 0 : PropertyValue& rValue = aLevel.getArray()[aLevel.getLength() - 1];
236 0 : rValue.Name = sValue;
237 0 : rValue.Value <<= (sal_Int32)(i + 1);
238 0 : sText = xFormatter->makeNumberingString( aLevel, aLocale );
239 : }
240 0 : catch(Exception&)
241 : {
242 : OSL_FAIL("Exception in DefaultNumberingProvider::makeNumberingString");
243 0 : }
244 : }
245 : // start just next to the left edge
246 0 : aStart.X() = aBLPos.X() + 2;
247 0 : aStart.Y() -= pDev->GetTextHeight()/2;
248 : }
249 0 : pDev->DrawText(aStart, sText);
250 0 : }
251 : }
252 0 : else if(NUM_PAGETYPE_NUM == nPageType )
253 : {
254 : // Outline numbering has to be painted into the virtual device
255 : // to get correct lines
256 : // has to be made again
257 0 : pVDev->DrawRect(aOrgRect);
258 0 : long nStartX = aOrgRect.TopLeft().X();
259 0 : long nStartY = aOrgRect.TopLeft().Y();
260 :
261 0 : if(xFormatter.is() && aOutlineSettings.getLength() > nItemId - 1)
262 : {
263 0 : Reference<XIndexAccess> xLevel = aOutlineSettings.getArray()[nItemId - 1];
264 : try
265 : {
266 0 : OUString sLevelTexts[5];
267 0 : OUString sFontNames[5];
268 0 : OUString sBulletChars[5];
269 : sal_Int16 aNumberingTypes[5];
270 0 : OUString sPrefixes[5];
271 0 : OUString sSuffixes[5];
272 : sal_Int16 aParentNumberings[5];
273 :
274 0 : sal_Int32 nLevelCount = xLevel->getCount();
275 0 : if(nLevelCount > 5)
276 0 : nLevelCount = 5;
277 0 : for( sal_Int32 i = 0; i < nLevelCount && i < 5; i++)
278 : {
279 0 : long nTop = nStartY + nRectHeight * (aLinesArr[2 * i + 11])/100 ;
280 0 : Point aLeft(nStartX + nRectWidth * (aLinesArr[2 * i + 10])/ 100, nTop );
281 :
282 0 : Any aLevelAny = xLevel->getByIndex(i);
283 0 : Sequence<PropertyValue> aLevel;
284 0 : aLevelAny >>= aLevel;
285 0 : const PropertyValue* pValues = aLevel.getConstArray();
286 0 : aNumberingTypes[i] = 0;
287 0 : aParentNumberings[i] = 0;
288 0 : for(sal_Int32 nProperty = 0; nProperty < aLevel.getLength() - 1; nProperty++)
289 : {
290 0 : if ( pValues[nProperty].Name == cNumberingType )
291 0 : pValues[nProperty].Value >>= aNumberingTypes[i];
292 0 : else if ( pValues[nProperty].Name == cBulletFontName )
293 0 : pValues[nProperty].Value >>= sFontNames[i];
294 0 : else if ( pValues[nProperty].Name == cBulletChar )
295 0 : pValues[nProperty].Value >>= sBulletChars[i];
296 0 : else if ( pValues[nProperty].Name == cPrefix )
297 0 : pValues[nProperty].Value >>= sPrefixes[i];
298 0 : else if ( pValues[nProperty].Name == cSuffix )
299 0 : pValues[nProperty].Value >>= sSuffixes[i];
300 0 : else if ( pValues[nProperty].Name == cParentNumbering )
301 0 : pValues[nProperty].Value >>= aParentNumberings[i];
302 : }
303 0 : Sequence< PropertyValue > aProperties(2);
304 0 : PropertyValue* pProperties = aProperties.getArray();
305 0 : pProperties[0].Name = "NumberingType";
306 0 : pProperties[0].Value <<= aNumberingTypes[i];
307 0 : pProperties[1].Name = "Value";
308 0 : pProperties[1].Value <<= (sal_Int32)1;
309 : try
310 : {
311 0 : sLevelTexts[i] = xFormatter->makeNumberingString( aProperties, aLocale );
312 : }
313 0 : catch(Exception&)
314 : {
315 : OSL_FAIL("Exception in DefaultNumberingProvider::makeNumberingString");
316 : }
317 :
318 0 : aLeft.Y() -= (pDev->GetTextHeight()/2);
319 0 : if(!sPrefixes[i].isEmpty() &&
320 0 : sPrefixes[i] != " ")
321 : {
322 0 : pVDev->SetFont(aFont);
323 0 : pVDev->DrawText(aLeft, sPrefixes[i]);
324 0 : aLeft.X() += pDev->GetTextWidth(sPrefixes[i]);
325 : }
326 0 : if(aParentNumberings[i])
327 : {
328 : //insert old numberings here
329 0 : sal_Int32 nStartLevel = std::min((sal_Int32)aParentNumberings[i], i);
330 0 : for(sal_Int32 nParentLevel = i - nStartLevel; nParentLevel < i; nParentLevel++)
331 : {
332 0 : OUString sTmp(sLevelTexts[nParentLevel]);
333 0 : sTmp += ".";
334 : lcl_PaintLevel(pVDev,
335 0 : aNumberingTypes[nParentLevel],
336 : sBulletChars[nParentLevel],
337 : sTmp,
338 : sFontNames[nParentLevel],
339 : aLeft,
340 : aRuleFont,
341 0 : aFont);
342 0 : }
343 : }
344 : lcl_PaintLevel(pVDev,
345 0 : aNumberingTypes[i],
346 0 : sBulletChars[i],
347 0 : sLevelTexts[i],
348 0 : sFontNames[i],
349 : aLeft,
350 : aRuleFont,
351 0 : aFont);
352 0 : if(!sSuffixes[i].isEmpty() &&
353 0 : !sSuffixes[i].startsWith(" "))
354 : {
355 0 : pVDev->SetFont(aFont);
356 0 : pVDev->DrawText(aLeft, sSuffixes[i]);
357 0 : aLeft.X() += pDev->GetTextWidth(sSuffixes[i]);
358 : }
359 :
360 0 : long nLineTop = nStartY + nRectHeight * aLinesArr[2 * i + 1]/100 ;
361 0 : Point aLineLeft(aLeft.X(), nLineTop );
362 0 : Point aLineRight(nStartX + nRectWidth * 90 /100, nLineTop );
363 0 : pVDev->DrawLine(aLineLeft, aLineRight);
364 0 : }
365 :
366 : }
367 : #ifdef DBG_UTIL
368 : catch(Exception&)
369 : {
370 : static bool bAssert = false;
371 : if(!bAssert)
372 : {
373 : OSL_FAIL("exception in ::UserDraw");
374 : bAssert = true;
375 : }
376 : }
377 : #else
378 0 : catch(Exception&)
379 : {
380 0 : }
381 : #endif
382 : }
383 : pDev->DrawOutDev( aRect.TopLeft(), aRectSize,
384 0 : aOrgRect.TopLeft(), aRectSize,
385 0 : *pVDev );
386 : }
387 :
388 0 : pDev->SetFont(aOldFont);
389 0 : pDev->SetLineColor(aOldColor);
390 0 : }
391 :
392 0 : SvxNumValueSet::SvxNumValueSet(vcl::Window* pParent, WinBits nWinBits)
393 : : ValueSet(pParent, nWinBits)
394 : , nPageType(0)
395 : , bHTMLMode(false)
396 0 : , pVDev(NULL)
397 : {
398 0 : }
399 :
400 0 : VCL_BUILDER_FACTORY_ARGS(SvxNumValueSet, WB_TABSTOP)
401 :
402 0 : void SvxNumValueSet::init(sal_uInt16 nType)
403 : {
404 0 : aLineColor = COL_LIGHTGRAY;
405 0 : nPageType = nType;
406 0 : bHTMLMode = false;
407 0 : pVDev = NULL;
408 :
409 0 : SetColCount( 4 );
410 0 : SetLineCount( 2 );
411 0 : SetStyle( GetStyle() | WB_ITEMBORDER | WB_DOUBLEBORDER );
412 0 : if(NUM_PAGETYPE_BULLET == nType)
413 : {
414 0 : for ( sal_uInt16 i = 0; i < 8; i++ )
415 : {
416 0 : InsertItem( i + 1, i );
417 0 : SetItemText( i + 1, SVX_RESSTR( RID_SVXSTR_BULLET_DESCRIPTIONS + i ) );
418 : }
419 : }
420 0 : }
421 :
422 0 : SvxNumValueSet::~SvxNumValueSet()
423 : {
424 0 : disposeOnce();
425 0 : }
426 :
427 0 : void SvxNumValueSet::dispose()
428 : {
429 0 : pVDev.disposeAndClear();
430 0 : ValueSet::dispose();
431 0 : }
432 :
433 0 : void SvxNumValueSet::SetNumberingSettings(
434 : const Sequence<Sequence<PropertyValue> >& aNum,
435 : Reference<XNumberingFormatter>& xFormat,
436 : const Locale& rLocale )
437 : {
438 0 : aNumSettings = aNum;
439 0 : xFormatter = xFormat;
440 0 : aLocale = rLocale;
441 0 : if(aNum.getLength() > 8)
442 0 : SetStyle( GetStyle()|WB_VSCROLL);
443 0 : for ( sal_Int32 i = 0; i < aNum.getLength(); i++ )
444 : {
445 0 : InsertItem( i + 1, i );
446 0 : if( i < 8 )
447 0 : SetItemText( i + 1, SVX_RESSTR( RID_SVXSTR_SINGLENUM_DESCRIPTIONS + i ));
448 : }
449 0 : }
450 :
451 0 : void SvxNumValueSet::SetOutlineNumberingSettings(
452 : Sequence<Reference<XIndexAccess> >& rOutline,
453 : Reference<XNumberingFormatter>& xFormat,
454 : const Locale& rLocale)
455 : {
456 0 : aOutlineSettings = rOutline;
457 0 : xFormatter = xFormat;
458 0 : aLocale = rLocale;
459 0 : if(aOutlineSettings.getLength() > 8)
460 0 : SetStyle( GetStyle() | WB_VSCROLL );
461 0 : for ( sal_Int32 i = 0; i < aOutlineSettings.getLength(); i++ )
462 : {
463 0 : InsertItem( i + 1, i );
464 0 : if( i < 8 )
465 0 : SetItemText( i + 1, SVX_RESSTR( RID_SVXSTR_OUTLINENUM_DESCRIPTIONS + i ));
466 : }
467 0 : }
468 :
469 0 : SvxBmpNumValueSet::SvxBmpNumValueSet(vcl::Window* pParent, WinBits nWinBits)
470 0 : : SvxNumValueSet(pParent, nWinBits)
471 : {
472 0 : init();
473 0 : }
474 :
475 0 : VCL_BUILDER_FACTORY_ARGS(SvxBmpNumValueSet, WB_TABSTOP)
476 :
477 0 : void SvxBmpNumValueSet::init()
478 : {
479 0 : SvxNumValueSet::init(NUM_PAGETYPE_BMP);
480 0 : bGrfNotFound = false;
481 0 : GalleryExplorer::BeginLocking(GALLERY_THEME_BULLETS);
482 0 : SetStyle( GetStyle() | WB_VSCROLL );
483 0 : SetLineCount( 3 );
484 0 : aFormatIdle.SetPriority(SchedulerPriority::LOWEST);
485 0 : aFormatIdle.SetIdleHdl(LINK(this, SvxBmpNumValueSet, FormatHdl_Impl));
486 0 : }
487 :
488 :
489 0 : SvxBmpNumValueSet::~SvxBmpNumValueSet()
490 : {
491 0 : disposeOnce();
492 0 : }
493 :
494 0 : void SvxBmpNumValueSet::dispose()
495 : {
496 0 : GalleryExplorer::EndLocking(GALLERY_THEME_BULLETS);
497 0 : aFormatIdle.Stop();
498 0 : SvxNumValueSet::dispose();
499 0 : }
500 :
501 0 : void SvxBmpNumValueSet::UserDraw(const UserDrawEvent& rUDEvt)
502 : {
503 0 : SvxNumValueSet::UserDraw(rUDEvt);
504 :
505 0 : Rectangle aRect = rUDEvt.GetRect();
506 0 : OutputDevice* pDev = rUDEvt.GetDevice();
507 0 : sal_uInt16 nItemId = rUDEvt.GetItemId();
508 0 : Point aBLPos = aRect.TopLeft();
509 :
510 0 : long nRectHeight = aRect.GetHeight();
511 0 : Size aSize(nRectHeight/8, nRectHeight/8);
512 :
513 0 : Graphic aGraphic;
514 0 : if(!GalleryExplorer::GetGraphicObj( GALLERY_THEME_BULLETS, nItemId - 1,
515 0 : &aGraphic, NULL))
516 : {
517 0 : bGrfNotFound = true;
518 : }
519 : else
520 : {
521 0 : Point aPos(aBLPos.X() + 5, 0);
522 0 : for( sal_uInt16 i = 0; i < 3; i++ )
523 : {
524 0 : sal_uInt16 nY = 11 + i * 33;
525 0 : aPos.Y() = aBLPos.Y() + nRectHeight * nY / 100;
526 0 : aGraphic.Draw( pDev, aPos, aSize );
527 : }
528 0 : }
529 0 : }
530 :
531 0 : IMPL_LINK_NOARG_TYPED(SvxBmpNumValueSet, FormatHdl_Impl, Idle *, void)
532 : {
533 : // only when a graphics was not there, it needs to be formatted
534 0 : if (bGrfNotFound)
535 : {
536 0 : SetFormat();
537 0 : bGrfNotFound = false;
538 : }
539 0 : Invalidate();
540 390 : }
541 :
542 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|