diff --git a/.gitignore b/.gitignore
index b8b13012e..a628cc2d2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,6 +25,7 @@ ui_*.h
Makefile*
*-build-*
.moc
+moc_predefs.h
Makefile
Makefile.Debug
diff --git a/.travis.yml b/.travis.yml
index 7b9f6df48..a3940869c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -58,14 +58,15 @@ before_install:
brew upgrade python;
fi
brew install p7zip;
- brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/13d52537d1e0e5f913de46390123436d220035f6/Formula/qt.rb;
+ brew install qt;
brew link qt --force;
fi
install:
- pip3 freeze > requirements.txt
- pip3 install -r requirements.txt
- - sudo pip3 install google-api-python-client
+ - sudo pip3 install --upgrade oauth2client
+ - sudo pip3 install --upgrade google-api-python-client
- python3 -V
- pip3 -V
@@ -89,7 +90,7 @@ script:
- 'if [ "$TRAVIS_BRANCH" == "release" ]; then
qmake ../ PREFIX=/usr CONFIG+=release DEFINES+=QT_NO_DEBUG_OUTPUT DEFINES+=PENCIL2D_RELEASE;
else
- qmake ../ PREFIX=/usr CONFIG+=GIT CONFIG+=NIGHTLY;
+ qmake ../ PREFIX=/usr CONFIG+=release CONFIG+=GIT CONFIG+=NIGHTLY;
fi'
- make;
- "$TRAVIS_BUILD_DIR/build/tests/tests"
@@ -119,9 +120,9 @@ after_success:
echo "Copying ffmpeg plugin";
mkdir Pencil2D.app/Contents/MacOS/plugins;
- wget -P Pencil2D.app/Contents/MacOS/plugins https://evermeet.cx/ffmpeg/ffmpeg-3.2.4.7z;
- 7z x Pencil2D.app/Contents/MacOS/plugins/ffmpeg-3.2.4.7z -o"Pencil2D.app/Contents/MacOS/plugins";
- rm Pencil2D.app/Contents/MacOs/plugins/ffmpeg-3.2.4.7z;
+ wget -P Pencil2D.app/Contents/MacOS/plugins https://evermeet.cx/pub/ffmpeg/ffmpeg-3.4.2.7z;
+ 7z x Pencil2D.app/Contents/MacOS/plugins/ffmpeg-3.4.2.7z -o"Pencil2D.app/Contents/MacOS/plugins";
+ rm Pencil2D.app/Contents/MacOs/plugins/ffmpeg-3.4.2.7z;
echo "Copying necessary Qt frameworks";
macdeployqt Pencil2D.app;
diff --git a/README.md b/README.md
index 824fa1e3e..4d7b8eb0c 100644
--- a/README.md
+++ b/README.md
@@ -8,30 +8,18 @@
## Download ###
-### Pencil2D 0.6.1 (15 April 2018)
+### Pencil2D 0.6.1 (16 April 2018)
-[What's new?](https://www.pencil2d.org/2017/12/introducing-pencil2d-0.6.html)
+[What's new?](https://www.pencil2d.org/2018/04/maintenance-release-0.6.1.html)
| Windows 64 bit | Windows 32 bit | Mac | Linux |
| :--------------: | :---------------: | :-------------: | :---------------: |
| [Download][0] | [Download][1] | [Download][2] | [Download][3] |
-[0]: https://github.com/pencil2d/pencil/releases/download/v0.6.1/pencil2d-win64-0.6.1.zip
-[1]: https://github.com/pencil2d/pencil/releases/download/v0.6.1/pencil2d-win32-0.6.1.zip
-[2]: https://github.com/pencil2d/pencil/releases/download/v0.6.1/pencil2d-mac-0.6.1.zip
-[3]: https://github.com/pencil2d/pencil/releases/download/v0.6.1/pencil2d-linux-amd64-0.6.1.AppImage
-
-### Debian & Ubuntu
-
-```bash
-sudo apt-get install pencil2d
-```
-
-### Homebrew Cask (macOS)
-
-```bash
-brew cask install pencil2d
-```
+[0]: https://github.com/pencil2d/pencil/releases/download/v0.6.1.1/pencil2d-win64-0.6.1.1.zip
+[1]: https://github.com/pencil2d/pencil/releases/download/v0.6.1.1/pencil2d-win32-0.6.1.1.zip
+[2]: https://github.com/pencil2d/pencil/releases/download/v0.6.1.1/pencil2d-mac-0.6.1.1.zip
+[3]: https://github.com/pencil2d/pencil/releases/download/v0.6.1.1/pencil2d-linux-amd64-0.6.1.1.AppImage
### Nightly build
diff --git a/app/app.pro b/app/app.pro
index 20927b268..dcfa13afa 100644
--- a/app/app.pro
+++ b/app/app.pro
@@ -58,7 +58,8 @@ HEADERS += \
src/exportimagedialog.h \
src/importimageseqdialog.h \
src/spinslider.h \
- src/doubleprogressdialog.h
+ src/doubleprogressdialog.h \
+ src/colorslider.h
SOURCES += \
src/main.cpp \
@@ -85,7 +86,8 @@ SOURCES += \
src/exportimagedialog.cpp \
src/importimageseqdialog.cpp \
src/spinslider.cpp \
- src/doubleprogressdialog.cpp
+ src/doubleprogressdialog.cpp \
+ src/colorslider.cpp
FORMS += \
ui/mainwindow2.ui \
@@ -137,6 +139,12 @@ win32 {
linux {
target.path = $${PREFIX}/bin
+ bashcompletion.files = data/pencil2d
+ bashcompletion.path = $${PREFIX}/share/bash-completion/completions
+
+ zshcompletion.files = data/_pencil2d
+ zshcompletion.path = $${PREFIX}/share/zsh/site-functions
+
mimepackage.files = data/pencil2d.xml
mimepackage.path = $${PREFIX}/share/mime/packages
@@ -146,7 +154,7 @@ linux {
icon.files = data/pencil2d.png
icon.path = $${PREFIX}/share/icons/hicolor/256x256/apps
- INSTALLS += target mimepackage desktopentry icon
+ INSTALLS += bashcompletion zshcompletion target mimepackage desktopentry icon
}
diff --git a/app/data/Info.plist b/app/data/Info.plist
index edcb5b581..c8a0dbf4f 100644
--- a/app/data/Info.plist
+++ b/app/data/Info.plist
@@ -58,11 +58,11 @@
CFBundlePackageType
BNDL
CFBundleShortVersionString
- 0.6.0
+ 0.6.2
CFBundleSignature
PC2D
CFBundleVersion
- 0.6.0.0
+ 0.6.2.0
LSApplicationCategoryType
public.app-category.graphics-design
NSHighResolutionCapable
diff --git a/app/data/_pencil2d b/app/data/_pencil2d
new file mode 100644
index 000000000..8bf4b29db
--- /dev/null
+++ b/app/data/_pencil2d
@@ -0,0 +1,13 @@
+#compdef pencil2d
+
+_arguments \
+ {-h,--help}'[Display help]' \
+ {-v,--version}'[Display version information]' \
+ {-o+,--export=}'[Render the file to the given path]:output file:_files' \
+ '--camera=[Name of the camera layer to use]:name of the camera layer to use:' \
+ '--width=[Width of the output frames]:width of the output frames:' \
+ '--height=[Height of the output frames]:height of the output frames:' \
+ '--start=[The first frame to export]:number of first frame to include:' \
+ '--end=[The last frame to include]:number of last frame to include:((last\:"Last frame containing animation" last-sound\:"Last frame containing animation or sound"))' \
+ '--transparency[Render transparency when possible]' \
+ '::input file:_files -g "*.pcl|*.pclx"'
diff --git a/app/data/app.qrc b/app/data/app.qrc
index 7a68a10d1..78158697e 100644
--- a/app/data/app.qrc
+++ b/app/data/app.qrc
@@ -50,6 +50,12 @@
icons/new/svg/selection.svg
icons/new/svg/smudge_detailed.svg
icons/new/svg/trash_detailed.svg
+ icons/new/checkerboard_smaller
+ icons/new/arrow-diagonalleft.png
+ icons/new/arrow-diagonalright.png
+ icons/new/arrow-horizontal.png
+ icons/new/arrow-selectmove.png
+ icons/new/arrow-vertical.png
icons/onion-blue.png
@@ -62,5 +68,7 @@
icons/mirrorV.png
icons/dialog-error.svg
pencil2d_quick_guide.pdf
+ icons/new/svg/color-dialog.svg
+ icons/new/svg/more_options.svg
diff --git a/app/data/icons/new/arrow-diagonalleft.png b/app/data/icons/new/arrow-diagonalleft.png
new file mode 100644
index 000000000..7044dd583
Binary files /dev/null and b/app/data/icons/new/arrow-diagonalleft.png differ
diff --git a/app/data/icons/new/arrow-diagonalright.png b/app/data/icons/new/arrow-diagonalright.png
new file mode 100644
index 000000000..08bd6ca1e
Binary files /dev/null and b/app/data/icons/new/arrow-diagonalright.png differ
diff --git a/app/data/icons/new/arrow-horizontal.png b/app/data/icons/new/arrow-horizontal.png
new file mode 100644
index 000000000..322ae64d7
Binary files /dev/null and b/app/data/icons/new/arrow-horizontal.png differ
diff --git a/app/data/icons/new/arrow-selectmove.png b/app/data/icons/new/arrow-selectmove.png
new file mode 100644
index 000000000..b3b6c617c
Binary files /dev/null and b/app/data/icons/new/arrow-selectmove.png differ
diff --git a/app/data/icons/new/arrow-vertical.png b/app/data/icons/new/arrow-vertical.png
new file mode 100644
index 000000000..7ef55b72b
Binary files /dev/null and b/app/data/icons/new/arrow-vertical.png differ
diff --git a/app/data/icons/new/checkerboard_smaller b/app/data/icons/new/checkerboard_smaller
new file mode 100644
index 000000000..71a0822a5
Binary files /dev/null and b/app/data/icons/new/checkerboard_smaller differ
diff --git a/app/data/icons/new/svg/color-dialog.svg b/app/data/icons/new/svg/color-dialog.svg
new file mode 100644
index 000000000..4a79d4bc7
--- /dev/null
+++ b/app/data/icons/new/svg/color-dialog.svg
@@ -0,0 +1,38 @@
+
+
+
diff --git a/app/data/icons/new/svg/more_options.svg b/app/data/icons/new/svg/more_options.svg
new file mode 100644
index 000000000..07f79b30f
--- /dev/null
+++ b/app/data/icons/new/svg/more_options.svg
@@ -0,0 +1,21 @@
+
+
+
diff --git a/app/data/pencil2d b/app/data/pencil2d
new file mode 100644
index 000000000..061e40137
--- /dev/null
+++ b/app/data/pencil2d
@@ -0,0 +1,27 @@
+_pencil2d()
+{
+ local cur prev words cword opts
+ _init_completion || return
+ opts="-h --help -v --version -o --export --camera --width --height --start --end --transparency"
+
+ case "${prev}" in
+ --camera|--width|--height|--start)
+ return 0
+ ;;
+ --end)
+ COMPREPLY=( $(compgen -W "last last-sound" -- ${cur}) )
+ return 0
+ ;;
+ -o|--export)
+ _filedir
+ return 0
+ ;;
+ esac
+
+ if [[ ${cur} == -* ]]; then
+ COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
+ else
+ _filedir "pcl?(x)"
+ fi
+} &&
+complete -F _pencil2d pencil2d
diff --git a/app/data/pencil2d.desktop b/app/data/pencil2d.desktop
index 10dcd9db2..87d41cd34 100644
--- a/app/data/pencil2d.desktop
+++ b/app/data/pencil2d.desktop
@@ -6,10 +6,12 @@ GenericName=Animation Software
Comment=Create traditional hand-drawn animation using both bitmap and vector graphics
Icon=pencil2d
Exec=pencil2d %f
-MimeType=application/x-pencil-pcl;application/x-pencil-pclx;
+MimeType=application/x-pencil2d-pcl;application/x-pencil2d-pclx;application/x-pencil2d-palette;
Categories=Graphics;2DGraphics;VectorGraphics;RasterGraphics;Qt;AudioVideo;Video;
Keywords=picture;drawing;vector;bitmap;cartoon;
-Keywords[de]=Bild;Zeichnen;Vektor;Raster;Zeichentrick;
+
+
+# Translations
# Translations
@@ -17,10 +19,10 @@ Name[he]=Pencil2D
GenericName[he]=תוכנת הנפשה
Comment[he]=צרו הנפשה מסורתית מצויירת ביד באמצעות כלי מפת ביטים וכלים וקטוריים
Icon[he]=pencil2d
-Name[sl]=Svinčnik2D
-GenericName[sl]=Program za animacijo
-Comment[sl]=Ustvarite tradicionalno ročno risano animacijo s pomočjo bitne in vektorske grafike
-Icon[sl]=svinčnik2d
+Name[ar]=بنسل2D
+GenericName[ar]=برنامج تحريك الرسومات
+Comment[ar]=أنشئ رسومات متحركة بالطريقة الكلاسكية بإستخدام bitmap و vector graphics
+Icon[ar]=pencil2d
Name[pt]=Pencil2D
GenericName[pt]=Programa de Animação
Comment[pt]=Cria animações tradicionais feitas a mão utilizando bitmaps e vectores gráficos
@@ -43,17 +45,26 @@ Name[de]=Pencil2D
GenericName[de]=Animationssoftware
Comment[de]=Traditionelle handgezeichnete Animation sowohl mit Raster- als auch mit Vektorgrafik schaffen
Icon[de]=pencil2d
+Name[sl]=Svinčnik2D
+GenericName[sl]=Program za animacijo
+Comment[sl]=Ustvarite tradicionalno ročno risano animacijo s pomočjo bitne in vektorske grafike
+Icon[sl]=pencil2d
Name[es]=Pencil2D
GenericName[es]=Programa de Animación
Comment[es]=Crea animaciones tradicionales hechas a mano usando mapas de bits o vectores gráficos
Icon[es]=pencil2d
Name[et]=Pencil2D
GenericName[et]=Animeerimistarkvara
+Comment[et]=Loo traditsioonilisi käsitsijoonistatud animatsioone kasutades nii raster- kui vektorgraafikat.
Icon[et]=pencil2d
Name[vi]=Pencil 2D
GenericName[vi]=Phần mềm làm phim hoạt hình
Comment[vi]=Tạo nên các chuyển Động hoạt hình vẽ tay truyền thống bằng các hình ảnh Bitmap và Vector
-Icon[vi]=Pencil 2D
+Icon[vi]=pencil2d
+Name[zh_CN]=Pencil2D
+GenericName[zh_CN]=动画软件
+Comment[zh_CN]=用位图和矢量两种图像技术创作传统手绘动画
+Icon[zh_CN]=pencil2d
Name[fr]=Pencil2D
GenericName[fr]=Logiciel d'Animation
Comment[fr]=Créez une animation traditionnelle dessinée à la main à l'aide de graphiques bitmap et vectoriels
diff --git a/app/src/aboutdialog.cpp b/app/src/aboutdialog.cpp
index f3c68b4c5..f1c79e3c1 100644
--- a/app/src/aboutdialog.cpp
+++ b/app/src/aboutdialog.cpp
@@ -21,6 +21,8 @@ GNU General Public License for more details.
#include
#include
+#include "pencildef.h"
+
AboutDialog::AboutDialog(QWidget* parent) :
QDialog(parent),
ui(new Ui::AboutDialog)
@@ -37,13 +39,8 @@ AboutDialog::~AboutDialog()
void AboutDialog::init()
{
-#define STRINGIFY(x) #x
-#define TOSTRING(x) STRINGIFY(x)
-#define S__GIT_TIMESTAMP TOSTRING(GIT_TIMESTAMP)
-#define S__GIT_COMMIT_HASH TOSTRING(GIT_CURRENT_SHA1)
-
QStringList devText;
- devText << tr("Version: %1", "Version Number in About Dialog").arg(APP_VERSION);
+ devText << tr("Version: %1", "Version Number in About Dialog").arg(APP_VERSION " RC1");
#if defined(GIT_EXISTS) && defined(NIGHTLY_BUILD)
devText << "commit: " S__GIT_COMMIT_HASH ;
devText << "date: " S__GIT_TIMESTAMP ;
diff --git a/app/src/actioncommands.cpp b/app/src/actioncommands.cpp
index bb5406c48..35c5b77af 100644
--- a/app/src/actioncommands.cpp
+++ b/app/src/actioncommands.cpp
@@ -133,9 +133,20 @@ Status ActionCommands::importSound()
return st;
}
-Status ActionCommands::exportMovie()
+Status ActionCommands::exportGif()
{
- auto dialog = new ExportMovieDialog(mParent);
+ // exporting gif
+ return exportMovie(true);
+}
+
+Status ActionCommands::exportMovie(bool isGif)
+{
+ ExportMovieDialog* dialog = nullptr;
+ if (isGif) {
+ dialog = new ExportMovieDialog(mParent, ImportExportDialog::Export, FileType::GIF);
+ } else {
+ dialog = new ExportMovieDialog(mParent);
+ }
OnScopeExit(dialog->deleteLater());
dialog->init();
@@ -227,6 +238,17 @@ Status ActionCommands::exportMovie()
if (st.ok() && QFile::exists(strMoviePath))
{
+ if (isGif) {
+ auto btn = QMessageBox::question(mParent, "Pencil2D",
+ tr("Finished. Open file location?"));
+
+ if (btn == QMessageBox::Yes)
+ {
+ QString path = dialog->getAbsolutePath();
+ QDesktopServices::openUrl(QUrl::fromLocalFile(path));
+ }
+ return Status::OK;
+ }
auto btn = QMessageBox::question(mParent, "Pencil2D",
tr("Finished. Open movie now?", "When movie export done."));
if (btn == QMessageBox::Yes)
@@ -266,6 +288,11 @@ Status ActionCommands::exportImageSequence()
}
dialog->setCamerasInfo(camerasInfo);
+ int lengthWithSounds = mEditor->layers()->animationLength(true);
+ int length = mEditor->layers()->animationLength(false);
+
+ dialog->setDefaultRange(1, length, lengthWithSounds);
+
dialog->exec();
if (dialog->result() == QDialog::Rejected)
@@ -277,8 +304,8 @@ Status ActionCommands::exportImageSequence()
QSize exportSize = dialog->getExportSize();
QString exportFormat = dialog->getExportFormat();
bool useTranparency = dialog->getTransparency();
-
- int totalLength = mEditor->layers()->animationLength();
+ int startFrame = dialog->getStartFrame();
+ int endFrame = dialog->getEndFrame();
QString sCameraLayerName = dialog->getCameraLayerName();
LayerCamera* cameraLayer = (LayerCamera*)mEditor->layers()->findLayerByName(sCameraLayerName, Layer::CAMERA);
@@ -289,7 +316,7 @@ Status ActionCommands::exportImageSequence()
progress.setWindowModality(Qt::WindowModal);
progress.show();
- mEditor->object()->exportFrames(1, totalLength,
+ mEditor->object()->exportFrames(startFrame, endFrame,
cameraLayer,
exportSize,
strFilePath,
@@ -546,6 +573,8 @@ void ActionCommands::moveFrameForward()
mEditor->scrubForward();
}
}
+
+ mEditor->layers()->notifyAnimationLengthChanged();
}
void ActionCommands::moveFrameBackward()
diff --git a/app/src/actioncommands.h b/app/src/actioncommands.h
index aea346665..bbd058ad0 100644
--- a/app/src/actioncommands.h
+++ b/app/src/actioncommands.h
@@ -36,9 +36,10 @@ class ActionCommands : public QObject
// file
Status importSound();
- Status exportMovie();
+ Status exportMovie(bool isGif = false);
Status exportImageSequence();
Status exportImage();
+ Status exportGif();
// edit
void flipSelectionX();
diff --git a/app/src/colorbox.cpp b/app/src/colorbox.cpp
index 3ea82ae6b..60401a8b6 100644
--- a/app/src/colorbox.cpp
+++ b/app/src/colorbox.cpp
@@ -16,20 +16,27 @@ GNU General Public License for more details.
#include
#include "colorwheel.h"
-#include "colorinspector.h"
#include "colorbox.h"
+#include "editor.h"
+#include "colormanager.h"
+
ColorBox::ColorBox( QWidget* parent ) : BaseDockWidget( parent )
{
- setWindowTitle(tr("Color Wheel", "Color Wheel's window title"));
+ setWindowTitle(tr("Color Box", "Color Box window title"));
+}
+ColorBox::~ColorBox()
+{
+}
+
+void ColorBox::initUI()
+{
mColorWheel = new ColorWheel(this);
- mColorInspector = new ColorInspector(this);
- QVBoxLayout* layout = new QVBoxLayout();
+ QVBoxLayout* layout = new QVBoxLayout;
layout->setContentsMargins(5, 5, 5, 5);
layout->addWidget(mColorWheel);
- layout->addWidget(mColorInspector);
layout->setStretch(0, 1);
layout->setStretch(1, 0);
QWidget* mainWidget = new QWidget;
@@ -37,25 +44,15 @@ ColorBox::ColorBox( QWidget* parent ) : BaseDockWidget( parent )
setWidget(mainWidget);
connect(mColorWheel, &ColorWheel::colorChanged, this, &ColorBox::onWheelMove);
- connect(mColorInspector, &ColorInspector::colorChanged, this, &ColorBox::onSpinboxChange);
connect(mColorWheel, &ColorWheel::colorSelected, this, &ColorBox::onWheelRelease);
- QColor defaultColor;
- defaultColor.setHsv(0, 0, 0);
- mColorWheel->setColor(defaultColor);
- mColorInspector->setColor(defaultColor);
-}
-
-ColorBox::~ColorBox()
-{
-}
-
-void ColorBox::initUI()
-{
+ connect(editor(), &Editor::objectLoaded, this, &ColorBox::updateUI);
}
void ColorBox::updateUI()
{
+ QColor newColor = editor()->color()->frontColor();
+ setColor(newColor);
}
QColor ColorBox::color()
@@ -63,36 +60,22 @@ QColor ColorBox::color()
return mColorWheel->color();
}
-void ColorBox::setColor(const QColor& newColor)
+void ColorBox::setColor(QColor newColor)
{
- if ( newColor.toHsv() != mColorWheel->color() )
- {
- mColorWheel->setColor(newColor);
- mColorInspector->setColor(newColor);
-
- emit colorChanged(newColor);
- }
-}
+ newColor = newColor.toHsv();
-void ColorBox::onSpinboxChange(const QColor& color)
-{
- if ( mColorWheel->color() != color.toHsv() )
+ if ( newColor != mColorWheel->color() )
{
- mColorWheel->setColor(color);
- emit colorChanged(color);
+ mColorWheel->setColor(newColor);
}
}
void ColorBox::onWheelMove(const QColor& color)
{
- if ( mColorInspector->color() != color )
- {
- mColorInspector->setColor(color);
- }
+ emit colorChanged(color);
}
void ColorBox::onWheelRelease(const QColor& color)
{
- mColorInspector->setColor(color);
emit colorChanged(color);
}
diff --git a/app/src/colorbox.h b/app/src/colorbox.h
index 23d8b604b..1d5bbb2dd 100644
--- a/app/src/colorbox.h
+++ b/app/src/colorbox.h
@@ -19,8 +19,6 @@ GNU General Public License for more details.
#include "basedockwidget.h"
class ColorWheel;
-class ColorInspector;
-
class ColorBox : public BaseDockWidget
{
@@ -34,18 +32,18 @@ class ColorBox : public BaseDockWidget
void updateUI() override;
QColor color();
- void setColor(const QColor&);
+ void setColor(QColor);
Q_SIGNALS:
void colorChanged(const QColor&);
private:
- void onSpinboxChange(const QColor&);
void onWheelMove(const QColor&);
void onWheelRelease(const QColor&);
ColorWheel* mColorWheel = nullptr;
- ColorInspector* mColorInspector = nullptr;
+
+// ColorInspector* mColorInspector = nullptr;
};
#endif // COLORBOX_H
diff --git a/app/src/colordictionary.h b/app/src/colordictionary.h
index 140bb3d5e..543050f4d 100644
--- a/app/src/colordictionary.h
+++ b/app/src/colordictionary.h
@@ -1,5 +1,8 @@
+#ifndef _COLOR_DICTIONARY_H_
+#define _COLOR_DICTIONARY_H_
+
// Initialize NBS/ISCC Color dictionary, John Foster version (http://tx4.us/nbs-iscc.htm), converted to CIE L*u*v with D65 white point and rounded.
const int dictSize = 267;
@@ -543,4 +546,6 @@ static const QString nameDict[dictSize] =
QObject::tr("Medium Gray"),
QObject::tr("Dark Gray"),
QObject::tr("Black")
-};
\ No newline at end of file
+};
+
+#endif
\ No newline at end of file
diff --git a/app/src/colorinspector.cpp b/app/src/colorinspector.cpp
index 385eb5555..19904031b 100644
--- a/app/src/colorinspector.cpp
+++ b/app/src/colorinspector.cpp
@@ -16,37 +16,144 @@ GNU General Public License for more details.
#include "colorinspector.h"
#include "ui_colorinspector.h"
+#include
+#include
#include
+#include
+#include
+
+#include "colorslider.h"
+#include "pencildef.h"
+#include "editor.h"
+#include "colormanager.h"
+
ColorInspector::ColorInspector(QWidget *parent) :
- QWidget(parent),
- ui(new Ui::ColorInspector)
+ BaseDockWidget(parent)
+{
+ QWidget* innerWidget = new QWidget;
+ setWindowTitle(tr("Color Inspector", "Window title of color inspector"));
+
+ ui = new Ui::ColorInspector;
+ ui->setupUi(innerWidget);
+ setWidget(innerWidget);
+
+ QButtonGroup* colorModeChangeGroup = new QButtonGroup;
+
+ colorModeChangeGroup->addButton(ui->hsvButton);
+ colorModeChangeGroup->addButton(ui->rgbButton);
+ colorModeChangeGroup->setExclusive(true);
+}
+
+ColorInspector::~ColorInspector()
{
- ui->setupUi(this);
+ delete ui;
+}
+
+void ColorInspector::initUI()
+{
+ mCurrentColor = editor()->color()->frontColor();
+
+ QSettings settings(PENCIL2D, PENCIL2D);
+ isRgbColors = settings.value("isRgb").toBool();
+
+ if (isRgbColors) {
+ ui->rgbButton->setChecked(true);
+ } else {
+ ui->hsvButton->setChecked(true);
+ }
+ onModeChanged();
+
+ QPalette p1 = ui->colorWrapper->palette(), p2 = ui->color->palette();
+ p1.setBrush(QPalette::Background, QBrush(QImage(":/background/checkerboard.png")));
+ p2.setColor(QPalette::Background, mCurrentColor);
+ ui->colorWrapper->setPalette(p1);
+ ui->color->setPalette(p2);
+
+ if (isRgbColors)
+ {
+ ui->red_slider->init(ColorSlider::ColorType::RED, mCurrentColor, 0.0, 255.0);
+ ui->green_slider->init(ColorSlider::ColorType::GREEN, mCurrentColor, 0.0, 255.0);
+ ui->blue_slider->init(ColorSlider::ColorType::BLUE, mCurrentColor, 0.0, 255.0);
+ ui->alpha_slider->init(ColorSlider::ColorType::ALPHA, mCurrentColor, 0.0, 255.0);
+ }
+ else
+ {
+ ui->red_slider->init(ColorSlider::ColorType::HUE, mCurrentColor, 0.0, 359.0);
+ ui->green_slider->init(ColorSlider::ColorType::SAT, mCurrentColor, 0.0, 255.0);
+ ui->blue_slider->init(ColorSlider::ColorType::VAL, mCurrentColor, 0.0, 255.0);
+ ui->alpha_slider->init(ColorSlider::ColorType::ALPHA, mCurrentColor, 0.0, 255.0);
+ }
auto spinBoxChanged = static_cast(&QSpinBox::valueChanged);
connect(ui->RedspinBox, spinBoxChanged, this, &ColorInspector::onColorChanged);
connect(ui->GreenspinBox, spinBoxChanged, this, &ColorInspector::onColorChanged);
connect(ui->BluespinBox, spinBoxChanged, this, &ColorInspector::onColorChanged);
connect(ui->AlphaspinBox, spinBoxChanged, this, &ColorInspector::onColorChanged);
- connect(ui->rgb, &QRadioButton::toggled, this, &ColorInspector::onModeChanged);
+ connect(ui->rgbButton, &QPushButton::clicked, this, &ColorInspector::onModeChanged);
+ connect(ui->hsvButton, &QPushButton::clicked, this, &ColorInspector::onModeChanged);
+
+ connect(ui->red_slider, &ColorSlider::valueChanged, this, &ColorInspector::onSliderChanged);
+ connect(ui->green_slider, &ColorSlider::valueChanged, this, &ColorInspector::onSliderChanged);
+ connect(ui->blue_slider, &ColorSlider::valueChanged, this, &ColorInspector::onSliderChanged);
+ connect(ui->alpha_slider, &ColorSlider::valueChanged, this, &ColorInspector::onSliderChanged);
+
+ connect(editor(), &Editor::objectLoaded, this, &ColorInspector::updateUI);
}
-ColorInspector::~ColorInspector()
+void ColorInspector::updateUI()
{
- delete ui;
+ QColor newColor = editor()->color()->frontColor();
+ setColor(newColor);
}
-void ColorInspector::setColor(const QColor &newColor)
+void ColorInspector::onSliderChanged(QColor color)
{
- if (newColor == m_color)
+ if (isRgbColors) {
+ ui->red_slider->setRgb(color);
+ ui->green_slider->setRgb(color);
+ ui->blue_slider->setRgb(color);
+ ui->alpha_slider->setRgb(color);
+ } else {
+ ui->red_slider->setHsv(color);
+ ui->green_slider->setHsv(color);
+ ui->blue_slider->setHsv(color);
+ ui->alpha_slider->setHsv(color);
+ }
+
+ emit colorChanged(color);
+}
+
+void ColorInspector::setColor(QColor newColor)
+{
+ // this is a UI update function, never emit any signals
+ // grab the color from color manager, and then update itself, that's it.
+
+ // compare under the same color spec
+ newColor = (isRgbColors) ? newColor.toRgb() : newColor.toHsv();
+
+ if (newColor == mCurrentColor)
{
return;
}
- noColorUpdate = true;
if(isRgbColors)
{
+ QSignalBlocker b1(ui->red_slider);
+ QSignalBlocker b2(ui->green_slider);
+ QSignalBlocker b3(ui->blue_slider);
+ QSignalBlocker b4(ui->alpha_slider);
+
+ ui->red_slider->setRgb(newColor);
+ ui->green_slider->setRgb(newColor);
+ ui->blue_slider->setRgb(newColor);
+ ui->alpha_slider->setRgb(newColor);
+
+ QSignalBlocker b5(ui->RedspinBox);
+ QSignalBlocker b6(ui->GreenspinBox);
+ QSignalBlocker b7(ui->BluespinBox);
+ QSignalBlocker b8(ui->AlphaspinBox);
+
ui->RedspinBox->setValue(newColor.red());
ui->GreenspinBox->setValue(newColor.green());
ui->BluespinBox->setValue(newColor.blue());
@@ -54,42 +161,82 @@ void ColorInspector::setColor(const QColor &newColor)
}
else
{
+ QSignalBlocker b1(ui->red_slider);
+ QSignalBlocker b2(ui->green_slider);
+ QSignalBlocker b3(ui->blue_slider);
+ QSignalBlocker b4(ui->alpha_slider);
+
+ ui->red_slider->setHsv(newColor);
+ ui->green_slider->setHsv(newColor);
+ ui->blue_slider->setHsv(newColor);
+ ui->alpha_slider->setHsv(newColor);
+
+ QSignalBlocker b5(ui->RedspinBox);
+ QSignalBlocker b6(ui->GreenspinBox);
+ QSignalBlocker b7(ui->BluespinBox);
+ QSignalBlocker b8(ui->AlphaspinBox);
+
ui->RedspinBox->setValue(newColor.hsvHue());
ui->GreenspinBox->setValue(qRound(newColor.hsvSaturation() / 2.55));
ui->BluespinBox->setValue(qRound(newColor.value() / 2.55));
ui->AlphaspinBox->setValue(qRound(newColor.alpha() / 2.55));
}
- m_color = newColor;
+
+ mCurrentColor = newColor;
QPalette p1 = ui->colorWrapper->palette(), p2 = ui->color->palette();
p1.setBrush(QPalette::Background, QBrush(QImage(":/background/checkerboard.png")));
- p2.setColor(QPalette::Background, m_color);
+ p2.setColor(QPalette::Background, mCurrentColor);
ui->colorWrapper->setPalette(p1);
ui->color->setPalette(p2);
- noColorUpdate = false;
+
+ update();
}
QColor ColorInspector::color()
{
- return m_color;
+ return mCurrentColor;
}
-void ColorInspector::onModeChanged()
+void ColorInspector::paintEvent(QPaintEvent*)
{
- bool newValue = ui->rgb->isChecked();
- if (isRgbColors == newValue)
+ // HACK: possible bug in 5.9
+ // title style is not set when window is not docked
+ // this enforces the style again. This is what QDockWidget
+ // should be doing behind the scene
+ if (!this->isFloating())
{
- return;
+ QStyleOptionDockWidget opt;
+ initStyleOption(&opt);
+
+ QStylePainter p(this);
+ p.drawControl(QStyle::CE_DockWidgetTitle, opt);
}
+}
+
+void ColorInspector::onModeChanged()
+{
+ // assume hsv if not checked
+ bool newValue = ui->rgbButton->isChecked();
+
+ QSettings settings(PENCIL2D, PENCIL2D);
+ settings.setValue("isRgb", newValue);
+
isRgbColors = newValue;
- noColorUpdate = true;
if (isRgbColors)
{
- ui->red->setText(tr("Red"));
- ui->green->setText(tr("Green"));
- ui->blue->setText(tr("Blue"));
- ui->alpha->setText(tr("Alpha"));
+ // Spinboxes may emit unwanted valueChanged signals when setting ranges
+ // so block them all first
+ QSignalBlocker b1(ui->RedspinBox);
+ QSignalBlocker b2(ui->GreenspinBox);
+ QSignalBlocker b3(ui->BluespinBox);
+ QSignalBlocker b4(ui->AlphaspinBox);
+
+ ui->red->setText("R");
+ ui->green->setText("G");
+ ui->blue->setText("B");
+ ui->alpha->setText("A");
ui->RedspinBox->setRange(0,255);
ui->RedspinBox->setSuffix("");
@@ -99,18 +246,45 @@ void ColorInspector::onModeChanged()
ui->BluespinBox->setSuffix("");
ui->AlphaspinBox->setRange(0,255);
ui->AlphaspinBox->setSuffix("");
- m_color = m_color.toRgb();
- ui->RedspinBox->setValue(m_color.red());
- ui->GreenspinBox->setValue(m_color.green());
- ui->BluespinBox->setValue(m_color.blue());
- ui->AlphaspinBox->setValue(m_color.alpha());
+
+ mCurrentColor = mCurrentColor.toRgb();
+
+ ui->red_slider->setMax(255);
+ ui->red_slider->setColorType(ColorSlider::ColorType::RED);
+ ui->red_slider->setColorSpecType(ColorSlider::ColorSpecType::RGB);
+ ui->green_slider->setColorSpecType(ColorSlider::ColorSpecType::RGB);
+ ui->green_slider->setColorType(ColorSlider::ColorType::GREEN);
+ ui->blue_slider->setColorSpecType(ColorSlider::ColorSpecType::RGB);
+ ui->blue_slider->setColorType(ColorSlider::ColorType::BLUE);
+ ui->alpha_slider->setColorSpecType(ColorSlider::ColorSpecType::RGB);
+ ui->alpha_slider->setColorType(ColorSlider::ColorType::ALPHA);
+
+ ui->RedspinBox->setValue(mCurrentColor.red());
+ ui->GreenspinBox->setValue(mCurrentColor.green());
+ ui->BluespinBox->setValue(mCurrentColor.blue());
+ ui->AlphaspinBox->setValue(mCurrentColor.alpha());
}
else
{
- ui->red->setText(tr("Hue"));
- ui->green->setText(tr("Saturation"));
- ui->blue->setText(tr("Value"));
- ui->alpha->setText(tr("Alpha"));
+ QSignalBlocker b1(ui->RedspinBox);
+ QSignalBlocker b2(ui->GreenspinBox);
+ QSignalBlocker b3(ui->BluespinBox);
+ QSignalBlocker b4(ui->AlphaspinBox);
+
+ ui->red->setText("H");
+ ui->green->setText("S");
+ ui->blue->setText("V");
+ ui->alpha->setText("A");
+
+ ui->red_slider->setMax(359);
+ ui->red_slider->setColorType(ColorSlider::ColorType::HUE);
+ ui->red_slider->setColorSpecType(ColorSlider::ColorSpecType::HSV);
+ ui->green_slider->setColorType(ColorSlider::ColorType::SAT);
+ ui->green_slider->setColorSpecType(ColorSlider::ColorSpecType::HSV);
+ ui->blue_slider->setColorType(ColorSlider::ColorType::VAL);
+ ui->blue_slider->setColorSpecType(ColorSlider::ColorSpecType::HSV);
+ ui->alpha_slider->setColorType(ColorSlider::ColorType::ALPHA);
+ ui->alpha_slider->setColorSpecType(ColorSlider::ColorSpecType::HSV);
ui->RedspinBox->setRange(0,359);
ui->RedspinBox->setSuffix("°");
@@ -121,34 +295,35 @@ void ColorInspector::onModeChanged()
ui->AlphaspinBox->setRange(0,100);
ui->AlphaspinBox->setSuffix("%");
- m_color = m_color.toHsv();
- ui->RedspinBox->setValue(m_color.hue());
- ui->GreenspinBox->setValue(m_color.saturation());
- ui->BluespinBox->setValue(m_color.value());
- ui->AlphaspinBox->setValue(m_color.alpha());
+ mCurrentColor = mCurrentColor.toHsv();
+
+ const qreal bound = 100.0 / 255.0; // from 255 to 100
+
+ ui->RedspinBox->setValue(mCurrentColor.hsvHue());
+ ui->GreenspinBox->setValue(qRound(mCurrentColor.hsvSaturation()*bound));
+ ui->BluespinBox->setValue(qRound(mCurrentColor.value()*bound));
+ ui->AlphaspinBox->setValue(qRound(mCurrentColor.alpha()*bound));
}
- noColorUpdate = false;
+
emit modeChange(isRgbColors);
}
void ColorInspector::onColorChanged()
{
- if(noColorUpdate) return;
-
QColor c;
if (isRgbColors) {
- c = QColor::fromRgb(
+ c.setRgb(
ui->RedspinBox->value(),
ui->GreenspinBox->value(),
ui->BluespinBox->value(),
ui->AlphaspinBox->value());
} else {
- c = QColor::fromHsv(
+ c.setHsv(
ui->RedspinBox->value(),
- ui->GreenspinBox->value() * 2.555,
- ui->BluespinBox->value() * 2.555,
- ui->AlphaspinBox->value() * 2.555);
+ ui->GreenspinBox->value()* 2.55,
+ ui->BluespinBox->value()* 2.55,
+ ui->AlphaspinBox->value()* 2.55);
}
emit colorChanged(c);
diff --git a/app/src/colorinspector.h b/app/src/colorinspector.h
index 137f88f6e..1b4bca07e 100644
--- a/app/src/colorinspector.h
+++ b/app/src/colorinspector.h
@@ -16,36 +16,47 @@ GNU General Public License for more details.
#ifndef COLORSPINBOXGROUP_H
#define COLORSPINBOXGROUP_H
-#include
+#include
+#include "basedockwidget.h"
namespace Ui {
class ColorInspector;
}
-class ColorInspector : public QWidget
+class ColorInspector : public BaseDockWidget
{
Q_OBJECT
+
+ friend class ColorSliders;
public:
explicit ColorInspector(QWidget *parent = 0);
~ColorInspector();
QColor color();
+
+ void initUI() override;
+ void updateUI() override;
+
+protected:
+ void paintEvent(QPaintEvent *) override;
+
signals:
void colorChanged(const QColor& c);
void modeChange(const bool& isRgb);
public slots:
- void setColor(const QColor &c);
+ void setColor(QColor newColor);
private slots:
void onModeChanged();
void onColorChanged();
+ void onSliderChanged(QColor color);
private:
+
Ui::ColorInspector* ui = nullptr;
bool isRgbColors = true;
- bool noColorUpdate = false;
- QColor m_color;
+ QColor mCurrentColor;
};
#endif // COLORSPINBOXGROUP_H
diff --git a/app/src/colorpalettewidget.cpp b/app/src/colorpalettewidget.cpp
index 0a081b6c2..25b604e57 100644
--- a/app/src/colorpalettewidget.cpp
+++ b/app/src/colorpalettewidget.cpp
@@ -15,14 +15,24 @@ GNU General Public License for more details.
*/
#include "colorpalettewidget.h"
+#include "ui_colorpalette.h"
+
+// Standard libraries
+#include
+// Qt
#include
#include
#include
#include
#include
-#include "ui_colorpalette.h"
+#include
+#include
+#include
+#include
+#include
+// Project
#include "colordictionary.h"
#include "colourref.h"
#include "object.h"
@@ -46,21 +56,39 @@ ColorPaletteWidget::~ColorPaletteWidget()
void ColorPaletteWidget::initUI()
{
- // "Remove color" feature is disabled because
- // vector strokes that are linked to palette
- // colors don't handle color removal from palette
- mIconSize = QSize(34, 34);
- ui->removeColorButton->hide();
- updateUI();
+ QSettings settings(PENCIL2D, PENCIL2D);
+ int colorGridSize = settings.value("PreferredColorGridSize", 34).toInt();
+
+ mIconSize = QSize(colorGridSize, colorGridSize);
+
+ ui->colorListWidget->setContextMenuPolicy(Qt::CustomContextMenu);
+
+ QString sViewMode = settings.value("ColorPaletteViewMode", "ListMode").toString();
+ if (sViewMode == "ListMode")
+ setListMode();
+ else
+ setGridMode();
+
+ buttonStylesheet = "::menu-indicator{ image: none; }"
+ "QPushButton { border: 0px; }"
+ "QPushButton:pressed { border: 1px solid #ADADAD; border-radius: 2px; background-color: #D5D5D5; }"
+ "QPushButton:checked { border: 1px solid #ADADAD; border-radius: 2px; background-color: #D5D5D5; }";
+
+ ui->addColorButton->setStyleSheet(buttonStylesheet);
+ ui->removeColorButton->setStyleSheet(buttonStylesheet);
+ ui->colorDialogButton->setStyleSheet(buttonStylesheet);
+
palettePreferences();
- connect(ui->colorListWidget, &QListWidget::currentItemChanged, this, &ColorPaletteWidget::colorListCurrentItemChanged);
connect(ui->colorListWidget, &QListWidget::itemClicked, this, &ColorPaletteWidget::clickColorListItem);
+
connect(ui->colorListWidget, &QListWidget::itemDoubleClicked, this, &ColorPaletteWidget::changeColourName);
- connect(ui->colorListWidget, &QListWidget::currentTextChanged, this, &ColorPaletteWidget::onActiveColorNameChange);
+ connect(ui->colorListWidget, &QListWidget::itemChanged, this, &ColorPaletteWidget::onItemChanged);
connect(ui->addColorButton, &QPushButton::clicked, this, &ColorPaletteWidget::clickAddColorButton);
+ connect(ui->colorDialogButton, &QPushButton::clicked, this, &ColorPaletteWidget::clickColorDialogButton);
connect(ui->removeColorButton, &QPushButton::clicked, this, &ColorPaletteWidget::clickRemoveColorButton);
+ connect(ui->colorListWidget, &QListWidget::customContextMenuRequested, this, &ColorPaletteWidget::showContextMenu);
}
void ColorPaletteWidget::updateUI()
@@ -69,6 +97,54 @@ void ColorPaletteWidget::updateUI()
updateGridUI();
}
+void ColorPaletteWidget::showContextMenu(const QPoint &pos)
+{
+ QPoint globalPos = ui->colorListWidget->mapToGlobal(pos);
+
+ QMenu* menu = new QMenu();
+ menu->addAction(tr("Add"), this, &ColorPaletteWidget::addItem, 0);
+ menu->addAction(tr("Replace"), this, &ColorPaletteWidget::replaceItem, 0);
+ menu->addAction(tr("Remove"), this, &ColorPaletteWidget::removeItem, 0);
+
+ menu->exec(globalPos);
+}
+
+void ColorPaletteWidget::addItem()
+{
+ QSignalBlocker b(ui->colorListWidget);
+ QColor newColour = editor()->color()->frontColor();
+
+ // add in front of selected color
+ int colorIndex = ui->colorListWidget->currentRow()+1;
+
+ ColourRef ref(newColour);
+ ref.name = getDefaultColorName(newColour);
+
+ editor()->object()->addColourAtIndex(colorIndex, ref);
+ refreshColorList();
+}
+
+void ColorPaletteWidget::replaceItem()
+{
+ QSignalBlocker b(ui->colorListWidget);
+ int index = ui->colorListWidget->currentRow();
+
+ QColor newColour = editor()->color()->frontColor();
+
+ if (index >= 0)
+ {
+ updateItemColor(index, newColour);
+ emit colorChanged(newColour);
+ ui->colorListWidget->setCurrentRow(index);
+ }
+}
+
+void ColorPaletteWidget::removeItem()
+{
+ QSignalBlocker b(ui->colorListWidget);
+ clickRemoveColorButton();
+}
+
void ColorPaletteWidget::setColor(QColor newColor, int colorIndex)
{
QSignalBlocker b(ui->colorListWidget);
@@ -76,7 +152,6 @@ void ColorPaletteWidget::setColor(QColor newColor, int colorIndex)
if (colorIndex > 0)
{
- updateItemColor(colorIndex, newColor);
emit colorChanged(newColor);
}
}
@@ -98,7 +173,6 @@ int ColorPaletteWidget::currentColourNumber()
void ColorPaletteWidget::refreshColorList()
{
QSignalBlocker b(ui->colorListWidget);
-
if (ui->colorListWidget->count() > 0)
{
ui->colorListWidget->clear();
@@ -109,14 +183,20 @@ void ColorPaletteWidget::refreshColorList()
swatchPainter.drawTiledPixmap(0, 0, mIconSize.width(), mIconSize.height(), QPixmap(":/background/checkerboard.png"));
swatchPainter.end();
QPixmap colourSwatch;
+ QPen borderShadow(QColor(0, 0, 0, 200), 1, Qt::DotLine, Qt::FlatCap, Qt::MiterJoin);
+ QVector dashPattern;
+ dashPattern << 4 << 4;
+ borderShadow.setDashPattern(dashPattern);
+ QPen borderHighlight(borderShadow);
+ borderHighlight.setColor(QColor(255, 255, 255, 200));
+ borderHighlight.setDashOffset(4);
- QListWidgetItem* colourItem;
- ColourRef colourRef;
- for (int i = 0; i < editor()->object()->getColourCount(); i++)
- {
- colourRef = editor()->object()->getColour(i);
+ int colourCount = editor()->object()->getColourCount();
- colourItem = new QListWidgetItem(ui->colorListWidget);
+ for (int i = 0; i < colourCount; i++)
+ {
+ const ColourRef colourRef = editor()->object()->getColour(i);
+ QListWidgetItem* colourItem = new QListWidgetItem(ui->colorListWidget);
if (ui->colorListWidget->viewMode() != QListView::IconMode)
{
@@ -129,12 +209,26 @@ void ColorPaletteWidget::refreshColorList()
colourSwatch = originalColourSwatch;
swatchPainter.begin(&colourSwatch);
swatchPainter.fillRect(0, 0, mIconSize.width(), mIconSize.height(), colourRef.colour);
+
+ QIcon swatchIcon;
+ swatchIcon.addPixmap(colourSwatch, QIcon::Normal);
+
+ // Draw selection border
+ if(ui->colorListWidget->viewMode() == QListView::IconMode) {
+ swatchPainter.setPen(borderHighlight);
+ swatchPainter.drawRect(0, 0, mIconSize.width() - 1, mIconSize.height() - 1);
+ swatchPainter.setPen(borderShadow);
+ swatchPainter.drawRect(0, 0, mIconSize.width() - 1, mIconSize.height() - 1);
+ }
+ swatchIcon.addPixmap(colourSwatch, QIcon::Selected);
+
+ colourItem->setIcon(swatchIcon);
swatchPainter.end();
- colourItem->setIcon(colourSwatch);
colourItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable);
ui->colorListWidget->addItem(colourItem);
}
+ updateGridUI();
update();
}
@@ -163,25 +257,21 @@ void ColorPaletteWidget::changeColourName(QListWidgetItem* item)
}
}
-void ColorPaletteWidget::onActiveColorNameChange(QString name)
+void ColorPaletteWidget::onItemChanged(QListWidgetItem* item)
{
- if (!name.isNull())
- {
- editor()->object()->renameColour(ui->colorListWidget->currentRow(), name);
- }
-}
-
-void ColorPaletteWidget::colorListCurrentItemChanged(QListWidgetItem* current, QListWidgetItem* previous)
-{
- if (!current)
- {
- current = previous;
- }
- emit colorNumberChanged(ui->colorListWidget->row(current));
+ int index = ui->colorListWidget->row(item);
+ QString newColorName = item->text();
+ editor()->object()->renameColour(index, newColorName);
}
void ColorPaletteWidget::clickColorListItem(QListWidgetItem* currentItem)
{
+ auto modifiers = qApp->keyboardModifiers();
+
+ // to avoid conflicts with multiple selections
+ // ie. will be seen as selected twice and cause problems
+ if (modifiers & Qt::ShiftModifier || modifiers & Qt::ControlModifier) { return; }
+
int colorIndex = ui->colorListWidget->row(currentItem);
emit colorNumberChanged(colorIndex);
@@ -192,10 +282,20 @@ void ColorPaletteWidget::palettePreferences()
ui->colorListWidget->setMinimumWidth(ui->colorListWidget->sizeHintForColumn(0));
// Let's pretend this button is a separator
- mSeparator = new QAction(tr(""), this);
+ mSeparator = new QAction("", this);
mSeparator->setSeparator(true);
+ buttonStylesheet = "::menu-indicator{ image: none; }"
+ "QToolButton { border: 0px; }"
+ "QToolButton:pressed { border: 1px solid #ADADAD; border-radius: 2px; background-color: #D5D5D5; }"
+ "QToolButton:checked { border: 1px solid #ADADAD; border-radius: 2px; background-color: #D5D5D5; }";
+
+
// Add to UI
+ ui->palettePref->setIcon(QIcon(":/app/icons/new/svg/more_options.svg"));
+ ui->palettePref->setIconSize(QSize(15,15));
+ ui->palettePref->setArrowType(Qt::ArrowType::NoArrow);
+ ui->palettePref->setStyleSheet(buttonStylesheet);
ui->palettePref->addAction(ui->listModeAction);
ui->palettePref->addAction(ui->gridModeAction);
ui->palettePref->addAction(mSeparator);
@@ -203,6 +303,15 @@ void ColorPaletteWidget::palettePreferences()
ui->palettePref->addAction(ui->mediumSwatchAction);
ui->palettePref->addAction(ui->largeSwatchAction);
+ if (mIconSize.width() > 30) ui->largeSwatchAction->setChecked(true);
+ else if (mIconSize.width() > 20) ui->mediumSwatchAction->setChecked(true);
+ else ui->smallSwatchAction->setChecked(true);
+
+ if (ui->colorListWidget->viewMode() == QListView::ListMode)
+ ui->listModeAction->setChecked(true);
+ else
+ ui->gridModeAction->setChecked(true);
+
connect(ui->listModeAction, &QAction::triggered, this, &ColorPaletteWidget::setListMode);
connect(ui->gridModeAction, &QAction::triggered, this, &ColorPaletteWidget::setGridMode);
connect(ui->smallSwatchAction, &QAction::triggered, this, &ColorPaletteWidget::setSwatchSizeSmall);
@@ -216,6 +325,9 @@ void ColorPaletteWidget::setListMode()
ui->colorListWidget->setMovement(QListView::Static);
ui->colorListWidget->setGridSize(QSize(-1, -1));
updateUI();
+
+ QSettings settings(PENCIL2D, PENCIL2D);
+ settings.setValue("ColorPaletteViewMode", "ListMode");
}
void ColorPaletteWidget::setGridMode()
@@ -223,34 +335,17 @@ void ColorPaletteWidget::setGridMode()
ui->colorListWidget->setViewMode(QListView::IconMode);
ui->colorListWidget->setMovement(QListView::Static); // TODO: update swatch index on move
ui->colorListWidget->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
- ui->colorListWidget->setGridSize(mIconSize);
+ ui->colorListWidget->setGridSize(QSize(mIconSize.width() + 1, mIconSize.height() + 1));
+
updateUI();
+
+ QSettings settings(PENCIL2D, PENCIL2D);
+ settings.setValue("ColorPaletteViewMode", "GridMode");
}
void ColorPaletteWidget::resizeEvent(QResizeEvent* event)
{
- if (ui->colorListWidget->viewMode() == QListView::IconMode)
- {
- // Find the value to divide with
- for (int i = 1; i < 75; i++)
- {
- int size = (ui->colorListWidget->width() - 18) / i; // subtract scrollbar width
- if (size >= mIconSize.width() && size <= mIconSize.width() + 8)
- {
- stepper = size;
- }
- }
- QSize tempSize = QSize(stepper, mIconSize.height());
-
- ui->colorListWidget->setIconSize(QSize(tempSize.width(), mIconSize.height()));
- ui->colorListWidget->setGridSize(QSize(tempSize.width(), mIconSize.height()));
- mIconSize.setWidth(mIconSize.width());
- }
- else
- {
- ui->colorListWidget->setIconSize(mIconSize);
- ui->colorListWidget->setGridSize(QSize(-1, -1));
- }
+ updateUI();
QWidget::resizeEvent(event);
}
@@ -259,8 +354,11 @@ void ColorPaletteWidget::setSwatchSizeSmall()
if (mIconSize.width() > 18)
{
mIconSize = QSize(14, 14);
+ updateUI();
+
+ QSettings settings(PENCIL2D, PENCIL2D);
+ settings.setValue("PreferredColorGridSize", 14);
}
- updateUI();
}
void ColorPaletteWidget::setSwatchSizeMedium()
@@ -269,6 +367,9 @@ void ColorPaletteWidget::setSwatchSizeMedium()
{
mIconSize = QSize(26, 26);
updateUI();
+
+ QSettings settings(PENCIL2D, PENCIL2D);
+ settings.setValue("PreferredColorGridSize", 26);
}
}
@@ -278,21 +379,41 @@ void ColorPaletteWidget::setSwatchSizeLarge()
{
mIconSize = QSize(34, 34);
updateUI();
+
+ QSettings settings(PENCIL2D, PENCIL2D);
+ settings.setValue("PreferredColorGridSize", 34);
}
}
void ColorPaletteWidget::updateGridUI()
{
- if (ui->colorListWidget->viewMode() == QListView::IconMode)
- ui->colorListWidget->setGridSize(QSize(mIconSize.width(), mIconSize.height()));
+ if (ui->colorListWidget->viewMode() == QListView::IconMode) {
+ // Find the value to divide with
+ for (int i = 1; i < 75; i++)
+ {
+ int size = (ui->colorListWidget->width() - 18) / i; // subtract scrollbar width
+ if (size >= mIconSize.width() && size <= mIconSize.width() + 8)
+ {
+ stepper = size;
+ }
+ }
+ QSize tempSize = QSize(stepper, mIconSize.height());
+
+ ui->colorListWidget->setIconSize(QSize(tempSize.width(), mIconSize.height()));
+ ui->colorListWidget->setGridSize(QSize(tempSize.width(), mIconSize.height() + 2));
+ mIconSize.setWidth(mIconSize.width());
+ }
else
+ {
+ ui->colorListWidget->setIconSize(mIconSize);
ui->colorListWidget->setGridSize(QSize(-1, -1));
-
- ui->colorListWidget->setIconSize(mIconSize);
+ }
}
QString ColorPaletteWidget::getDefaultColorName(QColor c)
{
+ using std::pow;
+
// Separate rgb values for convenience
const int r = c.red();
const int g = c.green();
@@ -307,7 +428,7 @@ QString ColorPaletteWidget::getDefaultColorName(QColor c)
// Convert XYZ to CEI L*u*v
// (algorithm source: https://www.cs.rit.edu/~ncs/color/t_convert.html#XYZ%20to%20CIE%20L*a*b*%20(CIELAB)%20&%20CIELAB%20to%20XYZ)
// Helper function for the conversion
- auto f = [](const double a) { return a > 0.008856 ? cbrt(a) : 7.787 * a + 16 / 116; };
+ auto f = [](const double a) { return a > 0.008856 ? std::cbrt(a) : 7.787 * a + 16 / 116; };
// XYZ tristimulus values for D65 (taken from: https://en.wikipedia.org/wiki/Illuminant_D65#Definition)
const qreal xn = 95.047,
yn = 100,
@@ -322,10 +443,9 @@ QString ColorPaletteWidget::getDefaultColorName(QColor c)
{
// The color is grayscale so only compare to gray centroids so there is no 'false hue'
qreal minDist = pow(colorDict[dictSize - 5][0] - l, 2) + pow(colorDict[dictSize - 5][1] - u, 2) + pow(colorDict[dictSize - 5][2] - v, 2);
- qreal curDist;
for (int i = dictSize - 4; i < dictSize; i++)
{
- curDist = pow(colorDict[i][0] - l, 2) + pow(colorDict[i][1] - u, 2) + pow(colorDict[i][2] - v, 2);
+ qreal curDist = pow(colorDict[i][0] - l, 2) + pow(colorDict[i][1] - u, 2) + pow(colorDict[i][2] - v, 2);
if (curDist < minDist)
{
minDist = curDist;
@@ -336,10 +456,9 @@ QString ColorPaletteWidget::getDefaultColorName(QColor c)
else
{
qreal minDist = pow(colorDict[0][0] - l, 2) + pow(colorDict[0][1] - u, 2) + pow(colorDict[0][2] - v, 2);
- qreal curDist;
for (int i = 1; i < dictSize; i++)
{
- curDist = pow(colorDict[i][0] - l, 2) + pow(colorDict[i][1] - u, 2) + pow(colorDict[i][2] - v, 2);
+ qreal curDist = pow(colorDict[i][0] - l, 2) + pow(colorDict[i][1] - u, 2) + pow(colorDict[i][2] - v, 2);
if (curDist < minDist)
{
minDist = curDist;
@@ -350,40 +469,98 @@ QString ColorPaletteWidget::getDefaultColorName(QColor c)
return nameDict[minLoc];
}
+void ColorPaletteWidget::clickColorDialogButton()
+{
+ mIsColorDialog = true;
+ clickAddColorButton();
+ mIsColorDialog = false;
+}
+
void ColorPaletteWidget::clickAddColorButton()
{
QColor prevColor = Qt::white;
- if (currentColourNumber() > -1)
- {
- prevColor = editor()->object()->getColour(currentColourNumber()).colour;
- }
+ QColor newColour;
+
+ if (mIsColorDialog)
+ newColour = QColorDialog::getColor(prevColor.rgba(), this, QString(), QColorDialog::ShowAlphaChannel);
+ else
+ newColour = editor()->color()->frontColor();
- QColor newColour = QColorDialog::getColor(prevColor.rgba(), this, QString(), QColorDialog::ShowAlphaChannel);
if (!newColour.isValid())
{
- // User canceled operation
- return;
+ return; // User canceled operation
}
+ int colorIndex = editor()->object()->getColourCount();
ColourRef ref(newColour);
ref.name = getDefaultColorName(newColour);
editor()->object()->addColour(ref);
refreshColorList();
- int colorIndex = editor()->object()->getColourCount() - 1;
-
editor()->color()->setColorNumber(colorIndex);
editor()->color()->setColor(ref.colour);
}
void ColorPaletteWidget::clickRemoveColorButton()
{
- int colorNumber = ui->colorListWidget->currentRow();
- editor()->object()->removeColour(colorNumber);
+ for (auto item : ui->colorListWidget->selectedItems())
+ {
+ int index = ui->colorListWidget->row(item);
- refreshColorList();
+ // items are not deleted by qt, it has to be done manually
+ // delete should happen before removing the color from from palette
+ // as the palette will be one ahead and crash otherwise
+ if (editor()->object()->isColourInUse(index))
+ {
+ bool accepted = false;
+ if (!mMultipleSelected)
+ accepted = showPaletteWarning();
+
+ if ((accepted || mMultipleSelected) && editor()->object()->getColourCount() > 1)
+ {
+ delete item;
+ editor()->object()->removeColour(index);
+ }
+ }
+ else if (editor()->object()->getColourCount() > 1)
+ {
+ delete item;
+ editor()->object()->removeColour(index);
+ }
+ else if (editor()->object()->getColourCount() == 1)
+ {
+ showPaletteReminder();
+ }
+ editor()->updateCurrentFrame();
+ }
+ mMultipleSelected = false;
+}
+
+bool ColorPaletteWidget::showPaletteWarning()
+{
+ QMessageBox msgBox;
+ msgBox.setText(tr("The color(s) you are about to delete are currently being used by one or multiple strokes."));
+ msgBox.addButton(tr("Cancel"), QMessageBox::RejectRole);
+ QPushButton* removeButton = msgBox.addButton(tr("Delete"), QMessageBox::AcceptRole);
+
+ msgBox.exec();
+ if (msgBox.clickedButton() == removeButton)
+ {
+ if (ui->colorListWidget->selectedItems().size() > 1)
+ {
+ mMultipleSelected = true;
+ }
+ return true;
+ }
+ return false;
+}
+
+void ColorPaletteWidget::showPaletteReminder()
+{
+ QMessageBox::warning(nullptr, tr("Palette Restriction"),
+ tr("The palette requires at least one swatch to remain functional"));
}
void ColorPaletteWidget::updateItemColor(int itemIndex, QColor newColor)
@@ -392,7 +569,29 @@ void ColorPaletteWidget::updateItemColor(int itemIndex, QColor newColor)
QPainter swatchPainter(&colourSwatch);
swatchPainter.drawTiledPixmap(0, 0, mIconSize.width(), mIconSize.height(), QPixmap(":/background/checkerboard.png"));
swatchPainter.fillRect(0, 0, mIconSize.width(), mIconSize.height(), newColor);
- ui->colorListWidget->item(itemIndex)->setIcon(colourSwatch);
+
+ QPen borderShadow(QColor(0, 0, 0, 200), 1, Qt::DotLine, Qt::FlatCap, Qt::MiterJoin);
+ QVector dashPattern;
+ dashPattern << 4 << 4;
+ borderShadow.setDashPattern(dashPattern);
+ QPen borderHighlight(borderShadow);
+ borderHighlight.setColor(QColor(255, 255, 255, 200));
+ borderHighlight.setDashOffset(4);
+
+ QIcon swatchIcon;
+ swatchIcon.addPixmap(colourSwatch, QIcon::Normal);
+
+ if(ui->colorListWidget->viewMode() == QListView::IconMode)
+ {
+ // Draw selection border
+ swatchPainter.setPen(borderHighlight);
+ swatchPainter.drawRect(0, 0, mIconSize.width() - 1, mIconSize.height() - 1);
+ swatchPainter.setPen(borderShadow);
+ swatchPainter.drawRect(0, 0, mIconSize.width() - 1, mIconSize.height() - 1);
+ }
+ swatchIcon.addPixmap(colourSwatch, QIcon::Selected);
+
+ ui->colorListWidget->item(itemIndex)->setIcon(swatchIcon);
// Make sure to update grid in grid mode
if (ui->colorListWidget->viewMode() == QListView::IconMode)
diff --git a/app/src/colorpalettewidget.h b/app/src/colorpalettewidget.h
index 7386c5b29..d978ac368 100644
--- a/app/src/colorpalettewidget.h
+++ b/app/src/colorpalettewidget.h
@@ -41,6 +41,7 @@ class ColorPaletteWidget : public BaseDockWidget
Q_OBJECT
public:
+
explicit ColorPaletteWidget(QWidget* parent);
~ColorPaletteWidget();
@@ -53,19 +54,21 @@ class ColorPaletteWidget : public BaseDockWidget
void setColor(QColor, int);
void refreshColorList();
+ void showContextMenu(const QPoint&);
+
signals:
void colorChanged(QColor);
void colorNumberChanged(int);
protected:
- void resizeEvent(QResizeEvent *event) override;
+ void resizeEvent(QResizeEvent* event) override;
private slots:
- void colorListCurrentItemChanged(QListWidgetItem*, QListWidgetItem*);
void clickColorListItem(QListWidgetItem*);
void changeColourName(QListWidgetItem*);
- void onActiveColorNameChange(QString name);
+ void onItemChanged(QListWidgetItem* item);
void clickAddColorButton();
+ void clickColorDialogButton();
void clickRemoveColorButton();
void palettePreferences();
void setListMode();
@@ -73,6 +76,12 @@ private slots:
void setSwatchSizeSmall();
void setSwatchSizeMedium();
void setSwatchSizeLarge();
+ void addItem();
+ void replaceItem();
+ void removeItem();
+ void showPaletteReminder();
+
+ bool showPaletteWarning();
private:
void updateItemColor(int, QColor);
@@ -88,12 +97,17 @@ private slots:
QAction* mSmallSwatchAction = nullptr;
QAction* mMediumSwatchAction = nullptr;
QAction* mLargeSwatchAction = nullptr;
- QAction* mSeparator;
+ QAction* mSeparator = nullptr;
QSize mIconSize{ 34, 34 };
QMenu* mToolMenu = nullptr;
int stepper = 0;
+ QString buttonStylesheet;
+
+ bool mIsColorDialog = false;
+ bool mMultipleSelected = false;
+
};
#endif
diff --git a/app/src/colorslider.cpp b/app/src/colorslider.cpp
new file mode 100644
index 000000000..3d2597b7b
--- /dev/null
+++ b/app/src/colorslider.cpp
@@ -0,0 +1,400 @@
+#include "colorslider.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+
+ColorSlider::ColorSlider(QWidget* parent) : QWidget(parent)
+{
+
+}
+
+ColorSlider::~ColorSlider()
+{
+
+}
+
+void ColorSlider::init(ColorType type, QColor color, qreal min, float max)
+{
+ init(type, color, min, max, QSize(this->size()));
+}
+
+void ColorSlider::init(ColorType type, QColor color, qreal min, float max, QSize size)
+{
+ mMin = min;
+ mMax = max;
+ mColor = color;
+ mColorType = type;
+
+ drawColorBox(color, size);
+}
+
+void ColorSlider::paintEvent(QPaintEvent *)
+{
+ QPainter painter(this);
+
+ drawColorBox(mColor, mSize);
+
+ painter.drawPixmap(0, 0, mBoxPixmapSource);
+ drawPicker(mColor);
+ painter.end();
+
+}
+
+void ColorSlider::resizeEvent(QResizeEvent *event)
+{
+ mSize = event->size();
+ drawColorBox(mColor, event->size());
+}
+
+QLinearGradient ColorSlider::setColorSpec(QColor color)
+{
+ if (mSpecType == ColorSpecType::HSV)
+ {
+ return hsvGradient(color);
+ }
+ else if (mSpecType == ColorSpecType::RGB)
+ {
+ return rgbGradient(color);
+ }
+ Q_ASSERT(false);
+ return QLinearGradient();
+}
+
+QLinearGradient ColorSlider::rgbGradient(QColor color)
+{
+ int val = 0;
+ if (mColorType == ColorType::RED)
+ {
+
+ for (; val < mMax; val += 1)
+ {
+ mGradient.setColorAt(val / mMax, QColor::fromRgb(val,
+ 255,
+ 255,
+ color.alpha()));
+ }
+ }
+ else if (mColorType == ColorType::GREEN)
+ {
+
+ for (; val < mMax; val += 1)
+ {
+ mGradient.setColorAt(val / mMax, QColor::fromRgb(color.red(),
+ val,
+ color.blue(),
+ color.alpha()));
+ }
+ }
+ else if (mColorType == ColorType::BLUE)
+ {
+ for (; val < mMax; val += 1)
+ {
+ mGradient.setColorAt(val / mMax, QColor::fromRgb(color.red(),
+ color.green(),
+ val,
+ color.alpha()));
+ }
+ }
+ else if (mColorType == ColorType::ALPHA)
+ {
+ for (; val < mMax; val += 1)
+ {
+ mGradient.setColorAt(val / mMax, QColor::fromRgb(0,
+ 0,
+ 0,
+ val));
+ }
+ }
+ return mGradient;
+}
+
+QLinearGradient ColorSlider::hsvGradient(QColor color)
+{
+ int val = 0;
+ if (mColorType == ColorType::HUE) {
+
+ for (; val < mMax; val += 1)
+ {
+ mGradient.setColorAt(val / mMax, QColor::fromHsv(val,
+ 255,
+ 255,
+ color.alpha()));
+ }
+ }
+ else if (mColorType == ColorType::SAT)
+ {
+
+ for (; val < mMax; val += 1)
+ {
+ mGradient.setColorAt(val / mMax, QColor::fromHsv(color.hsvHue(),
+ val,
+ color.value(),
+ color.alpha()));
+ }
+ }
+ else if (mColorType == ColorType::VAL)
+ {
+ for (; val < mMax; val += 1)
+ {
+ mGradient.setColorAt(val / mMax, QColor::fromHsv(color.hsvHue(),
+ color.hsvSaturation(),
+ val,
+ color.alpha()));
+ }
+ }
+ else if (mColorType == ColorType::ALPHA)
+ {
+ for (; val < mMax; val += 1)
+ {
+ mGradient.setColorAt(val / mMax, QColor::fromHsv(0,
+ 0,
+ 0,
+ val));
+ }
+ }
+ return mGradient;
+}
+
+void ColorSlider::drawColorBox(QColor color, QSize size)
+{
+ QStyleOption option;
+ option.initFrom(this);
+
+ QBrush backgroundBrush = option.palette.window();
+
+ mBoxPixmapSource = QPixmap(size);
+
+ QPainter painter(&mBoxPixmapSource);
+ painter.setRenderHint(QPainter::Antialiasing);
+
+ painter.fillRect(mBoxPixmapSource.rect(), backgroundBrush);
+
+ mGradient = QLinearGradient(0,0,mBoxPixmapSource.width(),0);
+ mGradient = setColorSpec(color);
+
+ painter.end();
+
+ // draw checkerboard background
+ painter.begin(&mBoxPixmapSource);
+ QBrush brush2(QBrush(QPixmap("://icons/new/checkerboard_smaller")));
+
+ painter.setBrush(brush2);
+ QPen pen2;
+ pen2.setWidthF(0);
+ pen2.setColor(Qt::gray);
+ pen2.setCosmetic(false);
+ painter.setPen(pen2);
+ painter.drawRoundedRect(0,
+ 0,
+ mBoxPixmapSource.width(),
+ mBoxPixmapSource.height(),
+ 4,
+ mBoxPixmapSource.width(),
+ Qt::SizeMode::AbsoluteSize);
+
+ painter.end();
+
+ painter.begin(&mBoxPixmapSource);
+ painter.setRenderHint(QPainter::Antialiasing);
+
+ QBrush brush(mGradient);
+ QPen pen;
+ pen.setWidthF(0);
+ pen.setColor(Qt::gray);
+ pen.setCosmetic(false);
+ painter.setPen(pen);
+
+
+ painter.setBrush(brush);
+
+ painter.drawRoundedRect(0,
+ 0,
+ mBoxPixmapSource.width(),
+ mBoxPixmapSource.height(),
+ 4,
+ mBoxPixmapSource.width(),
+ Qt::SizeMode::AbsoluteSize);
+ painter.end();
+}
+
+void ColorSlider::mouseMoveEvent(QMouseEvent* event)
+{
+ colorPicked(event->pos());
+}
+
+void ColorSlider::mousePressEvent(QMouseEvent *event)
+{
+ colorPicked(event->pos());
+
+}
+
+void ColorSlider::drawPicker(QColor color)
+{
+ QPainter painter(this);
+ qreal val = 0;
+ QSize mPickerSize = QSize(10, this->height()-1);
+
+ QPen pen;
+ pen.setWidth(0);
+ pen.setColor(QColor(0,0,0,255));
+
+ if (mSpecType == ColorSpecType::HSV) {
+ if (mColorType == ColorType::HUE)
+ {
+ val = color.hsvHueF() * (mBoxPixmapSource.width()-mPickerSize.width());
+ }
+ else if (mColorType == ColorType::SAT)
+ {
+ if ( (color.hsvSaturation() > 127 || color.value() < 127) && color.alpha() > 127)
+ {
+ pen.setColor(Qt::white);
+ }
+ val = color.hsvSaturationF() * (mBoxPixmapSource.width()-mPickerSize.width());
+ }
+ else if (mColorType == ColorType::VAL)
+ {
+ if ( color.value() < 127 && color.alpha() > 127)
+ {
+ pen.setColor(Qt::white);
+ }
+ val = color.valueF() * (mBoxPixmapSource.width()-mPickerSize.width());
+ }
+ } else if (mSpecType == ColorSpecType::RGB) {
+ if (mColorType == ColorType::RED)
+ {
+ val = color.redF() * (mBoxPixmapSource.width()-mPickerSize.width());
+ }
+ else if (mColorType == ColorType::GREEN)
+ {
+ if ( color.alpha() > 127)
+ {
+ pen.setColor(Qt::white);
+ }
+ val = color.greenF() * (mBoxPixmapSource.width()-mPickerSize.width());
+ }
+ else if (mColorType == ColorType::BLUE)
+ {
+ if (color.alpha() > 127)
+ {
+ pen.setColor(Qt::white);
+ }
+ val = color.blueF() * (mBoxPixmapSource.width()-mPickerSize.width());
+ }
+ }
+ if (mColorType == ColorType::ALPHA)
+ {
+ if ( color.alpha() > 127)
+ {
+ pen.setColor(Qt::white);
+ }
+ val = color.alphaF() * (mBoxPixmapSource.width()-mPickerSize.width());
+ }
+
+
+ painter.setPen(pen);
+ painter.drawRect(val, 0, mPickerSize.width(), mPickerSize.height());
+ painter.end();
+}
+
+void ColorSlider::colorPicked(QPoint point)
+{
+ QColor colorPicked = mColor;
+ int colorMax = mMax;
+ int colorVal = 0;
+
+ colorVal = point.x()*colorMax/mBoxPixmapSource.width();
+
+ colorVal = (colorVal > colorMax) ? colorMax : colorVal;
+ colorVal = (colorVal < 0) ? 0 : colorVal;
+
+ if (mSpecType == ColorSpecType::HSV) {
+ switch(mColorType)
+ {
+ case ColorType::HUE:
+ {
+ colorPicked = QColor::fromHsv(colorVal,
+ mColor.hsvSaturation(),
+ mColor.value(),
+ mColor.alpha());
+
+ break;
+ }
+ case ColorType::SAT:
+ {
+ colorPicked = QColor::fromHsv(mColor.hsvHue(),
+ colorVal,
+ mColor.value(),
+ mColor.alpha());
+ break;
+ }
+ case ColorType::VAL:
+ {
+ colorPicked = QColor::fromHsv(mColor.hsvHue(),
+ mColor.hsvSaturation(),
+ colorVal,
+ mColor.alpha());
+ break;
+ }
+ case ColorType::ALPHA:
+ {
+
+ colorPicked = QColor::fromHsv(mColor.hsvHue(),
+ mColor.hsvSaturation(),
+ mColor.value(),
+ colorVal);
+ break;
+ }
+ default:
+ break;
+ }
+ } else if (mSpecType == ColorSpecType::RGB)
+ {
+ switch(mColorType)
+ {
+ case ColorType::RED:
+ {
+ colorPicked = QColor::fromRgb(colorVal,
+ mColor.green(),
+ mColor.blue(),
+ mColor.alpha());
+
+ break;
+ }
+ case ColorType::GREEN:
+ {
+ colorPicked = QColor::fromRgb(mColor.red(),
+ colorVal,
+ mColor.blue(),
+ mColor.alpha());
+ break;
+ }
+ case ColorType::BLUE:
+ {
+ colorPicked = QColor::fromRgb(mColor.red(),
+ mColor.green(),
+ colorVal,
+ mColor.alpha());
+ break;
+ }
+ case ColorType::ALPHA:
+ {
+
+ colorPicked = QColor::fromRgb(mColor.red(),
+ mColor.green(),
+ mColor.blue(),
+ colorVal);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ mColor = colorPicked;
+ emit valueChanged(mColor);
+}
diff --git a/app/src/colorslider.h b/app/src/colorslider.h
new file mode 100644
index 000000000..310d83f7e
--- /dev/null
+++ b/app/src/colorslider.h
@@ -0,0 +1,92 @@
+#ifndef COLORSLIDER_H
+#define COLORSLIDER_H
+
+#include
+
+
+class ColorSlider : public QWidget
+{
+ Q_OBJECT
+public:
+
+ enum ColorType {
+ HUE,
+ SAT,
+ VAL,
+ RED,
+ GREEN,
+ BLUE,
+ ALPHA
+ };
+ enum ColorSpecType {
+ RGB,
+ HSV,
+ HSL,
+ CMYK
+ };
+
+ explicit ColorSlider(QWidget* parent);
+ ~ColorSlider();
+
+ void init(ColorType type, QColor color, qreal min, float max);
+ void init(ColorType type, QColor color, qreal min, float max, QSize size);
+
+ QLinearGradient setColorSpec(QColor color);
+
+ QColor color() { return mColor; }
+
+ void setHsv(QColor hsv) { mColor.setHsv(hsv.hsvHue(),
+ hsv.hsvSaturation(),
+ hsv.value(),
+ hsv.alpha());
+ }
+
+ void setRgb(QColor rgb) { mColor.setRgb(rgb.red(),
+ rgb.green(),
+ rgb.blue(),
+ rgb.alpha());
+ }
+
+ void setColorSpecType(ColorSpecType newType) { this->mSpecType = newType; }
+ void setColorType(ColorType newType) { this->mColorType = newType; }
+
+ void setMin(float min) { mMin = min; }
+ void setMax(float max) { mMax = max; }
+
+protected:
+ void paintEvent(QPaintEvent* event) override;
+ void resizeEvent(QResizeEvent* event) override;
+ void mouseMoveEvent(QMouseEvent* event) override;
+ void mousePressEvent(QMouseEvent* event) override;
+
+//public slots:
+
+signals:
+ void valueChanged(QColor color);
+// void valueChanged(QColor color);
+
+private:
+
+ void drawColorBox(QColor color, QSize size);
+ void drawPicker(QColor color);
+ QLinearGradient hsvGradient(QColor color);
+ QLinearGradient rgbGradient(QColor color);
+
+ void colorPicked(QPoint point);
+
+ QPixmap mBoxPixmapTarget;
+ QPixmap mBoxPixmapSource;
+
+ QColor mColor;
+ float mMin = 0.0;
+ float mMax = 0.0;
+
+ ColorType mColorType;
+ ColorSpecType mSpecType;
+
+ QSize mSize = QSize(0,0);
+
+ QLinearGradient mGradient;
+};
+
+#endif // COLORSLIDER_H
diff --git a/app/src/colorwheel.cpp b/app/src/colorwheel.cpp
index 9c2613ddf..cdc2287d1 100644
--- a/app/src/colorwheel.cpp
+++ b/app/src/colorwheel.cpp
@@ -14,18 +14,22 @@ GNU General Public License for more details.
*/
-#include
+#include
#include
#include
#include
+#include
#include
+#include
#include
#include
+#include "pencildef.h"
#include "colorwheel.h"
ColorWheel::ColorWheel(QWidget* parent) : QWidget(parent)
{
+ setWindowTitle(tr("Color Wheel", "Color Wheel's window title"));
mCurrentColor = mCurrentColor.toHsv();
setMinimumHeight(100);
}
@@ -35,84 +39,22 @@ QColor ColorWheel::color()
return mCurrentColor;
}
-void ColorWheel::changeColor(const QColor& color)
+void ColorWheel::setColor(QColor color)
{
- if (color.toHsv() == mCurrentColor)
- {
- return;
- }
+ // this is a UI updating function, never emit any signals
+ // and don't call any functions that will emit signals
- if (color.spec() == QColor::Spec::Rgb)
- changeRgbColors(color);
- else
- changeHsvColors(color);
-
- if (color.alpha() != mCurrentColor.alpha())
- {
- alphaChanged(color.alpha());
- }
- update();
-}
+ color = color.toHsv();
-void ColorWheel::setColor(const QColor& color)
-{
- if (color.toHsv() == mCurrentColor)
+ if (color == mCurrentColor)
{
return;
}
- if (color.spec() == QColor::Spec::Rgb)
- changeRgbColors(color);
- else if (color.spec() == QColor::Spec::Hsv)
- changeHsvColors(color);
- else
- Q_ASSERT(false);
+ mCurrentColor = color;
drawSquareImage(color.hue());
-
- if (color.alpha() != mCurrentColor.alpha())
- {
- alphaChanged(color.alpha());
- }
-
update();
- emit colorSelected(color);
-}
-
-void ColorWheel::changeRgbColors(const QColor& color)
-{
- if (color.red() != mCurrentColor.red())
- {
- redChanged(color.red());
- }
-
- if (color.green() != mCurrentColor.green())
- {
- greenChanged(color.green());
- }
-
- if (color.blue() != mCurrentColor.blue())
- {
- blueChanged(color.blue());
- }
-}
-
-void ColorWheel::changeHsvColors(const QColor& color)
-{
- if (color.hue() != mCurrentColor.hue())
- {
- hueChanged(color.hue());
- }
-
- if (color.saturation() != mCurrentColor.saturation())
- {
- saturationChanged(color.saturation());
- }
-
- if (color.value() != mCurrentColor.value())
- {
- valueChanged(color.value());
- }
}
QColor ColorWheel::pickColor(const QPoint& point)
@@ -236,18 +178,23 @@ void ColorWheel::resizeEvent(QResizeEvent* event)
mWheelPixmap.fill(palette().background().color());
drawWheelImage(event->size());
drawSquareImage(mCurrentColor.hue());
+
update();
}
void ColorWheel::paintEvent(QPaintEvent*)
{
- QPainter painter(this);
+ QPainter painter;
+
+ painter.begin(this);
QStyleOption opt;
opt.initFrom(this);
+
composeWheel(mWheelPixmap);
painter.translate(width() / 2, height() / 2);
painter.translate(-mWheelPixmap.width() / 2, -mWheelPixmap.height() / 2);
painter.drawPixmap(0, 0, mWheelPixmap);
+
style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this);
}
@@ -312,18 +259,25 @@ void ColorWheel::drawSquareImage(const int &hue)
qreal m1 = (width() / 2) - (ir / qSqrt(2));
qreal m2 = (height() / 2) - (ir / qSqrt(2));
- QImage square(255, 255, QImage::Format_ARGB32_Premultiplied);
- QColor color;
+ QImage square(255, 255, QImage::Format_ARGB32);
- for (int i = 0; i < 255; ++i)
- {
- for (int j = 0; j < 255; ++j)
- {
- color = QColor::fromHsv(hue, i, 255 - j);
- QRgb rgb = qRgb(color.red(), color.green(), color.blue());
- square.setPixel(i, j, rgb);
- }
- }
+ QLinearGradient colorGradient = QLinearGradient(0, 0, square.width(), 0);
+ colorGradient.setColorAt(0, QColor(255,255,255));
+
+ // color square should always use full value and saturation
+ colorGradient.setColorAt(1, QColor::fromHsv(hue, 255, 255));
+
+ QLinearGradient blackGradient = QLinearGradient(0, 0, 0, square.height());
+ blackGradient.setColorAt(0, QColor(0,0,0,0));
+ blackGradient.setColorAt(1, QColor(0,0,0,255));
+
+ QBrush colorGradiantBrush = QBrush(colorGradient);
+ QBrush blackGradiantBrush = QBrush(blackGradient);
+
+ QPainter painter(&square);
+
+ painter.fillRect(square.rect(), colorGradiantBrush);
+ painter.fillRect(square.rect(), blackGradiantBrush);
qreal SquareWidth = 2 * ir / qSqrt(2.1);
mSquareImage = square.scaled(SquareWidth, SquareWidth);
@@ -359,25 +313,28 @@ void ColorWheel::drawPicker(const QColor& color)
{
QPainter painter(&mWheelPixmap);
painter.setRenderHint(QPainter::Antialiasing);
+ int ellipseSize = 10;
- QPoint squareTopLeft = mSquareRegion.boundingRect().topLeft();
+ QPoint squareTopLeft = mSquareRegion.boundingRect().topLeft()-QPoint(1,1);
- painter.translate(squareTopLeft.x(), squareTopLeft.y());
+ QSize squareSize = mSquareRegion.boundingRect().size() * 1.01;
- QSize squareSize = mSquareRegion.boundingRect().size();
-
- qreal S = color.saturationF() * squareSize.width();
- qreal V = squareSize.height() - (color.valueF() * squareSize.height());
+ qreal S = color.hsvSaturationF() * (squareSize.width());
+ qreal V = (squareSize.height() - (color.valueF() * squareSize.height()));
QPen pen;
- pen.setWidth(3);
- if (color.saturation() > 30 || color.value() < 50)
+ pen.setWidth(1);
+ if (color.hsvSaturation() > 30 || color.value() < 50)
{
pen.setColor(Qt::white);
}
painter.setPen(pen);
- painter.drawEllipse(S - 2, V - 2, 10, 10);
+ QTransform transform;
+ transform.translate(-ellipseSize/2,-ellipseSize/2);
+ transform.translate(squareTopLeft.x()+2,squareTopLeft.y()+2);
+ painter.setTransform(transform);
+ painter.drawEllipse(S, V, ellipseSize, ellipseSize);
}
void ColorWheel::composeWheel(QPixmap& pixmap)
@@ -388,69 +345,17 @@ void ColorWheel::composeWheel(QPixmap& pixmap)
composePainter.translate(-mSquareImage.width() / 2, -mSquareImage.height() / 2); //move to center of image
composePainter.drawImage(0, 0, mSquareImage);
composePainter.end();
- drawHueIndicator(mCurrentColor.hue());
+ drawHueIndicator(mCurrentColor.hsvHue());
drawPicker(mCurrentColor);
}
-void ColorWheel::redChanged(const int &red)
-{
- int g = mCurrentColor.green();
- int b = mCurrentColor.blue();
- int a = mCurrentColor.alpha();
-
- mCurrentColor.setRgb(red, g, b, a);
-
- if (!isVisible())
- {
- return;
- }
-
- update();
- emit colorChanged(mCurrentColor);
-}
-
-void ColorWheel::greenChanged(const int &green)
-{
- int r = mCurrentColor.red();
- int b = mCurrentColor.blue();
- int a = mCurrentColor.alpha();
-
- mCurrentColor.setRgb(r, green, b, a);
-
- if (!isVisible())
- {
- return;
- }
-
- update();
- emit colorChanged(mCurrentColor);
-}
-
-void ColorWheel::blueChanged(const int &blue)
-{
- int r = mCurrentColor.red();
- int g = mCurrentColor.green();
- int a = mCurrentColor.alpha();
-
- mCurrentColor.setRgb(r, g, blue, a);
-
- if (!isVisible())
- {
- return;
- }
-
- update();
- emit colorChanged(mCurrentColor);
-}
-
-
void ColorWheel::hueChanged(const int &hue)
{
if (hue < 0 || hue > 359)
{
return;
}
- int s = mCurrentColor.saturation();
+ int s = mCurrentColor.hsvSaturation();
int v = mCurrentColor.value();
int a = mCurrentColor.alpha();
@@ -469,7 +374,7 @@ void ColorWheel::hueChanged(const int &hue)
void ColorWheel::saturationChanged(const int &sat)
{
- int hue = mCurrentColor.hue();
+ int hue = mCurrentColor.hsvHue();
int value = mCurrentColor.value();
int alpha = mCurrentColor.alpha();
@@ -481,23 +386,11 @@ void ColorWheel::saturationChanged(const int &sat)
void ColorWheel::valueChanged(const int &value)
{
- int hue = mCurrentColor.hue();
- int sat = mCurrentColor.saturation();
+ int hue = mCurrentColor.hsvHue();
+ int sat = mCurrentColor.hsvSaturation();
int alpha = mCurrentColor.alpha();
mCurrentColor.setHsv(hue, sat, value, alpha);
update();
emit colorChanged(mCurrentColor);
}
-
-void ColorWheel::alphaChanged(const int &alpha)
-{
- mCurrentColor.setAlpha(alpha);
-
- if (!isVisible())
- {
- return;
- }
-
- emit colorChanged(mCurrentColor);
-}
diff --git a/app/src/colorwheel.h b/app/src/colorwheel.h
index 714936a8c..e2c32acc3 100644
--- a/app/src/colorwheel.h
+++ b/app/src/colorwheel.h
@@ -33,27 +33,19 @@ class ColorWheel : public QWidget
void colorChanged(const QColor& color);
public slots:
- void setColor(const QColor& color);
+ void setColor(QColor color);
protected:
- void mousePressEvent(QMouseEvent*);
- void mouseMoveEvent(QMouseEvent*);
- void mouseReleaseEvent(QMouseEvent*);
- void resizeEvent(QResizeEvent*);
- void paintEvent(QPaintEvent*);
+ void mousePressEvent(QMouseEvent*) override;
+ void mouseMoveEvent(QMouseEvent*) override;
+ void mouseReleaseEvent(QMouseEvent*) override;
+ void resizeEvent(QResizeEvent*) override;
+ void paintEvent(QPaintEvent*) override;
private:
- void changeColor(const QColor&);
void hueChanged(const int& hue);
void saturationChanged(const int& sat);
void valueChanged(const int& value);
- void redChanged(const int& red);
- void greenChanged(const int& green);
- void blueChanged(const int& blue);
- void alphaChanged(const int& alpha);
-
- void changeRgbColors(const QColor& color);
- void changeHsvColors(const QColor& color);
QColor pickColor(const QPoint& point);
diff --git a/app/src/displayoptionwidget.cpp b/app/src/displayoptionwidget.cpp
index 16e902046..4bdbe240d 100644
--- a/app/src/displayoptionwidget.cpp
+++ b/app/src/displayoptionwidget.cpp
@@ -17,10 +17,11 @@ GNU General Public License for more details.
#include "displayoptionwidget.h"
#include "ui_displayoption.h"
-#include
#include
#include
+
#include "preferencemanager.h"
+#include "viewmanager.h"
#include "scribblearea.h"
#include "editor.h"
#include "util.h"
diff --git a/app/src/exportimagedialog.cpp b/app/src/exportimagedialog.cpp
index d88524f3e..47287dcd4 100644
--- a/app/src/exportimagedialog.cpp
+++ b/app/src/exportimagedialog.cpp
@@ -17,23 +17,25 @@ GNU General Public License for more details.
#include "exportimagedialog.h"
#include "ui_exportimageoptions.h"
+#include "util.h"
-ExportImageDialog::ExportImageDialog(QWidget *parent, FileType eFileType) :
+ExportImageDialog::ExportImageDialog(QWidget* parent, FileType eFileType) :
ImportExportDialog(parent, ImportExportDialog::Export, eFileType),
ui(new Ui::ExportImageOptions)
{
- ui->setupUi( getOptionsGroupBox() );
+ ui->setupUi(getOptionsGroupBox());
if (eFileType == FileType::IMAGE_SEQUENCE)
{
- setWindowTitle( tr( "Export image sequence" ) );
+ setWindowTitle(tr("Export image sequence"));
}
else
{
- setWindowTitle( tr( "Export image" ) );
+ setWindowTitle(tr("Export image"));
+ ui->frameRangeGroupBox->hide();
}
- connect( ui->formatComboBox, &QComboBox::currentTextChanged, this, &ExportImageDialog::formatChanged );
- formatChanged( getExportFormat() ); // Make sure file extension matches format combobox
+ connect(ui->formatComboBox, &QComboBox::currentTextChanged, this, &ExportImageDialog::formatChanged);
+ formatChanged(getExportFormat()); // Make sure file extension matches format combobox
}
ExportImageDialog::~ExportImageDialog()
@@ -43,55 +45,85 @@ ExportImageDialog::~ExportImageDialog()
void ExportImageDialog::setCamerasInfo(const std::vector>& cameraInfo)
{
- Q_ASSERT(ui->cameraCombo);
+ Q_ASSERT(ui->cameraCombo);
- ui->cameraCombo->clear();
- for (const std::pair& it : cameraInfo)
- {
- ui->cameraCombo->addItem(it.first, it.second);
- }
+ ui->cameraCombo->clear();
+ for (const std::pair& it : cameraInfo)
+ {
+ ui->cameraCombo->addItem(it.first, it.second);
+ }
- auto indexChanged = static_cast(&QComboBox::currentIndexChanged);
- connect(ui->cameraCombo, indexChanged, this, &ExportImageDialog::cameraComboChanged);
+ const auto indexChanged = static_cast(&QComboBox::currentIndexChanged);
+ connect(ui->cameraCombo, indexChanged, this, &ExportImageDialog::cameraComboChanged);
- cameraComboChanged(0);
+ cameraComboChanged(0);
+}
+
+void ExportImageDialog::setDefaultRange(int startFrame, int endFrame, int endFrameWithSounds)
+{
+ mEndFrame = endFrame;
+ mEndFrameWithSounds = endFrameWithSounds;
+
+ SignalBlocker b1( ui->startSpinBox );
+ SignalBlocker b2( ui->endSpinBox );
+
+ ui->startSpinBox->setValue( startFrame );
+ ui->endSpinBox->setValue( endFrame );
+
+ connect(ui->frameCheckBox, &QCheckBox::clicked, this, &ExportImageDialog::frameCheckboxClicked);
+}
+
+int ExportImageDialog::getStartFrame() const
+{
+ return ui->startSpinBox->value();
+}
+
+int ExportImageDialog::getEndFrame() const
+{
+ return ui->endSpinBox->value();
+}
+
+void ExportImageDialog::frameCheckboxClicked(bool checked)
+{
+ int e = (checked) ? mEndFrameWithSounds : mEndFrame;
+ ui->endSpinBox->setValue(e);
}
void ExportImageDialog::setExportSize(QSize size)
{
- ui->imgWidthSpinBox->setValue( size.width() );
- ui->imgHeightSpinBox->setValue( size.height() );
+ ui->imgWidthSpinBox->setValue(size.width());
+ ui->imgHeightSpinBox->setValue(size.height());
}
-QSize ExportImageDialog::getExportSize()
+QSize ExportImageDialog::getExportSize() const
{
- return QSize( ui->imgWidthSpinBox->value(), ui->imgHeightSpinBox->value() );
+ return QSize(ui->imgWidthSpinBox->value(), ui->imgHeightSpinBox->value());
}
-bool ExportImageDialog::getTransparency()
+bool ExportImageDialog::getTransparency() const
{
return ui->cbTransparency->checkState() == Qt::Checked;
}
-QString ExportImageDialog::getExportFormat()
+QString ExportImageDialog::getExportFormat() const
{
return ui->formatComboBox->currentText();
}
-QString ExportImageDialog::getCameraLayerName()
+QString ExportImageDialog::getCameraLayerName() const
{
- return ui->cameraCombo->currentText();
+ return ui->cameraCombo->currentText();
}
-void ExportImageDialog::formatChanged(QString format)
+void ExportImageDialog::formatChanged(const QString& format)
{
- setFileExtension( format.toLower() );
+ setFileExtension(format.toLower());
}
void ExportImageDialog::cameraComboChanged(int index)
{
- QSize cameraSize = ui->cameraCombo->itemData(index).toSize();
+ const QSize cameraSize = ui->cameraCombo->itemData(index).toSize();
- ui->imgWidthSpinBox->setValue(cameraSize.width());
- ui->imgHeightSpinBox->setValue(cameraSize.height());
+ ui->imgWidthSpinBox->setValue(cameraSize.width());
+ ui->imgHeightSpinBox->setValue(cameraSize.height());
}
diff --git a/app/src/exportimagedialog.h b/app/src/exportimagedialog.h
index 69de942e3..4c885baf5 100644
--- a/app/src/exportimagedialog.h
+++ b/app/src/exportimagedialog.h
@@ -33,19 +33,28 @@ class ExportImageDialog : public ImportExportDialog
~ExportImageDialog();
void setCamerasInfo(const std::vector>& camInfo);
+ void setDefaultRange( int startFrame, int endFrame, int endFrameWithSounds );
void setExportSize( QSize size );
- QSize getExportSize();
- bool getTransparency();
- QString getExportFormat();
- QString getCameraLayerName();
+ QSize getExportSize() const;
+ bool getTransparency() const;
+ QString getExportFormat() const;
+ QString getCameraLayerName() const;
+
+ int getStartFrame() const;
+ int getEndFrame() const;
private slots:
- void formatChanged(QString format);
+ void frameCheckboxClicked(bool checked);
+ void formatChanged(const QString& format);
void cameraComboChanged(int index);
+
private:
Ui::ExportImageOptions* ui = nullptr;
+
+ int mEndFrameWithSounds = 0;
+ int mEndFrame = 0;
};
#endif // EXPORTIMAGEDIALOG_H
diff --git a/app/src/exportmoviedialog.cpp b/app/src/exportmoviedialog.cpp
index 75ac31d65..c0730b57f 100644
--- a/app/src/exportmoviedialog.cpp
+++ b/app/src/exportmoviedialog.cpp
@@ -19,12 +19,17 @@ GNU General Public License for more details.
#include "ui_exportmovieoptions.h"
#include "util.h"
-ExportMovieDialog::ExportMovieDialog(QWidget *parent) :
- ImportExportDialog(parent, ImportExportDialog::Export, FileType::MOVIE),
+ExportMovieDialog::ExportMovieDialog(QWidget *parent, Mode mode, FileType fileType) :
+ ImportExportDialog(parent, mode, fileType),
ui(new Ui::ExportMovieOptions)
{
ui->setupUi(getOptionsGroupBox());
- setWindowTitle(tr("Export Movie"));
+
+ if (fileType == FileType::GIF) {
+ setWindowTitle(tr("Export Animated GIF"));
+ } else {
+ setWindowTitle(tr("Export Movie"));
+ }
connect(this, &ExportMovieDialog::filePathsChanged, this, &ExportMovieDialog::onFilePathsChanged);
}
diff --git a/app/src/exportmoviedialog.h b/app/src/exportmoviedialog.h
index 850730f26..03a3731d1 100644
--- a/app/src/exportmoviedialog.h
+++ b/app/src/exportmoviedialog.h
@@ -31,7 +31,7 @@ class ExportMovieDialog : public ImportExportDialog
Q_OBJECT
public:
- explicit ExportMovieDialog(QWidget* parent = 0);
+ explicit ExportMovieDialog(QWidget* parent = 0, Mode mode = ImportExportDialog::Export, FileType fileType = FileType::MOVIE);
~ExportMovieDialog();
void setCamerasInfo(const std::vector>);
diff --git a/app/src/filedialogex.cpp b/app/src/filedialogex.cpp
index 4d481e7c1..bf66ceedb 100644
--- a/app/src/filedialogex.cpp
+++ b/app/src/filedialogex.cpp
@@ -132,6 +132,7 @@ QString FileDialog::openDialogTitle( FileType fileType )
case FileType::ANIMATION: return tr( "Open animation" );
case FileType::IMAGE: return tr( "Import image" );
case FileType::IMAGE_SEQUENCE: return tr( "Import image sequence" );
+ case FileType::GIF: return tr( "Import Animated GIF" );
case FileType::MOVIE: return tr( "Import movie" );
case FileType::SOUND: return tr( "Import sound" );
case FileType::PALETTE: return tr( "Import palette" );
@@ -147,6 +148,7 @@ QString FileDialog::saveDialogTitle( FileType fileType )
case FileType::ANIMATION: return tr( "Save animation" );
case FileType::IMAGE: return tr( "Export image" );
case FileType::IMAGE_SEQUENCE: return tr( "Export image sequence" );
+ case FileType::GIF: return tr( "Export Animated GIF" );
case FileType::MOVIE: return tr( "Export movie" );
case FileType::SOUND: return tr( "Export sound" );
case FileType::PALETTE: return tr( "Export palette" );
@@ -161,10 +163,11 @@ QString FileDialog::openFileFilters( FileType fileType )
{
case FileType::ANIMATION: return PFF_OPEN_ALL_FILE_FILTER;
case FileType::IMAGE: return PENCIL_IMAGE_FILTER;
- case FileType::IMAGE_SEQUENCE: return PENCIL_IMAGE_FILTER;
+ case FileType::IMAGE_SEQUENCE: return PENCIL_IMAGE_SEQ_FILTER;
+ case FileType::GIF: return tr("Animated GIF (*.gif)");
case FileType::MOVIE: { Q_ASSERT(false); return PENCIL_MOVIE_EXT; } // currently not supported
case FileType::SOUND: return tr( "Sounds (*.wav *.mp3);;WAV (*.wav);;MP3 (*.mp3)" );
- case FileType::PALETTE: return tr( "Palette (*.xml)" );
+ case FileType::PALETTE: return tr( "Pencil2D Palette (*.xml);; Gimp Palette (*.gpl)" );
default: Q_ASSERT( false );
}
return "";
@@ -177,9 +180,10 @@ QString FileDialog::saveFileFilters( FileType fileType )
case FileType::ANIMATION: return PFF_SAVE_ALL_FILE_FILTER;
case FileType::IMAGE: return "";
case FileType::IMAGE_SEQUENCE: return "";
- case FileType::MOVIE: return tr( "MP4 (*.mp4);; AVI (*.avi);; WebM (*.webm);; GIF (*.gif);; APNG (*.apng)" );
+ case FileType::GIF: return tr("Animated GIF (*.gif)");
+ case FileType::MOVIE: return tr( "MP4 (*.mp4);; AVI (*.avi);; WebM (*.webm);; APNG (*.apng)" );
case FileType::SOUND: return "";
- case FileType::PALETTE: return tr( "Palette (*.xml)" );
+ case FileType::PALETTE: return tr( "Pencil2D Palette (*.xml);; Gimp Palette (*.gpl)" );
default: Q_ASSERT( false );
}
return "";
@@ -187,7 +191,7 @@ QString FileDialog::saveFileFilters( FileType fileType )
QString FileDialog::getFilterForFile(QString filters, QString filePath)
{
- qDebug() << "Getfilterforfile" << filters << filePath;
+ qDebug() << __FUNCTION__ << filters << filePath;
if(!filePath.contains("."))
{
return QString();
@@ -222,6 +226,7 @@ QString FileDialog::defaultFileName( FileType fileType )
case FileType::ANIMATION: return tr( "MyAnimation.pclx" );
case FileType::IMAGE: return "untitled.png";
case FileType::IMAGE_SEQUENCE: return "untitled.png";
+ case FileType::GIF: return "untitled.gif";
case FileType::MOVIE: return "untitled.mp4";
case FileType::SOUND: return "untitled.wav";
case FileType::PALETTE: return "untitled.xml";
@@ -237,6 +242,7 @@ QString FileDialog::toSettingKey( FileType fileType )
case FileType::ANIMATION: return "Animation";
case FileType::IMAGE: return "Image";
case FileType::IMAGE_SEQUENCE: return "ImageSequence";
+ case FileType::GIF: return "Animated GIF";
case FileType::MOVIE: return "Movie";
case FileType::SOUND: return "Sound";
case FileType::PALETTE: return "Palette";
diff --git a/app/src/filedialogex.h b/app/src/filedialogex.h
index f22dca02c..998314647 100644
--- a/app/src/filedialogex.h
+++ b/app/src/filedialogex.h
@@ -25,6 +25,7 @@ enum class FileType
ANIMATION,
IMAGE,
IMAGE_SEQUENCE,
+ GIF,
MOVIE,
SOUND,
PALETTE
diff --git a/app/src/importexportdialog.cpp b/app/src/importexportdialog.cpp
index 3a61c0a3e..9d86b4349 100644
--- a/app/src/importexportdialog.cpp
+++ b/app/src/importexportdialog.cpp
@@ -49,6 +49,12 @@ QStringList ImportExportDialog::getFilePaths()
return m_filePaths;
}
+QString ImportExportDialog::getAbsolutePath()
+{
+ QFileInfo info(m_filePaths.first());
+ return info.absolutePath();
+}
+
void ImportExportDialog::init()
{
switch (mMode)
diff --git a/app/src/importexportdialog.h b/app/src/importexportdialog.h
index 8130db325..d78628b31 100644
--- a/app/src/importexportdialog.h
+++ b/app/src/importexportdialog.h
@@ -38,6 +38,7 @@ class ImportExportDialog : public QDialog
void init();
QString getFilePath();
+ QString getAbsolutePath();
QStringList getFilePaths();
signals:
diff --git a/app/src/importimageseqdialog.cpp b/app/src/importimageseqdialog.cpp
index 2d1ec2a0e..84dfb2e4c 100644
--- a/app/src/importimageseqdialog.cpp
+++ b/app/src/importimageseqdialog.cpp
@@ -19,13 +19,18 @@ GNU General Public License for more details.
#include "ui_importimageseqoptions.h"
#include "util.h"
-ImportImageSeqDialog::ImportImageSeqDialog(QWidget* parent) :
- ImportExportDialog(parent, ImportExportDialog::Import, FileType::IMAGE_SEQUENCE)
+ImportImageSeqDialog::ImportImageSeqDialog(QWidget* parent, Mode mode, FileType fileType) :
+ ImportExportDialog(parent, mode, fileType)
{
ui = new Ui::ImportImageSeqOptions;
ui->setupUi(getOptionsGroupBox());
- setWindowTitle(tr("Import image sequence"));
+ if (fileType == FileType::GIF) {
+ setWindowTitle(tr("Import Animated GIF"));
+ } else {
+ setWindowTitle(tr("Import image sequence"));
+ }
+
connect(ui->spaceSpinBox, static_cast(&QSpinBox::valueChanged), this, &ImportImageSeqDialog::setSpace);
}
diff --git a/app/src/importimageseqdialog.h b/app/src/importimageseqdialog.h
index fafeba7ce..81d57de35 100644
--- a/app/src/importimageseqdialog.h
+++ b/app/src/importimageseqdialog.h
@@ -29,7 +29,7 @@ class ImportImageSeqDialog : public ImportExportDialog
Q_OBJECT
public:
- explicit ImportImageSeqDialog(QWidget *parent = 0);
+ explicit ImportImageSeqDialog(QWidget *parent = 0, Mode mode = ImportExportDialog::Import, FileType fileType = FileType::IMAGE_SEQUENCE);
~ImportImageSeqDialog();
int getSpace();
diff --git a/app/src/main.cpp b/app/src/main.cpp
index 01982b690..3de339111 100644
--- a/app/src/main.cpp
+++ b/app/src/main.cpp
@@ -19,6 +19,10 @@ GNU General Public License for more details.
#include
#include
#include
+#include
+#include
+#include
+
#include "editor.h"
#include "mainwindow2.h"
#include "pencilapplication.h"
@@ -269,7 +273,6 @@ int handleArguments( PencilApplication& app )
format = "PNG";
}
- bool asMovie;
QMap formatMapping;
formatMapping[ "PNG" ] = false;
formatMapping[ "JPG" ] = false;
@@ -280,7 +283,7 @@ int handleArguments( PencilApplication& app )
formatMapping[ "GIF" ] = true;
formatMapping[ "WEBM" ] = true;
formatMapping[ "APNG" ] = true;
- asMovie = formatMapping[format];
+ bool asMovie = formatMapping[format];
if ( asMovie )
{
@@ -307,6 +310,9 @@ int main(int argc, char* argv[])
Q_INIT_RESOURCE(core_lib);
QSettings settings(PENCIL2D, PENCIL2D);
+#ifdef Q_OS_MACOS
+ QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
+#endif
if (settings.value("EnableHighDpiScaling", "true").toBool())
{
// Enable auto screen scaling on high dpi display, for example, a 4k monitor
diff --git a/app/src/mainwindow2.cpp b/app/src/mainwindow2.cpp
index 763a4c770..aed17cc85 100644
--- a/app/src/mainwindow2.cpp
+++ b/app/src/mainwindow2.cpp
@@ -35,14 +35,17 @@ GNU General Public License for more details.
#include "pencildef.h"
#include "pencilsettings.h"
#include "object.h"
-#include "filemanager.h"
#include "editor.h"
+
+#include "filemanager.h"
#include "colormanager.h"
#include "layermanager.h"
-#include "layercamera.h"
#include "toolmanager.h"
#include "playbackmanager.h"
#include "soundmanager.h"
+#include "viewmanager.h"
+
+#include "layercamera.h"
#include "actioncommands.h"
#include "fileformat.h" //contains constants used by Pencil File Format
#include "util.h"
@@ -51,12 +54,14 @@ GNU General Public License for more details.
// app headers
#include "scribblearea.h"
#include "colorbox.h"
+#include "colorinspector.h"
#include "colorpalettewidget.h"
#include "displayoptionwidget.h"
#include "tooloptionwidget.h"
#include "preferencesdialog.h"
#include "timeline.h"
#include "toolbox.h"
+
//#include "preview.h"
#include "timeline2.h"
#include "errordialog.h"
@@ -78,7 +83,7 @@ GNU General Public License for more details.
#ifdef NIGHTLY_BUILD
#define PENCIL_WINDOW_TITLE QString("[*]Pencil2D - Nightly Build %1").arg( BUILD_DATE )
#else
-#define PENCIL_WINDOW_TITLE QString("[*]Pencil2D v%1").arg(APP_VERSION)
+#define PENCIL_WINDOW_TITLE QString("[*]Pencil2D v%1").arg(APP_VERSION " RC1")
#endif
@@ -137,9 +142,13 @@ void MainWindow2::createDockWidgets()
mTimeLine = new TimeLine(this);
mTimeLine->setObjectName("TimeLine");
- mColorWheel = new ColorBox(this);
- mColorWheel->setToolTip(tr("color palette:
use (C)
toggle at cursor"));
- mColorWheel->setObjectName("ColorWheel");
+ mColorBox = new ColorBox(this);
+ mColorBox->setToolTip(tr("color palette:
use (C)
toggle at cursor"));
+ mColorBox->setObjectName("ColorWheel");
+
+ mColorInspector = new ColorInspector(this);
+ mColorInspector->setToolTip(tr("Color inspector"));
+ mColorInspector->setObjectName("Color Inspector");
mColorPalette = new ColorPaletteWidget(this);
mColorPalette->setObjectName("ColorPalette");
@@ -161,12 +170,14 @@ void MainWindow2::createDockWidgets()
mDockWidgets
<< mTimeLine
- << mColorWheel
+ << mColorBox
+ << mColorInspector
<< mColorPalette
<< mDisplayOptionWidget
<< mToolOptions
<< mToolBox;
+// mColorInspector->setFloating(true);
mStartIcon = QIcon(":icons/controls/play.png");
mStopIcon = QIcon(":icons/controls/stop.png");
@@ -182,11 +193,12 @@ void MainWindow2::createDockWidgets()
qDebug() << "Init Dock widget: " << pWidget->objectName();
}
- addDockWidget(Qt::RightDockWidgetArea, mColorWheel);
+ addDockWidget(Qt::RightDockWidgetArea, mColorBox);
+ addDockWidget(Qt::RightDockWidgetArea, mColorInspector);
addDockWidget(Qt::RightDockWidgetArea, mColorPalette);
- addDockWidget(Qt::RightDockWidgetArea, mDisplayOptionWidget);
addDockWidget(Qt::LeftDockWidgetArea, mToolBox);
addDockWidget(Qt::LeftDockWidgetArea, mToolOptions);
+ addDockWidget(Qt::LeftDockWidgetArea, mDisplayOptionWidget);
addDockWidget(Qt::BottomDockWidgetArea, mTimeLine);
setDockNestingEnabled(true);
//addDockWidget( Qt::BottomDockWidgetArea, mTimeline2);
@@ -201,7 +213,8 @@ void MainWindow2::createDockWidgets()
makeConnections(mEditor);
makeConnections(mEditor, mTimeLine);
- makeConnections(mEditor, mColorWheel);
+ makeConnections(mEditor, mColorBox);
+ makeConnections(mEditor, mColorInspector);
makeConnections(mEditor, mColorPalette);
makeConnections(mEditor, mToolOptions);
@@ -226,6 +239,7 @@ void MainWindow2::createMenus()
connect(ui->actionExport_Image, &QAction::triggered, mCommands, &ActionCommands::exportImage);
connect(ui->actionExport_ImageSeq, &QAction::triggered, mCommands, &ActionCommands::exportImageSequence);
connect(ui->actionExport_Movie, &QAction::triggered, mCommands, &ActionCommands::exportMovie);
+ connect(ui->actionExport_Animated_GIF, &QAction::triggered, mCommands, &ActionCommands::exportGif);
connect(ui->actionExport_Palette, &QAction::triggered, this, &MainWindow2::exportPalette);
@@ -233,6 +247,7 @@ void MainWindow2::createMenus()
//connect( ui->actionExport_Svg_Image, &QAction::triggered, editor, &Editor::saveSvg );
connect(ui->actionImport_Image, &QAction::triggered, this, &MainWindow2::importImage);
connect(ui->actionImport_ImageSeq, &QAction::triggered, this, &MainWindow2::importImageSequence);
+ connect(ui->actionImport_Gif, &QAction::triggered, this, &MainWindow2::importGIF);
connect(ui->actionImport_Movie, &QAction::triggered, this, &MainWindow2::importMovie);
connect(ui->actionImport_Sound, &QAction::triggered, mCommands, &ActionCommands::importSound);
@@ -288,6 +303,7 @@ void MainWindow2::createMenus()
connect(pPlaybackManager, &PlaybackManager::rangedPlaybackStateChanged, mTimeLine, &TimeLine::setRangeState);
connect(pPlaybackManager, &PlaybackManager::playStateChanged, mTimeLine, &TimeLine::setPlaying);
connect(pPlaybackManager, &PlaybackManager::playStateChanged, this, &MainWindow2::changePlayState);
+ connect(pPlaybackManager, &PlaybackManager::playStateChanged, mEditor, &Editor::updateCurrentFrame);
connect(ui->actionAdd_Frame, &QAction::triggered, mCommands, &ActionCommands::addNewKey);
connect(ui->actionRemove_Frame, &QAction::triggered, mCommands, &ActionCommands::removeKey);
@@ -320,10 +336,11 @@ void MainWindow2::createMenus()
{
mToolBox->toggleViewAction(),
mToolOptions->toggleViewAction(),
- mColorWheel->toggleViewAction(),
+ mColorBox->toggleViewAction(),
mColorPalette->toggleViewAction(),
mTimeLine->toggleViewAction(),
- mDisplayOptionWidget->toggleViewAction()
+ mDisplayOptionWidget->toggleViewAction(),
+ mColorInspector->toggleViewAction()
};
for (QAction* action : actions)
@@ -416,9 +433,9 @@ void MainWindow2::tabletEvent(QTabletEvent* event)
event->ignore();
}
-void MainWindow2::newDocument()
+void MainWindow2::newDocument(bool force)
{
- if (maybeSave())
+ if (force || maybeSave())
{
Object* object = new Object();
object->init();
@@ -452,14 +469,8 @@ void MainWindow2::openDocument()
return;
}
- bool ok = openObject(fileName);
- if (!ok)
- {
- QMessageBox::warning(this, tr("Warning"), tr("Pencil cannot read this file. If you want to import images, use the command import."));
- newDocument();
- }
+ openObject(fileName, false);
}
- updateSaveState();
}
bool MainWindow2::saveAsNewDocument()
@@ -480,16 +491,53 @@ bool MainWindow2::saveAsNewDocument()
void MainWindow2::openFile(QString filename)
{
- bool ok = openObject(filename);
- if (!ok)
- {
- QMessageBox::warning(this, tr("Warning"), tr("Pencil cannot read this file. If you want to import images, use the command import."));
- newDocument();
- }
+ openObject(filename, true);
}
-bool MainWindow2::openObject(QString strFilePath)
+bool MainWindow2::openObject(QString strFilePath, bool checkForChanges)
{
+ if(checkForChanges && !maybeSave()) return false; // Open cancelled by user
+
+ // Check for potential issues with the file
+ QFileInfo fileInfo(strFilePath);
+ if (fileInfo.isDir())
+ {
+ ErrorDialog errorDialog(tr("Could not open file"),
+ tr("The file you have selected is a directory, so we are unable to open it. "
+ "If you are are trying to open a project that uses the old structure, please "
+ "open the file ending with .pcl, not the data folder."),
+ QString("Raw file path: %1\nResolved file path: %2").arg(strFilePath, fileInfo.absoluteFilePath()));
+ errorDialog.exec();
+ return false;
+ }
+ if (!fileInfo.exists())
+ {
+ ErrorDialog errorDialog(tr("Could not open file"),
+ tr("The file you have selected does not exist, so we are unable to open it. Please check "
+ "to make sure that you've entered the correct path and that the file is accessible and try again."),
+ QString("Raw file path: %1\nResolved file path: %2").arg(strFilePath, fileInfo.absoluteFilePath()));
+ errorDialog.exec();
+ return false;
+ }
+ if (!fileInfo.isReadable())
+ {
+ ErrorDialog errorDialog(tr("Could not open file"),
+ tr("This program does not have permission to read the file you have selected. "
+ "Please check that you have read permissions for this file and try again."),
+ QString("Raw file path: %1\nResolved file path: %2\nPermissions: 0x%3") \
+ .arg(strFilePath, fileInfo.absoluteFilePath(), QString::number(fileInfo.permissions(), 16)));
+ errorDialog.exec();
+ return false;
+ }
+ if (!fileInfo.isWritable())
+ {
+ QMessageBox::warning(this, tr("Warning"),
+ tr("This program does not currently have permission to write to the file you have selected. "
+ "Please make sure you have write permission for this file before attempting to save it. "
+ "Alternatively, you can use the Save As... menu option to save to a writable location."),
+ QMessageBox::Ok);
+ }
+
QProgressDialog progress(tr("Opening document..."), tr("Abort"), 0, 100, this);
// Don't show progress bar if running without a GUI (aka. when rendering from command line)
@@ -517,8 +565,27 @@ bool MainWindow2::openObject(QString strFilePath)
Object* object = fm.load(strFilePath);
- if (object == nullptr || !fm.error().ok())
+ if (!fm.error().ok())
{
+ Status error = fm.error();
+ DebugDetails dd;
+ dd << QString("Raw file path: ").append(strFilePath)
+ << QString("Resolved file path: ").append(fileInfo.absoluteFilePath());
+ dd.collect(error.details());
+ ErrorDialog errorDialog(error.title(),
+ error.description(),
+ dd.str());
+ errorDialog.exec();
+ newDocument(true);
+ return false;
+ }
+
+ if (object == nullptr) {
+ ErrorDialog errorDialog(tr("Could not open file"),
+ tr("An unknown error occurred while trying to load the file and we are not able to load your file."),
+ QString("Raw file path: %1\nResolved file path: %2").arg(strFilePath, fileInfo.absoluteFilePath()));
+ errorDialog.exec();
+ newDocument(true);
return false;
}
@@ -538,11 +605,12 @@ bool MainWindow2::openObject(QString strFilePath)
// Refresh the Palette
mColorPalette->refreshColorList();
- mEditor->color()->setColorNumber(0);
mEditor->layers()->notifyAnimationLengthChanged();
progress.setValue(progress.maximum());
+ updateSaveState();
+
return true;
}
@@ -584,7 +652,7 @@ bool MainWindow2::saveObject(QString strSavedFileName)
if (eLog.open(QIODevice::WriteOnly | QIODevice::Text))
{
QTextStream out(&eLog);
- out << st.details().replace("
", "\n", Qt::CaseInsensitive);
+ out << st.details().str(); // .replace("
", "\n", Qt::CaseInsensitive);
}
eLog.close();
@@ -592,7 +660,7 @@ bool MainWindow2::saveObject(QString strSavedFileName)
st.description().append(tr("
An error has occurred and your file may not have saved successfully."
"If you believe that this error is an issue with Pencil2D, please create a new issue at:"
"
https://github.com/pencil2d/pencil/issues
"
- "Please be sure to include the following details in your issue:")), st.details());
+ "Please be sure to include the following details in your issue:")), st.details().html());
errorDialog.exec();
return false;
}
@@ -721,21 +789,18 @@ void MainWindow2::importImageSequence()
progress.setMaximum(totalImagesToImport);
int imagesImportedSoFar = 0;
+ QString failedFiles;
+ bool failedImport = false;
for (QString strImgFile : files)
{
QString strImgFileLower = strImgFile.toLower();
+
if (strImgFileLower.endsWith(".png") ||
strImgFileLower.endsWith(".jpg") ||
strImgFileLower.endsWith(".jpeg") ||
- strImgFileLower.endsWith(".tif") ||
- strImgFileLower.endsWith(".tiff") ||
strImgFileLower.endsWith(".bmp"))
{
mEditor->importImage(strImgFile);
- for (int i = 1; i < number; i++)
- {
- mEditor->scrubForward();
- }
imagesImportedSoFar++;
progress.setValue(imagesImportedSoFar);
@@ -745,10 +810,85 @@ void MainWindow2::importImageSequence()
{
break;
}
+ } else {
+ failedFiles += strImgFile + "\n";
+ if (!failedImport)
+ {
+ failedImport = true;
+ }
+ }
+
+ for (int i = 1; i < number; i++)
+ {
+ mEditor->scrubForward();
}
}
+
+ if (failedImport)
+ {
+ QMessageBox::warning(this,
+ tr("Warning"),
+ tr("was unable to import") + failedFiles,
+ QMessageBox::Ok,
+ QMessageBox::Ok);
+ }
+
+ mEditor->layers()->notifyAnimationLengthChanged();
+
+ progress.close();
+
+ mIsImportingImageSequence = false;
+}
+
+void MainWindow2::importGIF()
+{
+ auto gifDialog = new ImportImageSeqDialog(this, ImportExportDialog::Import, FileType::GIF);
+ gifDialog->exec();
+ if (gifDialog->result() == QDialog::Rejected)
+ {
+ return;
+ }
+
+ // Flag this so we don't prompt the user about auto-save in the middle of the import.
+ mIsImportingImageSequence = true;
+
+ int space = gifDialog->getSpace();
+
+ // Show a progress dialog, as this could take a while if the gif is huge
+ QProgressDialog progress(tr("Importing Animated GIF..."), tr("Abort"), 0, 100, this);
+ hideQuestionMark(progress);
+ progress.setWindowModality(Qt::WindowModal);
+ progress.show();
+
+ bool failedImport = false;
+ QString strImgFileLower = gifDialog->getFilePath();
+
+ if (strImgFileLower.endsWith(".gif"))
+ {
+ mEditor->importGIF(strImgFileLower, space);
+
+ progress.setValue(50);
+ QApplication::processEvents(); // Required to make progress bar update on-screen.
+
+ } else {
+ if (!failedImport)
+ {
+ failedImport = true;
+ }
+ }
+
+ if (failedImport)
+ {
+ QMessageBox::warning(this,
+ tr("Warning"),
+ tr("was unable to import") + strImgFileLower,
+ QMessageBox::Ok,
+ QMessageBox::Ok);
+ }
+
mEditor->layers()->notifyAnimationLengthChanged();
+ progress.setValue(100);
progress.close();
mIsImportingImageSequence = false;
@@ -769,7 +909,8 @@ void MainWindow2::lockWidgets(bool shouldLock)
{
QDockWidget::DockWidgetFeature feat = shouldLock ? QDockWidget::DockWidgetClosable : QDockWidget::AllDockWidgetFeatures;
- mColorWheel->setFeatures(feat);
+ mColorBox->setFeatures(feat);
+ mColorInspector->setFeatures(feat);
mColorPalette->setFeatures(feat);
mDisplayOptionWidget->setFeatures(feat);
mToolOptions->setFeatures(feat);
@@ -936,10 +1077,11 @@ void MainWindow2::setupKeyboardShortcuts()
mToolBox->toggleViewAction()->setShortcut(cmdKeySeq(CMD_TOGGLE_TOOLBOX));
mToolOptions->toggleViewAction()->setShortcut(cmdKeySeq(CMD_TOGGLE_TOOL_OPTIONS));
- mColorWheel->toggleViewAction()->setShortcut(cmdKeySeq(CMD_TOGGLE_COLOR_WHEEL));
+ mColorBox->toggleViewAction()->setShortcut(cmdKeySeq(CMD_TOGGLE_COLOR_WHEEL));
mColorPalette->toggleViewAction()->setShortcut(cmdKeySeq(CMD_TOGGLE_COLOR_LIBRARY));
mTimeLine->toggleViewAction()->setShortcut(cmdKeySeq(CMD_TOGGLE_TIMELINE));
mDisplayOptionWidget->toggleViewAction()->setShortcut(cmdKeySeq(CMD_TOGGLE_DISPLAY_OPTIONS));
+ mColorInspector->toggleViewAction()->setShortcut(cmdKeySeq(CMD_TOGGLE_COLOR_INSPECTOR));
ui->actionHelp->setShortcut(cmdKeySeq(CMD_HELP));
ui->actionExit->setShortcut(cmdKeySeq(CMD_EXIT));
@@ -954,7 +1096,7 @@ void MainWindow2::clearKeyboardShortcuts()
}
}
-void MainWindow2::undoActSetText(void)
+void MainWindow2::undoActSetText()
{
if (mEditor->mBackupIndex < 0)
{
@@ -983,7 +1125,7 @@ void MainWindow2::undoActSetText(void)
}
}
-void MainWindow2::undoActSetEnabled(void)
+void MainWindow2::undoActSetEnabled()
{
ui->actionUndo->setEnabled(true);
ui->actionRedo->setEnabled(true);
@@ -1022,11 +1164,18 @@ void MainWindow2::makeConnections(Editor* editor, ColorBox* colorBox)
connect(editor->color(), &ColorManager::colorChanged, colorBox, &ColorBox::setColor);
}
+void MainWindow2::makeConnections(Editor* editor, ColorInspector* colorInspector)
+{
+ connect(colorInspector, &ColorInspector::colorChanged, editor->color(), &ColorManager::setColor);
+ connect(editor->color(), &ColorManager::colorChanged, colorInspector, &ColorInspector::setColor);
+}
+
void MainWindow2::makeConnections(Editor* editor, ScribbleArea* scribbleArea)
{
connect(editor->tools(), &ToolManager::toolChanged, scribbleArea, &ScribbleArea::setCurrentTool);
connect(editor->tools(), &ToolManager::toolPropertyChanged, scribbleArea, &ScribbleArea::updateToolCursor);
connect(editor->layers(), &LayerManager::currentLayerChanged, scribbleArea, &ScribbleArea::updateAllFrames);
+ connect(editor->layers(), &LayerManager::layerDeleted, scribbleArea, &ScribbleArea::updateAllFrames);
connect(editor, &Editor::currentFrameChanged, scribbleArea, &ScribbleArea::updateFrame);
@@ -1051,6 +1200,7 @@ void MainWindow2::makeConnections(Editor* pEditor, TimeLine* pTimeline)
connect(pTimeline, &TimeLine::newCameraLayer, mCommands, &ActionCommands::addNewCameraLayer);
connect(pTimeline, &TimeLine::toogleAbsoluteOnionClick, pEditor, &Editor::toogleOnionSkinType);
+ connect(mTimeLine, &TimeLine::playButtonTriggered, mCommands, &ActionCommands::PlayStop);
connect(pEditor->layers(), &LayerManager::currentLayerChanged, pTimeline, &TimeLine::updateUI);
connect(pEditor->layers(), &LayerManager::layerCountChanged, pTimeline, &TimeLine::updateUI);
diff --git a/app/src/mainwindow2.h b/app/src/mainwindow2.h
index e7b51d4b7..d557ed4ca 100644
--- a/app/src/mainwindow2.h
+++ b/app/src/mainwindow2.h
@@ -36,6 +36,7 @@ class ToolBoxWidget;
class PreferencesDialog;
class PreviewWidget;
class ColorBox;
+class ColorInspector;
class RecentFileMenu;
class Timeline2;
class ActionCommands;
@@ -67,7 +68,7 @@ class MainWindow2 : public QMainWindow
public:
void setOpacity(int opacity);
- void newDocument();
+ void newDocument(bool force = false);
void openDocument();
bool saveDocument();
bool saveAsNewDocument();
@@ -78,6 +79,7 @@ class MainWindow2 : public QMainWindow
void importImage();
void importImageSequence();
void importMovie();
+ void importGIF();
void lockWidgets(bool shouldLock);
@@ -98,7 +100,7 @@ private slots:
void resetAndDockAllSubWidgets();
private:
- bool openObject(QString strFilename);
+ bool openObject(QString strFilename, bool checkForChanges);
bool saveObject(QString strFileName);
void createDockWidgets();
@@ -117,7 +119,8 @@ private slots:
void changePlayState(bool isPlaying);
void makeConnections(Editor*);
- void makeConnections(Editor*, ColorBox*);
+ void makeConnections(Editor*, ColorBox* colorBox);
+ void makeConnections(Editor*, ColorInspector*);
void makeConnections(Editor*, ScribbleArea*);
void makeConnections(Editor*, ColorPaletteWidget*);
void makeConnections(Editor*, TimeLine*);
@@ -127,7 +130,7 @@ private slots:
void bindActionWithSetting(QAction*, SETTING);
// UI: Dock widgets
- ColorBox* mColorWheel = nullptr;
+ ColorBox* mColorBox = nullptr;
ColorPaletteWidget* mColorPalette = nullptr;
DisplayOptionWidget* mDisplayOptionWidget = nullptr;
ToolOptionWidget* mToolOptions = nullptr;
@@ -136,7 +139,8 @@ private slots:
RecentFileMenu* mRecentFileMenu = nullptr;
PreferencesDialog* mPrefDialog = nullptr;
//PreviewWidget* mPreview = nullptr;
- TimeLine* mTimeLine; // be public temporary
+ TimeLine* mTimeLine = nullptr; // be public temporary
+ ColorInspector* mColorInspector = nullptr;
// backup
BackupElement* mBackupAtSave = nullptr;
diff --git a/app/src/pencilapplication.h b/app/src/pencilapplication.h
index 308d5b007..8e94d382d 100644
--- a/app/src/pencilapplication.h
+++ b/app/src/pencilapplication.h
@@ -27,7 +27,7 @@ class PencilApplication : public QApplication
public:
PencilApplication(int &argc, char **argv);
- bool event(QEvent* event);
+ bool event(QEvent* event) override;
void emitOpenFileRequest();
signals:
diff --git a/app/src/preferencesdialog.cpp b/app/src/preferencesdialog.cpp
index 03f0ca949..c5cd972f3 100644
--- a/app/src/preferencesdialog.cpp
+++ b/app/src/preferencesdialog.cpp
@@ -15,15 +15,17 @@ GNU General Public License for more details.
*/
#include "preferencesdialog.h"
+
+#include
+#include
#include "ui_preferencesdialog.h"
#include "ui_generalpage.h"
#include "ui_timelinepage.h"
#include "ui_filespage.h"
#include "ui_toolspage.h"
-#include
-#include
#include "util.h"
+
PreferencesDialog::PreferencesDialog( QWidget* parent ) :
QDialog(parent),
ui(new Ui::PreferencesDialog)
@@ -96,19 +98,22 @@ GeneralPage::GeneralPage(QWidget* parent) :
ui->languageCombo->addItem(tr("Danish") + " (Danish)", "da");
ui->languageCombo->addItem(tr("German") + " (German)", "de");
ui->languageCombo->addItem(tr("English") + " (English)", "en");
+ ui->languageCombo->addItem(tr("Estonian") + " (Estonian)", "et");
ui->languageCombo->addItem(tr("Spanish") + " (Spanish)", "es");
ui->languageCombo->addItem(tr("French") + " (French)", "fr");
ui->languageCombo->addItem(tr("Hebrew") + " (Hebrew)", "he");
- ui->languageCombo->addItem(tr("Hungarian") + " (Hungarian)", "hu-HU");
+ ui->languageCombo->addItem(tr("Hungarian") + " (Hungarian)", "hu_HU");
ui->languageCombo->addItem(tr("Indonesian") + " (Indonesian)", "id");
ui->languageCombo->addItem(tr("Italian") + " (Italian)", "it");
ui->languageCombo->addItem(tr("Japanese") + " (Japanese)", "ja");
+ ui->languageCombo->addItem(tr("Polish") + " (Polish)", "pl");
ui->languageCombo->addItem(tr("Portuguese - Portugal") + "(Portuguese - Portugal)", "pt");
- ui->languageCombo->addItem(tr("Portuguese - Brazil") + "(Portuguese - Brazil)", "pt-BR");
+ ui->languageCombo->addItem(tr("Portuguese - Brazil") + "(Portuguese - Brazil)", "pt_BR");
ui->languageCombo->addItem(tr("Russian") + " (Russian)", "ru");
ui->languageCombo->addItem(tr("Slovenian") + " (Slovenian)", "sl");
ui->languageCombo->addItem(tr("Vietnamese") + " (Vietnamese)", "vi");
- ui->languageCombo->addItem(tr("Chinese - Taiwan") + " (Chinese - Taiwan)", "zh-TW");
+ ui->languageCombo->addItem(tr("Chinese - China") + " (Chinese - China)", "zh_CN");
+ ui->languageCombo->addItem(tr("Chinese - Taiwan") + " (Chinese - Taiwan)", "zh_TW");
int value = settings.value("windowOpacity").toInt();
ui->windowOpacityLevel->setValue(100 - value);
@@ -274,6 +279,10 @@ TimelinePage::TimelinePage(QWidget* parent) :
connect(ui->frameSize, &QSlider::valueChanged, this, &TimelinePage::frameSizeChange);
connect(ui->timelineLength, spinBoxValueChange, this, &TimelinePage::timelineLengthChanged);
connect(ui->scrubBox, &QCheckBox::stateChanged, this, &TimelinePage::scrubChange);
+ connect(ui->radioButtonAddNewKey, &QRadioButton::toggled, this, &TimelinePage::radioButtonToggled);
+ connect(ui->radioButtonDuplicate, &QRadioButton::toggled, this, &TimelinePage::radioButtonToggled);
+ connect(ui->radioButtonDrawOnPrev, &QRadioButton::toggled, this, &TimelinePage::radioButtonToggled);
+ connect(ui->onionWhilePlayback, &QCheckBox::stateChanged, this, &TimelinePage::playbackStateChanged);
}
TimelinePage::~TimelinePage()
@@ -297,6 +306,27 @@ void TimelinePage::updateValues()
ui->timelineLength->setValue(mManager->getInt(SETTING::TIMELINE_SIZE));
if (mManager->getString(SETTING::TIMELINE_SIZE).toInt() <= 0)
ui->timelineLength->setValue(240);
+
+ SignalBlocker b4(ui->radioButtonAddNewKey);
+ SignalBlocker b5(ui->radioButtonDuplicate);
+ SignalBlocker b6(ui->radioButtonDrawOnPrev);
+ int action = mManager->getInt(SETTING::DRAW_ON_EMPTY_FRAME_ACTION);
+ switch (action) {
+ case CREATE_NEW_KEY:
+ ui->radioButtonAddNewKey->setChecked(true);
+ break;
+ case DUPLICATE_PREVIOUS_KEY:
+ ui->radioButtonDuplicate->setChecked(true);
+ break;
+ case KEEP_DRAWING_ON_PREVIOUS_KEY:
+ ui->radioButtonDrawOnPrev->setChecked(true);
+ break;
+ default:
+ break;
+ }
+
+ SignalBlocker b7(ui->onionWhilePlayback);
+ ui->onionWhilePlayback->setChecked(mManager->getInt(SETTING::ONION_WHILE_PLAYBACK));
}
void TimelinePage::timelineLengthChanged(int value)
@@ -324,6 +354,27 @@ void TimelinePage::scrubChange(int value)
mManager->set(SETTING::SHORT_SCRUB, value != Qt::Unchecked);
}
+void TimelinePage::playbackStateChanged(int value)
+{
+ mManager->set(SETTING::ONION_WHILE_PLAYBACK, value);
+}
+
+void TimelinePage::radioButtonToggled(bool)
+{
+ if(ui->radioButtonAddNewKey->isChecked())
+ {
+ mManager->set(SETTING::DRAW_ON_EMPTY_FRAME_ACTION, CREATE_NEW_KEY);
+ }
+ else if(ui->radioButtonDuplicate->isChecked())
+ {
+ mManager->set(SETTING::DRAW_ON_EMPTY_FRAME_ACTION, DUPLICATE_PREVIOUS_KEY);
+ }
+ else if(ui->radioButtonDrawOnPrev->isChecked())
+ {
+ mManager->set(SETTING::DRAW_ON_EMPTY_FRAME_ACTION, KEEP_DRAWING_ON_PREVIOUS_KEY);
+ }
+}
+
FilesPage::FilesPage(QWidget* parent) :
QWidget(parent),
ui(new Ui::FilesPage)
diff --git a/app/src/preferencesdialog.h b/app/src/preferencesdialog.h
index cfaeed661..6f3f537a1 100644
--- a/app/src/preferencesdialog.h
+++ b/app/src/preferencesdialog.h
@@ -18,31 +18,13 @@ GNU General Public License for more details.
#define _PREFERENCES_H_
#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
#include "pencildef.h"
#include "scribblearea.h"
-#include "shortcutspage.h"
#include "preferencemanager.h"
-class QListWidget;
class QListWidgetItem;
-class QStackedWidget;
-class QComboBox;
class PreferenceManager;
-class FilesPage;
namespace Ui {
class PreferencesDialog;
@@ -135,6 +117,8 @@ public slots:
void frameSizeChange(int);
void labelChange(bool);
void scrubChange(int);
+ void playbackStateChanged(int);
+ void radioButtonToggled(bool);
private:
Ui::TimelinePage* ui = nullptr;
diff --git a/app/src/shortcutfilter.h b/app/src/shortcutfilter.h
index 68ab036ff..cce3fe4ad 100644
--- a/app/src/shortcutfilter.h
+++ b/app/src/shortcutfilter.h
@@ -27,7 +27,7 @@ class ShortcutFilter : public QObject
public:
ShortcutFilter(ScribbleArea* scribbleArea, QObject* parent);
protected:
- bool eventFilter(QObject* obj, QEvent* event);
+ bool eventFilter(QObject* obj, QEvent* event) override;
ScribbleArea* mScribbleArea = nullptr;
};
diff --git a/app/src/timeline2.h b/app/src/timeline2.h
index 82236bd71..77bb25a92 100644
--- a/app/src/timeline2.h
+++ b/app/src/timeline2.h
@@ -17,9 +17,9 @@ GNU General Public License for more details.
#ifndef TIMELINE2_H
#define TIMELINE2_H
-#include
#include "basedockwidget.h"
+
namespace Ui {
class Timeline2;
}
diff --git a/app/src/toolbox.cpp b/app/src/toolbox.cpp
index 87f864843..c6202fc8f 100644
--- a/app/src/toolbox.cpp
+++ b/app/src/toolbox.cpp
@@ -166,6 +166,8 @@ void ToolBoxWidget::updateUI()
void ToolBoxWidget::pencilOn()
{
+ if (!leavingTool(ui->pencilButton)) { return; }
+
editor()->tools()->setCurrentTool(PENCIL);
deselectAllTools();
@@ -174,6 +176,8 @@ void ToolBoxWidget::pencilOn()
void ToolBoxWidget::eraserOn()
{
+ if (!leavingTool(ui->eraserButton)) { return; }
+
editor()->tools()->setCurrentTool(ERASER);
deselectAllTools();
@@ -182,6 +186,8 @@ void ToolBoxWidget::eraserOn()
void ToolBoxWidget::selectOn()
{
+ if (!leavingTool(ui->selectButton)) { return; }
+
editor()->tools()->setCurrentTool(SELECT);
deselectAllTools();
@@ -190,6 +196,8 @@ void ToolBoxWidget::selectOn()
void ToolBoxWidget::moveOn()
{
+ if (!leavingTool(ui->moveButton)) { return; }
+
editor()->tools()->setCurrentTool(MOVE);
deselectAllTools();
@@ -198,6 +206,8 @@ void ToolBoxWidget::moveOn()
void ToolBoxWidget::penOn()
{
+ if (!leavingTool(ui->penButton)) { return; }
+
editor()->tools()->setCurrentTool(PEN);
deselectAllTools();
@@ -206,6 +216,8 @@ void ToolBoxWidget::penOn()
void ToolBoxWidget::handOn()
{
+ if (!leavingTool(ui->handButton)) { return; }
+
editor()->tools()->setCurrentTool( HAND );
deselectAllTools();
@@ -214,6 +226,8 @@ void ToolBoxWidget::handOn()
void ToolBoxWidget::polylineOn()
{
+ if (!leavingTool(ui->polylineButton)) { return; }
+
editor()->tools()->setCurrentTool(POLYLINE);
deselectAllTools();
@@ -222,6 +236,8 @@ void ToolBoxWidget::polylineOn()
void ToolBoxWidget::bucketOn()
{
+ if (!leavingTool(ui->bucketButton)) { return; }
+
editor()->tools()->setCurrentTool(BUCKET);
deselectAllTools();
@@ -230,6 +246,7 @@ void ToolBoxWidget::bucketOn()
void ToolBoxWidget::eyedropperOn()
{
+ if (!leavingTool(ui->eyedropperButton)) { return; }
editor()->tools()->setCurrentTool(EYEDROPPER);
deselectAllTools();
@@ -238,6 +255,8 @@ void ToolBoxWidget::eyedropperOn()
void ToolBoxWidget::brushOn()
{
+ if (!leavingTool(ui->brushButton)) { return; }
+
editor()->tools()->setCurrentTool( BRUSH );
deselectAllTools();
@@ -246,6 +265,7 @@ void ToolBoxWidget::brushOn()
void ToolBoxWidget::smudgeOn()
{
+ if (!leavingTool(ui->smudgeButton)) { return; }
editor()->tools()->setCurrentTool(SMUDGE);
deselectAllTools();
@@ -266,3 +286,15 @@ void ToolBoxWidget::deselectAllTools()
ui->brushButton->setChecked(false);
ui->smudgeButton->setChecked(false);
}
+
+bool ToolBoxWidget::leavingTool(QToolButton* toolButton)
+{
+ if (!editor()->tools()->leavingThisTool())
+ {
+ if (toolButton->isChecked()) {
+ toolButton->setChecked(false);
+ }
+ return false;
+ }
+ return true;
+}
diff --git a/app/src/toolbox.h b/app/src/toolbox.h
index 52cc5f7b9..ea7adcd42 100644
--- a/app/src/toolbox.h
+++ b/app/src/toolbox.h
@@ -61,6 +61,7 @@ public slots:
private:
void deselectAllTools();
+ bool leavingTool(QToolButton*);
Ui::ToolBoxWidget* ui = nullptr;
};
diff --git a/app/src/tooloptionwidget.cpp b/app/src/tooloptionwidget.cpp
index bdcad05e9..c8192fd88 100644
--- a/app/src/tooloptionwidget.cpp
+++ b/app/src/tooloptionwidget.cpp
@@ -77,7 +77,7 @@ void ToolOptionWidget::updateUI()
setPreserveAlpha(p.preserveAlpha);
setVectorMergeEnabled(p.vectorMergeEnabled);
setAA(p.useAA);
- setInpolLevel(p.inpolLevel);
+ setStabilizerLevel(p.stabilizerLevel);
setTolerance(p.tolerance);
setFillContour(p.useFillContour);
}
@@ -106,7 +106,7 @@ void ToolOptionWidget::makeConnectionToEditor(Editor* editor)
connect(ui->vectorMergeBox, &QCheckBox::clicked, toolManager, &ToolManager::setVectorMergeEnabled);
connect(ui->useAABox, &QCheckBox::clicked, toolManager, &ToolManager::setAA);
- connect(ui->inpolLevelsCombo, static_cast(&QComboBox::activated), toolManager, &ToolManager::setInpolLevel);
+ connect(ui->inpolLevelsCombo, static_cast(&QComboBox::activated), toolManager, &ToolManager::setStabilizerLevel);
connect(ui->toleranceSlider, &SpinSlider::valueChanged, toolManager, &ToolManager::setTolerance);
connect(ui->toleranceSpinBox, static_cast(&QSpinBox::valueChanged), toolManager, &ToolManager::setTolerance);
@@ -131,7 +131,7 @@ void ToolOptionWidget::onToolPropertyChanged(ToolType, ToolPropertyType ePropert
case PRESERVEALPHA: setPreserveAlpha(p.preserveAlpha); break;
case VECTORMERGE: setVectorMergeEnabled(p.vectorMergeEnabled); break;
case ANTI_ALIASING: setAA(p.useAA); break;
- case INTERPOLATION: setInpolLevel(p.inpolLevel); break;
+ case STABILIZATION: setStabilizerLevel(p.stabilizerLevel); break;
case TOLERANCE: setTolerance(p.tolerance); break;
case FILLCONTOUR: setFillContour(p.useFillContour); break;
case BEZIER: setBezier(p.bezier_state); break;
@@ -153,8 +153,8 @@ void ToolOptionWidget::setVisibility(BaseTool* tool)
ui->makeInvisibleBox->setVisible(tool->isPropertyEnabled(INVISIBILITY));
ui->preserveAlphaBox->setVisible(tool->isPropertyEnabled(PRESERVEALPHA));
ui->useAABox->setVisible(tool->isPropertyEnabled(ANTI_ALIASING));
- ui->stabilizerLabel->setVisible(tool->isPropertyEnabled(INTERPOLATION));
- ui->inpolLevelsCombo->setVisible(tool->isPropertyEnabled(INTERPOLATION));
+ ui->stabilizerLabel->setVisible(tool->isPropertyEnabled(STABILIZATION));
+ ui->inpolLevelsCombo->setVisible(tool->isPropertyEnabled(STABILIZATION));
ui->toleranceSlider->setVisible(tool->isPropertyEnabled(TOLERANCE));
ui->toleranceSpinBox->setVisible(tool->isPropertyEnabled(TOLERANCE));
ui->fillContourBox->setVisible(tool->isPropertyEnabled(FILLCONTOUR));
@@ -295,7 +295,7 @@ void ToolOptionWidget::setAA(int x)
}
}
-void ToolOptionWidget::setInpolLevel(int x)
+void ToolOptionWidget::setStabilizerLevel(int x)
{
ui->inpolLevelsCombo->setCurrentIndex(qBound(0, x, ui->inpolLevelsCombo->count() - 1));
}
diff --git a/app/src/tooloptionwidget.h b/app/src/tooloptionwidget.h
index 41ee37c87..c4ea56ae6 100644
--- a/app/src/tooloptionwidget.h
+++ b/app/src/tooloptionwidget.h
@@ -61,7 +61,7 @@ public slots:
void setPreserveAlpha(int);
void setVectorMergeEnabled(int);
void setAA(int);
- void setInpolLevel(int);
+ void setStabilizerLevel(int);
void setTolerance(int);
void setFillContour(int);
void setBezier(bool);
diff --git a/app/ui/colorinspector.ui b/app/ui/colorinspector.ui
index 904581966..952a845a1 100644
--- a/app/ui/colorinspector.ui
+++ b/app/ui/colorinspector.ui
@@ -7,8 +7,8 @@
0
0
- 114
- 152
+ 271
+ 175
@@ -22,7 +22,7 @@
2
- 2
+ 5
2
@@ -34,43 +34,172 @@
2
-
-
+
2
+
+ 0
+
-
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 40
+ 0
+
+
+
+
+ 80
+ 30
+
+
HSV
+
+
+ 16
+ 16
+
+
+
+ true
+
-
-
+
+
+
+ 0
+ 0
+
+
+
+
+ 40
+ 25
+
+
+
+
+ 80
+ 30
+
+
RGB
-
+
true
+
+ false
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Expanding
+
+
+
+ 40
+ 20
+
+
+
+
-
- 2
+ 0
-
-
-
+
-
+
+
+
+ 60
+ 16777215
+
+
+
+ 255
+
+
+
+ -
+
+
+
+ 60
+ 16777215
+
+
+
+ 255
+
+
+
+ -
+
- Red
+ A
+
+
+ 2
- -
+
-
+
+
+
+ 16777215
+ 10
+
+
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
+
+
+ -
+
+
+ 60
+ 16777215
+
+
255
@@ -79,45 +208,142 @@
-
- Green
+ G
+
+
+ 2
-
-
-
- 255
+
+
+
+ 16777215
+ 10
+
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
- -
-
+
-
+
+
+
+ 0
+ 0
+
+
- Blue
+ R
-
-
- -
-
-
- 255
+
+ 2
- -
-
+
-
+
- Alpha
+ B
+
+
+ 2
-
-
+
+
+
+ 16777215
+ 10
+
+
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
+
+
+ -
+
+
+
+ 60
+ 16777215
+
+
255
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 10
+
+
+
+
+ 16777215
+ 10
+
+
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
+
+
-
@@ -162,9 +388,15 @@
+
+
+ ColorSlider
+ QWidget
+
+ 1
+
+
- hsv
- rgb
RedspinBox
GreenspinBox
BluespinBox
diff --git a/app/ui/colorpalette.ui b/app/ui/colorpalette.ui
index 4614f1d97..50b14930f 100644
--- a/app/ui/colorpalette.ui
+++ b/app/ui/colorpalette.ui
@@ -2,18 +2,18 @@
ColorPalette
+
+
+ 0
+ 0
+ 268
+ 241
+
+
Color Palette
-
-
- 0
- 0
- 246
- 337
-
-
1
@@ -87,6 +87,69 @@
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 10
+ 20
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 16
+ 16
+
+
+
+
+ 20
+ 20
+
+
+
+
+ 20
+ 20
+
+
+
+ Native color dialog window
+
+
+
+
+
+
+ :/app/icons/new/svg/color-dialog.svg:/app/icons/new/svg/color-dialog.svg
+
+
+
+ 16
+ 16
+
+
+
+ true
+
+
+
-
@@ -115,12 +178,16 @@
-1
- ...
+
+
+
+
+ :/app/icons/new/svg/more_options.svg:/app/icons/new/svg/more_options.svg
15
- 10
+ 15
@@ -161,6 +228,9 @@
16
+
+ QAbstractItemView::ExtendedSelection
+
QListView::Fixed
@@ -245,24 +315,7 @@
-
-
- removeColorButton
- clicked()
- ColorPalette
- clickRemoveColorButton()
-
-
- 36
- 43
-
-
- 133
- 128
-
-
-
-
+
colorListCurrentItemChanged(QListWidgetItem*,QListWidgetItem*)
clickColorListItem(QListWidgetItem*)
diff --git a/app/ui/errordialog.ui b/app/ui/errordialog.ui
index 71cdf0412..7e955dca5 100644
--- a/app/ui/errordialog.ui
+++ b/app/ui/errordialog.ui
@@ -73,6 +73,9 @@
true
+
+ true
+
diff --git a/app/ui/exportimageoptions.ui b/app/ui/exportimageoptions.ui
index 73e6198b0..f6015893c 100644
--- a/app/ui/exportimageoptions.ui
+++ b/app/ui/exportimageoptions.ui
@@ -7,7 +7,7 @@
0
0
380
- 259
+ 470
@@ -105,6 +105,131 @@
+ -
+
+
+ Range
+
+
+
+ 9
+
+
+ 9
+
+
+ 9
+
+
+ 9
+
+
+ 3
+
+
-
+
+
+ 1
+
+
+ 99999
+
+
+ 1
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 35
+
+
+
+ The last frame you want to include in the exported movie
+
+
+ End Frame
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 35
+
+
+
+ The first frame you want to include in the exported movie
+
+
+ Start Frame
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 50
+ 35
+
+
+
+
+ 0
+ 0
+
+
+
+ <html><head/><body><p>End frame is set to last paintable keyframe (Useful when you only want to export to the last animated frame)</p></body></html>
+
+
+
+
+
+ To the end of sound clips
+
+
+
+ -
+
+
+ 1
+
+
+ 999999
+
+
+ 10
+
+
+
+
+
+
diff --git a/app/ui/mainwindow2.ui b/app/ui/mainwindow2.ui
index c04406af5..af3efc5b3 100644
--- a/app/ui/mainwindow2.ui
+++ b/app/ui/mainwindow2.ui
@@ -64,6 +64,7 @@
+
@@ -76,6 +77,7 @@
+
@@ -254,7 +256,7 @@
:/icons/saveas.png:/icons/saveas.png
- Save As ..
+ Save As...
@@ -876,6 +878,16 @@
F1
+
+
+ Animated GIF...
+
+
+
+
+ Animated GIF...
+
+
diff --git a/app/ui/timelinepage.ui b/app/ui/timelinepage.ui
index c65c39906..e72998b6a 100644
--- a/app/ui/timelinepage.ui
+++ b/app/ui/timelinepage.ui
@@ -6,8 +6,8 @@
0
0
- 315
- 262
+ 342
+ 457
@@ -83,15 +83,111 @@
+ -
+
+
+ Drawing
+
+
+
-
+
+
+ When drawing on an empty frame:
+
+
+
+ -
+
+
+ Create a new (blank) key-frame and start drawing on it.
+
+
+ Create a new (blank) key-frame
+
+
+ true
+
+
+
+ -
+
+
+ Duplicate the previous key-frame and start drawing on the duplicate.
+
+
+ Duplicate the previous key-frame
+
+
+
+ -
+
+
+ Keep drawing on the previous key-frame
+
+
+
+ -
+
+
+
+ 10
+
+
+
+ <html><head/><body><p>(Applies to Pencil, Erasor, Pen, Polyline, Bucket and Brush tools)</p></body></html>
+
+
+ true
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 60
+
+
+
+
+ 16777215
+ 60
+
+
+
+ Playback
+
+
+
+
+ 10
+ 30
+ 301
+ 16
+
+
+
+ Show onion skin while playing
+
+
+
+
-
Qt::Vertical
+
+ QSizePolicy::Preferred
+
- 0
- 0
+ 20
+ 30
diff --git a/appveyor.yml b/appveyor.yml
index bd2855d3c..7cc36dab5 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -38,6 +38,7 @@ install:
- "%PYTHON3% -m pip install -r requirements.txt"
- "%PYTHON3% -m pip install setuptools"
- "%PYTHON3% -m pip install virtualenvwrapper"
+ - "%PYTHON3% -m pip install --upgrade oauth2client"
- "%PYTHON3% -m pip install --upgrade google-api-python-client"
- "%PYTHON3% -V"
- set QTDIR=C:\Qt\%qt%
diff --git a/common.pri b/common.pri
index d3449fc59..8fad62f6e 100644
--- a/common.pri
+++ b/common.pri
@@ -1,5 +1,5 @@
-VERSION = 0.6.1.1
+VERSION = 0.6.2
DEFINES += APP_VERSION=\\\"$$VERSION\\\"
NIGHTLY {
diff --git a/core_lib/core_lib.pro b/core_lib/core_lib.pro
index 93c078674..693843c88 100644
--- a/core_lib/core_lib.pro
+++ b/core_lib/core_lib.pro
@@ -87,6 +87,7 @@ HEADERS += \
src/util/pencilsettings.h \
src/util/util.h \
src/util/log.h \
+ src/util/movemode.h \
src/canvaspainter.h \
src/soundplayer.h \
src/movieexporter.h \
@@ -156,6 +157,9 @@ SOURCES += src/graphics/bitmap/bitmapimage.cpp \
src/qminiz.cpp \
src/activeframepool.cpp
+FORMS += \
+ ui/camerapropertiesdialog.ui
+
win32 {
CONFIG -= flat
@@ -173,5 +177,10 @@ unix:!macx {
SOURCES += src/external/linux/linux.cpp
}
-FORMS += \
- ui/camerapropertiesdialog.ui
+contains(QT_ARCH, i386) {
+ message("32-bit")
+ DEFINES += "FRAME_POOL_SIZE=200"
+} else {
+ message("64-bit")
+ DEFINES += "FRAME_POOL_SIZE=400"
+}
diff --git a/core_lib/data/resources/kb.ini b/core_lib/data/resources/kb.ini
index 9ad5097ab..4e1be0b29 100644
--- a/core_lib/data/resources/kb.ini
+++ b/core_lib/data/resources/kb.ini
@@ -66,3 +66,4 @@ CmdToggleColorWheel=Ctrl+3
CmdToggleColorLibrary=Ctrl+4
CmdToggleDisplayOptions=Ctrl+5
CmdToggleTimeline=Ctrl+6
+CmdToggleColorInspector=Ctrl+7
diff --git a/core_lib/src/activeframepool.cpp b/core_lib/src/activeframepool.cpp
index 0d5f03867..91f8083b0 100644
--- a/core_lib/src/activeframepool.cpp
+++ b/core_lib/src/activeframepool.cpp
@@ -73,6 +73,12 @@ void ActiveFramePool::clear()
mCacheFramesMap.clear();
}
+bool ActiveFramePool::isFrameInPool(KeyFrame* key)
+{
+ auto it = mCacheFramesMap.find(key);
+ return (it != mCacheFramesMap.end());
+}
+
void ActiveFramePool::onKeyFrameDestroy(KeyFrame* key)
{
auto it = mCacheFramesMap.find(key);
@@ -85,5 +91,6 @@ void ActiveFramePool::onKeyFrameDestroy(KeyFrame* key)
void ActiveFramePool::unloadFrame(KeyFrame* key)
{
+ //qDebug() << "Unload frame:" << key->pos();
key->unloadFile();
}
diff --git a/core_lib/src/activeframepool.h b/core_lib/src/activeframepool.h
index 785437618..ce55d4310 100644
--- a/core_lib/src/activeframepool.h
+++ b/core_lib/src/activeframepool.h
@@ -36,13 +36,14 @@ class ActiveFramePool : public KeyFrameEventListener
void put(KeyFrame* key);
size_t size() const;
void clear();
+ bool isFrameInPool(KeyFrame*);
void onKeyFrameDestroy(KeyFrame*) override;
private:
void unloadFrame(KeyFrame* key);
- typedef std::list::iterator list_iterator_t;
+ using list_iterator_t = std::list::iterator;
std::list mCacheFramesList;
std::unordered_map mCacheFramesMap;
diff --git a/core_lib/src/canvaspainter.cpp b/core_lib/src/canvaspainter.cpp
index 1e85c68a1..877b57312 100644
--- a/core_lib/src/canvaspainter.cpp
+++ b/core_lib/src/canvaspainter.cpp
@@ -77,9 +77,9 @@ void CanvasPainter::paint(const Object* object, int layer, int frame, QRect rect
mCurrentLayerIndex = layer;
mFrameNumber = frame;
- QRectF mappedInvCanvas = mViewInverse.mapRect(QRectF(mCanvas->rect()));
- QSizeF croppedPainter = QSizeF(mappedInvCanvas.size());
- QRectF aligned = QRectF(QPointF(mappedInvCanvas.topLeft()), croppedPainter);
+ //QRectF mappedInvCanvas = mViewInverse.mapRect(QRectF(mCanvas->rect()));
+ //QSizeF croppedPainter = QSizeF(mappedInvCanvas.size());
+ //QRectF aligned = QRectF(QPointF(mappedInvCanvas.topLeft()), croppedPainter);
QPainter painter(mCanvas);
painter.setWorldMatrixEnabled(true);
@@ -90,7 +90,7 @@ void CanvasPainter::paint(const Object* object, int layer, int frame, QRect rect
paintBackground();
paintOnionSkin(painter);
- painter.setClipRect(aligned);
+ //painter.setClipRect(aligned); // this aligned rect is valid only for bitmap images.
paintCurrentFrame(painter);
paintCameraBorder(painter);
@@ -108,6 +108,8 @@ void CanvasPainter::paintBackground()
void CanvasPainter::paintOnionSkin(QPainter& painter)
{
+ if (!mOptions.onionWhilePlayback && mOptions.isPlaying) { return; }
+
Layer* layer = mObject->getLayer(mCurrentLayerIndex);
if (layer->visible() == false)
@@ -189,7 +191,7 @@ void CanvasPainter::paintBitmapFrame(QPainter& painter,
LayerBitmap* bitmapLayer = static_cast(layer);
#endif
- qCDebug(mLog) << "Paint Onion skin bitmap, Frame = " << nFrame;
+ //qCDebug(mLog) << "Paint Onion skin bitmap, Frame = " << nFrame;
BitmapImage* paintedImage = nullptr;
if (useLastKeyFrame)
{
@@ -206,7 +208,7 @@ void CanvasPainter::paintBitmapFrame(QPainter& painter,
}
paintedImage->loadFile(); // Critical! force the BitmapImage to load the image
- qCDebug(mLog) << "Paint Image Size:" << paintedImage->image()->size();
+ //qCDebug(mLog) << "Paint Image Size:" << paintedImage->image()->size();
BitmapImage paintToImage;
paintToImage.paste(paintedImage);
@@ -241,7 +243,6 @@ void CanvasPainter::paintBitmapFrame(QPainter& painter,
painter.setWorldMatrixEnabled(true);
prescale(&paintToImage);
-
paintToImage.paintImage(painter, mScaledBitmap, mScaledBitmap.rect(), paintToImage.bounds());
}
@@ -269,7 +270,7 @@ void CanvasPainter::prescale(BitmapImage* bitmapImage)
}
void CanvasPainter::paintVectorFrame(QPainter& painter,
- Layer* layer,
+ Layer* layer,
int nFrame,
bool colorize,
bool useLastKeyFrame)
diff --git a/core_lib/src/canvaspainter.h b/core_lib/src/canvaspainter.h
index b212cce30..c4ba741c1 100644
--- a/core_lib/src/canvaspainter.h
+++ b/core_lib/src/canvaspainter.h
@@ -17,11 +17,10 @@ GNU General Public License for more details.
#ifndef CANVASPAINTER_H
#define CANVASPAINTER_H
-
+#include
#include
#include
#include
-#include
#include "log.h"
@@ -49,6 +48,8 @@ struct CanvasPainterOptions
int nShowAllLayers = 3;
bool bIsOnionAbsolute = false;
float scaling = 1.0f;
+ bool isPlaying = false;
+ bool onionWhilePlayback = false;
};
diff --git a/core_lib/src/external/linux/linux.cpp b/core_lib/src/external/linux/linux.cpp
index 979152ebc..2ae39883f 100644
--- a/core_lib/src/external/linux/linux.cpp
+++ b/core_lib/src/external/linux/linux.cpp
@@ -22,6 +22,8 @@ GNU General Public License for more details.
#include
#include
#include
+#include
+#include
#include "object.h"
#include "editor.h"
#include "layersound.h"
diff --git a/core_lib/src/external/macosx/macosx.cpp b/core_lib/src/external/macosx/macosx.cpp
index 6b1d28604..f8803c21c 100644
--- a/core_lib/src/external/macosx/macosx.cpp
+++ b/core_lib/src/external/macosx/macosx.cpp
@@ -20,6 +20,13 @@ GNU General Public License for more details.
#include
#include
#include
+#include
+#include
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0)
+ #include
+#endif
+
#include "object.h"
#include "editor.h"
#include "pencildef.h"
@@ -59,7 +66,7 @@ void enableCoalescing()
SetMouseCoalescingEnabled(true, NULL);
}
-void Editor::importMovie (QString filePath, int fps)
+void Editor::importMovie(QString filePath, int fps)
{
int i;
@@ -96,7 +103,9 @@ void Editor::importMovie (QString filePath, int fps)
{qDebug() << "ERROR: FFmpeg did not finish executing.";}
}
else
- {qDebug() << "Please install FFMPEG: sudo apt-get install ffmpeg";}
+ {
+ qDebug() << "Please install FFMPEG: sudo apt-get install ffmpeg";
+ }
progress.setValue(50);
QDir dir1(tempPath);
int nFiles = dir1.entryList().count();
diff --git a/core_lib/src/external/win32/win32.cpp b/core_lib/src/external/win32/win32.cpp
index 14aa12974..7fca053f4 100644
--- a/core_lib/src/external/win32/win32.cpp
+++ b/core_lib/src/external/win32/win32.cpp
@@ -24,44 +24,13 @@ GNU General Public License for more details.
#include
#include
#include
+#include
+#include
#include "object.h"
#include "editor.h"
#include "layersound.h"
-#define MIN(a, b) ((a)>(b) ? (b) : (a))
-
-
-
-
-int16_t safeSum( int16_t a, int16_t b )
-{
- if ( ( ( int )a + ( int )b ) > 32767 )
- return 32767;
- if ( ( ( int )a + ( int )b ) < -32768 )
- return -32768;
- return a + b;
-}
-
-void initialise()
-{
- //qDebug() << "Initialize win32: ";
-
- // QImageReader capabilities
- QList formats = QImageReader::supportedImageFormats();
- foreach( QString format, formats )
- {
- //qDebug() << "QImageReader capability: " << format;
- }
-
- // QImageWriter capabilities
- formats = QImageWriter::supportedImageFormats();
- foreach( QString format, formats )
- {
- //qDebug() << "QImageWriter capability: " << format;
- }
-}
-
void Editor::importMovie( QString filePath, int fps )
{
@@ -120,8 +89,8 @@ void Editor::importMovie( QString filePath, int fps )
QDir dir( tempPath );
QStringList filtername( "*.*" );
QStringList entries = dir.entryList( filtername, QDir::Files, QDir::Type );
- for ( int i = 0; i < entries.size(); i++ )
- dir.remove( entries[ i ] );
+ for ( int e = 0; e < entries.size(); e++ )
+ dir.remove( entries[ e ] );
}
else
{
diff --git a/core_lib/src/graphics/bitmap/bitmapimage.cpp b/core_lib/src/graphics/bitmap/bitmapimage.cpp
index 37aa2d3be..5446b7945 100644
--- a/core_lib/src/graphics/bitmap/bitmapimage.cpp
+++ b/core_lib/src/graphics/bitmap/bitmapimage.cpp
@@ -18,18 +18,20 @@ GNU General Public License for more details.
#include
#include
+#include
+#include
#include "util.h"
BitmapImage::BitmapImage()
{
- mImage = std::make_shared(1, 1, QImage::Format_ARGB32_Premultiplied); // don't create null image
- mImage->fill(QColor(0,0,0,0));
- mBounds = QRect(0, 0, 1, 1);
+ mImage = std::make_shared(); // create null image
+ mBounds = QRect(0, 0, 0, 0);
}
BitmapImage::BitmapImage(const BitmapImage& a) : KeyFrame(a)
{
mBounds = a.mBounds;
+ mMinBound = a.mMinBound;
mImage = std::make_shared(*a.mImage);
}
@@ -38,25 +40,23 @@ BitmapImage::BitmapImage(const QRect& rectangle, const QColor& colour)
mBounds = rectangle;
mImage = std::make_shared(mBounds.size(), QImage::Format_ARGB32_Premultiplied);
mImage->fill(colour.rgba());
+ mMinBound = false;
}
-BitmapImage::BitmapImage(const QRect& rectangle, const QImage& image)
+BitmapImage::BitmapImage(const QPoint& topLeft, const QImage& image)
{
- mBounds = rectangle.normalized();
- mExtendable = true;
+ mBounds = QRect(topLeft, image.size());
+ mMinBound = true;
mImage = std::make_shared(image);
- if (mImage->width() != rectangle.width() || mImage->height() != rectangle.height())
- {
- qDebug() << "Error instancing bitmapImage.";
- }
}
-BitmapImage::BitmapImage(const QString& path, const QPoint& topLeft)
+BitmapImage::BitmapImage(const QPoint& topLeft, const QString& path)
{
setFileName(path);
mImage.reset();
- mBounds = QRect(topLeft, QSize(1,1));
+ mBounds = QRect(topLeft, QSize(0, 0));
+ mMinBound = true;
setModified(false);
}
@@ -68,6 +68,7 @@ void BitmapImage::setImage(QImage* img)
{
Q_CHECK_PTR(img);
mImage.reset(img);
+ mMinBound = false;
modification();
}
@@ -75,6 +76,7 @@ void BitmapImage::setImage(QImage* img)
BitmapImage& BitmapImage::operator=(const BitmapImage& a)
{
mBounds = a.mBounds;
+ mMinBound = a.mMinBound;
mImage = std::make_shared(*a.mImage);
modification();
return *this;
@@ -89,10 +91,9 @@ void BitmapImage::loadFile()
{
if (mImage == nullptr)
{
- Q_ASSERT(isModified() == false);
mImage = std::make_shared(fileName());
mBounds.setSize(mImage->size());
- //qDebug() << "Load file=" << fileName();
+ mMinBound = false;
}
}
@@ -104,14 +105,19 @@ void BitmapImage::unloadFile()
}
}
+bool BitmapImage::isLoaded()
+{
+ return (mImage != nullptr);
+}
+
void BitmapImage::paintImage(QPainter& painter)
{
- painter.drawImage(topLeft(), *image());
+ painter.drawImage(mBounds.topLeft(), *image());
}
void BitmapImage::paintImage(QPainter& painter, QImage& image, QRect sourceRect, QRect destRect)
{
- painter.drawImage(QRect(QPoint(topLeft()), destRect.size()),
+ painter.drawImage(QRect(mBounds.topLeft(), destRect.size()),
image,
sourceRect);
}
@@ -124,33 +130,24 @@ QImage* BitmapImage::image()
BitmapImage BitmapImage::copy()
{
- return BitmapImage(mBounds, *image());
+ return BitmapImage(mBounds.topLeft(), *image());
}
BitmapImage BitmapImage::copy(QRect rectangle)
{
- QRect intersection2 = rectangle.translated(-topLeft());
- BitmapImage result = BitmapImage(rectangle, image()->copy(intersection2));
+ QRect intersection2 = rectangle.translated(-mBounds.topLeft());
+ BitmapImage result = BitmapImage(rectangle.topLeft(), image()->copy(intersection2));
return result;
}
-void BitmapImage::paste(BitmapImage* bitmapImage)
-{
- paste(bitmapImage, QPainter::CompositionMode_SourceOver);
-}
-
void BitmapImage::paste(BitmapImage* bitmapImage, QPainter::CompositionMode cm)
{
- QRect newBoundaries;
- if (image()->width() == 0 || image()->height() == 0)
+ if(bitmapImage->width() <= 0 || bitmapImage->height() <= 0)
{
- newBoundaries = bitmapImage->mBounds;
- }
- else
- {
- newBoundaries = mBounds.united(bitmapImage->mBounds);
+ return;
}
- extend(newBoundaries);
+
+ setCompositionModeBounds(bitmapImage, cm);
QImage* image2 = bitmapImage->image();
@@ -162,90 +159,10 @@ void BitmapImage::paste(BitmapImage* bitmapImage, QPainter::CompositionMode cm)
modification();
}
-void BitmapImage::add(BitmapImage* bitmapImage)
-{
- QImage* image2 = bitmapImage->image();
-
- QRect newBoundaries;
- if (image()->width() == 0 || image()->height() == 0)
- {
- newBoundaries = bitmapImage->mBounds;
- }
- else
- {
- newBoundaries = mBounds.united(bitmapImage->mBounds);
- }
- extend(newBoundaries);
- QPoint offset = bitmapImage->mBounds.topLeft() - mBounds.topLeft();
- for (int y = 0; y < image2->height(); y++)
- {
- for (int x = 0; x < image2->width(); x++)
- {
- QRgb p1 = image()->pixel(offset.x() + x, offset.y() + y);
- QRgb p2 = image2->pixel(x, y);
-
- int a1 = qAlpha(p1);
- int a2 = qAlpha(p2);
- int r1 = qRed(p1);
- int r2 = qRed(p2); // remember that the bitmap format is RGB32 Premultiplied
- int g1 = qGreen(p1);
- int g2 = qGreen(p2);
- int b1 = qBlue(p1);
- int b2 = qBlue(p2);
-
- // unite
- int a = qMax(a1, a2);
- int r = qMax(r1, r2);
- int g = qMax(g1, g2);
- int b = qMax(b1, b2);
-
- QRgb mix = qRgba(r, g, b, a);
- if (a2 != 0)
- {
- mImage->setPixel(offset.x() + x, offset.y() + y, mix);
- }
- }
- }
- modification();
-}
-
-void BitmapImage::compareAlpha(BitmapImage* bitmapImage) // this function picks the greater alpha value
-{
- QImage* image2 = bitmapImage->image();
-
- QRect newBoundaries;
- if (image()->width() == 0 || image()->height() == 0)
- {
- newBoundaries = bitmapImage->mBounds;
- }
- else
- {
- newBoundaries = mBounds.united(bitmapImage->mBounds);
- }
- extend(newBoundaries);
- QPoint offset = bitmapImage->mBounds.topLeft() - mBounds.topLeft();
- for (int y = 0; y < image2->height(); y++)
- {
- for (int x = 0; x < image2->width(); x++)
- {
- QRgb p1 = image()->pixel(offset.x() + x, offset.y() + y);
- QRgb p2 = image2->pixel(x, y);
-
- int a1 = qAlpha(p1);
- int a2 = qAlpha(p2);
-
- if (a1 <= a2)
- {
- QRgb mix = qRgba(qRed(p2), qGreen(p2), qBlue(p2), a2);
- image()->setPixel(offset.x() + x, offset.y() + y, mix);
- }
- }
- }
-}
-
void BitmapImage::moveTopLeft(QPoint point)
{
mBounds.moveTopLeft(point);
+ // Size is unchanged so there is no need to update mBounds
modification();
}
@@ -283,7 +200,7 @@ BitmapImage BitmapImage::transformed(QRect selection, QTransform transform, bool
{
transformedImage = selectedPart.image()->transformed(transform);
}
- return BitmapImage(transform.mapRect(selection), transformedImage);
+ return BitmapImage(transform.mapRect(selection).normalized().topLeft(), transformedImage);
}
BitmapImage BitmapImage::transformed(QRect newBoundaries, bool smoothTransform)
@@ -297,23 +214,48 @@ BitmapImage BitmapImage::transformed(QRect newBoundaries, bool smoothTransform)
return transformedImage;
}
+/** Update image bounds.
+ *
+ * @param[in] newBoundaries the new bounds
+ *
+ * Sets this image's bounds to rectangle.
+ * Modifies mBounds and crops mImage.
+ */
+void BitmapImage::updateBounds(QRect newBoundaries)
+{
+ // Check to make sure changes actually need to be made
+ if (mBounds == newBoundaries) return;
+
+ QImage* newImage = new QImage( newBoundaries.size(), QImage::Format_ARGB32_Premultiplied);
+ newImage->fill(Qt::transparent);
+ if (!newImage->isNull())
+ {
+ QPainter painter(newImage);
+ painter.drawImage(mBounds.topLeft() - newBoundaries.topLeft(), *mImage);
+ painter.end();
+ }
+ mImage.reset( newImage );
+ mBounds = newBoundaries;
+ mMinBound = false;
+
+ modification();
+}
-void BitmapImage::extend(QPoint P)
+void BitmapImage::extend(const QPoint &p)
{
- if (!mBounds.contains(P))
+ if (!mBounds.contains(p))
{
- extend(QRect(P, QSize(1, 1)));
+ extend(QRect(p, QSize(1, 1)));
}
}
void BitmapImage::extend(QRect rectangle)
{
- if (!mExtendable) return;
if (rectangle.width() <= 0) rectangle.setWidth(1);
if (rectangle.height() <= 0) rectangle.setHeight(1);
if (mBounds.contains(rectangle))
{
- // nothing
+ // Do nothing
}
else
{
@@ -333,16 +275,251 @@ void BitmapImage::extend(QRect rectangle)
}
}
+/** Updates the bounds after a drawImage operation with the composition mode cm.
+ *
+ * @param[in] source The source image used for the drawImage call.
+ * @param[in] cm The composition mode that will be used for the draw image
+ *
+ * @see BitmapImage::setCompositionModeBounds(BitmapImage, QPainter::CompositionMode)
+ */
+void BitmapImage::setCompositionModeBounds(BitmapImage *source, QPainter::CompositionMode cm)
+{
+ if (source)
+ {
+ setCompositionModeBounds(source->mBounds, source->mMinBound, cm);
+ }
+}
+
+/** Updates the bounds after a draw operation with the composition mode cm.
+ *
+ * @param[in] sourceBounds The bounds of the source used for drawcall.
+ * @param[in] isSourceMinBounds Is sourceBounds the minimal bounds for the source image
+ * @param[in] cm The composition mode that will be used for the draw image
+ *
+ * For a call to draw image of a QPainter (initialized with mImage) with an argument
+ * of source, this function intelligently calculates the bounds. It will attempt to
+ * preserve minimum bounds based on the composition mode.
+ *
+ * This works baed on the principle that some minimal bounds can be determined
+ * solely by the minimal bounds of this and source, depending on the value of cm.
+ * Some composition modes only expand, or have no affect on the bounds.
+ *
+ * @warning The draw operation described by the arguments of this
+ * function needs to be called after this function is run,
+ * or the bounds will be out of sync. If mBounds is null,
+ * no draw operation needs to be performed.
+ */
+void BitmapImage::setCompositionModeBounds(QRect sourceBounds, bool isSourceMinBounds, QPainter::CompositionMode cm)
+{
+ QRect newBoundaries;
+ switch(cm)
+ {
+ case QPainter::CompositionMode_Destination:
+ case QPainter::CompositionMode_SourceAtop:
+ // The Destination and SourceAtop modes
+ // do not change the bounds from destination.
+ newBoundaries = mBounds;
+ // mMinBound remains the same
+ break;
+ case QPainter::CompositionMode_SourceIn:
+ case QPainter::CompositionMode_DestinationIn:
+ case QPainter::CompositionMode_Clear:
+ case QPainter::CompositionMode_DestinationOut:
+ // The bounds of the result of SourceIn, DestinationIn, Clear, and DestinationOut
+ // modes are no larger than the destination bounds
+ newBoundaries = mBounds;
+ mMinBound = false;
+ break;
+ default:
+ // If it's not one of the above cases, create a union of the two bounds.
+ // This contains the minimum bounds, if both the destination and source
+ // use their respective minimum bounds.
+ newBoundaries = mBounds.united(sourceBounds);
+ mMinBound = mMinBound && isSourceMinBounds;
+ }
+
+ updateBounds(newBoundaries);
+}
+
+/** Removes any transparent borders by reducing the boundaries.
+ *
+ * This function reduces the bounds of an image until the top and
+ * bottom rows, and the left and right columns of pixels each
+ * contain at least one pixel with a non-zero alpha value
+ * (i.e. non-transparent pixel). Both mBounds and
+ * the size of #mImage are updated.
+ *
+ * @pre mBounds.size() == mImage->size()
+ * @post Either the first and last rows and columns all contain a
+ * pixel with alpha > 0 or mBounds.isEmpty() == true
+ * @post isMinimallyBounded() == true
+ */
+void BitmapImage::autoCrop()
+{
+ if (!mEnableAutoCrop) return;
+ if (mBounds.isEmpty()) return; // Exit if current bounds are null
+ if (!mImage) return;
+
+ Q_ASSERT(mBounds.size() == mImage->size());
+
+ // Exit if already min bounded
+ if (mMinBound) return;
+
+ // Get image properties
+ const int width = mImage->width();
+
+ // Relative top and bottom row indices (inclusive)
+ int relTop = 0;
+ int relBottom = mBounds.height()-1;
+
+ // Check top row
+ bool isEmpty = true; // Used to track if a non-transparent pixel has been found
+ while (isEmpty && relTop <= relBottom) // Loop through rows
+ {
+ // Point cursor to the first pixel in the current top row
+ const QRgb* cursor = reinterpret_cast(mImage->constScanLine(relTop));
+ for (int col = 0; col < width; col++) // Loop through pixels in row
+ {
+ // If the pixel is not transparent
+ // (i.e. alpha channel > 0)
+ if (qAlpha(*cursor) != 0)
+ {
+ // We've found a non-transparent pixel in row relTop,
+ // so we can stop looking for one
+ isEmpty = false;
+ break;
+ }
+ // Move cursor to point to the next pixel in the row
+ cursor++;
+ }
+ if (isEmpty)
+ {
+ // If the row we just checked was empty, increase relTop
+ // to remove the empty row from the top of the bounding box
+ ++relTop;
+ }
+ }
+
+ // Check bottom row
+ isEmpty = true; // Reset isEmpty
+ while (isEmpty && relBottom >= relTop) // Loop through rows
+ {
+ // Point cursor to the first pixel in the current bottom row
+ const QRgb* cursor = reinterpret_cast(mImage->constScanLine(relBottom));
+ for (int col = 0; col < width; col++) // Loop through pixels in row
+ {
+ // If the pixel is not transparent
+ // (i.e. alpha channel > 0)
+ if(qAlpha(*cursor) != 0)
+ {
+ // We've found a non-transparent pixel in row relBottom,
+ // so we can stop looking for one
+ isEmpty = false;
+ break;
+ }
+ // Move cursor to point to the next pixel in the row
+ ++cursor;
+ }
+ if (isEmpty)
+ {
+ // If the row we just checked was empty, decrease relBottom
+ // to remove the empty row from the bottom of the bounding box
+ --relBottom;
+ }
+ }
+
+ // Relative left and right column indices (inclusive)
+ int relLeft = 0;
+ int relRight = mBounds.width()-1;
+
+ // Check left row
+ isEmpty = true; // Reset isEmpty
+ while (isEmpty && relLeft <= relRight) // Loop through columns
+ {
+ // Point cursor to the pixel at row relTop and column relLeft
+ const QRgb* cursor = reinterpret_cast(mImage->constScanLine(relTop)) + relLeft;
+ // Loop through pixels in column
+ // Note: we only need to loop from relTop to relBottom (inclusive)
+ // not the full image height, because rows 0 to relTop-1 and
+ // relBottom+1 to mBounds.height() have already been
+ // confirmed to contain only transparent pixels
+ for (int row = relTop; row <= relBottom; row++)
+ {
+ // If the pixel is not transparent
+ // (i.e. alpha channel > 0)
+ if(qAlpha(*cursor) != 0)
+ {
+ // We've found a non-transparent pixel in column relLeft,
+ // so we can stop looking for one
+ isEmpty = false;
+ break;
+ }
+ // Move cursor to point to next pixel in the column
+ // Increment by width because the data is in row-major order
+ cursor += width;
+ }
+ if (isEmpty)
+ {
+ // If the column we just checked was empty, increase relLeft
+ // to remove the empty column from the left of the bounding box
+ ++relLeft;
+ }
+ }
+
+ // Check right row
+ isEmpty = true; // Reset isEmpty
+ while (isEmpty && relRight >= relLeft) // Loop through columns
+ {
+ // Point cursor to the pixel at row relTop and column relRight
+ const QRgb* cursor = reinterpret_cast(mImage->constScanLine(relTop)) + relRight;
+ // Loop through pixels in column
+ // Note: we only need to loop from relTop to relBottom (inclusive)
+ // not the full image height, because rows 0 to relTop-1 and
+ // relBottom+1 to mBounds.height()-1 have already been
+ // confirmed to contain only transparent pixels
+ for (int row = relTop; row <= relBottom; row++)
+ {
+ // If the pixel is not transparent
+ // (i.e. alpha channel > 0)
+ if(qAlpha(*cursor) != 0)
+ {
+ // We've found a non-transparent pixel in column relRight,
+ // so we can stop looking for one
+ isEmpty = false;
+ break;
+ }
+ // Move cursor to point to next pixel in the column
+ // Increment by width because the data is in row-major order
+ cursor += width;
+ }
+ if (isEmpty)
+ {
+ // If the column we just checked was empty, increase relRight
+ // to remove the empty column from the left of the bounding box
+ --relRight;
+ }
+ }
+
+ //qDebug() << "Original" << mBounds;
+ //qDebug() << "Autocrop" << relLeft << relTop << relRight - mBounds.width() + 1 << relBottom - mBounds.height() + 1;
+ // Update mBounds and mImage if necessary
+ updateBounds(mBounds.adjusted(relLeft, relTop, relRight - mBounds.width() + 1, relBottom - mBounds.height() + 1));
+
+ //qDebug() << "New bounds" << mBounds;
+
+ mMinBound = true;
+}
+
QRgb BitmapImage::pixel(int x, int y)
{
return pixel(QPoint(x, y));
}
-QRgb BitmapImage::pixel(QPoint P)
+QRgb BitmapImage::pixel(QPoint p)
{
QRgb result = qRgba(0, 0, 0, 0); // black
- if (mBounds.contains(P))
- result = image()->pixel(P - topLeft());
+ if (mBounds.contains(p))
+ result = image()->pixel(p - mBounds.topLeft());
return result;
}
@@ -351,12 +528,12 @@ void BitmapImage::setPixel(int x, int y, QRgb colour)
setPixel(QPoint(x, y), colour);
}
-void BitmapImage::setPixel(QPoint P, QRgb colour)
+void BitmapImage::setPixel(QPoint p, QRgb colour)
{
- extend(P);
- if (mBounds.contains(P))
+ setCompositionModeBounds(QRect(p, QSize(1,1)), true, QPainter::CompositionMode_SourceOver);
+ if (mBounds.contains(p))
{
- image()->setPixel(P - topLeft(), colour);
+ image()->setPixel(p - mBounds.topLeft(), colour);
}
modification();
}
@@ -365,14 +542,14 @@ void BitmapImage::setPixel(QPoint P, QRgb colour)
void BitmapImage::drawLine(QPointF P1, QPointF P2, QPen pen, QPainter::CompositionMode cm, bool antialiasing)
{
int width = 2 + pen.width();
- extend(QRect(P1.toPoint(), P2.toPoint()).normalized().adjusted(-width, -width, width, width));
+ setCompositionModeBounds(QRect(P1.toPoint(), P2.toPoint()).normalized().adjusted(-width, -width, width, width), true, cm);
if (!image()->isNull())
{
QPainter painter(image());
painter.setCompositionMode(cm);
painter.setRenderHint(QPainter::Antialiasing, antialiasing);
painter.setPen(pen);
- painter.drawLine(P1 - topLeft(), P2 - topLeft());
+ painter.drawLine(P1 - mBounds.topLeft(), P2 - mBounds.topLeft());
painter.end();
}
modification();
@@ -381,12 +558,12 @@ void BitmapImage::drawLine(QPointF P1, QPointF P2, QPen pen, QPainter::Compositi
void BitmapImage::drawRect(QRectF rectangle, QPen pen, QBrush brush, QPainter::CompositionMode cm, bool antialiasing)
{
int width = pen.width();
- extend(rectangle.adjusted(-width, -width, width, width).toRect());
+ setCompositionModeBounds(rectangle.adjusted(-width, -width, width, width).toRect(), true, cm);
if (brush.style() == Qt::RadialGradientPattern)
{
QRadialGradient* gradient = (QRadialGradient*)brush.gradient();
- gradient->setCenter(gradient->center() - topLeft());
- gradient->setFocalPoint(gradient->focalPoint() - topLeft());
+ gradient->setCenter(gradient->center() - mBounds.topLeft());
+ gradient->setFocalPoint(gradient->focalPoint() - mBounds.topLeft());
}
if (!image()->isNull())
{
@@ -395,7 +572,7 @@ void BitmapImage::drawRect(QRectF rectangle, QPen pen, QBrush brush, QPainter::C
painter.setRenderHint(QPainter::Antialiasing, antialiasing);
painter.setPen(pen);
painter.setBrush(brush);
- painter.drawRect(rectangle.translated(-topLeft()));
+ painter.drawRect(rectangle.translated(-mBounds.topLeft()));
painter.end();
}
modification();
@@ -404,12 +581,12 @@ void BitmapImage::drawRect(QRectF rectangle, QPen pen, QBrush brush, QPainter::C
void BitmapImage::drawEllipse(QRectF rectangle, QPen pen, QBrush brush, QPainter::CompositionMode cm, bool antialiasing)
{
int width = pen.width();
- extend(rectangle.adjusted(-width, -width, width, width).toRect());
+ setCompositionModeBounds(rectangle.adjusted(-width, -width, width, width).toRect(), true, cm);
if (brush.style() == Qt::RadialGradientPattern)
{
QRadialGradient* gradient = (QRadialGradient*)brush.gradient();
- gradient->setCenter(gradient->center() - topLeft());
- gradient->setFocalPoint(gradient->focalPoint() - topLeft());
+ gradient->setCenter(gradient->center() - mBounds.topLeft());
+ gradient->setFocalPoint(gradient->focalPoint() - mBounds.topLeft());
}
if (!image()->isNull())
{
@@ -419,7 +596,7 @@ void BitmapImage::drawEllipse(QRectF rectangle, QPen pen, QBrush brush, QPainter
painter.setPen(pen);
painter.setBrush(brush);
painter.setCompositionMode(cm);
- painter.drawEllipse(rectangle.translated(-topLeft()));
+ painter.drawEllipse(rectangle.translated(-mBounds.topLeft()));
painter.end();
}
modification();
@@ -431,7 +608,7 @@ void BitmapImage::drawPath(QPainterPath path, QPen pen, QBrush brush,
int width = pen.width();
// qreal inc = 1.0 + width / 20.0;
- extend(path.controlPointRect().adjusted(-width, -width, width, width).toRect());
+ setCompositionModeBounds(path.controlPointRect().adjusted(-width, -width, width, width).toRect(), true, cm);
if (!image()->isNull())
{
@@ -440,7 +617,7 @@ void BitmapImage::drawPath(QPainterPath path, QPen pen, QBrush brush,
painter.setRenderHint(QPainter::Antialiasing, antialiasing);
painter.setPen(pen);
painter.setBrush(brush);
- painter.setTransform(QTransform().translate(-topLeft().x(), -topLeft().y()));
+ painter.setTransform(QTransform().translate(-mBounds.left(), -mBounds.top()));
painter.setMatrixEnabled(true);
if (path.length() > 0)
{
@@ -479,14 +656,25 @@ Status BitmapImage::writeFile(const QString& filename)
bool b = mImage->save(filename);
return (b) ? Status::OK : Status::FAIL;
}
+
+ if (bounds().isEmpty())
+ {
+ QFile f(filename);
+ if(f.exists())
+ {
+ bool b = f.remove();
+ return (b) ? Status::OK : Status::FAIL;
+ }
+ return Status::SAFE;
+ }
return Status::SAFE;
}
void BitmapImage::clear()
{
- mImage = std::make_shared(1, 1, QImage::Format_ARGB32_Premultiplied); // null image
- mBounds = QRect(0, 0, 1, 1);
- mImage->fill(QColor(0,0,0,0));
+ mImage = std::make_shared(); // null image
+ mBounds = QRect(0, 0, 0, 0);
+ mMinBound = true;
modification();
}
@@ -495,7 +683,7 @@ QRgb BitmapImage::constScanLine(int x, int y)
QRgb result = qRgba(0, 0, 0, 0);
if (mBounds.contains(QPoint(x, y)))
{
- result = *(reinterpret_cast(mImage->constScanLine(y - topLeft().y())) + x - topLeft().x());
+ result = *(reinterpret_cast(mImage->constScanLine(y - mBounds.top())) + x - mBounds.left());
}
return result;
}
@@ -506,7 +694,7 @@ void BitmapImage::scanLine(int x, int y, QRgb colour)
if (mBounds.contains(QPoint(x, y)))
{
// Make sure color is premultiplied before calling
- *(reinterpret_cast(image()->scanLine(y - topLeft().y())) + x - topLeft().x()) =
+ *(reinterpret_cast(image()->scanLine(y - mBounds.top())) + x - mBounds.left()) =
qRgba(
qRed(colour),
qGreen(colour),
@@ -518,7 +706,9 @@ void BitmapImage::scanLine(int x, int y, QRgb colour)
void BitmapImage::clear(QRect rectangle)
{
QRect clearRectangle = mBounds.intersected(rectangle);
- clearRectangle.moveTopLeft(clearRectangle.topLeft() - topLeft());
+ clearRectangle.moveTopLeft(clearRectangle.topLeft() - mBounds.topLeft());
+
+ setCompositionModeBounds(clearRectangle, true, QPainter::CompositionMode_Clear);
QPainter painter(image());
painter.setCompositionMode(QPainter::CompositionMode_Clear);
@@ -528,34 +718,46 @@ void BitmapImage::clear(QRect rectangle)
modification();
}
-bool BitmapImage::compareColor(QRgb color1, QRgb color2, int tolerance)
-{
- if (color1 == color2) return true;
-
- int red1 = qRed(color1);
- int green1 = qGreen(color1);
- int blue1 = qBlue(color1);
- int alpha1 = qAlpha(color1);
-
- int red2 = qRed(color2);
- int green2 = qGreen(color2);
- int blue2 = qBlue(color2);
- int alpha2 = qAlpha(color2);
-
- int diffRed = abs(red2 - red1);
- int diffGreen = abs(green2 - green1);
- int diffBlue = abs(blue2 - blue1);
- int diffAlpha = abs(alpha2 - alpha1);
-
- if (diffRed > tolerance ||
- diffGreen > tolerance ||
- diffBlue > tolerance ||
- diffAlpha > tolerance)
+/** Compare colors for the purposes of flood filling
+ *
+ * Calculates the Eulcidian difference of the RGB channels
+ * of the image and compares it to the tolerance
+ *
+ * @param[in] newColor The first color to compare
+ * @param[in] oldColor The second color to compare
+ * @param[in] tolerance The threshold limit between a matching and non-matching color
+ * @param[in,out] cache Contains a mapping of previous results of compareColor with rule that
+ * cache[someColor] = compareColor(someColor, oldColor, tolerance)
+ *
+ * @return Returns true if the colors have a similarity below the tolerance level
+ * (i.e. if Eulcidian distance squared is <= tolerance)
+ */
+bool BitmapImage::compareColor(QRgb newColor, QRgb oldColor, int tolerance, QHash *cache)
+{
+ // Handle trivial case
+ if (newColor == oldColor) return true;
+
+ if(cache && cache->contains(newColor)) return cache->value(newColor);
+
+ // Get Eulcidian distance between colors
+ // Not an accurate representation of human perception,
+ // but it's the best any image editing program ever does
+ int diffRed = qPow(qRed(oldColor) - qRed(newColor), 2);
+ int diffGreen = qPow(qGreen(oldColor) - qGreen(newColor), 2);
+ int diffBlue = qPow(qBlue(oldColor) - qBlue(newColor), 2);
+ // This may not be the best way to handle alpha since the other channels become less relevant as
+ // the alpha is reduces (ex. QColor(0,0,0,0) is the same as QColor(255,255,255,0))
+ int diffAlpha = qPow(qAlpha(oldColor) - qAlpha(newColor), 2);
+
+ bool isSimilar = (diffRed + diffGreen + diffBlue + diffAlpha) <= tolerance;
+
+ if(cache)
{
- return false;
+ Q_ASSERT(cache->contains(isSimilar) ? isSimilar == (*cache)[newColor] : true);
+ (*cache)[newColor] = isSimilar;
}
- return true;
+ return isSimilar;
}
// Flood fill
@@ -563,14 +765,19 @@ bool BitmapImage::compareColor(QRgb color1, QRgb color2, int tolerance)
void BitmapImage::floodFill(BitmapImage* targetImage,
QRect cameraRect,
QPoint point,
- QRgb oldColor,
QRgb newColor,
int tolerance)
{
- if (oldColor == newColor)
+ // If the point we are supposed to fill is outside the image and camera bounds, do nothing
+ if(!cameraRect.united(targetImage->bounds()).contains(point))
+ {
return;
-
- oldColor = targetImage->pixel(point);
+ }
+
+ // Square tolerance for use with compareColor
+ tolerance = qPow(tolerance, 2);
+
+ QRgb oldColor = targetImage->pixel(point);
oldColor = qRgba(qRed(oldColor), qGreen(oldColor), qBlue(oldColor), qAlpha(oldColor));
// Preparations
@@ -579,6 +786,7 @@ void BitmapImage::floodFill(BitmapImage* targetImage,
BitmapImage* replaceImage = nullptr;
QPoint tempPoint;
QRgb newPlacedColor = 0;
+ QScopedPointer< QHash > cache(new QHash());
int xTemp = 0;
bool spanLeft = false;
@@ -601,41 +809,41 @@ void BitmapImage::floodFill(BitmapImage* targetImage,
xTemp = point.x();
newPlacedColor = replaceImage->constScanLine(xTemp, point.y());
- while (xTemp >= targetImage->topLeft().x() &&
- compareColor(targetImage->constScanLine(xTemp, point.y()), oldColor, tolerance)) xTemp--;
+ while (xTemp >= targetImage->mBounds.left() &&
+ compareColor(targetImage->constScanLine(xTemp, point.y()), oldColor, tolerance, cache.data())) xTemp--;
xTemp++;
spanLeft = spanRight = false;
- while (xTemp <= targetImage->right() &&
- compareColor(targetImage->constScanLine(xTemp, point.y()), oldColor, tolerance) &&
+ while (xTemp <= targetImage->mBounds.right() &&
+ compareColor(targetImage->constScanLine(xTemp, point.y()), oldColor, tolerance, cache.data()) &&
newPlacedColor != newColor)
{
// Set pixel color
replaceImage->scanLine(xTemp, point.y(), newColor);
- if (!spanLeft && (point.y() > targetImage->top()) &&
- compareColor(targetImage->constScanLine(xTemp, point.y() - 1), oldColor, tolerance)) {
+ if (!spanLeft && (point.y() > targetImage->mBounds.top()) &&
+ compareColor(targetImage->constScanLine(xTemp, point.y() - 1), oldColor, tolerance, cache.data())) {
queue.append(QPoint(xTemp, point.y() - 1));
spanLeft = true;
}
- else if (spanLeft && (point.y() > targetImage->top()) &&
- !compareColor(targetImage->constScanLine(xTemp, point.y() - 1), oldColor, tolerance)) {
+ else if (spanLeft && (point.y() > targetImage->mBounds.top()) &&
+ !compareColor(targetImage->constScanLine(xTemp, point.y() - 1), oldColor, tolerance, cache.data())) {
spanLeft = false;
}
- if (!spanRight && point.y() < targetImage->bottom() &&
- compareColor(targetImage->constScanLine(xTemp, point.y() + 1), oldColor, tolerance)) {
+ if (!spanRight && point.y() < targetImage->mBounds.bottom() &&
+ compareColor(targetImage->constScanLine(xTemp, point.y() + 1), oldColor, tolerance, cache.data())) {
queue.append(QPoint(xTemp, point.y() + 1));
spanRight = true;
}
- else if (spanRight && point.y() < targetImage->bottom() &&
- !compareColor(targetImage->constScanLine(xTemp, point.y() + 1), oldColor, tolerance)) {
+ else if (spanRight && point.y() < targetImage->mBounds.bottom() &&
+ !compareColor(targetImage->constScanLine(xTemp, point.y() + 1), oldColor, tolerance, cache.data())) {
spanRight = false;
}
- Q_ASSERT(queue.count() < (targetImage->width() * targetImage->height()));
+ Q_ASSERT(queue.count() < (targetImage->mBounds.width() * targetImage->mBounds.height()));
xTemp++;
}
}
diff --git a/core_lib/src/graphics/bitmap/bitmapimage.h b/core_lib/src/graphics/bitmap/bitmapimage.h
index ddeb7bbd0..5136f1e96 100644
--- a/core_lib/src/graphics/bitmap/bitmapimage.h
+++ b/core_lib/src/graphics/bitmap/bitmapimage.h
@@ -27,9 +27,9 @@ class BitmapImage : public KeyFrame
public:
BitmapImage();
BitmapImage(const BitmapImage&);
- BitmapImage(const QRect& boundaries, const QColor& colour);
- BitmapImage(const QRect& boundaries, const QImage& image);
- BitmapImage(const QString& path, const QPoint& topLeft);
+ BitmapImage(const QRect &rectangle, const QColor& colour);
+ BitmapImage(const QPoint& topLeft, const QImage& image);
+ BitmapImage(const QPoint& topLeft, const QString& path);
~BitmapImage();
BitmapImage& operator=(const BitmapImage& a);
@@ -37,6 +37,7 @@ class BitmapImage : public KeyFrame
BitmapImage* clone() override;
void loadFile() override;
void unloadFile() override;
+ bool isLoaded() override;
void paintImage(QPainter& painter);
void paintImage(QPainter &painter, QImage &image, QRect sourceRect, QRect destRect);
@@ -46,8 +47,7 @@ class BitmapImage : public KeyFrame
BitmapImage copy();
BitmapImage copy(QRect rectangle);
- void paste(BitmapImage*);
- void paste(BitmapImage*, QPainter::CompositionMode cm);
+ void paste(BitmapImage*, QPainter::CompositionMode cm = QPainter::CompositionMode_SourceOver);
void add(BitmapImage*);
void compareAlpha(BitmapImage*);
@@ -61,46 +61,70 @@ class BitmapImage : public KeyFrame
bool contains(QPoint P) { return mBounds.contains(P); }
bool contains(QPointF P) { return contains(P.toPoint()); }
- void extend(QPoint P);
- void extend(QRect rectangle);
+ void autoCrop();
QRgb pixel(int x, int y);
- QRgb pixel(QPoint P);
+ QRgb pixel(QPoint p);
void setPixel(int x, int y, QRgb colour);
- void setPixel(QPoint P, QRgb colour);
+ void setPixel(QPoint p, QRgb colour);
QRgb constScanLine(int x, int y);
void scanLine(int x, int y, QRgb colour);
void clear();
void clear(QRect rectangle);
void clear(QRectF rectangle) { clear(rectangle.toRect()); }
- static bool compareColor(QRgb color1, QRgb color2, int tolerance);
- static void floodFill(BitmapImage* targetImage, QRect cameraRect, QPoint point, QRgb oldColor, QRgb newColor, int tolerance);
+ static bool compareColor(QRgb newColor, QRgb oldColor, int tolerance, QHash *cache);
+ static void floodFill(BitmapImage* targetImage, QRect cameraRect, QPoint point, QRgb newColor, int tolerance);
void drawLine(QPointF P1, QPointF P2, QPen pen, QPainter::CompositionMode cm, bool antialiasing);
void drawRect(QRectF rectangle, QPen pen, QBrush brush, QPainter::CompositionMode cm, bool antialiasing);
void drawEllipse(QRectF rectangle, QPen pen, QBrush brush, QPainter::CompositionMode cm, bool antialiasing);
void drawPath(QPainterPath path, QPen pen, QBrush brush, QPainter::CompositionMode cm, bool antialiasing);
- QPoint topLeft() { return mBounds.topLeft(); }
- QPoint topRight() { return mBounds.topRight(); }
- QPoint bottomLeft() { return mBounds.bottomLeft(); }
- QPoint bottomRight() { return mBounds.bottomRight(); }
- int left() { return mBounds.left(); }
- int right() { return mBounds.right(); }
- int top() { return mBounds.top(); }
- int bottom() { return mBounds.bottom(); }
- int width() { return mBounds.width(); }
- int height() { return mBounds.height(); }
-
- QRect& bounds() { return mBounds; }
+ QPoint topLeft() { autoCrop(); return mBounds.topLeft(); }
+ QPoint topRight() { autoCrop(); return mBounds.topRight(); }
+ QPoint bottomLeft() { autoCrop(); return mBounds.bottomLeft(); }
+ QPoint bottomRight() { autoCrop(); return mBounds.bottomRight(); }
+ int left() { autoCrop(); return mBounds.left(); }
+ int right() { autoCrop(); return mBounds.right(); }
+ int top() { autoCrop(); return mBounds.top(); }
+ int bottom() { autoCrop(); return mBounds.bottom(); }
+ int width() { autoCrop(); return mBounds.width(); }
+ int height() { autoCrop(); return mBounds.height(); }
+ QSize size() { autoCrop(); return mBounds.size(); }
+
+ QRect& bounds() { autoCrop(); return mBounds; }
+
+ /** Determines if the BitmapImage is minimally bounded.
+ *
+ * A BitmapImage is minimally bounded if all edges contain
+ * at least 1 non-transparent pixel (alpha > 0). In other words,
+ * the size of the image cannot be decreased without
+ * cropping visible data.
+ *
+ * @return True only if bounds() is the minimal bounding box
+ * for the contained image.
+ */
+ bool isMinimallyBounded() const { return mMinBound; }
+ void enableAutoCrop(bool b) { mEnableAutoCrop = b; }
Status writeFile(const QString& filename);
+protected:
+ void updateBounds(QRect rectangle);
+ void extend(const QPoint& p);
+ void extend(QRect rectangle);
+
+ void setCompositionModeBounds(BitmapImage *source, QPainter::CompositionMode cm);
+ void setCompositionModeBounds(QRect sourceBounds, bool isSourceMinBounds, QPainter::CompositionMode cm);
+
private:
std::shared_ptr< QImage > mImage;
QRect mBounds;
- bool mExtendable = true;
+
+ /** @see isMinimallyBounded() */
+ bool mMinBound = true;
+ bool mEnableAutoCrop = false;
};
#endif
diff --git a/core_lib/src/graphics/vector/bezierarea.cpp b/core_lib/src/graphics/vector/bezierarea.cpp
index fdea410cf..0387ff81f 100644
--- a/core_lib/src/graphics/vector/bezierarea.cpp
+++ b/core_lib/src/graphics/vector/bezierarea.cpp
@@ -72,11 +72,12 @@ Status BezierArea::createDomElement( QXmlStreamWriter& xmlStream )
if ( xmlStream.hasError() && errorLocation >= 0 )
{
- QStringList debugInfo = QStringList() << "BezierArea::createDomElement"
- << QString( "colourNumber = %1" ).arg( mColourNumber )
- << QString( "- mVertex[%1] has failed to write" ).arg( errorLocation )
- << QString( " curve = %1" ).arg( mVertex.at( errorLocation ).curveNumber )
- << QString( " vertex = %1 " ).arg( mVertex.at( errorLocation ).vertexNumber );
+ DebugDetails debugInfo;
+ debugInfo << "BezierArea::createDomElement";
+ debugInfo << QString("colourNumber = %1").arg(mColourNumber);
+ debugInfo << QString("- mVertex[%1] has failed to write").arg(errorLocation);
+ debugInfo << QString(" curve = %1").arg(mVertex.at(errorLocation).curveNumber);
+ debugInfo << QString(" vertex = %1 ").arg(mVertex.at(errorLocation).vertexNumber);
return Status( Status::FAIL, debugInfo );
}
diff --git a/core_lib/src/graphics/vector/beziercurve.cpp b/core_lib/src/graphics/vector/beziercurve.cpp
index 773d2b296..c55ff4667 100644
--- a/core_lib/src/graphics/vector/beziercurve.cpp
+++ b/core_lib/src/graphics/vector/beziercurve.cpp
@@ -110,26 +110,27 @@ Status BezierCurve::createDomElement( QXmlStreamWriter& xmlStream )
if ( xmlStream.hasError() && errorLocation >= 0 )
{
- QStringList debugInfo = QStringList() << "BezierCurve::createDomElement"
- << QString( "width = %1" ).arg( width )
- << QString( "variableWidth = %1" ).arg( "variableWidth" )
- << QString( "feather = %1" ).arg( feather )
- << QString( "invisible = %1" ).arg( invisible )
- << QString( "filled = %1" ).arg( mFilled )
- << QString( "colourNumber = %1" ).arg( colourNumber )
- << QString( "originX = %1" ).arg( origin.x() )
- << QString( "originY = %1" ).arg( origin.y() )
- << QString( "originPressure = %1" ).arg( pressure.at( 0 ) )
- << QString( "- segmentTag[%1] has failed to write" ).arg( errorLocation )
- << QString( " c1x = %1" ).arg( c1.at( errorLocation ).x() )
- << QString( " c1y = %1" ).arg( c1.at( errorLocation ).y() )
- << QString( " c2x = %1" ).arg( c2.at( errorLocation ).x() )
- << QString( " c2y = %1" ).arg( c2.at( errorLocation ).y() )
- << QString( " vx = %1" ).arg( vertex.at( errorLocation ).x() )
- << QString( " vy = %1" ).arg( vertex.at( errorLocation ).y() )
- << QString( " pressure = %1" ).arg( pressure.at( errorLocation + 1 ) );
-
- return Status( Status::FAIL, debugInfo );
+ DebugDetails debugInfo;
+ debugInfo << "BezierCurve::createDomElement";
+ debugInfo << QString("width = %1").arg(width);
+ debugInfo << QString("variableWidth = %1").arg("variableWidth");
+ debugInfo << QString("feather = %1").arg(feather);
+ debugInfo << QString("invisible = %1").arg(invisible);
+ debugInfo << QString("filled = %1").arg(mFilled);
+ debugInfo << QString("colourNumber = %1").arg(colourNumber);
+ debugInfo << QString("originX = %1").arg(origin.x());
+ debugInfo << QString("originY = %1").arg(origin.y());
+ debugInfo << QString("originPressure = %1").arg(pressure.at(0));
+ debugInfo << QString("- segmentTag[%1] has failed to write").arg(errorLocation);
+ debugInfo << QString(" c1x = %1").arg(c1.at(errorLocation).x());
+ debugInfo << QString(" c1y = %1").arg(c1.at(errorLocation).y());
+ debugInfo << QString(" c2x = %1").arg(c2.at(errorLocation).x());
+ debugInfo << QString(" c2y = %1").arg(c2.at(errorLocation).y());
+ debugInfo << QString(" vx = %1").arg(vertex.at(errorLocation).x());
+ debugInfo << QString(" vy = %1").arg(vertex.at(errorLocation).y());
+ debugInfo << QString(" pressure = %1").arg(pressure.at(errorLocation + 1));
+
+ return Status(Status::FAIL, debugInfo);
}
return Status::OK;
diff --git a/core_lib/src/graphics/vector/colourref.cpp b/core_lib/src/graphics/vector/colourref.cpp
index f01fd26a3..a13bb7a27 100644
--- a/core_lib/src/graphics/vector/colourref.cpp
+++ b/core_lib/src/graphics/vector/colourref.cpp
@@ -16,6 +16,8 @@ GNU General Public License for more details.
*/
#include "colourref.h"
+#include
+
ColourRef::ColourRef()
{
colour = Qt::green;
@@ -51,3 +53,12 @@ bool ColourRef::operator!=(ColourRef colourRef1)
return false;
}
}
+
+QDebug& operator<<(QDebug debug, const ColourRef& colourRef)
+{
+ debug.nospace() << "ColourRef(" << colourRef.colour << " " << colourRef.name <<")";
+ return debug.maybeSpace();
+}
+
+
+
diff --git a/core_lib/src/graphics/vector/colourref.h b/core_lib/src/graphics/vector/colourref.h
index 53b564a96..e2dc8c4d8 100644
--- a/core_lib/src/graphics/vector/colourref.h
+++ b/core_lib/src/graphics/vector/colourref.h
@@ -33,4 +33,6 @@ class ColourRef
QString name;
};
+QDebug& operator<<(QDebug debug, const ColourRef &colourRef);
+
#endif
diff --git a/core_lib/src/graphics/vector/vectorimage.cpp b/core_lib/src/graphics/vector/vectorimage.cpp
index faca4c54e..a66648d5c 100644
--- a/core_lib/src/graphics/vector/vectorimage.cpp
+++ b/core_lib/src/graphics/vector/vectorimage.cpp
@@ -88,7 +88,7 @@ bool VectorImage::read(QString filePath)
*/
Status VectorImage::write(QString filePath, QString format)
{
- QStringList debugInfo;
+ DebugDetails debugInfo;
debugInfo << "VectorImage::write";
debugInfo << QString("filePath = ").append(filePath);
debugInfo << QString("format = ").append(format);
@@ -98,13 +98,14 @@ Status VectorImage::write(QString filePath, QString format)
if (!result)
{
qDebug() << "VectorImage - Cannot write file" << filePath << file.error();
- return Status(Status::FAIL, debugInfo << QString("file.error() = ").append(file.errorString()));
+ debugInfo << ("file.error() = " + file.errorString());
+ return Status(Status::FAIL, debugInfo);
}
if (format != "VEC")
{
- qDebug() << "--- Not the VEC format!";
- return Status(Status::FAIL, debugInfo << "Unrecognized format");
+ debugInfo << "Unrecognized format";
+ return Status(Status::FAIL, debugInfo);
}
QXmlStreamWriter xmlStream(&file);
@@ -114,11 +115,13 @@ Status VectorImage::write(QString filePath, QString format)
xmlStream.writeStartElement("image");
xmlStream.writeAttribute("type", "vector");
+
Status st = createDomElement(xmlStream);
if (!st.ok())
{
- const QString xmlDetails = st.detailsList().join(" ");
- return Status(Status::FAIL, debugInfo << "- xml creation failed" << xmlDetails);
+ debugInfo.collect(st.details());
+ debugInfo << "- xml creation failed";
+ return Status(Status::FAIL, debugInfo);
}
xmlStream.writeEndElement(); // Close image element
xmlStream.writeEndDocument();
@@ -134,14 +137,17 @@ Status VectorImage::write(QString filePath, QString format)
*/
Status VectorImage::createDomElement(QXmlStreamWriter& xmlStream)
{
- QStringList debugInfo = QStringList() << "VectorImage::createDomElement";
+ DebugDetails debugInfo;
+ debugInfo << "VectorImage::createDomElement";
+
for (int i = 0; i < mCurves.size(); i++)
{
Status st = mCurves[i].createDomElement(xmlStream);
if (!st.ok())
{
- const QString curveDetails = st.detailsList().join(" ");
- return Status(Status::FAIL, debugInfo << QString("- m_curves[%1] failed to write").arg(i) << curveDetails);
+ debugInfo.collect(st.details());
+ debugInfo << QString("- m_curves[%1] failed to write").arg(i);
+ return Status(Status::FAIL, debugInfo);
}
}
for (int i = 0; i < mArea.size(); i++)
@@ -149,8 +155,9 @@ Status VectorImage::createDomElement(QXmlStreamWriter& xmlStream)
Status st = mArea[i].createDomElement(xmlStream);
if (!st.ok())
{
- const QString areaDetails = st.detailsList().join(" ");
- return Status(Status::FAIL, debugInfo << QString("- area[%1] failed to write").arg(i) << areaDetails);
+ debugInfo.collect(st.details());
+ debugInfo << QString("- area[%1] failed to write").arg(i);
+ return Status(Status::FAIL, debugInfo);
}
}
return Status::OK;
@@ -1400,6 +1407,9 @@ QList VectorImage::getVerticesCloseTo(QPointF P1, qreal maxDistance)
{
QList result;
+ // Square maxDistance rather than taking the square root for each distance
+ maxDistance *= maxDistance;
+
for (int curve = 0; curve < mCurves.size(); curve++)
{
for (int vertex = -1; vertex < mCurves.at(curve).getVertexSize(); vertex++)
diff --git a/core_lib/src/interface/basedockwidget.cpp b/core_lib/src/interface/basedockwidget.cpp
index 8fe6aeafe..e129d5dfc 100644
--- a/core_lib/src/interface/basedockwidget.cpp
+++ b/core_lib/src/interface/basedockwidget.cpp
@@ -19,7 +19,7 @@ GNU General Public License for more details.
#include "basedockwidget.h"
BaseDockWidget::BaseDockWidget(QWidget* pParent)
-: QDockWidget(pParent, Qt::Tool )
+: QDockWidget(pParent, Qt::Tool)
{
}
diff --git a/core_lib/src/interface/basedockwidget.h b/core_lib/src/interface/basedockwidget.h
index 6aa95de0c..fffbac7c1 100644
--- a/core_lib/src/interface/basedockwidget.h
+++ b/core_lib/src/interface/basedockwidget.h
@@ -27,14 +27,14 @@ class BaseDockWidget : public QDockWidget
{
Q_OBJECT
protected:
- explicit BaseDockWidget( QWidget* pParent );
+ explicit BaseDockWidget(QWidget* pParent);
virtual ~BaseDockWidget();
public:
virtual void initUI() = 0;
virtual void updateUI() = 0;
- Editor* editor() { return mEditor; }
+ Editor* editor() const { return mEditor; }
void setEditor( Editor* e ) { mEditor = e; }
private:
diff --git a/core_lib/src/interface/editor.cpp b/core_lib/src/interface/editor.cpp
index 4cac70766..6c63887a3 100644
--- a/core_lib/src/interface/editor.cpp
+++ b/core_lib/src/interface/editor.cpp
@@ -37,7 +37,6 @@ GNU General Public License for more details.
#include "layervector.h"
#include "layercamera.h"
#include "backupelement.h"
-#include "activeframepool.h"
#include "colormanager.h"
#include "toolmanager.h"
@@ -65,15 +64,12 @@ Editor::Editor(QObject* parent) : QObject(parent)
clipboardBitmapOk = false;
clipboardVectorOk = false;
clipboardSoundClipOk = false;
-
- mActiveFramePool.reset(new ActiveFramePool(200));
}
Editor::~Editor()
{
// a lot more probably needs to be cleaned here...
clearUndoStack();
- mActiveFramePool->clear();
}
bool Editor::init()
@@ -191,7 +187,8 @@ void Editor::backup(QString undoText)
if (layers()->currentLayer()->type() == Layer::SOUND)
{
frame = layers()->currentLayer()->getKeyFrameWhichCovers(mLastModifiedFrame);
- if (frame != nullptr) {
+ if (frame != nullptr)
+ {
backup(mLastModifiedLayer, frame->pos(), undoText);
}
}
@@ -229,6 +226,7 @@ void Editor::backup(int backupLayer, int backupFrame, QString undoText)
delete mBackupList.takeFirst();
mBackupIndex--;
}
+
Layer* layer = mObject->getLayer(backupLayer);
if (layer != NULL)
{
@@ -245,7 +243,7 @@ void Editor::backup(int backupLayer, int backupFrame, QString undoText)
element->layer = backupLayer;
element->frame = backupFrame;
element->undoText = undoText;
- element->somethingSelected = getScribbleArea()->somethingSelected;
+ element->somethingSelected = getScribbleArea()->isSomethingSelected();
element->mySelection = getScribbleArea()->mySelection;
element->myTransformedSelection = getScribbleArea()->myTransformedSelection;
element->myTempTransformedSelection = getScribbleArea()->myTempTransformedSelection;
@@ -262,7 +260,7 @@ void Editor::backup(int backupLayer, int backupFrame, QString undoText)
element->layer = backupLayer;
element->frame = backupFrame;
element->undoText = undoText;
- element->somethingSelected = getScribbleArea()->somethingSelected;
+ element->somethingSelected = getScribbleArea()->isSomethingSelected();
element->mySelection = getScribbleArea()->mySelection;
element->myTransformedSelection = getScribbleArea()->myTransformedSelection;
element->myTempTransformedSelection = getScribbleArea()->myTempTransformedSelection;
@@ -355,10 +353,7 @@ void Editor::restoreKey()
void BackupBitmapElement::restore(Editor* editor)
{
Layer* layer = editor->object()->getLayer(this->layer);
- editor->getScribbleArea()->somethingSelected = this->somethingSelected;
- editor->getScribbleArea()->mySelection = this->mySelection;
- editor->getScribbleArea()->myTransformedSelection = this->myTransformedSelection;
- editor->getScribbleArea()->myTempTransformedSelection = this->myTempTransformedSelection;
+ editor->getScribbleArea()->setSelection(mySelection);
editor->updateFrame(this->frame);
editor->scrubTo(this->frame);
@@ -383,10 +378,7 @@ void BackupBitmapElement::restore(Editor* editor)
void BackupVectorElement::restore(Editor* editor)
{
Layer* layer = editor->object()->getLayer(this->layer);
- editor->getScribbleArea()->somethingSelected = this->somethingSelected;
- editor->getScribbleArea()->mySelection = this->mySelection;
- editor->getScribbleArea()->myTransformedSelection = this->myTransformedSelection;
- editor->getScribbleArea()->myTempTransformedSelection = this->myTempTransformedSelection;
+ editor->getScribbleArea()->setSelection(mySelection);
editor->updateFrameAndVector(this->frame);
editor->scrubTo(this->frame);
@@ -490,21 +482,6 @@ void Editor::updateAutoSaveCounter()
}
}
-void Editor::updateActiveFrames(int frame)
-{
- int beginFrame = std::max(frame - 3, 1);
- int endFrame = frame + 4;
- for (int i = 0; i < mObject->getLayerCount(); ++i)
- {
- Layer* layer = mObject->getLayer(i);
- for (int k = beginFrame; k < endFrame; ++k)
- {
- KeyFrame* key = layer->getKeyFrameAt(k);
- mActiveFramePool->put(key);
- }
- }
-}
-
void Editor::cut()
{
copy();
@@ -517,24 +494,28 @@ void Editor::copy()
Layer* layer = mObject->getLayer(layers()->currentLayerIndex());
if (layer != NULL)
{
- if (layer->type() == Layer::BITMAP)
+ return;
+ }
+
+ if (layer->type() == Layer::BITMAP)
+ {
+ LayerBitmap* layerBitmap = (LayerBitmap*)layer;
+ if (mScribbleArea->isSomethingSelected())
{
- if (mScribbleArea->somethingSelected)
- {
- g_clipboardBitmapImage = ((LayerBitmap*)layer)->getLastBitmapImageAtFrame(currentFrame(), 0)->copy(mScribbleArea->getSelection().toRect()); // copy part of the image
- }
- else
- {
- g_clipboardBitmapImage = ((LayerBitmap*)layer)->getLastBitmapImageAtFrame(currentFrame(), 0)->copy(); // copy the whole image
- }
- clipboardBitmapOk = true;
- if (g_clipboardBitmapImage.image() != NULL) QApplication::clipboard()->setImage(*g_clipboardBitmapImage.image());
+ g_clipboardBitmapImage = layerBitmap->getLastBitmapImageAtFrame(currentFrame(), 0)->copy(mScribbleArea->getSelection().toRect()); // copy part of the image
}
- if (layer->type() == Layer::VECTOR)
+ else
{
- clipboardVectorOk = true;
- g_clipboardVectorImage = *(((LayerVector*)layer)->getLastVectorImageAtFrame(currentFrame(), 0)); // copy the image
+ g_clipboardBitmapImage = layerBitmap->getLastBitmapImageAtFrame(currentFrame(), 0)->copy(); // copy the whole image
}
+ clipboardBitmapOk = true;
+ if (g_clipboardBitmapImage.image() != NULL)
+ QApplication::clipboard()->setImage(*g_clipboardBitmapImage.image());
+ }
+ if (layer->type() == Layer::VECTOR)
+ {
+ clipboardVectorOk = true;
+ g_clipboardVectorImage = *(((LayerVector*)layer)->getLastVectorImageAtFrame(currentFrame(), 0)); // copy the image
}
}
@@ -549,7 +530,7 @@ void Editor::paste()
BitmapImage tobePasted = g_clipboardBitmapImage.copy();
qDebug() << "to be pasted --->" << tobePasted.image()->size();
- if (mScribbleArea->somethingSelected)
+ if (mScribbleArea->isSomethingSelected())
{
QRectF selection = mScribbleArea->getSelection();
if (g_clipboardBitmapImage.width() <= selection.width() && g_clipboardBitmapImage.height() <= selection.height())
@@ -570,7 +551,7 @@ void Editor::paste()
mScribbleArea->deselectAll();
VectorImage* vectorImage = ((LayerVector*)layer)->getLastVectorImageAtFrame(currentFrame(), 0);
vectorImage->paste(g_clipboardVectorImage); // paste the clipboard
- mScribbleArea->setSelection(vectorImage->getSelectionRect(), true);
+ mScribbleArea->setSelection(vectorImage->getSelectionRect());
}
}
mScribbleArea->updateCurrentFrame();
@@ -636,19 +617,23 @@ Status Editor::setObject(Object* newObject)
return Status::SAFE;
}
+ clearUndoStack();
mObject.reset(newObject);
-
for (BaseManager* m : mAllManagers)
{
m->load(mObject.get());
}
- mActiveFramePool->clear();
g_clipboardVectorImage.setObject(newObject);
updateObject();
+ if (mViewManager)
+ {
+ connect(newObject, &Object::layerViewChanged, mViewManager, &ViewManager::viewChanged);
+ }
+
emit objectLoaded();
return Status::OK;
@@ -659,8 +644,6 @@ void Editor::updateObject()
scrubTo(mObject->data()->getCurrentFrame());
setCurrentLayerIndex(mObject->data()->getCurrentLayer());
- clearUndoStack();
-
mAutosaveCounter = 0;
mAutosaveNerverAskAgain = false;
@@ -755,7 +738,7 @@ QString Editor::workingDir() const
return mObject->workingDir();
}
-bool Editor::importBitmapImage(QString filePath)
+bool Editor::importBitmapImage(QString filePath, int space)
{
QImageReader reader(filePath);
@@ -776,13 +759,14 @@ bool Editor::importBitmapImage(QString filePath)
}
BitmapImage* bitmapImage = layer->getBitmapImageAtFrame(currentFrame());
- QRect boundaries = img.rect();
- boundaries.moveTopLeft(mScribbleArea->getCentralPoint().toPoint() - QPoint(boundaries.width() / 2, boundaries.height() / 2));
-
- BitmapImage importedBitmapImage{ boundaries, img };
+ BitmapImage importedBitmapImage(mScribbleArea->getCentralPoint().toPoint() - QPoint(img.width() / 2, img.height() / 2), img);
bitmapImage->paste(&importedBitmapImage);
- scrubTo(currentFrame() + 1);
+ if (space > 1) {
+ scrubTo(currentFrame() + space);
+ } else {
+ scrubTo(currentFrame() + 1);
+ }
backup(tr("Import Image"));
}
@@ -836,6 +820,16 @@ bool Editor::importImage(QString filePath)
}
}
+bool Editor::importGIF(QString filePath, int numOfImages)
+{
+ Layer* layer = layers()->currentLayer();
+ if (layer->type() == Layer::BITMAP) {
+ return importBitmapImage(filePath, numOfImages);
+ } else {
+ return false;
+ }
+}
+
void Editor::updateFrame(int frameNumber)
{
mScribbleArea->updateFrame(frameNumber);
@@ -864,10 +858,7 @@ void Editor::setCurrentLayerIndex(int i)
void Editor::scrubTo(int frame)
{
- if (frame < 1)
- {
- frame = 1;
- }
+ if (frame < 1) { frame = 1; }
int oldFrame = mFrame;
mFrame = frame;
@@ -881,8 +872,7 @@ void Editor::scrubTo(int frame)
{
emit updateTimeLine(); // needs to update the timeline to update onion skin positions
}
-
- updateActiveFrames(frame);
+ mObject->updateActiveFrames(frame);
}
void Editor::scrubForward()
diff --git a/core_lib/src/interface/editor.h b/core_lib/src/interface/editor.h
index 09a4cc638..7269e5794 100644
--- a/core_lib/src/interface/editor.h
+++ b/core_lib/src/interface/editor.h
@@ -18,7 +18,6 @@ GNU General Public License for more details.
#ifndef EDITOR_H
#define EDITOR_H
-#include
#include
#include
#include
@@ -123,6 +122,7 @@ class Editor : public QObject
void cut();
bool importImage(QString filePath);
+ bool importGIF(QString filePath, int numOfImages = 0);
void updateFrame(int frameNumber);
void restoreKey();
@@ -164,7 +164,7 @@ class Editor : public QObject
void dropEvent(QDropEvent*);
private:
- bool importBitmapImage(QString);
+ bool importBitmapImage(QString, int space = 0);
bool importVectorImage(QString);
// the object to be edited by the editor
@@ -205,10 +205,6 @@ class Editor : public QObject
bool clipboardBitmapOk = true;
bool clipboardVectorOk = true;
bool clipboardSoundClipOk = true;
-
- // Memory management
- void updateActiveFrames(int frame);
- std::unique_ptr mActiveFramePool;
};
#endif
diff --git a/core_lib/src/interface/scribblearea.cpp b/core_lib/src/interface/scribblearea.cpp
index 5ec9e8a1f..0f4d42811 100644
--- a/core_lib/src/interface/scribblearea.cpp
+++ b/core_lib/src/interface/scribblearea.cpp
@@ -30,10 +30,12 @@ GNU General Public License for more details.
#include "bitmapimage.h"
#include "vectorimage.h"
+#include "colormanager.h"
#include "toolmanager.h"
#include "strokemanager.h"
#include "layermanager.h"
#include "playbackmanager.h"
+#include "viewmanager.h"
ScribbleArea::ScribbleArea(QWidget* parent) : QWidget(parent),
@@ -59,18 +61,16 @@ bool ScribbleArea::init()
connect(mPrefs, &PreferenceManager::optionChanged, this, &ScribbleArea::settingUpdated);
- int curveSmoothingLevel = mPrefs->getInt(SETTING::CURVE_SMOOTHING);
+ const int curveSmoothingLevel = mPrefs->getInt(SETTING::CURVE_SMOOTHING);
mCurveSmoothingLevel = curveSmoothingLevel / 20.0; // default value is 1.0
mQuickSizing = mPrefs->isOn(SETTING::QUICK_SIZING);
mMakeInvisible = false;
- somethingSelected = false;
+ mSomethingSelected = false;
mIsSimplified = mPrefs->isOn(SETTING::OUTLINES);
mMultiLayerOnionSkin = mPrefs->isOn(SETTING::MULTILAYER_ONION);
- mShowAllLayers = 1;
-
mBufferImg = new BitmapImage;
QRect newSelection(QPoint(0, 0), QSize(0, 0));
@@ -80,7 +80,7 @@ bool ScribbleArea::init()
mOffset.setX(0);
mOffset.setY(0);
selectionTransformation.reset();
- selectionTolerance = 8.0;
+
updateCanvasCursor();
setMouseTracking(true); // reacts to mouse move events, even if the button is not pressed
@@ -254,7 +254,7 @@ void ScribbleArea::keyPressEvent(QKeyEvent *event)
switch (event->key())
{
case Qt::Key_Right:
- if (somethingSelected)
+ if (mSomethingSelected)
{
myTempTransformedSelection.translate(1, 0);
myTransformedSelection = myTempTransformedSelection;
@@ -268,7 +268,7 @@ void ScribbleArea::keyPressEvent(QKeyEvent *event)
}
break;
case Qt::Key_Left:
- if (somethingSelected)
+ if (mSomethingSelected)
{
myTempTransformedSelection.translate(-1, 0);
myTransformedSelection = myTempTransformedSelection;
@@ -282,7 +282,7 @@ void ScribbleArea::keyPressEvent(QKeyEvent *event)
}
break;
case Qt::Key_Up:
- if (somethingSelected)
+ if (mSomethingSelected)
{
myTempTransformedSelection.translate(0, -1);
myTransformedSelection = myTempTransformedSelection;
@@ -296,7 +296,7 @@ void ScribbleArea::keyPressEvent(QKeyEvent *event)
}
break;
case Qt::Key_Down:
- if (somethingSelected)
+ if (mSomethingSelected)
{
myTempTransformedSelection.translate(0, 1);
myTransformedSelection = myTempTransformedSelection;
@@ -310,7 +310,7 @@ void ScribbleArea::keyPressEvent(QKeyEvent *event)
}
break;
case Qt::Key_Return:
- if (somethingSelected)
+ if (mSomethingSelected)
{
applyTransformedSelection();
paintTransformedSelection();
@@ -322,14 +322,14 @@ void ScribbleArea::keyPressEvent(QKeyEvent *event)
}
break;
case Qt::Key_Escape:
- if (somethingSelected)
+ if (mSomethingSelected)
{
deselectAll();
applyTransformedSelection();
}
break;
case Qt::Key_Backspace:
- if (somethingSelected)
+ if (mSomethingSelected)
{
deleteSelection();
deselectAll();
@@ -372,6 +372,9 @@ void ScribbleArea::keyReleaseEvent(QKeyEvent *event)
// mouse and tablet event handlers
void ScribbleArea::wheelEvent(QWheelEvent* event)
{
+ // Don't change view if tool is in use
+ if(mMouseInUse) return;
+
const QPoint pixels = event->pixelDelta();
const QPoint angle = event->angleDelta();
//qDebug() <<"angle"<layers()->currentLayer();
Q_ASSUME(layer != nullptr);
- if (layer->type() == Layer::VECTOR)
- {
- auto pLayerVector = static_cast(layer);
- VectorImage* vectorImage = pLayerVector->getLastVectorImageAtFrame(mEditor->currentFrame(), 0);
- Q_CHECK_PTR(vectorImage);
- }
- else if (layer->type() == Layer::BITMAP)
- {
- auto pLayerBitmap = static_cast(layer);
- BitmapImage* bitmapImage = pLayerBitmap->getLastBitmapImageAtFrame(mEditor->currentFrame(), 0);
- Q_CHECK_PTR(bitmapImage);
- }
-
if (!layer->visible() && currentTool()->type() != HAND && (event->button() != Qt::RightButton))
{
QMessageBox::warning(this, tr("Warning"),
@@ -675,7 +665,9 @@ void ScribbleArea::paintBitmapBuffer()
int frameNumber = mEditor->currentFrame();
- if (layer->getKeyFrameAt(frameNumber) == nullptr)
+ // If there is no keyframe at or before the current position,
+ // just return (since we have nothing to paint on).
+ if (layer->getLastKeyFrameAtPosition(frameNumber) == nullptr)
{
updateCurrentFrame();
return;
@@ -696,7 +688,7 @@ void ScribbleArea::paintBitmapBuffer()
case PENCIL:
if (getTool(currentTool()->type())->properties.preserveAlpha)
{
- cm = QPainter::CompositionMode_SourceAtop;
+ cm = QPainter::CompositionMode_SourceOver;
}
break;
default: //nothing
@@ -705,20 +697,21 @@ void ScribbleArea::paintBitmapBuffer()
targetImage->paste(mBufferImg, cm);
}
- //qCDebug( mLog ) << "Paste Rect" << mBufferImg->bounds();
QRect rect = mEditor->view()->mapCanvasToScreen(mBufferImg->bounds()).toRect();
drawCanvas(frameNumber, rect.adjusted(-1, -1, 1, 1));
update(rect);
- QPixmapCache::remove(mPixmapCacheKeys[frameNumber]);
- mPixmapCacheKeys[frameNumber] = QPixmapCache::Key();
+ // Update the cache for the last key-frame.
+ auto lastKeyFramePosition = mEditor->layers()->LastFrameAtFrame(frameNumber);
+ QPixmapCache::remove(mPixmapCacheKeys[lastKeyFramePosition]);
+ mPixmapCacheKeys[lastKeyFramePosition] = QPixmapCache::Key();
layer->setModified(frameNumber, true);
mBufferImg->clear();
}
-void ScribbleArea::paintBitmapBufferRect(QRect rect)
+void ScribbleArea::paintBitmapBufferRect(const QRect& rect)
{
if (allowSmudging() || mEditor->playback()->isPlaying())
{
@@ -850,6 +843,73 @@ void ScribbleArea::updateCanvasCursor()
}
+void ScribbleArea::handleDrawingOnEmptyFrame()
+{
+ auto layer = mEditor->layers()->currentLayer();
+
+ if(!layer || !layer->isPaintable())
+ {
+ return;
+ }
+
+ int frameNumber = mEditor->currentFrame();
+ auto previousKeyFrame = layer->getLastKeyFrameAtPosition(frameNumber);
+
+ if(layer->getKeyFrameAt(frameNumber) == nullptr)
+ {
+ // Drawing on an empty frame; take action based on preference.
+ int action = mPrefs->getInt(SETTING::DRAW_ON_EMPTY_FRAME_ACTION);
+
+ switch(action)
+ {
+ case CREATE_NEW_KEY:
+ mEditor->addNewKey();
+ mEditor->scrubTo(frameNumber); // Refresh timeline.
+
+ // Hack to clear previous frame's content.
+ if(layer->type() == Layer::BITMAP && previousKeyFrame)
+ {
+ auto asBitmapImage = dynamic_cast (previousKeyFrame);
+
+ if(asBitmapImage)
+ {
+ drawCanvas(frameNumber, asBitmapImage->bounds());
+ }
+ }
+
+ if(layer->type() == Layer::VECTOR)
+ {
+ auto asVectorImage = dynamic_cast (previousKeyFrame);
+
+ if(asVectorImage)
+ {
+ auto copy(*asVectorImage);
+ copy.selectAll();
+
+ drawCanvas(frameNumber, copy.getSelectionRect().toRect());
+ }
+ }
+
+ break;
+ case DUPLICATE_PREVIOUS_KEY:
+ {
+ if(previousKeyFrame)
+ {
+ KeyFrame* dupKey = previousKeyFrame->clone();
+ layer->addKeyFrame(frameNumber, dupKey);
+ mEditor->scrubTo(frameNumber); // Refresh timeline.
+ }
+ break;
+ }
+ case KEEP_DRAWING_ON_PREVIOUS_KEY:
+ // No action needed.
+ break;
+ default:
+ break;
+ }
+ }
+}
+
void ScribbleArea::paintEvent(QPaintEvent* event)
{
if (!mMouseInUse || currentTool()->type() == MOVE || currentTool()->type() == HAND || mMouseRightButtonInUse)
@@ -979,38 +1039,9 @@ void ScribbleArea::paintEvent(QPaintEvent* event)
mCanvasPainter.renderGrid(painter);
// paints the selection outline
- if (somethingSelected && (myTempTransformedSelection.isValid() || mMoveMode == ROTATION)) // @revise
+ if (mSomethingSelected && !myTempTransformedSelection.isNull())
{
- // outline of the transformed selection
- painter.setWorldMatrixEnabled(false);
- painter.setOpacity(1.0);
- QPolygon tempRect = mEditor->view()->getView().mapToPolygon(myTempTransformedSelection.normalized().toRect());
-
- Layer* layer = mEditor->layers()->currentLayer();
- if (layer != NULL)
- {
- if (layer->type() == Layer::BITMAP)
- {
- painter.setBrush(Qt::NoBrush);
- painter.setPen(Qt::DashLine);
- }
- if (layer->type() == Layer::VECTOR)
- {
- painter.setBrush(QColor(0, 0, 0, 20));
- painter.setPen(Qt::gray);
- }
- painter.drawPolygon(tempRect);
-
- if (layer->type() != Layer::VECTOR || currentTool()->type() != SELECT)
- {
- painter.setPen(Qt::SolidLine);
- painter.setBrush(QBrush(Qt::gray));
- painter.drawRect(tempRect.point(0).x() - 3, tempRect.point(0).y() - 3, 6, 6);
- painter.drawRect(tempRect.point(1).x() - 3, tempRect.point(1).y() - 3, 6, 6);
- painter.drawRect(tempRect.point(2).x() - 3, tempRect.point(2).y() - 3, 6, 6);
- painter.drawRect(tempRect.point(3).x() - 3, tempRect.point(3).y() - 3, 6, 6);
- }
- }
+ paintSelectionVisuals(painter);
}
}
@@ -1025,6 +1056,83 @@ void ScribbleArea::paintEvent(QPaintEvent* event)
event->accept();
}
+void ScribbleArea::paintSelectionVisuals(QPainter& painter)
+{
+ // outline of the transformed selection
+ painter.setWorldMatrixEnabled(false);
+ painter.setOpacity(1.0);
+ mCurrentTransformSelection = mEditor->view()->getView().mapToPolygon(myTempTransformedSelection.toAlignedRect());
+ mLastTransformSelection = mEditor->view()->getView().mapToPolygon(myTransformedSelection.toAlignedRect());
+
+ Layer* layer = mEditor->layers()->currentLayer();
+ if (layer != NULL)
+ {
+ if (layer->type() == Layer::BITMAP)
+ {
+ painter.setBrush(Qt::NoBrush);
+
+ QPen pen = QPen(Qt::DashLine);
+ painter.setPen(pen);
+
+ // Draw previous selection
+ painter.drawPolygon(mLastTransformSelection.toPolygon());
+
+ // Draw current selection
+ painter.drawPolygon(mCurrentTransformSelection.toPolygon());
+
+ }
+ if (layer->type() == Layer::VECTOR)
+ {
+ painter.setBrush(QColor(0, 0, 0, 20));
+ painter.setPen(Qt::gray);
+ painter.drawPolygon(mCurrentTransformSelection);
+ }
+
+ if (layer->type() != Layer::VECTOR || currentTool()->type() != SELECT)
+ {
+ painter.setPen(Qt::SolidLine);
+ painter.setBrush(QBrush(Qt::gray));
+ int width = 6;
+ int radius = width/2;
+
+ QRectF topLeftCorner = QRectF(mCurrentTransformSelection[0].x() - radius,
+ mCurrentTransformSelection[0].y() - radius,
+ width, width);
+
+ QRectF topRightCorner = QRectF(mCurrentTransformSelection[1].x() - radius,
+ mCurrentTransformSelection[1].y() - radius,
+ width, width);
+
+ QRectF bottomRightCorner = QRectF(mCurrentTransformSelection[2].x() - radius,
+ mCurrentTransformSelection[2].y() - radius,
+ width, width);
+
+ QRectF bottomLeftCorner = QRectF(mCurrentTransformSelection[3].x() - radius,
+ mCurrentTransformSelection[3].y() - radius,
+ width, width);
+
+ painter.drawRect(topLeftCorner.x(),
+ topLeftCorner.y(),
+ width, width);
+
+ painter.drawRect(topRightCorner.x(),
+ topRightCorner.y(),
+ width, width);
+
+ painter.drawRect(bottomRightCorner.x(),
+ bottomRightCorner.y(),
+ width, width);
+
+ painter.drawRect(bottomLeftCorner.x(),
+ bottomLeftCorner.y(),
+ width, width);
+
+ painter.setBrush(QColor(0, 255, 0, 50));
+ painter.setPen(Qt::green);
+ }
+ }
+}
+
void ScribbleArea::drawCanvas(int frame, QRect rect)
{
Object* object = mEditor->object();
@@ -1041,12 +1149,14 @@ void ScribbleArea::drawCanvas(int frame, QRect rect)
o.bAntiAlias = mPrefs->isOn(SETTING::ANTIALIAS);
o.bGrid = mPrefs->isOn(SETTING::GRID);
o.nGridSize = mPrefs->getInt(SETTING::GRID_SIZE);
- o.bAxis = mPrefs->isOn(SETTING::AXIS);
+ o.bAxis = false;
o.bThinLines = mPrefs->isOn(SETTING::INVISIBLE_LINES);
o.bOutlines = mPrefs->isOn(SETTING::OUTLINES);
o.nShowAllLayers = mShowAllLayers;
o.bIsOnionAbsolute = (mPrefs->getString(SETTING::ONION_TYPE) == "absolute");
o.scaling = mEditor->view()->scaling();
+ o.onionWhilePlayback = mPrefs->getInt(SETTING::ONION_WHILE_PLAYBACK);
+ o.isPlaying = mEditor->playback()->isPlaying() ? true : false;
mCanvasPainter.setOptions(o);
mCanvasPainter.setCanvas(&mCanvas);
@@ -1055,14 +1165,12 @@ void ScribbleArea::drawCanvas(int frame, QRect rect)
mCanvasPainter.setViewTransform(vm->getView(), vm->getViewInverse());
mCanvasPainter.paint(object, mEditor->layers()->currentLayerIndex(), frame, rect);
-
- return;
}
-void ScribbleArea::setGaussianGradient(QGradient &gradient, QColor colour, qreal opacity, qreal mOffset)
+void ScribbleArea::setGaussianGradient(QGradient &gradient, QColor colour, qreal opacity, qreal offset)
{
- if (mOffset < 0) { mOffset = 0; }
- if (mOffset > 100) { mOffset = 100; }
+ if (offset < 0) { offset = 0; }
+ if (offset > 100) { offset = 100; }
int r = colour.red();
int g = colour.green();
@@ -1072,11 +1180,11 @@ void ScribbleArea::setGaussianGradient(QGradient &gradient, QColor colour, qreal
int mainColorAlpha = qRound(a * 255 * opacity);
// the more feather (offset), the more softness (opacity)
- int alphaAdded = qRound((mainColorAlpha * mOffset) / 100);
+ int alphaAdded = qRound((mainColorAlpha * offset) / 100);
gradient.setColorAt(0.0, QColor(r, g, b, mainColorAlpha - alphaAdded));
gradient.setColorAt(1.0, QColor(r, g, b, 0));
- gradient.setColorAt(1.0 - (mOffset / 100.0), QColor(r, g, b, mainColorAlpha - alphaAdded));
+ gradient.setColorAt(1.0 - (offset / 100.0), QColor(r, g, b, mainColorAlpha - alphaAdded));
}
void ScribbleArea::drawPen(QPointF thePoint, qreal brushWidth, QColor fillColour, bool useAA)
@@ -1096,13 +1204,12 @@ void ScribbleArea::drawBrush(QPointF thePoint, qreal brushWidth, qreal mOffset,
{
QRectF rectangle(thePoint.x() - 0.5 * brushWidth, thePoint.y() - 0.5 * brushWidth, brushWidth, brushWidth);
- BitmapImage gradientImg;
- if (usingFeather == true)
+ if (usingFeather)
{
QRadialGradient radialGrad(thePoint, 0.5 * brushWidth);
setGaussianGradient(radialGrad, fillColour, opacity, mOffset);
- gradientImg.drawEllipse(rectangle, Qt::NoPen, radialGrad,
+ mBufferImg->drawEllipse(rectangle, Qt::NoPen, radialGrad,
QPainter::CompositionMode_SourceOver, false);
}
else
@@ -1110,7 +1217,6 @@ void ScribbleArea::drawBrush(QPointF thePoint, qreal brushWidth, qreal mOffset,
mBufferImg->drawEllipse(rectangle, Qt::NoPen, QBrush(fillColour, Qt::SolidPattern),
QPainter::CompositionMode_SourceOver, useAA);
}
- mBufferImg->paste(&gradientImg);
}
/**
@@ -1127,7 +1233,7 @@ void ScribbleArea::flipSelection(bool flipVertical)
QTransform _translate = QTransform::fromTranslate(-centerPoints[1].x(), -centerPoints[1].y());
QTransform scale = QTransform::fromScale(-scaleX, scaleY);
- if (flipVertical == true)
+ if (flipVertical)
{
scale = QTransform::fromScale(scaleX, -scaleY);
}
@@ -1149,7 +1255,7 @@ void ScribbleArea::blurBrush(BitmapImage *bmiSource_, QPointF srcPoint_, QPointF
QRectF trgRect(thePoint_.x() - 0.5 * brushWidth_, thePoint_.y() - 0.5 * brushWidth_, brushWidth_, brushWidth_);
BitmapImage bmiSrcClip = bmiSource_->copy(srcRect.toRect());
- BitmapImage bmiTmpClip = bmiSrcClip; // todo: find a shorter way
+ BitmapImage bmiTmpClip = bmiSrcClip; // TODO: find a shorter way
bmiTmpClip.drawRect(srcRect, Qt::NoPen, radialGrad, QPainter::CompositionMode_Source, mPrefs->isOn(SETTING::ANTIALIAS));
bmiSrcClip.bounds().moveTo(trgRect.topLeft().toPoint());
@@ -1166,23 +1272,22 @@ void ScribbleArea::liquifyBrush(BitmapImage *bmiSource_, QPointF srcPoint_, QPoi
setGaussianGradient(radialGrad, QColor(255, 255, 255, 255), opacity_, mOffset_);
// Create gradient brush
- BitmapImage* bmiTmpClip = new BitmapImage;
- bmiTmpClip->drawRect(trgRect, Qt::NoPen, radialGrad, QPainter::CompositionMode_Source, mPrefs->isOn(SETTING::ANTIALIAS));
+ BitmapImage bmiTmpClip;
+ bmiTmpClip.drawRect(trgRect, Qt::NoPen, radialGrad, QPainter::CompositionMode_Source, mPrefs->isOn(SETTING::ANTIALIAS));
// Slide texture/pixels of the source image
qreal factor, factorGrad;
- int xb, yb, xa, ya;
- for (yb = bmiTmpClip->bounds().top(); yb < bmiTmpClip->bounds().bottom(); yb++)
+ for (int yb = bmiTmpClip.top(); yb < bmiTmpClip.bottom(); yb++)
{
- for (xb = bmiTmpClip->bounds().left(); xb < bmiTmpClip->bounds().right(); xb++)
+ for (int xb = bmiTmpClip.left(); xb < bmiTmpClip.right(); xb++)
{
QColor color;
- color.setRgba(bmiTmpClip->pixel(xb, yb));
+ color.setRgba(bmiTmpClip.pixel(xb, yb));
factorGrad = color.alphaF(); // any from r g b a is ok
- xa = xb - factorGrad*delta.x();
- ya = yb - factorGrad*delta.y();
+ int xa = xb - factorGrad*delta.x();
+ int ya = yb - factorGrad*delta.y();
color.setRgba(bmiSource_->pixel(xa, ya));
factor = color.alphaF();
@@ -1199,15 +1304,15 @@ void ScribbleArea::liquifyBrush(BitmapImage *bmiSource_, QPointF srcPoint_, QPoi
color.setBlue(color.blue()*factorGrad);
color.setAlpha(255 * factorGrad); // Premultiplied color
- bmiTmpClip->setPixel(xb, yb, color.rgba());
+ bmiTmpClip.setPixel(xb, yb, color.rgba());
}
- else {
- bmiTmpClip->setPixel(xb, yb, qRgba(255, 255, 255, 255));
+ else
+ {
+ bmiTmpClip.setPixel(xb, yb, qRgba(255, 255, 255, 255));
}
}
}
- mBufferImg->paste(bmiTmpClip);
- delete bmiTmpClip;
+ mBufferImg->paste(&bmiTmpClip);
}
void ScribbleArea::drawPolyline(QPainterPath path, QPen pen, bool useAA)
@@ -1225,64 +1330,202 @@ void ScribbleArea::drawPolyline(QPainterPath path, QPen pen, bool useAA)
/************************************************************************************/
// view handling
-QTransform ScribbleArea::getView()
+QRectF ScribbleArea::getCameraRect()
{
+ return mCanvasPainter.getCameraRect();
+}
+
+QPointF ScribbleArea::getCentralPoint()
+{
+ return mEditor->view()->mapScreenToCanvas(QPointF(width() / 2, height() / 2));
+}
+
+/************************************************************************************/
+// selection handling
+
+void ScribbleArea::calculateSelectionRect()
+{
+ selectionTransformation.reset();
Layer* layer = mEditor->layers()->currentLayer();
- if (layer == NULL)
+ if (layer == NULL) { return; }
+ if (layer->type() == Layer::VECTOR)
{
- Q_ASSERT(false);
- return QTransform(); // TODO: error
+ VectorImage *vectorImage = ((LayerVector *)layer)->getLastVectorImageAtFrame(mEditor->currentFrame(), 0);
+ vectorImage->calculateSelectionRect();
+ setSelection(vectorImage->getSelectionRect());
}
+}
+
+bool ScribbleArea::isSomethingSelected() const
+{
+ return mSomethingSelected;
+}
+
+void ScribbleArea::findMoveModeOfCornerInRange()
+{
+ const double marginInPixels = 15;
+ const double scale = mEditor->view()->getView().inverted().m11();
+ const double scaledMargin = fabs(marginInPixels * scale);
- if (layer->type() == Layer::CAMERA)
+ // MAYBE: if this is broken, use myTransformedSelection
+ QRectF transformRect = myTempTransformedSelection;
+ QPointF lastPoint = currentTool()->getLastPoint();
+
+ MoveMode mode;
+ if (QLineF(lastPoint, transformRect.topLeft()).length() < scaledMargin)
{
- return ((LayerCamera *)layer)->getViewAtFrame(mEditor->currentFrame());
+ mode = MoveMode::TOPLEFT;
}
- else
+ else if (QLineF(lastPoint, transformRect.topRight()).length() < scaledMargin)
+ {
+ mode = MoveMode::TOPRIGHT;
+ }
+ else if (QLineF(lastPoint, transformRect.bottomLeft()).length() < scaledMargin)
+ {
+ mode = MoveMode::BOTTOMLEFT;
+
+ }
+ else if (QLineF(lastPoint, transformRect.bottomRight()).length() < scaledMargin)
{
- return mEditor->view()->getView();
+ mode = MoveMode::BOTTOMRIGHT;
}
+ else if (myTransformedSelection.translated(mOffset).contains(lastPoint))
+ {
+ mode = MoveMode::MIDDLE;
+ }
+ else {
+ mode = MoveMode::NONE;
+ }
+ mMoveMode = mode;
}
-QRectF ScribbleArea::getViewRect()
+MoveMode ScribbleArea::getMoveModeForSelectionAnchor()
{
- QRectF rect = QRectF(-width() / 2, -height() / 2, width(), height());
- Layer* layer = mEditor->layers()->currentLayer();
- if (layer == NULL) { return rect; }
- if (layer->type() == Layer::CAMERA)
+
+ const double marginInPixels = 15;
+ const double radius = marginInPixels/2;
+ const double scale = mEditor->view()->getView().inverted().m11();
+ const double scaledMargin = fabs(marginInPixels * scale);
+
+ if (mCurrentTransformSelection.isEmpty()) { return MoveMode::NONE; }
+
+ QRectF topLeftCorner = mEditor->view()->mapScreenToCanvas(QRectF(mCurrentTransformSelection[0].x() - radius,
+ mCurrentTransformSelection[0].y() - radius,
+ marginInPixels, marginInPixels));
+
+ QRectF topRightCorner = mEditor->view()->mapScreenToCanvas(QRectF(mCurrentTransformSelection[1].x() - radius,
+ mCurrentTransformSelection[1].y() - radius,
+ marginInPixels, marginInPixels));
+
+ QRectF bottomRightCorner = mEditor->view()->mapScreenToCanvas(QRectF(mCurrentTransformSelection[2].x() - radius,
+ mCurrentTransformSelection[2].y() - radius,
+ marginInPixels, marginInPixels));
+
+ QRectF bottomLeftCorner = mEditor->view()->mapScreenToCanvas(QRectF(mCurrentTransformSelection[3].x() - radius,
+ mCurrentTransformSelection[3].y() - radius,
+ marginInPixels, marginInPixels));
+
+ QPointF currentPos = currentTool()->getCurrentPoint();
+
+ if (QLineF(currentPos, topLeftCorner.center()).length() < scaledMargin)
{
- return ((LayerCamera *)layer)->getViewRect();
+ return MoveMode::TOPLEFT;
}
- else
+ else if (QLineF(currentPos, topRightCorner.center()).length() < scaledMargin)
+ {
+ return MoveMode::TOPRIGHT;
+ }
+ else if (QLineF(currentPos, bottomLeftCorner.center()).length() < scaledMargin)
{
- return rect;
+ return MoveMode::BOTTOMLEFT;
+
+ }
+ else if (QLineF(currentPos, bottomRightCorner.center()).length() < scaledMargin)
+ {
+ return MoveMode::BOTTOMRIGHT;
+ }
+ else if (myTempTransformedSelection.contains(currentPos))
+ {
+ return MoveMode::MIDDLE;
}
+
+ return MoveMode::NONE;
}
-QRectF ScribbleArea::getCameraRect()
+QPointF ScribbleArea::whichAnchorPoint(QPointF anchorPoint)
{
- return mCanvasPainter.getCameraRect();
+ MoveMode mode = getMoveModeForSelectionAnchor();
+ if (mode == MoveMode::TOPLEFT)
+ {
+ anchorPoint = mySelection.bottomRight();
+ }
+ else if (mode == MoveMode::TOPRIGHT)
+ {
+ anchorPoint = mySelection.bottomLeft();
+ }
+ else if (mode == MoveMode::BOTTOMLEFT)
+ {
+ anchorPoint = mySelection.topRight();
+ }
+ else if (mode == MoveMode::BOTTOMRIGHT)
+ {
+ anchorPoint = mySelection.topLeft();
+ }
+ return anchorPoint;
}
-QPointF ScribbleArea::getCentralPoint()
+void ScribbleArea::adjustSelection(float offsetX, float offsetY, qreal rotatedAngle)
{
- return mEditor->view()->mapScreenToCanvas(QPointF(width() / 2, height() / 2));
-}
+ QRectF& transformedSelection = myTransformedSelection;
-/************************************************************************************/
-// selection handling
+ switch (mMoveMode)
+ {
+ case MoveMode::MIDDLE:
+ {
+ myTempTransformedSelection =
+ transformedSelection.translated(QPointF(offsetX, offsetY));
-void ScribbleArea::calculateSelectionRect()
-{
- selectionTransformation.reset();
- Layer* layer = mEditor->layers()->currentLayer();
- if (layer == NULL) { return; }
- if (layer->type() == Layer::VECTOR)
+ break;
+ }
+ case MoveMode::TOPRIGHT:
{
- VectorImage *vectorImage = ((LayerVector *)layer)->getLastVectorImageAtFrame(mEditor->currentFrame(), 0);
- vectorImage->calculateSelectionRect();
- setSelection(vectorImage->getSelectionRect(), true);
+ myTempTransformedSelection =
+ transformedSelection.adjusted(0, offsetY, offsetX, 0);
+
+ break;
+ }
+ case MoveMode::TOPLEFT:
+ {
+ myTempTransformedSelection =
+ transformedSelection.adjusted(offsetX, offsetY, 0, 0);
+
+ break;
+ }
+ case MoveMode::BOTTOMLEFT:
+ {
+ myTempTransformedSelection =
+ transformedSelection.adjusted(offsetX, 0, 0, offsetY);
+ break;
+ }
+ case MoveMode::BOTTOMRIGHT:
+ {
+ myTempTransformedSelection =
+ transformedSelection.adjusted(0, 0, offsetX, offsetY);
+ break;
+
+ }
+ case MoveMode::ROTATION:
+ {
+ myTempTransformedSelection =
+ transformedSelection; // @ necessary?
+ myRotatedAngle = (currentTool()->getCurrentPixel().x() -
+ currentTool()->getLastPressPixel().x()) + rotatedAngle;
+ break;
+ }
+ default:
+ break;
}
+ update();
}
/**
@@ -1297,45 +1540,30 @@ QVector ScribbleArea::calcSelectionCenterPoints()
tempSelectionCenterX,
tempSelectionCenterY;
- tempSelectionCenterX = 0.5 * (myTempTransformedSelection.left() +
- myTempTransformedSelection.right());
- tempSelectionCenterY = 0.5 * (myTempTransformedSelection.top() +
- myTempTransformedSelection.bottom());
- selectionCenterX = 0.5 * (mySelection.left() + mySelection.right());
- selectionCenterY = 0.5 * (mySelection.top() + mySelection.bottom());
+ tempSelectionCenterX = myTempTransformedSelection.center().x();
+ tempSelectionCenterY = myTempTransformedSelection.center().y();
+ selectionCenterX = mySelection.center().x();
+ selectionCenterY = mySelection.center().y();
centerPoints.append(QPoint(tempSelectionCenterX, tempSelectionCenterY));
centerPoints.append(QPoint(selectionCenterX, selectionCenterY));
return centerPoints;
}
-void ScribbleArea::calculateSelectionTransformation() // Vector layer transform
+void ScribbleArea::calculateSelectionTransformation()
{
- float scaleX, scaleY;
QVector centerPoints = calcSelectionCenterPoints();
- if (mySelection.width() == 0)
- {
- scaleX = 1.0;
- }
- else
- {
- scaleX = myTempTransformedSelection.width() / mySelection.width();
- }
-
- if (mySelection.height() == 0)
- {
- scaleY = 1.0;
- }
- else
- {
- scaleY = myTempTransformedSelection.height() / mySelection.height();
- }
-
selectionTransformation.reset();
selectionTransformation.translate(centerPoints[0].x(), centerPoints[0].y());
selectionTransformation.rotate(myRotatedAngle);
- selectionTransformation.scale(scaleX, scaleY);
+
+ if (mySelection.width() > 0 && mySelection.height() > 0) // can't divide by 0
+ {
+ float scaleX = myTempTransformedSelection.width() / mySelection.width();
+ float scaleY = myTempTransformedSelection.height() / mySelection.height();
+ selectionTransformation.scale(scaleX, scaleY);
+ }
selectionTransformation.translate(-centerPoints[1].x(), -centerPoints[1].y());
}
@@ -1348,11 +1576,11 @@ void ScribbleArea::paintTransformedSelection()
return;
}
- if (somethingSelected) // there is something selected
+ if (mSomethingSelected) // there is something selected
{
if (layer->type() == Layer::BITMAP)
{
- mCanvasPainter.setTransformedSelection(mySelection.toRect(), selectionTransformation);
+ mCanvasPainter.setTransformedSelection(mySelection.toAlignedRect(), selectionTransformation);
}
else if (layer->type() == Layer::VECTOR)
{
@@ -1367,6 +1595,31 @@ void ScribbleArea::paintTransformedSelection()
update();
}
+void ScribbleArea::applySelectionChanges()
+{
+
+ // we haven't applied our last modifications yet
+ // therefore apply the transformed selection first.
+ applyTransformedSelection();
+
+ // make sure the current transformed selection is valid
+ if (!myTempTransformedSelection.isValid())
+ {
+ myTempTransformedSelection = myTempTransformedSelection.normalized();
+ }
+ setSelection(myTempTransformedSelection);
+
+ // paint the transformed selection
+ paintTransformedSelection();
+
+ // Calculate the new transformation based on the new selection
+ calculateSelectionTransformation();
+
+ // apply the transformed selection to make the selection modification absolute.
+ applyTransformedSelection();
+
+}
+
void ScribbleArea::applyTransformedSelection()
{
mCanvasPainter.ignoreTransformedSelection();
@@ -1377,23 +1630,23 @@ void ScribbleArea::applyTransformedSelection()
return;
}
- if (somethingSelected) // there is something selected
+ if (mSomethingSelected) // there is something selected
{
+ if (mySelection.isEmpty()) { return; }
+
if (layer->type() == Layer::BITMAP)
{
- if (!mySelection.isEmpty())
- {
BitmapImage* bitmapImage = dynamic_cast(layer)->getLastBitmapImageAtFrame(mEditor->currentFrame(), 0);
BitmapImage transformedImage = bitmapImage->transformed(mySelection.toRect(), selectionTransformation, true);
bitmapImage->clear(mySelection);
bitmapImage->paste(&transformedImage, QPainter::CompositionMode_SourceOver);
- }
}
else if (layer->type() == Layer::VECTOR)
{
VectorImage *vectorImage = ((LayerVector *)layer)->getLastVectorImageAtFrame(mEditor->currentFrame(), 0);
- vectorImage->applySelectionTransformation();
+ vectorImage->applySelectionTransformation();
+
}
setModified(mEditor->layers()->currentLayerIndex(), mEditor->currentFrame());
@@ -1406,7 +1659,7 @@ void ScribbleArea::cancelTransformedSelection()
{
mCanvasPainter.ignoreTransformedSelection();
- if (somethingSelected) {
+ if (mSomethingSelected) {
Layer* layer = mEditor->layers()->currentLayer();
if (layer == NULL)
@@ -1420,7 +1673,7 @@ void ScribbleArea::cancelTransformedSelection()
vectorImage->setSelectionTransformation(QTransform());
}
- setSelection(mySelection, true);
+ setSelection(mySelection);
selectionTransformation.reset();
@@ -1431,19 +1684,63 @@ void ScribbleArea::cancelTransformedSelection()
}
}
-void ScribbleArea::setSelection(QRectF rect, bool trueOrFalse)
+void ScribbleArea::setSelection(QRectF rect)
{
+ Layer* layer = mEditor->layers()->currentLayer();
+ if (layer->type() == Layer::BITMAP) {
+ rect = rect.toAlignedRect();
+ }
mySelection = rect;
myTransformedSelection = rect;
myTempTransformedSelection = rect;
- somethingSelected = trueOrFalse;
+ mSomethingSelected = (mySelection.isNull() ? false : true);
// Temporary disabled this as it breaks selection rotate key (ctrl) event.
// displaySelectionProperties();
}
+/**
+ * @brief ScribbleArea::manageSelectionOrigin
+ * switches anchor point when crossing threshold
+ */
+void ScribbleArea::manageSelectionOrigin(QPointF currentPoint, QPointF originPoint)
+{
+ int mouseX = currentPoint.x();
+ int mouseY = currentPoint.y();
+
+ QRectF selectRect;
+
+ if (mouseX <= originPoint.x())
+ {
+ selectRect.setLeft(mouseX);
+ selectRect.setRight(originPoint.x());
+
+ }
+ else
+ {
+ selectRect.setLeft(originPoint.x());
+ selectRect.setRight(mouseX);
+ }
+
+ if (mouseY <= originPoint.y())
+ {
+ selectRect.setTop(mouseY);
+ selectRect.setBottom(originPoint.y());
+ }
+ else
+ {
+ selectRect.setTop(originPoint.y());
+ selectRect.setBottom(mouseY);
+ }
+
+ if (currentTool()->type() == ToolType::SELECT) {
+ myTempTransformedSelection = selectRect;
+ }
+
+}
+
void ScribbleArea::displaySelectionProperties()
{
Layer* layer = mEditor->layers()->currentLayer();
@@ -1492,7 +1789,7 @@ void ScribbleArea::selectAll()
// as the drawing area is not limited
//
BitmapImage *bitmapImage = ((LayerBitmap *)layer)->getLastBitmapImageAtFrame(mEditor->currentFrame(), 0);
- setSelection(bitmapImage->bounds(), true);
+ setSelection(bitmapImage->bounds());
}
@@ -1500,7 +1797,7 @@ void ScribbleArea::selectAll()
{
VectorImage *vectorImage = ((LayerVector *)layer)->getLastVectorImageAtFrame(mEditor->currentFrame(), 0);
vectorImage->selectAll();
- setSelection(vectorImage->getSelectionRect(), true);
+ setSelection(vectorImage->getSelectionRect());
}
updateCurrentFrame();
}
@@ -1530,8 +1827,7 @@ void ScribbleArea::deselectAll()
{
((LayerVector *)layer)->getLastVectorImageAtFrame(mEditor->currentFrame(), 0)->deselectAll();
}
- somethingSelected = false;
- isTransforming = false;
+ mSomethingSelected = false;
mBufferImg->clear();
@@ -1625,7 +1921,7 @@ void ScribbleArea::setTemporaryTool(ToolType eToolMode)
void ScribbleArea::deleteSelection()
{
- if (somethingSelected) // there is something selected
+ if (mSomethingSelected) // there is something selected
{
Layer* layer = mEditor->layers()->currentLayer();
if (layer == NULL) { return; }
diff --git a/core_lib/src/interface/scribblearea.h b/core_lib/src/interface/scribblearea.h
index 05cecd679..db9445563 100644
--- a/core_lib/src/interface/scribblearea.h
+++ b/core_lib/src/interface/scribblearea.h
@@ -30,22 +30,19 @@ GNU General Public License for more details.
#include
#include
+#include "movemode.h"
#include "log.h"
#include "pencildef.h"
#include "bitmapimage.h"
#include "colourref.h"
#include "vectorselection.h"
-#include "colormanager.h"
-#include "viewmanager.h"
#include "canvaspainter.h"
#include "preferencemanager.h"
-
class Layer;
class Editor;
class BaseTool;
class StrokeManager;
-class ColorManager;
class ScribbleArea : public QWidget
@@ -65,13 +62,18 @@ class ScribbleArea : public QWidget
void setCore( Editor* pCore ) { mEditor = pCore; }
void deleteSelection();
- void setSelection( QRectF rect, bool );
+ void setSelection( QRectF rect );
+ void adjustSelection(float offsetX, float offsetY, qreal rotatedAngle);
+ void applySelectionChanges();
void displaySelectionProperties();
void resetSelectionProperties();
+
+ bool isSomethingSelected() const;
QRectF getSelection() const { return mySelection; }
- bool somethingSelected = false;
- QRectF mySelection, myTransformedSelection, myTempTransformedSelection;
- qreal myRotatedAngle;
+ QRectF mySelection;
+ QRectF myTransformedSelection;
+ QRectF myTempTransformedSelection;
+ qreal myRotatedAngle = 0.0;
QList mClosestCurves;
bool areLayersSane() const;
@@ -89,12 +91,13 @@ class ScribbleArea : public QWidget
bool usePressure() const { return mUsePressure; }
bool makeInvisible() const { return mMakeInvisible; }
- enum MoveMode { MIDDLE, TOPLEFT, TOPRIGHT, BOTTOMLEFT, BOTTOMRIGHT, ROTATION, SYMMETRY, NONE };
- MoveMode getMoveMode() const { return mMoveMode; }
void setMoveMode( MoveMode moveMode ) { mMoveMode = moveMode; }
+ MoveMode getMoveMode() const { return mMoveMode; }
+ void findMoveModeOfCornerInRange();
+ MoveMode getMoveModeForSelectionAnchor();
+
+ QPointF whichAnchorPoint(QPointF anchorPoint);
- QTransform getView();
- QRectF getViewRect();
QRectF getCameraRect();
QPointF getCentralPoint();
@@ -102,7 +105,7 @@ class ScribbleArea : public QWidget
void updateFrame( int frame );
void updateAllFrames();
void updateAllVectorLayersAtCurrentFrame();
- void updateAllVectorLayersAt( int frame );
+ void updateAllVectorLayersAt(int frameNumber);
bool shouldUpdateAll() const { return mNeedUpdateAll; }
void setAllDirty() { mNeedUpdateAll = true; }
@@ -115,11 +118,14 @@ class ScribbleArea : public QWidget
StrokeManager* getStrokeManager() const { return mStrokeManager.get(); }
- Editor* editor() { return mEditor; }
+ Editor* editor() const { return mEditor; }
void floodFillError( int errorType );
- bool isMouseInUse() { return mMouseInUse; }
+ bool isMouseInUse() const { return mMouseInUse; }
+ bool isTemporaryTool() const { return instantTool; }
+
+ void manageSelectionOrigin(QPointF currentPoint, QPointF originPoint);
signals:
void modification( int );
@@ -129,13 +135,17 @@ class ScribbleArea : public QWidget
public slots:
void clearImage();
void calculateSelectionRect();
- QTransform getSelectionTransformation() { return selectionTransformation; }
+ QTransform getSelectionTransformation() const { return selectionTransformation; }
void calculateSelectionTransformation();
void paintTransformedSelection();
void applyTransformedSelection();
void cancelTransformedSelection();
void setModified( int layerNumber, int frameNumber );
+ inline bool transformHasBeenModified() {
+ return (mySelection != myTempTransformedSelection) || myRotatedAngle != 0;
+ }
+
void selectAll();
void deselectAll();
@@ -170,7 +180,7 @@ public slots:
void liquifyBrush( BitmapImage *bmiSource_, QPointF srcPoint_, QPointF thePoint_, qreal brushWidth_, qreal offset_, qreal opacity_ );
void paintBitmapBuffer();
- void paintBitmapBufferRect( QRect rect );
+ void paintBitmapBufferRect(const QRect& rect);
void paintCanvasCursor(QPainter& painter);
void clearBitmapBuffer();
void refreshBitmap( const QRectF& rect, int rad );
@@ -179,22 +189,28 @@ public slots:
void updateCanvasCursor();
+ /// Call this when starting to use a paint tool. Checks whether we are drawing
+ /// on an empty frame, and if so, takes action according to use preference.
+ void handleDrawingOnEmptyFrame();
+
BitmapImage* mBufferImg = nullptr; // used to pre-draw vector modifications
BitmapImage* mStrokeImg = nullptr; // used for brush strokes before they are finalized
QPixmap mCursorImg;
QPixmap mTransCursImg;
+ QPointF getTransformOffset() { return mOffset; }
+
private:
void drawCanvas( int frame, QRect rect );
void settingUpdated(SETTING setting);
+ void paintSelectionVisuals(QPainter& painter);
- MoveMode mMoveMode = MIDDLE;
- ToolType mPrevTemporalToolType;
+ MoveMode mMoveMode = MoveMode::NONE;
+ ToolType mPrevTemporalToolType = ERASER;
ToolType mPrevToolType = PEN; // previous tool (except temporal)
BitmapImage mBitmapSelection; // used to temporary store a transformed portion of a bitmap image
- bool isTransforming = false;
std::unique_ptr< StrokeManager > mStrokeManager;
@@ -203,7 +219,7 @@ public slots:
bool mIsSimplified = false;
bool mShowThinLines = false;
bool mQuickSizing = true;
- int mShowAllLayers;
+ int mShowAllLayers = 1;
bool mUsePressure = true;
bool mMakeInvisible = false;
bool mToolCursors = true;
@@ -223,20 +239,21 @@ public slots:
QPointF mLastPoint;
QPointF mCurrentPoint;
- qreal selectionTolerance;
+ qreal selectionTolerance = 8.0;
QList mClosestVertices;
QPointF mOffset;
QPoint mCursorCenterPos;
- int mCursorWidth;
+
QPointF transformedCursorPos;
//instant tool (temporal eg. eraser)
bool instantTool = false; //whether or not using temporal tool
+ bool mSomethingSelected = false;
VectorSelection vectorSelection;
QTransform selectionTransformation;
- PreferenceManager *mPrefs = nullptr;
+ PreferenceManager* mPrefs = nullptr;
QPixmap mCanvas;
CanvasPainter mCanvasPainter;
@@ -248,6 +265,9 @@ public slots:
QRectF mDebugRect;
QLoggingCategory mLog;
std::deque< clock_t > mDebugTimeQue;
+
+ QPolygonF mCurrentTransformSelection;
+ QPolygonF mLastTransformSelection;
};
#endif
diff --git a/core_lib/src/interface/timecontrols.cpp b/core_lib/src/interface/timecontrols.cpp
index de67c88d0..ed87e63bb 100644
--- a/core_lib/src/interface/timecontrols.cpp
+++ b/core_lib/src/interface/timecontrols.cpp
@@ -176,15 +176,7 @@ void TimeControls::makeConnections()
void TimeControls::playButtonClicked()
{
- if (mEditor->playback()->isPlaying())
- {
- mEditor->playback()->stop();
- }
- else
- {
- mEditor->playback()->play();
- }
- updatePlayState();
+ emit playButtonTriggered();
}
void TimeControls::updatePlayState()
diff --git a/core_lib/src/interface/timecontrols.h b/core_lib/src/interface/timecontrols.h
index 0cf08bee4..7038283c7 100644
--- a/core_lib/src/interface/timecontrols.h
+++ b/core_lib/src/interface/timecontrols.h
@@ -47,6 +47,7 @@ class TimeControls : public QToolBar
Q_SIGNALS:
void soundClick(bool);
void fpsClick(int);
+ void playButtonTriggered();
public slots:
void toggleLoop(bool);
diff --git a/core_lib/src/interface/timeline.cpp b/core_lib/src/interface/timeline.cpp
index b580142aa..9d7dc32ec 100644
--- a/core_lib/src/interface/timeline.cpp
+++ b/core_lib/src/interface/timeline.cpp
@@ -203,6 +203,7 @@ void TimeLine::initUI()
connect(mTimeControls, &TimeControls::soundClick, this, &TimeLine::soundClick);
connect(mTimeControls, &TimeControls::fpsClick, this, &TimeLine::fpsClick);
connect(mTimeControls, &TimeControls::fpsClick, this, &TimeLine::updateLength);
+ connect(mTimeControls, &TimeControls::playButtonTriggered, this, &TimeLine::playButtonTriggered);
connect(newBitmapLayerAct, &QAction::triggered, this, &TimeLine::newBitmapLayer);
connect(newVectorLayerAct, &QAction::triggered, this, &TimeLine::newVectorLayer);
@@ -239,12 +240,21 @@ void TimeLine::setLength(int frame)
updateLength();
}
+/** Extends the timeline frame length if necessary
+ *
+ * @param[in] frame The new animation length
+ */
void TimeLine::extendLength(int frame)
{
- int extendFrame = frame + 10;
- if (extendFrame > mTracks->getFrameLength())
+ int currentLength = mTracks->getFrameLength();
+ if(frame > (currentLength * 0.75))
{
- mTracks->setFrameLength(extendFrame);
+ int newLength = std::max(frame, currentLength) * 1.5;
+
+ if (newLength > 9999)
+ newLength = 9999;
+
+ mTracks->setFrameLength(newLength);
updateLength();
}
}
diff --git a/core_lib/src/interface/timeline.h b/core_lib/src/interface/timeline.h
index 023e4366e..7085ffb47 100644
--- a/core_lib/src/interface/timeline.h
+++ b/core_lib/src/interface/timeline.h
@@ -70,6 +70,7 @@ class TimeLine : public BaseDockWidget
void fpsClick( int );
void onionPrevClick();
void onionNextClick();
+ void playButtonTriggered();
public:
bool scrubbing = false;
diff --git a/core_lib/src/interface/timelinecells.cpp b/core_lib/src/interface/timelinecells.cpp
index 129c1cbac..166d0d2cf 100644
--- a/core_lib/src/interface/timelinecells.cpp
+++ b/core_lib/src/interface/timelinecells.cpp
@@ -418,7 +418,6 @@ void TimeLineCells::resizeEvent(QResizeEvent* event)
void TimeLineCells::mousePressEvent(QMouseEvent* event)
{
- if (primaryButton != Qt::NoButton) return;
int frameNumber = getFrameNumber(event->pos().x());
int layerNumber = getLayerNumber(event->pos().y());
@@ -439,7 +438,9 @@ void TimeLineCells::mousePressEvent(QMouseEvent* event)
primaryButton = event->button();
- mEditor->tools()->currentTool()->switchingLayers();
+ bool switchLayer = mEditor->tools()->currentTool()->switchingLayer();
+ if (!switchLayer) { return; }
+
switch (mType)
{
case TIMELINE_CELL_TYPE::Layers:
@@ -529,7 +530,6 @@ void TimeLineCells::mousePressEvent(QMouseEvent* event)
mCanMoveFrame = true;
}
- currentLayer->mousePress(event, frameNumber);
mTimeLine->updateContent();
}
else
@@ -593,6 +593,7 @@ void TimeLineCells::mouseMoveEvent(QMouseEvent* event)
int offset = frameNumber - mLastFrameNumber;
currentLayer->moveSelectedFrames(offset);
+ mEditor->layers()->notifyAnimationLengthChanged();
mEditor->updateCurrentFrame();
}
else if (mCanBoxSelect)
@@ -606,7 +607,6 @@ void TimeLineCells::mouseMoveEvent(QMouseEvent* event)
}
mLastFrameNumber = frameNumber;
}
- currentLayer->mouseMove(event, frameNumber);
}
}
}
@@ -638,8 +638,6 @@ void TimeLineCells::mouseReleaseEvent(QMouseEvent* event)
// Add/remove from already selected
currentLayer->toggleFrameSelected(frameNumber, multipleSelection);
}
-
- currentLayer->mouseRelease(event, frameNumber);
}
if (mType == TIMELINE_CELL_TYPE::Layers && layerNumber != mStartLayerNumber && mStartLayerNumber != -1 && layerNumber != -1)
{
diff --git a/core_lib/src/managers/colormanager.cpp b/core_lib/src/managers/colormanager.cpp
index 126f54bd8..5ad12c5ec 100644
--- a/core_lib/src/managers/colormanager.cpp
+++ b/core_lib/src/managers/colormanager.cpp
@@ -38,6 +38,7 @@ Status ColorManager::load(Object* o)
{
mCurrentColorIndex = 0;
mCurrentFrontColor = o->data()->getCurrentColor();
+
return Status::OK;
}
@@ -59,6 +60,7 @@ void ColorManager::workingLayerChanged(Layer* layer)
QColor ColorManager::frontColor()
{
+
if (mIsWorkingOnVectorLayer)
return object()->getColour(mCurrentColorIndex).colour;
else
@@ -72,6 +74,7 @@ void ColorManager::setColorNumber(int n)
mCurrentColorIndex = n;
QColor currentColor = object()->getColour(mCurrentColorIndex).colour;
+
emit colorNumberChanged(mCurrentColorIndex);
emit colorChanged(currentColor, mCurrentColorIndex);
}
@@ -81,11 +84,13 @@ void ColorManager::setColor(const QColor& newColor)
if (mCurrentFrontColor != newColor)
{
mCurrentFrontColor = newColor;
-
- if ( mIsWorkingOnVectorLayer )
- object()->setColour(mCurrentColorIndex, newColor);
- emit colorChanged(newColor, (mIsWorkingOnVectorLayer) ? mCurrentColorIndex : -1);
+ emit colorChanged(mCurrentFrontColor, mCurrentColorIndex);
+
+ if (mIsWorkingOnVectorLayer)
+ {
+ object()->setColour(mCurrentColorIndex, newColor);
+ }
}
}
diff --git a/core_lib/src/managers/colormanager.h b/core_lib/src/managers/colormanager.h
index 6a7942e23..523c2499a 100644
--- a/core_lib/src/managers/colormanager.h
+++ b/core_lib/src/managers/colormanager.h
@@ -45,7 +45,7 @@ class ColorManager : public BaseManager
void colorNumberChanged(int);
private:
- QColor mCurrentFrontColor{ 33, 33, 33 };
+ QColor mCurrentFrontColor{ 33, 33, 33, 255 };
int mCurrentColorIndex = 0;
bool mIsWorkingOnVectorLayer = false;
};
diff --git a/core_lib/src/managers/layermanager.cpp b/core_lib/src/managers/layermanager.cpp
index 4e15539ca..a94130beb 100644
--- a/core_lib/src/managers/layermanager.cpp
+++ b/core_lib/src/managers/layermanager.cpp
@@ -48,8 +48,8 @@ Status LayerManager::load(Object* o)
Status LayerManager::save(Object* o)
{
- o->data()->setCurrentLayer(editor()->currentLayerIndex());
- return Status::OK;
+ o->data()->setCurrentLayer(editor()->currentLayerIndex());
+ return Status::OK;
}
Layer* LayerManager::getLastCameraLayer()
@@ -59,7 +59,7 @@ Layer* LayerManager::getLastCameraLayer()
{
return layer;
}
-
+
// it's not a camera layer
std::vector camLayers = object()->getLayersByType();
if (camLayers.size() > 0)
@@ -74,13 +74,13 @@ Layer* LayerManager::currentLayer()
return currentLayer(0);
}
-Layer* LayerManager::currentLayer( int incr )
+Layer* LayerManager::currentLayer(int incr)
{
- Q_ASSERT( object() != NULL );
+ Q_ASSERT(object() != NULL);
return object()->getLayer(editor()->currentLayerIndex() + incr);
}
-Layer* LayerManager::getLayer( int index )
+Layer* LayerManager::getLayer(int index)
{
Q_ASSERT(object() != NULL);
return object()->getLayer(index);
@@ -96,14 +96,14 @@ int LayerManager::currentLayerIndex()
return editor()->currentLayerIndex();
}
-void LayerManager::setCurrentLayer( int layerIndex )
+void LayerManager::setCurrentLayer(int layerIndex)
{
Q_ASSERT(layerIndex >= 0);
Object* o = object();
if (layerIndex >= o->getLayerCount())
{
- Q_ASSERT( false );
+ Q_ASSERT(false);
return;
}
@@ -113,9 +113,9 @@ void LayerManager::setCurrentLayer( int layerIndex )
Q_EMIT currentLayerChanged(layerIndex);
}
- if ( object() )
+ if (object())
{
- if ( object()->getLayer( layerIndex )->type() == Layer::CAMERA )
+ if (object()->getLayer(layerIndex)->type() == Layer::CAMERA)
{
mLastCameraLayerIdx = layerIndex;
}
@@ -129,71 +129,71 @@ void LayerManager::setCurrentLayer(Layer* layer)
void LayerManager::gotoNextLayer()
{
- if (editor()->currentLayerIndex() < object()->getLayerCount() - 1 )
+ if (editor()->currentLayerIndex() < object()->getLayerCount() - 1)
{
editor()->setCurrentLayerIndex(editor()->currentLayerIndex() + 1);
- Q_EMIT currentLayerChanged(editor()->currentLayerIndex());
+ Q_EMIT currentLayerChanged(editor()->currentLayerIndex());
}
}
void LayerManager::gotoPreviouslayer()
{
- if (editor()->currentLayerIndex() > 0 )
+ if (editor()->currentLayerIndex() > 0)
{
- editor()->setCurrentLayerIndex(editor()->currentLayerIndex() - 1);
- Q_EMIT currentLayerChanged(editor()->currentLayerIndex());
+ editor()->setCurrentLayerIndex(editor()->currentLayerIndex() - 1);
+ Q_EMIT currentLayerChanged(editor()->currentLayerIndex());
}
}
-LayerBitmap* LayerManager::createBitmapLayer( const QString& strLayerName )
+LayerBitmap* LayerManager::createBitmapLayer(const QString& strLayerName)
{
LayerBitmap* layer = object()->addNewBitmapLayer();
- layer->setName( strLayerName );
-
- Q_EMIT layerCountChanged( count() );
-
+ layer->setName(strLayerName);
+
+ Q_EMIT layerCountChanged(count());
+
return layer;
}
-LayerVector* LayerManager::createVectorLayer( const QString& strLayerName )
+LayerVector* LayerManager::createVectorLayer(const QString& strLayerName)
{
LayerVector* layer = object()->addNewVectorLayer();
- layer->setName( strLayerName );
-
- Q_EMIT layerCountChanged( count() );
-
+ layer->setName(strLayerName);
+
+ Q_EMIT layerCountChanged(count());
+
return layer;
}
-LayerCamera* LayerManager::createCameraLayer( const QString& strLayerName )
+LayerCamera* LayerManager::createCameraLayer(const QString& strLayerName)
{
LayerCamera* layer = object()->addNewCameraLayer();
- layer->setName( strLayerName );
-
- Q_EMIT layerCountChanged( count() );
-
+ layer->setName(strLayerName);
+
+ Q_EMIT layerCountChanged(count());
+
return layer;
}
-LayerSound* LayerManager::createSoundLayer( const QString& strLayerName )
+LayerSound* LayerManager::createSoundLayer(const QString& strLayerName)
{
LayerSound* layer = object()->addNewSoundLayer();
- layer->setName( strLayerName );
-
- Q_EMIT layerCountChanged( count() );
+ layer->setName(strLayerName);
+
+ Q_EMIT layerCountChanged(count());
return layer;
}
-int LayerManager::LastFrameAtFrame( int frameIndex )
+int LayerManager::LastFrameAtFrame(int frameIndex)
{
Object* o = object();
- for ( int i = frameIndex; i >= 0; i -= 1 )
+ for (int i = frameIndex; i >= 0; i -= 1)
{
- for ( int layerIndex = 0; layerIndex < o->getLayerCount(); ++layerIndex )
+ for (int layerIndex = 0; layerIndex < o->getLayerCount(); ++layerIndex)
{
- auto pLayer = o->getLayer( layerIndex );
- if ( pLayer->keyExists( i ) )
+ auto pLayer = o->getLayer(layerIndex);
+ if (pLayer->keyExists(i))
{
return i;
}
@@ -207,12 +207,12 @@ int LayerManager::firstKeyFrameIndex()
int minPosition = INT_MAX;
Object* o = object();
- for ( int i = 0; i < o->getLayerCount(); ++i )
+ for (int i = 0; i < o->getLayerCount(); ++i)
{
- Layer* pLayer = o->getLayer( i );
+ Layer* pLayer = o->getLayer(i);
int position = pLayer->firstKeyFramePosition();
- if ( position < minPosition )
+ if (position < minPosition)
{
minPosition = position;
}
@@ -224,12 +224,12 @@ int LayerManager::lastKeyFrameIndex()
{
int maxPosition = 0;
- for ( int i = 0; i < object()->getLayerCount(); ++i )
+ for (int i = 0; i < object()->getLayerCount(); ++i)
{
- Layer* pLayer = object()->getLayer( i );
+ Layer* pLayer = object()->getLayer(i);
int position = pLayer->getMaxKeyFramePosition();
- if ( position > maxPosition )
+ if (position > maxPosition)
{
maxPosition = position;
}
@@ -248,20 +248,21 @@ Status LayerManager::deleteLayer(int index)
if (layer->type() == Layer::CAMERA)
{
std::vector camLayers = object()->getLayersByType();
- if ( camLayers.size() == 1 )
+ if (camLayers.size() == 1)
return Status::ERROR_NEED_AT_LEAST_ONE_CAMERA_LAYER;
}
- object()->deleteLayer( layer );
+ object()->deleteLayer(layer);
// current layer is the last layer && we are deleting it
if (index == object()->getLayerCount() &&
index == currentLayerIndex())
{
- setCurrentLayer( currentLayerIndex() - 1 );
+ setCurrentLayer(currentLayerIndex() - 1);
}
- Q_EMIT layerCountChanged( count() );
+ Q_EMIT layerDeleted(index);
+ Q_EMIT layerCountChanged(count());
return Status::OK;
}
@@ -289,7 +290,7 @@ int LayerManager::animationLength(bool includeSounds)
int maxFrame = -1;
Object* o = object();
- for ( int i = 0; i < o->getLayerCount(); i++ )
+ for (int i = 0; i < o->getLayerCount(); i++)
{
if (o->getLayer(i)->type() == Layer::SOUND)
{
diff --git a/core_lib/src/managers/layermanager.h b/core_lib/src/managers/layermanager.h
index 87f6cf82c..886818536 100644
--- a/core_lib/src/managers/layermanager.h
+++ b/core_lib/src/managers/layermanager.h
@@ -70,9 +70,10 @@ class LayerManager : public BaseManager
void notifyAnimationLengthChanged();
Q_SIGNALS:
- void currentLayerChanged(int n);
+ void currentLayerChanged(int index);
void layerCountChanged(int count);
void animationLengthChanged(int length);
+ void layerDeleted(int index);
private:
int getIndex(Layer*) const;
diff --git a/core_lib/src/managers/playbackmanager.cpp b/core_lib/src/managers/playbackmanager.cpp
index 9ebca3cd9..a8234460d 100644
--- a/core_lib/src/managers/playbackmanager.cpp
+++ b/core_lib/src/managers/playbackmanager.cpp
@@ -18,13 +18,14 @@ GNU General Public License for more details.
#include "playbackmanager.h"
#include
+#include
+#include
#include "object.h"
#include "editor.h"
#include "layersound.h"
#include "layermanager.h"
-#include "soundmanager.h"
#include "soundclip.h"
-#include "soundplayer.h"
+
PlaybackManager::PlaybackManager(Editor* editor) : BaseManager(editor)
{
diff --git a/core_lib/src/managers/preferencemanager.cpp b/core_lib/src/managers/preferencemanager.cpp
index 43fed126a..3c3bc9d0f 100644
--- a/core_lib/src/managers/preferencemanager.cpp
+++ b/core_lib/src/managers/preferencemanager.cpp
@@ -34,14 +34,14 @@ bool PreferenceManager::init()
return true;
}
-Status PreferenceManager::load( Object* )
+Status PreferenceManager::load(Object*)
{
return Status::OK;
}
-Status PreferenceManager::save( Object * )
+Status PreferenceManager::save(Object*)
{
- return Status::OK;
+ return Status::OK;
}
void PreferenceManager::loadPrefs()
@@ -49,96 +49,91 @@ void PreferenceManager::loadPrefs()
QSettings settings( PENCIL2D, PENCIL2D );
// Display
- set( SETTING::GRID, settings.value( SETTING_SHOW_GRID, false ).toBool() );
- set( SETTING::INVISIBLE_LINES, settings.value( SETTING_INVISIBLE_LINES, false ).toBool() );
- set( SETTING::OUTLINES, settings.value( SETTING_OUTLINES, false ).toBool() );
+ set(SETTING::GRID, settings.value(SETTING_SHOW_GRID, false).toBool());
+ set(SETTING::INVISIBLE_LINES, settings.value(SETTING_INVISIBLE_LINES, false).toBool());
+ set(SETTING::OUTLINES, settings.value(SETTING_OUTLINES, false).toBool());
// Grid
- set( SETTING::GRID_SIZE, settings.value( SETTING_GRID_SIZE, 50 ).toInt() );
+ set(SETTING::GRID_SIZE, settings.value(SETTING_GRID_SIZE, 50).toInt());
// General
- //
- set( SETTING::ANTIALIAS, settings.value( SETTING_ANTIALIAS, true ).toBool() );
- set( SETTING::TOOL_CURSOR, settings.value( SETTING_TOOL_CURSOR, true ).toBool() );
- set( SETTING::DOTTED_CURSOR, settings.value( SETTING_DOTTED_CURSOR, true ).toBool() );
- set( SETTING::HIGH_RESOLUTION, settings.value( SETTING_HIGH_RESOLUTION, true ).toBool() );
- set( SETTING::SHADOW, settings.value( SETTING_SHADOW, false ).toBool() );
- set( SETTING::QUICK_SIZING, settings.value( SETTING_QUICK_SIZING, true ).toBool() );
+ set(SETTING::ANTIALIAS, settings.value(SETTING_ANTIALIAS, true).toBool());
+ set(SETTING::TOOL_CURSOR, settings.value(SETTING_TOOL_CURSOR, true).toBool());
+ set(SETTING::DOTTED_CURSOR, settings.value(SETTING_DOTTED_CURSOR, true).toBool());
+ set(SETTING::HIGH_RESOLUTION, settings.value(SETTING_HIGH_RESOLUTION, true).toBool());
+ set(SETTING::SHADOW, settings.value(SETTING_SHADOW, false).toBool());
+ set(SETTING::QUICK_SIZING, settings.value(SETTING_QUICK_SIZING, true).toBool());
- set( SETTING::WINDOW_OPACITY, settings.value( SETTING_WINDOW_OPACITY, 0 ).toInt() );
- set( SETTING::CURVE_SMOOTHING, settings.value( SETTING_CURVE_SMOOTHING, 20 ).toInt() );
+ set(SETTING::WINDOW_OPACITY, settings.value(SETTING_WINDOW_OPACITY, 0).toInt());
+ set(SETTING::CURVE_SMOOTHING, settings.value(SETTING_CURVE_SMOOTHING, 20).toInt());
- set( SETTING::BACKGROUND_STYLE, settings.value( SETTING_BACKGROUND_STYLE, "white" ).toString() );
+ set(SETTING::BACKGROUND_STYLE, settings.value(SETTING_BACKGROUND_STYLE, "white").toString());
- set( SETTING::LAYOUT_LOCK, settings.value( SETTING_LAYOUT_LOCK, false ).toBool() );
+ set(SETTING::LAYOUT_LOCK, settings.value(SETTING_LAYOUT_LOCK, false).toBool());
// Files
- set( SETTING::AUTO_SAVE, settings.value( SETTING_AUTO_SAVE, true ).toBool() );
- set( SETTING::AUTO_SAVE_NUMBER, settings.value( SETTING_AUTO_SAVE_NUMBER, 25 ).toInt() );
+ set(SETTING::AUTO_SAVE, settings.value(SETTING_AUTO_SAVE, true ).toBool());
+ set(SETTING::AUTO_SAVE_NUMBER, settings.value(SETTING_AUTO_SAVE_NUMBER, 25).toInt());
// Timeline
- //
- set( SETTING::SHORT_SCRUB, settings.value( SETTING_SHORT_SCRUB, false ).toBool() );
- set( SETTING::FRAME_SIZE, settings.value( SETTING_FRAME_SIZE, 12 ).toInt() );
- set( SETTING::TIMELINE_SIZE, settings.value( SETTING_TIMELINE_SIZE, 240 ).toInt() );
- set( SETTING::DRAW_LABEL, settings.value( SETTING_DRAW_LABEL, false ).toBool() );
- set( SETTING::LABEL_FONT_SIZE, settings.value( SETTING_LABEL_FONT_SIZE, 12 ).toInt() );
+ set(SETTING::SHORT_SCRUB, settings.value(SETTING_SHORT_SCRUB, false ).toBool());
+ set(SETTING::FRAME_SIZE, settings.value(SETTING_FRAME_SIZE, 12).toInt());
+ set(SETTING::TIMELINE_SIZE, settings.value(SETTING_TIMELINE_SIZE, 240).toInt());
+ set(SETTING::DRAW_LABEL, settings.value(SETTING_DRAW_LABEL, false ).toBool());
+ set(SETTING::LABEL_FONT_SIZE, settings.value(SETTING_LABEL_FONT_SIZE, 12).toInt());
+
+ set( SETTING::DRAW_ON_EMPTY_FRAME_ACTION, settings.value( SETTING_DRAW_ON_EMPTY_FRAME_ACTION,
+ KEEP_DRAWING_ON_PREVIOUS_KEY).toInt() );
// Onion Skin
- //
- set( SETTING::PREV_ONION, settings.value( SETTING_PREV_ONION, false ).toBool() );
- set( SETTING::NEXT_ONION, settings.value( SETTING_NEXT_ONION, false ).toBool() );
- set( SETTING::MULTILAYER_ONION, settings.value( SETTING_MULTILAYER_ONION, false ).toBool() );
- set( SETTING::ONION_BLUE, settings.value( SETTING_ONION_BLUE, false ).toBool() );
- set( SETTING::ONION_RED, settings.value( SETTING_ONION_RED, false ).toBool() );
-
- set( SETTING::ONION_MAX_OPACITY, settings.value( SETTING_ONION_MAX_OPACITY, 50 ).toInt() );
- set( SETTING::ONION_MIN_OPACITY, settings.value( SETTING_ONION_MIN_OPACITY, 20 ).toInt() );
- set( SETTING::ONION_PREV_FRAMES_NUM, settings.value( SETTING_ONION_PREV_FRAMES_NUM, 5 ).toInt() );
- set( SETTING::ONION_NEXT_FRAMES_NUM, settings.value( SETTING_ONION_NEXT_FRAMES_NUM, 5 ).toInt() );
- set( SETTING::ONION_TYPE, settings.value( SETTING_ONION_TYPE, "relative" ).toString() );
-
- set( SETTING::LANGUAGE, settings.value( SETTING_LANGUAGE ).toString() );
-
- set( SETTING::AXIS, false );
-//#define DRAW_AXIS
-#ifdef DRAW_AXIS
- set( SETTING::AXIS, true );
-#endif
+ set(SETTING::PREV_ONION, settings.value(SETTING_PREV_ONION, false).toBool());
+ set(SETTING::NEXT_ONION, settings.value(SETTING_NEXT_ONION, false).toBool());
+ set(SETTING::MULTILAYER_ONION, settings.value(SETTING_MULTILAYER_ONION, false).toBool());
+ set(SETTING::ONION_BLUE, settings.value(SETTING_ONION_BLUE, false).toBool());
+ set(SETTING::ONION_RED, settings.value(SETTING_ONION_RED, false).toBool());
+
+ set(SETTING::ONION_MAX_OPACITY, settings.value(SETTING_ONION_MAX_OPACITY, 50).toInt());
+ set(SETTING::ONION_MIN_OPACITY, settings.value(SETTING_ONION_MIN_OPACITY, 20).toInt());
+ set(SETTING::ONION_PREV_FRAMES_NUM, settings.value(SETTING_ONION_PREV_FRAMES_NUM, 5).toInt());
+ set(SETTING::ONION_NEXT_FRAMES_NUM, settings.value(SETTING_ONION_NEXT_FRAMES_NUM, 5).toInt());
+ set(SETTING::ONION_WHILE_PLAYBACK, settings.value(SETTING_ONION_WHILE_PLAYBACK, 0).toInt());
+ set(SETTING::ONION_TYPE, settings.value(SETTING_ONION_TYPE, "relative").toString());
+
+ set(SETTING::LANGUAGE, settings.value(SETTING_LANGUAGE).toString());
}
-void PreferenceManager::turnOn( SETTING option )
+void PreferenceManager::turnOn(SETTING option)
{
- set( option, true );
+ set(option, true);
}
-void PreferenceManager::turnOff( SETTING option )
+void PreferenceManager::turnOff(SETTING option)
{
- set( option, false );
+ set(option, false);
}
-bool PreferenceManager::isOn( SETTING option )
+bool PreferenceManager::isOn(SETTING option)
{
- int optionId = static_cast< int >( option );
+ int optionId = static_cast(option);
return mBooleanSet.value(optionId, false);
}
-int PreferenceManager::getInt( SETTING option )
+int PreferenceManager::getInt(SETTING option)
{
int optionId = static_cast(option);
return mIntegerSet.value(optionId, -1);
}
-QString PreferenceManager::getString( SETTING option )
+QString PreferenceManager::getString(SETTING option)
{
- int optionId = static_cast< int >( option );
- if ( mIntegerSet.contains( optionId ) )
+ int optionId = static_cast(option);
+ if (mIntegerSet.contains(optionId))
{
- return QString::number( mIntegerSet.value( optionId, -1 ) );
+ return QString::number(mIntegerSet.value(optionId, -1));
}
- else if ( mBooleanSet.contains( optionId ) )
+ else if (mBooleanSet.contains(optionId))
{
- if ( mBooleanSet.value( optionId, false ) )
+ if (mBooleanSet.value(optionId, false))
{
return "true";
}
@@ -150,165 +145,163 @@ QString PreferenceManager::getString( SETTING option )
return mStringSet.value(optionId);
}
-
-// Set string value
-//
void PreferenceManager::set(SETTING option, QString value)
{
- QSettings settings( PENCIL2D, PENCIL2D );
- switch ( option )
+ QSettings settings(PENCIL2D, PENCIL2D);
+ switch (option)
{
case SETTING::BACKGROUND_STYLE:
- settings.setValue( SETTING_BACKGROUND_STYLE, value );
+ settings.setValue(SETTING_BACKGROUND_STYLE, value);
break;
case SETTING::ONION_TYPE:
- settings.setValue( SETTING_ONION_TYPE, value );
+ settings.setValue(SETTING_ONION_TYPE, value);
break;
case SETTING::LANGUAGE:
- settings.setValue( SETTING_LANGUAGE, value );
+ settings.setValue(SETTING_LANGUAGE, value);
break;
default:
break;
}
- int optionId = static_cast< int >( option );
- if ( mStringSet[ optionId ] != value )
+ int optionId = static_cast(option);
+ if (mStringSet[optionId] != value)
{
- mStringSet[ optionId ] = value;
- emit optionChanged( option );
+ mStringSet[optionId] = value;
+ emit optionChanged(option);
}
}
-// Set int value
-//
-void PreferenceManager::set( SETTING option, int value )
+void PreferenceManager::set(SETTING option, int value)
{
- QSettings settings( PENCIL2D, PENCIL2D );
- switch ( option )
+ QSettings settings(PENCIL2D, PENCIL2D);
+ switch (option)
{
case SETTING::WINDOW_OPACITY:
- settings.setValue( SETTING_WINDOW_OPACITY, value );
+ settings.setValue(SETTING_WINDOW_OPACITY, value);
break;
case SETTING::CURVE_SMOOTHING:
- settings.setValue( SETTING_CURVE_SMOOTHING, value );
+ settings.setValue(SETTING_CURVE_SMOOTHING, value);
break;
case SETTING::AUTO_SAVE_NUMBER:
- settings.setValue ( SETTING_AUTO_SAVE_NUMBER, value );
+ settings.setValue(SETTING_AUTO_SAVE_NUMBER, value);
break;
case SETTING::FRAME_SIZE:
if (value < 4) { value = 4; }
else if (value > 20) { value = 20; }
- settings.setValue ( SETTING_FRAME_SIZE, value );
+ settings.setValue(SETTING_FRAME_SIZE, value);
break;
case SETTING::TIMELINE_SIZE:
if (value < 2) { value = 2; }
- settings.setValue ( SETTING_TIMELINE_SIZE, value );
+ settings.setValue(SETTING_TIMELINE_SIZE, value);
break;
case SETTING::LABEL_FONT_SIZE:
if (value < 12) { value = 12; }
- settings.setValue ( SETTING_LABEL_FONT_SIZE, value );
+ settings.setValue(SETTING_LABEL_FONT_SIZE, value);
break;
case SETTING::ONION_MAX_OPACITY:
- settings.setValue ( SETTING_ONION_MAX_OPACITY, value );
+ settings.setValue(SETTING_ONION_MAX_OPACITY, value);
break;
case SETTING::ONION_MIN_OPACITY:
- settings.setValue ( SETTING_ONION_MIN_OPACITY, value );
+ settings.setValue(SETTING_ONION_MIN_OPACITY, value);
break;
case SETTING::ONION_PREV_FRAMES_NUM:
- settings.setValue ( SETTING_ONION_PREV_FRAMES_NUM, value );
+ settings.setValue(SETTING_ONION_PREV_FRAMES_NUM, value);
break;
case SETTING::ONION_NEXT_FRAMES_NUM:
- settings.setValue ( SETTING_ONION_NEXT_FRAMES_NUM, value );
+ settings.setValue(SETTING_ONION_NEXT_FRAMES_NUM, value);
break;
case SETTING::GRID_SIZE:
- settings.setValue ( SETTING_GRID_SIZE, value );
+ settings.setValue(SETTING_GRID_SIZE, value);
+ break;
+ case SETTING::DRAW_ON_EMPTY_FRAME_ACTION:
+ settings.setValue( SETTING_DRAW_ON_EMPTY_FRAME_ACTION, value);
+ break;
+ case SETTING::ONION_WHILE_PLAYBACK:
+ settings.setValue(SETTING_ONION_WHILE_PLAYBACK, value);
break;
default:
- Q_ASSERT( false );
+ Q_ASSERT(false);
break;
}
- int optionId = static_cast< int >( option );
- if ( mIntegerSet[ optionId ] != value )
+ int optionId = static_cast(option);
+ if (mIntegerSet[optionId] != value)
{
- mIntegerSet[ optionId ] = value;
- emit optionChanged( option );
+ mIntegerSet[optionId] = value;
+ emit optionChanged(option);
}
}
// Set bool value
//
-void PreferenceManager::set( SETTING option, bool value )
+void PreferenceManager::set(SETTING option, bool value)
{
- QSettings settings( PENCIL2D, PENCIL2D );
- switch ( option )
+ QSettings settings(PENCIL2D, PENCIL2D);
+ switch (option)
{
case SETTING::ANTIALIAS:
- settings.setValue( SETTING_ANTIALIAS, value );
+ settings.setValue(SETTING_ANTIALIAS, value);
break;
case SETTING::GRID:
- settings.setValue( SETTING_SHOW_GRID, value );
+ settings.setValue(SETTING_SHOW_GRID, value);
break;
case SETTING::SHADOW:
- settings.setValue ( SETTING_SHADOW, value );
+ settings.setValue(SETTING_SHADOW, value);
break;
case SETTING::PREV_ONION:
- settings.setValue ( SETTING_PREV_ONION, value );
+ settings.setValue(SETTING_PREV_ONION, value);
break;
case SETTING::NEXT_ONION:
- settings.setValue ( SETTING_NEXT_ONION, value );
+ settings.setValue(SETTING_NEXT_ONION, value);
break;
case SETTING::MULTILAYER_ONION:
- settings.setValue( SETTING_MULTILAYER_ONION, value);
- break;
- case SETTING::AXIS:
- settings.setValue ( SETTING_AXIS, value );
+ settings.setValue(SETTING_MULTILAYER_ONION, value);
break;
case SETTING::INVISIBLE_LINES:
- settings.setValue ( SETTING_INVISIBLE_LINES, value );
+ settings.setValue(SETTING_INVISIBLE_LINES, value);
break;
case SETTING::OUTLINES:
- settings.setValue ( SETTING_OUTLINES, value );
+ settings.setValue(SETTING_OUTLINES, value);
break;
case SETTING::ONION_BLUE:
- settings.setValue ( SETTING_ONION_BLUE, value );
+ settings.setValue(SETTING_ONION_BLUE, value);
break;
case SETTING::ONION_RED:
- settings.setValue ( SETTING_ONION_RED, value );
+ settings.setValue(SETTING_ONION_RED, value);
break;
case SETTING::TOOL_CURSOR:
- settings.setValue ( SETTING_TOOL_CURSOR, value );
+ settings.setValue(SETTING_TOOL_CURSOR, value);
break;
case SETTING::DOTTED_CURSOR:
- settings.setValue ( SETTING_DOTTED_CURSOR, value );
+ settings.setValue(SETTING_DOTTED_CURSOR, value);
break;
case SETTING::HIGH_RESOLUTION:
- settings.setValue ( SETTING_HIGH_RESOLUTION, value );
+ settings.setValue(SETTING_HIGH_RESOLUTION, value);
break;
case SETTING::AUTO_SAVE:
- settings.setValue ( SETTING_AUTO_SAVE, value );
+ settings.setValue(SETTING_AUTO_SAVE, value);
break;
case SETTING::SHORT_SCRUB:
- settings.setValue ( SETTING_SHORT_SCRUB, value );
+ settings.setValue(SETTING_SHORT_SCRUB, value);
break;
case SETTING::DRAW_LABEL:
- settings.setValue ( SETTING_DRAW_LABEL, value );
+ settings.setValue(SETTING_DRAW_LABEL, value);
break;
case SETTING::QUICK_SIZING:
- settings.setValue ( SETTING_QUICK_SIZING, value );
+ settings.setValue(SETTING_QUICK_SIZING, value);
break;
case SETTING::LAYOUT_LOCK:
- settings.setValue( SETTING_LAYOUT_LOCK, value );
+ settings.setValue(SETTING_LAYOUT_LOCK, value);
break;
default:
- Q_ASSERT( false );
+ Q_ASSERT(false);
break;
}
- int optionId = static_cast< int >( option );
- if ( mBooleanSet[ optionId ] != value )
+ int optionId = static_cast(option);
+ if (mBooleanSet[optionId] != value)
{
- mBooleanSet[ optionId ] = value;
- emit optionChanged( option );
+ mBooleanSet[optionId] = value;
+ emit optionChanged(option);
}
}
diff --git a/core_lib/src/managers/preferencemanager.h b/core_lib/src/managers/preferencemanager.h
index cf3ea4a0d..df816e428 100644
--- a/core_lib/src/managers/preferencemanager.h
+++ b/core_lib/src/managers/preferencemanager.h
@@ -31,7 +31,6 @@ enum class SETTING
SHADOW,
PREV_ONION,
NEXT_ONION,
- AXIS,
INVISIBLE_LINES,
OUTLINES,
ONION_BLUE,
@@ -53,15 +52,25 @@ enum class SETTING
ONION_MIN_OPACITY,
ONION_PREV_FRAMES_NUM,
ONION_NEXT_FRAMES_NUM,
+ ONION_WHILE_PLAYBACK,
ONION_TYPE,
GRID_SIZE,
QUICK_SIZING,
MULTILAYER_ONION,
LANGUAGE,
LAYOUT_LOCK,
+ DRAW_ON_EMPTY_FRAME_ACTION,
COUNT, // COUNT must always be the last one.
};
+// Actions for drawing on an empty frame.
+enum DrawOnEmptyFrameAction
+{
+ CREATE_NEW_KEY,
+ DUPLICATE_PREVIOUS_KEY,
+ KEEP_DRAWING_ON_PREVIOUS_KEY
+};
+
class PreferenceManager : public BaseManager
{
Q_OBJECT
@@ -71,29 +80,28 @@ class PreferenceManager : public BaseManager
~PreferenceManager();
virtual bool init() override;
- Status load( Object* ) override;
- Status save( Object* ) override;
+ Status load(Object*) override;
+ Status save(Object*) override;
void loadPrefs();
- void set(SETTING option, QString value );
- void set(SETTING option, int value );
- void set(SETTING option, bool value );
+ void set(SETTING option, QString value);
+ void set(SETTING option, int value);
+ void set(SETTING option, bool value);
- void turnOn(SETTING option );
- void turnOff(SETTING option );
- bool isOn(SETTING option );
+ void turnOn(SETTING option);
+ void turnOff(SETTING option);
+ bool isOn(SETTING option);
QString getString(SETTING option);
int getInt(SETTING option);
Q_SIGNALS:
- void optionChanged( SETTING e );
-
+ void optionChanged(SETTING e);
private:
- QHash< int, QString > mStringSet;
- QHash< int, int > mIntegerSet;
- QHash< int, bool > mBooleanSet;
+ QHash mStringSet;
+ QHash mIntegerSet;
+ QHash mBooleanSet;
};
#endif // PREFERENCEMANAGER_H
diff --git a/core_lib/src/managers/soundmanager.cpp b/core_lib/src/managers/soundmanager.cpp
index abb75057a..2ee060457 100644
--- a/core_lib/src/managers/soundmanager.cpp
+++ b/core_lib/src/managers/soundmanager.cpp
@@ -18,6 +18,7 @@ GNU General Public License for more details.
#include "soundmanager.h"
#include
+#include
#include "editor.h"
#include "object.h"
#include "layersound.h"
diff --git a/core_lib/src/managers/toolmanager.cpp b/core_lib/src/managers/toolmanager.cpp
index ef1152af9..92aca6853 100644
--- a/core_lib/src/managers/toolmanager.cpp
+++ b/core_lib/src/managers/toolmanager.cpp
@@ -90,12 +90,18 @@ void ToolManager::setCurrentTool(ToolType eToolType)
{
if (mCurrentTool != NULL)
{
- mCurrentTool->leavingThisTool();
+ leavingThisTool();
}
+
mCurrentTool = getTool(eToolType);
Q_EMIT toolChanged(eToolType);
}
+bool ToolManager::leavingThisTool()
+{
+ return mCurrentTool->leavingThisTool();
+}
+
void ToolManager::cleanupAllToolsData()
{
foreach(BaseTool* pTool, mToolSetHash.values())
@@ -110,16 +116,16 @@ void ToolManager::resetAllTools()
// Beta-testers should be recommended to reset before sending tool related issues.
// This can prevent from users to stop working on their project.
getTool(PEN)->properties.width = 1.5; // not supposed to use feather
- getTool(PEN)->properties.inpolLevel = -1;
+ getTool(PEN)->properties.stabilizerLevel = -1;
getTool(POLYLINE)->properties.width = 1.5; // PEN dependent
getTool(PENCIL)->properties.width = 1.0;
getTool(PENCIL)->properties.feather = -1.0; // locks feather usage (can be changed)
- getTool(PENCIL)->properties.inpolLevel = -1;
+ getTool(PENCIL)->properties.stabilizerLevel = -1;
getTool(ERASER)->properties.width = 25.0;
getTool(ERASER)->properties.feather = 50.0;
getTool(BRUSH)->properties.width = 15.0;
getTool(BRUSH)->properties.feather = 200.0;
- getTool(BRUSH)->properties.inpolLevel = -1;
+ getTool(BRUSH)->properties.stabilizerLevel = -1;
getTool(BRUSH)->properties.useFeather = false;
getTool(SMUDGE)->properties.width = 25.0;
getTool(SMUDGE)->properties.feather = 200.0;
@@ -201,10 +207,10 @@ void ToolManager::setAA(int usingAA)
Q_EMIT toolPropertyChanged(currentTool()->type(), ANTI_ALIASING);
}
-void ToolManager::setInpolLevel(int level)
+void ToolManager::setStabilizerLevel(int level)
{
- currentTool()->setInpolLevel(level);
- Q_EMIT toolPropertyChanged(currentTool()->type(), INTERPOLATION);
+ currentTool()->setStabilizerLevel(level);
+ Q_EMIT toolPropertyChanged(currentTool()->type(), STABILIZATION);
}
void ToolManager::setTolerance(int newTolerance)
diff --git a/core_lib/src/managers/toolmanager.h b/core_lib/src/managers/toolmanager.h
index ef4010bd6..cf2c5cbcb 100644
--- a/core_lib/src/managers/toolmanager.h
+++ b/core_lib/src/managers/toolmanager.h
@@ -40,6 +40,7 @@ class ToolManager : public BaseManager
void setDefaultTool();
void setCurrentTool(ToolType eToolType);
void cleanupAllToolsData();
+ bool leavingThisTool();
void tabletSwitchToEraser();
void tabletRestorePrevTool();
@@ -65,7 +66,7 @@ public slots:
void setBezier(bool);
void setPressure(bool);
void setAA(int);
- void setInpolLevel(int);
+ void setStabilizerLevel(int);
void setTolerance(int);
void setUseFillContour(bool);
diff --git a/core_lib/src/managers/viewmanager.cpp b/core_lib/src/managers/viewmanager.cpp
index e19958c2c..786a086cf 100644
--- a/core_lib/src/managers/viewmanager.cpp
+++ b/core_lib/src/managers/viewmanager.cpp
@@ -58,7 +58,7 @@ Status ViewManager::load(Object*)
Status ViewManager::save(Object* o)
{
o->data()->setCurrentView(mView);
- return Status();
+ return Status::OK;
}
void ViewManager::workingLayerChanged(Layer* layer)
diff --git a/core_lib/src/miniz.cpp b/core_lib/src/miniz.cpp
index 4ed5a36b2..4b640d0ac 100644
--- a/core_lib/src/miniz.cpp
+++ b/core_lib/src/miniz.cpp
@@ -31,7 +31,7 @@ typedef unsigned char mz_validate_uint32[sizeof(mz_uint32) == 4 ? 1 : -1];
typedef unsigned char mz_validate_uint64[sizeof(mz_uint64) == 8 ? 1 : -1];
#ifdef __cplusplus
-//extern "C" {
+extern "C" {
#endif
/* ------------------- zlib-style API's */
@@ -571,7 +571,7 @@ const char *mz_error(int err)
#endif /*MINIZ_NO_ZLIB_APIS */
#ifdef __cplusplus
-//}
+}
#endif
/*
@@ -630,7 +630,7 @@ const char *mz_error(int err)
#ifdef __cplusplus
-//extern "C" {
+extern "C" {
#endif
/* ------------------- Low-level Compression (independent from all decompression API's) */
@@ -1936,6 +1936,8 @@ tdefl_status tdefl_init(tdefl_compressor *d, tdefl_put_buf_func_ptr pPut_buf_fun
d->m_pSrc = NULL;
d->m_src_buf_left = 0;
d->m_out_buf_ofs = 0;
+ if (!(flags & TDEFL_NONDETERMINISTIC_PARSING_FLAG))
+ MZ_CLEAR_OBJ(d->m_dict);
memset(&d->m_huff_count[0][0], 0, sizeof(d->m_huff_count[0][0]) * TDEFL_MAX_HUFF_SYMBOLS_0);
memset(&d->m_huff_count[1][0], 0, sizeof(d->m_huff_count[1][0]) * TDEFL_MAX_HUFF_SYMBOLS_1);
return TDEFL_STATUS_OKAY;
@@ -2159,7 +2161,7 @@ void tdefl_compressor_free(tdefl_compressor *pComp)
#endif
#ifdef __cplusplus
-//}
+}
#endif
/**************************************************************************
*
@@ -2190,7 +2192,7 @@ void tdefl_compressor_free(tdefl_compressor *pComp)
#ifdef __cplusplus
-//extern "C" {
+extern "C" {
#endif
/* ------------------- Low-level Decompression (completely independent from all compression API's) */
@@ -2892,7 +2894,7 @@ void tinfl_decompressor_free(tinfl_decompressor *pDecomp)
}
#ifdef __cplusplus
-//}
+}
#endif
/**************************************************************************
*
@@ -3015,7 +3017,7 @@ static FILE *mz_freopen(const char *pPath, const char *pMode, FILE *pStream)
#define MZ_FFLUSH fflush
#define MZ_FREOPEN(p, m, s) freopen64(p, m, s)
#define MZ_DELETE_FILE remove
-#elif defined(__APPLE__) && _LARGEFILE64_SOURCE
+#elif defined(__APPLE__)
#ifndef MINIZ_NO_TIME
#include
#endif
@@ -3881,7 +3883,10 @@ mz_bool mz_zip_reader_init_file_v2(mz_zip_archive *pZip, const char *pFilename,
/* TODO: Better sanity check archive_size and the # of actual remaining bytes */
if (file_size < MZ_ZIP_END_OF_CENTRAL_DIR_HEADER_SIZE)
+ {
+ MZ_FCLOSE(pFile);
return mz_zip_set_error(pZip, MZ_ZIP_NOT_AN_ARCHIVE);
+ }
if (!mz_zip_reader_init_internal(pZip, flags))
{
@@ -6031,14 +6036,15 @@ mz_bool mz_zip_writer_add_mem_ex_v2(mz_zip_archive *pZip, const char *pArchive_n
mz_uint8 extra_data[MZ_ZIP64_MAX_CENTRAL_EXTRA_FIELD_SIZE];
mz_uint16 bit_flags = 0;
+ if ((int)level_and_flags < 0)
+ level_and_flags = MZ_DEFAULT_LEVEL;
+
if (uncomp_size || (buf_size && !(level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA)))
bit_flags |= MZ_ZIP_LDH_BIT_FLAG_HAS_LOCATOR;
if (!(level_and_flags & MZ_ZIP_FLAG_ASCII_FILENAME))
bit_flags |= MZ_ZIP_GENERAL_PURPOSE_BIT_FLAG_UTF8;
- if ((int)level_and_flags < 0)
- level_and_flags = MZ_DEFAULT_LEVEL;
level = level_and_flags & 0xF;
store_data_uncompressed = ((!level) || (level_and_flags & MZ_ZIP_FLAG_COMPRESSED_DATA));
diff --git a/core_lib/src/miniz.h b/core_lib/src/miniz.h
index 48c7f03dc..939add071 100644
--- a/core_lib/src/miniz.h
+++ b/core_lib/src/miniz.h
@@ -1,4 +1,4 @@
-/* miniz.c 2.0.6 beta - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing
+/* miniz.c 2.0.7 - public domain deflate/inflate, zlib-subset, ZIP reading/writing/appending, PNG writing
See "unlicense" statement at the end of this file.
Rich Geldreich , last updated Oct. 13, 2013
Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt
@@ -125,7 +125,7 @@
/* If MINIZ_NO_TIME is specified then the ZIP archive functions will not be able to get the current time, or */
/* get/set file times, and the C run-time funcs that get/set times won't be called. */
/* The current downside is the times written to your archives will be from 1979. */
-/*#define MINIZ_NO_TIME */
+#define MINIZ_NO_TIME
/* Define MINIZ_NO_ARCHIVE_APIS to disable all ZIP archive API's. */
/*#define MINIZ_NO_ARCHIVE_APIS */
@@ -185,7 +185,7 @@
#endif
#ifdef __cplusplus
-//extern "C" {
+extern "C" {
#endif
/* ------------------- zlib-style API Definitions. */
@@ -234,11 +234,11 @@ enum
MZ_DEFAULT_COMPRESSION = -1
};
-#define MZ_VERSION "10.0.1"
-#define MZ_VERNUM 0xA010
+#define MZ_VERSION "10.0.2"
+#define MZ_VERNUM 0xA020
#define MZ_VER_MAJOR 10
#define MZ_VER_MINOR 0
-#define MZ_VER_REVISION 1
+#define MZ_VER_REVISION 2
#define MZ_VER_SUBREVISION 0
#ifndef MINIZ_NO_ZLIB_APIS
@@ -465,7 +465,7 @@ typedef void *const voidpc;
#endif /* MINIZ_NO_ZLIB_APIS */
#ifdef __cplusplus
-//}
+}
#endif
#pragma once
#include
diff --git a/core_lib/src/movieexporter.cpp b/core_lib/src/movieexporter.cpp
index e19d705c4..2e4c26e11 100644
--- a/core_lib/src/movieexporter.cpp
+++ b/core_lib/src/movieexporter.cpp
@@ -24,80 +24,13 @@ GNU General Public License for more details.
#include
#include
#include
+#include
+
#include "object.h"
#include "layercamera.h"
#include "layersound.h"
#include "soundclip.h"
-// refs
-// http://www.topherlee.com/software/pcm-tut-wavformat.html
-// http://soundfile.sapp.org/doc/WaveFormat/
-//
-struct WavFileHeader
-{
- char riff[4];
- int32_t chuckSize;
- char format[4];
- char fmtID[4];
- int32_t fmtChuckSize;
- int16_t audioFormat;
- int16_t numChannels;
- int32_t sampleRate;
- int32_t byteRate;
- int16_t blockAlign;
- int16_t bitsPerSample;
- char dataChuckID[4];
- int32_t dataSize;
-
- void InitWithDefaultValues()
- {
- strncpy(riff, "RIFF", 4);
- chuckSize = 0;
- strncpy(format, "WAVE", 4);
- strncpy(fmtID, "fmt ", 4);
- fmtChuckSize = 16;
- audioFormat = 1; // 1 means PCM
- numChannels = 2; // stereo
- sampleRate = 44100;
- bitsPerSample = 16;
- blockAlign = (bitsPerSample * numChannels) / 8;
- byteRate = (sampleRate * bitsPerSample * numChannels) / 8;
-
- strncpy(dataChuckID, "data", 4);
- dataSize = 0;
- }
-};
-
-int16_t safeSumInt16(int16_t a, int16_t b)
-{
- int32_t a32 = static_cast(a);
- int32_t b32 = static_cast(b);
-
- if ((a32 + b32) > INT16_MAX)
- {
- return INT16_MAX;
- }
- else if ((a32 + b32) < INT16_MIN)
- {
- return INT16_MIN;
- }
- return a + b;
-}
-
-void skipUselessChucks(WavFileHeader& header, QFile& file)
-{
- // We only care about the 'data' chuck
- while (memcmp(header.dataChuckID, "data", 4) != 0)
- {
- int skipByteCount = header.dataSize;
- std::vector skipData(skipByteCount);
- file.read(skipData.data(), skipByteCount);
-
- file.read((char*)&header.dataChuckID, 4);
- file.read((char*)&header.dataSize, 4);
- }
-}
-
QString ffmpegLocation()
{
#ifdef _WIN32
@@ -171,8 +104,6 @@ Status MovieExporter::run(const Object* obj,
{
#ifdef _WIN32
qCritical() << "Please place ffmpeg.exe in " << ffmpegPath << " directory";
-#elif __APPLE__
- qCritical() << "Please place ffmpeg in " << ffmpegPath << " directory";
#else
qCritical() << "Please place ffmpeg in " << ffmpegPath << " directory";
#endif
@@ -244,22 +175,13 @@ Status MovieExporter::assembleAudio(const Object* obj,
std::function progress)
{
// Quicktime assemble call
- int startFrame = mDesc.startFrame;
- int endFrame = mDesc.endFrame;
- int fps = mDesc.fps;
+ const int startFrame = mDesc.startFrame;
+ const int endFrame = mDesc.endFrame;
+ const int fps = mDesc.fps;
Q_ASSERT(startFrame >= 0);
Q_ASSERT(endFrame >= startFrame);
- float lengthInSec = (endFrame - startFrame + 1) / (float)fps;
- qDebug() << "Audio Length = " << lengthInSec << " seconds";
-
- int32_t audioDataSize = 44100 * 2 * 2 * lengthInSec;
-
- std::vector audioData(audioDataSize / sizeof(int16_t));
-
- bool audioDataValid = false;
-
QDir dir(mTempWorkDir);
Q_ASSERT(dir.exists());
@@ -279,6 +201,9 @@ Status MovieExporter::assembleAudio(const Object* obj,
int clipCount = 0;
+ QString strCmd, filterComplex, amixInput;
+ strCmd += QString("\"%1\"").arg(ffmpegPath);
+
for (SoundClip* clip : allSoundClips)
{
if (mCanceled)
@@ -286,69 +211,32 @@ Status MovieExporter::assembleAudio(const Object* obj,
return Status::CANCELED;
}
- // convert audio file: 44100Hz sampling rate, stereo, signed 16 bit little endian
- // supported audio file types: wav, mp3, ogg... ( all file types supported by ffmpeg )
- QString strCmd;
- strCmd += QString("\"%1\"").arg(ffmpegPath);
- strCmd += QString(" -i \"%1\" ").arg(clip->fileName());
- strCmd += "-ar 44100 -acodec pcm_s16le -ac 2 -y ";
- strCmd += QString("\"%1\"").arg(tempAudioPath);
-
- executeFFMpeg(strCmd, [](float){});
- qDebug() << "audio file: " + tempAudioPath;
-
- // Read wav file header
- WavFileHeader header;
- QFile file(tempAudioPath);
- file.open(QIODevice::ReadOnly);
- file.read((char*)&header, sizeof(WavFileHeader));
-
- skipUselessChucks(header, file);
+ // Add sound file as input
+ strCmd += QString(" -i \"%1\"").arg(clip->fileName());
- int32_t audioSize = header.dataSize;
+ // Offset the sound to its correct position
+ // See https://superuser.com/questions/716320/ffmpeg-placing-audio-at-specific-location
+ filterComplex += QString("[%1:a:0] adelay=%2S|%2S [ad%1];")
+ .arg(clipCount).arg(qRound(44100.0 * (clip->pos() - 1) / fps));
+ amixInput += QString("[ad%1]").arg(clipCount);
- qDebug() << "audio len " << audioSize;
-
- // before calling malloc should check: audioSize < max credible value
- std::vector< int16_t > data(audioSize / sizeof(int16_t));
- file.read((char*)data.data(), audioSize);
- audioDataValid = true;
-
- float fframe = (float)clip->pos() / (float)fps;
- int delta = fframe * 44100 * 2;
- qDebug() << "audio delta " << delta;
-
- int indexMax = std::min(audioSize / 2, audioDataSize / 2 - delta);
-
- // audio files 'mixing': 'higher' sound layers overwrite 'lower' sound layers
- for (int i = 0; i < indexMax; i++)
- {
- audioData[i + delta] = safeSumInt16(audioData[i + delta], data[i]);
- }
-
- file.close();
-
- progress((float)clipCount / allSoundClips.size());
clipCount++;
}
-
- if (!audioDataValid)
- {
- return Status::SAFE;
- }
-
- // save mixed audio file ( will be used as audio stream )
- QFile file(mTempWorkDir + "/tmpaudio.wav");
- file.open(QIODevice::WriteOnly);
-
- WavFileHeader outputHeader;
- outputHeader.InitWithDefaultValues();
- outputHeader.dataSize = audioDataSize;
- outputHeader.chuckSize = 36 + audioDataSize;
-
- file.write((char*)&outputHeader, sizeof(outputHeader));
- file.write((char*)audioData.data(), audioDataSize);
- file.close();
+ // Output arguments
+ // Mix audio
+ strCmd += QString(" -filter_complex \"%1%2 amix=inputs=%3 [out]\"")
+ .arg(filterComplex).arg(amixInput).arg(clipCount);
+ // Convert audio file: 44100Hz sampling rate, stereo, signed 16 bit little endian
+ // Supported audio file types: wav, mp3, ogg... ( all file types supported by ffmpeg )
+ strCmd += " -ar 44100 -acodec pcm_s16le -ac 2 -map \"[out]\" -y";
+ // Trim audio
+ strCmd += QString(" -ss %1").arg((startFrame - 1) / static_cast(fps));
+ strCmd += QString(" -to %1").arg(endFrame / static_cast(fps));
+ // Output path
+ strCmd += " " + mTempWorkDir + "/tmpaudio.wav";
+
+ STATUS_CHECK(executeFFMpeg(strCmd, progress));
+ qDebug() << "audio file: " + tempAudioPath;
return Status::OK;
}
@@ -409,8 +297,6 @@ Status MovieExporter::generateMovie(
}
imageToExportBase.fill(bgColor);
- QTransform view = cameraLayer->getViewAtFrame(currentFrame);
-
QSize camSize = cameraLayer->getViewSize();
QTransform centralizeCamera;
centralizeCamera.translate(camSize.width() / 2, camSize.height() / 2);
@@ -432,7 +318,7 @@ Status MovieExporter::generateMovie(
QString strCmd = QString("\"%1\"").arg(ffmpegPath);
strCmd += QString(" -f rawvideo -pixel_format bgra");
- strCmd += QString(" -video_size %1x%2").arg(camSize.width()).arg(camSize.height());
+ strCmd += QString(" -video_size %1x%2").arg(exportSize.width()).arg(exportSize.height());
strCmd += QString(" -framerate %1").arg(mDesc.fps);
//strCmd += QString( " -r %1").arg( exportFps );
@@ -444,7 +330,6 @@ Status MovieExporter::generateMovie(
strCmd += QString(" -i \"%1\" ").arg(tempAudioPath);
}
- strCmd += QString(" -s %1x%2").arg(exportSize.width()).arg(exportSize.height());
if(strOutputFile.endsWith(".apng"))
{
strCmd += QString(" -plays %1").arg(loop ? "0" : "1");
@@ -477,10 +362,12 @@ Status MovieExporter::generateMovie(
QImage imageToExport = imageToExportBase.copy();
QPainter painter(&imageToExport);
+ QTransform view = cameraLayer->getViewAtFrame(currentFrame);
painter.setWorldTransform(view * centralizeCamera);
painter.setWindow(QRect(0, 0, camSize.width(), camSize.height()));
obj->paintImage(painter, currentFrame, false, true);
+ painter.end();
// Should use sizeInBytes instead of byteCount to support large images,
// but this is only supported in QT 5.10+
@@ -552,8 +439,6 @@ Status MovieExporter::generateGif(
}
imageToExportBase.fill(bgColor);
- QTransform view = cameraLayer->getViewAtFrame(currentFrame);
-
QSize camSize = cameraLayer->getViewSize();
QTransform centralizeCamera;
centralizeCamera.translate(camSize.width() / 2, camSize.height() / 2);
@@ -562,15 +447,13 @@ Status MovieExporter::generateGif(
QString strCmd = QString("\"%1\"").arg(ffmpegPath);
strCmd += QString(" -f rawvideo -pixel_format bgra");
- strCmd += QString(" -video_size %1x%2").arg(camSize.width()).arg(camSize.height());
+ strCmd += QString(" -video_size %1x%2").arg(exportSize.width()).arg(exportSize.height());
strCmd += QString(" -framerate %1").arg(mDesc.fps);
strCmd += " -i -";
strCmd += " -y";
- strCmd += QString(" -s %1x%2").arg(exportSize.width()).arg(exportSize.height());
-
strCmd += " -filter_complex \"[0:v]palettegen [p]; [0:v][p] paletteuse\"";
strCmd += QString(" -loop %1").arg(loop ? "0" : "-1");
@@ -598,6 +481,7 @@ Status MovieExporter::generateGif(
QImage imageToExport = imageToExportBase.copy();
QPainter painter(&imageToExport);
+ QTransform view = cameraLayer->getViewAtFrame(currentFrame);
painter.setWorldTransform(view * centralizeCamera);
painter.setWindow(QRect(0, 0, camSize.width(), camSize.height()));
diff --git a/core_lib/src/qminiz.cpp b/core_lib/src/qminiz.cpp
index 6ba54e189..f9302de6a 100644
--- a/core_lib/src/qminiz.cpp
+++ b/core_lib/src/qminiz.cpp
@@ -38,53 +38,68 @@ bool MiniZ::isZip(const QString& sZipFilePath)
return (num > 0);
}
-bool MiniZ::compressFolder(QString sZipFilePath, QString sSrcPath)
+// ReSharper disable once CppInconsistentNaming
+Status MiniZ::compressFolder(QString zipFilePath, QString srcFolderPath, const QStringList& fileList)
{
- if (!sSrcPath.endsWith("/"))
+ DebugDetails dd;
+ dd << QString("Creating Zip %1 from folder %2").arg(zipFilePath).arg(srcFolderPath);
+
+ if (!srcFolderPath.endsWith("/"))
{
- sSrcPath.append("/");
+ srcFolderPath.append("/");
}
mz_zip_archive* mz = new mz_zip_archive;
OnScopeExit(delete mz);
mz_zip_zero_struct(mz);
- mz_bool ok = mz_zip_writer_init_file(mz, sZipFilePath.toUtf8().data(), 0);
-
- QDirIterator it(sSrcPath, QDirIterator::Subdirectories);
- while (it.hasNext())
+ mz_bool ok = mz_zip_writer_init_file(mz, zipFilePath.toUtf8().data(), 0);
+ if (!ok)
{
- QString sFullPath = it.next();
+ mz_zip_error err = mz_zip_get_last_error(mz);
+ dd << QString("Miniz writer init failed: %1").arg((int)err);
+ }
- if (it.fileInfo().isDir())
- {
- continue;
- }
+ //qDebug() << "SrcFolder=" << srcFolderPath;
+ for (const QString& filePath : fileList)
+ {
+ QString sRelativePath = filePath;
+ sRelativePath.replace(srcFolderPath, "");
- QString sRelativePath = sFullPath;
- sRelativePath.replace(sSrcPath, "");
+ dd << QString("Add file to zip: ").append(sRelativePath);
ok = mz_zip_writer_add_file(mz,
sRelativePath.toUtf8().data(),
- sFullPath.toUtf8().data(),
- "", 0, MZ_DEFAULT_COMPRESSION);
+ filePath.toUtf8().data(),
+ "", 0, MZ_BEST_SPEED);
if (!ok)
- break;
+ {
+ mz_zip_error err = mz_zip_get_last_error(mz);
+ dd << QString(" Cannot add %1: error %2, %3").arg(sRelativePath).arg((int)err).arg(mz_zip_get_error_string(err));
+ }
}
ok &= mz_zip_writer_finalize_archive(mz);
mz_zip_writer_end(mz);
- return ok;
+ if (!ok)
+ {
+ dd << "Miniz finalize archive failed";
+ return Status(Status::FAIL, dd);
+ }
+ return Status::OK;
}
-bool MiniZ::uncompressFolder(QString sZipFilePath, QString sDestPath)
+Status MiniZ::uncompressFolder(QString zipFilePath, QString destPath)
{
- if (!QFile::exists(sZipFilePath))
+ DebugDetails dd;
+ dd << QString("Unzip file %1 to folder %2").arg(zipFilePath).arg(destPath);
+
+ if (!QFile::exists(zipFilePath))
{
- return false;
+ return Status::FILE_NOT_FOUND;
}
- QString sBaseDir = QFileInfo(sDestPath).absolutePath();
+ QString sBaseDir = QFileInfo(destPath).absolutePath();
QDir baseDir(sBaseDir);
if (!baseDir.exists())
{
@@ -98,8 +113,9 @@ bool MiniZ::uncompressFolder(QString sZipFilePath, QString sDestPath)
OnScopeExit(delete mz);
mz_zip_zero_struct(mz);
- mz_bool ok = mz_zip_reader_init_file(mz, sZipFilePath.toUtf8().data(), 0);
- if (!ok) return false;
+ mz_bool ok = mz_zip_reader_init_file(mz, zipFilePath.toUtf8().data(), 0);
+ if (!ok)
+ return Status(Status::FAIL, dd);
int num = mz_zip_reader_get_num_files(mz);
@@ -113,8 +129,12 @@ bool MiniZ::uncompressFolder(QString sZipFilePath, QString sDestPath)
if (stat->m_is_directory)
{
QString sFolderPath = QString::fromUtf8(stat->m_filename);
- bool b = baseDir.mkpath(sFolderPath);
- Q_ASSERT(b);
+ dd << QString("Make Dir: ").append(sFolderPath);
+
+ bool mkDirOK = baseDir.mkpath(sFolderPath);
+ Q_ASSERT(mkDirOK);
+ if (!mkDirOK)
+ dd << " Make Dir failed.";
}
}
@@ -125,10 +145,16 @@ bool MiniZ::uncompressFolder(QString sZipFilePath, QString sDestPath)
if (!stat->m_is_directory)
{
QString sFullPath = baseDir.filePath(QString::fromUtf8(stat->m_filename));
+ dd << QString("Unzip file: ").append(sFullPath);
bool b = QFileInfo(sFullPath).absoluteDir().mkpath(".");
Q_ASSERT(b);
- ok &= mz_zip_reader_extract_to_file(mz, i, sFullPath.toUtf8(), 0);
+ bool extractOK = mz_zip_reader_extract_to_file(mz, i, sFullPath.toUtf8(), 0);
+ if (!extractOK)
+ {
+ ok = false;
+ dd << " File extraction failed.";
+ }
}
}
@@ -136,7 +162,7 @@ bool MiniZ::uncompressFolder(QString sZipFilePath, QString sDestPath)
if (!ok)
{
- qDebug() << "Unzip error!";
+ dd << "Unzip error!";
}
- return ok;
+ return Status::OK;
}
diff --git a/core_lib/src/qminiz.h b/core_lib/src/qminiz.h
index 17d3a53de..3395264a9 100644
--- a/core_lib/src/qminiz.h
+++ b/core_lib/src/qminiz.h
@@ -17,11 +17,12 @@ GNU General Public License for more details.
#define QMINIZ_H
#include
+#include "pencilerror.h"
namespace MiniZ
{
bool isZip(const QString& sZipFilePath);
- bool compressFolder(QString sZipFilePath, QString sSrcPath);
- bool uncompressFolder(QString sZipFilePath, QString sDestPath);
+ Status compressFolder(QString zipFilePath, QString srcFolderPath, const QStringList& fileList);
+ Status uncompressFolder(QString zipFilePath, QString destPath);
}
#endif
diff --git a/core_lib/src/structure/filemanager.cpp b/core_lib/src/structure/filemanager.cpp
index 1cac220b2..124dcff99 100644
--- a/core_lib/src/structure/filemanager.cpp
+++ b/core_lib/src/structure/filemanager.cpp
@@ -17,58 +17,80 @@ GNU General Public License for more details.
#include "filemanager.h"
+#include
+#include
#include "pencildef.h"
#include "qminiz.h"
#include "fileformat.h"
#include "object.h"
+#include "layercamera.h"
+
+
+QString openErrorTitle = QObject::tr("Could not open file");
+QString openErrorDesc = QObject::tr("There was an error processing your file. This usually means that your project has "
+ "been at least partially corrupted. You can try again with a newer version of Pencil2D, "
+ "or you can try to use a backup file if you have one. If you contact us through one of "
+ "our offical channels we may be able to help you. For reporting issues, "
+ "the best places to reach us are:");
+QString contactLinks = "";
FileManager::FileManager(QObject *parent) : QObject(parent),
mLog("FileManager")
{
ENABLE_DEBUG_LOG(mLog, false);
+ srand(time(nullptr));
}
-Object* FileManager::load(QString strFileName)
+Object* FileManager::load(QString sFileName)
{
- if (!QFile::exists(strFileName))
+ DebugDetails dd;
+ dd << QString("File name: ").append(sFileName);
+ if (!QFile::exists(sFileName))
{
qCDebug(mLog) << "ERROR - File doesn't exist.";
- return cleanUpWithErrorCode(Status::FILE_NOT_FOUND);
+ return cleanUpWithErrorCode(Status(Status::FILE_NOT_FOUND, dd, tr("Could not open file"),
+ tr("The file does not exist, so we are unable to open it. Please check "
+ "to make sure the path is correct and that the file is accessible and try again.")));
}
progressForward();
Object* obj = new Object;
- obj->setFilePath(strFileName);
+ obj->setFilePath(sFileName);
obj->createWorkingDir();
QString strMainXMLFile;
QString strDataFolder;
// Test file format: new zipped .pclx or old .pcl?
- bool oldFormat = isOldForamt(strFileName);
+ bool oldFormat = isOldForamt(sFileName);
+ dd << QString("Is old format: ").append(oldFormat ? "true" : "false");
if (oldFormat)
{
- qCDebug(mLog) << "Recognized Old Pencil File Format (*.pcl) !";
+ dd << "Recognized Old Pencil File Format (*.pcl) !";
- strMainXMLFile = strFileName;
+ strMainXMLFile = sFileName;
strDataFolder = strMainXMLFile + "." + PFF_OLD_DATA_DIR;
}
else
{
- qCDebug(mLog) << "Recognized New zipped Pencil File Format (*.pclx) !";
+ dd << "Recognized New zipped Pencil File Format (*.pclx) !";
- unzip(strFileName, obj->workingDir());
+ unzip(sFileName, obj->workingDir());
strMainXMLFile = QDir(obj->workingDir()).filePath(PFF_XML_FILE_NAME);
strDataFolder = QDir(obj->workingDir()).filePath(PFF_DATA_DIR);
}
- qCDebug(mLog) << " XML=" << strMainXMLFile;
- qCDebug(mLog) << " Data Folder=" << strDataFolder;
- qCDebug(mLog) << " Working Folder=" << obj->workingDir();
+ dd << QString("XML file: ").append(strMainXMLFile)
+ << QString("Data folder: ").append(strDataFolder)
+ << QString("Working folder: ").append(obj->workingDir());
obj->setDataDir(strDataFolder);
obj->setMainXMLFile(strMainXMLFile);
@@ -78,28 +100,38 @@ Object* FileManager::load(QString strFileName)
emit progressRangeChanged(mMaxProgressValue);
QFile file(strMainXMLFile);
+ if (!file.exists())
+ {
+ dd << "Main XML file does not exist";
+ return cleanUpWithErrorCode(Status(Status::ERROR_INVALID_XML_FILE, dd, openErrorTitle, openErrorDesc.append(contactLinks)));
+ }
if (!file.open(QFile::ReadOnly))
{
- return cleanUpWithErrorCode(Status::ERROR_FILE_CANNOT_OPEN);
+ return cleanUpWithErrorCode(Status(Status::ERROR_FILE_CANNOT_OPEN, dd, tr("Could not open file"),
+ tr("This program does not have permission to read the file you have selected. "
+ "Please check that you have read permissions for this file and try again.")));
}
QDomDocument xmlDoc;
if (!xmlDoc.setContent(&file))
{
qCDebug(mLog) << "Couldn't open the main XML file.";
- return cleanUpWithErrorCode(Status::ERROR_INVALID_XML_FILE);
+ dd << "Error parsing or opening the main XML file";
+ return cleanUpWithErrorCode(Status(Status::ERROR_INVALID_XML_FILE, dd, openErrorTitle, openErrorDesc.append(contactLinks)));
}
QDomDocumentType type = xmlDoc.doctype();
if (!(type.name() == "PencilDocument" || type.name() == "MyObject"))
{
- return cleanUpWithErrorCode(Status::ERROR_INVALID_PENCIL_FILE);
+ dd << QString("Invalid main XML doctype: ").append(type.name());
+ return cleanUpWithErrorCode(Status(Status::ERROR_INVALID_PENCIL_FILE, dd, openErrorTitle, openErrorDesc.append(contactLinks)));
}
QDomElement root = xmlDoc.documentElement();
if (root.isNull())
{
- return cleanUpWithErrorCode(Status::ERROR_INVALID_PENCIL_FILE);
+ dd << "Main XML root node is null";
+ return cleanUpWithErrorCode(Status(Status::ERROR_INVALID_PENCIL_FILE, dd, openErrorTitle, openErrorDesc.append(contactLinks)));
}
loadPalette(obj);
@@ -118,7 +150,8 @@ Object* FileManager::load(QString strFileName)
if (!ok)
{
delete obj;
- return cleanUpWithErrorCode(Status::ERROR_INVALID_PENCIL_FILE);
+ dd << "Issue occurred during object loading";
+ return cleanUpWithErrorCode(Status(Status::ERROR_INVALID_PENCIL_FILE, dd, ""));
}
verifyObject(obj);
@@ -130,11 +163,9 @@ bool FileManager::loadObject(Object* object, const QDomElement& root)
{
QDomElement e = root.firstChildElement("object");
if (e.isNull())
- {
return false;
- }
- bool isOK = true;
+ bool ok = true;
for (QDomNode node = root.firstChild(); !node.isNull(); node = node.nextSibling())
{
QDomElement element = node.toElement(); // try to convert the node to an element.
@@ -145,9 +176,9 @@ bool FileManager::loadObject(Object* object, const QDomElement& root)
if (element.tagName() == "object")
{
- isOK = object->loadXML(element, [this]{ progressForward(); });
+ ok = object->loadXML(element, [this]{ progressForward(); });
- if (!isOK) qCDebug(mLog) << "Failed to Load object";
+ if (!ok) qCDebug(mLog) << "Failed to Load object";
}
else if (element.tagName() == "editor" || element.tagName() == "projectdata")
@@ -160,8 +191,7 @@ bool FileManager::loadObject(Object* object, const QDomElement& root)
Q_ASSERT(false);
}
}
-
- return isOK;
+ return ok;
}
bool FileManager::loadObjectOldWay(Object* object, const QDomElement& root)
@@ -169,20 +199,21 @@ bool FileManager::loadObjectOldWay(Object* object, const QDomElement& root)
return object->loadXML(root, [this] { progressForward(); });
}
-bool FileManager::isOldForamt(const QString& fileName)
+bool FileManager::isOldForamt(const QString& fileName) const
{
- return (MiniZ::isZip(fileName) == false);
+ return !(MiniZ::isZip(fileName));
}
-Status FileManager::save(Object* object, QString strFileName)
+Status FileManager::save(Object* object, QString sFileName)
{
- QStringList debugDetails;
- debugDetails << "FileManager::save";
- debugDetails << QString("strFileName = ").append(strFileName);
+ DebugDetails dd;
+ dd << "FileManager::save";
+ dd << ("sFileName = " + sFileName);
if (object == nullptr)
{
- return Status(Status::INVALID_ARGUMENT, debugDetails << "object parameter is null");
+ dd << "object parameter is null";
+ return Status(Status::INVALID_ARGUMENT, dd);
}
int totalCount = object->totalKeyFrameCount();
@@ -191,114 +222,119 @@ Status FileManager::save(Object* object, QString strFileName)
progressForward();
- QFileInfo fileInfo(strFileName);
+ QFileInfo fileInfo(sFileName);
if (fileInfo.isDir())
{
- debugDetails << "FileName points to a directory";
- return Status(Status::INVALID_ARGUMENT,
- debugDetails,
+ dd << "FileName points to a directory";
+ return Status(Status::INVALID_ARGUMENT, dd,
tr("Invalid Save Path"),
tr("The path (\"%1\") points to a directory.").arg(fileInfo.absoluteFilePath()));
}
QFileInfo parentDirInfo(fileInfo.dir().absolutePath());
if (!parentDirInfo.exists())
{
- debugDetails << "The parent directory of strFileName does not exist";
- return Status(Status::INVALID_ARGUMENT,
- debugDetails,
+ dd << "The parent directory of sFileName does not exist";
+ return Status(Status::INVALID_ARGUMENT, dd,
tr("Invalid Save Path"),
tr("The directory (\"%1\") does not exist.").arg(parentDirInfo.absoluteFilePath()));
}
if ((fileInfo.exists() && !fileInfo.isWritable()) || !parentDirInfo.isWritable())
{
- debugDetails << "Filename points to a location that is not writable";
- return Status(Status::INVALID_ARGUMENT,
- debugDetails,
+ dd << "Filename points to a location that is not writable";
+ return Status(Status::INVALID_ARGUMENT, dd,
tr("Invalid Save Path"),
tr("The path (\"%1\") is not writable.").arg(fileInfo.absoluteFilePath()));
}
- QString strTempWorkingFolder;
- QString strMainXMLFile;
- QString strDataFolder;
+ QString sTempWorkingFolder;
+ QString sMainXMLFile;
+ QString sDataFolder;
- bool isOldFile = strFileName.endsWith(PFF_OLD_EXTENSION);
- if (isOldFile)
+ bool isOldType = sFileName.endsWith(PFF_OLD_EXTENSION);
+ if (isOldType)
{
- qCDebug(mLog) << "Old Pencil File Format (*.pcl) !";
+ dd << "Old Pencil File Format (*.pcl) !";
- strMainXMLFile = strFileName;
- strDataFolder = strMainXMLFile + "." + PFF_OLD_DATA_DIR;
+ sMainXMLFile = sFileName;
+ sDataFolder = sMainXMLFile + "." + PFF_OLD_DATA_DIR;
}
else
{
- qCDebug(mLog) << "New zipped Pencil File Format (*.pclx) !";
+ dd << "New zipped Pencil File Format (*.pclx) !";
- strTempWorkingFolder = object->workingDir();
- Q_ASSERT(QDir(strTempWorkingFolder).exists());
- debugDetails << QString("strTempWorkingFolder = ").append(strTempWorkingFolder);
+ sTempWorkingFolder = object->workingDir();
+ Q_ASSERT(QDir(sTempWorkingFolder).exists());
+ dd << QString("TempWorkingFolder = ").append(sTempWorkingFolder);
- qCDebug(mLog) << "Temp Folder=" << strTempWorkingFolder;
- strMainXMLFile = QDir(strTempWorkingFolder).filePath(PFF_XML_FILE_NAME);
- strDataFolder = QDir(strTempWorkingFolder).filePath(PFF_OLD_DATA_DIR);
+ sMainXMLFile = QDir(sTempWorkingFolder).filePath(PFF_XML_FILE_NAME);
+ sDataFolder = QDir(sTempWorkingFolder).filePath(PFF_OLD_DATA_DIR);
}
- QFileInfo dataInfo(strDataFolder);
+ QFileInfo dataInfo(sDataFolder);
if (!dataInfo.exists())
{
- QDir dir(strDataFolder); // the directory where all key frames will be saved
+ QDir dir(sDataFolder); // the directory where all key frames will be saved
- if (!dir.mkpath(strDataFolder))
+ if (!dir.mkpath(sDataFolder))
{
- debugDetails << QString("dir.absolutePath() = %1").arg(dir.absolutePath());
-
- return Status(Status::FAIL, debugDetails,
+ dd << QString("dir.absolutePath() = %1").arg(dir.absolutePath());
+ return Status(Status::FAIL, dd,
tr("Cannot Create Data Directory"),
- tr("Failed to create directory \"%1\". Please make sure you have sufficient permissions.").arg(strDataFolder));
+ tr("Failed to create directory \"%1\". Please make sure you have sufficient permissions.").arg(sDataFolder));
}
}
if (!dataInfo.isDir())
{
- debugDetails << QString("dataInfo.absoluteFilePath() = ").append(dataInfo.absoluteFilePath());
+ dd << QString("dataInfo.absoluteFilePath() = ").append(dataInfo.absoluteFilePath());
return Status(Status::FAIL,
- debugDetails,
+ dd,
tr("Cannot Create Data Directory"),
tr("\"%1\" is a file. Please delete the file and try again.").arg(dataInfo.absoluteFilePath()));
}
// save data
- int layerCount = object->getLayerCount();
- debugDetails << QString("layerCount = %1").arg(layerCount);
+ int numLayers = object->getLayerCount();
+ dd << QString("Total %1 layers").arg(numLayers);
- bool saveLayerOK = true;
- for (int i = 0; i < layerCount; ++i)
+ for (int i = 0; i < numLayers; ++i)
{
Layer* layer = object->getLayer(i);
- debugDetails << QString("layer[%1] = Layer[id=%2, name=%3, type=%4]").arg(i).arg(layer->id()).arg(layer->name()).arg(layer->type());
+ layer->presave(sDataFolder);
+ }
+
+ QStringList zippedFiles;
+
+ bool saveLayersOK = true;
+ for (int i = 0; i < numLayers; ++i)
+ {
+ Layer* layer = object->getLayer(i);
+
+ dd << QString("Layer[%1] = [id=%2, name=%3, type=%4]").arg(i).arg(layer->id()).arg(layer->name()).arg(layer->type());
- Status st = layer->save(strDataFolder, [this] { progressForward(); });
+ Status st = layer->save(sDataFolder, zippedFiles, [this] { progressForward(); });
if (!st.ok())
{
- saveLayerOK = false;
- QStringList layerDetails = st.detailsList();
- for (QString& detail : layerDetails)
- {
- detail.prepend(" ");
- }
- debugDetails << QString("- Layer[%1] failed to save").arg(i) << layerDetails;
+ saveLayersOK = false;
+ dd.collect(st.details());
+ dd << QString(" !! Failed to save Layer[%1] %2").arg(i).arg(layer->name());
}
}
+ dd << "All Layers saved";
// save palette
- object->savePalette(strDataFolder);
-
+ QString sPaletteFile = object->savePalette(sDataFolder);
+ if (!sPaletteFile.isEmpty())
+ zippedFiles.append(sPaletteFile);
+ else
+ dd << "Failed to save the palette xml";
+
progressForward();
// -------- save main XML file -----------
- QFile file(strMainXMLFile);
+ QFile file(sMainXMLFile);
if (!file.open(QFile::WriteOnly | QFile::Text))
{
- return Status::ERROR_FILE_CANNOT_OPEN;
+ return Status(Status::ERROR_FILE_CANNOT_OPEN, dd);
}
QDomDocument xmlDoc("PencilDocument");
@@ -310,43 +346,56 @@ Status FileManager::save(Object* object, QString strFileName)
progressForward();
// save editor information
- QDomElement projectDataElement = saveProjectData(object->data(), xmlDoc);
- root.appendChild(projectDataElement);
+ QDomElement projDataXml = saveProjectData(object->data(), xmlDoc);
+ root.appendChild(projDataXml);
// save object
QDomElement objectElement = object->saveXML(xmlDoc);
root.appendChild(objectElement);
- const int IndentSize = 2;
+ dd << "Writing main xml file...";
+
+ const int indentSize = 2;
QTextStream out(&file);
- xmlDoc.save(out, IndentSize);
+ xmlDoc.save(out, indentSize);
out.flush();
file.close();
+ dd << "Done writing main xml file at" << sMainXMLFile;
+
+ zippedFiles.append(sMainXMLFile);
+
progressForward();
- if (!isOldFile)
+ if (!isOldType)
{
- bool ok = MiniZ::compressFolder(strFileName, strTempWorkingFolder);
- if (!ok)
+ dd << "Miniz";
+
+ QString sBackupFile = backupPreviousFile(sFileName);
+
+ Status s = MiniZ::compressFolder(sFileName, sTempWorkingFolder, zippedFiles);
+ if (!s.ok())
{
- return Status(Status::ERROR_MINIZ_FAIL, debugDetails,
- tr("Internal Error"),
+ dd.collect(s.details());
+ return Status(Status::ERROR_MINIZ_FAIL, dd,
+ tr("Miniz Error"),
tr("An internal error occurred. Your file may not be saved successfully."));
}
- qCDebug(mLog) << "Compressed. File saved.";
+ dd << "Zip file saved successfully";
+
+ if (s.ok() && saveLayersOK)
+ deleteBackupFile(sBackupFile);
}
- object->setFilePath(strFileName);
+ object->setFilePath(sFileName);
object->setModified(false);
progressForward();
- if (!saveLayerOK)
+ if (!saveLayersOK)
{
- return Status(Status::FAIL,
- debugDetails,
+ return Status(Status::FAIL, dd,
tr("Internal Error"),
tr("An internal error occurred. Your file may not be saved successfully."));
}
@@ -379,7 +428,6 @@ ObjectData* FileManager::loadProjectData(const QDomElement& docElem)
return data;
}
-
QDomElement FileManager::saveProjectData(ObjectData* data, QDomDocument& xmlDoc)
{
QDomElement rootTag = xmlDoc.createElement("projectdata");
@@ -501,6 +549,32 @@ Object* FileManager::cleanUpWithErrorCode(Status error)
return nullptr;
}
+QString FileManager::backupPreviousFile(const QString& fileName)
+{
+ if (!QFile::exists(fileName))
+ return "";
+
+ QFileInfo info(fileName);
+ QString sBackupFile = info.completeBaseName() + ".backup." + info.suffix();
+ QString sBackupFileFullPath = QDir(info.absolutePath()).filePath(sBackupFile);
+
+ bool ok = QFile::rename(info.absoluteFilePath(), sBackupFileFullPath);
+ if (!ok)
+ {
+ qDebug() << "Cannot backup the previous file.";
+ return "";
+ }
+ return sBackupFileFullPath;
+}
+
+void FileManager::deleteBackupFile(const QString& fileName)
+{
+ if (QFile::exists(fileName))
+ {
+ QFile::remove(fileName);
+ }
+}
+
void FileManager::progressForward()
{
mCurrentProgress++;
@@ -511,7 +585,7 @@ bool FileManager::loadPalette(Object* obj)
{
qCDebug(mLog) << "Load Palette..";
- QString paletteFilePath = obj->dataDir() + "/" + PFF_PALETTE_FILE;
+ QString paletteFilePath = QDir(obj->dataDir()).filePath(PFF_PALETTE_FILE);
if (!obj->importPalette(paletteFilePath))
{
obj->loadDefaultPalette();
@@ -524,8 +598,8 @@ void FileManager::unzip(const QString& strZipFile, const QString& strUnzipTarget
// removes the previous directory first - better approach
removePFFTmpDirectory(strUnzipTarget);
- bool bOK = MiniZ::uncompressFolder(strZipFile, strUnzipTarget);
- Q_ASSERT(bOK);
+ Status s = MiniZ::uncompressFolder(strZipFile, strUnzipTarget);
+ Q_ASSERT(s.ok());
mstrLastTempFolder = strUnzipTarget;
}
@@ -551,6 +625,12 @@ Status FileManager::verifyObject(Object* obj)
{
obj->data()->setCurrentLayer(maxLayer - 1);
}
-
+
+ // Must have at least 1 camera layer
+ std::vector camLayers = obj->getLayersByType();
+ if (camLayers.empty())
+ {
+ obj->addNewCameraLayer();
+ }
return Status::OK;
}
diff --git a/core_lib/src/structure/filemanager.h b/core_lib/src/structure/filemanager.h
index 4dbc0e6ef..9ace6e863 100644
--- a/core_lib/src/structure/filemanager.h
+++ b/core_lib/src/structure/filemanager.h
@@ -38,11 +38,11 @@ class FileManager : public QObject
public:
FileManager(QObject* parent = 0);
- Object* load(QString strFilenNme);
- Status save(Object*, QString strFileName);
+ Object* load(QString sFilenNme);
+ Status save(Object*, QString sFileName);
QList loadPaletteFile(QString strFilename);
- Status error() { return mError; }
+ Status error() const { return mError; }
Status verifyObject(Object* obj);
Q_SIGNALS:
@@ -54,16 +54,18 @@ class FileManager : public QObject
bool loadObject(Object*, const QDomElement& root);
bool loadObjectOldWay(Object*, const QDomElement& root);
- bool isOldForamt(const QString& fileName);
+ bool isOldForamt(const QString& fileName) const;
bool loadPalette(Object*);
ObjectData* loadProjectData(const QDomElement& element);
QDomElement saveProjectData(ObjectData*, QDomDocument& xmlDoc);
void extractProjectData(const QDomElement& element, ObjectData* data);
-
Object* cleanUpWithErrorCode(Status);
+ QString backupPreviousFile(const QString& fileName);
+ void deleteBackupFile(const QString& fileName);
+
void progressForward();
private:
diff --git a/core_lib/src/structure/keyframe.h b/core_lib/src/structure/keyframe.h
index 8922a7836..2475c3950 100644
--- a/core_lib/src/structure/keyframe.h
+++ b/core_lib/src/structure/keyframe.h
@@ -55,6 +55,7 @@ class KeyFrame
virtual KeyFrame* clone() { return nullptr; }
virtual void loadFile() {};
virtual void unloadFile() {}
+ virtual bool isLoaded() { return true; }
private:
int mFrame = -1;
diff --git a/core_lib/src/structure/layer.cpp b/core_lib/src/structure/layer.cpp
index 094721343..f7ab8e3d6 100644
--- a/core_lib/src/structure/layer.cpp
+++ b/core_lib/src/structure/layer.cpp
@@ -17,9 +17,9 @@ GNU General Public License for more details.
#include "layer.h"
#include
+#include
#include "keyframe.h"
#include "object.h"
-#include "timeline.h"
#include "timelinecells.h"
@@ -125,10 +125,7 @@ int Layer::getPreviousFrameNumber(int position, bool isAbsolute) const
{
return -1; // There is no previous keyframe
}
- else
- {
- return prevNumber;
- }
+ return prevNumber;
}
int Layer::getNextFrameNumber(int position, bool isAbsolute) const
@@ -167,7 +164,7 @@ int Layer::getMaxKeyFramePosition() const
bool Layer::addNewKeyFrameAt(int position)
{
if (position <= 0) return false;
-
+
KeyFrame* key = createKeyFrame(position, mObject);
return addKeyFrame(position, key);
}
@@ -280,38 +277,41 @@ bool Layer::loadKey(KeyFrame* pKey)
return true;
}
-Status Layer::save(QString strDataFolder, ProgressCallback progressStep)
+Status Layer::save(const QString& sDataFolder, QStringList& attachedFiles, ProgressCallback progressStep)
{
- QStringList debugInfo;
- debugInfo << "Layer::save";
- debugInfo << QString("strDataFolder = ").append(strDataFolder);
+ DebugDetails dd;
+ dd << __FUNCTION__;
+
+ bool ok = true;
- bool isOkay = true;
for (auto pair : mKeyFrames)
{
- KeyFrame* pKeyFrame = pair.second;
- Status st = saveKeyFrameFile(pKeyFrame, strDataFolder);
- if (!st.ok())
+ KeyFrame* keyFrame = pair.second;
+ Status st = saveKeyFrameFile(keyFrame, sDataFolder);
+ if (st.ok())
{
- isOkay = false;
-
- QStringList keyFrameDetails = st.detailsList();
- for (QString detail : keyFrameDetails)
- {
- detail.prepend(" ");
- }
- debugInfo << QString("- Keyframe[%1] failed to save").arg(pKeyFrame->pos()) << keyFrameDetails;
+ //qDebug() << "Layer [" << name() << "] FN=" << keyFrame->fileName();
+ if (!keyFrame->fileName().isEmpty())
+ attachedFiles.append(keyFrame->fileName());
+ }
+ else
+ {
+ ok = false;
+ dd.collect(st.details());
+ dd << QString("- Keyframe[%1] failed to save").arg(keyFrame->pos());
}
progressStep();
}
- if (!isOkay)
+ if (!ok)
{
- return Status(Status::FAIL, debugInfo);
+ return Status(Status::FAIL, dd);
}
return Status::OK;
}
-void Layer::paintTrack(QPainter& painter, TimeLineCells* cells, int x, int y, int width, int height, bool selected, int frameSize)
+void Layer::paintTrack(QPainter& painter, TimeLineCells* cells,
+ int x, int y, int width, int height,
+ bool selected, int frameSize)
{
if (mVisible)
{
@@ -375,7 +375,9 @@ void Layer::paintFrames(QPainter& painter, TimeLineCells* cells, int y, int heig
}
}
-void Layer::paintLabel(QPainter& painter, TimeLineCells* cells, int x, int y, int width, int height, bool selected, int allLayers)
+void Layer::paintLabel(QPainter& painter, TimeLineCells* cells,
+ int x, int y, int width, int height,
+ bool selected, int allLayers)
{
Q_UNUSED(cells);
painter.setBrush(Qt::lightGray);
@@ -384,9 +386,9 @@ void Layer::paintLabel(QPainter& painter, TimeLineCells* cells, int x, int y, in
if (mVisible)
{
- if (allLayers == 0) painter.setBrush(Qt::NoBrush);
- if (allLayers == 1) painter.setBrush(Qt::darkGray);
- if ((allLayers == 2) || selected) painter.setBrush(Qt::black);
+ if (allLayers == 0) painter.setBrush(Qt::NoBrush);
+ if (allLayers == 1) painter.setBrush(Qt::darkGray);
+ if ((allLayers == 2) || selected) painter.setBrush(Qt::black);
}
else
{
@@ -424,30 +426,12 @@ void Layer::paintSelection(QPainter& painter, int x, int y, int width, int heigh
painter.drawRect(x, y, width, height - 1);
}
-void Layer::mousePress(QMouseEvent* event, int frameNumber)
-{
- Q_UNUSED(event);
- Q_UNUSED(frameNumber);
-}
-
void Layer::mouseDoubleClick(QMouseEvent* event, int frameNumber)
{
Q_UNUSED(event);
Q_UNUSED(frameNumber);
}
-void Layer::mouseMove(QMouseEvent* event, int frameNumber)
-{
- Q_UNUSED(event);
- Q_UNUSED(frameNumber);
-}
-
-void Layer::mouseRelease(QMouseEvent* event, int frameNumber)
-{
- Q_UNUSED(event);
- Q_UNUSED(frameNumber);
-}
-
void Layer::editProperties()
{
}
@@ -461,9 +445,9 @@ void Layer::setModified(int position, bool modified)
}
}
-bool Layer::isFrameSelected(int position)
+bool Layer::isFrameSelected(int position) const
{
- KeyFrame *keyFrame = getKeyFrameWhichCovers(position);
+ KeyFrame* keyFrame = getKeyFrameWhichCovers(position);
if (keyFrame)
{
return mSelectedFrames_byLast.contains(keyFrame->pos());
@@ -473,7 +457,7 @@ bool Layer::isFrameSelected(int position)
void Layer::setFrameSelected(int position, bool isSelected)
{
- KeyFrame *keyFrame = getKeyFrameWhichCovers(position);
+ KeyFrame* keyFrame = getKeyFrameWhichCovers(position);
if (keyFrame != nullptr)
{
int startPosition = keyFrame->pos();
@@ -481,14 +465,12 @@ void Layer::setFrameSelected(int position, bool isSelected)
if (isSelected && !mSelectedFrames_byLast.contains(startPosition))
{
// Add the selected frame to the lists
- //
mSelectedFrames_byLast.insert(0, startPosition);
mSelectedFrames_byPosition.append(startPosition);
// We need to keep the list of selected frames sorted
// in order to easily handle their movement
std::sort(mSelectedFrames_byPosition.begin(), mSelectedFrames_byPosition.end(), sortAsc);
-
}
else if (!isSelected)
{
@@ -507,7 +489,8 @@ void Layer::toggleFrameSelected(int position, bool allowMultiple)
{
bool wasSelected = isFrameSelected(position);
- if (!allowMultiple) {
+ if (!allowMultiple)
+ {
deselectAll();
}
@@ -656,20 +639,9 @@ bool Layer::moveSelectedFrames(int offset)
return false;
}
-bool Layer::isPaintable()
+bool Layer::isPaintable() const
{
- switch (type())
- {
- case Layer::BITMAP:
- case Layer::VECTOR:
- return true;
- case Layer::CAMERA:
- case Layer::SOUND:
- return false;
- default:
- break;
- }
- return false;
+ return (type() == BITMAP || type() == VECTOR);
}
bool Layer::keyExistsWhichCovers(int frameNumber)
@@ -677,7 +649,7 @@ bool Layer::keyExistsWhichCovers(int frameNumber)
return getKeyFrameWhichCovers(frameNumber) != nullptr;
}
-KeyFrame *Layer::getKeyFrameWhichCovers(int frameNumber)
+KeyFrame* Layer::getKeyFrameWhichCovers(int frameNumber) const
{
auto keyFrame = getLastKeyFrameAtPosition(frameNumber);
if (keyFrame != nullptr)
diff --git a/core_lib/src/structure/layer.h b/core_lib/src/structure/layer.h
index b0f2a6919..2b076bd41 100644
--- a/core_lib/src/structure/layer.h
+++ b/core_lib/src/structure/layer.h
@@ -19,9 +19,11 @@ GNU General Public License for more details.
#include