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 #include "extimageprops.h"
8 #include <QHBoxLayout>
9 #include <QVBoxLayout>
10 #include <QListWidget>
11 #include <QListWidgetItem>
12 #include <QPixmap>
13 #include <QTabWidget>
14 #include <QLabel>
15 #include <QCheckBox>
16 #include <QComboBox>
17 #include <QPushButton>
18 #include <QToolTip>
19 #include <QPainter>
20 #include <QWidget>
21 #include <QHeaderView>
22 #include <QTableWidget>
23 #include <QTableWidgetItem>
24 #include <QTimer>
25 
26 #include "commonstrings.h"
27 #include "pageitem.h"
28 #include "scpainter.h"
29 #include "scribusdoc.h"
30 #include "scribusview.h"
31 #include "iconmanager.h"
32 #include "util_math.h"
33 #include "util.h"
34 #include "ui/scrspinbox.h"
35 
ExtImageProps(QWidget * parent,ImageInfoRecord * info,PageItem * item,ScribusView * view)36 ExtImageProps::ExtImageProps( QWidget* parent, ImageInfoRecord *info, PageItem *item, ScribusView *view )
37 	: QDialog( parent )
38 {
39 	setModal(true);
40 	setWindowTitle( tr( "Extended Image Properties" ) );
41 	setWindowIcon(IconManager::instance().loadIcon("AppIcon.png"));
42 	ExtImagePropsLayout = new QVBoxLayout( this );
43 	ExtImagePropsLayout->setContentsMargins(9, 9, 9, 9);
44 	ExtImagePropsLayout->setSpacing(6);
45 	m_view  = view;
46 	m_timer = nullptr;
47 	if (info->layerInfo.count() != 0)
48 	{
49 		m_timer = new QTimer(this);
50 		m_timer->setSingleShot(true);
51 		m_timer->setInterval(350);
52 	}
53 	m_item = item;
54 	currentLayer = 0;
55 	originalInfo = *info;
56 	originalImageClip = item->imageClip.copy();
57 	blendModes.clear();
58 	blendModes.insert("norm", tr("Normal"));
59 	blendModes.insert("dark", tr("Darken"));
60 	blendModes.insert("lite", tr("Lighten"));
61 	blendModes.insert("hue ", tr("Hue"));
62 	blendModes.insert("sat ", tr("Saturation"));
63 	blendModes.insert("colr", tr("Color"));
64 	blendModes.insert("lum ", tr("Luminosity"));
65 	blendModes.insert("mul ", tr("Multiply"));
66 	blendModes.insert("scrn", tr("Screen"));
67 	blendModes.insert("diss", tr("Dissolve"));
68 	blendModes.insert("over", tr("Overlay"));
69 	blendModes.insert("hLit", tr("Hard Light"));
70 	blendModes.insert("sLit", tr("Soft Light"));
71 	blendModes.insert("diff", tr("Difference"));
72 	blendModes.insert("smud", tr("Exclusion"));
73 	blendModes.insert("div ", tr("Color Dodge"));
74 	blendModes.insert("idiv", tr("Color Burn"));
75 	blendModes.insert("plus", tr("Plus"));
76 	blendModes.insert("dsti", tr("Destination In"));
77 	blendModes.insert("dsto", tr("Destination Out"));
78 	blendModesRev.clear();
79 	blendModesRev.insert( tr("Normal"), "norm");
80 	blendModesRev.insert( tr("Darken"), "dark");
81 	blendModesRev.insert( tr("Lighten"), "lite");
82 	blendModesRev.insert( tr("Hue"), "hue ");
83 	blendModesRev.insert( tr("Saturation"), "sat ");
84 	blendModesRev.insert( tr("Color"), "colr");
85 	blendModesRev.insert( tr("Luminosity"), "lum ");
86 	blendModesRev.insert( tr("Multiply"), "mul ");
87 	blendModesRev.insert( tr("Screen"), "scrn");
88 	blendModesRev.insert( tr("Dissolve"), "diss");
89 	blendModesRev.insert( tr("Overlay"), "over");
90 	blendModesRev.insert( tr("Hard Light"), "hLit");
91 	blendModesRev.insert( tr("Soft Light"), "sLit");
92 	blendModesRev.insert( tr("Difference"), "diff");
93 	blendModesRev.insert( tr("Exclusion"), "smud");
94 	blendModesRev.insert( tr("Color Dodge"), "div ");
95 	blendModesRev.insert( tr("Color Burn"), "idiv");
96 	blendModesRev.insert( tr("Plus"), "plus");
97 	blendModesRev.insert( tr("Destination In"), "dsti");
98 	blendModesRev.insert( tr("Destination Out"), "dsto");
99 	propsTab = new QTabWidget( this );
100 	QPalette palette;
101 	palette.setColor(backgroundRole(), Qt::white);
102 	if (info->layerInfo.count() != 0)
103 	{
104 		tab = new QWidget( propsTab );
105 		tabLayout = new QVBoxLayout( tab );
106 		tabLayout->setContentsMargins(9, 9, 9, 9);
107 		tabLayout->setSpacing(6);
108 		layout1 = new QHBoxLayout;
109 		layout1->setContentsMargins(0, 0, 0, 0);
110 		layout1->setSpacing(6);
111 		textLabel1 = new QLabel( tab );
112 		textLabel1->setText( tr( "Blend Mode:" ) );
113 		layout1->addWidget( textLabel1 );
114 		blendMode = new QComboBox( tab );
115 		blendMode->clear();
116 		blendMode->addItem( tr("Normal"));
117 		blendMode->addItem( tr("Darken"));
118 		blendMode->addItem( tr("Lighten"));
119 		blendMode->addItem( tr("Hue"));
120 		blendMode->addItem( tr("Saturation"));
121 		blendMode->addItem( tr("Color"));
122 		blendMode->addItem( tr("Luminosity"));
123 		blendMode->addItem( tr("Multiply"));
124 		blendMode->addItem( tr("Screen"));
125 		blendMode->addItem( tr("Dissolve"));
126 		blendMode->addItem( tr("Overlay"));
127 		blendMode->addItem( tr("Hard Light"));
128 		blendMode->addItem( tr("Soft Light"));
129 		blendMode->addItem( tr("Difference"));
130 		blendMode->addItem( tr("Exclusion"));
131 		blendMode->addItem( tr("Color Dodge"));
132 		blendMode->addItem( tr("Color Burn"));
133 		blendMode->addItem( tr("Plus"));
134 		blendMode->addItem( tr("Destination In"));
135 		blendMode->addItem( tr("Destination Out"));
136 		layout1->addWidget( blendMode );
137 		textLabel2 = new QLabel( tab );
138 		textLabel2->setText( tr( "Opacity:" ) );
139 		layout1->addWidget( textLabel2 );
140 		opacitySpinBox = new ScrSpinBox( tab );
141 		opacitySpinBox->setMinimum(0);
142 		opacitySpinBox->setDecimals(0);
143 		opacitySpinBox->setMaximum(100);
144 		opacitySpinBox->setSingleStep(10);
145 		opacitySpinBox->setSuffix( tr(" %"));
146 		layout1->addWidget( opacitySpinBox );
147 		tabLayout->addLayout( layout1 );
148 		layerTable = new QTableWidget(info->layerInfo.count(), 3, tab );
149 		layerTable->setHorizontalHeaderItem(0, new QTableWidgetItem(IconManager::instance().loadIcon("16/show-object.png"), ""));
150 		layerTable->setHorizontalHeaderItem(1, new QTableWidgetItem(""));
151 		layerTable->setHorizontalHeaderItem(2, new QTableWidgetItem( tr("Name")));
152 		QHeaderView* headerH = layerTable->horizontalHeader();
153 		headerH->setStretchLastSection(true);
154 		headerH->setSectionsClickable(false );
155 		headerH->setSectionsMovable( false );
156 		if (info->layerInfo.count() == 1)
157 		{
158 			layerTable->setColumnWidth(1, 40);
159 			layerTable->setColumnWidth(0, 24);
160 		}
161 		layerTable->setSortingEnabled(false);
162 		layerTable->setSelectionBehavior(QTableWidget::SelectRows);
163 		QHeaderView *Header = layerTable->verticalHeader();
164 		Header->setSectionsMovable( false );
165 		Header->setSectionResizeMode(QHeaderView::Fixed);
166 		Header->hide();
167 		FlagsSicht.clear();
168 		int col2Width = 0;
169 		int col1Width = 0;
170 		if ((info->isRequest) && (info->RequestProps.contains(0)))
171 		{
172 			opacitySpinBox->setValue(qRound(info->RequestProps[0].opacity / 255.0 * 100));
173 			setCurrentComboItem(blendMode, blendModes[info->RequestProps[0].blend]);
174 		}
175 		else
176 		{
177 			opacitySpinBox->setValue(qRound(info->layerInfo[0].opacity / 255.0 * 100));
178 			setCurrentComboItem(blendMode, blendModes[info->layerInfo[0].blend]);
179 		}
180 		opacitySpinBox->setEnabled(true);
181 		blendMode->setEnabled(true);
182 
183 		QList<PSDLayer>::iterator it2;
184 		uint counter = 0;
185 		for (it2 = info->layerInfo.begin(); it2 != info->layerInfo.end(); ++it2)
186 		{
187 			QCheckBox *cp = new QCheckBox(it2->layerName, this);
188 			cp->setPalette(palette);
189 			QPixmap pm;
190 			pm=QPixmap::fromImage(it2->thumb);
191 			col1Width = qMax(col1Width, pm.width());
192 			cp->setIcon(pm);
193 			FlagsSicht.append(cp);
194 			connect(cp, SIGNAL(clicked()), this, SLOT(changedLayer()));
195 			layerTable->setCellWidget(info->layerInfo.count()-counter-1, 0, cp);
196 			if ((info->isRequest) && (info->RequestProps.contains(counter)))
197 				cp->setChecked(info->RequestProps[counter].visible);
198 			else
199 				cp->setChecked(!(it2->flags & 2));
200 			if (!it2->thumb_mask.isNull())
201 			{
202 				QCheckBox *cp2 = new QCheckBox(it2->layerName, this);
203 				cp2->setPalette(palette);
204 				QPixmap pm2;
205 				pm2=QPixmap::fromImage(it2->thumb_mask);
206 				col2Width = qMax(col2Width, pm2.width());
207 				cp2->setIcon(pm2);
208 				connect(cp2, SIGNAL(clicked()), this, SLOT(changedLayer()));
209 				layerTable->setCellWidget(info->layerInfo.count()-counter-1, 1, cp2);
210 				if ((info->isRequest) && (info->RequestProps.contains(counter)))
211 					cp2->setChecked(info->RequestProps[counter].useMask);
212 				else
213 					cp2->setChecked(true);
214 				FlagsMask.append(cp2);
215 			}
216 			else
217 				FlagsMask.append(0);
218 			QTableWidgetItem *tW = new QTableWidgetItem(it2->layerName);
219 			tW->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled);
220 			layerTable->setItem(info->layerInfo.count()-counter-1, 2, tW);
221 			layerTable->setRowHeight(info->layerInfo.count()-counter-1, 40);
222 			counter++;
223 		}
224 		tabLayout->addWidget( layerTable );
225 		layerTable->setColumnWidth(1, 24 + col2Width);
226 		layerTable->setColumnWidth(0, 24 + col1Width);
227 		blendMode->setCurrentIndex(0);
228 // 		headerH->setResizeMode(QHeaderView::Fixed);
229 		propsTab->addTab( tab,  tr( "Layers" ) );
230 	}
231 	tab_2 = new QWidget( propsTab );
232 	tabLayout_2 = new QVBoxLayout( tab_2 );
233 	tabLayout_2->setContentsMargins(9, 9, 9, 9);
234 	tabLayout_2->setSpacing(6);
235 	pathList = new QListWidget( tab_2 );
236 	pathList->clear();
237 	pathList->setIconSize(QSize(40, 40));
238 	QMap<QString, FPointArray>::Iterator it;
239 	if (info->PDSpathData.count() != 0)
240 	{
241 		for (it = info->PDSpathData.begin(); it != info->PDSpathData.end(); ++it)
242 		{
243 			QImage pixm(40, 40, QImage::Format_ARGB32_Premultiplied);
244 			ScPainter *p = new ScPainter(&pixm, 40, 40);
245 			p->clear();
246 			p->translate(3.0, 3.0);
247 			if (it.key() == info->clipPath)
248 			{
249 				pixm.fill(Qt::green);
250 				p->clear(Qt::green);
251 			}
252 			else
253 				pixm.fill(Qt::white);
254 			FPointArray Path;
255 			Path.resize(0);
256 			Path = info->PDSpathData[it.key()].copy();
257 			FPoint min = getMinClipF(&Path);
258 			Path.translate(-min.x(), -min.y());
259 			FPoint max = Path.widthHeight();
260 			QTransform mm;
261 			mm.scale(34.0 / qMax(max.x(), max.y()), 34.0 / qMax(max.x(), max.y()));
262 			Path.map(mm);
263 			p->setupPolygon(&Path);
264 			p->setPen(Qt::black, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin);
265 			p->setBrush(Qt::white);
266 			p->setFillMode(ScPainter::None);
267 			p->setStrokeMode(ScPainter::Solid);
268 			p->strokePath();
269 			p->end();
270 			delete p;
271 			QPixmap pm;
272 			pm=QPixmap::fromImage(pixm);
273 			new QListWidgetItem(QIcon(pm), it.key(), pathList);
274 			if (it.key() == info->usedPath)
275 			{
276 				pathList->setCurrentRow(pathList->count()-1);
277 				pathList->currentItem()->setSelected(true);
278 			}
279 		}
280 	}
281 	tabLayout_2->addWidget( pathList );
282 	resetPath = new QPushButton( tr("Don't use any Path"), tab_2);
283 	tabLayout_2->addWidget( resetPath );
284 	propsTab->addTab( tab_2, tr( "Paths" ) );
285 	ExtImagePropsLayout->addWidget( propsTab );
286 
287 	layoutBottom = new QHBoxLayout;
288 	layoutBottom->setContentsMargins(0, 0, 0, 0);
289 	layoutBottom->setSpacing(6);
290 	livePreview = new QCheckBox( this );
291 	livePreview->setText( tr( "Live Preview" ) );
292 	livePreview->setChecked(true);
293 	doPreview = true;
294 	layoutBottom->addWidget( livePreview );
295 	QSpacerItem* spacer = new QSpacerItem( 2, 2, QSizePolicy::Expanding, QSizePolicy::Minimum );
296 	layoutBottom->addItem( spacer );
297 	okButton = new QPushButton( CommonStrings::tr_OK, this );
298 	layoutBottom->addWidget( okButton );
299 	cancelButton = new QPushButton( CommonStrings::tr_Cancel, this );
300 	cancelButton->setDefault( true );
301 	layoutBottom->addWidget( cancelButton );
302 	ExtImagePropsLayout->addLayout( layoutBottom );
303 	resize(330, 320);
304 
305 	connect(pathList, SIGNAL( itemClicked(QListWidgetItem*) ), this, SLOT( selPath(QListWidgetItem*) ) );
306 	connect(resetPath, SIGNAL(clicked()), this, SLOT(noPath()));
307 	connect(livePreview, SIGNAL(clicked()), this, SLOT(changePreview()));
308 	connect(okButton, SIGNAL(clicked()), this, SLOT(leaveOK()));
309 	connect(cancelButton, SIGNAL(clicked()), this, SLOT(leaveCancel()));
310 	if (info->layerInfo.count() != 0)
311 	{
312 		layerTable->selectionModel()->clearSelection();
313 		opacitySpinBox->setEnabled(false);
314 		blendMode->setEnabled(false);
315 		connect(m_timer, SIGNAL(timeout()), this,  SLOT(changedLayer()));
316 		connect(layerTable, SIGNAL(itemSelectionChanged()), this, SLOT(selLayer()));
317 		connect(opacitySpinBox, SIGNAL(valueChanged(double)), this, SLOT(delayedLayerChange()));
318 		connect(blendMode, SIGNAL(activated(int)), this, SLOT(changedLayer()));
319 	}
320 }
321 
leaveOK()322 void ExtImageProps::leaveOK()
323 {
324 	doPreview = false;
325 	if (originalInfo.layerInfo.count() != 0)
326 		changedLayer();
327 	m_view->m_doc->loadPict(m_item->Pfile, m_item, true);
328 	if (pathList->count() != 0)
329 	{
330 		QList<QListWidgetItem *>sel = pathList->selectedItems();
331 		if (sel.count() != 0)
332 		{
333 			m_item->imageClip = m_item->pixm.imgInfo.PDSpathData[sel[0]->text()].copy();
334 			m_item->pixm.imgInfo.usedPath = sel[0]->text();
335 			QTransform cl;
336 			cl.translate(m_item->imageXOffset()*m_item->imageXScale(), m_item->imageYOffset()*m_item->imageYScale());
337 			cl.rotate(m_item->imageRotation());
338 			cl.scale(m_item->imageXScale(), m_item->imageYScale());
339 			m_item->imageClip.map(cl);
340 		}
341 		else
342 		{
343 			m_item->imageClip.resize(0);
344 			m_item->pixm.imgInfo.usedPath = "";
345 		}
346 	}
347 	m_item->update();
348 	accept();
349 }
350 
leaveCancel()351 void ExtImageProps::leaveCancel()
352 {
353 	m_item->pixm.imgInfo = originalInfo;
354 	m_view->m_doc->loadPict(m_item->Pfile, m_item, true);
355 	m_item->imageClip = originalImageClip.copy();
356 	m_item->update();
357 	reject();
358 }
359 
changePreview()360 void ExtImageProps::changePreview()
361 {
362 	doPreview = livePreview->isChecked();
363 	if (doPreview)
364 	{
365 		if (originalInfo.layerInfo.count() != 0)
366 			changedLayer();
367 		m_view->m_doc->loadPict(m_item->Pfile, m_item, true);
368 		if (pathList->count() != 0)
369 		{
370 			QList<QListWidgetItem *>sel = pathList->selectedItems();
371 			if (sel.count() != 0)
372 			{
373 				m_item->imageClip = m_item->pixm.imgInfo.PDSpathData[sel[0]->text()].copy();
374 				m_item->pixm.imgInfo.usedPath = sel[0]->text();
375 				QTransform cl;
376 				cl.translate(m_item->imageXOffset()*m_item->imageXScale(), m_item->imageYOffset()*m_item->imageYScale());
377 				cl.rotate(m_item->imageRotation());
378 				cl.scale(m_item->imageXScale(), m_item->imageYScale());
379 				m_item->imageClip.map(cl);
380 			}
381 			else
382 			{
383 				m_item->imageClip.resize(0);
384 				m_item->pixm.imgInfo.usedPath = "";
385 			}
386 		}
387 		m_item->update();
388 	}
389 	else
390 	{
391 		m_item->pixm.imgInfo = originalInfo;
392 		m_view->m_doc->loadPict(m_item->Pfile, m_item, true);
393 		m_item->imageClip = originalImageClip.copy();
394 		m_item->update();
395 	}
396 }
397 
changedLayer()398 void ExtImageProps::changedLayer()
399 {
400 	updateLayerInfo();
401 	if (doPreview)
402 	{
403 		m_view->m_doc->loadPict(m_item->Pfile, m_item, true);
404 		m_item->update();
405 	}
406 }
407 
delayedLayerChange()408 void ExtImageProps::delayedLayerChange()
409 {
410 	if (m_timer->isActive())
411 		m_timer->stop();
412 	updateLayerInfo();
413 	m_timer->start();
414 }
415 
selLayer()416 void ExtImageProps::selLayer()
417 {
418 	QModelIndexList selectedRows = layerTable->selectionModel()->selectedRows();
419 	if (selectedRows.count() <= 0)
420 	{
421 		currentLayer = -1;
422 		opacitySpinBox->setEnabled(false);
423 		blendMode->setEnabled(false);
424 		return;
425 	}
426 
427 	int selectedRow = selectedRows.at(0).row();
428 	currentLayer = layerTable->rowCount() - selectedRow - 1;
429 
430 	disconnect(opacitySpinBox, SIGNAL(valueChanged(double)), this, SLOT(delayedLayerChange()));
431 	disconnect(blendMode, SIGNAL(activated(int)), this, SLOT(changedLayer()));
432 	if ((m_item->pixm.imgInfo.isRequest) && (m_item->pixm.imgInfo.RequestProps.contains(currentLayer)))
433 	{
434 		opacitySpinBox->setValue(qRound(m_item->pixm.imgInfo.RequestProps[currentLayer].opacity / 255.0 * 100));
435 		setCurrentComboItem(blendMode, blendModes[m_item->pixm.imgInfo.RequestProps[currentLayer].blend]);
436 	}
437 	else
438 	{
439 		opacitySpinBox->setValue(qRound(m_item->pixm.imgInfo.layerInfo[currentLayer].opacity / 255.0 * 100));
440 		setCurrentComboItem(blendMode, blendModes[m_item->pixm.imgInfo.layerInfo[currentLayer].blend]);
441 	}
442 	opacitySpinBox->setEnabled(true);
443 	blendMode->setEnabled(true);
444 	connect(opacitySpinBox, SIGNAL(valueChanged(double)), this, SLOT(delayedLayerChange()));
445 	connect(blendMode, SIGNAL(activated(int)), this, SLOT(changedLayer()));
446 }
447 
updateLayerInfo()448 void ExtImageProps::updateLayerInfo()
449 {
450 	struct ImageLoadRequest loadingInfo;
451 	bool isRequest = m_item->pixm.imgInfo.isRequest;
452 	for (int r = 0; r < layerTable->rowCount(); ++r)
453 	{
454 		int layerIndex = layerTable->rowCount() - r - 1;
455 		if (currentLayer == layerIndex)
456 		{
457 			loadingInfo.blend = blendModesRev[blendMode->currentText()];
458 			loadingInfo.opacity = qRound(opacitySpinBox->value() / 100.0 * 255);
459 		}
460 		else if ((isRequest) && (m_item->pixm.imgInfo.RequestProps.contains(layerIndex)))
461 		{
462 			loadingInfo.blend = m_item->pixm.imgInfo.RequestProps[layerIndex].blend;
463 			loadingInfo.opacity = m_item->pixm.imgInfo.RequestProps[layerIndex].opacity;
464 		}
465 		else
466 		{
467 			loadingInfo.blend = m_item->pixm.imgInfo.layerInfo[layerIndex].blend;
468 			loadingInfo.opacity = m_item->pixm.imgInfo.layerInfo[layerIndex].opacity;
469 		}
470 		loadingInfo.visible = FlagsSicht.at(layerIndex)->isChecked();
471 		if (FlagsMask.at(layerIndex))
472 			loadingInfo.useMask = FlagsMask.at(layerIndex)->isChecked();
473 		else
474 			loadingInfo.useMask = true;
475 		m_item->pixm.imgInfo.RequestProps.insert(layerIndex, loadingInfo);
476 	}
477 	m_item->pixm.imgInfo.isRequest = true;
478 }
479 
noPath()480 void ExtImageProps::noPath()
481 {
482 	disconnect(pathList, SIGNAL( itemClicked(QListWidgetItem*) ), this, SLOT( selPath(QListWidgetItem*) ) );
483 	pathList->clearSelection();
484 	if (doPreview)
485 	{
486 		m_item->imageClip.resize(0);
487 		m_item->pixm.imgInfo.usedPath = "";
488 		m_item->update();
489 	}
490 	connect(pathList, SIGNAL( itemClicked(QListWidgetItem*) ), this, SLOT( selPath(QListWidgetItem*) ) );
491 }
492 
selPath(QListWidgetItem * c)493 void ExtImageProps::selPath(QListWidgetItem *c)
494 {
495 	if ((c != nullptr) && (doPreview))
496 	{
497 		m_item->imageClip = m_item->pixm.imgInfo.PDSpathData[c->text()].copy();
498 		m_item->pixm.imgInfo.usedPath = c->text();
499 		QTransform cl;
500 		cl.translate(m_item->imageXOffset()*m_item->imageXScale(), m_item->imageYOffset()*m_item->imageYScale());
501 		cl.rotate(m_item->imageRotation());
502 		cl.scale(m_item->imageXScale(), m_item->imageYScale());
503 		m_item->imageClip.map(cl);
504 		m_item->update();
505 	}
506 }
507 
508