/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
if(nullptr != GetConnection(true).GetSdrObject() || nullptr != GetConnection(false).GetSdrObject())
{ // check broadcasters; when we are not inserted we do not need broadcasters // TTTT not yet added, but keep hint to do this here // mpCon1->ownerPageChange(); // mpCon2->ownerPageChange();
}
}
// Do not overwrite existing value with default. ImpSetAttrToEdgeInfo() is called several // times with a set, that does not have SDRATTR_EDGEOOXMLCURVE item. if (rSet.HasItem(SDRATTR_EDGEOOXMLCURVE))
m_aEdgeInfo.m_bUseOOXMLCurve = rSet.Get(SDRATTR_EDGEOOXMLCURVE).GetValue();
} elseif(eKind == SdrEdgeKind::ThreeLines)
{ bool bHor1 = m_aEdgeInfo.m_nAngle1 == 0 || m_aEdgeInfo.m_nAngle1 == 18000; bool bHor2 = m_aEdgeInfo.m_nAngle2 == 0 || m_aEdgeInfo.m_nAngle2 == 18000;
// Here no more notifying is necessary, just local changes are OK. if(n != nValCnt)
{
GetProperties().SetObjectItemDirect(SdrEdgeLineDeltaCountItem(n));
}
void SdrEdgeObj::ImpRecalcEdgeTrack()
{ // #i120437# if bEdgeTrackUserDefined, do not recalculate if(m_bEdgeTrackUserDefined)
{ return;
}
// #i120437# also not when model locked during import, but remember if(getSdrModelFromSdrObject().isLocked())
{
mbSuppressed = true; return;
}
// #i110649# if(mbBoundRectCalculationRunning)
{ // This object is involved into another ImpRecalcEdgeTrack() call // from another SdrEdgeObj. Do not calculate again to avoid loop. // Also, do not change bEdgeTrackDirty so that it gets recalculated // later at the first non-looping call.
} else
{ // To not run in a depth loop, use a coloring algorithm on // SdrEdgeObj BoundRect calculations
mbBoundRectCalculationRunning = true;
if(mbSuppressed)
{ // #i123048# If layouting was ever suppressed, it needs to be done once // and the attr need to be set at EdgeInfo, else these attr *will be lost* // in the following call to ImpSetEdgeInfoToAttr() since they were never // set before (!)
*m_pEdgeTrack=ImpCalcEdgeTrack(*m_pEdgeTrack,m_aCon1,m_aCon2,&m_aEdgeInfo);
ImpSetAttrToEdgeInfo();
mbSuppressed = false;
}
tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetCurrentBoundRect();
SetBoundAndSnapRectsDirty();
*m_pEdgeTrack=ImpCalcEdgeTrack(*m_pEdgeTrack,m_aCon1,m_aCon2,&m_aEdgeInfo);
ImpSetEdgeInfoToAttr(); // copy values from aEdgeInfo into the pool
m_bEdgeTrackDirty=false;
// Only redraw here, no object change
ActionChanged();
Point aP1(rStPt); // mandatory difference first,... if (bLks) aP1.setX(rRect.Left() ); if (bRts) aP1.setX(rRect.Right() ); if (bObn) aP1.setY(rRect.Top() ); if (bUnt) aP1.setY(rRect.Bottom() );
Point aP2(aP1); // ...now increase to Meeting height, if necessary if (bLks && rMeeting.X()<=aP2.X()) aP2.setX(rMeeting.X() ); if (bRts && rMeeting.X()>=aP2.X()) aP2.setX(rMeeting.X() ); if (bObn && rMeeting.Y()<=aP2.Y()) aP2.setY(rMeeting.Y() ); if (bUnt && rMeeting.Y()>=aP2.Y()) aP2.setY(rMeeting.Y() );
aXP.Insert(XPOLY_APPEND,aP2,PolyFlags::Normal);
Point aP3(aP2); if ((bLks && rMeeting.X()>aP2.X()) || (bRts && rMeeting.X()<aP2.X())) { // around if (rMeeting.Y()<aP2.Y()) {
aP3.setY(rRect.Top() ); if (rMeeting.Y()<aP3.Y()) aP3.setY(rMeeting.Y() );
} else {
aP3.setY(rRect.Bottom() ); if (rMeeting.Y()>aP3.Y()) aP3.setY(rMeeting.Y() );
}
aXP.Insert(XPOLY_APPEND,aP3,PolyFlags::Normal); if (aP3.Y()!=rMeeting.Y()) {
aP3.setX(rMeeting.X() );
aXP.Insert(XPOLY_APPEND,aP3,PolyFlags::Normal);
}
} if ((bObn && rMeeting.Y()>aP2.Y()) || (bUnt && rMeeting.Y()<aP2.Y())) { // around if (rMeeting.X()<aP2.X()) {
aP3.setX(rRect.Left() ); if (rMeeting.X()<aP3.X()) aP3.setX(rMeeting.X() );
} else {
aP3.setX(rRect.Right() ); if (rMeeting.X()>aP3.X()) aP3.setX(rMeeting.X() );
}
aXP.Insert(XPOLY_APPEND,aP3,PolyFlags::Normal); if (aP3.X()!=rMeeting.X()) {
aP3.setY(rMeeting.Y() );
aXP.Insert(XPOLY_APPEND,aP3,PolyFlags::Normal);
}
} #ifdef DBG_UTIL if (aXP.GetPointCount()>4) {
OSL_FAIL("SdrEdgeObj::ImpCalcObjToCenter(): Polygon has more than 4 points!");
} #endif return aXP;
}
XPolygon SdrEdgeObj::ImpCalcEdgeTrack(const XPolygon& rTrack0, SdrObjConnection& rCon1, SdrObjConnection& rCon2, SdrEdgeInfoRec* pInfo) const
{
Point aPt1,aPt2;
SdrGluePoint aGP1,aGP2;
SdrEscapeDirection nEsc1=SdrEscapeDirection::ALL,nEsc2=SdrEscapeDirection::ALL;
tools::Rectangle aBoundRect1;
tools::Rectangle aBoundRect2;
tools::Rectangle aBewareRect1;
tools::Rectangle aBewareRect2; // first, get the old corner points if (rTrack0.GetPointCount()!=0) {
aPt1=rTrack0[0];
sal_uInt16 nSiz=rTrack0.GetPointCount();
nSiz--;
aPt2=rTrack0[nSiz];
} else
{ const tools::Rectangle& rRectangle = getOutRectangle(); if (!rRectangle.IsEmpty()) {
aPt1 = rRectangle.TopLeft();
aPt2 = rRectangle.BottomRight();
}
}
// #i54102# To allow interactive preview, do also if not inserted constbool bCon1(nullptr != rCon1.m_pSdrObj && rCon1.m_pSdrObj->getSdrPageFromSdrObject() == getSdrPageFromSdrObject()); constbool bCon2(nullptr != rCon2.m_pSdrObj && rCon2.m_pSdrObj->getSdrPageFromSdrObject() == getSdrPageFromSdrObject()); const SfxItemSet& rSet = GetObjectItemSet();
if (bCon1)
{ if (rCon1.m_pSdrObj==static_cast<SdrObject const *>(this))
{ // check, just in case
aBoundRect1 = getOutRectangle();
} else
{ if (getSdrModelFromSdrObject().GetCompatibilityFlag(SdrCompatibilityFlag::ConnectorUseSnapRect))
aBoundRect1 = rCon1.m_pSdrObj->GetSnapRect(); else
aBoundRect1 = rCon1.m_pSdrObj->GetCurrentBoundRect();
}
if (bCon2)
{ if (rCon2.m_pSdrObj==static_cast<SdrObject const *>(this))
{ // check, just in case
aBoundRect2 = getOutRectangle();
} else
{ if (getSdrModelFromSdrObject().GetCompatibilityFlag(SdrCompatibilityFlag::ConnectorUseSnapRect))
aBoundRect2 = rCon2.m_pSdrObj->GetSnapRect(); else
aBoundRect2 = rCon2.m_pSdrObj->GetCurrentBoundRect();
}
for (sal_uInt16 nNum1=0; nNum1<nCount1; nNum1++)
{ if (bAuto1) rCon1.m_nConId=nNum1; if (bCon1 && rCon1.TakeGluePoint(aGP1))
{
aPt1=aGP1.GetPos();
nEsc1=aGP1.GetEscDir(); if (nEsc1==SdrEscapeDirection::SMART) nEsc1=ImpCalcEscAngle(rCon1.m_pSdrObj,aPt1-rCon1.m_aObjOfs);
} for (sal_uInt16 nNum2=0; nNum2<nCount2; nNum2++)
{ if (bAuto2) rCon2.m_nConId=nNum2; if (bCon2 && rCon2.TakeGluePoint(aGP2))
{
aPt2=aGP2.GetPos();
nEsc2=aGP2.GetEscDir(); if (nEsc2==SdrEscapeDirection::SMART) nEsc2=ImpCalcEscAngle(rCon2.m_pSdrObj,aPt2-rCon2.m_aObjOfs);
} for (tools::Long nA1=0; nA1<36000; nA1+=9000)
{
SdrEscapeDirection nE1 = nA1==0 ? SdrEscapeDirection::RIGHT : nA1==9000 ? SdrEscapeDirection::TOP : nA1==18000 ? SdrEscapeDirection::LEFT : nA1==27000 ? SdrEscapeDirection::BOTTOM : SdrEscapeDirection::SMART; for (tools::Long nA2=0; nA2<36000; nA2+=9000)
{
SdrEscapeDirection nE2 = nA2==0 ? SdrEscapeDirection::RIGHT : nA2==9000 ? SdrEscapeDirection::TOP : nA2==18000 ? SdrEscapeDirection::LEFT : nA2==27000 ? SdrEscapeDirection::BOTTOM : SdrEscapeDirection::SMART; if ((nEsc1&nE1) && (nEsc2&nE2))
{
sal_uIntPtr nQual=0;
SdrEdgeInfoRec aInfo; if (pInfo!=nullptr) aInfo=*pInfo;
XPolygon aXP(ImpCalcEdgeTrack(aPt1,nA1,aBoundRect1,aBewareRect1,aPt2,nA2,aBoundRect2,aBewareRect2,&nQual,&aInfo)); if (nQual<nBestQual)
{
aBestXP=std::move(aXP);
nBestQual=nQual;
aBestInfo=aInfo;
nBestAuto1=nNum1;
nBestAuto2=nNum2;
}
}
}
}
}
} if (bAuto1) rCon1.m_nConId=nBestAuto1; if (bAuto2) rCon2.m_nConId=nBestAuto2; if (pInfo!=nullptr) *pInfo=aBestInfo; return aBestXP;
}
XPolygon SdrEdgeObj::ImpCalcEdgeTrack(const Point& rPt1, tools::Long nAngle1, const tools::Rectangle& rBoundRect1, const tools::Rectangle& rBewareRect1, const Point& rPt2, tools::Long nAngle2, const tools::Rectangle& rBoundRect2, const tools::Rectangle& rBewareRect2,
sal_uIntPtr* pnQuality, SdrEdgeInfoRec* pInfo) const
{
SdrEdgeKind eKind=GetObjectItem(SDRATTR_EDGEKIND).GetValue(); bool bRts1=nAngle1==0; bool bObn1=nAngle1==9000; bool bLks1=nAngle1==18000; bool bUnt1=nAngle1==27000; bool bHor1=bLks1 || bRts1; bool bVer1=bObn1 || bUnt1; bool bRts2=nAngle2==0; bool bObn2=nAngle2==9000; bool bLks2=nAngle2==18000; bool bUnt2=nAngle2==27000; bool bHor2=bLks2 || bRts2; bool bVer2=bObn2 || bUnt2; if (pInfo) {
pInfo->m_nAngle1=nAngle1;
pInfo->m_nAngle2=nAngle2;
pInfo->m_nObj1Lines=1;
pInfo->m_nObj2Lines=1;
pInfo->m_nMiddleLine=0xFFFF;
}
Point aPt1(rPt1);
Point aPt2(rPt2);
tools::Rectangle aBoundRect1 (rBoundRect1 );
tools::Rectangle aBoundRect2 (rBoundRect2 );
tools::Rectangle aBewareRect1(rBewareRect1);
tools::Rectangle aBewareRect2(rBewareRect2);
Point aMeeting((aPt1.X()+aPt2.X()+1)/2,(aPt1.Y()+aPt2.Y()+1)/2); if (eKind==SdrEdgeKind::OneLine) {
XPolygon aXP(2);
aXP[0]=rPt1;
aXP[1]=rPt2; if (pnQuality!=nullptr) {
*pnQuality=std::abs(rPt1.X()-rPt2.X())+std::abs(rPt1.Y()-rPt2.Y());
} return aXP;
} elseif (eKind==SdrEdgeKind::ThreeLines) {
XPolygon aXP(4);
aXP[0]=rPt1;
aXP[1]=rPt1;
aXP[2]=rPt2;
aXP[3]=rPt2; if (bRts1) aXP[1].setX(aBewareRect1.Right() ); //+=500; if (bObn1) aXP[1].setY(aBewareRect1.Top() ); //-=500; if (bLks1) aXP[1].setX(aBewareRect1.Left() ); //-=500; if (bUnt1) aXP[1].setY(aBewareRect1.Bottom() ); //+=500; if (bRts2) aXP[2].setX(aBewareRect2.Right() ); //+=500; if (bObn2) aXP[2].setY(aBewareRect2.Top() ); //-=500; if (bLks2) aXP[2].setX(aBewareRect2.Left() ); //-=500; if (bUnt2) aXP[2].setY(aBewareRect2.Bottom() ); //+=500; if (pnQuality!=nullptr) {
tools::Long nQ=std::abs(aXP[1].X()-aXP[0].X())+std::abs(aXP[1].Y()-aXP[0].Y());
nQ+=std::abs(aXP[2].X()-aXP[1].X())+std::abs(aXP[2].Y()-aXP[1].Y());
nQ+=std::abs(aXP[3].X()-aXP[2].X())+std::abs(aXP[3].Y()-aXP[2].Y());
*pnQuality=nQ;
} if (pInfo) {
pInfo->m_nObj1Lines=2;
pInfo->m_nObj2Lines=2; if (bHor1) {
aXP[1].AdjustX(pInfo->m_aObj1Line2.X() );
} else {
aXP[1].AdjustY(pInfo->m_aObj1Line2.Y() );
} if (bHor2) {
aXP[2].AdjustX(pInfo->m_aObj2Line2.X() );
} else {
aXP[2].AdjustY(pInfo->m_aObj2Line2.Y() );
}
} return aXP;
}
sal_uInt16 nIntersections=0;
{
Point aC1(aBewareRect1.Center());
Point aC2(aBewareRect2.Center()); if (aBewareRect1.Left()<=aBewareRect2.Right() && aBewareRect1.Right()>=aBewareRect2.Left()) { // overlapping on the x axis
tools::Long n1=std::max(aBewareRect1.Left(),aBewareRect2.Left());
tools::Long n2=std::min(aBewareRect1.Right(),aBewareRect2.Right());
aMeeting.setX((n1+n2+1)/2 );
} else { // otherwise the center point of the empty space if (aC1.X()<aC2.X()) {
aMeeting.setX((aBewareRect1.Right()+aBewareRect2.Left()+1)/2 );
} else {
aMeeting.setX((aBewareRect1.Left()+aBewareRect2.Right()+1)/2 );
}
} if (aBewareRect1.Top()<=aBewareRect2.Bottom() && aBewareRect1.Bottom()>=aBewareRect2.Top()) { // overlapping on the x axis
tools::Long n1=std::max(aBewareRect1.Top(),aBewareRect2.Top());
tools::Long n2=std::min(aBewareRect1.Bottom(),aBewareRect2.Bottom());
aMeeting.setY((n1+n2+1)/2 );
} else { // otherwise the center point of the empty space if (aC1.Y()<aC2.Y()) {
aMeeting.setY((aBewareRect1.Bottom()+aBewareRect2.Top()+1)/2 );
} else {
aMeeting.setY((aBewareRect1.Top()+aBewareRect2.Bottom()+1)/2 );
}
} // Here, there are three cases: // 1. both go into the same direction // 2. both go into opposite directions // 3. one is vertical, the other is horizontal
tools::Long nXMin=std::min(aBewareRect1.Left(),aBewareRect2.Left());
tools::Long nXMax=std::max(aBewareRect1.Right(),aBewareRect2.Right());
tools::Long nYMin=std::min(aBewareRect1.Top(),aBewareRect2.Top());
tools::Long nYMax=std::max(aBewareRect1.Bottom(),aBewareRect2.Bottom()); bool bBewareOverlap=aBewareRect1.Right()>aBewareRect2.Left() && aBewareRect1.Left()<aBewareRect2.Right() &&
aBewareRect1.Bottom()>aBewareRect2.Top() && aBewareRect1.Top()<aBewareRect2.Bottom(); unsigned nMainCase=3; if (nAngle1==nAngle2) nMainCase=1; elseif ((bHor1 && bHor2) || (bVer1 && bVer2)) nMainCase=2; if (nMainCase==1) { // case 1 (both go in one direction) is possible if (bVer1) aMeeting.setX((aPt1.X()+aPt2.X()+1)/2 ); // Here, this is better than if (bHor1) aMeeting.setY((aPt1.Y()+aPt2.Y()+1)/2 ); // using center point of empty space // bX1Ok means that the vertical exiting Obj1 doesn't conflict with Obj2, ... bool bX1Ok=aPt1.X()<=aBewareRect2.Left() || aPt1.X()>=aBewareRect2.Right(); bool bX2Ok=aPt2.X()<=aBewareRect1.Left() || aPt2.X()>=aBewareRect1.Right(); bool bY1Ok=aPt1.Y()<=aBewareRect2.Top() || aPt1.Y()>=aBewareRect2.Bottom(); bool bY2Ok=aPt2.Y()<=aBewareRect1.Top() || aPt2.Y()>=aBewareRect1.Bottom(); if (bLks1 && (bY1Ok || aBewareRect1.Left()<aBewareRect2.Right()) && (bY2Ok || aBewareRect2.Left()<aBewareRect1.Right())) {
aMeeting.setX(nXMin );
} if (bRts1 && (bY1Ok || aBewareRect1.Right()>aBewareRect2.Left()) && (bY2Ok || aBewareRect2.Right()>aBewareRect1.Left())) {
aMeeting.setX(nXMax );
} if (bObn1 && (bX1Ok || aBewareRect1.Top()<aBewareRect2.Bottom()) && (bX2Ok || aBewareRect2.Top()<aBewareRect1.Bottom())) {
aMeeting.setY(nYMin );
} if (bUnt1 && (bX1Ok || aBewareRect1.Bottom()>aBewareRect2.Top()) && (bX2Ok || aBewareRect2.Bottom()>aBewareRect1.Top())) {
aMeeting.setY(nYMax );
}
} elseif (nMainCase==2) { // case 2: if (bHor1) { // both horizontal /* 9 sub-cases: (legend: line exits to the left (-|), right (|-))
2.1: Facing; overlap only on y axis * * * |--| * * * *
2.2, 2.3: Facing, offset vertically; no overlap on either axis |- * * * * * * -| * * -| * * * * , * * *
2.4, 2.5: One below the other; overlap only on y axis * |- * * * * * -| * * -| * * * * , * |- *
2.6, 2.7: Not facing, offset vertically; no overlap on either axis * * |- * * * * -| * * -| * * * * , * * |-
2.8: Not facing; overlap only on y axis * * * * -| |- * * *
2.9: The objects's BewareRects overlap on x and y axis
These cases, with some modifications are also valid for horizontal line exits. Cases 2.1 through 2.7 are covered well enough with the default meetings. Only for cases 2.8 and 2.9 do we determine special meeting points here.
*/
// normalization; be aR1 the one exiting to the right, // be aR2 the one exiting to the left
tools::Rectangle aBewR1(bRts1 ? aBewareRect1 : aBewareRect2);
tools::Rectangle aBewR2(bRts1 ? aBewareRect2 : aBewareRect1);
tools::Rectangle aBndR1(bRts1 ? aBoundRect1 : aBoundRect2);
tools::Rectangle aBndR2(bRts1 ? aBoundRect2 : aBoundRect1); if (aBewR1.Bottom()>aBewR2.Top() && aBewR1.Top()<aBewR2.Bottom()) { // overlap on y axis; cases 2.1, 2.8, 2.9 if (aBewR1.Right()>aBewR2.Left()) { /* Cases 2.8, 2.9: Case 2.8: always going around on the outside (bDirect=false).
Case 2.9 could also be a direct connection (in the case that the BewareRects overlap only slightly and the BoundRects don't overlap at all and if the line exits would otherwise violate the respective other object's BewareRect).
*/ bool bCase29Direct = false; bool bCase29=aBewR1.Right()>aBewR2.Left(); if (aBndR1.Right()<=aBndR2.Left()) { // case 2.9 without BoundRect overlap if ((aPt1.Y()>aBewareRect2.Top() && aPt1.Y()<aBewareRect2.Bottom()) ||
(aPt2.Y()>aBewareRect1.Top() && aPt2.Y()<aBewareRect1.Bottom())) {
bCase29Direct = true;
}
} if (!bCase29Direct) { bool bObenLang=std::abs(nYMin-aMeeting.Y())<=std::abs(nYMax-aMeeting.Y()); if (bObenLang) {
aMeeting.setY(nYMin );
} else {
aMeeting.setY(nYMax );
} if (bCase29) { // now make sure that the surrounded object // isn't traversed if ((aBewR1.Center().Y()<aBewR2.Center().Y()) != bObenLang) {
aMeeting.setX(aBewR2.Right() );
} else {
aMeeting.setX(aBewR1.Left() );
}
}
} else { // We need a direct connection (3-line Z connection), // because we have to violate the BewareRects. // Use rule of three to scale down the BewareRects.
tools::Long nWant1=aBewR1.Right()-aBndR1.Right(); // distance at Obj1
tools::Long nWant2=aBndR2.Left()-aBewR2.Left(); // distance at Obj2
tools::Long nSpace=aBndR2.Left()-aBndR1.Right(); // available space
tools::Long nGet1=BigMulDiv(nWant1,nSpace,nWant1+nWant2);
tools::Long nGet2=nSpace-nGet1; if (bRts1) { // revert normalization
aBewareRect1.AdjustRight(nGet1-nWant1 );
aBewareRect2.AdjustLeft( -(nGet2-nWant2) );
} else {
aBewareRect2.AdjustRight(nGet1-nWant1 );
aBewareRect1.AdjustLeft( -(nGet2-nWant2) );
}
nIntersections++; // lower quality
}
}
}
} elseif (bVer1) { // both horizontal
tools::Rectangle aBewR1(bUnt1 ? aBewareRect1 : aBewareRect2);
tools::Rectangle aBewR2(bUnt1 ? aBewareRect2 : aBewareRect1);
tools::Rectangle aBndR1(bUnt1 ? aBoundRect1 : aBoundRect2);
tools::Rectangle aBndR2(bUnt1 ? aBoundRect2 : aBoundRect1); if (aBewR1.Right()>aBewR2.Left() && aBewR1.Left()<aBewR2.Right()) { // overlap on y axis; cases 2.1, 2.8, 2.9 if (aBewR1.Bottom()>aBewR2.Top()) { /* Cases 2.8, 2.9 Case 2.8 always going around on the outside (bDirect=false).
Case 2.9 could also be a direct connection (in the case that the BewareRects overlap only slightly and the BoundRects don't overlap at all and if the line exits would otherwise violate the respective other object's BewareRect).
*/ bool bCase29Direct = false; bool bCase29=aBewR1.Bottom()>aBewR2.Top(); if (aBndR1.Bottom()<=aBndR2.Top()) { // case 2.9 without BoundRect overlap if ((aPt1.X()>aBewareRect2.Left() && aPt1.X()<aBewareRect2.Right()) ||
(aPt2.X()>aBewareRect1.Left() && aPt2.X()<aBewareRect1.Right())) {
bCase29Direct = true;
}
} if (!bCase29Direct) { bool bLinksLang=std::abs(nXMin-aMeeting.X())<=std::abs(nXMax-aMeeting.X()); if (bLinksLang) {
aMeeting.setX(nXMin );
} else {
aMeeting.setX(nXMax );
} if (bCase29) { // now make sure that the surrounded object // isn't traversed if ((aBewR1.Center().X()<aBewR2.Center().X()) != bLinksLang) {
aMeeting.setY(aBewR2.Bottom() );
} else {
aMeeting.setY(aBewR1.Top() );
}
}
} else { // We need a direct connection (3-line Z connection), // because we have to violate the BewareRects. // Use rule of three to scale down the BewareRects.
tools::Long nWant1=aBewR1.Bottom()-aBndR1.Bottom(); // difference at Obj1
tools::Long nWant2=aBndR2.Top()-aBewR2.Top(); // difference at Obj2
tools::Long nSpace=aBndR2.Top()-aBndR1.Bottom(); // available space
tools::Long nGet1=BigMulDiv(nWant1,nSpace,nWant1+nWant2);
tools::Long nGet2=nSpace-nGet1; if (bUnt1) { // revert normalization
aBewareRect1.AdjustBottom(nGet1-nWant1 );
aBewareRect2.AdjustTop( -(nGet2-nWant2) );
} else {
aBewareRect2.AdjustBottom(nGet1-nWant1 );
aBewareRect1.AdjustTop( -(nGet2-nWant2) );
}
nIntersections++; // lower quality
}
}
}
}
} elseif (nMainCase==3) { // case 3: one horizontal, the other vertical /* legend: The line exits to the: -| left
|- right
_|_ top
T bottom
* . * . * -- no overlap, at most might touch . . . . . -- overlap * . |- . * -- same height . . . . . -- overlap * . * . * -- no overlap, at most might touch
Overall, there are 96 possible constellations, some of these can't even be unambiguously assigned to a certain case/method of handling.
3.1: All those constellations that are covered reasonably well by the default MeetingPoint (20+12).
T T T . _|_ _|_ . T T T these 12 * . * T * * . * . * * T * . * * . * . * . . . . _|_ _|_ . . . . constellations . . . . . . . . . T . . . . . T . . . . * . |- . * * . -| . * are covered * . |- . _|_ * . |- . T _|_ . -| . * T . -| . * . . . . T T . . . . only in . . . . _|_ . . . . . _|_ . . . . . . . . . _|__|__|_ . T T . _|__|__|_ part: * . * _|_ * * . * . * * _|_ * . * * . * . *
The last 16 of these cases can be excluded, if the objects face each other openly.
3.2: The objects face each other openly, thus a connection using only two lines is possible (4+20); This case is priority #1. * . * . T T . * . * these 20 * . * T * * T * . * * . * . * * . * . * . . . . . . . . . . constellations . . . T T T T . . . . . . . . . . . . . * . |- . * * . -| . * are covered * . |-_|__|_ _|__|_-| . * * . |- T T T T -| . * . . . . . . . . . . only in . . . _|__|_ _|__|_ . . . . . . . . . . . . . * . * . _|_ _|_ . * . * part: * . * _|_ * * _|_ * . * * . * . * * . * . *
3.3: The line exits point away from the other object or miss its back (52+4). _|__|__|__|_ * * _|__|__|__|_ * . . . * * . * . * these 4 * . * . * * . * . * _|__|__|__|_ . . _|__|__|__|_ T T T . . . . T T T constellations . . . T . . T . . . _|__|_ |- . * * . -| _|__|_ T T |- . * * . -| T T are covered * . |- . * * . -| . * _|__|__|_ . . . . _|__|__|_ T T T T . . T T T T only in . . . _|_ . . _|_ . . . * . * . * * . * . * T T T T * * T T T T part: * . * . * * . * . *
*/
// case 3.2
tools::Rectangle aTmpR1(aBewareRect1);
tools::Rectangle aTmpR2(aBewareRect2); if (bBewareOverlap) { // overlapping BewareRects: use BoundRects for checking for case 3.2
aTmpR1=aBoundRect1;
aTmpR2=aBoundRect2;
} if ((((bRts1 && aTmpR1.Right ()<=aPt2.X()) || (bLks1 && aTmpR1.Left()>=aPt2.X())) &&
((bUnt2 && aTmpR2.Bottom()<=aPt1.Y()) || (bObn2 && aTmpR2.Top ()>=aPt1.Y()))) ||
(((bRts2 && aTmpR2.Right ()<=aPt1.X()) || (bLks2 && aTmpR2.Left()>=aPt1.X())) &&
((bUnt1 && aTmpR1.Bottom()<=aPt2.Y()) || (bObn1 && aTmpR1.Top ()>=aPt2.Y())))) { // case 3.2 applies: connector with only 2 lines if (bHor1) {
aMeeting.setX(aPt2.X() );
aMeeting.setY(aPt1.Y() );
} else {
aMeeting.setX(aPt1.X() );
aMeeting.setY(aPt2.Y() );
} // in the case of overlapping BewareRects:
aBewareRect1=aTmpR1;
aBewareRect2=aTmpR2;
} elseif ((((bRts1 && aBewareRect1.Right ()>aBewareRect2.Left ()) ||
(bLks1 && aBewareRect1.Left ()<aBewareRect2.Right ())) &&
((bUnt2 && aBewareRect2.Bottom()>aBewareRect1.Top ()) ||
(bObn2 && aBewareRect2.Top ()<aBewareRect1.Bottom()))) ||
(((bRts2 && aBewareRect2.Right ()>aBewareRect1.Left ()) ||
(bLks2 && aBewareRect2.Left ()<aBewareRect1.Right ())) &&
((bUnt1 && aBewareRect1.Bottom()>aBewareRect2.Top ()) ||
(bObn1 && aBewareRect1.Top ()<aBewareRect2.Bottom())))) { // case 3.3 if (bRts1 || bRts2) { aMeeting.setX(nXMax ); } if (bLks1 || bLks2) { aMeeting.setX(nXMin ); } if (bUnt1 || bUnt2) { aMeeting.setY(nYMax ); } if (bObn1 || bObn2) { aMeeting.setY(nYMin ); }
}
}
}
XPolygon aXP1(ImpCalcObjToCenter(aPt1,nAngle1,aBewareRect1,aMeeting));
XPolygon aXP2(ImpCalcObjToCenter(aPt2,nAngle2,aBewareRect2,aMeeting));
sal_uInt16 nXP1Cnt=aXP1.GetPointCount();
sal_uInt16 nXP2Cnt=aXP2.GetPointCount();
assert(nXP1Cnt >= 2 && nXP2Cnt >= 2 && "ImpCalcObjToCenter inserts a min of 2 points"); if (pInfo) {
pInfo->m_nObj1Lines=nXP1Cnt; pInfo->m_nObj1Lines--;
pInfo->m_nObj2Lines=nXP2Cnt; pInfo->m_nObj2Lines--;
}
Point aEP1(aXP1[nXP1Cnt-1]);
Point aEP2(aXP2[nXP2Cnt-1]); bool bInsMeetingPoint=aEP1.X()!=aEP2.X() && aEP1.Y()!=aEP2.Y(); bool bHorzE1=aEP1.Y()==aXP1[nXP1Cnt-2].Y(); // is last line of XP1 horizontal? bool bHorzE2=aEP2.Y()==aXP2[nXP2Cnt-2].Y(); // is last line of XP2 horizontal? if (aEP1==aEP2 && ((bHorzE1 && bHorzE2 && aEP1.Y()==aEP2.Y()) || (!bHorzE1 && !bHorzE2 && aEP1.X()==aEP2.X()))) { // special casing 'I' connectors
nXP1Cnt--; aXP1.Remove(nXP1Cnt,1);
nXP2Cnt--; aXP2.Remove(nXP2Cnt,1);
} if (bInsMeetingPoint) {
aXP1.Insert(XPOLY_APPEND,aMeeting,PolyFlags::Normal); if (pInfo) { // Inserting a MeetingPoint adds 2 new lines, // either might become the center line. if (pInfo->m_nObj1Lines==pInfo->m_nObj2Lines) {
pInfo->m_nObj1Lines++;
pInfo->m_nObj2Lines++;
} else { if (pInfo->m_nObj1Lines>pInfo->m_nObj2Lines) {
pInfo->m_nObj2Lines++;
pInfo->m_nMiddleLine=nXP1Cnt-1;
} else {
pInfo->m_nObj1Lines++;
pInfo->m_nMiddleLine=nXP1Cnt;
}
}
}
} elseif (pInfo && aEP1!=aEP2 && nXP1Cnt+nXP2Cnt>=4) { // By connecting both ends, another line is added, this becomes the center line.
pInfo->m_nMiddleLine=nXP1Cnt-1;
}
sal_uInt16 nNum=aXP2.GetPointCount(); if (nXP1Cnt > 1 && nXP2Cnt > 1 && aXP1[nXP1Cnt-1] == aXP2[nXP2Cnt-1]) nNum--; while (nNum>0) {
nNum--;
aXP1.Insert(XPOLY_APPEND,aXP2[nNum],PolyFlags::Normal);
}
sal_uInt16 nPointCount=aXP1.GetPointCount(); char cForm; if (pInfo || pnQuality!=nullptr) { if (nPointCount==2) cForm='I'; elseif (nPointCount==3) cForm='L'; elseif (nPointCount==4) { // Z or U if (nAngle1==nAngle2) cForm='U'; else cForm='Z';
} elseif (nPointCount==6) { // S or C or ... if (nAngle1!=nAngle2) { // For type S, line 2 has the same direction as line 4. // For type C, the opposite is true.
Point aP1(aXP1[1]);
Point aP2(aXP1[2]);
Point aP3(aXP1[3]);
Point aP4(aXP1[4]); if (aP1.Y()==aP2.Y()) { // else both lines are horizontal if ((aP1.X()<aP2.X())==(aP3.X()<aP4.X())) cForm='S'; else cForm='C';
} else { // else both lines are vertical if ((aP1.Y()<aP2.Y())==(aP3.Y()<aP4.Y())) cForm='S'; else cForm='C';
}
} else cForm='4'; // else is case 3 with 5 lines
} else cForm='?'; // more shapes: if (pInfo) { if (cForm=='I' || cForm=='L' || cForm=='Z' || cForm=='U') {
pInfo->m_nObj1Lines=1;
pInfo->m_nObj2Lines=1; if (cForm=='Z' || cForm=='U') {
pInfo->m_nMiddleLine=1;
} else {
pInfo->m_nMiddleLine=0xFFFF;
}
} elseif (cForm=='S' || cForm=='C') {
pInfo->m_nObj1Lines=2;
pInfo->m_nObj2Lines=2;
pInfo->m_nMiddleLine=2;
}
}
} else
{
cForm = 0;
} if (pnQuality!=nullptr) {
sal_uIntPtr nQual=0;
sal_uIntPtr nQual0=nQual; // prevent overruns bool bOverflow = false;
Point aPt0(aXP1[0]); for (sal_uInt16 nPntNum=1; nPntNum<nPointCount; nPntNum++) {
Point aPt1b(aXP1[nPntNum]);
nQual+=std::abs(aPt1b.X()-aPt0.X())+std::abs(aPt1b.Y()-aPt0.Y()); if (nQual<nQual0) bOverflow = true;
nQual0=nQual;
aPt0=aPt1b;
}
sal_uInt16 nTmp=nPointCount; if (cForm=='Z') {
nTmp=2; // Z shape with good quality (nTmp=2 instead of 4)
sal_uIntPtr n1=std::abs(aXP1[1].X()-aXP1[0].X())+std::abs(aXP1[1].Y()-aXP1[0].Y());
sal_uIntPtr n2=std::abs(aXP1[2].X()-aXP1[1].X())+std::abs(aXP1[2].Y()-aXP1[1].Y());
sal_uIntPtr n3=std::abs(aXP1[3].X()-aXP1[2].X())+std::abs(aXP1[3].Y()-aXP1[2].Y()); // try to make lines lengths similar
sal_uIntPtr nBesser=0;
n1+=n3;
n3=n2/4; if (n1>=n2) nBesser=6; elseif (n1>=3*n3) nBesser=4; elseif (n1>=2*n3) nBesser=2; if (aXP1[0].Y()!=aXP1[1].Y()) nBesser++; // vertical starting line gets a plus (for H/V-Prio) if (nQual>nBesser) nQual-=nBesser; else nQual=0;
} if (nTmp>=3) {
nQual0=nQual;
nQual+=static_cast<sal_uIntPtr>(nTmp)*0x01000000; if (nQual<nQual0 || nTmp>15) bOverflow = true;
} if (nPointCount>=2) { // check exit angle again
Point aP1(aXP1[1]); aP1-=aXP1[0];
Point aP2(aXP1[nPointCount-2]); aP2-=aXP1[nPointCount-1];
tools::Long nAng1=0; if (aP1.X()<0) nAng1=18000; if (aP1.Y()>0) nAng1=27000; if (aP1.Y()<0) nAng1=9000; if (aP1.X()!=0 && aP1.Y()!=0) nAng1=1; // slant?!
tools::Long nAng2=0; if (aP2.X()<0) nAng2=18000; if (aP2.Y()>0) nAng2=27000; if (aP2.Y()<0) nAng2=9000; if (aP2.X()!=0 && aP2.Y()!=0) nAng2=1; // slant?! if (nAng1!=nAngle1) nIntersections++; if (nAng2!=nAngle2) nIntersections++;
}
// For the quality check, use the original Rects and at the same time // check whether one them was scaled down for the calculation of the // Edges (e. g. case 2.9)
aBewareRect1=rBewareRect1;
aBewareRect2=rBewareRect2;
for (sal_uInt16 i=0; i<nPointCount; i++) {
Point aPt1b(aXP1[i]); bool b1=aPt1b.X()>aBewareRect1.Left() && aPt1b.X()<aBewareRect1.Right() &&
aPt1b.Y()>aBewareRect1.Top() && aPt1b.Y()<aBewareRect1.Bottom(); bool b2=aPt1b.X()>aBewareRect2.Left() && aPt1b.X()<aBewareRect2.Right() &&
aPt1b.Y()>aBewareRect2.Top() && aPt1b.Y()<aBewareRect2.Bottom();
sal_uInt16 nInt0=nIntersections; if (i==0 || i==nPointCount-1) { if (b1 && b2) nIntersections++;
} else { if (b1) nIntersections++; if (b2) nIntersections++;
} // check for overlaps if (i>0 && nInt0==nIntersections) { if (aPt0.Y()==aPt1b.Y()) { // horizontal line if (aPt0.Y()>aBewareRect1.Top() && aPt0.Y()<aBewareRect1.Bottom() &&
((aPt0.X()<=aBewareRect1.Left() && aPt1b.X()>=aBewareRect1.Right()) ||
(aPt1b.X()<=aBewareRect1.Left() && aPt0.X()>=aBewareRect1.Right()))) nIntersections++; if (aPt0.Y()>aBewareRect2.Top() && aPt0.Y()<aBewareRect2.Bottom() &&
((aPt0.X()<=aBewareRect2.Left() && aPt1b.X()>=aBewareRect2.Right()) ||
(aPt1b.X()<=aBewareRect2.Left() && aPt0.X()>=aBewareRect2.Right()))) nIntersections++;
} else { // vertical line if (aPt0.X()>aBewareRect1.Left() && aPt0.X()<aBewareRect1.Right() &&
((aPt0.Y()<=aBewareRect1.Top() && aPt1b.Y()>=aBewareRect1.Bottom()) ||
(aPt1b.Y()<=aBewareRect1.Top() && aPt0.Y()>=aBewareRect1.Bottom()))) nIntersections++; if (aPt0.X()>aBewareRect2.Left() && aPt0.X()<aBewareRect2.Right() &&
((aPt0.Y()<=aBewareRect2.Top() && aPt1b.Y()>=aBewareRect2.Bottom()) ||
(aPt1b.Y()<=aBewareRect2.Top() && aPt0.Y()>=aBewareRect2.Bottom()))) nIntersections++;
}
}
aPt0=aPt1b;
} if (nPointCount<=1) nIntersections++;
nQual0=nQual;
nQual+=static_cast<sal_uIntPtr>(nIntersections)*0x10000000; if (nQual<nQual0 || nIntersections>15) bOverflow = true;
if (bOverflow || nQual==0xFFFFFFFF) nQual=0xFFFFFFFE;
*pnQuality=nQual;
} if (pInfo) { // now apply line offsets to aXP1 if (pInfo->m_nMiddleLine!=0xFFFF) {
sal_uInt16 nIdx=pInfo->ImpGetPolyIdx(SdrEdgeLineCode::MiddleLine,aXP1); if (pInfo->ImpIsHorzLine(SdrEdgeLineCode::MiddleLine,aXP1)) {
aXP1[nIdx].AdjustY(pInfo->m_aMiddleLine.Y() );
aXP1[nIdx+1].AdjustY(pInfo->m_aMiddleLine.Y() );
} else {
aXP1[nIdx].AdjustX(pInfo->m_aMiddleLine.X() );
aXP1[nIdx+1].AdjustX(pInfo->m_aMiddleLine.X() );
}
} if (pInfo->m_nObj1Lines>=2) {
sal_uInt16 nIdx=pInfo->ImpGetPolyIdx(SdrEdgeLineCode::Obj1Line2,aXP1); if (pInfo->ImpIsHorzLine(SdrEdgeLineCode::Obj1Line2,aXP1)) {
aXP1[nIdx].AdjustY(pInfo->m_aObj1Line2.Y() );
aXP1[nIdx+1].AdjustY(pInfo->m_aObj1Line2.Y() );
} else {
aXP1[nIdx].AdjustX(pInfo->m_aObj1Line2.X() );
aXP1[nIdx+1].AdjustX(pInfo->m_aObj1Line2.X() );
}
} if (pInfo->m_nObj1Lines>=3) {
sal_uInt16 nIdx=pInfo->ImpGetPolyIdx(SdrEdgeLineCode::Obj1Line3,aXP1); if (pInfo->ImpIsHorzLine(SdrEdgeLineCode::Obj1Line3,aXP1)) {
aXP1[nIdx].AdjustY(pInfo->m_aObj1Line3.Y() );
aXP1[nIdx+1].AdjustY(pInfo->m_aObj1Line3.Y() );
} else {
aXP1[nIdx].AdjustX(pInfo->m_aObj1Line3.X() );
aXP1[nIdx+1].AdjustX(pInfo->m_aObj1Line3.X() );
}
} if (pInfo->m_nObj2Lines>=2) {
sal_uInt16 nIdx=pInfo->ImpGetPolyIdx(SdrEdgeLineCode::Obj2Line2,aXP1); if (pInfo->ImpIsHorzLine(SdrEdgeLineCode::Obj2Line2,aXP1)) {
aXP1[nIdx].AdjustY(pInfo->m_aObj2Line2.Y() );
aXP1[nIdx+1].AdjustY(pInfo->m_aObj2Line2.Y() );
} else {
aXP1[nIdx].AdjustX(pInfo->m_aObj2Line2.X() );
aXP1[nIdx+1].AdjustX(pInfo->m_aObj2Line2.X() );
}
} if (pInfo->m_nObj2Lines>=3) {
sal_uInt16 nIdx=pInfo->ImpGetPolyIdx(SdrEdgeLineCode::Obj2Line3,aXP1); if (pInfo->ImpIsHorzLine(SdrEdgeLineCode::Obj2Line3,aXP1)) {
aXP1[nIdx].AdjustY(pInfo->m_aObj2Line3.Y() );
aXP1[nIdx+1].AdjustY(pInfo->m_aObj2Line3.Y() );
} else {
aXP1[nIdx].AdjustX(pInfo->m_aObj2Line3.X() );
aXP1[nIdx+1].AdjustX(pInfo->m_aObj2Line3.X() );
}
}
} // make the connector a bezier curve, if appropriate if (eKind != SdrEdgeKind::Bezier || nPointCount <= 2) return aXP1;
if (pInfo && pInfo->m_bUseOOXMLCurve) // Routing method OOXML
{ // The additional points needed are located on the segments of the path of the // corresponding bentConnector as calculated above. auto SegmentPoint = [&aXP1](const sal_uInt16& nEnd, constdouble& fFactor) { return Point(
aXP1[nEnd - 1].X() + basegfx::fround<tools::Long>(fFactor * (aXP1[nEnd].X() - aXP1[nEnd - 1].X())),
aXP1[nEnd - 1].Y() + basegfx::fround<tools::Long>(fFactor * (aXP1[nEnd].Y() - aXP1[nEnd - 1].Y())));
};
// We change the path going from end to start. Thus inserting points does not affect the index // of the preceding points. // The end point has index nPointCount-1 and is a normal point and kept. // Insert new control point in the middle of last segments.
Point aControl = SegmentPoint(nPointCount - 1, 0.5); // Insert happens before specified index.
aXP1.Insert(nPointCount - 1, aControl, PolyFlags::Control); for (sal_uInt16 nSegment = nPointCount - 2; nSegment > 1; --nSegment)
{ // We need a normal point at center of segment and control points at 1/4 and 3/4 of // segment. At center and 1/4 are new points, at 3/4 will be replacement for the end // point of the segment.
aControl = SegmentPoint(nSegment, 0.25);
Point aNormal = SegmentPoint(nSegment, 0.5);
aXP1.SetFlags(nSegment, PolyFlags::Control);
aXP1[nSegment] = SegmentPoint(nSegment, 0.75);
aXP1.Insert(nSegment, aNormal, PolyFlags::Normal);
aXP1.Insert(nSegment, aControl, PolyFlags::Control);
} // The first segments needs a control point in the middle. It is replacement for the // second point.
aXP1.SetFlags(1, PolyFlags::Control);
aXP1[1] = SegmentPoint(1, 0.5);
} else// Routing method LO
{
Point* pPt1=&aXP1[0];
Point* pPt2=&aXP1[1];
Point* pPt3=&aXP1[nPointCount-2];
Point* pPt4=&aXP1[nPointCount-1];
tools::Long dx1=pPt2->X()-pPt1->X();
tools::Long dy1=pPt2->Y()-pPt1->Y();
tools::Long dx2=pPt3->X()-pPt4->X();
tools::Long dy2=pPt3->Y()-pPt4->Y(); if (cForm=='L') { // nPointCount==3
aXP1.SetFlags(1,PolyFlags::Control);
Point aPt3(*pPt2);
aXP1.Insert(2,aPt3,PolyFlags::Control);
nPointCount=aXP1.GetPointCount();
pPt2=&aXP1[1];
pPt3=&aXP1[nPointCount-2];
pPt2->AdjustX( -(dx1/3) );
pPt2->AdjustY( -(dy1/3) );
pPt3->AdjustX( -(dx2/3) );
pPt3->AdjustY( -(dy2/3) );
} elseif (nPointCount>=4 && nPointCount<=6) { // Z or U or ... // To all others, the end points of the original lines become control // points for now. Thus, we need to do some more work for nPointCount>4!
aXP1.SetFlags(1,PolyFlags::Control);
aXP1.SetFlags(nPointCount-2,PolyFlags::Control); // distance x1.5
pPt2->AdjustX(dx1/2 );
pPt2->AdjustY(dy1/2 );
pPt3->AdjustX(dx2/2 );
pPt3->AdjustY(dy2/2 ); if (nPointCount==5) { // add a control point before and after center
Point aCenter(aXP1[2]);
tools::Long dx1b=aCenter.X()-aXP1[1].X();
tools::Long dy1b=aCenter.Y()-aXP1[1].Y();
tools::Long dx2b=aCenter.X()-aXP1[3].X();
tools::Long dy2b=aCenter.Y()-aXP1[3].Y();
aXP1.Insert(2,aCenter,PolyFlags::Control);
aXP1.SetFlags(3,PolyFlags::Symmetric);
aXP1.Insert(4,aCenter,PolyFlags::Control);
aXP1[2].AdjustX( -(dx1b/2) );
aXP1[2].AdjustY( -(dy1b/2) );
aXP1[3].AdjustX( -((dx1b+dx2b)/4) );
aXP1[3].AdjustY( -((dy1b+dy2b)/4) );
aXP1[4].AdjustX( -(dx2b/2) );
aXP1[4].AdjustY( -(dy2b/2) );
} if (nPointCount==6) {
Point aPt1b(aXP1[2]);
Point aPt2b(aXP1[3]);
aXP1.Insert(2,aPt1b,PolyFlags::Control);
aXP1.Insert(5,aPt2b,PolyFlags::Control);
tools::Long dx=aPt1b.X()-aPt2b.X();
tools::Long dy=aPt1b.Y()-aPt2b.Y();
aXP1[3].AdjustX( -(dx/2) );
aXP1[3].AdjustY( -(dy/2) );
aXP1.SetFlags(3,PolyFlags::Symmetric);
aXP1.Remove(4,1); // because it's identical with aXP1[3]
}
}
} return aXP1;
}
/* There could be a maximum of 64 different developments with 5 lines, a maximum of 32 developments with 4 lines, a maximum of 16 developments with 3 lines, a maximum of 8 developments with 2 lines. This gives us a total of 124 possibilities. Normalized for the 1st exit angle to the right, there remain 31 possibilities. Now, normalizing away the vertical mirroring, we get to a total of 16 characteristic developments with 1 through 5 lines:
1 line (type "I") --
2 lines (type "L") __|
3 lines (type "U") __ (type "Z") _ __| _| _ _ 4 lines #1 _| #2 | | #3 |_ #4 | | _| _| _| _| Of these, #1 is implausible, #2 is a rotated version of #3. This leaves #2 (from now on referred to as 4.1) and #4 (from now on referred to as 4.2). _ _ 5 lines #1 _| #2 _| #3 ___ #4 _ _| _| _| _| _| |_ _ _ _ #5 |_ #6 |_ #7 _| | #8 ____ _| _| _| |_ _| Of these, 5.1, 5.2, 5.4 and 5.5 are implausible, 5.7 is a reversed version of 5.3. This leaves 5.3 (type "4"), 5.6 (type "S") and 5.8 (type "C").
We now have discerned the 9 basic types to cover all 400 possible constellations of object positions and exit angles. 4 of the 9 types have got a center line (CL). The number of object margins per object varies between 0 and 3:
CL O1 O2 Note "I": n 0 0 "L": n 0 0 "U": n 0-1 0-1 "Z": y 0 0 4.2: y 0 1 = U+1, respectively 1+U 4.4: n 0-2 0-2 = Z+1 "4": y 0 2 = Z+2 "S": y 1 1 = 1+Z+1 "C": n 0-3 0-3 = 1+U+1
*/
void SdrEdgeObj::Notify(SfxBroadcaster& rBC, const SfxHint& rHint)
{ const SfxHintId nId = rHint.GetId(); bool bDataChg=nId==SfxHintId::DataChanged; bool bDying=nId==SfxHintId::Dying; bool bObj1=m_aCon1.m_pSdrObj!=nullptr && m_aCon1.m_pSdrObj->GetBroadcaster()==&rBC; bool bObj2=m_aCon2.m_pSdrObj!=nullptr && m_aCon2.m_pSdrObj->GetBroadcaster()==&rBC; if (bDying && (bObj1 || bObj2)) { // catch Dying, so AttrObj doesn't start broadcasting // about an alleged change of template if (bObj1) m_aCon1.m_pSdrObj=nullptr; if (bObj2) m_aCon2.m_pSdrObj=nullptr; return;
} if ( bObj1 || bObj2 )
{
m_bEdgeTrackUserDefined = false;
}
SdrTextObj::Notify(rBC,rHint); if (m_nNotifyingCount!=0)return;
// a locking flag
m_nNotifyingCount++; const SdrHint* pSdrHint = ( rHint.GetId() == SfxHintId::ThisIsAnSdrHint ? static_cast<const SdrHint*>(&rHint) : nullptr );
if (bDataChg) { // StyleSheet changed
ImpSetAttrToEdgeInfo(); // when changing templates, copy values from Pool to aEdgeInfo
} if (bDataChg ||
(bObj1 && m_aCon1.m_pSdrObj->getSdrPageFromSdrObject() == getSdrPageFromSdrObject()) ||
(bObj2 && m_aCon2.m_pSdrObj->getSdrPageFromSdrObject() == getSdrPageFromSdrObject()) ||
(pSdrHint && pSdrHint->GetKind()==SdrHintKind::ObjectRemoved))
{ // broadcasting only, if on the same page
tools::Rectangle aBoundRect0; if (m_pUserCall!=nullptr) aBoundRect0=GetCurrentBoundRect();
ImpDirtyEdgeTrack();
// only redraw here, object hasn't actually changed
ActionChanged();
/** updates edges that are connected to the edges of this object as if the connected objects sent a repaint broadcast
*/ void SdrEdgeObj::Reformat()
{ if( nullptr != m_aCon1.m_pSdrObj )
{
SfxHint aHint( SfxHintId::DataChanged );
Notify( *m_aCon1.m_pSdrObj->GetBroadcaster(), aHint );
}
// #i110629# also set aRect and maSnapeRect depending on pEdgeTrack const tools::Rectangle aPolygonBounds(m_pEdgeTrack->GetBoundRect());
setRectangle(aPolygonBounds);
maSnapRect = aPolygonBounds;
}
}
// copy connections for clone, SdrEdgeObj::operator= does not do this
pRetval->ConnectToNode(true, GetConnectedNode(true));
pRetval->ConnectToNode(false, GetConnectedNode(false));
if(!bOriginalEdgeModified && pOriginalEdge)
{ // copy connections when clone is modified. This is needed because // as preparation to this modification the data from the original object // was copied to the clone using the operator=. As can be seen there, // that operator does not copy the connections (for good reason)
ConnectToNode(true, pOriginalEdge->GetConnection(true).GetSdrObject());
ConnectToNode(false, pOriginalEdge->GetConnection(false).GetSdrObject());
}
if(rDragStat.GetHdl()->GetPointNum() < 2)
{ // start or end point connector drag constbool bDragA(0 == rDragStat.GetHdl()->GetPointNum()); const Point aPointNow(rDragStat.GetNow());
// look for new connection
ImpFindConnector(aPointNow, *rDragStat.GetPageView(), *pDraggedOne, pOriginalEdge, nullptr, &rDragStat);
if(pDraggedOne->m_pSdrObj)
{ // if found, officially connect to it; ImpFindConnector only // sets pObj hard
SdrObject* pNewConnection = pDraggedOne->m_pSdrObj;
pDraggedOne->m_pSdrObj = nullptr;
ConnectToNode(bDragA, pNewConnection);
}
if(rDragStat.GetView() && !bOriginalEdgeModified)
{ // show IA helper, but only do this during IA, so not when the original // Edge gets modified in the last call
rDragStat.GetView()->SetConnectMarker(*pDraggedOne);
}
}
// force recalculation of EdgeTrack
*m_pEdgeTrack = ImpCalcEdgeTrack(*m_pEdgeTrack, m_aCon1, m_aCon2, &m_aEdgeInfo);
m_bEdgeTrackDirty=false;
// save EdgeInfos and mark object as user modified
ImpSetEdgeInfoToAttr();
m_bEdgeTrackUserDefined = false;
SetBoundAndSnapRectsDirty();
if(bOriginalEdgeModified && rDragStat.GetView())
{ // hide connect marker helper again when original gets changed. // This happens at the end of the interaction
rDragStat.GetView()->HideConnectMarker();
}
// if resize is not from paste, forget user distances if (!getSdrModelFromSdrObject().IsPasteResize())
{
m_aEdgeInfo.m_aObj1Line2 = Point();
m_aEdgeInfo.m_aObj1Line3 = Point();
m_aEdgeInfo.m_aObj2Line2 = Point();
m_aEdgeInfo.m_aObj2Line3 = Point();
m_aEdgeInfo.m_aMiddleLine = Point();
}
}
// #i54102# added rotation support void SdrEdgeObj::NbcRotate(const Point& rRef, Degree100 nAngle, double sn, double cs)
{ if(m_bEdgeTrackUserDefined)
{ // #i120437# special handling when track is imported, apply // transformation directly to imported track.
SdrTextObj::NbcRotate(rRef, nAngle, sn, cs);
RotateXPoly(*m_pEdgeTrack, rRef, sn, cs);
} else
{ // handle start and end point if not connected constbool bCon1(nullptr != m_aCon1.m_pSdrObj && m_aCon1.m_pSdrObj->getSdrPageFromSdrObject() == getSdrPageFromSdrObject()); constbool bCon2(nullptr != m_aCon2.m_pSdrObj && m_aCon2.m_pSdrObj->getSdrPageFromSdrObject() == getSdrPageFromSdrObject());
void SdrEdgeObj::RestoreGeoData(const SdrObjGeoData& rGeo)
{
SdrTextObj::RestoreGeoData(rGeo); const SdrEdgeObjGeoData& rEGeo=static_cast<const SdrEdgeObjGeoData&>(rGeo); if (m_aCon1.m_pSdrObj!=rEGeo.m_aCon1.m_pSdrObj) { if (m_aCon1.m_pSdrObj!=nullptr) m_aCon1.m_pSdrObj->RemoveListener(*this);
m_aCon1=rEGeo.m_aCon1; if (m_aCon1.m_pSdrObj!=nullptr) m_aCon1.m_pSdrObj->AddListener(*this);
} else
m_aCon1=rEGeo.m_aCon1;
if (m_aCon2.m_pSdrObj!=rEGeo.m_aCon2.m_pSdrObj) { if (m_aCon2.m_pSdrObj!=nullptr) m_aCon2.m_pSdrObj->RemoveListener(*this);
m_aCon2=rEGeo.m_aCon2; if (m_aCon2.m_pSdrObj!=nullptr) m_aCon2.m_pSdrObj->AddListener(*this);
} else
m_aCon2=rEGeo.m_aCon2;
/** this method is used by the api to set a gluepoint for a connection nId == -1 : The best default point is automatically chosen 0 <= nId <= 3 : One of the default points is chosen nId >= 4 : A user defined gluepoint is chosen
*/ void SdrEdgeObj::setGluePointIndex( bool bTail, sal_Int32 nIndex /* = -1 */ )
{
SdrObjConnection& rConn1 = GetConnection( bTail );
if( nIndex > 3 )
{
nIndex -= 3; // the start api index is 0, whereas the implementation in svx starts from 1
// for user defined gluepoints we have // to get the id for this index first const SdrGluePointList* pList = rConn1.GetSdrObject() ? rConn1.GetSdrObject()->GetGluePointList() : nullptr; if( pList == nullptr || SDRGLUEPOINT_NOTFOUND == pList->FindGluePoint(static_cast<sal_uInt16>(nIndex)) ) return;
} elseif( nIndex < 0 )
{
nIndex = 0;
}
/** this method is used by the api to return a gluepoint id for a connection.
See setGluePointId for possible return values */
sal_Int32 SdrEdgeObj::getGluePointIndex( bool bTail )
{
SdrObjConnection& rConn1 = GetConnection( bTail );
sal_Int32 nId = -1; if( !rConn1.IsBestConnection() )
{
nId = rConn1.GetConnectorId(); if( !rConn1.IsAutoVertex() )
nId += 3; // the start api index is 0, whereas the implementation in svx starts from 1
} return nId;
}
// Implementation was missing; edge track needs to be invalidated additionally. void SdrEdgeObj::NbcSetAnchorPos(const Point& rPnt)
{ // call parent functionality
SdrTextObj::NbcSetAnchorPos(rPnt);
bool SdrEdgeObj::TRGetBaseGeometry(basegfx::B2DHomMatrix& rMatrix, basegfx::B2DPolyPolygon& rPolyPolygon) const
{ // use base method from SdrObject, it's not rotatable and // a call to GetSnapRect() is used. That's what we need for Connector. return SdrObject::TRGetBaseGeometry(rMatrix, rPolyPolygon);
}
void SdrEdgeObj::TRSetBaseGeometry(const basegfx::B2DHomMatrix& rMatrix, const basegfx::B2DPolyPolygon& rPolyPolygon)
{ // where appropriate take care for existing connections. For now, just use the // implementation from SdrObject.
SdrObject::TRSetBaseGeometry(rMatrix, rPolyPolygon);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.