You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1172 lines
31 KiB
1172 lines
31 KiB
/*
|
|
* Copyright (c) 1999 Matthias Elter <me@kde.org>
|
|
* Copyright (c) 2000 John Califf <jcaliff@compuzone.net>
|
|
* Copyright (c) 2001 Toshitaka Fujioka <fujioka@kde.org>
|
|
* Copyright (c) 2002, 2003 Patrick Julien <freak@codepimps.org>
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
// Qt
|
|
#include <qapplication.h>
|
|
#include <qdom.h>
|
|
#include <qimage.h>
|
|
#include <qpainter.h>
|
|
#include <qtl.h>
|
|
#include <qstringlist.h>
|
|
#include <qwidget.h>
|
|
#include <qpaintdevicemetrics.h>
|
|
|
|
// KDE
|
|
#include <dcopobject.h>
|
|
#include <kapplication.h>
|
|
#include <kcommand.h>
|
|
#include <kdebug.h>
|
|
#include <kimageio.h>
|
|
#include <kfiledialog.h>
|
|
#include <kglobal.h>
|
|
#include <kmimetype.h>
|
|
#include <knotifyclient.h>
|
|
#include <klocale.h>
|
|
#include <kmessagebox.h>
|
|
|
|
// KOffice
|
|
#include <KoFilterManager.h>
|
|
#include <KoMainWindow.h>
|
|
#include <KoQueryTrader.h>
|
|
#include <KoStore.h>
|
|
#include <KoStoreDevice.h>
|
|
#include <KoTemplateChooseDia.h>
|
|
#include <KoApplication.h>
|
|
#include <KoCommandHistory.h>
|
|
|
|
// Local
|
|
#include <kis_clipboard.h>
|
|
#include <kis_meta_registry.h>
|
|
#include "kis_annotation.h"
|
|
#include "kis_types.h"
|
|
#include "kis_config.h"
|
|
#include "kis_debug_areas.h"
|
|
#include "kis_doc.h"
|
|
#include "kis_factory.h"
|
|
#include "kis_image.h"
|
|
#include "kis_layer.h"
|
|
#include "kis_paint_layer.h"
|
|
#include "kis_nameserver.h"
|
|
#include "kis_painter.h"
|
|
#include "kis_selection.h"
|
|
#include "kis_fill_painter.h"
|
|
#include "kis_command.h"
|
|
#include "kis_view.h"
|
|
#include "kis_colorspace.h"
|
|
#include "kis_colorspace_factory_registry.h"
|
|
#include "kis_profile.h"
|
|
#include "kis_id.h"
|
|
#include "kis_part_layer.h"
|
|
#include "kis_doc_iface.h"
|
|
#include "kis_paint_device_action.h"
|
|
#include "kis_custom_image_widget.h"
|
|
#include "kis_load_visitor.h"
|
|
#include "kis_save_visitor.h"
|
|
#include "kis_savexml_visitor.h"
|
|
|
|
static const char *CURRENT_DTD_VERSION = "1.3";
|
|
|
|
/**
|
|
* Mime type for this app - not same as file type, but file types
|
|
* can be associated with a mime type and are opened with applications
|
|
* associated with the same mime type
|
|
*/
|
|
#define APP_MIMETYPE "application/x-krita"
|
|
|
|
/**
|
|
* Mime type for native file format
|
|
*/
|
|
#define NATIVE_MIMETYPE "application/x-kra"
|
|
|
|
namespace {
|
|
class KisCommandImageMv : public KisCommand {
|
|
typedef KisCommand super;
|
|
|
|
public:
|
|
KisCommandImageMv(KisDoc *doc,
|
|
KisUndoAdapter *adapter,
|
|
const QString& name,
|
|
const QString& oldName) : super(i18n("Rename Image"), adapter)
|
|
{
|
|
m_doc = doc;
|
|
m_name = name;
|
|
m_oldName = oldName;
|
|
}
|
|
|
|
virtual ~KisCommandImageMv()
|
|
{
|
|
}
|
|
|
|
virtual void execute()
|
|
{
|
|
adapter()->setUndo(false);
|
|
m_doc->renameImage(m_oldName, m_name);
|
|
adapter()->setUndo(true);
|
|
}
|
|
|
|
virtual void unexecute()
|
|
{
|
|
adapter()->setUndo(false);
|
|
m_doc->renameImage(m_name, m_oldName);
|
|
adapter()->setUndo(true);
|
|
}
|
|
|
|
private:
|
|
KisDoc *m_doc;
|
|
QString m_name;
|
|
QString m_oldName;
|
|
};
|
|
|
|
}
|
|
|
|
KisDoc::KisDoc(QWidget *parentWidget, const char *widgetName, QObject *parent, const char *name, bool singleViewMode) :
|
|
super(parentWidget, widgetName, parent, name, singleViewMode)
|
|
{
|
|
|
|
m_undo = false;
|
|
m_dcop = 0;
|
|
m_cmdHistory = 0;
|
|
m_nserver = 0;
|
|
m_currentImage = 0;
|
|
m_currentMacro = 0;
|
|
m_macroNestDepth = 0;
|
|
m_ioProgressBase = 0;
|
|
m_ioProgressTotalSteps = 0;
|
|
|
|
setInstance( KisFactory::instance(), false );
|
|
setTemplateType( "krita_template" );
|
|
|
|
init();
|
|
|
|
if (name)
|
|
dcopObject();
|
|
}
|
|
|
|
KisDoc::~KisDoc()
|
|
{
|
|
delete m_cmdHistory;
|
|
delete m_nserver;
|
|
m_undoListeners.setAutoDelete(false);
|
|
delete m_dcop;
|
|
}
|
|
|
|
QCString KisDoc::mimeType() const
|
|
{
|
|
return APP_MIMETYPE;
|
|
}
|
|
|
|
DCOPObject *KisDoc::dcopObject()
|
|
{
|
|
if (!m_dcop) {
|
|
m_dcop = new KisDocIface(this);
|
|
Q_CHECK_PTR(m_dcop);
|
|
}
|
|
return m_dcop;
|
|
}
|
|
|
|
bool KisDoc::initDoc(InitDocFlags flags, QWidget* parentWidget)
|
|
{
|
|
if (!init())
|
|
return false;
|
|
|
|
bool ok = false;
|
|
|
|
QString file;
|
|
KoTemplateChooseDia::DialogType dlgtype;
|
|
|
|
if (flags != KoDocument::InitDocFileNew) {
|
|
dlgtype = KoTemplateChooseDia::Everything;
|
|
} else {
|
|
dlgtype = KoTemplateChooseDia::OnlyTemplates;
|
|
}
|
|
|
|
KoTemplateChooseDia::ReturnType ret =
|
|
KoTemplateChooseDia::choose(KisFactory::instance(),
|
|
file,
|
|
dlgtype,
|
|
"krita_template",
|
|
parentWidget);
|
|
setUndo(false);
|
|
|
|
if (ret == KoTemplateChooseDia::Template) {
|
|
resetURL();
|
|
ok = loadNativeFormat( file );
|
|
setEmpty();
|
|
ok = true;
|
|
|
|
} else if (ret == KoTemplateChooseDia::File) {
|
|
KURL url( file );
|
|
ok = openURL(url);
|
|
} else if (ret == KoTemplateChooseDia::Empty) {
|
|
setEmpty();
|
|
ok = true;
|
|
}
|
|
|
|
setModified(false);
|
|
KisConfig cfg;
|
|
setUndo(cfg.undoEnabled());
|
|
|
|
return ok;
|
|
}
|
|
|
|
void KisDoc::openExistingFile(const QString& file)
|
|
{
|
|
setUndo(false);
|
|
|
|
KoDocument::openExistingFile(file);
|
|
|
|
setUndo(true);
|
|
}
|
|
|
|
void KisDoc::openTemplate(const QString& file)
|
|
{
|
|
setUndo(false);
|
|
|
|
KoDocument::openTemplate(file);
|
|
|
|
setUndo(true);
|
|
}
|
|
|
|
bool KisDoc::init()
|
|
{
|
|
if (m_cmdHistory) {
|
|
delete m_cmdHistory;
|
|
m_cmdHistory = 0;
|
|
}
|
|
|
|
if (m_nserver) {
|
|
delete m_nserver;
|
|
m_nserver = 0;
|
|
}
|
|
|
|
m_cmdHistory = new KoCommandHistory(actionCollection(), true);
|
|
Q_CHECK_PTR(m_cmdHistory);
|
|
|
|
connect(m_cmdHistory, SIGNAL(documentRestored()), this, SLOT(slotDocumentRestored()));
|
|
connect(m_cmdHistory, SIGNAL(commandExecuted(KCommand *)), this, SLOT(slotCommandExecuted(KCommand *)));
|
|
setUndo(true);
|
|
|
|
m_nserver = new KisNameServer(i18n("Image %1"), 1);
|
|
Q_CHECK_PTR(m_nserver);
|
|
|
|
if (!KisMetaRegistry::instance()->csRegistry()->exists(KisID("RGBA",""))) {
|
|
KMessageBox::sorry(0, i18n("No colorspace modules loaded: cannot run Krita"));
|
|
return false;
|
|
}
|
|
|
|
m_undoListeners.setAutoDelete(false);
|
|
|
|
return true;
|
|
}
|
|
|
|
QDomDocument KisDoc::saveXML()
|
|
{
|
|
QDomDocument doc = createDomDocument("DOC", CURRENT_DTD_VERSION);
|
|
QDomElement root = doc.documentElement();
|
|
|
|
root.setAttribute("editor", "Krita");
|
|
root.setAttribute("depth", sizeof(Q_UINT8));
|
|
root.setAttribute("syntaxVersion", "1");
|
|
|
|
root.appendChild(saveImage(doc, m_currentImage));
|
|
|
|
return doc;
|
|
}
|
|
|
|
bool KisDoc::loadOasis( const QDomDocument&, KoOasisStyles&, const QDomDocument&, KoStore* )
|
|
{
|
|
//XXX: todo (and that includes defining an OASIS format for layered 2D raster data!)
|
|
return false;
|
|
}
|
|
|
|
|
|
bool KisDoc::saveOasis( KoStore*, KoXmlWriter* )
|
|
{
|
|
//XXX: todo (and that includes defining an OASIS format for layered 2D raster data!)
|
|
return false;
|
|
}
|
|
|
|
bool KisDoc::loadXML(QIODevice *, const QDomDocument& doc)
|
|
{
|
|
QDomElement root;
|
|
QString attr;
|
|
QDomNode node;
|
|
KisImageSP img;
|
|
|
|
if (!init())
|
|
return false;
|
|
if (doc.doctype().name() != "DOC")
|
|
return false;
|
|
root = doc.documentElement();
|
|
attr = root.attribute("syntaxVersion");
|
|
if (attr.toInt() > 1)
|
|
return false;
|
|
if ((attr = root.attribute("depth")).isNull())
|
|
return false;
|
|
m_conversionDepth = attr.toInt();
|
|
|
|
if (!root.hasChildNodes()) {
|
|
return false; // XXX used to be: return slotNewImage();
|
|
}
|
|
|
|
setUndo(false);
|
|
|
|
for (node = root.firstChild(); !node.isNull(); node = node.nextSibling()) {
|
|
if (node.isElement()) {
|
|
if (node.nodeName() == "IMAGE") {
|
|
QDomElement elem = node.toElement();
|
|
if (!(img = loadImage(elem)))
|
|
return false;
|
|
m_currentImage = img;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
emit loadingFinished();
|
|
return true;
|
|
}
|
|
|
|
bool KisDoc::loadChildren(KoStore* store) {
|
|
QPtrListIterator<KoDocumentChild> it(children());
|
|
for( ; it.current(); ++it ) {
|
|
if (!it.current()->loadDocument(store)) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
QDomElement KisDoc::saveImage(QDomDocument& doc, KisImageSP img)
|
|
{
|
|
QDomElement image = doc.createElement("IMAGE");
|
|
|
|
Q_ASSERT(img);
|
|
image.setAttribute("name", img->name());
|
|
image.setAttribute("mime", "application/x-kra");
|
|
image.setAttribute("width", img->width());
|
|
image.setAttribute("height", img->height());
|
|
image.setAttribute("colorspacename", img->colorSpace()->id().id());
|
|
image.setAttribute("description", img->description());
|
|
// XXX: Save profile as blob inside the image, instead of the product name.
|
|
if (img->getProfile() && img->getProfile()-> valid())
|
|
image.setAttribute("profile", img->getProfile()->productName());
|
|
image.setAttribute("x-res", img->xRes());
|
|
image.setAttribute("y-res", img->yRes());
|
|
|
|
Q_UINT32 count=0;
|
|
KisSaveXmlVisitor visitor(doc, image, count, true);
|
|
|
|
m_currentImage->rootLayer()->accept(visitor);
|
|
|
|
return image;
|
|
}
|
|
|
|
KisImageSP KisDoc::loadImage(const QDomElement& element)
|
|
{
|
|
|
|
KisConfig cfg;
|
|
QString attr;
|
|
QDomNode node;
|
|
QDomNode child;
|
|
KisImageSP img;
|
|
QString name;
|
|
Q_INT32 width;
|
|
Q_INT32 height;
|
|
QString description;
|
|
QString profileProductName;
|
|
double xres;
|
|
double yres;
|
|
QString colorspacename;
|
|
KisColorSpace * cs;
|
|
|
|
if ((attr = element.attribute("mime")) == NATIVE_MIMETYPE) {
|
|
if ((name = element.attribute("name")).isNull())
|
|
return 0;
|
|
if ((attr = element.attribute("width")).isNull())
|
|
return 0;
|
|
width = attr.toInt();
|
|
if ((attr = element.attribute("height")).isNull())
|
|
return 0;
|
|
height = attr.toInt();
|
|
|
|
description = element.attribute("description");
|
|
|
|
if ((attr = element.attribute("x-res")).isNull())
|
|
xres = 100.0;
|
|
xres = attr.toDouble();
|
|
|
|
if ((attr = element.attribute("y-res")).isNull())
|
|
yres = 100.0;
|
|
yres = attr.toDouble();
|
|
|
|
if ((colorspacename = element.attribute("colorspacename")).isNull())
|
|
{
|
|
// An old file: take a reasonable default.
|
|
// Krita didn't support anything else in those
|
|
// days anyway.
|
|
colorspacename = "RGBA";
|
|
}
|
|
|
|
// A hack for an old colorspacename
|
|
if (colorspacename == "Grayscale + Alpha")
|
|
colorspacename = "GRAYA";
|
|
|
|
if ((profileProductName = element.attribute("profile")).isNull()) {
|
|
// no mention of profile so get default profile
|
|
cs = KisMetaRegistry::instance()->csRegistry()->getColorSpace(colorspacename,"");
|
|
}
|
|
else {
|
|
cs = KisMetaRegistry::instance()->csRegistry()->getColorSpace(colorspacename, profileProductName);
|
|
}
|
|
|
|
if (cs == 0) {
|
|
kdWarning(DBG_AREA_FILE) << "Could not open colorspace\n";
|
|
return 0;
|
|
}
|
|
|
|
img = new KisImage(this, width, height, cs, name);
|
|
img->blockSignals(true); // Don't send out signals while we're building the image
|
|
Q_CHECK_PTR(img);
|
|
connect( img, SIGNAL( sigImageModified() ), this, SLOT( slotImageUpdated() ));
|
|
img->setDescription(description);
|
|
img->setResolution(xres, yres);
|
|
|
|
loadLayers(element, img, img->rootLayer().data());
|
|
|
|
}
|
|
|
|
img->notifyImageLoaded();
|
|
|
|
return img;
|
|
}
|
|
|
|
void KisDoc::loadLayers(const QDomElement& element, KisImageSP img, KisGroupLayerSP parent)
|
|
{
|
|
QDomNode node = element.firstChild();
|
|
QDomNode child;
|
|
|
|
if(!node.isNull())
|
|
{
|
|
if (node.isElement()) {
|
|
if (node.nodeName() == "LAYERS") {
|
|
for (child = node.firstChild(); !child.isNull(); child = child.nextSibling()) {
|
|
KisLayerSP layer = loadLayer(child.toElement(), img);
|
|
|
|
if (!layer) {
|
|
kdWarning(DBG_AREA_FILE) << "Could not load layer\n";
|
|
}
|
|
else {
|
|
img->nextLayerName(); // Make sure the nameserver is current with the number of layers.
|
|
img->addLayer(layer, parent, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
KisLayerSP KisDoc::loadLayer(const QDomElement& element, KisImageSP img)
|
|
{
|
|
// Nota bene: If you add new properties to layers, you should
|
|
// ALWAYS define a default value in case the property is not
|
|
// present in the layer definition: this helps a LOT with backward
|
|
// compatibilty.
|
|
QString attr;
|
|
QString name;
|
|
Q_INT32 x;
|
|
Q_INT32 y;
|
|
Q_INT32 opacity;
|
|
bool visible;
|
|
bool locked;
|
|
|
|
if ((name = element.attribute("name")).isNull())
|
|
return 0;
|
|
|
|
if ((attr = element.attribute("x")).isNull())
|
|
return 0;
|
|
x = attr.toInt();
|
|
|
|
if ((attr = element.attribute("y")).isNull())
|
|
return 0;
|
|
|
|
y = attr.toInt();
|
|
|
|
if ((attr = element.attribute("opacity")).isNull())
|
|
return 0;
|
|
|
|
if ((opacity = attr.toInt()) < 0 || opacity > Q_UINT8_MAX)
|
|
opacity = OPACITY_OPAQUE;
|
|
|
|
|
|
QString compositeOpName = element.attribute("compositeop");
|
|
KisCompositeOp compositeOp;
|
|
|
|
if (compositeOpName.isNull()) {
|
|
compositeOp = COMPOSITE_OVER;
|
|
} else {
|
|
compositeOp = KisCompositeOp(compositeOpName);
|
|
}
|
|
|
|
if (!compositeOp.isValid()) {
|
|
return 0;
|
|
}
|
|
|
|
if ((attr = element.attribute("visible")).isNull())
|
|
attr = "1";
|
|
|
|
visible = attr == "0" ? false : true;
|
|
|
|
if ((attr = element.attribute("locked")).isNull())
|
|
attr = "0";
|
|
|
|
locked = attr == "0" ? false : true;
|
|
|
|
// Now find out the layer type and do specific handling
|
|
if ((attr = element.attribute("layertype")).isNull())
|
|
return loadPaintLayer(element, img, name, x, y, opacity, visible, locked, compositeOp) ;
|
|
|
|
if(attr == "paintlayer")
|
|
return loadPaintLayer(element, img, name, x, y, opacity, visible, locked, compositeOp);
|
|
|
|
if(attr == "grouplayer")
|
|
return loadGroupLayer(element, img, name, x, y, opacity, visible, locked, compositeOp).data();
|
|
|
|
if(attr == "adjustmentlayer")
|
|
return loadAdjustmentLayer(element, img, name, x, y, opacity, visible, locked, compositeOp).data();
|
|
|
|
if(attr == "partlayer")
|
|
return loadPartLayer(element, img, name, x, y, opacity, visible, locked, compositeOp).data();
|
|
|
|
kdWarning(DBG_AREA_FILE) << "Specified layertype is not recognised\n";
|
|
return 0;
|
|
}
|
|
|
|
|
|
KisLayerSP KisDoc::loadPaintLayer(const QDomElement& element, KisImageSP img,
|
|
QString name, Q_INT32 x, Q_INT32 y,
|
|
Q_INT32 opacity, bool visible, bool locked, KisCompositeOp compositeOp)
|
|
{
|
|
QString attr;
|
|
KisPaintLayerSP layer;
|
|
KisColorSpace * cs;
|
|
|
|
QString colorspacename;
|
|
QString profileProductName;
|
|
|
|
if ((colorspacename = element.attribute("colorspacename")).isNull())
|
|
cs = img->colorSpace();
|
|
else
|
|
// use default profile - it will be replaced later in completLoading
|
|
cs = KisMetaRegistry::instance()->csRegistry()->getColorSpace(colorspacename,"");
|
|
|
|
layer = new KisPaintLayer(img, name, opacity, cs);
|
|
Q_CHECK_PTR(layer);
|
|
|
|
layer->setCompositeOp(compositeOp);
|
|
layer->setVisible(visible);
|
|
layer->setLocked(locked);
|
|
layer->setX(x);
|
|
layer->setY(y);
|
|
|
|
if ((element.attribute("filename")).isNull())
|
|
m_layerFilenames[layer.data()] = name;
|
|
else
|
|
m_layerFilenames[layer.data()] = QString(element.attribute("filename"));
|
|
|
|
if ((attr = element.attribute("hasmask")).isNull())
|
|
attr = "0";
|
|
|
|
if (attr == "1") {
|
|
// We add a mask, but we'll fill in the actual mask later in completeLoading with the visitor
|
|
layer->createMask();
|
|
}
|
|
|
|
|
|
// Load exif info
|
|
for( QDomNode node = element.firstChild(); !node.isNull(); node = node.nextSibling() )
|
|
{
|
|
QDomElement e = node.toElement();
|
|
if ( !e.isNull() && e.tagName() == "ExifInfo" )
|
|
{
|
|
layer->paintDevice()->exifInfo()->load(e);
|
|
}
|
|
}
|
|
return layer.data();
|
|
}
|
|
|
|
KisGroupLayerSP KisDoc::loadGroupLayer(const QDomElement& element, KisImageSP img,
|
|
QString name, Q_INT32 x, Q_INT32 y, Q_INT32 opacity, bool visible, bool locked,
|
|
KisCompositeOp compositeOp)
|
|
{
|
|
QString attr;
|
|
KisGroupLayerSP layer;
|
|
|
|
layer = new KisGroupLayer(img, name, opacity);
|
|
Q_CHECK_PTR(layer);
|
|
|
|
layer->setCompositeOp(compositeOp);
|
|
layer->setVisible(visible);
|
|
layer->setLocked(locked);
|
|
layer->setX(x);
|
|
layer->setY(y);
|
|
|
|
loadLayers(element, img, layer);
|
|
|
|
return layer;
|
|
}
|
|
|
|
KisAdjustmentLayerSP KisDoc::loadAdjustmentLayer(const QDomElement& element, KisImageSP img,
|
|
QString name, Q_INT32 x, Q_INT32 y, Q_INT32 opacity, bool visible, bool locked,
|
|
KisCompositeOp compositeOp)
|
|
{
|
|
QString attr;
|
|
KisAdjustmentLayerSP layer;
|
|
QString filtername;
|
|
|
|
if ((filtername = element.attribute("filtername")).isNull()) {
|
|
// XXX: Invalid adjustmentlayer! We should warn about it!
|
|
kdWarning(DBG_AREA_FILE) << "No filter in adjustment layer" << endl;
|
|
return 0;
|
|
}
|
|
|
|
KisFilter * f = KisFilterRegistry::instance()->get(filtername);
|
|
if (!f) {
|
|
kdWarning(DBG_AREA_FILE) << "No filter for filtername " << filtername << "\n";
|
|
return 0; // XXX: We don't have this filter. We should warn about it!
|
|
}
|
|
|
|
KisFilterConfiguration * kfc = f->configuration();
|
|
|
|
// We'll load the configuration and the selection later.
|
|
layer = new KisAdjustmentLayer(img, name, kfc, 0);
|
|
Q_CHECK_PTR(layer);
|
|
|
|
layer->setCompositeOp(compositeOp);
|
|
layer->setVisible(visible);
|
|
layer->setLocked(locked);
|
|
layer->setX(x);
|
|
layer->setY(y);
|
|
layer->setOpacity(opacity);
|
|
|
|
if ((element.attribute("filename")).isNull())
|
|
m_layerFilenames[layer.data()] = name;
|
|
else
|
|
m_layerFilenames[layer.data()] = QString(element.attribute("filename"));
|
|
|
|
return layer;
|
|
}
|
|
|
|
KisPartLayerSP KisDoc::loadPartLayer(const QDomElement& element, KisImageSP img,
|
|
QString name, Q_INT32 /*x*/, Q_INT32 /*y*/, Q_INT32 opacity,
|
|
bool visible, bool locked,
|
|
KisCompositeOp compositeOp) {
|
|
KisChildDoc* child = new KisChildDoc(this);
|
|
QString filename(element.attribute("filename"));
|
|
QDomElement partElement = element.namedItem("object").toElement();
|
|
|
|
if (partElement.isNull()) {
|
|
kdWarning() << "loadPartLayer failed with partElement isNull" << endl;
|
|
return 0;
|
|
}
|
|
|
|
child->load(partElement);
|
|
insertChild(child);
|
|
|
|
KisPartLayerSP layer = new KisPartLayerImpl(img, child);
|
|
Q_CHECK_PTR(layer);
|
|
|
|
layer->setCompositeOp(compositeOp);
|
|
layer->setVisible(visible);
|
|
layer->setLocked(locked);
|
|
layer->setOpacity(opacity);
|
|
layer->setName(name);
|
|
|
|
return layer;
|
|
}
|
|
|
|
bool KisDoc::completeSaving(KoStore *store)
|
|
{
|
|
QString uri = url().url();
|
|
QString location;
|
|
bool external = isStoredExtern();
|
|
Q_INT32 totalSteps = 0;
|
|
|
|
if (!m_currentImage) return false;
|
|
|
|
totalSteps = (m_currentImage)->nlayers();
|
|
|
|
|
|
setIOSteps(totalSteps + 1);
|
|
|
|
// Save the layers data
|
|
Q_UINT32 count=0;
|
|
KisSaveVisitor visitor(m_currentImage, store, count);
|
|
|
|
if(external)
|
|
visitor.setExternalUri(uri);
|
|
|
|
m_currentImage->rootLayer()->accept(visitor);
|
|
|
|
// saving annotations
|
|
// XXX this only saves EXIF and ICC info. This would probably need
|
|
// a redesign of the dtd of the krita file to do this more generally correct
|
|
// e.g. have <ANNOTATION> tags or so.
|
|
KisAnnotationSP annotation = (m_currentImage)->annotation("exif");
|
|
if (annotation) {
|
|
location = external ? QString::null : uri;
|
|
location += (m_currentImage)->name() + "/annotations/exif";
|
|
if (store->open(location)) {
|
|
store->write(annotation->annotation());
|
|
store->close();
|
|
}
|
|
}
|
|
if (m_currentImage->getProfile()) {
|
|
annotation = m_currentImage->getProfile()->annotation();
|
|
|
|
if (annotation) {
|
|
location = external ? QString::null : uri;
|
|
location += m_currentImage->name() + "/annotations/icc";
|
|
if (store->open(location)) {
|
|
store->write(annotation->annotation());
|
|
store->close();
|
|
}
|
|
}
|
|
}
|
|
|
|
IODone();
|
|
return true;
|
|
}
|
|
|
|
bool KisDoc::completeLoading(KoStore *store)
|
|
{
|
|
QString uri = url().url();
|
|
QString location;
|
|
bool external = isStoredExtern();
|
|
Q_INT32 totalSteps = 0;
|
|
|
|
totalSteps = (m_currentImage)->nlayers();
|
|
|
|
setIOSteps(totalSteps);
|
|
|
|
// Load the layers data
|
|
KisLoadVisitor visitor(m_currentImage, store, m_layerFilenames);
|
|
|
|
if(external)
|
|
visitor.setExternalUri(uri);
|
|
|
|
m_currentImage->rootLayer()->accept(visitor);
|
|
|
|
// annotations
|
|
// exif
|
|
location = external ? QString::null : uri;
|
|
location += (m_currentImage)->name() + "/annotations/exif";
|
|
if (store->hasFile(location)) {
|
|
QByteArray data;
|
|
store->open(location);
|
|
data = store->read(store->size());
|
|
store->close();
|
|
(m_currentImage)->addAnnotation(new KisAnnotation("exif", "", data));
|
|
}
|
|
// icc profile
|
|
location = external ? QString::null : uri;
|
|
location += (m_currentImage)->name() + "/annotations/icc";
|
|
if (store->hasFile(location)) {
|
|
QByteArray data;
|
|
store->open(location);
|
|
data = store->read(store->size());
|
|
store->close();
|
|
(m_currentImage)->setProfile(new KisProfile(data));
|
|
}
|
|
|
|
IODone();
|
|
|
|
setModified( false );
|
|
setUndo(true);
|
|
return true;
|
|
}
|
|
|
|
QWidget* KisDoc::createCustomDocumentWidget(QWidget *parent)
|
|
{
|
|
|
|
KisConfig cfg;
|
|
|
|
int w = cfg.defImgWidth();
|
|
int h = cfg.defImgHeight();
|
|
|
|
QSize sz = KisClipboard::instance()->clipSize();
|
|
if (sz.isValid() && sz.width() != 0 && sz.height() != 0) {
|
|
w = sz.width();
|
|
h = sz.height();
|
|
}
|
|
return new KisCustomImageWidget(parent, this, w, h, cfg.defImgResolution(), cfg.workingColorSpace(),"unnamed");
|
|
}
|
|
|
|
|
|
KoDocument* KisDoc::hitTest(const QPoint &pos, const QWMatrix& matrix) {
|
|
KoDocument* doc = super::hitTest(pos, matrix);
|
|
if (doc && doc != this) {
|
|
// We hit a child document. We will only acknowledge we hit it, if the hit child
|
|
// is the currently active parts layer.
|
|
KisPartLayerImpl* partLayer
|
|
= dynamic_cast<KisPartLayerImpl*>(currentImage()->activeLayer().data());
|
|
|
|
if (!partLayer)
|
|
return this;
|
|
|
|
if (doc == partLayer->childDoc()->document()) {
|
|
return doc;
|
|
}
|
|
return this;
|
|
}
|
|
return doc;
|
|
}
|
|
|
|
void KisDoc::renameImage(const QString& oldName, const QString& newName)
|
|
{
|
|
(m_currentImage)->setName(newName);
|
|
|
|
if (undo())
|
|
addCommand(new KisCommandImageMv(this, this, newName, oldName));
|
|
}
|
|
|
|
|
|
KisImageSP KisDoc::newImage(const QString& name, Q_INT32 width, Q_INT32 height, KisColorSpace * colorstrategy)
|
|
{
|
|
if (!init())
|
|
return 0;
|
|
|
|
setUndo(false);
|
|
|
|
KisImageSP img = new KisImage(this, width, height, colorstrategy, name);
|
|
Q_CHECK_PTR(img);
|
|
connect( img, SIGNAL( sigImageModified() ), this, SLOT( slotImageUpdated() ));
|
|
|
|
KisPaintLayer *layer = new KisPaintLayer(img, img->nextLayerName(), OPACITY_OPAQUE,colorstrategy);
|
|
Q_CHECK_PTR(layer);
|
|
|
|
KisColorSpace * cs = KisMetaRegistry::instance()->csRegistry()->getRGB8();
|
|
KisFillPainter painter;
|
|
|
|
painter.begin(layer->paintDevice());
|
|
painter.fillRect(0, 0, width, height, KisColor(Qt::white, cs), OPACITY_OPAQUE);
|
|
painter.end();
|
|
|
|
img->addLayer(layer, img->rootLayer(), 0);
|
|
img->activate(layer);
|
|
|
|
m_currentImage = img;
|
|
|
|
setUndo(true);
|
|
|
|
return img;
|
|
}
|
|
|
|
bool KisDoc::newImage(const QString& name, Q_INT32 width, Q_INT32 height, KisColorSpace * cs, const KisColor &bgColor, const QString &imgDescription, const double imgResolution)
|
|
{
|
|
if (!init())
|
|
return false;
|
|
|
|
KisConfig cfg;
|
|
|
|
Q_UINT8 opacity = OPACITY_OPAQUE;//bgColor.getAlpha();
|
|
KisImageSP img;
|
|
KisPaintLayer *layer;
|
|
|
|
if (!cs) return false;
|
|
|
|
setUndo(false);
|
|
|
|
img = new KisImage(this, width, height, cs, name);
|
|
Q_CHECK_PTR(img);
|
|
connect( img, SIGNAL( sigImageModified() ), this, SLOT( slotImageUpdated() ));
|
|
img->setResolution(imgResolution, imgResolution);
|
|
img->setDescription(imgDescription);
|
|
img->setProfile(cs->getProfile());
|
|
|
|
layer = new KisPaintLayer(img, img->nextLayerName(), OPACITY_OPAQUE, cs);
|
|
Q_CHECK_PTR(layer);
|
|
|
|
KisFillPainter painter;
|
|
painter.begin(layer->paintDevice());
|
|
painter.fillRect(0, 0, width, height, bgColor, opacity);
|
|
painter.end();
|
|
|
|
QValueVector<KisPaintDeviceAction *> actions = KisMetaRegistry::instance() ->
|
|
csRegistry()->paintDeviceActionsFor(cs);
|
|
for (uint i = 0; i < actions.count(); i++)
|
|
actions.at(i)->act(layer->paintDevice(), img->width(), img->height());
|
|
|
|
img->setBackgroundColor(bgColor);
|
|
img->addLayer(layer, img->rootLayer(), 0);
|
|
img->activate(layer);
|
|
|
|
m_currentImage = img;
|
|
|
|
cfg.defImgWidth(width);
|
|
cfg.defImgHeight(height);
|
|
cfg.defImgResolution(imgResolution);
|
|
|
|
setUndo(true);
|
|
|
|
return true;
|
|
}
|
|
|
|
KoView* KisDoc::createViewInstance(QWidget* parent, const char *name)
|
|
{
|
|
KisView * v = new KisView(this, this, parent, name);
|
|
Q_CHECK_PTR(v);
|
|
|
|
return v;
|
|
}
|
|
|
|
void KisDoc::paintContent(QPainter& painter, const QRect& rc, bool transparent, double zoomX, double zoomY)
|
|
{
|
|
KisConfig cfg;
|
|
QString monitorProfileName = cfg.monitorProfile();
|
|
KisProfile * profile = KisMetaRegistry::instance()->csRegistry()->getProfileByName(monitorProfileName);
|
|
painter.scale(zoomX, zoomY);
|
|
QRect rect = rc & m_currentImage->bounds();
|
|
KisImage::PaintFlags paintFlags;
|
|
if (transparent) {
|
|
paintFlags = KisImage::PAINT_SELECTION;
|
|
} else {
|
|
paintFlags = (KisImage::PaintFlags)(KisImage::PAINT_BACKGROUND|KisImage::PAINT_SELECTION);
|
|
}
|
|
|
|
paintFlags = (KisImage::PaintFlags)(paintFlags | KisImage::PAINT_EMBEDDED_RECT);
|
|
|
|
m_currentImage->renderToPainter(rect.left(), rect.top(), rect.right(), rect.bottom(), painter, profile, paintFlags);
|
|
}
|
|
|
|
void KisDoc::slotImageUpdated()
|
|
{
|
|
emit docUpdated();
|
|
setModified(true);
|
|
}
|
|
|
|
void KisDoc::slotImageUpdated(const QRect& rect)
|
|
{
|
|
emit docUpdated(rect);
|
|
}
|
|
|
|
void KisDoc::beginMacro(const QString& macroName)
|
|
{
|
|
if (m_undo) {
|
|
if (m_macroNestDepth == 0) {
|
|
Q_ASSERT(m_currentMacro == 0);
|
|
m_currentMacro = new KMacroCommand(macroName);
|
|
Q_CHECK_PTR(m_currentMacro);
|
|
}
|
|
|
|
m_macroNestDepth++;
|
|
}
|
|
}
|
|
|
|
void KisDoc::endMacro()
|
|
{
|
|
if (m_undo) {
|
|
Q_ASSERT(m_macroNestDepth > 0);
|
|
if (m_macroNestDepth > 0) {
|
|
m_macroNestDepth--;
|
|
|
|
if (m_macroNestDepth == 0) {
|
|
Q_ASSERT(m_currentMacro != 0);
|
|
|
|
m_cmdHistory->addCommand(m_currentMacro, false);
|
|
m_currentMacro = 0;
|
|
emit sigCommandExecuted();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void KisDoc::setCommandHistoryListener(const KisCommandHistoryListener * l)
|
|
{
|
|
// Never have more than one instance of a listener around. Qt should prove a Set class for this...
|
|
m_undoListeners.removeRef(l);
|
|
m_undoListeners.append(l);
|
|
}
|
|
|
|
void KisDoc::removeCommandHistoryListener(const KisCommandHistoryListener * l)
|
|
{
|
|
m_undoListeners.removeRef(l);
|
|
}
|
|
|
|
KCommand * KisDoc::presentCommand()
|
|
{
|
|
return m_cmdHistory->presentCommand();
|
|
}
|
|
|
|
void KisDoc::addCommand(KCommand *cmd)
|
|
{
|
|
Q_ASSERT(cmd);
|
|
|
|
KisCommandHistoryListener* l = 0;
|
|
|
|
for (l = m_undoListeners.first(); l; l = m_undoListeners.next()) {
|
|
l->notifyCommandAdded(cmd);
|
|
}
|
|
|
|
setModified(true);
|
|
|
|
if (m_undo) {
|
|
if (m_currentMacro)
|
|
m_currentMacro->addCommand(cmd);
|
|
else {
|
|
m_cmdHistory->addCommand(cmd, false);
|
|
emit sigCommandExecuted();
|
|
}
|
|
} else {
|
|
kdDebug() << "Deleting command\n";
|
|
delete cmd;
|
|
}
|
|
}
|
|
|
|
void KisDoc::setUndo(bool undo)
|
|
{
|
|
m_undo = undo;
|
|
if (m_undo && m_cmdHistory->undoLimit() == 50 /*default*/) {
|
|
KisConfig cfg;
|
|
setUndoLimit( cfg.defUndoLimit() );
|
|
}
|
|
}
|
|
|
|
Q_INT32 KisDoc::undoLimit() const
|
|
{
|
|
return m_cmdHistory->undoLimit();
|
|
}
|
|
|
|
void KisDoc::setUndoLimit(Q_INT32 limit)
|
|
{
|
|
m_cmdHistory->setUndoLimit(limit);
|
|
}
|
|
|
|
Q_INT32 KisDoc::redoLimit() const
|
|
{
|
|
return m_cmdHistory->redoLimit();
|
|
}
|
|
|
|
void KisDoc::setRedoLimit(Q_INT32 limit)
|
|
{
|
|
m_cmdHistory->setRedoLimit(limit);
|
|
}
|
|
|
|
void KisDoc::slotDocumentRestored()
|
|
{
|
|
setModified(false);
|
|
}
|
|
|
|
void KisDoc::slotCommandExecuted(KCommand *command)
|
|
{
|
|
setModified(true);
|
|
emit sigCommandExecuted();
|
|
|
|
KisCommandHistoryListener* l = 0;
|
|
|
|
for (l = m_undoListeners.first(); l; l = m_undoListeners.next()) {
|
|
l->notifyCommandExecuted(command);
|
|
}
|
|
|
|
}
|
|
|
|
void KisDoc::slotUpdate(KisImageSP, Q_UINT32 x, Q_UINT32 y, Q_UINT32 w, Q_UINT32 h)
|
|
{
|
|
QRect rc(x, y, w, h);
|
|
|
|
emit docUpdated(rc);
|
|
}
|
|
|
|
bool KisDoc::undo() const
|
|
{
|
|
return m_undo;
|
|
}
|
|
|
|
void KisDoc::setIOSteps(Q_INT32 nsteps)
|
|
{
|
|
m_ioProgressTotalSteps = nsteps * 100;
|
|
m_ioProgressBase = 0;
|
|
emitProgress(0);
|
|
}
|
|
|
|
void KisDoc::IOCompletedStep()
|
|
{
|
|
m_ioProgressBase += 100;
|
|
}
|
|
|
|
void KisDoc::IODone()
|
|
{
|
|
emitProgress(-1);
|
|
}
|
|
|
|
void KisDoc::slotIOProgress(Q_INT8 percentage)
|
|
{
|
|
KApplication *app = KApplication::kApplication();
|
|
|
|
Q_ASSERT(app);
|
|
|
|
if (app->hasPendingEvents())
|
|
app->processEvents();
|
|
|
|
int totalPercentage = ((m_ioProgressBase + percentage) * 100) / m_ioProgressTotalSteps;
|
|
|
|
emitProgress(totalPercentage);
|
|
}
|
|
|
|
KisChildDoc * KisDoc::createChildDoc( const QRect & rect, KoDocument* childDoc )
|
|
{
|
|
KisChildDoc * ch = new KisChildDoc( this, rect, childDoc );
|
|
insertChild( ch );
|
|
ch->document()->setStoreInternal(true);
|
|
return ch;
|
|
}
|
|
|
|
void KisDoc::prepareForImport()
|
|
{
|
|
if (m_nserver == 0)
|
|
init();
|
|
setUndo(false);
|
|
}
|
|
|
|
KisImageSP KisDoc::currentImage()
|
|
{
|
|
return m_currentImage;
|
|
}
|
|
|
|
void KisDoc::setCurrentImage(KisImageSP image)
|
|
{
|
|
m_currentImage = image;
|
|
setUndo(true);
|
|
image->notifyImageLoaded();
|
|
emit loadingFinished();
|
|
}
|
|
|
|
void KisDoc::initEmpty()
|
|
{
|
|
KisConfig cfg;
|
|
KisColorSpace * rgb = KisMetaRegistry::instance()->csRegistry()->getRGB8();
|
|
newImage("", cfg.defImgWidth(), cfg.defImgHeight(), rgb);
|
|
}
|
|
|
|
#include "kis_doc.moc"
|
|
|