diff --git a/AUTHORS b/AUTHORS index dba5c0a..f82d468 100644 --- a/AUTHORS +++ b/AUTHORS @@ -3,7 +3,7 @@ Upstream Authors: Hong Jen Yee (PCMan) Copyright: - Copyright (c) 2013-2015 LXQt team + Copyright (c) 2013-2017 LXQt team License: GPL-2+ and LGPL-2.1+ The full text of the licenses can be found in the 'COPYING' file. diff --git a/CHANGELOG b/CHANGELOG index 4f0abd0..f5a9d3e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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) * Create lximage-qt-screenshot_it.desktop (#83) * Add *da.desktop files diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e0b354..9f1d464 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,37 +4,35 @@ project(lximage-qt) include(GNUInstallDirs) set(MAJOR_VERSION 0) -set(MINOR_VERSION 5) -set(PATCH_VERSION 1) +set(MINOR_VERSION 6) +set(PATCH_VERSION 0) 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_INCLUDE_CURRENT_DIR ON) set(CMAKE_POSITION_INDEPENDENT_CODE ON) find_package(Qt5Widgets REQUIRED) +find_package(Qt5Network REQUIRED) find_package(Qt5DBus REQUIRED) -find_package(Qt5PrintSupport REQUIRED QUIET) -find_package(Qt5X11Extras REQUIRED QUIET) -find_package(Qt5LinguistTools REQUIRED QUIET) -find_package(Qt5Svg REQUIRED QUIET) -find_package(fm-qt REQUIRED QUIET) +find_package(Qt5PrintSupport REQUIRED) +find_package(Qt5X11Extras REQUIRED) +find_package(Qt5LinguistTools REQUIRED) +find_package(Qt5Svg REQUIRED) +find_package(fm-qt 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) -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. # for screenshot support find_package(X11 REQUIRED) +find_package(PkgConfig REQUIRED) # Xfixes is needed to capture the mouse cursor image pkg_check_modules(XFIXES REQUIRED xfixes) diff --git a/debian/changelog b/debian/changelog index 52a8e83..5ac4df1 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,14 @@ +lximage-qt (0.6.0-1) experimental; urgency=medium + + * Cherry-picking upstream release 0.6.0. + * Switched to experimental + * Bumped libfm-qt-dev to >=0.12.0 + * Bumped build-tools to >= 0.4.0 + * Bumped Standards to 4.1.1 + * Bumped years in copyright + + -- Alf Gaida Tue, 24 Oct 2017 23:03:31 +0200 + lximage-qt (0.5.1-1) unstable; urgency=medium * Cherry-picking upstream release 0.5.1. diff --git a/debian/control b/debian/control index 06ab901..af671a6 100644 --- a/debian/control +++ b/debian/control @@ -9,17 +9,17 @@ Build-Depends: debhelper (>= 10), libexif-dev, libglib2.0-dev, libfm-dev, - libfm-qt-dev (>= 0.11.2), + libfm-qt-dev (>= 0.12.0), libkf5windowsystem-dev, libmenu-cache-dev, libqt5svg5-dev, libqt5x11extras5-dev, libx11-dev, libxfixes-dev, - lxqt-build-tools (>= 0.3.0) -Standards-Version: 3.9.8 -Vcs-Browser: https://anonscm.debian.org/cgit/pkg-lxqt/lximage-qt.git/?h=debian/sid -Vcs-Git: https://anonscm.debian.org/git/pkg-lxqt/lximage-qt.git -b debian/sid + lxqt-build-tools (>= 0.4.0), +Standards-Version: 4.1.1 +Vcs-Browser: https://anonscm.debian.org/cgit/pkg-lxqt/lximage-qt.git/?h=debian/experimental +Vcs-Git: https://anonscm.debian.org/git/pkg-lxqt/lximage-qt.git -b debian/experimental Homepage: https://github.com/lxde/lximage-qt Package: lximage-qt diff --git a/debian/copyright b/debian/copyright index 87d2c0a..fd22e39 100644 --- a/debian/copyright +++ b/debian/copyright @@ -4,7 +4,7 @@ Source: https://github.com/lxde/lximage-qt Files: * Copyright: 2013-2016 Hong Jen Yee (PCMan) - 2013-2016 LXQt team + 2013-2017 LXQt team License: GPL-2+ and LGPL-2.1+ Files: src/application.* @@ -17,17 +17,8 @@ Files: src/application.* Copyright: 2013-2016 Hong Jen Yee (PCMan) License: GPL-2+ -Files: src/imageview.* -Copyright: Copyright (C) 2013 -License: GPL-2+ - -Files: src/job.* - src/screenshotdialog.* -Copyright: Copyright (C) 2013 - 2014 -License: LGPL-2.1+ - Files: debian/* -Copyright: 2014-2016 Alf Gaida +Copyright: 2014-2017 Alf Gaida 2015 Andrew Lee (李健秋) 2015 ChangZhuo Chen (陳昌倬) License: LGPL-2.1+ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index eb7e8ae..77e138c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -16,11 +16,21 @@ set(lximage-qt_SRCS application.cpp imageview.cpp modelfilter.cpp - job.cpp loadimagejob.cpp saveimagejob.cpp screenshotdialog.cpp + screenshotselectarea.cpp + screenshotselectareagraphicsview.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 @@ -35,6 +45,8 @@ set(lximage-qt_UIS mainwindow.ui preferencesdialog.ui screenshotdialog.ui + + upload/uploaddialog.ui ) qt5_wrap_ui(lximage-qt_UI_H ${lximage-qt_UIS}) @@ -64,7 +76,7 @@ include(LXQtTranslateDesktop) file(GLOB desktop_files_in ../data/*.desktop.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 ${lximage-qt_SRCS} @@ -74,11 +86,11 @@ add_executable(lximage-qt ) add_definitions( - -DLXIMAGE_DATA_DIR="${CMAKE_INSTALL_PREFIX}/share/lximage-qt" + -DLXIMAGE_DATA_DIR="${CMAKE_INSTALL_FULL_DATAROOTDIR}/lximage-qt" -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 fm-qt @@ -88,4 +100,4 @@ target_link_libraries(lximage-qt ${XFIXES_LIBRARIES} ) -install(TARGETS lximage-qt RUNTIME DESTINATION bin) +install(TARGETS lximage-qt RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}") diff --git a/src/application.cpp b/src/application.cpp index e84024b..d5e699a 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -41,6 +41,11 @@ Application::Application(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 qtTranslator.load("qt_" + QLocale::system().name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); installTranslator(&qtTranslator); diff --git a/src/job.h b/src/graphicsscene.cpp similarity index 57% rename from src/job.h rename to src/graphicsscene.cpp index 165bf5c..3d767e9 100644 --- a/src/job.h +++ b/src/graphicsscene.cpp @@ -18,45 +18,31 @@ * */ -#ifndef LXIMAGE_JOB_H -#define LXIMAGE_JOB_H - -#include +#include "graphicsscene.h" +#include +#include namespace LxImage { -class Job { -public: - 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; +GraphicsScene::GraphicsScene(QObject *parent): + QGraphicsScene (parent) { +} -protected: - GCancellable* cancellable_; - GError* error_; +void GraphicsScene::dragEnterEvent(QGraphicsSceneDragDropEvent *event) { + if(event->mimeData()->hasUrls()) + event->acceptProposedAction(); +} -private: - static gboolean _jobThread(GIOSchedulerJob* job, GCancellable* cancellable, Job* pThis); - static gboolean _finish(Job* pThis); - static void _freeMe(Job* pThis); -}; +void GraphicsScene::dragMoveEvent(QGraphicsSceneDragDropEvent *event) { + if(event->mimeData()->hasUrls()) + event->acceptProposedAction(); +} +void GraphicsScene::dropEvent(QGraphicsSceneDragDropEvent* event) { + QList urlList = event->mimeData()->urls(); + if(!urlList.isEmpty()) + Q_EMIT fileDropped(urlList.first().toLocalFile()); + event->acceptProposedAction(); } -#endif // LXIMAGE_JOB_H +} diff --git a/src/graphicsscene.h b/src/graphicsscene.h new file mode 100644 index 0000000..b25b5e4 --- /dev/null +++ b/src/graphicsscene.h @@ -0,0 +1,46 @@ +/* + + Copyright (C) 2013 + + 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 +#include + +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 diff --git a/src/imageview.cpp b/src/imageview.cpp index 015e925..46fda02 100644 --- a/src/imageview.cpp +++ b/src/imageview.cpp @@ -35,7 +35,7 @@ namespace LxImage { ImageView::ImageView(QWidget* parent): QGraphicsView(parent), - scene_(new QGraphicsScene(this)), + scene_(new GraphicsScene(this)), imageItem_(new QGraphicsRectItem()), gifMovie_(nullptr), cacheTimer_(nullptr), @@ -49,6 +49,7 @@ ImageView::ImageView(QWidget* parent): setLineWidth(0); setScene(scene_); + connect(scene_, &GraphicsScene::fileDropped, this, &ImageView::onFileDropped); imageItem_->hide(); imageItem_->setPen(QPen(Qt::NoPen)); // remove the border scene_->addItem(imageItem_); @@ -68,6 +69,9 @@ ImageView::~ImageView() { } } +void ImageView::onFileDropped(const QString file) { + Q_EMIT fileDropped(file); +} void ImageView::wheelEvent(QWheelEvent* event) { int delta = event->delta(); diff --git a/src/imageview.h b/src/imageview.h index ac566cf..849a790 100644 --- a/src/imageview.h +++ b/src/imageview.h @@ -21,6 +21,7 @@ #ifndef LXIMAGE_IMAGEVIEW_H #define LXIMAGE_IMAGEVIEW_H +#include "graphicsscene.h" #include #include #include @@ -71,6 +72,9 @@ public: // if set to true, hides the cursor after 3s of inactivity void hideCursor(bool enable); +Q_SIGNALS: + void fileDropped(const QString file); + protected: virtual void wheelEvent(QWheelEvent* event); virtual void mouseDoubleClickEvent(QMouseEvent* event); @@ -87,11 +91,12 @@ private: QRect sceneToViewport(const QRectF& rect); private Q_SLOTS: + void onFileDropped(const QString file); void generateCache(); void blankCursor(); 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 QImage image_; // image to show QMovie *gifMovie_; // gif animation to show (should be deleted explicitly) diff --git a/src/job.cpp b/src/job.cpp deleted file mode 100644 index a7afd99..0000000 --- a/src/job.cpp +++ /dev/null @@ -1,64 +0,0 @@ -/* - * - * Copyright (C) 2014 - * - * 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; -} - -} diff --git a/src/loadimagejob.cpp b/src/loadimagejob.cpp index a57cb55..d4f483b 100644 --- a/src/loadimagejob.cpp +++ b/src/loadimagejob.cpp @@ -21,104 +21,106 @@ #include "loadimagejob.h" #include "mainwindow.h" #include -#include +#include #include #include using namespace LxImage; -LoadImageJob::LoadImageJob(MainWindow* window, FmPath* filePath): - Job(), - mainWindow_(window), - path_(fm_path_ref(filePath)) { +LoadImageJob::LoadImageJob(const Fm::FilePath & filePath): + path_{filePath} { } LoadImageJob::~LoadImageJob() { - fm_path_unref(path_); } // This is called from the worker thread, not main thread -bool LoadImageJob::run() { - GFile* gfile = fm_path_to_gfile(path_); - GFileInputStream* fileStream = g_file_read(gfile, cancellable_, &error_); - g_object_unref(gfile); +void LoadImageJob::exec() { + GFileInputStream* fileStream = nullptr; + Fm::GErrorPtr error; + 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 - QBuffer imageBuffer; + // the file stream is successfully opened + imageBuffer.truncate(0); GInputStream* inputStream = G_INPUT_STREAM(fileStream); - while(!g_cancellable_is_cancelled(cancellable_)) { + while(!error && !isCancelled()) { char buffer[4096]; + error.reset(); gssize readSize = g_input_stream_read(inputStream, buffer, 4096, - cancellable_, &error_); + cancellable().get(), &error); if(readSize == -1 || readSize == 0) // error or EOF break; // append the bytes read to the image buffer - imageBuffer.buffer().append(buffer, readSize); + imageBuffer.append(buffer, readSize); } g_input_stream_close(inputStream, NULL, NULL); - // 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 - image_ = QImage::fromData(imageBuffer.buffer()); + if (!error) + break; // everything read or cancel requested + + act = emitError(error); + } + + // FIXME: maybe it's a better idea to implement a GInputStream based QIODevice. + if(!error && !isCancelled()) { // load the image from buffer if there are no errors + image_ = QImage::fromData(imageBuffer); - if(!image_.isNull()) { // if the image is loaded correctly - // check if this file is a jpeg file - // FIXME: can we use FmFileInfo instead if it's available? - const char* basename = fm_path_get_basename(path_); - char* mime_type = g_content_type_guess(basename, NULL, 0, NULL); - if(mime_type && strcmp(mime_type, "image/jpeg") == 0) { // this is a jpeg file - // use libexif to extract additional info embedded in jpeg files - ExifLoader *exif_loader = exif_loader_new(); - // write image data to exif loader - exif_loader_write(exif_loader, (unsigned char*)imageBuffer.data().constData(), (unsigned int)imageBuffer.size()); - ExifData *exif_data = exif_loader_get_data(exif_loader); - exif_loader_unref(exif_loader); - if(exif_data) { - /* reference for EXIF orientation tag: - * http://www.impulseadventure.com/photo/exif-orientation.html */ - ExifEntry* orient_ent = exif_data_get_entry(exif_data, EXIF_TAG_ORIENTATION); - if(orient_ent) { /* orientation flag found in EXIF */ - gushort orient; - ExifByteOrder bo = exif_data_get_byte_order(exif_data); - /* bo == EXIF_BYTE_ORDER_INTEL ; */ - orient = exif_get_short (orient_ent->data, bo); - qreal rotate_degrees = 0.0; - switch(orient) { - case 1: /* no rotation */ - break; - case 8: - rotate_degrees = 270.0; - break; - case 3: - rotate_degrees = 180.0; - break; - case 6: - rotate_degrees = 90.0; - break; - } - // rotate the image according to EXIF orientation tag - if(rotate_degrees != 0.0) { - QTransform transform; - transform.rotate(rotate_degrees); - image_ = image_.transformed(transform, Qt::SmoothTransformation); - } - // TODO: handle other EXIF tags as well + if(!image_.isNull()) { // if the image is loaded correctly + // check if this file is a jpeg file + // FIXME: can we use FmFileInfo instead if it's available? + const Fm::CStrPtr basename = path_.baseName(); + const Fm::CStrPtr mime_type{g_content_type_guess(basename.get(), NULL, 0, NULL)}; + 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 + std::unique_ptr exif_loader{exif_loader_new(), &exif_loader_unref}; + // write image data to exif loader + exif_loader_write(exif_loader.get(), reinterpret_cast(const_cast(imageBuffer.constData())), static_cast(imageBuffer.size())); + std::unique_ptr exif_data{exif_loader_get_data(exif_loader.get()), &exif_data_unref}; + exif_loader.reset(); + if (exif_data) { + /* reference for EXIF orientation tag: + * http://www.impulseadventure.com/photo/exif-orientation.html */ + ExifEntry* orient_ent = exif_data_get_entry(exif_data.get(), EXIF_TAG_ORIENTATION); + if(orient_ent) { /* orientation flag found in EXIF */ + gushort orient; + ExifByteOrder bo = exif_data_get_byte_order(exif_data.get()); + /* bo == EXIF_BYTE_ORDER_INTEL ; */ + orient = exif_get_short (orient_ent->data, bo); + qreal rotate_degrees = 0.0; + switch(orient) { + case 1: /* no rotation */ + break; + case 8: + rotate_degrees = 270.0; + break; + case 3: + rotate_degrees = 180.0; + break; + case 6: + rotate_degrees = 90.0; + break; } - exif_data_unref(exif_data); + // rotate the image according to EXIF orientation tag + if(rotate_degrees != 0.0) { + QTransform transform; + transform.rotate(rotate_degrees); + image_ = image_.transformed(transform, Qt::SmoothTransformation); + } + // TODO: handle other EXIF tags as well } } - 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); - } -} diff --git a/src/loadimagejob.h b/src/loadimagejob.h index cb2e9d5..13d9afc 100644 --- a/src/loadimagejob.h +++ b/src/loadimagejob.h @@ -21,37 +21,31 @@ #ifndef LXIMAGE_LOADIMAGEJOB_H #define LXIMAGE_LOADIMAGEJOB_H -#include -#include +#include #include -#include "job.h" +#include namespace LxImage { -class MainWindow; - -class LoadImageJob : public Job { +class LoadImageJob : public Fm::Job { public: - LoadImageJob(MainWindow* window, FmPath* filePath); + LoadImageJob(const Fm::FilePath & filePath); QImage image() const { return image_; } - FmPath* filePath() const { + const Fm::FilePath & filePath() const { return path_; } private: ~LoadImageJob(); // prevent direct deletion - virtual bool run(); - virtual void finish(); + virtual void exec() override; -public: - MainWindow* mainWindow_; - FmPath* path_; + const Fm::FilePath path_; QImage image_; }; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 795e184..d8a50d4 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -43,6 +43,9 @@ #include #include #include +#include + +#include "upload/uploaddialog.h" using namespace LxImage; @@ -51,11 +54,9 @@ MainWindow::MainWindow(): contextMenu_(new QMenu(this)), slideShowTimer_(nullptr), image_(), - currentFile_(nullptr), // currentFileInfo_(nullptr), imageModified_(false), folder_(nullptr), - folderPath_(nullptr), folderModel_(new Fm::FolderModel()), proxyModel_(new Fm::ProxyFolderModel()), modelFilter_(new ModelFilter()), @@ -83,6 +84,8 @@ MainWindow::MainWindow(): ui.view->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui.view, &QWidget::customContextMenuRequested, this, &MainWindow::onContextMenu); + connect(ui.view, &ImageView::fileDropped, this, &MainWindow::onFileDropped); + // install an event filter on the image view ui.view->installEventFilter(this); ui.view->setBackgroundBrush(QBrush(settings.bgColor())); @@ -128,16 +131,8 @@ MainWindow::~MainWindow() { loadJob_->cancel(); // we don't need to do delete here. It will be done automatically } - if(currentFile_) - fm_path_unref(currentFile_); //if(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 proxyModel_; delete modelFilter_; @@ -175,7 +170,7 @@ void MainWindow::on_actionZoomOut_triggered() { 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 // since the folder is fully loaded. if(currentFile_ && !currentIndex_.isValid()) { @@ -187,22 +182,20 @@ void MainWindow::onFolderLoaded(FmFolder* folder) { } } // this is used to open the first image of a folder - else if (currentFile_ == nullptr) + else if (!currentFile_) on_actionFirst_triggered(); } void MainWindow::openImageFile(QString fileName) { - FmPath* path = fm_path_new_for_str(qPrintable(fileName)); - if(currentFile_ && fm_path_equal(currentFile_, path)) { + const Fm::FilePath path = Fm::FilePath::fromPathStr(qPrintable(fileName)); // the same file! do not load it again - fm_path_unref(path); + if(currentFile_ && currentFile_ == path) return; - } + if (QFileInfo(fileName).isDir()) { - if(fm_path_equal(path, folderPath_)) { - fm_path_unref(path); + if(path == folderPath_) return; - } + QList formats = QImageReader::supportedImageFormats(); QStringList formatsFilters; for (const QByteArray& format: formats) @@ -210,20 +203,16 @@ void MainWindow::openImageFile(QString fileName) { QDir dir(fileName); dir.setNameFilters(formatsFilters); dir.setFilter(QDir::Files | QDir::NoDotAndDotDot); - if(dir.entryList().isEmpty()) { - fm_path_unref(path); + if(dir.entryList().isEmpty()) return; - } - if(currentFile_) - fm_path_unref(currentFile_); - currentFile_ = nullptr; + + currentFile_ = Fm::FilePath{}; loadFolder(path); } else { // load the image file asynchronously loadImage(path); - loadFolder(fm_path_get_parent(path)); + loadFolder(path.parent()); } - fm_path_unref(path); } // paste the specified image into the current view, @@ -238,9 +227,7 @@ void MainWindow::pasteImage(QImage newImage) { setModified(true); currentIndex_ = QModelIndex(); // invaludate current index since we don't have a folder model now - if(currentFile_) - fm_path_unref(currentFile_); - currentFile_ = nullptr; + currentFile_ = Fm::FilePath{}; image_ = newImage; ui.view->setImage(image_); @@ -342,44 +329,36 @@ void MainWindow::on_actionSaveAs_triggered() { if(saveJob_) // if we're currently saving another file return; QString baseName; - if(currentFile_) { - char* dispName = fm_path_display_name(currentFile_, false); - baseName = QString::fromUtf8(dispName); - g_free(dispName); - } + if(currentFile_) + baseName = QString::fromUtf8(currentFile_.displayName().get()); + QString fileName = saveFileName(baseName); 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 saveImage(path); if(!currentFile_) { // if we haven't load any file yet currentFile_ = path; - loadFolder(fm_path_get_parent(path)); + loadFolder(path.parent()); } - else - fm_path_unref(path); } } void MainWindow::on_actionDelete_triggered() { // delete the current file - if(currentFile_) { - FmPathList* paths = fm_path_list_new(); - fm_path_list_push_tail(paths, currentFile_); - Fm::FileOperation::deleteFiles(paths); - fm_path_list_unref(paths); - } + if(currentFile_) + Fm::FileOperation::deleteFiles({currentFile_}); } void MainWindow::on_actionFileProperties_triggered() { 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 // 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. 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); else index = proxyModel_->index(0, 0); - FmFileInfo* info = proxyModel_->fileInfoFromIndex(index); - if(info) { - FmPath* path = fm_file_info_get_path(info); - loadImage(path, index); - } + const auto info = proxyModel_->fileInfoFromIndex(index); + if(info) + loadImage(info->path(), index); } } @@ -413,79 +390,72 @@ void MainWindow::on_actionPrevious_triggered() { index = proxyModel_->index(currentIndex_.row() - 1, 0); else index = proxyModel_->index(proxyModel_->rowCount() - 1, 0); - FmFileInfo* info = proxyModel_->fileInfoFromIndex(index); - if(info) { - FmPath* path = fm_file_info_get_path(info); - loadImage(path, index); - } + const auto info = proxyModel_->fileInfoFromIndex(index); + if(info) + loadImage(info->path(), index); } } void MainWindow::on_actionFirst_triggered() { QModelIndex index = proxyModel_->index(0, 0); if(index.isValid()) { - FmFileInfo* info = proxyModel_->fileInfoFromIndex(index); - if(info) { - FmPath* path = fm_file_info_get_path(info); - loadImage(path, index); - } + const auto info = proxyModel_->fileInfoFromIndex(index); + if(info) + loadImage(info->path(), index); } } void MainWindow::on_actionLast_triggered() { QModelIndex index = proxyModel_->index(proxyModel_->rowCount() - 1, 0); if(index.isValid()) { - FmFileInfo* info = proxyModel_->fileInfoFromIndex(index); - if(info) { - FmPath* path = fm_file_info_get_path(info);; - loadImage(path, index); - } + const auto info = proxyModel_->fileInfoFromIndex(index); + if(info) + loadImage(info->path(), index); } } -void MainWindow::loadFolder(FmPath* newFolderPath) { +void MainWindow::loadFolder(const Fm::FilePath & newFolderPath) { if(folder_) { // an folder is already loaded - if(fm_path_equal(newFolderPath, folderPath_)) // same folder, ignore + if(newFolderPath == folderPath_) // same folder, ignore return; - // free current folder - g_signal_handlers_disconnect_by_func(folder_, gpointer(_onFolderLoaded), this); - g_object_unref(folder_); - fm_path_unref(folderPath_); + disconnect(folder_.get(), nullptr, this, nullptr); // disconnect from all signals } - folderPath_ = fm_path_ref(newFolderPath); - folder_ = fm_folder_from_path(newFolderPath); - g_signal_connect(folder_, "finish-loading", G_CALLBACK(_onFolderLoaded), this); + folderPath_ = newFolderPath; + folder_ = Fm::Folder::fromPath(folderPath_); + connect(folder_.get(), &Fm::Folder::finishLoading, this, &MainWindow::onFolderLoaded); folderModel_->setFolder(folder_); currentIndex_ = QModelIndex(); // set current index to invalid } // the image is loaded (the method is only called if the loading is not cancelled) -void MainWindow::onImageLoaded(LoadImageJob* job) { - loadJob_ = nullptr; // the job object will be freed later automatically +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(); - image_ = job->image(); - ui.view->setAutoZoomFit(true); - ui.view->setImage(image_); + loadJob_ = nullptr; // the job object will be freed later automatically - if(!currentIndex_.isValid()) - currentIndex_ = indexFromPath(currentFile_); + ui.view->setAutoZoomFit(true); + ui.view->setImage(image_); - updateUI(); + if(!currentIndex_.isValid()) + currentIndex_ = indexFromPath(currentFile_); - if(job->error()) { - // if there are errors - // TODO: show a info bar? - } + updateUI(); - /* we resized and moved the window without showing - it in updateUI(), so we need to show it here */ - show(); + /* we resized and moved the window without showing + it in updateUI(), so we need to show it here */ + show(); + } } -void MainWindow::onImageSaved(SaveImageJob* job) { - if(!job->error()) { +void MainWindow::onImageSaved() { + if(!saveJob_->failed()) { setModified(false); } saveJob_ = nullptr; @@ -535,16 +505,16 @@ bool MainWindow::eventFilter(QObject* watched, QEvent* 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 // 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; int count = proxyModel_->rowCount(); for(int row = 0; row < count; ++row) { index = proxyModel_->index(row, 0); - FmFileInfo* info = proxyModel_->fileInfoFromIndex(index); - if(info && fm_path_equal(filePath, fm_file_info_get_path(info))) { + const auto info = proxyModel_->fileInfoFromIndex(index); + if(info && filePath == info->path()) { return index; } } @@ -564,19 +534,19 @@ void MainWindow::updateUI() { QString title; if(currentFile_) { - char* dispName = fm_path_display_basename(currentFile_); + const Fm::CStrPtr dispName = currentFile_.displayName(); if(loadJob_) { // if loading is in progress title = tr("[*]%1 (Loading...) - Image Viewer") - .arg(QString::fromUtf8(dispName)); + .arg(QString::fromUtf8(dispName.get())); } else { if(image_.isNull()) { title = tr("[*]%1 (Failed to Load) - Image Viewer") - .arg(QString::fromUtf8(dispName)); + .arg(QString::fromUtf8(dispName.get())); } else { title = tr("[*]%1 (%2x%3) - Image Viewer") - .arg(QString::fromUtf8(dispName)) + .arg(QString::fromUtf8(dispName.get())) .arg(image_.width()) .arg(image_.height()); /* 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 } else { @@ -620,7 +589,7 @@ void MainWindow::updateUI() { // Load the specified image file asynchronously in a worker thread. // 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 if(loadJob_) { loadJob_->cancel(); // the job object will be freed automatically later @@ -633,14 +602,12 @@ void MainWindow::loadImage(FmPath* filePath, QModelIndex index) { } currentIndex_ = index; - if(currentFile_) - fm_path_unref(currentFile_); - currentFile_ = fm_path_ref(filePath); + currentFile_ = filePath; // clear current image, but do not update the view now to prevent flickers image_ = QImage(); - const char* basename = fm_path_get_basename(currentFile_); - char* mime_type = g_content_type_guess(basename, NULL, 0, NULL); + const Fm::CStrPtr basename = currentFile_.baseName(); + char* mime_type = g_content_type_guess(basename.get(), NULL, 0, NULL); QString mimeType; if (mime_type) { mimeType = QString(mime_type); @@ -648,33 +615,47 @@ void MainWindow::loadImage(FmPath* filePath, QModelIndex index) { } if(mimeType == "image/gif" || mimeType == "image/svg+xml" || mimeType == "image/svg+xml-compressed") { - char *file_name = fm_path_to_str(currentFile_); - QString fileName(file_name); - g_free(file_name); + const Fm::CStrPtr file_name = currentFile_.toString(); ui.view->setAutoZoomFit(true); // like in onImageLoaded() if(mimeType == "image/gif") - ui.view->setGifAnimation(fileName); + ui.view->setGifAnimation(QString{file_name.get()}); else - ui.view->setSVG(fileName); + ui.view->setSVG(QString{file_name.get()}); image_ = ui.view->image(); updateUI(); show(); } else { // start a new gio job to load the specified image - loadJob_ = new LoadImageJob(this, filePath); - loadJob_->start(); + loadJob_ = new LoadImageJob(currentFile_); + 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(); } } -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 return; // start a new gio job to save current image to the specified path - saveJob_ = new SaveImageJob(this, filePath); - saveJob_->start(); + saveJob_ = new SaveImageJob(image_, filePath); + 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? } @@ -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() { bool hasQGraphicsItem(false); if(QGraphicsItem *graphItem = getGraphicsItem()) { @@ -871,8 +859,10 @@ void MainWindow::setShowThumbnails(bool show) { listView->installEventFilter(this); // FIXME: optimize the size of the thumbnail view // FIXME if the thumbnail view is docked elsewhere, update the settings. - int scrollHeight = style()->pixelMetric(QStyle::PM_ScrollBarExtent); - thumbnailsView_->setFixedHeight(listView->gridSize().height() + scrollHeight); + if(Fm::FolderItemDelegate* delegate = static_cast(listView->itemDelegateForColumn(Fm::FolderModel::ColumnFileName))) { + int scrollHeight = style()->pixelMetric(QStyle::PM_ScrollBarExtent); + thumbnailsView_->setFixedHeight(delegate->itemSize().height() + scrollHeight); + } thumbnailsView_->setModel(proxyModel_); proxyModel_->setShowThumbnails(true); if (currentFile_) { // select the loaded image @@ -978,14 +968,18 @@ void MainWindow::onExitFullscreen() { 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 if(!selected.isEmpty()) { QModelIndex index = selected.first().topLeft(); if(index.isValid() && index != currentIndex_) { - FmFileInfo* file = proxyModel_->fileInfoFromIndex(index); + const auto file = proxyModel_->fileInfoFromIndex(index); if(file) - loadImage(fm_file_info_get_path(file), index); + loadImage(file->path(), index); } } } + +void MainWindow::onFileDropped(const QString path) { + openImageFile(path); +} diff --git a/src/mainwindow.h b/src/mainwindow.h index 60cb18b..36e8f69 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -62,7 +62,7 @@ public: void pasteImage(QImage newImage); - FmPath* currentFile() const { + const Fm::FilePath & currentFile() const { return currentFile_; } @@ -70,9 +70,9 @@ public: void applySettings(); protected: - void loadImage(FmPath* filePath, QModelIndex index = QModelIndex()); - void saveImage(FmPath* filePath); // save current image to a file - void loadFolder(FmPath* newFolderPath); + void loadImage(const Fm::FilePath & filePath, QModelIndex index = QModelIndex()); + void saveImage(const Fm::FilePath & filePath); // save current image to a file + void loadFolder(const Fm::FilePath & newFolderPath); QString openFileName(); QString openDirectory(); QString saveFileName(QString defaultName = QString()); @@ -80,8 +80,8 @@ protected: virtual void resizeEvent(QResizeEvent *event); virtual void closeEvent(QCloseEvent *event); - void onImageLoaded(LoadImageJob* job); - void onImageSaved(SaveImageJob* job); + void onImageLoaded(); + void onImageSaved(); virtual bool eventFilter(QObject* watched, QEvent* event); private Q_SLOTS: @@ -103,6 +103,7 @@ private Q_SLOTS: void on_actionFlipHorizontal_triggered(); void on_actionCopy_triggered(); void on_actionPaste_triggered(); + void on_actionUpload_triggered(); void on_actionShowThumbnails_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 onFileDropped(const QString path); + private: - void onFolderLoaded(FmFolder* folder); + void onFolderLoaded(); void updateUI(); void setModified(bool modified); - QModelIndex indexFromPath(FmPath* filePath); + QModelIndex indexFromPath(const Fm::FilePath & filePath); QGraphicsItem* getGraphicsItem(); - // GObject related signal handers and callbacks - static void _onFolderLoaded(FmFolder* folder, MainWindow* _this) { - _this->onFolderLoaded(folder); - } - private: Ui::MainWindow ui; QMenu* contextMenu_; QTimer* slideShowTimer_; 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 bool imageModified_; // the current image is modified by rotation, flip, or others and needs to be saved // folder browsing - FmFolder* folder_; - FmPath* folderPath_; + std::shared_ptr folder_; + Fm::FilePath folderPath_; Fm::FolderModel* folderModel_; Fm::ProxyFolderModel* proxyModel_; ModelFilter* modelFilter_; @@ -161,6 +159,6 @@ private: SaveImageJob* saveJob_; }; -}; +} #endif // LXIMAGE_MAINWINDOW_H diff --git a/src/mainwindow.ui b/src/mainwindow.ui index c67fa6d..d0cac02 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -108,6 +108,8 @@ + + @@ -466,6 +468,17 @@ Ctrl+D + + + + + + Upload + + + Upload the image + + diff --git a/src/modelfilter.cpp b/src/modelfilter.cpp index 4261ad4..092ef08 100644 --- a/src/modelfilter.cpp +++ b/src/modelfilter.cpp @@ -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& info) const +{ + Q_UNUSED(model) + // filter out non-image files and formats that we don't support. - if(!fm_file_info_is_image(info)) - return false; - return true; + return info && info->isImage(); } diff --git a/src/modelfilter.h b/src/modelfilter.h index 699c386..ddd8f39 100644 --- a/src/modelfilter.h +++ b/src/modelfilter.h @@ -28,7 +28,7 @@ namespace LxImage { class ModelFilter: public Fm::ProxyFolderModelFilter { public: ModelFilter(); - virtual bool filterAcceptsRow(const Fm::ProxyFolderModel* model, FmFileInfo* info) const; + virtual bool filterAcceptsRow(const Fm::ProxyFolderModel* model, const std::shared_ptr& info) const override; virtual ~ModelFilter(); }; diff --git a/src/preferencesdialog.cpp b/src/preferencesdialog.cpp index 123345c..c35183b 100644 --- a/src/preferencesdialog.cpp +++ b/src/preferencesdialog.cpp @@ -111,7 +111,7 @@ void PreferencesDialog::initIconThemes(Settings& settings) { iconThemes.remove("hicolor"); // remove hicolor, which is only a fallback QHash::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->model()->sort(0); // sort the list of icon theme names diff --git a/src/saveimagejob.cpp b/src/saveimagejob.cpp index 7922d33..c9f2e7d 100644 --- a/src/saveimagejob.cpp +++ b/src/saveimagejob.cpp @@ -26,47 +26,58 @@ using namespace LxImage; -SaveImageJob::SaveImageJob(MainWindow* window, FmPath* filePath): - Job(), - mainWindow_(window), - path_(fm_path_ref(filePath)), - image_(window->image()) { +SaveImageJob::SaveImageJob(const QImage & image, const Fm::FilePath & filePath): + path_{filePath}, + image_{image}, + failed_{true} +{ } SaveImageJob::~SaveImageJob() { - fm_path_unref(path_); } // This is called from the worker thread, not main thread -bool SaveImageJob::run() { - GFile* gfile = fm_path_to_gfile(path_); - GFileOutputStream* fileStream = g_file_replace(gfile, NULL, false, G_FILE_CREATE_NONE, cancellable_, &error_); - g_object_unref(gfile); +void SaveImageJob::exec() { + const Fm::CStrPtr f = path_.baseName(); + char const * format = f.get(); + format = strrchr(format, '.'); + if(format) // use filename extension as the image format + ++format; - if(fileStream) { // if the file stream is successfually opened - const char* format = fm_path_get_basename(path_); - format = strrchr(format, '.'); - if(format) // use filename extension as the image format - ++format; + QBuffer imageBuffer; + image_.save(&imageBuffer, format); // save the image to buffer - QBuffer imageBuffer; - image_.save(&imageBuffer, format); // save the image to buffer - GOutputStream* outputStream = G_OUTPUT_STREAM(fileStream); - g_output_stream_write_all(outputStream, - imageBuffer.data().constData(), - imageBuffer.size(), - NULL, - cancellable_, - &error_); - g_output_stream_close(outputStream, NULL, NULL); - } - return false; -} + 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); + g_output_stream_write_all(outputStream, + imageBuffer.data().constData(), + imageBuffer.size(), + NULL, + cancellable().get(), + &error); + g_output_stream_close(outputStream, NULL, NULL); + if (!error) + { + // successfully written + failed_ = false; + break; // successfully written + } -// this function is called from main thread only -void SaveImageJob::finish() { - // only do processing if the job is not cancelled - if(!g_cancellable_is_cancelled(cancellable_)) { - mainWindow_->onImageSaved(this); + act = emitError(error); + } } } diff --git a/src/saveimagejob.h b/src/saveimagejob.h index d21047d..3bbbdab 100644 --- a/src/saveimagejob.h +++ b/src/saveimagejob.h @@ -21,39 +21,39 @@ #ifndef LXIMAGE_SAVEIMAGEJOB_H #define LXIMAGE_SAVEIMAGEJOB_H -#include -#include +#include +#include #include -#include "job.h" namespace LxImage { -class MainWindow; - -class SaveImageJob : public Job { +class SaveImageJob : public Fm::Job { public: - SaveImageJob(MainWindow* window, FmPath* filePath); + SaveImageJob(const QImage & image, const Fm::FilePath & filePath); QImage image() const { return image_; } - FmPath* filePath() const { + const Fm::FilePath & filePath() const { return path_; } + bool failed() const + { + return failed_; + } + protected: - virtual bool run(); - virtual void finish(); + virtual void exec() override; private: ~SaveImageJob(); // prevent direct deletion -public: - MainWindow* mainWindow_; - FmPath* path_; - QImage image_; + const Fm::FilePath path_; + const QImage image_; + bool failed_; }; } diff --git a/src/screenshotdialog.cpp b/src/screenshotdialog.cpp index b5c442d..d87747d 100644 --- a/src/screenshotdialog.cpp +++ b/src/screenshotdialog.cpp @@ -19,6 +19,7 @@ #include "screenshotdialog.h" +#include "screenshotselectarea.h" #include #include #include @@ -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(qApp); MainWindow* window = app->createWindow(); if(!image.isNull()) diff --git a/src/screenshotdialog.ui b/src/screenshotdialog.ui index 0ce0c81..b3067bb 100644 --- a/src/screenshotdialog.ui +++ b/src/screenshotdialog.ui @@ -15,8 +15,7 @@ - - + .. @@ -52,6 +51,13 @@ + + + + Capture an area of the screen + + + diff --git a/src/screenshotselectarea.cpp b/src/screenshotselectarea.cpp new file mode 100644 index 0000000..5807409 --- /dev/null +++ b/src/screenshotselectarea.cpp @@ -0,0 +1,51 @@ +/* + + Copyright (C) 2013 + + 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 + +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(); +} \ No newline at end of file diff --git a/src/screenshotselectarea.h b/src/screenshotselectarea.h new file mode 100644 index 0000000..fb9ce29 --- /dev/null +++ b/src/screenshotselectarea.h @@ -0,0 +1,50 @@ +/* + + Copyright (C) 2013 + + 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 +#include +#include +#include + + +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 diff --git a/src/screenshotselectareagraphicsview.cpp b/src/screenshotselectareagraphicsview.cpp new file mode 100644 index 0000000..aca9450 --- /dev/null +++ b/src/screenshotselectareagraphicsview.cpp @@ -0,0 +1,58 @@ +/* + + Copyright (C) 2013 + + 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 + +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()); +} diff --git a/src/screenshotselectareagraphicsview.h b/src/screenshotselectareagraphicsview.h new file mode 100644 index 0000000..273d9e3 --- /dev/null +++ b/src/screenshotselectareagraphicsview.h @@ -0,0 +1,52 @@ +/* + + Copyright (C) 2013 + + 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 +#include +#include +#include + + +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 diff --git a/src/translations/lximage-qt-screenshot_lt.desktop b/src/translations/lximage-qt-screenshot_lt.desktop new file mode 100644 index 0000000..b137907 --- /dev/null +++ b/src/translations/lximage-qt-screenshot_lt.desktop @@ -0,0 +1,4 @@ +#Translations +Name[lt]=Ekrano kopija +GenericName[lt]=Ekrano kopija +Comment[lt]=Padaryti ekrano kopiją diff --git a/src/translations/lximage-qt_lt.desktop b/src/translations/lximage-qt_lt.desktop new file mode 100644 index 0000000..0318472 --- /dev/null +++ b/src/translations/lximage-qt_lt.desktop @@ -0,0 +1,4 @@ +#Translations +Name[lt]=LXImage +GenericName[lt]=Paveikslų žiūryklė +Comment[lt]=LXQt paveikslų žiūryklė diff --git a/src/upload/imageshackprovider.cpp b/src/upload/imageshackprovider.cpp new file mode 100644 index 0000000..a64dda5 --- /dev/null +++ b/src/upload/imageshackprovider.cpp @@ -0,0 +1,56 @@ +/* + LxImage - image viewer and screenshot tool for lxqt + Copyright (C) 2017 Nathan Osman + + 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 +#include +#include +#include +#include + +#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)); +} diff --git a/src/upload/imageshackprovider.h b/src/upload/imageshackprovider.h new file mode 100644 index 0000000..524808c --- /dev/null +++ b/src/upload/imageshackprovider.h @@ -0,0 +1,43 @@ +/* + LxImage - image viewer and screenshot tool for lxqt + Copyright (C) 2017 Nathan Osman + + 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 diff --git a/src/upload/imageshackupload.cpp b/src/upload/imageshackupload.cpp new file mode 100644 index 0000000..4dc62d4 --- /dev/null +++ b/src/upload/imageshackupload.cpp @@ -0,0 +1,55 @@ +/* + LxImage - image viewer and screenshot tool for lxqt + Copyright (C) 2017 Nathan Osman + + 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 +#include +#include +#include + +#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); + } +} diff --git a/src/upload/imageshackupload.h b/src/upload/imageshackupload.h new file mode 100644 index 0000000..8e708d4 --- /dev/null +++ b/src/upload/imageshackupload.h @@ -0,0 +1,45 @@ +/* + LxImage - image viewer and screenshot tool for lxqt + Copyright (C) 2017 Nathan Osman + + 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 diff --git a/src/upload/imgurprovider.cpp b/src/upload/imgurprovider.cpp new file mode 100644 index 0000000..198150b --- /dev/null +++ b/src/upload/imgurprovider.cpp @@ -0,0 +1,42 @@ +/* + LxImage - image viewer and screenshot tool for lxqt + Copyright (C) 2017 Nathan Osman + + 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 +#include +#include + +#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)); +} diff --git a/src/upload/imgurprovider.h b/src/upload/imgurprovider.h new file mode 100644 index 0000000..664b763 --- /dev/null +++ b/src/upload/imgurprovider.h @@ -0,0 +1,41 @@ +/* + LxImage - image viewer and screenshot tool for lxqt + Copyright (C) 2017 Nathan Osman + + 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 diff --git a/src/upload/imgurupload.cpp b/src/upload/imgurupload.cpp new file mode 100644 index 0000000..e2228d2 --- /dev/null +++ b/src/upload/imgurupload.cpp @@ -0,0 +1,52 @@ +/* + LxImage - image viewer and screenshot tool for lxqt + Copyright (C) 2017 Nathan Osman + + 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 +#include + +#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); + } +} diff --git a/src/upload/imgurupload.h b/src/upload/imgurupload.h new file mode 100644 index 0000000..7bed9d6 --- /dev/null +++ b/src/upload/imgurupload.h @@ -0,0 +1,43 @@ +/* + LxImage - image viewer and screenshot tool for lxqt + Copyright (C) 2017 Nathan Osman + + 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 diff --git a/src/upload/provider.cpp b/src/upload/provider.cpp new file mode 100644 index 0000000..b5dc7e8 --- /dev/null +++ b/src/upload/provider.cpp @@ -0,0 +1,24 @@ +/* + LxImage - image viewer and screenshot tool for lxqt + Copyright (C) 2017 Nathan Osman + + 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; diff --git a/src/upload/provider.h b/src/upload/provider.h new file mode 100644 index 0000000..99e53e3 --- /dev/null +++ b/src/upload/provider.h @@ -0,0 +1,54 @@ +/* + LxImage - image viewer and screenshot tool for lxqt + Copyright (C) 2017 Nathan Osman + + 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 +#include +#include + +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 diff --git a/src/upload/upload.cpp b/src/upload/upload.cpp new file mode 100644 index 0000000..50f1a0f --- /dev/null +++ b/src/upload/upload.cpp @@ -0,0 +1,57 @@ +/* + LxImage - image viewer and screenshot tool for lxqt + Copyright (C) 2017 Nathan Osman + + 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( + static_cast(bytesSent) / static_cast(bytesTotal) * 100.0 + )); + }); + + // Emit error() when a socket error occurs + connect(mReply, static_cast(&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(); +} diff --git a/src/upload/upload.h b/src/upload/upload.h new file mode 100644 index 0000000..b98f950 --- /dev/null +++ b/src/upload/upload.h @@ -0,0 +1,96 @@ +/* + LxImage - image viewer and screenshot tool for lxqt + Copyright (C) 2017 Nathan Osman + + 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 +#include + +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 diff --git a/src/upload/uploaddialog.cpp b/src/upload/uploaddialog.cpp new file mode 100644 index 0000000..ba9f11d --- /dev/null +++ b/src/upload/uploaddialog.cpp @@ -0,0 +1,141 @@ +/* + LxImage - image viewer and screenshot tool for lxqt + Copyright (C) 2017 Nathan Osman + + 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 +#include +#include +#include +#include +#include + +#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(); + + // 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(); +} diff --git a/src/upload/uploaddialog.h b/src/upload/uploaddialog.h new file mode 100644 index 0000000..667ae7b --- /dev/null +++ b/src/upload/uploaddialog.h @@ -0,0 +1,73 @@ +/* + LxImage - image viewer and screenshot tool for lxqt + Copyright (C) 2017 Nathan Osman + + 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 +#include + +#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 diff --git a/src/upload/uploaddialog.ui b/src/upload/uploaddialog.ui new file mode 100644 index 0000000..ce0ad20 --- /dev/null +++ b/src/upload/uploaddialog.ui @@ -0,0 +1,79 @@ + + + UploadDialog + + + + 0 + 0 + 300 + 100 + + + + Upload + + + + + + + + + true + + + + + + + + + true + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + +