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                           picstatus.cpp  -  description
9                              -------------------
10     begin                : Fri Nov 29 2001
11     copyright            : (C) 2001 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 "picstatus.h"
24 
25 #include <QAction>
26 #include <QCheckBox>
27 #include <QDesktopServices>
28 #include <QFileInfo>
29 #include <QLabel>
30 #include <QListWidget>
31 #include <QMenu>
32 #include <QMessageBox>
33 #include <QMultiMap>
34 #include <QPainter>
35 #include <QPixmap>
36 #include <QPushButton>
37 #include <QScopedPointer>
38 #include <QToolButton>
39 
40 #include "commonstrings.h"
41 #include "effectsdialog.h"
42 #include "extimageprops.h"
43 #include "filesearch.h"
44 #include "iconmanager.h"
45 #include "pageitem.h"
46 #include "picsearch.h"
47 #include "picsearchoptions.h"
48 #include "scribuscore.h"
49 #include "scribusdoc.h"
50 #include "units.h"
51 #include "util_color.h"
52 #include "util_formats.h"
53 
54 
55 
PicItem(QListWidget * parent,const QString & text,const QPixmap & pix,PageItem * pgItem)56 PicItem::PicItem(QListWidget* parent, const QString& text, const QPixmap& pix, PageItem* pgItem)
57 	: QListWidgetItem(pix, text, parent)
58 {
59 	PageItemObject = pgItem;
60 }
61 
PicStatus(QWidget * parent,ScribusDoc * docu)62 PicStatus::PicStatus(QWidget* parent, ScribusDoc *docu) : QDialog( parent )
63 {
64 	setupUi(this);
65 	setModal(true);
66 	imageViewArea->setIconSize(QSize(128, 128));
67 	imageViewArea->setContextMenuPolicy(Qt::CustomContextMenu);
68 	m_Doc = docu;
69 	setWindowIcon(IconManager::instance().loadIcon("AppIcon.png"));
70 	fillTable();
71 	workTab->setCurrentIndex(0);
72 	connect(closeButton, SIGNAL(clicked()), this, SLOT(accept()));
73 	connect(imageViewArea, SIGNAL(itemSelectionChanged()), this, SLOT(newImageSelected()));
74 	connect(isPrinting, SIGNAL(clicked()), this, SLOT(PrintPic()));
75 	connect(isVisibleCheck, SIGNAL(clicked()), this, SLOT(visiblePic()));
76 	connect(goPageButton, SIGNAL(clicked()), this, SLOT(GotoPic()));
77 	connect(selectButton, SIGNAL(clicked()), this, SLOT(SelectPic()));
78 	connect(searchButton, SIGNAL(clicked()), this, SLOT(SearchPic()));
79 	connect(fileManagerButton, SIGNAL(clicked()), this, SLOT(FileManager()));
80 	connect(effectsButton, SIGNAL(clicked()), this, SLOT(doImageEffects()));
81 	connect(buttonLayers, SIGNAL(clicked()), this, SLOT(doImageExtProp()));
82 	connect(buttonEdit, SIGNAL(clicked()), this, SLOT(doEditImage()));
83 	connect(imageViewArea, SIGNAL(customContextMenuRequested (const QPoint &)), this, SLOT(slotRightClick()));
84 }
85 
createImgIcon(PageItem * item)86 QPixmap PicStatus::createImgIcon(PageItem* item)
87 {
88 	QPainter p;
89 	QPixmap pm(128, 128);
90 	QBrush b(QColor(205,205,205), IconManager::instance().loadPixmap("testfill.png"));
91 	p.begin(&pm);
92 	p.fillRect(0, 0, 128, 128, imageViewArea->palette().window());
93 	p.setPen(QPen(Qt::black, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
94 	p.setBrush(palette().window());
95 	p.drawRoundedRect(0, 0, 127, 127, 10, 10, Qt::RelativeSize);
96 	p.setPen(Qt::NoPen);
97 	p.setBrush(b);
98 	p.drawRect(12, 12, 104, 104);
99 	if (item->imageIsAvailable && QFile::exists(item->externalFile()))
100 	{
101 		QImage im2 = item->pixm.scaled(104, 104, Qt::KeepAspectRatio, Qt::SmoothTransformation);
102 		p.drawImage((104 - im2.width()) / 2 + 12, (104 - im2.height()) / 2 + 12, im2);
103 	}
104 	else
105 	{
106 		p.setBrush(Qt::NoBrush);
107 		p.setPen(QPen(Qt::red, 2, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
108 		p.drawLine(12, 12, 116, 116);
109 		p.drawLine(12, 116, 116, 12);
110 	}
111 	p.setPen(QPen(Qt::black, 1, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin));
112 	p.setBrush(Qt::NoBrush);
113 	p.drawRect(12, 12, 104, 104);
114 	p.end();
115 	return pm;
116 }
117 
enableWidgets(bool enabled)118 void PicStatus::enableWidgets(bool enabled)
119 {
120 	isPrinting->setEnabled(enabled);
121 	isVisibleCheck->setEnabled(enabled);
122 	goPageButton->setEnabled(enabled);
123 	selectButton->setEnabled(enabled);
124 	searchButton->setEnabled(enabled);
125 	fileManagerButton->setEnabled(enabled);
126 	effectsButton->setEnabled(enabled);
127 	buttonLayers->setEnabled(enabled);
128 	buttonEdit->setEnabled(enabled);
129 }
130 
fillTable()131 void PicStatus::fillTable()
132 {
133 	PageItem *item;
134 	imageViewArea->clear();
135 	QListWidgetItem *firstItem=nullptr;
136 	QListWidgetItem *tempItem=nullptr;
137 
138 	QList<PageItem*> allItems;
139 	for (int i = 0; i < m_Doc->MasterItems.count(); ++i)
140 	{
141 		PageItem *currItem = m_Doc->MasterItems.at(i);
142 		if (currItem->isGroup())
143 			allItems = currItem->getAllChildren();
144 		else
145 			allItems.append(currItem);
146 		for (int ii = 0; ii < allItems.count(); ii++)
147 		{
148 			item = allItems.at(ii);
149 			QFileInfo fi = QFileInfo(item->Pfile);
150 			QString Iname = "";
151 			if (item->isInlineImage)
152 				Iname = tr("Embedded Image");
153 			else
154 				Iname = fi.fileName();
155 			if ((item->itemType() == PageItem::ImageFrame) && (!item->isLatexFrame()))
156 				tempItem = new PicItem(imageViewArea, Iname, createImgIcon(item), item);
157 			if (firstItem == nullptr)
158 				firstItem = tempItem;
159 		}
160 		allItems.clear();
161 	}
162 	allItems.clear();
163 	for (int i = 0; i < m_Doc->DocItems.count(); ++i)
164 	{
165 		PageItem *currItem = m_Doc->DocItems.at(i);
166 		if (currItem->isGroup())
167 			allItems = currItem->getAllChildren();
168 		else
169 			allItems.append(currItem);
170 		for (int ii = 0; ii < allItems.count(); ii++)
171 		{
172 			item = allItems.at(ii);
173 			QFileInfo fi = QFileInfo(item->Pfile);
174 			QString Iname = "";
175 			if (item->isInlineImage)
176 				Iname = tr("Embedded Image");
177 			else
178 				Iname = fi.fileName();
179 			if ((item->itemType() == PageItem::ImageFrame) && (!item->isLatexFrame()))
180 				tempItem = new PicItem(imageViewArea, Iname, createImgIcon(item), item);
181 			// if an image is selected in a doc, Manage Pictures should
182 			// display the selected image and its values
183 			if (firstItem == nullptr || item->isSelected())
184 				firstItem = tempItem;
185 		}
186 		allItems.clear();
187 	}
188 	imageViewArea->setCurrentItem(firstItem);
189 	if (firstItem!=nullptr)
190 		imageSelected(firstItem);
191 
192 	// Disable all features when there is no image in the document.
193 	// It should never be used (see ScribusMainWindow::extrasMenuAboutToShow())
194 	// but who knows if it can be configured for shortcut or macro...
195 	imageViewArea->setEnabled(imageViewArea->count() > 0);
196 	workTab->setEnabled(imageViewArea->count() > 0);
197 	sortByName();
198 }
199 
sortByName()200 void PicStatus::sortByName()
201 {
202 	QMultiMap<QString, PicItem*> sorted;
203 
204 	int num = imageViewArea->count();
205 	if (num == 0)
206 		return;
207 
208 	auto firstItem = imageViewArea->currentItem();
209 	for (int i = num - 1; i > -1; --i)
210 	{
211 		QListWidgetItem *ite = imageViewArea->takeItem(i);
212 		PicItem *item = (PicItem*) ite;
213 		QFileInfo fi = QFileInfo(item->PageItemObject->Pfile);
214 		sorted.insert(fi.fileName(), item);
215 	}
216 
217 	int counter = 0;
218 	foreach (const QString& i, sorted.uniqueKeys())
219 	{
220 		foreach (PicItem* val, sorted.values(i))
221 		{
222 			imageViewArea->insertItem(counter, val);
223 			counter++;
224 		}
225 	}
226 	imageViewArea->setCurrentItem(firstItem);
227 	imageSelected(firstItem);
228 	sortOrder = 0;
229 }
230 
sortByPage()231 void PicStatus::sortByPage()
232 {
233 	QMap<int, PicItem*> sorted;
234 
235 	int num = imageViewArea->count();
236 	if (num == 0)
237 		return;
238 
239 	auto firstItem = imageViewArea->currentItem();
240 	for (int a = num-1; a > -1; --a)
241 	{
242 		QListWidgetItem *ite = imageViewArea->takeItem(a);
243 		PicItem *item = (PicItem*)ite;
244 		sorted.insertMulti(item->PageItemObject->OwnPage, item);
245 	}
246 	int counter = 0;
247 	foreach (int i, sorted.uniqueKeys())
248 	{
249 		foreach (PicItem* val, sorted.values(i))
250 		{
251 			imageViewArea->insertItem(counter, val);
252 			counter++;
253 		}
254 	}
255 	imageViewArea->setCurrentItem(firstItem);
256 	imageSelected(firstItem);
257 	sortOrder = 1;
258 }
259 
slotRightClick()260 void PicStatus::slotRightClick()
261 {
262 	QMenu *pmen = new QMenu();
263 	qApp->changeOverrideCursor(QCursor(Qt::ArrowCursor));
264 	QAction* Act1 = pmen->addAction( tr("Sort by Name"));
265 	Act1->setCheckable(true);
266 	QAction* Act2 = pmen->addAction( tr("Sort by Page"));
267 	Act2->setCheckable(true);
268 	if (sortOrder == 0)
269 		Act1->setChecked(true);
270 	else if (sortOrder == 1)
271 		Act2->setChecked(true);
272 	connect(Act1, SIGNAL(triggered()), this, SLOT(sortByName()));
273 	connect(Act2, SIGNAL(triggered()), this, SLOT(sortByPage()));
274 	pmen->exec(QCursor::pos());
275 	delete pmen;
276 }
277 
newImageSelected()278 void PicStatus::newImageSelected()
279 {
280 	QList<QListWidgetItem*> items = imageViewArea->selectedItems();
281 	imageSelected((items.count() > 0) ? items.at(0) : nullptr);
282 }
283 
imageSelected(QListWidgetItem * ite)284 void PicStatus::imageSelected(QListWidgetItem *ite)
285 {
286 	if (ite == nullptr)
287 	{
288 		currItem = nullptr;
289 		enableWidgets(false);
290 		return;
291 	}
292 
293 	enableWidgets(true);
294 
295 	PicItem *item = (PicItem*) ite;
296 	currItem = item->PageItemObject;
297 	if (!currItem->OnMasterPage.isEmpty())
298 		displayPage->setText(currItem->OnMasterPage);
299 	else
300 	{
301 		if (currItem->OwnPage == -1)
302 			displayPage->setText(  tr("Not on a Page"));
303 		else
304 			displayPage->setText(QString::number(currItem->OwnPage + 1));
305 	}
306 	displayObjekt->setText(currItem->itemName());
307 	if (currItem->imageIsAvailable)
308 	{
309 		QFileInfo fi = QFileInfo(currItem->Pfile);
310 		QString ext = fi.suffix().toLower();
311 		if (currItem->isInlineImage)
312 		{
313 			displayName->setText( tr("Embedded Image"));
314 			displayPath->setText("");
315 			searchButton->setEnabled(false);
316 			fileManagerButton->setEnabled(false);
317 		}
318 		else
319 		{
320 			displayName->setText(fi.fileName());
321 			displayPath->setText(QDir::toNativeSeparators(fi.path()));
322 			searchButton->setEnabled(true);
323 			fileManagerButton->setEnabled(true);
324 		}
325 		QString format = "";
326 		switch (currItem->pixm.imgInfo.type)
327 		{
328 			case 0:
329 				format = tr("JPG");
330 				break;
331 			case 1:
332 				format = tr("TIFF");
333 				break;
334 			case 2:
335 				format = tr("PSD");
336 				break;
337 			case 3:
338 				format = tr("EPS/PS");
339 				break;
340 			case 4:
341 				format = tr("PDF");
342 				break;
343 			case 5:
344 				format = tr("JPG2000");
345 				break;
346 			case 6:
347 				format = ext.toUpper();
348 				break;
349 			case 7:
350 				format = tr("emb. PSD");
351 				break;
352 		}
353 		displayFormat->setText(format);
354 		QString cSpace;
355 		if ((extensionIndicatesPDF(ext) || extensionIndicatesEPSorPS(ext)) && (currItem->pixm.imgInfo.type != ImageType7))
356 			cSpace = tr("Unknown");
357 		else
358 			cSpace=colorSpaceText(currItem->pixm.imgInfo.colorspace);
359 		displayColorspace->setText(cSpace);
360 		displayDPI->setText(QString("%1 x %2").arg(currItem->pixm.imgInfo.xres).arg(currItem->pixm.imgInfo.yres));
361 		displayEffDPI->setText(QString("%1 x %2").arg(qRound(72.0 / currItem->imageXScale())).arg(qRound(72.0 / currItem->imageYScale())));
362 		displaySizePixel->setText(QString("%1 x %2").arg(currItem->OrigW).arg(currItem->OrigH));
363 		displayScale->setText(QString("%1 x %2 %").arg(currItem->imageXScale() * 100 / 72.0 * currItem->pixm.imgInfo.xres, 5, 'f', 1).arg(currItem->imageYScale() * 100 / 72.0 * currItem->pixm.imgInfo.yres, 5, 'f', 1));
364 		displayPrintSize->setText(QString("%1 x %2%3").arg(currItem->OrigW * currItem->imageXScale() * m_Doc->unitRatio(), 7, 'f', 2).arg(currItem->OrigH * currItem->imageXScale() * m_Doc->unitRatio(), 7, 'f', 2).arg(unitGetSuffixFromIndex(m_Doc->unitIndex())));
365 		isPrinting->setChecked(currItem->printEnabled());
366 		isVisibleCheck->setChecked(currItem->imageVisible());
367 		buttonEdit->setEnabled(currItem->isRaster);
368 		effectsButton->setEnabled(currItem->isRaster);
369 		buttonLayers->setEnabled(currItem->pixm.imgInfo.valid);
370 	}
371 	else
372 	{
373 		QString trNA = tr("n/a");
374 		if (!currItem->Pfile.isEmpty())
375 		{
376 			QFileInfo fi = QFileInfo(currItem->Pfile);
377 			displayName->setText(fi.fileName());
378 			displayPath->setText(QDir::toNativeSeparators(fi.path()));
379 			searchButton->setEnabled(true);
380 			fileManagerButton->setEnabled(true);
381 		}
382 		else
383 		{
384 			displayName->setText(trNA);
385 			displayPath->setText(trNA);
386 			searchButton->setEnabled(false);
387 			fileManagerButton->setEnabled(false);
388 		}
389 		displayFormat->setText(trNA);
390 		displayColorspace->setText(trNA);
391 		displayDPI->setText(trNA);
392 		displayEffDPI->setText(trNA);
393 		displaySizePixel->setText(trNA);
394 		displayScale->setText(trNA);
395 		displayPrintSize->setText(trNA);
396 		buttonEdit->setEnabled(false);
397 		effectsButton->setEnabled(false);
398 		buttonLayers->setEnabled(false);
399 	}
400 }
401 
PrintPic()402 void PicStatus::PrintPic()
403 {
404 	if (currItem != nullptr)
405 		currItem->setPrintEnabled(isPrinting->isChecked());
406 }
407 
visiblePic()408 void PicStatus::visiblePic()
409 {
410 	if (currItem == nullptr)
411 		return;
412 	currItem->setImageVisible(isVisibleCheck->isChecked());
413 }
414 
GotoPic()415 void PicStatus::GotoPic()
416 {
417 	if (currItem == nullptr)
418 		return;
419 
420 	if (currItem->OnMasterPage.isEmpty() && m_Doc->masterPageMode())
421 		ScCore->primaryMainWindow()->closeActiveWindowMasterPageEditor();
422 	if (!currItem->OnMasterPage.isEmpty())
423 		emit selectMasterPage(currItem->OnMasterPage);
424 	else
425 		emit selectPage(currItem->OwnPage);
426 
427 	emit selectElementByItem(currItem, true, 1);
428 }
429 
SelectPic()430 void PicStatus::SelectPic()
431 {
432 	if (currItem == nullptr)
433 		return;
434 
435 	if (currItem->OnMasterPage.isEmpty() && m_Doc->masterPageMode())
436 		ScCore->primaryMainWindow()->closeActiveWindowMasterPageEditor();
437 	else
438 		if (!currItem->OnMasterPage.isEmpty() && !m_Doc->masterPageMode())
439 			emit selectMasterPage(currItem->OnMasterPage);
440 
441 	emit selectElementByItem(currItem, true, 1);
442 }
443 
loadPict(PageItem * item,const QString & newFilePath)444 bool PicStatus::loadPict(PageItem* item, const QString & newFilePath)
445 {
446 	// Hack to fool the LoadPict function
447 	item->Pfile = newFilePath;
448 	bool masterPageMode = !item->OnMasterPage.isEmpty();
449 	bool oldMasterPageMode = m_Doc->masterPageMode();
450 	if (masterPageMode != oldMasterPageMode)
451 		m_Doc->setMasterPageMode(masterPageMode);
452 	m_Doc->loadPict(newFilePath, item, true);
453 	if (masterPageMode != oldMasterPageMode)
454 		m_Doc->setMasterPageMode(oldMasterPageMode);
455 	return item->imageIsAvailable;
456 }
457 
SearchPic()458 void PicStatus::SearchPic()
459 {
460 	// no action where is no item selected. It should never happen.
461 	if (currItem == nullptr)
462 		return;
463 	static QString lastSearchPath;
464 
465 	if (lastSearchPath.isEmpty())
466 		lastSearchPath = displayPath->text();
467 
468 	QScopedPointer<PicSearchOptions> dia(new PicSearchOptions(this, displayName->text(), lastSearchPath));
469 	if (dia->exec() != QDialog::Accepted)
470 		return;
471 
472 	lastSearchPath = dia->getLastDirSearched();
473 	if (dia->getMatches().count() == 0)
474 	{
475 		ScMessageBox::information(this, tr("Scribus - Image Search"), tr("No images named \"%1\" were found.").arg(dia->getFileName()),
476 				QMessageBox::Ok|QMessageBox::Default|QMessageBox::Escape,
477 				QMessageBox::NoButton);
478 		return;
479 	}
480 
481 	auto item = static_cast<PicItem*>(imageViewArea->currentItem());
482 	bool brokenLink = !(item->PageItemObject->imageIsAvailable);
483 
484 	QScopedPointer<PicSearch> dia2(new PicSearch(this, dia->getFileName(), dia->getMatches(), brokenLink));
485 	if (dia2->exec() != QDialog::Accepted)
486 		return;
487 
488 	auto source = QFileInfo(currItem->Pfile);
489 
490 	loadPict(currItem, dia2->getSelectedImage());
491 	auto target = QFileInfo(currItem->Pfile);
492 	item->setText(target.fileName());
493 	item->setIcon(createImgIcon(currItem));
494 	imageSelected(imageViewArea->currentItem());
495 
496 	if (dia2->isApplyToMatchingImages())
497 		relinkMatchingImages(source, target, brokenLink);
498 }
499 
FileManager()500 void PicStatus::FileManager()
501 {
502 	if (currItem == nullptr)
503 		return;
504 	QFileInfo fi = QFileInfo(currItem->Pfile);
505 	QString path = fi.canonicalPath();
506 	if (path.isEmpty())
507 		return;
508 	QDesktopServices::openUrl(QUrl::fromLocalFile(path));
509 }
510 
doImageEffects()511 void PicStatus::doImageEffects()
512 {
513 	if (currItem == nullptr)
514 		return;
515 
516 	EffectsDialog* dia = new EffectsDialog(this, currItem, m_Doc);
517 	if (dia->exec())
518 	{
519 		currItem->effectsInUse = dia->effectsList;
520 		loadPict(currItem, currItem->Pfile);
521 		imageViewArea->currentItem()->setIcon(createImgIcon(currItem));
522 	}
523 	delete dia;
524 }
525 
doImageExtProp()526 void PicStatus::doImageExtProp()
527 {
528 	if (currItem == nullptr)
529 		return;
530 
531 	ExtImageProps dia(this, &currItem->pixm.imgInfo, currItem, m_Doc->view());
532 	if (dia.exec())
533 	{
534 		loadPict(currItem, currItem->Pfile);
535 		imageViewArea->currentItem()->setIcon(createImgIcon(currItem));
536 	}
537 }
538 
doEditImage()539 void PicStatus::doEditImage()
540 {
541 	if (currItem == nullptr)
542 		return;
543 	SelectPic();
544 	ScCore->primaryMainWindow()->callImageEditor();
545 }
546 
547 /**
548  * Relink all matching images.
549  * Images match if they have the same path as the pattern.
550  * If the "pattern" was a broken link, only images with broken links will match.
551  */
relinkMatchingImages(const QFileInfo & source,const QFileInfo & target,bool brokenLink)552 void PicStatus::relinkMatchingImages(const QFileInfo& source, const QFileInfo& target, bool brokenLink)
553 {
554 	for (int i = 0; i < imageViewArea->count(); i++)
555 	{
556 		auto item = static_cast<PicItem*>(imageViewArea->item(i));
557 
558 		if (brokenLink && item->PageItemObject->imageIsAvailable)
559 			continue;
560 
561 		auto fi = QFileInfo(item->PageItemObject->Pfile);
562 
563 		if (fi.path() != source.path())
564 			continue;
565 		if (fi.fileName() == target.fileName())
566 			continue;
567 
568 		loadPict(item->PageItemObject, QDir(target.path()).filePath(fi.fileName()));
569 		item->setText(fi.fileName());
570 		item->setIcon(createImgIcon(item->PageItemObject));
571 	}
572 }
573