diff --git a/libs/ui/KisApplication.cpp b/libs/ui/KisApplication.cpp index 4d43185fc5..c8aa3014b5 100644 --- a/libs/ui/KisApplication.cpp +++ b/libs/ui/KisApplication.cpp @@ -1,743 +1,750 @@ /* This file is part of the KDE project Copyright (C) 1998, 1999 Torben Weis Copyright (C) 2009 Thomas Zander Copyright (C) 2012 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. */ #include "KisApplication.h" #include #ifdef Q_OS_WIN #include #include #endif #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 "KoGlobal.h" #include "KoConfig.h" #include #include #include #include "thememanager.h" #include "KisPrintJob.h" #include "KisDocument.h" #include "KisMainWindow.h" #include "KisAutoSaveRecoveryDialog.h" #include "KisPart.h" #include #include "kis_md5_generator.h" #include "kis_splash_screen.h" #include "kis_config.h" #include "flake/kis_shape_selection.h" #include #include #include #include #include #include #include #include "kisexiv2/kis_exiv2.h" #include "KisApplicationArguments.h" #include #include "kis_action_registry.h" #include #include #include #include "kis_image_barrier_locker.h" #include "opengl/kis_opengl.h" +#include "kis_spin_box_unit_manager.h" +#include "kis_document_aware_spin_box_unit_manager.h" #include namespace { const QTime appStartTime(QTime::currentTime()); } class KisApplicationPrivate { public: KisApplicationPrivate() : splashScreen(0) {} KisSplashScreen *splashScreen; }; class KisApplication::ResetStarting { public: ResetStarting(KisSplashScreen *splash = 0) : m_splash(splash) { } ~ResetStarting() { if (m_splash) { KConfigGroup cfg( KSharedConfig::openConfig(), "SplashScreen"); bool hideSplash = cfg.readEntry("HideSplashAfterStartup", false); if (hideSplash) { m_splash->hide(); } else { m_splash->setWindowFlags(Qt::Tool); QRect r(QPoint(), m_splash->size()); m_splash->move(QApplication::desktop()->availableGeometry().center() - r.center()); m_splash->setWindowTitle(qAppName()); m_splash->setParent(qApp->activeWindow()); Q_FOREACH (QObject *o, m_splash->children()) { QWidget *w = qobject_cast(o); if (w && w->isHidden()) { w->setVisible(true); } } m_splash->show(); } } } KisSplashScreen *m_splash; }; KisApplication::KisApplication(const QString &key, int &argc, char **argv) : QtSingleApplication(key, argc, argv) , d(new KisApplicationPrivate) , m_autosaveDialog(0) , m_mainWindow(0) , m_batchRun(false) { QCoreApplication::addLibraryPath(QCoreApplication::applicationDirPath()); setApplicationDisplayName("Krita"); setApplicationName("krita"); // Note: Qt docs suggest we set this, but if we do, we get resource paths of the form of krita/krita, which is weird. // setOrganizationName("krita"); setOrganizationDomain("krita.org"); QString version = KritaVersionWrapper::versionString(true); setApplicationVersion(version); setWindowIcon(KisIconUtils::loadIcon("calligrakrita")); if (qgetenv("KRITA_NO_STYLE_OVERRIDE").isEmpty()) { QStringList styles = QStringList() << "fusion" << "plastique"; if (!styles.contains(style()->objectName().toLower())) { Q_FOREACH (const QString & style, styles) { if (!setStyle(style)) { qDebug() << "No" << style << "available."; } else { qDebug() << "Set style" << style; break; } } } } else { qDebug() << "Style override disabled, using" << style()->objectName(); } KisOpenGL::initialize(); qDebug() << "krita has opengl" << KisOpenGL::hasOpenGL(); } #if defined(Q_OS_WIN) && defined(ENV32BIT) typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); LPFN_ISWOW64PROCESS fnIsWow64Process; BOOL isWow64() { BOOL bIsWow64 = FALSE; //IsWow64Process is not available on all supported versions of Windows. //Use GetModuleHandle to get a handle to the DLL that contains the function //and GetProcAddress to get a pointer to the function if available. fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress( GetModuleHandle(TEXT("kernel32")),"IsWow64Process"); if(0 != fnIsWow64Process) { if (!fnIsWow64Process(GetCurrentProcess(),&bIsWow64)) { //handle error } } return bIsWow64; } #endif void initializeGlobals(const KisApplicationArguments &args) { int dpiX = args.dpiX(); int dpiY = args.dpiY(); if (dpiX > 0 && dpiY > 0) { KoDpi::setDPI(dpiX, dpiY); } } void addResourceTypes() { // All Krita's resource types KoResourcePaths::addResourceType("kis_pics", "data", "/pics/"); KoResourcePaths::addResourceType("kis_images", "data", "/images/"); KoResourcePaths::addResourceType("icc_profiles", "data", "/profiles/"); KoResourcePaths::addResourceType("metadata_schema", "data", "/metadata/schemas/"); KoResourcePaths::addResourceType("kis_brushes", "data", "/brushes/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("kis_taskset", "data", "/taskset/"); KoResourcePaths::addResourceType("gmic_definitions", "data", "/gmic/"); KoResourcePaths::addResourceType("kis_resourcebundles", "data", "/bundles/"); KoResourcePaths::addResourceType("kis_defaultpresets", "data", "/defaultpresets/"); KoResourcePaths::addResourceType("kis_paintoppresets", "data", "/paintoppresets/"); KoResourcePaths::addResourceType("kis_workspaces", "data", "/workspaces/"); KoResourcePaths::addResourceType("psd_layer_style_collections", "data", "/asl"); KoResourcePaths::addResourceType("ko_patterns", "data", "/patterns/", true); KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/"); KoResourcePaths::addResourceType("ko_gradients", "data", "/gradients/", true); KoResourcePaths::addResourceType("ko_palettes", "data", "/palettes/", true); KoResourcePaths::addResourceType("kis_shortcuts", "data", "/shortcuts/"); KoResourcePaths::addResourceType("kis_actions", "data", "/actions"); KoResourcePaths::addResourceType("icc_profiles", "data", "/color/icc"); KoResourcePaths::addResourceType("ko_effects", "data", "/effects/"); KoResourcePaths::addResourceType("tags", "data", "/tags/"); KoResourcePaths::addResourceType("templates", "data", "/templates"); // // Extra directories to look for create resources. (Does anyone actually use that anymore?) // KoResourcePaths::addResourceDir("ko_gradients", "/usr/share/create/gradients/gimp"); // KoResourcePaths::addResourceDir("ko_gradients", QDir::homePath() + QString("/.create/gradients/gimp")); // KoResourcePaths::addResourceDir("ko_patterns", "/usr/share/create/patterns/gimp"); // KoResourcePaths::addResourceDir("ko_patterns", QDir::homePath() + QString("/.create/patterns/gimp")); // KoResourcePaths::addResourceDir("kis_brushes", "/usr/share/create/brushes/gimp"); // KoResourcePaths::addResourceDir("kis_brushes", QDir::homePath() + QString("/.create/brushes/gimp")); // KoResourcePaths::addResourceDir("ko_palettes", "/usr/share/create/swatches"); // KoResourcePaths::addResourceDir("ko_palettes", QDir::homePath() + QString("/.create/swatches")); // Make directories for all resources we can save, and tags QDir d; d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/tags/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/asl/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/bundles/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/gradients/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/paintoppresets/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/palettes/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/patterns/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/taskset/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/workspaces/"); d.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/input/"); } void KisApplication::loadResources() { setSplashScreenLoadingText(i18n("Loading Gradients...")); KoResourceServerProvider::instance()->gradientServer(true); // Load base resources setSplashScreenLoadingText(i18n("Loading Patterns...")); KoResourceServerProvider::instance()->patternServer(true); setSplashScreenLoadingText(i18n("Loading Palettes...")); KoResourceServerProvider::instance()->paletteServer(false); setSplashScreenLoadingText(i18n("Loading Brushes...")); KisBrushServer::instance()->brushServer(true); // load paintop presets setSplashScreenLoadingText(i18n("Loading Paint Operations...")); KisResourceServerProvider::instance()->paintOpPresetServer(true); setSplashScreenLoadingText(i18n("Loading Resource Bundles...")); KisResourceServerProvider::instance()->resourceBundleServer(); } void KisApplication::loadPlugins() { KoShapeRegistry* r = KoShapeRegistry::instance(); r->add(new KisShapeSelectionFactory()); KisActionRegistry::instance(); KisFilterRegistry::instance(); KisGeneratorRegistry::instance(); KisPaintOpRegistry::instance(); KoColorSpaceRegistry::instance(); // Load the krita-specific tools setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Tool...")); KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Tool"), QString::fromLatin1("[X-Krita-Version] == 28")); // Load dockers setSplashScreenLoadingText(i18n("Loading Plugins for Krita/Dock...")); KoPluginLoader::instance()->load(QString::fromLatin1("Krita/Dock"), QString::fromLatin1("[X-Krita-Version] == 28")); // XXX_EXIV: make the exiv io backends real plugins setSplashScreenLoadingText(i18n("Loading Plugins Exiv/IO...")); KisExiv2::initialize(); } bool KisApplication::start(const KisApplicationArguments &args) { #if defined(Q_OS_WIN) || defined (Q_OS_MAC) #ifdef ENV32BIT KisConfig cfg; if (isWow64() && !cfg.readEntry("WarnedAbout32Bits", false)) { QMessageBox::information(0, i18nc("@title:window", "Krita: Warning"), i18n("You are running a 32 bits build on a 64 bits Windows.\n" "This is not recommended.\n" "Please download and install the x64 build instead.")); cfg.writeEntry("WarnedAbout32Bits", true); } #endif #endif setSplashScreenLoadingText(i18n("Initializing Globals")); initializeGlobals(args); const bool doTemplate = args.doTemplate(); const bool print = args.print(); const bool exportAs = args.exportAs(); const bool exportAsPdf = args.exportAsPdf(); const QString exportFileName = args.exportFileName(); m_batchRun = (print || exportAs || exportAsPdf || !exportFileName.isEmpty()); // print & exportAsPdf do user interaction ATM const bool needsMainWindow = !exportAs; // only show the mainWindow when no command-line mode option is passed // TODO: fix print & exportAsPdf to work without mainwindow shown const bool showmainWindow = !exportAs; // would be !batchRun; const bool showSplashScreen = !m_batchRun && qgetenv("NOSPLASH").isEmpty() && qgetenv("XDG_CURRENT_DESKTOP") != "GNOME"; if (showSplashScreen) { d->splashScreen->show(); d->splashScreen->repaint(); processEvents(); } KoHashGeneratorProvider::instance()->setGenerator("MD5", new KisMD5Generator()); // Initialize all Krita directories etc. KoGlobal::initialize(); KConfigGroup group(KSharedConfig::openConfig(), "theme"); Digikam::ThemeManager themeManager; themeManager.setCurrentTheme(group.readEntry("Theme", "Krita dark")); ResetStarting resetStarting(d->splashScreen); // remove the splash when done Q_UNUSED(resetStarting); // Make sure we can save resources and tags setSplashScreenLoadingText(i18n("Adding resource types")); addResourceTypes(); // Load all resources and tags before the plugins do that loadResources(); // Load the plugins loadPlugins(); if (needsMainWindow) { // show a mainWindow asap, if we want that setSplashScreenLoadingText(i18n("Loading Main Window...")); m_mainWindow = KisPart::instance()->createMainWindow(); if (showmainWindow) { QTimer::singleShot(1, m_mainWindow, SLOT(show())); } } short int numberOfOpenDocuments = 0; // number of documents open // Check for autosave files that can be restored, if we're not running a batchrun (test, print, export to pdf) if (!m_batchRun) { checkAutosaveFiles(); } setSplashScreenLoadingText(""); // done loading, so clear out label + //configure the unit manager + KisSpinBoxUnitManagerFactory::setDefaultUnitManagerBuilder(new KisDocumentAwareSpinBoxUnitManagerBuilder()); + connect(this, &KisApplication::aboutToQuit, &KisSpinBoxUnitManagerFactory::clearUnitManagerBuilder); //ensure the builder is destroyed when the application leave. + //the new syntax slot syntax allow to connect to a non q_object static method. + // Get the command line arguments which we have to parse int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments short int nPrinted = 0; for (int argNumber = 0; argNumber < argsCount; argNumber++) { QString fileName = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { // called in mix with batch options? ignore and silently skip if (m_batchRun) { continue; } if (createNewDocFromTemplate(fileName, m_mainWindow)) { ++numberOfOpenDocuments; } // now try to load } else { if (exportAs) { QString outputMimetype = KisMimeDatabase::mimeTypeForFile(exportFileName); if (outputMimetype == "application/octetstream") { dbgKrita << i18n("Mimetype not found, try using the -mimetype option") << endl; return 1; } KisDocument *doc = KisPart::instance()->createDocument(); doc->setFileBatchMode(m_batchRun); doc->openUrl(QUrl::fromLocalFile(fileName)); qApp->processEvents(); // For vector layers to be updated KisImageBarrierLocker locker(doc->image()); KisImportExportFilter::ConversionStatus status = KisImportExportFilter::OK; KisImportExportManager manager(doc); manager.setBatchMode(true); QByteArray mime(outputMimetype.toLatin1()); status = manager.exportDocument(exportFileName, mime); if (status != KisImportExportFilter::OK) { dbgKrita << "Could not export " << fileName << "to" << exportFileName << ":" << (int)status; } nPrinted++; QTimer::singleShot(0, this, SLOT(quit())); } else if (m_mainWindow) { KisDocument *doc = KisPart::instance()->createDocument(); doc->setFileBatchMode(m_batchRun); if (m_mainWindow->openDocumentInternal(QUrl::fromLocalFile(fileName), doc)) { if (print) { m_mainWindow->slotFilePrint(); nPrinted++; // TODO: trigger closing of app once printing is done } else if (exportAsPdf) { KisPrintJob *job = m_mainWindow->exportToPdf(exportFileName); if (job) connect (job, SIGNAL(destroyed(QObject*)), m_mainWindow, SLOT(slotFileQuit()), Qt::QueuedConnection); nPrinted++; } else { // Normal case, success numberOfOpenDocuments++; } } else { // .... if failed // delete doc; done by openDocument } } } } if (m_batchRun) { return nPrinted > 0; } } // not calling this before since the program will quit there. return true; } KisApplication::~KisApplication() { delete d; } void KisApplication::setSplashScreen(QWidget *splashScreen) { d->splashScreen = qobject_cast(splashScreen); } void KisApplication::setSplashScreenLoadingText(QString textToLoad) { d->splashScreen->loadingLabel->setText(textToLoad); d->splashScreen->repaint(); } void KisApplication::hideSplashScreen() { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } } bool KisApplication::notify(QObject *receiver, QEvent *event) { try { return QApplication::notify(receiver, event); } catch (std::exception &e) { qWarning("Error %s sending event %i to object %s", e.what(), event->type(), qPrintable(receiver->objectName())); } catch (...) { qWarning("Error sending event %i to object %s", event->type(), qPrintable(receiver->objectName())); } return false; } void KisApplication::remoteArguments(QByteArray message, QObject *socket) { Q_UNUSED(socket); // check if we have any mainwindow KisMainWindow *mw = qobject_cast(qApp->activeWindow()); if (!mw) { mw = KisPart::instance()->mainWindows().first(); } if (!mw) { return; } KisApplicationArguments args = KisApplicationArguments::deserialize(message); const bool doTemplate = args.doTemplate(); const int argsCount = args.filenames().count(); if (argsCount > 0) { // Loop through arguments for (int argNumber = 0; argNumber < argsCount; ++argNumber) { QString filename = args.filenames().at(argNumber); // are we just trying to open a template? if (doTemplate) { createNewDocFromTemplate(filename, mw); } else if (QFile(filename).exists()) { KisDocument *doc = KisPart::instance()->createDocument(); doc->setFileBatchMode(m_batchRun); mw->openDocumentInternal(QUrl::fromLocalFile(filename), doc); } } } } void KisApplication::fileOpenRequested(const QString &url) { KisMainWindow *mainWindow = KisPart::instance()->mainWindows().first(); if (mainWindow) { KisDocument *doc = KisPart::instance()->createDocument(); doc->setFileBatchMode(m_batchRun); mainWindow->openDocumentInternal(QUrl::fromLocalFile(url), doc); } } void KisApplication::checkAutosaveFiles() { if (m_batchRun) return; // Check for autosave files from a previous run. There can be several, and // we want to offer a restore for every one. Including a nice thumbnail! QStringList filters; filters << QString(".krita-*-*-autosave.kra"); #ifdef Q_OS_WIN QDir dir = QDir::temp(); #else QDir dir = QDir::home(); #endif // all autosave files for our application m_autosaveFiles = dir.entryList(filters, QDir::Files | QDir::Hidden); // Allow the user to make their selection if (m_autosaveFiles.size() > 0) { if (d->splashScreen) { // hide the splashscreen to see the dialog d->splashScreen->hide(); } m_autosaveDialog = new KisAutoSaveRecoveryDialog(m_autosaveFiles, activeWindow()); connect(m_autosaveDialog, SIGNAL(finished(int)), this, SLOT(onAutoSaveFinished(int))); m_autosaveDialog->exec(); } } void KisApplication::onAutoSaveFinished(int result) { if (m_batchRun) return; #ifdef Q_OS_WIN QDir dir = QDir::temp(); #else QDir dir = QDir::home(); #endif if (result == QDialog::Accepted) { QStringList filesToRecover = m_autosaveDialog->recoverableFiles(); Q_FOREACH (const QString &autosaveFile, m_autosaveFiles) { if (!filesToRecover.contains(autosaveFile)) { QFile::remove(dir.absolutePath() + "/" + autosaveFile); } } m_autosaveFiles = filesToRecover; } else { m_autosaveFiles.clear(); } QList autosaveUrls; if (m_autosaveFiles.size() > 0) { Q_FOREACH (const QString &autoSaveFile, m_autosaveFiles) { const QUrl url = QUrl::fromLocalFile(dir.absolutePath() + QLatin1Char('/') + autoSaveFile); autosaveUrls << url; } } if (m_mainWindow) { Q_FOREACH (const QUrl &url, autosaveUrls) { KisDocument *doc = KisPart::instance()->createDocument(); doc->setFileBatchMode(m_batchRun); m_mainWindow->openDocumentInternal(url, doc); } } } bool KisApplication::createNewDocFromTemplate(const QString &fileName, KisMainWindow *mainWindow) { QString templatePath; const QUrl templateUrl = QUrl::fromLocalFile(fileName); if (QFile::exists(fileName)) { templatePath = templateUrl.toLocalFile(); dbgUI << "using full path..."; } else { QString desktopName(fileName); const QString templatesResourcePath = QStringLiteral("templates/"); QStringList paths = KoResourcePaths::findAllResources("data", templatesResourcePath + "*/" + desktopName); if (paths.isEmpty()) { paths = KoResourcePaths::findAllResources("data", templatesResourcePath + desktopName); } if (paths.isEmpty()) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("No template found for: %1", desktopName)); } else if (paths.count() > 1) { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Too many templates found for: %1", desktopName)); } else { templatePath = paths.at(0); } } if (!templatePath.isEmpty()) { QUrl templateBase; templateBase.setPath(templatePath); KDesktopFile templateInfo(templatePath); QString templateName = templateInfo.readUrl(); QUrl templateURL; templateURL.setPath(templateBase.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash).path() + '/' + templateName); KisDocument *doc = KisPart::instance()->createDocument(); doc->setFileBatchMode(m_batchRun); if (mainWindow->openDocumentInternal(templateURL, doc)) { doc->resetURL(); doc->setEmpty(); doc->setTitleModified(); dbgUI << "Template loaded..."; return true; } else { QMessageBox::critical(0, i18nc("@title:window", "Krita"), i18n("Template %1 failed to load.", templateURL.toDisplayString())); } } return false; } void KisApplication::clearConfig() { KIS_ASSERT_RECOVER_RETURN(qApp->thread() == QThread::currentThread()); KSharedConfigPtr config = KSharedConfig::openConfig(); // find user settings file bool createDir = false; QString kritarcPath = KoResourcePaths::locateLocal("config", "kritarc", createDir); QFile configFile(kritarcPath); if (configFile.exists()) { // clear file if (configFile.open(QFile::WriteOnly)) { configFile.close(); } else { QMessageBox::warning(0, i18nc("@title:window", "Krita"), i18n("Failed to clear %1\n\n" "Please make sure no other program is using the file and try again.", kritarcPath), QMessageBox::Ok, QMessageBox::Ok); } } // reload from disk; with the user file settings cleared, // this should load any default configuration files shipping with the program config->reparseConfiguration(); config->sync(); } void KisApplication::askClearConfig() { Qt::KeyboardModifiers mods = QApplication::queryKeyboardModifiers(); bool askClearConfig = (mods & Qt::ControlModifier) && (mods & Qt::ShiftModifier) && (mods & Qt::AltModifier); if (askClearConfig) { bool ok = QMessageBox::question(0, i18nc("@title:window", "Krita"), i18n("Do you want to clear the settings file?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes; if (ok) { clearConfig(); } } } diff --git a/libs/ui/utils/kis_document_aware_spin_box_unit_manager.cpp b/libs/ui/utils/kis_document_aware_spin_box_unit_manager.cpp index c7eabf31c5..425becbdeb 100644 --- a/libs/ui/utils/kis_document_aware_spin_box_unit_manager.cpp +++ b/libs/ui/utils/kis_document_aware_spin_box_unit_manager.cpp @@ -1,148 +1,153 @@ /* * Copyright (c) 2016 Laurent Valentin Jospin * * 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 "kis_document_aware_spin_box_unit_manager.h" #include "KisPart.h" #include "KisMainWindow.h" #include "KisView.h" #include "KisDocument.h" #include "kis_types.h" #include "kis_image.h" #include "kis_image_animation_interface.h" #include "kis_time_range.h" +KisSpinBoxUnitManager* KisDocumentAwareSpinBoxUnitManagerBuilder::buildUnitManager(QObject* parent) +{ + return new KisDocumentAwareSpinBoxUnitManager(parent); +} + void KisDocumentAwareSpinBoxUnitManager::setDocumentAwarnessToExistingUnitSpinBox(KisDoubleParseUnitSpinBox* spinBox, bool setUnitFromOutsideToggle) { KisDocumentAwareSpinBoxUnitManager* manager = new KisDocumentAwareSpinBoxUnitManager(spinBox); spinBox->setUnitManager(manager); spinBox->setUnitChangeFromOutsideBehavior(setUnitFromOutsideToggle); } KisDoubleParseUnitSpinBox* KisDocumentAwareSpinBoxUnitManager::createUnitSpinBoxWithDocumentAwarness(QWidget* parent) { KisDoubleParseUnitSpinBox* spinBox = new KisDoubleParseUnitSpinBox(parent); setDocumentAwarnessToExistingUnitSpinBox(spinBox); return spinBox; } KisDocumentAwareSpinBoxUnitManager::KisDocumentAwareSpinBoxUnitManager(QObject *parent, int pPixDir): KisSpinBoxUnitManager(parent) { if (pPixDir == PIX_DIR_Y) { pixDir = PIX_DIR_Y; } else { pixDir = PIX_DIR_X; } grantDocumentRelativeUnits(); //the purpose of this class is to manage document relative units. } qreal KisDocumentAwareSpinBoxUnitManager::getConversionFactor(UnitDimension dim, QString symbol) const { qreal factor = KisSpinBoxUnitManager::getConversionFactor(dim, symbol); if (factor > 0) { //no errors occured at a lower level, so the conversion factor has been get. return factor; } factor = 1; //fall back to something natural in case document is unreachable (1 px = 1 pt = 1vw = 1vh). So a virtual document of 100x100 with a resolution of 1. KisView* view = KisPart::instance()->currentMainwindow()->activeView(); if (view == nullptr) { return factor; } KisDocument* doc = view->document(); if (doc == nullptr) { return factor; } KisImage* img = doc->image().data(); if (img == nullptr) { return factor; } qreal resX = img->xRes(); qreal resY = img->yRes(); qreal sizeX = img->width(); qreal sizeY = img->height(); switch (dim) { case LENGTH: if (symbol == "px") { if (pixDir == PIX_DIR_X) { factor = resX; } else { factor = resY; } } else if (symbol == "vw") { qreal docWidth = sizeX/resX; factor = 100.0/docWidth; //1 vw is 1% of document width, 1 vw in point is docWidth/100 so 1 point in vw is the inverse. } else if (symbol == "vh") { qreal docHeight = sizeY/resY; factor = 100.0/docHeight; } break; case TIME: { if (symbol == "s") { qreal fps = img->animationInterface()->framerate(); factor = 1/fps; } else if (symbol == "%") { const KisTimeRange & time_range = img->animationInterface()->fullClipRange(); qreal n_frame = time_range.end() - time_range.start(); factor = 100/n_frame; } } break; default: break; } return factor; } qreal KisDocumentAwareSpinBoxUnitManager::getConversionConstant(UnitDimension dim, QString symbol) const { if (dim == TIME && symbol == "%") { KisImage* img = KisPart::instance()->currentMainwindow()->activeView()->document()->image().data(); const KisTimeRange & time_range = img->animationInterface()->fullClipRange(); qreal n_frame = time_range.end() - time_range.start(); return -time_range.start()*100.0/n_frame; } return KisSpinBoxUnitManager::getConversionConstant(dim, symbol); } diff --git a/libs/ui/utils/kis_document_aware_spin_box_unit_manager.h b/libs/ui/utils/kis_document_aware_spin_box_unit_manager.h index 226cdbad5e..da01dadc6f 100644 --- a/libs/ui/utils/kis_document_aware_spin_box_unit_manager.h +++ b/libs/ui/utils/kis_document_aware_spin_box_unit_manager.h @@ -1,55 +1,58 @@ /* * Copyright (c) 2016 Laurent Valentin Jospin * * 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 KISDOCUMENTAWARESPINBOXUNITMANAGER_H #define KISDOCUMENTAWARESPINBOXUNITMANAGER_H #include "kis_spin_box_unit_manager.h" #include "kis_double_parse_unit_spin_box.h" #include "kritaui_export.h" - +class KisDocumentAwareSpinBoxUnitManagerBuilder : public KisSpinBoxUnitManagerBuilder +{ + KisSpinBoxUnitManager* buildUnitManager(QObject* parent); +}; class KRITAUI_EXPORT KisDocumentAwareSpinBoxUnitManager : public KisSpinBoxUnitManager { Q_OBJECT public: enum PixDir { PIX_DIR_X, PIX_DIR_Y }; //in case the image has not the same x and y resolution, indicate on which direction get the resolution. //! \brief configure a KisDocumentAwareSpinBoxUnitManager for the given spinbox (make the manager a child of the spinbox and attach it to the spinbox). static void setDocumentAwarnessToExistingUnitSpinBox(KisDoubleParseUnitSpinBox* spinBox, bool setUnitFromOutsideToggle = false); //! \brief create a unitSpinBox that is already document aware. static KisDoubleParseUnitSpinBox* createUnitSpinBoxWithDocumentAwarness(QWidget* parent = 0); KisDocumentAwareSpinBoxUnitManager(QObject *parent = 0, int pPixDir = PIX_DIR_X); virtual qreal getConversionFactor(UnitDimension dim, QString symbol) const; virtual qreal getConversionConstant(UnitDimension dim, QString symbol) const; private: PixDir pixDir; }; #endif // KISDOCUMENTAWARESPINBOXUNITMANAGER_H diff --git a/libs/widgets/kis_double_parse_unit_spin_box.cpp b/libs/widgets/kis_double_parse_unit_spin_box.cpp index 809c761c10..9e956399b0 100644 --- a/libs/widgets/kis_double_parse_unit_spin_box.cpp +++ b/libs/widgets/kis_double_parse_unit_spin_box.cpp @@ -1,348 +1,357 @@ /* * Copyright (c) 2016 Laurent Valentin Jospin * * 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 "kis_double_parse_unit_spin_box.h" #include "kis_spin_box_unit_manager.h" #include class Q_DECL_HIDDEN KisDoubleParseUnitSpinBox::Private { public: - Private(double low, double up, double step) + Private(double low, double up, double step, KisSpinBoxUnitManager* unitManager) : lowerInPoints(low), upperInPoints(up), stepInPoints(step), unit(KoUnit(KoUnit::Point)), - unitManager(new KisSpinBoxUnitManager()), + unitManager(unitManager), defaultUnitManager(unitManager), isDeleting(false), unitHasBeenChangedFromOutSideOnce(false), letUnitBeChangedFromOutsideMoreThanOnce(true) { } double lowerInPoints; ///< lowest value in points double upperInPoints; ///< highest value in points double stepInPoints; ///< step in points KoUnit unit; KisSpinBoxUnitManager* unitManager; //manage more units than permitted by KoUnit. KisSpinBoxUnitManager* defaultUnitManager; //the default unit manager is the one the spinbox rely on and go back to if a connected unit manager is destroyed before the spinbox. bool isDeleting; bool unitHasBeenChangedFromOutSideOnce; //in some part of the code the unit is reset. We want to prevent this overriding the unit defined by the user. We use this switch to do so. bool letUnitBeChangedFromOutsideMoreThanOnce; }; KisDoubleParseUnitSpinBox::KisDoubleParseUnitSpinBox(QWidget *parent) : KisDoubleParseSpinBox(parent), - d(new Private(-9999, 9999, 1)) + d(new Private(-9999, 9999, 1, KisSpinBoxUnitManagerFactory::buildDefaultUnitManager(this))) { setUnit( KoUnit(KoUnit::Point) ); setAlignment( Qt::AlignRight ); connect(this, SIGNAL(valueChanged( double )), SLOT(privateValueChanged())); connect(lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(detectUnitChanges()) ); + + } KisDoubleParseUnitSpinBox::~KisDoubleParseUnitSpinBox() { d->isDeleting = true; delete d->defaultUnitManager; delete d; } void KisDoubleParseUnitSpinBox::setUnitManager(KisSpinBoxUnitManager* unitManager) { qreal oldVal = d->unitManager->getReferenceValue(KisDoubleParseSpinBox::value()); QString oldSymbol = d->unitManager->getApparentUnitSymbol(); if (oldSymbol == unitManager->getApparentUnitSymbol() && d->unitManager->getUnitDimensionType() == unitManager->getUnitDimensionType()) { d->unitManager = unitManager; //set the new unitmanager anyway, since it may be a subclass, so change the behavior anyway. return; } qreal newVal; if (d->unitManager->getUnitDimensionType() != unitManager->getUnitDimensionType()) { //dimension is the same, calculate the new value newVal = unitManager->getApparentValue(oldVal); } else { newVal = unitManager->getApparentValue(d->lowerInPoints); } double newMin = unitManager->getApparentValue(d->lowerInPoints); double newMax = unitManager->getApparentValue(d->upperInPoints); double newStep = unitManager->getApparentValue(d->stepInPoints); if (unitManager->getApparentUnitSymbol() == KoUnit(KoUnit::Pixel).symbol()) { // limit the pixel step by 1.0 newStep = qMax(qreal(1.0), newStep); } KisDoubleParseSpinBox::setMinimum(newMin); KisDoubleParseSpinBox::setMaximum(newMax); KisDoubleParseSpinBox::setSingleStep(newStep); if (d->unitManager != d->defaultUnitManager) { disconnect(d->unitManager, &QObject::destroyed, this, &KisDoubleParseUnitSpinBox::disconnectExternalUnitManager); //there's no dependance anymore. } d->unitManager = unitManager; connect(d->unitManager, &QObject::destroyed, this, &KisDoubleParseUnitSpinBox::disconnectExternalUnitManager); KisDoubleParseSpinBox::setValue(newVal); } void KisDoubleParseUnitSpinBox::changeValue( double newValue ) { if (d->unitManager->getApparentValue(newValue) == KisDoubleParseSpinBox::value()) { return; } KisDoubleParseSpinBox::setValue( d->unitManager->getApparentValue(newValue) ); } void KisDoubleParseUnitSpinBox::setUnit( const KoUnit & unit) { if (d->unitHasBeenChangedFromOutSideOnce && !d->letUnitBeChangedFromOutsideMoreThanOnce) { return; } if (d->unitManager->getUnitDimensionType() != KisSpinBoxUnitManager::LENGTH) { d->unitManager->setUnitDim(KisSpinBoxUnitManager::LENGTH); //setting the unit using a KoUnit mean you want to use a length. } setUnit(unit.symbol()); d->unit = unit; } void KisDoubleParseUnitSpinBox::setUnit(const QString &symbol) { double oldValue = d->unitManager->getReferenceValue(KisDoubleParseSpinBox::value()); QString oldSymbol = d->unitManager->getApparentUnitSymbol(); if (symbol == oldSymbol) { return; } d->unitManager->setApparentUnitFromSymbol(symbol); if (d->unitManager->getApparentUnitSymbol() == oldSymbol) { //the setApparentUnitFromSymbol is a bit clever, for example in regard of Casesensitivity. So better check like this. return; } KisDoubleParseSpinBox::setMinimum( d->unitManager->getApparentValue( d->lowerInPoints ) ); KisDoubleParseSpinBox::setMaximum( d->unitManager->getApparentValue( d->upperInPoints ) ); qreal step = d->unitManager->getApparentValue( d->stepInPoints ); if (symbol == KoUnit(KoUnit::Pixel).symbol()) { // limit the pixel step by 1.0 step = qMax(qreal(1.0), step); } KisDoubleParseSpinBox::setSingleStep( step ); KisDoubleParseSpinBox::setValue( d->unitManager->getApparentValue( oldValue ) ); d->unitHasBeenChangedFromOutSideOnce = true; } void KisDoubleParseUnitSpinBox::setDimensionType(int dim) { if (!KisSpinBoxUnitManager::isUnitId(dim)) { return; } d->unitManager->setUnitDim((KisSpinBoxUnitManager::UnitDimension) dim); } double KisDoubleParseUnitSpinBox::value( ) const { return d->unitManager->getReferenceValue( KisDoubleParseSpinBox::value() ); } void KisDoubleParseUnitSpinBox::setMinimum(double min) { d->lowerInPoints = min; KisDoubleParseSpinBox::setMinimum( d->unitManager->getApparentValue( min ) ); } void KisDoubleParseUnitSpinBox::setMaximum(double max) { d->upperInPoints = max; KisDoubleParseSpinBox::setMaximum( d->unitManager->getApparentValue( max ) ); } void KisDoubleParseUnitSpinBox::setLineStep(double step) { d->stepInPoints = d->unitManager->getReferenceValue(step); KisDoubleParseSpinBox::setSingleStep( step ); } void KisDoubleParseUnitSpinBox::setLineStepPt(double step) { d->stepInPoints = step; KisDoubleParseSpinBox::setSingleStep( d->unitManager->getApparentValue( step ) ); } void KisDoubleParseUnitSpinBox::setMinMaxStep( double min, double max, double step ) { setMinimum( min ); setMaximum( max ); setLineStepPt( step ); } QValidator::State KisDoubleParseUnitSpinBox::validate(QString &input, int &pos) const { Q_UNUSED(pos); QRegExp regexp ("([ a-zA-Z]+)$"); // Letters or spaces at end const int res = input.indexOf( regexp ); if ( res == -1 ) { // Nothing like an unit? The user is probably editing the unit return QValidator::Intermediate; } QString expr ( input.left( res ) ); const QString unitName ( regexp.cap( 1 ).trimmed().toLower() ); bool ok = true; bool interm = false; QValidator::State exprState = KisDoubleParseSpinBox::validate(expr, pos); if (exprState == QValidator::Invalid) { return exprState; } else if (exprState == QValidator::Intermediate) { interm = true; } //check if we can parse the unit. QStringList listOfSymbol = d->unitManager->getsUnitSymbolList(); ok = listOfSymbol.contains(unitName); if (!ok || interm) { return QValidator::Intermediate; } return QValidator::Acceptable; } QString KisDoubleParseUnitSpinBox::textFromValue( double value ) const { QString txt = KisDoubleParseSpinBox::textFromValue(value); if (!txt.endsWith(d->unitManager->getApparentUnitSymbol())) { txt += " " + d->unitManager->getApparentUnitSymbol(); } return txt; } QString KisDoubleParseUnitSpinBox::veryCleanText() const { return makeTextClean(cleanText()); } double KisDoubleParseUnitSpinBox::valueFromText( const QString& str ) const { QString txt = makeTextClean(str); return KisDoubleParseSpinBox::valueFromText(txt); //this function will take care of prefix (and don't mind if suffix has been removed. } void KisDoubleParseUnitSpinBox::setUnitChangeFromOutsideBehavior(bool toggle) { d->letUnitBeChangedFromOutsideMoreThanOnce = toggle; } void KisDoubleParseUnitSpinBox::privateValueChanged() { emit valueChangedPt( value() ); } QString KisDoubleParseUnitSpinBox::detectUnit() { QString str = veryCleanText().trimmed(); //text with the new unit but not the old one. QRegExp regexp ("([ ]*[a-zA-Z]+[ ]*)$"); // Letters or spaces at end int res = str.indexOf( regexp ); if (res > -1) { QString expr ( str.right( str.size() - res ) ); expr = expr.trimmed(); return expr; } return ""; } void KisDoubleParseUnitSpinBox::detectUnitChanges() { QString unitSymb = detectUnit(); if (unitSymb.isEmpty()) { return; } + QString oldUnitSymb = d->unitManager->getApparentUnitSymbol(); + setUnit(unitSymb); setValue(valueFromText(cleanText())); //change value keep the old value, but converted to new unit... which is different from the value the user entered in the new unit. So we need to set the new value. + + if (oldUnitSymb != d->unitManager->getApparentUnitSymbol()) { + // the user has changed the unit, so we block changes from outside. + setUnitChangeFromOutsideBehavior(false); + } } QString KisDoubleParseUnitSpinBox::makeTextClean(QString const& txt) const { QString expr = txt; QString symbol = d->unitManager->getApparentUnitSymbol(); if ( expr.endsWith(suffix()) ) { expr.remove(expr.size()-suffix().size(), suffix().size()); } expr = expr.trimmed(); if ( expr.endsWith(symbol) ) { expr.remove(expr.size()-symbol.size(), symbol.size()); } return expr; } void KisDoubleParseUnitSpinBox::disconnectExternalUnitManager() { if (!d->isDeleting) { setUnitManager(d->defaultUnitManager); //go back to default unit manager. } } diff --git a/libs/widgetutils/kis_spin_box_unit_manager.cpp b/libs/widgetutils/kis_spin_box_unit_manager.cpp index 0201f6ada4..207f0f4ee6 100644 --- a/libs/widgetutils/kis_spin_box_unit_manager.cpp +++ b/libs/widgetutils/kis_spin_box_unit_manager.cpp @@ -1,421 +1,451 @@ /* * Copyright (c) 2016 Laurent Valentin Jospin * * 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 "kis_spin_box_unit_manager.h" #include "KoUnit.h" #include #include + +KisSpinBoxUnitManagerBuilder* KisSpinBoxUnitManagerFactory::builder = nullptr; + +KisSpinBoxUnitManager* KisSpinBoxUnitManagerFactory::buildDefaultUnitManager(QObject* parent) +{ + if (builder == nullptr) { + return new KisSpinBoxUnitManager(parent); + } + + return builder->buildUnitManager(parent); +} + +void KisSpinBoxUnitManagerFactory::setDefaultUnitManagerBuilder(KisSpinBoxUnitManagerBuilder* pBuilder) +{ + if (builder != nullptr) { + delete builder; //The factory took over the lifecycle of the builder, so it delete it when replaced. + } + + builder = pBuilder; +} + +void KisSpinBoxUnitManagerFactory::clearUnitManagerBuilder() +{ + if (builder != nullptr) { + delete builder; //The factory took over the lifecycle of the builder, so it delete it when replaced. + } + + builder = nullptr; +} + const QStringList KisSpinBoxUnitManager::referenceUnitSymbols = {"pt", "°", "frame"}; const QStringList KisSpinBoxUnitManager::documentRelativeLengthUnitSymbols = {"px", "vw", "vh"}; //px are relative to the resolution, vw and vh to the width and height. const QStringList KisSpinBoxUnitManager::documentRelativeTimeUnitSymbols = {"s", "%"}; //secondes are relative to the framerate, % to the sequence length. class Q_DECL_HIDDEN KisSpinBoxUnitManager::Private { public: Private(KisSpinBoxUnitManager::UnitDimension pDim = KisSpinBoxUnitManager::LENGTH, QString pUnitSymbol = "pt", double pConv = 1.0): dim(pDim), unitSymbol(pUnitSymbol), conversionFactor(pConv), conversionFactorIsFixed(true), conversionConstant(0), conversionConstantIsFixed(true), constrains(0), unitListCached(false), hasHundredPercent(false), canAccessDocument(false) { } KisSpinBoxUnitManager::UnitDimension dim; QString unitSymbol; mutable double conversionFactor; bool conversionFactorIsFixed; //tell if it's possible to trust the conversion factor stored or if it's needed to recompute it. mutable double conversionConstant; bool conversionConstantIsFixed; //tell if it's possible to trust the conversion constant stored or if it's needed to recompute it. KisSpinBoxUnitManager::Constrains constrains; mutable QStringList unitList; mutable bool unitListCached; mutable QStringList unitListWithName; mutable bool unitListWithNameCached; //it's possible to store a reference for the % unit, for lenght. bool hasHundredPercent; qreal hundredPercent; bool canAccessDocument; }; KisSpinBoxUnitManager::KisSpinBoxUnitManager(QObject *parent) : QObject(parent) { d = new Private(); } KisSpinBoxUnitManager::~KisSpinBoxUnitManager() { delete d; } int KisSpinBoxUnitManager::getUnitDimensionType() const { return d->dim; } QString KisSpinBoxUnitManager::getReferenceUnitSymbol() const { return referenceUnitSymbols[d->dim]; } QString KisSpinBoxUnitManager::getApparentUnitSymbol() const { return d->unitSymbol; } int KisSpinBoxUnitManager::getApparentUnitId() const { QStringList list = getsUnitSymbolList(); return list.indexOf(d->unitSymbol); } QStringList KisSpinBoxUnitManager::getsUnitSymbolList(bool withName) const{ QStringList list; //TODO: cache if (withName) { if (d->unitListWithNameCached) { return d->unitListWithName; } } else { if (d->unitListCached) { return d->unitList; } } switch (d->dim) { case LENGTH: for (int i = 0; i < KoUnit::TypeCount; i++) { if (KoUnit::Type(i) == KoUnit::Pixel) { continue; //skip pixel, which is a document relative unit, in the base classe. } if (withName) { list << KoUnit::unitDescription(KoUnit::Type(i)); } else { list << KoUnit(KoUnit::Type(i)).symbol(); } } if (d->canAccessDocument) { // ad document relative units if (withName) { list << KoUnit::unitDescription(KoUnit::Pixel) << i18n("view width (vw)") << i18n("view height (vh)"); } else { list << documentRelativeLengthUnitSymbols; } } break; case ANGLE: if (withName) { list << i18n("degrees (°)") << i18n("radians (rad)") << i18n("gons (gon)") << i18n("percent of circle (%)"); } else { list << "°" << "rad" << "gon" << "%"; } break; case TIME: if (withName) { list << i18n("frames (f)"); } else { list << "f"; } if (d->canAccessDocument) { if (withName) { list << i18n("seconds (s)") << i18n("percent of animation (%)"); } else { list << documentRelativeTimeUnitSymbols; } } break; } if (withName) { d->unitListWithName = list; d->unitListWithNameCached = true; } else { d->unitList = list; d->unitListCached = true; } return list; } qreal KisSpinBoxUnitManager::getConversionConstant(UnitDimension dim, QString symbol) const { return 0; // all units managed here are transform via a linear function, so this wll alway be 0 in this class. } qreal KisSpinBoxUnitManager::getReferenceValue(double apparentValue) const { if (!d->conversionFactorIsFixed) { recomputeConversionFactor(); } if(!d->conversionConstantIsFixed) { recomputeConvesrionConstant(); } qreal v = (apparentValue - d->conversionConstant)/d->conversionFactor; if (d->constrains &= REFISINT) { v = qFloor(v); } return v; } qreal KisSpinBoxUnitManager::getApparentValue(double refValue) const { if (!d->conversionFactorIsFixed) { recomputeConversionFactor(); } if(!d->conversionConstantIsFixed) { recomputeConvesrionConstant(); } qreal v = refValue*d->conversionFactor + d->conversionConstant; if (d->constrains &= VALISINT) { v = qFloor(v); } return v; } qreal KisSpinBoxUnitManager::getConversionFactor(UnitDimension dim, QString symbol) const { qreal factor = -1; switch (dim) { case LENGTH: do { if (symbol == "px") { break; } bool ok; KoUnit unit = KoUnit::fromSymbol(symbol, &ok); if (! ok) { break; } factor = unit.toUserValue(1.0); } while (0) ; break; case ANGLE: if (symbol == "°") { factor = 1.0; break; } if (symbol == "rad") { factor = acos(-1)/90.0; break; } if (symbol == "gon") { factor = 10.0/9.0; break; } if (symbol == "%") { factor = 2.5/9.0; //(25% of circle is 90°) break; } break; case TIME: if (symbol != "f") { //we have only frames for the moment. break; } factor = 1.0; break; } return factor; } void KisSpinBoxUnitManager::setUnitDim(UnitDimension dim) { if (dim == d->dim) { return; } d->dim = dim; d->unitSymbol = referenceUnitSymbols[d->dim]; //Active dim is reference dim when just changed. d->conversionFactor = 1.0; emit unitDimensionChanged(d->dim); } void KisSpinBoxUnitManager::setApparentUnitFromSymbol(QString pSymbol) { QString symbol = pSymbol.trimmed(); if (symbol == d->unitSymbol) { return; } QString newSymb = ""; switch (d->dim) { case ANGLE: if (symbol.toLower() == "deg") { newSymb = "°"; break; } goto default_indentifier; //alway do default after handling possible special cases. default_indentifier: default: QStringList list = getsUnitSymbolList(); if (list.contains(symbol, Qt::CaseInsensitive)) { for (QString str : list) { if (str.toLower() == symbol.toLower()) { newSymb = str; //official symbol may contain capitals letters, so better take the official version. break; } } break; } } if(newSymb.isEmpty()) { return; //abort if it was impossible to locate the correct symbol. } if (d->canAccessDocument) { //manage document relative units. QStringList speUnits; switch (d->dim) { case LENGTH: speUnits = documentRelativeLengthUnitSymbols; goto default_identifier_conv_fact; case TIME: speUnits = documentRelativeTimeUnitSymbols; goto default_identifier_conv_fact; default_identifier_conv_fact: default: if (speUnits.isEmpty()) { d->conversionFactorIsFixed = true; break; } if (speUnits.contains(newSymb)) { d->conversionFactorIsFixed = false; break; } d->conversionFactorIsFixed = true; break; } if (d->dim == TIME) { if (newSymb == "%") { d->conversionConstantIsFixed = false; } } else { d->conversionConstantIsFixed = true; } } qreal conversFact = getConversionFactor(d->dim, newSymb); qreal oldConversFact = d->conversionFactor; d->conversionFactor = conversFact; emit conversionFactorChanged(d->conversionFactor, oldConversFact); d->unitSymbol = newSymb; emit unitChanged(newSymb); } void KisSpinBoxUnitManager::recomputeConversionFactor() const { if (d->conversionFactorIsFixed) { return; } qreal oldConversionFactor = d->conversionFactor; d->conversionFactor = getConversionFactor(d->dim, d->unitSymbol); if (oldConversionFactor != d->conversionFactor) { emit conversionFactorChanged(d->conversionFactor, oldConversionFactor); } } void KisSpinBoxUnitManager::recomputeConvesrionConstant() const { if (d->conversionConstantIsFixed) { return; } qreal oldConversionConstant = d->conversionConstant; d->conversionConstant = getConversionConstant(d->dim, d->unitSymbol); } void KisSpinBoxUnitManager::grantDocumentRelativeUnits() { d->canAccessDocument = true; } diff --git a/libs/widgetutils/kis_spin_box_unit_manager.h b/libs/widgetutils/kis_spin_box_unit_manager.h index 1fedbdc787..65c75d8330 100644 --- a/libs/widgetutils/kis_spin_box_unit_manager.h +++ b/libs/widgetutils/kis_spin_box_unit_manager.h @@ -1,114 +1,140 @@ /* * Copyright (c) 2016 Laurent Valentin Jospin * * 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 KISSPINBOXUNITMANAGER_H #define KISSPINBOXUNITMANAGER_H #include #include #include "kritawidgetutils_export.h" +class KisSpinBoxUnitManager; +class KisSpinBoxUnitManagerBuilder; +class KisSpinBoxUnitManagerFactory; + +class KRITAWIDGETUTILS_EXPORT KisSpinBoxUnitManagerFactory +{ +public: + + static KisSpinBoxUnitManager* buildDefaultUnitManager(QObject* parent); + //! \brief set a builder the factory can use. The factory should take on the lifecycle of the builder, so to delete it call clearUnitManagerBuilder(); + static void setDefaultUnitManagerBuilder(KisSpinBoxUnitManagerBuilder* pBuilder); + static void clearUnitManagerBuilder(); + +private: + + static KisSpinBoxUnitManagerBuilder* builder; + +}; + +class KRITAWIDGETUTILS_EXPORT KisSpinBoxUnitManagerBuilder +{ + +public: + + virtual KisSpinBoxUnitManager* buildUnitManager(QObject* parent) = 0; //this pure virtual function is used to build a unitmanager, it will be used by the unitManagerFactory. +}; /** * @brief The KisSpinBoxUnitManager class is an abstract interface for the unitspinboxes classes to manage different type of units. * * The class make a difference between unit dimension (distance, angle, time). * * The class allow to converte values between reference unit and apparent unit, but also to get other information like possible units symbols. * * This class don't allow to use relative units (units which conversion factor is dependant of the context), even if it's private data are prepared to manage it. * The reason for this is that from the library of this class it's very hard to acess easily the informations needed. So all will be managed by subclasses in other libs. * */ class KRITAWIDGETUTILS_EXPORT KisSpinBoxUnitManager : public QObject { Q_OBJECT public: enum UnitDimension{ LENGTH = 0, ANGLE = 1, TIME = 2 }; static inline bool isUnitId(int code) { return (code == LENGTH || code == ANGLE || code == TIME); } //! \brief this list hold the symbols of the referenc unit per dimension. The index is equal to the value in UnitDimension so that the dimension name can be used to index the list. static const QStringList referenceUnitSymbols; enum Constrain{ NOCONSTR = 0, REFISINT = 1, VALISINT = 2 }; Q_DECLARE_FLAGS(Constrains, Constrain) explicit KisSpinBoxUnitManager(QObject *parent = 0); ~KisSpinBoxUnitManager(); int getUnitDimensionType() const; QString getReferenceUnitSymbol() const; QString getApparentUnitSymbol() const; //! \brief get the position of the apparent unit in the list of units. It is usefull if we want to build a model for combo-box based unit management. int getApparentUnitId() const; virtual QStringList getsUnitSymbolList(bool withName = false) const; qreal getReferenceValue(double apparentValue) const; qreal getApparentValue(double refValue) const; //! \brief gets the conversion factor of a managed unit, or -1 in case of error. This method is the one that need to be overridden to extend the ability of the KisSpinBoxUnitManager. virtual qreal getConversionFactor(UnitDimension dim, QString symbol) const; //! \brief some units conversions are done via an affine transform, not just a linear transform. This function gives the constant of this affine transform (usually 0). virtual qreal getConversionConstant(UnitDimension dim, QString symbol) const; Q_SIGNALS: void unitDimensionChanged(int dimCode); void unitChanged(QString symbol); void conversionFactorChanged(qreal newConversionFactor, qreal oldConversionFactor) const; void unitListChanged(); public Q_SLOTS: void setUnitDim(UnitDimension dim); void setApparentUnitFromSymbol(QString pSymbol); protected: class Private; Private * d; //unit's that may be used only if acess to the document informations exists. static const QStringList documentRelativeLengthUnitSymbols; static const QStringList documentRelativeTimeUnitSymbols; void recomputeConversionFactor() const; void recomputeConvesrionConstant() const; //! \brief calling this method give acess to document relative units. Only subclasses that manage thoses units should call it. void grantDocumentRelativeUnits(); }; #endif // KISSPINBOXUNITMANAGER_H diff --git a/plugins/flake/pathshapes/rectangle/RectangleShapeConfigWidget.cpp b/plugins/flake/pathshapes/rectangle/RectangleShapeConfigWidget.cpp index ccaef06b84..bc6b809651 100644 --- a/plugins/flake/pathshapes/rectangle/RectangleShapeConfigWidget.cpp +++ b/plugins/flake/pathshapes/rectangle/RectangleShapeConfigWidget.cpp @@ -1,87 +1,84 @@ /* This file is part of the KDE project * Copyright (C) 2007 Jan Hambrecht * * 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 "RectangleShapeConfigWidget.h" #include "RectangleShape.h" #include "RectangleShapeConfigCommand.h" #include "kis_document_aware_spin_box_unit_manager.h" RectangleShapeConfigWidget::RectangleShapeConfigWidget() { widget.setupUi(this); - KisDocumentAwareSpinBoxUnitManager::setDocumentAwarnessToExistingUnitSpinBox(widget.cornerRadiusX); - KisDocumentAwareSpinBoxUnitManager::setDocumentAwarnessToExistingUnitSpinBox(widget.cornerRadiusY); - connect(widget.cornerRadiusX, SIGNAL(editingFinished()), this, SIGNAL(propertyChanged())); connect(widget.cornerRadiusY, SIGNAL(editingFinished()), this, SIGNAL(propertyChanged())); } void RectangleShapeConfigWidget::setUnit(const KoUnit &unit) { widget.cornerRadiusX->setUnit(unit); widget.cornerRadiusY->setUnit(unit); } void RectangleShapeConfigWidget::open(KoShape *shape) { m_rectangle = dynamic_cast(shape); if (!m_rectangle) { return; } widget.cornerRadiusX->blockSignals(true); widget.cornerRadiusY->blockSignals(true); QSizeF size = m_rectangle->size(); widget.cornerRadiusX->setMaximum(0.5 * size.width()); widget.cornerRadiusX->changeValue(0.01 * m_rectangle->cornerRadiusX() * 0.5 * size.width()); widget.cornerRadiusY->setMaximum(0.5 * size.height()); widget.cornerRadiusY->changeValue(0.01 * m_rectangle->cornerRadiusY() * 0.5 * size.height()); widget.cornerRadiusX->blockSignals(false); widget.cornerRadiusY->blockSignals(false); } void RectangleShapeConfigWidget::save() { if (!m_rectangle) { return; } QSizeF size = m_rectangle->size(); m_rectangle->setCornerRadiusX(100.0 * widget.cornerRadiusX->value() / (0.5 * size.width())); m_rectangle->setCornerRadiusY(100.0 * widget.cornerRadiusY->value() / (0.5 * size.height())); } KUndo2Command *RectangleShapeConfigWidget::createCommand() { if (!m_rectangle) { return 0; } QSizeF size = m_rectangle->size(); qreal cornerRadiusX = 100.0 * widget.cornerRadiusX->value() / (0.5 * size.width()); qreal cornerRadiusY = 100.0 * widget.cornerRadiusY->value() / (0.5 * size.height()); return new RectangleShapeConfigCommand(m_rectangle, cornerRadiusX, cornerRadiusY); } diff --git a/plugins/flake/pathshapes/star/StarShapeConfigWidget.cpp b/plugins/flake/pathshapes/star/StarShapeConfigWidget.cpp index 9c51af3269..0d203d231b 100644 --- a/plugins/flake/pathshapes/star/StarShapeConfigWidget.cpp +++ b/plugins/flake/pathshapes/star/StarShapeConfigWidget.cpp @@ -1,98 +1,95 @@ /* This file is part of the KDE project * Copyright (C) 2007 Jan Hambrecht * * 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 "StarShapeConfigWidget.h" #include "StarShape.h" #include "StarShapeConfigCommand.h" #include "kis_document_aware_spin_box_unit_manager.h" StarShapeConfigWidget::StarShapeConfigWidget() { widget.setupUi(this); - KisDocumentAwareSpinBoxUnitManager::setDocumentAwarnessToExistingUnitSpinBox(widget.innerRadius); - KisDocumentAwareSpinBoxUnitManager::setDocumentAwarnessToExistingUnitSpinBox(widget.outerRadius); - connect(widget.corners, SIGNAL(valueChanged(int)), this, SIGNAL(propertyChanged())); connect(widget.innerRadius, SIGNAL(editingFinished()), this, SIGNAL(propertyChanged())); connect(widget.outerRadius, SIGNAL(editingFinished()), this, SIGNAL(propertyChanged())); connect(widget.convex, SIGNAL(stateChanged(int)), this, SIGNAL(propertyChanged())); connect(widget.convex, SIGNAL(clicked()), this, SLOT(typeChanged())); } void StarShapeConfigWidget::setUnit(const KoUnit &unit) { widget.innerRadius->setUnit(unit); widget.outerRadius->setUnit(unit); } void StarShapeConfigWidget::open(KoShape *shape) { m_star = dynamic_cast(shape); if (!m_star) { return; } widget.corners->blockSignals(true); widget.innerRadius->blockSignals(true); widget.outerRadius->blockSignals(true); widget.convex->blockSignals(true); widget.corners->setValue(m_star->cornerCount()); widget.innerRadius->changeValue(m_star->baseRadius()); widget.outerRadius->changeValue(m_star->tipRadius()); widget.convex->setCheckState(m_star->convex() ? Qt::Checked : Qt::Unchecked); typeChanged(); widget.corners->blockSignals(false); widget.innerRadius->blockSignals(false); widget.outerRadius->blockSignals(false); widget.convex->blockSignals(false); } void StarShapeConfigWidget::save() { if (!m_star) { return; } m_star->setCornerCount(widget.corners->value()); m_star->setBaseRadius(widget.innerRadius->value()); m_star->setTipRadius(widget.outerRadius->value()); m_star->setConvex(widget.convex->checkState() == Qt::Checked); } KUndo2Command *StarShapeConfigWidget::createCommand() { if (!m_star) { return 0; } else return new StarShapeConfigCommand(m_star, widget.corners->value(), widget.innerRadius->value(), widget.outerRadius->value(), widget.convex->checkState() == Qt::Checked); } void StarShapeConfigWidget::typeChanged() { if (widget.convex->checkState() == Qt::Checked) { widget.innerRadius->setEnabled(false); } else { widget.innerRadius->setEnabled(true); } } diff --git a/plugins/tools/defaulttool/defaulttool/DefaultToolTransformWidget.cpp b/plugins/tools/defaulttool/defaulttool/DefaultToolTransformWidget.cpp index 36f5a35a95..37461e944e 100644 --- a/plugins/tools/defaulttool/defaulttool/DefaultToolTransformWidget.cpp +++ b/plugins/tools/defaulttool/defaulttool/DefaultToolTransformWidget.cpp @@ -1,289 +1,286 @@ /* This file is part of the KDE project * Copyright (C) 2007 Martin Pfeiffer * Copyright (C) 2007 Jan Hambrecht * Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2008 C. Boemann * * 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 "DefaultToolTransformWidget.h" #include #include #include #include #include #include #include #include #include #include "SelectionDecorator.h" #include #include #include #include #include #include #include #include "kis_document_aware_spin_box_unit_manager.h" DefaultToolTransformWidget::DefaultToolTransformWidget(KoInteractionTool *tool, QWidget *parent) : QMenu(parent) { m_tool = tool; setupUi(this); setUnit(m_tool->canvas()->unit()); - KisDocumentAwareSpinBoxUnitManager::setDocumentAwarnessToExistingUnitSpinBox(shearXSpinBox); - KisDocumentAwareSpinBoxUnitManager::setDocumentAwarnessToExistingUnitSpinBox(shearYSpinBox); - connect(m_tool->canvas()->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), this, SLOT(resourceChanged(int,QVariant))); connect(rotateButton, SIGNAL(clicked()), this, SLOT(rotationChanged())); connect(shearXButton, SIGNAL(clicked()), this, SLOT(shearXChanged())); connect(shearYButton, SIGNAL(clicked()), this, SLOT(shearYChanged())); connect(scaleXButton, SIGNAL(clicked()), this, SLOT(scaleXChanged())); connect(scaleYButton, SIGNAL(clicked()), this, SLOT(scaleYChanged())); connect(scaleAspectCheckBox, SIGNAL(toggled(bool)), scaleYSpinBox, SLOT(setDisabled(bool))); connect(scaleAspectCheckBox, SIGNAL(toggled(bool)), scaleYButton, SLOT(setDisabled(bool))); connect(resetButton, SIGNAL(clicked()), this, SLOT(resetTransformations())); } void DefaultToolTransformWidget::setUnit(const KoUnit &unit) { shearXSpinBox->setUnit(unit); shearYSpinBox->setUnit(unit); } void DefaultToolTransformWidget::resourceChanged(int key, const QVariant &res) { if (key == KoCanvasResourceManager::Unit) { setUnit(res.value()); } } void DefaultToolTransformWidget::rotationChanged() { QList selectedShapes = m_tool->canvas()->shapeManager()->selection()->selectedShapes(KoFlake::TopLevelSelection); QList oldTransforms; Q_FOREACH (KoShape *shape, selectedShapes) { oldTransforms << shape->transformation(); } qreal angle = rotateSpinBox->value(); QPointF rotationCenter = m_tool->canvas()->shapeManager()->selection()->absolutePosition(SelectionDecorator::hotPosition()); QTransform matrix; matrix.translate(rotationCenter.x(), rotationCenter.y()); matrix.rotate(angle); matrix.translate(-rotationCenter.x(), -rotationCenter.y()); Q_FOREACH (KoShape *shape, selectedShapes) { shape->update(); shape->applyAbsoluteTransformation(matrix); shape->update(); } m_tool->canvas()->shapeManager()->selection()->applyAbsoluteTransformation(matrix); QList newTransforms; Q_FOREACH (KoShape *shape, selectedShapes) { newTransforms << shape->transformation(); } KoShapeTransformCommand *cmd = new KoShapeTransformCommand(selectedShapes, oldTransforms, newTransforms); cmd->setText(kundo2_i18n("Rotate")); m_tool->canvas()->addCommand(cmd); } void DefaultToolTransformWidget::shearXChanged() { KoSelection *selection = m_tool->canvas()->shapeManager()->selection(); QList selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection); QList oldTransforms; Q_FOREACH (KoShape *shape, selectedShapes) { oldTransforms << shape->transformation(); } qreal shearX = shearXSpinBox->value() / selection->size().height(); QPointF basePoint = selection->absolutePosition(SelectionDecorator::hotPosition()); QTransform matrix; matrix.translate(basePoint.x(), basePoint.y()); matrix.shear(shearX, 0.0); matrix.translate(-basePoint.x(), -basePoint.y()); Q_FOREACH (KoShape *shape, selectedShapes) { shape->update(); shape->applyAbsoluteTransformation(matrix); shape->update(); } selection->applyAbsoluteTransformation(matrix); QList newTransforms; Q_FOREACH (KoShape *shape, selectedShapes) { newTransforms << shape->transformation(); } KoShapeTransformCommand *cmd = new KoShapeTransformCommand(selectedShapes, oldTransforms, newTransforms); cmd->setText(kundo2_i18n("Shear X")); m_tool->canvas()->addCommand(cmd); } void DefaultToolTransformWidget::shearYChanged() { KoSelection *selection = m_tool->canvas()->shapeManager()->selection(); QList selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection); QList oldTransforms; Q_FOREACH (KoShape *shape, selectedShapes) { oldTransforms << shape->transformation(); } qreal shearY = shearYSpinBox->value() / selection->size().width(); QPointF basePoint = selection->absolutePosition(SelectionDecorator::hotPosition()); QTransform matrix; matrix.translate(basePoint.x(), basePoint.y()); matrix.shear(0.0, shearY); matrix.translate(-basePoint.x(), -basePoint.y()); Q_FOREACH (KoShape *shape, selectedShapes) { shape->update(); shape->applyAbsoluteTransformation(matrix); shape->update(); } selection->applyAbsoluteTransformation(matrix); QList newTransforms; Q_FOREACH (KoShape *shape, selectedShapes) { newTransforms << shape->transformation(); } KoShapeTransformCommand *cmd = new KoShapeTransformCommand(selectedShapes, oldTransforms, newTransforms); cmd->setText(kundo2_i18n("Shear Y")); m_tool->canvas()->addCommand(cmd); } void DefaultToolTransformWidget::scaleXChanged() { QList selectedShapes = m_tool->canvas()->shapeManager()->selection()->selectedShapes(KoFlake::TopLevelSelection); QList oldTransforms; Q_FOREACH (KoShape *shape, selectedShapes) { oldTransforms << shape->transformation(); } qreal scale = scaleXSpinBox->value() * 0.01; // Input is in per cent QPointF basePoint = m_tool->canvas()->shapeManager()->selection()->absolutePosition(SelectionDecorator::hotPosition()); QTransform matrix; matrix.translate(basePoint.x(), basePoint.y()); if (scaleAspectCheckBox->isChecked()) { matrix.scale(scale, scale); } else { matrix.scale(scale, 1.0); } matrix.translate(-basePoint.x(), -basePoint.y()); Q_FOREACH (KoShape *shape, selectedShapes) { shape->update(); shape->applyAbsoluteTransformation(matrix); shape->update(); } m_tool->canvas()->shapeManager()->selection()->applyAbsoluteTransformation(matrix); QList newTransforms; Q_FOREACH (KoShape *shape, selectedShapes) { newTransforms << shape->transformation(); } KoShapeTransformCommand *cmd = new KoShapeTransformCommand(selectedShapes, oldTransforms, newTransforms); cmd->setText(kundo2_i18n("Scale")); m_tool->canvas()->addCommand(cmd); } void DefaultToolTransformWidget::scaleYChanged() { QList selectedShapes = m_tool->canvas()->shapeManager()->selection()->selectedShapes(KoFlake::TopLevelSelection); QList oldTransforms; Q_FOREACH (KoShape *shape, selectedShapes) { oldTransforms << shape->transformation(); } qreal scale = scaleYSpinBox->value() * 0.01; // Input is in per cent QPointF basePoint = m_tool->canvas()->shapeManager()->selection()->absolutePosition(SelectionDecorator::hotPosition()); QTransform matrix; matrix.translate(basePoint.x(), basePoint.y()); matrix.scale(1.0, scale); matrix.translate(-basePoint.x(), -basePoint.y()); Q_FOREACH (KoShape *shape, selectedShapes) { shape->update(); shape->applyAbsoluteTransformation(matrix); shape->update(); } m_tool->canvas()->shapeManager()->selection()->applyAbsoluteTransformation(matrix); QList newTransforms; Q_FOREACH (KoShape *shape, selectedShapes) { newTransforms << shape->transformation(); } KoShapeTransformCommand *cmd = new KoShapeTransformCommand(selectedShapes, oldTransforms, newTransforms); cmd->setText(kundo2_i18n("Scale")); m_tool->canvas()->addCommand(cmd); } void DefaultToolTransformWidget::resetTransformations() { QList selectedShapes = m_tool->canvas()->shapeManager()->selection()->selectedShapes(KoFlake::TopLevelSelection); QList oldTransforms; Q_FOREACH (KoShape *shape, selectedShapes) { oldTransforms << shape->transformation(); } QTransform matrix; Q_FOREACH (KoShape *shape, selectedShapes) { shape->update(); shape->setTransformation(matrix); shape->update(); } m_tool->canvas()->shapeManager()->selection()->applyAbsoluteTransformation(matrix); QList newTransforms; Q_FOREACH (KoShape *shape, selectedShapes) { newTransforms << shape->transformation(); } KoShapeTransformCommand *cmd = new KoShapeTransformCommand(selectedShapes, oldTransforms, newTransforms); cmd->setText(kundo2_i18n("Reset Transformations")); m_tool->canvas()->addCommand(cmd); } diff --git a/plugins/tools/defaulttool/defaulttool/DefaultToolWidget.cpp b/plugins/tools/defaulttool/defaulttool/DefaultToolWidget.cpp index d6e8bf7fff..69c2b8c980 100644 --- a/plugins/tools/defaulttool/defaulttool/DefaultToolWidget.cpp +++ b/plugins/tools/defaulttool/defaulttool/DefaultToolWidget.cpp @@ -1,284 +1,279 @@ /* This file is part of the KDE project * Copyright (C) 2007 Martin Pfeiffer * Copyright (C) 2007 Jan Hambrecht Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2010 Thomas Zander * * 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 "DefaultToolWidget.h" #include "DefaultTool.h" #include #include #include #include #include #include #include #include #include #include #include "SelectionDecorator.h" #include "DefaultToolTransformWidget.h" #include #include #include #include #include #include #include #include #include "kis_document_aware_spin_box_unit_manager.h" DefaultToolWidget::DefaultToolWidget(KoInteractionTool *tool, QWidget *parent) : QWidget(parent) , m_tool(tool) , m_blockSignals(false) { setupUi(this); setUnit(m_tool->canvas()->unit()); aspectButton->setKeepAspectRatio(false); updatePosition(); updateSize(); - KisDocumentAwareSpinBoxUnitManager::setDocumentAwarnessToExistingUnitSpinBox(positionXSpinBox); - KisDocumentAwareSpinBoxUnitManager::setDocumentAwarnessToExistingUnitSpinBox(positionYSpinBox); - KisDocumentAwareSpinBoxUnitManager::setDocumentAwarnessToExistingUnitSpinBox(widthSpinBox); - KisDocumentAwareSpinBoxUnitManager::setDocumentAwarnessToExistingUnitSpinBox(heightSpinBox); - connect(positionSelector, SIGNAL(positionSelected(KoFlake::Position)), this, SLOT(positionSelected(KoFlake::Position))); connect(positionXSpinBox, SIGNAL(editingFinished()), this, SLOT(positionHasChanged())); connect(positionYSpinBox, SIGNAL(editingFinished()), this, SLOT(positionHasChanged())); connect(widthSpinBox, SIGNAL(editingFinished()), this, SLOT(sizeHasChanged())); connect(heightSpinBox, SIGNAL(editingFinished()), this, SLOT(sizeHasChanged())); KoSelection *selection = m_tool->canvas()->shapeManager()->selection(); connect(selection, SIGNAL(selectionChanged()), this, SLOT(updatePosition())); connect(selection, SIGNAL(selectionChanged()), this, SLOT(updateSize())); KoShapeManager *manager = m_tool->canvas()->shapeManager(); connect(manager, SIGNAL(selectionContentChanged()), this, SLOT(updatePosition())); connect(manager, SIGNAL(selectionContentChanged()), this, SLOT(updateSize())); connect(m_tool->canvas()->resourceManager(), SIGNAL(canvasResourceChanged(int,QVariant)), this, SLOT(resourceChanged(int,QVariant))); connect(aspectButton, SIGNAL(keepAspectRatioChanged(bool)), this, SLOT(aspectButtonToggled(bool))); } void DefaultToolWidget::positionSelected(KoFlake::Position position) { m_tool->canvas()->resourceManager()->setResource(DefaultTool::HotPosition, QVariant(position)); updatePosition(); } void DefaultToolWidget::updatePosition() { QPointF selPosition(0, 0); KoFlake::Position position = positionSelector->position(); KoSelection *selection = m_tool->canvas()->shapeManager()->selection(); if (selection->count()) { selPosition = selection->absolutePosition(position); } positionXSpinBox->setEnabled(selection->count()); positionYSpinBox->setEnabled(selection->count()); if (m_blockSignals) { return; } m_blockSignals = true; positionXSpinBox->changeValue(selPosition.x()); positionYSpinBox->changeValue(selPosition.y()); QList selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection); bool aspectLocked = false; foreach (KoShape *shape, selectedShapes) { aspectLocked = aspectLocked | shape->keepAspectRatio(); } aspectButton->setKeepAspectRatio(aspectLocked); m_blockSignals = false; } void DefaultToolWidget::positionHasChanged() { KoSelection *selection = m_tool->canvas()->shapeManager()->selection(); if (!selection->count()) { return; } KoFlake::Position position = positionSelector->position(); QPointF newPos(positionXSpinBox->value(), positionYSpinBox->value()); QPointF oldPos = selection->absolutePosition(position); if (oldPos == newPos) { return; } QList selectedShapes = selection->selectedShapes(KoFlake::TopLevelSelection); QPointF moveBy = newPos - oldPos; QList oldPositions; QList newPositions; Q_FOREACH (KoShape *shape, selectedShapes) { oldPositions.append(shape->position()); newPositions.append(shape->position() + moveBy); } selection->setPosition(selection->position() + moveBy); m_tool->canvas()->addCommand(new KoShapeMoveCommand(selectedShapes, oldPositions, newPositions)); updatePosition(); } void DefaultToolWidget::updateSize() { QSizeF selSize(0, 0); KoSelection *selection = m_tool->canvas()->shapeManager()->selection(); uint selectionCount = selection->count(); if (selectionCount) { selSize = selection->boundingRect().size(); } widthSpinBox->setEnabled(selectionCount); heightSpinBox->setEnabled(selectionCount); if (m_blockSignals) { return; } m_blockSignals = true; widthSpinBox->changeValue(selSize.width()); heightSpinBox->changeValue(selSize.height()); m_blockSignals = false; } void DefaultToolWidget::sizeHasChanged() { if (aspectButton->hasFocus()) { return; } if (m_blockSignals) { return; } QSizeF newSize(widthSpinBox->value(), heightSpinBox->value()); KoSelection *selection = m_tool->canvas()->shapeManager()->selection(); QRectF rect = selection->boundingRect(); if (aspectButton->keepAspectRatio()) { qreal aspect = rect.width() / rect.height(); if (rect.width() != newSize.width()) { newSize.setHeight(newSize.width() / aspect); } else if (rect.height() != newSize.height()) { newSize.setWidth(newSize.height() * aspect); } } if (rect.width() != newSize.width() || rect.height() != newSize.height()) { // get the scale/resize center from the position selector QPointF scaleCenter = selection->absolutePosition(positionSelector->position()); QTransform resizeMatrix; resizeMatrix.translate(scaleCenter.x(), scaleCenter.y()); // make sure not to divide by 0 in case the selection is a line and has no width. In this case just scale by 1. resizeMatrix.scale(rect.width() ? newSize.width() / rect.width() : 1, rect.height() ? newSize.height() / rect.height() : 1); resizeMatrix.translate(-scaleCenter.x(), -scaleCenter.y()); QList selectedShapes = selection->selectedShapes(KoFlake::StrippedSelection); QList oldSizes, newSizes; QList oldState; QList newState; Q_FOREACH (KoShape *shape, selectedShapes) { shape->update(); QSizeF oldSize = shape->size(); oldState << shape->transformation(); QTransform shapeMatrix = shape->absoluteTransformation(0); // calculate the matrix we would apply to the local shape matrix // that tells us the effective scale values we have to use for the resizing QTransform localMatrix = shapeMatrix * resizeMatrix * shapeMatrix.inverted(); // save the effective scale values, without any mirroring portion const qreal scaleX = qAbs(localMatrix.m11()); const qreal scaleY = qAbs(localMatrix.m22()); // calculate the scale matrix which is equivalent to our resizing above QTransform scaleMatrix = (QTransform().scale(scaleX, scaleY)); scaleMatrix = shapeMatrix.inverted() * scaleMatrix * shapeMatrix; // calculate the new size of the shape, using the effective scale values oldSizes << oldSize; QSizeF newSize = QSizeF(scaleX * oldSize.width(), scaleY * oldSize.height()); newSizes << newSize; shape->setSize(newSize); // apply the rest of the transformation without the resizing part shape->applyAbsoluteTransformation(scaleMatrix.inverted() * resizeMatrix); newState << shape->transformation(); } m_tool->repaintDecorations(); selection->applyAbsoluteTransformation(resizeMatrix); KUndo2Command *cmd = new KUndo2Command(kundo2_i18n("Resize")); new KoShapeSizeCommand(selectedShapes, oldSizes, newSizes, cmd); new KoShapeTransformCommand(selectedShapes, oldState, newState, cmd); m_tool->canvas()->addCommand(cmd); updateSize(); updatePosition(); } } void DefaultToolWidget::setUnit(const KoUnit &unit) { m_blockSignals = true; positionXSpinBox->setUnit(unit); positionYSpinBox->setUnit(unit); widthSpinBox->setUnit(unit); heightSpinBox->setUnit(unit); m_blockSignals = false; updatePosition(); updateSize(); } void DefaultToolWidget::resourceChanged(int key, const QVariant &res) { if (key == KoCanvasResourceManager::Unit) { setUnit(res.value()); } else if (key == DefaultTool::HotPosition) { if (res.toInt() != positionSelector->position()) { positionSelector->setPosition(static_cast(res.toInt())); updatePosition(); } } } void DefaultToolWidget::aspectButtonToggled(bool keepAspect) { if (m_blockSignals) { return; } KoSelection *selection = m_tool->canvas()->shapeManager()->selection(); foreach (KoShape *shape, selection->selectedShapes(KoFlake::TopLevelSelection)) { shape->setKeepAspectRatio(keepAspect); } }