bump product version to 5.0.4.1
[LibreOffice.git] / svx / source / svdraw / svdmark.cxx
blobb1f8464b0076864c04bfd96deffbcd8051d0dee8
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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 .
23 #include <svx/svdmark.hxx>
24 #include <svx/svdetc.hxx>
25 #include <svx/svdobj.hxx>
26 #include <svx/svdpage.hxx>
27 #include "svx/svditer.hxx"
28 #include <svx/svdpagv.hxx>
29 #include <svx/svdopath.hxx>
30 #include <svx/svdogrp.hxx>
31 #include <svx/svdorect.hxx>
32 #include "svx/svdstr.hrc"
33 #include "svdglob.hxx"
36 #include <svx/obj3d.hxx>
37 #include <svx/scene3d.hxx>
38 #include <svl/SfxBroadcaster.hxx>
39 #include <svx/svdoedge.hxx>
44 SdrMark::SdrMark(SdrObject* pNewObj, SdrPageView* pNewPageView)
45 : mpSelectedSdrObject(pNewObj),
46 mpPageView(pNewPageView),
47 mpPoints(0L),
48 mpLines(0L),
49 mpGluePoints(0L),
50 mbCon1(false),
51 mbCon2(false),
52 mnUser(0)
54 if(mpSelectedSdrObject)
56 mpSelectedSdrObject->AddObjectUser( *this );
60 SdrMark::SdrMark(const SdrMark& rMark)
61 : ObjectUser(),
62 mpSelectedSdrObject(0L),
63 mpPageView(0L),
64 mpPoints(0L),
65 mpLines(0L),
66 mpGluePoints(0L),
67 mbCon1(false),
68 mbCon2(false),
69 mnUser(0)
71 *this = rMark;
74 SdrMark::~SdrMark()
76 if(mpSelectedSdrObject)
78 mpSelectedSdrObject->RemoveObjectUser( *this );
81 if(mpPoints)
83 delete mpPoints;
86 if(mpLines)
88 delete mpLines;
91 if(mpGluePoints)
93 delete mpGluePoints;
97 void SdrMark::ObjectInDestruction(const SdrObject& rObject)
99 (void) rObject; // avoid warnings
100 OSL_ENSURE(mpSelectedSdrObject && mpSelectedSdrObject == &rObject, "SdrMark::ObjectInDestruction: called form object different from hosted one (!)");
101 OSL_ENSURE(mpSelectedSdrObject, "SdrMark::ObjectInDestruction: still selected SdrObject is deleted, deselect first (!)");
102 mpSelectedSdrObject = 0L;
105 void SdrMark::SetMarkedSdrObj(SdrObject* pNewObj)
107 if(mpSelectedSdrObject)
109 mpSelectedSdrObject->RemoveObjectUser( *this );
112 mpSelectedSdrObject = pNewObj;
114 if(mpSelectedSdrObject)
116 mpSelectedSdrObject->AddObjectUser( *this );
121 SdrMark& SdrMark::operator=(const SdrMark& rMark)
123 SetMarkedSdrObj(rMark.mpSelectedSdrObject);
124 mpPageView = rMark.mpPageView;
125 mbCon1 = rMark.mbCon1;
126 mbCon2 = rMark.mbCon2;
127 mnUser = rMark.mnUser;
129 if(!rMark.mpPoints)
131 if(mpPoints)
133 delete mpPoints;
134 mpPoints = 0L;
137 else
139 if(!mpPoints)
141 mpPoints = new SdrUShortCont(*rMark.mpPoints);
143 else
145 *mpPoints = *rMark.mpPoints;
149 if(!rMark.mpLines)
151 if(mpLines)
153 delete mpLines;
154 mpLines = 0L;
157 else
159 if(!mpLines)
161 mpLines = new SdrUShortCont(*rMark.mpLines);
163 else
165 *mpLines = *rMark.mpLines;
169 if(!rMark.mpGluePoints)
171 if(mpGluePoints)
173 delete mpGluePoints;
174 mpGluePoints = 0L;
177 else
179 if(!mpGluePoints)
181 mpGluePoints = new SdrUShortCont(*rMark.mpGluePoints);
183 else
185 *mpGluePoints = *rMark.mpGluePoints;
189 return *this;
192 bool SdrMark::operator==(const SdrMark& rMark) const
194 bool bRet(mpSelectedSdrObject == rMark.mpSelectedSdrObject && mpPageView == rMark.mpPageView && mbCon1 == rMark.mbCon1 && mbCon2 == rMark.mbCon2 && mnUser == rMark.mnUser);
196 if((mpPoints != 0L) != (rMark.mpPoints != 0L))
197 bRet = false;
199 if((mpLines != 0L) != (rMark.mpLines != 0L))
200 bRet = false;
202 if((mpGluePoints != 0L) != (rMark.mpGluePoints != 0L))
203 bRet = false;
205 if(bRet && mpPoints && *mpPoints != *rMark.mpPoints)
206 bRet = false;
208 if(bRet && mpLines && *mpLines != *rMark.mpLines)
209 bRet = false;
211 if(bRet && mpGluePoints && *mpGluePoints != *rMark.mpGluePoints)
212 bRet = false;
214 return bRet;
219 static bool ImpSdrMarkListSorter(SdrMark* const& lhs, SdrMark* const& rhs)
221 SdrObject* pObj1 = lhs->GetMarkedSdrObj();
222 SdrObject* pObj2 = rhs->GetMarkedSdrObj();
223 SdrObjList* pOL1 = (pObj1) ? pObj1->GetObjList() : 0L;
224 SdrObjList* pOL2 = (pObj2) ? pObj2->GetObjList() : 0L;
226 if (pOL1 == pOL2)
228 // AF: Note that I reverted a change from sal_uInt32 to sal_uLong (made
229 // for 64bit compliance, #i78198#) because internally in SdrObject
230 // both nOrdNum and mnNavigationPosition are stored as sal_uInt32.
231 sal_uInt32 nObjOrd1((pObj1) ? pObj1->GetNavigationPosition() : 0);
232 sal_uInt32 nObjOrd2((pObj2) ? pObj2->GetNavigationPosition() : 0);
234 return nObjOrd1 < nObjOrd2;
236 else
238 return pOL1 < pOL2;
244 void SdrMarkList::ForceSort() const
246 if(!mbSorted)
248 const_cast<SdrMarkList*>(this)->ImpForceSort();
252 void SdrMarkList::ImpForceSort()
254 if(!mbSorted)
256 mbSorted = true;
257 size_t nCount = maList.size();
259 // remove invalid
260 if(nCount > 0 )
262 for(std::vector<SdrMark*>::iterator it = maList.begin(); it != maList.end(); )
264 SdrMark* pAkt = *it;
265 if(pAkt->GetMarkedSdrObj() == 0)
267 it = maList.erase( it );
268 delete pAkt;
270 else
271 ++it;
273 nCount = maList.size();
276 if(nCount > 1)
278 std::sort(maList.begin(), maList.end(), ImpSdrMarkListSorter);
280 // remove duplicates
281 if(maList.size() > 1)
283 SdrMark* pAkt = maList.back();
284 for (size_t i = maList.size() - 2; i; --i)
286 SdrMark* pCmp = maList[i];
287 if(pAkt->GetMarkedSdrObj() == pCmp->GetMarkedSdrObj() && pAkt->GetMarkedSdrObj())
289 // Con1/Con2 Merging
290 if(pCmp->IsCon1())
291 pAkt->SetCon1(true);
293 if(pCmp->IsCon2())
294 pAkt->SetCon2(true);
296 // delete pCmp
297 maList.erase(maList.begin() + i);
299 delete pCmp;
301 else
303 pAkt = pCmp;
311 void SdrMarkList::Clear()
313 for(size_t i = 0; i < GetMarkCount(); ++i)
315 SdrMark* pMark = GetMark(i);
316 delete pMark;
319 maList.clear();
320 SetNameDirty();
323 void SdrMarkList::operator=(const SdrMarkList& rLst)
325 Clear();
327 for(size_t i = 0; i < rLst.GetMarkCount(); ++i)
329 SdrMark* pMark = rLst.GetMark(i);
330 SdrMark* pNeuMark = new SdrMark(*pMark);
331 maList.push_back(pNeuMark);
334 maMarkName = rLst.maMarkName;
335 mbNameOk = rLst.mbNameOk;
336 maPointName = rLst.maPointName;
337 mbPointNameOk = rLst.mbPointNameOk;
338 maGluePointName = rLst.maGluePointName;
339 mbGluePointNameOk = rLst.mbGluePointNameOk;
340 mbSorted = rLst.mbSorted;
343 SdrMark* SdrMarkList::GetMark(size_t nNum) const
345 return (nNum < maList.size()) ? maList[nNum] : NULL;
348 size_t SdrMarkList::FindObject(const SdrObject* pObj) const
350 // Since relying on OrdNums is not allowed for the selection because objects in the
351 // selection may not be inserted in a list if they are e.g. modified ATM, i changed
352 // this loop to just look if the object pointer is in the selection.
354 // Problem is that GetOrdNum() which is const, internally casts to non-const and
355 // hardly sets the OrdNum member of the object (nOrdNum) to 0 (ZERO) if the object
356 // is not inserted in a object list.
357 // Since this may be by purpose and necessary somewhere else i decided that it is
358 // less dangerous to change this method then changing SdrObject::GetOrdNum().
359 if(pObj && !maList.empty())
361 for(size_t a = 0; a < maList.size(); ++a)
363 if(maList[a]->GetMarkedSdrObj() == pObj)
365 return a;
370 return SAL_MAX_SIZE;
373 void SdrMarkList::InsertEntry(const SdrMark& rMark, bool bChkSort)
375 SetNameDirty();
376 const size_t nCount(maList.size());
378 if(!bChkSort || !mbSorted || nCount == 0)
380 if(!bChkSort)
381 mbSorted = false;
383 maList.push_back(new SdrMark(rMark));
385 else
387 SdrMark* pLast = GetMark(nCount - 1);
388 const SdrObject* pLastObj = pLast->GetMarkedSdrObj();
389 const SdrObject* pNeuObj = rMark.GetMarkedSdrObj();
391 if(pLastObj == pNeuObj)
393 // This one already exists.
394 // Con1/Con2 Merging
395 if(rMark.IsCon1())
396 pLast->SetCon1(true);
398 if(rMark.IsCon2())
399 pLast->SetCon2(true);
401 else
403 SdrMark* pKopie = new SdrMark(rMark);
404 maList.push_back(pKopie);
406 // now check if the sort is ok
407 const SdrObjList* pLastOL = pLastObj!=0L ? pLastObj->GetObjList() : 0L;
408 const SdrObjList* pNeuOL = pNeuObj !=0L ? pNeuObj ->GetObjList() : 0L;
410 if(pLastOL == pNeuOL)
412 const sal_uLong nLastNum(pLastObj!=0L ? pLastObj->GetOrdNum() : 0);
413 const sal_uLong nNeuNum(pNeuObj !=0L ? pNeuObj ->GetOrdNum() : 0);
415 if(nNeuNum < nLastNum)
417 // at some point, we have to sort
418 mbSorted = false;
421 else
423 // at some point, we have to sort
424 mbSorted = false;
429 return;
432 void SdrMarkList::DeleteMark(size_t nNum)
434 SdrMark* pMark = GetMark(nNum);
435 DBG_ASSERT(pMark!=0L,"DeleteMark: MarkEntry not found.");
437 if(pMark)
439 maList.erase(maList.begin() + nNum);
440 delete pMark;
441 SetNameDirty();
445 void SdrMarkList::ReplaceMark(const SdrMark& rNewMark, size_t nNum)
447 SdrMark* pMark = GetMark(nNum);
448 DBG_ASSERT(pMark!=0L,"ReplaceMark: MarkEntry not found.");
450 if(pMark)
452 delete pMark;
453 SetNameDirty();
454 SdrMark* pKopie = new SdrMark(rNewMark);
455 maList[nNum] = pKopie;
456 mbSorted = false;
460 void SdrMarkList::Merge(const SdrMarkList& rSrcList, bool bReverse)
462 const size_t nCount(rSrcList.maList.size());
464 if(rSrcList.mbSorted)
466 // merge without forcing a Sort in rSrcList
467 bReverse = false;
470 if(!bReverse)
472 for(size_t i = 0; i < nCount; ++i)
474 SdrMark* pM = rSrcList.maList[i];
475 InsertEntry(*pM);
478 else
480 for(size_t i = nCount; i > 0;)
482 --i;
483 SdrMark* pM = rSrcList.maList[i];
484 InsertEntry(*pM);
489 bool SdrMarkList::DeletePageView(const SdrPageView& rPV)
491 bool bChgd(false);
493 for(std::vector<SdrMark*>::iterator it = maList.begin(); it != maList.end(); )
495 SdrMark* pMark = *it;
497 if(pMark->GetPageView()==&rPV)
499 it = maList.erase(it);
500 delete pMark;
501 SetNameDirty();
502 bChgd = true;
504 else
505 ++it;
508 return bChgd;
511 bool SdrMarkList::InsertPageView(const SdrPageView& rPV)
513 bool bChgd(false);
514 DeletePageView(rPV); // delete all of them, then append the entire page
515 const SdrObjList* pOL = rPV.GetObjList();
516 const size_t nObjAnz(pOL->GetObjCount());
518 for(size_t nO = 0; nO < nObjAnz; ++nO)
520 SdrObject* pObj = pOL->GetObj(nO);
521 bool bDoIt(rPV.IsObjMarkable(pObj));
523 if(bDoIt)
525 SdrMark* pM = new SdrMark(pObj, const_cast<SdrPageView*>(&rPV));
526 maList.push_back(pM);
527 SetNameDirty();
528 bChgd = true;
532 return bChgd;
535 const OUString& SdrMarkList::GetMarkDescription() const
537 const size_t nCount(GetMarkCount());
539 if(mbNameOk && 1L == nCount)
541 // if it's a single selection, cache only text frame
542 const SdrObject* pObj = GetMark(0)->GetMarkedSdrObj();
543 const SdrTextObj* pTextObj = PTR_CAST(SdrTextObj, pObj);
545 if(!pTextObj || !pTextObj->IsTextFrame())
547 const_cast<SdrMarkList*>(this)->mbNameOk = false;
551 if(!mbNameOk)
553 SdrMark* pMark = GetMark(0);
554 OUString aNam;
556 if(!nCount)
558 const_cast<SdrMarkList*>(this)->maMarkName = ImpGetResStr(STR_ObjNameNoObj);
560 else if(1L == nCount)
562 if(pMark->GetMarkedSdrObj())
564 aNam = pMark->GetMarkedSdrObj()->TakeObjNameSingul();
567 else
569 if(pMark->GetMarkedSdrObj())
571 aNam = pMark->GetMarkedSdrObj()->TakeObjNamePlural();
572 bool bEq(true);
574 for(size_t i = 1; i < GetMarkCount() && bEq; ++i)
576 SdrMark* pMark2 = GetMark(i);
577 OUString aStr1(pMark2->GetMarkedSdrObj()->TakeObjNamePlural());
578 bEq = aNam == aStr1;
581 if(!bEq)
583 aNam = ImpGetResStr(STR_ObjNamePlural);
587 aNam = OUString::number( nCount ) + " " + aNam;
590 const_cast<SdrMarkList*>(this)->maMarkName = aNam;
591 const_cast<SdrMarkList*>(this)->mbNameOk = true;
594 return maMarkName;
597 const OUString& SdrMarkList::GetPointMarkDescription(bool bGlue) const
599 bool& rNameOk = (bool&)(bGlue ? mbGluePointNameOk : mbPointNameOk);
600 OUString& rName = const_cast<OUString&>(bGlue ? maGluePointName : maPointName);
601 const size_t nMarkCount(GetMarkCount());
602 size_t nMarkPtAnz(0);
603 size_t nMarkPtObjAnz(0);
604 size_t n1stMarkNum(SAL_MAX_SIZE);
606 for(size_t nMarkNum = 0; nMarkNum < nMarkCount; ++nMarkNum)
608 const SdrMark* pMark = GetMark(nMarkNum);
609 const SdrUShortCont* pPts = bGlue ? pMark->GetMarkedGluePoints() : pMark->GetMarkedPoints();
610 const size_t nCount(pPts ? pPts->size() : 0);
612 if(nCount)
614 if(n1stMarkNum == SAL_MAX_SIZE)
616 n1stMarkNum = nMarkNum;
619 nMarkPtAnz += nCount;
620 nMarkPtObjAnz++;
623 if(nMarkPtObjAnz > 1 && rNameOk)
625 // preliminary decision
626 return rName;
630 if(rNameOk && 1 == nMarkPtObjAnz)
632 // if it's a single selection, cache only text frame
633 const SdrObject* pObj = GetMark(0)->GetMarkedSdrObj();
634 const SdrTextObj* pTextObj = PTR_CAST(SdrTextObj,pObj);
636 if(!pTextObj || !pTextObj->IsTextFrame())
638 rNameOk = false;
642 if(!nMarkPtObjAnz)
644 rName.clear();
645 rNameOk = true;
647 else if(!rNameOk)
649 const SdrMark* pMark = GetMark(n1stMarkNum);
650 OUString aNam;
652 if(1L == nMarkPtObjAnz)
654 if(pMark->GetMarkedSdrObj())
656 aNam = pMark->GetMarkedSdrObj()->TakeObjNameSingul();
659 else
661 if(pMark->GetMarkedSdrObj())
663 aNam = pMark->GetMarkedSdrObj()->TakeObjNamePlural();
666 bool bEq(true);
668 for(size_t i = n1stMarkNum + 1; i < GetMarkCount() && bEq; ++i)
670 const SdrMark* pMark2 = GetMark(i);
671 const SdrUShortCont* pPts = bGlue ? pMark2->GetMarkedGluePoints() : pMark2->GetMarkedPoints();
673 if(pPts && !pPts->empty() && pMark2->GetMarkedSdrObj())
675 OUString aStr1(pMark2->GetMarkedSdrObj()->TakeObjNamePlural());
676 bEq = aNam == aStr1;
680 if(!bEq)
682 aNam = ImpGetResStr(STR_ObjNamePlural);
685 aNam = OUString::number( nMarkPtObjAnz ) + " " + aNam;
688 OUString aStr1;
690 if(1L == nMarkPtAnz)
692 aStr1 = (ImpGetResStr(bGlue ? STR_ViewMarkedGluePoint : STR_ViewMarkedPoint));
694 else
696 aStr1 = (ImpGetResStr(bGlue ? STR_ViewMarkedGluePoints : STR_ViewMarkedPoints));
697 aStr1 = aStr1.replaceFirst("%2", OUString::number( nMarkPtAnz ));
700 aStr1 = aStr1.replaceFirst("%1", aNam);
701 rName = aStr1;
702 rNameOk = true;
705 return rName;
708 bool SdrMarkList::TakeBoundRect(SdrPageView* pPV, Rectangle& rRect) const
710 bool bFnd(false);
711 Rectangle aR;
713 for(size_t i = 0; i < GetMarkCount(); ++i)
715 SdrMark* pMark = GetMark(i);
717 if(!pPV || pMark->GetPageView() == pPV)
719 if(pMark->GetMarkedSdrObj())
721 aR = pMark->GetMarkedSdrObj()->GetCurrentBoundRect();
723 if(bFnd)
725 rRect.Union(aR);
727 else
729 rRect = aR;
730 bFnd = true;
736 return bFnd;
739 bool SdrMarkList::TakeSnapRect(SdrPageView* pPV, Rectangle& rRect) const
741 bool bFnd(false);
743 for(size_t i = 0; i < GetMarkCount(); ++i)
745 SdrMark* pMark = GetMark(i);
747 if(!pPV || pMark->GetPageView() == pPV)
749 if(pMark->GetMarkedSdrObj())
751 Rectangle aR(pMark->GetMarkedSdrObj()->GetSnapRect());
753 if(bFnd)
755 rRect.Union(aR);
757 else
759 rRect = aR;
760 bFnd = true;
766 return bFnd;
771 namespace sdr
773 ViewSelection::ViewSelection()
774 : mbEdgesOfMarkedNodesDirty(false)
778 void ViewSelection::SetEdgesOfMarkedNodesDirty()
780 if(!mbEdgesOfMarkedNodesDirty)
782 mbEdgesOfMarkedNodesDirty = true;
783 maEdgesOfMarkedNodes.Clear();
784 maMarkedEdgesOfMarkedNodes.Clear();
785 maAllMarkedObjects.clear();
789 const SdrMarkList& ViewSelection::GetEdgesOfMarkedNodes() const
791 if(mbEdgesOfMarkedNodesDirty)
793 const_cast<ViewSelection*>(this)->ImpForceEdgesOfMarkedNodes();
796 return maEdgesOfMarkedNodes;
799 const SdrMarkList& ViewSelection::GetMarkedEdgesOfMarkedNodes() const
801 if(mbEdgesOfMarkedNodesDirty)
803 const_cast<ViewSelection*>(this)->ImpForceEdgesOfMarkedNodes();
806 return maMarkedEdgesOfMarkedNodes;
809 const std::vector<SdrObject*>& ViewSelection::GetAllMarkedObjects() const
811 if(mbEdgesOfMarkedNodesDirty)
812 const_cast<ViewSelection*>(this)->ImpForceEdgesOfMarkedNodes();
814 return maAllMarkedObjects;
817 void ViewSelection::ImplCollectCompleteSelection(SdrObject* pObj)
819 if(pObj)
821 bool bIsGroup(pObj->IsGroupObject());
823 if(bIsGroup && pObj->ISA(E3dObject) && !pObj->ISA(E3dScene))
825 bIsGroup = false;
828 if(bIsGroup)
830 SdrObjList* pList = pObj->GetSubList();
832 for(size_t a = 0; a < pList->GetObjCount(); ++a)
834 SdrObject* pObj2 = pList->GetObj(a);
835 ImplCollectCompleteSelection(pObj2);
839 maAllMarkedObjects.push_back(pObj);
843 void ViewSelection::ImpForceEdgesOfMarkedNodes()
845 if(mbEdgesOfMarkedNodesDirty)
847 mbEdgesOfMarkedNodesDirty = false;
848 maMarkedObjectList.ForceSort();
849 maEdgesOfMarkedNodes.Clear();
850 maMarkedEdgesOfMarkedNodes.Clear();
851 maAllMarkedObjects.clear();
853 // GetMarkCount after ForceSort
854 const size_t nMarkCount(maMarkedObjectList.GetMarkCount());
856 for(size_t a = 0; a < nMarkCount; ++a)
858 SdrObject* pCandidate = maMarkedObjectList.GetMark(a)->GetMarkedSdrObj();
860 if(pCandidate)
862 // build transitive hull
863 ImplCollectCompleteSelection(pCandidate);
865 if(pCandidate->IsNode())
867 // travel over broadcaster/listener to access edges connected to the selected object
868 const SfxBroadcaster* pBC = pCandidate->GetBroadcaster();
870 if(pBC)
872 const size_t nLstAnz(pBC->GetSizeOfVector());
874 for(size_t nl=0; nl < nLstAnz; ++nl)
876 SfxListener* pLst = pBC->GetListener(nl);
877 SdrEdgeObj* pEdge = PTR_CAST(SdrEdgeObj, pLst);
879 if(pEdge && pEdge->IsInserted() && pEdge->GetPage() == pCandidate->GetPage())
881 SdrMark aM(pEdge, maMarkedObjectList.GetMark(a)->GetPageView());
883 if(pEdge->GetConnectedNode(true) == pCandidate)
885 aM.SetCon1(true);
888 if(pEdge->GetConnectedNode(false) == pCandidate)
890 aM.SetCon2(true);
893 if(SAL_MAX_SIZE == maMarkedObjectList.FindObject(pEdge))
895 // check if it itself is selected
896 maEdgesOfMarkedNodes.InsertEntry(aM);
898 else
900 maMarkedEdgesOfMarkedNodes.InsertEntry(aM);
909 maEdgesOfMarkedNodes.ForceSort();
910 maMarkedEdgesOfMarkedNodes.ForceSort();
913 } // end of namespace sdr
915 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */