1 /*
2 For general Scribus (>=1.3.2) copyright and licensing information please refer
3 to the COPYING file provided with the program. Following this notice may exist
4 a copyright and/or license notice that predates the release of Scribus 1.3.2
5 for which a new license (GPL+exception) is in place.
6 */
7 /***************************************************************************
8 *   Copyright (C) 2007 by Franz Schmid                                     *
9 *   franz.schmid@altmuehlnet.de                                            *
10 *                                                                          *
11 *   This program is free software; you can redistribute it and/or modify   *
12 *   it under the terms of the GNU General Public License as published by   *
13 *   the Free Software Foundation; either version 2 of the License, or      *
14 *   (at your option) any later version.                                    *
15 *                                                                          *
16 *   This program is distributed in the hope that it will be useful,        *
17 *   but WITHOUT ANY WARRANTY; without even the implied warranty of         *
18 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
19 *   GNU General Public License for more details.                           *
20 *                                                                          *
21 *   You should have received a copy of the GNU General Public License      *
22 *   along with this program; if not, write to the                          *
23 *   Free Software Foundation, Inc.,                                        *
24 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.              *
25 ****************************************************************************/
26 
27 #include "pathalongpath.h"
28 
29 #include "appmodes.h"
30 #include "pageitem_group.h"
31 #include "pathdialog.h"
32 #include "scribuscore.h"
33 #include "scribusview.h"
34 #include "util.h"
35 #include "util_math.h"
36 
37 
pathalongpath_getPluginAPIVersion()38 int pathalongpath_getPluginAPIVersion()
39 {
40 	return PLUGIN_API_VERSION;
41 }
42 
pathalongpath_getPlugin()43 ScPlugin* pathalongpath_getPlugin()
44 {
45 	PathAlongPathPlugin* plug = new PathAlongPathPlugin();
46 	Q_CHECK_PTR(plug);
47 	return plug;
48 }
49 
pathalongpath_freePlugin(ScPlugin * plugin)50 void pathalongpath_freePlugin(ScPlugin* plugin)
51 {
52 	PathAlongPathPlugin* plug = dynamic_cast<PathAlongPathPlugin*>(plugin);
53 	Q_ASSERT(plug);
54 	delete plug;
55 }
56 
PathAlongPathPlugin()57 PathAlongPathPlugin::PathAlongPathPlugin() :
58 	patternItem(nullptr),
59 	pathItem(nullptr),
60 	originalRot(0.0),
61 	originalXPos(0.0),
62 	originalYPos(0.0),
63 	m_doc(nullptr),
64 	firstUpdate(false),
65 	m_scaling(0.0),
66 	nbCopies(0),
67 	pattWidth(0.0),
68 	m_offsetX(0.0),
69 	m_offsetY(0.0),
70 	m_gapval(0.0),
71 	m_rotate(0),
72 	selOffs(0),
73 	selCount(0)
74 {
75 	// Set action info in languageChange, so we only have to do
76 	// it in one place.
77 	languageChange();
78 }
79 
~PathAlongPathPlugin()80 PathAlongPathPlugin::~PathAlongPathPlugin() {};
81 
languageChange()82 void PathAlongPathPlugin::languageChange()
83 {
84 	// Note that we leave the unused members unset. They'll be initialised
85 	// with their default ctors during construction.
86 	// Action name
87 	m_actionInfo.name = "PathAlongPath";
88 	// Action text for menu, including accel
89 	m_actionInfo.text = tr("Path Along Path...");
90 	m_actionInfo.helpText = tr("Bends a Polygon along a Path.");
91 	// Menu
92 	m_actionInfo.menu = "ItemPathOps";
93 	m_actionInfo.parentMenu = "Item";
94 	m_actionInfo.subMenuName = tr("Path Tools");
95 	m_actionInfo.enabledOnStartup = false;
96 	m_actionInfo.notSuitableFor.append(PageItem::Line);
97 	m_actionInfo.notSuitableFor.append(PageItem::TextFrame);
98 	m_actionInfo.notSuitableFor.append(PageItem::ImageFrame);
99 	m_actionInfo.notSuitableFor.append(PageItem::PathText);
100 	m_actionInfo.notSuitableFor.append(PageItem::LatexFrame);
101 	m_actionInfo.notSuitableFor.append(PageItem::Symbol);
102 	m_actionInfo.notSuitableFor.append(PageItem::RegularPolygon);
103 	m_actionInfo.notSuitableFor.append(PageItem::Arc);
104 	m_actionInfo.notSuitableFor.append(PageItem::Spiral);
105 	m_actionInfo.forAppMode.append(modeNormal);
106 	m_actionInfo.needsNumObjects = 2;
107 	m_actionInfo.firstObjectType.append(PageItem::PolyLine);
108 	m_actionInfo.secondObjectType.append(PageItem::Polygon);
109 }
110 
fullTrName() const111 QString PathAlongPathPlugin::fullTrName() const
112 {
113 	return QObject::tr("PathAlongPath");
114 }
115 
getAboutData() const116 const ScActionPlugin::AboutData* PathAlongPathPlugin::getAboutData() const
117 {
118 	AboutData* about = new AboutData;
119 	Q_CHECK_PTR(about);
120 	about->authors = QString::fromUtf8("Franz Schmid <Franz.Schmid@altmuehlnet.de>");
121 	about->shortDescription = tr("Bends a Polygon along a Polyline");
122 	about->description = tr("This plugin bends a Polygon with the help of a Polyline.");
123 	// about->version
124 	// about->releaseDate
125 	// about->copyright
126 	about->license = "GPL";
127 	return about;
128 }
129 
deleteAboutData(const AboutData * about) const130 void PathAlongPathPlugin::deleteAboutData(const AboutData* about) const
131 {
132 	Q_ASSERT(about);
133 	delete about;
134 }
135 
handleSelection(ScribusDoc * doc,int SelectedType)136 bool PathAlongPathPlugin::handleSelection(ScribusDoc* doc, int SelectedType)
137 {
138 	bool ret = ScActionPlugin::handleSelection(doc, SelectedType);
139 	if (!ret)
140 	{
141 		if (doc->m_Selection->count() == 2)
142 		{
143 			PageItem *currItem = doc->m_Selection->itemAt(0);
144 			if (currItem->isGroup())
145 			{
146 				currItem = doc->m_Selection->itemAt(1);
147 				ret = currItem->itemType() == PageItem::PolyLine;
148 			}
149 			else
150 			{
151 				if (currItem->itemType() != PageItem::PolyLine)
152 					ret = false;
153 				else
154 				{
155 					currItem = doc->m_Selection->itemAt(1);
156 					if (currItem->isGroup())
157 					{
158 						ret = true;
159 					}
160 				}
161 			}
162 		}
163 	}
164 	return ret;
165 }
166 
run(ScribusDoc * doc,const QString &)167 bool PathAlongPathPlugin::run(ScribusDoc* doc, const QString&)
168 {
169 	firstUpdate = true;
170 	m_doc = doc;
171 	originalPathG.clear();
172 	originalRotG.clear();
173 	originalXPosG.clear();
174 	originalYPosG.clear();
175 	patternItemG.clear();
176 	if (m_doc == nullptr)
177 		m_doc = ScCore->primaryMainWindow()->doc;
178 	if (m_doc->m_Selection->count() > 1)
179 	{
180 		if ((m_doc->m_Selection->itemAt(0)->isGroup()) || (m_doc->m_Selection->itemAt(1)->isGroup()))
181 		{
182 			selOffs = 0;
183 			selCount = m_doc->m_Selection->count() - 1;
184 			if (!m_doc->m_Selection->itemAt(0)->isGroup())
185 			{
186 				pathItem = m_doc->m_Selection->itemAt(0);
187 				selOffs = 1;
188 			}
189 			else
190 				pathItem = m_doc->m_Selection->itemAt(selCount);
191 			effectPath = pathItem->PoLine.copy();
192 			QTransform mp;
193 			mp.rotate(pathItem->rotation());
194 			effectPath.map(mp);
195 			PageItem* bxi = m_doc->m_Selection->itemAt(selOffs);
196 			bxi->asGroupFrame()->adjustXYPosition();
197 			originalPathG.append(bxi->PoLine.copy());
198 			originalXPosG.append(bxi->xPos());
199 			originalYPosG.append(bxi->yPos());
200 			originalXPosGi.append(bxi->gXpos);
201 			originalYPosGi.append(bxi->gYpos);
202 			originalRotG.append(bxi->rotation());
203 			originalWidth.append(bxi->width());
204 			originalHeight.append(bxi->height());
205 			originalWidthG.append(bxi->groupWidth);
206 			originalHeightG.append(bxi->groupHeight);
207 			patternItemG.append(bxi);
208 			QList<PageItem*> bxiL = bxi->getAllChildren();
209 			for (int bx = 0; bx < bxiL.count(); ++bx)
210 			{
211 				PageItem* cIte = bxiL.at(bx);
212 				originalPathG.append(cIte->PoLine.copy());
213 				originalXPosG.append(cIte->xPos());
214 				originalYPosG.append(cIte->yPos());
215 				originalWidth.append(cIte->width());
216 				originalHeight.append(cIte->height());
217 				originalWidthG.append(cIte->groupWidth);
218 				originalHeightG.append(cIte->groupHeight);
219 				originalXPosGi.append(cIte->gXpos);
220 				originalYPosGi.append(cIte->gYpos);
221 				originalRotG.append(cIte->rotation());
222 				patternItemG.append(cIte);
223 			}
224 			QPainterPath tmpPath = effectPath.toQPainterPath(false);
225 			PathDialog *dia = new PathDialog(m_doc->scMW(), m_doc->unitIndex(), tmpPath.length(), true);
226 			connect(dia, SIGNAL(updateValues(int, double, double, double, int)), this, SLOT(updateEffectG(int, double, double, double, int)));
227 			if (dia->exec())
228 			{
229 				updateEffectG(dia->effectType, dia->offset, dia->offsetY, dia->gap, dia->rotate);
230 				m_doc->changed();
231 				if (bxi->isGroup())
232 				{
233 					m_doc->resizeGroupToContents(bxi);
234 					bxi->SetRectFrame();
235 					m_doc->view()->DrawNew();
236 				}
237 			}
238 			else
239 			{
240 				updateEffectG(-1, dia->offset, dia->offsetY, dia->gap, dia->rotate);
241 				m_doc->view()->DrawNew();
242 			}
243 			delete dia;
244 		}
245 		else
246 		{
247 			patternItem = m_doc->m_Selection->itemAt(0);
248 			pathItem = m_doc->m_Selection->itemAt(1);
249 			if (pathItem->itemType() != PageItem::PolyLine)
250 			{
251 				patternItem = m_doc->m_Selection->itemAt(1);
252 				pathItem = m_doc->m_Selection->itemAt(0);
253 			}
254 			effectPath = pathItem->PoLine.copy();
255 			QTransform mp;
256 			mp.rotate(pathItem->rotation());
257 			effectPath.map(mp);
258 			originalPath = patternItem->PoLine.copy();
259 			originalXPos = patternItem->xPos();
260 			originalYPos = patternItem->yPos();
261 			originalRot = patternItem->rotation();
262 			QPainterPath tmpPath = effectPath.toQPainterPath(false);
263 			PathDialog *dia = new PathDialog(m_doc->scMW(), m_doc->unitIndex(), tmpPath.length(), false);
264 			connect(dia, SIGNAL(updateValues(int, double, double, double, int)), this, SLOT(updateEffect(int, double, double, double, int)));
265 			if (dia->exec())
266 			{
267 				updateEffect(dia->effectType, dia->offset, dia->offsetY, dia->gap, dia->rotate);
268 				patternItem->ContourLine = patternItem->PoLine.copy();
269 				m_doc->changed();
270 			}
271 			else
272 			{
273 				patternItem->PoLine = originalPath;
274 				patternItem->ClipEdited = true;
275 				patternItem->FrameType = 3;
276 				patternItem->setXYPos(originalXPos, originalYPos);
277 				patternItem->setRotation(originalRot);
278 				m_doc->adjustItemSize(patternItem);
279 				patternItem->OldB2 = patternItem->width();
280 				patternItem->OldH2 = patternItem->height();
281 				patternItem->updateClip();
282 				m_doc->view()->DrawNew();
283 			}
284 			delete dia;
285 		}
286 	}
287 	return true;
288 }
289 
updateEffectG(int effectType,double offset,double offsetY,double gap,int rotate)290 void PathAlongPathPlugin::updateEffectG(int effectType, double offset, double offsetY, double gap, int rotate)
291 {
292 	qApp->changeOverrideCursor(QCursor(Qt::WaitCursor));
293 	for (int bx = 0; bx < patternItemG.count(); ++bx)
294 	{
295 		PageItem* bxi = patternItemG[bx];
296 		bxi->PoLine = originalPathG[bx];
297 		bxi->ClipEdited = true;
298 		bxi->FrameType = 3;
299 		bxi->setXYPos(originalXPosG[bx], originalYPosG[bx], true);
300 		bxi->setRotation(originalRotG[bx]);
301 		bxi->gXpos = originalXPosGi[bx];
302 		bxi->gYpos = originalYPosGi[bx];
303 		bxi->setWidthHeight(originalWidth[bx], originalHeight[bx], true);
304 		bxi->groupWidth = originalWidthG[bx];
305 		bxi->groupHeight = originalHeightG[bx];
306 		bxi->OldB2 = bxi->width();
307 		bxi->OldH2 = bxi->height();
308 		bxi->updateClip();
309 		bxi->ContourLine = bxi->PoLine.copy();
310 	}
311 	firstUpdate = true;
312 	if (effectType != -1)
313 	{
314 		Geom::Piecewise<Geom::D2<Geom::SBasis> > originaldpwd2 = FPointArray2Piecewise(effectPath, false);
315 		Geom::Piecewise<Geom::D2<Geom::SBasis> > patternpwd2;
316 		PageItem* bxi = patternItemG[0];
317 		double originX = originalXPosG[0];
318 		double originY = originalYPosG[0];
319 		if (bxi->itemType() == PageItem::PolyLine)
320 			patternpwd2 = FPointArray2Piecewise(originalPathG[0], false);
321 		else
322 			patternpwd2 = FPointArray2Piecewise(originalPathG[0], true);
323 		setUpEffect(originaldpwd2, patternpwd2, effectType, offset / m_doc->unitRatio(), offsetY / m_doc->unitRatio(), gap / m_doc->unitRatio(), rotate);
324 		for (int bx = 0; bx < patternItemG.count(); ++bx)
325 		{
326 			PageItem* bxi = patternItemG[bx];
327 			FPointArray pathP = originalPathG[bx].copy();
328 			FPoint tp(getMinClipF(&pathP));
329 			double deltaX, deltaY;
330 			deltaX = originalXPosG[bx] - originX;
331 			deltaY = originalYPosG[bx] - originY;
332 			QTransform mm;
333 			mm.rotate(originalRotG[bx]);
334 			pathP.map(mm);
335 			pathP.translate(deltaX, deltaY);
336 			if (bxi->itemType() == PageItem::PolyLine)
337 				patternpwd2 = FPointArray2Piecewise(pathP, false);
338 			else
339 				patternpwd2 = FPointArray2Piecewise(pathP, true);
340 			bxi->PoLine = doEffect_pwd2(patternpwd2);
341 			bxi->PoLine.translate(-deltaX, -deltaY);
342 			QTransform mm2;
343 			mm2.rotate(-originalRotG[bx]);
344 			bxi->PoLine.map(mm2);
345 			bxi->ClipEdited = true;
346 			bxi->FrameType = 3;
347 			bxi->setXYPos(pathItem->xPos()+deltaX, pathItem->yPos()+deltaY, true);
348 			double oW = bxi->width();
349 			double oH = bxi->height();
350 			m_doc->adjustItemSize(bxi, true);
351 			bxi->OldB2 = bxi->width();
352 			bxi->OldH2 = bxi->height();
353 			if (bxi->isGroup())
354 			{
355 				bxi->groupWidth = bxi->groupWidth * (bxi->OldB2 / oW);
356 				bxi->groupHeight = bxi->groupHeight * (bxi->OldH2 / oH);
357 			}
358 			bxi->updateClip();
359 			bxi->ContourLine = bxi->PoLine.copy();
360 		}
361 	}
362 	qApp->changeOverrideCursor(QCursor(Qt::ArrowCursor));
363 	if (firstUpdate)
364 		m_doc->view()->DrawNew();
365 	else
366 	{
367 		double gx, gy, gh, gw;
368 		m_doc->m_Selection->getGroupRect(&gx, &gy, &gw, &gh);
369 		QRectF oldR(pathItem->getBoundingRect());
370 		QRectF newR = QRectF(gx, gy, gw, gh);
371 		m_doc->regionsChanged()->update(newR.united(oldR));
372 	}
373 	if (effectType != -1)
374 		firstUpdate = false;
375 }
376 
updateEffect(int effectType,double offset,double offsetY,double gap,int rotate)377 void PathAlongPathPlugin::updateEffect(int effectType, double offset, double offsetY, double gap, int rotate)
378 {
379 	if (effectType == -1)
380 	{
381 		patternItem->PoLine = originalPath;
382 		patternItem->ClipEdited = true;
383 		patternItem->FrameType = 3;
384 		patternItem->setXYPos(originalXPos, originalYPos);
385 		patternItem->setRotation(originalRot);
386 		firstUpdate = true;
387 	}
388 	else
389 	{
390 		Geom::Piecewise<Geom::D2<Geom::SBasis> > originaldpwd2 = FPointArray2Piecewise(effectPath, false);
391 		Geom::Piecewise<Geom::D2<Geom::SBasis> > patternpwd2;
392 		if (patternItem->itemType() == PageItem::PolyLine)
393 			patternpwd2 = FPointArray2Piecewise(originalPath, false);
394 		else
395 			patternpwd2 = FPointArray2Piecewise(originalPath, true);
396 		setUpEffect(originaldpwd2, patternpwd2, effectType, offset / m_doc->unitRatio(), offsetY / m_doc->unitRatio(), gap / m_doc->unitRatio(), rotate);
397 		patternItem->PoLine = doEffect_pwd2(patternpwd2);
398 		patternItem->ClipEdited = true;
399 		patternItem->FrameType = 3;
400 		patternItem->setXYPos(pathItem->xPos(), pathItem->yPos());
401 		patternItem->setRotation(0);
402 	}
403 	m_doc->adjustItemSize(patternItem, true);
404 	patternItem->OldB2 = patternItem->width();
405 	patternItem->OldH2 = patternItem->height();
406 	patternItem->updateClip();
407 	if (firstUpdate)
408 		m_doc->view()->DrawNew();
409 	else
410 	{
411 		QRectF oldR(pathItem->getBoundingRect());
412 		QRectF newR(patternItem->getBoundingRect());
413 		m_doc->regionsChanged()->update(newR.united(oldR));
414 	}
415 	if (effectType != -1)
416 		firstUpdate = false;
417 }
418 
setUpEffect(Geom::Piecewise<Geom::D2<Geom::SBasis>> & pwd2_in,Geom::Piecewise<Geom::D2<Geom::SBasis>> & pattern,int effect,double offset,double offsetY,double gap,int rotate)419 void PathAlongPathPlugin::setUpEffect(Geom::Piecewise<Geom::D2<Geom::SBasis> > &pwd2_in, Geom::Piecewise<Geom::D2<Geom::SBasis> > &pattern, int effect, double offset, double offsetY, double gap, int rotate)
420 {
421 	m_offsetX = offset;
422 	m_offsetY = offsetY;
423 	m_gapval = gap;
424 	m_rotate = rotate;
425 	uskeleton = arc_length_parametrization(pwd2_in, 2, .1);
426 	uskeleton = remove_short_cuts(uskeleton,.01);
427 	n = rot90(derivative(uskeleton));
428 	n = force_continuity(remove_short_cuts(n,.1));
429 	D2<Piecewise<SBasis> > patternd2;
430 	if (rotate == 1)
431 		patternd2 = make_cuts_independant(rot90(pattern));
432 	else if (rotate == 2)
433 		patternd2 = make_cuts_independant(rot90(rot90(pattern)));
434 	else if (rotate == 3)
435 		patternd2 = make_cuts_independant(rot90(rot90(rot90(pattern))));
436 	else
437 		patternd2 = make_cuts_independant(pattern);
438 	Piecewise<SBasis> x = Piecewise<SBasis>(patternd2[0]);
439 	Piecewise<SBasis> y = Piecewise<SBasis>(patternd2[1]);
440 	pattBnds = bounds_exact(x);
441 	x -= pattBnds.min();
442 	pattBndsY = bounds_exact(y);
443 	y -= (pattBndsY.max()+pattBndsY.min()) / 2.0;
444 	y -= offsetY;
445 	m_scaling = 1.0;
446 	nbCopies = int(uskeleton.cuts.back()/pattBnds.extent());
447 	if (effect == 0)
448 	{
449 		nbCopies = 1;
450 		m_scaling = 1.0;
451 	}
452 	else if (effect == 1)
453 	{
454 		nbCopies = 1;
455 		m_scaling = (uskeleton.cuts.back()-m_offsetX)/pattBnds.extent();
456 	}
457 	else if (effect == 2)
458 	{
459 		nbCopies = int((uskeleton.cuts.back()-m_offsetX)/(pattBnds.extent()+m_gapval));
460 		m_scaling = 1.0;
461 	}
462 	else if (effect == 3)
463 	{
464 		nbCopies = int((uskeleton.cuts.back()-m_offsetX)/(pattBnds.extent()+m_gapval));
465 		m_scaling = (uskeleton.cuts.back()-m_offsetX)/((((double)nbCopies)*pattBnds.extent()) + (((double)nbCopies-1)*m_gapval));
466 	}
467 	pattWidth = pattBnds.extent() * m_scaling;
468 }
469 
doEffect_pwd2(Geom::Piecewise<Geom::D2<Geom::SBasis>> & pattern)470 FPointArray PathAlongPathPlugin::doEffect_pwd2(Geom::Piecewise<Geom::D2<Geom::SBasis> > &pattern)
471 {
472 	double offs = m_offsetX;
473 	D2<Piecewise<SBasis> > patternd2;
474 	if (m_rotate == 1)
475 		patternd2 = make_cuts_independant(rot90(pattern));
476 	else if (m_rotate == 2)
477 		patternd2 = make_cuts_independant(rot90(rot90(pattern)));
478 	else if (m_rotate == 3)
479 		patternd2 = make_cuts_independant(rot90(rot90(rot90(pattern))));
480 	else
481 		patternd2 = make_cuts_independant(pattern);
482 	Piecewise<SBasis> x = Piecewise<SBasis>(patternd2[0]);
483 	Piecewise<SBasis> y = Piecewise<SBasis>(patternd2[1]);
484 	x -= pattBnds.min();
485 	y -= (pattBndsY.max()+pattBndsY.min()) / 2.0;
486 	y -= m_offsetY;
487 	if (m_scaling != 1.0)
488 		x*=m_scaling;
489 	FPointArray pathP;
490 	for (int i=0; i<nbCopies; i++)
491 	{
492 		Piecewise<D2<SBasis> > output;
493 		output.concat(compose(uskeleton,x+offs)+y*compose(n,x+offs));
494 		offs+=pattWidth+m_gapval;
495 		Piecewise2FPointArray(&pathP, output);
496 		if (nbCopies > 1)
497 			pathP.setMarker();
498 	}
499 	return pathP;
500 }
501