diff --git a/app/semanticinfocontextmanageritem.cpp b/app/semanticinfocontextmanageritem.cpp index 745feaf6..a67f55f0 100644 --- a/app/semanticinfocontextmanageritem.cpp +++ b/app/semanticinfocontextmanageritem.cpp @@ -1,469 +1,471 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2008 Aurélien Gâteau 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 Street, Fifth Floor, Cambridge, MA 02110-1301, USA. */ // Self #include "semanticinfocontextmanageritem.h" // Qt #include #include #include #include #include #include #include #include // KDE #include #include #include #include #include #include #include #include // Local #include "viewmainpage.h" #include "sidebar.h" #include "ui_semanticinfosidebaritem.h" #include "ui_semanticinfodialog.h" #include #include #include #include #include #include #include #include #include namespace Gwenview { static const int RATING_INDICATOR_HIDE_DELAY = 2000; struct SemanticInfoDialog : public QDialog, public Ui_SemanticInfoDialog { SemanticInfoDialog(QWidget* parent) : QDialog(parent) { setLayout(new QVBoxLayout); QWidget* mainWidget = new QWidget; layout()->addWidget(mainWidget); setupUi(mainWidget); mainWidget->layout()->setMargin(0); setWindowTitle(mainWidget->windowTitle()); KWindowConfig::restoreWindowSize(windowHandle(), configGroup()); } ~SemanticInfoDialog() { KConfigGroup group = configGroup(); KWindowConfig::saveWindowSize(windowHandle(), group); } KConfigGroup configGroup() const { KSharedConfigPtr config = KSharedConfig::openConfig(); return KConfigGroup(config, "SemanticInfoDialog"); } }; /** * A QGraphicsPixmapItem-like class, but which inherits from QGraphicsWidget */ class GraphicsPixmapWidget : public QGraphicsWidget { public: void setPixmap(const QPixmap& pix) { mPix = pix; setMinimumSize(pix.size()); } void paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) Q_DECL_OVERRIDE { painter->drawPixmap( (size().width() - mPix.width()) / 2, (size().height() - mPix.height()) / 2, mPix); } private: QPixmap mPix; }; class RatingIndicator : public HudWidget { public: RatingIndicator() : HudWidget() , mPixmapWidget(new GraphicsPixmapWidget) , mDeleteTimer(new QTimer(this)) { updatePixmap(0); setOpacity(0); init(mPixmapWidget, OptionNone); mDeleteTimer->setInterval(RATING_INDICATOR_HIDE_DELAY); mDeleteTimer->setSingleShot(true); connect(mDeleteTimer, SIGNAL(timeout()), SLOT(fadeOut())); connect(this, SIGNAL(fadedOut()), SLOT(deleteLater())); } void setRating(int rating) { updatePixmap(rating); update(); mDeleteTimer->start(); fadeIn(); } private: GraphicsPixmapWidget* mPixmapWidget; QTimer* mDeleteTimer; void updatePixmap(int rating) { KRatingPainter ratingPainter; const int iconSize = KIconLoader::global()->currentSize(KIconLoader::Small); QPixmap pix(iconSize * 5 + ratingPainter.spacing() * 4, iconSize); pix.fill(Qt::transparent); { QPainter painter(&pix); ratingPainter.paint(&painter, pix.rect(), rating); } mPixmapWidget->setPixmap(pix); } }; struct SemanticInfoContextManagerItemPrivate : public Ui_SemanticInfoSideBarItem { SemanticInfoContextManagerItem* q; SideBarGroup* mGroup; KActionCollection* mActionCollection; ViewMainPage* mViewMainPage; QPointer mSemanticInfoDialog; TagInfo mTagInfo; QAction * mEditTagsAction; QSignalMapper* mRatingMapper; /** A list of all actions, so that we can disable them when necessary */ QList mActions; QPointer mRatingIndicator; void setupGroup() { - mGroup = new SideBarGroup(i18n("Semantic Information")); + mGroup = new SideBarGroup(i18n("Semantic Information"), false); q->setWidget(mGroup); EventWatcher::install(mGroup, QEvent::Show, q, SLOT(update())); QWidget* container = new QWidget; setupUi(container); container->layout()->setMargin(0); mGroup->addWidget(container); + formLayout->setContentsMargins(DEFAULT_LAYOUT_MARGIN, 0, 0, 0); + QObject::connect(mRatingWidget, SIGNAL(ratingChanged(int)), q, SLOT(slotRatingChanged(int))); QObject::connect(mRatingMapper, SIGNAL(mapped(int)), mRatingWidget, SLOT(setRating(int))); mDescriptionTextEdit->installEventFilter(q); QObject::connect(mTagLabel, SIGNAL(linkActivated(QString)), mEditTagsAction, SLOT(trigger())); } void setupActions() { KActionCategory* edit = new KActionCategory(i18nc("@title actions category", "Edit"), mActionCollection); mEditTagsAction = edit->addAction("edit_tags"); mEditTagsAction->setText(i18nc("@action", "Edit Tags")); mActionCollection->setDefaultShortcut(mEditTagsAction, Qt::CTRL + Qt::Key_T); QObject::connect(mEditTagsAction, SIGNAL(triggered()), q, SLOT(showSemanticInfoDialog())); mActions << mEditTagsAction; mRatingMapper = new QSignalMapper(q); for (int rating = 0; rating <= 5; ++rating) { QAction * action = edit->addAction(QString("rate_%1").arg(rating)); if (rating == 0) { action->setText(i18nc("@action Rating value of zero", "Zero")); } else { action->setText(QString(rating, QChar(0x22C6))); /* 0x22C6 is the 'star' character */ } mActionCollection->setDefaultShortcut(action, Qt::Key_0 + rating); QObject::connect(action, SIGNAL(triggered()), mRatingMapper, SLOT(map())); mRatingMapper->setMapping(action, rating * 2); mActions << action; } QObject::connect(mRatingMapper, SIGNAL(mapped(int)), q, SLOT(slotRatingChanged(int))); } void updateTagLabel() { if (q->contextManager()->selectedFileItemList().isEmpty()) { mTagLabel->clear(); return; } AbstractSemanticInfoBackEnd* backEnd = q->contextManager()->dirModel()->semanticInfoBackEnd(); TagInfo::ConstIterator it = mTagInfo.constBegin(), end = mTagInfo.constEnd(); QMap labelMap; for (; it != end; ++it) { SemanticInfoTag tag = it.key(); QString label = backEnd->labelForTag(tag); if (!it.value()) { // Tag is not present for all urls label += '*'; } labelMap[label.toLower()] = label; } QStringList labels(labelMap.values()); QString editLink = i18n("Edit"); QString text = labels.join(", ") + QString(" %1").arg(editLink); mTagLabel->setText(text); } void updateSemanticInfoDialog() { mSemanticInfoDialog->mTagWidget->setEnabled(!q->contextManager()->selectedFileItemList().isEmpty()); mSemanticInfoDialog->mTagWidget->setTagInfo(mTagInfo); } }; SemanticInfoContextManagerItem::SemanticInfoContextManagerItem(ContextManager* manager, KActionCollection* actionCollection, ViewMainPage* viewMainPage) : AbstractContextManagerItem(manager) , d(new SemanticInfoContextManagerItemPrivate) { d->q = this; d->mActionCollection = actionCollection; d->mViewMainPage = viewMainPage; connect(contextManager(), SIGNAL(selectionChanged()), SLOT(slotSelectionChanged())); connect(contextManager(), SIGNAL(selectionDataChanged()), SLOT(update())); connect(contextManager(), SIGNAL(currentDirUrlChanged(QUrl)), SLOT(update())); d->setupActions(); d->setupGroup(); } SemanticInfoContextManagerItem::~SemanticInfoContextManagerItem() { delete d; } inline int ratingForVariant(const QVariant& variant) { if (variant.isValid()) { return variant.toInt(); } else { return 0; } } void SemanticInfoContextManagerItem::slotSelectionChanged() { update(); } void SemanticInfoContextManagerItem::update() { KFileItemList itemList = contextManager()->selectedFileItemList(); bool first = true; int rating = 0; QString description; SortedDirModel* dirModel = contextManager()->dirModel(); // This hash stores for how many items the tag is present // If you have 3 items, and only 2 have the "Holiday" tag, // then tagHash["Holiday"] will be 2 at the end of the loop. typedef QHash TagHash; TagHash tagHash; Q_FOREACH(const KFileItem & item, itemList) { QModelIndex index = dirModel->indexForItem(item); QVariant value = dirModel->data(index, SemanticInfoDirModel::RatingRole); if (first) { rating = ratingForVariant(value); } else if (rating != ratingForVariant(value)) { // Ratings aren't the same, reset rating = 0; } QString indexDescription = index.data(SemanticInfoDirModel::DescriptionRole).toString(); if (first) { description = indexDescription; } else if (description != indexDescription) { description.clear(); } // Fill tagHash, incrementing the tag count if it's already there TagSet tagSet = TagSet::fromVariant(index.data(SemanticInfoDirModel::TagsRole)); Q_FOREACH(const QString & tag, tagSet) { TagHash::Iterator it = tagHash.find(tag); if (it == tagHash.end()) { tagHash[tag] = 1; } else { ++it.value(); } } first = false; } { SignalBlocker blocker(d->mRatingWidget); d->mRatingWidget->setRating(rating); } d->mDescriptionTextEdit->setText(description); // Init tagInfo from tagHash d->mTagInfo.clear(); int itemCount = itemList.count(); TagHash::ConstIterator it = tagHash.constBegin(), end = tagHash.constEnd(); for (; it != end; ++it) { QString tag = it.key(); int count = it.value(); d->mTagInfo[tag] = count == itemCount; } bool enabled = !contextManager()->selectedFileItemList().isEmpty(); Q_FOREACH(QAction * action, d->mActions) { action->setEnabled(enabled); } d->updateTagLabel(); if (d->mSemanticInfoDialog) { d->updateSemanticInfoDialog(); } } void SemanticInfoContextManagerItem::slotRatingChanged(int rating) { KFileItemList itemList = contextManager()->selectedFileItemList(); // Show rating indicator in view mode, and only if sidebar is not visible if (d->mViewMainPage->isVisible() && !d->mRatingWidget->isVisible()) { if (!d->mRatingIndicator.data()) { d->mRatingIndicator = new RatingIndicator; d->mViewMainPage->showMessageWidget(d->mRatingIndicator, Qt::AlignBottom | Qt::AlignHCenter); } d->mRatingIndicator->setRating(rating); } SortedDirModel* dirModel = contextManager()->dirModel(); Q_FOREACH(const KFileItem & item, itemList) { QModelIndex index = dirModel->indexForItem(item); dirModel->setData(index, rating, SemanticInfoDirModel::RatingRole); } } void SemanticInfoContextManagerItem::storeDescription() { if (!d->mDescriptionTextEdit->document()->isModified()) { return; } d->mDescriptionTextEdit->document()->setModified(false); QString description = d->mDescriptionTextEdit->toPlainText(); KFileItemList itemList = contextManager()->selectedFileItemList(); SortedDirModel* dirModel = contextManager()->dirModel(); Q_FOREACH(const KFileItem & item, itemList) { QModelIndex index = dirModel->indexForItem(item); dirModel->setData(index, description, SemanticInfoDirModel::DescriptionRole); } } void SemanticInfoContextManagerItem::assignTag(const SemanticInfoTag& tag) { KFileItemList itemList = contextManager()->selectedFileItemList(); SortedDirModel* dirModel = contextManager()->dirModel(); Q_FOREACH(const KFileItem & item, itemList) { QModelIndex index = dirModel->indexForItem(item); TagSet tags = TagSet::fromVariant(dirModel->data(index, SemanticInfoDirModel::TagsRole)); if (!tags.contains(tag)) { tags << tag; dirModel->setData(index, tags.toVariant(), SemanticInfoDirModel::TagsRole); } } } void SemanticInfoContextManagerItem::removeTag(const SemanticInfoTag& tag) { KFileItemList itemList = contextManager()->selectedFileItemList(); SortedDirModel* dirModel = contextManager()->dirModel(); Q_FOREACH(const KFileItem & item, itemList) { QModelIndex index = dirModel->indexForItem(item); TagSet tags = TagSet::fromVariant(dirModel->data(index, SemanticInfoDirModel::TagsRole)); if (tags.contains(tag)) { tags.remove(tag); dirModel->setData(index, tags.toVariant(), SemanticInfoDirModel::TagsRole); } } } void SemanticInfoContextManagerItem::showSemanticInfoDialog() { if (!d->mSemanticInfoDialog) { d->mSemanticInfoDialog = new SemanticInfoDialog(d->mGroup); d->mSemanticInfoDialog->setAttribute(Qt::WA_DeleteOnClose, true); connect(d->mSemanticInfoDialog->mPreviousButton, SIGNAL(clicked()), d->mActionCollection->action("go_previous"), SLOT(trigger())); connect(d->mSemanticInfoDialog->mNextButton, SIGNAL(clicked()), d->mActionCollection->action("go_next"), SLOT(trigger())); connect(d->mSemanticInfoDialog->mButtonBox, SIGNAL(rejected()), d->mSemanticInfoDialog, SLOT(close())); AbstractSemanticInfoBackEnd* backEnd = contextManager()->dirModel()->semanticInfoBackEnd(); d->mSemanticInfoDialog->mTagWidget->setSemanticInfoBackEnd(backEnd); connect(d->mSemanticInfoDialog->mTagWidget, SIGNAL(tagAssigned(SemanticInfoTag)), SLOT(assignTag(SemanticInfoTag))); connect(d->mSemanticInfoDialog->mTagWidget, SIGNAL(tagRemoved(SemanticInfoTag)), SLOT(removeTag(SemanticInfoTag))); } d->updateSemanticInfoDialog(); d->mSemanticInfoDialog->show(); } bool SemanticInfoContextManagerItem::eventFilter(QObject*, QEvent* event) { if (event->type() == QEvent::FocusOut) { storeDescription(); } return false; } } // namespace diff --git a/app/sidebar.cpp b/app/sidebar.cpp index f55c2f7e..e7f79ff2 100644 --- a/app/sidebar.cpp +++ b/app/sidebar.cpp @@ -1,246 +1,252 @@ /* Gwenview: an image viewer Copyright 2007 Aurélien Gâteau 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "sidebar.h" // Qt #include #include #include #include #include #include #include #include // KDE #include #include // Local namespace Gwenview { /** * A button which always leave room for an icon, even if there is none, so that * all button texts are correctly aligned. */ class SideBarButton : public QToolButton { protected: void paintEvent(QPaintEvent* event) Q_DECL_OVERRIDE { forceIcon(); QToolButton::paintEvent(event); } QSize sizeHint() const Q_DECL_OVERRIDE { const_cast(this)->forceIcon(); return QToolButton::sizeHint(); } private: void forceIcon() { if (!icon().isNull()) { return; } // Assign an empty icon to the button if there is no icon associated // with the action so that all button texts are correctly aligned. QSize wantedSize = iconSize(); if (mEmptyIcon.isNull() || mEmptyIcon.actualSize(wantedSize) != wantedSize) { QPixmap pix(wantedSize); pix.fill(Qt::transparent); mEmptyIcon.addPixmap(pix); } setIcon(mEmptyIcon); } QIcon mEmptyIcon; }; //- SideBarGroup --------------------------------------------------------------- struct SideBarGroupPrivate { QFrame* mContainer; QLabel* mTitleLabel; + bool mDefaultContainerMarginEnabled; }; -SideBarGroup::SideBarGroup(const QString& title) +SideBarGroup::SideBarGroup(const QString& title, bool defaultContainerMarginEnabled) : QFrame() , d(new SideBarGroupPrivate) { d->mContainer = 0; d->mTitleLabel = new QLabel(this); + d->mDefaultContainerMarginEnabled = defaultContainerMarginEnabled; d->mTitleLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); d->mTitleLabel->setFixedHeight(d->mTitleLabel->sizeHint().height() * 3 / 2); QFont font(d->mTitleLabel->font()); font.setBold(true); d->mTitleLabel->setFont(font); d->mTitleLabel->setText(title); QVBoxLayout* layout = new QVBoxLayout(this); layout->setMargin(0); layout->setSpacing(0); layout->addWidget(d->mTitleLabel); + d->mTitleLabel->setContentsMargins(DEFAULT_LAYOUT_MARGIN, 0, 0, 0); clear(); } SideBarGroup::~SideBarGroup() { delete d; } void SideBarGroup::paintEvent(QPaintEvent* event) { QFrame::paintEvent(event); if (parentWidget()->layout()->indexOf(this) != 0) { // Draw a separator, but only if we are not the first group QPainter painter(this); QPen pen(palette().mid().color()); painter.setPen(pen); painter.drawLine(rect().topLeft(), rect().topRight()); } } void SideBarGroup::addWidget(QWidget* widget) { widget->setParent(d->mContainer); d->mContainer->layout()->addWidget(widget); } void SideBarGroup::clear() { if (d->mContainer) { d->mContainer->deleteLater(); } d->mContainer = new QFrame(this); QVBoxLayout* containerLayout = new QVBoxLayout(d->mContainer); containerLayout->setMargin(0); containerLayout->setSpacing(0); layout()->addWidget(d->mContainer); + if(d->mDefaultContainerMarginEnabled) { + d->mContainer->layout()->setContentsMargins(DEFAULT_LAYOUT_MARGIN, 0, 0, 0); + } } void SideBarGroup::addAction(QAction* action) { int size = KIconLoader::global()->currentSize(KIconLoader::Small); QToolButton* button = new SideBarButton(); button->setFocusPolicy(Qt::NoFocus); button->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); button->setAutoRaise(true); button->setDefaultAction(action); button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); button->setIconSize(QSize(size, size)); if (action->menu()) { button->setPopupMode(QToolButton::InstantPopup); } addWidget(button); } //- SideBarPage ---------------------------------------------------------------- struct SideBarPagePrivate { QString mTitle; QVBoxLayout* mLayout; }; SideBarPage::SideBarPage(const QString& title) : QWidget() , d(new SideBarPagePrivate) { d->mTitle = title; d->mLayout = new QVBoxLayout(this); d->mLayout->setMargin(0); } SideBarPage::~SideBarPage() { delete d; } const QString& SideBarPage::title() const { return d->mTitle; } void SideBarPage::addWidget(QWidget* widget) { d->mLayout->addWidget(widget); } void SideBarPage::addStretch() { d->mLayout->addStretch(); } //- SideBar -------------------------------------------------------------------- struct SideBarPrivate { }; SideBar::SideBar(QWidget* parent) : QTabWidget(parent) , d(new SideBarPrivate) { setFont(QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont)); tabBar()->setDocumentMode(true); tabBar()->setUsesScrollButtons(false); tabBar()->setFocusPolicy(Qt::NoFocus); setTabPosition(QTabWidget::South); setElideMode(Qt::ElideRight); } SideBar::~SideBar() { delete d; } QSize SideBar::sizeHint() const { return QSize(200, 200); } void SideBar::addPage(SideBarPage* page) { addTab(page, page->title()); } QString SideBar::currentPage() const { return currentWidget()->objectName(); } void SideBar::setCurrentPage(const QString& name) { for (int index = 0; index < count(); ++index) { if (widget(index)->objectName() == name) { setCurrentIndex(index); } } } } // namespace diff --git a/app/sidebar.h b/app/sidebar.h index 0a4b17b5..c21e87a3 100644 --- a/app/sidebar.h +++ b/app/sidebar.h @@ -1,88 +1,90 @@ /* Gwenview: an image viewer Copyright 2007 Aurélien Gâteau 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 Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef SIDEBAR_H #define SIDEBAR_H // Qt #include #include namespace Gwenview { +static const int DEFAULT_LAYOUT_MARGIN = 6; + class SideBar; struct SideBarGroupPrivate; class SideBarGroup : public QFrame { Q_OBJECT public: - SideBarGroup(const QString& title); + SideBarGroup(const QString& title, bool defaultContainerMarginEnabled = true); ~SideBarGroup(); void addWidget(QWidget*); void addAction(QAction*); void clear(); protected: void paintEvent(QPaintEvent*) Q_DECL_OVERRIDE; private: SideBarGroupPrivate* const d; }; struct SideBarPagePrivate; class SideBarPage : public QWidget { Q_OBJECT public: SideBarPage(const QString& title); ~SideBarPage(); void addWidget(QWidget*); void addStretch(); const QString& title() const; private: SideBarPagePrivate* const d; }; struct SideBarPrivate; class SideBar : public QTabWidget { Q_OBJECT public: SideBar(QWidget* parent); ~SideBar(); void addPage(SideBarPage*); QString currentPage() const; void setCurrentPage(const QString& name); QSize sizeHint() const Q_DECL_OVERRIDE; private: SideBarPrivate* const d; }; } // namespace #endif /* SIDEBAR_H */