Cherry-picking upstream version 0.6.0.

ubuntu/disco
Alf Gaida 7 years ago
parent 75af31920b
commit 71999b516b

@ -3,7 +3,7 @@ Upstream Authors:
Hong Jen Yee (PCMan) <pcman.tw@gmail.com> Hong Jen Yee (PCMan) <pcman.tw@gmail.com>
Copyright: Copyright:
Copyright (c) 2013-2015 LXQt team Copyright (c) 2013-2017 LXQt team
License: GPL-2+ and LGPL-2.1+ License: GPL-2+ and LGPL-2.1+
The full text of the licenses can be found in the 'COPYING' file. The full text of the licenses can be found in the 'COPYING' file.

@ -1,7 +1,41 @@
lximage-qt-0.5.1 / 2016-12-21 lximage-qt-0.6.0 / 2017-10-21
============================= =============================
* Add ImageShack upload provider.
* Fix warnings issued by GCC and Clang.
* Ensure file is closed when upload finishes.
* Make image URL read-only.
* Update copyright in initial file comment for new additions.
* Make QNetworkAccessManager static and fix initialization order in UploadDialog.
* Add support for uploading files (fixes #98).
* Bump versions
* Don't export github templates
* Don't use hardcoded install dir
* Update CMakeLists.txt
* Fix regression in thumbnail view
* Add Lithuanian .desktop files
* liblxqt don't fit here
* Copied issue-template
* Drops Qt5Core_VERSION_STRING
* set Qt::AA_UseHighDpiPixmaps to true
* MainWindow: Fix crash for quick image changes
* Use GNUInstallDirs
* jobs: Do proper error handling
* Adapt to changes in libfm-qt(the c++11 port)
* Use the new lxqt-build-tools new FindExif CMake module
* Simpler code for ScreenshotSelectAreaGraphicsView class.
* Change Screenshot select area green color by actual hightlight color. Use lximage-qt private variables style.
* Adapt to C++11 and RAM improvements.
* Screenshot captures an area of the screen.
* Use const iterators
* Bump year
* File and folder DND Fixes https://github.com/lxde/lximage-qt/issues/69.
0.5.1 / 2016-12-21
==================
* Release 0.5.1: Update changelog
* Bump patch version and (#82) * Bump patch version and (#82)
* Create lximage-qt-screenshot_it.desktop (#83) * Create lximage-qt-screenshot_it.desktop (#83)
* Add *da.desktop files * Add *da.desktop files

@ -4,37 +4,35 @@ project(lximage-qt)
include(GNUInstallDirs) include(GNUInstallDirs)
set(MAJOR_VERSION 0) set(MAJOR_VERSION 0)
set(MINOR_VERSION 5) set(MINOR_VERSION 6)
set(PATCH_VERSION 1) set(PATCH_VERSION 0)
set(LXIMAGE_VERSION ${MAJOR_VERSION}.${MINOR_VERSION}.${PATCH_VERSION}) set(LXIMAGE_VERSION ${MAJOR_VERSION}.${MINOR_VERSION}.${PATCH_VERSION})
set(LXQTBT_MINIMUM_VERSION "0.3.0") set(LXQTBT_MINIMUM_VERSION "0.4.0")
set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON)
find_package(Qt5Widgets REQUIRED) find_package(Qt5Widgets REQUIRED)
find_package(Qt5Network REQUIRED)
find_package(Qt5DBus REQUIRED) find_package(Qt5DBus REQUIRED)
find_package(Qt5PrintSupport REQUIRED QUIET) find_package(Qt5PrintSupport REQUIRED)
find_package(Qt5X11Extras REQUIRED QUIET) find_package(Qt5X11Extras REQUIRED)
find_package(Qt5LinguistTools REQUIRED QUIET) find_package(Qt5LinguistTools REQUIRED)
find_package(Qt5Svg REQUIRED QUIET) find_package(Qt5Svg REQUIRED)
find_package(fm-qt REQUIRED QUIET) find_package(fm-qt REQUIRED)
find_package(lxqt-build-tools ${LXQTBT_MINIMUM_VERSION} REQUIRED) find_package(lxqt-build-tools ${LXQTBT_MINIMUM_VERSION} REQUIRED)
message(STATUS "Building with Qt ${Qt5Core_VERSION_STRING}") find_package(Exif REQUIRED)
message(STATUS "Building with Qt ${Qt5Core_VERSION}")
include(LXQtCompilerSettings NO_POLICY_SCOPE) include(LXQtCompilerSettings NO_POLICY_SCOPE)
find_package(PkgConfig REQUIRED)
# FIXME: we'll need this to provide detail info for photos in the future
pkg_check_modules(EXIF REQUIRED libexif)
# TODO: make the X11 stuff optional. # TODO: make the X11 stuff optional.
# for screenshot support # for screenshot support
find_package(X11 REQUIRED) find_package(X11 REQUIRED)
find_package(PkgConfig REQUIRED)
# Xfixes is needed to capture the mouse cursor image # Xfixes is needed to capture the mouse cursor image
pkg_check_modules(XFIXES REQUIRED xfixes) pkg_check_modules(XFIXES REQUIRED xfixes)

@ -16,11 +16,21 @@ set(lximage-qt_SRCS
application.cpp application.cpp
imageview.cpp imageview.cpp
modelfilter.cpp modelfilter.cpp
job.cpp
loadimagejob.cpp loadimagejob.cpp
saveimagejob.cpp saveimagejob.cpp
screenshotdialog.cpp screenshotdialog.cpp
screenshotselectarea.cpp
screenshotselectareagraphicsview.cpp
settings.cpp settings.cpp
graphicsscene.cpp
upload/imageshackprovider.cpp
upload/imageshackupload.cpp
upload/imgurprovider.cpp
upload/imgurupload.cpp
upload/provider.cpp
upload/upload.cpp
upload/uploaddialog.cpp
) )
qt5_add_dbus_adaptor(lximage-qt_SRCS qt5_add_dbus_adaptor(lximage-qt_SRCS
@ -35,6 +45,8 @@ set(lximage-qt_UIS
mainwindow.ui mainwindow.ui
preferencesdialog.ui preferencesdialog.ui
screenshotdialog.ui screenshotdialog.ui
upload/uploaddialog.ui
) )
qt5_wrap_ui(lximage-qt_UI_H ${lximage-qt_UIS}) qt5_wrap_ui(lximage-qt_UI_H ${lximage-qt_UIS})
@ -64,7 +76,7 @@ include(LXQtTranslateDesktop)
file(GLOB desktop_files_in ../data/*.desktop.in) file(GLOB desktop_files_in ../data/*.desktop.in)
lxqt_translate_desktop(desktop_files SOURCES ${desktop_files_in}) lxqt_translate_desktop(desktop_files SOURCES ${desktop_files_in})
install(FILES ${desktop_files} DESTINATION share/applications) install(FILES ${desktop_files} DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications")
add_executable(lximage-qt add_executable(lximage-qt
${lximage-qt_SRCS} ${lximage-qt_SRCS}
@ -74,11 +86,11 @@ add_executable(lximage-qt
) )
add_definitions( add_definitions(
-DLXIMAGE_DATA_DIR="${CMAKE_INSTALL_PREFIX}/share/lximage-qt" -DLXIMAGE_DATA_DIR="${CMAKE_INSTALL_FULL_DATAROOTDIR}/lximage-qt"
-DLXIMAGE_VERSION="${LXIMAGE_VERSION}" -DLXIMAGE_VERSION="${LXIMAGE_VERSION}"
) )
set(QT_LIBRARIES Qt5::Widgets Qt5::Core Qt5::DBus Qt5::PrintSupport Qt5::X11Extras Qt5::Svg) set(QT_LIBRARIES Qt5::Widgets Qt5::Network Qt5::Core Qt5::DBus Qt5::PrintSupport Qt5::X11Extras Qt5::Svg)
target_link_libraries(lximage-qt target_link_libraries(lximage-qt
fm-qt fm-qt
@ -88,4 +100,4 @@ target_link_libraries(lximage-qt
${XFIXES_LIBRARIES} ${XFIXES_LIBRARIES}
) )
install(TARGETS lximage-qt RUNTIME DESTINATION bin) install(TARGETS lximage-qt RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}")

@ -41,6 +41,11 @@ Application::Application(int& argc, char** argv):
} }
bool Application::init(int argc, char** argv) { bool Application::init(int argc, char** argv) {
Q_UNUSED(argc)
Q_UNUSED(argv)
setAttribute(Qt::AA_UseHighDpiPixmaps, true);
// install the translations built-into Qt itself // install the translations built-into Qt itself
qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath));
installTranslator(&qtTranslator); installTranslator(&qtTranslator);

@ -18,45 +18,31 @@
* *
*/ */
#ifndef LXIMAGE_JOB_H #include "graphicsscene.h"
#define LXIMAGE_JOB_H #include <QMimeData>
#include <QUrl>
#include <gio/gio.h>
namespace LxImage { namespace LxImage {
class Job { GraphicsScene::GraphicsScene(QObject *parent):
public: QGraphicsScene (parent) {
Job(); }
virtual ~Job();
void cancel() {
g_cancellable_cancel(cancellable_);
}
void start();
GError* error() const {
return error_;
}
bool isCancelled() const {
return bool(g_cancellable_is_cancelled(cancellable_));
}
protected:
virtual bool run() = 0;
virtual void finish() = 0;
protected: void GraphicsScene::dragEnterEvent(QGraphicsSceneDragDropEvent *event) {
GCancellable* cancellable_; if(event->mimeData()->hasUrls())
GError* error_; event->acceptProposedAction();
}
private: void GraphicsScene::dragMoveEvent(QGraphicsSceneDragDropEvent *event) {
static gboolean _jobThread(GIOSchedulerJob* job, GCancellable* cancellable, Job* pThis); if(event->mimeData()->hasUrls())
static gboolean _finish(Job* pThis); event->acceptProposedAction();
static void _freeMe(Job* pThis); }
};
void GraphicsScene::dropEvent(QGraphicsSceneDragDropEvent* event) {
QList<QUrl> urlList = event->mimeData()->urls();
if(!urlList.isEmpty())
Q_EMIT fileDropped(urlList.first().toLocalFile());
event->acceptProposedAction();
} }
#endif // LXIMAGE_JOB_H }

@ -0,0 +1,46 @@
/*
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) 2013 <copyright holder> <email>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef LXIMAGE_GRAPHICSSCENE_H
#define LXIMAGE_GRAPHICSSCENE_H
#include <QGraphicsScene>
#include <QGraphicsSceneDragDropEvent>
namespace LxImage {
class GraphicsScene : public QGraphicsScene
{
Q_OBJECT
public:
GraphicsScene(QObject *parent = 0);
protected:
virtual void dragEnterEvent(QGraphicsSceneDragDropEvent *event);
virtual void dragMoveEvent(QGraphicsSceneDragDropEvent *event);
virtual void dropEvent(QGraphicsSceneDragDropEvent* event);
Q_SIGNALS:
void fileDropped(const QString file);
};
}
#endif // LXIMAGE_GRAPHICSSCENE_H

@ -35,7 +35,7 @@ namespace LxImage {
ImageView::ImageView(QWidget* parent): ImageView::ImageView(QWidget* parent):
QGraphicsView(parent), QGraphicsView(parent),
scene_(new QGraphicsScene(this)), scene_(new GraphicsScene(this)),
imageItem_(new QGraphicsRectItem()), imageItem_(new QGraphicsRectItem()),
gifMovie_(nullptr), gifMovie_(nullptr),
cacheTimer_(nullptr), cacheTimer_(nullptr),
@ -49,6 +49,7 @@ ImageView::ImageView(QWidget* parent):
setLineWidth(0); setLineWidth(0);
setScene(scene_); setScene(scene_);
connect(scene_, &GraphicsScene::fileDropped, this, &ImageView::onFileDropped);
imageItem_->hide(); imageItem_->hide();
imageItem_->setPen(QPen(Qt::NoPen)); // remove the border imageItem_->setPen(QPen(Qt::NoPen)); // remove the border
scene_->addItem(imageItem_); scene_->addItem(imageItem_);
@ -68,6 +69,9 @@ ImageView::~ImageView() {
} }
} }
void ImageView::onFileDropped(const QString file) {
Q_EMIT fileDropped(file);
}
void ImageView::wheelEvent(QWheelEvent* event) { void ImageView::wheelEvent(QWheelEvent* event) {
int delta = event->delta(); int delta = event->delta();

@ -21,6 +21,7 @@
#ifndef LXIMAGE_IMAGEVIEW_H #ifndef LXIMAGE_IMAGEVIEW_H
#define LXIMAGE_IMAGEVIEW_H #define LXIMAGE_IMAGEVIEW_H
#include "graphicsscene.h"
#include <QGraphicsView> #include <QGraphicsView>
#include <QGraphicsScene> #include <QGraphicsScene>
#include <QGraphicsRectItem> #include <QGraphicsRectItem>
@ -71,6 +72,9 @@ public:
// if set to true, hides the cursor after 3s of inactivity // if set to true, hides the cursor after 3s of inactivity
void hideCursor(bool enable); void hideCursor(bool enable);
Q_SIGNALS:
void fileDropped(const QString file);
protected: protected:
virtual void wheelEvent(QWheelEvent* event); virtual void wheelEvent(QWheelEvent* event);
virtual void mouseDoubleClickEvent(QMouseEvent* event); virtual void mouseDoubleClickEvent(QMouseEvent* event);
@ -87,11 +91,12 @@ private:
QRect sceneToViewport(const QRectF& rect); QRect sceneToViewport(const QRectF& rect);
private Q_SLOTS: private Q_SLOTS:
void onFileDropped(const QString file);
void generateCache(); void generateCache();
void blankCursor(); void blankCursor();
private: private:
QGraphicsScene* scene_; // the topmost container of all graphic items GraphicsScene* scene_; // the topmost container of all graphic items
QGraphicsRectItem* imageItem_; // the rect item used to draw the image QGraphicsRectItem* imageItem_; // the rect item used to draw the image
QImage image_; // image to show QImage image_; // image to show
QMovie *gifMovie_; // gif animation to show (should be deleted explicitly) QMovie *gifMovie_; // gif animation to show (should be deleted explicitly)

@ -1,64 +0,0 @@
/*
* <one line to give the library's name and an idea of what it does.>
* Copyright (C) 2014 <copyright holder> <email>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include "job.h"
namespace LxImage {
Job::Job():
cancellable_(g_cancellable_new()),
error_(NULL) {
}
Job::~Job() {
g_object_unref(cancellable_);
if(error_)
g_error_free(error_);
}
// This is called from the worker thread, not main thread
gboolean Job::_jobThread(GIOSchedulerJob* job, GCancellable* cancellable, Job* pThis) {
pThis->run();
// do final step in the main thread
if(!g_cancellable_is_cancelled(pThis->cancellable_))
g_io_scheduler_job_send_to_mainloop(job, GSourceFunc(_finish), pThis, NULL);
return FALSE;
}
void Job::start() {
g_io_scheduler_push_job(GIOSchedulerJobFunc(_jobThread),
this, GDestroyNotify(_freeMe),
G_PRIORITY_DEFAULT, cancellable_);
}
// this function is called from main thread only
gboolean Job::_finish(Job* pThis) {
// only do processing if the job is not cancelled
if(!g_cancellable_is_cancelled(pThis->cancellable_)) {
pThis->finish();
}
return TRUE;
}
void Job::_freeMe(Job* pThis) {
delete pThis;
}
}

@ -21,66 +21,79 @@
#include "loadimagejob.h" #include "loadimagejob.h"
#include "mainwindow.h" #include "mainwindow.h"
#include <QImageReader> #include <QImageReader>
#include <QBuffer> #include <QByteArray>
#include <qvarlengtharray.h> #include <qvarlengtharray.h>
#include <libexif/exif-loader.h> #include <libexif/exif-loader.h>
using namespace LxImage; using namespace LxImage;
LoadImageJob::LoadImageJob(MainWindow* window, FmPath* filePath): LoadImageJob::LoadImageJob(const Fm::FilePath & filePath):
Job(), path_{filePath} {
mainWindow_(window),
path_(fm_path_ref(filePath)) {
} }
LoadImageJob::~LoadImageJob() { LoadImageJob::~LoadImageJob() {
fm_path_unref(path_);
} }
// This is called from the worker thread, not main thread // This is called from the worker thread, not main thread
bool LoadImageJob::run() { void LoadImageJob::exec() {
GFile* gfile = fm_path_to_gfile(path_); GFileInputStream* fileStream = nullptr;
GFileInputStream* fileStream = g_file_read(gfile, cancellable_, &error_); Fm::GErrorPtr error;
g_object_unref(gfile); ErrorAction act = ErrorAction::RETRY;
QByteArray imageBuffer;
while (act == ErrorAction::RETRY && !isCancelled())
{
error.reset();
if (nullptr == (fileStream = g_file_read(path_.gfile().get(), cancellable().get(), &error)))
{
act = emitError(error);
continue;
}
if(fileStream) { // if the file stream is successfually opened // the file stream is successfully opened
QBuffer imageBuffer; imageBuffer.truncate(0);
GInputStream* inputStream = G_INPUT_STREAM(fileStream); GInputStream* inputStream = G_INPUT_STREAM(fileStream);
while(!g_cancellable_is_cancelled(cancellable_)) { while(!error && !isCancelled()) {
char buffer[4096]; char buffer[4096];
error.reset();
gssize readSize = g_input_stream_read(inputStream, gssize readSize = g_input_stream_read(inputStream,
buffer, 4096, buffer, 4096,
cancellable_, &error_); cancellable().get(), &error);
if(readSize == -1 || readSize == 0) // error or EOF if(readSize == -1 || readSize == 0) // error or EOF
break; break;
// append the bytes read to the image buffer // append the bytes read to the image buffer
imageBuffer.buffer().append(buffer, readSize); imageBuffer.append(buffer, readSize);
} }
g_input_stream_close(inputStream, NULL, NULL); g_input_stream_close(inputStream, NULL, NULL);
if (!error)
break; // everything read or cancel requested
act = emitError(error);
}
// FIXME: maybe it's a better idea to implement a GInputStream based QIODevice. // FIXME: maybe it's a better idea to implement a GInputStream based QIODevice.
if(!error_ && !g_cancellable_is_cancelled(cancellable_)) { // load the image from buffer if there are no errors if(!error && !isCancelled()) { // load the image from buffer if there are no errors
image_ = QImage::fromData(imageBuffer.buffer()); image_ = QImage::fromData(imageBuffer);
if(!image_.isNull()) { // if the image is loaded correctly if(!image_.isNull()) { // if the image is loaded correctly
// check if this file is a jpeg file // check if this file is a jpeg file
// FIXME: can we use FmFileInfo instead if it's available? // FIXME: can we use FmFileInfo instead if it's available?
const char* basename = fm_path_get_basename(path_); const Fm::CStrPtr basename = path_.baseName();
char* mime_type = g_content_type_guess(basename, NULL, 0, NULL); const Fm::CStrPtr mime_type{g_content_type_guess(basename.get(), NULL, 0, NULL)};
if(mime_type && strcmp(mime_type, "image/jpeg") == 0) { // this is a jpeg file if(mime_type && strcmp(mime_type.get(), "image/jpeg") == 0) { // this is a jpeg file
// use libexif to extract additional info embedded in jpeg files // use libexif to extract additional info embedded in jpeg files
ExifLoader *exif_loader = exif_loader_new(); std::unique_ptr<ExifLoader, decltype (&exif_loader_unref)> exif_loader{exif_loader_new(), &exif_loader_unref};
// write image data to exif loader // write image data to exif loader
exif_loader_write(exif_loader, (unsigned char*)imageBuffer.data().constData(), (unsigned int)imageBuffer.size()); exif_loader_write(exif_loader.get(), reinterpret_cast<unsigned char*>(const_cast<char *>(imageBuffer.constData())), static_cast<unsigned int>(imageBuffer.size()));
ExifData *exif_data = exif_loader_get_data(exif_loader); std::unique_ptr<ExifData, decltype (&exif_data_unref)> exif_data{exif_loader_get_data(exif_loader.get()), &exif_data_unref};
exif_loader_unref(exif_loader); exif_loader.reset();
if(exif_data) { if (exif_data) {
/* reference for EXIF orientation tag: /* reference for EXIF orientation tag:
* http://www.impulseadventure.com/photo/exif-orientation.html */ * http://www.impulseadventure.com/photo/exif-orientation.html */
ExifEntry* orient_ent = exif_data_get_entry(exif_data, EXIF_TAG_ORIENTATION); ExifEntry* orient_ent = exif_data_get_entry(exif_data.get(), EXIF_TAG_ORIENTATION);
if(orient_ent) { /* orientation flag found in EXIF */ if(orient_ent) { /* orientation flag found in EXIF */
gushort orient; gushort orient;
ExifByteOrder bo = exif_data_get_byte_order(exif_data); ExifByteOrder bo = exif_data_get_byte_order(exif_data.get());
/* bo == EXIF_BYTE_ORDER_INTEL ; */ /* bo == EXIF_BYTE_ORDER_INTEL ; */
orient = exif_get_short (orient_ent->data, bo); orient = exif_get_short (orient_ent->data, bo);
qreal rotate_degrees = 0.0; qreal rotate_degrees = 0.0;
@ -105,20 +118,9 @@ bool LoadImageJob::run() {
} }
// TODO: handle other EXIF tags as well // TODO: handle other EXIF tags as well
} }
exif_data_unref(exif_data);
} }
} }
g_free(mime_type);
} }
} }
}
return false;
} }
// this function is called from main thread only
void LoadImageJob::finish() {
// only do processing if the job is not cancelled
if(!g_cancellable_is_cancelled(cancellable_)) {
mainWindow_->onImageLoaded(this);
}
}

@ -21,37 +21,31 @@
#ifndef LXIMAGE_LOADIMAGEJOB_H #ifndef LXIMAGE_LOADIMAGEJOB_H
#define LXIMAGE_LOADIMAGEJOB_H #define LXIMAGE_LOADIMAGEJOB_H
#include <gio/gio.h> #include <libfm-qt/core/filepath.h>
#include <libfm/fm.h>
#include <QImage> #include <QImage>
#include "job.h" #include <libfm-qt/core/job.h>
namespace LxImage { namespace LxImage {
class MainWindow; class LoadImageJob : public Fm::Job {
class LoadImageJob : public Job {
public: public:
LoadImageJob(MainWindow* window, FmPath* filePath); LoadImageJob(const Fm::FilePath & filePath);
QImage image() const { QImage image() const {
return image_; return image_;
} }
FmPath* filePath() const { const Fm::FilePath & filePath() const {
return path_; return path_;
} }
private: private:
~LoadImageJob(); // prevent direct deletion ~LoadImageJob(); // prevent direct deletion
virtual bool run(); virtual void exec() override;
virtual void finish();
public: const Fm::FilePath path_;
MainWindow* mainWindow_;
FmPath* path_;
QImage image_; QImage image_;
}; };

@ -43,6 +43,9 @@
#include <libfm-qt/folderview.h> #include <libfm-qt/folderview.h>
#include <libfm-qt/filepropsdialog.h> #include <libfm-qt/filepropsdialog.h>
#include <libfm-qt/fileoperation.h> #include <libfm-qt/fileoperation.h>
#include <libfm-qt/folderitemdelegate.h>
#include "upload/uploaddialog.h"
using namespace LxImage; using namespace LxImage;
@ -51,11 +54,9 @@ MainWindow::MainWindow():
contextMenu_(new QMenu(this)), contextMenu_(new QMenu(this)),
slideShowTimer_(nullptr), slideShowTimer_(nullptr),
image_(), image_(),
currentFile_(nullptr),
// currentFileInfo_(nullptr), // currentFileInfo_(nullptr),
imageModified_(false), imageModified_(false),
folder_(nullptr), folder_(nullptr),
folderPath_(nullptr),
folderModel_(new Fm::FolderModel()), folderModel_(new Fm::FolderModel()),
proxyModel_(new Fm::ProxyFolderModel()), proxyModel_(new Fm::ProxyFolderModel()),
modelFilter_(new ModelFilter()), modelFilter_(new ModelFilter()),
@ -83,6 +84,8 @@ MainWindow::MainWindow():
ui.view->setContextMenuPolicy(Qt::CustomContextMenu); ui.view->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui.view, &QWidget::customContextMenuRequested, this, &MainWindow::onContextMenu); connect(ui.view, &QWidget::customContextMenuRequested, this, &MainWindow::onContextMenu);
connect(ui.view, &ImageView::fileDropped, this, &MainWindow::onFileDropped);
// install an event filter on the image view // install an event filter on the image view
ui.view->installEventFilter(this); ui.view->installEventFilter(this);
ui.view->setBackgroundBrush(QBrush(settings.bgColor())); ui.view->setBackgroundBrush(QBrush(settings.bgColor()));
@ -128,16 +131,8 @@ MainWindow::~MainWindow() {
loadJob_->cancel(); loadJob_->cancel();
// we don't need to do delete here. It will be done automatically // we don't need to do delete here. It will be done automatically
} }
if(currentFile_)
fm_path_unref(currentFile_);
//if(currentFileInfo_) //if(currentFileInfo_)
// fm_file_info_unref(currentFileInfo_); // fm_file_info_unref(currentFileInfo_);
if(folder_) {
g_signal_handlers_disconnect_by_func(folder_, gpointer(_onFolderLoaded), this);
g_object_unref(folder_);
}
if(folderPath_)
fm_path_unref(folderPath_);
delete folderModel_; delete folderModel_;
delete proxyModel_; delete proxyModel_;
delete modelFilter_; delete modelFilter_;
@ -175,7 +170,7 @@ void MainWindow::on_actionZoomOut_triggered() {
ui.view->zoomOut(); ui.view->zoomOut();
} }
void MainWindow::onFolderLoaded(FmFolder* folder) { void MainWindow::onFolderLoaded() {
// if currently we're showing a file, get its index in the folder now // if currently we're showing a file, get its index in the folder now
// since the folder is fully loaded. // since the folder is fully loaded.
if(currentFile_ && !currentIndex_.isValid()) { if(currentFile_ && !currentIndex_.isValid()) {
@ -187,22 +182,20 @@ void MainWindow::onFolderLoaded(FmFolder* folder) {
} }
} }
// this is used to open the first image of a folder // this is used to open the first image of a folder
else if (currentFile_ == nullptr) else if (!currentFile_)
on_actionFirst_triggered(); on_actionFirst_triggered();
} }
void MainWindow::openImageFile(QString fileName) { void MainWindow::openImageFile(QString fileName) {
FmPath* path = fm_path_new_for_str(qPrintable(fileName)); const Fm::FilePath path = Fm::FilePath::fromPathStr(qPrintable(fileName));
if(currentFile_ && fm_path_equal(currentFile_, path)) {
// the same file! do not load it again // the same file! do not load it again
fm_path_unref(path); if(currentFile_ && currentFile_ == path)
return; return;
}
if (QFileInfo(fileName).isDir()) { if (QFileInfo(fileName).isDir()) {
if(fm_path_equal(path, folderPath_)) { if(path == folderPath_)
fm_path_unref(path);
return; return;
}
QList<QByteArray> formats = QImageReader::supportedImageFormats(); QList<QByteArray> formats = QImageReader::supportedImageFormats();
QStringList formatsFilters; QStringList formatsFilters;
for (const QByteArray& format: formats) for (const QByteArray& format: formats)
@ -210,20 +203,16 @@ void MainWindow::openImageFile(QString fileName) {
QDir dir(fileName); QDir dir(fileName);
dir.setNameFilters(formatsFilters); dir.setNameFilters(formatsFilters);
dir.setFilter(QDir::Files | QDir::NoDotAndDotDot); dir.setFilter(QDir::Files | QDir::NoDotAndDotDot);
if(dir.entryList().isEmpty()) { if(dir.entryList().isEmpty())
fm_path_unref(path);
return; return;
}
if(currentFile_) currentFile_ = Fm::FilePath{};
fm_path_unref(currentFile_);
currentFile_ = nullptr;
loadFolder(path); loadFolder(path);
} else { } else {
// load the image file asynchronously // load the image file asynchronously
loadImage(path); loadImage(path);
loadFolder(fm_path_get_parent(path)); loadFolder(path.parent());
} }
fm_path_unref(path);
} }
// paste the specified image into the current view, // paste the specified image into the current view,
@ -238,9 +227,7 @@ void MainWindow::pasteImage(QImage newImage) {
setModified(true); setModified(true);
currentIndex_ = QModelIndex(); // invaludate current index since we don't have a folder model now currentIndex_ = QModelIndex(); // invaludate current index since we don't have a folder model now
if(currentFile_) currentFile_ = Fm::FilePath{};
fm_path_unref(currentFile_);
currentFile_ = nullptr;
image_ = newImage; image_ = newImage;
ui.view->setImage(image_); ui.view->setImage(image_);
@ -342,44 +329,36 @@ void MainWindow::on_actionSaveAs_triggered() {
if(saveJob_) // if we're currently saving another file if(saveJob_) // if we're currently saving another file
return; return;
QString baseName; QString baseName;
if(currentFile_) { if(currentFile_)
char* dispName = fm_path_display_name(currentFile_, false); baseName = QString::fromUtf8(currentFile_.displayName().get());
baseName = QString::fromUtf8(dispName);
g_free(dispName);
}
QString fileName = saveFileName(baseName); QString fileName = saveFileName(baseName);
if(!fileName.isEmpty()) { if(!fileName.isEmpty()) {
FmPath* path = fm_path_new_for_str(qPrintable(fileName)); const Fm::FilePath path = Fm::FilePath::fromPathStr(qPrintable(fileName));
// save the image file asynchronously // save the image file asynchronously
saveImage(path); saveImage(path);
if(!currentFile_) { // if we haven't load any file yet if(!currentFile_) { // if we haven't load any file yet
currentFile_ = path; currentFile_ = path;
loadFolder(fm_path_get_parent(path)); loadFolder(path.parent());
} }
else
fm_path_unref(path);
} }
} }
void MainWindow::on_actionDelete_triggered() { void MainWindow::on_actionDelete_triggered() {
// delete the current file // delete the current file
if(currentFile_) { if(currentFile_)
FmPathList* paths = fm_path_list_new(); Fm::FileOperation::deleteFiles({currentFile_});
fm_path_list_push_tail(paths, currentFile_);
Fm::FileOperation::deleteFiles(paths);
fm_path_list_unref(paths);
}
} }
void MainWindow::on_actionFileProperties_triggered() { void MainWindow::on_actionFileProperties_triggered() {
if(currentIndex_.isValid()) { if(currentIndex_.isValid()) {
FmFileInfo* file = proxyModel_->fileInfoFromIndex(currentIndex_); const auto file = proxyModel_->fileInfoFromIndex(currentIndex_);
// it's better to use an async job to query the file info since it's // it's better to use an async job to query the file info since it's
// possible that loading of the folder is not finished and the file info is // possible that loading of the folder is not finished and the file info is
// not available yet, but it's overkill for a rarely used function. // not available yet, but it's overkill for a rarely used function.
if(file) if(file)
Fm::FilePropsDialog::showForFile(file); Fm::FilePropsDialog::showForFile(std::move(file));
} }
} }
@ -396,11 +375,9 @@ void MainWindow::on_actionNext_triggered() {
index = proxyModel_->index(currentIndex_.row() + 1, 0); index = proxyModel_->index(currentIndex_.row() + 1, 0);
else else
index = proxyModel_->index(0, 0); index = proxyModel_->index(0, 0);
FmFileInfo* info = proxyModel_->fileInfoFromIndex(index); const auto info = proxyModel_->fileInfoFromIndex(index);
if(info) { if(info)
FmPath* path = fm_file_info_get_path(info); loadImage(info->path(), index);
loadImage(path, index);
}
} }
} }
@ -413,59 +390,56 @@ void MainWindow::on_actionPrevious_triggered() {
index = proxyModel_->index(currentIndex_.row() - 1, 0); index = proxyModel_->index(currentIndex_.row() - 1, 0);
else else
index = proxyModel_->index(proxyModel_->rowCount() - 1, 0); index = proxyModel_->index(proxyModel_->rowCount() - 1, 0);
FmFileInfo* info = proxyModel_->fileInfoFromIndex(index); const auto info = proxyModel_->fileInfoFromIndex(index);
if(info) { if(info)
FmPath* path = fm_file_info_get_path(info); loadImage(info->path(), index);
loadImage(path, index);
}
} }
} }
void MainWindow::on_actionFirst_triggered() { void MainWindow::on_actionFirst_triggered() {
QModelIndex index = proxyModel_->index(0, 0); QModelIndex index = proxyModel_->index(0, 0);
if(index.isValid()) { if(index.isValid()) {
FmFileInfo* info = proxyModel_->fileInfoFromIndex(index); const auto info = proxyModel_->fileInfoFromIndex(index);
if(info) { if(info)
FmPath* path = fm_file_info_get_path(info); loadImage(info->path(), index);
loadImage(path, index);
}
} }
} }
void MainWindow::on_actionLast_triggered() { void MainWindow::on_actionLast_triggered() {
QModelIndex index = proxyModel_->index(proxyModel_->rowCount() - 1, 0); QModelIndex index = proxyModel_->index(proxyModel_->rowCount() - 1, 0);
if(index.isValid()) { if(index.isValid()) {
FmFileInfo* info = proxyModel_->fileInfoFromIndex(index); const auto info = proxyModel_->fileInfoFromIndex(index);
if(info) { if(info)
FmPath* path = fm_file_info_get_path(info);; loadImage(info->path(), index);
loadImage(path, index);
}
} }
} }
void MainWindow::loadFolder(FmPath* newFolderPath) { void MainWindow::loadFolder(const Fm::FilePath & newFolderPath) {
if(folder_) { // an folder is already loaded if(folder_) { // an folder is already loaded
if(fm_path_equal(newFolderPath, folderPath_)) // same folder, ignore if(newFolderPath == folderPath_) // same folder, ignore
return; return;
// free current folder disconnect(folder_.get(), nullptr, this, nullptr); // disconnect from all signals
g_signal_handlers_disconnect_by_func(folder_, gpointer(_onFolderLoaded), this);
g_object_unref(folder_);
fm_path_unref(folderPath_);
} }
folderPath_ = fm_path_ref(newFolderPath); folderPath_ = newFolderPath;
folder_ = fm_folder_from_path(newFolderPath); folder_ = Fm::Folder::fromPath(folderPath_);
g_signal_connect(folder_, "finish-loading", G_CALLBACK(_onFolderLoaded), this); connect(folder_.get(), &Fm::Folder::finishLoading, this, &MainWindow::onFolderLoaded);
folderModel_->setFolder(folder_); folderModel_->setFolder(folder_);
currentIndex_ = QModelIndex(); // set current index to invalid currentIndex_ = QModelIndex(); // set current index to invalid
} }
// the image is loaded (the method is only called if the loading is not cancelled) // the image is loaded (the method is only called if the loading is not cancelled)
void MainWindow::onImageLoaded(LoadImageJob* job) { void MainWindow::onImageLoaded() {
// Note: As the signal finished() is emitted from different thread,
// we can get it even after canceling the job (and setting the loadJob_
// to nullptr). This simple check should be enough.
if (sender() == loadJob_)
{
image_ = loadJob_->image();
loadJob_ = nullptr; // the job object will be freed later automatically loadJob_ = nullptr; // the job object will be freed later automatically
image_ = job->image();
ui.view->setAutoZoomFit(true); ui.view->setAutoZoomFit(true);
ui.view->setImage(image_); ui.view->setImage(image_);
@ -474,18 +448,14 @@ void MainWindow::onImageLoaded(LoadImageJob* job) {
updateUI(); updateUI();
if(job->error()) {
// if there are errors
// TODO: show a info bar?
}
/* we resized and moved the window without showing /* we resized and moved the window without showing
it in updateUI(), so we need to show it here */ it in updateUI(), so we need to show it here */
show(); show();
}
} }
void MainWindow::onImageSaved(SaveImageJob* job) { void MainWindow::onImageSaved() {
if(!job->error()) { if(!saveJob_->failed()) {
setModified(false); setModified(false);
} }
saveJob_ = nullptr; saveJob_ = nullptr;
@ -535,16 +505,16 @@ bool MainWindow::eventFilter(QObject* watched, QEvent* event) {
return QObject::eventFilter(watched, event); return QObject::eventFilter(watched, event);
} }
QModelIndex MainWindow::indexFromPath(FmPath* filePath) { QModelIndex MainWindow::indexFromPath(const Fm::FilePath & filePath) {
// if the folder is already loaded, figure out our index // if the folder is already loaded, figure out our index
// otherwise, it will be done again in onFolderLoaded() when the folder is fully loaded. // otherwise, it will be done again in onFolderLoaded() when the folder is fully loaded.
if(folder_ && fm_folder_is_loaded(folder_)) { if(folder_ && folder_->isLoaded()) {
QModelIndex index; QModelIndex index;
int count = proxyModel_->rowCount(); int count = proxyModel_->rowCount();
for(int row = 0; row < count; ++row) { for(int row = 0; row < count; ++row) {
index = proxyModel_->index(row, 0); index = proxyModel_->index(row, 0);
FmFileInfo* info = proxyModel_->fileInfoFromIndex(index); const auto info = proxyModel_->fileInfoFromIndex(index);
if(info && fm_path_equal(filePath, fm_file_info_get_path(info))) { if(info && filePath == info->path()) {
return index; return index;
} }
} }
@ -564,19 +534,19 @@ void MainWindow::updateUI() {
QString title; QString title;
if(currentFile_) { if(currentFile_) {
char* dispName = fm_path_display_basename(currentFile_); const Fm::CStrPtr dispName = currentFile_.displayName();
if(loadJob_) { // if loading is in progress if(loadJob_) { // if loading is in progress
title = tr("[*]%1 (Loading...) - Image Viewer") title = tr("[*]%1 (Loading...) - Image Viewer")
.arg(QString::fromUtf8(dispName)); .arg(QString::fromUtf8(dispName.get()));
} }
else { else {
if(image_.isNull()) { if(image_.isNull()) {
title = tr("[*]%1 (Failed to Load) - Image Viewer") title = tr("[*]%1 (Failed to Load) - Image Viewer")
.arg(QString::fromUtf8(dispName)); .arg(QString::fromUtf8(dispName.get()));
} }
else { else {
title = tr("[*]%1 (%2x%3) - Image Viewer") title = tr("[*]%1 (%2x%3) - Image Viewer")
.arg(QString::fromUtf8(dispName)) .arg(QString::fromUtf8(dispName.get()))
.arg(image_.width()) .arg(image_.width())
.arg(image_.height()); .arg(image_.height());
/* Here we try to implement the following behavior as far as possible: /* Here we try to implement the following behavior as far as possible:
@ -608,7 +578,6 @@ void MainWindow::updateUI() {
} }
} }
} }
g_free(dispName);
// TODO: update status bar, show current index in the folder // TODO: update status bar, show current index in the folder
} }
else { else {
@ -620,7 +589,7 @@ void MainWindow::updateUI() {
// Load the specified image file asynchronously in a worker thread. // Load the specified image file asynchronously in a worker thread.
// When the loading is finished, onImageLoaded() will be called. // When the loading is finished, onImageLoaded() will be called.
void MainWindow::loadImage(FmPath* filePath, QModelIndex index) { void MainWindow::loadImage(const Fm::FilePath & filePath, QModelIndex index) {
// cancel loading of current image // cancel loading of current image
if(loadJob_) { if(loadJob_) {
loadJob_->cancel(); // the job object will be freed automatically later loadJob_->cancel(); // the job object will be freed automatically later
@ -633,14 +602,12 @@ void MainWindow::loadImage(FmPath* filePath, QModelIndex index) {
} }
currentIndex_ = index; currentIndex_ = index;
if(currentFile_) currentFile_ = filePath;
fm_path_unref(currentFile_);
currentFile_ = fm_path_ref(filePath);
// clear current image, but do not update the view now to prevent flickers // clear current image, but do not update the view now to prevent flickers
image_ = QImage(); image_ = QImage();
const char* basename = fm_path_get_basename(currentFile_); const Fm::CStrPtr basename = currentFile_.baseName();
char* mime_type = g_content_type_guess(basename, NULL, 0, NULL); char* mime_type = g_content_type_guess(basename.get(), NULL, 0, NULL);
QString mimeType; QString mimeType;
if (mime_type) { if (mime_type) {
mimeType = QString(mime_type); mimeType = QString(mime_type);
@ -648,33 +615,47 @@ void MainWindow::loadImage(FmPath* filePath, QModelIndex index) {
} }
if(mimeType == "image/gif" if(mimeType == "image/gif"
|| mimeType == "image/svg+xml" || mimeType == "image/svg+xml-compressed") { || mimeType == "image/svg+xml" || mimeType == "image/svg+xml-compressed") {
char *file_name = fm_path_to_str(currentFile_); const Fm::CStrPtr file_name = currentFile_.toString();
QString fileName(file_name);
g_free(file_name);
ui.view->setAutoZoomFit(true); // like in onImageLoaded() ui.view->setAutoZoomFit(true); // like in onImageLoaded()
if(mimeType == "image/gif") if(mimeType == "image/gif")
ui.view->setGifAnimation(fileName); ui.view->setGifAnimation(QString{file_name.get()});
else else
ui.view->setSVG(fileName); ui.view->setSVG(QString{file_name.get()});
image_ = ui.view->image(); image_ = ui.view->image();
updateUI(); updateUI();
show(); show();
} }
else { else {
// start a new gio job to load the specified image // start a new gio job to load the specified image
loadJob_ = new LoadImageJob(this, filePath); loadJob_ = new LoadImageJob(currentFile_);
loadJob_->start(); connect(loadJob_, &Fm::Job::finished, this, &MainWindow::onImageLoaded);
connect(loadJob_, &Fm::Job::error, this
, [] (const Fm::GErrorPtr & err, Fm::Job::ErrorSeverity /*severity*/, Fm::Job::ErrorAction & /*response*/)
{
// TODO: show a info bar?
qWarning().noquote() << "lximage-qt:" << err.message();
}
, Qt::BlockingQueuedConnection);
loadJob_->runAsync();
updateUI(); updateUI();
} }
} }
void MainWindow::saveImage(FmPath* filePath) { void MainWindow::saveImage(const Fm::FilePath & filePath) {
if(saveJob_) // do not launch a new job if the current one is still in progress if(saveJob_) // do not launch a new job if the current one is still in progress
return; return;
// start a new gio job to save current image to the specified path // start a new gio job to save current image to the specified path
saveJob_ = new SaveImageJob(this, filePath); saveJob_ = new SaveImageJob(image_, filePath);
saveJob_->start(); connect(saveJob_, &Fm::Job::finished, this, &MainWindow::onImageSaved);
connect(saveJob_, &Fm::Job::error, this
, [] (const Fm::GErrorPtr & err, Fm::Job::ErrorSeverity /*severity*/, Fm::Job::ErrorAction & /*response*/)
{
// TODO: show a info bar?
qWarning().noquote() << "lximage-qt:" << err.message();
}
, Qt::BlockingQueuedConnection);
saveJob_->runAsync();
// FIXME: add a cancel button to the UI? update status bar? // FIXME: add a cancel button to the UI? update status bar?
} }
@ -753,6 +734,13 @@ void MainWindow::on_actionPaste_triggered() {
} }
} }
void MainWindow::on_actionUpload_triggered()
{
if (currentFile_.isValid()) {
UploadDialog(this, currentFile_.localPath().get()).exec();
}
}
void MainWindow::on_actionFlipVertical_triggered() { void MainWindow::on_actionFlipVertical_triggered() {
bool hasQGraphicsItem(false); bool hasQGraphicsItem(false);
if(QGraphicsItem *graphItem = getGraphicsItem()) { if(QGraphicsItem *graphItem = getGraphicsItem()) {
@ -871,8 +859,10 @@ void MainWindow::setShowThumbnails(bool show) {
listView->installEventFilter(this); listView->installEventFilter(this);
// FIXME: optimize the size of the thumbnail view // FIXME: optimize the size of the thumbnail view
// FIXME if the thumbnail view is docked elsewhere, update the settings. // FIXME if the thumbnail view is docked elsewhere, update the settings.
if(Fm::FolderItemDelegate* delegate = static_cast<Fm::FolderItemDelegate*>(listView->itemDelegateForColumn(Fm::FolderModel::ColumnFileName))) {
int scrollHeight = style()->pixelMetric(QStyle::PM_ScrollBarExtent); int scrollHeight = style()->pixelMetric(QStyle::PM_ScrollBarExtent);
thumbnailsView_->setFixedHeight(listView->gridSize().height() + scrollHeight); thumbnailsView_->setFixedHeight(delegate->itemSize().height() + scrollHeight);
}
thumbnailsView_->setModel(proxyModel_); thumbnailsView_->setModel(proxyModel_);
proxyModel_->setShowThumbnails(true); proxyModel_->setShowThumbnails(true);
if (currentFile_) { // select the loaded image if (currentFile_) { // select the loaded image
@ -978,14 +968,18 @@ void MainWindow::onExitFullscreen() {
showNormal(); showNormal();
} }
void MainWindow::onThumbnailSelChanged(const QItemSelection& selected, const QItemSelection& deselected) { void MainWindow::onThumbnailSelChanged(const QItemSelection& selected, const QItemSelection& /*deselected*/) {
// the selected item of thumbnail view is changed // the selected item of thumbnail view is changed
if(!selected.isEmpty()) { if(!selected.isEmpty()) {
QModelIndex index = selected.first().topLeft(); QModelIndex index = selected.first().topLeft();
if(index.isValid() && index != currentIndex_) { if(index.isValid() && index != currentIndex_) {
FmFileInfo* file = proxyModel_->fileInfoFromIndex(index); const auto file = proxyModel_->fileInfoFromIndex(index);
if(file) if(file)
loadImage(fm_file_info_get_path(file), index); loadImage(file->path(), index);
} }
} }
} }
void MainWindow::onFileDropped(const QString path) {
openImageFile(path);
}

@ -62,7 +62,7 @@ public:
void pasteImage(QImage newImage); void pasteImage(QImage newImage);
FmPath* currentFile() const { const Fm::FilePath & currentFile() const {
return currentFile_; return currentFile_;
} }
@ -70,9 +70,9 @@ public:
void applySettings(); void applySettings();
protected: protected:
void loadImage(FmPath* filePath, QModelIndex index = QModelIndex()); void loadImage(const Fm::FilePath & filePath, QModelIndex index = QModelIndex());
void saveImage(FmPath* filePath); // save current image to a file void saveImage(const Fm::FilePath & filePath); // save current image to a file
void loadFolder(FmPath* newFolderPath); void loadFolder(const Fm::FilePath & newFolderPath);
QString openFileName(); QString openFileName();
QString openDirectory(); QString openDirectory();
QString saveFileName(QString defaultName = QString()); QString saveFileName(QString defaultName = QString());
@ -80,8 +80,8 @@ protected:
virtual void resizeEvent(QResizeEvent *event); virtual void resizeEvent(QResizeEvent *event);
virtual void closeEvent(QCloseEvent *event); virtual void closeEvent(QCloseEvent *event);
void onImageLoaded(LoadImageJob* job); void onImageLoaded();
void onImageSaved(SaveImageJob* job); void onImageSaved();
virtual bool eventFilter(QObject* watched, QEvent* event); virtual bool eventFilter(QObject* watched, QEvent* event);
private Q_SLOTS: private Q_SLOTS:
@ -103,6 +103,7 @@ private Q_SLOTS:
void on_actionFlipHorizontal_triggered(); void on_actionFlipHorizontal_triggered();
void on_actionCopy_triggered(); void on_actionCopy_triggered();
void on_actionPaste_triggered(); void on_actionPaste_triggered();
void on_actionUpload_triggered();
void on_actionShowThumbnails_triggered(bool checked); void on_actionShowThumbnails_triggered(bool checked);
void on_actionFullScreen_triggered(bool checked); void on_actionFullScreen_triggered(bool checked);
@ -123,31 +124,28 @@ private Q_SLOTS:
void onThumbnailSelChanged(const QItemSelection & selected, const QItemSelection & deselected); void onThumbnailSelChanged(const QItemSelection & selected, const QItemSelection & deselected);
void onFileDropped(const QString path);
private: private:
void onFolderLoaded(FmFolder* folder); void onFolderLoaded();
void updateUI(); void updateUI();
void setModified(bool modified); void setModified(bool modified);
QModelIndex indexFromPath(FmPath* filePath); QModelIndex indexFromPath(const Fm::FilePath & filePath);
QGraphicsItem* getGraphicsItem(); QGraphicsItem* getGraphicsItem();
// GObject related signal handers and callbacks
static void _onFolderLoaded(FmFolder* folder, MainWindow* _this) {
_this->onFolderLoaded(folder);
}
private: private:
Ui::MainWindow ui; Ui::MainWindow ui;
QMenu* contextMenu_; QMenu* contextMenu_;
QTimer* slideShowTimer_; QTimer* slideShowTimer_;
QImage image_; // the image currently shown QImage image_; // the image currently shown
FmPath* currentFile_; // path to current image file Fm::FilePath currentFile_; // path to current image file
// FmFileInfo* currentFileInfo_; // info of the current file, can be NULL // FmFileInfo* currentFileInfo_; // info of the current file, can be NULL
bool imageModified_; // the current image is modified by rotation, flip, or others and needs to be saved bool imageModified_; // the current image is modified by rotation, flip, or others and needs to be saved
// folder browsing // folder browsing
FmFolder* folder_; std::shared_ptr<Fm::Folder> folder_;
FmPath* folderPath_; Fm::FilePath folderPath_;
Fm::FolderModel* folderModel_; Fm::FolderModel* folderModel_;
Fm::ProxyFolderModel* proxyModel_; Fm::ProxyFolderModel* proxyModel_;
ModelFilter* modelFilter_; ModelFilter* modelFilter_;
@ -161,6 +159,6 @@ private:
SaveImageJob* saveJob_; SaveImageJob* saveJob_;
}; };
}; }
#endif // LXIMAGE_MAINWINDOW_H #endif // LXIMAGE_MAINWINDOW_H

@ -108,6 +108,8 @@
<addaction name="actionCopy"/> <addaction name="actionCopy"/>
<addaction name="actionPaste"/> <addaction name="actionPaste"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionUpload"/>
<addaction name="separator"/>
<addaction name="actionPreferences"/> <addaction name="actionPreferences"/>
</widget> </widget>
<addaction name="menu_File"/> <addaction name="menu_File"/>
@ -466,6 +468,17 @@
<string>Ctrl+D</string> <string>Ctrl+D</string>
</property> </property>
</action> </action>
<action name="actionUpload">
<property name="icon">
<iconset theme="upload-media"/>
</property>
<property name="text">
<string>Upload</string>
</property>
<property name="toolTip">
<string>Upload the image</string>
</property>
</action>
</widget> </widget>
<customwidgets> <customwidgets>
<customwidget> <customwidget>

@ -30,10 +30,11 @@ ModelFilter::~ModelFilter() {
} }
bool ModelFilter::filterAcceptsRow(const Fm::ProxyFolderModel* model, FmFileInfo* info) const { bool ModelFilter::filterAcceptsRow(const Fm::ProxyFolderModel* model, const std::shared_ptr<const Fm::FileInfo>& info) const
{
Q_UNUSED(model)
// filter out non-image files and formats that we don't support. // filter out non-image files and formats that we don't support.
if(!fm_file_info_is_image(info)) return info && info->isImage();
return false;
return true;
} }

@ -28,7 +28,7 @@ namespace LxImage {
class ModelFilter: public Fm::ProxyFolderModelFilter { class ModelFilter: public Fm::ProxyFolderModelFilter {
public: public:
ModelFilter(); ModelFilter();
virtual bool filterAcceptsRow(const Fm::ProxyFolderModel* model, FmFileInfo* info) const; virtual bool filterAcceptsRow(const Fm::ProxyFolderModel* model, const std::shared_ptr<const Fm::FileInfo>& info) const override;
virtual ~ModelFilter(); virtual ~ModelFilter();
}; };

@ -111,7 +111,7 @@ void PreferencesDialog::initIconThemes(Settings& settings) {
iconThemes.remove("hicolor"); // remove hicolor, which is only a fallback iconThemes.remove("hicolor"); // remove hicolor, which is only a fallback
QHash<QString, QString>::const_iterator it; QHash<QString, QString>::const_iterator it;
for(it = iconThemes.begin(); it != iconThemes.end(); ++it) { for(it = iconThemes.constBegin(); it != iconThemes.constEnd(); ++it) {
ui.iconTheme->addItem(it.value(), it.key()); ui.iconTheme->addItem(it.value(), it.key());
} }
ui.iconTheme->model()->sort(0); // sort the list of icon theme names ui.iconTheme->model()->sort(0); // sort the list of icon theme names

@ -26,47 +26,58 @@
using namespace LxImage; using namespace LxImage;
SaveImageJob::SaveImageJob(MainWindow* window, FmPath* filePath): SaveImageJob::SaveImageJob(const QImage & image, const Fm::FilePath & filePath):
Job(), path_{filePath},
mainWindow_(window), image_{image},
path_(fm_path_ref(filePath)), failed_{true}
image_(window->image()) { {
} }
SaveImageJob::~SaveImageJob() { SaveImageJob::~SaveImageJob() {
fm_path_unref(path_);
} }
// This is called from the worker thread, not main thread // This is called from the worker thread, not main thread
bool SaveImageJob::run() { void SaveImageJob::exec() {
GFile* gfile = fm_path_to_gfile(path_); const Fm::CStrPtr f = path_.baseName();
GFileOutputStream* fileStream = g_file_replace(gfile, NULL, false, G_FILE_CREATE_NONE, cancellable_, &error_); char const * format = f.get();
g_object_unref(gfile);
if(fileStream) { // if the file stream is successfually opened
const char* format = fm_path_get_basename(path_);
format = strrchr(format, '.'); format = strrchr(format, '.');
if(format) // use filename extension as the image format if(format) // use filename extension as the image format
++format; ++format;
QBuffer imageBuffer; QBuffer imageBuffer;
image_.save(&imageBuffer, format); // save the image to buffer image_.save(&imageBuffer, format); // save the image to buffer
GFileOutputStream* fileStream = nullptr;
Fm::GErrorPtr error;
ErrorAction act = ErrorAction::RETRY;
while (act == ErrorAction::RETRY && !isCancelled())
{
error.reset();
if (nullptr == (fileStream = g_file_replace(path_.gfile().get(), NULL, false, G_FILE_CREATE_NONE, cancellable().get(), &error)))
{
act = emitError(error);
continue;
}
// the file stream is successfually opened
if (!isCancelled())
{
GOutputStream* outputStream = G_OUTPUT_STREAM(fileStream); GOutputStream* outputStream = G_OUTPUT_STREAM(fileStream);
g_output_stream_write_all(outputStream, g_output_stream_write_all(outputStream,
imageBuffer.data().constData(), imageBuffer.data().constData(),
imageBuffer.size(), imageBuffer.size(),
NULL, NULL,
cancellable_, cancellable().get(),
&error_); &error);
g_output_stream_close(outputStream, NULL, NULL); g_output_stream_close(outputStream, NULL, NULL);
if (!error)
{
// successfully written
failed_ = false;
break; // successfully written
} }
return false;
}
// this function is called from main thread only act = emitError(error);
void SaveImageJob::finish() { }
// only do processing if the job is not cancelled
if(!g_cancellable_is_cancelled(cancellable_)) {
mainWindow_->onImageSaved(this);
} }
} }

@ -21,39 +21,39 @@
#ifndef LXIMAGE_SAVEIMAGEJOB_H #ifndef LXIMAGE_SAVEIMAGEJOB_H
#define LXIMAGE_SAVEIMAGEJOB_H #define LXIMAGE_SAVEIMAGEJOB_H
#include <gio/gio.h> #include <libfm-qt/core/job.h>
#include <libfm/fm.h> #include <libfm-qt/core/filepath.h>
#include <QImage> #include <QImage>
#include "job.h"
namespace LxImage { namespace LxImage {
class MainWindow; class SaveImageJob : public Fm::Job {
class SaveImageJob : public Job {
public: public:
SaveImageJob(MainWindow* window, FmPath* filePath); SaveImageJob(const QImage & image, const Fm::FilePath & filePath);
QImage image() const { QImage image() const {
return image_; return image_;
} }
FmPath* filePath() const { const Fm::FilePath & filePath() const {
return path_; return path_;
} }
bool failed() const
{
return failed_;
}
protected: protected:
virtual bool run(); virtual void exec() override;
virtual void finish();
private: private:
~SaveImageJob(); // prevent direct deletion ~SaveImageJob(); // prevent direct deletion
public: const Fm::FilePath path_;
MainWindow* mainWindow_; const QImage image_;
FmPath* path_; bool failed_;
QImage image_;
}; };
} }

@ -19,6 +19,7 @@
#include "screenshotdialog.h" #include "screenshotdialog.h"
#include "screenshotselectarea.h"
#include <QTimer> #include <QTimer>
#include <QPixmap> #include <QPixmap>
#include <QImage> #include <QImage>
@ -173,6 +174,13 @@ void ScreenshotDialog::doScreenshot() {
} }
} }
if(ui.screenArea->isChecked() && !image.isNull()) {
ScreenshotSelectArea selectArea(image);
if(QDialog::Accepted == selectArea.exec()) {
image = image.copy(selectArea.selectedArea());
}
}
Application* app = static_cast<Application*>(qApp); Application* app = static_cast<Application*>(qApp);
MainWindow* window = app->createWindow(); MainWindow* window = app->createWindow();
if(!image.isNull()) if(!image.isNull())

@ -15,8 +15,7 @@
</property> </property>
<property name="windowIcon"> <property name="windowIcon">
<iconset theme="camera-photo"> <iconset theme="camera-photo">
<normaloff/> <normaloff>.</normaloff>.</iconset>
</iconset>
</property> </property>
<layout class="QFormLayout" name="formLayout"> <layout class="QFormLayout" name="formLayout">
<property name="fieldGrowthPolicy"> <property name="fieldGrowthPolicy">
@ -52,6 +51,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QRadioButton" name="screenArea">
<property name="text">
<string>Capture an area of the screen</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

@ -0,0 +1,51 @@
/*
<one line to give the library's name and an idea of what it does.>
Copyright (C) 2013 <copyright holder> <email>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "screenshotselectarea.h"
#include <QMouseEvent>
using namespace LxImage;
ScreenshotSelectArea::ScreenshotSelectArea(const QImage & image, QWidget* parent) : QDialog(parent)
{
scene_ = new QGraphicsScene(this);
scene_->addPixmap(QPixmap::fromImage(image));
view_ = new ScreenshotSelectAreaGraphicsView(scene_, this);
view_->setRenderHints( QPainter::Antialiasing );
view_->setHorizontalScrollBarPolicy ( Qt::ScrollBarAlwaysOff );
view_->setVerticalScrollBarPolicy ( Qt::ScrollBarAlwaysOff );
view_->show();
view_->move(0,0);
view_->resize(image.width(), image.height());
setWindowState(windowState() | Qt::WindowFullScreen);
connect(view_, &ScreenshotSelectAreaGraphicsView::selectedArea, this, &ScreenshotSelectArea::areaSelected);
}
QRect ScreenshotSelectArea::selectedArea()
{
return selectedRect_;
}
void ScreenshotSelectArea::areaSelected(QRect rect)
{
this->selectedRect_ = rect;
accept();
}

@ -0,0 +1,50 @@
/*
<one line to give the library's name and an idea of what it does.>
Copyright (C) 2013 <copyright holder> <email>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef LXIMAGE_SCREENSHOTDIALOG_SELECT_AREA_H
#define LXIMAGE_SCREENSHOTDIALOG_SELECT_AREA_H
#include "screenshotselectareagraphicsview.h"
#include <QDialog>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
namespace LxImage {
class ScreenshotSelectArea : public QDialog {
Q_OBJECT
public:
ScreenshotSelectArea(const QImage & image, QWidget* parent = 0);
QRect selectedArea();
private Q_SLOTS:
void areaSelected(QRect rect);
private:
QGraphicsScene *scene_;
ScreenshotSelectAreaGraphicsView *view_;
QRect selectedRect_;
};
}
#endif // LXIMAGE_SCREENSHOTDIALOG_SELECT_AREA_H

@ -0,0 +1,58 @@
/*
<one line to give the library's name and an idea of what it does.>
Copyright (C) 2013 <copyright holder> <email>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "screenshotselectareagraphicsview.h"
#include <QMouseEvent>
using namespace LxImage;
ScreenshotSelectAreaGraphicsView::ScreenshotSelectAreaGraphicsView(QGraphicsScene* scene, QWidget* parent) : QGraphicsView(scene, parent)
{
p0_ = QPointF(-1.0, -1.0);
selectedAreaRect_ = nullptr;
setCursor(Qt::CrossCursor);
}
void ScreenshotSelectAreaGraphicsView::mousePressEvent(QMouseEvent *event)
{
if(p0_.x() < 0) {
p0_ = QPointF(event->pos());
} else {
if(selectedAreaRect_ == nullptr) {
QColor highlight = palette().color(QPalette::Active,QPalette::Highlight);
QPen pen(highlight, 3, Qt::DashDotLine, Qt::RoundCap, Qt::RoundJoin);
QColor color(highlight);
color.setAlpha(128);
QBrush brush(color);
selectedAreaRect_ = scene()->addRect(QRectF(), pen, brush);
}
selectedAreaRect_->setRect(QRectF(p0_,QPointF(event->pos())).normalized());
}
}
void ScreenshotSelectAreaGraphicsView::mouseMoveEvent(QMouseEvent *event)
{
mousePressEvent(event);
}
void ScreenshotSelectAreaGraphicsView::mouseReleaseEvent(QMouseEvent *event)
{
Q_EMIT selectedArea(QRectF(p0_,QPointF(event->pos())).normalized().toRect());
}

@ -0,0 +1,52 @@
/*
<one line to give the library's name and an idea of what it does.>
Copyright (C) 2013 <copyright holder> <email>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef LXIMAGE_SCREENSHOTDIALOG_SELECT_AREA_GRAPICS_VIEW_H
#define LXIMAGE_SCREENSHOTDIALOG_SELECT_AREA_GRAPICS_VIEW_H
#include <QDialog>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsRectItem>
namespace LxImage {
class ScreenshotSelectAreaGraphicsView : public QGraphicsView {
Q_OBJECT
public:
ScreenshotSelectAreaGraphicsView(QGraphicsScene* scene, QWidget* parent = 0);
Q_SIGNALS:
void selectedArea(QRect rect);
protected:
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
private:
QPointF p0_;
QGraphicsRectItem *selectedAreaRect_;
};
}
#endif // LXIMAGE_SCREENSHOTDIALOG_SELECT_AREA_H

@ -0,0 +1,4 @@
#Translations
Name[lt]=Ekrano kopija
GenericName[lt]=Ekrano kopija
Comment[lt]=Padaryti ekrano kopiją

@ -0,0 +1,4 @@
#Translations
Name[lt]=LXImage
GenericName[lt]=Paveikslų žiūryklė
Comment[lt]=LXQt paveikslų žiūryklė

@ -0,0 +1,56 @@
/*
LxImage - image viewer and screenshot tool for lxqt
Copyright (C) 2017 Nathan Osman <nathan@quickmediasolutions.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <QHttpMultiPart>
#include <QHttpPart>
#include <QNetworkRequest>
#include <QUrl>
#include <QUrlQuery>
#include "imageshackprovider.h"
#include "imageshackupload.h"
using namespace LxImage;
const QString gUploadURL = "https://api.imageshack.com/v2/images";
const QByteArray gAPIKey = "4DINORVXbcbda9ac64b424a0e6b37caed4cf3b8b";
Upload *ImageShackProvider::upload(QIODevice *device)
{
// Construct the URL that will be used for the upload
QUrlQuery query;
query.addQueryItem("api_key", gAPIKey);
QUrl url(gUploadURL);
url.setQuery(query);
// The first (and only) part is the file upload
QHttpPart filePart;
filePart.setBodyDevice(device);
filePart.setHeader(
QNetworkRequest::ContentDispositionHeader,
"form-data; name=\"file\"; filename=\"upload.jpg\""
);
// Create the multipart and append the file part
QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType, device);
multiPart->append(filePart);
// Start the request and wrap it in an ImageShackUpload
return new ImageShackUpload(sManager.post(QNetworkRequest(url), multiPart));
}

@ -0,0 +1,43 @@
/*
LxImage - image viewer and screenshot tool for lxqt
Copyright (C) 2017 Nathan Osman <nathan@quickmediasolutions.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef LXIMAGE_IMAGESHACKPROVIDER_H
#define LXIMAGE_IMAGESHACKPROVIDER_H
#include "provider.h"
namespace LxImage {
class Upload;
/**
* @brief Create uploads to ImageShack's API
*/
class ImageShackProvider : public Provider
{
Q_OBJECT
public:
virtual Upload *upload(QIODevice *device);
};
}
#endif // LXIMAGE_IMAGESHACKPROVIDER_H

@ -0,0 +1,55 @@
/*
LxImage - image viewer and screenshot tool for lxqt
Copyright (C) 2017 Nathan Osman <nathan@quickmediasolutions.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include "imageshackupload.h"
using namespace LxImage;
ImageShackUpload::ImageShackUpload(QNetworkReply *reply)
: Upload(reply)
{
}
void ImageShackUpload::processReply(const QByteArray &data)
{
// Obtain the root object from the JSON response
QJsonObject object(QJsonDocument::fromJson(data).object());
// Attempt to retrieve the link
bool success = object.value("success").toBool();
QString link = object.value("result").toObject().value("images").toArray()
.at(0).toObject().value("direct_link").toString();
// Check for success
if (!success || link.isNull()) {
QString errorMessage = object.value("error").toObject()
.value("error_message").toString();
if (errorMessage.isNull()) {
errorMessage = tr("unknown error response");
}
Q_EMIT error(errorMessage);
} else {
Q_EMIT completed("https://" + link);
}
}

@ -0,0 +1,45 @@
/*
LxImage - image viewer and screenshot tool for lxqt
Copyright (C) 2017 Nathan Osman <nathan@quickmediasolutions.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef LXIMAGE_IMAGESHACKUPLOAD_H
#define LXIMAGE_IMAGESHACKUPLOAD_H
#include "upload.h"
namespace LxImage {
/**
* @brief Upload to ImageShack's API
*/
class ImageShackUpload : public Upload
{
Q_OBJECT
public:
explicit ImageShackUpload(QNetworkReply *reply);
protected:
virtual void processReply(const QByteArray &data);
};
}
#endif // LXIMAGE_IMAGESHACKUPLOAD_H

@ -0,0 +1,42 @@
/*
LxImage - image viewer and screenshot tool for lxqt
Copyright (C) 2017 Nathan Osman <nathan@quickmediasolutions.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QUrl>
#include "imgurprovider.h"
#include "imgurupload.h"
using namespace LxImage;
const QUrl gUploadURL("https://api.imgur.com/3/upload.json");
const QByteArray gAuthHeader = "Client-ID 63ff047cd8bcf9e";
const QByteArray gTypeHeader = "application/x-www-form-urlencoded";
Upload *ImgurProvider::upload(QIODevice *device)
{
// Create the request with the correct HTTP headers
QNetworkRequest request(gUploadURL);
request.setHeader(QNetworkRequest::ContentTypeHeader, gTypeHeader);
request.setRawHeader("Authorization", gAuthHeader);
// Start the request and wrap it in an ImgurUpload
return new ImgurUpload(sManager.post(request, device));
}

@ -0,0 +1,41 @@
/*
LxImage - image viewer and screenshot tool for lxqt
Copyright (C) 2017 Nathan Osman <nathan@quickmediasolutions.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef LXIMAGE_IMGURPROVIDER_H
#define LXIMAGE_IMGURPROVIDER_H
#include "provider.h"
namespace LxImage {
/**
* @brief Create uploads to Imgur's API
*/
class ImgurProvider : public Provider
{
Q_OBJECT
public:
virtual Upload *upload(QIODevice *device);
};
}
#endif // LXIMAGE_IMGURPROVIDER_H

@ -0,0 +1,52 @@
/*
LxImage - image viewer and screenshot tool for lxqt
Copyright (C) 2017 Nathan Osman <nathan@quickmediasolutions.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <QJsonDocument>
#include <QJsonObject>
#include "imgurupload.h"
using namespace LxImage;
ImgurUpload::ImgurUpload(QNetworkReply *reply)
: Upload(reply)
{
}
void ImgurUpload::processReply(const QByteArray &data)
{
// Obtain the root object from the JSON response
QJsonObject object(QJsonDocument::fromJson(data).object());
// Attempt to retrieve the value for "success" and "data->link"
bool success = object.value("success").toBool();
QJsonObject dataObject = object.value("data").toObject();
QString dataLink = dataObject.value("link").toString();
QString dataError = dataObject.value("error").toString();
// Ensure that "success" is true & link is valid, otherwise throw an error
if (!success || dataLink.isNull()) {
if (dataError.isNull()) {
dataError = tr("unknown error response");
}
Q_EMIT error(dataError);
} else {
Q_EMIT completed(dataLink);
}
}

@ -0,0 +1,43 @@
/*
LxImage - image viewer and screenshot tool for lxqt
Copyright (C) 2017 Nathan Osman <nathan@quickmediasolutions.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef LXIMAGE_IMGURUPLOAD_H
#define LXIMAGE_IMGURUPLOAD_H
#include "upload.h"
namespace LxImage {
/**
* @brief Process uploads to Imgur's API
*/
class ImgurUpload : public Upload
{
Q_OBJECT
public:
explicit ImgurUpload(QNetworkReply *reply);
virtual void processReply(const QByteArray &data);
};
}
#endif // LXIMAGE_IMGURUPLOAD_H

@ -0,0 +1,24 @@
/*
LxImage - image viewer and screenshot tool for lxqt
Copyright (C) 2017 Nathan Osman <nathan@quickmediasolutions.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "provider.h"
using namespace LxImage;
QNetworkAccessManager Provider::sManager;

@ -0,0 +1,54 @@
/*
LxImage - image viewer and screenshot tool for lxqt
Copyright (C) 2017 Nathan Osman <nathan@quickmediasolutions.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef LXIMAGE_PROVIDER_H
#define LXIMAGE_PROVIDER_H
#include <QIODevice>
#include <QNetworkAccessManager>
#include <QObject>
namespace LxImage {
class Upload;
/**
* @brief Base class for providers
*/
class Provider : public QObject
{
Q_OBJECT
public:
/**
* @brief Upload the provided data
* @param device reader for uploaded data from
* @return newly created upload
*/
virtual Upload *upload(QIODevice *device) = 0;
protected:
static QNetworkAccessManager sManager;
};
}
#endif // LXIMAGE_PROVIDER_H

@ -0,0 +1,57 @@
/*
LxImage - image viewer and screenshot tool for lxqt
Copyright (C) 2017 Nathan Osman <nathan@quickmediasolutions.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "upload.h"
using namespace LxImage;
Upload::Upload(QNetworkReply *reply)
: mReply(reply)
{
// Reparent the reply to this object
mReply->setParent(this);
// Emit progress() when upload progress changes
connect(mReply, &QNetworkReply::uploadProgress, [this](qint64 bytesSent, qint64 bytesTotal) {
Q_EMIT progress(static_cast<int>(
static_cast<double>(bytesSent) / static_cast<double>(bytesTotal) * 100.0
));
});
// Emit error() when a socket error occurs
connect(mReply, static_cast<void(QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error), [this](QNetworkReply::NetworkError) {
Q_EMIT error(mReply->errorString());
});
// Process the request when it finishes
connect(mReply, &QNetworkReply::finished, [this]() {
if (mReply->error() == QNetworkReply::NoError) {
processReply(mReply->readAll());
}
});
// Emit finished() when completed() or error() is emitted
connect(this, &Upload::completed, this, &Upload::finished);
connect(this, &Upload::error, this, &Upload::finished);
}
void Upload::abort()
{
mReply->abort();
}

@ -0,0 +1,96 @@
/*
LxImage - image viewer and screenshot tool for lxqt
Copyright (C) 2017 Nathan Osman <nathan@quickmediasolutions.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef LXIMAGE_UPLOAD_H
#define LXIMAGE_UPLOAD_H
#include <QNetworkReply>
#include <QObject>
namespace LxImage {
/**
* @brief Base class for uploads
*/
class Upload : public QObject
{
Q_OBJECT
public:
/**
* @brief Create an upload
* @param reply network reply
*
* The upload will assume ownership of the network reply and connect to its
* signals, emitting uploadError() when something goes wrong.
*/
explicit Upload(QNetworkReply *reply);
/**
* @brief Abort the upload
*/
void abort();
Q_SIGNALS:
/**
* @brief Indicate that upload progress has changed
* @param value new progress value
*/
void progress(int value);
/**
* @brief Indicate that the upload completed
* @param url new URL of the upload
*/
void completed(const QString &url);
/**
* @brief Indicate that an error occurred
* @param message description of the error
*/
void error(const QString &message);
/**
* @brief Indicate that the upload finished
*
* This signal is emitted after either completed() or error().
*/
void finished();
protected:
/**
* @brief Process the data from the reply
* @param data content from the reply
*
* This method should parse the data and either emit the completed() or
* error() signal.
*/
virtual void processReply(const QByteArray &data) = 0;
private:
QNetworkReply *mReply;
};
}
#endif // LXIMAGE_UPLOAD_H

@ -0,0 +1,141 @@
/*
LxImage - image viewer and screenshot tool for lxqt
Copyright (C) 2017 Nathan Osman <nathan@quickmediasolutions.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <QComboBox>
#include <QLineEdit>
#include <QMessageBox>
#include <QProgressBar>
#include <QPushButton>
#include <QVariant>
#include "imageshackprovider.h"
#include "imgurprovider.h"
#include "provider.h"
#include "upload.h"
#include "uploaddialog.h"
using namespace LxImage;
ImgurProvider gImgurProvider;
ImageShackProvider gImageShackProvider;
UploadDialog::UploadDialog(QWidget *parent, const QString &filename)
: QDialog(parent),
mState(SelectProvider),
mFile(filename),
mUpload(nullptr)
{
ui.setupUi(this);
// Populate the list of providers
ui.providerComboBox->addItem(tr("Imgur"), QVariant::fromValue(&gImgurProvider));
ui.providerComboBox->addItem(tr("ImageShack"), QVariant::fromValue(&gImageShackProvider));
updateUi();
}
void UploadDialog::on_actionButton_clicked()
{
switch (mState) {
case SelectProvider:
start();
break;
case UploadInProgress:
mUpload->abort();
break;
case Completed:
accept();
break;
}
}
void UploadDialog::start()
{
// Attempt to open the file
if (!mFile.open(QIODevice::ReadOnly)) {
showError(mFile.errorString());
return;
}
// Retrieve the selected provider
Provider *provider = ui.providerComboBox->itemData(
ui.providerComboBox->currentIndex()
).value<Provider*>();
// Create the upload
mUpload = provider->upload(&mFile);
// Update the progress bar as the upload progresses
connect(mUpload, &Upload::progress, ui.progressBar, &QProgressBar::setValue);
// If the request completes, show the link to the user
connect(mUpload, &Upload::completed, [this](const QString &url) {
ui.linkLineEdit->setText(url);
mState = Completed;
updateUi();
});
// If the request fails, show an error
connect(mUpload, &Upload::error, [this](const QString &message) {
showError(message);
});
// Destroy the upload when it completes
connect(mUpload, &Upload::finished, [this]() {
mFile.close();
mUpload->deleteLater();
mUpload = nullptr;
});
mState = UploadInProgress;
updateUi();
}
void UploadDialog::updateUi()
{
// Show the appropriate control given the current state
ui.providerComboBox->setVisible(mState == SelectProvider);
ui.progressBar->setVisible(mState == UploadInProgress);
ui.linkLineEdit->setVisible(mState == Completed);
// Reset the progress bar to zero
ui.progressBar->setValue(0);
// Set the correct button text
switch (mState) {
case SelectProvider:
ui.actionButton->setText(tr("Start"));
break;
case UploadInProgress:
ui.actionButton->setText(tr("Stop"));
break;
case Completed:
ui.actionButton->setText(tr("Close"));
break;
}
}
void UploadDialog::showError(const QString &message)
{
QMessageBox::critical(this, tr("Error"), message);
mState = SelectProvider;
updateUi();
}

@ -0,0 +1,73 @@
/*
LxImage - image viewer and screenshot tool for lxqt
Copyright (C) 2017 Nathan Osman <nathan@quickmediasolutions.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef LXIMAGE_UPLOADDIALOG_H
#define LXIMAGE_UPLOADDIALOG_H
#include <QDialog>
#include <QFile>
#include "ui_uploaddialog.h"
namespace LxImage {
class Upload;
/**
* @brief Dialog for uploading an image
*/
class UploadDialog : public QDialog
{
Q_OBJECT
public:
/**
* @brief Create a dialog for uploading the specified file
* @param parent widget parent
* @param filename absolute path to file
*/
UploadDialog(QWidget *parent, const QString &filename);
private Q_SLOTS:
void on_actionButton_clicked();
private:
void start();
void updateUi();
void showError(const QString &message);
Ui::UploadDialog ui;
enum {
SelectProvider,
UploadInProgress,
Completed,
} mState;
QFile mFile;
Upload *mUpload;
};
}
#endif // LXIMAGE_UPLOADDIALOG_H

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>UploadDialog</class>
<widget class="QDialog" name="UploadDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>300</width>
<height>100</height>
</rect>
</property>
<property name="windowTitle">
<string>Upload</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QComboBox" name="providerComboBox"/>
</item>
<item>
<widget class="QProgressBar" name="progressBar">
<property name="enabled">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLineEdit" name="linkLineEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="actionButton">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
Loading…
Cancel
Save