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                           shapepalette.cpp  -  description
9                              -------------------
10     begin                : Sat Mar 28 2015
11     copyright            : (C) 2015 by Franz Schmid
12     email                : Franz.Schmid@altmuehlnet.de
13  ***************************************************************************/
14 
15 /***************************************************************************
16  *                                                                         *
17  *   This program is free software; you can redistribute it and/or modify  *
18  *   it under the terms of the GNU General Public License as published by  *
19  *   the Free Software Foundation; either version 2 of the License, or     *
20  *   (at your option) any later version.                                   *
21  *                                                                         *
22  ***************************************************************************/
23 #include "iconmanager.h"
24 #include "prefsfile.h"
25 #include "prefsmanager.h"
26 #include "scmimedata.h"
27 #include "scpainter.h"
28 #include "scribus.h"
29 #include "scribusapp.h"
30 #include "scribusdoc.h"
31 #include "scribusXml.h"
32 #include "selection.h"
33 #include "shapepalette.h"
34 #include "ui/scmessagebox.h"
35 #include "util.h"
36 #include "util_math.h"
37 
38 #include <QApplication>
39 #include <QByteArray>
40 #include <QDomDocument>
41 #include <QDomElement>
42 #include <QDrag>
43 #include <QFileDialog>
44 #include <QMimeData>
45 #include <QPainter>
46 
47 
ShapeView(QWidget * parent)48 ShapeView::ShapeView(QWidget* parent) : QListWidget(parent)
49 {
50 	shapes.clear();
51 	scMW = nullptr;
52 
53 	setDragEnabled(true);
54 	setViewMode(QListView::IconMode);
55 	setFlow(QListView::LeftToRight);
56 	setSortingEnabled(true);
57 	setWrapping(true);
58 	setResizeMode(QListView::Adjust);
59 	setAcceptDrops(false);
60 	setDropIndicatorShown(true);
61 	setDragDropMode(QAbstractItemView::DragDrop);
62 	setSelectionMode(QAbstractItemView::SingleSelection);
63 	setContextMenuPolicy(Qt::CustomContextMenu);
64 	delegate = new ScListWidgetDelegate(this, this);
65 	delegate->setIconOnly(false);
66 	setItemDelegate(delegate);
67 	setIconSize(QSize(48, 48));
68 
69 	connect(this, SIGNAL(customContextMenuRequested (const QPoint &)), this, SLOT(HandleContextMenu(QPoint)));
70 }
71 
HandleContextMenu(QPoint)72 void ShapeView::HandleContextMenu(QPoint)
73 {
74 	QMenu *pmenu = new QMenu();
75 	if (this->count() != 0)
76 	{
77 		QListWidgetItem* it = currentItem();
78 		if (it != nullptr)
79 		{
80 			QAction* delAct = pmenu->addAction( tr("Delete selected Shape"));
81 			connect(delAct, SIGNAL(triggered()), this, SLOT(delOne()));
82 		}
83 		QAction* delAAct = pmenu->addAction( tr("Delete all Shapes"));
84 		connect(delAAct, SIGNAL(triggered()), this, SLOT(deleteAll()));
85 		pmenu->addSeparator();
86 	}
87 	QAction* viewAct = pmenu->addAction( tr("Display Icons only"));
88 	viewAct->setCheckable(true);
89 	viewAct->setChecked(delegate->iconOnly());
90 	connect(viewAct, SIGNAL(triggered()), this, SLOT(changeDisplay()));
91 	pmenu->exec(QCursor::pos());
92 	delete pmenu;
93 }
94 
deleteAll()95 void ShapeView::deleteAll()
96 {
97 	int t = ScMessageBox::warning(this, CommonStrings::trWarning, tr("Do you really want to clear all your shapes in this tab?"),
98 				QMessageBox::Yes | QMessageBox::No,
99 				QMessageBox::No,	// GUI default
100 				QMessageBox::Yes);	// batch default
101 	if (t == QMessageBox::No)
102 		return;
103 	shapes.clear();
104 	clear();
105 }
106 
delOne()107 void ShapeView::delOne()
108 {
109 	QListWidgetItem* it = currentItem();
110 	if (it != nullptr)
111 	{
112 		QString key = it->data(Qt::UserRole).toString();
113 		shapes.remove(key);
114 		updateShapeList();
115 	}
116 }
117 
changeDisplay()118 void ShapeView::changeDisplay()
119 {
120 	reset();
121 	delegate->setIconOnly(!delegate->iconOnly());
122 	repaint();
123 }
124 
viewportEvent(QEvent * event)125 bool ShapeView::viewportEvent(QEvent *event)
126 {
127 	if (event != nullptr)
128 	{
129 		if (event->type() == QEvent::ToolTip)
130 		{
131 			QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
132 			QListWidgetItem* it = itemAt(helpEvent->pos());
133 			if (it != nullptr)
134 			{
135 				event->accept();
136 				QString tipText = it->text();
137 				QToolTip::showText(helpEvent->globalPos(), tipText, this);
138 				return true;
139 			}
140 		}
141 		else if (event->type() == QEvent::MouseButtonRelease)
142 		{
143 			QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
144 			if (mouseEvent->button() == Qt::RightButton)
145 			{
146 				emit customContextMenuRequested(mouseEvent->pos());
147 				return true;
148 			}
149 		}
150 	}
151 	return QListWidget::viewportEvent(event);
152 }
153 
keyPressEvent(QKeyEvent * e)154 void ShapeView::keyPressEvent(QKeyEvent* e)
155 {
156 	switch (e->key())
157 	{
158 		case Qt::Key_Backspace:
159 		case Qt::Key_Delete:
160 			{
161 				QListWidgetItem* it = currentItem();
162 				if (it != nullptr)
163 				{
164 					QString key = it->data(Qt::UserRole).toString();
165 					if (shapes.contains(key))
166 					{
167 						shapes.remove(key);
168 						updateShapeList();
169 						e->accept();
170 					}
171 				}
172 			}
173 			break;
174 		default:
175 			break;
176 	}
177 }
178 
dragEnterEvent(QDragEnterEvent * e)179 void ShapeView::dragEnterEvent(QDragEnterEvent *e)
180 {
181 	if (e->source() == this)
182 		e->ignore();
183 	else
184 		e->acceptProposedAction();
185 }
186 
dragMoveEvent(QDragMoveEvent * e)187 void ShapeView::dragMoveEvent(QDragMoveEvent *e)
188 {
189 	if (e->source() == this)
190 		e->ignore();
191 	else
192 		e->acceptProposedAction();
193 }
194 
dropEvent(QDropEvent * e)195 void ShapeView::dropEvent(QDropEvent *e)
196 {
197 	if (e->mimeData()->hasText())
198 	{
199 		e->acceptProposedAction();
200 		if (e->source() == this)
201 			return;
202 		QString text = e->mimeData()->text();
203 		if ((text.startsWith("<SCRIBUSELEM")) || (text.startsWith("SCRIBUSELEMUTF8")))
204 			emit objectDropped();
205 	}
206 	else
207 		e->ignore();
208 }
209 
startDrag(Qt::DropActions supportedActions)210 void ShapeView::startDrag(Qt::DropActions supportedActions)
211 {
212 	QString key = currentItem()->data(Qt::UserRole).toString();
213 	if (shapes.contains(key))
214 	{
215 		int w = shapes[key].width;
216 		int h = shapes[key].height;
217 		ScribusDoc *m_Doc = new ScribusDoc();
218 		m_Doc->setup(0, 1, 1, 1, 1, "Custom", "Custom");
219 		m_Doc->setPage(w, h, 0, 0, 0, 0, 0, 0, false, false);
220 		m_Doc->addPage(0);
221 		m_Doc->setGUI(false, scMW, nullptr);
222 		int z = m_Doc->itemAdd(PageItem::Polygon, PageItem::Unspecified, m_Doc->currentPage()->xOffset(), m_Doc->currentPage()->yOffset(), w, h, m_Doc->itemToolPrefs().shapeLineWidth, m_Doc->itemToolPrefs().shapeFillColor, m_Doc->itemToolPrefs().shapeLineColor);
223 		PageItem* ite = m_Doc->Items->at(z);
224 		ite->PoLine = shapes[key].path.copy();
225 		FPoint wh = getMaxClipF(&ite->PoLine);
226 		ite->setWidthHeight(wh.x(),wh.y());
227 		ite->setTextFlowMode(PageItem::TextFlowDisabled);
228 		m_Doc->adjustItemSize(ite);
229 		ite->OldB2 = ite->width();
230 		ite->OldH2 = ite->height();
231 		ite->updateClip();
232 		ite->ClipEdited = true;
233 		ite->FrameType = 3;
234 		m_Doc->m_Selection->addItem(ite, true);
235 		ScElemMimeData* md = ScriXmlDoc::writeToMimeData(m_Doc, m_Doc->m_Selection);
236 		QDrag* dr = new QDrag(this);
237 		dr->setMimeData(md);
238 		dr->setPixmap(currentItem()->icon().pixmap(QSize(48, 48)));
239 		dr->exec();
240 		delete m_Doc;
241 	}
242 }
243 
updateShapeList()244 void ShapeView::updateShapeList()
245 {
246 	clear();
247 	setWordWrap(true);
248 	for (QHash<QString, shapeData>::Iterator it = shapes.begin(); it != shapes.end(); ++it)
249 	{
250 		int w = it.value().width + 4;
251 		int h = it.value().height + 4;
252 		QImage Ico(w, h, QImage::Format_ARGB32_Premultiplied);
253 		Ico.fill(0);
254 		ScPainter *painter = new ScPainter(&Ico, w, h);
255 		painter->setBrush(qRgb(0, 0, 0));
256 		painter->setPen(qRgb(0, 0, 0), 1.0, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin);
257 		painter->setFillMode(ScPainter::Solid);
258 		painter->setStrokeMode(ScPainter::Solid);
259 		painter->translate(2.0, 2.0);
260 		painter->setupPolygon(&it.value().path);
261 		painter->drawPolygon();
262 		painter->end();
263 		delete painter;
264 		QPixmap pm;
265 		if (w >= h)
266 			pm = QPixmap::fromImage(Ico.scaledToWidth(48, Qt::SmoothTransformation));
267 		else
268 			pm = QPixmap::fromImage(Ico.scaledToHeight(48, Qt::SmoothTransformation));
269 		QPixmap pm2(48, 48);
270 		pm2.fill(palette().color(QPalette::Base));
271 		QPainter p;
272 		p.begin(&pm2);
273 		p.drawPixmap(24 - pm.width() / 2, 24 - pm.height() / 2, pm);
274 		p.end();
275 		QListWidgetItem *item = new QListWidgetItem(pm2, it.value().name, this);
276 		item->setData(Qt::UserRole, it.key());
277 		item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled);
278 	}
279 }
280 
ShapePalette(QWidget * parent)281 ShapePalette::ShapePalette( QWidget* parent) : ScDockPalette(parent, "Shap", Qt::WindowFlags())
282 {
283 	setMinimumSize( QSize( 220, 240 ) );
284 	setObjectName(QString::fromLocal8Bit("Shap"));
285 	setSizePolicy( QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum));
286 	containerWidget = new QWidget(this);
287 	vLayout = new QVBoxLayout( containerWidget );
288 	vLayout->setSpacing(3);
289 	vLayout->setContentsMargins(3, 3, 3, 3);
290 	buttonLayout = new QHBoxLayout;
291 	buttonLayout->setSpacing(6);
292 	buttonLayout->setContentsMargins(0, 0, 0, 0);
293 
294 	importButton = new QToolButton(this);
295 	importButton->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
296 	importButton->setIcon(IconManager::instance().loadIcon("16/document-open.png"));
297 	importButton->setIconSize(QSize(16, 16));
298 	buttonLayout->addWidget( importButton );
299 
300 	QSpacerItem* spacer = new QSpacerItem( 1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum );
301 	buttonLayout->addItem( spacer );
302 
303 	closeButton = new QToolButton(this);
304 	closeButton->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
305 	closeButton->setIcon(IconManager::instance().loadIcon("16/close.png"));
306 	closeButton->setIconSize(QSize(16, 16));
307 	buttonLayout->addWidget( closeButton );
308 	vLayout->addLayout( buttonLayout );
309 
310 	Frame3 = new QToolBox( this );
311 	vLayout->addWidget(Frame3);
312 	setWidget(containerWidget);
313 
314 	unsetDoc();
315 	m_scMW  = nullptr;
316 
317 	languageChange();
318 
319 	connect(ScQApp, SIGNAL(iconSetChanged()), this, SLOT(iconSetChange()));
320 
321 	connect(importButton, SIGNAL(clicked()), this, SLOT(Import()));
322 	connect(closeButton, SIGNAL(clicked()), this, SLOT(closeTab()));
323 }
324 
writeToPrefs()325 void ShapePalette::writeToPrefs()
326 {
327 	QString prFile = QDir::toNativeSeparators(PrefsManager::instance().preferencesLocation()+"/scribusshapes.xml");
328 	QFile f(prFile);
329 	if (!f.open(QIODevice::WriteOnly))
330 		return;
331 	QDomDocument docu = QDomDocument("svgdoc");
332 	QString vo = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
333 	QString st = "<ScribusShape></ScribusShape>";
334 	docu.setContent(st);
335 	QDomElement docElement = docu.documentElement();
336 	for (int a = 0; a < Frame3->count(); a++)
337 	{
338 		ShapeViewWidget = (ShapeView*)Frame3->widget(a);
339 		QDomElement fil = docu.createElement("file");
340 		fil.setAttribute("name", Frame3->itemText(a));
341 		for (QHash<QString, shapeData>::Iterator it = ShapeViewWidget->shapes.begin(); it != ShapeViewWidget->shapes.end(); ++it)
342 		{
343 			QDomElement shp = docu.createElement("shape");
344 			shp.setAttribute("width", it.value().width);
345 			shp.setAttribute("height", it.value().width);
346 			shp.setAttribute("name", it.value().name);
347 			shp.setAttribute("path", it.value().path.svgPath(true));
348 			shp.setAttribute("uuid", it.key());
349 			fil.appendChild(shp);
350 		}
351 		docElement.appendChild(fil);
352 	}
353 	QDataStream s(&f);
354 	QString wr = vo;
355 	wr += docu.toString();
356 	QByteArray utf8wr = wr.toUtf8();
357 	s.writeRawData(utf8wr.data(), utf8wr.length());
358 	f.close();
359 }
360 
readFromPrefs()361 void ShapePalette::readFromPrefs()
362 {
363 	QString prFile = QDir::toNativeSeparators(PrefsManager::instance().preferencesLocation()+"/scribusshapes.xml");
364 	QFileInfo fi(prFile);
365 	if (fi.exists())
366 	{
367 		QByteArray docBytes("");
368 		loadRawText(prFile, docBytes);
369 		QString docText("");
370 		docText = QString::fromUtf8(docBytes);
371 		QDomDocument docu("scridoc");
372 		docu.setContent(docText);
373 		QDomElement docElem = docu.documentElement();
374 		for(QDomElement drawPag = docElem.firstChildElement(); !drawPag.isNull(); drawPag = drawPag.nextSiblingElement() )
375 		{
376 			if (drawPag.tagName() == "file")
377 			{
378 				ShapeViewWidget = new ShapeView(this);
379 				ShapeViewWidget->scMW = m_scMW;
380 				Frame3->addItem(ShapeViewWidget, drawPag.attribute("name"));
381 				for(QDomElement dpg = drawPag.firstChildElement(); !dpg.isNull(); dpg = dpg.nextSiblingElement() )
382 				{
383 					if (dpg.tagName() == "shape")
384 					{
385 						shapeData shData;
386 						shData.height = dpg.attribute("height", "1").toInt();
387 						shData.width = dpg.attribute("width", "1").toInt();
388 						shData.path.parseSVG(dpg.attribute("path"));
389 						shData.name = dpg.attribute("name");
390 						ShapeViewWidget->shapes.insert(dpg.attribute("uuid"), shData);
391 					}
392 				}
393 				ShapeViewWidget->updateShapeList();
394 			}
395 		}
396 		if (Frame3->count() > 0)
397 			Frame3->setCurrentIndex(0);
398 	}
399 }
400 
closeTab()401 void ShapePalette::closeTab()
402 {
403 	int index = Frame3->currentIndex();
404 	ShapeViewWidget = (ShapeView*)Frame3->widget(index);
405 	Frame3->removeItem(index);
406 	delete ShapeViewWidget;
407 }
408 
decodePSDfloat(uint data)409 double ShapePalette::decodePSDfloat(uint data)
410 {
411 	double ret = 0.0;
412 	char man = (data & 0xFF000000) >> 24;
413 	if (man >= 0)
414 	{
415 		ret = (data & 0x00FFFFFF) / 16777215.0;
416 		ret = (ret + man);
417 	}
418 	else
419 	{
420 		ret = (~data & 0x00FFFFFF) / 16777215.0;
421 		ret = (ret + ~man) * -1;
422 	}
423 	return ret;
424 }
425 
Import()426 void ShapePalette::Import()
427 {
428 	PrefsContext* dirs = PrefsManager::instance().prefsFile->getContext("dirs");
429 	QString s = QFileDialog::getOpenFileName(this, tr("Choose a shape file to import"), dirs->get("shape_load", "."), tr("Photoshop Custom Shape (*.csh *.CSH)"));
430 	if (s.isEmpty())
431 		return;
432 
433 	QFileInfo fi(s);
434 	ShapeViewWidget = new ShapeView(this);
435 	int nIndex = Frame3->addItem(ShapeViewWidget, fi.baseName());
436 	dirs->set("shape_load", s.left(s.lastIndexOf(QDir::toNativeSeparators("/"))));
437 	QFile file(s);
438 	if (!file.open(QFile::ReadOnly))
439 		return;
440 	QApplication::setOverrideCursor(Qt::WaitCursor);
441 	QDataStream ds(&file);
442 	ds.setByteOrder(QDataStream::BigEndian);
443 	QByteArray magic;
444 	magic.resize(4);
445 	ds.readRawData(magic.data(), 4);
446 	if (magic != "cush")
447 		return;
448 	quint32 version, count, shpCounter;
449 	shpCounter = 1;
450 	ds >> version >> count;
451 	while (!ds.atEnd())
452 	{
453 		QString  string = "";
454 		quint32  length, dummy, shpLen, paDataLen;
455 		ds >> length;
456 		for (uint i = 0; i < length; ++i)
457 		{
458 			quint16  ch;
459 			ds >> ch;
460 			if (ch > 0)
461 				string += char(ch);
462 		}
463 		if (length % 2 != 0)
464 			ds.skipRawData(2);
465 		ds >> dummy;
466 		ds >> shpLen;
467 		qint64 posi = ds.device()->pos();
468 		ds.skipRawData(1);
469 		QByteArray uuid;
470 		uuid.resize(36);
471 		ds.readRawData(uuid.data(), 36);
472 		qint32 x, y, w, h;
473 		ds >> y >> x >> h >> w;
474 		paDataLen = shpLen - 53;
475 		QRect bounds = QRect(QPoint(x,y), QPoint(w, h));
476 		bool first = false;
477 		bool pathOpen = false;
478 		FPoint firstPoint, firstControl;
479 		FPointArray clip2;
480 		short type;
481 		uint data;
482 		double frac1, frac2, frac3, frac4, frac5, frac6;
483 		uint offset2;
484 		offset2 = 0;
485 		clip2.resize(0);
486 		while (offset2 < paDataLen)
487 		{
488 			ds >> type;
489 			ds >> data;
490 			frac1 = decodePSDfloat(data) * bounds.height();
491 			ds >> data;
492 			frac2 = decodePSDfloat(data) * bounds.width();
493 			ds >> data;
494 			frac3 = decodePSDfloat(data) * bounds.height();
495 			ds >> data;
496 			frac4 = decodePSDfloat(data) * bounds.width();
497 			ds >> data;
498 			frac5 = decodePSDfloat(data) * bounds.height();
499 			ds >> data;
500 			frac6 = decodePSDfloat(data) * bounds.width();
501 			switch (type)
502 			{
503 				case 0:
504 				case 3:
505 					if (pathOpen)
506 					{
507 						clip2.addPoint(firstPoint);
508 						clip2.addPoint(firstControl);
509 						clip2.setMarker();
510 					}
511 					pathOpen = false;
512 					first = true;
513 					break;
514 				case 1:
515 				case 2:
516 				case 4:
517 				case 5:
518 					if (first)
519 					{
520 						firstControl = FPoint(frac2, frac1);
521 						firstPoint = FPoint(frac4, frac3);
522 						clip2.addPoint(FPoint(frac4, frac3));
523 						clip2.addPoint(FPoint(frac6, frac5));
524 					}
525 					else
526 					{
527 						clip2.addPoint(frac4, frac3);
528 						clip2.addPoint(frac2, frac1);
529 						clip2.addPoint(frac4, frac3);
530 						clip2.addPoint(frac6, frac5);
531 					}
532 					pathOpen = true;
533 					first = false;
534 					break;
535 				case 6:
536 					first = true;
537 					break;
538 				default:
539 					break;
540 			}
541 			offset2 += 26;
542 		}
543 		clip2.addPoint(firstPoint);
544 		clip2.addPoint(firstControl);
545 		shapeData shData;
546 		shData.height = bounds.height();
547 		shData.width = bounds.width();
548 		shData.path = clip2.copy();
549 		shData.name = string;
550 		ShapeViewWidget->shapes.insert(QString(uuid), shData);
551 		ds.device()->seek(posi + shpLen);
552 		shpCounter++;
553 	}
554 	file.close();
555 	Frame3->setCurrentIndex(nIndex);
556 	ShapeViewWidget->updateShapeList();
557 	ShapeViewWidget->scMW = m_scMW;
558 	QApplication::restoreOverrideCursor();
559 }
560 
setMainWindow(ScribusMainWindow * mw)561 void ShapePalette::setMainWindow(ScribusMainWindow *mw)
562 {
563 	m_scMW = mw;
564 	for (int a = 0; a < Frame3->count(); a++)
565 	{
566 		ShapeViewWidget = (ShapeView*)Frame3->widget(a);
567 		ShapeViewWidget->scMW = mw;
568 	}
569 }
570 
setDoc(ScribusDoc * newDoc)571 void ShapePalette::setDoc(ScribusDoc *newDoc)
572 {
573 	if (m_scMW == nullptr)
574 		m_doc = nullptr;
575 	else
576 		m_doc = newDoc;
577 	if (m_doc == nullptr)
578 		setEnabled(true);
579 	else
580 		setEnabled(!m_doc->drawAsPreview);
581 }
582 
unsetDoc()583 void ShapePalette::unsetDoc()
584 {
585 	m_doc = nullptr;
586 	setEnabled(true);
587 }
588 
changeEvent(QEvent * e)589 void ShapePalette::changeEvent(QEvent *e)
590 {
591 	if (e->type() == QEvent::LanguageChange)
592 	{
593 		languageChange();
594 	}
595 	else
596 		ScDockPalette::changeEvent(e);
597 }
598 
iconSetChange()599 void ShapePalette::iconSetChange()
600 {
601 	IconManager& iconManager = IconManager::instance();
602 
603 	importButton->setIcon(iconManager.loadIcon("16/document-open.png"));
604 	importButton->setIconSize(QSize(16, 16));
605 
606 	closeButton->setIcon(iconManager.loadIcon("16/close.png"));
607 	closeButton->setIconSize(QSize(16, 16));
608 }
609 
languageChange()610 void ShapePalette::languageChange()
611 {
612 	setWindowTitle( tr( "Custom Shapes" ) );
613 	importButton->setToolTip( tr("Load Photoshop Custom Shapes"));
614 	closeButton->setToolTip( tr("Close current Tab"));
615 }
616