";
browser->setHtml(warning);
browser->setMinimumHeight(200);
browser->setMinimumWidth(400);
layout->addWidget(browser);
setMainWidget(page);
setButtons(KoDialog::Ok);
resize(minimumSize());
}
};
bool KisDocument::openFile()
{
//dbgUI <<"for" << localFilePath();
if (!QFile::exists(localFilePath())) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("File %1 does not exist.", localFilePath()));
return false;
}
QString filename = localFilePath();
QString typeName = mimeType();
if (typeName.isEmpty()) {
typeName = KisMimeDatabase::mimeTypeForFile(filename);
}
//qDebug() << "mimetypes 4:" << typeName;
// Allow to open backup files, don't keep the mimetype application/x-trash.
if (typeName == "application/x-trash") {
QString path = filename;
while (path.length() > 0) {
path.chop(1);
typeName = KisMimeDatabase::mimeTypeForFile(path);
//qDebug() << "\t" << path << typeName;
if (!typeName.isEmpty()) {
break;
}
}
//qDebug() << "chopped" << filename << "to" << path << "Was trash, is" << typeName;
}
dbgUI << localFilePath() << "type:" << typeName;
KisMainWindow *window = KisPart::instance()->currentMainwindow();
if (window && window->viewManager()) {
KoUpdaterPtr updater = window->viewManager()->createUnthreadedUpdater(i18n("Opening document"));
d->importExportManager->setUpdater(updater);
}
KisImportExportFilter::ConversionStatus status;
status = d->importExportManager->importDocument(localFilePath(), typeName);
if (status != KisImportExportFilter::OK) {
QString msg = KisImportExportFilter::conversionStatusString(status);
if (!msg.isEmpty()) {
DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
i18n("Could not open %2.\nReason: %1.", msg, prettyPathOrUrl()),
errorMessage().split("\n") + warningMessage().split("\n"));
dlg.exec();
}
return false;
}
else if (!warningMessage().isEmpty()) {
DlgLoadMessages dlg(i18nc("@title:window", "Krita"),
i18n("There were problems opening %1.", prettyPathOrUrl()),
warningMessage().split("\n"));
dlg.exec();
setUrl(QUrl());
}
setMimeTypeAfterLoading(typeName);
emit sigLoadingFinished();
undoStack()->clear();
return true;
}
// shared between openFile and koMainWindow's "create new empty document" code
void KisDocument::setMimeTypeAfterLoading(const QString& mimeType)
{
d->mimeType = mimeType.toLatin1();
d->outputMimeType = d->mimeType;
}
bool KisDocument::loadNativeFormat(const QString & file_)
{
return openUrl(QUrl::fromLocalFile(file_));
}
void KisDocument::setModified(bool mod)
{
if (mod) {
updateEditingTime(false);
}
if (d->isAutosaving) // ignore setModified calls due to autosaving
return;
if ( !d->readwrite && d->modified ) {
errKrita << "Can't set a read-only document to 'modified' !" << endl;
return;
}
//dbgUI<<" url:" << url.path();
//dbgUI<<" mod="<docInfo->aboutInfo("editing-time").toInt() + d->firstMod.secsTo(d->lastMod)));
d->firstMod = now;
} else if (firstModDelta > 60 || forceStoreElapsed) {
d->docInfo->setAboutInfo("editing-time", QString::number(d->docInfo->aboutInfo("editing-time").toInt() + firstModDelta));
d->firstMod = now;
}
d->lastMod = now;
}
QString KisDocument::prettyPathOrUrl() const
{
QString _url(url().toDisplayString());
#ifdef Q_OS_WIN
if (url().isLocalFile()) {
_url = QDir::toNativeSeparators(_url);
}
#endif
return _url;
}
// Get caption from document info (title(), in about page)
QString KisDocument::caption() const
{
QString c;
const QString _url(url().fileName());
// if URL is empty...it is probably an unsaved file
if (_url.isEmpty()) {
c = " [" + i18n("Not Saved") + "] ";
} else {
c = _url; // Fall back to document URL
}
return c;
}
void KisDocument::setTitleModified()
{
emit titleModified(caption(), isModified());
}
QDomDocument KisDocument::createDomDocument(const QString& tagName, const QString& version) const
{
return createDomDocument("krita", tagName, version);
}
//static
QDomDocument KisDocument::createDomDocument(const QString& appName, const QString& tagName, const QString& version)
{
QDomImplementation impl;
QString url = QString("http://www.calligra.org/DTD/%1-%2.dtd").arg(appName).arg(version);
QDomDocumentType dtype = impl.createDocumentType(tagName,
QString("-//KDE//DTD %1 %2//EN").arg(appName).arg(version),
url);
// The namespace URN doesn't need to include the version number.
QString namespaceURN = QString("http://www.calligra.org/DTD/%1").arg(appName);
QDomDocument doc = impl.createDocument(namespaceURN, tagName, dtype);
doc.insertBefore(doc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\""), doc.documentElement());
return doc;
}
bool KisDocument::isNativeFormat(const QByteArray& mimetype) const
{
if (mimetype == nativeFormatMimeType())
return true;
return extraNativeMimeTypes().contains(mimetype);
}
void KisDocument::setErrorMessage(const QString& errMsg)
{
d->lastErrorMessage = errMsg;
}
QString KisDocument::errorMessage() const
{
return d->lastErrorMessage;
}
void KisDocument::setWarningMessage(const QString& warningMsg)
{
d->lastWarningMessage = warningMsg;
}
QString KisDocument::warningMessage() const
{
return d->lastWarningMessage;
}
void KisDocument::removeAutoSaveFiles()
{
//qDebug() << "removeAutoSaveFiles";
// Eliminate any auto-save file
QString asf = generateAutoSaveFileName(localFilePath()); // the one in the current dir
//qDebug() << "\tfilename:" << asf << "exists:" << QFile::exists(asf);
if (QFile::exists(asf)) {
//qDebug() << "\tremoving autosavefile" << asf;
QFile::remove(asf);
}
asf = generateAutoSaveFileName(QString()); // and the one in $HOME
//qDebug() << "Autsavefile in $home" << asf;
if (QFile::exists(asf)) {
//qDebug() << "\tremoving autsavefile 2" << asf;
QFile::remove(asf);
}
}
KoUnit KisDocument::unit() const
{
return d->unit;
}
void KisDocument::setUnit(const KoUnit &unit)
{
if (d->unit != unit) {
d->unit = unit;
emit unitChanged(unit);
}
}
KUndo2Stack *KisDocument::undoStack()
{
return d->undoStack;
}
KisImportExportManager *KisDocument::importExportManager() const
{
return d->importExportManager;
}
void KisDocument::addCommand(KUndo2Command *command)
{
if (command)
d->undoStack->push(command);
}
void KisDocument::beginMacro(const KUndo2MagicString & text)
{
d->undoStack->beginMacro(text);
}
void KisDocument::endMacro()
{
d->undoStack->endMacro();
}
void KisDocument::slotUndoStackCleanChanged(bool value)
{
setModified(!value);
}
void KisDocument::slotConfigChanged()
{
KisConfig cfg(true);
d->undoStack->setUndoLimit(cfg.undoStackLimit());
d->autoSaveDelay = cfg.autoSaveInterval();
setNormalAutoSaveInterval();
}
void KisDocument::clearUndoHistory()
{
d->undoStack->clear();
}
KisGridConfig KisDocument::gridConfig() const
{
return d->gridConfig;
}
void KisDocument::setGridConfig(const KisGridConfig &config)
{
d->gridConfig = config;
}
-QList &KisDocument::paletteList()
+QList &KisDocument::paletteList()
{
return d->paletteList;
}
-void KisDocument::setPaletteList(const QList &paletteList)
+void KisDocument::setPaletteList(const QList &paletteList)
{
d->paletteList = paletteList;
}
const KisGuidesConfig& KisDocument::guidesConfig() const
{
return d->guidesConfig;
}
void KisDocument::setGuidesConfig(const KisGuidesConfig &data)
{
if (d->guidesConfig == data) return;
d->guidesConfig = data;
emit sigGuidesConfigChanged(d->guidesConfig);
}
void KisDocument::resetURL() {
setUrl(QUrl());
setLocalFilePath(QString());
}
KoDocumentInfoDlg *KisDocument::createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const
{
return new KoDocumentInfoDlg(parent, docInfo);
}
bool KisDocument::isReadWrite() const
{
return d->readwrite;
}
QUrl KisDocument::url() const
{
return d->m_url;
}
bool KisDocument::closeUrl(bool promptToSave)
{
if (promptToSave) {
if ( isReadWrite() && isModified()) {
Q_FOREACH (KisView *view, KisPart::instance()->views()) {
if (view && view->document() == this) {
if (!view->queryClose()) {
return false;
}
}
}
}
}
// Not modified => ok and delete temp file.
d->mimeType = QByteArray();
// It always succeeds for a read-only part,
// but the return value exists for reimplementations
// (e.g. pressing cancel for a modified read-write part)
return true;
}
void KisDocument::setUrl(const QUrl &url)
{
d->m_url = url;
}
QString KisDocument::localFilePath() const
{
return d->m_file;
}
void KisDocument::setLocalFilePath( const QString &localFilePath )
{
d->m_file = localFilePath;
}
bool KisDocument::openUrlInternal(const QUrl &url)
{
if ( !url.isValid() )
return false;
if (d->m_bAutoDetectedMime) {
d->mimeType = QByteArray();
d->m_bAutoDetectedMime = false;
}
QByteArray mimetype = d->mimeType;
if ( !closeUrl() )
return false;
d->mimeType = mimetype;
setUrl(url);
d->m_file.clear();
if (d->m_url.isLocalFile()) {
d->m_file = d->m_url.toLocalFile();
bool ret;
// set the mimetype only if it was not already set (for example, by the host application)
if (d->mimeType.isEmpty()) {
// get the mimetype of the file
// using findByUrl() to avoid another string -> url conversion
QString mime = KisMimeDatabase::mimeTypeForFile(d->m_url.toLocalFile());
d->mimeType = mime.toLocal8Bit();
d->m_bAutoDetectedMime = true;
}
setUrl(d->m_url);
ret = openFile();
if (ret) {
emit completed();
} else {
emit canceled(QString());
}
return ret;
}
return false;
}
bool KisDocument::newImage(const QString& name,
qint32 width, qint32 height,
const KoColorSpace* cs,
const KoColor &bgColor, bool backgroundAsLayer,
int numberOfLayers,
const QString &description, const double imageResolution)
{
Q_ASSERT(cs);
KisImageSP image;
KisPaintLayerSP layer;
if (!cs) return false;
QApplication::setOverrideCursor(Qt::BusyCursor);
image = new KisImage(createUndoStore(), width, height, cs, name);
Q_CHECK_PTR(image);
connect(image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
image->setResolution(imageResolution, imageResolution);
image->assignImageProfile(cs->profile());
documentInfo()->setAboutInfo("title", name);
documentInfo()->setAboutInfo("abstract", description);
layer = new KisPaintLayer(image.data(), image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
Q_CHECK_PTR(layer);
if (backgroundAsLayer) {
image->setDefaultProjectionColor(KoColor(cs));
if (bgColor.opacityU8() == OPACITY_OPAQUE_U8) {
layer->paintDevice()->setDefaultPixel(bgColor);
} else {
// Hack: with a semi-transparent background color, the projection isn't composited right if we just set the default pixel
KisFillPainter painter;
painter.begin(layer->paintDevice());
painter.fillRect(0, 0, width, height, bgColor, bgColor.opacityU8());
}
} else {
image->setDefaultProjectionColor(bgColor);
}
layer->setDirty(QRect(0, 0, width, height));
image->addNode(layer.data(), image->rootLayer().data());
setCurrentImage(image);
for(int i = 1; i < numberOfLayers; ++i) {
KisPaintLayerSP layer = new KisPaintLayer(image, image->nextLayerName(), OPACITY_OPAQUE_U8, cs);
image->addNode(layer, image->root(), i);
layer->setDirty(QRect(0, 0, width, height));
}
KisConfig cfg(false);
cfg.defImageWidth(width);
cfg.defImageHeight(height);
cfg.defImageResolution(imageResolution);
cfg.defColorModel(image->colorSpace()->colorModelId().id());
cfg.setDefaultColorDepth(image->colorSpace()->colorDepthId().id());
cfg.defColorProfile(image->colorSpace()->profile()->name());
QApplication::restoreOverrideCursor();
return true;
}
bool KisDocument::isSaving() const
{
const bool result = d->savingMutex.tryLock();
if (result) {
d->savingMutex.unlock();
}
return !result;
}
void KisDocument::waitForSavingToComplete()
{
if (isSaving()) {
KisAsyncActionFeedback f(i18nc("progress dialog message when the user closes the document that is being saved", "Waiting for saving to complete..."), 0);
f.waitForMutex(&d->savingMutex);
}
}
KoShapeBasedDocumentBase *KisDocument::shapeController() const
{
return d->shapeController;
}
KoShapeLayer* KisDocument::shapeForNode(KisNodeSP layer) const
{
return d->shapeController->shapeForNode(layer);
}
QList KisDocument::assistants() const
{
return d->assistants;
}
void KisDocument::setAssistants(const QList &value)
{
d->assistants = value;
}
KisSharedPtr KisDocument::referenceImagesLayer() const
{
return d->referenceImagesLayer.data();
}
void KisDocument::setReferenceImagesLayer(KisSharedPtr layer, bool updateImage)
{
if (d->referenceImagesLayer) {
d->referenceImagesLayer->disconnect(this);
}
if (updateImage) {
if (layer) {
d->image->addNode(layer);
} else {
d->image->removeNode(d->referenceImagesLayer);
}
}
d->referenceImagesLayer = layer;
if (d->referenceImagesLayer) {
connect(d->referenceImagesLayer, SIGNAL(sigUpdateCanvas(const QRectF&)),
this, SIGNAL(sigReferenceImagesChanged()));
}
}
void KisDocument::setPreActivatedNode(KisNodeSP activatedNode)
{
d->preActivatedNode = activatedNode;
}
KisNodeSP KisDocument::preActivatedNode() const
{
return d->preActivatedNode;
}
KisImageWSP KisDocument::image() const
{
return d->image;
}
KisImageSP KisDocument::savingImage() const
{
return d->savingImage;
}
void KisDocument::setCurrentImage(KisImageSP image, bool forceInitialUpdate)
{
if (d->image) {
// Disconnect existing sig/slot connections
d->image->disconnect(this);
d->shapeController->setImage(0);
d->image = 0;
}
if (!image) return;
d->setImageAndInitIdleWatcher(image);
d->shapeController->setImage(image);
setModified(false);
connect(d->image, SIGNAL(sigImageModified()), this, SLOT(setImageModified()), Qt::UniqueConnection);
if (forceInitialUpdate) {
d->image->initialRefreshGraph();
}
}
void KisDocument::hackPreliminarySetImage(KisImageSP image)
{
KIS_SAFE_ASSERT_RECOVER_RETURN(!d->image);
d->setImageAndInitIdleWatcher(image);
d->shapeController->setImage(image);
}
void KisDocument::setImageModified()
{
setModified(true);
}
KisUndoStore* KisDocument::createUndoStore()
{
return new KisDocumentUndoStore(this);
}
bool KisDocument::isAutosaving() const
{
return d->isAutosaving;
}
QString KisDocument::exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage)
{
return errorMessage.isEmpty() ? KisImportExportFilter::conversionStatusString(status) : errorMessage;
}
void KisDocument::setAssistantsGlobalColor(QColor color)
{
d->globalAssistantsColor = color;
}
QColor KisDocument::assistantsGlobalColor()
{
return d->globalAssistantsColor;
}
QRectF KisDocument::documentBounds() const
{
QRectF bounds = d->image->bounds();
if (d->referenceImagesLayer) {
bounds |= d->referenceImagesLayer->boundingImageRect();
}
return bounds;
}
diff --git a/libs/ui/KisDocument.h b/libs/ui/KisDocument.h
index b80d2feee4..e419913587 100644
--- a/libs/ui/KisDocument.h
+++ b/libs/ui/KisDocument.h
@@ -1,655 +1,655 @@
/* This file is part of the Krita project
*
* Copyright (C) 2014 Boudewijn Rempt
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KISDOCUMENT_H
#define KISDOCUMENT_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "kritaui_export.h"
#include
class QString;
class KUndo2Command;
class KoUnit;
class KoColor;
class KoColorSpace;
class KoShapeBasedDocumentBase;
class KoShapeLayer;
class KoStore;
class KoOdfReadStore;
class KoDocumentInfo;
class KoDocumentInfoDlg;
class KisImportExportManager;
class KisUndoStore;
class KisPart;
class KisGridConfig;
class KisGuidesConfig;
class QDomDocument;
class KisReferenceImagesLayer;
#define KIS_MIME_TYPE "application/x-krita"
/**
* The %Calligra document class
*
* This class provides some functionality each %Calligra document should have.
*
* @short The %Calligra document class
*/
class KRITAUI_EXPORT KisDocument : public QObject, public KoDocumentBase
{
Q_OBJECT
protected:
explicit KisDocument();
/**
* @brief KisDocument makes a deep copy of the document \p rhs.
* The caller *must* ensure that the image is properly
* locked and is in consistent state before asking for
* cloning.
* @param rhs the source document to copy from
*/
explicit KisDocument(const KisDocument &rhs);
public:
enum OpenFlag {
None = 0,
DontAddToRecent = 0x1,
RecoveryFile = 0x2
};
Q_DECLARE_FLAGS(OpenFlags, OpenFlag)
/**
* Destructor.
*
* The destructor does not delete any attached KisView objects and it does not
* delete the attached widget as returned by widget().
*/
~KisDocument() override;
/**
* @brief reload Reloads the document from the original url
* @return the result of loading the document
*/
bool reload();
/**
* @brief creates a clone of the document and returns it. Please make sure that you
* hold all the necessary locks on the image before asking for a clone!
*/
KisDocument* clone();
/**
* @brief openUrl Open an URL
* @param url The URL to open
* @param flags Control specific behavior
* @return success status
*/
bool openUrl(const QUrl &url, OpenFlags flags = None);
/**
* Opens the document given by @p url, without storing the URL
* in the KisDocument.
* Call this instead of openUrl() to implement KisMainWindow's
* File --> Import feature.
*
* @note This will call openUrl(). To differentiate this from an ordinary
* Open operation (in any reimplementation of openUrl() or openFile())
* call isImporting().
*/
bool importDocument(const QUrl &url);
/**
* Saves the document as @p url without changing the state of the
* KisDocument (URL, modified flag etc.). Call this instead of
* KisParts::ReadWritePart::saveAs() to implement KisMainWindow's
* File --> Export feature.
*/
bool exportDocument(const QUrl &url, const QByteArray &mimeType, bool showWarnings = false, KisPropertiesConfigurationSP exportConfiguration = 0);
bool exportDocumentSync(const QUrl &url, const QByteArray &mimeType, KisPropertiesConfigurationSP exportConfiguration = 0);
private:
bool exportDocumentImpl(const KritaUtils::ExportFileJob &job, KisPropertiesConfigurationSP exportConfiguration);
public:
/**
* @brief Sets whether the document can be edited or is read only.
*
* This recursively applied to all child documents and
* KisView::updateReadWrite is called for every attached
* view.
*/
void setReadWrite(bool readwrite = true);
/**
* To be preferred when a document exists. It is fast when calling
* it multiple times since it caches the result that readNativeFormatMimeType()
* delivers.
* This comes from the X-KDE-NativeMimeType key in the .desktop file.
*/
static QByteArray nativeFormatMimeType() { return KIS_MIME_TYPE; }
/// Checks whether a given mimetype can be handled natively.
bool isNativeFormat(const QByteArray& mimetype) const;
/// Returns a list of the mimetypes considered "native", i.e. which can
/// be saved by KisDocument without a filter, in *addition* to the main one
static QStringList extraNativeMimeTypes() { return QStringList() << KIS_MIME_TYPE; }
/**
* Returns the actual mimetype of the document
*/
QByteArray mimeType() const override;
/**
* @brief Sets the mime type for the document.
*
* When choosing "save as" this is also the mime type
* selected by default.
*/
void setMimeType(const QByteArray & mimeType) override;
/**
* @return true if file operations should inhibit the option dialog
*/
bool fileBatchMode() const;
/**
* @param batchMode if true, do not show the option dialog for file operations.
*/
void setFileBatchMode(const bool batchMode);
/**
* Sets the error message to be shown to the user (use i18n()!)
* when loading or saving fails.
* If you asked the user about something and they chose "Cancel",
*/
void setErrorMessage(const QString& errMsg);
/**
* Return the last error message. Usually KisDocument takes care of
* showing it; this method is mostly provided for non-interactive use.
*/
QString errorMessage() const;
/**
* Sets the warning message to be shown to the user (use i18n()!)
* when loading or saving fails.
*/
void setWarningMessage(const QString& warningMsg);
/**
* Return the last warning message set by loading or saving. Warnings
* mean that the document could not be completely loaded, but the errors
* were not absolutely fatal.
*/
QString warningMessage() const;
/**
* @brief Generates a preview picture of the document
* @note The preview is used in the File Dialog and also to create the Thumbnail
*/
QPixmap generatePreview(const QSize& size);
/**
* Tells the document that its title has been modified, either because
* the modified status changes (this is done by setModified() ) or
* because the URL or the document-info's title changed.
*/
void setTitleModified();
/**
* @brief Sets the document to empty.
*
* Used after loading a template
* (which is not empty, but not the user's input).
*
* @see isEmpty()
*/
void setEmpty(bool empty = true);
/**
* Return a correctly created QDomDocument for this KisDocument,
* including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element.
* @param tagName the name of the tag for the root element
* @param version the DTD version (usually the application's version).
*/
QDomDocument createDomDocument(const QString& tagName, const QString& version) const;
/**
* Return a correctly created QDomDocument for an old (1.3-style) %Calligra document,
* including processing instruction, complete DOCTYPE tag (with systemId and publicId), and root element.
* This static method can be used e.g. by filters.
* @param appName the app's instance name, e.g. words, kspread, kpresenter etc.
* @param tagName the name of the tag for the root element, e.g. DOC for words/kpresenter.
* @param version the DTD version (usually the application's version).
*/
static QDomDocument createDomDocument(const QString& appName, const QString& tagName, const QString& version);
/**
* Loads a document in the native format from a given URL.
* Reimplement if your native format isn't XML.
*
* @param file the file to load - usually KReadOnlyPart::m_file or the result of a filter
*/
bool loadNativeFormat(const QString & file);
/**
* Set standard autosave interval that is set by a config file
*/
void setNormalAutoSaveInterval();
/**
* Set emergency interval that autosave uses when the image is busy,
* by default it is 10 sec
*/
void setEmergencyAutoSaveInterval();
/**
* Disable autosave
*/
void setInfiniteAutoSaveInterval();
/**
* @return the information concerning this document.
* @see KoDocumentInfo
*/
KoDocumentInfo *documentInfo() const;
/**
* Performs a cleanup of unneeded backup files
*/
void removeAutoSaveFiles();
/**
* Returns true if this document or any of its internal child documents are modified.
*/
bool isModified() const override;
/**
* @return caption of the document
*
* Caption is of the form "[title] - [url]",
* built out of the document info (title) and pretty-printed
* document URL.
* If the title is not present, only the URL it returned.
*/
QString caption() const;
/**
* Sets the document URL to empty URL
* KParts doesn't allow this, but %Calligra apps have e.g. templates
* After using loadNativeFormat on a template, one wants
* to set the url to QUrl()
*/
void resetURL();
/**
* @internal (public for KisMainWindow)
*/
void setMimeTypeAfterLoading(const QString& mimeType);
/**
* Returns the unit used to display all measures/distances.
*/
KoUnit unit() const;
/**
* Sets the unit used to display all measures/distances.
*/
void setUnit(const KoUnit &unit);
KisGridConfig gridConfig() const;
void setGridConfig(const KisGridConfig &config);
/// returns the guides data for this document.
const KisGuidesConfig& guidesConfig() const;
void setGuidesConfig(const KisGuidesConfig &data);
- QList &paletteList();
- void setPaletteList(const QList &paletteList);
+ QList &paletteList();
+ void setPaletteList(const QList &paletteList);
void clearUndoHistory();
/**
* Sets the modified flag on the document. This means that it has
* to be saved or not before deleting it.
*/
void setModified(bool _mod);
void setRecovered(bool value);
bool isRecovered() const;
void updateEditingTime(bool forceStoreElapsed);
/**
* Returns the global undo stack
*/
KUndo2Stack *undoStack();
/**
* @brief importExportManager gives access to the internal import/export manager
* @return the document's import/export manager
*/
KisImportExportManager *importExportManager() const;
/**
* @brief serializeToNativeByteArray daves the document into a .kra file wtitten
* to a memory-based byte-array
* @return a byte array containing the .kra file
*/
QByteArray serializeToNativeByteArray();
/**
* @brief isInSaving shown if the document has any (background) saving process or not
* @return true if there is some saving in action
*/
bool isInSaving() const;
public Q_SLOTS:
/**
* Adds a command to the undo stack and executes it by calling the redo() function.
* @param command command to add to the undo stack
*/
void addCommand(KUndo2Command *command);
/**
* Begins recording of a macro command. At the end endMacro needs to be called.
* @param text command description
*/
void beginMacro(const KUndo2MagicString &text);
/**
* Ends the recording of a macro command.
*/
void endMacro();
Q_SIGNALS:
/**
* This signal is emitted when the unit is changed by setUnit().
* It is common to connect views to it, in order to change the displayed units
* (e.g. in the rulers)
*/
void unitChanged(const KoUnit &unit);
/**
* Emitted e.g. at the beginning of a save operation
* This is emitted by KisDocument and used by KisView to display a statusbar message
*/
void statusBarMessage(const QString& text, int timeout = 0);
/**
* Emitted e.g. at the end of a save operation
* This is emitted by KisDocument and used by KisView to clear the statusbar message
*/
void clearStatusBarMessage();
/**
* Emitted when the document is modified
*/
void modified(bool);
void titleModified(const QString &caption, bool isModified);
void sigLoadingFinished();
void sigSavingFinished();
void sigGuidesConfigChanged(const KisGuidesConfig &config);
void sigBackgroundSavingFinished(KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
void sigCompleteBackgroundSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
void sigReferenceImagesChanged();
private Q_SLOTS:
void finishExportInBackground();
void slotChildCompletedSavingInBackground(KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
void slotCompleteAutoSaving(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
void slotCompleteSavingDocument(const KritaUtils::ExportFileJob &job, KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
void slotInitiateAsyncAutosaving(KisDocument *clonedDocument);
private:
friend class KisPart;
friend class SafeSavingLocker;
bool initiateSavingInBackground(const QString actionName,
const QObject *receiverObject, const char *receiverMethod,
const KritaUtils::ExportFileJob &job,
KisPropertiesConfigurationSP exportConfiguration,
std::unique_ptr &&optionalClonedDocument);
bool initiateSavingInBackground(const QString actionName,
const QObject *receiverObject, const char *receiverMethod,
const KritaUtils::ExportFileJob &job,
KisPropertiesConfigurationSP exportConfiguration);
bool startExportInBackground(const QString &actionName, const QString &location,
const QString &realLocation,
const QByteArray &mimeType,
bool showWarnings,
KisPropertiesConfigurationSP exportConfiguration);
/**
* Activate/deactivate/configure the autosave feature.
* @param delay in seconds, 0 to disable
*/
void setAutoSaveDelay(int delay);
/**
* Generate a name for the document.
*/
QString newObjectName();
QString generateAutoSaveFileName(const QString & path) const;
/**
* Loads a document
*
* Applies a filter if necessary, and calls loadNativeFormat in any case
* You should not have to reimplement, except for very special cases.
*
* NOTE: this method also creates a new KisView instance!
*
* This method is called from the KReadOnlyPart::openUrl method.
*/
bool openFile();
public:
bool isAutosaving() const override;
public:
QString localFilePath() const override;
void setLocalFilePath( const QString &localFilePath );
KoDocumentInfoDlg* createDocumentInfoDialog(QWidget *parent, KoDocumentInfo *docInfo) const;
bool isReadWrite() const;
QUrl url() const override;
void setUrl(const QUrl &url) override;
bool closeUrl(bool promptToSave = true);
bool saveAs(const QUrl &url, const QByteArray &mimeType, bool showWarnings, KisPropertiesConfigurationSP exportConfigration = 0);
/**
* Create a new image that has this document as a parent and
* replace the current image with this image.
*/
bool newImage(const QString& name, qint32 width, qint32 height, const KoColorSpace * cs, const KoColor &bgColor, bool backgroundAsLayer,
int numberOfLayers, const QString &imageDescription, const double imageResolution);
bool isSaving() const;
void waitForSavingToComplete();
KisImageWSP image() const;
/**
* @brief savingImage provides a detached, shallow copy of the original image that must be used when saving.
* Any strokes in progress will not be applied to this image, so the result might be missing some data. On
* the other hand, it won't block.
*
* @return a shallow copy of the original image, or 0 is saving is not in progress
*/
KisImageSP savingImage() const;
/**
* Set the current image to the specified image and turn undo on.
*/
void setCurrentImage(KisImageSP image, bool forceInitialUpdate = true);
/**
* Set the image of the document preliminary, before the document
* has completed loading. Some of the document items (shapes) may want
* to access image properties (bounds and resolution), so we should provide
* it to them even before the entire image is loaded.
*
* Right now, the only use by KoShapeRegistry::createShapeFromOdf(), remove
* after it is deprecated.
*/
void hackPreliminarySetImage(KisImageSP image);
KisUndoStore* createUndoStore();
/**
* The shape controller matches internal krita image layers with
* the flake shape hierarchy.
*/
KoShapeBasedDocumentBase * shapeController() const;
KoShapeLayer* shapeForNode(KisNodeSP layer) const;
/**
* Set the list of nodes that was marked as currently active. Used *only*
* for saving loading. Never use it for tools or processing.
*/
void setPreActivatedNode(KisNodeSP activatedNode);
/**
* @return the node that was set as active during loading. Used *only*
* for saving loading. Never use it for tools or processing.
*/
KisNodeSP preActivatedNode() const;
/// @return the list of assistants associated with this document
QList assistants() const;
/// @replace the current list of assistants with @param value
void setAssistants(const QList &value);
void setAssistantsGlobalColor(QColor color);
QColor assistantsGlobalColor();
/**
* Get existing reference images layer or null if none exists.
*/
KisSharedPtr referenceImagesLayer() const;
void setReferenceImagesLayer(KisSharedPtr layer, bool updateImage);
bool save(bool showWarnings, KisPropertiesConfigurationSP exportConfiguration);
/**
* Return the bounding box of the image and associated elements (e.g. reference images)
*/
QRectF documentBounds() const;
Q_SIGNALS:
void completed();
void canceled(const QString &);
private Q_SLOTS:
void setImageModified();
void slotAutoSave();
void slotUndoStackCleanChanged(bool value);
void slotConfigChanged();
private:
/**
* @brief try to clone the image. This method handles all the locking for you. If locking
* has failed, no cloning happens
* @return cloned document on success, null otherwise
*/
KisDocument *lockAndCloneForSaving();
QString exportErrorToUserMessage(KisImportExportFilter::ConversionStatus status, const QString &errorMessage);
QString prettyPathOrUrl() const;
bool openUrlInternal(const QUrl &url);
void slotAutoSaveImpl(std::unique_ptr &&optionalClonedDocument);
class Private;
Private *const d;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(KisDocument::OpenFlags)
Q_DECLARE_METATYPE(KisDocument*)
#endif
diff --git a/libs/ui/KisMainWindow.cpp b/libs/ui/KisMainWindow.cpp
index a37d91784f..7fe6c4e4a6 100644
--- a/libs/ui/KisMainWindow.cpp
+++ b/libs/ui/KisMainWindow.cpp
@@ -1,2646 +1,2647 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis
Copyright (C) 2000-2006 David Faure
Copyright (C) 2007, 2009 Thomas zander
Copyright (C) 2010 Benjamin Port
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "KisMainWindow.h"
#include
// qt includes
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef HAVE_KIO
#include
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "KoDockFactoryBase.h"
#include "KoDocumentInfoDlg.h"
#include "KoDocumentInfo.h"
#include "KoFileDialog.h"
#include
#include
#include
#include
#include
#include "KoToolDocker.h"
#include "KoToolBoxDocker_p.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include "dialogs/kis_about_application.h"
#include "dialogs/kis_delayed_save_dialog.h"
#include "dialogs/kis_dlg_preferences.h"
#include "kis_action.h"
#include "kis_action_manager.h"
#include "KisApplication.h"
#include "kis_canvas2.h"
#include "kis_canvas_controller.h"
#include "kis_canvas_resource_provider.h"
#include "kis_clipboard.h"
#include "kis_config.h"
#include "kis_config_notifier.h"
#include "kis_custom_image_widget.h"
#include
#include "kis_group_layer.h"
#include "kis_icon_utils.h"
#include "kis_image_from_clipboard_widget.h"
#include "kis_image.h"
#include
#include "KisImportExportManager.h"
#include "kis_mainwindow_observer.h"
#include "kis_memory_statistics_server.h"
#include "kis_node.h"
#include "KisOpenPane.h"
#include "kis_paintop_box.h"
#include "KisPart.h"
#include "KisPrintJob.h"
#include "KisResourceServerProvider.h"
#include "kis_signal_compressor_with_param.h"
#include "kis_statusbar.h"
#include "KisView.h"
#include "KisViewManager.h"
#include "thememanager.h"
#include "kis_animation_importer.h"
#include "dialogs/kis_dlg_import_image_sequence.h"
#include
#include "KisWindowLayoutManager.h"
#include
#include "KisWelcomePageWidget.h"
#include
#ifdef Q_OS_WIN
#include
#endif
class ToolDockerFactory : public KoDockFactoryBase
{
public:
ToolDockerFactory() : KoDockFactoryBase() { }
QString id() const override {
return "sharedtooldocker";
}
QDockWidget* createDockWidget() override {
KoToolDocker* dockWidget = new KoToolDocker();
return dockWidget;
}
DockPosition defaultDockPosition() const override {
return DockRight;
}
};
class Q_DECL_HIDDEN KisMainWindow::Private
{
public:
Private(KisMainWindow *parent, QUuid id)
: q(parent)
, id(id)
, dockWidgetMenu(new KActionMenu(i18nc("@action:inmenu", "&Dockers"), parent))
, windowMenu(new KActionMenu(i18nc("@action:inmenu", "&Window"), parent))
, documentMenu(new KActionMenu(i18nc("@action:inmenu", "New &View"), parent))
, workspaceMenu(new KActionMenu(i18nc("@action:inmenu", "Wor&kspace"), parent))
, welcomePage(new KisWelcomePageWidget(parent))
, widgetStack(new QStackedWidget(parent))
, mdiArea(new QMdiArea(parent))
, windowMapper(new QSignalMapper(parent))
, documentMapper(new QSignalMapper(parent))
{
if (id.isNull()) this->id = QUuid::createUuid();
widgetStack->addWidget(welcomePage);
widgetStack->addWidget(mdiArea);
mdiArea->setTabsMovable(true);
mdiArea->setActivationOrder(QMdiArea::ActivationHistoryOrder);
}
~Private() {
qDeleteAll(toolbarList);
}
KisMainWindow *q {0};
QUuid id;
KisViewManager *viewManager {0};
QPointer activeView;
QList toolbarList;
bool firstTime {true};
bool windowSizeDirty {false};
bool readOnly {false};
KisAction *showDocumentInfo {0};
KisAction *saveAction {0};
KisAction *saveActionAs {0};
// KisAction *printAction;
// KisAction *printActionPreview;
// KisAction *exportPdf {0};
KisAction *importAnimation {0};
KisAction *closeAll {0};
// KisAction *reloadFile;
KisAction *importFile {0};
KisAction *exportFile {0};
KisAction *undo {0};
KisAction *redo {0};
KisAction *newWindow {0};
KisAction *close {0};
KisAction *mdiCascade {0};
KisAction *mdiTile {0};
KisAction *mdiNextWindow {0};
KisAction *mdiPreviousWindow {0};
KisAction *toggleDockers {0};
KisAction *toggleDockerTitleBars {0};
KisAction *fullScreenMode {0};
KisAction *showSessionManager {0};
KisAction *expandingSpacers[2];
KActionMenu *dockWidgetMenu;
KActionMenu *windowMenu;
KActionMenu *documentMenu;
KActionMenu *workspaceMenu;
KHelpMenu *helpMenu {0};
KRecentFilesAction *recentFiles {0};
KoResourceModel *workspacemodel {0};
QScopedPointer undoActionsUpdateManager;
QString lastExportLocation;
QMap dockWidgetsMap;
QByteArray dockerStateBeforeHiding;
KoToolDocker *toolOptionsDocker {0};
QCloseEvent *deferredClosingEvent {0};
Digikam::ThemeManager *themeManager {0};
KisWelcomePageWidget *welcomePage {0};
QStackedWidget *widgetStack {0};
QMdiArea *mdiArea;
QMdiSubWindow *activeSubWindow {0};
QSignalMapper *windowMapper;
QSignalMapper *documentMapper;
QByteArray lastExportedFormat;
QScopedPointer > tabSwitchCompressor;
QMutex savingEntryMutex;
KConfigGroup windowStateConfig;
QUuid workspaceBorrowedBy;
KisActionManager * actionManager() {
return viewManager->actionManager();
}
QTabBar* findTabBarHACK() {
QObjectList objects = mdiArea->children();
Q_FOREACH (QObject *object, objects) {
QTabBar *bar = qobject_cast(object);
if (bar) {
return bar;
}
}
return 0;
}
};
KisMainWindow::KisMainWindow(QUuid uuid)
: KXmlGuiWindow()
, d(new Private(this, uuid))
{
auto rserver = KisResourceServerProvider::instance()->workspaceServer();
QSharedPointer adapter(new KoResourceServerAdapter(rserver));
d->workspacemodel = new KoResourceModel(adapter, this);
connect(d->workspacemodel, &KoResourceModel::afterResourcesLayoutReset, this, [&]() { updateWindowMenu(); });
d->viewManager = new KisViewManager(this, actionCollection());
KConfigGroup group( KSharedConfig::openConfig(), "theme");
d->themeManager = new Digikam::ThemeManager(group.readEntry("Theme", "Krita dark"), this);
d->windowStateConfig = KSharedConfig::openConfig()->group("MainWindow");
setAcceptDrops(true);
setStandardToolBarMenuEnabled(true);
setTabPosition(Qt::AllDockWidgetAreas, QTabWidget::North);
setDockNestingEnabled(true);
qApp->setStartDragDistance(25); // 25 px is a distance that works well for Tablet and Mouse events
#ifdef Q_OS_OSX
setUnifiedTitleAndToolBarOnMac(true);
#endif
connect(this, SIGNAL(restoringDone()), this, SLOT(forceDockTabFonts()));
connect(this, SIGNAL(themeChanged()), d->viewManager, SLOT(updateIcons()));
connect(KisPart::instance(), SIGNAL(documentClosed(QString)), SLOT(updateWindowMenu()));
connect(KisPart::instance(), SIGNAL(documentOpened(QString)), SLOT(updateWindowMenu()));
connect(KisConfigNotifier::instance(), SIGNAL(configChanged()), this, SLOT(configChanged()));
actionCollection()->addAssociatedWidget(this);
KoPluginLoader::instance()->load("Krita/ViewPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), d->viewManager, false);
// Load the per-application plugins (Right now, only Python) We do this only once, when the first mainwindow is being created.
KoPluginLoader::instance()->load("Krita/ApplicationPlugin", "Type == 'Service' and ([X-Krita-Version] == 28)", KoPluginLoader::PluginsConfig(), qApp, true);
KoToolBoxFactory toolBoxFactory;
QDockWidget *toolbox = createDockWidget(&toolBoxFactory);
toolbox->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable);
KisConfig cfg(true);
if (cfg.toolOptionsInDocker()) {
ToolDockerFactory toolDockerFactory;
d->toolOptionsDocker = qobject_cast(createDockWidget(&toolDockerFactory));
d->toolOptionsDocker->toggleViewAction()->setEnabled(true);
}
QMap dockwidgetActions;
dockwidgetActions[toolbox->toggleViewAction()->text()] = toolbox->toggleViewAction();
Q_FOREACH (const QString & docker, KoDockRegistry::instance()->keys()) {
KoDockFactoryBase *factory = KoDockRegistry::instance()->value(docker);
QDockWidget *dw = createDockWidget(factory);
dockwidgetActions[dw->toggleViewAction()->text()] = dw->toggleViewAction();
}
if (d->toolOptionsDocker) {
dockwidgetActions[d->toolOptionsDocker->toggleViewAction()->text()] = d->toolOptionsDocker->toggleViewAction();
}
connect(KoToolManager::instance(), SIGNAL(toolOptionWidgetsChanged(KoCanvasController*, QList >)), this, SLOT(newOptionWidgets(KoCanvasController*, QList >)));
Q_FOREACH (QString title, dockwidgetActions.keys()) {
d->dockWidgetMenu->addAction(dockwidgetActions[title]);
}
Q_FOREACH (QDockWidget *wdg, dockWidgets()) {
if ((wdg->features() & QDockWidget::DockWidgetClosable) == 0) {
wdg->setVisible(true);
}
}
Q_FOREACH (KoCanvasObserverBase* observer, canvasObservers()) {
observer->setObservedCanvas(0);
KisMainwindowObserver* mainwindowObserver = dynamic_cast(observer);
if (mainwindowObserver) {
mainwindowObserver->setViewManager(d->viewManager);
}
}
d->mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
d->mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
d->mdiArea->setTabPosition(QTabWidget::North);
d->mdiArea->setTabsClosable(true);
setCentralWidget(d->widgetStack);
d->widgetStack->setCurrentIndex(0);
connect(d->mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(subWindowActivated()));
connect(d->windowMapper, SIGNAL(mapped(QWidget*)), this, SLOT(setActiveSubWindow(QWidget*))); connect(d->documentMapper, SIGNAL(mapped(QObject*)), this, SLOT(newView(QObject*)));
createActions();
// the welcome screen needs to grab actions...so make sure this line goes after the createAction() so they exist
d->welcomePage->setMainWindow(this);
setAutoSaveSettings(d->windowStateConfig, false);
subWindowActivated();
updateWindowMenu();
if (isHelpMenuEnabled() && !d->helpMenu) {
// workaround for KHelpMenu (or rather KAboutData::applicationData()) internally
// not using the Q*Application metadata ATM, which results e.g. in the bugreport wizard
// not having the app version preset
// fixed hopefully in KF5 5.22.0, patch pending
QGuiApplication *app = qApp;
KAboutData aboutData(app->applicationName(), app->applicationDisplayName(), app->applicationVersion());
aboutData.setOrganizationDomain(app->organizationDomain().toUtf8());
d->helpMenu = new KHelpMenu(this, aboutData, false);
// workaround-less version:
// d->helpMenu = new KHelpMenu(this, QString()/*unused*/, false);
// The difference between using KActionCollection->addAction() is that
// these actions do not get tied to the MainWindow. What does this all do?
KActionCollection *actions = d->viewManager->actionCollection();
QAction *helpContentsAction = d->helpMenu->action(KHelpMenu::menuHelpContents);
QAction *whatsThisAction = d->helpMenu->action(KHelpMenu::menuWhatsThis);
QAction *reportBugAction = d->helpMenu->action(KHelpMenu::menuReportBug);
QAction *switchLanguageAction = d->helpMenu->action(KHelpMenu::menuSwitchLanguage);
QAction *aboutAppAction = d->helpMenu->action(KHelpMenu::menuAboutApp);
QAction *aboutKdeAction = d->helpMenu->action(KHelpMenu::menuAboutKDE);
if (helpContentsAction) {
actions->addAction(helpContentsAction->objectName(), helpContentsAction);
}
if (whatsThisAction) {
actions->addAction(whatsThisAction->objectName(), whatsThisAction);
}
if (reportBugAction) {
actions->addAction(reportBugAction->objectName(), reportBugAction);
}
if (switchLanguageAction) {
actions->addAction(switchLanguageAction->objectName(), switchLanguageAction);
}
if (aboutAppAction) {
actions->addAction(aboutAppAction->objectName(), aboutAppAction);
}
if (aboutKdeAction) {
actions->addAction(aboutKdeAction->objectName(), aboutKdeAction);
}
connect(d->helpMenu, SIGNAL(showAboutApplication()), SLOT(showAboutApplication()));
}
// KDE' libs 4''s help contents action is broken outside kde, for some reason... We can handle it just as easily ourselves
QAction *helpAction = actionCollection()->action("help_contents");
helpAction->disconnect();
connect(helpAction, SIGNAL(triggered()), this, SLOT(showManual()));
#if 0
//check for colliding shortcuts
QSet existingShortcuts;
Q_FOREACH (QAction* action, actionCollection()->actions()) {
if(action->shortcut() == QKeySequence(0)) {
continue;
}
dbgKrita << "shortcut " << action->text() << " " << action->shortcut();
Q_ASSERT(!existingShortcuts.contains(action->shortcut()));
existingShortcuts.insert(action->shortcut());
}
#endif
configChanged();
// If we have customized the toolbars, load that first
setLocalXMLFile(KoResourcePaths::locateLocal("data", "krita4.xmlgui"));
setXMLFile(":/kxmlgui5/krita4.xmlgui");
guiFactory()->addClient(this);
// Create and plug toolbar list for Settings menu
QList toolbarList;
Q_FOREACH (QWidget* it, guiFactory()->containers("ToolBar")) {
KToolBar * toolBar = ::qobject_cast(it);
if (toolBar) {
if (toolBar->objectName() == "BrushesAndStuff") {
toolBar->setEnabled(false);
}
KToggleAction* act = new KToggleAction(i18n("Show %1 Toolbar", toolBar->windowTitle()), this);
actionCollection()->addAction(toolBar->objectName().toUtf8(), act);
act->setCheckedState(KGuiItem(i18n("Hide %1 Toolbar", toolBar->windowTitle())));
connect(act, SIGNAL(toggled(bool)), this, SLOT(slotToolbarToggled(bool)));
act->setChecked(!toolBar->isHidden());
toolbarList.append(act);
} else {
warnUI << "Toolbar list contains a " << it->metaObject()->className() << " which is not a toolbar!";
}
}
plugActionList("toolbarlist", toolbarList);
d->toolbarList = toolbarList;
applyToolBarLayout();
d->viewManager->updateGUI();
d->viewManager->updateIcons();
#ifdef Q_OS_WIN
auto w = qApp->activeWindow();
if (w) QWindowsWindowFunctions::setHasBorderInFullScreen(w->windowHandle(), true);
#endif
QTimer::singleShot(1000, this, SLOT(checkSanity()));
{
using namespace std::placeholders; // For _1 placeholder
std::function callback(
std::bind(&KisMainWindow::switchTab, this, _1));
d->tabSwitchCompressor.reset(
new KisSignalCompressorWithParam(500, callback, KisSignalCompressor::FIRST_INACTIVE));
}
}
KisMainWindow::~KisMainWindow()
{
// Q_FOREACH (QAction *ac, actionCollection()->actions()) {
// QAction *action = qobject_cast(ac);
// if (action) {
// dbgKrita << "objectName()
// << "icon=" << action->icon().name()
// << "text=" << action->text().replace("&", "&")
// << "whatsThis=" << action->whatsThis()
// << "toolTip=" << action->toolTip().replace("", "").replace("", "")
// << "iconText=" << action->iconText().replace("&", "&")
// << "shortcut=" << action->shortcut(QAction::ActiveShortcut).toString()
// << "defaultShortcut=" << action->shortcut(QAction::DefaultShortcut).toString()
// << "isCheckable=" << QString((action->isChecked() ? "true" : "false"))
// << "statusTip=" << action->statusTip()
// << "/>" ;
// }
// else {
// dbgKrita << "Got a QAction:" << ac->objectName();
// }
// }
// The doc and view might still exist (this is the case when closing the window)
KisPart::instance()->removeMainWindow(this);
delete d->viewManager;
delete d;
}
QUuid KisMainWindow::id() const {
return d->id;
}
void KisMainWindow::addView(KisView *view)
{
if (d->activeView == view) return;
if (d->activeView) {
d->activeView->disconnect(this);
}
// register the newly created view in the input manager
viewManager()->inputManager()->addTrackedCanvas(view->canvasBase());
showView(view);
updateCaption();
emit restoringDone();
if (d->activeView) {
connect(d->activeView, SIGNAL(titleModified(QString,bool)), SLOT(slotDocumentTitleModified()));
connect(d->viewManager->statusBar(), SIGNAL(memoryStatusUpdated()), this, SLOT(updateCaption()));
}
}
void KisMainWindow::notifyChildViewDestroyed(KisView *view)
{
viewManager()->inputManager()->removeTrackedCanvas(view->canvasBase());
if (view->canvasBase() == viewManager()->canvasBase()) {
viewManager()->setCurrentView(0);
}
}
void KisMainWindow::showView(KisView *imageView)
{
if (imageView && activeView() != imageView) {
// XXX: find a better way to initialize this!
imageView->setViewManager(d->viewManager);
imageView->canvasBase()->setFavoriteResourceManager(d->viewManager->paintOpBox()->favoriteResourcesManager());
imageView->slotLoadingFinished();
QMdiSubWindow *subwin = d->mdiArea->addSubWindow(imageView);
imageView->setSubWindow(subwin);
subwin->setAttribute(Qt::WA_DeleteOnClose, true);
connect(subwin, SIGNAL(destroyed()), SLOT(updateWindowMenu()));
KisConfig cfg(true);
subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL()));
subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL()));
subwin->setWindowIcon(qApp->windowIcon());
/**
* Hack alert!
*
* Here we explicitly request KoToolManager to emit all the tool
* activation signals, to reinitialize the tool options docker.
*
* That is needed due to a design flaw we have in the
* initialization procedure. The tool in the KoToolManager is
* initialized in KisView::setViewManager() calls, which
* happens early enough. During this call the tool manager
* requests KoCanvasControllerWidget to emit the signal to
* update the widgets in the tool docker. *But* at that moment
* of time the view is not yet connected to the main window,
* because it happens in KisViewManager::setCurrentView a bit
* later. This fact makes the widgets updating signals be lost
* and never reach the tool docker.
*
* So here we just explicitly call the tool activation stub.
*/
KoToolManager::instance()->initializeCurrentToolForCanvas();
if (d->mdiArea->subWindowList().size() == 1) {
imageView->showMaximized();
}
else {
imageView->show();
}
// No, no, no: do not try to call this _before_ the show() has
// been called on the view; only when that has happened is the
// opengl context active, and very bad things happen if we tell
// the dockers to update themselves with a view if the opengl
// context is not active.
setActiveView(imageView);
updateWindowMenu();
updateCaption();
}
}
void KisMainWindow::slotPreferences()
{
if (KisDlgPreferences::editPreferences()) {
KisConfigNotifier::instance()->notifyConfigChanged();
KisConfigNotifier::instance()->notifyPixelGridModeChanged();
KisUpdateSchedulerConfigNotifier::instance()->notifyConfigChanged();
// XXX: should this be changed for the views in other windows as well?
Q_FOREACH (QPointer koview, KisPart::instance()->views()) {
KisViewManager *view = qobject_cast(koview);
if (view) {
// Update the settings for all nodes -- they don't query
// KisConfig directly because they need the settings during
// compositing, and they don't connect to the config notifier
// because nodes are not QObjects (because only one base class
// can be a QObject).
KisNode* node = dynamic_cast(view->image()->rootLayer().data());
node->updateSettings();
}
}
d->viewManager->showHideScrollbars();
}
}
void KisMainWindow::slotThemeChanged()
{
// save theme changes instantly
KConfigGroup group( KSharedConfig::openConfig(), "theme");
group.writeEntry("Theme", d->themeManager->currentThemeName());
// reload action icons!
Q_FOREACH (QAction *action, actionCollection()->actions()) {
KisIconUtils::updateIcon(action);
}
emit themeChanged();
}
void KisMainWindow::updateReloadFileAction(KisDocument *doc)
{
Q_UNUSED(doc);
// d->reloadFile->setEnabled(doc && !doc->url().isEmpty());
}
void KisMainWindow::setReadWrite(bool readwrite)
{
d->saveAction->setEnabled(readwrite);
d->importFile->setEnabled(readwrite);
d->readOnly = !readwrite;
updateCaption();
}
void KisMainWindow::addRecentURL(const QUrl &url)
{
// Add entry to recent documents list
// (call coming from KisDocument because it must work with cmd line, template dlg, file/open, etc.)
if (!url.isEmpty()) {
bool ok = true;
if (url.isLocalFile()) {
QString path = url.adjusted(QUrl::StripTrailingSlash).toLocalFile();
const QStringList tmpDirs = KoResourcePaths::resourceDirs("tmp");
for (QStringList::ConstIterator it = tmpDirs.begin() ; ok && it != tmpDirs.end() ; ++it) {
if (path.contains(*it)) {
ok = false; // it's in the tmp resource
}
}
#ifdef HAVE_KIO
if (ok) {
KRecentDocument::add(QUrl::fromLocalFile(path));
}
#endif
}
#ifdef HAVE_KIO
else {
KRecentDocument::add(url.adjusted(QUrl::StripTrailingSlash));
}
#endif
if (ok) {
d->recentFiles->addUrl(url);
}
saveRecentFiles();
}
}
void KisMainWindow::saveRecentFiles()
{
// Save list of recent files
KSharedConfigPtr config = KSharedConfig::openConfig();
d->recentFiles->saveEntries(config->group("RecentFiles"));
config->sync();
// Tell all windows to reload their list, after saving
// Doesn't work multi-process, but it's a start
Q_FOREACH (KisMainWindow *mw, KisPart::instance()->mainWindows()) {
if (mw != this) {
mw->reloadRecentFileList();
}
}
}
QList KisMainWindow::recentFilesUrls()
{
return d->recentFiles->urls();
}
void KisMainWindow::clearRecentFiles()
{
d->recentFiles->clear();
}
void KisMainWindow::reloadRecentFileList()
{
d->recentFiles->loadEntries(KSharedConfig::openConfig()->group("RecentFiles"));
}
void KisMainWindow::updateCaption()
{
if (!d->mdiArea->activeSubWindow()) {
updateCaption(QString(), false);
}
else if (d->activeView && d->activeView->document() && d->activeView->image()){
KisDocument *doc = d->activeView->document();
QString caption(doc->caption());
if (d->readOnly) {
caption += " [" + i18n("Write Protected") + "] ";
}
if (doc->isRecovered()) {
caption += " [" + i18n("Recovered") + "] ";
}
// new documents aren't saved yet, so we don't need to say it is modified
// new files don't have a URL, so we are using that for the check
if (!doc->url().isEmpty()) {
if ( doc->isModified()) {
caption += " [" + i18n("Modified") + "] ";
}
}
// show the file size for the document
KisMemoryStatisticsServer::Statistics m_fileSizeStats = KisMemoryStatisticsServer::instance()->fetchMemoryStatistics(d->activeView ? d->activeView->image() : 0);
if (m_fileSizeStats.imageSize) {
caption += QString(" (").append( KFormat().formatByteSize(m_fileSizeStats.imageSize)).append( ")");
}
d->activeView->setWindowTitle(caption);
d->activeView->setWindowModified(doc->isModified());
updateCaption(caption, doc->isModified());
if (!doc->url().fileName().isEmpty())
d->saveAction->setToolTip(i18n("Save as %1", doc->url().fileName()));
else
d->saveAction->setToolTip(i18n("Save"));
}
}
void KisMainWindow::updateCaption(const QString & caption, bool mod)
{
dbgUI << "KisMainWindow::updateCaption(" << caption << "," << mod << ")";
#ifdef KRITA_ALPHA
setCaption(QString("ALPHA %1: %2").arg(KRITA_ALPHA).arg(caption), mod);
return;
#endif
#ifdef KRITA_BETA
setCaption(QString("BETA %1: %2").arg(KRITA_BETA).arg(caption), mod);
return;
#endif
#ifdef KRITA_RC
setCaption(QString("RELEASE CANDIDATE %1: %2").arg(KRITA_RC).arg(caption), mod);
return;
#endif
setCaption(caption, mod);
}
KisView *KisMainWindow::activeView() const
{
if (d->activeView) {
return d->activeView;
}
return 0;
}
bool KisMainWindow::openDocument(const QUrl &url, OpenFlags flags)
{
if (!QFile(url.toLocalFile()).exists()) {
if (!(flags & BatchMode)) {
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("The file %1 does not exist.", url.url()));
}
d->recentFiles->removeUrl(url); //remove the file from the recent-opened-file-list
saveRecentFiles();
return false;
}
return openDocumentInternal(url, flags);
}
bool KisMainWindow::openDocumentInternal(const QUrl &url, OpenFlags flags)
{
if (!url.isLocalFile()) {
qWarning() << "KisMainWindow::openDocumentInternal. Not a local file:" << url;
return false;
}
KisDocument *newdoc = KisPart::instance()->createDocument();
if (flags & BatchMode) {
newdoc->setFileBatchMode(true);
}
d->firstTime = true;
connect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted()));
connect(newdoc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &)));
KisDocument::OpenFlags openFlags = KisDocument::None;
if (flags & RecoveryFile) {
openFlags |= KisDocument::RecoveryFile;
}
bool openRet = !(flags & Import) ? newdoc->openUrl(url, openFlags) : newdoc->importDocument(url);
if (!openRet) {
delete newdoc;
return false;
}
KisPart::instance()->addDocument(newdoc);
updateReloadFileAction(newdoc);
if (!QFileInfo(url.toLocalFile()).isWritable()) {
setReadWrite(false);
}
return true;
}
void KisMainWindow::showDocument(KisDocument *document) {
Q_FOREACH(QMdiSubWindow *subwindow, d->mdiArea->subWindowList()) {
KisView *view = qobject_cast(subwindow->widget());
KIS_SAFE_ASSERT_RECOVER_NOOP(view);
if (view) {
if (view->document() == document) {
setActiveSubWindow(subwindow);
return;
}
}
}
addViewAndNotifyLoadingCompleted(document);
}
KisView* KisMainWindow::addViewAndNotifyLoadingCompleted(KisDocument *document)
{
showWelcomeScreen(false); // see workaround in function header
KisView *view = KisPart::instance()->createView(document, resourceManager(), actionCollection(), this);
addView(view);
emit guiLoadingFinished();
return view;
}
QStringList KisMainWindow::showOpenFileDialog(bool isImporting)
{
KoFileDialog dialog(this, KoFileDialog::ImportFiles, "OpenDocument");
dialog.setDefaultDir(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
dialog.setMimeTypeFilters(KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import));
dialog.setCaption(isImporting ? i18n("Import Images") : i18n("Open Images"));
return dialog.filenames();
}
// Separate from openDocument to handle async loading (remote URLs)
void KisMainWindow::slotLoadCompleted()
{
KisDocument *newdoc = qobject_cast(sender());
if (newdoc && newdoc->image()) {
addViewAndNotifyLoadingCompleted(newdoc);
disconnect(newdoc, SIGNAL(completed()), this, SLOT(slotLoadCompleted()));
disconnect(newdoc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &)));
emit loadCompleted();
}
}
void KisMainWindow::slotLoadCanceled(const QString & errMsg)
{
dbgUI << "KisMainWindow::slotLoadCanceled";
if (!errMsg.isEmpty()) // empty when canceled by user
QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg);
// ... can't delete the document, it's the one who emitted the signal...
KisDocument* doc = qobject_cast(sender());
Q_ASSERT(doc);
disconnect(doc, SIGNAL(completed()), this, SLOT(slotLoadCompleted()));
disconnect(doc, SIGNAL(canceled(const QString &)), this, SLOT(slotLoadCanceled(const QString &)));
}
void KisMainWindow::slotSaveCanceled(const QString &errMsg)
{
dbgUI << "KisMainWindow::slotSaveCanceled";
if (!errMsg.isEmpty()) // empty when canceled by user
QMessageBox::critical(this, i18nc("@title:window", "Krita"), errMsg);
slotSaveCompleted();
}
void KisMainWindow::slotSaveCompleted()
{
dbgUI << "KisMainWindow::slotSaveCompleted";
KisDocument* doc = qobject_cast(sender());
Q_ASSERT(doc);
disconnect(doc, SIGNAL(completed()), this, SLOT(slotSaveCompleted()));
disconnect(doc, SIGNAL(canceled(const QString &)), this, SLOT(slotSaveCanceled(const QString &)));
if (d->deferredClosingEvent) {
KXmlGuiWindow::closeEvent(d->deferredClosingEvent);
}
}
bool KisMainWindow::hackIsSaving() const
{
StdLockableWrapper wrapper(&d->savingEntryMutex);
std::unique_lock> l(wrapper, std::try_to_lock);
return !l.owns_lock();
}
bool KisMainWindow::installBundle(const QString &fileName) const
{
QFileInfo from(fileName);
QFileInfo to(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName());
if (to.exists()) {
QFile::remove(to.canonicalFilePath());
}
return QFile::copy(fileName, QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/" + from.fileName());
}
bool KisMainWindow::saveDocument(KisDocument *document, bool saveas, bool isExporting)
{
if (!document) {
return true;
}
/**
* Make sure that we cannot enter this method twice!
*
* The lower level functions may call processEvents() so
* double-entry is quite possible to achieve. Here we try to lock
* the mutex, and if it is failed, just cancel saving.
*/
StdLockableWrapper wrapper(&d->savingEntryMutex);
std::unique_lock> l(wrapper, std::try_to_lock);
if (!l.owns_lock()) return false;
// no busy wait for saving because it is dangerous!
KisDelayedSaveDialog dlg(document->image(), KisDelayedSaveDialog::SaveDialog, 0, this);
dlg.blockIfImageIsBusy();
if (dlg.result() == KisDelayedSaveDialog::Rejected) {
return false;
}
else if (dlg.result() == KisDelayedSaveDialog::Ignored) {
QMessageBox::critical(0,
i18nc("@title:window", "Krita"),
i18n("You are saving a file while the image is "
"still rendering. The saved file may be "
"incomplete or corrupted.\n\n"
"Please select a location where the original "
"file will not be overridden!"));
saveas = true;
}
if (document->isRecovered()) {
saveas = true;
}
bool reset_url;
if (document->url().isEmpty()) {
reset_url = true;
saveas = true;
}
else {
reset_url = false;
}
connect(document, SIGNAL(completed()), this, SLOT(slotSaveCompleted()));
connect(document, SIGNAL(canceled(const QString &)), this, SLOT(slotSaveCanceled(const QString &)));
QUrl oldURL = document->url();
QString oldFile = document->localFilePath();
QByteArray nativeFormat = document->nativeFormatMimeType();
QByteArray oldMimeFormat = document->mimeType();
QUrl suggestedURL = document->url();
QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export);
mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Export);
if (!mimeFilter.contains(oldMimeFormat)) {
dbgUI << "KisMainWindow::saveDocument no export filter for" << oldMimeFormat;
// --- don't setOutputMimeType in case the user cancels the Save As
// dialog and then tries to just plain Save ---
// suggest a different filename extension (yes, we fortunately don't all live in a world of magic :))
QString suggestedFilename = QFileInfo(suggestedURL.toLocalFile()).baseName();
if (!suggestedFilename.isEmpty()) { // ".kra" looks strange for a name
suggestedFilename = suggestedFilename + "." + KisMimeDatabase::suffixesForMimeType(KIS_MIME_TYPE).first();
suggestedURL = suggestedURL.adjusted(QUrl::RemoveFilename);
suggestedURL.setPath(suggestedURL.path() + suggestedFilename);
}
// force the user to choose outputMimeType
saveas = true;
}
bool ret = false;
if (document->url().isEmpty() || isExporting || saveas) {
// if you're just File/Save As'ing to change filter options you
// don't want to be reminded about overwriting files etc.
bool justChangingFilterOptions = false;
KoFileDialog dialog(this, KoFileDialog::SaveFile, "SaveAs");
dialog.setCaption(isExporting ? i18n("Exporting") : i18n("Saving As"));
//qDebug() << ">>>>>" << isExporting << d->lastExportLocation << d->lastExportedFormat << QString::fromLatin1(document->mimeType());
if (isExporting && !d->lastExportLocation.isEmpty()) {
// Use the location where we last exported to, if it's set, as the opening location for the file dialog
QString proposedPath = QFileInfo(d->lastExportLocation).absolutePath();
// If the document doesn't have a filename yet, use the title
QString proposedFileName = suggestedURL.isEmpty() ? document->documentInfo()->aboutInfo("title") : QFileInfo(suggestedURL.toLocalFile()).baseName();
// Use the last mimetype we exported to by default
QString proposedMimeType = d->lastExportedFormat.isEmpty() ? "" : d->lastExportedFormat;
QString proposedExtension = KisMimeDatabase::suffixesForMimeType(proposedMimeType).first().remove("*,");
// Set the default dir: this overrides the one loaded from the config file, since we're exporting and the lastExportLocation is not empty
dialog.setDefaultDir(proposedPath + "/" + proposedFileName + "." + proposedExtension, true);
dialog.setMimeTypeFilters(mimeFilter, proposedMimeType);
}
else {
// Get the last used location for saving
KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs");
QString proposedPath = group.readEntry("SaveAs", "");
// if that is empty, get the last used location for loading
if (proposedPath.isEmpty()) {
proposedPath = group.readEntry("OpenDocument", "");
}
// If that is empty, too, use the Pictures location.
if (proposedPath.isEmpty()) {
proposedPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
}
// But only use that if the suggestedUrl, that is, the document's own url is empty, otherwise
// open the location where the document currently is.
dialog.setDefaultDir(suggestedURL.isEmpty() ? proposedPath : suggestedURL.toLocalFile(), true);
// If exporting, default to all supported file types if user is exporting
QByteArray default_mime_type = "";
if (!isExporting) {
// otherwise use the document's mimetype, or if that is empty, kra, which is the savest.
default_mime_type = document->mimeType().isEmpty() ? nativeFormat : document->mimeType();
}
dialog.setMimeTypeFilters(mimeFilter, QString::fromLatin1(default_mime_type));
}
QUrl newURL = QUrl::fromUserInput(dialog.filename());
if (newURL.isLocalFile()) {
QString fn = newURL.toLocalFile();
if (QFileInfo(fn).completeSuffix().isEmpty()) {
fn.append(KisMimeDatabase::suffixesForMimeType(nativeFormat).first());
newURL = QUrl::fromLocalFile(fn);
}
}
if (document->documentInfo()->aboutInfo("title") == i18n("Unnamed")) {
QString fn = newURL.toLocalFile();
QFileInfo info(fn);
document->documentInfo()->setAboutInfo("title", info.baseName());
}
QByteArray outputFormat = nativeFormat;
QString outputFormatString = KisMimeDatabase::mimeTypeForFile(newURL.toLocalFile(), false);
outputFormat = outputFormatString.toLatin1();
if (!isExporting) {
justChangingFilterOptions = (newURL == document->url()) && (outputFormat == document->mimeType());
}
else {
QString path = QFileInfo(d->lastExportLocation).absolutePath();
QString filename = QFileInfo(document->url().toLocalFile()).baseName();
justChangingFilterOptions = (QFileInfo(newURL.toLocalFile()).absolutePath() == path)
&& (QFileInfo(newURL.toLocalFile()).baseName() == filename)
&& (outputFormat == d->lastExportedFormat);
}
bool bOk = true;
if (newURL.isEmpty()) {
bOk = false;
}
if (bOk) {
bool wantToSave = true;
// don't change this line unless you know what you're doing :)
if (!justChangingFilterOptions) {
if (!document->isNativeFormat(outputFormat))
wantToSave = true;
}
if (wantToSave) {
if (!isExporting) { // Save As
ret = document->saveAs(newURL, outputFormat, true);
if (ret) {
dbgUI << "Successful Save As!";
KisPart::instance()->addRecentURLToAllMainWindows(newURL);
setReadWrite(true);
} else {
dbgUI << "Failed Save As!";
document->setUrl(oldURL);
document->setLocalFilePath(oldFile);
}
}
else { // Export
ret = document->exportDocument(newURL, outputFormat);
if (ret) {
d->lastExportLocation = newURL.toLocalFile();
d->lastExportedFormat = outputFormat;
}
}
} // if (wantToSave) {
else
ret = false;
} // if (bOk) {
else
ret = false;
} else { // saving
// We cannot "export" into the currently
// opened document. We are not Gimp.
KIS_ASSERT_RECOVER_NOOP(!isExporting);
// be sure document has the correct outputMimeType!
if (document->isModified()) {
ret = document->save(true, 0);
}
if (!ret) {
dbgUI << "Failed Save!";
document->setUrl(oldURL);
document->setLocalFilePath(oldFile);
}
}
if (ret && !isExporting) {
document->setRecovered(false);
}
if (!ret && reset_url)
document->resetURL(); //clean the suggested filename as the save dialog was rejected
updateReloadFileAction(document);
updateCaption();
return ret;
}
void KisMainWindow::undo()
{
if (activeView()) {
activeView()->document()->undoStack()->undo();
}
}
void KisMainWindow::redo()
{
if (activeView()) {
activeView()->document()->undoStack()->redo();
}
}
void KisMainWindow::closeEvent(QCloseEvent *e)
{
if (!KisPart::instance()->closingSession()) {
QAction *action= d->viewManager->actionCollection()->action("view_show_canvas_only");
if ((action) && (action->isChecked())) {
action->setChecked(false);
}
// Save session when last window is closed
if (KisPart::instance()->mainwindowCount() == 1) {
bool closeAllowed = KisPart::instance()->closeSession();
if (!closeAllowed) {
e->setAccepted(false);
return;
}
}
}
d->mdiArea->closeAllSubWindows();
QList childrenList = d->mdiArea->subWindowList();
if (childrenList.isEmpty()) {
d->deferredClosingEvent = e;
saveWindowState(true);
} else {
e->setAccepted(false);
}
}
void KisMainWindow::saveWindowSettings()
{
KSharedConfigPtr config = KSharedConfig::openConfig();
if (d->windowSizeDirty ) {
dbgUI << "KisMainWindow::saveWindowSettings";
KConfigGroup group = d->windowStateConfig;
KWindowConfig::saveWindowSize(windowHandle(), group);
config->sync();
d->windowSizeDirty = false;
}
if (!d->activeView || d->activeView->document()) {
// Save toolbar position into the config file of the app, under the doc's component name
KConfigGroup group = d->windowStateConfig;
saveMainWindowSettings(group);
// Save collapsible state of dock widgets
for (QMap::const_iterator i = d->dockWidgetsMap.constBegin();
i != d->dockWidgetsMap.constEnd(); ++i) {
if (i.value()->widget()) {
KConfigGroup dockGroup = group.group(QString("DockWidget ") + i.key());
dockGroup.writeEntry("Collapsed", i.value()->widget()->isHidden());
dockGroup.writeEntry("Locked", i.value()->property("Locked").toBool());
dockGroup.writeEntry("DockArea", (int) dockWidgetArea(i.value()));
dockGroup.writeEntry("xPosition", (int) i.value()->widget()->x());
dockGroup.writeEntry("yPosition", (int) i.value()->widget()->y());
dockGroup.writeEntry("width", (int) i.value()->widget()->width());
dockGroup.writeEntry("height", (int) i.value()->widget()->height());
}
}
}
KSharedConfig::openConfig()->sync();
resetAutoSaveSettings(); // Don't let KMainWindow override the good stuff we wrote down
}
void KisMainWindow::resizeEvent(QResizeEvent * e)
{
d->windowSizeDirty = true;
KXmlGuiWindow::resizeEvent(e);
}
void KisMainWindow::setActiveView(KisView* view)
{
d->activeView = view;
updateCaption();
if (d->undoActionsUpdateManager) {
d->undoActionsUpdateManager->setCurrentDocument(view ? view->document() : 0);
}
d->viewManager->setCurrentView(view);
KisWindowLayoutManager::instance()->activeDocumentChanged(view->document());
+ emit sigActiveDocumentChanged();
}
void KisMainWindow::dragEnterEvent(QDragEnterEvent *event)
{
d->welcomePage->showDropAreaIndicator(true);
if (event->mimeData()->hasUrls() ||
event->mimeData()->hasFormat("application/x-krita-node") ||
event->mimeData()->hasFormat("application/x-qt-image")) {
event->accept();
}
}
void KisMainWindow::dropEvent(QDropEvent *event)
{
d->welcomePage->showDropAreaIndicator(false);
if (event->mimeData()->hasUrls() && event->mimeData()->urls().size() > 0) {
Q_FOREACH (const QUrl &url, event->mimeData()->urls()) {
if (url.toLocalFile().endsWith(".bundle")) {
bool r = installBundle(url.toLocalFile());
qDebug() << "\t" << r;
}
else {
openDocument(url, None);
}
}
}
}
void KisMainWindow::dragMoveEvent(QDragMoveEvent * event)
{
QTabBar *tabBar = d->findTabBarHACK();
if (!tabBar && d->mdiArea->viewMode() == QMdiArea::TabbedView) {
qWarning() << "WARNING!!! Cannot find QTabBar in the main window! Looks like Qt has changed behavior. Drag & Drop between multiple tabs might not work properly (tabs will not switch automatically)!";
}
if (tabBar && tabBar->isVisible()) {
QPoint pos = tabBar->mapFromGlobal(mapToGlobal(event->pos()));
if (tabBar->rect().contains(pos)) {
const int tabIndex = tabBar->tabAt(pos);
if (tabIndex >= 0 && tabBar->currentIndex() != tabIndex) {
d->tabSwitchCompressor->start(tabIndex);
}
} else if (d->tabSwitchCompressor->isActive()) {
d->tabSwitchCompressor->stop();
}
}
}
void KisMainWindow::dragLeaveEvent(QDragLeaveEvent * /*event*/)
{
d->welcomePage->showDropAreaIndicator(false);
if (d->tabSwitchCompressor->isActive()) {
d->tabSwitchCompressor->stop();
}
}
void KisMainWindow::switchTab(int index)
{
QTabBar *tabBar = d->findTabBarHACK();
if (!tabBar) return;
tabBar->setCurrentIndex(index);
}
void KisMainWindow::showWelcomeScreen(bool show)
{
d->widgetStack->setCurrentIndex(!show);
}
void KisMainWindow::slotFileNew()
{
const QStringList mimeFilter = KisImportExportManager::supportedMimeTypes(KisImportExportManager::Import);
KisOpenPane *startupWidget = new KisOpenPane(this, mimeFilter, QStringLiteral("templates/"));
startupWidget->setWindowModality(Qt::WindowModal);
startupWidget->setWindowTitle(i18n("Create new document"));
KisConfig cfg(true);
int w = cfg.defImageWidth();
int h = cfg.defImageHeight();
const double resolution = cfg.defImageResolution();
const QString colorModel = cfg.defColorModel();
const QString colorDepth = cfg.defaultColorDepth();
const QString colorProfile = cfg.defColorProfile();
CustomDocumentWidgetItem item;
item.widget = new KisCustomImageWidget(startupWidget,
w,
h,
resolution,
colorModel,
colorDepth,
colorProfile,
i18n("Unnamed"));
item.icon = "document-new";
startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon);
QSize sz = KisClipboard::instance()->clipSize();
if (sz.isValid() && sz.width() != 0 && sz.height() != 0) {
w = sz.width();
h = sz.height();
}
item.widget = new KisImageFromClipboard(startupWidget,
w,
h,
resolution,
colorModel,
colorDepth,
colorProfile,
i18n("Unnamed"));
item.title = i18n("Create from Clipboard");
item.icon = "tab-new";
startupWidget->addCustomDocumentWidget(item.widget, item.title, item.icon);
// calls deleteLater
connect(startupWidget, SIGNAL(documentSelected(KisDocument*)), KisPart::instance(), SLOT(startCustomDocument(KisDocument*)));
// calls deleteLater
connect(startupWidget, SIGNAL(openTemplate(const QUrl&)), KisPart::instance(), SLOT(openTemplate(const QUrl&)));
startupWidget->exec();
// Cancel calls deleteLater...
}
void KisMainWindow::slotImportFile()
{
dbgUI << "slotImportFile()";
slotFileOpen(true);
}
void KisMainWindow::slotFileOpen(bool isImporting)
{
QStringList urls = showOpenFileDialog(isImporting);
if (urls.isEmpty())
return;
Q_FOREACH (const QString& url, urls) {
if (!url.isEmpty()) {
OpenFlags flags = isImporting ? Import : None;
bool res = openDocument(QUrl::fromLocalFile(url), flags);
if (!res) {
warnKrita << "Loading" << url << "failed";
}
}
}
}
void KisMainWindow::slotFileOpenRecent(const QUrl &url)
{
(void) openDocument(QUrl::fromLocalFile(url.toLocalFile()), None);
}
void KisMainWindow::slotFileSave()
{
if (saveDocument(d->activeView->document(), false, false)) {
emit documentSaved();
}
}
void KisMainWindow::slotFileSaveAs()
{
if (saveDocument(d->activeView->document(), true, false)) {
emit documentSaved();
}
}
void KisMainWindow::slotExportFile()
{
if (saveDocument(d->activeView->document(), true, true)) {
emit documentSaved();
}
}
void KisMainWindow::slotShowSessionManager() {
KisPart::instance()->showSessionManager();
}
KoCanvasResourceManager *KisMainWindow::resourceManager() const
{
return d->viewManager->resourceProvider()->resourceManager();
}
int KisMainWindow::viewCount() const
{
return d->mdiArea->subWindowList().size();
}
const KConfigGroup &KisMainWindow::windowStateConfig() const
{
return d->windowStateConfig;
}
void KisMainWindow::saveWindowState(bool restoreNormalState)
{
if (restoreNormalState) {
QAction *showCanvasOnly = d->viewManager->actionCollection()->action("view_show_canvas_only");
if (showCanvasOnly && showCanvasOnly->isChecked()) {
showCanvasOnly->setChecked(false);
}
d->windowStateConfig.writeEntry("ko_geometry", saveGeometry().toBase64());
d->windowStateConfig.writeEntry("State", saveState().toBase64());
if (!d->dockerStateBeforeHiding.isEmpty()) {
restoreState(d->dockerStateBeforeHiding);
}
statusBar()->setVisible(true);
menuBar()->setVisible(true);
saveWindowSettings();
} else {
saveMainWindowSettings(d->windowStateConfig);
}
}
bool KisMainWindow::restoreWorkspaceState(const QByteArray &state)
{
QByteArray oldState = saveState();
// needed because otherwise the layout isn't correctly restored in some situations
Q_FOREACH (QDockWidget *dock, dockWidgets()) {
dock->toggleViewAction()->setEnabled(true);
dock->hide();
}
bool success = KXmlGuiWindow::restoreState(state);
if (!success) {
KXmlGuiWindow::restoreState(oldState);
return false;
}
return success;
}
bool KisMainWindow::restoreWorkspace(KisWorkspaceResource *workspace)
{
bool success = restoreWorkspaceState(workspace->dockerState());
if (activeKisView()) {
activeKisView()->resourceProvider()->notifyLoadingWorkspace(workspace);
}
return success;
}
QByteArray KisMainWindow::borrowWorkspace(KisMainWindow *other)
{
QByteArray currentWorkspace = saveState();
if (!d->workspaceBorrowedBy.isNull()) {
if (other->id() == d->workspaceBorrowedBy) {
// We're swapping our original workspace back
d->workspaceBorrowedBy = QUuid();
return currentWorkspace;
} else {
// Get our original workspace back before swapping with a third window
KisMainWindow *borrower = KisPart::instance()->windowById(d->workspaceBorrowedBy);
if (borrower) {
QByteArray originalLayout = borrower->borrowWorkspace(this);
borrower->restoreWorkspaceState(currentWorkspace);
d->workspaceBorrowedBy = other->id();
return originalLayout;
}
}
}
d->workspaceBorrowedBy = other->id();
return currentWorkspace;
}
void KisMainWindow::swapWorkspaces(KisMainWindow *a, KisMainWindow *b)
{
QByteArray workspaceA = a->borrowWorkspace(b);
QByteArray workspaceB = b->borrowWorkspace(a);
a->restoreWorkspaceState(workspaceB);
b->restoreWorkspaceState(workspaceA);
}
KisViewManager *KisMainWindow::viewManager() const
{
return d->viewManager;
}
void KisMainWindow::slotDocumentInfo()
{
if (!d->activeView->document())
return;
KoDocumentInfo *docInfo = d->activeView->document()->documentInfo();
if (!docInfo)
return;
KoDocumentInfoDlg *dlg = d->activeView->document()->createDocumentInfoDialog(this, docInfo);
if (dlg->exec()) {
if (dlg->isDocumentSaved()) {
d->activeView->document()->setModified(false);
} else {
d->activeView->document()->setModified(true);
}
d->activeView->document()->setTitleModified();
}
delete dlg;
}
bool KisMainWindow::slotFileCloseAll()
{
Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) {
if (subwin) {
if(!subwin->close())
return false;
}
}
updateCaption();
return true;
}
void KisMainWindow::slotFileQuit()
{
KisPart::instance()->closeSession();
}
void KisMainWindow::slotFilePrint()
{
if (!activeView())
return;
KisPrintJob *printJob = activeView()->createPrintJob();
if (printJob == 0)
return;
applyDefaultSettings(printJob->printer());
QPrintDialog *printDialog = activeView()->createPrintDialog( printJob, this );
if (printDialog && printDialog->exec() == QDialog::Accepted) {
printJob->printer().setPageMargins(0.0, 0.0, 0.0, 0.0, QPrinter::Point);
printJob->printer().setPaperSize(QSizeF(activeView()->image()->width() / (72.0 * activeView()->image()->xRes()),
activeView()->image()->height()/ (72.0 * activeView()->image()->yRes())),
QPrinter::Inch);
printJob->startPrinting(KisPrintJob::DeleteWhenDone);
}
else {
delete printJob;
}
delete printDialog;
}
void KisMainWindow::slotFilePrintPreview()
{
if (!activeView())
return;
KisPrintJob *printJob = activeView()->createPrintJob();
if (printJob == 0)
return;
/* Sets the startPrinting() slot to be blocking.
The Qt print-preview dialog requires the printing to be completely blocking
and only return when the full document has been printed.
By default the KisPrintingDialog is non-blocking and
multithreading, setting blocking to true will allow it to be used in the preview dialog */
printJob->setProperty("blocking", true);
QPrintPreviewDialog *preview = new QPrintPreviewDialog(&printJob->printer(), this);
printJob->setParent(preview); // will take care of deleting the job
connect(preview, SIGNAL(paintRequested(QPrinter*)), printJob, SLOT(startPrinting()));
preview->exec();
delete preview;
}
KisPrintJob* KisMainWindow::exportToPdf(QString pdfFileName)
{
if (!activeView())
return 0;
if (!activeView()->document())
return 0;
KoPageLayout pageLayout;
pageLayout.width = 0;
pageLayout.height = 0;
pageLayout.topMargin = 0;
pageLayout.bottomMargin = 0;
pageLayout.leftMargin = 0;
pageLayout.rightMargin = 0;
if (pdfFileName.isEmpty()) {
KConfigGroup group = KSharedConfig::openConfig()->group("File Dialogs");
QString defaultDir = group.readEntry("SavePdfDialog");
if (defaultDir.isEmpty())
defaultDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
QUrl startUrl = QUrl::fromLocalFile(defaultDir);
KisDocument* pDoc = d->activeView->document();
/** if document has a file name, take file name and replace extension with .pdf */
if (pDoc && pDoc->url().isValid()) {
startUrl = pDoc->url();
QString fileName = startUrl.toLocalFile();
fileName = fileName.replace( QRegExp( "\\.\\w{2,5}$", Qt::CaseInsensitive ), ".pdf" );
startUrl = startUrl.adjusted(QUrl::RemoveFilename);
startUrl.setPath(startUrl.path() + fileName );
}
QPointer layoutDlg(new KoPageLayoutDialog(this, pageLayout));
layoutDlg->setWindowModality(Qt::WindowModal);
if (layoutDlg->exec() != QDialog::Accepted || !layoutDlg) {
delete layoutDlg;
return 0;
}
pageLayout = layoutDlg->pageLayout();
delete layoutDlg;
KoFileDialog dialog(this, KoFileDialog::SaveFile, "OpenDocument");
dialog.setCaption(i18n("Export as PDF"));
dialog.setDefaultDir(startUrl.toLocalFile());
dialog.setMimeTypeFilters(QStringList() << "application/pdf");
QUrl url = QUrl::fromUserInput(dialog.filename());
pdfFileName = url.toLocalFile();
if (pdfFileName.isEmpty())
return 0;
}
KisPrintJob *printJob = activeView()->createPrintJob();
if (printJob == 0)
return 0;
if (isHidden()) {
printJob->setProperty("noprogressdialog", true);
}
applyDefaultSettings(printJob->printer());
// TODO for remote files we have to first save locally and then upload.
printJob->printer().setOutputFileName(pdfFileName);
printJob->printer().setDocName(pdfFileName);
printJob->printer().setColorMode(QPrinter::Color);
if (pageLayout.format == KoPageFormat::CustomSize) {
printJob->printer().setPaperSize(QSizeF(pageLayout.width, pageLayout.height), QPrinter::Millimeter);
} else {
printJob->printer().setPaperSize(KoPageFormat::printerPageSize(pageLayout.format));
}
printJob->printer().setPageMargins(pageLayout.leftMargin, pageLayout.topMargin, pageLayout.rightMargin, pageLayout.bottomMargin, QPrinter::Millimeter);
switch (pageLayout.orientation) {
case KoPageFormat::Portrait:
printJob->printer().setOrientation(QPrinter::Portrait);
break;
case KoPageFormat::Landscape:
printJob->printer().setOrientation(QPrinter::Landscape);
break;
}
//before printing check if the printer can handle printing
if (!printJob->canPrint()) {
QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Cannot export to the specified file"));
}
printJob->startPrinting(KisPrintJob::DeleteWhenDone);
return printJob;
}
void KisMainWindow::importAnimation()
{
if (!activeView()) return;
KisDocument *document = activeView()->document();
if (!document) return;
KisDlgImportImageSequence dlg(this, document);
if (dlg.exec() == QDialog::Accepted) {
QStringList files = dlg.files();
int firstFrame = dlg.firstFrame();
int step = dlg.step();
KoUpdaterPtr updater =
!document->fileBatchMode() ? viewManager()->createUnthreadedUpdater(i18n("Import frames")) : 0;
KisAnimationImporter importer(document->image(), updater);
KisImportExportFilter::ConversionStatus status = importer.import(files, firstFrame, step);
if (status != KisImportExportFilter::OK && status != KisImportExportFilter::InternalError) {
QString msg = KisImportExportFilter::conversionStatusString(status);
if (!msg.isEmpty())
QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Could not finish import animation:\n%1", msg));
}
activeView()->canvasBase()->refetchDataFromImage();
}
}
void KisMainWindow::slotConfigureToolbars()
{
saveWindowState();
KEditToolBar edit(factory(), this);
connect(&edit, SIGNAL(newToolBarConfig()), this, SLOT(slotNewToolbarConfig()));
(void) edit.exec();
applyToolBarLayout();
}
void KisMainWindow::slotNewToolbarConfig()
{
applyMainWindowSettings(d->windowStateConfig);
KXMLGUIFactory *factory = guiFactory();
Q_UNUSED(factory);
// Check if there's an active view
if (!d->activeView)
return;
plugActionList("toolbarlist", d->toolbarList);
applyToolBarLayout();
}
void KisMainWindow::slotToolbarToggled(bool toggle)
{
//dbgUI <<"KisMainWindow::slotToolbarToggled" << sender()->name() <<" toggle=" << true;
// The action (sender) and the toolbar have the same name
KToolBar * bar = toolBar(sender()->objectName());
if (bar) {
if (toggle) {
bar->show();
}
else {
bar->hide();
}
if (d->activeView && d->activeView->document()) {
saveWindowState();
}
} else
warnUI << "slotToolbarToggled : Toolbar " << sender()->objectName() << " not found!";
}
void KisMainWindow::viewFullscreen(bool fullScreen)
{
KisConfig cfg(false);
cfg.setFullscreenMode(fullScreen);
if (fullScreen) {
setWindowState(windowState() | Qt::WindowFullScreen); // set
} else {
setWindowState(windowState() & ~Qt::WindowFullScreen); // reset
}
}
void KisMainWindow::setMaxRecentItems(uint _number)
{
d->recentFiles->setMaxItems(_number);
}
void KisMainWindow::slotReloadFile()
{
KisDocument* document = d->activeView->document();
if (!document || document->url().isEmpty())
return;
if (document->isModified()) {
bool ok = QMessageBox::question(this,
i18nc("@title:window", "Krita"),
i18n("You will lose all changes made since your last save\n"
"Do you want to continue?"),
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes;
if (!ok)
return;
}
QUrl url = document->url();
saveWindowSettings();
if (!document->reload()) {
QMessageBox::critical(this, i18nc("@title:window", "Krita"), i18n("Error: Could not reload this document"));
}
return;
}
QDockWidget* KisMainWindow::createDockWidget(KoDockFactoryBase* factory)
{
QDockWidget* dockWidget = 0;
if (!d->dockWidgetsMap.contains(factory->id())) {
dockWidget = factory->createDockWidget();
// It is quite possible that a dock factory cannot create the dock; don't
// do anything in that case.
if (!dockWidget) {
warnKrita << "Could not create docker for" << factory->id();
return 0;
}
dockWidget->setFont(KoDockRegistry::dockFont());
dockWidget->setObjectName(factory->id());
dockWidget->setParent(this);
if (dockWidget->widget() && dockWidget->widget()->layout())
dockWidget->widget()->layout()->setContentsMargins(1, 1, 1, 1);
Qt::DockWidgetArea side = Qt::RightDockWidgetArea;
bool visible = true;
switch (factory->defaultDockPosition()) {
case KoDockFactoryBase::DockTornOff:
dockWidget->setFloating(true); // position nicely?
break;
case KoDockFactoryBase::DockTop:
side = Qt::TopDockWidgetArea; break;
case KoDockFactoryBase::DockLeft:
side = Qt::LeftDockWidgetArea; break;
case KoDockFactoryBase::DockBottom:
side = Qt::BottomDockWidgetArea; break;
case KoDockFactoryBase::DockRight:
side = Qt::RightDockWidgetArea; break;
case KoDockFactoryBase::DockMinimized:
default:
side = Qt::RightDockWidgetArea;
visible = false;
}
KConfigGroup group = d->windowStateConfig.group("DockWidget " + factory->id());
side = static_cast(group.readEntry("DockArea", static_cast(side)));
if (side == Qt::NoDockWidgetArea) side = Qt::RightDockWidgetArea;
addDockWidget(side, dockWidget);
if (!visible) {
dockWidget->hide();
}
d->dockWidgetsMap.insert(factory->id(), dockWidget);
}
else {
dockWidget = d->dockWidgetsMap[factory->id()];
}
#ifdef Q_OS_OSX
dockWidget->setAttribute(Qt::WA_MacSmallSize, true);
#endif
dockWidget->setFont(KoDockRegistry::dockFont());
connect(dockWidget, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), this, SLOT(forceDockTabFonts()));
return dockWidget;
}
void KisMainWindow::forceDockTabFonts()
{
Q_FOREACH (QObject *child, children()) {
if (child->inherits("QTabBar")) {
((QTabBar *)child)->setFont(KoDockRegistry::dockFont());
}
}
}
QList KisMainWindow::dockWidgets() const
{
return d->dockWidgetsMap.values();
}
QDockWidget* KisMainWindow::dockWidget(const QString &id)
{
if (!d->dockWidgetsMap.contains(id)) return 0;
return d->dockWidgetsMap[id];
}
QList KisMainWindow::canvasObservers() const
{
QList observers;
Q_FOREACH (QDockWidget *docker, dockWidgets()) {
KoCanvasObserverBase *observer = dynamic_cast(docker);
if (observer) {
observers << observer;
}
else {
warnKrita << docker << "is not a canvas observer";
}
}
return observers;
}
void KisMainWindow::toggleDockersVisibility(bool visible)
{
if (!visible) {
d->dockerStateBeforeHiding = saveState();
Q_FOREACH (QObject* widget, children()) {
if (widget->inherits("QDockWidget")) {
QDockWidget* dw = static_cast(widget);
if (dw->isVisible()) {
dw->hide();
}
}
}
}
else {
restoreState(d->dockerStateBeforeHiding);
}
}
void KisMainWindow::slotDocumentTitleModified()
{
updateCaption();
updateReloadFileAction(d->activeView ? d->activeView->document() : 0);
}
void KisMainWindow::subWindowActivated()
{
bool enabled = (activeKisView() != 0);
d->mdiCascade->setEnabled(enabled);
d->mdiNextWindow->setEnabled(enabled);
d->mdiPreviousWindow->setEnabled(enabled);
d->mdiTile->setEnabled(enabled);
d->close->setEnabled(enabled);
d->closeAll->setEnabled(enabled);
setActiveSubWindow(d->mdiArea->activeSubWindow());
Q_FOREACH (QToolBar *tb, toolBars()) {
if (tb->objectName() == "BrushesAndStuff") {
tb->setEnabled(enabled);
}
}
updateCaption();
d->actionManager()->updateGUI();
}
void KisMainWindow::windowFocused()
{
auto *kisPart = KisPart::instance();
auto *layoutManager = KisWindowLayoutManager::instance();
if (!layoutManager->primaryWorkspaceFollowsFocus()) return;
QUuid primary = layoutManager->primaryWindowId();
if (primary.isNull()) return;
if (d->id == primary) {
if (!d->workspaceBorrowedBy.isNull()) {
KisMainWindow *borrower = kisPart->windowById(d->workspaceBorrowedBy);
if (!borrower) return;
swapWorkspaces(this, borrower);
}
} else {
if (d->workspaceBorrowedBy == primary) return;
KisMainWindow *primaryWindow = kisPart->windowById(primary);
if (!primaryWindow) return;
swapWorkspaces(this, primaryWindow);
}
}
void KisMainWindow::updateWindowMenu()
{
QMenu *menu = d->windowMenu->menu();
menu->clear();
menu->addAction(d->newWindow);
menu->addAction(d->documentMenu);
QMenu *docMenu = d->documentMenu->menu();
docMenu->clear();
Q_FOREACH (QPointer doc, KisPart::instance()->documents()) {
if (doc) {
QString title = doc->url().toDisplayString();
if (title.isEmpty() && doc->image()) {
title = doc->image()->objectName();
}
QAction *action = docMenu->addAction(title);
action->setIcon(qApp->windowIcon());
connect(action, SIGNAL(triggered()), d->documentMapper, SLOT(map()));
d->documentMapper->setMapping(action, doc);
}
}
menu->addAction(d->workspaceMenu);
QMenu *workspaceMenu = d->workspaceMenu->menu();
workspaceMenu->clear();
auto workspaces = KisResourceServerProvider::instance()->workspaceServer()->resources();
auto m_this = this;
for (auto &w : workspaces) {
auto action = workspaceMenu->addAction(w->name());
connect(action, &QAction::triggered, this, [=]() {
m_this->restoreWorkspace(w);
});
}
workspaceMenu->addSeparator();
connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&Import Workspace...")),
&QAction::triggered,
this,
[&]() {
QString extensions = d->workspacemodel->extensions();
QStringList mimeTypes;
for(const QString &suffix : extensions.split(":")) {
mimeTypes << KisMimeDatabase::mimeTypeForSuffix(suffix);
}
KoFileDialog dialog(0, KoFileDialog::OpenFile, "OpenDocument");
dialog.setMimeTypeFilters(mimeTypes);
dialog.setCaption(i18nc("@title:window", "Choose File to Add"));
QString filename = dialog.filename();
d->workspacemodel->importResourceFile(filename);
});
connect(workspaceMenu->addAction(i18nc("@action:inmenu", "&New Workspace...")),
&QAction::triggered,
[=]() {
QString name = QInputDialog::getText(this, i18nc("@title:window", "New Workspace..."),
i18nc("@label:textbox", "Name:"));
if (name.isEmpty()) return;
auto rserver = KisResourceServerProvider::instance()->workspaceServer();
KisWorkspaceResource* workspace = new KisWorkspaceResource("");
workspace->setDockerState(m_this->saveState());
d->viewManager->resourceProvider()->notifySavingWorkspace(workspace);
workspace->setValid(true);
QString saveLocation = rserver->saveLocation();
bool newName = false;
if(name.isEmpty()) {
newName = true;
name = i18n("Workspace");
}
QFileInfo fileInfo(saveLocation + name + workspace->defaultFileExtension());
int i = 1;
while (fileInfo.exists()) {
fileInfo.setFile(saveLocation + name + QString("%1").arg(i) + workspace->defaultFileExtension());
i++;
}
workspace->setFilename(fileInfo.filePath());
if(newName) {
name = i18n("Workspace %1", i);
}
workspace->setName(name);
rserver->addResource(workspace);
});
// TODO: What to do about delete?
// workspaceMenu->addAction(i18nc("@action:inmenu", "&Delete Workspace..."));
menu->addSeparator();
menu->addAction(d->close);
menu->addAction(d->closeAll);
if (d->mdiArea->viewMode() == QMdiArea::SubWindowView) {
menu->addSeparator();
menu->addAction(d->mdiTile);
menu->addAction(d->mdiCascade);
}
menu->addSeparator();
menu->addAction(d->mdiNextWindow);
menu->addAction(d->mdiPreviousWindow);
menu->addSeparator();
QList windows = d->mdiArea->subWindowList();
for (int i = 0; i < windows.size(); ++i) {
QPointerchild = qobject_cast(windows.at(i)->widget());
if (child && child->document()) {
QString text;
if (i < 9) {
text = i18n("&%1 %2", i + 1, child->document()->url().toDisplayString());
}
else {
text = i18n("%1 %2", i + 1, child->document()->url().toDisplayString());
}
QAction *action = menu->addAction(text);
action->setIcon(qApp->windowIcon());
action->setCheckable(true);
action->setChecked(child == activeKisView());
connect(action, SIGNAL(triggered()), d->windowMapper, SLOT(map()));
d->windowMapper->setMapping(action, windows.at(i));
}
}
bool showMdiArea = windows.count( ) > 0;
if (!showMdiArea) {
showWelcomeScreen(true); // see workaround in function in header
// keep the recent file list updated when going back to welcome screen
reloadRecentFileList();
d->welcomePage->populateRecentDocuments();
}
// enable/disable the toolbox docker if there are no documents open
Q_FOREACH (QObject* widget, children()) {
if (widget->inherits("QDockWidget")) {
QDockWidget* dw = static_cast(widget);
if ( dw->objectName() == "ToolBox") {
dw->setEnabled(showMdiArea);
}
}
}
updateCaption();
}
void KisMainWindow::setActiveSubWindow(QWidget *window)
{
if (!window) return;
QMdiSubWindow *subwin = qobject_cast(window);
//dbgKrita << "setActiveSubWindow();" << subwin << d->activeSubWindow;
if (subwin && subwin != d->activeSubWindow) {
KisView *view = qobject_cast(subwin->widget());
//dbgKrita << "\t" << view << activeView();
if (view && view != activeView()) {
d->mdiArea->setActiveSubWindow(subwin);
setActiveView(view);
}
d->activeSubWindow = subwin;
}
updateWindowMenu();
d->actionManager()->updateGUI();
}
void KisMainWindow::configChanged()
{
KisConfig cfg(true);
QMdiArea::ViewMode viewMode = (QMdiArea::ViewMode)cfg.readEntry("mdi_viewmode", (int)QMdiArea::TabbedView);
d->mdiArea->setViewMode(viewMode);
Q_FOREACH (QMdiSubWindow *subwin, d->mdiArea->subWindowList()) {
subwin->setOption(QMdiSubWindow::RubberBandMove, cfg.readEntry("mdi_rubberband", cfg.useOpenGL()));
subwin->setOption(QMdiSubWindow::RubberBandResize, cfg.readEntry("mdi_rubberband", cfg.useOpenGL()));
/**
* Dirty workaround for a bug in Qt (checked on Qt 5.6.1):
*
* If you make a window "Show on top" and then switch to the tabbed mode
* the window will contiue to be painted in its initial "mid-screen"
* position. It will persist here until you explicitly switch to its tab.
*/
if (viewMode == QMdiArea::TabbedView) {
Qt::WindowFlags oldFlags = subwin->windowFlags();
Qt::WindowFlags flags = oldFlags;
flags &= ~Qt::WindowStaysOnTopHint;
flags &= ~Qt::WindowStaysOnBottomHint;
if (flags != oldFlags) {
subwin->setWindowFlags(flags);
subwin->showMaximized();
}
}
}
KConfigGroup group( KSharedConfig::openConfig(), "theme");
d->themeManager->setCurrentTheme(group.readEntry("Theme", "Krita dark"));
d->actionManager()->updateGUI();
QBrush brush(cfg.getMDIBackgroundColor());
d->mdiArea->setBackground(brush);
QString backgroundImage = cfg.getMDIBackgroundImage();
if (backgroundImage != "") {
QImage image(backgroundImage);
QBrush brush(image);
d->mdiArea->setBackground(brush);
}
d->mdiArea->update();
}
KisView* KisMainWindow::newView(QObject *document)
{
KisDocument *doc = qobject_cast(document);
KisView *view = addViewAndNotifyLoadingCompleted(doc);
d->actionManager()->updateGUI();
return view;
}
void KisMainWindow::newWindow()
{
KisMainWindow *mainWindow = KisPart::instance()->createMainWindow();
mainWindow->initializeGeometry();
mainWindow->show();
}
void KisMainWindow::closeCurrentWindow()
{
d->mdiArea->currentSubWindow()->close();
d->actionManager()->updateGUI();
}
void KisMainWindow::checkSanity()
{
// print error if the lcms engine is not available
if (!KoColorSpaceEngineRegistry::instance()->contains("icc")) {
// need to wait 1 event since exiting here would not work.
m_errorMessage = i18n("The Krita LittleCMS color management plugin is not installed. Krita will quit now.");
m_dieOnError = true;
QTimer::singleShot(0, this, SLOT(showErrorAndDie()));
return;
}
KisPaintOpPresetResourceServer * rserver = KisResourceServerProvider::instance()->paintOpPresetServer();
if (rserver->resources().isEmpty()) {
m_errorMessage = i18n("Krita cannot find any brush presets! Krita will quit now.");
m_dieOnError = true;
QTimer::singleShot(0, this, SLOT(showErrorAndDie()));
return;
}
}
void KisMainWindow::showErrorAndDie()
{
QMessageBox::critical(0, i18nc("@title:window", "Installation error"), m_errorMessage);
if (m_dieOnError) {
exit(10);
}
}
void KisMainWindow::showAboutApplication()
{
KisAboutApplication dlg(this);
dlg.exec();
}
QPointer KisMainWindow::activeKisView()
{
if (!d->mdiArea) return 0;
QMdiSubWindow *activeSubWindow = d->mdiArea->activeSubWindow();
//dbgKrita << "activeKisView" << activeSubWindow;
if (!activeSubWindow) return 0;
return qobject_cast(activeSubWindow->widget());
}
void KisMainWindow::newOptionWidgets(KoCanvasController *controller, const QList > &optionWidgetList)
{
KIS_ASSERT_RECOVER_NOOP(controller == KoToolManager::instance()->activeCanvasController());
bool isOurOwnView = false;
Q_FOREACH (QPointer view, KisPart::instance()->views()) {
if (view && view->canvasController() == controller) {
isOurOwnView = view->mainWindow() == this;
}
}
if (!isOurOwnView) return;
Q_FOREACH (QWidget *w, optionWidgetList) {
#ifdef Q_OS_OSX
w->setAttribute(Qt::WA_MacSmallSize, true);
#endif
w->setFont(KoDockRegistry::dockFont());
}
if (d->toolOptionsDocker) {
d->toolOptionsDocker->setOptionWidgets(optionWidgetList);
}
else {
d->viewManager->paintOpBox()->newOptionWidgets(optionWidgetList);
}
}
void KisMainWindow::applyDefaultSettings(QPrinter &printer) {
if (!d->activeView) return;
QString title = d->activeView->document()->documentInfo()->aboutInfo("title");
if (title.isEmpty()) {
QFileInfo info(d->activeView->document()->url().fileName());
title = info.baseName();
}
if (title.isEmpty()) {
// #139905
title = i18n("%1 unsaved document (%2)", qApp->applicationDisplayName(),
QLocale().toString(QDate::currentDate(), QLocale::ShortFormat));
}
printer.setDocName(title);
}
void KisMainWindow::createActions()
{
KisActionManager *actionManager = d->actionManager();
actionManager->createStandardAction(KStandardAction::New, this, SLOT(slotFileNew()));
actionManager->createStandardAction(KStandardAction::Open, this, SLOT(slotFileOpen()));
actionManager->createStandardAction(KStandardAction::Quit, this, SLOT(slotFileQuit()));
actionManager->createStandardAction(KStandardAction::ConfigureToolbars, this, SLOT(slotConfigureToolbars()));
d->fullScreenMode = actionManager->createStandardAction(KStandardAction::FullScreen, this, SLOT(viewFullscreen(bool)));
d->recentFiles = KStandardAction::openRecent(this, SLOT(slotFileOpenRecent(QUrl)), actionCollection());
connect(d->recentFiles, SIGNAL(recentListCleared()), this, SLOT(saveRecentFiles()));
KSharedConfigPtr configPtr = KSharedConfig::openConfig();
d->recentFiles->loadEntries(configPtr->group("RecentFiles"));
d->saveAction = actionManager->createStandardAction(KStandardAction::Save, this, SLOT(slotFileSave()));
d->saveAction->setActivationFlags(KisAction::ACTIVE_IMAGE);
d->saveActionAs = actionManager->createStandardAction(KStandardAction::SaveAs, this, SLOT(slotFileSaveAs()));
d->saveActionAs->setActivationFlags(KisAction::ACTIVE_IMAGE);
// d->printAction = actionManager->createStandardAction(KStandardAction::Print, this, SLOT(slotFilePrint()));
// d->printAction->setActivationFlags(KisAction::ACTIVE_IMAGE);
// d->printActionPreview = actionManager->createStandardAction(KStandardAction::PrintPreview, this, SLOT(slotFilePrintPreview()));
// d->printActionPreview->setActivationFlags(KisAction::ACTIVE_IMAGE);
d->undo = actionManager->createStandardAction(KStandardAction::Undo, this, SLOT(undo()));
d->undo->setActivationFlags(KisAction::ACTIVE_IMAGE);
d->redo = actionManager->createStandardAction(KStandardAction::Redo, this, SLOT(redo()));
d->redo->setActivationFlags(KisAction::ACTIVE_IMAGE);
d->undoActionsUpdateManager.reset(new KisUndoActionsUpdateManager(d->undo, d->redo));
d->undoActionsUpdateManager->setCurrentDocument(d->activeView ? d->activeView->document() : 0);
// d->exportPdf = actionManager->createAction("file_export_pdf");
// connect(d->exportPdf, SIGNAL(triggered()), this, SLOT(exportToPdf()));
d->importAnimation = actionManager->createAction("file_import_animation");
connect(d->importAnimation, SIGNAL(triggered()), this, SLOT(importAnimation()));
d->closeAll = actionManager->createAction("file_close_all");
connect(d->closeAll, SIGNAL(triggered()), this, SLOT(slotFileCloseAll()));
// d->reloadFile = actionManager->createAction("file_reload_file");
// d->reloadFile->setActivationFlags(KisAction::CURRENT_IMAGE_MODIFIED);
// connect(d->reloadFile, SIGNAL(triggered(bool)), this, SLOT(slotReloadFile()));
d->importFile = actionManager->createAction("file_import_file");
connect(d->importFile, SIGNAL(triggered(bool)), this, SLOT(slotImportFile()));
d->exportFile = actionManager->createAction("file_export_file");
connect(d->exportFile, SIGNAL(triggered(bool)), this, SLOT(slotExportFile()));
/* The following entry opens the document information dialog. Since the action is named so it
intends to show data this entry should not have a trailing ellipses (...). */
d->showDocumentInfo = actionManager->createAction("file_documentinfo");
connect(d->showDocumentInfo, SIGNAL(triggered(bool)), this, SLOT(slotDocumentInfo()));
d->themeManager->setThemeMenuAction(new KActionMenu(i18nc("@action:inmenu", "&Themes"), this));
d->themeManager->registerThemeActions(actionCollection());
connect(d->themeManager, SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged()));
connect(d->themeManager, SIGNAL(signalThemeChanged()), d->welcomePage, SLOT(slotUpdateThemeColors()));
d->toggleDockers = actionManager->createAction("view_toggledockers");
KisConfig(true).showDockers(true);
d->toggleDockers->setChecked(true);
connect(d->toggleDockers, SIGNAL(toggled(bool)), SLOT(toggleDockersVisibility(bool)));
actionCollection()->addAction("settings_dockers_menu", d->dockWidgetMenu);
actionCollection()->addAction("window", d->windowMenu);
d->mdiCascade = actionManager->createAction("windows_cascade");
connect(d->mdiCascade, SIGNAL(triggered()), d->mdiArea, SLOT(cascadeSubWindows()));
d->mdiTile = actionManager->createAction("windows_tile");
connect(d->mdiTile, SIGNAL(triggered()), d->mdiArea, SLOT(tileSubWindows()));
d->mdiNextWindow = actionManager->createAction("windows_next");
connect(d->mdiNextWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activateNextSubWindow()));
d->mdiPreviousWindow = actionManager->createAction("windows_previous");
connect(d->mdiPreviousWindow, SIGNAL(triggered()), d->mdiArea, SLOT(activatePreviousSubWindow()));
d->newWindow = actionManager->createAction("view_newwindow");
connect(d->newWindow, SIGNAL(triggered(bool)), this, SLOT(newWindow()));
d->close = actionManager->createAction("file_close");
connect(d->close, SIGNAL(triggered()), SLOT(closeCurrentWindow()));
d->showSessionManager = actionManager->createAction("file_sessions");
connect(d->showSessionManager, SIGNAL(triggered(bool)), this, SLOT(slotShowSessionManager()));
actionManager->createStandardAction(KStandardAction::Preferences, this, SLOT(slotPreferences()));
for (int i = 0; i < 2; i++) {
d->expandingSpacers[i] = new KisAction(i18n("Expanding Spacer"));
d->expandingSpacers[i]->setDefaultWidget(new QWidget(this));
d->expandingSpacers[i]->defaultWidget()->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
actionManager->addAction(QString("expanding_spacer_%1").arg(i), d->expandingSpacers[i]);
}
}
void KisMainWindow::applyToolBarLayout()
{
const bool isPlastiqueStyle = style()->objectName() == "plastique";
Q_FOREACH (KToolBar *toolBar, toolBars()) {
toolBar->layout()->setSpacing(4);
if (isPlastiqueStyle) {
toolBar->setContentsMargins(0, 0, 0, 2);
}
//Hide text for buttons with an icon in the toolbar
Q_FOREACH (QAction *ac, toolBar->actions()){
if (ac->icon().pixmap(QSize(1,1)).isNull() == false){
ac->setPriority(QAction::LowPriority);
}else {
ac->setIcon(QIcon());
}
}
}
}
void KisMainWindow::initializeGeometry()
{
// if the user didn's specify the geometry on the command line (does anyone do that still?),
// we first figure out some good default size and restore the x,y position. See bug 285804Z.
KConfigGroup cfg = d->windowStateConfig;
QByteArray geom = QByteArray::fromBase64(cfg.readEntry("ko_geometry", QByteArray()));
if (!restoreGeometry(geom)) {
const int scnum = QApplication::desktop()->screenNumber(parentWidget());
QRect desk = QApplication::desktop()->availableGeometry(scnum);
// if the desktop is virtual then use virtual screen size
if (QApplication::desktop()->isVirtualDesktop()) {
desk = QApplication::desktop()->availableGeometry(QApplication::desktop()->screen(scnum));
}
quint32 x = desk.x();
quint32 y = desk.y();
quint32 w = 0;
quint32 h = 0;
// Default size -- maximize on small screens, something useful on big screens
const int deskWidth = desk.width();
if (deskWidth > 1024) {
// a nice width, and slightly less than total available
// height to componensate for the window decs
w = (deskWidth / 3) * 2;
h = (desk.height() / 3) * 2;
}
else {
w = desk.width();
h = desk.height();
}
x += (desk.width() - w) / 2;
y += (desk.height() - h) / 2;
move(x,y);
setGeometry(geometry().x(), geometry().y(), w, h);
}
d->fullScreenMode->setChecked(isFullScreen());
}
void KisMainWindow::showManual()
{
QDesktopServices::openUrl(QUrl("https://docs.krita.org"));
}
void KisMainWindow::moveEvent(QMoveEvent *e)
{
/**
* For checking if the display number has changed or not we should always use
* positional overload, not using QWidget overload. Otherwise we might get
* inconsistency, because screenNumber(widget) can return -1, but screenNumber(pos)
* will always return the nearest screen.
*/
const int oldScreen = qApp->desktop()->screenNumber(e->oldPos());
const int newScreen = qApp->desktop()->screenNumber(e->pos());
if (oldScreen != newScreen) {
KisConfigNotifier::instance()->notifyConfigChanged();
}
}
#include
diff --git a/libs/ui/KisMainWindow.h b/libs/ui/KisMainWindow.h
index d4e46a06bb..dfec33093f 100644
--- a/libs/ui/KisMainWindow.h
+++ b/libs/ui/KisMainWindow.h
@@ -1,500 +1,501 @@
/* This file is part of the KDE project
Copyright (C) 1998, 1999 Torben Weis
Copyright (C) 2000-2004 David Faure
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KIS_MAIN_WINDOW_H
#define KIS_MAIN_WINDOW_H
#include "kritaui_export.h"
#include
#include
#include
#include
#include
#include
#include
#include "KisView.h"
class QCloseEvent;
class QMoveEvent;
struct KoPageLayout;
class KoCanvasResourceManager;
class KisDocument;
class KisPrintJob;
class KoDockFactoryBase;
class QDockWidget;
class KisView;
class KisViewManager;
class KoCanvasController;
class KisWorkspaceResource;
/**
* @brief Main window for Krita
*
* This class is used to represent a main window within a Krita session. Each
* main window contains a menubar and some toolbars, and potentially several
* views of several canvases.
*
*/
class KRITAUI_EXPORT KisMainWindow : public KXmlGuiWindow, public KoCanvasSupervisor
{
Q_OBJECT
public:
enum OpenFlag {
None = 0,
Import = 0x1,
BatchMode = 0x2,
RecoveryFile = 0x4
};
Q_DECLARE_FLAGS(OpenFlags, OpenFlag)
public:
/**
* Constructor.
*
* Initializes a Calligra main window (with its basic GUI etc.).
*/
explicit KisMainWindow(QUuid id = QUuid());
/**
* Destructor.
*/
~KisMainWindow() override;
QUuid id() const;
/**
* @brief showView shows the given view. Override this if you want to show
* the view in a different way than by making it the central widget, for instance
* as an QMdiSubWindow
*/
virtual void showView(KisView *view);
/**
* @returns the currently active view
*/
KisView *activeView() const;
/**
* Sets the maximum number of recent documents entries.
*/
void setMaxRecentItems(uint _number);
/**
* The document opened a URL -> store into recent documents list.
*/
void addRecentURL(const QUrl &url);
/**
* get list of URL strings for recent files
*/
QList recentFilesUrls();
/**
* clears the list of the recent files
*/
void clearRecentFiles();
/**
* Load the desired document and show it.
* @param url the URL to open
*
* @return TRUE on success.
*/
bool openDocument(const QUrl &url, OpenFlags flags);
/**
* Activate a view containing the document in this window, creating one if needed.
*/
void showDocument(KisDocument *document);
/**
* Toggles between showing the welcome screen and the MDI area
*
* hack: There seems to be a bug that prevents events happening to the MDI area if it
* isn't actively displayed (set in the widgetStack). This can cause things like the title bar
* not to update correctly Before doing any actions related to opening or creating documents,
* make sure to switch this first to make sure everything can communicate to the MDI area correctly
*/
void showWelcomeScreen(bool show);
/**
* Saves the document, asking for a filename if necessary.
*
* @param saveas if set to TRUE the user is always prompted for a filename
* @param silent if set to TRUE rootDocument()->setTitleModified will not be called.
*
* @return TRUE on success, false on error or cancel
* (don't display anything in this case, the error dialog box is also implemented here
* but restore the original URL in slotFileSaveAs)
*/
bool saveDocument(KisDocument *document, bool saveas, bool isExporting);
void setReadWrite(bool readwrite);
/// Return the list of dock widgets belonging to this main window.
QList dockWidgets() const;
QDockWidget* dockWidget(const QString &id);
QList canvasObservers() const override;
KoCanvasResourceManager *resourceManager() const;
int viewCount() const;
void saveWindowState(bool restoreNormalState =false);
const KConfigGroup &windowStateConfig() const;
/**
* A wrapper around restoreState
* @param state the saved state
* @return TRUE on success
*/
bool restoreWorkspace(KisWorkspaceResource *workspace);
bool restoreWorkspaceState(const QByteArray &state);
static void swapWorkspaces(KisMainWindow *a, KisMainWindow *b);
KisViewManager *viewManager() const;
KisView *addViewAndNotifyLoadingCompleted(KisDocument *document);
QStringList showOpenFileDialog(bool isImporting);
/**
* Shows if the main window is saving anything right now. If the
* user presses Ctrl+W too fast, then the document can be close
* before the saving is completed. I'm not sure if it is fixable
* in any way without avoiding using porcessEvents()
* everywhere (DK)
*
* Don't use it unless you have no option.
*/
bool hackIsSaving() const;
/// Copy the given file into the bundle directory.
bool installBundle(const QString &fileName) const;
Q_SIGNALS:
+ void sigActiveDocumentChanged();
/**
* This signal is emitted if the document has been saved successfully.
*/
void documentSaved();
/// This signal is emitted when this windows has finished loading of a
/// document. The document may be opened in another window in the end.
/// In this case, the signal means there is no link between the window
/// and the document anymore.
void loadCompleted();
/// This signal is emitted right after the docker states have been succefully restored from config
void restoringDone();
/// This signal is emitted when the color theme changes
void themeChanged();
/// This signal is emitted when the shortcut key configuration has changed
void keyBindingsChanged();
void guiLoadingFinished();
public Q_SLOTS:
/**
* Slot for opening a new document.
*
* If the current document is empty, the new document replaces it.
* If not, a new mainwindow will be opened for showing the document.
*/
void slotFileNew();
/**
* Slot for opening a saved file.
*
* If the current document is empty, the opened document replaces it.
* If not a new mainwindow will be opened for showing the opened file.
*/
void slotFileOpen(bool isImporting = false);
/**
* Slot for opening a file among the recently opened files.
*
* If the current document is empty, the opened document replaces it.
* If not a new mainwindow will be opened for showing the opened file.
*/
void slotFileOpenRecent(const QUrl &);
/**
* @brief slotPreferences open the preferences dialog
*/
void slotPreferences();
/**
* Update caption from document info - call when document info
* (title in the about page) changes.
*/
void updateCaption();
/**
* Saves the current document with the current name.
*/
void slotFileSave();
void slotShowSessionManager();
// XXX: disabled
KisPrintJob* exportToPdf(QString pdfFileName = QString());
/**
* Update the option widgets to the argument ones, removing the currently set widgets.
*/
void newOptionWidgets(KoCanvasController *controller, const QList > & optionWidgetList);
KisView *newView(QObject *document);
void notifyChildViewDestroyed(KisView *view);
/// Set the active view, this will update the undo/redo actions
void setActiveView(KisView *view);
void subWindowActivated();
void windowFocused();
/**
* Reloads the recent documents list.
*/
void reloadRecentFileList();
private Q_SLOTS:
/**
* Save the list of recent files.
*/
void saveRecentFiles();
void slotLoadCompleted();
void slotLoadCanceled(const QString &);
void slotSaveCompleted();
void slotSaveCanceled(const QString &);
void forceDockTabFonts();
/**
* @internal
*/
void slotDocumentTitleModified();
/**
* Prints the actual document.
*/
void slotFilePrint();
/**
* Saves the current document with a new name.
*/
void slotFileSaveAs();
void slotFilePrintPreview();
void importAnimation();
/**
* Show a dialog with author and document information.
*/
void slotDocumentInfo();
/**
* Closes all open documents.
*/
bool slotFileCloseAll();
/**
* @brief showAboutApplication show the about box
*/
virtual void showAboutApplication();
/**
* Closes the mainwindow.
*/
void slotFileQuit();
/**
* Configure toolbars.
*/
void slotConfigureToolbars();
/**
* Post toolbar config.
* (Plug action lists back in, etc.)
*/
void slotNewToolbarConfig();
/**
* Shows or hides a toolbar
*/
void slotToolbarToggled(bool toggle);
/**
* Toggle full screen on/off.
*/
void viewFullscreen(bool fullScreen);
/**
* Reload file
*/
void slotReloadFile();
/**
* File --> Import
*
* This will call slotFileOpen().
*/
void slotImportFile();
/**
* File --> Export
*
* This will call slotFileSaveAs().
*/
void slotExportFile();
/**
* Hide the dockers
*/
void toggleDockersVisibility(bool visible);
/**
* Handle theme changes from theme manager
*/
void slotThemeChanged();
void undo();
void redo();
void updateWindowMenu();
void setActiveSubWindow(QWidget *window);
void configChanged();
void newWindow();
void closeCurrentWindow();
void checkSanity();
/// Quits Krita with error message from m_errorMessage.
void showErrorAndDie();
void initializeGeometry();
void showManual();
void switchTab(int index);
protected:
void closeEvent(QCloseEvent * e) override;
void resizeEvent(QResizeEvent * e) override;
// QWidget overrides
void dragEnterEvent(QDragEnterEvent * event) override;
void dropEvent(QDropEvent * event) override;
void dragMoveEvent(QDragMoveEvent * event) override;
void dragLeaveEvent(QDragLeaveEvent * event) override;
void moveEvent(QMoveEvent *e) override;
private:
/**
* Add a the given view to the list of views of this mainwindow.
* This is a private implementation. For public usage please use
* newView() and addViewAndNotifyLoadingCompleted().
*/
void addView(KisView *view);
friend class KisPart;
/**
* Returns the dockwidget specified by the @p factory. If the dock widget doesn't exist yet it's created.
* Add a "view_palette_action_menu" action to your view menu if you want to use closable dock widgets.
* @param factory the factory used to create the dock widget if needed
* @return the dock widget specified by @p factory (may be 0)
*/
QDockWidget* createDockWidget(KoDockFactoryBase* factory);
bool openDocumentInternal(const QUrl &url, KisMainWindow::OpenFlags flags = 0);
/**
* Updates the window caption based on the document info and path.
*/
void updateCaption(const QString & caption, bool mod);
void updateReloadFileAction(KisDocument *doc);
void saveWindowSettings();
QPointer activeKisView();
void applyDefaultSettings(QPrinter &printer);
void createActions();
void applyToolBarLayout();
QByteArray borrowWorkspace(KisMainWindow *borrower);
private:
/**
* Struct used in the list created by createCustomDocumentWidgets()
*/
struct CustomDocumentWidgetItem {
/// Pointer to the custom document widget
QWidget *widget;
/// title used in the sidebar. If left empty it will be displayed as "Custom Document"
QString title;
/// icon used in the sidebar. If left empty it will use the unknown icon
QString icon;
};
class Private;
Private * const d;
QString m_errorMessage;
bool m_dieOnError;
};
Q_DECLARE_OPERATORS_FOR_FLAGS(KisMainWindow::OpenFlags)
#endif
diff --git a/libs/widgets/KisPaletteComboBox.cpp b/libs/widgets/KisPaletteComboBox.cpp
index 876cd440b3..4534ea1f07 100644
--- a/libs/widgets/KisPaletteComboBox.cpp
+++ b/libs/widgets/KisPaletteComboBox.cpp
@@ -1,114 +1,114 @@
// Qt
#include
#include
#include
// STL
#include
#include "KisPaletteComboBox.h"
KisPaletteComboBox::KisPaletteComboBox(QWidget *parent)
: QComboBox(parent)
, m_completer(new QCompleter(QComboBox::model(), this))
, m_model(Q_NULLPTR)
{
m_completer->setCompletionMode(QCompleter::PopupCompletion);
m_completer->setCaseSensitivity(Qt::CaseInsensitive);
m_completer->setFilterMode(Qt::MatchContains);
setCompleter(m_completer.data());
connect(this, SIGNAL(currentIndexChanged(int)), SLOT(slotIndexSelected(int)));
}
KisPaletteComboBox::~KisPaletteComboBox()
{ }
void KisPaletteComboBox::setPaletteModel(const KisPaletteModel *paletteModel)
{
if (!m_model.isNull()) {
m_model->disconnect(this);
}
m_model = paletteModel;
if (m_model.isNull()) { return; }
slotPaletteChanged();
connect(m_model, SIGNAL(modelReset()),
SLOT(slotPaletteChanged()));
connect(m_model, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex&, const QVector &)),
SLOT(slotPaletteChanged()));
}
void KisPaletteComboBox::slotPaletteChanged()
{
if (QPointer(m_model->colorSet()).isNull()) { return; }
- int oldIdx = currentIndex();
+ blockSignals(true);
clear();
m_groupMapMap.clear();
m_idxSwatchMap.clear();
for (const QString &groupName : m_model->colorSet()->getGroupNames()) {
QVector infoList;
PosIdxMapType posIdxMap;
const KisSwatchGroup *group = m_model->colorSet()->getGroup(groupName);
for (const SwatchInfoType &info : group->infoList()) {
infoList.append(info);
}
std::sort(infoList.begin(), infoList.end(), swatchInfoLess);
for (const SwatchInfoType &info : infoList) {
const KisSwatch &swatch = info.swatch;
QString name = swatch.name();
if (!swatch.id().isEmpty()){
name = swatch.id() + " - " + swatch.name();
}
addItem(QIcon(createColorSquare(swatch)), name);
posIdxMap[SwatchPosType(info.column, info.row)] = count() - 1;
m_idxSwatchMap.push_back(swatch);
}
m_groupMapMap[group->name()] = posIdxMap;
}
-
- setCurrentIndex(oldIdx);
+ setCurrentIndex(0);
+ blockSignals(false);
}
bool KisPaletteComboBox::swatchInfoLess(const SwatchInfoType &first, const SwatchInfoType &second)
{
return first.swatch.name() < second.swatch.name();
}
QPixmap KisPaletteComboBox::createColorSquare(const KisSwatch &swatch) const
{
QPixmap colorSquare(32, 32);
if (swatch.spotColor()) {
QImage img = QImage(32, 32, QImage::Format_ARGB32);
QPainter circlePainter;
img.fill(Qt::transparent);
circlePainter.begin(&img);
QBrush brush = QBrush(Qt::SolidPattern);
brush.setColor(swatch.color().toQColor());
circlePainter.setBrush(brush);
QPen pen = circlePainter.pen();
pen.setColor(Qt::transparent);
pen.setWidth(0);
circlePainter.setPen(pen);
circlePainter.drawEllipse(0, 0, 32, 32);
circlePainter.end();
colorSquare = QPixmap::fromImage(img);
} else {
colorSquare.fill(swatch.color().toQColor());
}
return colorSquare;
}
void KisPaletteComboBox::slotSwatchSelected(const QModelIndex &index)
{
QString gName = qvariant_cast(index.data(KisPaletteModel::GroupNameRole));
int rowInGroup = qvariant_cast(index.data(KisPaletteModel::RowInGroupRole));
setCurrentIndex(m_groupMapMap[gName][SwatchPosType(index.column(), rowInGroup)]);
}
void KisPaletteComboBox::slotIndexSelected(int idx)
{
if (idx >= 0 && idx < m_idxSwatchMap.size()) {
emit sigColorSelected(m_idxSwatchMap[idx].color());
}
}
diff --git a/libs/widgets/KisPaletteListWidget.cpp b/libs/widgets/KisPaletteListWidget.cpp
index bcaf8c33a2..2e5519a4de 100644
--- a/libs/widgets/KisPaletteListWidget.cpp
+++ b/libs/widgets/KisPaletteListWidget.cpp
@@ -1,294 +1,294 @@
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "KisDlgPaletteEditor.h"
#include
#include "KisPaletteListWidget.h"
#include "KisPaletteListWidget_p.h"
/*
bool KisPaletteView::addGroupWithDialog()
{
KoDialog *window = new KoDialog();
window->setWindowTitle(i18nc("@title:window","Add a new group"));
QFormLayout *editableItems = new QFormLayout();
window->mainWidget()->setLayout(editableItems);
QLineEdit *lnName = new QLineEdit();
editableItems->addRow(i18nc("Name for a group", "Name"), lnName);
lnName->setText(i18nc("Part of default name for a new group", "Color Group")+""+QString::number(m_d->model->colorSet()->getGroupNames().size()+1));
if (window->exec() == KoDialog::Accepted) {
QString groupName = lnName->text();
m_d->model->addGroup(groupName);
m_d->model->colorSet()->save();
return true;
}
return false;
}
*/
KisPaletteListWidget::KisPaletteListWidget(QWidget *parent)
: QWidget(parent)
, m_ui(new Ui_WdgPaletteListWidget)
, m_d(new KisPaletteListWidgetPrivate(this))
{
m_d->allowModification = false;
m_d->actAdd.reset(new QAction(KisIconUtils::loadIcon("list-add"),
i18n("Add a new palette")));
m_d->actRemove.reset(new QAction(KisIconUtils::loadIcon("list-remove"),
i18n("Remove current palette")));
m_d->actModify.reset(new QAction(KisIconUtils::loadIcon("edit-rename"),
i18n("Rename choosen palette")));
m_d->actImport.reset(new QAction(KisIconUtils::loadIcon("document-import"),
i18n("Import a new palette from file")));
m_d->actExport.reset(new QAction(KisIconUtils::loadIcon("document-export"),
i18n("Export current palette to file")));
m_d->model->setColumnCount(1);
m_ui->setupUi(this);
m_ui->bnAdd->setDefaultAction(m_d->actAdd.data());
m_ui->bnRemove->setDefaultAction(m_d->actRemove.data());
m_ui->bnEdit->setDefaultAction(m_d->actModify.data());
m_ui->bnImport->setDefaultAction(m_d->actImport.data());
m_ui->bnExport->setDefaultAction(m_d->actExport.data());
connect(m_d->actAdd.data(), SIGNAL(triggered()), SLOT(slotAdd()));
connect(m_d->actRemove.data(), SIGNAL(triggered()), SLOT(slotRemove()));
connect(m_d->actModify.data(), SIGNAL(triggered()), SLOT(slotModify()));
connect(m_d->actImport.data(), SIGNAL(triggered()), SLOT(slotImport()));
connect(m_d->actExport.data(), SIGNAL(triggered()), SLOT(slotExport()));
m_d->itemChooser->setItemDelegate(m_d->delegate.data());
m_d->itemChooser->setRowHeight(40);
m_d->itemChooser->setColumnCount(1);
m_d->itemChooser->showButtons(false);
m_d->itemChooser->showTaggingBar(true);
m_ui->viewPalette->setLayout(new QHBoxLayout(m_ui->viewPalette));
m_ui->viewPalette->layout()->addWidget(m_d->itemChooser.data());
connect(m_d->itemChooser.data(), SIGNAL(resourceSelected(KoResource *)), SLOT(slotPaletteResourceSelected(KoResource*)));
m_d->itemChooser->setCurrentItem(0, 0);
}
KisPaletteListWidget::~KisPaletteListWidget()
{ }
void KisPaletteListWidget::slotPaletteResourceSelected(KoResource *r)
{
KoColorSet *g = static_cast(r);
if (g->isEditable() && m_d->allowModification) {
m_ui->bnAdd->setEnabled(true);
m_ui->bnRemove->setEnabled(true);
m_ui->bnEdit->setEnabled(true);
m_ui->bnImport->setEnabled(true);
m_ui->bnExport->setEnabled(true);
} else {
m_ui->bnAdd->setEnabled(false);
m_ui->bnRemove->setEnabled(false);
m_ui->bnEdit->setEnabled(false);
m_ui->bnImport->setEnabled(false);
m_ui->bnExport->setEnabled(false);
}
emit sigPaletteSelected(g);
}
void KisPaletteListWidget::slotAdd()
{
KoColorSet *newColorSet = new KoColorSet(newPaletteFileName());
newColorSet->setPaletteType(KoColorSet::KPL);
newColorSet->setIsGlobal(false);
newColorSet->setIsEditable(true);
newColorSet->setName("New Palette");
m_d->rAdapter->addResource(newColorSet);
m_d->itemChooser->setCurrentResource(newColorSet);
emit sigPaletteListChanged();
}
void KisPaletteListWidget::slotRemove()
{
if (m_d->itemChooser->currentResource()) {
- KoColorSet *group = static_cast(m_d->itemChooser->currentResource());
- if (!group || !group->isEditable()) {
+ KoColorSet *cs = static_cast(m_d->itemChooser->currentResource());
+ if (!cs || !cs->isEditable()) {
return;
}
- m_d->rAdapter->removeResource(m_d->itemChooser->currentResource());
- if (group->isGlobal()) {
- QFile::remove(m_d->itemChooser->currentResource()->filename());
+ if (cs->isGlobal()) {
+ QFile::remove(cs->filename());
}
+ m_d->rAdapter->removeResource(cs);
}
m_d->itemChooser->setCurrentItem(0, 0);
emit sigPaletteListChanged();
}
void KisPaletteListWidget::slotModify()
{
KisDlgPaletteEditor dlg;
KoColorSet *colorSet = static_cast(m_d->itemChooser->currentResource());
if (!colorSet) { return; }
dlg.setPalette(colorSet);
if (dlg.exec() != QDialog::Accepted){ return; }
if (!dlg.isModified()) { return; }
colorSet->setName(dlg.name());
colorSet->setColumnCount(dlg.columnCount());
if (dlg.isGlobal()) {
setPaletteGlobal(colorSet);
} else {
setPaletteNonGlobal(colorSet);
}
for (const QString &newGroupName : dlg.newGroupNames()) {
colorSet->addGroup(newGroupName);
colorSet->getGroup(newGroupName)->setRowCount(dlg.groupRowNumber(newGroupName));
}
for (const QString &groupName : colorSet->getGroupNames()) {
if (groupName == KoColorSet::GLOBAL_GROUP_NAME) {
continue;
}
if (dlg.groupRemoved(groupName)) {
colorSet->removeGroup(groupName, dlg.groupKeelColors(groupName));
continue;
}
colorSet->getGroup(groupName)->setRowCount(dlg.groupRowNumber(groupName));
if (!dlg.groupRenamedTo(groupName).isEmpty()) {
colorSet->changeGroupName(groupName, dlg.groupRenamedTo(groupName));
}
}
for (const KoResource *r : m_d->rAdapter->resources()) {
if (r != colorSet && r->filename() == dlg.filename()) {
QMessageBox msgFilenameDuplicate;
msgFilenameDuplicate.setWindowTitle(i18n("Duplicate filename"));
msgFilenameDuplicate.setText(i18n("Duplicate filename! Palette not saved."));
msgFilenameDuplicate.exec();
return;
}
}
colorSet->setFilename(dlg.filename());
emit sigPaletteSelected(colorSet); // to update elements in the docker
emit sigPaletteListChanged();
}
void KisPaletteListWidget::slotImport()
{
emit sigPaletteListChanged();
}
void KisPaletteListWidget::slotExport()
{
}
void KisPaletteListWidget::setPaletteGlobal(KoColorSet *colorSet)
{
if (QPointer(colorSet).isNull()) { return; }
KoResourceServer *rserver = KoResourceServerProvider::instance()->paletteServer();
QString saveLocation = rserver->saveLocation();
QString name = colorSet->filename();
QFileInfo fileInfo(saveLocation + name);
colorSet->setFilename(fileInfo.filePath());
colorSet->setIsGlobal(true);
}
void KisPaletteListWidget::setPaletteNonGlobal(KoColorSet *colorSet)
{
if (QPointer(colorSet).isNull()) { return; }
QString filename = newPaletteFileName();
QFile::remove(colorSet->filename());
colorSet->setFilename(filename);
colorSet->setIsGlobal(false);
}
void KisPaletteListWidget::setAllowModification(bool allowModification)
{
m_d->allowModification = allowModification;
}
QString KisPaletteListWidget::newPaletteFileName()
{
KoColorSet tmpColorSet;
QString result = "new_palette_";
QSet nameSet;
QList rlist = m_d->rAdapter->resources();
for (const KoResource *r : rlist) {
nameSet.insert(r->filename());
}
int i = 0;
while (nameSet.contains(result + QString::number(i) + tmpColorSet.defaultFileExtension())) {
i++;
}
result = result + QString::number(i) + tmpColorSet.defaultFileExtension();
return result;
}
/************************* KisPaletteListWidgetPrivate **********************/
KisPaletteListWidgetPrivate::KisPaletteListWidgetPrivate(KisPaletteListWidget *a_c)
: c(a_c)
, rAdapter(new KoResourceServerAdapter(KoResourceServerProvider::instance()->paletteServer()))
, itemChooser(new KoResourceItemChooser(rAdapter, a_c))
, model(new Model(rAdapter, a_c))
, delegate(new Delegate(a_c))
{ }
KisPaletteListWidgetPrivate::~KisPaletteListWidgetPrivate()
{ }
/******************* KisPaletteListWidgetPrivate::Delegate ******************/
KisPaletteListWidgetPrivate::Delegate::Delegate(QObject *parent)
: QAbstractItemDelegate(parent)
{ }
KisPaletteListWidgetPrivate::Delegate::~Delegate()
{ }
void KisPaletteListWidgetPrivate::Delegate::paint(QPainter * painter,
const QStyleOptionViewItem & option,
const QModelIndex & index) const
{
painter->save();
if (!index.isValid())
return;
KoResource* resource = static_cast(index.internalPointer());
KoColorSet* colorSet = static_cast(resource);
QRect previewRect(option.rect.x() + 2,
option.rect.y() + 2,
option.rect.height() - 4,
option.rect.height() - 4);
painter->drawImage(previewRect, colorSet->image());
if (option.state & QStyle::State_Selected) {
painter->fillRect(option.rect, option.palette.highlight());
painter->drawImage(previewRect, colorSet->image());
painter->setPen(option.palette.highlightedText().color());
} else {
painter->setBrush(option.palette.text().color());
}
painter->drawText(option.rect.x() + previewRect.width() + 10,
option.rect.y() + painter->fontMetrics().ascent() + 5,
colorSet->name());
painter->restore();
}
inline QSize KisPaletteListWidgetPrivate::Delegate::sizeHint(const QStyleOptionViewItem & option,
const QModelIndex &) const
{
return option.decorationSize;
}
diff --git a/libs/widgets/KisPaletteModel.cpp b/libs/widgets/KisPaletteModel.cpp
index dbca464191..18a4197285 100644
--- a/libs/widgets/KisPaletteModel.cpp
+++ b/libs/widgets/KisPaletteModel.cpp
@@ -1,449 +1,455 @@
/*
* Copyright (c) 2013 Sven Langkamp
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "KisPaletteModel.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
KisPaletteModel::KisPaletteModel(QObject* parent)
: QAbstractTableModel(parent)
, m_colorSet(0)
, m_displayRenderer(KoDumbColorDisplayRenderer::instance())
{
}
KisPaletteModel::~KisPaletteModel()
{
}
QVariant KisPaletteModel::data(const QModelIndex& index, int role) const
{
bool groupNameRow = m_groupNameRows.contains(index.row());
if (role == IsGroupNameRole) {
return groupNameRow;
}
if (groupNameRow) {
return dataForGroupNameRow(index, role);
} else {
return dataForSwatch(index, role);
}
}
int KisPaletteModel::rowCount(const QModelIndex& /*parent*/) const
{
if (!m_colorSet)
return 0;
return m_colorSet->rowCount() // count of color rows
+ m_groupNameRows.size() // rows for names
- 1; // global doesn't have a name
}
int KisPaletteModel::columnCount(const QModelIndex& /*parent*/) const
{
if (m_colorSet && m_colorSet->columnCount() > 0) {
return m_colorSet->columnCount();
}
if (!m_colorSet) {
return 0;
}
return 16;
}
Qt::ItemFlags KisPaletteModel::flags(const QModelIndex& index) const
{
if (index.isValid()) {
return Qt::ItemIsSelectable |
Qt::ItemIsEnabled |
Qt::ItemIsUserCheckable |
Qt::ItemIsDragEnabled |
Qt::ItemIsDropEnabled;
}
return Qt::ItemIsDropEnabled;
}
QModelIndex KisPaletteModel::index(int row, int column, const QModelIndex& parent) const
{
Q_UNUSED(parent);
Q_ASSERT(m_colorSet);
KisSwatchGroup *group = Q_NULLPTR;
int groupNameRow = groupNameRowForRow(row);
group = m_colorSet->getGroup(m_groupNameRows[groupNameRow]);
Q_ASSERT(group);
return createIndex(row, column, group);
}
void KisPaletteModel::setColorSet(KoColorSet* colorSet)
{
beginResetModel();
m_groupNameRows.clear();
m_colorSet = colorSet;
- int row = -1;
- for (const QString &groupName : m_colorSet->getGroupNames()) {
- m_groupNameRows[row] = groupName;
- row += m_colorSet->getGroup(groupName)->rowCount();
- row += 1; // row for group name
+ if (colorSet) {
+ int row = -1;
+ for (const QString &groupName : m_colorSet->getGroupNames()) {
+ m_groupNameRows[row] = groupName;
+ row += m_colorSet->getGroup(groupName)->rowCount();
+ row += 1; // row for group name
+ }
}
endResetModel();
}
KoColorSet* KisPaletteModel::colorSet() const
{
return m_colorSet;
}
int KisPaletteModel::rowNumberInGroup(int rowInModel) const
{
if (m_groupNameRows.contains(rowInModel)) {
return -1;
}
for (auto it = m_groupNameRows.keys().rbegin(); it != m_groupNameRows.keys().rend(); it++) {
if (*it < rowInModel) {
return rowInModel - *it - 1;
}
}
return rowInModel;
}
bool KisPaletteModel::addEntry(const KisSwatch &entry, const QString &groupName)
{
KisSwatchGroup *group = m_colorSet->getGroup(groupName);
beginResetModel();
if (group->checkEntry(m_colorSet->columnCount(), group->rowCount())) {
m_colorSet->add(entry, groupName);
} else {
m_colorSet->add(entry, groupName);
}
endResetModel();
m_colorSet->save();
+ emit sigPaletteModifed();
return true;
}
bool KisPaletteModel::removeEntry(const QModelIndex &index, bool keepColors)
{
if (!qvariant_cast(data(index, IsGroupNameRole))) {
static_cast(index.internalPointer())->removeEntry(index.column(),
rowNumberInGroup(index.row()));
emit dataChanged(index, index);
} else {
int groupNameRow = groupNameRowForRow(index.row());
QString groupName = m_groupNameRows[groupNameRow];
beginResetModel();
m_colorSet->removeGroup(groupName, keepColors);
m_groupNameRows.clear();
int row = -1;
for (const QString &groupName : m_colorSet->getGroupNames()) {
m_groupNameRows[row] = groupName;
row += m_colorSet->getGroup(groupName)->rowCount();
row += 1; // row for group name
}
endResetModel();
}
+ emit sigPaletteModifed();
return true;
}
bool KisPaletteModel::dropMimeData(const QMimeData *data, Qt::DropAction action,
int row, int column, const QModelIndex &parent)
{
Q_UNUSED(row);
Q_UNUSED(column);
if (!data->hasFormat("krita/x-colorsetentry") && !data->hasFormat("krita/x-colorsetgroup")) {
return false;
}
if (action == Qt::IgnoreAction) {
return false;
}
if (data->hasFormat("krita/x-colorsetgroup")) {
// dragging group not supported for now
/*
QByteArray encodedData = data->data("krita/x-colorsetgroup");
QDataStream stream(&encodedData, QIODevice::ReadOnly);
while (!stream.atEnd()) {
QString groupName;
stream >> groupName;
QModelIndex index = this->index(endRow, 0);
if (index.isValid()) {
QStringList entryList = qvariant_cast(index.data(RetrieveEntryRole));
QString groupDroppedOn = QString();
if (!entryList.isEmpty()) {
groupDroppedOn = entryList.at(0);
}
int groupIndex = colorSet()->getGroupNames().indexOf(groupName);
beginMoveRows( QModelIndex(), groupIndex, groupIndex, QModelIndex(), endRow);
m_colorSet->moveGroup(groupName, groupDroppedOn);
m_colorSet->save();
endMoveRows();
++endRow;
}
*/
return true;
}
QModelIndex finalIdx = parent;
if (!finalIdx.isValid()) { return false; }
if (qvariant_cast(finalIdx.data(KisPaletteModel::IsGroupNameRole))) {
return true;
}
QByteArray encodedData = data->data("krita/x-colorsetentry");
QDataStream stream(&encodedData, QIODevice::ReadOnly);
while (!stream.atEnd()) {
KisSwatch entry;
QString name, id;
bool spotColor;
QString oldGroupName;
int oriRow;
int oriColumn;
QString colorXml;
stream >> name >> id >> spotColor
>> oriRow >> oriColumn
>> oldGroupName
>> colorXml;
entry.setName(name);
entry.setId(id);
entry.setSpotColor(spotColor);
QDomDocument doc;
doc.setContent(colorXml);
QDomElement e = doc.documentElement();
QDomElement c = e.firstChildElement();
if (!c.isNull()) {
QString colorDepthId = c.attribute("bitdepth", Integer8BitsColorDepthID.id());
entry.setColor(KoColor::fromXML(c, colorDepthId));
}
if (action == Qt::MoveAction){
KisSwatchGroup *g = m_colorSet->getGroup(oldGroupName);
if (g) {
if (qvariant_cast(finalIdx.data(KisPaletteModel::CheckSlotRole))) {
g->setEntry(getEntry(finalIdx), oriColumn, oriRow);
} else {
g->removeEntry(oriColumn, oriRow);
}
}
setEntry(entry, finalIdx);
- emit dataChanged(finalIdx, finalIdx);
+ emit sigPaletteModifed();
if (m_colorSet->isGlobal()) {
m_colorSet->save();
}
}
}
return true;
}
QMimeData *KisPaletteModel::mimeData(const QModelIndexList &indexes) const
{
QMimeData *mimeData = new QMimeData();
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
QString mimeTypeName = "krita/x-colorsetentry";
QModelIndex index = indexes.last();
if (index.isValid()) {
if (qvariant_cast(index.data(IsGroupNameRole))==false) {
KisSwatch entry = getEntry(index);
QDomDocument doc;
QDomElement root = doc.createElement("Color");
root.setAttribute("bitdepth", entry.color().colorSpace()->colorDepthId().id());
doc.appendChild(root);
entry.color().toXML(doc, root);
stream << entry.name() << entry.id() << entry.spotColor()
<< rowNumberInGroup(index.row()) << index.column()
<< qvariant_cast(index.data(KisPaletteModel::GroupNameRole))
<< doc.toString();
} else {
mimeTypeName = "krita/x-colorsetgroup";
QStringList entryList = qvariant_cast(index.data(RetrieveEntryRole));
QString groupName = QString();
if (!entryList.isEmpty()) {
groupName = entryList.at(0);
}
stream << groupName;
}
}
mimeData->setData(mimeTypeName, encodedData);
return mimeData;
}
QStringList KisPaletteModel::mimeTypes() const
{
return QStringList() << "krita/x-colorsetentry" << "krita/x-colorsetgroup";
}
Qt::DropActions KisPaletteModel::supportedDropActions() const
{
return Qt::MoveAction;
}
void KisPaletteModel::setEntry(const KisSwatch &entry,
const QModelIndex &index)
{
KisSwatchGroup *group = static_cast(index.internalPointer());
Q_ASSERT(group);
group->setEntry(entry, index.column(), rowNumberInGroup(index.row()));
+ emit sigPaletteModifed();
emit dataChanged(index, index);
if (m_colorSet->isGlobal()) {
m_colorSet->save();
}
}
bool KisPaletteModel::renameGroup(const QString &groupName, const QString &newName)
{
beginResetModel();
bool success = m_colorSet->changeGroupName(groupName, newName);
for (auto it = m_groupNameRows.begin(); it != m_groupNameRows.end(); it++) {
if (it.value() == groupName) {
m_groupNameRows[it.key()] = newName;
break;
}
}
endResetModel();
+ emit sigPaletteModifed();
return success;
}
QVariant KisPaletteModel::dataForGroupNameRow(const QModelIndex &idx, int role) const
{
KisSwatchGroup *group = static_cast(idx.internalPointer());
Q_ASSERT(group);
QString groupName = group->name();
switch (role) {
case Qt::ToolTipRole:
case Qt::DisplayRole: {
return groupName;
}
case GroupNameRole: {
return groupName;
}
case CheckSlotRole: {
return true;
}
case RowInGroupRole: {
return -1;
}
default: {
return QVariant();
}
}
}
QVariant KisPaletteModel::dataForSwatch(const QModelIndex &idx, int role) const
{
KisSwatchGroup *group = static_cast(idx.internalPointer());
Q_ASSERT(group);
int rowInGroup = rowNumberInGroup(idx.row());
bool entryPresent = group->checkEntry(idx.column(), rowInGroup);
KisSwatch entry;
if (entryPresent) {
entry = group->getEntry(idx.column(), rowInGroup);
}
switch (role) {
case Qt::ToolTipRole:
case Qt::DisplayRole: {
return entryPresent ? entry.name() : i18n("Empty slot");
}
case Qt::BackgroundRole: {
QColor color(0, 0, 0, 0);
if (entryPresent) {
color = m_displayRenderer->toQColor(entry.color());
}
return QBrush(color);
}
case GroupNameRole: {
return group->name();
}
case CheckSlotRole: {
return entryPresent;
}
case RowInGroupRole: {
return rowInGroup;
}
default: {
return QVariant();
}
}
}
void KisPaletteModel::setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer)
{
if (displayRenderer) {
if (m_displayRenderer) {
disconnect(m_displayRenderer, 0, this, 0);
}
m_displayRenderer = displayRenderer;
connect(m_displayRenderer, SIGNAL(displayConfigurationChanged()),
SLOT(slotDisplayConfigurationChanged()));
} else {
m_displayRenderer = KoDumbColorDisplayRenderer::instance();
}
}
void KisPaletteModel::slotDisplayConfigurationChanged()
{
beginResetModel();
endResetModel();
}
QModelIndex KisPaletteModel::indexForClosest(const KoColor &compare)
{
KisSwatchGroup::SwatchInfo info = colorSet()->getClosestColorInfo(compare);
return createIndex(indexRowForInfo(info), info.column, colorSet()->getGroup(info.group));
}
int KisPaletteModel::indexRowForInfo(const KisSwatchGroup::SwatchInfo &info)
{
for (auto it = m_groupNameRows.begin(); it != m_groupNameRows.end(); it++) {
if (it.value() == info.group) {
return it.key() + info.row + 1;
}
}
return info.row;
}
KisSwatch KisPaletteModel::getEntry(const QModelIndex &index) const
{
KisSwatchGroup *group = static_cast(index.internalPointer());
if (!group || !group->checkEntry(index.column(), rowNumberInGroup(index.row()))) {
return KisSwatch();
}
return group->getEntry(index.column(), rowNumberInGroup(index.row()));
}
int KisPaletteModel::groupNameRowForRow(int rowInModel) const
{
return rowInModel - rowNumberInGroup(rowInModel) - 1;
}
diff --git a/libs/widgets/KisPaletteModel.h b/libs/widgets/KisPaletteModel.h
index 098156905f..1482f8949f 100644
--- a/libs/widgets/KisPaletteModel.h
+++ b/libs/widgets/KisPaletteModel.h
@@ -1,161 +1,164 @@
/*
* Copyright (c) 2013 Sven Langkamp
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef KIS_PALETTEMODEL_H
#define KIS_PALETTEMODEL_H
#include
#include
#include
#include
#include "kritawidgets_export.h"
#include
#include
class KoColorSet;
class KisPaletteView;
/**
* @brief The KisPaletteModel class
* This, together with KisPaletteView and KisPaletteDelegate forms a mvc way to access kocolorsets.
* A display renderer is given to this model to convert KoColor to QColor when
* colors are requested
*/
class KRITAWIDGETS_EXPORT KisPaletteModel : public QAbstractTableModel
{
Q_OBJECT
public:
KisPaletteModel(QObject* parent = 0);
~KisPaletteModel() override;
enum AdditionalRoles {
IsGroupNameRole = Qt::UserRole + 1,
RetrieveEntryRole,
CheckSlotRole,
GroupNameRole,
RowInGroupRole
};
public /* overriden methods */: // QAbstractTableModel
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
/**
* @brief index
* @param row
* @param column
* @param parent
* @return the index of for the data at row, column
* if the data is a color entry, the internal pointer points to the group
* the entry belongs to, and the row and column are row number and column
* number inside the group.
* if the data is a group, the row number and group number is Q_INFINIFY,
* and the internal pointer also points to the group
*/
QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
/**
* @brief dropMimeData
* This is an overridden function that handles dropped mimedata.
* right now only colorsetentries and colorsetgroups are handled.
* @return
*/
bool dropMimeData(const QMimeData *data, Qt::DropAction action,
int row, int column, const QModelIndex &parent) override;
/**
* @brief mimeData
* gives the mimedata for a kocolorsetentry or a kocolorsetgroup.
* @param indexes
* @return the mimedata for the given indices
*/
QMimeData *mimeData(const QModelIndexList &indexes) const override;
QStringList mimeTypes() const override;
Qt::DropActions supportedDropActions() const override;
/**
* @brief setData
* setData is not used as KoColor is not a QVariant
* use setEntry, addEntry and removeEntry instead
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
*/
+Q_SIGNALS:
+ void sigPaletteModifed();
+
public /* methods */:
/**
* @brief addEntry
* proper function to handle adding entries.
* @return whether successful.
*/
bool addEntry(const KisSwatch &entry,
const QString &groupName = KoColorSet::GLOBAL_GROUP_NAME);
void setEntry(const KisSwatch &entry, const QModelIndex &index);
/**
* @brief removeEntry
* proper function to remove the colorsetentry at the given index.
* The consolidtes both removeentry and removegroup.
* @param keepColors: This bool determines whether, when deleting a group,
* the colors should be added to the default group. This is usually desirable,
* so hence the default is true.
* @return if successful
*/
bool removeEntry(const QModelIndex &index, bool keepColors=true);
KisSwatch getEntry(const QModelIndex &index) const;
bool renameGroup(const QString &groupName, const QString &newName);
void setColorSet(KoColorSet* colorSet);
KoColorSet* colorSet() const;
QModelIndex indexForClosest(const KoColor &compare);
int indexRowForInfo(const KisSwatchGroup::SwatchInfo &info);
public Q_SLOTS:
private Q_SLOTS:
void slotDisplayConfigurationChanged();
private /* methods */:
QVariant dataForGroupNameRow(const QModelIndex &idx, int role) const;
QVariant dataForSwatch(const QModelIndex &idx, int role) const;
int rowNumberInGroup(int rowInModel) const;
int groupNameRowForRow(int rowInModel) const;
/**
* Installs a display renderer object for a palette that will
* convert the KoColor to the displayable QColor. Default is the
* dumb renderer.
*/
void setDisplayRenderer(const KoColorDisplayRendererInterface *displayRenderer);
private /* member variables */:
QPointer m_colorSet;
QPointer m_displayRenderer;
QMap m_groupNameRows;
friend class KisPaletteView;
};
#endif
diff --git a/plugins/dockers/palettedocker/PaletteListSaver.cpp b/plugins/dockers/palettedocker/PaletteListSaver.cpp
index f772cbac21..71915f19b2 100644
--- a/plugins/dockers/palettedocker/PaletteListSaver.cpp
+++ b/plugins/dockers/palettedocker/PaletteListSaver.cpp
@@ -1,53 +1,65 @@
#include "PaletteListSaver.h"
#include "palettedocker_dock.h"
#include
#include
#include