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