summaryrefslogtreecommitdiff
path: root/src/ui/imageviewer
diff options
context:
space:
mode:
authorIru Cai <mytbk920423@gmail.com>2016-04-03 00:29:10 +0800
committerIru Cai <mytbk920423@gmail.com>2016-04-03 10:16:53 +0800
commit3a2cb75c85b8f98ebb0262ec3d5b590dcb9d79a1 (patch)
tree4d9ffee201e903f9ea2137327565d38395e4fd70 /src/ui/imageviewer
parentb91ee982b8ce1e08aad128a65312501ef5dd8ecd (diff)
downloadfqterm-3a2cb75c85b8f98ebb0262ec3d5b590dcb9d79a1.tar.xz
move imageviewer sources to ui/imageviewer
Diffstat (limited to 'src/ui/imageviewer')
-rw-r--r--src/ui/imageviewer/CMakeLists.txt32
-rw-r--r--src/ui/imageviewer/fqterm_canvas.cpp537
-rw-r--r--src/ui/imageviewer/fqterm_canvas.h105
-rw-r--r--src/ui/imageviewer/fqtermimage.cpp9
-rw-r--r--src/ui/imageviewer/fqtermimage.h22
-rw-r--r--src/ui/imageviewer/imageviewer.cpp1339
-rw-r--r--src/ui/imageviewer/imageviewer.h246
-rw-r--r--src/ui/imageviewer/imageviewer_origin.cpp454
-rw-r--r--src/ui/imageviewer/imageviewer_origin.h108
-rw-r--r--src/ui/imageviewer/pictureflow.cpp1201
-rw-r--r--src/ui/imageviewer/pictureflow.h198
11 files changed, 4251 insertions, 0 deletions
diff --git a/src/ui/imageviewer/CMakeLists.txt b/src/ui/imageviewer/CMakeLists.txt
new file mode 100644
index 0000000..594493c
--- /dev/null
+++ b/src/ui/imageviewer/CMakeLists.txt
@@ -0,0 +1,32 @@
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+if(IMAGE_USE_PICFLOW)
+ set(IMGVIEW_SRCS
+ imageviewer.h
+ imageviewer.cpp)
+else(IMAGE_USE_PICFLOW)
+ set(IMGVIEW_SRCS
+ imageviewer_origin.cpp
+ imageviewer_origin.h)
+endif(IMAGE_USE_PICFLOW)
+
+set(fqterm_imageviewer_SRCS
+ fqtermimage.cpp
+ fqtermimage.h
+ fqterm_canvas.cpp
+ fqterm_canvas.h
+ pictureflow.cpp
+ pictureflow.h
+ ${IMGVIEW_SRCS})
+
+include_directories(
+ ${QT_INCLUDE_DIR}
+ ${QT_QTCORE_INCLUDE_DIR}
+ ${QT_QTGUI_INCLUDE_DIR}
+ ${CMAKE_CURRENT_SOURCE_DIR}/../../common
+ ${CMAKE_CURRENT_BINARY_DIR}
+)
+
+add_library(fqterm_imageviewer
+ ${fqterm_imageviewer_SRCS})
diff --git a/src/ui/imageviewer/fqterm_canvas.cpp b/src/ui/imageviewer/fqterm_canvas.cpp
new file mode 100644
index 0000000..82fc971
--- /dev/null
+++ b/src/ui/imageviewer/fqterm_canvas.cpp
@@ -0,0 +1,537 @@
+/***************************************************************************
+ * 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 <QCloseEvent>
+#include <QKeyEvent>
+#include <QResizeEvent>
+#include <QMouseEvent>
+#include <QMenu>
+#include <QMenuBar>
+#include <QFileDialog>
+#include <QMessageBox>
+#include <QLabel>
+#include <QMatrix>
+#include <QToolBar>
+#include <QActionGroup>
+#include <QToolButton>
+#include <QDesktopServices>
+#include <QDir>
+#include <QUrl>
+#include <QImageReader>
+#include <QMovie>
+
+#include "fqterm_canvas.h"
+#include "fqterm.h"
+#include "fqterm_config.h"
+#include "fqterm_path.h"
+#include "fqterm_filedialog.h"
+
+namespace FQTerm {
+
+FQTermCanvas::FQTermCanvas(FQTermConfig * config, QWidget *parent, Qt::WindowFlags f)
+ : QScrollArea(parent),
+ adjustMode_(Fit),
+ aspectRatioMode_(Qt::KeepAspectRatio){
+ // TODO: dirty trick
+
+ if (f == 0) {
+ isEmbedded = true;
+ } else {
+ isEmbedded = false;
+ }
+
+ config_ = config;
+
+ menu_ = new QMenu(parent);
+ menu_->addAction(tr("zoom 1:1"), this, SLOT(oriSize()), tr("Ctrl+Z"));
+ menu_->addAction(tr("adjust size"), this, SLOT(autoAdjust()), tr("Ctrl+X"));
+ menu_->addSeparator();
+ QAction* zoomInAction = menu_->addAction(tr("zoom in"), this,
+ SLOT(zoomIn()), tr("Ctrl+="));
+ QAction* zoomOutAction = menu_->addAction(tr("zoom out"), this,
+ SLOT(zoomOut()), tr("Ctrl+-"));
+ if (!isEmbedded) {
+ menu_->addAction(tr("fullscreen"), this, SLOT(fullScreen()), tr("Ctrl+F"));
+ }
+ menu_->addSeparator();
+ menu_->addAction(tr("rotate CW 90"), this, SLOT(cwRotate()),
+ tr("Ctrl+]"));
+ menu_->addAction(tr("rotate CCW 90"), this, SLOT(ccwRotate()),
+ tr("Ctrl+["));
+ menu_->addSeparator();
+ gifPlayAction_ = menu_->addAction(tr("play gif"), this,
+ SLOT(playGIF()), tr("Ctrl+/"));
+ menu_->addSeparator();
+ menu_->addAction(tr("save as"), this, SLOT(saveImage()), tr("Ctrl+S"));
+ menu_->addAction(tr("copy to"), this, SLOT(copyImage()), tr("Ctrl+C"));
+ menu_->addAction(tr("silent copy"), this, SLOT(silentCopy()),
+ tr("Shift+S"));
+
+ if (!isEmbedded) {
+ menu_->addAction(tr("delete"), this, SLOT(deleteImage()), tr("Ctrl+D"));
+
+ menu_->addSeparator();
+ menu_->addAction(tr("exit"), this, SLOT(close()), tr("Ctrl+Q"));
+ }
+
+ toolBar_ = new QToolBar(this);
+
+ toolBar_->addAction(QIcon(getPath(RESOURCE) + "pic/ViewerButtons/open.png"),
+ tr("Open Dir"), this, SLOT(openDir()));
+
+ zoomInAction->setIcon(
+ QIcon(getPath(RESOURCE) + "pic/ViewerButtons/zoomin.png"));
+ zoomOutAction->setIcon(
+ QIcon(getPath(RESOURCE) + "pic/ViewerButtons/zoomout.png"));
+
+ toolBar_->addAction(zoomInAction);
+ toolBar_->addAction(zoomOutAction);
+
+ QActionGroup* gifGroup = new QActionGroup(this);
+ gifPlayAction_->setIcon(
+ QIcon(getPath(RESOURCE) + "pic/ViewerButtons/play_gif.png"));
+ toolBar_->addAction(gifPlayAction_);
+
+ QActionGroup* showSettingGroup = new QActionGroup(this);
+ QMenu* showSettingMenu = new QMenu(this);
+
+ QAction* showSettingFitAction =
+ showSettingMenu->addAction(tr("show Fit"), this, SLOT(SetAdjustMode()));
+ showSettingFitAction->setCheckable(true);
+ showSettingFitAction->setData(Fit);
+ showSettingGroup->addAction(showSettingFitAction);
+
+ QAction* showSettingMaxFitAction =
+ showSettingMenu->addAction(tr("show MaxFit"),
+ this, SLOT(SetAdjustMode()));
+ showSettingMaxFitAction->setCheckable(true);
+ showSettingMaxFitAction->setData(MaxFit);
+ showSettingGroup->addAction(showSettingMaxFitAction);
+
+ QAction* showSettingOriginAction =
+ showSettingMenu->addAction(tr("show Origin"),
+ this, SLOT(SetAdjustMode()));
+ showSettingOriginAction->setCheckable(true);
+ showSettingOriginAction->setData(Origin);
+ showSettingGroup->addAction(showSettingOriginAction);
+
+ QAction* showSettingStrechAction =
+ showSettingMenu->addAction(tr("show Stretch"),
+ this, SLOT(SetAdjustMode()));
+ showSettingStrechAction->setCheckable(true);
+ showSettingStrechAction->setData(Stretch);
+ showSettingGroup->addAction(showSettingStrechAction);
+
+ QToolButton* showSettingButton = new QToolButton(this);
+ QAction* dummyAction = new QAction(
+ QIcon(getPath(RESOURCE) + "pic/ViewerButtons/adjustsize.png"),
+ tr("Adjust Mode"), showSettingButton);
+ showSettingButton->setDefaultAction(dummyAction);
+ showSettingButton->setMenu(showSettingMenu);
+ showSettingButton->setPopupMode(QToolButton::InstantPopup);
+
+ showSettingFitAction->setChecked(true);
+
+ toolBar_->addWidget(showSettingButton);
+
+
+
+#if QT_VERSION >= 0x040200
+ setAlignment(Qt::AlignCenter);
+#endif
+ label_ = new QLabel(viewport());
+ label_->setScaledContents(true);
+ label_->setAlignment(Qt::AlignCenter);
+ label_->setText(tr("No Preview Available"));
+ setWidget(label_);
+ // resize(200, 100);
+}
+
+FQTermCanvas::~FQTermCanvas() {
+ //delete label;
+ //delete m_pMenu;
+}
+
+void FQTermCanvas::oriSize() {
+ useAdjustMode_ = false;
+ imageSize_ = image_.size();
+ adjustSize(size());
+}
+
+void FQTermCanvas::zoomIn() {
+ useAdjustMode_ = false;
+ resizeImage(0.05f);
+}
+
+void FQTermCanvas::zoomOut() {
+ useAdjustMode_ = false;
+ resizeImage(-0.05f);
+}
+
+void FQTermCanvas::autoAdjust() {
+ useAdjustMode_ = true;
+ adjustSize(size());
+}
+
+void FQTermCanvas::cwRotate() {
+ rotateImage(90);
+}
+
+void FQTermCanvas::ccwRotate() {
+ rotateImage(-90);
+}
+
+void FQTermCanvas::fullScreen() {
+ if (!isFullScreen()) {
+ showFullScreen();
+ } else {
+ showNormal();
+ }
+}
+
+void FQTermCanvas::loadImage(const QString& name, bool performAdjust) {
+ if (label_->movie()) {
+ label_->movie()->stop();
+ label_->setMovie(NULL);
+ }
+
+ gifPlayAction_->setEnabled(name.endsWith("gif"));
+
+
+ bool res = image_.load(name);
+ if (!res) {
+ QList<QByteArray> formats = QImageReader::supportedImageFormats();
+ for (QList<QByteArray>::iterator it = formats.begin();
+ !res && it != formats.end();
+ ++it) {
+ res = image_.load(name, it->data());
+ }
+ }
+ if (!image_.isNull()) {
+ fileName_ = QFileInfo(name).absoluteFilePath();
+ setWindowTitle(QFileInfo(name).fileName());
+
+ useAdjustMode_ = true;
+
+ if (!isEmbedded) {
+ QSize szView(image_.size());
+ szView.scale(640, 480, aspectRatioMode_);
+
+ if (szView.width() < image_.width()) {
+ imageSize_ = szView;
+ label_->resize(imageSize_);
+ label_->clear();
+ label_->setAlignment(Qt::AlignCenter);
+ label_->setPixmap(scaleImage(imageSize_));
+
+ if (!isEmbedded) {
+ resize(szView *1.1);
+ }
+ } else {
+ imageSize_ = image_.size();
+ label_->resize(imageSize_);
+ label_->setPixmap(QPixmap::fromImage(image_));
+ if (!isEmbedded) {
+ resize(imageSize_ + QSize(5, 5));
+ }
+ }
+ }
+ if (isEmbedded) {
+ label_->hide();
+ if (performAdjust) autoAdjust();
+ else label_->setPixmap(QPixmap::fromImage(image_));
+
+ label_->show();
+ }
+
+ } else {
+ FQ_TRACE("canvas", 1) << "Can't load the image: " << name;
+ }
+}
+
+void FQTermCanvas::playGIF() {
+ if (fileName_.endsWith("gif")) {
+ gifPlayer_.setFileName(fileName_);
+ if (gifPlayer_.isValid()) {
+ label_->setMovie(&gifPlayer_);
+ gifPlayer_.stop();
+ gifPlayer_.start();
+ return;
+ }
+ }
+}
+
+void FQTermCanvas::resizeImage(float ratio) {
+ QSize szImg = imageSize_;
+ szImg *= (1+ratio);
+ //we dont need so big
+ if (szImg.width() > 10000 || szImg.height() > 10000 ||
+ szImg.width() < 1 || szImg.height() < 1) {
+ return ;
+ }
+ imageSize_ = szImg;
+
+ if (!isFullScreen() && !isEmbedded) {
+ resize(imageSize_ *1.1);
+ } else {
+ adjustSize(size());
+ }
+}
+
+void FQTermCanvas::rotateImage(float ang) {
+ QMatrix wm;
+
+ wm.rotate(ang);
+
+ image_ = image_.transformed(wm, Qt::SmoothTransformation);
+
+ imageSize_ = image_.size();
+
+ adjustSize(size());
+}
+
+void FQTermCanvas::copyImage() {
+ QFileInfo fi(fileName_);
+// QString strSave =
+// QFileDialog::getSaveFileName(
+// this,tr("Choose a filename to save under"),
+// QDir::currentPath() + fi.fileName());
+ FQTermFileDialog fileDialog(config_);
+ QString strSave = fileDialog.getSaveName(fi.fileName(), "");
+ if (strSave.isEmpty()) {
+ return ;
+ }
+ QFile file(fileName_);
+ if (file.open(QIODevice::ReadOnly)) {
+ QFile save(strSave);
+ if (save.open(QIODevice::WriteOnly)) {
+ QByteArray ba = file.readAll();
+ QDataStream ds(&save);
+ ds.writeRawData(ba, ba.size());
+ save.close();
+ }
+ file.close();
+ }
+}
+
+void FQTermCanvas::silentCopy() {
+ // save it to $savefiledialog
+ QString strPath = config_->getItemValue("global", "savefiledialog");
+
+ QFileInfo fi(fileName_);
+ QString strSave = strPath + "/" + fi.fileName();
+
+ fi.setFile(strSave);
+
+ // add (%d) if exist
+ int i = 1;
+ while (fi.exists()) {
+ strSave = QString("%1/%2(%3).%4").arg(fi.dir().path()).arg
+ (fi.completeBaseName()).arg(i).arg(fi.suffix());
+ fi.setFile(strSave);
+ }
+
+ // copy it
+ QFile file(fileName_);
+ if (file.open(QIODevice::ReadOnly)) {
+ QFile save(strSave);
+ if (save.open(QIODevice::WriteOnly)) {
+ QByteArray ba = file.readAll();
+ QDataStream ds(&save);
+ ds.writeRawData(ba, ba.size());
+ save.close();
+ }
+ file.close();
+ }
+}
+
+QPixmap FQTermCanvas::scaleImage(const QSize &sz) {
+ return QPixmap::fromImage(
+ image_.scaled(sz, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
+}
+
+void FQTermCanvas::moveImage(float dx, float dy) {
+ scrollContentsBy(int(widget()->width()*dx), int(widget()->height() *dy));
+}
+
+void FQTermCanvas::saveImage() {
+
+ QFileInfo fi(fileName_);
+ FQTermFileDialog fileDialog(config_);
+ QString strSave = fileDialog.getSaveName(fi.fileName(), "");
+ if (strSave.isEmpty()) {
+ return ;
+ }
+ QString fmt = fi.suffix().toUpper();
+ if (!image_.save(strSave, fmt.toLatin1())) {
+ QMessageBox::warning(this, tr("Failed to save file"),
+ tr("Cant save file, maybe format not supported"));
+ }
+}
+
+void FQTermCanvas::deleteImage() {
+ QFile::remove(fileName_);
+ close();
+}
+
+void FQTermCanvas::closeEvent(QCloseEvent *ce) {
+ if (!isEmbedded) {
+ delete this;
+ }
+}
+
+void FQTermCanvas::viewportResizeEvent(QResizeEvent *re) {
+ adjustSize(re->size());
+}
+
+void FQTermCanvas::mousePressEvent(QMouseEvent *me) {
+ /* remove this to avoid click by mistake
+ if(me->button()&LeftButton)
+ {
+ close();
+ return;
+ }
+ */
+ if (me->button() &Qt::RightButton) {
+ menu_->popup(me->globalPos());
+ }
+}
+
+void FQTermCanvas::keyPressEvent(QKeyEvent *ke) {
+ switch (ke->key()) {
+ case Qt::Key_Escape:
+ if (!isEmbedded) {
+ if (isFullScreen()) {
+ showNormal();
+ } else {
+ close();
+ }
+ }
+
+ break;
+ case Qt::Key_D:
+ if (!isEmbedded){
+ deleteImage();
+ }
+ break;
+ case Qt::Key_Q:
+ if (!isEmbedded){
+ close();
+ }
+ break;
+ }
+}
+
+void FQTermCanvas::adjustSize(QSize szView) {
+ szView -= QSize(2 * frameWidth(), 2 * frameWidth());
+ if (label_->pixmap() == NULL && image_.isNull()) {
+ label_->resize(szView);
+ return ;
+ }
+
+ QSize szImg = imageSize_;
+
+ if (useAdjustMode_) {
+ szImg = image_.size();
+ switch(adjustMode_)
+ {
+ case MaxFit:
+ szImg.scale(szView, aspectRatioMode_);
+ break;
+ case Fit:
+ if (szImg.width() > szView.width() || szImg.height() > szView.height() ||
+ szImg.width() < image_.width()) {
+ szImg.scale(szView, aspectRatioMode_);
+ }
+ szImg = szImg.boundedTo(image_.size());
+ if (szImg.width() < 1 || szImg.height() < 1) {
+ return;
+ }
+ break;
+ case Origin:
+ break;
+ case Stretch:
+ szImg = szView;
+ break;
+ }
+ }
+
+ imageSize_ = szImg;
+ label_->resize(imageSize_);
+ label_->setPixmap(scaleImage(imageSize_));
+}
+
+void FQTermCanvas::resizeEvent(QResizeEvent * event) {
+ if (useAdjustMode_){
+ autoAdjust();
+ }
+ else adjustSize(size());
+ QScrollArea::resizeEvent(event);
+}
+
+QMenu* FQTermCanvas::menu() {
+ return menu_;
+}
+
+
+QToolBar* FQTermCanvas::ToolBar() {
+ return toolBar_;
+}
+
+void FQTermCanvas::SetAdjustMode(AdjustMode am) {
+ adjustMode_ = am;
+ if (adjustMode_ == Stretch) {
+ aspectRatioMode_ = Qt::IgnoreAspectRatio;
+ }
+ else {
+ aspectRatioMode_ = Qt::KeepAspectRatio;
+ }
+ autoAdjust();
+}
+
+void FQTermCanvas::SetAdjustMode() {
+ SetAdjustMode(AdjustMode((((QAction*)sender())->data()).toInt()));
+}
+
+void FQTermCanvas::openDir() {
+ QString poolPath = config_->getItemValue("preference", "pool");
+ if (poolPath.isEmpty()) {
+ poolPath = getPath(USER_CONFIG) + "pool/";
+ }
+#ifdef WIN32
+ QString path = "file:///" + poolPath;
+#else
+ QString path = "file://" + poolPath;
+#endif
+ QDesktopServices::openUrl(path);
+}
+
+void FQTermCanvas::updateImage(const QString& filename)
+{
+ if (QFileInfo(filename).absoluteFilePath().toLower() == fileName_) {
+ loadImage(fileName_);
+ }
+
+}
+
+
+} // namespace FQTerm
+
+#include "fqterm_canvas.moc"
diff --git a/src/ui/imageviewer/fqterm_canvas.h b/src/ui/imageviewer/fqterm_canvas.h
new file mode 100644
index 0000000..5941700
--- /dev/null
+++ b/src/ui/imageviewer/fqterm_canvas.h
@@ -0,0 +1,105 @@
+/***************************************************************************
+ * 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. *
+ ***************************************************************************/
+
+#ifndef FQTERM_CANVAS_H
+#define FQTERM_CANVAS_H
+
+#include <QScrollArea>
+#include <QImage>
+#include <QMovie>
+
+class QAction;
+class QLabel;
+class QMenu;
+class QCloseEvent;
+class QMouseEvent;
+class QKeyEvent;
+class QResizeEvent;
+class QToolBar;
+class QImage;
+
+namespace FQTerm {
+
+class FQTermConfig;
+class ExifExtractor;
+class FQTermFileDialog;
+
+class FQTermCanvas: public QScrollArea {
+ Q_OBJECT;
+ public:
+ FQTermCanvas(FQTermConfig *, QWidget *parent_ = NULL, Qt::WindowFlags f = Qt::Window);
+ ~FQTermCanvas();
+
+ enum AdjustMode{Origin, Fit, Stretch, MaxFit};
+
+ void updateImage(const QString& filename);
+ void loadImage(const QString&, bool = true);
+ QMenu* menu();
+ QToolBar* ToolBar();
+ public slots:
+ void oriSize();
+ void zoomIn();
+ void zoomOut();
+ void autoAdjust();
+ void fullScreen();
+ void saveImage();
+ void copyImage();
+ void silentCopy();
+ void cwRotate();
+ void ccwRotate();
+ void deleteImage();
+ void SetAdjustMode(AdjustMode am);
+ void openDir();
+ void playGIF();
+ protected slots:
+ void SetAdjustMode();
+ protected:
+ void resizeEvent (QResizeEvent * event);
+ void moveImage(float, float);
+ void resizeImage(float);
+ void rotateImage(float);
+
+ void closeEvent(QCloseEvent*);
+ void mousePressEvent(QMouseEvent*);
+ void keyPressEvent(QKeyEvent *ke);
+ void viewportResizeEvent(QResizeEvent *re);
+ void adjustSize(QSize);
+ QPixmap scaleImage(const QSize &);
+ protected:
+ QLabel *label_;
+ bool useAdjustMode_;
+ QSize imageSize_;
+ QString fileName_;
+ QImage image_;
+ QMovie gifPlayer_;
+ QMenu *menu_;
+ QToolBar *toolBar_;
+ FQTermConfig * config_;
+ QAction* gifPlayAction_;
+
+ AdjustMode adjustMode_;
+ Qt::AspectRatioMode aspectRatioMode_;
+ // TODO: Very dirty trick, I hate it
+ bool isEmbedded;
+};
+
+} // namespace FQTerm
+
+#endif // FQTERM_CANVAS_H
diff --git a/src/ui/imageviewer/fqtermimage.cpp b/src/ui/imageviewer/fqtermimage.cpp
new file mode 100644
index 0000000..49be3d7
--- /dev/null
+++ b/src/ui/imageviewer/fqtermimage.cpp
@@ -0,0 +1,9 @@
+#include "fqtermimage.h"
+
+namespace FQTerm
+{
+ FQTermImage::FQTermImage(QWidget * parent, Qt::WindowFlags f)
+ : QWidget(parent, f)
+ {
+ }
+}
diff --git a/src/ui/imageviewer/fqtermimage.h b/src/ui/imageviewer/fqtermimage.h
new file mode 100644
index 0000000..8c75ac3
--- /dev/null
+++ b/src/ui/imageviewer/fqtermimage.h
@@ -0,0 +1,22 @@
+#ifndef FQTERMIMAGE_H
+#define FQTERMIMAGE_H
+
+#include <QWidget>
+#include <QString>
+
+namespace FQTerm
+{
+#define ICON_SOURCE "pic/ViewerButtons/"
+
+ class FQTermImage : public QWidget {
+
+ public:
+ FQTermImage(QWidget * parent, Qt::WindowFlags f);
+ virtual void adjustItemSize() = 0;
+ virtual void scrollTo(const QString &) = 0;
+ virtual void updateImage(const QString &) = 0;
+ };
+
+}
+
+#endif
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"
diff --git a/src/ui/imageviewer/imageviewer.h b/src/ui/imageviewer/imageviewer.h
new file mode 100644
index 0000000..1948633
--- /dev/null
+++ b/src/ui/imageviewer/imageviewer.h
@@ -0,0 +1,246 @@
+
+/***************************************************************************
+ * 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. *
+ ***************************************************************************/
+
+#ifndef IMAGE_VIERWER_H
+#define IMAGE_VIERWER_H
+
+#include <QDesktopWidget>
+#include <QDirModel>
+#include <QLabel>
+#include <QPainter>
+#include <QPixmapCache>
+#include <QScrollArea>
+#include <QStatusBar>
+#include <QToolButton>
+#include <QToolBar>
+#include <QLayout>
+#include <QItemDelegate>
+#include <QItemSelection>
+
+class QString;
+class QPushButton;
+class QVBoxLayout;
+class QHBoxLayout;
+class QMenuBar;
+class QComboBox;
+
+#include "pictureflow.h"
+#include "fqtermimage.h"
+
+namespace FQTerm {
+
+#define DISP_MARGIN (10.0) // show a margin
+#define PIXMAP_CACHESIZE (5120) // in KBytes
+#define ZOOMIN_FACTOR (1.15)
+#define ZOOMOUT_FACTOR (0.85)
+#define ROTLEFT_DEG (-90.0)
+#define ROTRIGHT_DEG (90.0)
+#define IMG_CHUNK (16384) // in bytes
+
+#define IMAGE_BROWSER_NAME "FQLora [Image Companion for FQTerm]"
+#define POOL_SOURCE "pool/"
+#define SHADOW_SOURCE "shadow-cache/"
+#define TRASH_SOURCE ".Trash/"
+
+#define EMBLEM_LIKE "emblem-like.png"
+#define EMBLEM_NEW "emblem-new.png"
+#define EMBLEM_RECOVER "emblem-recover.png"
+#define EMBLEM_TRASH "emblem-trash.png"
+#define EMBLEM_TITLE "emblem-title.png"
+
+ class FQTermConfig;
+ class FQTermFileDialog;
+
+
+ class ImageFlow;
+ class ImageMenu;
+ class ImageFlowItem;
+
+ class FQTermImageFlow: public FQTermImage {
+ Q_OBJECT;
+
+ public:
+ FQTermImageFlow(FQTermConfig *, QWidget *, Qt::WindowFlags);
+ ~FQTermImageFlow();
+
+ /* don't modify the funcs below */
+ /* they are for API compatibiliy */
+ void adjustItemSize();
+ void scrollTo(const QString &);
+ void updateImage(const QString &);
+ /*********************************/
+
+ public slots:
+ void saveImages(void);
+ void clearImages(void);
+ void trashAllImages(void);
+ void recoverImages(void);
+ void showStatusMessage(const QString &message);
+
+ private:
+ ImageFlow *imageFlow_;
+ ImageMenu *imageMenu_;
+ QStatusBar *statusBar_;
+ FQTermConfig *config_;
+
+ QFileInfoList sortedList(const QString &path);
+ const QString& poolSource(void) const;
+ const QString& trashSource(void) const;
+
+ private slots:
+ void loadImages(const int status);
+ void reshuffleImages(const int status);
+ void checkTrashState(void);
+
+ signals:
+ void statusMessage(const QString &message);
+ void isTrashEmpty(void);
+ void trashStatus(const bool fullOrNot);
+ void saveStatus(const bool hasOrNot);
+ void clearStatus(const bool hasOrNot);
+
+ protected:
+ void showEvent(QShowEvent *event);
+ void closeEvent(QCloseEvent *event);
+ };
+
+
+ class ImageMenu: public QLabel {
+ Q_OBJECT;
+
+ public:
+ ImageMenu(QWidget *parent = 0);
+ ~ImageMenu();
+
+ public slots:
+ void updateEmblems(const int status);
+ void updateDustbin(const bool fullOrNot);
+ void updateSave(const bool hasOrNot);
+ void updateClear(const bool hasOrNot);
+
+ signals:
+ void toggleFlowStatus(const int status);
+ void recoverImages(void);
+ void saveImages(void);
+ void clearImages(void);
+
+ private:
+ class Private;
+ Private *fh;
+
+ protected:
+ void paintEvent(QPaintEvent *event);
+ void mouseReleaseEvent(QMouseEvent *event);
+ };
+
+ class ImageFlow: public PictureFlow {
+ Q_OBJECT;
+ Q_PROPERTY(QList<QByteArray> itemsTitles READ itemsTitles);
+
+ public:
+ ImageFlow(QWidget *parent);
+ ~ImageFlow();
+
+ const ImageFlowItem& operator[](int index) const;
+ ImageFlowItem& operator[](int index);
+ const ImageFlowItem& at(int index) const;
+
+ QList<QByteArray>& itemsTitles(void) const;
+
+ QFileInfoList& digest(const int status);
+ void strip(const int status);
+ QList<qint64> sort(const int status);
+ bool reorder(const QList<qint64>& itemsKey);
+
+ public slots:
+ void add(const ImageFlowItem &item);
+ void clear(void);
+ int count(void) const;
+ int size(void) const;
+ void rehash(void);
+ void toggleStatus(const int status);
+ void setCurrentImage(const int index);
+
+ private:
+ class Private;
+ Private *m;
+
+ signals:
+ void statusMessage(const QString &message);
+ void emblemStatus(const int status);
+ void saveStatus(const bool hasOrNot);
+ void clearStatus(const bool hasOrNot);
+ void titlesChanged(QList<QByteArray> &titles);
+
+ protected:
+ void paintEvent(QPaintEvent *event);
+ };
+
+ class ImageFlowItem: public QObject {
+ Q_OBJECT;
+
+ Q_PROPERTY(qint64 key READ key);
+ Q_PROPERTY(int status READ status WRITE setStatus);
+ Q_PROPERTY(QImage image READ image WRITE setImage);
+ Q_PROPERTY(QFileInfo info READ info WRITE setInfo);
+ Q_PROPERTY(QString comment READ comment WRITE setComment);
+
+ public:
+ ImageFlowItem(QObject *parent = 0);
+ ImageFlowItem(const ImageFlowItem &item);
+ ImageFlowItem(const QImage &image, QObject *parent = 0);
+ ImageFlowItem(const QImage &image, const QFileInfo &info, QObject *parent = 0);
+ ImageFlowItem(const QImage &image, const QFileInfo &info, const int state, QObject *parent = 0);
+ ImageFlowItem(const QImage &image, const QFileInfo &info, const QString &comment, QObject *parent = 0);
+ ImageFlowItem(const QImage &image, const QFileInfo &info, const QString& comment, const int status, QObject *parent = 0);
+ ~ImageFlowItem();
+
+ const QFileInfo& info(void) const;
+ const QString& comment(void) const;
+ const QImage& image(void) const;
+ int status(void) const;
+ qint64 key(void) const;
+
+ ImageFlowItem& operator=(const ImageFlowItem &item);
+ bool operator==(const ImageFlowItem &item) const;
+ bool operator!=(const ImageFlowItem &item) const;
+
+ public slots:
+ void setStatus(const int status);
+ void setInfo(const QFileInfo &info);
+ void setComment(const QString &comment);
+ void setImage(const QImage &image);
+ void setImage(const QString &filePath, int size);
+
+ private slots:
+ void loadImage(void);
+
+ private:
+ QImage resizeImage(const QImage &image, int size);
+ class Private;
+ Private *m;
+
+ };
+
+
+} // namespace FQTerm
+
+#endif // IMAGE_VIERWER_H
diff --git a/src/ui/imageviewer/imageviewer_origin.cpp b/src/ui/imageviewer/imageviewer_origin.cpp
new file mode 100644
index 0000000..6e3ceae
--- /dev/null
+++ b/src/ui/imageviewer/imageviewer_origin.cpp
@@ -0,0 +1,454 @@
+/* FQTerm image viewer origin.
+ */
+
+#include <QAction>
+#include <QBuffer>
+#include <QCursor>
+#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_origin.h"
+
+namespace FQTerm {
+
+ void FQTermImageOrigin::onChange(const QModelIndex & index) {
+
+ if (!model_->isDir(index)) {
+
+ if (!isHidden())
+ canvas_->hide();
+
+ QString exifInfo = QString::fromStdString(exifExtractor_->extractExifInfo(model_->filePath(tree_->currentIndex()).toLocal8Bit().data()));
+ bool resized = false;
+
+ if (exifInfo != "") {
+
+ if (!isExifTableShown_) {
+ adjustLayout(true);
+ isExifTableShown_ = true;
+ resized = true;
+ }
+
+ updateExifInfo();
+ } else {
+
+ if (isExifTableShown_) {
+ adjustLayout(false);
+ isExifTableShown_ = false;
+ resized = true;
+ }
+ }
+
+ QString path = QDir::toNativeSeparators(model_->filePath(index));
+
+ if (path.endsWith(QDir::separator()))
+ path.chop(1);
+
+ canvas_->loadImage(path, !resized);
+
+ // canvas_->autoAdjust();
+ if (!isHidden())
+ canvas_->show();
+ }
+ }
+
+ FQTermImageOrigin::~FQTermImageOrigin() {
+ delete menuBar_;
+ delete canvas_;
+ delete tree_;
+ delete model_;
+ }
+
+ FQTermImageOrigin::FQTermImageOrigin(FQTermConfig * config, QWidget *parent,
+ Qt::WindowFlags wflag) :
+ FQTermImage(parent, wflag),
+ config_(config),
+ isExifTableShown_(false) {
+
+ setWindowTitle(tr("FQTerm Image Viewer"));
+ ItemDelegate* itemDelegate = new ItemDelegate;
+ exifExtractor_ = new ExifExtractor;
+ exifTable_ = new ExifTable(this);
+ exifTable_->setTextFormat(Qt::RichText);
+ exifTable_->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
+ canvas_ = new FQTermCanvas(config, this, 0);
+ canvas_->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
+ model_ = new ImageViewerDirModel;
+ tree_ = new QTreeView;
+
+
+ tree_->setModel(model_);
+ tree_->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
+ adjustItemSize();
+ tree_->setItemDelegate(itemDelegate);
+ tree_->setColumnWidth(0, 150);
+ tree_->setColumnWidth(1, 0);
+
+ tree_->hideColumn(0);
+
+ tree_->setUniformRowHeights(true);
+ tree_->setWordWrap(true);
+ //tree_->header()->setResizeMode(0, QHeaderView::Fixed);
+ //tree_->header()->setResizeMode(1, QHeaderView::ResizeToContents);
+ //tree_->header()->resizeSection(0, 150);
+ //tree_->header()->resizeSection(1, 1);
+ comboBox_ = new QComboBox(this);
+ comboBox_->addItem(tr("Sort by name"), QDir::Name);
+ comboBox_->addItem(tr("Sort by time"), QDir::Time);
+ comboBox_->addItem(tr("Sort by size"), QDir::Size);
+ comboBox_->addItem(tr("Sort by type"), QDir::Type);
+
+ FQ_VERIFY(connect(comboBox_, SIGNAL(currentIndexChanged(int)), this, SLOT(sortFileList(int))));
+
+ comboBox_->setCurrentIndex(1);
+
+ layout_ = new QGridLayout;
+ menuBar_ = new QMenuBar(this);
+ menuBar_->addMenu(canvas_->menu());
+ menuBar_->resize(1,1);
+
+ canvas_->ToolBar()->addAction(
+ QIcon(getPath(RESOURCE) + ICON_SOURCE + "prev.png"), tr("Previous"),
+ this, SLOT(previous()));
+ canvas_->ToolBar()->addAction(
+ QIcon(getPath(RESOURCE) + ICON_SOURCE + "next.png"), tr("Next"),
+ this, SLOT(next()));
+
+ layout_->addWidget(tree_, 0, 0, 12, 1);
+ layout_->addWidget(comboBox_, 12, 0, 1, 1);
+ layout_->addWidget(canvas_, 0, 1, 12, 10);
+ // layout_->addWidget(exifTable_, 10, 1, 2, 10);
+ layout_->addWidget(canvas_->ToolBar(), 12, 1, 1, 10, Qt::AlignHCenter);
+ layout_->setColumnMinimumWidth(0, tree_->columnWidth(0) + 150);
+ setLayout(layout_);
+
+ /*
+ FQ_VERIFY(connect(tree_, SIGNAL(clicked(const QModelIndex &)),
+ this, SLOT(onChange(const QModelIndex &))));
+ */
+ FQ_VERIFY(connect(tree_, SIGNAL(activated(const QModelIndex &)),
+ this, SLOT(onChange(const QModelIndex &))));
+ FQ_VERIFY(connect(tree_->selectionModel(),
+ SIGNAL(selectionChanged(const QItemSelection&,
+ const QItemSelection&)),
+ this, SLOT(selectionChanged(const QItemSelection&,
+ const QItemSelection&))));
+ FQ_VERIFY(connect(exifTable_, SIGNAL(showExifDetails()),
+ this, SLOT(showFullExifInfo())));
+ }
+
+ void FQTermImageOrigin::scrollTo(const QString& filename) {
+
+ QString path = QFileInfo(filename).absolutePath();
+ model_->refresh();
+ tree_->setRootIndex(model_->index(path));
+ canvas_->loadImage(filename);
+
+ if (canvas_->isHidden() && !isHidden()) {
+ canvas_->show();
+ }
+
+ const QModelIndex& index = model_->index(filename);
+ tree_->scrollTo(index);
+ tree_->setCurrentIndex(index);
+ }
+
+ void FQTermImageOrigin::updateImage(const QString& filename) {
+
+ static int i = 0;
+ if (++i == 10) {
+ model_->refresh(model_->index(filename));
+ i = 0;
+ }
+ canvas_->updateImage(filename);
+ }
+
+ void FQTermImageOrigin::previous() {
+
+ const QModelIndex& index = tree_->indexAbove(tree_->currentIndex());
+ if (index.isValid()) {
+ tree_->setCurrentIndex(index);
+ canvas_->loadImage(QDir::toNativeSeparators(model_->filePath(index)));
+ }
+ }
+
+ void FQTermImageOrigin::next() {
+
+ const QModelIndex& index = tree_->indexBelow(tree_->currentIndex());
+ if (index.isValid()) {
+ tree_->setCurrentIndex(index);
+ canvas_->loadImage(QDir::toNativeSeparators(model_->filePath(index)));
+ }
+ }
+
+ void FQTermImageOrigin::adjustItemSize() {
+
+ QFontMetrics fm(font());
+ ItemDelegate::size_.setWidth(qMax(128, fm.width("WWWWWWWW.WWW")));
+ ItemDelegate::size_.setHeight(fm.height() + 150);
+ }
+
+ void FQTermImageOrigin::selectionChanged(const QItemSelection & selected,
+ const QItemSelection & deselected) {
+
+ onChange(tree_->selectionModel()->currentIndex());
+ }
+
+ void FQTermImageOrigin::sortFileList(int index) {
+
+ model_->setSorting(QDir::SortFlag(comboBox_->itemData(index).toInt()));
+ QString poolPath = config_->getItemValue("preference", "pool");
+
+ if (poolPath.isEmpty()) {
+ poolPath = getPath(USER_CONFIG) + "pool/";
+ }
+
+ tree_->setRootIndex(model_->index(poolPath));
+ }
+
+ void FQTermImageOrigin::showFullExifInfo() {
+
+ QString exifInfo = QString::fromStdString(exifExtractor_->extractExifInfo(model_->filePath(tree_->currentIndex()).toLocal8Bit().data()));
+ QString comment;
+
+ if ((*exifExtractor_)["UserComment"].length() > 8) {
+
+ QString commentEncoding = QString::fromStdString((*exifExtractor_)["UserComment"].substr(0, 8));
+
+ if (commentEncoding.startsWith("UNICODE")) {
+ //UTF-16
+ QTextCodec* c = QTextCodec::codecForName("UTF-16");
+ comment = c->toUnicode((*exifExtractor_)["UserComment"].substr(8).c_str());
+ } else if (commentEncoding.startsWith("JIS")) {
+ //JIS X 0208
+ QTextCodec* c = QTextCodec::codecForName("JIS X 0208");
+ comment = c->toUnicode((*exifExtractor_)["UserComment"].substr(8).c_str());
+ } else {
+ comment = QString::fromStdString((*exifExtractor_)["UserComment"].substr(8));
+ }
+ }
+
+
+ QTextEdit* info = new QTextEdit;
+ info->setText(exifInfo + tr("Comment : ") + comment + "\n");
+ info->setWindowFlags(Qt::Dialog);
+ info->setAttribute(Qt::WA_DeleteOnClose);
+ info->setAttribute(Qt::WA_ShowModal);
+ // info->setLineWrapMode(QTextEdit::NoWrap);
+ info->setReadOnly(true);
+ QFontMetrics fm(font());
+ info->resize(fm.width("Orientation : 1st row - 1st col : top - left side "), fm.height() * 20);
+ info->show();
+ }
+
+ void FQTermImageOrigin::adjustLayout(bool withExifTable) {
+
+ if (withExifTable) {
+
+ layout_->addWidget(canvas_, 0, 1, 11, 10);
+ layout_->addWidget(exifTable_, 11, 1, 1, 10, Qt::AlignHCenter);
+ if (!isHidden() && exifTable_->isHidden()) {
+ exifTable_->show();
+ }
+
+ layout_->addWidget(canvas_->ToolBar(), 12, 1, 1, 10, Qt::AlignHCenter);
+ } else {
+ layout_->addWidget(canvas_, 0, 1, 12, 10);
+ layout_->removeWidget(exifTable_);
+ exifTable_->hide();
+ layout_->addWidget(canvas_->ToolBar(), 12, 1, 1, 10, Qt::AlignHCenter);
+ }
+ }
+
+ void FQTermImageOrigin::updateExifInfo() {
+
+ exifTable_->clear();
+
+ QString exifInfoToShow = "<table border=\"1\"><tr><td>"
+ + tr("Model") + " : " + QString::fromStdString((*exifExtractor_)["Model"]) + "</td><td>"
+ + QString::fromStdString((*exifExtractor_)["DateTime"]) + "</td><td>"
+ + QString::fromStdString((*exifExtractor_)["Flash"]) + "</td>"
+ + "</tr><tr><td>"
+ + tr("ExposureTime") + " : " + QString::fromStdString((*exifExtractor_)["ExposureTime"]) + "</td><td>"
+ + tr("FNumber") + " : " + QString::fromStdString((*exifExtractor_)["FNumber"]) + "</td><td>"
+ + tr("ISO") + " : " + QString::fromStdString((*exifExtractor_)["ISOSpeedRatings"]) + "</td>"
+ + "</tr><tr><td>"
+ + tr("FocalLength") + " : " + QString::fromStdString((*exifExtractor_)["FocalLength"]) + "</td><td>"
+ + tr("MeteringMode") + " : " + QString::fromStdString((*exifExtractor_)["MeteringMode"]) + "</td><td>"
+ + tr("ExposureBias") + " : " + QString::fromStdString((*exifExtractor_)["ExposureBiasValue"]) + "</td></tr></tabel>";
+
+ exifTable_->setText(exifInfoToShow);
+ if (!isHidden() && exifTable_->isHidden()) {
+ exifTable_->show();
+ }
+ }
+
+ void FQTermImageOrigin::closeEvent( QCloseEvent *clse ) {
+
+ hide();
+ clse->ignore();
+ return ;
+ }
+
+ ImageViewerDirModel::ImageViewerDirModel(QObject *parent /*= 0*/)
+ : QDirModel(parent) {
+
+ //insertColumn(1);
+ QStringList nameFilterList;
+ QList<QByteArray> formats = QImageReader::supportedImageFormats();
+ for (QList<QByteArray>::iterator it = formats.begin();
+ it != formats.end();
+ ++it) {
+ QString filter("*.");
+ filter.append(it->data());
+ nameFilterList << filter;
+ }
+ setNameFilters(nameFilterList);
+ setFilter(QDir::Files);
+ }
+
+ int ImageViewerDirModel::columnCount(const QModelIndex &/*parent*/) const {
+
+ return 2;
+ }
+
+ QVariant ImageViewerDirModel::headerData(
+ int section, Qt::Orientation orientation, int role) const {
+ //if (section == 0) return QVariant();
+ if (role == Qt::DisplayRole) {
+ // if (section == 1) {
+ return QString(tr("Image Preview"));
+ //}
+ }
+ return QDirModel::headerData(section, orientation, role);
+ }
+
+ QVariant ImageViewerDirModel::data(const QModelIndex &index, int role) const {
+ //if (index.column() == 0) return QVariant();
+ if (role == Qt::DecorationRole) {
+ if (isDir(index)) {
+ return QVariant();
+ }
+
+ QString path = QDir::toNativeSeparators(filePath(index));
+ if (path.endsWith(QDir::separator()))
+ path.chop(1);
+
+ QPixmap pixmap;
+ bool res = pixmap.load(path);
+ if (!res) {
+ QList<QByteArray> formats = QImageReader::supportedImageFormats();
+ for (QList<QByteArray>::iterator it = formats.begin();
+ !res && it != formats.end();
+ ++it) {
+ res = pixmap.load(path, it->data());
+ }
+ }
+
+ if (pixmap.height() > 128 || pixmap.width() > 128) {
+ return pixmap.scaled(128, 128,
+ Qt::KeepAspectRatio, Qt::SmoothTransformation);
+ }
+
+ return pixmap;
+ } else if (role == Qt::DisplayRole) {
+ return fileName(index);
+ }/*
+ else if (role == Qt::TextAlignmentRole) {
+ return Qt::Qt::AlignBottom;
+ }*/
+ return QVariant();
+ }
+
+ QSize ItemDelegate::size_;
+
+ void ItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem & option,
+ const QModelIndex & index ) const {
+ //if (index.column() == 0) return;
+ QStyleOptionViewItemV3 opt = setOptions(index, option);
+
+ // prepare
+ painter->save();
+
+ // get the data and the rectangles
+ const QPixmap& pixmap
+ = qvariant_cast<QPixmap>(index.data(Qt::DecorationRole));
+ QRect decorationRect = QRect(opt.rect.topLeft(), pixmap.size());
+ decorationRect.moveTo(decorationRect.left(), decorationRect.top() + 10);
+ const QString& text = index.data(Qt::DisplayRole).toString();
+ QFontMetrics fm(painter->font());
+ QRect displayRect = QRect(decorationRect.bottomLeft(),
+ QSize(fm.width(text),fm.height()));
+
+ QRect checkRect;
+ Qt::CheckState checkState = Qt::Unchecked;
+ QVariant value = index.data(Qt::CheckStateRole);
+
+ if (value.isValid()) {
+ checkState = static_cast<Qt::CheckState>(value.toInt());
+#if QT_VERSION >= 0x050000
+ checkRect = doCheck(opt, opt.rect, value);
+#else
+ checkRect = check(opt, opt.rect, value);
+#endif
+ }
+
+ // do the layout
+
+ // doLayout(opt, &checkRect, &decorationRect, &displayRect, false);
+
+ // draw the item
+
+ drawBackground(painter, opt, index);
+ painter->drawPixmap(decorationRect, pixmap);
+ painter->drawText(displayRect, text);
+
+ drawFocus(painter, opt, displayRect);
+
+ // done
+ painter->restore();
+ }
+
+ void ExifTable::mouseReleaseEvent(QMouseEvent *pEvent) {
+
+ if (pEvent->button() == Qt::LeftButton) {
+ emit(showExifDetails());
+ }
+ }
+
+ ExifTable::ExifTable(QWidget *parent) : QLabel(parent) {
+
+ }
+
+}
+
+#include "imageviewer_origin.moc"
diff --git a/src/ui/imageviewer/imageviewer_origin.h b/src/ui/imageviewer/imageviewer_origin.h
new file mode 100644
index 0000000..15d9c62
--- /dev/null
+++ b/src/ui/imageviewer/imageviewer_origin.h
@@ -0,0 +1,108 @@
+///////////////////////////////////////////////////////
+//////// the origin image viewer //////////////////////
+///////////////////////////////////////////////////////
+
+#ifndef FQTERM_IMAGEVIEWER_ORIGIN_H
+#define FQTERM_IMAGEVIEWER_ORIGIN_H
+
+#include <QDesktopWidget>
+#include <QDirModel>
+#include <QLabel>
+#include <QPainter>
+#include <QPixmapCache>
+#include <QScrollArea>
+#include <QStatusBar>
+#include <QToolButton>
+#include <QToolBar>
+#include <QLayout>
+#include <QItemDelegate>
+#include <QItemSelection>
+#include <QTreeView>
+#include <QMenuBar>
+#include <QComboBox>
+
+#include "pictureflow.h"
+#include "fqtermimage.h"
+
+namespace FQTerm
+{
+
+ class FQTermCanvas;
+ class ExifExtractor;
+
+ class ItemDelegate : public QItemDelegate {
+ public:
+ static QSize size_;
+
+ ItemDelegate() {
+ size_ = QSize(250,200);
+ }
+
+ QSize sizeHint (const QStyleOptionViewItem & option, const QModelIndex & index) const {
+ return size_;
+ }
+
+ void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const;
+ };
+
+ class ExifTable : public QLabel {
+ Q_OBJECT;
+
+ public:
+ ExifTable(QWidget *parent);
+
+ signals:
+ void showExifDetails();
+
+ protected:
+ void mouseReleaseEvent(QMouseEvent *pEvent);
+ };
+
+ class ImageViewerDirModel : public QDirModel {
+ public:
+ ImageViewerDirModel(QObject *parent = 0);
+
+ int columnCount(const QModelIndex & = QModelIndex()) const;
+ QVariant headerData ( int section, Qt::Orientation orientation, int role) const;
+ QVariant data(const QModelIndex &index, int role) const;
+ };
+
+ class FQTermImageOrigin: public FQTermImage {
+ Q_OBJECT;
+
+ public:
+ FQTermImageOrigin(FQTermConfig * config, QWidget *parent, Qt::WindowFlags wflag);
+ ~FQTermImageOrigin();
+ void scrollTo(const QString& filename);
+ void updateImage(const QString& filename);
+
+ public slots:
+ void onChange(const QModelIndex & index);
+ void next();
+ void previous();
+ void adjustItemSize();
+ void selectionChanged(const QItemSelection & selected, const QItemSelection & deselected);
+ void sortFileList(int index);
+ void showFullExifInfo();
+ void adjustLayout(bool withExifTable);
+ void updateExifInfo();
+
+ protected:
+ void closeEvent(QCloseEvent *clse);
+
+ private:
+ FQTermCanvas* canvas_;
+ QTreeView* tree_;
+ ImageViewerDirModel* model_;
+ QMenuBar* menuBar_;
+ QComboBox* comboBox_;
+ FQTermConfig* config_;
+ ExifExtractor* exifExtractor_;
+ ExifTable* exifTable_;
+ QGridLayout* layout_;
+ bool isExifTableShown_;
+ };
+
+}
+
+#endif
diff --git a/src/ui/imageviewer/pictureflow.cpp b/src/ui/imageviewer/pictureflow.cpp
new file mode 100644
index 0000000..cd749f0
--- /dev/null
+++ b/src/ui/imageviewer/pictureflow.cpp
@@ -0,0 +1,1201 @@
+
+/***************************************************************************
+ * 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. *
+ ***************************************************************************/
+
+/*
+ PictureFlow - animated image show widget
+ http://pictureflow.googlecode.com
+
+ Copyright (C) 2007 Ariya Hidayat (ariya@kde.org)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*/
+
+#include "pictureflow.h"
+
+#include <QBasicTimer>
+#include <QMouseEvent>
+#include <QCache>
+#include <QImage>
+#include <QKeyEvent>
+#include <QPainter>
+#include <QPixmap>
+#include <QTimer>
+#include <QVector>
+#include <QWidget>
+
+// uncomment this to enable bilinear filtering for texture mapping
+// gives much better rendering, at the cost of memory space
+// #define PICTUREFLOW_BILINEAR_FILTER
+
+// for fixed-point arithmetic, we need minimum 32-bit long
+// long long (64-bit) might be useful for multiplication and division
+typedef long PFreal;
+#define PFREAL_SHIFT 10
+#define PFREAL_FACTOR (1 << PFREAL_SHIFT)
+#define PFREAL_ONE (1 << PFREAL_SHIFT)
+#define PFREAL_HALF (PFREAL_ONE >> 1)
+
+inline PFreal fmul(PFreal a, PFreal b)
+{
+ return ((long long)(a))*((long long)(b)) >> PFREAL_SHIFT;
+}
+
+inline PFreal fdiv(PFreal num, PFreal den)
+{
+ long long p = (long long)(num) << (PFREAL_SHIFT*2);
+ long long q = p / (long long)den;
+ long long r = q >> PFREAL_SHIFT;
+
+ return r;
+}
+
+#define IANGLE_MAX 1024
+#define IANGLE_MASK 1023
+
+// warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed!
+static const PFreal sinTable[IANGLE_MAX] = {
+ 3, 9, 15, 21, 28, 34, 40, 47,
+ 53, 59, 65, 72, 78, 84, 90, 97,
+ 103, 109, 115, 122, 128, 134, 140, 147,
+ 153, 159, 165, 171, 178, 184, 190, 196,
+ 202, 209, 215, 221, 227, 233, 239, 245,
+ 251, 257, 264, 270, 276, 282, 288, 294,
+ 300, 306, 312, 318, 324, 330, 336, 342,
+ 347, 353, 359, 365, 371, 377, 383, 388,
+ 394, 400, 406, 412, 417, 423, 429, 434,
+ 440, 446, 451, 457, 463, 468, 474, 479,
+ 485, 491, 496, 501, 507, 512, 518, 523,
+ 529, 534, 539, 545, 550, 555, 561, 566,
+ 571, 576, 581, 587, 592, 597, 602, 607,
+ 612, 617, 622, 627, 632, 637, 642, 647,
+ 652, 656, 661, 666, 671, 675, 680, 685,
+ 690, 694, 699, 703, 708, 712, 717, 721,
+ 726, 730, 735, 739, 743, 748, 752, 756,
+ 760, 765, 769, 773, 777, 781, 785, 789,
+ 793, 797, 801, 805, 809, 813, 816, 820,
+ 824, 828, 831, 835, 839, 842, 846, 849,
+ 853, 856, 860, 863, 866, 870, 873, 876,
+ 879, 883, 886, 889, 892, 895, 898, 901,
+ 904, 907, 910, 913, 916, 918, 921, 924,
+ 927, 929, 932, 934, 937, 939, 942, 944,
+ 947, 949, 951, 954, 956, 958, 960, 963,
+ 965, 967, 969, 971, 973, 975, 977, 978,
+ 980, 982, 984, 986, 987, 989, 990, 992,
+ 994, 995, 997, 998, 999, 1001, 1002, 1003,
+ 1004, 1006, 1007, 1008, 1009, 1010, 1011, 1012,
+ 1013, 1014, 1015, 1015, 1016, 1017, 1018, 1018,
+ 1019, 1019, 1020, 1020, 1021, 1021, 1022, 1022,
+ 1022, 1023, 1023, 1023, 1023, 1023, 1023, 1023,
+ 1023, 1023, 1023, 1023, 1023, 1023, 1023, 1022,
+ 1022, 1022, 1021, 1021, 1020, 1020, 1019, 1019,
+ 1018, 1018, 1017, 1016, 1015, 1015, 1014, 1013,
+ 1012, 1011, 1010, 1009, 1008, 1007, 1006, 1004,
+ 1003, 1002, 1001, 999, 998, 997, 995, 994,
+ 992, 990, 989, 987, 986, 984, 982, 980,
+ 978, 977, 975, 973, 971, 969, 967, 965,
+ 963, 960, 958, 956, 954, 951, 949, 947,
+ 944, 942, 939, 937, 934, 932, 929, 927,
+ 924, 921, 918, 916, 913, 910, 907, 904,
+ 901, 898, 895, 892, 889, 886, 883, 879,
+ 876, 873, 870, 866, 863, 860, 856, 853,
+ 849, 846, 842, 839, 835, 831, 828, 824,
+ 820, 816, 813, 809, 805, 801, 797, 793,
+ 789, 785, 781, 777, 773, 769, 765, 760,
+ 756, 752, 748, 743, 739, 735, 730, 726,
+ 721, 717, 712, 708, 703, 699, 694, 690,
+ 685, 680, 675, 671, 666, 661, 656, 652,
+ 647, 642, 637, 632, 627, 622, 617, 612,
+ 607, 602, 597, 592, 587, 581, 576, 571,
+ 566, 561, 555, 550, 545, 539, 534, 529,
+ 523, 518, 512, 507, 501, 496, 491, 485,
+ 479, 474, 468, 463, 457, 451, 446, 440,
+ 434, 429, 423, 417, 412, 406, 400, 394,
+ 388, 383, 377, 371, 365, 359, 353, 347,
+ 342, 336, 330, 324, 318, 312, 306, 300,
+ 294, 288, 282, 276, 270, 264, 257, 251,
+ 245, 239, 233, 227, 221, 215, 209, 202,
+ 196, 190, 184, 178, 171, 165, 159, 153,
+ 147, 140, 134, 128, 122, 115, 109, 103,
+ 97, 90, 84, 78, 72, 65, 59, 53,
+ 47, 40, 34, 28, 21, 15, 9, 3,
+ -4, -10, -16, -22, -29, -35, -41, -48,
+ -54, -60, -66, -73, -79, -85, -91, -98,
+ -104, -110, -116, -123, -129, -135, -141, -148,
+ -154, -160, -166, -172, -179, -185, -191, -197,
+ -203, -210, -216, -222, -228, -234, -240, -246,
+ -252, -258, -265, -271, -277, -283, -289, -295,
+ -301, -307, -313, -319, -325, -331, -337, -343,
+ -348, -354, -360, -366, -372, -378, -384, -389,
+ -395, -401, -407, -413, -418, -424, -430, -435,
+ -441, -447, -452, -458, -464, -469, -475, -480,
+ -486, -492, -497, -502, -508, -513, -519, -524,
+ -530, -535, -540, -546, -551, -556, -562, -567,
+ -572, -577, -582, -588, -593, -598, -603, -608,
+ -613, -618, -623, -628, -633, -638, -643, -648,
+ -653, -657, -662, -667, -672, -676, -681, -686,
+ -691, -695, -700, -704, -709, -713, -718, -722,
+ -727, -731, -736, -740, -744, -749, -753, -757,
+ -761, -766, -770, -774, -778, -782, -786, -790,
+ -794, -798, -802, -806, -810, -814, -817, -821,
+ -825, -829, -832, -836, -840, -843, -847, -850,
+ -854, -857, -861, -864, -867, -871, -874, -877,
+ -880, -884, -887, -890, -893, -896, -899, -902,
+ -905, -908, -911, -914, -917, -919, -922, -925,
+ -928, -930, -933, -935, -938, -940, -943, -945,
+ -948, -950, -952, -955, -957, -959, -961, -964,
+ -966, -968, -970, -972, -974, -976, -978, -979,
+ -981, -983, -985, -987, -988, -990, -991, -993,
+ -995, -996, -998, -999, -1000, -1002, -1003, -1004,
+ -1005, -1007, -1008, -1009, -1010, -1011, -1012, -1013,
+ -1014, -1015, -1016, -1016, -1017, -1018, -1019, -1019,
+ -1020, -1020, -1021, -1021, -1022, -1022, -1023, -1023,
+ -1023, -1024, -1024, -1024, -1024, -1024, -1024, -1024,
+ -1024, -1024, -1024, -1024, -1024, -1024, -1024, -1023,
+ -1023, -1023, -1022, -1022, -1021, -1021, -1020, -1020,
+ -1019, -1019, -1018, -1017, -1016, -1016, -1015, -1014,
+ -1013, -1012, -1011, -1010, -1009, -1008, -1007, -1005,
+ -1004, -1003, -1002, -1000, -999, -998, -996, -995,
+ -993, -991, -990, -988, -987, -985, -983, -981,
+ -979, -978, -976, -974, -972, -970, -968, -966,
+ -964, -961, -959, -957, -955, -952, -950, -948,
+ -945, -943, -940, -938, -935, -933, -930, -928,
+ -925, -922, -919, -917, -914, -911, -908, -905,
+ -902, -899, -896, -893, -890, -887, -884, -880,
+ -877, -874, -871, -867, -864, -861, -857, -854,
+ -850, -847, -843, -840, -836, -832, -829, -825,
+ -821, -817, -814, -810, -806, -802, -798, -794,
+ -790, -786, -782, -778, -774, -770, -766, -761,
+ -757, -753, -749, -744, -740, -736, -731, -727,
+ -722, -718, -713, -709, -704, -700, -695, -691,
+ -686, -681, -676, -672, -667, -662, -657, -653,
+ -648, -643, -638, -633, -628, -623, -618, -613,
+ -608, -603, -598, -593, -588, -582, -577, -572,
+ -567, -562, -556, -551, -546, -540, -535, -530,
+ -524, -519, -513, -508, -502, -497, -492, -486,
+ -480, -475, -469, -464, -458, -452, -447, -441,
+ -435, -430, -424, -418, -413, -407, -401, -395,
+ -389, -384, -378, -372, -366, -360, -354, -348,
+ -343, -337, -331, -325, -319, -313, -307, -301,
+ -295, -289, -283, -277, -271, -265, -258, -252,
+ -246, -240, -234, -228, -222, -216, -210, -203,
+ -197, -191, -185, -179, -172, -166, -160, -154,
+ -148, -141, -135, -129, -123, -116, -110, -104,
+ -98, -91, -85, -79, -73, -66, -60, -54,
+ -48, -41, -35, -29, -22, -16, -10, -4
+};
+
+// this is the program the generate the above table
+#if 0
+#include <stdio.h>
+#include <math.h>
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+#define PFREAL_ONE 1024
+#define IANGLE_MAX 1024
+
+int main(int, char**)
+{
+ FILE*f = fopen("table.c","wt");
+ fprintf(f,"PFreal sinTable[] = {\n");
+ for(int i = 0; i < 128; ++i)
+ {
+ for(int j = 0; j < 8; ++j)
+ {
+ int iang = j+i*8;
+ double ii = (double)iang + 0.5;
+ double angle = ii * 2 * M_PI / IANGLE_MAX;
+ double sinAngle = sin(angle);
+ fprintf(f,"%6d, ", (int)(floor(PFREAL_ONE*sinAngle)));
+ }
+ fprintf(f,"\n");
+ }
+ fprintf(f,"};\n");
+ fclose(f);
+
+ return 0;
+}
+#endif
+
+inline PFreal fsin(int iangle)
+{
+ while(iangle < 0)
+ iangle += IANGLE_MAX;
+ return sinTable[iangle & IANGLE_MASK];
+}
+
+inline PFreal fcos(int iangle)
+{
+ // quarter phase shift
+ return fsin(iangle + (IANGLE_MAX >> 2));
+}
+
+namespace FQTerm {
+
+struct SlideInfo
+{
+ int slideIndex;
+ int angle;
+ PFreal cx;
+ PFreal cy;
+};
+
+class PictureFlowPrivate
+{
+public:
+ PictureFlowPrivate(PictureFlow* widget);
+
+ int slideCount() const;
+ void setSlideCount(int count);
+
+ QSize slideSize() const;
+ void setSlideSize(QSize size);
+
+ int zoomFactor() const;
+ void setZoomFactor(int z);
+
+ QImage slide(int index) const;
+ void setSlide(int index, const QImage& image);
+
+ int currentSlide() const;
+ void setCurrentSlide(int index);
+
+ void showPrevious();
+ void showNext();
+ void showSlide(int index);
+
+ void resize(int w, int h);
+
+ void render();
+ void startAnimation();
+ void updateAnimation();
+
+ QImage buffer;
+ QBasicTimer animateTimer;
+ QVector<QRect> slidesRect;
+
+private:
+ PictureFlow* widget;
+
+ int slideWidth;
+ int slideHeight;
+ int zoom;
+
+ QVector<QImage> slideImages;
+ int centerIndex;
+
+ SlideInfo centerSlide;
+ QVector<SlideInfo> leftSlides;
+ QVector<SlideInfo> rightSlides;
+
+ QVector<PFreal> rays;
+ int itilt;
+ int spacing;
+ PFreal offsetX;
+ PFreal offsetY;
+
+ QImage blankSurface;
+ QCache<int, QImage> surfaceCache;
+ QTimer triggerTimer;
+
+ int slideFrame;
+ int step;
+ int target;
+ int fade;
+
+ void recalc(int w, int h);
+ QRect renderSlide(const SlideInfo &slide, int alpha=256, int col1=-1, int col=-1);
+ QImage* surface(int slideIndex);
+ void triggerRender();
+ void resetSlides();
+};
+
+PictureFlowPrivate::PictureFlowPrivate(PictureFlow* w)
+{
+ widget = w;
+
+ slideWidth = 200;
+ slideHeight = 200;
+ zoom = 100;
+
+ centerIndex = 0;
+
+ slideFrame = 0;
+ step = 0;
+ target = 0;
+ fade = 256;
+
+ triggerTimer.setSingleShot(true);
+ triggerTimer.setInterval(0);
+ QObject::connect(&triggerTimer, SIGNAL(timeout()), widget, SLOT(render()));
+
+ recalc(200, 200);
+ resetSlides();
+}
+
+int PictureFlowPrivate::slideCount() const
+{
+ return slideImages.count();
+}
+
+void PictureFlowPrivate::setSlideCount(int count)
+{
+ slideImages.resize(count);
+ surfaceCache.clear();
+ resetSlides();
+ triggerRender();
+}
+
+QSize PictureFlowPrivate::slideSize() const
+{
+ return QSize(slideWidth, slideHeight);
+}
+
+void PictureFlowPrivate::setSlideSize(QSize size)
+{
+ slideWidth = size.width();
+ slideHeight = size.height();
+ recalc(buffer.width(), buffer.height());
+ triggerRender();
+}
+
+int PictureFlowPrivate::zoomFactor() const
+{
+ return zoom;
+}
+
+void PictureFlowPrivate::setZoomFactor(int z)
+{
+ if(z <= 0)
+ return;
+
+ zoom = z;
+ recalc(buffer.width(), buffer.height());
+ triggerRender();
+}
+
+QImage PictureFlowPrivate::slide(int index) const
+{
+ return slideImages[index];
+}
+
+void PictureFlowPrivate::setSlide(int index, const QImage& image)
+{
+ if((index >= 0) && (index < slideImages.count()))
+ {
+ slideImages[index] = image;
+ surfaceCache.remove(index);
+ triggerRender();
+ }
+}
+
+int PictureFlowPrivate::currentSlide() const
+{
+ return centerIndex;
+}
+
+void PictureFlowPrivate::setCurrentSlide(int index)
+{
+ step = 0;
+ centerIndex = qBound(index, 0, slideImages.count()-1);
+ target = centerIndex;
+ slideFrame = index << 16;
+ resetSlides();
+ triggerRender();
+}
+
+void PictureFlowPrivate::showPrevious()
+{
+ if(step >= 0)
+ {
+ if(centerIndex > 0)
+ {
+ target = centerIndex - 1;
+ startAnimation();
+ }
+ }
+ else
+ {
+ target = qMax(0, centerIndex - 2);
+ }
+}
+
+void PictureFlowPrivate::showNext()
+{
+ if(step <= 0)
+ {
+ if(centerIndex < slideImages.count()-1)
+ {
+ target = centerIndex + 1;
+ startAnimation();
+ }
+ }
+ else
+ {
+ target = qMin(centerIndex + 2, slideImages.count()-1);
+ }
+}
+
+void PictureFlowPrivate::showSlide(int index)
+{
+ index = qMax(index, 0);
+ index = qMin(slideImages.count()-1, index);
+
+ target = index;
+ startAnimation();
+}
+
+void PictureFlowPrivate::resize(int w, int h)
+{
+ recalc(w, h);
+ resetSlides();
+ triggerRender();
+}
+
+
+// adjust slides so that they are in "steady state" position
+void PictureFlowPrivate::resetSlides()
+{
+ centerSlide.angle = 0;
+ centerSlide.cx = 0;
+ centerSlide.cy = 0;
+ centerSlide.slideIndex = centerIndex;
+
+ leftSlides.clear();
+ leftSlides.resize(6);
+ for(int i = 0; i < leftSlides.count(); ++i)
+ {
+ SlideInfo& si = leftSlides[i];
+ si.angle = itilt;
+ si.cx = -(offsetX + spacing*i*PFREAL_ONE);
+ si.cy = offsetY;
+ si.slideIndex = centerIndex-1-i;
+ }
+
+ rightSlides.clear();
+ rightSlides.resize(6);
+ for(int i = 0; i < rightSlides.count(); ++i)
+ {
+ SlideInfo& si = rightSlides[i];
+ si.angle = -itilt;
+ si.cx = offsetX + spacing*i*PFREAL_ONE;
+ si.cy = offsetY;
+ si.slideIndex = centerIndex+1+i;
+ }
+}
+
+#define BILINEAR_STRETCH_HOR 4
+#define BILINEAR_STRETCH_VER 4
+
+static QImage scaleImage (QImage image, int w, int h) {
+ // I don't have to scale it
+ if (w == image.width() && h == image.height())
+ return(image);
+
+ QImage output(w, h, QImage::Format_ARGB32_Premultiplied);
+ output.fill(0);
+
+ double scaleHeight = w / (double) image.height();
+ double scaleWidth = h / (double) image.width();
+
+ double s = qMin(scaleWidth, scaleHeight);
+
+ int sw = (int) qRound(s * image.width());
+ int sh = (int) qRound(s * image.height());
+
+ // Scale Image
+ QImage imgScaled = image.scaled(sw, sh, Qt::KeepAspectRatio, Qt::SmoothTransformation);
+
+ int _x = (w - sw) / 2;
+ int _y = (h - sh);
+
+ for (int x = 0; x < imgScaled.width(); ++x) {
+ for (int y = 0; y < imgScaled.height(); ++y) {
+ output.setPixel(x + _x, y + _y, imgScaled.pixel(x, y));
+ }
+ }
+
+ return(output);
+}
+
+static QImage prepareSurface(QImage img, int w, int h)
+{
+ img = scaleImage(img, w, h);
+
+ // slightly larger, to accomodate for the reflection
+ int hs = h * 2;
+ int hofs = h / 3;
+
+ // offscreen buffer: black is sweet
+ QImage result(hs, w, QImage::Format_RGB32);
+ result.fill(0);
+
+ // transpose the image, this is to speed-up the rendering
+ // because we process one column at a time
+ // (and much better and faster to work row-wise, i.e in one scanline)
+ for(int x = 0; x < w; x++)
+ for(int y = 0; y < h; y++)
+ result.setPixel(hofs + y, x, img.pixel(x, y));
+
+ // create the reflection
+ int ht = hs - h - hofs;
+ int hte = ht;
+ for(int x = 0; x < w; x++)
+ for(int y = 0; y < ht; y++)
+ {
+ QRgb color = img.pixel(x, img.height()-y-1);
+ int a = qAlpha(color);
+ int r = qRed(color) * a / 256 * (hte - y) / hte * 3/5;
+ int g = qGreen(color) * a / 256 * (hte - y) / hte * 3/5;
+ int b = qBlue(color) * a / 256 * (hte - y) / hte * 3/5;
+ result.setPixel(h+hofs+y, x, qRgb(r, g, b));
+ }
+
+#ifdef PICTUREFLOW_BILINEAR_FILTER
+ int hh = BILINEAR_STRETCH_VER*hs;
+ int ww = BILINEAR_STRETCH_HOR*w;
+ result = scaleImage(result, hh, ww);
+#endif
+
+ return result;
+}
+
+
+// get transformed image for specified slide
+// if it does not exist, create it and place it in the cache
+QImage* PictureFlowPrivate::surface(int slideIndex)
+{
+ if(slideIndex < 0)
+ return 0;
+ if(slideIndex >= slideImages.count())
+ return 0;
+
+ if(surfaceCache.contains(slideIndex))
+ return surfaceCache[slideIndex];
+
+ QImage img = widget->slide(slideIndex);
+ if(img.isNull())
+ {
+ if(blankSurface.isNull())
+ {
+ blankSurface = QImage(slideWidth, slideHeight, QImage::Format_RGB32);
+
+ QPainter painter(&blankSurface);
+ QPoint p1(slideWidth*4/10, 0);
+ QPoint p2(slideWidth*6/10, slideHeight);
+ QLinearGradient linearGrad(p1, p2);
+ linearGrad.setColorAt(0, Qt::black);
+ linearGrad.setColorAt(1, Qt::white);
+ painter.setBrush(linearGrad);
+ painter.fillRect(0, 0, slideWidth, slideHeight, QBrush(linearGrad));
+
+ painter.setPen(QPen(QColor(64,64,64), 4));
+ painter.setBrush(QBrush());
+ painter.drawRect(2, 2, slideWidth-3, slideHeight-3);
+ painter.end();
+ blankSurface = prepareSurface(blankSurface, slideWidth, slideHeight);
+ }
+ return &blankSurface;
+ }
+
+ surfaceCache.insert(slideIndex, new QImage(prepareSurface(img, slideWidth, slideHeight)));
+ return surfaceCache[slideIndex];
+}
+
+
+// Schedules rendering the slides. Call this function to avoid immediate
+// render and thus cause less flicker.
+void PictureFlowPrivate::triggerRender()
+{
+ triggerTimer.start();
+}
+
+// Render the slides. Updates only the offscreen buffer.
+void PictureFlowPrivate::render()
+{
+ buffer.fill(Qt::black);
+
+ int nleft = leftSlides.count();
+ int nright = rightSlides.count();
+
+ QRect r = renderSlide(centerSlide);
+ int c1 = r.left();
+ int c2 = r.right();
+
+ if(step == 0)
+ {
+ slidesRect.clear();
+ slidesRect.resize(11);
+
+ // no animation, boring plain rendering
+ for(int index = 0; index < nleft-1; index++)
+ {
+ int alpha = (index < nleft-2) ? 256 : 128;
+ QRect rs = renderSlide(leftSlides[index], alpha, 0, c1-1);
+ slidesRect[index] = rs;
+ if(!rs.isEmpty())
+ c1 = rs.left();
+ }
+ slidesRect[5] = r;
+ for(int index = 0; index < nright-1; index++)
+ {
+ int alpha = (index < nright-2) ? 256 : 128;
+ QRect rs = renderSlide(rightSlides[index], alpha, c2+1, buffer.width());
+ slidesRect[6 + index] = rs;
+ if(!rs.isEmpty())
+ c2 = rs.right();
+ }
+ }
+ else
+ {
+ // the first and last slide must fade in/fade out
+ for(int index = 0; index < nleft; index++)
+ {
+ int alpha = 256;
+ if(index == nleft-1)
+ alpha = (step > 0) ? 0 : 128-fade/2;
+ if(index == nleft-2)
+ alpha = (step > 0) ? 128-fade/2 : 256-fade/2;
+ if(index == nleft-3)
+ alpha = (step > 0) ? 256-fade/2 : 256;
+ QRect rs = renderSlide(leftSlides[index], alpha, 0, c1-1);
+ if(!rs.isEmpty())
+ c1 = rs.left();
+ }
+ for(int index = 0; index < nright; index++)
+ {
+ int alpha = (index < nright-2) ? 256 : 128;
+ if(index == nright-1)
+ alpha = (step > 0) ? fade/2 : 0;
+ if(index == nright-2)
+ alpha = (step > 0) ? 128+fade/2 : fade/2;
+ if(index == nright-3)
+ alpha = (step > 0) ? 256 : 128+fade/2;
+ QRect rs = renderSlide(rightSlides[index], alpha, c2+1, buffer.width());
+ if(!rs.isEmpty())
+ c2 = rs.right();
+ }
+ }
+}
+
+// Renders a slide to offscreen buffer. Returns a rect of the rendered area.
+// alpha=256 means normal, alpha=0 is fully black, alpha=128 half transparent
+// col1 and col2 limit the column for rendering.
+QRect PictureFlowPrivate::renderSlide(const SlideInfo &slide, int alpha,
+int col1, int col2)
+{
+ QImage* src = surface(slide.slideIndex);
+ if(!src)
+ return QRect();
+
+ QRect rect(0, 0, 0, 0);
+
+#ifdef PICTUREFLOW_BILINEAR_FILTER
+ int sw = src->height() / BILINEAR_STRETCH_HOR;
+ int sh = src->width() / BILINEAR_STRETCH_VER;
+#else
+ int sw = src->height();
+ int sh = src->width();
+#endif
+ int h = buffer.height();
+ int w = buffer.width();
+
+ if(col1 > col2)
+ {
+ int c = col2;
+ col2 = col1;
+ col1 = c;
+ }
+
+ col1 = (col1 >= 0) ? col1 : 0;
+ col2 = (col2 >= 0) ? col2 : w-1;
+ col1 = qMin(col1, w-1);
+ col2 = qMin(col2, w-1);
+
+ int distance = h * 100 / zoom;
+ PFreal sdx = fcos(slide.angle);
+ PFreal sdy = fsin(slide.angle);
+ PFreal xs = slide.cx - slideWidth * sdx/2;
+ PFreal ys = slide.cy - slideWidth * sdy/2;
+ PFreal dist = distance * PFREAL_ONE;
+
+ int xi = qMax((PFreal)0, ((w*PFREAL_ONE/2) + fdiv(xs*h, dist+ys)) >> PFREAL_SHIFT);
+ if(xi >= w)
+ return rect;
+
+ bool flag = false;
+ rect.setLeft(xi);
+
+ for(int x = qMax(xi, col1); x <= col2; x++)
+ {
+ PFreal hity = 0;
+ PFreal fk = rays[x];
+ if(sdy)
+ {
+ fk = fk - fdiv(sdx,sdy);
+ hity = -fdiv((rays[x]*distance - slide.cx + slide.cy*sdx/sdy), fk);
+ }
+
+ dist = distance*PFREAL_ONE + hity;
+ if(dist < 0)
+ continue;
+
+ PFreal hitx = fmul(dist, rays[x]);
+ PFreal hitdist = fdiv(hitx - slide.cx, sdx);
+
+#ifdef PICTUREFLOW_BILINEAR_FILTER
+ int column = sw*BILINEAR_STRETCH_HOR/2 + (hitdist*BILINEAR_STRETCH_HOR >> PFREAL_SHIFT);
+ if(column >= sw*BILINEAR_STRETCH_HOR)
+ break;
+#else
+ int column = sw/2 + (hitdist >> PFREAL_SHIFT);
+ if(column >= sw)
+ break;
+#endif
+ if(column < 0)
+ continue;
+
+ rect.setRight(x);
+ if(!flag)
+ rect.setLeft(x);
+ flag = true;
+
+ int y1 = h/2;
+ int y2 = y1+ 1;
+ QRgb* pixel1 = (QRgb*)(buffer.scanLine(y1)) + x;
+ QRgb* pixel2 = (QRgb*)(buffer.scanLine(y2)) + x;
+ QRgb pixelstep = pixel2 - pixel1;
+
+#ifdef PICTUREFLOW_BILINEAR_FILTER
+ int center = (sh*BILINEAR_STRETCH_VER/2);
+ int dy = dist*BILINEAR_STRETCH_VER / h;
+#else
+ int center = (sh/2);
+ int dy = dist / h;
+#endif
+ int p1 = center*PFREAL_ONE - dy/2;
+ int p2 = center*PFREAL_ONE + dy/2;
+
+ const QRgb *ptr = (const QRgb*)(src->scanLine(column));
+ if(alpha == 256)
+ while((y1 >= 0) && (y2 < h) && (p1 >= 0))
+ {
+ *pixel1 = ptr[p1 >> PFREAL_SHIFT];
+ *pixel2 = ptr[p2 >> PFREAL_SHIFT];
+ p1 -= dy;
+ p2 += dy;
+ y1--;
+ y2++;
+ pixel1 -= pixelstep;
+ pixel2 += pixelstep;
+ }
+ else
+ while((y1 >= 0) && (y2 < h) && (p1 >= 0))
+ {
+ QRgb c1 = ptr[p1 >> PFREAL_SHIFT];
+ QRgb c2 = ptr[p2 >> PFREAL_SHIFT];
+
+ int r1 = qRed(c1) * alpha/256;
+ int g1 = qGreen(c1) * alpha/256;
+ int b1 = qBlue(c1) * alpha/256;
+ int r2 = qRed(c2) * alpha/256;
+ int g2 = qGreen(c2) * alpha/256;
+ int b2 = qBlue(c2) * alpha/256;
+
+ *pixel1 = qRgb(r1, g1, b1);
+ *pixel2 = qRgb(r2, g2, b2);
+ p1 -= dy;
+ p2 += dy;
+ y1--;
+ y2++;
+ pixel1 -= pixelstep;
+ pixel2 += pixelstep;
+ }
+ }
+
+ rect.setTop(0);
+ rect.setBottom(h-1);
+ return rect;
+}
+
+// Updates look-up table and other stuff necessary for the rendering.
+// Call this when the viewport size or slide dimension is changed.
+void PictureFlowPrivate::recalc(int ww, int wh)
+{
+ int w = (ww+1)/2;
+ int h = (wh+1)/2;
+ buffer = QImage(ww, wh, QImage::Format_RGB32);
+ buffer.fill(0);
+
+ rays.resize(w*2);
+
+ for(int i = 0; i < w; ++i)
+ {
+ PFreal gg = (PFREAL_HALF + i * PFREAL_ONE) / (2*h);
+ rays[w-i-1] = -gg;
+ rays[w+i] = gg;
+ }
+
+
+ itilt = 70 * IANGLE_MAX / 360; // approx. 70 degrees tilted
+
+ offsetX = slideWidth/2 * (PFREAL_ONE-fcos(itilt));
+ offsetY = slideWidth/2 * fsin(itilt);
+ offsetX += slideWidth * PFREAL_ONE;
+ offsetY += slideWidth * PFREAL_ONE / 4;
+ spacing = 40;
+
+ surfaceCache.clear();
+ blankSurface = QImage();
+}
+
+void PictureFlowPrivate::startAnimation()
+{
+#if 1
+ if(!animateTimer.isActive())
+ {
+ step = (target < centerSlide.slideIndex) ? -1 : 1;
+ animateTimer.start(30, widget);
+ }
+#else
+ if (animateTimer.isActive())
+ animateTimer.stop();
+
+ step = (target < centerSlide.slideIndex) ? -1 : 1;
+ animateTimer.start(30, widget);
+#endif
+}
+
+// Updates the animation effect. Call this periodically from a timer.
+void PictureFlowPrivate::updateAnimation()
+{
+ if(!animateTimer.isActive())
+ return;
+ if(step == 0)
+ return;
+
+ int speed = 16384;
+
+ // deaccelerate when approaching the target
+ if(true)
+ {
+ const int max = 2 * 65536;
+
+ int fi = slideFrame;
+ fi -= (target << 16);
+ if(fi < 0)
+ fi = -fi;
+ fi = qMin(fi, max);
+
+ int ia = IANGLE_MAX * (fi-max/2) / (max*2);
+ speed = 512 + 16384 * (PFREAL_ONE+fsin(ia))/PFREAL_ONE;
+ }
+
+ slideFrame += speed*step;
+
+ int index = slideFrame >> 16;
+ int pos = slideFrame & 0xffff;
+ int neg = 65536 - pos;
+ int tick = (step < 0) ? neg : pos;
+ PFreal ftick = (tick * PFREAL_ONE) >> 16;
+
+ // the leftmost and rightmost slide must fade away
+ fade = pos / 256;
+
+ if(step < 0)
+ index++;
+ if(centerIndex != index)
+ {
+ centerIndex = index;
+ slideFrame = index << 16;
+ centerSlide.slideIndex = centerIndex;
+ for(int i = 0; i < leftSlides.count(); ++i)
+ leftSlides[i].slideIndex = centerIndex-1-i;
+ for(int i = 0; i < rightSlides.count(); ++i)
+ rightSlides[i].slideIndex = centerIndex+1+i;
+ }
+
+ centerSlide.angle = (step * tick * itilt) >> 16;
+ centerSlide.cx = -step * fmul(offsetX, ftick);
+ centerSlide.cy = fmul(offsetY, ftick);
+
+ if(centerIndex == target)
+ {
+ resetSlides();
+ animateTimer.stop();
+ triggerRender();
+ step = 0;
+ fade = 256;
+ return;
+ }
+
+ for(int i = 0; i < leftSlides.count(); ++i)
+ {
+ SlideInfo& si = leftSlides[i];
+ si.angle = itilt;
+ si.cx = -(offsetX + spacing*i*PFREAL_ONE + step*spacing*ftick);
+ si.cy = offsetY;
+ }
+
+ for(int i = 0; i < rightSlides.count(); ++i)
+ {
+ SlideInfo& si = rightSlides[i];
+ si.angle = -itilt;
+ si.cx = offsetX + spacing*i*PFREAL_ONE - step*spacing*ftick;
+ si.cy = offsetY;
+ }
+
+ if(step > 0)
+ {
+ PFreal ftick = (neg * PFREAL_ONE) >> 16;
+ rightSlides[0].angle = -(neg * itilt) >> 16;
+ rightSlides[0].cx = fmul(offsetX, ftick);
+ rightSlides[0].cy = fmul(offsetY, ftick);
+ }
+ else
+ {
+ PFreal ftick = (pos * PFREAL_ONE) >> 16;
+ leftSlides[0].angle = (pos * itilt) >> 16;
+ leftSlides[0].cx = -fmul(offsetX, ftick);
+ leftSlides[0].cy = fmul(offsetY, ftick);
+ }
+
+ // must change direction ?
+ if(target < index) if(step > 0)
+ step = -1;
+ if(target > index) if(step < 0)
+ step = 1;
+
+ triggerRender();
+}
+
+// -----------------------------------------
+
+PictureFlow::PictureFlow(QWidget* parent): QWidget(parent)
+{
+ d = new PictureFlowPrivate(this);
+
+ setAttribute(Qt::WA_StaticContents, true);
+ setAttribute(Qt::WA_OpaquePaintEvent, true);
+ setAttribute(Qt::WA_NoSystemBackground, true);
+}
+
+PictureFlow::~PictureFlow()
+{
+ delete d;
+}
+
+int PictureFlow::slideCount() const
+{
+ return d->slideCount();
+}
+
+void PictureFlow::setSlideCount(int count)
+{
+ d->setSlideCount(count);
+}
+
+QSize PictureFlow::slideSize() const
+{
+ return d->slideSize();
+}
+
+void PictureFlow::setSlideSize(QSize size)
+{
+ d->setSlideSize(size);
+}
+
+int PictureFlow::zoomFactor() const
+{
+ return d->zoomFactor();
+}
+
+void PictureFlow::setZoomFactor(int z)
+{
+ d->setZoomFactor(z);
+}
+
+QImage PictureFlow::slide(int index) const
+{
+ return d->slide(index);
+}
+
+void PictureFlow::setSlide(int index, const QImage& image)
+{
+ d->setSlide(index, image);
+}
+
+void PictureFlow::setSlide(int index, const QPixmap& pixmap)
+{
+ d->setSlide(index, pixmap.toImage());
+}
+
+int PictureFlow::currentSlide() const
+{
+ return d->currentSlide();
+}
+
+void PictureFlow::setCurrentSlide(int index)
+{
+ d->setCurrentSlide(index);
+}
+
+void PictureFlow::clear()
+{
+ d->setSlideCount(0);
+}
+
+void PictureFlow::render()
+{
+ d->render();
+ update();
+}
+
+void PictureFlow::showPrevious()
+{
+ d->showPrevious();
+}
+
+void PictureFlow::showNext()
+{
+ d->showNext();
+}
+
+void PictureFlow::showSlide(int index)
+{
+ d->showSlide(index);
+}
+
+void PictureFlow::showSlideAt (int x, int y) {
+ if (d->slidesRect[5].contains(x, y))
+ return;
+
+ if (x < d->slidesRect[5].x()) {
+ for (int i=0; i < 5; ++i) {
+ if (d->slidesRect[i].contains(x, y)) {
+ showSlide(d->currentSlide() - (i + 1));
+ return;
+ }
+ }
+ } else {
+ for (int i=6; i < 11; ++i) {
+ if (d->slidesRect[i].contains(x, y)) {
+ showSlide(d->currentSlide() + (i - 5));
+ return;
+ }
+ }
+ }
+}
+
+void PictureFlow::keyPressEvent(QKeyEvent* event)
+{
+
+ switch (event->key()) {
+ case Qt::Key_Left:
+ if(event->modifiers() == Qt::AltModifier) {
+ showSlide(currentSlide() - 5);
+ } else {
+ showPrevious();
+ }
+ event->accept();
+ return;
+ case Qt::Key_Right:
+ if(event->modifiers() == Qt::AltModifier) {
+ showSlide(currentSlide() + 5);
+ } else {
+ showNext();
+ }
+ event->accept();
+ return;
+ case Qt::Key_Up:
+ showSlide(currentSlide() + 10);
+ event->accept();
+ return;
+ case Qt::Key_Down:
+ showSlide(currentSlide() - 10);
+ event->accept();
+ return;
+ default:
+ break;
+ }
+
+ event->ignore();
+}
+
+void PictureFlow::mouseReleaseEvent (QMouseEvent *event) {
+ showSlideAt(event->x(), event->y());
+}
+
+void PictureFlow::mouseMoveEvent (QMouseEvent *event) {
+ showSlideAt(event->x(), event->y());
+}
+
+void PictureFlow::wheelEvent (QWheelEvent *event) {
+ int numDegrees = event->delta() / 8;
+ int numSteps = numDegrees / 15;
+
+ d->showSlide(d->currentSlide() + numSteps);
+ event->accept();
+}
+
+void PictureFlow::paintEvent(QPaintEvent* event)
+{
+ Q_UNUSED(event);
+ QPainter painter(this);
+ painter.setRenderHint(QPainter::Antialiasing, false);
+ painter.drawImage(QPoint(0,0), d->buffer);
+}
+
+void PictureFlow::resizeEvent(QResizeEvent* event)
+{
+ d->resize(width(), height());
+ QWidget::resizeEvent(event);
+}
+
+void PictureFlow::timerEvent(QTimerEvent* event)
+{
+ if(event->timerId() == d->animateTimer.timerId())
+ d->updateAnimation();
+ else
+ QWidget::timerEvent(event);
+}
+
+} // namespace FQTerm
+
+#include "pictureflow.moc"
diff --git a/src/ui/imageviewer/pictureflow.h b/src/ui/imageviewer/pictureflow.h
new file mode 100644
index 0000000..c9cf5aa
--- /dev/null
+++ b/src/ui/imageviewer/pictureflow.h
@@ -0,0 +1,198 @@
+
+/***************************************************************************
+ * 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. *
+ ***************************************************************************/
+
+/*
+ PictureFlow - animated image show widget
+ http://pictureflow.googlecode.com
+
+ Copyright (C) 2007 Ariya Hidayat (ariya@kde.org)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+*/
+
+#ifndef PICTUREFLOW_H
+#define PICTUREFLOW_H
+
+#include <QWidget>
+
+namespace FQTerm {
+
+class PictureFlowPrivate;
+
+/*!
+ Class PictureFlow implements an image show widget with animation effect
+ like Apple's CoverFlow (in iTunes and iPod). Images are arranged in form
+ of slides, one main slide is shown at the center with few slides on
+ the left and right sides of the center slide. When the next or previous
+ slide is brought to the front, the whole slides flow to the right or
+ the right with smooth animation effect; until the new slide is finally
+ placed at the center.
+
+ */
+class PictureFlow : public QWidget
+{
+Q_OBJECT
+
+ Q_PROPERTY(int slideCount READ slideCount WRITE setSlideCount)
+ Q_PROPERTY(int currentSlide READ currentSlide WRITE setCurrentSlide)
+ Q_PROPERTY(QSize slideSize READ slideSize WRITE setSlideSize)
+ Q_PROPERTY(int zoomFactor READ zoomFactor WRITE setZoomFactor)
+
+public:
+ /*!
+ Creates a new PictureFlow widget.
+ */
+ PictureFlow(QWidget* parent = 0);
+
+ /*!
+ Destroys the widget.
+ */
+ ~PictureFlow();
+
+ /*!
+ Returns the total number of slides.
+ */
+ int slideCount() const;
+
+ /*!
+ Sets the total number of slides.
+ */
+ void setSlideCount(int count);
+
+ /*!
+ Returns the dimension of each slide (in pixels).
+ */
+ QSize slideSize() const;
+
+ /*!
+ Sets the dimension of each slide (in pixels).
+ */
+ void setSlideSize(QSize size);
+
+ /*!
+ Sets the zoom factor (in percent).
+ */
+ void setZoomFactor(int zoom);
+
+ /*!
+ Returns the zoom factor (in percent).
+ */
+ int zoomFactor() const;
+
+ /*!
+ Returns QImage of specified slide.
+ This function will be called only whenever necessary, e.g. the 100th slide
+ will not be retrived when only the first few slides are visible.
+ */
+ virtual QImage slide(int index) const;
+
+ /*!
+ Sets an image for specified slide. If the slide already exists,
+ it will be replaced.
+ */
+ virtual void setSlide(int index, const QImage& image);
+
+ /*!
+ Sets a pixmap for specified slide. If the slide already exists,
+ it will be replaced.
+ */
+ virtual void setSlide(int index, const QPixmap& pixmap);
+
+ /*!
+ Returns the index of slide currently shown in the middle of the viewport.
+ */
+ int currentSlide() const;
+
+signals:
+ void slideSelected (int index);
+
+public slots:
+
+ /*!
+ Sets slide to be shown in the middle of the viewport. No animation
+ effect will be produced, unlike using showSlide.
+ */
+ void setCurrentSlide(int index);
+
+ /*!
+ Clears images of all slides.
+ */
+ void clear();
+
+ /*!
+ Rerender the widget. Normally this function will be automatically invoked
+ whenever necessary, e.g. during the transition animation.
+ */
+ void render();
+
+ /*!
+ Shows previous slide using animation effect.
+ */
+ void showPrevious();
+
+ /*!
+ Shows next slide using animation effect.
+ */
+ void showNext();
+
+ /*!
+ Go to specified slide using animation effect.
+ */
+ void showSlide(int index);
+
+
+ /*!
+ Go to slide at specified position using animation effect.
+ */
+ void showSlideAt(int x, int y);
+
+protected:
+ void mouseReleaseEvent (QMouseEvent *event);
+ void mouseMoveEvent (QMouseEvent *event);
+ void wheelEvent (QWheelEvent *event);
+ void paintEvent(QPaintEvent *event);
+ void keyPressEvent(QKeyEvent *event);
+ void resizeEvent(QResizeEvent *event);
+ void timerEvent(QTimerEvent *event);
+
+private:
+ PictureFlowPrivate* d;
+};
+
+} // namespace FQTerm
+
+#endif // PICTUREFLOW_H