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) 2008 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 "pathconnect.h"
28 #include "pathconnectdialog.h"
29 #include "selection.h"
30 #include "scribusdoc.h"
31 #include "scribusview.h"
32 #include "undomanager.h"
33
pathconnect_getPluginAPIVersion()34 int pathconnect_getPluginAPIVersion()
35 {
36 return PLUGIN_API_VERSION;
37 }
38
pathconnect_getPlugin()39 ScPlugin* pathconnect_getPlugin()
40 {
41 PathConnectPlugin* plug = new PathConnectPlugin();
42 Q_CHECK_PTR(plug);
43 return plug;
44 }
45
pathconnect_freePlugin(ScPlugin * plugin)46 void pathconnect_freePlugin(ScPlugin* plugin)
47 {
48 PathConnectPlugin* plug = qobject_cast<PathConnectPlugin*>(plugin);
49 Q_ASSERT(plug);
50 delete plug;
51 }
52
PathConnectPlugin()53 PathConnectPlugin::PathConnectPlugin()
54 {
55 // Set action info in languageChange, so we only have to do
56 // it in one place.
57 languageChange();
58 }
59
60 PathConnectPlugin::~PathConnectPlugin() = default;
61
languageChange()62 void PathConnectPlugin::languageChange()
63 {
64 // Note that we leave the unused members unset. They'll be initialised
65 // with their default ctors during construction.
66 // Action name
67 m_actionInfo.name = "PathConnect";
68 // Action text for menu, including accel
69 m_actionInfo.text = tr("Connect Paths");
70 m_actionInfo.helpText = tr("Connects two Paths.");
71 // Menu
72 m_actionInfo.menu = "ItemPathOps";
73 m_actionInfo.parentMenu = "Item";
74 m_actionInfo.subMenuName = tr("Path Tools");
75 m_actionInfo.enabledOnStartup = false;
76 m_actionInfo.notSuitableFor.append(PageItem::Line);
77 m_actionInfo.notSuitableFor.append(PageItem::TextFrame);
78 m_actionInfo.notSuitableFor.append(PageItem::ImageFrame);
79 m_actionInfo.notSuitableFor.append(PageItem::Polygon);
80 m_actionInfo.notSuitableFor.append(PageItem::PathText);
81 m_actionInfo.notSuitableFor.append(PageItem::LatexFrame);
82 m_actionInfo.notSuitableFor.append(PageItem::Symbol);
83 m_actionInfo.notSuitableFor.append(PageItem::RegularPolygon);
84 m_actionInfo.notSuitableFor.append(PageItem::Spiral);
85 m_actionInfo.notSuitableFor.append(PageItem::Arc);
86 m_actionInfo.needsNumObjects = 2;
87 }
88
fullTrName() const89 QString PathConnectPlugin::fullTrName() const
90 {
91 return QObject::tr("PathConnect");
92 }
93
getAboutData() const94 const ScActionPlugin::AboutData* PathConnectPlugin::getAboutData() const
95 {
96 AboutData* about = new AboutData;
97 Q_CHECK_PTR(about);
98 about->authors = QString::fromUtf8("Franz Schmid <Franz.Schmid@altmuehlnet.de>");
99 about->shortDescription = tr("Connect Paths");
100 about->description = tr("Connect 2 Polylines.");
101 // about->version
102 // about->releaseDate
103 // about->copyright
104 about->license = "GPL";
105 return about;
106 }
107
deleteAboutData(const AboutData * about) const108 void PathConnectPlugin::deleteAboutData(const AboutData* about) const
109 {
110 Q_ASSERT(about);
111 delete about;
112 }
113
run(ScribusDoc * doc,const QString &)114 bool PathConnectPlugin::run(ScribusDoc* doc, const QString&)
115 {
116 m_doc = doc;
117 firstUpdate = true;
118 if (m_doc == nullptr)
119 m_doc = ScCore->primaryMainWindow()->doc;
120 if (m_doc->m_Selection->count() > 1)
121 {
122 m_item1 = m_doc->m_Selection->itemAt(0);
123 m_item2 = m_doc->m_Selection->itemAt(1);
124 originalPath1 = m_item1->PoLine.copy();
125 originalPath2 = m_item2->PoLine.copy();
126 originalXPos = m_item1->xPos();
127 originalYPos = m_item1->yPos();
128 PathConnectDialog *dia = new PathConnectDialog(m_doc->scMW());
129 connect(dia, SIGNAL(updateValues(int,int,int,int)), this, SLOT(updateEffect(int,int,int,int)));
130 if (dia->exec())
131 {
132 int pointOne = dia->getFirstLinePoint();
133 int pointTwo = dia->getSecondLinePoint();
134 int mode = dia->getMode();
135 UndoTransaction trans;
136 if (UndoManager::undoEnabled())
137 trans = UndoManager::instance()->beginTransaction(Um::BezierCurve,Um::ILine,Um::ConnectPath,"",Um::ILine);
138
139 m_item1->PoLine = computePath(pointOne, pointTwo, mode, originalPath1, originalPath2);
140 m_item1->ClipEdited = true;
141 m_item1->FrameType = 3;
142 int oldRotMode = m_doc->rotationMode();
143 m_doc->setRotationMode(0);
144 m_doc->adjustItemSize(m_item1);
145 m_doc->setRotationMode(oldRotMode);
146 m_item1->OldB2 = m_item1->width();
147 m_item1->OldH2 = m_item1->height();
148 if (UndoManager::undoEnabled())
149 {
150 ScItemState<QPair<FPointArray,FPointArray> > *is = new ScItemState<QPair<FPointArray,FPointArray> >(Um::ConnectPath);
151 is->set("CONNECT_PATH");
152 is->set("OLDX", originalXPos);
153 is->set("OLDY", originalYPos);
154 is->set("NEWX", m_item1->xPos());
155 is->set("NEWY", m_item1->yPos());
156 is->setItem(qMakePair(originalPath1, m_item1->PoLine));
157 UndoManager::instance()->action(m_item1, is);
158 }
159 m_item1->updateClip();
160 m_item1->ContourLine = m_item1->PoLine.copy();
161 m_doc->m_Selection->removeItem(m_item1);
162 m_doc->itemSelection_DeleteItem();
163 m_doc->changed();
164 if (trans)
165 trans.commit();
166 }
167 else
168 {
169 m_item1->PoLine = originalPath1.copy();
170 m_item1->ClipEdited = true;
171 m_item1->FrameType = 3;
172 m_item1->setXYPos(originalXPos, originalYPos);
173 int oldRotMode = m_doc->rotationMode();
174 m_doc->setRotationMode(0);
175 m_doc->adjustItemSize(m_item1);
176 m_doc->setRotationMode(oldRotMode);
177 m_item1->OldB2 = m_item1->width();
178 m_item1->OldH2 = m_item1->height();
179 m_item1->updateClip();
180 m_item1->ContourLine = m_item1->PoLine.copy();
181 }
182 m_doc->view()->DrawNew();
183 delete dia;
184 }
185 return true;
186 }
187
updateEffect(int effectType,int pointOne,int pointTwo,int mode)188 void PathConnectPlugin::updateEffect(int effectType, int pointOne, int pointTwo, int mode)
189 {
190 // #12119: unnecessary to save actions generated by preview
191 UndoManager::instance()->setUndoEnabled(false);
192 if (effectType == -1)
193 {
194 m_item1->PoLine = originalPath1.copy();
195 m_item1->ClipEdited = true;
196 m_item1->FrameType = 3;
197 m_item1->setXYPos(originalXPos, originalYPos);
198 firstUpdate = true;
199 }
200 else
201 {
202 m_item1->setXYPos(originalXPos, originalYPos);
203 m_item1->PoLine = computePath(pointOne, pointTwo, mode, originalPath1, originalPath2);
204 m_item1->ClipEdited = true;
205 m_item1->FrameType = 3;
206 }
207 int oldRotMode = m_doc->rotationMode();
208 m_doc->setRotationMode(0);
209 m_doc->adjustItemSize(m_item1);
210 m_doc->setRotationMode(oldRotMode);
211 m_item1->OldB2 = m_item1->width();
212 m_item1->OldH2 = m_item1->height();
213 m_item1->updateClip();
214 if (firstUpdate)
215 m_doc->view()->DrawNew();
216 else
217 {
218 QRectF oldR(m_item1->getBoundingRect());
219 QRectF newR(m_item2->getBoundingRect());
220 m_doc->regionsChanged()->update(newR.united(oldR));
221 }
222 if (effectType != -1)
223 firstUpdate = false;
224 UndoManager::instance()->setUndoEnabled(true);
225 }
226
computePath(int pointOne,int pointTwo,int mode,FPointArray & p1,FPointArray & p2)227 FPointArray PathConnectPlugin::computePath(int pointOne, int pointTwo, int mode, FPointArray &p1, FPointArray &p2)
228 {
229 FPointArray result;
230 FPointArray path1 = p1.copy();
231 FPointArray path2 = p2.copy();
232 QTransform ma;
233 ma.translate(m_item2->xPos(), m_item2->yPos());
234 ma.rotate(m_item2->rotation());
235 path2.map(ma);
236 QTransform ma2;
237 ma2.translate(originalXPos, originalYPos);
238 ma2.rotate(m_item1->rotation());
239 ma2 = ma2.inverted();
240 path2.map(ma2);
241 FPoint startL1 = path1.point(0);
242 FPoint startL2 = path2.point(0);
243 FPoint endL1 = path1.point(path1.size() - 2);
244 FPoint endL2 = path2.point(path2.size() - 2);
245 if (pointOne == 0)
246 {
247 if (pointTwo == 0)
248 {
249 path2 = reversePath(path2);
250 if (mode == 0)
251 path2.addQuadPoint(startL2, startL2, startL1, startL1);
252 else
253 {
254 FPoint midP = (startL2 + startL1) * 0.5;
255 FPoint corr1 = path1.point(1) + (midP - startL1);
256 FPoint corr2 = path2.point(path2.size() - 1) + (midP - startL2);
257 path1.setPoint(1, corr1);
258 path2.setPoint(path2.size() - 1, corr2);
259 path2.setPoint(path2.size() - 2, midP);
260 path1.setPoint(0, midP);
261 }
262 path2.putPoints(path2.size(), path1.size(), path1);
263 result = path2.copy();
264 }
265 else if (pointTwo == 1)
266 {
267 if (mode == 0)
268 path2.addQuadPoint(endL2, endL2, startL1, startL1);
269 else
270 {
271 FPoint midP = (startL1 + endL2) * 0.5;
272 FPoint corr1 = path1.point(1) + (midP - startL1);
273 FPoint corr2 = path2.point(path2.size() - 1) + (midP - endL2);
274 path1.setPoint(1, corr1);
275 path2.setPoint(path2.size() - 1, corr2);
276 path2.setPoint(path2.size() - 2, midP);
277 path1.setPoint(0, midP);
278 }
279 path2.putPoints(path2.size(), path1.size(), path1);
280 result = path2.copy();
281 }
282 }
283 else if (pointOne == 1)
284 {
285 if (pointTwo == 0)
286 {
287 if (mode == 0)
288 path1.addQuadPoint(endL1, endL1, startL2, startL2);
289 else
290 {
291 FPoint midP = (startL2 + endL1) * 0.5;
292 FPoint corr1 = path1.point(path1.size() - 1) + (midP - endL1);
293 FPoint corr2 = path2.point(1) + (midP - startL2);
294 path1.setPoint(path1.size() - 1, corr1);
295 path2.setPoint(1, corr2);
296 path1.setPoint(path1.size() - 2, midP);
297 path2.setPoint(0, midP);
298 }
299 path1.putPoints(path1.size(), path2.size(), path2);
300 result = path1.copy();
301 }
302 else if (pointTwo == 1)
303 {
304 path2 = reversePath(path2);
305 if (mode == 0)
306 path1.addQuadPoint(endL1, endL1, endL2, endL2);
307 else
308 {
309 FPoint midP = (endL2 + endL1) * 0.5;
310 FPoint corr1 = path1.point(path1.size() - 1) + (midP - endL1);
311 FPoint corr2 = path2.point(1) + (midP - endL2);
312 path1.setPoint(path1.size() - 1, corr1);
313 path2.setPoint(1, corr2);
314 path1.setPoint(path1.size() - 2, midP);
315 path2.setPoint(0, midP);
316 }
317 path1.putPoints(path1.size(), path2.size(), path2);
318 result = path1.copy();
319 }
320 }
321 return result;
322 }
323
reversePath(FPointArray & path)324 FPointArray PathConnectPlugin::reversePath(FPointArray &path)
325 {
326 FPointArray result;
327 for (int a = path.size()-4; a > -1; a -= 4)
328 {
329 const FPoint& p1 = path.point(a);
330 const FPoint& p2 = path.point(a+1);
331 const FPoint& p3 = path.point(a+2);
332 const FPoint& p4 = path.point(a+3);
333 result.addQuadPoint(p3, p4, p1, p2);
334 }
335 return result;
336 }
337