mirror of
https://github.com/aseprite/aseprite.git
synced 2025-01-30 15:32:38 +00:00
Use cancelable async tasks to load/delete backup sessions
This commit is contained in:
parent
91665c62c3
commit
93fe19d353
2
laf
2
laf
@ -1 +1 @@
|
||||
Subproject commit 5afdc83f2ba9622aa37e520ad6b2a605d46022fb
|
||||
Subproject commit c293519ad611d1c419d9666d5fee077a86ac4c87
|
@ -391,6 +391,7 @@ if(ENABLE_UI)
|
||||
ui/slider2.cpp
|
||||
ui/status_bar.cpp
|
||||
ui/tabs.cpp
|
||||
ui/task_widget.cpp
|
||||
ui/timeline/ani_controls.cpp
|
||||
ui/timeline/timeline.cpp
|
||||
ui/toolbar.cpp
|
||||
@ -568,6 +569,7 @@ add_library(app-lib
|
||||
site.cpp
|
||||
snap_to_grid.cpp
|
||||
sprite_job.cpp
|
||||
task.cpp
|
||||
thumbnail_generator.cpp
|
||||
thumbnails.cpp
|
||||
tools/active_tool.cpp
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -167,6 +167,12 @@ void Console::printf(const char* format, ...)
|
||||
// static
|
||||
void Console::showException(const std::exception& e)
|
||||
{
|
||||
ui::assert_ui_thread();
|
||||
if (!ui::is_ui_thread()) {
|
||||
LOG(ERROR, "A problem has occurred.\n\nDetails:\n%s\n", e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
Console console;
|
||||
if (typeid(e) == typeid(std::bad_alloc))
|
||||
console.printf("There is not enough memory to complete the action.");
|
||||
|
@ -5,7 +5,7 @@
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
// Uncomment if you want to test the backup process each 5 secondsh
|
||||
// Uncomment if you want to test the backup process each 5 seconds.
|
||||
//#define TEST_BACKUPS_WITH_A_SHORT_PERIOD
|
||||
|
||||
// Uncomment if you want to check that backups are correctly saved
|
||||
@ -137,7 +137,7 @@ void BackupObserver::backgroundThread()
|
||||
else {
|
||||
DocReader reader(doc, 500);
|
||||
std::unique_ptr<Doc> copy(
|
||||
m_session->restoreBackupDocById(doc->id()));
|
||||
m_session->restoreBackupDocById(doc->id(), nullptr));
|
||||
DocDiff diff = compare_docs(doc, copy.get());
|
||||
if (diff.anything) {
|
||||
TRACE("RECO: Differences (%s/%s/%s/%s/%s/%s/%s)\n",
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -52,12 +52,14 @@ namespace {
|
||||
|
||||
class Reader : public SubObjectsIO {
|
||||
public:
|
||||
Reader(const std::string& dir)
|
||||
Reader(const std::string& dir,
|
||||
base::task_token* t)
|
||||
: m_sprite(nullptr)
|
||||
, m_dir(dir)
|
||||
, m_docId(0)
|
||||
, m_docVersions(nullptr)
|
||||
, m_loadInfo(nullptr) {
|
||||
, m_loadInfo(nullptr)
|
||||
, m_taskToken(t) {
|
||||
for (const auto& fn : base::list_files(dir)) {
|
||||
auto i = fn.find('-');
|
||||
if (i == std::string::npos)
|
||||
@ -220,7 +222,7 @@ private:
|
||||
m_loadInfo->width = w;
|
||||
m_loadInfo->height = h;
|
||||
m_loadInfo->frames = nframes;
|
||||
return (Sprite*)1;
|
||||
return (Sprite*)1; // TODO improve this
|
||||
}
|
||||
|
||||
std::unique_ptr<Sprite> spr(new Sprite(ImageSpec(mode, w, h), 256));
|
||||
@ -244,7 +246,10 @@ private:
|
||||
std::map<ObjectId, LayerGroup*> layersMap;
|
||||
layersMap[0] = spr->root(); // parentId = 0 is the root level
|
||||
|
||||
for (int i = 0; i < nlayers; ++i) {
|
||||
for (int i=0; i<nlayers; ++i) {
|
||||
if (canceled())
|
||||
return nullptr;
|
||||
|
||||
ObjectId layId = read32(s);
|
||||
ObjectId parentId = read32(s);
|
||||
|
||||
@ -267,10 +272,39 @@ private:
|
||||
Console().printf("Invalid number of layers #%d\n", nlayers);
|
||||
}
|
||||
|
||||
// Read all cels
|
||||
for (size_t i=0; i<m_celsToLoad.size(); ++i) {
|
||||
if (canceled())
|
||||
return nullptr;
|
||||
|
||||
const auto& pair = m_celsToLoad[i];
|
||||
LayerImage* lay = doc::get<LayerImage>(pair.first);
|
||||
if (!lay)
|
||||
continue;
|
||||
|
||||
ObjectId celId = pair.second;
|
||||
|
||||
Cel* cel = loadObject<Cel*>("cel", celId, &Reader::readCel);
|
||||
if (cel) {
|
||||
// Expand sprite size
|
||||
if (cel->frame() > m_sprite->lastFrame())
|
||||
m_sprite->setTotalFrames(cel->frame()+1);
|
||||
|
||||
lay->addCel(cel);
|
||||
}
|
||||
|
||||
if (m_taskToken) {
|
||||
m_taskToken->set_progress(float(i) / float(m_celsToLoad.size()));
|
||||
}
|
||||
}
|
||||
|
||||
// Read palettes
|
||||
int npalettes = read32(s);
|
||||
if (npalettes >= 1 && npalettes < 0xfffff) {
|
||||
for (int i = 0; i < npalettes; ++i) {
|
||||
if (canceled())
|
||||
return nullptr;
|
||||
|
||||
ObjectId palId = read32(s);
|
||||
Palette* pal = loadObject<Palette*>("pal", palId, &Reader::readPalette);
|
||||
if (pal)
|
||||
@ -282,6 +316,9 @@ private:
|
||||
int nfrtags = read32(s);
|
||||
if (nfrtags >= 1 && nfrtags < 0xfffff) {
|
||||
for (int i = 0; i < nfrtags; ++i) {
|
||||
if (canceled())
|
||||
return nullptr;
|
||||
|
||||
ObjectId tagId = read32(s);
|
||||
FrameTag* tag = loadObject<FrameTag*>("frtag", tagId, &Reader::readFrameTag);
|
||||
if (tag)
|
||||
@ -293,6 +330,9 @@ private:
|
||||
int nslices = read32(s);
|
||||
if (nslices >= 1 && nslices < 0xffffff) {
|
||||
for (int i = 0; i < nslices; ++i) {
|
||||
if (canceled())
|
||||
return nullptr;
|
||||
|
||||
ObjectId sliceId = read32(s);
|
||||
Slice* slice = loadObject<Slice*>("slice", sliceId, &Reader::readSlice);
|
||||
if (slice)
|
||||
@ -345,15 +385,12 @@ private:
|
||||
// Cels
|
||||
int ncels = read32(s);
|
||||
for (int i=0; i<ncels; ++i) {
|
||||
ObjectId celId = read32(s);
|
||||
Cel* cel = loadObject<Cel*>("cel", celId, &Reader::readCel);
|
||||
if (cel) {
|
||||
// Expand sprite size
|
||||
if (cel->frame() > m_sprite->lastFrame())
|
||||
m_sprite->setTotalFrames(cel->frame()+1);
|
||||
if (canceled())
|
||||
return nullptr;
|
||||
|
||||
lay->addCel(cel);
|
||||
}
|
||||
// Add a new cel to load in the future after we load all layers
|
||||
ObjectId celId = read32(s);
|
||||
m_celsToLoad.push_back(std::make_pair(lay->id(), celId));
|
||||
}
|
||||
return lay.release();
|
||||
}
|
||||
@ -417,14 +454,23 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
bool canceled() const {
|
||||
if (m_taskToken)
|
||||
return m_taskToken->canceled();
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
Sprite* m_sprite; // Used to pass the sprite in LayerImage() ctor
|
||||
std::string m_dir;
|
||||
ObjectVersion m_docId;
|
||||
ObjVersionsMap m_objVersions;
|
||||
ObjVersions* m_docVersions;
|
||||
DocumentInfo* m_loadInfo;
|
||||
std::vector<std::pair<ObjectId, ObjectId> > m_celsToLoad;
|
||||
std::map<ObjectId, ImageRef> m_images;
|
||||
std::map<ObjectId, CelDataRef> m_celdatas;
|
||||
base::task_token* m_taskToken;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
@ -434,18 +480,20 @@ private:
|
||||
|
||||
bool read_document_info(const std::string& dir, DocumentInfo& info)
|
||||
{
|
||||
return Reader(dir).loadDocumentInfo(info);
|
||||
return Reader(dir, nullptr).loadDocumentInfo(info);
|
||||
}
|
||||
|
||||
Doc* read_document(const std::string& dir)
|
||||
Doc* read_document(const std::string& dir,
|
||||
base::task_token* t)
|
||||
{
|
||||
return Reader(dir).loadDocument();
|
||||
return Reader(dir, t).loadDocument();
|
||||
}
|
||||
|
||||
Doc* read_document_with_raw_images(const std::string& dir,
|
||||
RawImagesAs as)
|
||||
RawImagesAs as,
|
||||
base::task_token* t)
|
||||
{
|
||||
Reader reader(dir);
|
||||
Reader reader(dir, t);
|
||||
|
||||
DocumentInfo info;
|
||||
if (!reader.loadDocumentInfo(info)) {
|
||||
@ -462,8 +510,13 @@ Doc* read_document_with_raw_images(const std::string& dir,
|
||||
auto lay = new LayerImage(spr);
|
||||
spr->root()->addLayer(lay);
|
||||
|
||||
int i = 0;
|
||||
frame_t frame = 0;
|
||||
for (const auto& fn : base::list_files(dir)) {
|
||||
auto fns = base::list_files(dir);
|
||||
for (const auto& fn : fns) {
|
||||
if (t)
|
||||
t->set_progress((i++) / fns.size());
|
||||
|
||||
if (fn.compare(0, 3, "img") != 0)
|
||||
continue;
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (c) 2018 Igara Studio S.A.
|
||||
// Copyright (c) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2016 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -10,6 +10,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "app/crash/raw_images_as.h"
|
||||
#include "base/task.h"
|
||||
#include "doc/color_mode.h"
|
||||
#include "doc/frame.h"
|
||||
|
||||
@ -34,10 +35,13 @@ namespace crash {
|
||||
}
|
||||
};
|
||||
|
||||
bool read_document_info(const std::string& dir, DocumentInfo& info);
|
||||
Doc* read_document(const std::string& dir);
|
||||
bool read_document_info(const std::string& dir,
|
||||
DocumentInfo& info);
|
||||
Doc* read_document(const std::string& dir,
|
||||
base::task_token* t);
|
||||
Doc* read_document_with_raw_images(const std::string& dir,
|
||||
RawImagesAs as);
|
||||
RawImagesAs as,
|
||||
base::task_token* t);
|
||||
|
||||
} // namespace crash
|
||||
} // namespace app
|
||||
|
@ -27,6 +27,7 @@
|
||||
#include "base/process.h"
|
||||
#include "base/split_string.h"
|
||||
#include "base/string.h"
|
||||
#include "base/thread.h"
|
||||
#include "base/time.h"
|
||||
#include "doc/cancel_io.h"
|
||||
|
||||
@ -254,16 +255,17 @@ void Session::removeDocument(Doc* doc)
|
||||
|
||||
markDocumentAsCorrectlyClosed(doc);
|
||||
}
|
||||
catch (const std::exception&) {
|
||||
// TODO Log this error
|
||||
catch (const std::exception& ex) {
|
||||
LOG(FATAL) << "Exception deleting document " << ex.what() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
Doc* Session::restoreBackupDoc(const std::string& backupDir)
|
||||
Doc* Session::restoreBackupDoc(const std::string& backupDir,
|
||||
base::task_token* t)
|
||||
{
|
||||
Console console;
|
||||
try {
|
||||
Doc* doc = read_document(backupDir);
|
||||
Doc* doc = read_document(backupDir, t);
|
||||
if (doc) {
|
||||
fixFilename(doc);
|
||||
return doc;
|
||||
@ -275,47 +277,49 @@ Doc* Session::restoreBackupDoc(const std::string& backupDir)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Session::restoreBackup(Backup* backup)
|
||||
Doc* Session::restoreBackupDoc(Backup* backup,
|
||||
base::task_token* t)
|
||||
{
|
||||
Doc* doc = restoreBackupDoc(backup->dir());
|
||||
if (doc)
|
||||
UIContext::instance()->documents().add(doc);
|
||||
return restoreBackupDoc(backup->dir(), t);
|
||||
}
|
||||
|
||||
void Session::restoreBackupById(const ObjectId id)
|
||||
Doc* Session::restoreBackupById(const doc::ObjectId id,
|
||||
base::task_token* t)
|
||||
{
|
||||
std::string docDir = base::join_path(m_path, base::convert_to<std::string>(int(id)));
|
||||
if (!base::is_directory(docDir))
|
||||
return;
|
||||
|
||||
Doc* doc = restoreBackupDoc(docDir);
|
||||
if (doc)
|
||||
UIContext::instance()->documents().add(doc);
|
||||
if (base::is_directory(docDir))
|
||||
return restoreBackupDoc(docDir, t);
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Doc* Session::restoreBackupDocById(const doc::ObjectId id)
|
||||
Doc* Session::restoreBackupDocById(const doc::ObjectId id,
|
||||
base::task_token* t)
|
||||
{
|
||||
std::string docDir = base::join_path(m_path, base::convert_to<std::string>(int(id)));
|
||||
if (!base::is_directory(docDir))
|
||||
return nullptr;
|
||||
|
||||
return restoreBackupDoc(docDir);
|
||||
return restoreBackupDoc(docDir, t);
|
||||
}
|
||||
|
||||
void Session::restoreRawImages(Backup* backup, RawImagesAs as)
|
||||
Doc* Session::restoreBackupRawImages(Backup* backup,
|
||||
const RawImagesAs as,
|
||||
base::task_token* t)
|
||||
{
|
||||
Console console;
|
||||
try {
|
||||
Doc* doc = read_document_with_raw_images(backup->dir(), as);
|
||||
Doc* doc = read_document_with_raw_images(backup->dir(), as, t);
|
||||
if (doc) {
|
||||
if (isCrashedSession())
|
||||
fixFilename(doc);
|
||||
UIContext::instance()->documents().add(doc);
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
catch (const std::exception& ex) {
|
||||
Console::showException(ex);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Session::deleteBackup(Backup* backup)
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "base/disable_copying.h"
|
||||
#include "base/process.h"
|
||||
#include "base/shared_ptr.h"
|
||||
#include "base/task.h"
|
||||
#include "doc/object_id.h"
|
||||
|
||||
#include <fstream>
|
||||
@ -58,14 +59,17 @@ namespace crash {
|
||||
bool saveDocumentChanges(Doc* doc);
|
||||
void removeDocument(Doc* doc);
|
||||
|
||||
void restoreBackup(Backup* backup);
|
||||
void restoreBackupById(const doc::ObjectId id);
|
||||
Doc* restoreBackupDocById(const doc::ObjectId id);
|
||||
void restoreRawImages(Backup* backup, RawImagesAs as);
|
||||
Doc* restoreBackupDoc(Backup* backup,
|
||||
base::task_token* t);
|
||||
Doc* restoreBackupById(const doc::ObjectId id, base::task_token* t);
|
||||
Doc* restoreBackupDocById(const doc::ObjectId id, base::task_token* t);
|
||||
Doc* restoreBackupRawImages(Backup* backup,
|
||||
const RawImagesAs as, base::task_token* t);
|
||||
void deleteBackup(Backup* backup);
|
||||
|
||||
private:
|
||||
Doc* restoreBackupDoc(const std::string& backupDir);
|
||||
Doc* restoreBackupDoc(const std::string& backupDir,
|
||||
base::task_token* t);
|
||||
void loadPid();
|
||||
std::string pidFilename() const;
|
||||
std::string verFilename() const;
|
||||
|
@ -607,10 +607,12 @@ bool CustomizedGuiManager::onProcessDevModeKeyDown(KeyMessage* msg)
|
||||
App::instance()->dataRecovery()->activeSession() &&
|
||||
current_editor &&
|
||||
current_editor->document()) {
|
||||
App::instance()
|
||||
Doc* doc = App::instance()
|
||||
->dataRecovery()
|
||||
->activeSession()
|
||||
->restoreBackupById(current_editor->document()->id());
|
||||
->restoreBackupById(current_editor->document()->id(), nullptr);
|
||||
if (doc)
|
||||
UIContext::instance()->documents().add(doc);
|
||||
return true;
|
||||
}
|
||||
#endif // ENABLE_DATA_RECOVERY
|
||||
|
35
src/app/task.cpp
Normal file
35
src/app/task.cpp
Normal file
@ -0,0 +1,35 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "app/task.h"
|
||||
|
||||
#include "base/task.h"
|
||||
#include "base/thread_pool.h"
|
||||
|
||||
namespace app {
|
||||
|
||||
static base::thread_pool tasks_pool(4);
|
||||
|
||||
Task::Task()
|
||||
: m_token(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
Task::~Task()
|
||||
{
|
||||
}
|
||||
|
||||
void Task::run(base::task::func_t&& func)
|
||||
{
|
||||
m_task.on_execute(std::move(func));
|
||||
m_token = &m_task.start(tasks_pool);
|
||||
}
|
||||
|
||||
} // namespace app
|
59
src/app/task.h
Normal file
59
src/app/task.h
Normal file
@ -0,0 +1,59 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifndef APP_TASK_H_INCLUDED
|
||||
#define APP_TASK_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "base/task.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace app {
|
||||
|
||||
class Task {
|
||||
public:
|
||||
Task();
|
||||
~Task();
|
||||
|
||||
void run(base::task::func_t&& func);
|
||||
|
||||
bool completed() const {
|
||||
return m_task.completed();
|
||||
}
|
||||
|
||||
bool canceled() const {
|
||||
if (m_token)
|
||||
return m_token->canceled();
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
float progress() const {
|
||||
if (m_token)
|
||||
return m_token->progress();
|
||||
else
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
if (m_token)
|
||||
m_token->cancel();
|
||||
}
|
||||
|
||||
void set_progress(float progress) {
|
||||
if (m_token)
|
||||
m_token->set_progress(progress);
|
||||
}
|
||||
|
||||
private:
|
||||
base::task m_task;
|
||||
base::task_token* m_token;
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
||||
#endif
|
@ -11,15 +11,21 @@
|
||||
|
||||
#include "app/ui/data_recovery_view.h"
|
||||
|
||||
#include "app/app.h"
|
||||
#include "app/app_menus.h"
|
||||
#include "app/crash/data_recovery.h"
|
||||
#include "app/crash/session.h"
|
||||
#include "app/i18n/strings.h"
|
||||
#include "app/modules/gui.h"
|
||||
#include "app/task.h"
|
||||
#include "app/ui/data_recovery_view.h"
|
||||
#include "app/ui/drop_down_button.h"
|
||||
#include "app/ui/separator_in_view.h"
|
||||
#include "app/ui/skin/skin_theme.h"
|
||||
#include "app/ui/task_widget.h"
|
||||
#include "app/ui/workspace.h"
|
||||
#include "app/ui/workspace.h"
|
||||
#include "app/ui_context.h"
|
||||
#include "base/bind.h"
|
||||
#include "fmt/format.h"
|
||||
#include "ui/alert.h"
|
||||
@ -49,12 +55,73 @@ public:
|
||||
: ListItem(backup->description())
|
||||
, m_session(session)
|
||||
, m_backup(backup)
|
||||
, m_task(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
crash::Session* session() const { return m_session; }
|
||||
crash::Session::Backup* backup() const { return m_backup; }
|
||||
|
||||
bool isTaskRunning() const { return m_task != nullptr; }
|
||||
|
||||
void restoreBackup() {
|
||||
if (m_task)
|
||||
return;
|
||||
m_task = new TaskWidget(
|
||||
[this](base::task_token& t) {
|
||||
// Warning: This is executed from a worker thread
|
||||
Doc* doc = m_session->restoreBackupDoc(m_backup, &t);
|
||||
ui::execute_from_ui_thread(
|
||||
[this, doc]{
|
||||
onOpenDoc(doc);
|
||||
onDeleteTaskWidget();
|
||||
});
|
||||
});
|
||||
addChild(m_task);
|
||||
parent()->layout();
|
||||
}
|
||||
|
||||
void restoreRawImages(crash::RawImagesAs as) {
|
||||
if (m_task)
|
||||
return;
|
||||
m_task = new TaskWidget(
|
||||
[this, as](base::task_token& t){
|
||||
// Warning: This is executed from a worker thread
|
||||
Doc* doc = m_session->restoreBackupRawImages(m_backup, as, &t);
|
||||
ui::execute_from_ui_thread(
|
||||
[this, doc]{
|
||||
onOpenDoc(doc);
|
||||
onDeleteTaskWidget();
|
||||
});
|
||||
});
|
||||
addChild(m_task);
|
||||
updateView();
|
||||
}
|
||||
|
||||
void deleteBackup() {
|
||||
if (m_task)
|
||||
return;
|
||||
|
||||
m_task = new TaskWidget(
|
||||
TaskWidget::kCannotCancel,
|
||||
[this](base::task_token& t){
|
||||
// Warning: This is executed from a worker thread
|
||||
m_session->deleteBackup(m_backup);
|
||||
ui::execute_from_ui_thread(
|
||||
[this]{
|
||||
onDeleteTaskWidget();
|
||||
|
||||
// We cannot use this->deferDelete() here because it looks
|
||||
// like the m_task field can be still in use.
|
||||
setVisible(false);
|
||||
|
||||
updateView();
|
||||
});
|
||||
});
|
||||
addChild(m_task);
|
||||
updateView();
|
||||
}
|
||||
|
||||
private:
|
||||
void onSizeHint(SizeHintEvent& ev) override {
|
||||
ListItem::onSizeHint(ev);
|
||||
@ -63,8 +130,67 @@ private:
|
||||
ev.setSizeHint(sz);
|
||||
}
|
||||
|
||||
void onResize(ResizeEvent& ev) override {
|
||||
setBoundsQuietly(ev.bounds());
|
||||
|
||||
if (m_task) {
|
||||
gfx::Size sz = m_task->sizeHint();
|
||||
gfx::Rect cpos = childrenBounds();
|
||||
|
||||
int x2;
|
||||
if (auto view = View::getView(this->parent()))
|
||||
x2 = view->viewportBounds().x2();
|
||||
else
|
||||
x2 = cpos.x2();
|
||||
|
||||
cpos.x = x2 - sz.w;
|
||||
cpos.w = sz.w;
|
||||
m_task->setBounds(cpos);
|
||||
}
|
||||
}
|
||||
|
||||
void onOpenDoc(Doc* doc) {
|
||||
if (!doc)
|
||||
return;
|
||||
|
||||
Workspace* workspace = App::instance()->workspace();
|
||||
WorkspaceView* restoreView = workspace->activeView();
|
||||
|
||||
// If we have this specific item selected, and the active view in
|
||||
// the workspace is the DataRecoveryView, we can open the
|
||||
// recovered document. In other case, opening the recovered
|
||||
// document is confusing (because the user already selected other
|
||||
// item, or other tab from the workspace).
|
||||
if (dynamic_cast<DataRecoveryView*>(restoreView) &&
|
||||
isSelected()) {
|
||||
restoreView = nullptr;
|
||||
}
|
||||
|
||||
UIContext::instance()->documents().add(doc);
|
||||
|
||||
if (restoreView)
|
||||
workspace->setActiveView(restoreView);
|
||||
}
|
||||
|
||||
void onDeleteTaskWidget() {
|
||||
if (m_task) {
|
||||
removeChild(m_task);
|
||||
m_task->deferDelete();
|
||||
m_task = nullptr;
|
||||
layout();
|
||||
}
|
||||
}
|
||||
|
||||
void updateView() {
|
||||
if (auto view = View::getView(this->parent()))
|
||||
view->updateView();
|
||||
else
|
||||
parent()->layout();
|
||||
}
|
||||
|
||||
crash::Session* m_session;
|
||||
crash::Session::Backup* m_backup;
|
||||
TaskWidget* m_task;
|
||||
};
|
||||
|
||||
} // anonymous namespace
|
||||
@ -74,6 +200,7 @@ DataRecoveryView::DataRecoveryView(crash::DataRecovery* dataRecovery)
|
||||
, m_openButton(Strings::recover_files_recover_sprite().c_str())
|
||||
, m_deleteButton(Strings::recover_files_delete())
|
||||
, m_refreshButton(Strings::recover_files_refresh())
|
||||
, m_waitToEnableRefreshTimer(100)
|
||||
{
|
||||
m_listBox.setMultiselect(true);
|
||||
m_view.setExpansive(true);
|
||||
@ -110,6 +237,7 @@ DataRecoveryView::DataRecoveryView(crash::DataRecovery* dataRecovery)
|
||||
m_refreshButton.Click.connect(base::Bind(&DataRecoveryView::onRefresh, this));
|
||||
m_listBox.Change.connect(base::Bind(&DataRecoveryView::onChangeSelection, this));
|
||||
m_listBox.DoubleClickItem.connect(base::Bind(&DataRecoveryView::onOpen, this));
|
||||
m_waitToEnableRefreshTimer.Tick.connect(base::Bind(&DataRecoveryView::onCheckIfWeCanEnableRefreshButton, this));
|
||||
}
|
||||
|
||||
void DataRecoveryView::refreshListNotification()
|
||||
@ -190,6 +318,12 @@ void DataRecoveryView::fillListWith(const bool crashes)
|
||||
Empty();
|
||||
}
|
||||
|
||||
void DataRecoveryView::disableRefresh()
|
||||
{
|
||||
m_refreshButton.setEnabled(false);
|
||||
m_waitToEnableRefreshTimer.start();
|
||||
}
|
||||
|
||||
std::string DataRecoveryView::getTabText()
|
||||
{
|
||||
return Strings::recover_files_title();
|
||||
@ -221,26 +355,32 @@ void DataRecoveryView::onTabPopup(Workspace* workspace)
|
||||
|
||||
void DataRecoveryView::onOpen()
|
||||
{
|
||||
disableRefresh();
|
||||
|
||||
for (auto widget : m_listBox.children()) {
|
||||
if (!widget->isSelected())
|
||||
if (!widget->isVisible() ||
|
||||
!widget->isSelected())
|
||||
continue;
|
||||
|
||||
if (auto item = dynamic_cast<Item*>(widget)) {
|
||||
if (item->backup())
|
||||
item->session()->restoreBackup(item->backup());
|
||||
item->restoreBackup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DataRecoveryView::onOpenRaw(crash::RawImagesAs as)
|
||||
{
|
||||
disableRefresh();
|
||||
|
||||
for (auto widget : m_listBox.children()) {
|
||||
if (!widget->isSelected())
|
||||
if (!widget->isVisible() ||
|
||||
!widget->isSelected())
|
||||
continue;
|
||||
|
||||
if (auto item = dynamic_cast<Item*>(widget)) {
|
||||
if (item->backup())
|
||||
item->session()->restoreRawImages(item->backup(), as);
|
||||
item->restoreRawImages(as);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -263,14 +403,17 @@ void DataRecoveryView::onOpenMenu()
|
||||
|
||||
void DataRecoveryView::onDelete()
|
||||
{
|
||||
std::vector<Item*> items; // Items to delete.
|
||||
disableRefresh();
|
||||
|
||||
std::vector<Item*> items; // Items to delete.
|
||||
for (auto widget : m_listBox.children()) {
|
||||
if (!widget->isSelected())
|
||||
if (!widget->isVisible() ||
|
||||
!widget->isSelected())
|
||||
continue;
|
||||
|
||||
if (auto item = dynamic_cast<Item*>(widget)) {
|
||||
if (item->backup())
|
||||
if (item->backup() &&
|
||||
!item->isTaskRunning())
|
||||
items.push_back(item);
|
||||
}
|
||||
}
|
||||
@ -284,19 +427,8 @@ void DataRecoveryView::onDelete()
|
||||
int(items.size()))) != 1)
|
||||
return; // Cancel
|
||||
|
||||
for (auto item : items) {
|
||||
item->session()->deleteBackup(item->backup());
|
||||
m_listBox.removeChild(item);
|
||||
delete item;
|
||||
}
|
||||
onChangeSelection();
|
||||
|
||||
// Check if there is no more crash sessions
|
||||
if (!thereAreCrashSessions())
|
||||
Empty();
|
||||
|
||||
m_listBox.layout();
|
||||
m_view.updateView();
|
||||
for (auto item : items)
|
||||
item->deleteBackup();
|
||||
}
|
||||
|
||||
void DataRecoveryView::onRefresh()
|
||||
@ -312,7 +444,8 @@ void DataRecoveryView::onChangeSelection()
|
||||
{
|
||||
int count = 0;
|
||||
for (auto widget : m_listBox.children()) {
|
||||
if (!widget->isSelected())
|
||||
if (!widget->isVisible() ||
|
||||
!widget->isSelected())
|
||||
continue;
|
||||
|
||||
if (dynamic_cast<Item*>(widget)) {
|
||||
@ -332,11 +465,34 @@ void DataRecoveryView::onChangeSelection()
|
||||
}
|
||||
}
|
||||
|
||||
void DataRecoveryView::onCheckIfWeCanEnableRefreshButton()
|
||||
{
|
||||
for (auto widget : m_listBox.children()) {
|
||||
if (auto item = dynamic_cast<Item*>(widget)) {
|
||||
if (item->isTaskRunning())
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_refreshButton.setEnabled(true);
|
||||
m_waitToEnableRefreshTimer.stop();
|
||||
|
||||
onChangeSelection();
|
||||
|
||||
// Check if there is no more crash sessions
|
||||
if (!thereAreCrashSessions())
|
||||
Empty();
|
||||
|
||||
m_listBox.layout();
|
||||
m_view.updateView();
|
||||
}
|
||||
|
||||
bool DataRecoveryView::thereAreCrashSessions() const
|
||||
{
|
||||
for (auto widget : m_listBox.children()) {
|
||||
if (auto item = dynamic_cast<const Item*>(widget)) {
|
||||
if (item &&
|
||||
item->isVisible() &&
|
||||
item->session() &&
|
||||
item->session()->isCrashedSession())
|
||||
return true;
|
||||
|
@ -51,6 +51,7 @@ namespace app {
|
||||
void clearList();
|
||||
void fillList();
|
||||
void fillListWith(const bool crashes);
|
||||
void disableRefresh();
|
||||
|
||||
void onOpen();
|
||||
void onOpenRaw(crash::RawImagesAs as);
|
||||
@ -58,6 +59,7 @@ namespace app {
|
||||
void onDelete();
|
||||
void onRefresh();
|
||||
void onChangeSelection();
|
||||
void onCheckIfWeCanEnableRefreshButton();
|
||||
bool thereAreCrashSessions() const;
|
||||
|
||||
crash::DataRecovery* m_dataRecovery;
|
||||
@ -66,6 +68,7 @@ namespace app {
|
||||
DropDownButton m_openButton;
|
||||
ui::Button m_deleteButton;
|
||||
ui::Button m_refreshButton;
|
||||
ui::Timer m_waitToEnableRefreshTimer;
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2018 Igara Studio S.A.
|
||||
// Copyright (C) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2017 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -52,6 +52,7 @@ Tabs::Tabs(TabsDelegate* delegate)
|
||||
, m_clickedCloseButton(false)
|
||||
, m_selected(nullptr)
|
||||
, m_delegate(delegate)
|
||||
, m_addedTab(nullptr)
|
||||
, m_removedTab(nullptr)
|
||||
, m_isDragging(false)
|
||||
, m_dragCopy(false)
|
||||
@ -68,6 +69,7 @@ Tabs::Tabs(TabsDelegate* delegate)
|
||||
|
||||
Tabs::~Tabs()
|
||||
{
|
||||
m_addedTab.reset();
|
||||
m_removedTab.reset();
|
||||
|
||||
// Stop animation
|
||||
@ -99,6 +101,8 @@ void Tabs::addTab(TabView* tabView, bool from_drop, int pos)
|
||||
|
||||
tab->oldWidth = tab->width;
|
||||
tab->modified = (m_delegate ? m_delegate->isTabModified(this, tabView): false);
|
||||
|
||||
m_addedTab = tab;
|
||||
}
|
||||
|
||||
void Tabs::removeTab(TabView* tabView, bool with_animation)
|
||||
@ -497,7 +501,7 @@ void Tabs::onPaint(PaintEvent& ev)
|
||||
(tab->view != m_dragTab->view) ||
|
||||
(m_dragCopy)) {
|
||||
int dy = 0;
|
||||
if (animation() == ANI_ADDING_TAB && tab == m_selected) {
|
||||
if (animation() == ANI_ADDING_TAB && tab == m_addedTab) {
|
||||
double t = animationTime();
|
||||
dy = int(box.h - box.h * t);
|
||||
}
|
||||
@ -791,6 +795,8 @@ void Tabs::onAnimationFrame()
|
||||
|
||||
void Tabs::onAnimationStop(int animation)
|
||||
{
|
||||
m_addedTab.reset();
|
||||
|
||||
if (m_list.empty()) {
|
||||
Widget* root = window();
|
||||
if (root)
|
||||
@ -956,6 +962,8 @@ void Tabs::destroyFloatingTab()
|
||||
|
||||
tab->oldX = tab->x;
|
||||
tab->oldWidth = 0;
|
||||
|
||||
m_addedTab = tab;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
// Copyright (C) 2001-2018 David Capello
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
@ -234,6 +235,7 @@ namespace app {
|
||||
TabsDelegate* m_delegate;
|
||||
|
||||
// Variables for animation purposes
|
||||
TabPtr m_addedTab;
|
||||
TabPtr m_removedTab;
|
||||
|
||||
////////////////////////////////////////
|
||||
|
91
src/app/ui/task_widget.cpp
Normal file
91
src/app/ui/task_widget.cpp
Normal file
@ -0,0 +1,91 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "config.h"
|
||||
#endif
|
||||
|
||||
#include "app/ui/task_widget.h"
|
||||
|
||||
#include "app/i18n/strings.h"
|
||||
#include "app/modules/gui.h"
|
||||
#include "app/ui/skin/skin_theme.h"
|
||||
#include "base/clamp.h"
|
||||
#include "ui/scale.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace app {
|
||||
|
||||
using namespace ui;
|
||||
using namespace app::skin;
|
||||
|
||||
TaskWidget::TaskWidget(const Type type,
|
||||
base::task::func_t&& func)
|
||||
: Box(HORIZONTAL | HOMOGENEOUS)
|
||||
, m_monitorTimer(25)
|
||||
, m_cancelButton("Cancel")
|
||||
, m_progressBar(0, 100, 0)
|
||||
{
|
||||
if (int(type) & int(kCanCancel)) {
|
||||
addChild(&m_cancelButton);
|
||||
|
||||
m_cancelButton.Click.connect(
|
||||
[this](Event&){
|
||||
m_task.cancel();
|
||||
m_cancelButton.setEnabled(false);
|
||||
m_progressBar.setEnabled(false);
|
||||
});
|
||||
}
|
||||
|
||||
if (int(type) & int(kWithProgress)) {
|
||||
m_progressBar.setReadOnly(true);
|
||||
addChild(&m_progressBar);
|
||||
}
|
||||
|
||||
m_monitorTimer.Tick.connect(
|
||||
[this]{
|
||||
if (m_task.completed()) {
|
||||
m_monitorTimer.stop();
|
||||
onComplete();
|
||||
}
|
||||
else if (m_progressBar.parent()) {
|
||||
float v = m_task.progress();
|
||||
if (v > 0.0f) {
|
||||
TRACEARGS("progressBar setValue",
|
||||
int(base::clamp(v*100.0f, 0.0f, 100.0f)));
|
||||
m_progressBar.setValue(
|
||||
int(base::clamp(v*100.0f, 0.0f, 100.0f)));
|
||||
}
|
||||
}
|
||||
});
|
||||
m_monitorTimer.start();
|
||||
|
||||
InitTheme.connect(
|
||||
[this]{
|
||||
auto theme = static_cast<SkinTheme*>(this->theme());
|
||||
setTransparent(true);
|
||||
setBgColor(gfx::ColorNone);
|
||||
m_cancelButton.setTransparent(true);
|
||||
m_cancelButton.setStyle(theme->styles.miniButton());
|
||||
m_cancelButton.setBgColor(gfx::ColorNone);
|
||||
m_progressBar.setTransparent(true);
|
||||
m_progressBar.setBgColor(gfx::ColorNone);
|
||||
setup_mini_font(&m_cancelButton);
|
||||
setup_mini_look(&m_progressBar);
|
||||
setMaxSize(gfx::Size(std::numeric_limits<int>::max(), textHeight()));
|
||||
});
|
||||
initTheme();
|
||||
|
||||
m_task.run(std::move(func));
|
||||
}
|
||||
|
||||
void TaskWidget::onComplete()
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
} // namespace app
|
46
src/app/ui/task_widget.h
Normal file
46
src/app/ui/task_widget.h
Normal file
@ -0,0 +1,46 @@
|
||||
// Aseprite
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
//
|
||||
// This program is distributed under the terms of
|
||||
// the End-User License Agreement for Aseprite.
|
||||
|
||||
#ifndef APP_UI_TASK_WIDGET_H_INCLUDED
|
||||
#define APP_UI_TASK_WIDGET_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "app/task.h"
|
||||
#include "obs/signal.h"
|
||||
#include "ui/box.h"
|
||||
#include "ui/button.h"
|
||||
#include "ui/slider.h"
|
||||
#include "ui/timer.h"
|
||||
|
||||
namespace app {
|
||||
|
||||
class TaskWidget : public ui::Box {
|
||||
public:
|
||||
enum Type {
|
||||
kCanCancel = 1,
|
||||
kWithProgress = 2,
|
||||
kCannotCancel = kWithProgress,
|
||||
kCancelAndProgress = 3,
|
||||
};
|
||||
|
||||
TaskWidget(const Type type,
|
||||
base::task::func_t&& func);
|
||||
TaskWidget(base::task::func_t&& func)
|
||||
: TaskWidget(kCancelAndProgress, std::move(func)) { }
|
||||
|
||||
protected:
|
||||
virtual void onComplete();
|
||||
|
||||
private:
|
||||
ui::Timer m_monitorTimer;
|
||||
ui::Button m_cancelButton;
|
||||
ui::Slider m_progressBar;
|
||||
Task m_task;
|
||||
};
|
||||
|
||||
} // namespace app
|
||||
|
||||
#endif
|
Loading…
x
Reference in New Issue
Block a user