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