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