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 <hintids.hxx>
21 : #include <mdiexp.hxx>
22 : #include <tools/helpers.hxx>
23 : #include <tools/urlobj.hxx>
24 : #include <tools/fract.hxx>
25 : #include <svl/undo.hxx>
26 : #include <svl/fstathelper.hxx>
27 : #include <svtools/imap.hxx>
28 : #include <vcl/graphicfilter.hxx>
29 : #include <sot/storage.hxx>
30 : #include <sfx2/docfile.hxx>
31 : #include <sfx2/linkmgr.hxx>
32 : #include <editeng/boxitem.hxx>
33 : #include <sot/formats.hxx>
34 : #include <fmtfsize.hxx>
35 : #include <fmturl.hxx>
36 : #include <frmfmt.hxx>
37 : #include <doc.hxx>
38 : #include <IDocumentLinksAdministration.hxx>
39 : #include <IDocumentLayoutAccess.hxx>
40 : #include <frmatr.hxx>
41 : #include <grfatr.hxx>
42 : #include <swtypes.hxx>
43 : #include <ndgrf.hxx>
44 : #include <fmtcol.hxx>
45 : #include <hints.hxx>
46 : #include <swbaslnk.hxx>
47 : #include <pagefrm.hxx>
48 : #include <editsh.hxx>
49 : #include <pam.hxx>
50 :
51 : #include <rtl/ustring.hxx>
52 : #include <unotools/ucbstreamhelper.hxx>
53 : #include <com/sun/star/embed/ElementModes.hpp>
54 : #include <com/sun/star/embed/XTransactedObject.hpp>
55 : #include <vcl/svapp.hxx>
56 : #include <com/sun/star/io/XSeekable.hpp>
57 : #include <retrieveinputstreamconsumer.hxx>
58 : #include <drawinglayer/processor2d/objectinfoextractor2d.hxx>
59 : #include <drawinglayer/primitive2d/objectinfoprimitive2d.hxx>
60 :
61 : using namespace com::sun::star;
62 :
63 495 : SwGrfNode::SwGrfNode(
64 : const SwNodeIndex & rWhere,
65 : const OUString& rGrfName, const OUString& rFltName,
66 : const Graphic* pGraphic,
67 : SwGrfFormatColl *pGrfColl,
68 : SwAttrSet* pAutoAttr ) :
69 : SwNoTextNode( rWhere, ND_GRFNODE, pGrfColl, pAutoAttr ),
70 : maGrfObj(),
71 : mpReplacementGraphic(0),
72 : // #i73788#
73 : mbLinkedInputStreamReady( false ),
74 495 : mbIsStreamReadOnly( false )
75 : {
76 495 : maGrfObj.SetSwapStreamHdl( LINK(this, SwGrfNode, SwapGraphic) );
77 : bInSwapIn = bChgTwipSize =
78 495 : bFrameInPaint = bScaleImageMap = false;
79 :
80 495 : bGraphicArrived = true;
81 495 : ReRead(rGrfName, rFltName, pGraphic, 0, false);
82 495 : }
83 :
84 188 : SwGrfNode::SwGrfNode( const SwNodeIndex & rWhere,
85 : const GraphicObject& rGrfObj,
86 : SwGrfFormatColl *pGrfColl, SwAttrSet* pAutoAttr ) :
87 : SwNoTextNode( rWhere, ND_GRFNODE, pGrfColl, pAutoAttr ),
88 : maGrfObj(rGrfObj),
89 : mpReplacementGraphic(0),
90 : // #i73788#
91 : mbLinkedInputStreamReady( false ),
92 188 : mbIsStreamReadOnly( false )
93 : {
94 188 : maGrfObj.SetSwapStreamHdl( LINK(this, SwGrfNode, SwapGraphic) );
95 : bInSwapIn = bChgTwipSize =
96 188 : bFrameInPaint = bScaleImageMap = false;
97 188 : bGraphicArrived = true;
98 188 : }
99 :
100 : /** Create new SW/G reader.
101 : *
102 : * Use this ctor if you want to read a linked graphic.
103 : *
104 : * @note Does not read/open the image itself!
105 : */
106 0 : SwGrfNode::SwGrfNode( const SwNodeIndex & rWhere,
107 : const OUString& rGrfName, const OUString& rFltName,
108 : SwGrfFormatColl *pGrfColl,
109 : SwAttrSet* pAutoAttr ) :
110 : SwNoTextNode( rWhere, ND_GRFNODE, pGrfColl, pAutoAttr ),
111 : maGrfObj(),
112 : mpReplacementGraphic(0),
113 : // #i73788#
114 : mbLinkedInputStreamReady( false ),
115 0 : mbIsStreamReadOnly( false )
116 : {
117 0 : maGrfObj.SetSwapStreamHdl( LINK(this, SwGrfNode, SwapGraphic) );
118 :
119 0 : Graphic aGrf; aGrf.SetDefaultType();
120 0 : maGrfObj.SetGraphic( aGrf, rGrfName );
121 :
122 : bInSwapIn = bChgTwipSize =
123 0 : bFrameInPaint = bScaleImageMap = false;
124 0 : bGraphicArrived = true;
125 :
126 0 : InsertLink( rGrfName, rFltName );
127 0 : if( IsLinkedFile() )
128 : {
129 0 : INetURLObject aUrl( rGrfName );
130 0 : if( INetProtocol::File == aUrl.GetProtocol() &&
131 0 : FStatHelper::IsDocument( aUrl.GetMainURL( INetURLObject::NO_DECODE ) ))
132 : {
133 : // file exists, so create connection without an update
134 0 : static_cast<SwBaseLink*>(&refLink)->Connect();
135 0 : }
136 0 : }
137 0 : }
138 :
139 495 : bool SwGrfNode::ReRead(
140 : const OUString& rGrfName, const OUString& rFltName,
141 : const Graphic* pGraphic, const GraphicObject* pGrfObj,
142 : bool bNewGrf )
143 : {
144 495 : bool bReadGrf = false;
145 495 : bool bSetTwipSize = true;
146 495 : delete mpReplacementGraphic;
147 495 : mpReplacementGraphic = 0;
148 :
149 : OSL_ENSURE( pGraphic || pGrfObj || !rGrfName.isEmpty(),
150 : "GraphicNode without a name, Graphic or GraphicObject" );
151 :
152 : // with name
153 495 : if( refLink.Is() )
154 : {
155 : OSL_ENSURE( !bInSwapIn, "ReRead: I am still in SwapIn" );
156 :
157 0 : if( !rGrfName.isEmpty() )
158 : {
159 : // Note: If there is DDE in the FltName, than it is a DDE-linked graphic
160 0 : OUString sCmd( rGrfName );
161 0 : if( !rFltName.isEmpty() )
162 : {
163 : sal_uInt16 nNewType;
164 0 : if( rFltName == "DDE" )
165 0 : nNewType = OBJECT_CLIENT_DDE;
166 : else
167 : {
168 0 : sfx2::MakeLnkName( sCmd, 0, rGrfName, OUString(), &rFltName );
169 0 : nNewType = OBJECT_CLIENT_GRF;
170 : }
171 :
172 0 : if( nNewType != refLink->GetObjType() )
173 : {
174 0 : refLink->Disconnect();
175 0 : static_cast<SwBaseLink*>(&refLink)->SetObjType( nNewType );
176 : }
177 : }
178 :
179 0 : refLink->SetLinkSourceName( sCmd );
180 : }
181 : else // no name anymore, so remove link
182 : {
183 0 : GetDoc()->getIDocumentLinksAdministration().GetLinkManager().Remove( refLink );
184 0 : refLink.Clear();
185 : }
186 :
187 0 : if( pGraphic )
188 : {
189 0 : maGrfObj.SetGraphic( *pGraphic, rGrfName );
190 0 : onGraphicChanged();
191 0 : bReadGrf = true;
192 : }
193 0 : else if( pGrfObj )
194 : {
195 0 : maGrfObj = *pGrfObj;
196 0 : maGrfObj.SetLink( rGrfName );
197 0 : onGraphicChanged();
198 0 : bReadGrf = true;
199 : }
200 : else
201 : {
202 : // reset data of the old graphic so that the correct placeholder is
203 : // shown in case the new link could not be loaded
204 0 : Graphic aGrf; aGrf.SetDefaultType();
205 0 : maGrfObj.SetGraphic( aGrf, rGrfName );
206 :
207 0 : if( refLink.Is() )
208 : {
209 0 : if( getLayoutFrm( GetDoc()->getIDocumentLayoutAccess().GetCurrentLayout() ) )
210 : {
211 0 : SwMsgPoolItem aMsgHint( RES_GRF_REREAD_AND_INCACHE );
212 0 : ModifyNotification( &aMsgHint, &aMsgHint );
213 : }
214 0 : else if ( bNewGrf )
215 : {
216 : //TODO refLink->setInputStream(getInputStream());
217 0 : static_cast<SwBaseLink*>(&refLink)->SwapIn();
218 : }
219 : }
220 0 : onGraphicChanged();
221 0 : bSetTwipSize = false;
222 : }
223 : }
224 495 : else if( pGraphic && rGrfName.isEmpty() )
225 : {
226 481 : maGrfObj.SetGraphic( *pGraphic );
227 481 : onGraphicChanged();
228 481 : bReadGrf = true;
229 : }
230 14 : else if( pGrfObj && rGrfName.isEmpty() )
231 : {
232 0 : maGrfObj = *pGrfObj;
233 0 : onGraphicChanged();
234 0 : bReadGrf = true;
235 : }
236 : // Was the graphic already loaded?
237 14 : else if( !bNewGrf && GRAPHIC_NONE != maGrfObj.GetType() )
238 0 : return true;
239 : else
240 : {
241 : // create new link for the graphic object
242 14 : InsertLink( rGrfName, rFltName );
243 :
244 14 : if( GetNodes().IsDocNodes() )
245 : {
246 14 : if( pGraphic )
247 : {
248 12 : maGrfObj.SetGraphic( *pGraphic, rGrfName );
249 12 : onGraphicChanged();
250 12 : bReadGrf = true;
251 : // create connection without update, as we have the graphic
252 12 : static_cast<SwBaseLink*>(&refLink)->Connect();
253 : }
254 2 : else if( pGrfObj )
255 : {
256 0 : maGrfObj = *pGrfObj;
257 0 : maGrfObj.SetLink( rGrfName );
258 0 : onGraphicChanged();
259 0 : bReadGrf = true;
260 : // create connection without update, as we have the graphic
261 0 : static_cast<SwBaseLink*>(&refLink)->Connect();
262 : }
263 : else
264 : {
265 2 : Graphic aGrf;
266 2 : aGrf.SetDefaultType();
267 2 : maGrfObj.SetGraphic( aGrf, rGrfName );
268 2 : onGraphicChanged();
269 2 : if ( bNewGrf )
270 : {
271 0 : static_cast<SwBaseLink*>(&refLink)->SwapIn();
272 2 : }
273 : }
274 : }
275 : }
276 :
277 : // Bug 39281: Do not delete Size immediately - Events on ImageMaps should have
278 : // something to work with when swapping
279 495 : if( bSetTwipSize )
280 495 : SetTwipSize( ::GetGraphicSizeTwip( maGrfObj.GetGraphic(), 0 ) );
281 :
282 : // create an updates for the frames
283 495 : if( bReadGrf && bNewGrf )
284 : {
285 0 : SwMsgPoolItem aMsgHint( RES_UPDATE_ATTR );
286 0 : ModifyNotification( &aMsgHint, &aMsgHint );
287 : }
288 :
289 495 : return bReadGrf;
290 : }
291 :
292 2049 : SwGrfNode::~SwGrfNode()
293 : {
294 683 : delete mpReplacementGraphic;
295 683 : mpReplacementGraphic = 0;
296 :
297 : // #i73788#
298 683 : mpThreadConsumer.reset();
299 :
300 683 : SwDoc* pDoc = GetDoc();
301 683 : if( refLink.Is() )
302 : {
303 : OSL_ENSURE( !bInSwapIn, "DTOR: I am still in SwapIn" );
304 14 : pDoc->getIDocumentLinksAdministration().GetLinkManager().Remove( refLink );
305 14 : refLink->Disconnect();
306 : }
307 : else
308 : {
309 : // #i40014# - A graphic node, which is in a linked
310 : // section, whose link is another section in the document, doesn't
311 : // have to remove the stream from the storage.
312 : // Because it's hard to detect this case here and it would only fix
313 : // one problem with shared graphic files - there are also problems,
314 : // a certain graphic file is referenced by two independent graphic nodes,
315 : // brush item or drawing objects, the stream isn't no longer removed here.
316 : // To do this stuff correctly, a reference counting on shared streams
317 : // inside one document has to be implemented.
318 : }
319 : //#39289# delete frames already here since the Frms' dtor needs the graphic for its StopAnimation
320 683 : if( HasWriterListeners() )
321 0 : DelFrms();
322 1366 : }
323 :
324 : /// allow reaction on change of content of GraphicObject
325 577 : void SwGrfNode::onGraphicChanged()
326 : {
327 : // try to access SwFlyFrameFormat; since title/desc/name are set there, there is no
328 : // use to continue if it is not yet set. If not yet set, call onGraphicChanged()
329 : // when it is set.
330 577 : SwFlyFrameFormat* pFlyFormat = dynamic_cast< SwFlyFrameFormat* >(GetFlyFormat());
331 :
332 577 : if(pFlyFormat)
333 : {
334 258 : OUString aName;
335 516 : OUString aTitle;
336 516 : OUString aDesc;
337 258 : const SvgDataPtr& rSvgDataPtr = GetGrf().getSvgData();
338 :
339 258 : if(rSvgDataPtr.get())
340 : {
341 1 : const drawinglayer::primitive2d::Primitive2DSequence aSequence(rSvgDataPtr->getPrimitive2DSequence());
342 :
343 1 : if(aSequence.hasElements())
344 : {
345 1 : drawinglayer::geometry::ViewInformation2D aViewInformation2D;
346 2 : drawinglayer::processor2d::ObjectInfoPrimitiveExtractor2D aProcessor(aViewInformation2D);
347 :
348 1 : aProcessor.process(aSequence);
349 :
350 1 : const drawinglayer::primitive2d::ObjectInfoPrimitive2D* pResult = aProcessor.getResult();
351 :
352 1 : if(pResult)
353 : {
354 0 : aName = pResult->getName();
355 0 : aTitle = pResult->getTitle();
356 0 : aDesc = pResult->getDesc();
357 1 : }
358 1 : }
359 : }
360 :
361 258 : if(!aTitle.isEmpty())
362 : {
363 0 : SetTitle(aTitle);
364 : }
365 258 : else if (!aName.isEmpty())
366 : {
367 0 : SetTitle(aName);
368 : }
369 :
370 258 : if(!aDesc.isEmpty())
371 : {
372 0 : SetDescription(aDesc);
373 258 : }
374 : }
375 577 : }
376 :
377 24 : void SwGrfNode::SetGraphic(const Graphic& rGraphic, const OUString& rLink)
378 : {
379 24 : maGrfObj.SetGraphic(rGraphic, rLink);
380 24 : onGraphicChanged();
381 24 : }
382 :
383 593 : const Graphic& SwGrfNode::GetGrf(bool bWait) const
384 : {
385 593 : const_cast<SwGrfNode*>(this)->SwapIn(bWait);
386 593 : return maGrfObj.GetGraphic();
387 : }
388 :
389 240 : const GraphicObject& SwGrfNode::GetGrfObj(bool bWait) const
390 : {
391 240 : const_cast<SwGrfNode*>(this)->SwapIn(bWait);
392 240 : return maGrfObj;
393 : }
394 :
395 6 : const GraphicObject* SwGrfNode::GetReplacementGrfObj() const
396 : {
397 6 : if(!mpReplacementGraphic)
398 : {
399 6 : const SvgDataPtr& rSvgDataPtr = GetGrfObj().GetGraphic().getSvgData();
400 :
401 6 : if(rSvgDataPtr.get())
402 : {
403 0 : const_cast< SwGrfNode* >(this)->mpReplacementGraphic = new GraphicObject(rSvgDataPtr->getReplacement());
404 : }
405 : }
406 :
407 6 : return mpReplacementGraphic;
408 : }
409 :
410 0 : SwContentNode *SwGrfNode::SplitContentNode( const SwPosition & )
411 : {
412 0 : return this;
413 : }
414 :
415 495 : SwGrfNode * SwNodes::MakeGrfNode( const SwNodeIndex & rWhere,
416 : const OUString& rGrfName,
417 : const OUString& rFltName,
418 : const Graphic* pGraphic,
419 : SwGrfFormatColl* pGrfColl,
420 : SwAttrSet* pAutoAttr,
421 : bool bDelayed )
422 : {
423 : OSL_ENSURE( pGrfColl, "MakeGrfNode: Formatpointer ist 0." );
424 : SwGrfNode *pNode;
425 : // create object delayed, only from a SW/G-reader
426 495 : if( bDelayed )
427 : pNode = new SwGrfNode( rWhere, rGrfName,
428 0 : rFltName, pGrfColl, pAutoAttr );
429 : else
430 : pNode = new SwGrfNode( rWhere, rGrfName,
431 495 : rFltName, pGraphic, pGrfColl, pAutoAttr );
432 495 : return pNode;
433 : }
434 :
435 188 : SwGrfNode * SwNodes::MakeGrfNode( const SwNodeIndex & rWhere,
436 : const GraphicObject& rGrfObj,
437 : SwGrfFormatColl* pGrfColl,
438 : SwAttrSet* pAutoAttr )
439 : {
440 : OSL_ENSURE( pGrfColl, "MakeGrfNode: Formatpointer ist 0." );
441 188 : return new SwGrfNode( rWhere, rGrfObj, pGrfColl, pAutoAttr );
442 : }
443 :
444 200 : Size SwGrfNode::GetTwipSize() const
445 : {
446 200 : if( !nGrfSize.Width() && !nGrfSize.Height() )
447 : {
448 82 : const_cast<SwGrfNode*>(this)->SwapIn();
449 : }
450 200 : return nGrfSize;
451 : }
452 :
453 58 : bool SwGrfNode::ImportGraphic( SvStream& rStrm )
454 : {
455 58 : Graphic aGraphic;
456 116 : const OUString aURL(maGrfObj.GetUserData());
457 :
458 58 : if(!GraphicFilter::GetGraphicFilter().ImportGraphic(aGraphic, aURL, rStrm))
459 : {
460 58 : delete mpReplacementGraphic;
461 58 : mpReplacementGraphic = 0;
462 :
463 58 : maGrfObj.SetGraphic( aGraphic );
464 58 : onGraphicChanged();
465 58 : return true;
466 : }
467 :
468 58 : return false;
469 : }
470 :
471 : namespace
472 : {
473 :
474 116 : struct StreamAndStorageNames
475 : {
476 : OUString sStream;
477 : OUString sStorage;
478 : };
479 :
480 58 : StreamAndStorageNames lcl_GetStreamStorageNames( const OUString& sUserData )
481 : {
482 58 : StreamAndStorageNames aNames;
483 58 : if( sUserData.isEmpty() )
484 0 : return aNames;
485 :
486 116 : const OUString aProt( "vnd.sun.star.Package:" );
487 58 : if (sUserData.startsWithIgnoreAsciiCase(aProt))
488 : {
489 : // 6.0 (XML) Package
490 58 : const sal_Int32 nPos = sUserData.indexOf('/');
491 58 : if (nPos<0)
492 : {
493 0 : aNames.sStream = sUserData.copy(aProt.getLength());
494 : }
495 : else
496 : {
497 58 : const sal_Int32 nPathStart = aProt.getLength();
498 58 : aNames.sStorage = sUserData.copy( nPathStart, nPos-nPathStart );
499 58 : aNames.sStream = sUserData.copy( nPos+1 );
500 : }
501 : }
502 : else
503 : {
504 : OSL_FAIL( "<lcl_GetStreamStorageNames(..)> - unknown graphic URL type. Code for handling 3.1 - 5.2 storages has been deleted by issue i53025." );
505 : }
506 : OSL_ENSURE( aNames.sStream.indexOf('/')<0, "invalid graphic stream name" );
507 58 : return aNames;
508 : }
509 :
510 : }
511 :
512 : /**
513 : * @return true if ReRead or reading successful,
514 : * false if not loaded
515 : */
516 916 : bool SwGrfNode::SwapIn( bool bWaitForData )
517 : {
518 916 : if( bInSwapIn ) // not recursively!
519 131 : return !maGrfObj.IsSwappedOut();
520 :
521 785 : bool bRet = false;
522 785 : bInSwapIn = true;
523 785 : SwBaseLink* pLink = static_cast<SwBaseLink*>(static_cast<sfx2::SvBaseLink*>(refLink));
524 :
525 785 : if( pLink )
526 : {
527 45 : if( GRAPHIC_NONE == maGrfObj.GetType() ||
528 6 : GRAPHIC_DEFAULT == maGrfObj.GetType() )
529 : {
530 : // link was not loaded yet
531 35 : if( pLink->SwapIn( bWaitForData ) )
532 : {
533 21 : bRet = true;
534 : }
535 14 : else if( GRAPHIC_DEFAULT == maGrfObj.GetType() )
536 : {
537 : // no default bitmap anymore, thus re-paint
538 0 : delete mpReplacementGraphic;
539 0 : mpReplacementGraphic = 0;
540 :
541 0 : maGrfObj.SetGraphic( Graphic() );
542 0 : onGraphicChanged();
543 0 : SwMsgPoolItem aMsgHint( RES_GRAPHIC_PIECE_ARRIVED );
544 0 : ModifyNotification( &aMsgHint, &aMsgHint );
545 : }
546 : }
547 4 : else if( maGrfObj.IsSwappedOut() )
548 : {
549 : // link to download
550 0 : bRet = pLink->SwapIn( bWaitForData );
551 : }
552 : else
553 4 : bRet = true;
554 : }
555 746 : else if( maGrfObj.IsSwappedOut() )
556 : {
557 : // graphic is in storage or in a temp file
558 94 : if( !HasEmbeddedStreamName() )
559 : {
560 36 : bRet = maGrfObj.SwapIn();
561 : }
562 : else
563 : {
564 : try
565 : {
566 58 : const StreamAndStorageNames aNames = lcl_GetStreamStorageNames( maGrfObj.GetUserData() );
567 116 : uno::Reference < embed::XStorage > refPics = _GetDocSubstorageOrRoot( aNames.sStorage );
568 58 : SvStream* pStrm = _GetStreamForEmbedGrf( refPics, aNames.sStream );
569 58 : if ( pStrm )
570 : {
571 58 : bRet = ImportGraphic( *pStrm );
572 58 : delete pStrm;
573 58 : if( bRet )
574 : {
575 58 : maGrfObj.SetUserData();
576 : }
577 58 : }
578 : }
579 0 : catch (const uno::Exception&)
580 : {
581 : // #i48434#
582 : OSL_FAIL( "<SwGrfNode::SwapIn(..)> - unhandled exception!" );
583 : }
584 : }
585 :
586 94 : if( bRet )
587 : {
588 94 : SwMsgPoolItem aMsg( RES_GRAPHIC_SWAPIN );
589 94 : ModifyNotification( &aMsg, &aMsg );
590 : }
591 : }
592 : else
593 652 : bRet = true;
594 : OSL_ENSURE( bRet, "Cannot swap in graphic" );
595 :
596 785 : if( bRet )
597 : {
598 771 : if( !nGrfSize.Width() && !nGrfSize.Height() )
599 315 : SetTwipSize( ::GetGraphicSizeTwip( maGrfObj.GetGraphic(), 0 ) );
600 : }
601 785 : bInSwapIn = false;
602 785 : return bRet;
603 : }
604 :
605 1 : bool SwGrfNode::SwapOut()
606 : {
607 3 : if( maGrfObj.GetType() != GRAPHIC_DEFAULT &&
608 2 : maGrfObj.GetType() != GRAPHIC_NONE &&
609 3 : !maGrfObj.IsSwappedOut() && !bInSwapIn )
610 : {
611 1 : if( refLink.Is() )
612 : {
613 : // written graphics and links are removed here
614 0 : return maGrfObj.SwapOut( GRFMGR_AUTOSWAPSTREAM_LINK );
615 : }
616 : else
617 : {
618 1 : return maGrfObj.SwapOut();
619 : }
620 :
621 : }
622 0 : return true;
623 : }
624 :
625 28 : bool SwGrfNode::GetFileFilterNms( OUString* pFileNm, OUString* pFilterNm ) const
626 : {
627 28 : bool bRet = false;
628 28 : if( refLink.Is() && refLink->GetLinkManager() )
629 : {
630 28 : sal_uInt16 nType = refLink->GetObjType();
631 28 : if( OBJECT_CLIENT_GRF == nType )
632 : bRet = sfx2::LinkManager::GetDisplayNames(
633 28 : refLink, 0, pFileNm, 0, pFilterNm );
634 0 : else if( OBJECT_CLIENT_DDE == nType && pFileNm && pFilterNm )
635 : {
636 0 : OUString sApp;
637 0 : OUString sTopic;
638 0 : OUString sItem;
639 0 : if( sfx2::LinkManager::GetDisplayNames(
640 0 : refLink, &sApp, &sTopic, &sItem ) )
641 : {
642 0 : *pFileNm = sApp + OUString(sfx2::cTokenSeparator)
643 0 : + sTopic + OUString(sfx2::cTokenSeparator)
644 0 : + sItem;
645 0 : *pFilterNm = "DDE";
646 0 : bRet = true;
647 0 : }
648 : }
649 : }
650 28 : return bRet;
651 : }
652 :
653 : /** Make a graphic object ready for UNDO.
654 : *
655 : * If it is already in storage, it needs to be loaded.
656 : */
657 1 : bool SwGrfNode::SavePersistentData()
658 : {
659 1 : if( refLink.Is() )
660 : {
661 : OSL_ENSURE( !bInSwapIn, "SavePersistentData: I am still in SwapIn" );
662 0 : GetDoc()->getIDocumentLinksAdministration().GetLinkManager().Remove( refLink );
663 0 : return true;
664 : }
665 :
666 : // swap in first if in storage
667 1 : if( HasEmbeddedStreamName() && !SwapIn() )
668 0 : return false;
669 :
670 : // #i44367#
671 : // Do not delete graphic file in storage, because the graphic file could
672 : // be referenced by other graphic nodes.
673 : // Because it's hard to detect this case here and it would only fix
674 : // one problem with shared graphic files - there are also problems, if
675 : // a certain graphic file is referenced by two independent graphic nodes,
676 : // brush item or drawing objects, the stream isn't no longer removed here.
677 : // To do this stuff correct, a reference counting on shared streams
678 : // inside one document has to be implemented.
679 : // Important note: see also fix for #i40014#
680 :
681 : // swap out into temp file
682 1 : return SwapOut();
683 : }
684 :
685 1 : bool SwGrfNode::RestorePersistentData()
686 : {
687 1 : if( refLink.Is() )
688 : {
689 0 : IDocumentLinksAdministration* pIDLA = getIDocumentLinksAdministration();
690 0 : refLink->SetVisible( pIDLA->IsVisibleLinks() );
691 0 : pIDLA->GetLinkManager().InsertDDELink( refLink );
692 0 : if( getIDocumentLayoutAccess()->GetCurrentLayout() )
693 0 : refLink->Update();
694 : }
695 1 : return true;
696 : }
697 :
698 14 : void SwGrfNode::InsertLink( const OUString& rGrfName, const OUString& rFltName )
699 : {
700 14 : refLink = new SwBaseLink( SfxLinkUpdateMode::ONCALL, SotClipboardFormatId::GDIMETAFILE, this );
701 :
702 14 : IDocumentLinksAdministration* pIDLA = getIDocumentLinksAdministration();
703 14 : if( GetNodes().IsDocNodes() )
704 : {
705 14 : refLink->SetVisible( pIDLA->IsVisibleLinks() );
706 14 : if( rFltName == "DDE" )
707 : {
708 0 : sal_Int32 nTmp = 0;
709 0 : OUString sApp, sTopic, sItem;
710 0 : sApp = rGrfName.getToken( 0, sfx2::cTokenSeparator, nTmp );
711 0 : sTopic = rGrfName.getToken( 0, sfx2::cTokenSeparator, nTmp );
712 0 : sItem = rGrfName.copy( nTmp );
713 0 : pIDLA->GetLinkManager().InsertDDELink( refLink,
714 0 : sApp, sTopic, sItem );
715 : }
716 : else
717 : {
718 14 : const bool bSync = rFltName == "SYNCHRON";
719 14 : refLink->SetSynchron( bSync );
720 14 : refLink->SetContentType( SotClipboardFormatId::SVXB );
721 :
722 14 : pIDLA->GetLinkManager().InsertFileLink( *refLink,
723 : OBJECT_CLIENT_GRF, rGrfName,
724 28 : (!bSync && !rFltName.isEmpty() ? &rFltName : 0) );
725 : }
726 : }
727 14 : maGrfObj.SetLink( rGrfName );
728 14 : }
729 :
730 0 : void SwGrfNode::ReleaseLink()
731 : {
732 0 : if( refLink.Is() )
733 : {
734 0 : const OUString aFileName(maGrfObj.GetLink());
735 0 : const Graphic aLocalGraphic(maGrfObj.GetGraphic());
736 0 : const bool bHasOriginalData(aLocalGraphic.IsLink());
737 :
738 : {
739 0 : bInSwapIn = true;
740 0 : SwBaseLink* pLink = static_cast<SwBaseLink*>(static_cast<sfx2::SvBaseLink*>(refLink));
741 0 : pLink->SwapIn( true, true );
742 0 : bInSwapIn = false;
743 : }
744 :
745 0 : getIDocumentLinksAdministration()->GetLinkManager().Remove( refLink );
746 0 : refLink.Clear();
747 0 : maGrfObj.SetLink();
748 :
749 : // #i15508# added extra processing after getting rid of the link. Use whatever is
750 : // known from the formally linked graphic to get to a state as close to a directly
751 : // unlinked inserted graphic as possible. Goal is to have a valid GfxLink at the
752 : // ImplGraphic (see there) that holds temporary data to the original data and type
753 : // information about the original data. Only when this is given will
754 : // SvXMLGraphicHelper::ImplInsertGraphicURL which is used at export use that type
755 : // and use the original graphic at export for the ODF, without evtl. recoding
756 : // of trhe bitmap graphic data to something without loss (e.g. PNG) but bigger
757 0 : if(bHasOriginalData)
758 : {
759 : // #i15508# if we have the original data at the Graphic, let it survive
760 : // by using that Graphic again, this time at a GraphicObject without link.
761 : // This happens e.g. when inserting a linked graphic and breaking the link
762 0 : maGrfObj.SetGraphic(aLocalGraphic);
763 : }
764 0 : else if(!aFileName.isEmpty())
765 : {
766 : // #i15508# we have no original data, but a file name. This happens e.g.
767 : // when inserting a linked graphic and save, reload document. Try to access
768 : // that data from the original file; if this works, use it. Else use the
769 : // data we have (but without knowing the original format)
770 0 : GraphicFilter& rFlt = GraphicFilter::GetGraphicFilter();
771 0 : Graphic aNew;
772 0 : int nRes = GraphicFilter::LoadGraphic( aFileName, OUString(), aNew, &rFlt);
773 :
774 0 : if(GRFILTER_OK == nRes)
775 : {
776 0 : maGrfObj.SetGraphic(aNew);
777 0 : }
778 0 : }
779 : }
780 0 : }
781 :
782 813 : void SwGrfNode::SetTwipSize( const Size& rSz )
783 : {
784 813 : nGrfSize = rSz;
785 813 : if( IsScaleImageMap() && nGrfSize.Width() && nGrfSize.Height() )
786 : {
787 : // resize Image-Map to size of the graphic
788 0 : ScaleImageMap();
789 :
790 : // do not re-scale Image-Map
791 0 : SetScaleImageMap( false );
792 : }
793 813 : }
794 :
795 0 : void SwGrfNode::ScaleImageMap()
796 : {
797 0 : if( !nGrfSize.Width() || !nGrfSize.Height() )
798 0 : return;
799 :
800 : // re-scale Image-Map
801 0 : SwFrameFormat* pFormat = GetFlyFormat();
802 :
803 0 : if( !pFormat )
804 0 : return;
805 :
806 0 : SwFormatURL aURL( pFormat->GetURL() );
807 0 : if ( !aURL.GetMap() )
808 0 : return;
809 :
810 0 : bool bScale = false;
811 0 : Fraction aScaleX( 1, 1 );
812 0 : Fraction aScaleY( 1, 1 );
813 :
814 0 : const SwFormatFrmSize& rFrmSize = pFormat->GetFrmSize();
815 0 : const SvxBoxItem& rBox = pFormat->GetBox();
816 :
817 0 : if( !rFrmSize.GetWidthPercent() )
818 : {
819 0 : SwTwips nWidth = rFrmSize.GetWidth();
820 :
821 0 : nWidth -= rBox.CalcLineSpace(SvxBoxItemLine::LEFT) +
822 0 : rBox.CalcLineSpace(SvxBoxItemLine::RIGHT);
823 :
824 : OSL_ENSURE( nWidth>0, "Do any 0 twip wide graphics exist!?" );
825 :
826 0 : if( nGrfSize.Width() != nWidth )
827 : {
828 0 : aScaleX = Fraction( nGrfSize.Width(), nWidth );
829 0 : bScale = true;
830 : }
831 : }
832 0 : if( !rFrmSize.GetHeightPercent() )
833 : {
834 0 : SwTwips nHeight = rFrmSize.GetHeight();
835 :
836 0 : nHeight -= rBox.CalcLineSpace(SvxBoxItemLine::TOP) +
837 0 : rBox.CalcLineSpace(SvxBoxItemLine::BOTTOM);
838 :
839 : OSL_ENSURE( nHeight>0, "Do any 0 twip high graphics exist!?" );
840 :
841 0 : if( nGrfSize.Height() != nHeight )
842 : {
843 0 : aScaleY = Fraction( nGrfSize.Height(), nHeight );
844 0 : bScale = true;
845 : }
846 : }
847 :
848 0 : if( bScale )
849 : {
850 0 : aURL.GetMap()->Scale( aScaleX, aScaleY );
851 0 : pFormat->SetFormatAttr( aURL );
852 0 : }
853 : }
854 :
855 : /** helper method to get a substorage of the document storage for readonly access.
856 :
857 : OD, MAV 2005-08-17 #i53025#
858 : A substorage with the specified name will be opened readonly. If the provided
859 : name is empty the root storage will be returned.
860 : */
861 58 : uno::Reference< embed::XStorage > SwGrfNode::_GetDocSubstorageOrRoot( const OUString& aStgName ) const
862 : {
863 : uno::Reference < embed::XStorage > refStor =
864 58 : const_cast<SwGrfNode*>(this)->GetDoc()->GetDocStorage();
865 : OSL_ENSURE( refStor.is(), "Kein Storage am Doc" );
866 :
867 58 : if ( !aStgName.isEmpty() )
868 : {
869 58 : if( refStor.is() )
870 58 : return refStor->openStorageElement( aStgName, embed::ElementModes::READ );
871 : }
872 :
873 0 : return refStor;
874 : }
875 :
876 : /** helper method to determine stream for the embedded graphic.
877 :
878 : OD 2005-05-04 #i48434#
879 : Important note: caller of this method has to handle the thrown exceptions
880 : OD, MAV 2005-08-17 #i53025#
881 : Storage, which should contain the stream of the embedded graphic, is
882 : provided via parameter. Otherwise the returned stream will be closed
883 : after the method returns, because its parent stream is closed and deleted.
884 : Proposed name of embedded graphic stream is also provided by parameter.
885 : */
886 58 : SvStream* SwGrfNode::_GetStreamForEmbedGrf(
887 : const uno::Reference< embed::XStorage >& _refPics,
888 : const OUString& rStreamName ) const
889 : {
890 58 : SvStream* pStrm( 0L );
891 :
892 58 : if( _refPics.is() && !rStreamName.isEmpty() )
893 : {
894 58 : OUString sStreamName(rStreamName);
895 : // If stream doesn't exist in the storage, try access the graphic file by
896 : // re-generating its name.
897 : // A save action can have changed the filename of the embedded graphic,
898 : // because a changed unique ID of the graphic is calculated.
899 : // --> recursive calls of <GetUniqueID()> have to be avoided.
900 : // Thus, use local static boolean to assure this.
901 116 : if ( !_refPics->hasByName( sStreamName ) ||
902 58 : !_refPics->isStreamElement( sStreamName ) )
903 : {
904 0 : if ( GetGrfObj().GetType() != GRAPHIC_NONE )
905 : {
906 0 : const sal_Int32 nExtPos = sStreamName.indexOf('.');
907 0 : const OUString aExtStr = (nExtPos>=0) ? sStreamName.copy( nExtPos ) : OUString();
908 0 : sStreamName = OStringToOUString(GetGrfObj().GetUniqueID(),
909 0 : RTL_TEXTENCODING_ASCII_US) + aExtStr;
910 : }
911 : }
912 :
913 : // assure that graphic file exist in the storage.
914 116 : if ( _refPics->hasByName( sStreamName ) &&
915 58 : _refPics->isStreamElement( sStreamName ) )
916 : {
917 58 : uno::Reference < io::XStream > refStrm = _refPics->openStreamElement( sStreamName, embed::ElementModes::READ );
918 58 : pStrm = utl::UcbStreamHelper::CreateStream( refStrm );
919 : }
920 : else
921 : {
922 : OSL_FAIL( "<SwGrfNode::_GetStreamForEmbedGrf(..)> - embedded graphic file not found!" );
923 58 : }
924 : }
925 :
926 58 : return pStrm;
927 : }
928 :
929 177 : SwContentNode* SwGrfNode::MakeCopy( SwDoc* pDoc, const SwNodeIndex& rIdx ) const
930 : {
931 : // copy formats into the other document
932 177 : SwGrfFormatColl* pColl = pDoc->CopyGrfColl( *GetGrfColl() );
933 :
934 177 : Graphic aTmpGrf = GetGrf();
935 :
936 354 : OUString sFile, sFilter;
937 177 : if( IsLinkedFile() )
938 0 : sfx2::LinkManager::GetDisplayNames( refLink, 0, &sFile, 0, &sFilter );
939 177 : else if( IsLinkedDDE() )
940 : {
941 0 : OUString sTmp1, sTmp2;
942 0 : sfx2::LinkManager::GetDisplayNames( refLink, &sTmp1, &sTmp2, &sFilter );
943 0 : sfx2::MakeLnkName( sFile, &sTmp1, sTmp2, sFilter );
944 0 : sFilter = "DDE";
945 : }
946 :
947 : SwGrfNode* pGrfNd = SwNodes::MakeGrfNode( rIdx, sFile, sFilter,
948 : &aTmpGrf, pColl,
949 177 : const_cast<SwAttrSet*>(GetpSwAttrSet()) );
950 177 : pGrfNd->SetTitle( GetTitle() );
951 177 : pGrfNd->SetDescription( GetDescription() );
952 177 : pGrfNd->SetContour( HasContour(), HasAutomaticContour() );
953 354 : return pGrfNd;
954 : }
955 :
956 128 : IMPL_LINK( SwGrfNode, SwapGraphic, GraphicObject*, pGrfObj )
957 : {
958 : SvStream* pRet;
959 :
960 : // Keep graphic while in swap in. That's at least important
961 : // when breaking links, because in this situation a reschedule call and
962 : // a DataChanged call lead to a paint of the graphic.
963 64 : if( pGrfObj->IsInSwapOut() && (IsSelected() || bInSwapIn) )
964 0 : pRet = GRFMGR_AUTOSWAPSTREAM_NONE;
965 64 : else if( refLink.Is() )
966 : {
967 0 : if( pGrfObj->IsInSwapIn() )
968 : {
969 : // then make it by your self
970 0 : if( !bInSwapIn )
971 : {
972 0 : const bool bIsModifyLocked = IsModifyLocked();
973 0 : LockModify();
974 0 : SwapIn( false );
975 0 : if( !bIsModifyLocked )
976 0 : UnlockModify();
977 : }
978 0 : pRet = GRFMGR_AUTOSWAPSTREAM_NONE;
979 : }
980 : else
981 0 : pRet = GRFMGR_AUTOSWAPSTREAM_LINK;
982 : }
983 : else
984 : {
985 64 : pRet = GRFMGR_AUTOSWAPSTREAM_TEMP;
986 : }
987 :
988 64 : return reinterpret_cast<sal_IntPtr>(pRet);
989 : }
990 :
991 : /// returns the Graphic-Attr-Structure filled with our graphic attributes
992 101 : GraphicAttr& SwGrfNode::GetGraphicAttr( GraphicAttr& rGA,
993 : const SwFrm* pFrm ) const
994 : {
995 101 : const SwAttrSet& rSet = GetSwAttrSet();
996 :
997 101 : rGA.SetDrawMode( (GraphicDrawMode)rSet.GetDrawModeGrf().GetValue() );
998 :
999 101 : const SwMirrorGrf & rMirror = rSet.GetMirrorGrf();
1000 101 : BmpMirrorFlags nMirror = BmpMirrorFlags::NONE;
1001 101 : if( rMirror.IsGrfToggle() && pFrm && !pFrm->FindPageFrm()->OnRightPage() )
1002 : {
1003 0 : switch( rMirror.GetValue() )
1004 : {
1005 : case RES_MIRROR_GRAPH_DONT:
1006 0 : nMirror = BmpMirrorFlags::Horizontal;
1007 0 : break;
1008 : case RES_MIRROR_GRAPH_VERT:
1009 0 : nMirror = BmpMirrorFlags::NONE;
1010 0 : break;
1011 : case RES_MIRROR_GRAPH_HOR:
1012 0 : nMirror = BmpMirrorFlags::Horizontal|BmpMirrorFlags::Vertical;
1013 0 : break;
1014 : default:
1015 0 : nMirror = BmpMirrorFlags::Vertical;
1016 0 : break;
1017 : }
1018 : }
1019 : else
1020 101 : switch( rMirror.GetValue() )
1021 : {
1022 : case RES_MIRROR_GRAPH_BOTH:
1023 0 : nMirror = BmpMirrorFlags::Horizontal|BmpMirrorFlags::Vertical;
1024 0 : break;
1025 : case RES_MIRROR_GRAPH_VERT:
1026 0 : nMirror = BmpMirrorFlags::Horizontal;
1027 0 : break;
1028 : case RES_MIRROR_GRAPH_HOR:
1029 0 : nMirror = BmpMirrorFlags::Vertical;
1030 0 : break;
1031 : }
1032 :
1033 101 : rGA.SetMirrorFlags( nMirror );
1034 :
1035 101 : const SwCropGrf& rCrop = rSet.GetCropGrf();
1036 101 : rGA.SetCrop( convertTwipToMm100( rCrop.GetLeft() ),
1037 101 : convertTwipToMm100( rCrop.GetTop() ),
1038 101 : convertTwipToMm100( rCrop.GetRight() ),
1039 404 : convertTwipToMm100( rCrop.GetBottom() ));
1040 :
1041 101 : const SwRotationGrf& rRotation = rSet.GetRotationGrf();
1042 101 : rGA.SetRotation( rRotation.GetValue() );
1043 :
1044 101 : rGA.SetLuminance( rSet.GetLuminanceGrf().GetValue() );
1045 101 : rGA.SetContrast( rSet.GetContrastGrf().GetValue() );
1046 101 : rGA.SetChannelR( rSet.GetChannelRGrf().GetValue() );
1047 101 : rGA.SetChannelG( rSet.GetChannelGGrf().GetValue() );
1048 101 : rGA.SetChannelB( rSet.GetChannelBGrf().GetValue() );
1049 101 : rGA.SetGamma( rSet.GetGammaGrf().GetValue() );
1050 101 : rGA.SetInvert( rSet.GetInvertGrf().GetValue() );
1051 :
1052 101 : const sal_uInt16 nTrans = rSet.GetTransparencyGrf().GetValue();
1053 : rGA.SetTransparency( (sal_uInt8) FRound(
1054 101 : std::min( nTrans, (sal_uInt16) 100 ) * 2.55 ) );
1055 :
1056 101 : return rGA;
1057 : }
1058 :
1059 120 : bool SwGrfNode::IsTransparent() const
1060 : {
1061 167 : return maGrfObj.IsTransparent() ||
1062 167 : GetSwAttrSet().GetTransparencyGrf().GetValue() != 0;
1063 : }
1064 :
1065 64 : bool SwGrfNode::IsSelected() const
1066 : {
1067 64 : bool bRet = false;
1068 64 : const SwEditShell* pESh = GetDoc()->GetEditShell();
1069 64 : if( pESh )
1070 : {
1071 48 : const SwNode* pN = this;
1072 99 : for(const SwViewShell& rCurrentShell : pESh->GetRingContainer())
1073 : {
1074 99 : if( rCurrentShell.ISA( SwEditShell ) && pN == &static_cast<const SwCrsrShell*>(&rCurrentShell)
1075 48 : ->GetCrsr()->GetPoint()->nNode.GetNode() )
1076 : {
1077 0 : bRet = true;
1078 0 : break;
1079 : }
1080 : }
1081 : }
1082 64 : return bRet;
1083 : }
1084 :
1085 5 : void SwGrfNode::TriggerAsyncRetrieveInputStream()
1086 : {
1087 5 : if ( !IsLinkedFile() )
1088 : {
1089 : OSL_FAIL( "<SwGrfNode::TriggerAsyncLoad()> - Method is misused. Method call is only valid for graphic nodes, which refer a linked graphic file" );
1090 5 : return;
1091 : }
1092 :
1093 5 : if ( mpThreadConsumer.get() == 0 )
1094 : {
1095 4 : mpThreadConsumer.reset( new SwAsyncRetrieveInputStreamThreadConsumer( *this ) );
1096 :
1097 4 : OUString sGrfNm;
1098 4 : sfx2::LinkManager::GetDisplayNames( refLink, 0, &sGrfNm, 0, 0 );
1099 8 : OUString sReferer;
1100 4 : SfxObjectShell * sh = GetDoc()->GetPersist();
1101 4 : if (sh != 0 && sh->HasName())
1102 : {
1103 4 : sReferer = sh->GetMedium()->GetName();
1104 : }
1105 8 : mpThreadConsumer->CreateThread( sGrfNm, sReferer );
1106 : }
1107 : }
1108 :
1109 :
1110 0 : void SwGrfNode::ApplyInputStream(
1111 : com::sun::star::uno::Reference<com::sun::star::io::XInputStream> xInputStream,
1112 : const bool bIsStreamReadOnly )
1113 : {
1114 0 : if ( IsLinkedFile() )
1115 : {
1116 0 : if ( xInputStream.is() )
1117 : {
1118 0 : mxInputStream = xInputStream;
1119 0 : mbIsStreamReadOnly = bIsStreamReadOnly;
1120 0 : mbLinkedInputStreamReady = true;
1121 0 : SwMsgPoolItem aMsgHint( RES_LINKED_GRAPHIC_STREAM_ARRIVED );
1122 0 : ModifyNotification( &aMsgHint, &aMsgHint );
1123 : }
1124 : }
1125 0 : }
1126 :
1127 0 : void SwGrfNode::UpdateLinkWithInputStream()
1128 : {
1129 : // do not work on link, if a <SwapIn> has been triggered.
1130 0 : if ( !bInSwapIn && IsLinkedFile() )
1131 : {
1132 0 : GetLink()->setStreamToLoadFrom( mxInputStream, mbIsStreamReadOnly );
1133 0 : GetLink()->Update();
1134 0 : SwMsgPoolItem aMsgHint( RES_GRAPHIC_ARRIVED );
1135 0 : ModifyNotification( &aMsgHint, &aMsgHint );
1136 :
1137 : // #i88291#
1138 0 : mxInputStream.clear();
1139 0 : GetLink()->clearStreamToLoadFrom();
1140 0 : mbLinkedInputStreamReady = false;
1141 0 : mpThreadConsumer.reset();
1142 : }
1143 0 : }
1144 :
1145 : // #i90395#
1146 5 : bool SwGrfNode::IsAsyncRetrieveInputStreamPossible() const
1147 : {
1148 5 : bool bRet = false;
1149 :
1150 5 : if ( IsLinkedFile() )
1151 : {
1152 5 : OUString sGrfNm;
1153 5 : sfx2::LinkManager::GetDisplayNames( refLink, 0, &sGrfNm, 0, 0 );
1154 5 : if ( !sGrfNm.startsWith( "vnd.sun.star.pkg:" ) )
1155 : {
1156 5 : bRet = true;
1157 5 : }
1158 : }
1159 :
1160 5 : return bRet;
1161 177 : }
1162 :
1163 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|