Add UI and logic to recover a backup session

Changes:
* Add DataRecoveryView
* Split SubObjectsIO into an interface and SubObjectsFromSprite
* Add a "setId" parameter to some doc::read_* functions (we need this
  functionality from session recovery)
This commit is contained in:
David Capello 2015-04-08 20:05:05 -03:00
parent 5cf0396d98
commit a4478e9306
29 changed files with 803 additions and 82 deletions

View File

@ -2,6 +2,12 @@
<!-- Copyright (C) 2001-2015 by David Capello -->
<gui>
<vbox noborders="true" id="home_view" border="4" childspacing="2" expansive="true">
<hbox noborders="true" id="recover_sprites_placeholder">
<boxfiller />
<button id="recover_sprites" text="Recover Lost Sprites" border="8" />
<boxfiller />
</hbox>
<hbox noborders="true" id="header_placeholder">
<image file="icons/ase48.png" align="center" />
<vbox border="4" childspacing="4">

View File

@ -227,8 +227,9 @@ add_library(app-lib
context_flags.cpp
crash/backup_observer.cpp
crash/data_recovery.cpp
crash/document_io.cpp
crash/read_document.cpp
crash/session.cpp
crash/write_document.cpp
document.cpp
document_api.cpp
document_exporter.cpp
@ -294,6 +295,7 @@ add_library(app-lib
ui/color_sliders.cpp
ui/configure_timeline_popup.cpp
ui/context_bar.cpp
ui/data_recovery_view.cpp
ui/devconsole_view.cpp
ui/document_view.cpp
ui/drop_down_button.cpp

View File

@ -194,6 +194,10 @@ void App::initialize(const AppOptions& options)
// Default window title bar.
updateDisplayTitleBar();
// Recover data
if (!m_modules->m_recovery.sessions().empty())
m_mainWindow->showDataRecovery(&m_modules->m_recovery);
m_mainWindow->openWindow();
// Redraw the whole screen.

View File

@ -63,7 +63,7 @@ void AddCel::onRedo()
{
Layer* layer = this->layer();
SubObjectsIO io(layer->sprite());
SubObjectsFromSprite io(layer->sprite());
bool has_data = (read8(m_stream) != 0);
if (has_data) {
ImageRef image(read_image(m_stream));

View File

@ -51,7 +51,7 @@ void AddLayer::onUndo()
void AddLayer::onRedo()
{
Layer* folder = m_folder.layer();
SubObjectsIO io(folder->sprite());
SubObjectsFromSprite io(folder->sprite());
Layer* newLayer = read_layer(m_stream, &io);
Layer* afterThis = m_afterThis.layer();

View File

@ -54,8 +54,11 @@ void BackupObserver::onAddDocument(doc::Document* document)
void BackupObserver::onRemoveDocument(doc::Document* document)
{
TRACE("DataRecovery:: Remove document %p\n", document);
base::scoped_lock hold(m_mutex);
base::remove_from_container(m_documents, document);
{
base::scoped_lock hold(m_mutex);
base::remove_from_container(m_documents, document);
}
m_session->removeDocument(static_cast<app::Document*>(document));
}
void BackupObserver::backgroundThread()

View File

@ -86,6 +86,9 @@ DataRecovery::~DataRecovery()
m_backup->stop();
delete m_backup;
if (m_inProgress)
m_inProgress->removeFromDisk();
m_inProgress.reset();
}

View File

@ -0,0 +1,265 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/crash/read_document.h"
#include "app/console.h"
#include "app/document.h"
#include "base/convert_to.h"
#include "base/exception.h"
#include "base/fs.h"
#include "base/path.h"
#include "base/serialization.h"
#include "base/string.h"
#include "base/unique_ptr.h"
#include "doc/cel.h"
#include "doc/cel_data_io.h"
#include "doc/cel_io.h"
#include "doc/cels_range.h"
#include "doc/frame.h"
#include "doc/frame_tag.h"
#include "doc/frame_tag_io.h"
#include "doc/image_io.h"
#include "doc/layer.h"
#include "doc/palette.h"
#include "doc/palette_io.h"
#include "doc/sprite.h"
#include "doc/string_io.h"
#include "doc/subobjects_io.h"
#include <fstream>
#include <map>
namespace app {
namespace crash {
using namespace base::serialization;
using namespace base::serialization::little_endian;
using namespace doc;
#ifdef _WIN32
#define IFSTREAM(dir, name, fn) \
std::ifstream name(base::from_utf8(base::join_path(dir, fn)), std::ifstream::binary);
#else
#define IFSTREAM(dir, name, fn) \
std::ifstream name(base::join_path(dir, fn), std::ifstream::binary);
#endif
namespace {
class Reader : public SubObjectsIO {
public:
Reader(const std::string& dir)
: m_sprite(nullptr)
, m_dir(dir) {
}
Sprite* loadSprite(ObjectId sprId) {
return loadObject<Sprite*>("spr", sprId, &Reader::readSprite);
}
ImageRef getImageRef(ObjectId imageId) {
if (m_images.find(imageId) != m_images.end())
return m_images[imageId];
ImageRef image(loadObject<Image*>("img", imageId, &Reader::readImage));
return m_images[imageId] = image;
}
CelDataRef getCelDataRef(ObjectId celdataId) {
if (m_celdatas.find(celdataId) != m_celdatas.end())
return m_celdatas[celdataId];
CelDataRef celData(loadObject<CelData*>("celdata", celdataId, &Reader::readCelData));
return m_celdatas[celdataId] = celData;
}
private:
template<typename T>
T loadObject(const char* prefix, ObjectId id, T (Reader::*readMember)(std::ifstream&)) {
TRACE(" - Restoring %s%d\n", prefix, id);
IFSTREAM(m_dir, s, prefix + base::convert_to<std::string>(id));
T obj = nullptr;
if (s.good())
obj = (this->*readMember)(s);
if (obj) {
TRACE(" - %s%d restored successfully\n", prefix, id);
return obj;
}
else {
TRACE(" - %s%d was not restored\n", prefix, id);
throw base::Exception(std::string("Error loading object ")
+ prefix + base::convert_to<std::string>(id));
}
}
Sprite* readSprite(std::ifstream& s) {
PixelFormat format = (PixelFormat)read8(s);
int w = read16(s);
int h = read16(s);
color_t transparentColor = read32(s);
if (format != IMAGE_RGB &&
format != IMAGE_INDEXED &&
format != IMAGE_GRAYSCALE) {
Console().printf("Invalid sprite format #%d\n", (int)format);
return nullptr;
}
if (w < 1 || h < 1 || w > 0xfffff || h > 0xfffff) {
Console().printf("Invalid sprite dimension %dx%d\n", w, h);
return nullptr;
}
base::UniquePtr<Sprite> spr(new Sprite(format, w, h, 256));
m_sprite = spr.get();
spr->setTransparentColor(transparentColor);
// Frame durations
frame_t nframes = read32(s);
if (nframes >= 1) {
spr->setTotalFrames(nframes);
for (frame_t fr=0; fr<nframes; ++fr) {
int msecs = read32(s);
spr->setFrameDuration(fr, msecs);
}
}
else {
Console().printf("Invalid number of frames #%d\n", nframes);
}
// Read layers
int nlayers = read32(s);
if (nlayers >= 1 && nlayers < 0xfffff) {
for (int i = 0; i < nlayers; ++i) {
ObjectId layId = read32(s);
Layer* lay = loadObject<Layer*>("lay", layId, &Reader::readLayer);
if (lay)
spr->folder()->addLayer(lay);
}
}
else {
Console().printf("Invalid number of layers #%d\n", nlayers);
}
// Read palettes
int npalettes = read32(s);
if (npalettes >= 1 && npalettes < 0xfffff) {
for (int i = 0; i < npalettes; ++i) {
ObjectId palId = read32(s);
Palette* pal = loadObject<Palette*>("pal", palId, &Reader::readPalette);
if (pal)
spr->setPalette(pal, true);
}
}
return spr.release();
}
Layer* readLayer(std::ifstream& s) {
LayerFlags flags = (LayerFlags)read32(s);
ObjectType type = (ObjectType)read16(s);
ASSERT(type == ObjectType::LayerImage);
std::string name = read_string(s);
if (type == ObjectType::LayerImage) {
base::UniquePtr<LayerImage> lay(new LayerImage(m_sprite));
lay->setName(name);
lay->setFlags(flags);
// 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)
lay->addCel(cel);
}
return lay.release();
}
else {
Console().printf("Unable to load layer named '%s', type #%d\n",
name, (int)type);
return nullptr;
}
}
Cel* readCel(std::ifstream& s) {
return read_cel(s, this, false);
}
CelData* readCelData(std::ifstream& s) {
return read_celdata(s, this, false);
}
Image* readImage(std::ifstream& s) {
return read_image(s, false);
}
Palette* readPalette(std::ifstream& s) {
return read_palette(s);
}
Sprite* m_sprite; // Used to pass the sprite in LayerImage() ctor
std::string m_dir;
std::map<ObjectId, ImageRef> m_images;
std::map<ObjectId, CelDataRef> m_celdatas;
};
} // anonymous namespace
//////////////////////////////////////////////////////////////////////
// Public API
bool read_document_info(const std::string& dir, DocumentInfo& info)
{
ObjectId sprId;
if (base::is_file(base::join_path(dir, "doc"))) {
IFSTREAM(dir, s, "doc");
sprId = read32(s);
}
else
return false;
IFSTREAM(dir, s, "spr" + base::convert_to<std::string>(sprId));
info.format = (PixelFormat)read8(s);
info.width = read16(s);
info.height = read16(s);
read32(s); // Ignore transparent color
info.frames = read32(s);
return true;
}
app::Document* read_document(const std::string& dir)
{
if (!base::is_file(base::join_path(dir, "doc")))
return nullptr;
IFSTREAM(dir, s, "doc");
ObjectId sprId = read32(s);
Reader reader(dir);
Sprite* spr = reader.loadSprite(sprId);
if (spr)
return new app::Document(spr);
else {
Console().printf("Unable to load sprite #%d\n", sprId);
return nullptr;
}
}
} // namespace crash
} // namespace app

View File

@ -0,0 +1,34 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifndef APP_CRASH_READ_DOCUMENT_H_INCLUDED
#define APP_CRASH_READ_DOCUMENT_H_INCLUDED
#pragma once
#include "doc/frame.h"
#include "doc/pixel_format.h"
#include <string>
namespace app {
class Document;
namespace crash {
struct DocumentInfo {
doc::PixelFormat format;
int width;
int height;
doc::frame_t frames;
};
bool read_document_info(const std::string& dir, DocumentInfo& info);
app::Document* read_document(const std::string& dir);
} // namespace crash
} // namespace app
#endif

View File

@ -11,21 +11,41 @@
#include "app/crash/session.h"
#include "app/console.h"
#include "app/context.h"
#include "app/crash/document_io.h"
#include "app/crash/read_document.h"
#include "app/crash/write_document.h"
#include "app/document.h"
#include "app/document_access.h"
#include "app/file/file.h"
#include "app/ui_context.h"
#include "base/bind.h"
#include "base/convert_to.h"
#include "base/fs.h"
#include "base/path.h"
#include "base/process.h"
#include "base/split_string.h"
#include "base/string.h"
namespace app {
namespace crash {
Session::Backup::Backup(const std::string& dir)
: m_dir(dir)
{
DocumentInfo info;
read_document_info(dir, info);
std::vector<char> buf(1024);
sprintf(&buf[0], "%s Sprite %dx%d with %d frame(s)",
info.format == IMAGE_RGB ? "RGB":
info.format == IMAGE_GRAYSCALE ? "Grayscale":
info.format == IMAGE_INDEXED ? "Indexed":
info.format == IMAGE_BITMAP ? "Bitmap": "Unknown",
info.width, info.height, info.frames);
m_desc = &buf[0];
}
Session::Session(const std::string& path)
: m_path(path)
, m_pid(0)
@ -36,6 +56,32 @@ Session::~Session()
{
}
std::string Session::name() const
{
std::string name = base::get_file_title(m_path);
std::vector<std::string> parts;
base::split_string(name, parts, "-");
if (parts.size() == 3) {
return "Session date: " + parts[0] + " time: " + parts[1] + " (PID " + parts[2] + ")";
}
else
return name;
}
const Session::Backups& Session::backups()
{
if (m_backups.empty()) {
for (auto& item : base::list_files(m_path)) {
std::string docDir = base::join_path(m_path, item);
if (base::is_directory(docDir)) {
m_backups.push_back(new Backup(docDir));
}
}
}
return m_backups;
}
bool Session::isRunning()
{
loadPid();
@ -45,7 +91,7 @@ bool Session::isRunning()
bool Session::isEmpty()
{
for (auto& item : base::list_files(m_path)) {
if (base::is_directory(item))
if (base::is_directory(base::join_path(m_path, item)))
return false;
}
return true;
@ -71,8 +117,9 @@ void Session::removeFromDisk()
try {
base::remove_directory(m_path);
}
catch (const std::exception&) {
// Is not empty
catch (const std::exception& ex) {
TRACE("Session directory cannot be removed, it's not empty\nError: '%s'\n",
ex.what());
}
}
@ -92,6 +139,46 @@ void Session::saveDocumentChanges(app::Document* doc)
write_document(dir, doc);
}
void Session::removeDocument(app::Document* doc)
{
delete_document_internals(doc);
// Delete document backup directory
std::string dir = base::join_path(m_path,
base::convert_to<std::string>(doc->id()));
if (base::is_directory(dir))
deleteDirectory(dir);
}
void Session::restoreBackup(Backup* backup)
{
Console console;
try {
app::Document* doc = read_document(backup->dir());
if (doc)
UIContext::instance()->documents().add(doc);
}
catch (const std::exception& ex) {
Console::showException(ex);
}
}
void Session::deleteBackup(Backup* backup)
{
try {
auto it = std::find(m_backups.begin(), m_backups.end(), backup);
ASSERT(it != m_backups.end());
if (it != m_backups.end())
m_backups.erase(it);
if (base::is_directory(backup->dir()))
deleteDirectory(backup->dir());
}
catch (const std::exception& ex) {
Console::showException(ex);
}
}
void Session::loadPid()
{
if (m_pid)
@ -110,9 +197,14 @@ std::string Session::pidFilename() const
return base::join_path(m_path, "pid");
}
std::string Session::dataFilename() const
void Session::deleteDirectory(const std::string& dir)
{
return base::join_path(m_path, "data");
for (auto& item : base::list_files(dir)) {
std::string objfn = base::join_path(dir, item);
if (base::is_file(objfn))
base::delete_file(objfn);
}
base::remove_directory(dir);
}
} // namespace crash

View File

@ -15,6 +15,7 @@
#include <fstream>
#include <string>
#include <vector>
namespace app {
class Document;
@ -24,9 +25,23 @@ namespace crash {
// A class to record/restore session information.
class Session {
public:
class Backup {
public:
Backup(const std::string& dir);
const std::string& dir() const { return m_dir; }
const std::string& description() const { return m_desc; }
private:
std::string m_dir;
std::string m_desc;
};
typedef std::vector<Backup*> Backups;
Session(const std::string& path);
~Session();
std::string name() const;
const Backups& backups();
bool isRunning();
bool isEmpty();
@ -34,16 +49,21 @@ namespace crash {
void removeFromDisk();
void saveDocumentChanges(app::Document* doc);
void removeDocument(app::Document* doc);
void restoreBackup(Backup* backup);
void deleteBackup(Backup* backup);
private:
void loadPid();
std::string pidFilename() const;
std::string dataFilename() const;
void deleteDirectory(const std::string& dir);
base::pid m_pid;
std::string m_path;
std::fstream m_log;
std::fstream m_pidFile;
Backups m_backups;
DISABLE_COPYING(Session);
};

View File

@ -9,13 +9,15 @@
#include "config.h"
#endif
#include "app/crash/document_io.h"
#include "app/crash/write_document.h"
#include "app/document.h"
#include "base/convert_to.h"
#include "base/fs.h"
#include "base/path.h"
#include "base/serialization.h"
#include "base/string.h"
#include "base/unique_ptr.h"
#include "doc/cel.h"
#include "doc/cel_data_io.h"
#include "doc/cel_io.h"
@ -41,11 +43,11 @@ using namespace base::serialization::little_endian;
using namespace doc;
#ifdef _WIN32
#define OFSTREAM(name, fn) \
std::ofstream name(base::from_utf8(base::join_path(path, fn)));
#define OFSTREAM(dir, name, fn) \
std::ofstream name(base::from_utf8(base::join_path(dir, fn)), std::ofstream::binary);
#else
#define OFSTREAM(name, fn) \
std::ofstream name(base::join_path(path, fn));
#define OFSTREAM(dir, name, fn) \
std::ofstream name(base::join_path(dir, fn), std::ofstream::binary);
#endif
namespace {
@ -53,29 +55,56 @@ namespace {
typedef std::map<ObjectId, ObjectVersion> Versions;
static std::map<ObjectId, Versions> g_documentObjects;
template<typename T, typename WriteFunc>
void save_object(const char* prefix, T* obj, WriteFunc write_func, Versions& versions, const std::string& dir)
{
if (!obj->version())
obj->incrementVersion();
if (versions[obj->id()] != obj->version()) {
OFSTREAM(dir, s, prefix + base::convert_to<std::string>(obj->id()));
write_func(s, obj);
versions[obj->id()] = obj->version();
TRACE(" - %s %d saved with version %d\n",
prefix, obj->id(), obj->version());
}
else {
TRACE(" - Ignoring %s %d (version %d already saved)\n",
prefix, obj->id(), obj->version());
}
}
void write_sprite(std::ofstream& s, Sprite* spr)
{
write32(s, spr->id());
write32(s, spr->version());
write32(s, spr->folder()->version());
write32(s, spr->pixelFormat());
write32(s, spr->width());
write32(s, spr->height());
write8(s, spr->pixelFormat());
write16(s, spr->width());
write16(s, spr->height());
write32(s, spr->transparentColor());
// Frame durations
write32(s, spr->totalFrames());
for (frame_t fr = 0; fr < spr->totalFrames(); ++fr)
write32(s, spr->frameDuration(fr));
// IDs of all main layers
std::vector<Layer*> layers;
spr->getLayersList(layers);
write32(s, layers.size());
for (Layer* lay : layers)
write32(s, lay->id());
// IDs of all palettes
write32(s, spr->getPalettes().size());
for (Palette* pal : spr->getPalettes())
write32(s, pal->id());
}
void write_layer_structure(std::ofstream& s, Layer* lay)
{
write32(s, lay->id());
write_string(s, lay->name());
write32(s, static_cast<int>(lay->flags())); // Flags
write16(s, static_cast<int>(lay->type())); // Type
write_string(s, lay->name());
if (lay->type() == ObjectType::LayerImage) {
CelConstIterator it, begin = static_cast<const LayerImage*>(lay)->getCelBegin();
@ -90,58 +119,58 @@ void write_layer_structure(std::ofstream& s, Layer* lay)
}
}
template<typename T, typename WriteFunc>
void save_object(T* obj, const char* prefix, WriteFunc write_func, Versions& versions, const std::string& path)
{
if (!obj->version())
obj->incrementVersion();
if (versions[obj->id()] != obj->version()) {
OFSTREAM(s, prefix + base::convert_to<std::string>(obj->id()));
write_func(s, obj);
versions[obj->id()] = obj->version();
TRACE(" - %s %d saved with version %d\n",
prefix, obj->id(), obj->version());
}
else {
TRACE(" - Ignoring %s %d (version %d already saved)\n",
prefix, obj->id(), obj->version());
}
}
} // anonymous namespace
void write_document(const std::string& path, app::Document* doc)
//////////////////////////////////////////////////////////////////////
// Public API
void write_document(const std::string& dir, app::Document* doc)
{
Versions& versions = g_documentObjects[doc->id()];
Sprite* spr = doc->sprite();
save_object(spr, "spr", write_sprite, versions, path);
// Create a "doc" file with the main sprite ID
if (!base::is_file(base::join_path(dir, "doc"))) {
OFSTREAM(dir, s, "doc");
write32(s, spr->id());
}
save_object("spr", spr, write_sprite, versions, dir);
//////////////////////////////////////////////////////////////////////
// Create one stream for each object
for (Palette* pal : spr->getPalettes())
save_object(pal, "pal", write_palette, versions, path);
save_object("pal", pal, write_palette, versions, dir);
for (FrameTag* frtag : spr->frameTags())
save_object(frtag, "frtag", write_frame_tag, versions, path);
save_object("frtag", frtag, write_frame_tag, versions, dir);
std::vector<Layer*> layers;
spr->getLayersList(layers);
for (Layer* lay : layers)
save_object(lay, "lay", write_layer_structure, versions, path);
save_object("lay", lay, write_layer_structure, versions, dir);
for (Cel* cel : spr->cels())
save_object(cel, "cel", write_cel, versions, path);
save_object("cel", cel, write_cel, versions, dir);
// Images (CelData)
for (Cel* cel : spr->uniqueCels()) {
save_object(cel->data(), "celdata", write_celdata, versions, path);
save_object(cel->image(), "img", write_image, versions, path);
save_object("celdata", cel->data(), write_celdata, versions, dir);
save_object("img", cel->image(), write_image, versions, dir);
}
}
void delete_document_internals(app::Document* doc)
{
ASSERT(doc);
auto it = g_documentObjects.find(doc->id());
// The document could not be inside g_documentObjects in case it was
// never saved by the backup process.
if (it != g_documentObjects.end())
g_documentObjects.erase(it);
}
} // namespace crash
} // namespace app

View File

@ -16,6 +16,7 @@ class Document;
namespace crash {
void write_document(const std::string& dir, app::Document* doc);
void delete_document_internals(app::Document* doc);
} // namespace crash
} // namespace app

View File

@ -0,0 +1,152 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/ui/data_recovery_view.h"
#include "app/app_menus.h"
#include "app/crash/data_recovery.h"
#include "app/crash/session.h"
#include "app/modules/gui.h"
#include "app/ui/skin/skin_style_property.h"
#include "app/ui/skin/skin_theme.h"
#include "app/ui/workspace.h"
#include "base/bind.h"
#include "ui/button.h"
#include "ui/entry.h"
#include "ui/listitem.h"
#include "ui/message.h"
#include "ui/resize_event.h"
#include "ui/system.h"
#include "ui/view.h"
namespace app {
using namespace ui;
using namespace app::skin;
class BackupItem : public ListItem {
public:
BackupItem(crash::Session* session, crash::Session::Backup* backup)
: ListItem(" - " + backup->description())
, m_session(session)
, m_backup(backup)
, m_openButton("Open")
, m_deleteButton("Delete")
, m_restored(false)
{
addChild(&m_openButton);
addChild(&m_deleteButton);
m_openButton.Click.connect(Bind(&BackupItem::onOpen, this));
m_deleteButton.Click.connect(Bind(&BackupItem::onDelete, this));
setup_mini_look(&m_openButton);
setup_mini_look(&m_deleteButton);
}
protected:
void onResize(ResizeEvent& ev) override {
ListItem::onResize(ev);
gfx::Rect rc = ev.getBounds();
gfx::Size sz1 = m_openButton.getPreferredSize();
gfx::Size sz2 = m_deleteButton.getPreferredSize();
m_openButton.setBounds(gfx::Rect(rc.x+rc.w-sz2.w-sz1.w, rc.y, sz1.w, rc.h));
m_deleteButton.setBounds(gfx::Rect(rc.x+rc.w-sz2.w, rc.y, sz2.w, rc.h));
}
void onOpen() {
if (!m_restored) {
m_restored = true;
setText("RESTORED: " + getText());
}
m_session->restoreBackup(m_backup);
}
void onDelete() {
m_session->deleteBackup(m_backup);
getParent()->invalidate();
deferDelete();
}
private:
crash::Session* m_session;
crash::Session::Backup* m_backup;
ui::Button m_openButton;
ui::Button m_deleteButton;
bool m_restored;
};
DataRecoveryView::DataRecoveryView(crash::DataRecovery* dataRecovery)
: Box(JI_VERTICAL)
, m_dataRecovery(dataRecovery)
{
SkinTheme* theme = static_cast<SkinTheme*>(getTheme());
setBgColor(theme->colors.workspace());
addChild(&m_view);
m_view.setExpansive(true);
m_view.attachToView(&m_listBox);
for (auto& session : m_dataRecovery->sessions()) {
m_listBox.addChild(new ListItem(session->name()));
for (auto& backup : session->backups())
m_listBox.addChild(new BackupItem(session.get(), backup));
}
}
DataRecoveryView::~DataRecoveryView()
{
}
std::string DataRecoveryView::getTabText()
{
return "Data Recovery";
}
TabIcon DataRecoveryView::getTabIcon()
{
return TabIcon::NONE;
}
WorkspaceView* DataRecoveryView::cloneWorkspaceView()
{
return nullptr; // This view cannot be cloned
}
void DataRecoveryView::onWorkspaceViewSelected()
{
}
void DataRecoveryView::onClonedFrom(WorkspaceView* from)
{
ASSERT(false); // Never called
}
bool DataRecoveryView::onCloseView(Workspace* workspace)
{
workspace->removeView(this);
return true;
}
void DataRecoveryView::onTabPopup(Workspace* workspace)
{
Menu* menu = AppMenus::instance()->getTabPopupMenu();
if (!menu)
return;
menu->showPopup(ui::get_mouse_position());
}
} // namespace app

View File

@ -0,0 +1,50 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifndef APP_UI_DATA_RECOVERY_VIEW_H_INCLUDED
#define APP_UI_DATA_RECOVERY_VIEW_H_INCLUDED
#pragma once
#include "app/ui/tabs.h"
#include "app/ui/workspace_view.h"
#include "ui/box.h"
#include "ui/listbox.h"
#include "ui/view.h"
namespace app {
namespace crash {
class DataRecovery;
}
class DataRecoveryView : public ui::Box
, public TabView
, public WorkspaceView {
public:
DataRecoveryView(crash::DataRecovery* dataRecovery);
~DataRecoveryView();
// TabView implementation
std::string getTabText() override;
TabIcon getTabIcon() override;
// WorkspaceView implementation
ui::Widget* getContentWidget() override { return this; }
WorkspaceView* cloneWorkspaceView() override;
void onWorkspaceViewSelected() override;
void onClonedFrom(WorkspaceView* from) override;
bool onCloseView(Workspace* workspace) override;
void onTabPopup(Workspace* workspace) override;
private:
crash::DataRecovery* m_dataRecovery;
ui::View m_view;
ui::ListBox m_listBox;
};
} // namespace app
#endif

View File

@ -11,14 +11,18 @@
#include "app/ui/home_view.h"
#include "app/app.h"
#include "app/app_menus.h"
#include "app/commands/commands.h"
#include "app/commands/params.h"
#include "app/ui/data_recovery_view.h"
#include "app/ui/main_window.h"
#include "app/ui/news_listbox.h"
#include "app/ui/recent_listbox.h"
#include "app/ui/skin/skin_style_property.h"
#include "app/ui/skin/skin_theme.h"
#include "app/ui/workspace.h"
#include "app/ui/workspace_tabs.h"
#include "app/ui_context.h"
#include "base/bind.h"
#include "base/exception.h"
@ -37,6 +41,8 @@ HomeView::HomeView()
: m_files(new RecentFilesListBox)
, m_folders(new RecentFoldersListBox)
, m_news(new NewsListBox)
, m_dataRecovery(nullptr)
, m_dataRecoveryView(nullptr)
{
SkinTheme* theme = static_cast<SkinTheme*>(getTheme());
setBgColor(theme->colors.workspace());
@ -45,16 +51,29 @@ HomeView::HomeView()
newFile()->Click.connect(Bind(&HomeView::onNewFile, this));
openFile()->Click.connect(Bind(&HomeView::onOpenFile, this));
recoverSprites()->Click.connect(Bind(&HomeView::onRecoverSprites, this));
filesView()->attachToView(m_files);
foldersView()->attachToView(m_folders);
newsView()->attachToView(m_news);
checkUpdate()->setVisible(false);
recoverSpritesPlaceholder()->setVisible(false);
}
HomeView::~HomeView()
{
if (m_dataRecoveryView) {
if (m_dataRecoveryView->getParent())
App::instance()->getMainWindow()->getWorkspace()->removeView(m_dataRecoveryView);
delete m_dataRecoveryView;
}
}
void HomeView::showDataRecovery(crash::DataRecovery* dataRecovery)
{
m_dataRecovery = dataRecovery;
recoverSpritesPlaceholder()->setVisible(true);
}
std::string HomeView::getTabText()
@ -153,4 +172,15 @@ void HomeView::onNewUpdate(const std::string& url, const std::string& version)
layout();
}
void HomeView::onRecoverSprites()
{
if (!m_dataRecoveryView)
m_dataRecoveryView = new DataRecoveryView(m_dataRecovery);
if (!m_dataRecoveryView->getParent())
App::instance()->getMainWindow()->getWorkspace()->addView(m_dataRecoveryView);
App::instance()->getMainWindow()->getTabsBar()->selectTab(m_dataRecoveryView);
}
} // namespace app

View File

@ -22,10 +22,15 @@ namespace ui {
namespace app {
class DataRecoveryView;
class NewsListBox;
class RecentFilesListBox;
class RecentFoldersListBox;
namespace crash {
class DataRecovery;
}
class HomeView : public app::gen::HomeView
, public TabView
, public WorkspaceView
@ -37,6 +42,8 @@ namespace app {
HomeView();
~HomeView();
void showDataRecovery(crash::DataRecovery* dataRecovery);
// TabView implementation
std::string getTabText() override;
TabIcon getTabIcon() override;
@ -61,10 +68,13 @@ namespace app {
private:
void onNewFile();
void onOpenFile();
void onRecoverSprites();
RecentFilesListBox* m_files;
RecentFoldersListBox* m_folders;
NewsListBox* m_news;
crash::DataRecovery* m_dataRecovery;
DataRecoveryView* m_dataRecoveryView;
};
} // namespace app

View File

@ -222,6 +222,11 @@ void MainWindow::popTimeline()
setTimelineVisibility(true);
}
void MainWindow::showDataRecovery(crash::DataRecovery* dataRecovery)
{
getHomeView()->showDataRecovery(dataRecovery);
}
bool MainWindow::onProcessMessage(ui::Message* msg)
{
if (msg->type() == kOpenMessage)

View File

@ -38,6 +38,10 @@ namespace app {
class Workspace;
class WorkspaceTabs;
namespace crash {
class DataRecovery;
}
class MainWindow : public app::gen::MainWindow
, public TabsDelegate {
public:
@ -73,6 +77,8 @@ namespace app {
void setTimelineVisibility(bool visible);
void popTimeline();
void showDataRecovery(crash::DataRecovery* dataRecovery);
// TabsDelegate implementation.
bool onIsModified(Tabs* tabs, TabView* tabView) override;
void onSelectTab(Tabs* tabs, TabView* tabView) override;

View File

@ -25,26 +25,26 @@ using namespace base::serialization::little_endian;
void write_celdata(std::ostream& os, const CelData* celdata)
{
write32(os, celdata->id());
write16(os, (int16_t)celdata->position().x);
write16(os, (int16_t)celdata->position().y);
write32(os, (int16_t)celdata->position().x);
write32(os, (int16_t)celdata->position().y);
write8(os, celdata->opacity());
write32(os, celdata->image()->id());
}
CelData* read_celdata(std::istream& is, SubObjectsIO* subObjects)
CelData* read_celdata(std::istream& is, SubObjectsIO* subObjects, bool setId)
{
ObjectId id = read32(is);
int x = (int16_t)read16(is);
int y = (int16_t)read16(is);
int x = read32(is);
int y = read32(is);
int opacity = read8(is);
ObjectId imageId = read32(is);
ImageRef image(subObjects->getImageRef(imageId));
base::UniquePtr<CelData> celdata(new CelData(image));
celdata->setPosition(x, y);
celdata->setOpacity(opacity);
celdata->setId(id);
if (setId)
celdata->setId(id);
return celdata.release();
}

View File

@ -16,7 +16,7 @@ namespace doc {
class SubObjectsIO;
void write_celdata(std::ostream& os, const CelData* cel);
CelData* read_celdata(std::istream& is, SubObjectsIO* subObjects);
CelData* read_celdata(std::istream& is, SubObjectsIO* subObjects, bool setId = true);
} // namespace doc

View File

@ -29,7 +29,7 @@ void write_cel(std::ostream& os, const Cel* cel)
write32(os, cel->dataRef()->id());
}
Cel* read_cel(std::istream& is, SubObjectsIO* subObjects)
Cel* read_cel(std::istream& is, SubObjectsIO* subObjects, bool setId)
{
ObjectId id = read32(is);
frame_t frame(read16(is));
@ -38,7 +38,8 @@ Cel* read_cel(std::istream& is, SubObjectsIO* subObjects)
ASSERT(celData);
base::UniquePtr<Cel> cel(new Cel(frame, celData));
cel->setId(id);
if (setId)
cel->setId(id);
return cel.release();
}

View File

@ -16,7 +16,7 @@ namespace doc {
class SubObjectsIO;
void write_cel(std::ostream& os, const Cel* cel);
Cel* read_cel(std::istream& is, SubObjectsIO* subObjects);
Cel* read_cel(std::istream& is, SubObjectsIO* subObjects, bool setId = true);
} // namespace doc

View File

@ -46,7 +46,7 @@ void write_image(std::ostream& os, const Image* image)
os.write((char*)image->getPixelAddress(0, c), size);
}
Image* read_image(std::istream& is)
Image* read_image(std::istream& is, bool setId)
{
ObjectId id = read32(is);
int pixelFormat = read8(is); // Pixel format
@ -61,7 +61,8 @@ Image* read_image(std::istream& is)
is.read((char*)image->getPixelAddress(0, c), size);
image->setMaskColor(maskColor);
image->setId(id);
if (setId)
image->setId(id);
return image.release();
}

View File

@ -15,7 +15,7 @@ namespace doc {
class Image;
void write_image(std::ostream& os, const Image* image);
Image* read_image(std::istream& is);
Image* read_image(std::istream& is, bool setId = true);
} // namespace doc

View File

@ -96,7 +96,7 @@ void write_layer(std::ostream& os, const Layer* layer)
}
}
Layer* read_layer(std::istream& is, SubObjectsIO* subObjects)
Layer* read_layer(std::istream& is, SubObjectsFromSprite* subObjects)
{
ObjectId id = read32(is);
std::string name = read_string(is);

View File

@ -14,7 +14,7 @@
namespace doc {
class Layer;
class SubObjectsIO;
class SubObjectsFromSprite;
// Thrown when a invalid layer type is read from the istream.
class InvalidLayerType : public base::Exception {
@ -23,7 +23,7 @@ namespace doc {
};
void write_layer(std::ostream& os, const Layer* layer);
Layer* read_layer(std::istream& is, SubObjectsIO* subObjects);
Layer* read_layer(std::istream& is, SubObjectsFromSprite* subObjects);
} // namespace doc

View File

@ -22,19 +22,19 @@ namespace doc {
using namespace doc;
SubObjectsIO::SubObjectsIO(Sprite* sprite)
SubObjectsFromSprite::SubObjectsFromSprite(Sprite* sprite)
: m_sprite(sprite)
{
}
void SubObjectsIO::addImageRef(const ImageRef& image)
void SubObjectsFromSprite::addImageRef(const ImageRef& image)
{
ASSERT(image);
ASSERT(!getImageRef(image->id()));
m_images.insert(std::make_pair(image->id(), image));
}
ImageRef SubObjectsIO::getImageRef(ObjectId imageId)
ImageRef SubObjectsFromSprite::getImageRef(ObjectId imageId)
{
auto it = m_images.find(imageId);
if (it != m_images.end()) {
@ -47,14 +47,14 @@ ImageRef SubObjectsIO::getImageRef(ObjectId imageId)
return m_sprite->getImageRef(imageId);
}
void SubObjectsIO::addCelDataRef(const CelDataRef& celdata)
void SubObjectsFromSprite::addCelDataRef(const CelDataRef& celdata)
{
ASSERT(celdata);
ASSERT(!getCelDataRef(celdata->id()));
m_celdatas.insert(std::make_pair(celdata->id(), celdata));
}
CelDataRef SubObjectsIO::getCelDataRef(ObjectId celdataId)
CelDataRef SubObjectsFromSprite::getCelDataRef(ObjectId celdataId)
{
auto it = m_celdatas.find(celdataId);
if (it != m_celdatas.end()) {

View File

@ -1,5 +1,5 @@
// Aseprite Document Library
// Copyright (c) 2001-2014 David Capello
// Copyright (c) 2001-2015 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@ -17,18 +17,25 @@
namespace doc {
class Sprite;
// Helper class used to read children-objects by layers and cels.
class SubObjectsIO {
public:
SubObjectsIO(Sprite* sprite);
virtual ~SubObjectsIO() { }
virtual ImageRef getImageRef(ObjectId imageId) = 0;
virtual CelDataRef getCelDataRef(ObjectId celdataId) = 0;
};
// Helper class used to read children-objects by layers and cels.
class SubObjectsFromSprite : public SubObjectsIO {
public:
SubObjectsFromSprite(Sprite* sprite);
Sprite* sprite() const { return m_sprite; }
void addImageRef(const ImageRef& image);
void addCelDataRef(const CelDataRef& celdata);
ImageRef getImageRef(ObjectId imageId);
CelDataRef getCelDataRef(ObjectId celdataId);
ImageRef getImageRef(ObjectId imageId) override;
CelDataRef getCelDataRef(ObjectId celdataId) override;
private:
Sprite* m_sprite;