diff --git a/doc/index.docbook b/doc/index.docbook
index 9bc0fea7..367f25eb 100644
--- a/doc/index.docbook
+++ b/doc/index.docbook
@@ -1,905 +1,904 @@
]>
Gwenview User ManualAurélienGâteauagateau@kde.orgChristopherMartinchrsmrtn@debian.orgHenryde Valencehdevalence@gmail.com2005Aurélien Gâteau2008Henry de Valence&FDLNotice;
-2018-04-09
+2018-07-13Applications 18.08&gwenview; is an image and video viewer.KDEimageviewerartistphotopictureIntroductionWhat is &gwenview;
&gwenview; is a fast and easy to use image and video viewer.
&gwenview; features two main modes: Browse and View. Both modes can be used
in a normal application window and Full Screen. Browse Mode
lets you navigate through your computer showing thumbnails of your images, View
Mode lets you view images one at a time, and Full Screen lets you make quick
slideshows. There is also a start screen that displays a list of recently opened
folders and &URL;s as well as your places and tags.
Image loading is handled by the &Qt; library, so &gwenview; supports all
image formats your &Qt; installation supports. &gwenview; correctly displays images
with an alpha channel (transparency) as well as animations.&gwenview; supports the displaying and editing of EXIF comments in JPEG
images. Lossless JPEG transforms such as rotations and mirroring are also supported.&gwenview; can read embedded color profiles from PNG and JPEG files.
It can use the image color profile together with the display color profile
to output correct colors on the screen.The InterfaceStart PageThe start page lists recently opened folders and &URL;s on the left side,
and your places and tags on the right side.Start Page ScreenshotImage Operations&gwenview; has a few features which are available in both Browse, View, and
Full Screen view. &gwenview; has the capability to do basic alteration of your
images.Rotate: A rotate operation will rotate the image either to the left
or to the right, depending on whether you do
&Ctrl;REditRotate Right
or
&Ctrl;LEditRotate LeftEditMirror:
This operation will reflect the image along the vertical
axis, as if you were seeing it in a mirror.EditFlip:
This operation will reflect the image upside-down (a
reflection along the horizontal axis)&Shift;REditResize:
This operation will allow you to shrink or expand the image. Note that if you
increase the size of an image, it may appear
blurry and/or pixelated.These actions are also available on the Operations tab of the sidebar.If you have edited one or more images a bar with additional actions is displayed at the top of the image.
You can undo or redo your changes, go to the previous or next modified image and there are three options to save
the changed images.
Actions bar for modified imagesIf you have installed the KIPI Plugins, a Plugins
menu will be available that will allow you to perform many additional operations
on your images.Browse ModeWhen in Browse Mode, you can easily navigate through your files and folders.
The preview window shows thumbnails of the images in the current folder, as
well as subfolders. Browse Mode Screenshot
Moving the mouse over an image shows buttons to select or rotate the image as well as
a button to enter Fullscreen Mode.
Modified images are indicated by an icon down right, click it to save the changed image.
Clicking on an image takes you into View
Mode. You may select multiple images and switch to View Mode to view them side-by-side.The slider at the bottom right allows you to change the size of the images.
You can also filter the images by filename, date, tag or rating using the box on the
lower left. The toolbar appears in both Browse mode as well as View
mode and contains the most commonly used actions. Start Page: Open the start page.Browse: Switches to Browse Mode.View: Switches to View Mode.Full Screen: Switches to Full Screen Mode.Previous: Clicking this icon will go
to the previous image in the folder.Next: Clicking this button will go to
the next image in the folder.Rotate Left/Right: Same as discussed in
Image OperationsShare: Clicking this button opens an export menu to share your images through social
media and image collection web services. This functionality depends on the list of installed
KIPI plugins.View ModeView Mode displays full-size images. The same
sidebar available in Browse Mode is displayed on
the left. At the bottom, there is the Thumbnail Bar, which allows you to scroll
through the images in the current folder. The Thumbnail Bar can be minimized by
clicking on the Thumbnail Bar button. Clicking again will
restore it. To change the size of the thumbnails move the splitter with the &LMB;.View Mode supports viewing multiple images side-by-side. You may select
multiple images in Browse Mode before switching to View Mode, or you may click the
+ button that appears when hovering over images in the
Thumbnail Bar to add a pane displaying that image. A - will
then appear that will permit you to remove its pane.When multiple images are displayed, a small toolbar appears below each image
that permits you to delete the image or remove its pane. You may perform zoom operations
independently for each image, or synchronize them. Toggle this by checking the
Synchronize to the left of the zoom slider or by pressing
&Ctrl;Y. You can switch images
by clicking on their pane, or using your keyboard. To switch to the image on the
right, press 	. To switch to the image
on the left, press &Shift;	.View Mode ScreenshotThe slider at the bottom right controls the zoom of the image. The
Fit, Fill and 100%
buttons are next to the zoom slider and are three preset zoom levels. The
Fit button zooms the current image to fit the size
of the window, the Fill button zooms the image to fill the window
by fitting width or height and the 100% button zooms the image to
-the actual pixel size. The shortcut F switches to fit mode.
+the actual pixel size. The shortcut F toggles between Fit mode and 100%, pressing &Shift;F will toggle Fill respectively.
When an image is in zoom-to-fit mode, you can go to the previous and next
image with the arrow keys. When you zoom in, arrow keys are used to scroll the image.
This is very similar to the behavior provided by phones or digital cameras.When an image is zoomed in, a bird-eye view appears and lets you scroll
the image using the mouse and the arrow keys. The bird-eye view automatically hides
itself after a short delay, showing back only while zooming or scrolling.You can define what happens when going to image B after having zoomed in on an area of image A using the options in the Zoom mode group on the Image View page of the &gwenview; configuration window which can be reached using the SettingsConfigure &gwenview;....If set to Autofit each image, image B is zoomed out to fit the screen.If set to Keep same zoom and position, all images share the same zoom and position: image B is set to the same zoom parameters as image A (and if these are changed, image A will then be displayed with the updated zoom and position).If set to Per image zoom and position, all images remember their own zoom and position: image B is initially set to the same zoom parameters as image A, but will then remember its own zoom and position (if these are changed, image A will not be displayed with the updated zoom and position).You can start directly in View mode by starting &gwenview; from a
context menu like Open With in another program or by
launching it from the command line with an image as an argument.The following additional image operations are available only in View Mode:&Shift;CEditCrop:
This operation lets you discard parts of the image you don't want.
You can access the advanced cropping parameters by ticking Advanced settings check box on the bottom popup pane. Use the corresponding fields to tune up the cropping operation.
It is also possible to adjust the cropped area by dragging the gray square handles on the borders of the image. You can move the cropped area by clicking and holding the &LMB; and drag it with the mouse pointer.
Press the Crop button to see the results when you are ready. Use the upper popup pane to save the results or undo/redo the operation.
EditRed Eye Reduction:
This operation reduces the "red eye" effect commonly found in photographs taken
with a flash camera.
Full Screen ModesAccess Full Screen by pressing the Full Screen
button on the toolbar, or by
&Ctrl;&Shift;FViewFull Screen Mode.
To leave this mode press the &Esc; key.Browse Mode Full ScreenIn Browse Mode you can switch to fullscreen also by clicking on the button that
appears when you move the mouse over the thumbnails.
Full Screen View Mode ScreenshotGoing fullscreen while browsing gives you a more immersive experience while
you go through your pictures. It is quite nice on your regular computer, but makes
even more sense when you connect your laptop to the big TV in the living room to show
pictures to your guests.
View Mode Full ScreenThe full screen View Mode shows a slideshow of your images. Access Full Screen
Mode by clicking on the button that appears when you move the mouse over the
thumbnails in Browse Mode, by pressing the Full Screen
button on the taskbar.
Full Screen Browse Mode ScreenshotThe top bar will hide itself automatically; to show it simply move the
mouse to the top of the screen. If the mouse cursor is over the top bar, it
will not autohide. Most of the buttons on the bar are the same as the ones on
the toolbar in Browse or View Modes, except for the Exit Full
Screen Mode button which returns you to the &gwenview; window, the
Pause/Resume Slideshow button, and the
Configure Full Screen Mode button which shows a
small settings dialog that allows you to easily and quickly configure the
slideshow. The slideshow controls there are: The Interval slider controls how long
&gwenview; will show an image before it move to the next one.If the Loop check box is checked, when the
end of the slideshow is reached, it will continue from the beginning instead
of stopping. If the Random check box is checked,
instead of progressing through the folder alphabetically, images will be
shown in random order.Select Image Information to Display allows
you to define what metadata is displayed under the buttons on the toolbar.
If the Show thumbnails check box is checked,
thumbnails for all images in the current folder will be displayed to the right of
the toolbar.The Height slider changes the size of the
thumbnails displayed.If enabled, an area that shows you the other images in the current folder will
be shown on the top bar. Clicking on one will display it.SidebarThe sidebar on the left is available in the Browse and View modes, but does
not appear by default in Browse Mode. Its appearance can be toggled using
F4ViewSidebar
or using the ▮← / ▮→ button at the left side
of the statusbar. When clicked it collapses or expands the sidebar.
The sidebar contains several tabs:FoldersDisplays a list of all folders on your system permitting you to
switch between them. In Browse Mode thumbnails from the folder will be displayed,
while in View Mode the first image in the folder will appear, from which you can
browse through the folder using the Previous and
Next buttons or shortcuts. Clicking on a folder multiple
times toggles between View Mode and Browse Mode.InformationDisplays Meta Information like the filename
and size. The More... link permits you to view all available
metadata and select which data appear in the sidebar.OperationsThis permits you to perform the
previously described global image operations
as well as those specific to View Mode. It also permits common file operations like
copying, renaming, deleting, and creating new folders.&gwenview; ImporterIntroductionThe &gwenview; Importer allows you to import images from a digital camera or
removable media. To launch it, select Download Photos with &gwenview;
in the &kde; Device Notifier after connecting a supported device.When you plug in a device the &gwenview; importer recursively lists all images and videos.
This is not always what you expect, ⪚ plugging a smartphone you do not want to list all medias
of the device; but only the pictures you took, which are usually in a special subfolder.
Root Folder PickingIt is possible to select the root folder to list, and &gwenview; Importer will remember the
last root folder for each device. This way, next time you plug a device in, only the relevant
pictures and videos should be listed.
Importing Images&gwenview; Importer ScreenshotIf you wish, you may select the images you want to import under
Select the documents to import by pressing the +
button that appears when hovering over an image. You may also select the folder
that images are imported to in the text box at the bottom of the window. When you are done, click the
Import Selected button to import only the images you have
selected, or click Import All to import all images found
on the device.Automatic Renaming&gwenview; Importer can rename your files according to a specified pattern.
To configure this, select the Settings in the lower left
corner. You may turn this feature on or off using the check box at the top. The
Rename Format supports several special parameters, which will
be replaced by metadata such as the date the image was created or its filename.
They are listed below the text box. You may either click on the parameter name to
enter it into the text box or type one manually.TipsUsing the mousePanning with the mouseHolding down the left mouse button on an image allows you to
scroll the image.The mouse wheel will scroll the image up and
down.Zooming with the mouse
- Clicking the middle mouse button will toggle the auto zoom
- on/off.
+ Clicking the middle mouse button will toggle the Fit zoom button. Hold down the &Shift; key and middle-click to toggle the Fill zoom button.Hold down the &Ctrl; key, then either use the mouse wheel to
zoom in and out or left click to zoom in and right click to zoom
out.The mouse wheel, used while holding down the &Alt; key, will
scroll the image horizontally.Browsing with the mouseWhen in Browse mode, clicking an image switches into View mode and
shows that image.When in Browse mode, scrolling the mouse wheel will scroll up
or down the thumbnail view area.If the Mouse wheel behavior option in
SettingsConfigure
&gwenview;
is set to Browse, scrolling the mouse wheel while in View Mode will
move you through the images in the folder.Key bindings&gwenview; comes with a range of keyboard shortcuts, all of which can be
viewed and remapped by selecting
SettingsConfigure
Shortcuts.... Note that in the Files and Folders
windows, all the normal &plasma; shortcuts are functional, unless otherwise
remapped.A few of the most useful default bindings are:Space: Displays the next image
in the directory.&Backspace;:
Displays the previous image in the directory.&Alt;Up:
Moves to the parent folder of the current folder.&Ctrl;&Shift;F:
Switches into Full Screen Mode.&Esc;: Switches back to Browse Mode.
&Ctrl;M:
Show or hide the menubar.&Ctrl;B:
Show or hide the Thumbnail bar.F4:
Show or hide the Sidebar.F6:
Make the Location bar editable so that you can directly type in a file path.
You can return to the standard Location Bar by pressing the arrow at the
right.&Ctrl;R:
Rotate the current image to the right.&Ctrl;L:
Rotate the current image to the left.&Shift;R:
Resize the current image.&Shift;C:
Crop the current image.&Ctrl;Y:
When multiple images are displayed in View Mode, this synchronizes their views.
&Ctrl;S:
Save any changes made to the image.Del:
Move the current image to the trash.&Shift;Del:
Permanently delete the image from the disk. Note that this operation is
irreversible and cannot be undone.&Ctrl;P:
Print the current image.&Ctrl;O:
Open an image using the standard file selection dialog.F:
Pressing this shortcut toggles zoom-to-fit on and off.P:
Viewing a video this shortcut toggles playback on and off.&Ctrl;T:
Edit tags.F2:
Rename an image inline.Del:
Move an image to the trash.&Shift;Del:
Delete an image.&Ctrl;F7:
Copy an image.&Ctrl;F8:
Move an image.&Ctrl;F9:
Link an image.Configuring &gwenview;Configuration overview
You can configure &gwenview; by choosing SettingsConfigure &gwenview;....
The configuration dialog is split into three sections. This chapter describes the available
options in detail.
GeneralImage ViewAdvancedGeneralVideosCheck the Show videos checkbox to allow &gwenview; to show videos among the images.Background colorMove the slider to change the color of the gray background in Browse and View mode from black (the leftmost position) to white (the rightmost position).Thumbnail actionsWhether the thumbnail action buttons should be shown as overlays above image thumbnails.All buttons — Show all buttonsShow selection buttons — Show select/unselect button onlyNone — Do not show any thumbnail buttonImage ViewShow transparency withChoose how the background of transparent raster or SVG images is rendered.Surrounding background — Use background color from the General page.Checkerboard background — Use the traditional checkerboard background.Solid color background — Use a color that can be chosen using the color selector.Mouse wheel behaviorSelect how scrolling over an image in View mode behaves.Scroll — When zoomed in, pan the image vertically. Otherwise, nothing will happen.Browse — Switch to the next or the previous image.Zoom modeAllows choosing how zooming in View mode works.Autofit each image — Automatically resize images to the view pane.Keep same zoom and position — Use the same zoom level for all images.Per image zoom and position — Use individual zoom levels for each image.Enlarge smaller images — check this item to make &gwenview; enlarge smaller images in View mode.AnimationsAllows choosing how fading between images is done in View mode.OpenGL — Use hardware accelerated animations.Software — Do not use hardware accelerated animations. Select this option if OpenGL does not work satisfactory, but you still want to fade between images.None — Do not use animations when switching between images.Thumbnail BarAllows configuring the thumbnail bar.OrientationHorizontal — Show a horizontal thumbnail strip below the image.Vertical — Show a vertical thumbnail strip to the right of the image.Row count — Use the spin box to change the number of rows on the thumbnail barAdvancedCacheEnable the Delete thumbnail cache folder on exit option if you do not have a lot of disk space.Be careful: This will delete the folder named .thumbnails in your home folder, deleting all thumbnails previously generated by &gwenview; and other applications.HistoryCheck the Remember folders and URLs to make &gwenview; remember visited folders and &URL;s and show them on the Recent Folders pane.Rendering intentAllows choosing the rendering intent for showing images.Perceptual — scale all colors equally to fit them within the active color profile's color gamutRelative Colorimetric — squash colors that don't fit within the active color profile's color gamut, leaving other colors untouchedHidden Configuration Options
Some notes on hidden &gwenview; options can be found on
this page.
The options described on the above-mentioned page may help you
tune &gwenview; for specific needs, but please keep in mind
there is no warranty they will continue working from one
version to another.
Credits and Copyright&gwenview; is currently maintained by Aurélien GâteauThis document was written by Christopher MartinThis document was updated for &kde; 4 by Henry de Valence
&underFDL;
&underGPL;
diff --git a/lib/documentview/abstractimageview.cpp b/lib/documentview/abstractimageview.cpp
index 8119e4cc..65e45218 100644
--- a/lib/documentview/abstractimageview.cpp
+++ b/lib/documentview/abstractimageview.cpp
@@ -1,628 +1,620 @@
// vim: set tabstop=4 shiftwidth=4 expandtab:
/*
Gwenview: an image viewer
Copyright 2011 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 "abstractimageview.h"
// Local
// KDE
// Qt
#include
#include
#include
#include
#include
#include
namespace Gwenview
{
static const int UNIT_STEP = 16;
struct AbstractImageViewPrivate
{
enum Verbosity {
Silent,
Notify
};
AbstractImageView* q;
QCursor mZoomCursor;
Document::Ptr mDocument;
bool mControlKeyIsDown;
bool mEnlargeSmallerImages;
qreal mZoom;
bool mZoomToFit;
bool mZoomToFill;
QPointF mImageOffset;
QPointF mScrollPos;
QPointF mLastDragPos;
QSizeF mDocumentSize;
const QPixmap mAlphaBackgroundTexture = createAlphaBackgroundTexture();
void adjustImageOffset(Verbosity verbosity = Notify)
{
QSizeF zoomedDocSize = q->documentSize() * mZoom;
QSizeF viewSize = q->boundingRect().size();
QPointF offset(
qMax((viewSize.width() - zoomedDocSize.width()) / 2, qreal(0.)),
qMax((viewSize.height() - zoomedDocSize.height()) / 2, qreal(0.))
);
if (offset != mImageOffset) {
mImageOffset = offset;
if (verbosity == Notify) {
q->onImageOffsetChanged();
}
}
}
void adjustScrollPos(Verbosity verbosity = Notify)
{
setScrollPos(mScrollPos, verbosity);
}
void setScrollPos(const QPointF& _newPos, Verbosity verbosity = Notify)
{
if (!mDocument) {
mScrollPos = _newPos;
return;
}
QSizeF zoomedDocSize = q->documentSize() * mZoom;
QSizeF viewSize = q->boundingRect().size();
QPointF newPos(
qBound(qreal(0.), _newPos.x(), zoomedDocSize.width() - viewSize.width()),
qBound(qreal(0.), _newPos.y(), zoomedDocSize.height() - viewSize.height())
);
if (newPos != mScrollPos) {
QPointF oldPos = mScrollPos;
mScrollPos = newPos;
if (verbosity == Notify) {
q->onScrollPosChanged(oldPos);
}
// No verbosity test: we always notify the outside world about
// scrollPos changes
QMetaObject::invokeMethod(q, "scrollPosChanged");
}
}
void setupZoomCursor()
{
// We do not use "appdata" here because that does not work when this
// code is called from a KPart.
const QString path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("gwenview/cursors/zoom.png"));
QPixmap cursorPixmap = QPixmap(path);
mZoomCursor = QCursor(cursorPixmap, 11, 11);
}
QPixmap createAlphaBackgroundTexture()
{
QPixmap pix = QPixmap(32, 32);
QPainter painter(&pix);
painter.fillRect(pix.rect(), QColor(128, 128, 128));
const QColor light = QColor(192, 192, 192);
painter.fillRect(0, 0, 16, 16, light);
painter.fillRect(16, 16, 16, 16, light);
return pix;
}
void checkAndRequestZoomAction(const QGraphicsSceneMouseEvent* event)
{
if (event->modifiers() & Qt::ControlModifier) {
if (event->button() == Qt::LeftButton) {
q->zoomInRequested(event->pos());
} else if (event->button() == Qt::RightButton) {
q->zoomOutRequested(event->pos());
}
}
}
};
AbstractImageView::AbstractImageView(QGraphicsItem* parent)
: QGraphicsWidget(parent)
, d(new AbstractImageViewPrivate)
{
d->q = this;
d->mControlKeyIsDown = false;
d->mEnlargeSmallerImages = false;
d->mZoom = 1;
d->mZoomToFit = true;
d->mZoomToFill = false;
d->mImageOffset = QPointF(0, 0);
d->mScrollPos = QPointF(0, 0);
setFocusPolicy(Qt::WheelFocus);
setFlag(ItemIsSelectable);
setFlag(ItemClipsChildrenToShape);
setAcceptHoverEvents(true);
d->setupZoomCursor();
updateCursor();
}
AbstractImageView::~AbstractImageView()
{
if (d->mDocument) {
d->mDocument->stopAnimation();
}
delete d;
}
Document::Ptr AbstractImageView::document() const
{
return d->mDocument;
}
void AbstractImageView::setDocument(Document::Ptr doc)
{
if (d->mDocument) {
disconnect(d->mDocument.data(), nullptr, this, nullptr);
}
d->mDocument = doc;
loadFromDocument();
}
QSizeF AbstractImageView::documentSize() const
{
return d->mDocument ? d->mDocument->size() : QSizeF();
}
qreal AbstractImageView::zoom() const
{
return d->mZoom;
}
void AbstractImageView::setZoom(qreal zoom, const QPointF& _center, AbstractImageView::UpdateType updateType)
{
if (!d->mDocument) {
d->mZoom = zoom;
return;
}
if (updateType == UpdateIfNecessary
&& qFuzzyCompare(zoom, d->mZoom) && documentSize() == d->mDocumentSize) {
return;
}
qreal oldZoom = d->mZoom;
d->mZoom = zoom;
d->mDocumentSize = documentSize();
QPointF center;
if (_center == QPointF(-1, -1)) {
center = boundingRect().center();
} else {
center = _center;
}
/*
We want to keep the point at viewport coordinates "center" at the same
position after zooming. The coordinates of this point in image coordinates
can be expressed like this:
oldScroll + center
imagePointAtOldZoom = ------------------
oldZoom
scroll + center
imagePointAtZoom = ---------------
zoom
So we want:
imagePointAtOldZoom = imagePointAtZoom
oldScroll + center scroll + center
<=> ------------------ = ---------------
oldZoom zoom
zoom
<=> scroll = ------- (oldScroll + center) - center
oldZoom
*/
/*
Compute oldScroll
It's useless to take the new offset in consideration because if a direction
of the new offset is not 0, we won't be able to center on a specific point
in that direction.
*/
QPointF oldScroll = scrollPos() - imageOffset();
QPointF scroll = (zoom / oldZoom) * (oldScroll + center) - center;
d->adjustImageOffset(AbstractImageViewPrivate::Silent);
d->setScrollPos(scroll, AbstractImageViewPrivate::Silent);
onZoomChanged();
zoomChanged(d->mZoom);
}
bool AbstractImageView::zoomToFit() const
{
return d->mZoomToFit;
}
bool AbstractImageView::zoomToFill() const
{
return d->mZoomToFill;
}
void AbstractImageView::setZoomToFit(bool on)
{
d->mZoomToFit = on;
if (on) {
d->mZoomToFill = false;
setZoom(computeZoomToFit());
}
// We do not set zoom to 1 if zoomToFit is off, this is up to the code
// calling us. It may went to zoom to some other level and/or to zoom on
// a particular position
zoomToFitChanged(d->mZoomToFit);
}
void AbstractImageView::setZoomToFill(bool on)
{
d->mZoomToFill = on;
if (on) {
d->mZoomToFit = false;
setZoom(computeZoomToFill());
}
// We do not set zoom to 1 if zoomToFit is off, this is up to the code
// calling us. It may went to zoom to some other level and/or to zoom on
// a particular position
zoomToFillChanged(d->mZoomToFill);
}
const QPixmap& AbstractImageView::alphaBackgroundTexture() const
{
return d->mAlphaBackgroundTexture;
}
void AbstractImageView::resizeEvent(QGraphicsSceneResizeEvent* event)
{
QGraphicsWidget::resizeEvent(event);
if (d->mZoomToFit) {
// setZoom() calls adjustImageOffset(), but only if the zoom changes.
// If the view is resized but does not cause a zoom change, we call
// adjustImageOffset() ourself.
const qreal newZoom = computeZoomToFit();
if (qFuzzyCompare(zoom(), newZoom)) {
d->adjustImageOffset(AbstractImageViewPrivate::Notify);
} else {
setZoom(newZoom);
}
} else if (d->mZoomToFill) {
const qreal newZoom = computeZoomToFill();
if (qFuzzyCompare(zoom(), newZoom)) {
d->adjustImageOffset(AbstractImageViewPrivate::Notify);
} else {
setZoom(newZoom);
}
} else {
d->adjustImageOffset();
d->adjustScrollPos();
}
}
void AbstractImageView::focusInEvent(QFocusEvent* event)
{
QGraphicsWidget::focusInEvent(event);
// We might have missed a keyReleaseEvent for the control key, e.g. for Ctrl+O
const bool controlKeyIsCurrentlyDown = QGuiApplication::queryKeyboardModifiers() & Qt::ControlModifier;
if (d->mControlKeyIsDown != controlKeyIsCurrentlyDown) {
d->mControlKeyIsDown = controlKeyIsCurrentlyDown;
updateCursor();
}
}
qreal AbstractImageView::computeZoomToFit() const
{
QSizeF docSize = documentSize();
if (docSize.isEmpty()) {
return 1;
}
QSizeF viewSize = boundingRect().size();
qreal fitWidth = viewSize.width() / docSize.width();
qreal fitHeight = viewSize.height() / docSize.height();
qreal fit = qMin(fitWidth, fitHeight);
if (!d->mEnlargeSmallerImages) {
fit = qMin(fit, qreal(1.));
}
return fit;
}
qreal AbstractImageView::computeZoomToFill() const
{
QSizeF docSize = documentSize();
if (docSize.isEmpty()) {
return 1;
}
QSizeF viewSize = boundingRect().size();
qreal fitWidth = viewSize.width() / docSize.width();
qreal fitHeight = viewSize.height() / docSize.height();
qreal fill = qMax(fitWidth, fitHeight);
if (!d->mEnlargeSmallerImages) {
fill = qMin(fill, qreal(1.));
}
return fill;
}
void AbstractImageView::mousePressEvent(QGraphicsSceneMouseEvent* event)
{
QGraphicsItem::mousePressEvent(event);
- if (event->button() == Qt::MiddleButton) {
- bool value = !zoomToFit();
- setZoomToFit(value);
- if (!value) {
- setZoom(1., event->pos());
- }
- return;
- }
d->checkAndRequestZoomAction(event);
// Prepare for panning or dragging
if (event->button() == Qt::LeftButton) {
d->mLastDragPos = event->pos();
updateCursor();
}
}
void AbstractImageView::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
{
QGraphicsItem::mouseMoveEvent(event);
QPointF mousePos = event->pos();
QPointF newScrollPos = d->mScrollPos + d->mLastDragPos - mousePos;
#if 0 // commented out due to mouse pointer warping around, bug in Qt?
// Wrap mouse pos
qreal maxWidth = boundingRect().width();
qreal maxHeight = boundingRect().height();
// We need a margin because if the window is maximized, the mouse may not
// be able to go past the bounding rect.
// The mouse get placed 1 pixel before/after the margin to avoid getting
// considered as needing to wrap the other way in next mouseMoveEvent
// (because we don't check the move vector)
const int margin = 5;
if (mousePos.x() <= margin) {
mousePos.setX(maxWidth - margin - 1);
} else if (mousePos.x() >= maxWidth - margin) {
mousePos.setX(margin + 1);
}
if (mousePos.y() <= margin) {
mousePos.setY(maxHeight - margin - 1);
} else if (mousePos.y() >= maxHeight - margin) {
mousePos.setY(margin + 1);
}
// Set mouse pos (Hackish translation to screen coords!)
QPointF screenDelta = event->screenPos() - event->pos();
QCursor::setPos((mousePos + screenDelta).toPoint());
#endif
d->mLastDragPos = mousePos;
d->setScrollPos(newScrollPos);
}
void AbstractImageView::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
{
QGraphicsItem::mouseReleaseEvent(event);
if (!d->mLastDragPos.isNull()) {
d->mLastDragPos = QPointF();
}
updateCursor();
}
void AbstractImageView::keyPressEvent(QKeyEvent* event)
{
if (event->key() == Qt::Key_Control) {
d->mControlKeyIsDown = true;
updateCursor();
return;
}
if (zoomToFit() || qFuzzyCompare(computeZoomToFit(), zoom())) {
if (event->modifiers() != Qt::NoModifier) {
return;
}
switch (event->key()) {
case Qt::Key_Left:
case Qt::Key_Up:
previousImageRequested();
break;
case Qt::Key_Right:
case Qt::Key_Down:
nextImageRequested();
break;
default:
break;
}
return;
}
QPointF delta(0, 0);
qreal pageStep = boundingRect().height();
qreal unitStep;
if (event->modifiers() & Qt::ShiftModifier) {
unitStep = pageStep / 2;
} else {
unitStep = UNIT_STEP;
}
switch (event->key()) {
case Qt::Key_Left:
delta.setX(-unitStep);
break;
case Qt::Key_Right:
delta.setX(unitStep);
break;
case Qt::Key_Up:
delta.setY(-unitStep);
break;
case Qt::Key_Down:
delta.setY(unitStep);
break;
case Qt::Key_PageUp:
delta.setY(-pageStep);
break;
case Qt::Key_PageDown:
delta.setY(pageStep);
break;
case Qt::Key_Home:
d->setScrollPos(QPointF(d->mScrollPos.x(), 0));
return;
case Qt::Key_End:
d->setScrollPos(QPointF(d->mScrollPos.x(), documentSize().height() * zoom()));
return;
default:
return;
}
d->setScrollPos(d->mScrollPos + delta);
}
void AbstractImageView::keyReleaseEvent(QKeyEvent* event)
{
if (event->key() == Qt::Key_Control) {
d->mControlKeyIsDown = false;
updateCursor();
}
}
void AbstractImageView::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event)
{
if (event->modifiers() == Qt::NoModifier && event->button() == Qt::LeftButton) {
toggleFullScreenRequested();
}
d->checkAndRequestZoomAction(event);
}
QPointF AbstractImageView::imageOffset() const
{
return d->mImageOffset;
}
QPointF AbstractImageView::scrollPos() const
{
return d->mScrollPos;
}
void AbstractImageView::setScrollPos(const QPointF& pos)
{
d->setScrollPos(pos);
}
QPointF AbstractImageView::mapToView(const QPointF& imagePos) const
{
return imagePos * d->mZoom + d->mImageOffset - d->mScrollPos;
}
QPoint AbstractImageView::mapToView(const QPoint& imagePos) const
{
return mapToView(QPointF(imagePos)).toPoint();
}
QRectF AbstractImageView::mapToView(const QRectF& imageRect) const
{
return QRectF(
mapToView(imageRect.topLeft()),
imageRect.size() * zoom()
);
}
QRect AbstractImageView::mapToView(const QRect& imageRect) const
{
return QRect(
mapToView(imageRect.topLeft()),
imageRect.size() * zoom()
);
}
QPointF AbstractImageView::mapToImage(const QPointF& viewPos) const
{
return (viewPos - d->mImageOffset + d->mScrollPos) / d->mZoom;
}
QPoint AbstractImageView::mapToImage(const QPoint& viewPos) const
{
return mapToImage(QPointF(viewPos)).toPoint();
}
QRectF AbstractImageView::mapToImage(const QRectF& viewRect) const
{
return QRectF(
mapToImage(viewRect.topLeft()),
viewRect.size() / zoom()
);
}
QRect AbstractImageView::mapToImage(const QRect& viewRect) const
{
return QRect(
mapToImage(viewRect.topLeft()),
viewRect.size() / zoom()
);
}
void AbstractImageView::setEnlargeSmallerImages(bool value)
{
d->mEnlargeSmallerImages = value;
if (zoomToFit()) {
setZoom(computeZoomToFit());
}
}
void AbstractImageView::updateCursor()
{
if (d->mControlKeyIsDown) {
setCursor(d->mZoomCursor);
} else {
if (d->mLastDragPos.isNull()) {
setCursor(Qt::OpenHandCursor);
} else {
setCursor(Qt::ClosedHandCursor);
}
}
}
QSizeF AbstractImageView::visibleImageSize() const
{
if (!document()) {
return QSizeF();
}
QSizeF size = documentSize() * zoom();
return size.boundedTo(boundingRect().size());
}
void AbstractImageView::applyPendingScrollPos()
{
d->adjustImageOffset();
d->adjustScrollPos();
}
void AbstractImageView::resetDragCursor()
{
d->mLastDragPos = QPointF();
updateCursor();
}
} // namespace
diff --git a/lib/documentview/documentview.cpp b/lib/documentview/documentview.cpp
index ffea98d2..2942282c 100644
--- a/lib/documentview/documentview.cpp
+++ b/lib/documentview/documentview.cpp
@@ -1,996 +1,1009 @@
// 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 "documentview.h"
// C++ Standard library
#include
// Qt
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// KDE
#include
#include
#include
// Local
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
namespace Gwenview
{
#undef ENABLE_LOG
#undef LOG
//#define ENABLE_LOG
#ifdef ENABLE_LOG
#define LOG(x) //qDebug() << x
#else
#define LOG(x) ;
#endif
static const qreal REAL_DELTA = 0.001;
static const qreal MAXIMUM_ZOOM_VALUE = qreal(DocumentView::MaximumZoom);
static const auto MINSTEP = sqrt(0.5);
static const auto MAXSTEP = sqrt(2.0);
static const int COMPARE_MARGIN = 4;
const int DocumentView::MaximumZoom = 16;
const int DocumentView::AnimDuration = 250;
struct DocumentViewPrivate
{
DocumentView* q;
int mSortKey; // Used to sort views when displayed in compare mode
HudWidget* mHud;
BirdEyeView* mBirdEyeView;
QPointer mMoveAnimation;
QPointer mFadeAnimation;
QGraphicsOpacityEffect* mOpacityEffect;
LoadingIndicator* mLoadingIndicator;
QScopedPointer mAdapter;
QList mZoomSnapValues;
Document::Ptr mDocument;
DocumentView::Setup mSetup;
bool mCurrent;
bool mCompareMode;
int controlWheelAccumulatedDelta;
QPointF mDragStartPosition;
QPointer mDragThumbnailProvider;
QPointer mDrag;
void setCurrentAdapter(AbstractDocumentViewAdapter* adapter)
{
Q_ASSERT(adapter);
mAdapter.reset(adapter);
adapter->widget()->setParentItem(q);
resizeAdapterWidget();
if (adapter->canZoom()) {
QObject::connect(adapter, SIGNAL(zoomChanged(qreal)),
q, SLOT(slotZoomChanged(qreal)));
QObject::connect(adapter, SIGNAL(zoomInRequested(QPointF)),
q, SLOT(zoomIn(QPointF)));
QObject::connect(adapter, SIGNAL(zoomOutRequested(QPointF)),
q, SLOT(zoomOut(QPointF)));
QObject::connect(adapter, SIGNAL(zoomToFitChanged(bool)),
q, SIGNAL(zoomToFitChanged(bool)));
QObject::connect(adapter, SIGNAL(zoomToFillChanged(bool)),
q, SIGNAL(zoomToFillChanged(bool)));
}
QObject::connect(adapter, SIGNAL(scrollPosChanged()),
q, SIGNAL(positionChanged()));
QObject::connect(adapter, SIGNAL(previousImageRequested()),
q, SIGNAL(previousImageRequested()));
QObject::connect(adapter, SIGNAL(nextImageRequested()),
q, SIGNAL(nextImageRequested()));
QObject::connect(adapter, SIGNAL(toggleFullScreenRequested()),
q, SIGNAL(toggleFullScreenRequested()));
QObject::connect(adapter, SIGNAL(completed()),
q, SLOT(slotCompleted()));
adapter->loadConfig();
adapter->widget()->installSceneEventFilter(q);
if (mCurrent) {
adapter->widget()->setFocus();
}
if (mSetup.valid && adapter->canZoom()) {
adapter->setZoomToFit(mSetup.zoomToFit);
adapter->setZoomToFill(mSetup.zoomToFill);
if (!mSetup.zoomToFit && !mSetup.zoomToFill) {
adapter->setZoom(mSetup.zoom);
adapter->setScrollPos(mSetup.position);
}
}
q->adapterChanged();
q->positionChanged();
if (adapter->canZoom()) {
if (adapter->zoomToFit()) {
q->zoomToFitChanged(true);
} else if (adapter->zoomToFill()) {
q->zoomToFillChanged(true);
} else {
q->zoomChanged(adapter->zoom());
}
}
if (adapter->rasterImageView()) {
QObject::connect(adapter->rasterImageView(), SIGNAL(currentToolChanged(AbstractRasterImageViewTool*)),
q, SIGNAL(currentToolChanged(AbstractRasterImageViewTool*)));
}
}
void setupLoadingIndicator()
{
mLoadingIndicator = new LoadingIndicator(q);
GraphicsWidgetFloater* floater = new GraphicsWidgetFloater(q);
floater->setChildWidget(mLoadingIndicator);
}
HudButton* createHudButton(const QString& text, const char* iconName, bool showText)
{
HudButton* button = new HudButton;
if (showText) {
button->setText(text);
} else {
button->setToolTip(text);
}
button->setIcon(QIcon::fromTheme(iconName));
return button;
}
void setupHud()
{
HudButton* trashButton = createHudButton(i18nc("@info:tooltip", "Trash"), "user-trash", false);
HudButton* deselectButton = createHudButton(i18nc("@action:button", "Deselect"), "list-remove", true);
QGraphicsWidget* content = new QGraphicsWidget;
QGraphicsLinearLayout* layout = new QGraphicsLinearLayout(content);
layout->addItem(trashButton);
layout->addItem(deselectButton);
mHud = new HudWidget(q);
mHud->init(content, HudWidget::OptionNone);
GraphicsWidgetFloater* floater = new GraphicsWidgetFloater(q);
floater->setChildWidget(mHud);
floater->setAlignment(Qt::AlignBottom | Qt::AlignHCenter);
QObject::connect(trashButton, SIGNAL(clicked()), q, SLOT(emitHudTrashClicked()));
QObject::connect(deselectButton, SIGNAL(clicked()), q, SLOT(emitHudDeselectClicked()));
mHud->hide();
}
void setupBirdEyeView()
{
if (mBirdEyeView) {
delete mBirdEyeView;
}
mBirdEyeView = new BirdEyeView(q);
mBirdEyeView->setZValue(1);
}
void updateCaption()
{
if (!mCurrent) {
return;
}
QString caption;
Document::Ptr doc = mAdapter->document();
if (!doc) {
emit q->captionUpdateRequested(caption);
return;
}
caption = doc->url().fileName();
QSize size = doc->size();
if (size.isValid()) {
caption +=
QString(" - %1x%2")
.arg(size.width())
.arg(size.height());
if (mAdapter->canZoom()) {
int intZoom = qRound(mAdapter->zoom() * 100);
caption += QString(" - %1%")
.arg(intZoom);
}
}
emit q->captionUpdateRequested(caption);
}
void uncheckZoomToFit()
{
if (mAdapter->zoomToFit()) {
mAdapter->setZoomToFit(false);
}
}
void uncheckZoomToFill()
{
if (mAdapter->zoomToFill()) {
mAdapter->setZoomToFill(false);
}
}
void setZoom(qreal zoom, const QPointF& center = QPointF(-1, -1))
{
uncheckZoomToFit();
uncheckZoomToFill();
zoom = qBound(q->minimumZoom(), zoom, MAXIMUM_ZOOM_VALUE);
mAdapter->setZoom(zoom, center);
}
void updateZoomSnapValues()
{
qreal min = q->minimumZoom();
mZoomSnapValues.clear();
for (qreal zoom = MINSTEP; zoom > min; zoom *= MINSTEP) {
mZoomSnapValues << zoom;
}
mZoomSnapValues << min;
std::reverse(mZoomSnapValues.begin(), mZoomSnapValues.end());
for (qreal zoom = 1; zoom < MAXIMUM_ZOOM_VALUE; zoom *= MAXSTEP) {
mZoomSnapValues << zoom;
}
mZoomSnapValues << MAXIMUM_ZOOM_VALUE;
q->minimumZoomChanged(min);
}
void showLoadingIndicator()
{
if (!mLoadingIndicator) {
setupLoadingIndicator();
}
mLoadingIndicator->show();
mLoadingIndicator->setZValue(1);
}
void hideLoadingIndicator()
{
if (!mLoadingIndicator) {
return;
}
mLoadingIndicator->hide();
}
void resizeAdapterWidget()
{
QRectF rect = QRectF(QPointF(0, 0), q->boundingRect().size());
if (mCompareMode) {
rect.adjust(COMPARE_MARGIN, COMPARE_MARGIN, -COMPARE_MARGIN, -COMPARE_MARGIN);
}
mAdapter->widget()->setGeometry(rect);
}
void fadeTo(qreal value)
{
if (mFadeAnimation.data()) {
qreal endValue = mFadeAnimation.data()->endValue().toReal();
if (qFuzzyCompare(value, endValue)) {
// Same end value, don't change the actual animation
return;
}
}
// Create a new fade animation
QPropertyAnimation* anim = new QPropertyAnimation(mOpacityEffect, "opacity");
anim->setStartValue(mOpacityEffect->opacity());
anim->setEndValue(value);
if (qFuzzyCompare(value, 1)) {
QObject::connect(anim, SIGNAL(finished()),
q, SLOT(slotFadeInFinished()));
}
QObject::connect(anim, SIGNAL(finished()), q, SIGNAL(isAnimatedChanged()));
anim->setDuration(DocumentView::AnimDuration);
mFadeAnimation = anim;
q->isAnimatedChanged();
anim->start(QAbstractAnimation::DeleteWhenStopped);
}
bool canPan() const
{
if (!q->canZoom()) {
return false;
}
const QSize zoomedImageSize = mDocument->size() * q->zoom();
const QSize viewPortSize = q->boundingRect().size().toSize();
const bool imageWiderThanViewport = zoomedImageSize.width() > viewPortSize.width();
const bool imageTallerThanViewport = zoomedImageSize.height() > viewPortSize.height();
return (imageWiderThanViewport || imageTallerThanViewport);
}
void setDragPixmap(const QPixmap& pix)
{
if (mDrag) {
DragPixmapGenerator::DragPixmap dragPixmap = DragPixmapGenerator::generate({pix}, 1);
mDrag->setPixmap(dragPixmap.pix);
mDrag->setHotSpot(dragPixmap.hotSpot);
}
}
void executeDrag()
{
if (mDrag) {
if (mAdapter->imageView()) {
mAdapter->imageView()->resetDragCursor();
}
mDrag->exec(Qt::MoveAction | Qt::CopyAction | Qt::LinkAction, Qt::CopyAction);
}
}
void initDragThumbnailProvider() {
mDragThumbnailProvider = new ThumbnailProvider();
QObject::connect(mDragThumbnailProvider, &ThumbnailProvider::thumbnailLoaded,
q, &DocumentView::dragThumbnailLoaded);
QObject::connect(mDragThumbnailProvider, &ThumbnailProvider::thumbnailLoadingFailed,
q, &DocumentView::dragThumbnailLoadingFailed);
}
void startDragIfSensible()
{
if (q->document()->loadingState() == Document::LoadingFailed) {
return;
}
if (q->currentTool()) {
return;
}
if (mDrag) {
mDrag->deleteLater();
}
mDrag = new QDrag(q);
const auto itemList = KFileItemList({q->document()->url()});
mDrag->setMimeData(MimeTypeUtils::selectionMimeData(itemList));
if (q->document()->isModified()) {
setDragPixmap(QPixmap::fromImage(q->document()->image()));
executeDrag();
} else {
// Drag is triggered on success or failure of thumbnail generation
if (mDragThumbnailProvider.isNull()) {
initDragThumbnailProvider();
}
mDragThumbnailProvider->appendItems(itemList);
}
}
QPointF cursorPosition() {
const QGraphicsScene* sc = q->scene();
if (sc) {
const auto views = sc->views();
for (const QGraphicsView* view : views) {
if (view->underMouse()) {
return q->mapFromScene(view->mapFromGlobal(QCursor::pos()));
}
}
}
return QPointF(-1, -1);
}
};
DocumentView::DocumentView(QGraphicsScene* scene)
: d(new DocumentViewPrivate)
{
setFlag(ItemIsFocusable);
setFlag(ItemIsSelectable);
setFlag(ItemClipsChildrenToShape);
d->q = this;
d->mLoadingIndicator = nullptr;
d->mBirdEyeView = nullptr;
d->mCurrent = false;
d->mCompareMode = false;
d->controlWheelAccumulatedDelta = 0;
d->mDragStartPosition = QPointF(0, 0);
d->mDrag = nullptr;
// We use an opacity effect instead of using the opacity property directly, because the latter operates at
// the painter level, which means if you draw multiple layers in paint(), all layers get the specified
// opacity, resulting in all layers being visible when 0 < opacity < 1.
// QGraphicsEffects on the other hand, operate after all painting is done, therefore 'flattening' all layers.
// This is important for fade effects, where we don't want any background layers visible during the fade.
d->mOpacityEffect = new QGraphicsOpacityEffect(this);
d->mOpacityEffect->setOpacity(0);
setGraphicsEffect(d->mOpacityEffect);
scene->addItem(this);
d->setupHud();
d->setCurrentAdapter(new EmptyAdapter);
setAcceptDrops(true);
}
DocumentView::~DocumentView()
{
delete d->mDragThumbnailProvider;
delete d->mDrag;
delete d;
}
void DocumentView::createAdapterForDocument()
{
const MimeTypeUtils::Kind documentKind = d->mDocument->kind();
if (d->mAdapter && documentKind == d->mAdapter->kind() && documentKind != MimeTypeUtils::KIND_UNKNOWN) {
// Do not reuse for KIND_UNKNOWN: we may need to change the message
LOG("Reusing current adapter");
return;
}
AbstractDocumentViewAdapter* adapter = nullptr;
switch (documentKind) {
case MimeTypeUtils::KIND_RASTER_IMAGE:
adapter = new RasterImageViewAdapter;
break;
case MimeTypeUtils::KIND_SVG_IMAGE:
adapter = new SvgViewAdapter;
break;
case MimeTypeUtils::KIND_VIDEO:
adapter = new VideoViewAdapter;
connect(adapter, SIGNAL(videoFinished()),
SIGNAL(videoFinished()));
break;
case MimeTypeUtils::KIND_UNKNOWN:
adapter = new MessageViewAdapter;
static_cast(adapter)->setErrorMessage(i18n("Gwenview does not know how to display this kind of document"));
break;
default:
qWarning() << "should not be called for documentKind=" << documentKind;
adapter = new MessageViewAdapter;
break;
}
d->setCurrentAdapter(adapter);
}
void DocumentView::openUrl(const QUrl &url, const DocumentView::Setup& setup)
{
if (d->mDocument) {
if (url == d->mDocument->url()) {
return;
}
disconnect(d->mDocument.data(), nullptr, this, nullptr);
}
d->mSetup = setup;
d->mDocument = DocumentFactory::instance()->load(url);
connect(d->mDocument.data(), SIGNAL(busyChanged(QUrl,bool)), SLOT(slotBusyChanged(QUrl,bool)));
connect(d->mDocument.data(), &Document::modified, this, [this]() {
d->updateZoomSnapValues();
});
if (d->mDocument->loadingState() < Document::KindDetermined) {
MessageViewAdapter* messageViewAdapter = qobject_cast(d->mAdapter.data());
if (messageViewAdapter) {
messageViewAdapter->setInfoMessage(QString());
}
d->showLoadingIndicator();
connect(d->mDocument.data(), SIGNAL(kindDetermined(QUrl)),
SLOT(finishOpenUrl()));
} else {
QMetaObject::invokeMethod(this, "finishOpenUrl", Qt::QueuedConnection);
}
d->setupBirdEyeView();
}
void DocumentView::finishOpenUrl()
{
disconnect(d->mDocument.data(), SIGNAL(kindDetermined(QUrl)),
this, SLOT(finishOpenUrl()));
GV_RETURN_IF_FAIL(d->mDocument->loadingState() >= Document::KindDetermined);
if (d->mDocument->loadingState() == Document::LoadingFailed) {
slotLoadingFailed();
return;
}
createAdapterForDocument();
connect(d->mDocument.data(), SIGNAL(loadingFailed(QUrl)),
SLOT(slotLoadingFailed()));
d->mAdapter->setDocument(d->mDocument);
d->updateCaption();
}
void DocumentView::loadAdapterConfig()
{
d->mAdapter->loadConfig();
}
RasterImageView* DocumentView::imageView() const
{
return d->mAdapter->rasterImageView();
}
void DocumentView::slotCompleted()
{
d->hideLoadingIndicator();
d->updateCaption();
d->updateZoomSnapValues();
if (!d->mAdapter->zoomToFit() || !d->mAdapter->zoomToFill()) {
qreal min = minimumZoom();
if (d->mAdapter->zoom() < min) {
d->mAdapter->setZoom(min);
}
}
emit completed();
}
DocumentView::Setup DocumentView::setup() const
{
Setup setup;
if (d->mAdapter->canZoom()) {
setup.valid = true;
setup.zoomToFit = zoomToFit();
setup.zoomToFill = zoomToFill();
if (!setup.zoomToFit && !setup.zoomToFill) {
setup.zoom = zoom();
setup.position = position();
}
}
return setup;
}
void DocumentView::slotLoadingFailed()
{
d->hideLoadingIndicator();
MessageViewAdapter* adapter = new MessageViewAdapter;
adapter->setDocument(d->mDocument);
QString message = xi18n("Loading %1 failed", d->mDocument->url().fileName());
adapter->setErrorMessage(message, d->mDocument->errorString());
d->setCurrentAdapter(adapter);
emit completed();
}
bool DocumentView::canZoom() const
{
return d->mAdapter->canZoom();
}
void DocumentView::setZoomToFit(bool on)
{
if (on == d->mAdapter->zoomToFit()) {
return;
}
d->mAdapter->setZoomToFit(on);
}
void DocumentView::toggleZoomToFit() {
const bool zoomToFitOn = d->mAdapter->zoomToFit();
d->mAdapter->setZoomToFit(!zoomToFitOn);
if (zoomToFitOn) {
d->setZoom(1., d->cursorPosition());
}
}
void DocumentView::setZoomToFill(bool on)
{
if (on == d->mAdapter->zoomToFill()) {
return;
}
d->mAdapter->setZoomToFill(on);
}
void DocumentView::toggleZoomToFill() {
const bool zoomToFillOn = d->mAdapter->zoomToFill();
d->mAdapter->setZoomToFill(!zoomToFillOn);
if (zoomToFillOn) {
d->setZoom(1., d->cursorPosition());
}
}
bool DocumentView::zoomToFit() const
{
return d->mAdapter->zoomToFit();
}
bool DocumentView::zoomToFill() const
{
return d->mAdapter->zoomToFill();
}
void DocumentView::zoomActualSize()
{
d->uncheckZoomToFit();
d->uncheckZoomToFill();
d->mAdapter->setZoom(1.);
}
void DocumentView::zoomIn(const QPointF& center)
{
qreal currentZoom = d->mAdapter->zoom();
Q_FOREACH(qreal zoom, d->mZoomSnapValues) {
if (zoom > currentZoom + REAL_DELTA) {
d->setZoom(zoom, center);
return;
}
}
}
void DocumentView::zoomOut(const QPointF& center)
{
qreal currentZoom = d->mAdapter->zoom();
QListIterator it(d->mZoomSnapValues);
it.toBack();
while (it.hasPrevious()) {
qreal zoom = it.previous();
if (zoom < currentZoom - REAL_DELTA) {
d->setZoom(zoom, center);
return;
}
}
}
void DocumentView::slotZoomChanged(qreal zoom)
{
d->updateCaption();
zoomChanged(zoom);
}
void DocumentView::setZoom(qreal zoom)
{
d->setZoom(zoom);
}
qreal DocumentView::zoom() const
{
return d->mAdapter->zoom();
}
void DocumentView::resizeEvent(QGraphicsSceneResizeEvent *event)
{
d->resizeAdapterWidget();
d->updateZoomSnapValues();
QGraphicsWidget::resizeEvent(event);
}
+void DocumentView::mousePressEvent(QGraphicsSceneMouseEvent* event)
+{
+ QGraphicsWidget::mousePressEvent(event);
+
+ if (d->mAdapter->canZoom() && event->button() == Qt::MiddleButton) {
+ if (event->modifiers() == Qt::NoModifier) {
+ toggleZoomToFit();
+ } else if (event->modifiers() == Qt::SHIFT) {
+ toggleZoomToFill();
+ }
+ }
+}
+
void DocumentView::wheelEvent(QGraphicsSceneWheelEvent* event)
{
if (d->mAdapter->canZoom() && event->modifiers() & Qt::ControlModifier) {
d->controlWheelAccumulatedDelta += event->delta();
// Ctrl + wheel => zoom in or out
if (d->controlWheelAccumulatedDelta >= QWheelEvent::DefaultDeltasPerStep) {
zoomIn(event->pos());
d->controlWheelAccumulatedDelta = 0;
} else if (d->controlWheelAccumulatedDelta <= -QWheelEvent::DefaultDeltasPerStep) {
zoomOut(event->pos());
d->controlWheelAccumulatedDelta = 0;
}
return;
}
if (GwenviewConfig::mouseWheelBehavior() == MouseWheelBehavior::Browse
&& event->modifiers() == Qt::NoModifier) {
d->controlWheelAccumulatedDelta += event->delta();
// Browse with mouse wheel
if (d->controlWheelAccumulatedDelta >= QWheelEvent::DefaultDeltasPerStep) {
previousImageRequested();
d->controlWheelAccumulatedDelta = 0;
} else if (d->controlWheelAccumulatedDelta <= -QWheelEvent::DefaultDeltasPerStep) {
nextImageRequested();
d->controlWheelAccumulatedDelta = 0;
}
return;
}
// Scroll
qreal dx = 0;
// 16 = pixels for one line
// 120: see QWheelEvent::delta() doc
qreal dy = -qApp->wheelScrollLines() * 16 * event->delta() / 120;
if (event->orientation() == Qt::Horizontal) {
qSwap(dx, dy);
}
d->mAdapter->setScrollPos(d->mAdapter->scrollPos() + QPointF(dx, dy));
}
void DocumentView::contextMenuEvent(QGraphicsSceneContextMenuEvent* event)
{
// Filter out context menu if Ctrl is down to avoid showing it when
// zooming out with Ctrl + Right button
if (event->modifiers() != Qt::ControlModifier) {
contextMenuRequested();
}
}
void DocumentView::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/)
{
// Fill background manually, because setAutoFillBackground(true) fill with QPalette::Window,
// but our palettes use QPalette::Base for the background color/texture
painter->fillRect(rect(), palette().base());
// Selection indicator/highlight
if (d->mCompareMode && d->mCurrent) {
painter->save();
painter->setBrush(Qt::NoBrush);
painter->setPen(QPen(palette().highlight().color(), 2));
painter->setRenderHint(QPainter::Antialiasing);
const QRectF visibleRectF = mapRectFromItem(d->mAdapter->widget(), d->mAdapter->visibleDocumentRect());
// Round the point and size independently. This is different than calling toRect(),
// and is necessary to keep consistent rects, otherwise the selection rect can be
// drawn 1 pixel too big or small.
const QRect visibleRect = QRect(visibleRectF.topLeft().toPoint(), visibleRectF.size().toSize());
const QRect selectionRect = visibleRect.adjusted(-1, -1, 1, 1);
painter->drawRoundedRect(selectionRect, 3, 3);
painter->restore();
}
}
void DocumentView::slotBusyChanged(const QUrl&, bool busy)
{
if (busy) {
d->showLoadingIndicator();
} else {
d->hideLoadingIndicator();
}
}
qreal DocumentView::minimumZoom() const
{
// There is no point zooming out less than zoomToFit, but make sure it does
// not get too small either
return qBound(qreal(0.001), d->mAdapter->computeZoomToFit(), qreal(1.));
}
void DocumentView::setCompareMode(bool compare)
{
d->mCompareMode = compare;
if (compare) {
d->mHud->show();
d->mHud->setZValue(1);
} else {
d->mHud->hide();
}
}
void DocumentView::setCurrent(bool value)
{
d->mCurrent = value;
if (value) {
d->mAdapter->widget()->setFocus();
d->updateCaption();
}
update();
}
bool DocumentView::isCurrent() const
{
return d->mCurrent;
}
QPoint DocumentView::position() const
{
return d->mAdapter->scrollPos().toPoint();
}
void DocumentView::setPosition(const QPoint& pos)
{
d->mAdapter->setScrollPos(pos);
}
Document::Ptr DocumentView::document() const
{
return d->mDocument;
}
QUrl DocumentView::url() const
{
Document::Ptr doc = d->mDocument;
return doc ? doc->url() : QUrl();
}
void DocumentView::emitHudDeselectClicked()
{
hudDeselectClicked(this);
}
void DocumentView::emitHudTrashClicked()
{
hudTrashClicked(this);
}
void DocumentView::emitFocused()
{
focused(this);
}
void DocumentView::setGeometry(const QRectF& rect)
{
QGraphicsWidget::setGeometry(rect);
if (d->mBirdEyeView) {
d->mBirdEyeView->slotZoomOrSizeChanged();
}
}
void DocumentView::moveTo(const QRect& rect)
{
if (d->mMoveAnimation) {
d->mMoveAnimation.data()->setEndValue(rect);
} else {
setGeometry(rect);
}
}
void DocumentView::moveToAnimated(const QRect& rect)
{
QPropertyAnimation* anim = new QPropertyAnimation(this, "geometry");
anim->setStartValue(geometry());
anim->setEndValue(rect);
anim->setDuration(DocumentView::AnimDuration);
connect(anim, SIGNAL(finished()), SIGNAL(isAnimatedChanged()));
d->mMoveAnimation = anim;
isAnimatedChanged();
anim->start(QAbstractAnimation::DeleteWhenStopped);
}
QPropertyAnimation* DocumentView::fadeIn()
{
d->fadeTo(1);
return d->mFadeAnimation.data();
}
void DocumentView::fadeOut()
{
d->fadeTo(0);
}
void DocumentView::slotFadeInFinished()
{
fadeInFinished(this);
}
bool DocumentView::isAnimated() const
{
return d->mMoveAnimation || d->mFadeAnimation;
}
bool DocumentView::sceneEventFilter(QGraphicsItem*, QEvent* event)
{
if (event->type() == QEvent::GraphicsSceneMousePress) {
const QGraphicsSceneMouseEvent* mouseEvent = static_cast(event);
if (mouseEvent->button() == Qt::LeftButton) {
d->mDragStartPosition = mouseEvent->pos();
}
QMetaObject::invokeMethod(this, "emitFocused", Qt::QueuedConnection);
} else if (event->type() == QEvent::GraphicsSceneHoverMove) {
if (d->mBirdEyeView) {
d->mBirdEyeView->onMouseMoved();
}
} else if (event->type() == QEvent::GraphicsSceneMouseMove) {
const QGraphicsSceneMouseEvent* mouseEvent = static_cast(event);
const qreal dragDistance = (mouseEvent->pos() - d->mDragStartPosition).manhattanLength();
const qreal minDistanceToStartDrag = QGuiApplication::styleHints()->startDragDistance();
if (!d->canPan() && dragDistance >= minDistanceToStartDrag) {
d->startDragIfSensible();
}
}
return false;
}
AbstractRasterImageViewTool* DocumentView::currentTool() const
{
return imageView() ? imageView()->currentTool() : nullptr;
}
int DocumentView::sortKey() const
{
return d->mSortKey;
}
void DocumentView::setSortKey(int sortKey)
{
d->mSortKey = sortKey;
}
void DocumentView::hideAndDeleteLater()
{
hide();
deleteLater();
}
void DocumentView::setGraphicsEffectOpacity(qreal opacity)
{
d->mOpacityEffect->setOpacity(opacity);
}
void DocumentView::dragEnterEvent(QGraphicsSceneDragDropEvent* event)
{
QGraphicsWidget::dragEnterEvent(event);
const auto urls = KUrlMimeData::urlsFromMimeData(event->mimeData());
bool acceptDrag = !urls.isEmpty();
if (urls.size() == 1 && urls.first() == url()) {
// Do not allow dragging a single image onto itself
acceptDrag = false;
}
event->setAccepted(acceptDrag);
}
void DocumentView::dropEvent(QGraphicsSceneDragDropEvent* event)
{
QGraphicsWidget::dropEvent(event);
// Since we're capturing drops in View mode, we only support one url
const QUrl url = event->mimeData()->urls().first();
if (UrlUtils::urlIsDirectory(url)) {
emit openDirUrlRequested(url);
} else {
emit openUrlRequested(url);
}
}
void DocumentView::dragThumbnailLoaded(const KFileItem& item, const QPixmap& pix)
{
d->setDragPixmap(pix);
d->executeDrag();
d->mDragThumbnailProvider->removeItems(KFileItemList({item}));
}
void DocumentView::dragThumbnailLoadingFailed(const KFileItem& item)
{
d->executeDrag();
d->mDragThumbnailProvider->removeItems(KFileItemList({item}));
}
} // namespace
diff --git a/lib/documentview/documentview.h b/lib/documentview/documentview.h
index 8e1a2b07..d044bbe7 100644
--- a/lib/documentview/documentview.h
+++ b/lib/documentview/documentview.h
@@ -1,245 +1,246 @@
// 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.
*/
#ifndef DOCUMENTVIEW_H
#define DOCUMENTVIEW_H
#include
// Qt
#include
// KDE
// Local
#include
class QPropertyAnimation;
class QUrl;
namespace Gwenview
{
class AbstractRasterImageViewTool;
class RasterImageView;
struct DocumentViewPrivate;
/**
* This widget can display various documents, using an instance of
* AbstractDocumentViewAdapter
*/
class GWENVIEWLIB_EXPORT DocumentView : public QGraphicsWidget
{
Q_OBJECT
Q_PROPERTY(qreal zoom READ zoom WRITE setZoom NOTIFY zoomChanged)
Q_PROPERTY(bool zoomToFit READ zoomToFit WRITE setZoomToFit NOTIFY zoomToFitChanged)
Q_PROPERTY(bool zoomToFill READ zoomToFill WRITE setZoomToFill NOTIFY zoomToFillChanged)
Q_PROPERTY(QPoint position READ position WRITE setPosition NOTIFY positionChanged)
public:
static const int MaximumZoom;
static const int AnimDuration;
struct Setup {
Setup()
: valid(false)
, zoomToFit(true)
, zoomToFill(false)
, zoom(0)
{}
bool valid:1;
bool zoomToFit:1;
bool zoomToFill:1;
qreal zoom;
QPointF position;
};
enum AnimationMethod {
NoAnimation,
SoftwareAnimation,
GLAnimation
};
/**
* Create a new view attached to scene. We need the scene to be able to
* install scene event filters.
*/
explicit DocumentView(QGraphicsScene* scene);
~DocumentView() Q_DECL_OVERRIDE;
Document::Ptr document() const;
QUrl url() const;
void openUrl(const QUrl&, const Setup&);
Setup setup() const;
/**
* Tells the current adapter to load its config. Used when the user changed
* the config while the view was visible.
*/
void loadAdapterConfig();
bool canZoom() const;
qreal minimumZoom() const;
qreal zoom() const;
bool isCurrent() const;
void setCurrent(bool);
void setCompareMode(bool);
bool zoomToFit() const;
bool zoomToFill() const;
QPoint position() const;
/**
* Returns the RasterImageView of the current adapter, if it has one
*/
RasterImageView* imageView() const;
AbstractRasterImageViewTool* currentTool() const;
void moveTo(const QRect&);
void moveToAnimated(const QRect&);
QPropertyAnimation* fadeIn();
void fadeOut();
void fakeFadeOut();
void setGeometry(const QRectF& rect) Q_DECL_OVERRIDE;
int sortKey() const;
void setSortKey(int sortKey);
bool isAnimated() const;
/**
* Sets the opacity on the installed QGraphicsOpacityEffect.
* Use this instead of setOpacity().
*/
void setGraphicsEffectOpacity(qreal opacity);
public Q_SLOTS:
void setZoom(qreal);
void setZoomToFit(bool);
void toggleZoomToFit();
void setZoomToFill(bool);
void toggleZoomToFill();
void setPosition(const QPoint&);
void hideAndDeleteLater();
Q_SIGNALS:
/**
* Emitted when the part has finished loading
*/
void completed();
void previousImageRequested();
void nextImageRequested();
void openUrlRequested(const QUrl&);
void openDirUrlRequested(const QUrl&);
void captionUpdateRequested(const QString&);
void toggleFullScreenRequested();
void videoFinished();
void minimumZoomChanged(qreal);
void zoomChanged(qreal);
void adapterChanged();
void focused(DocumentView*);
void zoomToFitChanged(bool);
void zoomToFillChanged(bool);
void positionChanged();
void hudTrashClicked(DocumentView*);
void hudDeselectClicked(DocumentView*);
void fadeInFinished(DocumentView*);
void contextMenuRequested();
void currentToolChanged(AbstractRasterImageViewTool*);
void isAnimatedChanged();
protected:
void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = nullptr) Q_DECL_OVERRIDE;
void resizeEvent(QGraphicsSceneResizeEvent* event) Q_DECL_OVERRIDE;
+ void mousePressEvent(QGraphicsSceneMouseEvent* event) Q_DECL_OVERRIDE;
void wheelEvent(QGraphicsSceneWheelEvent* event) Q_DECL_OVERRIDE;
void contextMenuEvent(QGraphicsSceneContextMenuEvent* event) Q_DECL_OVERRIDE;
bool sceneEventFilter(QGraphicsItem*, QEvent*) Q_DECL_OVERRIDE;
void dragEnterEvent(QGraphicsSceneDragDropEvent* event) override;
void dropEvent(QGraphicsSceneDragDropEvent* event) override;
private Q_SLOTS:
void finishOpenUrl();
void slotCompleted();
void slotLoadingFailed();
void zoomActualSize();
void zoomIn(const QPointF& center = QPointF(-1, -1));
void zoomOut(const QPointF& center = QPointF(-1, -1));
void slotZoomChanged(qreal);
void slotBusyChanged(const QUrl&, bool);
void emitHudTrashClicked();
void emitHudDeselectClicked();
void emitFocused();
void slotFadeInFinished();
void dragThumbnailLoaded(const KFileItem&, const QPixmap&);
void dragThumbnailLoadingFailed(const KFileItem&);
private:
friend struct DocumentViewPrivate;
DocumentViewPrivate* const d;
void createAdapterForDocument();
};
} // namespace
#endif /* DOCUMENTVIEW_H */