aseprite/src/app/commands/cmd_save_file.cpp
2017-11-30 23:41:45 -03:00

480 lines
12 KiB
C++

// Aseprite
// Copyright (C) 2001-2017 David Capello
//
// 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/commands/cmd_save_file.h"
#include "app/app.h"
#include "app/commands/command.h"
#include "app/commands/commands.h"
#include "app/commands/params.h"
#include "app/console.h"
#include "app/context_access.h"
#include "app/file/file.h"
#include "app/file_selector.h"
#include "app/i18n/strings.h"
#include "app/job.h"
#include "app/modules/gui.h"
#include "app/pref/preferences.h"
#include "app/recent_files.h"
#include "app/restore_visible_layers.h"
#include "app/ui/layer_frame_comboboxes.h"
#include "app/ui/status_bar.h"
#include "base/bind.h"
#include "base/convert_to.h"
#include "base/fs.h"
#include "base/thread.h"
#include "base/unique_ptr.h"
#include "doc/frame_tag.h"
#include "doc/sprite.h"
#include "ui/ui.h"
namespace app {
class SaveAsCopyDelegate : public FileSelectorDelegate {
public:
SaveAsCopyDelegate(const Sprite* sprite,
const double scale,
const std::string& layer,
const std::string& frame,
const bool applyPixelRatio)
: m_sprite(sprite),
m_resizeScale(scale),
m_layer(layer),
m_frame(frame),
m_applyPixelRatio(applyPixelRatio) { }
bool hasResizeCombobox() override {
return true;
}
double getResizeScale() override {
return m_resizeScale;
}
void setResizeScale(double scale) override {
m_resizeScale = scale;
}
void fillLayersComboBox(ui::ComboBox* layers) override {
fill_layers_combobox(m_sprite, layers, m_layer);
}
void fillFramesComboBox(ui::ComboBox* frames) override {
fill_frames_combobox(m_sprite, frames, m_frame);
}
std::string getLayers() override { return m_layer; }
std::string getFrames() override { return m_frame; }
void setLayers(const std::string& layer) override {
m_layer = layer;
}
void setFrames(const std::string& frame) override {
m_frame = frame;
}
void setApplyPixelRatio(bool applyPixelRatio) override {
m_applyPixelRatio = applyPixelRatio;
}
bool applyPixelRatio() const override {
return m_applyPixelRatio;
}
doc::PixelRatio pixelRatio() override {
return m_sprite->pixelRatio();
}
private:
const Sprite* m_sprite;
double m_resizeScale;
std::string m_layer;
std::string m_frame;
bool m_applyPixelRatio;
};
class SaveFileJob : public Job, public IFileOpProgress {
public:
SaveFileJob(FileOp* fop)
: Job("Saving file")
, m_fop(fop)
{
}
void showProgressWindow() {
startJob();
if (isCanceled()) {
m_fop->stop();
}
waitJob();
}
private:
// Thread to do the hard work: save the file to the disk.
virtual void onJob() override {
try {
m_fop->operate(this);
}
catch (const std::exception& e) {
m_fop->setError("Error saving file:\n%s", e.what());
}
m_fop->done();
}
virtual void ackFileOpProgress(double progress) override {
jobProgress(progress);
}
FileOp* m_fop;
};
//////////////////////////////////////////////////////////////////////
SaveFileBaseCommand::SaveFileBaseCommand(const char* id, CommandFlags flags)
: Command(id, flags)
{
}
void SaveFileBaseCommand::onLoadParams(const Params& params)
{
m_filename = params.get("filename");
m_filenameFormat = params.get("filename-format");
m_frameTag = params.get("frame-tag");
m_slice = params.get("slice");
if (params.has_param("from-frame") ||
params.has_param("to-frame")) {
doc::frame_t fromFrame = params.get_as<doc::frame_t>("from-frame");
doc::frame_t toFrame = params.get_as<doc::frame_t>("to-frame");
m_selFrames.insert(fromFrame, toFrame);
m_adjustFramesByFrameTag = true;
}
else {
m_selFrames.clear();
m_adjustFramesByFrameTag = false;
}
}
// Returns true if there is a current sprite to save.
// [main thread]
bool SaveFileBaseCommand::onEnabled(Context* context)
{
return context->checkFlags(ContextFlags::ActiveDocumentIsWritable);
}
bool SaveFileBaseCommand::saveAsDialog(
Context* context,
const std::string& dlgTitle,
const std::string& forbiddenFilename,
FileSelectorDelegate* delegate)
{
const Document* document = context->activeDocument();
std::string filename;
// If there is a delegate, we're doing a "Save Copy As/Export", so we don't
// have to mark the file as saved.
const bool isExport = (delegate != nullptr);
const bool markAsSaved = (!isExport);
double xscale = 1.0;
double yscale = 1.0;
if (!m_filename.empty()) {
filename = m_filename;
}
else {
std::string exts = get_writable_extensions();
filename = document->filename();
again:;
FileSelectorFiles newfilename;
if (!app::show_file_selector(
dlgTitle, filename, exts,
FileSelectorType::Save, newfilename,
delegate))
return false;
filename = newfilename.front();
if (!forbiddenFilename.empty() &&
base::normalize_path(forbiddenFilename) ==
base::normalize_path(filename)) {
ui::Alert::show(Strings::alerts_cannot_file_overwrite_on_export());
goto again;
}
if (delegate &&
delegate->hasResizeCombobox()) {
xscale = yscale = delegate->getResizeScale();
}
}
std::string oldFilename;
{
ContextWriter writer(context);
Document* documentWriter = writer.document();
oldFilename = documentWriter->filename();
// Change the document file name
documentWriter->setFilename(filename.c_str());
m_selectedFilename = filename;
}
// Pixel ratio
if (delegate &&
delegate->applyPixelRatio()) {
doc::PixelRatio pr = delegate->pixelRatio();
xscale *= pr.w;
yscale *= pr.h;
}
// Apply scale
bool undoResize = false;
if (xscale != 1.0 || yscale != 1.0) {
Command* resizeCmd = Commands::instance()->byId(CommandId::SpriteSize);
ASSERT(resizeCmd);
if (resizeCmd) {
int width = document->sprite()->width();
int height = document->sprite()->height();
int newWidth = int(double(width) * xscale);
int newHeight = int(double(height) * yscale);
if (newWidth < 1) newWidth = 1;
if (newHeight < 1) newHeight = 1;
if (width != newWidth || height != newHeight) {
Params params;
params.set("use-ui", "false");
params.set("width", base::convert_to<std::string>(newWidth).c_str());
params.set("height", base::convert_to<std::string>(newHeight).c_str());
params.set("resize-method", "nearest-neighbor"); // TODO add algorithm in the UI?
context->executeCommand(resizeCmd, params);
undoResize = true;
}
}
}
{
RestoreVisibleLayers layersVisibility;
if (delegate) {
Site site = context->activeSite();
// Selected layers to export
calculate_visible_layers(site,
delegate->getLayers(),
layersVisibility);
// Selected frames to export
SelectedFrames selFrames;
FrameTag* frameTag = calculate_selected_frames(
site, delegate->getFrames(), selFrames);
if (frameTag)
m_frameTag = frameTag->name();
m_selFrames = selFrames;
m_adjustFramesByFrameTag = false;
}
// Save the document
saveDocumentInBackground(context, const_cast<Document*>(document), markAsSaved);
}
// Undo resize
if (undoResize) {
Command* undoCmd = Commands::instance()->byId(CommandId::Undo);
if (undoCmd)
context->executeCommand(undoCmd);
}
{
ContextWriter writer(context);
Document* documentWriter = writer.document();
if (document->isModified())
documentWriter->setFilename(oldFilename);
else
documentWriter->incrementVersion();
}
return true;
}
void SaveFileBaseCommand::saveDocumentInBackground(const Context* context,
const app::Document* document,
bool markAsSaved) const
{
base::UniquePtr<FileOp> fop(
FileOp::createSaveDocumentOperation(
context,
FileOpROI(document, m_slice, m_frameTag,
m_selFrames, m_adjustFramesByFrameTag),
document->filename(),
m_filenameFormat));
if (!fop)
return;
SaveFileJob job(fop);
job.showProgressWindow();
if (fop->hasError()) {
Console console;
console.printf(fop->error().c_str());
// We don't know if the file was saved correctly or not. So mark
// it as it should be saved again.
const_cast<Document*>(document)->impossibleToBackToSavedState();
}
// If the job was cancelled, mark the document as modified.
else if (fop->isStop()) {
const_cast<Document*>(document)->impossibleToBackToSavedState();
}
else if (context->isUIAvailable()) {
App::instance()->recentFiles()->addRecentFile(document->filename().c_str());
if (markAsSaved)
const_cast<Document*>(document)->markAsSaved();
StatusBar::instance()
->setStatusText(2000, "File %s, saved.",
document->name().c_str());
}
}
//////////////////////////////////////////////////////////////////////
class SaveFileCommand : public SaveFileBaseCommand {
public:
SaveFileCommand();
Command* clone() const override { return new SaveFileCommand(*this); }
protected:
void onExecute(Context* context) override;
};
SaveFileCommand::SaveFileCommand()
: SaveFileBaseCommand("SaveFile", CmdRecordableFlag)
{
}
// Saves the active document in a file.
// [main thread]
void SaveFileCommand::onExecute(Context* context)
{
Document* document = context->activeDocument();
// If the document is associated to a file in the file-system, we can
// save it directly without user interaction.
if (document->isAssociatedToFile()) {
ContextWriter writer(context);
Document* documentWriter = writer.document();
saveDocumentInBackground(context, documentWriter, true);
}
// If the document isn't associated to a file, we must to show the
// save-as dialog to the user to select for first time the file-name
// for this document.
else {
saveAsDialog(context, "Save File");
}
}
class SaveFileAsCommand : public SaveFileBaseCommand {
public:
SaveFileAsCommand();
Command* clone() const override { return new SaveFileAsCommand(*this); }
protected:
void onExecute(Context* context) override;
};
SaveFileAsCommand::SaveFileAsCommand()
: SaveFileBaseCommand("SaveFileAs", CmdRecordableFlag)
{
}
void SaveFileAsCommand::onExecute(Context* context)
{
saveAsDialog(context, "Save As");
}
class SaveFileCopyAsCommand : public SaveFileBaseCommand {
public:
SaveFileCopyAsCommand();
Command* clone() const override { return new SaveFileCopyAsCommand(*this); }
protected:
void onExecute(Context* context) override;
};
SaveFileCopyAsCommand::SaveFileCopyAsCommand()
: SaveFileBaseCommand("SaveFileCopyAs", CmdRecordableFlag)
{
}
void SaveFileCopyAsCommand::onExecute(Context* context)
{
const Document* document = context->activeDocument();
std::string oldFilename = document->filename();
// show "Save As" dialog
DocumentPreferences& docPref = Preferences::instance().document(document);
base::UniquePtr<SaveAsCopyDelegate> delegate;
if (context->isUIAvailable()) {
delegate.reset(
new SaveAsCopyDelegate(
document->sprite(),
docPref.saveCopy.resizeScale(),
docPref.saveCopy.layer(),
docPref.saveCopy.frameTag(),
docPref.saveCopy.applyPixelRatio()));
}
// Is a default output filename in the preferences?
if (!docPref.saveCopy.filename().empty()) {
ContextWriter writer(context);
writer.document()->setFilename(
docPref.saveCopy.filename());
}
if (saveAsDialog(context, "Export",
(document->isAssociatedToFile() ? oldFilename: std::string()),
delegate)) {
docPref.saveCopy.filename(document->filename());
if (delegate) {
docPref.saveCopy.resizeScale(delegate->getResizeScale());
docPref.saveCopy.layer(delegate->getLayers());
docPref.saveCopy.frameTag(delegate->getFrames());
docPref.saveCopy.applyPixelRatio(delegate->applyPixelRatio());
}
}
// Restore the file name.
{
ContextWriter writer(context);
writer.document()->setFilename(oldFilename.c_str());
}
}
Command* CommandFactory::createSaveFileCommand()
{
return new SaveFileCommand;
}
Command* CommandFactory::createSaveFileAsCommand()
{
return new SaveFileAsCommand;
}
Command* CommandFactory::createSaveFileCopyAsCommand()
{
return new SaveFileCopyAsCommand;
}
} // namespace app