1
/***************************************************************************
2
* fqterm, a terminal emulator for both BBS and *nix. *
3
* Copyright (C) 2008 fqterm development group. *
5
* This program is free software; you can redistribute it and/or modify *
6
* it under the terms of the GNU General Public License as published by *
7
* the Free Software Foundation; either version 2 of the License, or *
8
* (at your option) any later version. *
10
* This program is distributed in the hope that it will be useful, *
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13
* GNU General Public License for more details. *
15
* You should have received a copy of the GNU General Public License *
16
* along with this program; if not, write to the *
17
* Free Software Foundation, Inc., *
18
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. *
19
***************************************************************************/
26
#include <QDataStream>
33
#include <QMessageBox>
34
#include <QMouseEvent>
39
#include <QPushButton>
49
#include "fqterm_canvas.h"
50
#include "fqterm_config.h"
51
#include "fqterm_exif_extractor.h"
52
#include "fqterm_filedialog.h"
53
#include "fqterm_path.h"
54
#include "fqterm_trace.h"
55
#include "imageviewer.h"
116
static const char *fqloraTagStatus[] = {
124
static const char *fqloraGeneralStatus[] = {
129
"item in the trash.",
130
"items in the trash.",
138
"Nothing to recover.",
147
static const int toolBarFixedHeight = 40;
148
static const int statusBarFixedHeight = 18;
149
static const QSize sortBoxFixedSize(110, 25);
151
static const QString &iconPath(const QString &fileName) {
153
static QString p = "";
155
if (!fileName.isEmpty()) {
156
p = getPath(RESOURCE) + ICON_SOURCE + fileName;
162
const QString &isPlural(const int num, const int status) {
164
static QString m = "";
167
return (m = fqloraGeneralStatus[status]);
168
} else if (num > 1 && status > GEN_UNKNOWN && status < GEN_DELETES) {
169
return (m = fqloraGeneralStatus[status + 1]);
172
return (m = fqloraGeneralStatus[GEN_UNKNOWN]);
175
// get user's desktop rectangle
176
static const QRect &desktopSize() {
179
QDesktopWidget myDesktop;
180
return ((size = myDesktop.screenGeometry(myDesktop.primaryScreen())));
184
FQTermImageFlow::~FQTermImageFlow() {
195
FQTermImageFlow::FQTermImageFlow(FQTermConfig * config, QWidget *parent,
196
Qt::WindowFlags wflag) :
197
FQTermImage(parent, wflag),
198
imageFlow_(new ImageFlow(this)),
199
imageMenu_(new ImageMenu(this)),
200
statusBar_(new QStatusBar(this)),
203
setWindowTitle(IMAGE_BROWSER_NAME);
204
setAutoFillBackground(true);
205
setBackgroundRole(QPalette::Midlight);
206
setFixedSize(desktopSize().width() / 1.3, desktopSize().height() / 1.9);
208
FQ_VERIFY(connect(this, SIGNAL(isTrashEmpty()), this, SLOT(checkTrashState())));
211
FQ_VERIFY(connect(imageFlow_, SIGNAL(emblemStatus(const int)), imageMenu_, SLOT(updateEmblems(const int))));
212
FQ_VERIFY(connect(imageMenu_, SIGNAL(toggleFlowStatus(const int)), imageFlow_, SLOT(toggleStatus(const int))));
215
FQ_VERIFY(connect(imageFlow_, SIGNAL(clearStatus(const bool)), imageMenu_, SLOT(updateClear(const bool))));
216
FQ_VERIFY(connect(imageMenu_, SIGNAL(clearImages()), this, SLOT(clearImages())));
219
FQ_VERIFY(connect(imageFlow_, SIGNAL(saveStatus(const bool)), imageMenu_, SLOT(updateSave(const bool))));
220
FQ_VERIFY(connect(imageMenu_, SIGNAL(saveImages()), this, SLOT(saveImages())));
223
FQ_VERIFY(connect(this, SIGNAL(trashStatus(const bool)), imageMenu_, SLOT(updateDustbin(const bool))));
224
FQ_VERIFY(connect(imageMenu_, SIGNAL(recoverImages()), this, SLOT(recoverImages())));
226
// constructs a tool bar and its tool buttons
227
QAction *closeBrowserAct = new QAction(QIcon(iconPath("window-close.png")), tr("Close"), this);
228
closeBrowserAct->setShortcut(QKeySequence(Qt::Key_Q));
229
FQ_VERIFY(connect(closeBrowserAct, SIGNAL(triggered()), this, SLOT(close())));
231
QAction *trashAllAct = new QAction(QIcon(iconPath("trash-empty.png")), tr("Trash All"), this);
232
trashAllAct->setShortcut(QKeySequence(Qt::Key_Delete));
233
FQ_VERIFY(connect(trashAllAct, SIGNAL(triggered()), this, SLOT(trashAllImages())));
235
QLabel *sortLabel = new QLabel(this);
236
sortLabel->setMargin(5);
237
sortLabel->setToolTip("Reshuffle images by tags");
238
sortLabel->setPixmap(QPixmap(iconPath("edit-shuffle.png")));
240
QComboBox *sortBox = new QComboBox(this);
241
sortBox->setFocusPolicy(Qt::ClickFocus);
242
sortBox->setFont(QFont("Serif", 12));
243
sortBox->setFrame(false);
244
sortBox->setFixedSize(sortBoxFixedSize);
245
sortBox->insertItem(0, QIcon(iconPath("emblem-like-16x16.png")), tr("Like "));
246
sortBox->insertItem(1, QIcon(iconPath("emblem-new-16x16.png")), tr("New "));
247
sortBox->insertItem(2, QIcon(iconPath("emblem-trash-16x16.png")), tr("Trash "));
248
sortBox->insertItem(3, QIcon(iconPath("emblem-recover-16x16.png")), tr("Recover "));
249
sortBox->insertItem(4, QIcon(iconPath("emblem-title-16x16.png")), tr("Title "));
250
FQ_VERIFY(connect(sortBox, SIGNAL(currentIndexChanged(int)), this, SLOT(reshuffleImages(int))));
252
QToolBar *toolBar = new QToolBar(this);
253
toolBar->setFixedHeight(toolBarFixedHeight);
254
toolBar->setIconSize(QSize(32, 32));
255
toolBar->addAction(closeBrowserAct);
256
toolBar->addSeparator();
257
toolBar->addWidget(sortLabel);
258
toolBar->addWidget(sortBox);
259
toolBar->addSeparator();
260
toolBar->addAction(trashAllAct);
262
// constructs a status bar to show instant messages
263
statusBar_->setSizeGripEnabled(false);
264
statusBar_->setFixedHeight(statusBarFixedHeight);
265
statusBar_->setFont(QFont("Serif", 10, QFont::Bold));
266
FQ_VERIFY(connect(imageFlow_, SIGNAL(statusMessage(const QString &)), this, SLOT(showStatusMessage(const QString &))));
267
FQ_VERIFY(connect(this, SIGNAL(statusMessage(const QString &)), this, SLOT(showStatusMessage(const QString &))));
268
// constructs a horizontal layout
269
QVBoxLayout *vBox = new QVBoxLayout(this);
272
vBox->addWidget(toolBar);
273
vBox->addWidget(imageFlow_);
274
vBox->addWidget(imageMenu_);
275
vBox->addWidget(statusBar_);
276
vBox->setEnabled(true);
280
const QString &FQTermImageFlow::poolSource(void) const {
282
static QString p = "";
284
p = config_->getItemValue("preference", "pool");
287
p = getPath(USER_CONFIG) + POOL_SOURCE;
293
const QString &FQTermImageFlow::trashSource(void) const {
295
static QString p = "";
297
p = poolSource() + TRASH_SOURCE;
301
// these three functions below are kept for
302
// API compatibility.
303
void FQTermImageFlow::adjustItemSize() {
307
void FQTermImageFlow::scrollTo(const QString &filePath) {
311
void FQTermImageFlow::updateImage(const QString &filePath) {
315
// slots and functions
316
void FQTermImageFlow::loadImages(const int status) {
319
int itemStatus = (status == STAT_RECOVER) ? status : STAT_UNKNOWN;
321
QFileInfoList sourceList = sortedList(poolSource());
322
QFileInfoList targetList = imageFlow_->digest(STAT_ALL);
324
for (i = 0; i < sourceList.count(); i++) {
326
if (!targetList.contains(sourceList[i])) {
330
if (!image.load(sourceList[i].absoluteFilePath())) {
331
QFileIconProvider iconProvider;
332
image = iconProvider.icon(sourceList[i]).pixmap(desktopSize().height() / 6.0).toImage();
334
image = image.scaled(desktopSize().width() / 6.0, desktopSize().height() / 6.0,
335
Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
339
/* there should be a more flexible */
340
/* method to deal with items' status */
341
if (status == STAT_ALL) {
342
itemStatus = STAT_NEW;
345
ImageFlowItem newItem(image, sourceList[i], comment,itemStatus);
346
imageFlow_->add(newItem);
351
void FQTermImageFlow::reshuffleImages(const int status) {
353
// there are images in the pool directory
354
QList<qint64> itemKeys = imageFlow_->sort(status);
356
if (imageFlow_->reorder(itemKeys)) {
357
imageFlow_->setCurrentSlide(0);
358
emit statusMessage(tr(fqloraGeneralStatus[GEN_RESHUFFLE]) + tr(fqloraTagStatus[status]));
360
emit statusMessage(tr(fqloraGeneralStatus[GEN_NOCHANGE]) + tr(fqloraTagStatus[status]));
364
void FQTermImageFlow::saveImages() {
366
QFileInfoList toSave = imageFlow_->digest(STAT_LIKE);
368
if (!toSave.isEmpty()) {
371
box.setWindowModality(Qt::NonModal);
372
box.setWindowTitle("Information");
373
box.setIconPixmap(QPixmap(iconPath("emblem-info.png")));
374
box.setTextFormat(Qt::RichText);
375
box.setText(QString("<b>%1</b> ").arg(toSave.count()) + isPlural(toSave.count(), GEN_TOSAVE));
376
QPushButton *cancelButton = box.addButton(QMessageBox::Cancel);
377
QPushButton *saveAllButton = box.addButton(QMessageBox::SaveAll);
378
box.setDefaultButton(cancelButton);
381
if (box.clickedButton() == saveAllButton) {
383
FQTermFileDialog fileDialog(config_);
385
fileDialog.getExistingDirectory(tr("Save your likes under"), "*");
387
if (savePath.isEmpty()) {
388
emit statusMessage(tr(fqloraGeneralStatus[GEN_CANCEL]));
393
for (i = 0; i < toSave.count(); i++) {
394
QFile::rename(toSave[i].absoluteFilePath(), savePath + toSave[i].fileName());
397
imageFlow_->strip(STAT_LIKE);
398
emit statusMessage(QString("%1 ").arg(i) + isPlural(i, GEN_SAVE) + savePath);
400
emit statusMessage(tr(fqloraGeneralStatus[GEN_CANCEL]));
403
emit statusMessage(tr(fqloraGeneralStatus[GEN_NOTAG]) + tr(fqloraTagStatus[STAT_LIKE]));
407
void FQTermImageFlow::checkTrashState() {
409
QDir trashPath(trashSource());
411
if (trashPath.count() <= 2) {
412
emit trashStatus(false);
414
emit trashStatus(true);
418
void FQTermImageFlow::clearImages() {
421
QString trashPath = trashSource();
422
QDir trashDir(trashPath);
423
QFileInfoList toRemove = imageFlow_->digest(STAT_TRASH);
425
if (!trashDir.exists()) {
426
trashDir.mkdir(trashPath);
429
if (!toRemove.isEmpty()) {
431
for (i = 0; i < toRemove.count(); i++) {
432
if (QFile::exists(toRemove[i].absoluteFilePath())) {
433
QFile::rename(toRemove[i].absoluteFilePath(), trashPath + toRemove[i].fileName());
437
imageFlow_->strip(STAT_TRASH);
438
emit statusMessage(QString("%1 ").arg(i) + isPlural(i, GEN_TRASH));
441
emit statusMessage(tr(fqloraGeneralStatus[GEN_NOTAG]) + tr(fqloraTagStatus[STAT_TRASH]));
445
void FQTermImageFlow::trashAllImages() {
448
QString trashPath = trashSource();
449
QDir trashDir(trashPath);
450
QFileInfoList toRemove = imageFlow_->digest(STAT_ALL);
452
if (!trashDir.exists()) {
453
trashDir.mkdir(trashPath);
456
if (imageFlow_->count() > 0) {
457
for (i = 0; i < imageFlow_->count(); i++) {
458
if (QFile::exists(toRemove[i].absoluteFilePath())) {
459
QFile::rename(toRemove[i].absoluteFilePath(), trashPath + toRemove[i].fileName());
464
emit statusMessage(QString("%1 ").arg(i) + isPlural(i, GEN_TRASH));
467
emit statusMessage(tr(fqloraGeneralStatus[GEN_NOTAG]) + tr(fqloraTagStatus[STAT_TRASH]));
471
void FQTermImageFlow::recoverImages() {
473
QString trashPath = trashSource();
474
QDir trashDir(trashPath);
476
if (!trashDir.exists()) {
477
trashDir.mkdir(trashPath);
478
} else if (trashDir.count() > 2) {
481
QFileInfoList toRecover = sortedList(trashPath);
483
if (!toRecover.isEmpty()) {
486
box.setWindowModality(Qt::NonModal);
487
box.setWindowTitle("Take action");
488
box.setIconPixmap(QPixmap(iconPath("emblem-info.png")));
489
box.setTextFormat(Qt::RichText);
490
box.setText(QString("<b>%1</b> ").arg(toRecover.count()) + isPlural(toRecover.count(), GEN_INTRASH));
491
QPushButton *cancelButton = box.addButton(QMessageBox::Cancel);
492
QPushButton *recoverButton = box.addButton(tr("Recover"), QMessageBox::ApplyRole);
493
recoverButton->setIcon(QIcon(iconPath("button-recover.png")));
494
QPushButton *deleteButton = box.addButton(tr("Delete"), QMessageBox::ApplyRole);
495
deleteButton->setIcon(QIcon(iconPath("button-delete.png")));
496
box.setDefaultButton(cancelButton);
499
if (box.clickedButton() == cancelButton) {
500
emit statusMessage(tr(fqloraGeneralStatus[GEN_CANCEL]));
502
} else if (box.clickedButton() == recoverButton) {
504
} else if (box.clickedButton() == deleteButton) {
510
QString poolPath = poolSource();
512
for (i = 0; i < toRecover.count(); i++) {
514
QFile::rename(toRecover[i].absoluteFilePath(), poolPath + toRecover[i].fileName());
515
} else if (action == 0) {
516
imageFile.setFileName(toRecover[i].absoluteFilePath());
522
loadImages(STAT_RECOVER);
523
emit statusMessage(QString("%1 ").arg(i) + isPlural(i, GEN_RECOVER));
524
} else if (action == 0) {
525
emit statusMessage(QString("%1 ").arg(i) + isPlural(i, GEN_DELETE));
530
emit statusMessage(tr(fqloraGeneralStatus[GEN_NORECOVER]));
537
void FQTermImageFlow::showStatusMessage(const QString &message) {
540
statusBar_->showMessage(message, 3000);
544
QFileInfoList FQTermImageFlow::sortedList(const QString &path) {
548
if (!sortPath.exists()) {
549
sortPath.mkdir(path);
552
sortPath.setNameFilters((QStringList() << "*.jpg" << "*.jpeg" << "*.png" << "*.gif"
553
<< "*.bmp" << "*.tiff" << "*.mng"));
555
return (sortPath.entryInfoList(QDir::Files | QDir::Readable | QDir::NoSymLinks, QDir::Time));
558
// FQTermImageFlow events.
559
void FQTermImageFlow::showEvent(QShowEvent *event) {
563
if (imageFlow_->count() <= 0) {
564
loadImages(STAT_UNKNOWN);
565
move((desktopSize().width() - width()) / 2.0, (desktopSize().height() - height()) / 2.0);
567
// TODO: insert new images
568
loadImages(STAT_ALL);
572
void FQTermImageFlow::closeEvent(QCloseEvent *event) {
577
class ImageMenu::Private {
596
QPixmap pixmapLike, pixmapLikeGray;
597
QPixmap pixmapTrash, pixmapTrashGray;
598
QPixmap pixmapNew, pixmapNewGray;
599
QPixmap pixmapRecover, pixmapRecoverGray;
601
QPixmap pixmapClear, pixmapClearGray;
602
QPixmap pixmapSave, pixmapSaveGray;
603
QPixmap pixmapDustbin, pixmapDustbinGray;
607
ImageMenu::ImageMenu(QWidget *parent)
608
: fh(new ImageMenu::Private) {
610
setAutoFillBackground(true);
611
setBackgroundRole(QPalette::Shadow);
612
setFocusPolicy(Qt::NoFocus);
613
setMouseTracking(true);
616
fh->emblemStatus = -2;
617
fh->dustbinStatus = false;
618
fh->saveStatus = false;
619
fh->clearStatus = false;
623
fh->rectLike = QRect(50, 14, 32, 32);
624
fh->rectTrash = QRect(100, 14, 32, 32);
625
fh->rectNew = QRect(150, 14, 32, 32);
626
fh->rectRecover = QRect(200, 14, 32, 32);
627
fh->rectEmblems = QRect(50, 14, 232, 32);
629
fh->rectClear = QRect(300, 14, 32, 32);
630
fh->rectSave = QRect(350, 14, 32, 32);
631
fh->rectDustbin = QRect(400, 14, 32, 32);
633
fh->pixmapLike = QPixmap(iconPath("emblem-like.png"));
634
fh->pixmapLikeGray = QPixmap(iconPath("emblem-like-gray.png"));
636
fh->pixmapTrash = QPixmap(iconPath("emblem-trash.png"));
637
fh->pixmapTrashGray = QPixmap(iconPath("emblem-trash-gray.png"));
639
fh->pixmapNew = QPixmap(iconPath("emblem-new.png"));
640
fh->pixmapNewGray = QPixmap(iconPath("emblem-new-gray.png"));
642
fh->pixmapRecover = QPixmap(iconPath("emblem-recover.png"));
643
fh->pixmapRecoverGray = QPixmap(iconPath("emblem-recover-gray.png"));
645
fh->pixmapClear = QPixmap(iconPath("clear-state.png"));
646
fh->pixmapClearGray = QPixmap(iconPath("clear-state-gray.png"));
648
fh->pixmapSave = QPixmap(iconPath("save-state.png"));
649
fh->pixmapSaveGray = QPixmap(iconPath("save-state-gray.png"));
651
fh->pixmapDustbin = QPixmap(iconPath("trash-state.png"));
652
fh->pixmapDustbinGray = QPixmap(iconPath("trash-state-gray.png"));
655
ImageMenu::~ImageMenu() {
661
void ImageMenu::updateEmblems(const int status) {
663
if (fh->emblemStatus != status) {
664
update(fh->rectEmblems);
667
fh->emblemStatus = status;
670
void ImageMenu::updateClear(const bool hasOrNot) {
672
if (fh->clearStatus != hasOrNot) {
673
update(fh->rectClear);
676
fh->clearStatus = hasOrNot;
679
void ImageMenu::updateSave(const bool hasOrNot) {
681
if (fh->saveStatus != hasOrNot) {
682
update(fh->rectSave);
685
fh->saveStatus = hasOrNot;
688
void ImageMenu::updateDustbin(const bool fullOrNot) {
690
if (fh->dustbinStatus != fullOrNot) {
691
update(fh->rectDustbin);
694
fh->dustbinStatus = fullOrNot;
697
void ImageMenu::paintEvent(QPaintEvent *event) {
701
p.setRenderHints(QPainter::SmoothPixmapTransform | QPainter::Antialiasing);
703
if (fh->emblemStatus == STAT_LIKE) {
704
p.drawPixmap(fh->rectLike, fh->pixmapLike);
706
p.drawPixmap(fh->rectLike, fh->pixmapLikeGray);
709
if (fh->emblemStatus == STAT_TRASH) {
710
p.drawPixmap(fh->rectTrash, fh->pixmapTrash);
712
p.drawPixmap(fh->rectTrash, fh->pixmapTrashGray);
715
if (fh->emblemStatus == STAT_NEW) {
716
p.drawPixmap(fh->rectNew, fh->pixmapNew);
718
p.drawPixmap(fh->rectNew, fh->pixmapNewGray);
721
if (fh->emblemStatus == STAT_RECOVER) {
722
p.drawPixmap(fh->rectRecover, fh->pixmapRecover);
724
p.drawPixmap(fh->rectRecover, fh->pixmapRecoverGray);
727
if (fh->clearStatus == true) {
728
p.drawPixmap(fh->rectClear, fh->pixmapClear);
730
p.drawPixmap(fh->rectClear, fh->pixmapClearGray);
733
if (fh->saveStatus == true) {
734
p.drawPixmap(fh->rectSave, fh->pixmapSave);
736
p.drawPixmap(fh->rectSave, fh->pixmapSaveGray);
739
if (fh->dustbinStatus == true) {
740
p.drawPixmap(fh->rectDustbin, fh->pixmapDustbin);
742
p.drawPixmap(fh->rectDustbin, fh->pixmapDustbinGray);
748
void ImageMenu::mouseReleaseEvent(QMouseEvent *event) {
750
if (event->button() == Qt::LeftButton
751
|| event->button() == Qt::RightButton) {
753
if (fh->rectLike.contains(event->pos())) {
754
emit toggleFlowStatus(STAT_LIKE);
757
if (fh->rectTrash.contains(event->pos())) {
758
emit toggleFlowStatus(STAT_TRASH);
761
if (fh->rectClear.contains(event->pos())) {
765
if (fh->rectSave.contains(event->pos())) {
769
if (fh->rectDustbin.contains(event->pos())) {
770
emit recoverImages();
775
// ImageFlow: this subclasses PictureFlow
776
class ImageFlow::Private {
782
QList<QByteArray> itemsTitles;
783
QHash<QByteArray, qint64> itemsTitleKeyPairs;
784
QList<ImageFlowItem> items;
787
ImageFlow::~ImageFlow() {
793
// construct an emptry pictureflow.
794
ImageFlow::ImageFlow(QWidget *parent)
795
: PictureFlow(parent), m(new ImageFlow::Private) {
798
setFocusPolicy(Qt::StrongFocus);
801
m->emblemStatus = -1;
806
const ImageFlowItem& ImageFlow::operator[](int index) const {
808
return (m->items[index]);
811
ImageFlowItem& ImageFlow::operator[](int index) {
813
return (m->items[index]);
816
const ImageFlowItem& ImageFlow::at(int index) const {
818
return (m->items[index]);
821
void ImageFlow::add(const ImageFlowItem &item) {
823
QByteArray title = item.info().fileName().toLocal8Bit();
825
m->items.append(item);
826
m->itemsTitles.append(title);
827
m->itemsTitleKeyPairs.insert(title, item.key());
829
// set the status pixmap
830
int index = m->items.indexOf(item);
831
m->items[index].setStatus(item.status());
833
int count = m->items.count();
834
setSlideCount(count);
835
setSlide(count - 1, item.image());
838
QList<QByteArray>& ImageFlow::itemsTitles() const {
840
return (m->itemsTitles);
843
void ImageFlow::clear() {
848
PictureFlow::clear();
851
int ImageFlow::count() const {
853
return (m->items.count());
856
int ImageFlow::size() const {
858
return (m->items.size());
861
QFileInfoList& ImageFlow::digest(const int status) {
864
int count = m->items.size();
865
static QFileInfoList result;
869
for (i = 0; i < count; i++) {
872
result.append(m->items[i].info());
875
if (m->items[i].status() == status) {
876
result.append(m->items[i].info());
885
void ImageFlow::strip(const int status) {
888
int count = m->items.count();
889
QList<int> dirtyIndexes;
891
// We cannot delete items in a loop because
892
// items count will change while deleting.
893
// A better method is to use another list
894
// to record 'dirty' items, and by looping this
895
// dirty list, we can correctly delete those
896
// dirty items successfully.
897
for (i = 0; i < count; i++) {
898
if (m->items[i].status() == status) {
899
dirtyIndexes.append(i);
903
if (!dirtyIndexes.isEmpty()) {
905
for (i = 0; i < dirtyIndexes.count(); i++) {
906
// Note: dirtyIndexes[i] - i: the actual
907
// position of the dirty item.
908
m->items.removeAt(dirtyIndexes[i] - i);
911
dirtyIndexes.clear();
912
reorder(sort(STAT_ALL));
914
for (i = 0; i < m->items.count(); i++) {
915
setSlide(i, m->items[i].image());
924
emit saveStatus(false);
925
emit emblemStatus(STAT_UNKNOWN);
929
emit clearStatus(false);
930
emit emblemStatus(STAT_UNKNOWN);
938
QList<qint64> ImageFlow::sort(const int status) {
941
int count = m->items.count();
942
QList<qint64> itemKeys;
946
qStableSort(m->itemsTitles.begin(), m->itemsTitles.end(), qLess<QByteArray>());
947
for (i = 0; i < m->itemsTitles.count(); i++) {
948
itemKeys.append(m->itemsTitleKeyPairs.value(m->itemsTitles[i]));
952
for (i = 0; i < count; i++) {
953
if (m->items[i].status() == status) {
954
itemKeys.prepend(m->items[i].key());
956
itemKeys.append(m->items[i].key());
962
// this might be unnecessary here.
966
bool ImageFlow::reorder(const QList<qint64>& itemsKey) {
968
int items_count = m->items.size();
970
if (itemsKey.size() != items_count) {
976
QList<qint64> currentItemsKey;
977
for (int i = 0; i < items_count; i++) {
979
currentItemsKey.append(m->items.at(i).key());
982
if (currentItemsKey == itemsKey) {
983
// if identical, we don't sort.
988
for (int i=0; i < items_count; i++) {
990
int index = currentItemsKey.indexOf(itemsKey.at(i));
995
QImage imgA = m->items[i].image();
996
QImage imgB = m->items[index].image();
998
setSlide(index, imgA);
1001
m->items.swap(i, index);
1002
currentItemsKey.swap(i, index);
1010
void ImageFlow::rehash(void) {
1012
int count = m->items.count();
1020
m->itemsTitles.clear();
1021
m->itemsTitleKeyPairs.clear();
1026
for (i = 0; i < count; i++) {
1027
k = m->items[i].key();
1028
b = m->items[i].info().fileName().toLocal8Bit();
1029
m->itemsTitles.append(b);
1030
m->itemsTitleKeyPairs.insert(b, k);
1034
void ImageFlow::toggleStatus(const int status) {
1036
int index = currentSlide();
1037
int count = m->items.count();
1039
if (index >= 0 && index < count) {
1041
setUpdatesEnabled(false);
1042
if (m->items[index].status() != status) {
1047
if (m->items[index].status() == STAT_TRASH) {
1050
emit saveStatus(true);
1054
if (m->items[index].status() == STAT_LIKE) {
1057
emit clearStatus(true);
1063
m->items[index].setStatus(status);
1064
emit statusMessage(tr("Tagged as ") + tr(fqloraTagStatus[status]));
1079
m->items[index].setStatus(STAT_UNKNOWN);
1080
emit statusMessage("Tag cleared");
1083
if (m->likeCount <= 0) {
1084
emit saveStatus(false);
1086
emit saveStatus(true);
1089
if (m->clearCount <= 0) {
1090
emit clearStatus(false);
1092
emit clearStatus(true);
1095
setUpdatesEnabled(true);
1099
void ImageFlow::setCurrentImage(const int index) {
1103
if (count > 0 && index > 0 && index < count) {
1105
setCurrentSlide(index);
1110
void ImageFlow::paintEvent(QPaintEvent *event) {
1112
PictureFlow::paintEvent(event);
1114
if (slideCount() < 1) {
1120
// White Pen for File Info
1123
int cw = width() / 2;
1126
ImageFlowItem& item = m->items[currentSlide()];
1128
// Draw File Name if it's not empty
1129
QString title = item.info().fileName();
1130
if (!title.isEmpty()) {
1132
p.setFont(QFont(p.font().family(), p.font().pointSize() + 1, QFont::Bold));
1133
p.drawText(cw - (QFontMetrics(p.font()).width(title) / 2), wh - 20, title);
1138
if (m->emblemStatus != item.status()) {
1139
emit emblemStatus(item.status());
1142
m->emblemStatus = item.status();
1146
class ImageFlowItem::Private {
1157
ImageFlowItem::~ImageFlowItem() {
1163
// constructs an empty item
1164
ImageFlowItem::ImageFlowItem(QObject *parent)
1165
: QObject(parent), m(new ImageFlowItem::Private) {
1167
m->key = qHash(QString::number(m->image.cacheKey()) + m->info.fileName() + m->comment);
1170
ImageFlowItem::ImageFlowItem(const ImageFlowItem &item)
1171
: QObject(item.parent()), m(new ImageFlowItem::Private) {
1176
// constructs an item with a given image
1177
ImageFlowItem::ImageFlowItem(const QImage &image, QObject *parent)
1178
: QObject(parent), m(new ImageFlowItem::Private) {
1181
m->key = qHash(QString::number(m->image.cacheKey()) + m->info.fileName() + m->comment);
1184
// constructs an image with given image and title
1185
ImageFlowItem::ImageFlowItem(const QImage &image, const QFileInfo &info, QObject *parent)
1186
: QObject(parent), m(new ImageFlowItem::Private) {
1190
m->key = qHash(QString::number(m->image.cacheKey()) + m->info.fileName() + m->comment);
1193
// constructs an image with given image, title and comment
1194
ImageFlowItem::ImageFlowItem(const QImage &image, const QFileInfo &info, const QString &comment, QObject *parent)
1195
: QObject(parent), m(new ImageFlowItem::Private) {
1199
m->comment = comment;
1200
m->key = qHash(QString::number(m->image.cacheKey()) + m->info.fileName() + m->comment);
1203
// constructs an image with given image, title, type, size, time and state
1204
ImageFlowItem::ImageFlowItem(const QImage &image, const QFileInfo &info, const QString& comment, const int status, QObject *parent)
1205
: QObject(parent), m(new ImageFlowItem::Private) {
1209
m->comment = comment;
1212
m->key = qHash(QString::number(m->image.cacheKey()) + m->info.fileName() + m->comment);
1216
ImageFlowItem& ImageFlowItem::operator=(const ImageFlowItem &item) {
1218
m->info = item.m->info;
1219
m->comment = item.m->comment;
1220
m->key = item.m->key;
1222
if (item.m->filePath.isEmpty()) {
1223
m->image = item.m->image;
1225
setImage(item.m->filePath, m->key);
1231
bool ImageFlowItem::operator==(const ImageFlowItem& item) const {
1233
return (item.m->key == m->key);
1236
bool ImageFlowItem::operator!=(const ImageFlowItem& item) const {
1238
return (item.m->key != m->key);
1241
const QString& ImageFlowItem::comment() const {
1243
return (m->comment);
1246
const QImage& ImageFlowItem::image() const {
1251
int ImageFlowItem::status(void) const {
1256
const QFileInfo& ImageFlowItem::info(void) const {
1261
qint64 ImageFlowItem::key() const {
1266
void ImageFlowItem::setInfo(const QFileInfo& info) {
1269
m->key = qHash(QString::number(m->image.cacheKey()) + m->info.fileName() + m->comment);
1272
void ImageFlowItem::setComment(const QString &comment) {
1274
m->comment = comment;
1275
m->key = qHash(QString::number(m->image.cacheKey()) + m->info.fileName() + m->comment);
1278
void ImageFlowItem::setImage(const QImage &image) {
1280
m->image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
1281
m->key = qHash(QString::number(m->image.cacheKey()) + m->info.fileName() + m->comment);
1284
void ImageFlowItem::setImage(const QString &filePath, int size) {
1286
m->filePath = filePath;
1289
QTimer::singleShot(1000, this, SLOT(loadImage()));
1292
void ImageFlowItem::setStatus(const int status) {
1297
void ImageFlowItem::loadImage() {
1299
int imageHeight = m->key;
1301
if (!m->image.load(m->filePath)) {
1302
QFileIconProvider iconProvider;
1303
m->image = iconProvider.icon(QFileInfo(m->filePath)).pixmap(imageHeight).toImage();
1305
m->image = resizeImage(m->image, imageHeight);
1308
m->filePath.clear();
1309
m->key = qHash(QString::number(m->image.cacheKey()) + m->info.fileName() + m->comment);
1312
QImage ImageFlowItem::resizeImage(const QImage &image, int size) {
1314
if (size == image.width() && size == image.height()) {
1318
double scaleWidth = size / (double)image.width();
1319
double scaleHeight = size / (double)image.height();
1320
double smaller = qMin(scaleWidth, scaleHeight);
1322
int w = (int) qRound(smaller * image.width());
1323
int h = (int) qRound(smaller * image.height());
1325
return (image.scaled(w, h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
1329
void FQTermImageOrigin::onChange(const QModelIndex & index) {
1331
if (!model_->isDir(index)) {
1336
QString exifInfo = QString::fromStdString(exifExtractor_->extractExifInfo(model_->filePath(tree_->currentIndex()).toLocal8Bit().data()));
1337
bool resized = false;
1339
if (exifInfo != "") {
1341
if (!isExifTableShown_) {
1343
isExifTableShown_ = true;
1350
if (isExifTableShown_) {
1351
adjustLayout(false);
1352
isExifTableShown_ = false;
1357
QString path = QDir::toNativeSeparators(model_->filePath(index));
1359
if (path.endsWith(QDir::separator()))
1362
canvas_->loadImage(path, !resized);
1364
// canvas_->autoAdjust();
1370
FQTermImageOrigin::~FQTermImageOrigin() {
1377
FQTermImageOrigin::FQTermImageOrigin(FQTermConfig * config, QWidget *parent,
1378
Qt::WindowFlags wflag) :
1379
FQTermImage(parent, wflag),
1381
isExifTableShown_(false) {
1383
setWindowTitle(tr("FQTerm Image Viewer"));
1384
ItemDelegate* itemDelegate = new ItemDelegate;
1385
exifExtractor_ = new ExifExtractor;
1386
exifTable_ = new ExifTable(this);
1387
exifTable_->setTextFormat(Qt::RichText);
1388
exifTable_->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
1389
canvas_ = new FQTermCanvas(config, this, 0);
1390
canvas_->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding);
1391
model_ = new ImageViewerDirModel;
1392
tree_ = new QTreeView;
1393
tree_->setModel(model_);
1394
tree_->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
1396
tree_->setItemDelegate(itemDelegate);
1397
tree_->setColumnWidth(0, 150);
1398
// tree_->hideColumn(0);
1400
tree_->setUniformRowHeights(true);
1401
tree_->setWordWrap(true);
1403
comboBox_ = new QComboBox(this);
1404
comboBox_->addItem(tr("Sort by name"), QDir::Name);
1405
comboBox_->addItem(tr("Sort by time"), QDir::Time);
1406
comboBox_->addItem(tr("Sort by size"), QDir::Size);
1407
comboBox_->addItem(tr("Sort by type"), QDir::Type);
1409
FQ_VERIFY(connect(comboBox_, SIGNAL(currentIndexChanged(int)), this, SLOT(sortFileList(int))));
1411
comboBox_->setCurrentIndex(1);
1413
layout_ = new QGridLayout;
1414
menuBar_ = new QMenuBar(this);
1415
menuBar_->addMenu(canvas_->menu());
1416
menuBar_->resize(1,1);
1418
canvas_->ToolBar()->addAction(
1419
QIcon(getPath(RESOURCE) + ICON_SOURCE + "prev.png"), tr("Previous"),
1420
this, SLOT(previous()));
1421
canvas_->ToolBar()->addAction(
1422
QIcon(getPath(RESOURCE) + ICON_SOURCE + "next.png"), tr("Next"),
1423
this, SLOT(next()));
1425
layout_->addWidget(tree_, 0, 0, 12, 1);
1426
layout_->addWidget(comboBox_, 12, 0, 1, 1);
1427
layout_->addWidget(canvas_, 0, 1, 12, 10);
1428
// layout_->addWidget(exifTable_, 10, 1, 2, 10);
1429
layout_->addWidget(canvas_->ToolBar(), 12, 1, 1, 10, Qt::AlignHCenter);
1430
layout_->setColumnMinimumWidth(0, tree_->columnWidth(0) + 150);
1434
FQ_VERIFY(connect(tree_, SIGNAL(clicked(const QModelIndex &)),
1435
this, SLOT(onChange(const QModelIndex &))));
1437
FQ_VERIFY(connect(tree_, SIGNAL(activated(const QModelIndex &)),
1438
this, SLOT(onChange(const QModelIndex &))));
1439
FQ_VERIFY(connect(tree_->selectionModel(),
1440
SIGNAL(selectionChanged(const QItemSelection&,
1441
const QItemSelection&)),
1442
this, SLOT(selectionChanged(const QItemSelection&,
1443
const QItemSelection&))));
1444
FQ_VERIFY(connect(exifTable_, SIGNAL(showExifDetails()),
1445
this, SLOT(showFullExifInfo())));
1448
void FQTermImageOrigin::scrollTo(const QString& filename) {
1450
QString path = QFileInfo(filename).absolutePath();
1452
tree_->setRootIndex(model_->index(path));
1453
canvas_->loadImage(filename);
1455
if (canvas_->isHidden() && !isHidden()) {
1459
const QModelIndex& index = model_->index(filename);
1460
tree_->scrollTo(index);
1461
tree_->setCurrentIndex(index);
1464
void FQTermImageOrigin::updateImage(const QString& filename) {
1468
model_->refresh(model_->index(filename));
1471
canvas_->updateImage(filename);
1474
void FQTermImageOrigin::previous() {
1476
const QModelIndex& index = tree_->indexAbove(tree_->currentIndex());
1477
if (index.isValid()) {
1478
tree_->setCurrentIndex(index);
1479
canvas_->loadImage(QDir::toNativeSeparators(model_->filePath(index)));
1483
void FQTermImageOrigin::next() {
1485
const QModelIndex& index = tree_->indexBelow(tree_->currentIndex());
1486
if (index.isValid()) {
1487
tree_->setCurrentIndex(index);
1488
canvas_->loadImage(QDir::toNativeSeparators(model_->filePath(index)));
1492
void FQTermImageOrigin::adjustItemSize() {
1494
QFontMetrics fm(font());
1495
ItemDelegate::size_.setWidth(qMax(128, fm.width("WWWWWWWW.WWW")));
1496
ItemDelegate::size_.setHeight(fm.height() + 150);
1499
void FQTermImageOrigin::selectionChanged(const QItemSelection & selected,
1500
const QItemSelection & deselected) {
1502
onChange(tree_->selectionModel()->currentIndex());
1505
void FQTermImageOrigin::sortFileList(int index) {
1507
model_->setSorting(QDir::SortFlag(comboBox_->itemData(index).toInt()));
1508
QString poolPath = config_->getItemValue("preference", "pool");
1510
if (poolPath.isEmpty()) {
1511
poolPath = getPath(USER_CONFIG) + "pool/";
1514
tree_->setRootIndex(model_->index(poolPath));
1517
void FQTermImageOrigin::showFullExifInfo() {
1519
QString exifInfo = QString::fromStdString(exifExtractor_->extractExifInfo(model_->filePath(tree_->currentIndex()).toLocal8Bit().data()));
1522
if ((*exifExtractor_)["UserComment"].length() > 8) {
1524
QString commentEncoding = QString::fromStdString((*exifExtractor_)["UserComment"].substr(0, 8));
1526
if (commentEncoding.startsWith("UNICODE")) {
1528
QTextCodec* c = QTextCodec::codecForName("UTF-16");
1529
comment = c->toUnicode((*exifExtractor_)["UserComment"].substr(8).c_str());
1530
} else if (commentEncoding.startsWith("JIS")) {
1532
QTextCodec* c = QTextCodec::codecForName("JIS X 0208");
1533
comment = c->toUnicode((*exifExtractor_)["UserComment"].substr(8).c_str());
1535
comment = QString::fromStdString((*exifExtractor_)["UserComment"].substr(8));
1540
QTextEdit* info = new QTextEdit;
1541
info->setText(exifInfo + tr("Comment : ") + comment + "\n");
1542
info->setWindowFlags(Qt::Dialog);
1543
info->setAttribute(Qt::WA_DeleteOnClose);
1544
info->setAttribute(Qt::WA_ShowModal);
1545
// info->setLineWrapMode(QTextEdit::NoWrap);
1546
info->setReadOnly(true);
1547
QFontMetrics fm(font());
1548
info->resize(fm.width("Orientation : 1st row - 1st col : top - left side "), fm.height() * 20);
1552
void FQTermImageOrigin::adjustLayout(bool withExifTable) {
1554
if (withExifTable) {
1556
layout_->addWidget(canvas_, 0, 1, 11, 10);
1557
layout_->addWidget(exifTable_, 11, 1, 1, 10, Qt::AlignHCenter);
1558
if (!isHidden() && exifTable_->isHidden()) {
1562
layout_->addWidget(canvas_->ToolBar(), 12, 1, 1, 10, Qt::AlignHCenter);
1564
layout_->addWidget(canvas_, 0, 1, 12, 10);
1565
layout_->removeWidget(exifTable_);
1567
layout_->addWidget(canvas_->ToolBar(), 12, 1, 1, 10, Qt::AlignHCenter);
1571
void FQTermImageOrigin::updateExifInfo() {
1573
exifTable_->clear();
1575
QString exifInfoToShow = "<table border=\"1\"><tr><td>"
1576
+ tr("Model") + " : " + QString::fromStdString((*exifExtractor_)["Model"]) + "</td><td>"
1577
+ QString::fromStdString((*exifExtractor_)["DateTime"]) + "</td><td>"
1578
+ QString::fromStdString((*exifExtractor_)["Flash"]) + "</td>"
1580
+ tr("ExposureTime") + " : " + QString::fromStdString((*exifExtractor_)["ExposureTime"]) + "</td><td>"
1581
+ tr("FNumber") + " : " + QString::fromStdString((*exifExtractor_)["FNumber"]) + "</td><td>"
1582
+ tr("ISO") + " : " + QString::fromStdString((*exifExtractor_)["ISOSpeedRatings"]) + "</td>"
1584
+ tr("FocalLength") + " : " + QString::fromStdString((*exifExtractor_)["FocalLength"]) + "</td><td>"
1585
+ tr("MeteringMode") + " : " + QString::fromStdString((*exifExtractor_)["MeteringMode"]) + "</td><td>"
1586
+ tr("ExposureBias") + " : " + QString::fromStdString((*exifExtractor_)["ExposureBiasValue"]) + "</td></tr></tabel>";
1588
exifTable_->setText(exifInfoToShow);
1589
if (!isHidden() && exifTable_->isHidden()) {
1594
void FQTermImageOrigin::closeEvent( QCloseEvent *clse ) {
1601
ImageViewerDirModel::ImageViewerDirModel(QObject *parent /*= 0*/)
1602
: QDirModel(parent) {
1605
QStringList nameFilterList;
1606
nameFilterList << "*.jpg" << "*.jpeg" << "*.png"
1607
<< "*.mng" << "*.bmp" << "*.gif";
1608
setNameFilters(nameFilterList);
1609
setFilter(QDir::Files);
1612
int ImageViewerDirModel::columnCount(const QModelIndex &/*parent*/) const {
1617
QVariant ImageViewerDirModel::headerData(
1618
int section, Qt::Orientation orientation, int role) const {
1620
if (role == Qt::DisplayRole) {
1622
return QString(tr("Image Preview"));
1625
return QDirModel::headerData(section, orientation, role);
1628
QVariant ImageViewerDirModel::data(const QModelIndex &index, int role) const {
1630
if (role == Qt::DecorationRole) {
1635
QString path = QDir::toNativeSeparators(filePath(index));
1636
if (path.endsWith(QDir::separator()))
1639
QPixmap pixmap(path);
1640
if (pixmap.height() > 128 || pixmap.width() > 128) {
1641
return pixmap.scaled(128, 128,
1642
Qt::KeepAspectRatio, Qt::SmoothTransformation);
1646
} else if (role == Qt::DisplayRole) {
1647
return fileName(index);
1649
else if (role == Qt::TextAlignmentRole) {
1650
return Qt::Qt::AlignBottom;
1655
QSize ItemDelegate::size_;
1657
void ItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem & option,
1658
const QModelIndex & index ) const {
1659
QStyleOptionViewItemV3 opt = setOptions(index, option);
1664
// get the data and the rectangles
1665
const QPixmap& pixmap
1666
= qvariant_cast<QPixmap>(index.data(Qt::DecorationRole));
1667
QRect decorationRect = QRect(opt.rect.topLeft(), pixmap.size());
1668
decorationRect.moveTo(decorationRect.left(), decorationRect.top() + 10);
1669
const QString& text = index.data(Qt::DisplayRole).toString();
1670
QFontMetrics fm(painter->font());
1671
QRect displayRect = QRect(decorationRect.bottomLeft(),
1672
QSize(fm.width(text),fm.height()));
1675
Qt::CheckState checkState = Qt::Unchecked;
1676
QVariant value = index.data(Qt::CheckStateRole);
1678
if (value.isValid()) {
1679
checkState = static_cast<Qt::CheckState>(value.toInt());
1680
checkRect = check(opt, opt.rect, value);
1685
// doLayout(opt, &checkRect, &decorationRect, &displayRect, false);
1689
drawBackground(painter, opt, index);
1690
painter->drawPixmap(decorationRect, pixmap);
1691
painter->drawText(displayRect, text);
1693
drawFocus(painter, opt, displayRect);
1699
void ExifTable::mouseReleaseEvent(QMouseEvent *pEvent) {
1701
if (pEvent->button() == Qt::LeftButton) {
1702
emit(showExifDetails());
1706
ExifTable::ExifTable(QWidget *parent) : QLabel(parent) {
1710
FQTermImage::FQTermImage( QWidget * parent, Qt::WindowFlags f ) : QWidget(parent, f) {
1713
} // namespace FQTerm
1715
#include "imageviewer.moc"