代码拉取完成,页面将自动刷新
/*
Scan Tailor - Interactive post-processing tool for scanned pages.
Copyright (C) Joseph Artsimovich <[email protected]>
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 3 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, see <http://www.gnu.org/licenses/>.
*/
#include "ThumbnailSequence.h"
#include "ThumbnailSequence.h.moc"
#include "ThumbnailFactory.h"
#include "IncompleteThumbnail.h"
#include "PageSequence.h"
#include "PageOrderProvider.h"
#include "PageInfo.h"
#include "PageId.h"
#include "ImageId.h"
#include "RefCountable.h"
#include "IntrusivePtr.h"
#include "ScopedIncDec.h"
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index/mem_fun.hpp>
#include <boost/function.hpp>
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>
#include <boost/foreach.hpp>
#include <QGraphicsScene>
#include <QGraphicsItem>
#include <QGraphicsItemGroup>
#include <QGraphicsSimpleTextItem>
#include <QGraphicsPixmapItem>
#include <QGraphicsView>
#include <QStyle>
#include <QStyleOptionGraphicsItem>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsSceneContextMenuEvent>
#include <QPalette>
#include <QFont>
#include <QApplication>
#include <QVariant>
#include <QFileInfo>
#include <QPixmap>
#include <QRectF>
#include <QSizeF>
#include <QPointF>
#include <QPainter>
#include <QPainterPath>
#include <QTransform>
#include <QPen>
#include <QBrush>
#include <QColor>
#include <QString>
#include <QObject>
#include <QCursor>
#include <Qt>
#include <QDebug>
#include <algorithm>
#include <stddef.h>
#include <assert.h>
using namespace ::boost::multi_index;
using namespace ::boost::lambda;
class ThumbnailSequence::Item
{
public:
Item(PageInfo const& page_info, CompositeItem* comp_item);
PageId const& pageId() const { return pageInfo.id(); }
bool isSelected() const { return m_isSelected; }
bool isSelectionLeader() const { return m_isSelectionLeader; }
void setSelected(bool selected) const;
void setSelectionLeader(bool selection_leader) const;
PageInfo pageInfo;
mutable CompositeItem* composite;
mutable bool incompleteThumbnail;
private:
mutable bool m_isSelected;
mutable bool m_isSelectionLeader;
};
class ThumbnailSequence::GraphicsScene : public QGraphicsScene
{
public:
typedef boost::function<void (QGraphicsSceneContextMenuEvent*)> ContextMenuEventCallback;
void setContextMenuEventCallback(ContextMenuEventCallback callback) {
m_contextMenuEventCallback = callback;
}
protected:
virtual void contextMenuEvent(QGraphicsSceneContextMenuEvent* event) {
QGraphicsScene::contextMenuEvent(event);
if (!event->isAccepted() && m_contextMenuEventCallback) {
m_contextMenuEventCallback(event);
}
}
private:
ContextMenuEventCallback m_contextMenuEventCallback;
};
class ThumbnailSequence::Impl
{
public:
Impl(ThumbnailSequence& owner, QSizeF const& max_logical_thumb_size);
~Impl();
void setThumbnailFactory(IntrusivePtr<ThumbnailFactory> const& factory);
void attachView(QGraphicsView* view);
void reset(PageSequence const& pages,
SelectionAction const selection_action,
IntrusivePtr<PageOrderProvider const> const& provider);
IntrusivePtr<PageOrderProvider const> pageOrderProvider() const;
PageSequence toPageSequence() const;
void invalidateThumbnail(PageId const& page_id);
void invalidateThumbnail(PageInfo const& page_info);
void invalidateAllThumbnails();
bool setSelection(PageId const& page_id);
PageInfo selectionLeader() const;
PageInfo prevPage(PageId const& page_id) const;
PageInfo nextPage(PageId const& page_id) const;
PageInfo firstPage() const;
PageInfo lastPage() const;
void insert(PageInfo const& new_page,
BeforeOrAfter before_or_after, ImageId const& image);
void removePages(std::set<PageId> const& pages);
QRectF selectionLeaderSceneRect() const;
std::set<PageId> selectedItems() const;
std::vector<PageRange> selectedRanges() const;
void contextMenuRequested(
PageInfo const& page_info, QPoint const& screen_pos, bool selected);
void itemSelectedByUser(CompositeItem* item, Qt::KeyboardModifiers modifiers);
private:
class ItemsByIdTag;
class ItemsInOrderTag;
class SelectedThenUnselectedTag;
typedef multi_index_container<
Item,
indexed_by<
ordered_unique<
tag<ItemsByIdTag>,
const_mem_fun<Item, PageId const&, &Item::pageId>
>,
sequenced<tag<ItemsInOrderTag> >,
sequenced<tag<SelectedThenUnselectedTag> >
>
> Container;
typedef Container::index<ItemsByIdTag>::type ItemsById;
typedef Container::index<ItemsInOrderTag>::type ItemsInOrder;
typedef Container::index<SelectedThenUnselectedTag>::type SelectedThenUnselected;
void invalidateThumbnailImpl(ItemsById::iterator id_it);
void sceneContextMenuEvent(QGraphicsSceneContextMenuEvent* evt);
void selectItemNoModifiers(ItemsById::iterator const& it);
void selectItemWithControl(ItemsById::iterator const& it);
void selectItemWithShift(ItemsById::iterator const& it);
bool multipleItemsSelected() const;
void moveToSelected(Item const* item);
void moveToUnselected(Item const* item);
void clear();
void clearSelection();
/**
* Calculates the insertion position for an item with the given PageId
* based on m_ptrOrderProvider.
*
* \param begin Beginning of the interval to consider.
* \param end End of the interval to consider.
* \param page_id The item to find insertion position for.
* \param page_incomplete Whether the page is represented by IncompleteThumbnail.
* \param hint The place to start the search. Must be within [begin, end].
* \param dist_from_hint If provided, the distance from \p hint
* to the calculated insertion position will be written there.
* For example, \p dist_from_hint == -2 would indicate that the
* insertion position is two elements to the left of \p hint.
*/
ItemsInOrder::iterator itemInsertPosition(
ItemsInOrder::iterator begin, ItemsInOrder::iterator end,
PageId const& page_id, bool page_incomplete,
ItemsInOrder::iterator hint, int* dist_from_hint = 0);
std::auto_ptr<QGraphicsItem> getThumbnail(PageInfo const& page_info);
std::auto_ptr<LabelGroup> getLabelGroup(PageInfo const& page_info);
std::auto_ptr<CompositeItem> getCompositeItem(
Item const* item, PageInfo const& info);
void commitSceneRect();
static int const SPACING = 10;
ThumbnailSequence& m_rOwner;
QSizeF m_maxLogicalThumbSize;
Container m_items;
ItemsById& m_itemsById;
ItemsInOrder& m_itemsInOrder;
/**
* As the name implies, selected items go first here (in no particular order),
* then go unselected items (also in no particular order).
*/
SelectedThenUnselected& m_selectedThenUnselected;
Item const* m_pSelectionLeader;
IntrusivePtr<ThumbnailFactory> m_ptrFactory;
IntrusivePtr<PageOrderProvider const> m_ptrOrderProvider;
GraphicsScene m_graphicsScene;
QRectF m_sceneRect;
};
class ThumbnailSequence::PlaceholderThumb : public QGraphicsItem
{
public:
PlaceholderThumb(QSizeF const& max_size);
virtual QRectF boundingRect() const;
virtual void paint(QPainter* painter,
QStyleOptionGraphicsItem const* option, QWidget *widget);
private:
static QPainterPath m_sCachedPath;
QSizeF m_maxSize;
};
class ThumbnailSequence::LabelGroup : public QGraphicsItemGroup
{
public:
LabelGroup(std::auto_ptr<QGraphicsSimpleTextItem> label);
LabelGroup(
std::auto_ptr<QGraphicsSimpleTextItem> normal_label,
std::auto_ptr<QGraphicsSimpleTextItem> bold_label,
std::auto_ptr<QGraphicsPixmapItem> pixmap = std::auto_ptr<QGraphicsPixmapItem>());
void updateAppearence(bool selected, bool selection_leader);
private:
QGraphicsSimpleTextItem* m_pNormalLabel;
QGraphicsSimpleTextItem* m_pBoldLabel;
};
class ThumbnailSequence::CompositeItem : public QGraphicsItemGroup
{
public:
CompositeItem(
ThumbnailSequence::Impl& owner,
std::auto_ptr<QGraphicsItem> thumbnail,
std::auto_ptr<LabelGroup> label_group);
void setItem(Item const* item) { m_pItem = item; }
Item const* item() { return m_pItem; }
bool incompleteThumbnail() const;
void updateSceneRect(QRectF& scene_rect);
void updateAppearence(bool selected, bool selection_leader);
virtual QRectF boundingRect() const;
virtual void paint(QPainter* painter,
QStyleOptionGraphicsItem const* option, QWidget *widget);
protected:
virtual void contextMenuEvent(QGraphicsSceneContextMenuEvent* event);
virtual void mousePressEvent(QGraphicsSceneMouseEvent* event);
private:
// We no longer use QGraphicsView's selection mechanism, so we
// shadow isSelected() and setSelected() with unimplemented private
// functions. Just to be safe.
bool isSelected() const;
void setSelected(bool selected);
ThumbnailSequence::Impl& m_rOwner;
ThumbnailSequence::Item const* m_pItem;
QGraphicsItem* m_pThumb;
LabelGroup* m_pLabelGroup;
};
/*============================= ThumbnailSequence ===========================*/
ThumbnailSequence::ThumbnailSequence(QSizeF const& max_logical_thumb_size)
: m_ptrImpl(new Impl(*this, max_logical_thumb_size))
{
}
ThumbnailSequence::~ThumbnailSequence()
{
}
void
ThumbnailSequence::setThumbnailFactory(IntrusivePtr<ThumbnailFactory> const& factory)
{
m_ptrImpl->setThumbnailFactory(factory);
}
void
ThumbnailSequence::attachView(QGraphicsView* const view)
{
m_ptrImpl->attachView(view);
}
void
ThumbnailSequence::reset(
PageSequence const& pages,
SelectionAction const selection_action,
IntrusivePtr<PageOrderProvider const> const& order_provider)
{
m_ptrImpl->reset(pages, selection_action, order_provider);
}
IntrusivePtr<PageOrderProvider const>
ThumbnailSequence::pageOrderProvider() const
{
return m_ptrImpl->pageOrderProvider();
}
PageSequence
ThumbnailSequence::toPageSequence() const
{
return m_ptrImpl->toPageSequence();
}
void
ThumbnailSequence::invalidateThumbnail(PageId const& page_id)
{
m_ptrImpl->invalidateThumbnail(page_id);
}
void
ThumbnailSequence::invalidateThumbnail(PageInfo const& page_info)
{
m_ptrImpl->invalidateThumbnail(page_info);
}
void
ThumbnailSequence::invalidateAllThumbnails()
{
m_ptrImpl->invalidateAllThumbnails();
}
bool
ThumbnailSequence::setSelection(PageId const& page_id)
{
return m_ptrImpl->setSelection(page_id);
}
PageInfo
ThumbnailSequence::selectionLeader() const
{
return m_ptrImpl->selectionLeader();
}
PageInfo
ThumbnailSequence::prevPage(PageId const& reference_page) const
{
return m_ptrImpl->prevPage(reference_page);
}
PageInfo
ThumbnailSequence::nextPage(PageId const& reference_page) const
{
return m_ptrImpl->nextPage(reference_page);
}
PageInfo
ThumbnailSequence::firstPage() const
{
return m_ptrImpl->firstPage();
}
PageInfo
ThumbnailSequence::lastPage() const
{
return m_ptrImpl->lastPage();
}
void
ThumbnailSequence::insert(
PageInfo const& new_page,
BeforeOrAfter before_or_after, ImageId const& image)
{
m_ptrImpl->insert(new_page, before_or_after, image);
}
void
ThumbnailSequence::removePages(std::set<PageId> const& pages)
{
m_ptrImpl->removePages(pages);
}
QRectF
ThumbnailSequence::selectionLeaderSceneRect() const
{
return m_ptrImpl->selectionLeaderSceneRect();
}
std::set<PageId>
ThumbnailSequence::selectedItems() const
{
return m_ptrImpl->selectedItems();
}
std::vector<PageRange>
ThumbnailSequence::selectedRanges() const
{
return m_ptrImpl->selectedRanges();
}
void
ThumbnailSequence::emitNewSelectionLeader(
PageInfo const& page_info, CompositeItem const* composite,
SelectionFlags const flags)
{
QRectF const thumb_rect(
composite->mapToScene(composite->boundingRect()).boundingRect()
);
emit newSelectionLeader(page_info, thumb_rect, flags);
}
/*======================== ThumbnailSequence::Impl ==========================*/
ThumbnailSequence::Impl::Impl(
ThumbnailSequence& owner, QSizeF const& max_logical_thumb_size)
: m_rOwner(owner),
m_maxLogicalThumbSize(max_logical_thumb_size),
m_items(),
m_itemsById(m_items.get<ItemsByIdTag>()),
m_itemsInOrder(m_items.get<ItemsInOrderTag>()),
m_selectedThenUnselected(m_items.get<SelectedThenUnselectedTag>()),
m_pSelectionLeader(0)
{
m_graphicsScene.setContextMenuEventCallback(
bind(&Impl::sceneContextMenuEvent, this, _1)
);
}
ThumbnailSequence::Impl::~Impl()
{
}
void
ThumbnailSequence::Impl::setThumbnailFactory(
IntrusivePtr<ThumbnailFactory> const& factory)
{
m_ptrFactory = factory;
}
void
ThumbnailSequence::Impl::attachView(QGraphicsView* const view)
{
view->setScene(&m_graphicsScene);
}
void
ThumbnailSequence::Impl::reset(
PageSequence const& pages,
SelectionAction const selection_action,
IntrusivePtr<PageOrderProvider const> const& order_provider)
{
m_ptrOrderProvider = order_provider;
std::set<PageId> selected;
PageInfo selection_leader;
if (selection_action == KEEP_SELECTION) {
selectedItems().swap(selected);
if (m_pSelectionLeader) {
selection_leader = m_pSelectionLeader->pageInfo;
}
}
clear(); // Also clears the selection.
size_t const num_pages = pages.numPages();
if (num_pages == 0) {
return;
}
Item const* some_selected_item = 0;
for (size_t i = 0; i < num_pages; ++i) {
PageInfo const& page_info(pages.pageAt(i));
std::auto_ptr<CompositeItem> composite(getCompositeItem(0, page_info));
m_itemsInOrder.push_back(Item(page_info, composite.release()));
Item const* item = &m_itemsInOrder.back();
item->composite->setItem(item);
if (selected.find(page_info.id()) != selected.end()) {
item->setSelected(true);
moveToSelected(item);
some_selected_item = item;
}
if (page_info.id() == selection_leader.id()) {
m_pSelectionLeader = item;
}
}
invalidateAllThumbnails();
if (!m_pSelectionLeader) {
if (some_selected_item) {
m_pSelectionLeader = some_selected_item;
}
}
if (m_pSelectionLeader) {
m_pSelectionLeader->setSelectionLeader(true);
m_rOwner.emitNewSelectionLeader(
selection_leader, m_pSelectionLeader->composite, DEFAULT_SELECTION_FLAGS
);
}
}
IntrusivePtr<PageOrderProvider const>
ThumbnailSequence::Impl::pageOrderProvider() const
{
return m_ptrOrderProvider;
}
PageSequence
ThumbnailSequence::Impl::toPageSequence() const
{
PageSequence pages;
BOOST_FOREACH(Item const& item, m_itemsInOrder) {
pages.append(item.pageInfo);
}
return pages;
}
void
ThumbnailSequence::Impl::invalidateThumbnail(PageId const& page_id)
{
ItemsById::iterator const id_it(m_itemsById.find(page_id));
if (id_it != m_itemsById.end()) {
invalidateThumbnailImpl(id_it);
}
}
void
ThumbnailSequence::Impl::invalidateThumbnail(PageInfo const& page_info)
{
ItemsById::iterator const id_it(m_itemsById.find(page_info.id()));
if (id_it != m_itemsById.end()) {
m_itemsById.modify(id_it, bind(&Item::pageInfo, _1) = page_info);
invalidateThumbnailImpl(id_it);
}
}
void
ThumbnailSequence::Impl::invalidateThumbnailImpl(ItemsById::iterator const id_it)
{
std::auto_ptr<CompositeItem> composite(
getCompositeItem(&*id_it, id_it->pageInfo)
);
CompositeItem* const new_composite = composite.get();
CompositeItem* const old_composite = id_it->composite;
QSizeF const old_size(old_composite->boundingRect().size());
QSizeF const new_size(new_composite->boundingRect().size());
QPointF const old_pos(new_composite->pos());
new_composite->updateAppearence(id_it->isSelected(), id_it->isSelectionLeader());
m_graphicsScene.addItem(composite.release());
id_it->composite = new_composite;
id_it->incompleteThumbnail = new_composite->incompleteThumbnail();
delete old_composite;
ItemsInOrder::iterator after_old(m_items.project<ItemsInOrderTag>(id_it));
// Notice after_old++ below.
// Move our item to the beginning of m_itemsInOrder, to make it out of range
// we are going to pass to itemInsertPosition().
m_itemsInOrder.relocate(m_itemsInOrder.begin(), after_old++);
int dist = 0;
ItemsInOrder::iterator const after_new(
itemInsertPosition(
++m_itemsInOrder.begin(), m_itemsInOrder.end(),
id_it->pageInfo.id(), id_it->incompleteThumbnail,
after_old, &dist
)
);
// Move our item to its intended position.
m_itemsInOrder.relocate(after_new, m_itemsInOrder.begin());
// Now let's reposition the items on the scene.
ItemsInOrder::iterator ord_it, ord_end;
// The range of [ord_it, ord_end) is supposed to contain all items
// between the old and new positions of our item, with the new
// position in range.
if (dist <= 0) { // New position is before or equals to the old one.
ord_it = after_new;
--ord_it; // Include new item position in the range.
ord_end = after_old;
} else { // New position is after the old one.
ord_it = after_old;
ord_end = after_new;
}
double offset = 0;
if (ord_it != m_itemsInOrder.begin()) {
ItemsInOrder::iterator prev(ord_it);
--prev;
offset = prev->composite->pos().y() + prev->composite->boundingRect().height() + SPACING;
}
// Reposition items between the old and the new position of our item,
// including the item itself.
for (; ord_it != ord_end; ++ord_it) {
ord_it->composite->setPos(0.0, offset);
offset += ord_it->composite->boundingRect().height() + SPACING;
}
// Reposition the items following both the new and the old position
// of the item, if the item size has changed.
if (old_size != new_size) {
for (; ord_it != m_itemsInOrder.end(); ++ord_it) {
ord_it->composite->setPos(0.0, offset);
offset += ord_it->composite->boundingRect().height() + SPACING;
}
}
// Update scene rect.
m_sceneRect.setTop(m_sceneRect.bottom());
m_itemsInOrder.front().composite->updateSceneRect(m_sceneRect);
m_sceneRect.setBottom(m_sceneRect.top());
m_itemsInOrder.back().composite->updateSceneRect(m_sceneRect);
id_it->composite->updateSceneRect(m_sceneRect);
commitSceneRect();
// Possibly emit the newSelectionLeader() signal.
if (m_pSelectionLeader == &*id_it) {
if (old_size != new_size || old_pos != id_it->composite->pos()) {
m_rOwner.emitNewSelectionLeader(
id_it->pageInfo, id_it->composite, REDUNDANT_SELECTION
);
}
}
}
void
ThumbnailSequence::Impl::invalidateAllThumbnails()
{
// Recreate thumbnails now, whether a thumbnail is incomplete
// is taken into account when sorting.
ItemsInOrder::iterator ord_it(m_itemsInOrder.begin());
ItemsInOrder::iterator const ord_end(m_itemsInOrder.end());
for (; ord_it != ord_end; ++ord_it) {
CompositeItem* const old_composite = ord_it->composite;
ord_it->composite = getCompositeItem(&*ord_it, ord_it->pageInfo).release();
ord_it->incompleteThumbnail = ord_it->composite->incompleteThumbnail();
delete old_composite;
}
// Sort pages in m_itemsInOrder using m_ptrOrderProvider.
if (m_ptrOrderProvider.get()) {
m_itemsInOrder.sort(
bind(
&PageOrderProvider::precedes, m_ptrOrderProvider.get(),
bind(&Item::pageId, _1), bind(&Item::incompleteThumbnail, _1),
bind(&Item::pageId, _2), bind(&Item::incompleteThumbnail, _2)
)
);
}
m_sceneRect = QRectF(0.0, 0.0, 0.0, 0.0);
double offset = 0;
for (ord_it = m_itemsInOrder.begin(); ord_it != ord_end; ++ord_it) {
CompositeItem* composite = ord_it->composite;
composite->setPos(0.0, offset);
composite->updateSceneRect(m_sceneRect);
composite->updateAppearence(ord_it->isSelected(), ord_it->isSelectionLeader());
offset += composite->boundingRect().height() + SPACING;
m_graphicsScene.addItem(composite);
}
commitSceneRect();
}
bool
ThumbnailSequence::Impl::setSelection(PageId const& page_id)
{
ItemsById::iterator const id_it(m_itemsById.find(page_id));
if (id_it == m_itemsById.end()) {
return false;
}
bool const was_selection_leader = (&*id_it == m_pSelectionLeader);
// Clear selection from all items except the one for which
// selection is requested.
SelectedThenUnselected::iterator it(m_selectedThenUnselected.begin());
while (it != m_selectedThenUnselected.end()) {
Item const& item = *it;
if (!item.isSelected()) {
break;
}
++it;
if (&*id_it != &item) {
item.setSelected(false);
moveToUnselected(&item);
if (m_pSelectionLeader == &item) {
m_pSelectionLeader = 0;
}
}
}
if (!was_selection_leader) {
m_pSelectionLeader = &*id_it;
m_pSelectionLeader->setSelectionLeader(true);
moveToSelected(m_pSelectionLeader);
}
SelectionFlags flags = DEFAULT_SELECTION_FLAGS;
if (was_selection_leader) {
flags |= REDUNDANT_SELECTION;
}
m_rOwner.emitNewSelectionLeader(id_it->pageInfo, id_it->composite, flags);
return true;
}
PageInfo
ThumbnailSequence::Impl::selectionLeader() const
{
if (m_pSelectionLeader) {
return m_pSelectionLeader->pageInfo;
} else {
return PageInfo();
}
}
PageInfo
ThumbnailSequence::Impl::prevPage(PageId const& reference_page) const
{
ItemsInOrder::iterator ord_it;
if (m_pSelectionLeader && m_pSelectionLeader->pageInfo.id() == reference_page) {
// Common case optimization.
ord_it = m_itemsInOrder.iterator_to(*m_pSelectionLeader);
} else {
ord_it = m_items.project<ItemsInOrderTag>(m_itemsById.find(reference_page));
}
if (ord_it != m_itemsInOrder.end()) {
if (ord_it != m_itemsInOrder.begin()) {
--ord_it;
return ord_it->pageInfo;
}
}
return PageInfo();
}
PageInfo
ThumbnailSequence::Impl::nextPage(PageId const& reference_page) const
{
ItemsInOrder::iterator ord_it;
if (m_pSelectionLeader && m_pSelectionLeader->pageInfo.id() == reference_page) {
// Common case optimization.
ord_it = m_itemsInOrder.iterator_to(*m_pSelectionLeader);
} else {
ord_it = m_items.project<ItemsInOrderTag>(m_itemsById.find(reference_page));
}
if (ord_it != m_itemsInOrder.end()) {
++ord_it;
if (ord_it != m_itemsInOrder.end()) {
return ord_it->pageInfo;
}
}
return PageInfo();
}
PageInfo
ThumbnailSequence::Impl::firstPage() const
{
if (m_items.empty()) {
return PageInfo();
}
return m_itemsInOrder.front().pageInfo;
}
PageInfo
ThumbnailSequence::Impl::lastPage() const
{
if (m_items.empty()) {
return PageInfo();
}
return m_itemsInOrder.back().pageInfo;
}
void
ThumbnailSequence::Impl::insert(
PageInfo const& page_info,
BeforeOrAfter before_or_after, ImageId const& image)
{
ItemsInOrder::iterator ord_it;
if (before_or_after == BEFORE && image.isNull()) {
ord_it = m_itemsInOrder.end();
} else {
// Note that we have to use lower_bound() rather than find() because
// we are not searching for PageId(image) exactly, which implies
// PageId::SINGLE_PAGE configuration, but rather we search for
// a page with any configuration, as long as it references the same image.
ItemsById::iterator id_it(m_itemsById.lower_bound(PageId(image)));
if (id_it == m_itemsById.end() || id_it->pageInfo.imageId() != image) {
// Reference page not found.
return;
}
ord_it = m_items.project<ItemsInOrderTag>(id_it);
if (before_or_after == AFTER) {
++ord_it;
if (!m_ptrOrderProvider.get()) {
// Advance past not only the target page, but also its other half, if it follows.
while (ord_it != m_itemsInOrder.end() && ord_it->pageInfo.imageId() == image) {
++ord_it;
}
}
}
}
// If m_ptrOrderProvider is not set, ord_it won't change.
ord_it = itemInsertPosition(
m_itemsInOrder.begin(), m_itemsInOrder.end(), page_info.id(),
/*page_incomplete=*/true, ord_it
);
double offset = 0.0;
if (!m_items.empty()) {
if (ord_it != m_itemsInOrder.end()) {
offset = ord_it->composite->pos().y();
} else {
ItemsInOrder::iterator it(ord_it);
--it;
offset = it->composite->y()
+ it->composite->boundingRect().height() + SPACING;
}
}
std::auto_ptr<CompositeItem> composite(
getCompositeItem(0, page_info)
);
composite->setPos(0.0, offset);
composite->updateSceneRect(m_sceneRect);
QPointF const pos_delta(0.0, composite->boundingRect().height() + SPACING);
Item const item(page_info, composite.get());
std::pair<ItemsInOrder::iterator, bool> const ins(
m_itemsInOrder.insert(ord_it, item)
);
composite->setItem(&*ins.first);
m_graphicsScene.addItem(composite.release());
ItemsInOrder::iterator const ord_end(m_itemsInOrder.end());
for (; ord_it != ord_end; ++ord_it) {
ord_it->composite->setPos(ord_it->composite->pos() + pos_delta);
ord_it->composite->updateSceneRect(m_sceneRect);
}
commitSceneRect();
}
void
ThumbnailSequence::Impl::removePages(std::set<PageId> const& to_remove)
{
m_sceneRect = QRectF(0, 0, 0, 0);
std::set<PageId>::const_iterator const to_remove_end(to_remove.end());
QPointF pos_delta(0, 0);
ItemsInOrder::iterator ord_it(m_itemsInOrder.begin());
ItemsInOrder::iterator const ord_end(m_itemsInOrder.end());
while (ord_it != ord_end) {
if (to_remove.find(ord_it->pageInfo.id()) == to_remove_end) {
// Keeping this page.
if (pos_delta != QPointF(0, 0)) {
ord_it->composite->setPos(ord_it->composite->pos() + pos_delta);
}
ord_it->composite->updateSceneRect(m_sceneRect);
++ord_it;
} else {
// Removing this page.
if (m_pSelectionLeader == &*ord_it) {
m_pSelectionLeader = 0;
}
pos_delta.ry() -= ord_it->composite->boundingRect().height() + SPACING;
delete ord_it->composite;
m_itemsInOrder.erase(ord_it++);
}
}
commitSceneRect();
}
bool
ThumbnailSequence::Impl::multipleItemsSelected() const
{
SelectedThenUnselected::iterator it(m_selectedThenUnselected.begin());
SelectedThenUnselected::iterator const end(m_selectedThenUnselected.end());
for (int i = 0; i < 2; ++i, ++it) {
if (it == end || !it->isSelected()) {
return false;
}
}
return true;
}
void
ThumbnailSequence::Impl::moveToSelected(Item const* item)
{
m_selectedThenUnselected.relocate(
m_selectedThenUnselected.begin(),
m_selectedThenUnselected.iterator_to(*item)
);
}
void
ThumbnailSequence::Impl::moveToUnselected(Item const* item)
{
m_selectedThenUnselected.relocate(
m_selectedThenUnselected.end(),
m_selectedThenUnselected.iterator_to(*item)
);
}
QRectF
ThumbnailSequence::Impl::selectionLeaderSceneRect() const
{
if (!m_pSelectionLeader) {
return QRectF();
}
return m_pSelectionLeader->composite->mapToScene(
m_pSelectionLeader->composite->boundingRect()
).boundingRect();
}
std::set<PageId>
ThumbnailSequence::Impl::selectedItems() const
{
std::set<PageId> selection;
BOOST_FOREACH(Item const& item, m_selectedThenUnselected) {
if (!item.isSelected()) {
break;
}
selection.insert(item.pageInfo.id());
}
return selection;
}
std::vector<PageRange>
ThumbnailSequence::Impl::selectedRanges() const
{
std::vector<PageRange> ranges;
ItemsInOrder::iterator it(m_itemsInOrder.begin());
ItemsInOrder::iterator const end(m_itemsInOrder.end());
for (;;) {
for (; it != end && !it->isSelected(); ++it) {
// Skip unselected items.
}
if (it == end) {
break;
}
ranges.push_back(PageRange());
PageRange& range = ranges.back();
for (; it != end && it->isSelected(); ++it) {
range.pages.push_back(it->pageInfo.id());
}
}
return ranges;
}
void
ThumbnailSequence::Impl::contextMenuRequested(
PageInfo const& page_info, QPoint const& screen_pos, bool selected)
{
emit m_rOwner.pageContextMenuRequested(page_info, screen_pos, selected);
}
void
ThumbnailSequence::Impl::sceneContextMenuEvent(QGraphicsSceneContextMenuEvent* evt)
{
if (!m_itemsInOrder.empty()) {
CompositeItem* composite = m_itemsInOrder.back().composite;
QRectF const last_thumb_rect(
composite->mapToScene(composite->boundingRect()).boundingRect()
);
if (evt->scenePos().y() <= last_thumb_rect.bottom()) {
return;
}
}
emit m_rOwner.pastLastPageContextMenuRequested(evt->screenPos());
}
void
ThumbnailSequence::Impl::itemSelectedByUser(
CompositeItem* composite, Qt::KeyboardModifiers const modifiers)
{
ItemsById::iterator const id_it(m_itemsById.iterator_to(*composite->item()));
if (modifiers & Qt::ControlModifier) {
selectItemWithControl(id_it);
} else if (modifiers & Qt::ShiftModifier) {
selectItemWithShift(id_it);
} else {
selectItemNoModifiers(id_it);
}
}
void
ThumbnailSequence::Impl::selectItemWithControl(ItemsById::iterator const& id_it)
{
SelectionFlags flags = SELECTED_BY_USER;
if (!id_it->isSelected()) {
if (m_pSelectionLeader) {
m_pSelectionLeader->setSelectionLeader(false);
}
m_pSelectionLeader = &*id_it;
m_pSelectionLeader->setSelectionLeader(true);
moveToSelected(m_pSelectionLeader);
m_rOwner.emitNewSelectionLeader(
m_pSelectionLeader->pageInfo,
m_pSelectionLeader->composite, flags
);
return;
}
if (!multipleItemsSelected()) {
// Clicked on the only selected item.
flags |= REDUNDANT_SELECTION;
m_rOwner.emitNewSelectionLeader(
m_pSelectionLeader->pageInfo,
m_pSelectionLeader->composite, flags
);
return;
}
// Unselect it.
id_it->setSelected(false);
moveToUnselected(&*id_it);
if (m_pSelectionLeader != &*id_it) {
// The selection leader remains the same - we are done.
return;
}
// Select the new selection leader among other selected items.
m_pSelectionLeader = 0;
flags |= AVOID_SCROLLING_TO;
ItemsInOrder::iterator ord_it1(m_items.project<ItemsInOrderTag>(id_it));
ItemsInOrder::iterator ord_it2(ord_it1);
for (;;) {
if (ord_it1 != m_itemsInOrder.begin()) {
--ord_it1;
if (ord_it1->isSelected()) {
m_pSelectionLeader = &*ord_it1;
break;
}
}
if (ord_it2 != m_itemsInOrder.end()) {
++ord_it2;
if (ord_it2 != m_itemsInOrder.end()) {
if (ord_it2->isSelected()) {
m_pSelectionLeader = &*ord_it2;
break;
}
}
}
}
assert(m_pSelectionLeader); // We had multiple selected items.
m_pSelectionLeader->setSelectionLeader(true);
// No need to moveToSelected() as it was and remains selected.
m_rOwner.emitNewSelectionLeader(
m_pSelectionLeader->pageInfo, m_pSelectionLeader->composite, flags
);
}
void
ThumbnailSequence::Impl::selectItemWithShift(ItemsById::iterator const& id_it)
{
if (!m_pSelectionLeader) {
selectItemNoModifiers(id_it);
return;
}
SelectionFlags flags = SELECTED_BY_USER;
if (m_pSelectionLeader == &*id_it) {
flags |= REDUNDANT_SELECTION;
}
// Select all the items between the selection leader and the item that was clicked.
ItemsInOrder::iterator endpoint1(m_itemsInOrder.iterator_to(*m_pSelectionLeader));
ItemsInOrder::iterator endpoint2(m_items.project<ItemsInOrderTag>(id_it));
if (endpoint1 == endpoint2) {
// One-element sequence, already selected.
return;
}
// The problem is that we don't know which endpoint precedes the other.
// Let's find out.
ItemsInOrder::iterator ord_it1(endpoint1);
ItemsInOrder::iterator ord_it2(endpoint1);
for (;;) {
if (ord_it1 != m_itemsInOrder.begin()) {
--ord_it1;
if (ord_it1 == endpoint2) {
// endpoint2 was found before endpoint1.
std::swap(endpoint1, endpoint2);
break;
}
}
if (ord_it2 != m_itemsInOrder.end()) {
++ord_it2;
if (ord_it2 != m_itemsInOrder.end()) {
if (ord_it2 == endpoint2) {
// endpoint2 was found after endpoint1.
break;
}
}
}
}
++endpoint2; // Make the interval inclusive.
for (; endpoint1 != endpoint2; ++endpoint1) {
endpoint1->setSelected(true);
moveToSelected(&*endpoint1);
}
// Switch the selection leader.
assert(m_pSelectionLeader);
m_pSelectionLeader->setSelectionLeader(false);
m_pSelectionLeader = &*id_it;
m_pSelectionLeader->setSelectionLeader(true);
m_rOwner.emitNewSelectionLeader(id_it->pageInfo, id_it->composite, flags);
}
void
ThumbnailSequence::Impl::selectItemNoModifiers(ItemsById::iterator const& id_it)
{
SelectionFlags flags = SELECTED_BY_USER;
if (m_pSelectionLeader == &*id_it) {
flags |= REDUNDANT_SELECTION;
}
clearSelection();
m_pSelectionLeader = &*id_it;
m_pSelectionLeader->setSelectionLeader(true);
moveToSelected(m_pSelectionLeader);
m_rOwner.emitNewSelectionLeader(id_it->pageInfo, id_it->composite, flags);
}
void
ThumbnailSequence::Impl::clear()
{
m_pSelectionLeader = 0;
ItemsInOrder::iterator it(m_itemsInOrder.begin());
ItemsInOrder::iterator const end(m_itemsInOrder.end());
while (it != end) {
delete it->composite;
m_itemsInOrder.erase(it++);
}
assert(m_graphicsScene.items().empty());
m_sceneRect = QRectF(0.0, 0.0, 0.0, 0.0);
commitSceneRect();
}
void
ThumbnailSequence::Impl::clearSelection()
{
m_pSelectionLeader = 0;
BOOST_FOREACH(Item const& item, m_selectedThenUnselected) {
if (!item.isSelected()) {
break;
}
item.setSelected(false);
}
}
ThumbnailSequence::Impl::ItemsInOrder::iterator
ThumbnailSequence::Impl::itemInsertPosition(
ItemsInOrder::iterator const begin, ItemsInOrder::iterator const end,
PageId const& page_id, bool const page_incomplete,
ItemsInOrder::iterator const hint, int* dist_from_hint)
{
// Note that to preserve stable ordering, this function *must* return hint,
// as long as it's an acceptable position.
if (!m_ptrOrderProvider.get()) {
if (dist_from_hint) {
*dist_from_hint = 0;
}
return hint;
}
ItemsInOrder::iterator ins_pos(hint);
int dist = 0;
// While the element immediately preceeding ins_pos is supposed to
// follow the page we are inserting, move ins_pos one element back.
while (ins_pos != begin) {
ItemsInOrder::iterator prev(ins_pos);
--prev;
bool const precedes = m_ptrOrderProvider->precedes(
page_id, page_incomplete, prev->pageId(), prev->incompleteThumbnail
);
if (precedes) {
ins_pos = prev;
--dist;
} else {
break;
}
}
// While the element pointed to by ins_pos is supposed to precede
// the page we are inserting, advance ins_pos.
while (ins_pos != end) {
bool const precedes = m_ptrOrderProvider->precedes(
ins_pos->pageId(), ins_pos->incompleteThumbnail,
page_id, page_incomplete
);
if (precedes) {
++ins_pos;
++dist;
} else {
break;
}
}
if (dist_from_hint) {
*dist_from_hint = dist;
}
return ins_pos;
}
std::auto_ptr<QGraphicsItem>
ThumbnailSequence::Impl::getThumbnail(PageInfo const& page_info)
{
std::auto_ptr<QGraphicsItem> thumb;
if (m_ptrFactory.get()) {
thumb = m_ptrFactory->get(page_info);
}
if (!thumb.get()) {
thumb.reset(new PlaceholderThumb(m_maxLogicalThumbSize));
}
return thumb;
}
std::auto_ptr<ThumbnailSequence::LabelGroup>
ThumbnailSequence::Impl::getLabelGroup(PageInfo const& page_info)
{
PageId const& page_id = page_info.id();
QFileInfo const file_info(page_id.imageId().filePath());
QString const file_name(file_info.fileName());
QString text(file_name);
if (page_info.imageId().isMultiPageFile()) {
text = ThumbnailSequence::tr(
"%1 (page %2)"
).arg(file_name).arg(page_id.imageId().page());
}
std::auto_ptr<QGraphicsSimpleTextItem> normal_text_item(new QGraphicsSimpleTextItem);
normal_text_item->setText(text);
std::auto_ptr<QGraphicsSimpleTextItem> bold_text_item(new QGraphicsSimpleTextItem);
bold_text_item->setText(text);
QFont bold_font(bold_text_item->font());
bold_font.setWeight(QFont::Bold);
bold_text_item->setFont(bold_font);
bold_text_item->setBrush(QApplication::palette().highlightedText());
QRectF normal_text_box(normal_text_item->boundingRect());
QRectF bold_text_box(bold_text_item->boundingRect());
normal_text_box.moveCenter(bold_text_box.center());
normal_text_box.moveRight(bold_text_box.right());
normal_text_item->setPos(normal_text_box.topLeft());
bold_text_item->setPos(bold_text_box.topLeft());
char const* pixmap_resource = 0;
switch (page_id.subPage()) {
case PageId::LEFT_PAGE:
pixmap_resource = ":/icons/left_page_thumb.png";
break;
case PageId::RIGHT_PAGE:
pixmap_resource = ":/icons/right_page_thumb.png";
break;
default:
return std::auto_ptr<LabelGroup>(new LabelGroup(normal_text_item, bold_text_item));
}
QPixmap const pixmap(pixmap_resource);
std::auto_ptr<QGraphicsPixmapItem> pixmap_item(new QGraphicsPixmapItem);
pixmap_item->setPixmap(pixmap);
int const label_pixmap_spacing = 5;
QRectF pixmap_box(pixmap_item->boundingRect());
pixmap_box.moveCenter(bold_text_box.center());
pixmap_box.moveLeft(bold_text_box.right() + label_pixmap_spacing);
pixmap_item->setPos(pixmap_box.topLeft());
return std::auto_ptr<LabelGroup>(new LabelGroup(normal_text_item, bold_text_item, pixmap_item));
}
std::auto_ptr<ThumbnailSequence::CompositeItem>
ThumbnailSequence::Impl::getCompositeItem(
Item const* item, PageInfo const& page_info)
{
std::auto_ptr<QGraphicsItem> thumb(getThumbnail(page_info));
std::auto_ptr<LabelGroup> label_group(getLabelGroup(page_info));
std::auto_ptr<CompositeItem> composite(
new CompositeItem(*this, thumb, label_group)
);
composite->setItem(item);
return composite;
}
void
ThumbnailSequence::Impl::commitSceneRect()
{
if (m_sceneRect.isNull()) {
m_graphicsScene.setSceneRect(QRectF(0.0, 0.0, 1.0, 1.0));
} else {
m_graphicsScene.setSceneRect(m_sceneRect);
}
}
/*==================== ThumbnailSequence::Item ======================*/
ThumbnailSequence::Item::Item(PageInfo const& page_info, CompositeItem* comp_item)
: pageInfo(page_info),
composite(comp_item),
incompleteThumbnail(comp_item->incompleteThumbnail()),
m_isSelected(false),
m_isSelectionLeader(false)
{
}
void
ThumbnailSequence::Item::setSelected(bool selected) const
{
bool const was_selected = m_isSelected;
bool const was_selection_leader = m_isSelectionLeader;
m_isSelected = selected;
m_isSelectionLeader = m_isSelectionLeader && selected;
if (was_selected != m_isSelected || was_selection_leader != m_isSelectionLeader) {
composite->updateAppearence(m_isSelected, m_isSelectionLeader);
}
if (was_selected != m_isSelected) {
composite->update();
}
}
void
ThumbnailSequence::Item::setSelectionLeader(bool selection_leader) const
{
bool const was_selected = m_isSelected;
bool const was_selection_leader = m_isSelectionLeader;
m_isSelected = m_isSelected || selection_leader;
m_isSelectionLeader = selection_leader;
if (was_selected != m_isSelected || was_selection_leader != m_isSelectionLeader) {
composite->updateAppearence(m_isSelected, m_isSelectionLeader);
}
if (was_selected != m_isSelected) {
composite->update();
}
}
/*================== ThumbnailSequence::PlaceholderThumb ====================*/
QPainterPath ThumbnailSequence::PlaceholderThumb::m_sCachedPath;
ThumbnailSequence::PlaceholderThumb::PlaceholderThumb(QSizeF const& max_size)
: m_maxSize(max_size)
{
}
QRectF
ThumbnailSequence::PlaceholderThumb::boundingRect() const
{
return QRectF(QPointF(0.0, 0.0), m_maxSize);
}
void
ThumbnailSequence::PlaceholderThumb::paint(
QPainter* painter, QStyleOptionGraphicsItem const*, QWidget*)
{
IncompleteThumbnail::drawQuestionMark(*painter, boundingRect());
}
/*====================== ThumbnailSequence::LabelGroup ======================*/
ThumbnailSequence::LabelGroup::LabelGroup(
std::auto_ptr<QGraphicsSimpleTextItem> normal_label,
std::auto_ptr<QGraphicsSimpleTextItem> bold_label,
std::auto_ptr<QGraphicsPixmapItem> pixmap)
: m_pNormalLabel(normal_label.get()),
m_pBoldLabel(bold_label.get())
{
m_pNormalLabel->setVisible(true);
m_pBoldLabel->setVisible(false);
addToGroup(normal_label.release());
addToGroup(bold_label.release());
if (pixmap.get()) {
addToGroup(pixmap.release());
}
}
void
ThumbnailSequence::LabelGroup::updateAppearence(bool selected, bool selection_leader)
{
m_pNormalLabel->setVisible(!selection_leader);
m_pBoldLabel->setVisible(selection_leader);
if (selection_leader) {
assert(selected);
} else if (selected) {
m_pNormalLabel->setBrush(QApplication::palette().highlightedText());
} else {
m_pNormalLabel->setBrush(QApplication::palette().text());
}
}
/*==================== ThumbnailSequence::CompositeItem =====================*/
ThumbnailSequence::CompositeItem::CompositeItem(
ThumbnailSequence::Impl& owner,
std::auto_ptr<QGraphicsItem> thumbnail,
std::auto_ptr<LabelGroup> label_group)
: m_rOwner(owner),
m_pItem(0),
m_pThumb(thumbnail.get()),
m_pLabelGroup(label_group.get())
{
QSizeF const thumb_size(thumbnail->boundingRect().size());
QSizeF const label_size(label_group->boundingRect().size());
int const thumb_label_spacing = 1;
thumbnail->setPos(-0.5 * thumb_size.width(), 0.0);
label_group->setPos(
thumbnail->pos().x() + thumb_size.width() - label_size.width(),
thumb_size.height() + thumb_label_spacing
);
addToGroup(thumbnail.release());
addToGroup(label_group.release());
setCursor(Qt::PointingHandCursor);
setZValue(-1);
}
bool
ThumbnailSequence::CompositeItem::incompleteThumbnail() const
{
return dynamic_cast<IncompleteThumbnail*>(m_pThumb) != 0;
}
void
ThumbnailSequence::CompositeItem::updateSceneRect(QRectF& scene_rect)
{
QRectF rect(m_pThumb->boundingRect());
rect.translate(m_pThumb->pos());
rect.translate(pos());
QRectF bounding_rect(boundingRect());
bounding_rect.translate(pos());
rect.setTop(bounding_rect.top());
rect.setBottom(bounding_rect.bottom());
scene_rect |= rect;
}
void
ThumbnailSequence::CompositeItem::updateAppearence(bool selected, bool selection_leader)
{
m_pLabelGroup->updateAppearence(selected, selection_leader);
}
QRectF
ThumbnailSequence::CompositeItem::boundingRect() const
{
QRectF rect(QGraphicsItemGroup::boundingRect());
rect.adjust(-100, -5, 100, 3);
return rect;
}
void
ThumbnailSequence::CompositeItem::paint(
QPainter* painter, QStyleOptionGraphicsItem const* option, QWidget *widget)
{
if (m_pItem->isSelected()) {
painter->fillRect(
boundingRect(),
QApplication::palette().color(QPalette::Highlight)
);
}
}
void
ThumbnailSequence::CompositeItem::mousePressEvent(
QGraphicsSceneMouseEvent* const event)
{
QGraphicsItemGroup::mousePressEvent(event);
event->accept();
if (event->button() == Qt::LeftButton) {
m_rOwner.itemSelectedByUser(this, event->modifiers());
}
}
void
ThumbnailSequence::CompositeItem::contextMenuEvent(
QGraphicsSceneContextMenuEvent* const event)
{
event->accept(); // Prevent it from propagating further.
m_rOwner.contextMenuRequested(
m_pItem->pageInfo, event->screenPos(), m_pItem->isSelected()
);
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。