DigiKam – digital photo management application
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

687 lines
18 KiB

  1. /* ============================================================
  2. *
  3. * This file is a part of digiKam project
  4. * http://www.digikam.org
  5. *
  6. * Date : 2006-21-12
  7. * Description : a embedded view to show the image preview widget.
  8. *
  9. * Copyright (C) 2006-2009 Gilles Caulier <caulier dot gilles at gmail dot com>
  10. *
  11. * This program is free software; you can redistribute it
  12. * and/or modify it under the terms of the GNU General
  13. * Public License as published by the Free Software Foundation;
  14. * either version 2, or (at your option)
  15. * any later version.
  16. *
  17. * This program is distributed in the hope that it will be useful,
  18. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. * GNU General Public License for more details.
  21. *
  22. * ============================================================ */
  23. // TQt includes.
  24. #include <tqpainter.h>
  25. #include <tqcursor.h>
  26. #include <tqstring.h>
  27. #include <tqvaluevector.h>
  28. #include <tqfileinfo.h>
  29. #include <tqtoolbutton.h>
  30. #include <tqtooltip.h>
  31. #include <tqpixmap.h>
  32. // KDE includes.
  33. #include <kdialogbase.h>
  34. #include <tdelocale.h>
  35. #include <kservice.h>
  36. #include <krun.h>
  37. #include <ktrader.h>
  38. #include <kmimetype.h>
  39. #include <kiconloader.h>
  40. #include <kcursor.h>
  41. #include <kdatetbl.h>
  42. #include <kiconloader.h>
  43. #include <kprocess.h>
  44. #include <tdeapplication.h>
  45. // LibKipi includes.
  46. #include <libkipi/pluginloader.h>
  47. #include <libkipi/plugin.h>
  48. // Local includes.
  49. #include "dimg.h"
  50. #include "ddebug.h"
  51. #include "albumdb.h"
  52. #include "albummanager.h"
  53. #include "albumsettings.h"
  54. #include "albumwidgetstack.h"
  55. #include "imageinfo.h"
  56. #include "dmetadata.h"
  57. #include "dpopupmenu.h"
  58. #include "metadatahub.h"
  59. #include "paniconwidget.h"
  60. #include "previewloadthread.h"
  61. #include "loadingdescription.h"
  62. #include "tagspopupmenu.h"
  63. #include "ratingpopupmenu.h"
  64. #include "themeengine.h"
  65. #include "imagepreviewview.h"
  66. #include "imagepreviewview.moc"
  67. namespace Digikam
  68. {
  69. class ImagePreviewViewPriv
  70. {
  71. public:
  72. ImagePreviewViewPriv()
  73. {
  74. panIconPopup = 0;
  75. panIconWidget = 0;
  76. cornerButton = 0;
  77. previewThread = 0;
  78. previewPreloadThread = 0;
  79. imageInfo = 0;
  80. parent = 0;
  81. hasPrev = false;
  82. hasNext = false;
  83. loadFullImageSize = false;
  84. currentFitWindowZoom = 0;
  85. previewSize = 1024;
  86. }
  87. bool hasPrev;
  88. bool hasNext;
  89. bool loadFullImageSize;
  90. int previewSize;
  91. double currentFitWindowZoom;
  92. TQString path;
  93. TQString nextPath;
  94. TQString previousPath;
  95. TQToolButton *cornerButton;
  96. TDEPopupFrame *panIconPopup;
  97. PanIconWidget *panIconWidget;
  98. DImg preview;
  99. ImageInfo *imageInfo;
  100. PreviewLoadThread *previewThread;
  101. PreviewLoadThread *previewPreloadThread;
  102. AlbumWidgetStack *parent;
  103. };
  104. ImagePreviewView::ImagePreviewView(AlbumWidgetStack *parent)
  105. : PreviewWidget(parent)
  106. {
  107. d = new ImagePreviewViewPriv;
  108. d->parent = parent;
  109. // get preview size from screen size, but limit from VGA to WQXGA
  110. d->previewSize = TQMAX(TDEApplication::desktop()->height(),
  111. TDEApplication::desktop()->width());
  112. if (d->previewSize < 640)
  113. d->previewSize = 640;
  114. if (d->previewSize > 2560)
  115. d->previewSize = 2560;
  116. setSizePolicy(TQSizePolicy::Expanding, TQSizePolicy::Expanding);
  117. d->cornerButton = new TQToolButton(this);
  118. d->cornerButton->setIconSet(SmallIcon("move"));
  119. d->cornerButton->hide();
  120. TQToolTip::add(d->cornerButton, i18n("Pan the image to a region"));
  121. setCornerWidget(d->cornerButton);
  122. // ------------------------------------------------------------
  123. connect(d->cornerButton, TQT_SIGNAL(pressed()),
  124. this, TQT_SLOT(slotCornerButtonPressed()));
  125. connect(this, TQT_SIGNAL(signalShowNextImage()),
  126. this, TQT_SIGNAL(signalNextItem()));
  127. connect(this, TQT_SIGNAL(signalShowPrevImage()),
  128. this, TQT_SIGNAL(signalPrevItem()));
  129. connect(this, TQT_SIGNAL(signalRightButtonClicked()),
  130. this, TQT_SLOT(slotContextMenu()));
  131. connect(this, TQT_SIGNAL(signalLeftButtonClicked()),
  132. this, TQT_SIGNAL(signalBack2Album()));
  133. connect(ThemeEngine::instance(), TQT_SIGNAL(signalThemeChanged()),
  134. this, TQT_SLOT(slotThemeChanged()));
  135. // ------------------------------------------------------------
  136. slotReset();
  137. }
  138. ImagePreviewView::~ImagePreviewView()
  139. {
  140. delete d->previewThread;
  141. delete d->previewPreloadThread;
  142. delete d;
  143. }
  144. void ImagePreviewView::setLoadFullImageSize(bool b)
  145. {
  146. d->loadFullImageSize = b;
  147. reload();
  148. }
  149. void ImagePreviewView::setImage(const DImg& image)
  150. {
  151. d->preview = image;
  152. updateZoomAndSize(true);
  153. viewport()->setUpdatesEnabled(true);
  154. viewport()->update();
  155. }
  156. DImg& ImagePreviewView::getImage() const
  157. {
  158. return d->preview;
  159. }
  160. void ImagePreviewView::reload()
  161. {
  162. // cache is cleaned from AlbumIconView::refreshItems
  163. setImagePath(d->path);
  164. }
  165. void ImagePreviewView::setPreviousNextPaths(const TQString& previous, const TQString &next)
  166. {
  167. d->nextPath = next;
  168. d->previousPath = previous;
  169. }
  170. void ImagePreviewView::setImagePath(const TQString& path)
  171. {
  172. setCursor( KCursor::waitCursor() );
  173. d->path = path;
  174. d->nextPath = TQString();
  175. d->previousPath = TQString();
  176. if (d->path.isEmpty())
  177. {
  178. slotReset();
  179. unsetCursor();
  180. return;
  181. }
  182. if (!d->previewThread)
  183. {
  184. d->previewThread = new PreviewLoadThread();
  185. connect(d->previewThread, TQT_SIGNAL(signalImageLoaded(const LoadingDescription &, const DImg &)),
  186. this, TQT_SLOT(slotGotImagePreview(const LoadingDescription &, const DImg&)));
  187. }
  188. if (!d->previewPreloadThread)
  189. {
  190. d->previewPreloadThread = new PreviewLoadThread();
  191. connect(d->previewPreloadThread, TQT_SIGNAL(signalImageLoaded(const LoadingDescription &, const DImg &)),
  192. this, TQT_SLOT(slotNextPreload()));
  193. }
  194. if (d->loadFullImageSize)
  195. d->previewThread->loadHighQuality(LoadingDescription(path, 0, AlbumSettings::instance()->getExifRotate()));
  196. else
  197. d->previewThread->load(LoadingDescription(path, d->previewSize, AlbumSettings::instance()->getExifRotate()));
  198. }
  199. void ImagePreviewView::slotGotImagePreview(const LoadingDescription &description, const DImg& preview)
  200. {
  201. if (description.filePath != d->path)
  202. return;
  203. if (preview.isNull())
  204. {
  205. d->parent->setPreviewMode(AlbumWidgetStack::PreviewImageMode);
  206. TQPixmap pix(visibleWidth(), visibleHeight());
  207. pix.fill(ThemeEngine::instance()->baseColor());
  208. TQPainter p(&pix);
  209. TQFileInfo info(d->path);
  210. p.setPen(TQPen(ThemeEngine::instance()->textRegColor()));
  211. p.drawText(0, 0, pix.width(), pix.height(),
  212. TQt::AlignCenter|TQt::WordBreak,
  213. i18n("Cannot display preview for\n\"%1\"")
  214. .arg(info.fileName()));
  215. p.end();
  216. // three copies - but the image is small
  217. setImage(DImg(pix.convertToImage()));
  218. d->parent->previewLoaded();
  219. emit signalPreviewLoaded(false);
  220. }
  221. else
  222. {
  223. DImg img(preview);
  224. if (AlbumSettings::instance()->getExifRotate())
  225. d->previewThread->exifRotate(img, description.filePath);
  226. d->parent->setPreviewMode(AlbumWidgetStack::PreviewImageMode);
  227. setImage(img);
  228. d->parent->previewLoaded();
  229. emit signalPreviewLoaded(true);
  230. }
  231. unsetCursor();
  232. slotNextPreload();
  233. }
  234. void ImagePreviewView::slotNextPreload()
  235. {
  236. TQString loadPath;
  237. if (!d->nextPath.isNull())
  238. {
  239. loadPath = d->nextPath;
  240. d->nextPath = TQString();
  241. }
  242. else if (!d->previousPath.isNull())
  243. {
  244. loadPath = d->previousPath;
  245. d->previousPath = TQString();
  246. }
  247. else
  248. return;
  249. if (d->loadFullImageSize)
  250. d->previewThread->loadHighQuality(LoadingDescription(loadPath, 0,
  251. AlbumSettings::instance()->getExifRotate()));
  252. else
  253. d->previewPreloadThread->load(LoadingDescription(loadPath, d->previewSize,
  254. AlbumSettings::instance()->getExifRotate()));
  255. }
  256. void ImagePreviewView::setImageInfo(ImageInfo* info, ImageInfo *previous, ImageInfo *next)
  257. {
  258. d->imageInfo = info;
  259. d->hasPrev = previous;
  260. d->hasNext = next;
  261. if (d->imageInfo)
  262. setImagePath(info->filePath());
  263. else
  264. setImagePath();
  265. setPreviousNextPaths(previous ? previous->filePath() : TQString(),
  266. next ? next->filePath() : TQString());
  267. }
  268. ImageInfo* ImagePreviewView::getImageInfo() const
  269. {
  270. return d->imageInfo;
  271. }
  272. void ImagePreviewView::slotContextMenu()
  273. {
  274. RatingPopupMenu *ratingMenu = 0;
  275. TagsPopupMenu *assignTagsMenu = 0;
  276. TagsPopupMenu *removeTagsMenu = 0;
  277. if (!d->imageInfo)
  278. return;
  279. //-- Open With Actions ------------------------------------
  280. KURL url(d->imageInfo->kurl().path());
  281. KMimeType::Ptr mimePtr = KMimeType::findByURL(url, 0, true, true);
  282. TQValueVector<KService::Ptr> serviceVector;
  283. TDETrader::OfferList offers = TDETrader::self()->query(mimePtr->name(), "Type == 'Application'");
  284. TQPopupMenu openWithMenu;
  285. TDETrader::OfferList::Iterator iter;
  286. KService::Ptr ptr;
  287. int index = 100;
  288. for( iter = offers.begin(); iter != offers.end(); ++iter )
  289. {
  290. ptr = *iter;
  291. openWithMenu.insertItem( ptr->pixmap(TDEIcon::Small), ptr->name(), index++);
  292. serviceVector.push_back(ptr);
  293. }
  294. //-- Navigate actions -------------------------------------------
  295. DPopupMenu popmenu(this);
  296. popmenu.insertItem(SmallIcon("back"), i18n("Back"), 10);
  297. if (!d->hasPrev) popmenu.setItemEnabled(10, false);
  298. popmenu.insertItem(SmallIcon("forward"), i18n("Forward"), 11);
  299. if (!d->hasNext) popmenu.setItemEnabled(11, false);
  300. popmenu.insertItem(SmallIcon("folder_image"), i18n("Back to Album"), 15);
  301. //-- Edit actions -----------------------------------------------
  302. popmenu.insertSeparator();
  303. popmenu.insertItem(SmallIcon("slideshow"), i18n("SlideShow"), 16);
  304. popmenu.insertItem(SmallIcon("editimage"), i18n("Edit..."), 12);
  305. popmenu.insertItem(SmallIcon("lighttableadd"), i18n("Add to Light Table"), 17);
  306. popmenu.insertItem(i18n("Open With"), &openWithMenu, 13);
  307. // Merge in the KIPI plugins actions ----------------------------
  308. KIPI::PluginLoader* kipiPluginLoader = KIPI::PluginLoader::instance();
  309. KIPI::PluginLoader::PluginList pluginList = kipiPluginLoader->pluginList();
  310. for (KIPI::PluginLoader::PluginList::const_iterator it = pluginList.begin();
  311. it != pluginList.end(); ++it)
  312. {
  313. KIPI::Plugin* plugin = (*it)->plugin();
  314. if (plugin && (*it)->name() == "JPEGLossless")
  315. {
  316. DDebug() << "Found JPEGLossless plugin" << endl;
  317. TDEActionPtrList actionList = plugin->actions();
  318. for (TDEActionPtrList::const_iterator iter = actionList.begin();
  319. iter != actionList.end(); ++iter)
  320. {
  321. TDEAction* action = *iter;
  322. if (TQString::fromLatin1(action->name())
  323. == TQString::fromLatin1("jpeglossless_rotate"))
  324. {
  325. action->plug(&popmenu);
  326. }
  327. }
  328. }
  329. }
  330. //-- Trash action -------------------------------------------
  331. popmenu.insertSeparator();
  332. popmenu.insertItem(SmallIcon("edittrash"), i18n("Move to Trash"), 14);
  333. // Bulk assignment/removal of tags --------------------------
  334. TQ_LLONG id = d->imageInfo->id();
  335. TQValueList<TQ_LLONG> idList;
  336. idList.append(id);
  337. assignTagsMenu = new TagsPopupMenu(idList, 1000, TagsPopupMenu::ASSIGN);
  338. removeTagsMenu = new TagsPopupMenu(idList, 2000, TagsPopupMenu::REMOVE);
  339. popmenu.insertSeparator();
  340. popmenu.insertItem(i18n("Assign Tag"), assignTagsMenu);
  341. int i = popmenu.insertItem(i18n("Remove Tag"), removeTagsMenu);
  342. connect(assignTagsMenu, TQT_SIGNAL(signalTagActivated(int)),
  343. this, TQT_SLOT(slotAssignTag(int)));
  344. connect(removeTagsMenu, TQT_SIGNAL(signalTagActivated(int)),
  345. this, TQT_SLOT(slotRemoveTag(int)));
  346. AlbumDB* db = AlbumManager::instance()->albumDB();
  347. if (!db->hasTags( idList ))
  348. popmenu.setItemEnabled(i, false);
  349. popmenu.insertSeparator();
  350. // Assign Star Rating -------------------------------------------
  351. ratingMenu = new RatingPopupMenu();
  352. connect(ratingMenu, TQT_SIGNAL(activated(int)),
  353. this, TQT_SLOT(slotAssignRating(int)));
  354. popmenu.insertItem(i18n("Assign Rating"), ratingMenu);
  355. // --------------------------------------------------------
  356. int idm = popmenu.exec(TQCursor::pos());
  357. switch(idm)
  358. {
  359. case 10: // Back
  360. {
  361. emit signalPrevItem();
  362. break;
  363. }
  364. case 11: // Forward
  365. {
  366. emit signalNextItem();
  367. break;
  368. }
  369. case 12: // Edit...
  370. {
  371. emit signalEditItem();
  372. break;
  373. }
  374. case 14: // Move to trash
  375. {
  376. emit signalDeleteItem();
  377. break;
  378. }
  379. case 15: // Back to album
  380. {
  381. emit signalBack2Album();
  382. break;
  383. }
  384. case 16: // SlideShow
  385. {
  386. emit signalSlideShow();
  387. break;
  388. }
  389. case 17: // Place onto Light Table
  390. {
  391. emit signalInsert2LightTable();
  392. break;
  393. }
  394. default:
  395. break;
  396. }
  397. // Open With...
  398. if (idm >= 100 && idm < 1000)
  399. {
  400. KService::Ptr imageServicePtr = serviceVector[idm-100];
  401. KRun::run(*imageServicePtr, url);
  402. }
  403. serviceVector.clear();
  404. delete assignTagsMenu;
  405. delete removeTagsMenu;
  406. delete ratingMenu;
  407. }
  408. void ImagePreviewView::slotAssignTag(int tagID)
  409. {
  410. if (d->imageInfo)
  411. {
  412. MetadataHub hub;
  413. hub.load(d->imageInfo);
  414. hub.setTag(tagID, true);
  415. hub.write(d->imageInfo, MetadataHub::PartialWrite);
  416. hub.write(d->imageInfo->filePath(), MetadataHub::FullWriteIfChanged);
  417. }
  418. }
  419. void ImagePreviewView::slotRemoveTag(int tagID)
  420. {
  421. if (d->imageInfo)
  422. {
  423. MetadataHub hub;
  424. hub.load(d->imageInfo);
  425. hub.setTag(tagID, false);
  426. hub.write(d->imageInfo, MetadataHub::PartialWrite);
  427. hub.write(d->imageInfo->filePath(), MetadataHub::FullWriteIfChanged);
  428. }
  429. }
  430. void ImagePreviewView::slotAssignRating(int rating)
  431. {
  432. rating = TQMIN(5, TQMAX(0, rating));
  433. if (d->imageInfo)
  434. {
  435. MetadataHub hub;
  436. hub.load(d->imageInfo);
  437. hub.setRating(rating);
  438. hub.write(d->imageInfo, MetadataHub::PartialWrite);
  439. hub.write(d->imageInfo->filePath(), MetadataHub::FullWriteIfChanged);
  440. }
  441. }
  442. void ImagePreviewView::slotThemeChanged()
  443. {
  444. setBackgroundColor(ThemeEngine::instance()->baseColor());
  445. }
  446. void ImagePreviewView::slotCornerButtonPressed()
  447. {
  448. if (d->panIconPopup)
  449. {
  450. d->panIconPopup->hide();
  451. delete d->panIconPopup;
  452. d->panIconPopup = 0;
  453. }
  454. d->panIconPopup = new TDEPopupFrame(this);
  455. PanIconWidget *pan = new PanIconWidget(d->panIconPopup);
  456. pan->setImage(180, 120, getImage());
  457. d->panIconPopup->setMainWidget(pan);
  458. TQRect r((int)(contentsX() / zoomFactor()), (int)(contentsY() / zoomFactor()),
  459. (int)(visibleWidth() / zoomFactor()), (int)(visibleHeight() / zoomFactor()));
  460. pan->setRegionSelection(r);
  461. pan->setMouseFocus();
  462. connect(pan, TQT_SIGNAL(signalSelectionMoved(const TQRect&, bool)),
  463. this, TQT_SLOT(slotPanIconSelectionMoved(const TQRect&, bool)));
  464. connect(pan, TQT_SIGNAL(signalHiden()),
  465. this, TQT_SLOT(slotPanIconHiden()));
  466. TQPoint g = mapToGlobal(viewport()->pos());
  467. g.setX(g.x()+ viewport()->size().width());
  468. g.setY(g.y()+ viewport()->size().height());
  469. d->panIconPopup->popup(TQPoint(g.x() - d->panIconPopup->width(),
  470. g.y() - d->panIconPopup->height()));
  471. pan->setCursorToLocalRegionSelectionCenter();
  472. }
  473. void ImagePreviewView::slotPanIconHiden()
  474. {
  475. d->cornerButton->blockSignals(true);
  476. d->cornerButton->animateClick();
  477. d->cornerButton->blockSignals(false);
  478. }
  479. void ImagePreviewView::slotPanIconSelectionMoved(const TQRect& r, bool b)
  480. {
  481. setContentsPos((int)(r.x()*zoomFactor()), (int)(r.y()*zoomFactor()));
  482. if (b)
  483. {
  484. d->panIconPopup->hide();
  485. delete d->panIconPopup;
  486. d->panIconPopup = 0;
  487. slotPanIconHiden();
  488. }
  489. }
  490. void ImagePreviewView::zoomFactorChanged(double zoom)
  491. {
  492. updateScrollBars();
  493. if (horizontalScrollBar()->isVisible() || verticalScrollBar()->isVisible())
  494. d->cornerButton->show();
  495. else
  496. d->cornerButton->hide();
  497. PreviewWidget::zoomFactorChanged(zoom);
  498. }
  499. void ImagePreviewView::resizeEvent(TQResizeEvent* e)
  500. {
  501. if (!e) return;
  502. TQScrollView::resizeEvent(e);
  503. if (!d->imageInfo)
  504. d->cornerButton->hide();
  505. updateZoomAndSize(false);
  506. }
  507. void ImagePreviewView::updateZoomAndSize(bool alwaysFitToWindow)
  508. {
  509. // Set zoom for fit-in-window as minimum, but dont scale up images
  510. // that are smaller than the available space, only scale down.
  511. double zoom = calcAutoZoomFactor(ZoomInOnly);
  512. setZoomMin(zoom);
  513. setZoomMax(zoom*12.0);
  514. // Is currently the zoom factor set to fit to window? Then set it again to fit the new size.
  515. if (zoomFactor() < zoom || alwaysFitToWindow || zoomFactor() == d->currentFitWindowZoom)
  516. {
  517. setZoomFactor(zoom);
  518. }
  519. // store which zoom factor means it is fit to window
  520. d->currentFitWindowZoom = zoom;
  521. updateContentsSize();
  522. }
  523. int ImagePreviewView::previewWidth()
  524. {
  525. return d->preview.width();
  526. }
  527. int ImagePreviewView::previewHeight()
  528. {
  529. return d->preview.height();
  530. }
  531. bool ImagePreviewView::previewIsNull()
  532. {
  533. return d->preview.isNull();
  534. }
  535. void ImagePreviewView::resetPreview()
  536. {
  537. d->preview = DImg();
  538. d->path = TQString();
  539. d->imageInfo = 0;
  540. updateZoomAndSize(true);
  541. emit signalPreviewLoaded(false);
  542. }
  543. void ImagePreviewView::paintPreview(TQPixmap *pix, int sx, int sy, int sw, int sh)
  544. {
  545. DImg img = d->preview.smoothScaleSection(sx, sy, sw, sh, tileSize(), tileSize());
  546. TQPixmap pix2 = img.convertToPixmap();
  547. bitBlt(pix, 0, 0, &pix2, 0, 0);
  548. }
  549. } // NameSpace Digikam