diff options
Diffstat (limited to 'src/ui/imageviewer/imageviewer.cpp')
-rw-r--r-- | src/ui/imageviewer/imageviewer.cpp | 1339 |
1 files changed, 1339 insertions, 0 deletions
diff --git a/src/ui/imageviewer/imageviewer.cpp b/src/ui/imageviewer/imageviewer.cpp new file mode 100644 index 0000000..3292dd6 --- /dev/null +++ b/src/ui/imageviewer/imageviewer.cpp @@ -0,0 +1,1339 @@ +/*************************************************************************** + * fqterm, a terminal emulator for both BBS and *nix. * + * Copyright (C) 2008 fqterm development group. * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * + ***************************************************************************/ + +#include <QAction> +#include <QBuffer> +#include <QCursor> +#include <QComboBox> +#include <QDateTime> +#include <QDataStream> +#include <QDir> +#include <QFile> +#include <QFileInfo> +#include <QIcon> +#include <QKeyEvent> +#include <QLineEdit> +#include <QMessageBox> +#include <QMouseEvent> +#include <QMenu> +#include <QMenuBar> +#include <QPainter> +#include <QPixmap> +#include <QPushButton> +#include <QSizeGrip> +#include <QString> +#include <QTextCodec> +#include <QTimer> +#include <QToolTip> +#include <QTransform> +#include <QTreeView> +#include <QTextEdit> +#include <QHeaderView> +#include <QImageReader> + +#include "fqterm_canvas.h" +#include "fqterm_config.h" +#include "fqterm_exif_extractor.h" +#include "fqterm_filedialog.h" +#include "fqterm_path.h" +#include "fqterm_trace.h" +#include "imageviewer.h" + +namespace FQTerm { + + typedef enum { + V_UNKNOWN = -1, + V_ORIGINAL = 0, + V_SHADOW, + V_BESTFIT, + V_ZOOMIN, + V_ZOOMOUT, + V_ROTLEFT, + V_ROTRIGHT, + V_FULLSCREEN + } slideViewModes; + + typedef enum { + SORT_UNSORTED = -1, + SORT_LIKE, + SORT_NEW, + SORT_TRASH, + SORT_RECOVER, + SORT_TITLE + } slideSortFlags; + + + typedef enum { + STAT_UNKNOWN = -1, + STAT_LIKE, + STAT_NEW, + STAT_TRASH, + STAT_RECOVER, + STAT_TITLE, + STAT_ALL + } slideStatus; + + typedef enum { + GEN_UNKNOWN = -1, + GEN_CANCEL, + GEN_NOCHANGE, + GEN_RESHUFFLE, + GEN_NOTAG, + GEN_INTRASH, + GEN_INTRASHES, + GEN_TOSAVE, + GEN_TOSAVES, + GEN_TRASH, + GEN_TRASHES, + GEN_NOTRASH, + GEN_RECOVER, + GEN_RECOVERS, + GEN_NORECOVER, + GEN_SAVE, + GEN_SAVES, + GEN_DELETE, + GEN_DELETES, + GEN_SEARCHWRAP, + GEN_SEARCHNOMATCH + } generalStatus; + + static const char *fqloraTagStatus[] = { + "Like", + "New", + "Trash", + "Recover", + "Title" + }; + + static const char *fqloraGeneralStatus[] = { + "Canceled.", + "No changes for ", + "Reshuffled by ", + "No tags by ", + "item in the trash.", + "items in the trash.", + "item to save.", + "items to save.", + "image trashed.", + "images trashed.", + "Nothing to trash.", + "image recovered.", + "images recovered.", + "Nothing to recover.", + "image saved in ", + "images saved in ", + "image deleted.", + "images deleted.", + "Search wrapped.", + "No matches." + }; + + static const int toolBarFixedHeight = 40; + static const int statusBarFixedHeight = 18; + static const QSize sortBoxFixedSize(110, 25); + + static const QString &iconPath(const QString &fileName) { + + static QString p = ""; + + if (!fileName.isEmpty()) { + p = getPath(RESOURCE) + ICON_SOURCE + fileName; + } + + return p; + } + + const QString &isPlural(const int num, const int status) { + + static QString m = ""; + + if (num == 1) { + return (m = fqloraGeneralStatus[status]); + } else if (num > 1 && status > GEN_UNKNOWN && status < GEN_DELETES) { + return (m = fqloraGeneralStatus[status + 1]); + } + + return (m = fqloraGeneralStatus[GEN_UNKNOWN]); + } + + // get user's desktop rectangle + static const QRect &desktopSize() { + + static QRect size; + QDesktopWidget myDesktop; + return ((size = myDesktop.screenGeometry(myDesktop.primaryScreen()))); + } + + // FQTermImageFlow + FQTermImageFlow::~FQTermImageFlow() { + + delete statusBar_; + delete imageMenu_; + delete imageFlow_; + + statusBar_ = NULL; + imageMenu_ = NULL; + imageFlow_ = NULL; + } + + FQTermImageFlow::FQTermImageFlow(FQTermConfig * config, QWidget *parent, + Qt::WindowFlags wflag) : + FQTermImage(parent, wflag), + imageFlow_(new ImageFlow(this)), + imageMenu_(new ImageMenu(this)), + statusBar_(new QStatusBar(this)), + config_(config) { + + setWindowTitle(IMAGE_BROWSER_NAME); + setAutoFillBackground(true); + setBackgroundRole(QPalette::Midlight); + setFixedSize(desktopSize().width() / 1.3, desktopSize().height() / 1.9); + + FQ_VERIFY(connect(this, SIGNAL(isTrashEmpty()), this, SLOT(checkTrashState()))); + + // emblem state + FQ_VERIFY(connect(imageFlow_, SIGNAL(emblemStatus(const int)), imageMenu_, SLOT(updateEmblems(const int)))); + FQ_VERIFY(connect(imageMenu_, SIGNAL(toggleFlowStatus(const int)), imageFlow_, SLOT(toggleStatus(const int)))); + + // clear state + FQ_VERIFY(connect(imageFlow_, SIGNAL(clearStatus(const bool)), imageMenu_, SLOT(updateClear(const bool)))); + FQ_VERIFY(connect(imageMenu_, SIGNAL(clearImages()), this, SLOT(clearImages()))); + + // save state + FQ_VERIFY(connect(imageFlow_, SIGNAL(saveStatus(const bool)), imageMenu_, SLOT(updateSave(const bool)))); + FQ_VERIFY(connect(imageMenu_, SIGNAL(saveImages()), this, SLOT(saveImages()))); + + // trash state + FQ_VERIFY(connect(this, SIGNAL(trashStatus(const bool)), imageMenu_, SLOT(updateDustbin(const bool)))); + FQ_VERIFY(connect(imageMenu_, SIGNAL(recoverImages()), this, SLOT(recoverImages()))); + + // constructs a tool bar and its tool buttons + QAction *closeBrowserAct = new QAction(QIcon(iconPath("window-close.png")), tr("Close"), this); + closeBrowserAct->setShortcut(QKeySequence(Qt::Key_Q)); + FQ_VERIFY(connect(closeBrowserAct, SIGNAL(triggered()), this, SLOT(close()))); + + QAction *trashAllAct = new QAction(QIcon(iconPath("trash-empty.png")), tr("Trash All"), this); + trashAllAct->setShortcut(QKeySequence(Qt::Key_Delete)); + FQ_VERIFY(connect(trashAllAct, SIGNAL(triggered()), this, SLOT(trashAllImages()))); + + QLabel *sortLabel = new QLabel(this); + sortLabel->setMargin(5); + sortLabel->setToolTip("Reshuffle images by tags"); + sortLabel->setPixmap(QPixmap(iconPath("edit-shuffle.png"))); + + QComboBox *sortBox = new QComboBox(this); + sortBox->setFocusPolicy(Qt::ClickFocus); + sortBox->setFont(QFont("Serif", 12)); + sortBox->setFrame(false); + sortBox->setFixedSize(sortBoxFixedSize); + sortBox->insertItem(0, QIcon(iconPath("emblem-like-16x16.png")), tr("Like ")); + sortBox->insertItem(1, QIcon(iconPath("emblem-new-16x16.png")), tr("New ")); + sortBox->insertItem(2, QIcon(iconPath("emblem-trash-16x16.png")), tr("Trash ")); + sortBox->insertItem(3, QIcon(iconPath("emblem-recover-16x16.png")), tr("Recover ")); + sortBox->insertItem(4, QIcon(iconPath("emblem-title-16x16.png")), tr("Title ")); + FQ_VERIFY(connect(sortBox, SIGNAL(currentIndexChanged(int)), this, SLOT(reshuffleImages(int)))); + + QToolBar *toolBar = new QToolBar(this); + toolBar->setFixedHeight(toolBarFixedHeight); + toolBar->setIconSize(QSize(32, 32)); + toolBar->addAction(closeBrowserAct); + toolBar->addSeparator(); + toolBar->addWidget(sortLabel); + toolBar->addWidget(sortBox); + toolBar->addSeparator(); + toolBar->addAction(trashAllAct); + + // constructs a status bar to show instant messages + statusBar_->setSizeGripEnabled(false); + statusBar_->setFixedHeight(statusBarFixedHeight); + statusBar_->setFont(QFont("Serif", 10, QFont::Bold)); + FQ_VERIFY(connect(imageFlow_, SIGNAL(statusMessage(const QString &)), this, SLOT(showStatusMessage(const QString &)))); + FQ_VERIFY(connect(this, SIGNAL(statusMessage(const QString &)), this, SLOT(showStatusMessage(const QString &)))); + // constructs a horizontal layout + QVBoxLayout *vBox = new QVBoxLayout(this); + vBox->setMargin(0); + vBox->setSpacing(0); + vBox->addWidget(toolBar); + vBox->addWidget(imageFlow_); + vBox->addWidget(imageMenu_); + vBox->addWidget(statusBar_); + vBox->setEnabled(true); + setLayout(vBox); + } + + const QString &FQTermImageFlow::poolSource(void) const { + + static QString p = ""; + + p = config_->getItemValue("preference", "pool"); + + if (p.isEmpty()) { + p = getPath(USER_CONFIG) + POOL_SOURCE; + } + + return p; + } + + const QString &FQTermImageFlow::trashSource(void) const { + + static QString p = ""; + + p = poolSource() + TRASH_SOURCE; + return p; + } + + // these three functions below are kept for + // API compatibility. + void FQTermImageFlow::adjustItemSize() { + + } + + void FQTermImageFlow::scrollTo(const QString &filePath) { + + } + + void FQTermImageFlow::updateImage(const QString &filePath) { + + } + + // slots and functions + void FQTermImageFlow::loadImages(const int status) { + + int i; + int itemStatus = (status == STAT_RECOVER) ? status : STAT_UNKNOWN; + QString comment; + QFileInfoList sourceList = sortedList(poolSource()); + QFileInfoList targetList = imageFlow_->digest(STAT_ALL); + + for (i = 0; i < sourceList.count(); i++) { + + if (!targetList.contains(sourceList[i])) { + + QImage image; + + if (!image.load(sourceList[i].absoluteFilePath())) { + QFileIconProvider iconProvider; + image = iconProvider.icon(sourceList[i]).pixmap(desktopSize().height() / 6.0).toImage(); + } else { + image = image.scaled(desktopSize().width() / 6.0, desktopSize().height() / 6.0, + Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + } + + /* TODO */ + /* there should be a more flexible */ + /* method to deal with items' status */ + if (status == STAT_ALL) { + itemStatus = STAT_NEW; + } + + ImageFlowItem newItem(image, sourceList[i], comment,itemStatus); + imageFlow_->add(newItem); + } + } + } + + void FQTermImageFlow::reshuffleImages(const int status) { + + // there are images in the pool directory + QList<qint64> itemKeys = imageFlow_->sort(status); + + if (imageFlow_->reorder(itemKeys)) { + imageFlow_->setCurrentSlide(0); + emit statusMessage(tr(fqloraGeneralStatus[GEN_RESHUFFLE]) + tr(fqloraTagStatus[status])); + } else { + emit statusMessage(tr(fqloraGeneralStatus[GEN_NOCHANGE]) + tr(fqloraTagStatus[status])); + } + } + + void FQTermImageFlow::saveImages() { + + QFileInfoList toSave = imageFlow_->digest(STAT_LIKE); + + if (!toSave.isEmpty()) { + + QMessageBox box; + box.setWindowModality(Qt::NonModal); + box.setWindowTitle("Information"); + box.setIconPixmap(QPixmap(iconPath("emblem-info.png"))); + box.setTextFormat(Qt::RichText); + box.setText(QString("<b>%1</b> ").arg(toSave.count()) + isPlural(toSave.count(), GEN_TOSAVE)); + QPushButton *cancelButton = box.addButton(QMessageBox::Cancel); + QPushButton *saveAllButton = box.addButton(QMessageBox::SaveAll); + box.setDefaultButton(cancelButton); + box.exec(); + + if (box.clickedButton() == saveAllButton) { + + FQTermFileDialog fileDialog(config_); + QString savePath = + fileDialog.getExistingDirectory(tr("Save your likes under"), "*"); + + if (savePath.isEmpty()) { + emit statusMessage(tr(fqloraGeneralStatus[GEN_CANCEL])); + return; + } + + int i; + for (i = 0; i < toSave.count(); i++) { + QFile::rename(toSave[i].absoluteFilePath(), savePath + toSave[i].fileName()); + } + + imageFlow_->strip(STAT_LIKE); + emit statusMessage(QString("%1 ").arg(i) + isPlural(i, GEN_SAVE) + savePath); + } else { + emit statusMessage(tr(fqloraGeneralStatus[GEN_CANCEL])); + } + } else { + emit statusMessage(tr(fqloraGeneralStatus[GEN_NOTAG]) + tr(fqloraTagStatus[STAT_LIKE])); + } + } + + void FQTermImageFlow::checkTrashState() { + + QDir trashPath(trashSource()); + + if (trashPath.count() <= 2) { + emit trashStatus(false); + } else { + emit trashStatus(true); + } + } + + void FQTermImageFlow::clearImages() { + + int i; + QString trashPath = trashSource(); + QDir trashDir(trashPath); + QFileInfoList toRemove = imageFlow_->digest(STAT_TRASH); + + if (!trashDir.exists()) { + trashDir.mkdir(trashPath); + } + + if (!toRemove.isEmpty()) { + + for (i = 0; i < toRemove.count(); i++) { + if (QFile::exists(toRemove[i].absoluteFilePath())) { + QFile::rename(toRemove[i].absoluteFilePath(), trashPath + toRemove[i].fileName()); + } + } + + imageFlow_->strip(STAT_TRASH); + emit statusMessage(QString("%1 ").arg(i) + isPlural(i, GEN_TRASH)); + emit isTrashEmpty(); + } else { + emit statusMessage(tr(fqloraGeneralStatus[GEN_NOTAG]) + tr(fqloraTagStatus[STAT_TRASH])); + } + } + + void FQTermImageFlow::trashAllImages() { + + int i; + QString trashPath = trashSource(); + QDir trashDir(trashPath); + QFileInfoList toRemove = imageFlow_->digest(STAT_ALL); + + if (!trashDir.exists()) { + trashDir.mkdir(trashPath); + } + + if (imageFlow_->count() > 0) { + for (i = 0; i < imageFlow_->count(); i++) { + if (QFile::exists(toRemove[i].absoluteFilePath())) { + QFile::rename(toRemove[i].absoluteFilePath(), trashPath + toRemove[i].fileName()); + } + } + + imageFlow_->clear(); + emit statusMessage(QString("%1 ").arg(i) + isPlural(i, GEN_TRASH)); + emit isTrashEmpty(); + } else { + emit statusMessage(tr(fqloraGeneralStatus[GEN_NOTAG]) + tr(fqloraTagStatus[STAT_TRASH])); + } + } + + void FQTermImageFlow::recoverImages() { + + QString trashPath = trashSource(); + QDir trashDir(trashPath); + + if (!trashDir.exists()) { + trashDir.mkdir(trashPath); + } else if (trashDir.count() > 2) { + + int action = -1;; + QFileInfoList toRecover = sortedList(trashPath); + + if (!toRecover.isEmpty()) { + + QMessageBox box; + box.setWindowModality(Qt::NonModal); + box.setWindowTitle("Take action"); + box.setIconPixmap(QPixmap(iconPath("emblem-info.png"))); + box.setTextFormat(Qt::RichText); + box.setText(QString("<b>%1</b> ").arg(toRecover.count()) + isPlural(toRecover.count(), GEN_INTRASH)); + QPushButton *cancelButton = box.addButton(QMessageBox::Cancel); + QPushButton *recoverButton = box.addButton(tr("Recover"), QMessageBox::ApplyRole); + recoverButton->setIcon(QIcon(iconPath("button-recover.png"))); + QPushButton *deleteButton = box.addButton(tr("Delete"), QMessageBox::ApplyRole); + deleteButton->setIcon(QIcon(iconPath("button-delete.png"))); + box.setDefaultButton(cancelButton); + box.exec(); + + if (box.clickedButton() == cancelButton) { + emit statusMessage(tr(fqloraGeneralStatus[GEN_CANCEL])); + return; + } else if (box.clickedButton() == recoverButton) { + action = 1; + } else if (box.clickedButton() == deleteButton) { + action = 0; + } + + int i; + QFile imageFile; + QString poolPath = poolSource(); + + for (i = 0; i < toRecover.count(); i++) { + if (action == 1) { + QFile::rename(toRecover[i].absoluteFilePath(), poolPath + toRecover[i].fileName()); + } else if (action == 0) { + imageFile.setFileName(toRecover[i].absoluteFilePath()); + imageFile.remove(); + } + } + + if (action == 1) { + loadImages(STAT_RECOVER); + emit statusMessage(QString("%1 ").arg(i) + isPlural(i, GEN_RECOVER)); + } else if (action == 0) { + emit statusMessage(QString("%1 ").arg(i) + isPlural(i, GEN_DELETE)); + } + } + } else { + + emit statusMessage(tr(fqloraGeneralStatus[GEN_NORECOVER])); + } + + emit isTrashEmpty(); + } + + + void FQTermImageFlow::showStatusMessage(const QString &message) { + + if (statusBar_) { + statusBar_->showMessage(message, 3000); + } + } + + QFileInfoList FQTermImageFlow::sortedList(const QString &path) { + + QDir sortPath(path); + + if (!sortPath.exists()) { + sortPath.mkdir(path); + } + + QStringList filters; + QList<QByteArray> formats = QImageReader::supportedImageFormats(); + for (QList<QByteArray>::iterator it = formats.begin(); + it != formats.end(); + ++it) { + QString filter("*."); + filter.append(it->data()); + filters << filter; + } + sortPath.setNameFilters(filters); + + return (sortPath.entryInfoList(QDir::Files | QDir::Readable | QDir::NoSymLinks, QDir::Time)); + } + + // FQTermImageFlow events. + void FQTermImageFlow::showEvent(QShowEvent *event) { + + emit isTrashEmpty(); + + if (imageFlow_->count() <= 0) { + loadImages(STAT_UNKNOWN); + move((desktopSize().width() - width()) / 2.0, (desktopSize().height() - height()) / 2.0); + } else { + // TODO: insert new images + loadImages(STAT_ALL); + } + } + + void FQTermImageFlow::closeEvent(QCloseEvent *event) { + + hide(); + } + + class ImageMenu::Private { + public: + int emblemStatus; + bool dustbinStatus; + bool saveStatus; + bool clearStatus; + + QString tipMessage; + + QRect rectLike; + QRect rectTrash; + QRect rectNew; + QRect rectRecover; + QRect rectEmblems; + + QRect rectClear; + QRect rectSave; + QRect rectDustbin; + + QPixmap pixmapLike, pixmapLikeGray; + QPixmap pixmapTrash, pixmapTrashGray; + QPixmap pixmapNew, pixmapNewGray; + QPixmap pixmapRecover, pixmapRecoverGray; + + QPixmap pixmapClear, pixmapClearGray; + QPixmap pixmapSave, pixmapSaveGray; + QPixmap pixmapDustbin, pixmapDustbinGray; + }; + + //ImageMenu + ImageMenu::ImageMenu(QWidget *parent) + : fh(new ImageMenu::Private) { + + setAutoFillBackground(true); + setBackgroundRole(QPalette::Shadow); + setFocusPolicy(Qt::NoFocus); + setMouseTracking(true); + setFixedHeight(60); + + fh->emblemStatus = -2; + fh->dustbinStatus = false; + fh->saveStatus = false; + fh->clearStatus = false; + + fh->tipMessage = ""; + + fh->rectLike = QRect(50, 14, 32, 32); + fh->rectTrash = QRect(100, 14, 32, 32); + fh->rectNew = QRect(150, 14, 32, 32); + fh->rectRecover = QRect(200, 14, 32, 32); + fh->rectEmblems = QRect(50, 14, 232, 32); + + fh->rectClear = QRect(300, 14, 32, 32); + fh->rectSave = QRect(350, 14, 32, 32); + fh->rectDustbin = QRect(400, 14, 32, 32); + + fh->pixmapLike = QPixmap(iconPath("emblem-like.png")); + fh->pixmapLikeGray = QPixmap(iconPath("emblem-like-gray.png")); + + fh->pixmapTrash = QPixmap(iconPath("emblem-trash.png")); + fh->pixmapTrashGray = QPixmap(iconPath("emblem-trash-gray.png")); + + fh->pixmapNew = QPixmap(iconPath("emblem-new.png")); + fh->pixmapNewGray = QPixmap(iconPath("emblem-new-gray.png")); + + fh->pixmapRecover = QPixmap(iconPath("emblem-recover.png")); + fh->pixmapRecoverGray = QPixmap(iconPath("emblem-recover-gray.png")); + + fh->pixmapClear = QPixmap(iconPath("clear-state.png")); + fh->pixmapClearGray = QPixmap(iconPath("clear-state-gray.png")); + + fh->pixmapSave = QPixmap(iconPath("save-state.png")); + fh->pixmapSaveGray = QPixmap(iconPath("save-state-gray.png")); + + fh->pixmapDustbin = QPixmap(iconPath("trash-state.png")); + fh->pixmapDustbinGray = QPixmap(iconPath("trash-state-gray.png")); + } + + ImageMenu::~ImageMenu() { + + delete fh; + fh = NULL; + } + + void ImageMenu::updateEmblems(const int status) { + + if (fh->emblemStatus != status) { + update(fh->rectEmblems); + } + + fh->emblemStatus = status; + } + + void ImageMenu::updateClear(const bool hasOrNot) { + + if (fh->clearStatus != hasOrNot) { + update(fh->rectClear); + } + + fh->clearStatus = hasOrNot; + } + + void ImageMenu::updateSave(const bool hasOrNot) { + + if (fh->saveStatus != hasOrNot) { + update(fh->rectSave); + } + + fh->saveStatus = hasOrNot; + } + + void ImageMenu::updateDustbin(const bool fullOrNot) { + + if (fh->dustbinStatus != fullOrNot) { + update(fh->rectDustbin); + } + + fh->dustbinStatus = fullOrNot; + } + + void ImageMenu::paintEvent(QPaintEvent *event) { + + QPainter p(this); + + p.setRenderHints(QPainter::SmoothPixmapTransform | QPainter::Antialiasing); + + if (fh->emblemStatus == STAT_LIKE) { + p.drawPixmap(fh->rectLike, fh->pixmapLike); + } else { + p.drawPixmap(fh->rectLike, fh->pixmapLikeGray); + } + + if (fh->emblemStatus == STAT_TRASH) { + p.drawPixmap(fh->rectTrash, fh->pixmapTrash); + } else { + p.drawPixmap(fh->rectTrash, fh->pixmapTrashGray); + } + + if (fh->emblemStatus == STAT_NEW) { + p.drawPixmap(fh->rectNew, fh->pixmapNew); + } else { + p.drawPixmap(fh->rectNew, fh->pixmapNewGray); + } + + if (fh->emblemStatus == STAT_RECOVER) { + p.drawPixmap(fh->rectRecover, fh->pixmapRecover); + } else { + p.drawPixmap(fh->rectRecover, fh->pixmapRecoverGray); + } + + if (fh->clearStatus == true) { + p.drawPixmap(fh->rectClear, fh->pixmapClear); + } else { + p.drawPixmap(fh->rectClear, fh->pixmapClearGray); + } + + if (fh->saveStatus == true) { + p.drawPixmap(fh->rectSave, fh->pixmapSave); + } else { + p.drawPixmap(fh->rectSave, fh->pixmapSaveGray); + } + + if (fh->dustbinStatus == true) { + p.drawPixmap(fh->rectDustbin, fh->pixmapDustbin); + } else { + p.drawPixmap(fh->rectDustbin, fh->pixmapDustbinGray); + } + + p.end(); + } + + void ImageMenu::mouseReleaseEvent(QMouseEvent *event) { + + if (event->button() == Qt::LeftButton + || event->button() == Qt::RightButton) { + + if (fh->rectLike.contains(event->pos())) { + emit toggleFlowStatus(STAT_LIKE); + } + + if (fh->rectTrash.contains(event->pos())) { + emit toggleFlowStatus(STAT_TRASH); + } + + if (fh->rectClear.contains(event->pos())) { + emit clearImages(); + } + + if (fh->rectSave.contains(event->pos())) { + emit saveImages(); + } + + if (fh->rectDustbin.contains(event->pos())) { + emit recoverImages(); + } + } + } + + // ImageFlow: this subclasses PictureFlow + class ImageFlow::Private { + public: + int emblemStatus; + int likeCount; + int clearCount; + + QList<QByteArray> itemsTitles; + QHash<QByteArray, qint64> itemsTitleKeyPairs; + QList<ImageFlowItem> items; + }; + + ImageFlow::~ImageFlow() { + + delete m; + m = NULL; + } + + // construct an emptry pictureflow. + ImageFlow::ImageFlow(QWidget *parent) + : PictureFlow(parent), m(new ImageFlow::Private) { + + // initial setup + setFocusPolicy(Qt::StrongFocus); + setZoomFactor(120); + setSlideCount(0); + m->emblemStatus = -1; + m->likeCount = 0; + m->clearCount = 0; + } + + const ImageFlowItem& ImageFlow::operator[](int index) const { + + return (m->items[index]); + } + + ImageFlowItem& ImageFlow::operator[](int index) { + + return (m->items[index]); + } + + const ImageFlowItem& ImageFlow::at(int index) const { + + return (m->items[index]); + } + + void ImageFlow::add(const ImageFlowItem &item) { + + QByteArray title = item.info().fileName().toLocal8Bit(); + + m->items.append(item); + m->itemsTitles.append(title); + m->itemsTitleKeyPairs.insert(title, item.key()); + + // set the status pixmap + int index = m->items.indexOf(item); + m->items[index].setStatus(item.status()); + + int count = m->items.count(); + setSlideCount(count); + setSlide(count - 1, item.image()); + } + + QList<QByteArray>& ImageFlow::itemsTitles() const { + + return (m->itemsTitles); + } + + void ImageFlow::clear() { + + m->items.clear(); + setSlideCount(0); + setCurrentSlide(0); + PictureFlow::clear(); + } + + int ImageFlow::count() const { + + return (m->items.count()); + } + + int ImageFlow::size() const { + + return (m->items.size()); + } + + QFileInfoList& ImageFlow::digest(const int status) { + + int i; + int count = m->items.size(); + static QFileInfoList result; + + result.clear(); + + for (i = 0; i < count; i++) { + switch (status) { + case STAT_ALL: + result.append(m->items[i].info()); + break; + default: + if (m->items[i].status() == status) { + result.append(m->items[i].info()); + } + break; + } + } + + return result; + } + + void ImageFlow::strip(const int status) { + + int i; + int count = m->items.count(); + QList<int> dirtyIndexes; + + // We cannot delete items in a loop because + // items count will change while deleting. + // A better method is to use another list + // to record 'dirty' items, and by looping this + // dirty list, we can correctly delete those + // dirty items successfully. + for (i = 0; i < count; i++) { + if (m->items[i].status() == status) { + dirtyIndexes.append(i); + } + } + + if (!dirtyIndexes.isEmpty()) { + + for (i = 0; i < dirtyIndexes.count(); i++) { + // Note: dirtyIndexes[i] - i: the actual + // position of the dirty item. + m->items.removeAt(dirtyIndexes[i] - i); + } + + dirtyIndexes.clear(); + reorder(sort(STAT_ALL)); + + for (i = 0; i < m->items.count(); i++) { + setSlide(i, m->items[i].image()); + } + + setSlideCount(i); + setCurrentSlide(0); + + switch(status) { + case STAT_LIKE: + m->likeCount = 0; + emit saveStatus(false); + emit emblemStatus(STAT_UNKNOWN); + break; + case STAT_TRASH: + m->clearCount = 0; + emit clearStatus(false); + emit emblemStatus(STAT_UNKNOWN); + break; + default: + break; + } + } + } + + QList<qint64> ImageFlow::sort(const int status) { + + int i; + int count = m->items.count(); + QList<qint64> itemKeys; + + switch (status) { + case STAT_TITLE: + qStableSort(m->itemsTitles.begin(), m->itemsTitles.end(), qLess<QByteArray>()); + for (i = 0; i < m->itemsTitles.count(); i++) { + itemKeys.append(m->itemsTitleKeyPairs.value(m->itemsTitles[i])); + } + break; + default: + for (i = 0; i < count; i++) { + if (m->items[i].status() == status) { + itemKeys.prepend(m->items[i].key()); + } else { + itemKeys.append(m->items[i].key()); + } + } + break; + } + + // this might be unnecessary here. + return itemKeys; + } + + bool ImageFlow::reorder(const QList<qint64>& itemsKey) { + + int items_count = m->items.size(); + + if (itemsKey.size() != items_count) { + + return (false); + } + + // Collect Items Key + QList<qint64> currentItemsKey; + for (int i = 0; i < items_count; i++) { + + currentItemsKey.append(m->items.at(i).key()); + } + + if (currentItemsKey == itemsKey) { + // if identical, we don't sort. + return (false); + } + + // Swap Items + for (int i=0; i < items_count; i++) { + + int index = currentItemsKey.indexOf(itemsKey.at(i)); + if (i == index) { + continue; + } + + QImage imgA = m->items[i].image(); + QImage imgB = m->items[index].image(); + + setSlide(index, imgA); + setSlide(i, imgB); + + m->items.swap(i, index); + currentItemsKey.swap(i, index); + } + + update(); + rehash(); + return(true); + } + + void ImageFlow::rehash(void) { + + int count = m->items.count(); + + if (count <= 0) { + return; + } + + int i; + + m->itemsTitles.clear(); + m->itemsTitleKeyPairs.clear(); + + qint64 k; + QByteArray b; + + for (i = 0; i < count; i++) { + k = m->items[i].key(); + b = m->items[i].info().fileName().toLocal8Bit(); + m->itemsTitles.append(b); + m->itemsTitleKeyPairs.insert(b, k); + } + } + + void ImageFlow::toggleStatus(const int status) { + + int index = currentSlide(); + int count = m->items.count(); + + if (index >= 0 && index < count) { + + setUpdatesEnabled(false); + if (m->items[index].status() != status) { + + switch (status) { + case STAT_LIKE: + m->likeCount++; + if (m->items[index].status() == STAT_TRASH) { + m->clearCount--; + } + emit saveStatus(true); + break; + case STAT_TRASH: + m->clearCount++; + if (m->items[index].status() == STAT_LIKE) { + m->likeCount--; + } + emit clearStatus(true); + break; + default: + break; + } + + m->items[index].setStatus(status); + emit statusMessage(tr("Tagged as ") + tr(fqloraTagStatus[status])); + + } else { + + switch (status) { + case STAT_LIKE: + m->likeCount--; + break; + case STAT_TRASH: + m->clearCount--; + break; + default: + break; + } + + m->items[index].setStatus(STAT_UNKNOWN); + emit statusMessage("Tag cleared"); + } + + if (m->likeCount <= 0) { + emit saveStatus(false); + } else { + emit saveStatus(true); + } + + if (m->clearCount <= 0) { + emit clearStatus(false); + } else { + emit clearStatus(true); + } + + setUpdatesEnabled(true); + } + } + + void ImageFlow::setCurrentImage(const int index) { + + int count = size(); + + if (count > 0 && index > 0 && index < count) { + + setCurrentSlide(index); + } + } + +// events + void ImageFlow::paintEvent(QPaintEvent *event) { + + PictureFlow::paintEvent(event); + + if (slideCount() < 1) { + return; + } + + QPainter p(this); + + // White Pen for File Info + p.setPen(Qt::gray); + + int cw = width() / 2; + int wh = height(); + + ImageFlowItem& item = m->items[currentSlide()]; + + // Draw File Name if it's not empty + QString title = item.info().fileName(); + if (!title.isEmpty()) { + + p.setFont(QFont(p.font().family(), p.font().pointSize() + 1, QFont::Bold)); + p.drawText(cw - (QFontMetrics(p.font()).width(title) / 2), wh - 20, title); + } + + p.end(); + + if (m->emblemStatus != item.status()) { + emit emblemStatus(item.status()); + } + + m->emblemStatus = item.status(); + } + + // ImageFlowItem + class ImageFlowItem::Private { + public: + QString filePath; + QFileInfo info; + QString comment; + QImage image; + int status; + qint64 key; + + }; + + ImageFlowItem::~ImageFlowItem() { + + delete m; + m = NULL; + } + + // constructs an empty item + ImageFlowItem::ImageFlowItem(QObject *parent) + : QObject(parent), m(new ImageFlowItem::Private) { + + m->key = qHash(QString::number(m->image.cacheKey()) + m->info.fileName() + m->comment); + } + + ImageFlowItem::ImageFlowItem(const ImageFlowItem &item) + : QObject(item.parent()), m(new ImageFlowItem::Private) { + + operator=(item); + } + + // constructs an item with a given image + ImageFlowItem::ImageFlowItem(const QImage &image, QObject *parent) + : QObject(parent), m(new ImageFlowItem::Private) { + + m->image = image; + m->key = qHash(QString::number(m->image.cacheKey()) + m->info.fileName() + m->comment); + } + + // constructs an image with given image and title + ImageFlowItem::ImageFlowItem(const QImage &image, const QFileInfo &info, QObject *parent) + : QObject(parent), m(new ImageFlowItem::Private) { + + m->image = image; + m->info = info; + m->key = qHash(QString::number(m->image.cacheKey()) + m->info.fileName() + m->comment); + } + + // constructs an image with given image, title and comment + ImageFlowItem::ImageFlowItem(const QImage &image, const QFileInfo &info, const QString &comment, QObject *parent) + : QObject(parent), m(new ImageFlowItem::Private) { + + m->image = image; + m->info = info; + m->comment = comment; + m->key = qHash(QString::number(m->image.cacheKey()) + m->info.fileName() + m->comment); + } + + // constructs an image with given image, title, type, size, time and state + ImageFlowItem::ImageFlowItem(const QImage &image, const QFileInfo &info, const QString& comment, const int status, QObject *parent) + : QObject(parent), m(new ImageFlowItem::Private) { + + m->image = image; + m->info = info; + m->comment = comment; + m->status = status; + + m->key = qHash(QString::number(m->image.cacheKey()) + m->info.fileName() + m->comment); + } + + // funcs and slots + ImageFlowItem& ImageFlowItem::operator=(const ImageFlowItem &item) { + + m->info = item.m->info; + m->comment = item.m->comment; + m->key = item.m->key; + + if (item.m->filePath.isEmpty()) { + m->image = item.m->image; + } else { + setImage(item.m->filePath, m->key); + } + + return (*this); + } + + bool ImageFlowItem::operator==(const ImageFlowItem& item) const { + + return (item.m->key == m->key); + } + + bool ImageFlowItem::operator!=(const ImageFlowItem& item) const { + + return (item.m->key != m->key); + } + + const QString& ImageFlowItem::comment() const { + + return (m->comment); + } + + const QImage& ImageFlowItem::image() const { + + return (m->image); + } + + int ImageFlowItem::status(void) const { + + return (m->status); + } + + const QFileInfo& ImageFlowItem::info(void) const { + + return (m->info); + } + + qint64 ImageFlowItem::key() const { + + return (m->key); + } + + void ImageFlowItem::setInfo(const QFileInfo& info) { + + m->info = info; + m->key = qHash(QString::number(m->image.cacheKey()) + m->info.fileName() + m->comment); + } + + void ImageFlowItem::setComment(const QString &comment) { + + m->comment = comment; + m->key = qHash(QString::number(m->image.cacheKey()) + m->info.fileName() + m->comment); + } + + void ImageFlowItem::setImage(const QImage &image) { + + m->image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); + m->key = qHash(QString::number(m->image.cacheKey()) + m->info.fileName() + m->comment); + } + + void ImageFlowItem::setImage(const QString &filePath, int size) { + + m->filePath = filePath; + m->key = size; + + QTimer::singleShot(1000, this, SLOT(loadImage())); + } + + void ImageFlowItem::setStatus(const int status) { + + m->status = status; + } + + void ImageFlowItem::loadImage() { + + int imageHeight = m->key; + + if (!m->image.load(m->filePath)) { + QFileIconProvider iconProvider; + m->image = iconProvider.icon(QFileInfo(m->filePath)).pixmap(imageHeight).toImage(); + } else { + m->image = resizeImage(m->image, imageHeight); + } + + m->filePath.clear(); + m->key = qHash(QString::number(m->image.cacheKey()) + m->info.fileName() + m->comment); + } + + QImage ImageFlowItem::resizeImage(const QImage &image, int size) { + + if (size == image.width() && size == image.height()) { + return image; + } + + double scaleWidth = size / (double)image.width(); + double scaleHeight = size / (double)image.height(); + double smaller = qMin(scaleWidth, scaleHeight); + + int w = (int) qRound(smaller * image.width()); + int h = (int) qRound(smaller * image.height()); + + return (image.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + } + +} // namespace FQTerm + +#include "imageviewer.moc" |