diff --git a/CMakeLists.txt b/CMakeLists.txt
index f83b1a26f..68d4f0fc0 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -70,6 +70,7 @@ option(USE_SHARED_FREETYPE "Use shared FreeType library" off)
option(USE_SHARED_ALLEGRO4 "Use shared Allegro 4 library (without resize support)" off)
option(ENABLE_MEMLEAK "Enable memory-leaks detector (only for developers)" off)
option(ENABLE_UPDATER "Enable automatic check for updates" on)
+option(ENABLE_SCRIPTING "Compile with scripting support" on)
option(ENABLE_WEBSERVER "Enable support to run a webserver (for HTML5 gamedev)" off)
option(ENABLE_TESTS "Enable the unit tests" off)
option(ENABLE_TRIAL_MODE "Compile the trial version" off)
@@ -163,9 +164,16 @@ else()
add_definitions(-DNDEBUG)
endif()
-# Fix to compile gtest with VC11 (2012)
-if(MSVC_VERSION EQUAL 1700)
- add_definitions(-D_VARIADIC_MAX=10)
+if(MSVC)
+ if(USE_STATIC_LIBC)
+ if(CMAKE_BUILD_TYPE STREQUAL Debug)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -MTd")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -MTd")
+ else()
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -MT")
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -MT")
+ endif()
+ endif()
endif()
if(NOT WIN32 AND NOT APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
@@ -438,6 +446,10 @@ if(ENABLE_MEMLEAK)
add_definitions(-DLAF_MEMLEAK)
endif()
+if(NOT USE_STATIC_LIBC)
+ set(gtest_force_shared_crt ON CACHE BOOL "Use shared (DLL) run-time lib even when Google Test is built as static lib.")
+endif()
+
set(LAF_WITH_TESTS ${ENABLE_TESTS} CACHE BOOL "Enable LAF tests")
add_subdirectory(laf)
diff --git a/README.md b/README.md
index ee2332d1a..61f441f11 100644
--- a/README.md
+++ b/README.md
@@ -77,8 +77,7 @@ And it uses the following third-party libraries:
* [Allegro 4](http://alleg.sourceforge.net/) - [allegro4 license](https://github.com/aseprite/aseprite/tree/master/docs/licenses/allegro4-LICENSE.txt)
* [FreeType](http://www.freetype.org/) - [FTL license](https://github.com/aseprite/aseprite/tree/master/docs/licenses/FTL.txt)
-* [Google Test](https://github.com/google/googletest) - [gtest license](https://github.com/aseprite/aseprite/tree/master/docs/licenses/gtest-LICENSE.txt)
-* [XFree86](http://www.x.org/) - [XFree86 license](https://github.com/aseprite/aseprite/tree/master/docs/licenses/XFree86-LICENSE.txt)
+* [Google Test](https://github.com/google/googletest) - [BSD-like license](https://github.com/aseprite/googletest/blob/master/googletest/LICENSE)
* [curl](http://curl.haxx.se/) - [curl license](https://github.com/aseprite/aseprite/tree/master/docs/licenses/curl-LICENSE.txt)
* [duktape](http://duktape.org/) - [MIT license](https://github.com/aseprite/aseprite/tree/master/third_party/duktape/LICENSE.txt)
* [giflib](http://sourceforge.net/projects/giflib/) - [giflib license](https://github.com/aseprite/aseprite/tree/master/docs/licenses/giflib-LICENSE.txt)
@@ -86,9 +85,9 @@ And it uses the following third-party libraries:
* [libpng](http://www.libpng.org/pub/png/) - [libpng license](https://github.com/aseprite/aseprite/tree/master/docs/licenses/libpng-LICENSE.txt)
* [libwebp](https://developers.google.com/speed/webp/) - [libwebp license](https://chromium.googlesource.com/webm/libwebp/+/master/COPYING)
* [loadpng](http://tjaden.strangesoft.net/loadpng/) - [zlib license](https://github.com/aseprite/aseprite/tree/master/docs/licenses/ZLIB.txt)
-* [modp_b64](https://github.com/aseprite/aseprite/tree/master/third_party/modp_b64/modp_b64.h) - [BSD license](https://github.com/aseprite/aseprite/tree/master/third_party/modp_b64/LICENSE)
* [pixman](http://www.pixman.org/) - [MIT license](http://cgit.freedesktop.org/pixman/plain/COPYING)
* [simpleini](https://github.com/aseprite/simpleini/) - [MIT license](https://github.com/aseprite/simpleini/blob/aseprite/LICENCE.txt)
+* [stringencoders](https://github.com/client9/stringencoders) - [MIT license](https://github.com/aseprite/stringencoders/blob/master/LICENSE)
* [tinyxml](http://www.sourceforge.net/projects/tinyxml) - [zlib license](https://github.com/aseprite/aseprite/tree/master/docs/licenses/ZLIB.txt)
* [zlib](http://www.gzip.org/zlib/) - [ZLIB license](https://github.com/aseprite/aseprite/tree/master/docs/licenses/ZLIB.txt)
@@ -103,8 +102,7 @@ This program is distributed under three different licenses:
(e.g. [laf](https://github.com/aseprite/laf),
[clip](https://github.com/aseprite/clip),
[she](https://github.com/aseprite/aseprite/tree/master/src/she),
- [gfx](https://github.com/aseprite/gfx),
- [ui](https://github.com/aseprite/ui), etc.).
+ [gfx](src/gfx), [ui](src/ui), etc.).
2. You can request a special
[educational license](http://www.aseprite.org/faq/#is-there-an-educational-license)
in case you are a teacher in an educational institution and want to
diff --git a/data/icons/ase16.png b/data/icons/ase16.png
index 2389bef4f..e53241253 100644
Binary files a/data/icons/ase16.png and b/data/icons/ase16.png differ
diff --git a/data/icons/ase32.png b/data/icons/ase32.png
index 5af641971..da6c76459 100644
Binary files a/data/icons/ase32.png and b/data/icons/ase32.png differ
diff --git a/data/icons/ase48.png b/data/icons/ase48.png
index 90352b0a6..9583341ff 100644
Binary files a/data/icons/ase48.png and b/data/icons/ase48.png differ
diff --git a/data/icons/ase64.png b/data/icons/ase64.png
index 4d16e4282..1624dd8da 100644
Binary files a/data/icons/ase64.png and b/data/icons/ase64.png differ
diff --git a/data/icons/doc16.png b/data/icons/doc16.png
index be0174b74..b3ee801d7 100644
Binary files a/data/icons/doc16.png and b/data/icons/doc16.png differ
diff --git a/data/icons/doc32.png b/data/icons/doc32.png
index a4bafdb01..2bfe477fa 100644
Binary files a/data/icons/doc32.png and b/data/icons/doc32.png differ
diff --git a/data/icons/doc48.png b/data/icons/doc48.png
index 208651b2b..f6e913b15 100644
Binary files a/data/icons/doc48.png and b/data/icons/doc48.png differ
diff --git a/data/icons/doc64.png b/data/icons/doc64.png
index 5e6431373..19869ab1e 100644
Binary files a/data/icons/doc64.png and b/data/icons/doc64.png differ
diff --git a/data/pref.xml b/data/pref.xml
index b27cefb9a..cad993b7e 100644
--- a/data/pref.xml
+++ b/data/pref.xml
@@ -99,7 +99,7 @@
-
+
@@ -278,6 +278,9 @@
+
diff --git a/data/skins/default/font.png b/data/skins/default/font.png
index 5d50f5b9c..87362d72d 100644
Binary files a/data/skins/default/font.png and b/data/skins/default/font.png differ
diff --git a/data/widgets/open_sequence.xml b/data/widgets/open_sequence.xml
new file mode 100644
index 000000000..0fcae0707
--- /dev/null
+++ b/data/widgets/open_sequence.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/data/widgets/options.xml b/data/widgets/options.xml
index 5b566a257..850fac377 100644
--- a/data/widgets/options.xml
+++ b/data/widgets/options.xml
@@ -43,6 +43,9 @@
+
+
+
@@ -79,6 +82,10 @@
+
+
+
+
diff --git a/data/widgets/palette_popup.xml b/data/widgets/palette_popup.xml
index 2c2d2b5f9..362c52e90 100644
--- a/data/widgets/palette_popup.xml
+++ b/data/widgets/palette_popup.xml
@@ -1,13 +1,13 @@
-
+
diff --git a/data/widgets/timeline_conf.xml b/data/widgets/timeline_conf.xml
index 5ef14fe4c..1ecde2b5c 100644
--- a/data/widgets/timeline_conf.xml
+++ b/data/widgets/timeline_conf.xml
@@ -2,30 +2,35 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/laf b/laf
index 11274341a..42ad03a9a 160000
--- a/laf
+++ b/laf
@@ -1 +1 @@
-Subproject commit 11274341a6390f9dda085256f98567156aa21cc9
+Subproject commit 42ad03a9a30ebe619674aaf07ef27cf442133903
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 7cc357584..40f8242af 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -97,10 +97,14 @@ add_subdirectory(gfx)
add_subdirectory(net)
add_subdirectory(render)
add_subdirectory(docio)
-add_subdirectory(script)
add_subdirectory(she)
add_subdirectory(ui)
+if(ENABLE_SCRIPTING)
+ add_subdirectory(script)
+ add_definitions(-DENABLE_SCRIPTING)
+endif()
+
if(ENABLE_UPDATER)
add_subdirectory(updater)
endif()
diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt
index f2b69aba2..88f301da9 100644
--- a/src/app/CMakeLists.txt
+++ b/src/app/CMakeLists.txt
@@ -89,6 +89,23 @@ if(WITH_WEBP_SUPPORT)
list(APPEND file_formats file/webp_format.cpp)
endif()
+set(scripting_files)
+if(ENABLE_SCRIPTING)
+ set(scripting_files
+ commands/cmd_developer_console.cpp
+ commands/cmd_run_script.cpp
+ script/app_object.cpp
+ script/app_scripting.cpp
+ script/console_object.cpp
+ script/image_class.cpp
+ script/image_wrap.cpp
+ script/selection_class.cpp
+ script/sprite_class.cpp
+ script/sprite_wrap.cpp
+ shell.cpp
+ ui/devconsole_view.cpp)
+endif()
+
add_library(app-lib
app.cpp
app_brushes.cpp
@@ -191,7 +208,6 @@ add_library(app-lib
commands/cmd_crop.cpp
commands/cmd_cut.cpp
commands/cmd_deselect_mask.cpp
- commands/cmd_developer_console.cpp
commands/cmd_discard_brush.cpp
commands/cmd_duplicate_layer.cpp
commands/cmd_duplicate_sprite.cpp
@@ -253,7 +269,6 @@ add_library(app-lib
commands/cmd_reselect_mask.cpp
commands/cmd_reverse_frames.cpp
commands/cmd_rotate.cpp
- commands/cmd_run_script.cpp
commands/cmd_save_file.cpp
commands/cmd_save_mask.cpp
commands/cmd_save_palette.cpp
@@ -332,17 +347,8 @@ add_library(app-lib
resource_finder.cpp
restore_visible_layers.cpp
rw_lock.cpp
- script/app_object.cpp
- script/app_scripting.cpp
- script/console_object.cpp
- script/image_class.cpp
- script/image_wrap.cpp
- script/selection_class.cpp
- script/sprite_class.cpp
- script/sprite_wrap.cpp
send_crash.cpp
shade.cpp
- shell.cpp
snap_to_grid.cpp
thumbnail_generator.cpp
tools/active_tool.cpp
@@ -371,7 +377,6 @@ add_library(app-lib
ui/color_wheel.cpp
ui/configure_timeline_popup.cpp
ui/context_bar.cpp
- ui/devconsole_view.cpp
ui/document_view.cpp
ui/drop_down_button.cpp
ui/editor/brush_preview.cpp
@@ -455,6 +460,7 @@ add_library(app-lib
xml_document.cpp
xml_exception.cpp
${data_recovery_files}
+ ${scripting_files}
${generated_files})
target_link_libraries(app-lib
@@ -470,7 +476,6 @@ target_link_libraries(app-lib
gfx-lib
net-lib
render-lib
- script-lib
she
ui-lib
undo
@@ -482,6 +487,10 @@ target_link_libraries(app-lib
${ZLIB_LIBRARIES}
${FREETYPE_LIBRARIES})
+if(ENABLE_SCRIPTING)
+ target_link_libraries(app-lib script-lib)
+endif()
+
if(ENABLE_UPDATER)
target_link_libraries(app-lib updater-lib)
endif()
diff --git a/src/app/app.cpp b/src/app/app.cpp
index 14c4612ec..456e59a74 100644
--- a/src/app/app.cpp
+++ b/src/app/app.cpp
@@ -32,9 +32,7 @@
#include "app/pref/preferences.h"
#include "app/recent_files.h"
#include "app/resource_finder.h"
-#include "app/script/app_scripting.h"
#include "app/send_crash.h"
-#include "app/shell.h"
#include "app/tools/active_tool.h"
#include "app/tools/tool_box.h"
#include "app/ui/backup_indicator.h"
@@ -58,7 +56,7 @@
#include "base/unique_ptr.h"
#include "doc/site.h"
#include "doc/sprite.h"
-#include "script/engine_delegate.h"
+#include "render/render.h"
#include "she/display.h"
#include "she/error.h"
#include "she/system.h"
@@ -67,6 +65,12 @@
#include
+#ifdef ENABLE_SCRIPTING
+ #include "app/script/app_scripting.h"
+ #include "app/shell.h"
+ #include "script/engine_delegate.h"
+#endif
+
#ifdef ENABLE_STEAM
#include "steam/steam.h"
#endif
@@ -258,6 +262,7 @@ void App::run()
ui::Manager::getDefault()->run();
}
+#ifdef ENABLE_SCRIPTING
// Start shell to execute scripts.
if (m_isShell) {
script::StdoutEngineDelegate delegate;
@@ -266,6 +271,7 @@ void App::run()
Shell shell;
shell.run(engine);
}
+#endif
// Destroy all documents in the UIContext.
const doc::Documents& docs = m_modules->m_ui_context.documents();
diff --git a/src/app/cli/app_options.cpp b/src/app/cli/app_options.cpp
index 0acd2d20a..127cb5e91 100644
--- a/src/app/cli/app_options.cpp
+++ b/src/app/cli/app_options.cpp
@@ -26,7 +26,9 @@ AppOptions::AppOptions(int argc, const char* argv[])
, m_showHelp(false)
, m_showVersion(false)
, m_verboseLevel(kNoVerbose)
+#ifdef ENABLE_SCRIPTING
, m_shell(m_po.add("shell").description("Start an interactive console to execute scripts"))
+#endif
, m_batch(m_po.add("batch").mnemonic('b').description("Do not start the UI"))
, m_preview(m_po.add("preview").mnemonic('p').description("Do not execute actions, just print what will be\ndone"))
, m_saveAs(m_po.add("save-as").requiresValue("").description("Save the last given sprite with other format"))
@@ -54,7 +56,9 @@ AppOptions::AppOptions(int argc, const char* argv[])
, m_trim(m_po.add("trim").description("Trim all images before exporting"))
, m_crop(m_po.add("crop").requiresValue("x,y,width,height").description("Crop all the images to the given rectangle"))
, m_filenameFormat(m_po.add("filename-format").requiresValue("").description("Special format to generate filenames"))
+#ifdef ENABLE_SCRIPTING
, m_script(m_po.add("script").requiresValue("").description("Execute a specific script"))
+#endif
, m_listLayers(m_po.add("list-layers").description("List layers of the next given sprite\nor include layers in JSON data"))
, m_listTags(m_po.add("list-tags").description("List tags of the next given sprite sprite\nor include frame tags in JSON data"))
, m_verbose(m_po.add("verbose").mnemonic('v').description("Explain what is being done"))
@@ -70,7 +74,9 @@ AppOptions::AppOptions(int argc, const char* argv[])
else if (m_po.enabled(m_verbose))
m_verboseLevel = kVerbose;
+#ifdef ENABLE_SCRIPTING
m_startShell = m_po.enabled(m_shell);
+#endif
m_previewCLI = m_po.enabled(m_preview);
m_showHelp = m_po.enabled(m_help);
m_showVersion = m_po.enabled(m_version);
diff --git a/src/app/cli/app_options.h b/src/app/cli/app_options.h
index 45d9b377f..aeb6c9a13 100644
--- a/src/app/cli/app_options.h
+++ b/src/app/cli/app_options.h
@@ -70,7 +70,9 @@ public:
const Option& trim() const { return m_trim; }
const Option& crop() const { return m_crop; }
const Option& filenameFormat() const { return m_filenameFormat; }
+#ifdef ENABLE_SCRIPTING
const Option& script() const { return m_script; }
+#endif
const Option& listLayers() const { return m_listLayers; }
const Option& listTags() const { return m_listTags; }
@@ -86,7 +88,9 @@ private:
bool m_showVersion;
VerboseLevel m_verboseLevel;
+#ifdef ENABLE_SCRIPTING
Option& m_shell;
+#endif
Option& m_batch;
Option& m_preview;
Option& m_saveAs;
@@ -114,7 +118,9 @@ private:
Option& m_trim;
Option& m_crop;
Option& m_filenameFormat;
+#ifdef ENABLE_SCRIPTING
Option& m_script;
+#endif
Option& m_listLayers;
Option& m_listTags;
diff --git a/src/app/cli/cli_processor.cpp b/src/app/cli/cli_processor.cpp
index 56daf8b97..2877345e3 100644
--- a/src/app/cli/cli_processor.cpp
+++ b/src/app/cli/cli_processor.cpp
@@ -353,11 +353,13 @@ void CliProcessor::process()
}
}
}
+#ifdef ENABLE_SCRIPTING
// --script
else if (opt == &m_options.script()) {
std::string filename = value.value();
m_delegate->execScript(filename);
}
+#endif
// --list-layers
else if (opt == &m_options.listLayers()) {
cof.listLayers = true;
diff --git a/src/app/cli/default_cli_delegate.cpp b/src/app/cli/default_cli_delegate.cpp
index f8f139498..c404dd01f 100644
--- a/src/app/cli/default_cli_delegate.cpp
+++ b/src/app/cli/default_cli_delegate.cpp
@@ -18,14 +18,17 @@
#include "app/document.h"
#include "app/document_exporter.h"
#include "app/file/palette_file.h"
-#include "app/script/app_scripting.h"
#include "app/ui_context.h"
#include "base/convert_to.h"
#include "doc/frame_tag.h"
#include "doc/layer.h"
#include "doc/palette.h"
#include "doc/sprite.h"
-#include "script/engine_delegate.h"
+
+#ifdef ENABLE_SCRIPTING
+ #include "app/script/app_scripting.h"
+ #include "script/engine_delegate.h"
+#endif
#include
@@ -114,9 +117,11 @@ void DefaultCliDelegate::exportFiles(DocumentExporter& exporter)
void DefaultCliDelegate::execScript(const std::string& filename)
{
+#ifdef ENABLE_SCRIPTING
script::StdoutEngineDelegate delegate;
AppScripting engine(&delegate);
engine.evalFile(filename);
+#endif
}
} // namespace app
diff --git a/src/app/cmd/add_layer.cpp b/src/app/cmd/add_layer.cpp
index 7067cea24..87240a293 100644
--- a/src/app/cmd/add_layer.cpp
+++ b/src/app/cmd/add_layer.cpp
@@ -86,7 +86,7 @@ void AddLayer::removeLayer(Layer* group, Layer* layer)
static_cast(group)->removeLayer(layer);
group->incrementVersion();
- layer->sprite()->incrementVersion();
+ group->sprite()->incrementVersion();
doc->notify_observers(&DocumentObserver::onAfterRemoveLayer, ev);
diff --git a/src/app/commands/cmd_developer_console.cpp b/src/app/commands/cmd_developer_console.cpp
index f1e368d4e..65e319eea 100644
--- a/src/app/commands/cmd_developer_console.cpp
+++ b/src/app/commands/cmd_developer_console.cpp
@@ -8,6 +8,10 @@
#include "config.h"
#endif
+#ifndef ENABLE_SCRIPTING
+ #error ENABLE_SCRIPTING must be defined
+#endif
+
#include "app/app.h"
#include "app/commands/command.h"
#include "app/ui/main_window.h"
diff --git a/src/app/commands/cmd_exit.cpp b/src/app/commands/cmd_exit.cpp
index b462e231f..27a1913bb 100644
--- a/src/app/commands/cmd_exit.cpp
+++ b/src/app/commands/cmd_exit.cpp
@@ -13,6 +13,7 @@
#include "app/commands/commands.h"
#include "app/context.h"
#include "app/document.h"
+#include "app/job.h"
#include "app/ui/main_window.h"
#include "ui/alert.h"
@@ -36,6 +37,11 @@ ExitCommand::ExitCommand()
void ExitCommand::onExecute(Context* ctx)
{
+ // Ignore ExitCommand when we are saving documents or doing a
+ // background task
+ if (Job::runningJobs() > 0)
+ return;
+
if (ctx->hasModifiedDocuments()) {
Command* closeAll = CommandsModule::instance()->getCommandByName(CommandId::CloseAllFiles);
Params params;
diff --git a/src/app/commands/cmd_frame_properties.cpp b/src/app/commands/cmd_frame_properties.cpp
index 4f502c86e..7ec67526c 100644
--- a/src/app/commands/cmd_frame_properties.cpp
+++ b/src/app/commands/cmd_frame_properties.cpp
@@ -9,11 +9,12 @@
#endif
#include "app/app.h"
-#include "app/context.h"
#include "app/commands/command.h"
#include "app/commands/params.h"
+#include "app/context.h"
#include "app/context_access.h"
#include "app/document_api.h"
+#include "app/pref/preferences.h"
#include "app/transaction.h"
#include "base/convert_to.h"
#include "doc/sprite.h"
@@ -66,7 +67,7 @@ void FramePropertiesCommand::onLoadParams(const Params& params)
}
else {
m_target = SPECIFIC_FRAME;
- m_frame = frame_t(base::convert_to(frame)-1);
+ m_frame = frame_t(base::convert_to(frame));
}
}
@@ -79,7 +80,8 @@ void FramePropertiesCommand::onExecute(Context* context)
{
const ContextReader reader(context);
const Sprite* sprite = reader.sprite();
-
+ auto& docPref = Preferences::instance().document(context->activeDocument());
+ int base = docPref.timeline.firstFrame();
app::gen::FrameProperties window;
SelectedFrames selFrames;
@@ -101,7 +103,7 @@ void FramePropertiesCommand::onExecute(Context* context)
}
case SPECIFIC_FRAME:
- selFrames.insert(m_frame);
+ selFrames.insert(m_frame-base);
break;
}
@@ -110,11 +112,11 @@ void FramePropertiesCommand::onExecute(Context* context)
return;
if (selFrames.size() == 1)
- window.frame()->setTextf("%d", selFrames.firstFrame()+1);
+ window.frame()->setTextf("%d", selFrames.firstFrame()+base);
else if (selFrames.ranges() == 1) {
window.frame()->setTextf("[%d...%d]",
- selFrames.firstFrame()+1,
- selFrames.lastFrame()+1);
+ selFrames.firstFrame()+base,
+ selFrames.lastFrame()+base);
}
else
window.frame()->setTextf("Multiple Frames");
diff --git a/src/app/commands/cmd_goto_frame.cpp b/src/app/commands/cmd_goto_frame.cpp
index 9c6028fd9..bd1611392 100644
--- a/src/app/commands/cmd_goto_frame.cpp
+++ b/src/app/commands/cmd_goto_frame.cpp
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2001-2015 David Capello
+// Copyright (C) 2001-2016 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@@ -137,22 +137,27 @@ class GotoFrameCommand : public GotoCommand {
public:
GotoFrameCommand() : GotoCommand("GotoFrame",
"Go to Frame")
- , m_frame(0) { }
+ , m_showUI(true) { }
Command* clone() const override { return new GotoFrameCommand(*this); }
protected:
void onLoadParams(const Params& params) override {
std::string frame = params.get("frame");
- if (!frame.empty()) m_frame = strtol(frame.c_str(), NULL, 10);
- else m_frame = 0;
+ if (!frame.empty()) {
+ m_frame = strtol(frame.c_str(), nullptr, 10);
+ m_showUI = false;
+ }
+ else
+ m_showUI = true;
}
frame_t onGetFrame(Editor* editor) override {
- if (m_frame == 0) {
+ auto& docPref = editor->docPref();
+
+ if (m_showUI) {
app::gen::GotoFrame window;
-
- window.frame()->setTextf("%d", editor->frame()+1);
-
+ window.frame()->setTextf(
+ "%d", editor->frame()+docPref.timeline.firstFrame());
window.openWindowInForeground();
if (window.closer() != window.ok())
return editor->frame();
@@ -160,12 +165,11 @@ protected:
m_frame = window.frame()->textInt();
}
- return MID(0, m_frame-1, editor->sprite()->lastFrame());
+ return MID(0, m_frame-docPref.timeline.firstFrame(), editor->sprite()->lastFrame());
}
private:
- // The frame to go. 0 is "show the UI dialog", another value is the
- // frame (1 is the first name for the user).
+ bool m_showUI;
int m_frame;
};
diff --git a/src/app/commands/cmd_keyboard_shortcuts.cpp b/src/app/commands/cmd_keyboard_shortcuts.cpp
index 53ca073de..35320e251 100644
--- a/src/app/commands/cmd_keyboard_shortcuts.cpp
+++ b/src/app/commands/cmd_keyboard_shortcuts.cpp
@@ -62,7 +62,7 @@ public:
KeyItem(const std::string& text, Key* key, AppMenuItem* menuitem, int level)
: ListItem(text)
, m_key(key)
- , m_keyOrig(key ? new Key(*key): NULL)
+ , m_keyOrig(key ? new Key(*key): nullptr)
, m_menuitem(menuitem)
, m_level(level)
, m_hotAccel(-1)
@@ -73,6 +73,10 @@ public:
setBorder(border);
}
+ ~KeyItem() {
+ delete m_keyOrig;
+ }
+
Key* key() { return m_key; }
AppMenuItem* menuitem() const { return m_menuitem; }
@@ -381,6 +385,10 @@ public:
}
}
+ ~KeyboardShortcutsWindow() {
+ deleteAllKeyItems();
+ }
+
void restoreKeys() {
for (KeyItem* keyItem : m_allKeyItems) {
keyItem->restoreKeys();
@@ -389,21 +397,12 @@ public:
private:
void deleteAllKeyItems() {
- while (searchList()->lastChild())
- searchList()->removeChild(searchList()->lastChild());
- while (menus()->lastChild())
- menus()->removeChild(menus()->lastChild());
- while (commands()->lastChild())
- commands()->removeChild(commands()->lastChild());
- while (tools()->lastChild())
- tools()->removeChild(tools()->lastChild());
- while (actions()->lastChild())
- actions()->removeChild(actions()->lastChild());
-
- for (KeyItem* keyItem : m_allKeyItems) {
- delete keyItem;
- }
- m_allKeyItems.clear();
+ deleteList(searchList());
+ deleteList(menus());
+ deleteList(commands());
+ deleteList(tools());
+ deleteList(actions());
+ ASSERT(m_allKeyItems.empty());
}
void fillAllLists() {
@@ -468,9 +467,21 @@ private:
updateViews();
}
+ void deleteList(Widget* listbox) {
+ while (listbox->lastChild()) {
+ Widget* item = listbox->lastChild();
+ listbox->removeChild(item);
+
+ auto it = std::find(m_allKeyItems.begin(), m_allKeyItems.end(), item);
+ if (it != m_allKeyItems.end())
+ m_allKeyItems.erase(it);
+
+ delete item;
+ }
+ }
+
void fillSearchList(const std::string& search) {
- while (searchList()->lastChild())
- searchList()->removeChild(searchList()->lastChild());
+ deleteList(searchList());
std::vector parts;
base::split_string(base::string_to_lower(search), parts, " ");
@@ -504,6 +515,8 @@ private:
new KeyItem(itemText,
keyItem->key(),
keyItem->menuitem(), 0);
+
+ m_allKeyItems.push_back(copyItem);
searchList()->addChild(copyItem);
}
}
@@ -586,6 +599,7 @@ private:
menuItem->text().c_str(),
menuItem->key(), menuItem, level);
+ m_allKeyItems.push_back(keyItem);
listbox->addChild(keyItem);
if (menuItem->hasSubmenu())
diff --git a/src/app/commands/cmd_open_file.cpp b/src/app/commands/cmd_open_file.cpp
index 9a50e1fb0..018d01d33 100644
--- a/src/app/commands/cmd_open_file.cpp
+++ b/src/app/commands/cmd_open_file.cpp
@@ -8,6 +8,8 @@
#include "config.h"
#endif
+#include "app/commands/cmd_open_file.h"
+
#include "app/app.h"
#include "app/commands/command.h"
#include "app/commands/params.h"
@@ -32,22 +34,7 @@
namespace app {
-class OpenFileCommand : public Command {
-public:
- OpenFileCommand();
- Command* clone() const override { return new OpenFileCommand(*this); }
-
-protected:
- void onLoadParams(const Params& params) override;
- void onExecute(Context* context) override;
-
-private:
- std::string m_filename;
- std::string m_folder;
-};
-
-class OpenFileJob : public Job, public IFileOpProgress
-{
+class OpenFileJob : public Job, public IFileOpProgress {
public:
OpenFileJob(FileOp* fop)
: Job("Loading file")
@@ -91,6 +78,8 @@ OpenFileCommand::OpenFileCommand()
: Command("OpenFile",
"Open Sprite",
CmdRecordableFlag)
+ , m_repeatCheckbox(false)
+ , m_seqDecision(SequenceDecision::Ask)
{
}
@@ -98,12 +87,15 @@ void OpenFileCommand::onLoadParams(const Params& params)
{
m_filename = params.get("filename");
m_folder = params.get("folder"); // Initial folder
+ m_repeatCheckbox = (params.get("repeat_checkbox") == "true");
}
void OpenFileCommand::onExecute(Context* context)
{
Console console;
+ m_usedFiles.clear();
+
// interactive
if (context->isUIAvailable() && m_filename.empty()) {
std::string exts = get_readable_extensions();
@@ -118,9 +110,23 @@ void OpenFileCommand::onExecute(Context* context)
}
if (!m_filename.empty()) {
+ int flags = (m_repeatCheckbox ? FILE_LOAD_SEQUENCE_ASK_CHECKBOX: 0);
+
+ switch (m_seqDecision) {
+ case SequenceDecision::Ask:
+ flags |= FILE_LOAD_SEQUENCE_ASK;
+ break;
+ case SequenceDecision::Agree:
+ flags |= FILE_LOAD_SEQUENCE_YES;
+ break;
+ case SequenceDecision::Skip:
+ flags |= FILE_LOAD_SEQUENCE_NONE;
+ break;
+ }
+
base::UniquePtr fop(
FileOp::createLoadDocumentOperation(
- context, m_filename.c_str(), FILE_LOAD_SEQUENCE_ASK));
+ context, m_filename.c_str(), flags));
bool unrecent = false;
if (fop) {
@@ -129,6 +135,21 @@ void OpenFileCommand::onExecute(Context* context)
unrecent = true;
}
else {
+ if (fop->isSequence()) {
+
+ if (fop->sequenceFlags() & FILE_LOAD_SEQUENCE_YES) {
+ m_seqDecision = SequenceDecision::Agree;
+ }
+ else if (fop->sequenceFlags() & FILE_LOAD_SEQUENCE_NONE) {
+ m_seqDecision = SequenceDecision::Skip;
+ }
+
+ m_usedFiles = fop->filenames();
+ }
+ else {
+ m_usedFiles.push_back(fop->filename());
+ }
+
OpenFileJob task(fop);
task.showProgressWindow();
@@ -136,7 +157,7 @@ void OpenFileCommand::onExecute(Context* context)
fop->postLoad();
// Show any error
- if (fop->hasError())
+ if (fop->hasError() && !fop->isStop())
console.printf(fop->error().c_str());
Document* document = fop->document();
diff --git a/src/app/commands/cmd_open_file.h b/src/app/commands/cmd_open_file.h
new file mode 100644
index 000000000..62742fdd8
--- /dev/null
+++ b/src/app/commands/cmd_open_file.h
@@ -0,0 +1,53 @@
+// Aseprite
+// Copyright (C) 2016 David Capello
+//
+// This program is distributed under the terms of
+// the End-User License Agreement for Aseprite.
+
+#ifndef APP_COMMANDS_CMD_OPEN_FILE_H_INCLUDED
+#define APP_COMMANDS_CMD_OPEN_FILE_H_INCLUDED
+#pragma once
+
+#include "app/commands/command.h"
+
+#include
+#include
+
+namespace app {
+
+ class OpenFileCommand : public Command {
+ public:
+ enum class SequenceDecision {
+ Ask, Agree, Skip,
+ };
+
+ OpenFileCommand();
+ Command* clone() const override { return new OpenFileCommand(*this); }
+
+ SequenceDecision sequenceDecision() const {
+ return m_seqDecision;
+ }
+
+ void setSequenceDecision(SequenceDecision seqDecision) {
+ m_seqDecision = seqDecision;
+ }
+
+ const std::vector& usedFiles() const {
+ return m_usedFiles;
+ }
+
+ protected:
+ void onLoadParams(const Params& params) override;
+ void onExecute(Context* context) override;
+
+ private:
+ std::string m_filename;
+ std::string m_folder;
+ bool m_repeatCheckbox;
+ std::vector m_usedFiles;
+ SequenceDecision m_seqDecision;
+ };
+
+} // namespace app
+
+#endif
diff --git a/src/app/commands/cmd_options.cpp b/src/app/commands/cmd_options.cpp
index 977d58eac..a3ee62212 100644
--- a/src/app/commands/cmd_options.cpp
+++ b/src/app/commands/cmd_options.cpp
@@ -109,6 +109,8 @@ public:
if (m_pref.general.rewindOnStop())
rewindOnStop()->setSelected(true);
+ firstFrame()->setTextf("%d", m_globPref.timeline.firstFrame());
+
if (m_pref.general.expandMenubarOnMouseover())
expandMenubarOnMouseover()->setSelected(true);
@@ -158,9 +160,9 @@ public:
showScrollbars()->setSelected(true);
// Scope
- gridScope()->addItem("Global");
+ gridScope()->addItem("New Documents");
if (context->activeDocument()) {
- gridScope()->addItem("Current Document");
+ gridScope()->addItem("Active Document");
gridScope()->setSelectedItemIndex(1);
gridScope()->Change.connect(base::Bind(&OptionsWindow::onChangeGridScope, this));
}
@@ -244,6 +246,7 @@ public:
void saveConfig() {
m_pref.general.autoshowTimeline(autotimeline()->isSelected());
m_pref.general.rewindOnStop(rewindOnStop()->isSelected());
+ m_globPref.timeline.firstFrame(firstFrame()->textInt());
m_pref.general.showFullPath(showFullPath()->isSelected());
bool expandOnMouseover = expandMenubarOnMouseover()->isSelected();
@@ -252,7 +255,7 @@ public:
std::string warnings;
- int newPeriod = base::convert_to(dataRecoveryPeriod()->getValue());
+ double newPeriod = base::convert_to(dataRecoveryPeriod()->getValue());
if (enableDataRecovery()->isSelected() != m_pref.general.dataRecovery() ||
newPeriod != m_pref.general.dataRecoveryPeriod()) {
m_pref.general.dataRecovery(enableDataRecovery()->isSelected());
diff --git a/src/app/commands/cmd_palette_editor.cpp b/src/app/commands/cmd_palette_editor.cpp
index 1cb18c8a7..30eee3afb 100644
--- a/src/app/commands/cmd_palette_editor.cpp
+++ b/src/app/commands/cmd_palette_editor.cpp
@@ -251,10 +251,10 @@ PaletteEntryEditor::PaletteEntryEditor()
, m_selfPalChange(false)
, m_fromPalette(0, 0)
{
- m_colorType.addItem("RGB");
- m_colorType.addItem("HSB");
- m_changeMode.addItem("Abs");
- m_changeMode.addItem("Rel");
+ m_colorType.addItem("RGB")->setFocusStop(false);
+ m_colorType.addItem("HSB")->setFocusStop(false);
+ m_changeMode.addItem("Abs")->setFocusStop(false);
+ m_changeMode.addItem("Rel")->setFocusStop(false);
m_topBox.setBorder(gfx::Border(0));
m_topBox.setChildSpacing(0);
diff --git a/src/app/commands/cmd_run_script.cpp b/src/app/commands/cmd_run_script.cpp
index 27c68115f..c4dbc7122 100644
--- a/src/app/commands/cmd_run_script.cpp
+++ b/src/app/commands/cmd_run_script.cpp
@@ -8,6 +8,10 @@
#include "config.h"
#endif
+#ifndef ENABLE_SCRIPTING
+ #error ENABLE_SCRIPTING must be defined
+#endif
+
#include "app/commands/command.h"
#include "app/commands/params.h"
#include "app/console.h"
diff --git a/src/app/commands/commands_list.h b/src/app/commands/commands_list.h
index f00049d73..4648314d6 100644
--- a/src/app/commands/commands_list.h
+++ b/src/app/commands/commands_list.h
@@ -29,7 +29,6 @@ FOR_EACH_COMMAND(CropSprite)
FOR_EACH_COMMAND(Cut)
FOR_EACH_COMMAND(DeselectMask)
FOR_EACH_COMMAND(Despeckle)
-FOR_EACH_COMMAND(DeveloperConsole)
FOR_EACH_COMMAND(DiscardBrush)
FOR_EACH_COMMAND(DuplicateLayer)
FOR_EACH_COMMAND(DuplicateSprite)
@@ -100,7 +99,6 @@ FOR_EACH_COMMAND(ReplaceColor)
FOR_EACH_COMMAND(ReselectMask)
FOR_EACH_COMMAND(ReverseFrames)
FOR_EACH_COMMAND(Rotate)
-FOR_EACH_COMMAND(RunScript)
FOR_EACH_COMMAND(SaveFile)
FOR_EACH_COMMAND(SaveFileAs)
FOR_EACH_COMMAND(SaveFileCopyAs)
@@ -136,3 +134,8 @@ FOR_EACH_COMMAND(Undo)
FOR_EACH_COMMAND(UndoHistory)
FOR_EACH_COMMAND(UnlinkCel)
FOR_EACH_COMMAND(Zoom)
+
+#ifdef ENABLE_SCRIPTING
+FOR_EACH_COMMAND(DeveloperConsole)
+FOR_EACH_COMMAND(RunScript)
+#endif
diff --git a/src/app/console.cpp b/src/app/console.cpp
index d859fb778..d600d47d0 100644
--- a/src/app/console.cpp
+++ b/src/app/console.cpp
@@ -8,11 +8,13 @@
#include "config.h"
#endif
-#include
-#include
+#include
+#include
+#include
#include "base/bind.h"
#include "base/memory.h"
+#include "base/string.h"
#include "ui/ui.h"
#include "app/app.h"
@@ -106,15 +108,13 @@ Console::~Console()
void Console::printf(const char* format, ...)
{
- char buf[4096]; // TODO warning buffer overflow
- va_list ap;
-
+ std::va_list ap;
va_start(ap, format);
- vsprintf(buf, format, ap);
+ std::string msg = base::string_vprintf(format, ap);
va_end(ap);
if (!m_withUI || !wid_console) {
- fputs(buf, stdout);
+ fputs(msg.c_str(), stdout);
fflush(stdout);
return;
}
@@ -125,7 +125,7 @@ void Console::printf(const char* format, ...)
ui::Manager::getDefault()->invalidate();
}
- /* update the textbox */
+ // Update the textbox
if (!console_locked) {
console_locked = true;
@@ -142,7 +142,7 @@ void Console::printf(const char* format, ...)
std::string final;
if (!text.empty())
final += text;
- final += buf;
+ final += msg;
wid_textbox->setText(final.c_str());
}
diff --git a/src/app/crash/backup_observer.cpp b/src/app/crash/backup_observer.cpp
index 4334fc2bd..260ae44fa 100644
--- a/src/app/crash/backup_observer.cpp
+++ b/src/app/crash/backup_observer.cpp
@@ -82,8 +82,8 @@ void BackupObserver::onRemoveDocument(doc::Document* document)
void BackupObserver::backgroundThread()
{
- int normalPeriod = 60*Preferences::instance().general.dataRecoveryPeriod();
- int lockedPeriod = 10;
+ int normalPeriod = int(60.0*Preferences::instance().general.dataRecoveryPeriod());
+ int lockedPeriod = 5;
#if 0 // Just for testing purposes
normalPeriod = 5;
lockedPeriod = 5;
diff --git a/src/app/file/file.cpp b/src/app/file/file.cpp
index d8fb20937..f4705655f 100644
--- a/src/app/file/file.cpp
+++ b/src/app/file/file.cpp
@@ -32,6 +32,8 @@
#include "render/render.h"
#include "ui/alert.h"
+#include "open_sequence.xml.h"
+
#include
#include
@@ -191,6 +193,7 @@ FileOp* FileOp::createLoadDocumentOperation(Context* context, const char* filena
if (fop->m_format->support(FILE_SUPPORT_SEQUENCES)) {
/* prepare to load a sequence */
fop->prepareForSequence();
+ fop->m_seq.flags = flags;
/* per now, we want load just one file */
fop->m_seq.filename_list.push_back(filename);
@@ -221,18 +224,57 @@ FileOp* FileOp::createLoadDocumentOperation(Context* context, const char* filena
}
}
- /* TODO add a better dialog to edit file-names */
- if ((flags & FILE_LOAD_SEQUENCE_ASK) && context && context->isUIAvailable()) {
- /* really want load all files? */
- if ((fop->m_seq.filename_list.size() > 1) &&
- (ui::Alert::show("Notice"
- "<m_seq.filename_list[0]).c_str(),
- base::get_file_name(fop->m_seq.filename_list[1]).c_str()) != 1)) {
+ // TODO add a better dialog to edit file-names
+ if ((flags & FILE_LOAD_SEQUENCE_ASK) &&
+ context &&
+ context->isUIAvailable() &&
+ fop->m_seq.filename_list.size() > 1) {
+ app::gen::OpenSequence window;
+ window.repeat()->setVisible(flags & FILE_LOAD_SEQUENCE_ASK_CHECKBOX ? true: false);
+ for (const auto& fn : fop->m_seq.filename_list) {
+ auto item = new ui::ListItem(base::get_file_name(fn));
+ item->setSelected(true);
+ window.files()->addChild(item);
+ }
+
+ window.files()->Change.connect(
+ [&window]{
+ window.agree()->setEnabled(
+ window.files()->getSelectedChild() != nullptr);
+ });
+
+ window.openWindowInForeground();
+
+ // If the user selected the "do the same for other files"
+ // checkbox, we've to save what the user want to do for the
+ // following files.
+ if (window.repeat()->isSelected()) {
+ if (window.closer() == window.agree())
+ fop->m_seq.flags = FILE_LOAD_SEQUENCE_YES;
+ else
+ fop->m_seq.flags = FILE_LOAD_SEQUENCE_NONE;
+ }
+
+ if (window.closer() == window.agree()) {
+ // If the user replies "Agree", we load the selected files.
+ std::vector list;
+
+ auto it = window.files()->children().begin();
+ auto end = window.files()->children().end();
+ for (const auto& fn : fop->m_seq.filename_list) {
+ ASSERT(it != end);
+ if (it == end)
+ break;
+ if ((*it)->isSelected())
+ list.push_back(fn);
+ ++it;
+ }
+
+ ASSERT(!list.empty());
+ fop->m_seq.filename_list = list;
+ }
+ else {
// If the user replies "Skip", we need just one file name
// (the first one).
if (fop->m_seq.filename_list.size() > 1) {
@@ -944,6 +986,7 @@ FileOp::FileOp(FileOpType type, Context* context)
m_seq.frame = frame_t(0);
m_seq.layer = nullptr;
m_seq.last_cel = nullptr;
+ m_seq.flags = 0;
}
void FileOp::prepareForSequence()
diff --git a/src/app/file/file.h b/src/app/file/file.h
index 9950bbcac..8514a2bc6 100644
--- a/src/app/file/file.h
+++ b/src/app/file/file.h
@@ -22,8 +22,9 @@
// Flags for FileOp::createLoadDocumentOperation()
#define FILE_LOAD_SEQUENCE_NONE 0x00000001
#define FILE_LOAD_SEQUENCE_ASK 0x00000002
-#define FILE_LOAD_SEQUENCE_YES 0x00000004
-#define FILE_LOAD_ONE_FRAME 0x00000008
+#define FILE_LOAD_SEQUENCE_ASK_CHECKBOX 0x00000004
+#define FILE_LOAD_SEQUENCE_YES 0x00000008
+#define FILE_LOAD_ONE_FRAME 0x00000010
namespace doc {
class Document;
@@ -84,6 +85,11 @@ namespace app {
};
// Structure to load & save files.
+ //
+ // TODO This class do to many things. There should be a previous
+ // instance (class) to verify what the user want to do with the
+ // sequence of files, and the result of that operation should be the
+ // input of this one.
class FileOp {
public:
static FileOp* createLoadDocumentOperation(Context* context,
@@ -101,6 +107,7 @@ namespace app {
bool isOneFrame() const { return m_oneframe; }
const std::string& filename() const { return m_filename; }
+ const std::vector& filenames() const { return m_seq.filename_list; }
Context* context() const { return m_context; }
Document* document() const { return m_document; }
Document* releaseDocument() {
@@ -142,6 +149,9 @@ namespace app {
void sequenceSetHasAlpha(bool hasAlpha) {
m_seq.has_alpha = hasAlpha;
}
+ int sequenceFlags() const {
+ return m_seq.flags;
+ }
const std::string& error() const { return m_error; }
void setError(const char *error, ...);
@@ -191,6 +201,8 @@ namespace app {
bool has_alpha;
LayerImage* layer;
Cel* last_cel;
+ // Flags after the user choose what to do with the sequence.
+ int flags;
} m_seq;
void prepareForSequence();
diff --git a/src/app/job.cpp b/src/app/job.cpp
index 804b8a5a5..2038ca0f9 100644
--- a/src/app/job.cpp
+++ b/src/app/job.cpp
@@ -19,10 +19,19 @@
#include "ui/widget.h"
#include "ui/window.h"
+#include
+
static const int kMonitoringPeriod = 100;
+static std::atomic g_runningJobs(0);
namespace app {
+// static
+int Job::runningJobs()
+{
+ return g_runningJobs;
+}
+
Job::Job(const char* jobName)
{
m_mutex = NULL;
@@ -60,6 +69,7 @@ Job::~Job()
void Job::startJob()
{
m_thread = new base::thread(&Job::thread_proc, this);
+ ++g_runningJobs;
if (m_alert_window) {
m_alert_window->openWindowInForeground();
@@ -93,7 +103,9 @@ void Job::waitJob()
if (m_thread) {
m_thread->join();
delete m_thread;
- m_thread = NULL;
+ m_thread = nullptr;
+
+ --g_runningJobs;
}
}
diff --git a/src/app/job.h b/src/app/job.h
index d75b88cc2..e639249c2 100644
--- a/src/app/job.h
+++ b/src/app/job.h
@@ -24,6 +24,8 @@ namespace app {
class Job {
public:
+ static int runningJobs();
+
Job(const char* jobName);
virtual ~Job();
diff --git a/src/app/modules/gui.cpp b/src/app/modules/gui.cpp
index 615856a27..4ae09754d 100644
--- a/src/app/modules/gui.cpp
+++ b/src/app/modules/gui.cpp
@@ -9,6 +9,7 @@
#endif
#include "app/app.h"
+#include "app/commands/cmd_open_file.h"
#include "app/commands/command.h"
#include "app/commands/commands.h"
#include "app/commands/params.h"
@@ -345,16 +346,14 @@ bool CustomizedGuiManager::onProcessMessage(Message* msg)
case kDropFilesMessage:
{
- const DropFilesMessage::Files& files = static_cast(msg)->files();
-
- // Open all files
- Command* cmd_open_file =
- CommandsModule::instance()->getCommandByName(CommandId::OpenFile);
- Params params;
-
+ DropFilesMessage::Files files = static_cast(msg)->files();
UIContext* ctx = UIContext::instance();
+ OpenFileCommand cmd;
+
+ while (!files.empty()) {
+ auto fn = files.front();
+ files.erase(files.begin());
- for (const auto& fn : files) {
// If the document is already open, select it.
Document* doc = static_cast(ctx->documents().getByFileName(fn));
if (doc) {
@@ -367,8 +366,17 @@ bool CustomizedGuiManager::onProcessMessage(Message* msg)
}
// Load the file
else {
+ Params params;
params.set("filename", fn.c_str());
- ctx->executeCommand(cmd_open_file, params);
+ params.set("repeat_checkbox", "true");
+ ctx->executeCommand(&cmd, params);
+
+ // Remove all used file names from the "dropped files"
+ for (const auto& usedFn : cmd.usedFiles()) {
+ auto it = std::find(files.begin(), files.end(), usedFn);
+ if (it != files.end())
+ files.erase(it);
+ }
}
}
}
@@ -376,10 +384,12 @@ bool CustomizedGuiManager::onProcessMessage(Message* msg)
case kKeyDownMessage: {
#ifdef _DEBUG
+ auto keymsg = static_cast(msg);
+
// Ctrl+Shift+Q generates a crash (useful to test the anticrash feature)
if (msg->ctrlPressed() &&
msg->shiftPressed() &&
- static_cast(msg)->scancode() == kKeyQ) {
+ keymsg->scancode() == kKeyQ) {
int* p = nullptr;
*p = 0;
}
@@ -388,7 +398,7 @@ bool CustomizedGuiManager::onProcessMessage(Message* msg)
// Ctrl+Shift+R recover active sprite from the backup store
if (msg->ctrlPressed() &&
msg->shiftPressed() &&
- static_cast(msg)->scancode() == kKeyR &&
+ keymsg->scancode() == kKeyR &&
App::instance()->dataRecovery() &&
App::instance()->dataRecovery()->activeSession() &&
current_editor &&
diff --git a/src/app/script/app_scripting.h b/src/app/script/app_scripting.h
index 5fcc0e182..396905d3f 100644
--- a/src/app/script/app_scripting.h
+++ b/src/app/script/app_scripting.h
@@ -8,6 +8,10 @@
#define APP_SCRIPTING_H_INCLUDED
#pragma once
+#ifndef ENABLE_SCRIPTING
+ #error ENABLE_SCRIPTING must be defined
+#endif
+
#include "doc/object_id.h"
#include "script/engine.h"
diff --git a/src/app/shell.cpp b/src/app/shell.cpp
index df3f986df..ab013de76 100644
--- a/src/app/shell.cpp
+++ b/src/app/shell.cpp
@@ -8,6 +8,10 @@
#include "config.h"
#endif
+#ifndef ENABLE_SCRIPTING
+ #error ENABLE_SCRIPTING must be defined
+#endif
+
#include "app/shell.h"
#include "script/engine.h"
diff --git a/src/app/shell.h b/src/app/shell.h
index c1b1b0823..deddebeb4 100644
--- a/src/app/shell.h
+++ b/src/app/shell.h
@@ -8,6 +8,10 @@
#define APP_SHELL_H_INCLUDED
#pragma once
+#ifndef ENABLE_SCRIPTING
+ #error ENABLE_SCRIPTING must be defined
+#endif
+
namespace script {
class Engine;
}
diff --git a/src/app/ui/button_set.cpp b/src/app/ui/button_set.cpp
index 7ea2ff2bc..906e7914b 100644
--- a/src/app/ui/button_set.cpp
+++ b/src/app/ui/button_set.cpp
@@ -161,10 +161,8 @@ bool ButtonSet::Item::onProcessMessage(ui::Message* msg)
case ui::kKeyDownMessage:
if (isEnabled() && hasText()) {
KeyMessage* keymsg = static_cast(msg);
- bool mnemonicPressed =
- (msg->altPressed() &&
- mnemonicChar() &&
- mnemonicChar() == tolower(keymsg->unicodeChar()));
+ bool mnemonicPressed = (msg->altPressed() &&
+ mnemonicCharPressed(keymsg));
if (mnemonicPressed ||
(hasFocus() && keymsg->scancode() == kKeySpace)) {
diff --git a/src/app/ui/color_popup.cpp b/src/app/ui/color_popup.cpp
index 979f2e108..147e8b5ae 100644
--- a/src/app/ui/color_popup.cpp
+++ b/src/app/ui/color_popup.cpp
@@ -58,11 +58,11 @@ ColorPopup::ColorPopup(bool canPin)
, m_canPin(canPin)
, m_disableHexUpdate(false)
{
- m_colorType.addItem("Index");
- m_colorType.addItem("RGB");
- m_colorType.addItem("HSB");
- m_colorType.addItem("Gray");
- m_colorType.addItem("Mask");
+ m_colorType.addItem("Index")->setFocusStop(false);
+ m_colorType.addItem("RGB")->setFocusStop(false);
+ m_colorType.addItem("HSB")->setFocusStop(false);
+ m_colorType.addItem("Gray")->setFocusStop(false);
+ m_colorType.addItem("Mask")->setFocusStop(false);
m_topBox.setBorder(gfx::Border(0));
m_topBox.setChildSpacing(0);
@@ -260,6 +260,9 @@ void ColorPopup::selectColorType(app::Color::Type type)
case app::Color::MaskType: m_colorType.setSelectedItem(MASK_MODE); break;
}
+ // Remove focus from some RGB/HSB text entry
+ manager()->freeFocus();
+
m_vbox.layout();
m_vbox.invalidate();
}
diff --git a/src/app/ui/color_sliders.cpp b/src/app/ui/color_sliders.cpp
index 08adfaf44..f55d75ffd 100644
--- a/src/app/ui/color_sliders.cpp
+++ b/src/app/ui/color_sliders.cpp
@@ -13,10 +13,12 @@
#include "app/ui/skin/skin_slider_property.h"
#include "app/ui/skin/skin_theme.h"
#include "base/bind.h"
+#include "base/scoped_value.h"
#include "ui/box.h"
#include "ui/entry.h"
#include "ui/graphics.h"
#include "ui/label.h"
+#include "ui/message.h"
#include "ui/size_hint_event.h"
#include "ui/slider.h"
#include "ui/theme.h"
@@ -81,6 +83,104 @@ namespace {
app::Color m_color;
};
+ class ColorEntry : public Entry {
+ public:
+ ColorEntry(Slider* absSlider, Slider* relSlider)
+ : Entry(4, "0")
+ , m_absSlider(absSlider)
+ , m_relSlider(relSlider)
+ , m_recent_focus(false) {
+ }
+
+ private:
+ int minValue() const {
+ if (m_absSlider->isVisible())
+ return m_absSlider->getMinValue();
+ else if (m_relSlider->isVisible())
+ return m_relSlider->getMinValue();
+ else
+ return 0;
+ }
+
+ int maxValue() const {
+ if (m_absSlider->isVisible())
+ return m_absSlider->getMaxValue();
+ else if (m_relSlider->isVisible())
+ return m_relSlider->getMaxValue();
+ else
+ return 0;
+ }
+
+ bool onProcessMessage(Message* msg) override {
+ switch (msg->type()) {
+
+ case kFocusEnterMessage:
+ m_recent_focus = true;
+ break;
+
+ case kKeyDownMessage:
+ if (Entry::onProcessMessage(msg))
+ return true;
+
+ if (hasFocus()) {
+ int scancode = static_cast(msg)->scancode();
+
+ switch (scancode) {
+ // Enter just remove the focus
+ case kKeyEnter:
+ case kKeyEnterPad:
+ releaseFocus();
+ return true;
+
+ case kKeyDown:
+ case kKeyUp: {
+ int value = textInt();
+ if (scancode == kKeyDown)
+ --value;
+ else
+ ++value;
+
+ setTextf("%d", MID(minValue(), value, maxValue()));
+ selectAllText();
+
+ onChange();
+ return true;
+ }
+ }
+
+ // Process focus movement key here because if our
+ // CustomizedGuiManager catches this kKeyDownMessage it
+ // will process it as a shortcut to switch the Timeline.
+ //
+ // Note: The default ui::Manager handles focus movement
+ // shortcuts only for foreground windows.
+ // TODO maybe that should change
+ if (hasFocus() &&
+ manager()->processFocusMovementMessage(msg))
+ return true;
+ }
+ return false;
+ }
+
+ bool result = Entry::onProcessMessage(msg);
+
+ if (msg->type() == kMouseDownMessage && m_recent_focus) {
+ m_recent_focus = false;
+ selectAllText();
+ }
+
+ return result;
+ }
+
+ Slider* m_absSlider;
+ Slider* m_relSlider;
+
+ // TODO remove this calling setFocus() in
+ // Widget::onProcessMessage() instead of
+ // Manager::handleWindowZOrder()
+ bool m_recent_focus;
+ };
+
}
//////////////////////////////////////////////////////////////////////
@@ -90,6 +190,7 @@ ColorSliders::ColorSliders()
: Widget(kGenericWidget)
, m_grid(3, false)
, m_mode(Absolute)
+ , m_lockEntry(-1)
{
addChild(&m_grid);
m_grid.setChildSpacing(0);
@@ -136,7 +237,7 @@ void ColorSliders::addSlider(Channel channel, const char* labelText, int min, in
Label* label = new Label(labelText);
Slider* absSlider = new Slider(min, max, 0);
Slider* relSlider = new Slider(min-max, max-min, 0);
- Entry* entry = new Entry(4, "0");
+ Entry* entry = new ColorEntry(absSlider, relSlider);
m_label.push_back(label);
m_absSlider.push_back(absSlider);
@@ -155,6 +256,8 @@ void ColorSliders::addSlider(Channel channel, const char* labelText, int min, in
HBox* box = new HBox();
box->addChild(absSlider);
box->addChild(relSlider);
+ absSlider->setFocusStop(false);
+ relSlider->setFocusStop(false);
absSlider->setExpansive(true);
relSlider->setExpansive(true);
relSlider->setVisible(false);
@@ -193,6 +296,8 @@ void ColorSliders::onSliderChange(int i)
void ColorSliders::onEntryChange(int i)
{
+ base::ScopedValue lock(m_lockEntry, i, m_lockEntry);
+
// Update the slider related to the changed entry widget.
int value = m_entry[i]->textInt();
@@ -220,10 +325,15 @@ void ColorSliders::onControlChange(int i)
// Updates the entry related to the changed slider widget.
void ColorSliders::updateEntryText(int entryIndex)
{
+ if (m_lockEntry == entryIndex)
+ return;
+
Slider* slider = (m_mode == Absolute ? m_absSlider[entryIndex]:
m_relSlider[entryIndex]);
m_entry[entryIndex]->setTextf("%d", slider->getValue());
+ if (m_entry[entryIndex]->hasFocus())
+ m_entry[entryIndex]->selectAllText();
}
void ColorSliders::updateSlidersBgColor(const app::Color& color)
diff --git a/src/app/ui/color_sliders.h b/src/app/ui/color_sliders.h
index 16747825e..19dc89c2e 100644
--- a/src/app/ui/color_sliders.h
+++ b/src/app/ui/color_sliders.h
@@ -72,6 +72,7 @@ namespace app {
std::vector m_channel;
ui::Grid m_grid;
Mode m_mode;
+ int m_lockEntry;
};
//////////////////////////////////////////////////////////////////////
diff --git a/src/app/ui/configure_timeline_popup.cpp b/src/app/ui/configure_timeline_popup.cpp
index e5db53ff7..b6f989515 100644
--- a/src/app/ui/configure_timeline_popup.cpp
+++ b/src/app/ui/configure_timeline_popup.cpp
@@ -52,6 +52,7 @@ ConfigureTimelinePopup::ConfigureTimelinePopup()
addChild(m_box);
m_box->position()->ItemChange.connect(base::Bind(&ConfigureTimelinePopup::onChangePosition, this));
+ m_box->firstFrame()->Change.connect(base::Bind(&ConfigureTimelinePopup::onChangeFirstFrame, this));
m_box->merge()->Click.connect(base::Bind(&ConfigureTimelinePopup::onChangeType, this));
m_box->tint()->Click.connect(base::Bind(&ConfigureTimelinePopup::onChangeType, this));
m_box->opacity()->Change.connect(base::Bind(&ConfigureTimelinePopup::onOpacity, this));
@@ -92,6 +93,9 @@ void ConfigureTimelinePopup::updateWidgetsFromCurrentSettings()
}
m_box->position()->setSelectedItem(selItem, false);
+ m_box->firstFrame()->setTextf(
+ "%d", docPref.timeline.firstFrame());
+
switch (docPref.onionskin.type()) {
case app::gen::OnionskinType::MERGE:
m_box->merge()->setSelected(true);
@@ -155,6 +159,12 @@ void ConfigureTimelinePopup::onChangePosition()
Preferences::instance().general.timelinePosition(newTimelinePos);
}
+void ConfigureTimelinePopup::onChangeFirstFrame()
+{
+ docPref().timeline.firstFrame(
+ m_box->firstFrame()->textInt());
+}
+
void ConfigureTimelinePopup::onChangeType()
{
if (m_lockUpdates)
diff --git a/src/app/ui/configure_timeline_popup.h b/src/app/ui/configure_timeline_popup.h
index 18e28677f..c9564b4bb 100644
--- a/src/app/ui/configure_timeline_popup.h
+++ b/src/app/ui/configure_timeline_popup.h
@@ -33,6 +33,7 @@ namespace app {
protected:
bool onProcessMessage(ui::Message* msg) override;
void onChangePosition();
+ void onChangeFirstFrame();
void onChangeType();
void onOpacity();
void onOpacityStep();
diff --git a/src/app/ui/devconsole_view.cpp b/src/app/ui/devconsole_view.cpp
index cfced5601..a01828807 100644
--- a/src/app/ui/devconsole_view.cpp
+++ b/src/app/ui/devconsole_view.cpp
@@ -8,6 +8,10 @@
#include "config.h"
#endif
+#ifndef ENABLE_SCRIPTING
+ #error ENABLE_SCRIPTING must be defined
+#endif
+
#include "app/ui/devconsole_view.h"
#include "app/app_menus.h"
diff --git a/src/app/ui/devconsole_view.h b/src/app/ui/devconsole_view.h
index 974ec44bc..80313b0b2 100644
--- a/src/app/ui/devconsole_view.h
+++ b/src/app/ui/devconsole_view.h
@@ -8,6 +8,10 @@
#define APP_UI_DEVCONSOLE_VIEW_H_INCLUDED
#pragma once
+#ifndef ENABLE_SCRIPTING
+ #error ENABLE_SCRIPTING must be defined
+#endif
+
#include "app/script/app_scripting.h"
#include "app/ui/tabs.h"
#include "app/ui/workspace_view.h"
diff --git a/src/app/ui/editor/editor.cpp b/src/app/ui/editor/editor.cpp
index 3cb888b72..242fe0ab4 100644
--- a/src/app/ui/editor/editor.cpp
+++ b/src/app/ui/editor/editor.cpp
@@ -1409,7 +1409,7 @@ bool Editor::onProcessMessage(Message* msg)
case kFocusLeaveMessage:
// As we use keys like Space-bar as modifier, we can clear the
// keyboard buffer when we lost the focus.
- she::clear_keyboard_buffer();
+ she::instance()->clearKeyboardBuffer();
break;
case kMouseWheelMessage:
diff --git a/src/app/ui/editor/standby_state.cpp b/src/app/ui/editor/standby_state.cpp
index 1a5cfb64e..25813d1c9 100644
--- a/src/app/ui/editor/standby_state.cpp
+++ b/src/app/ui/editor/standby_state.cpp
@@ -232,7 +232,7 @@ bool StandbyState::onMouseDown(Editor* editor, MouseMessage* msg)
MovingCelState* newState = new MovingCelState(editor, msg, handle);
editor->setState(EditorStatePtr(newState));
}
- catch (const LockedDocumentException& ex) {
+ catch (const LockedDocumentException&) {
// TODO break the background task that is locking this sprite
StatusBar::instance()->showTip(1000,
"Sprite is used by a backup/data recovery task");
@@ -502,7 +502,7 @@ bool StandbyState::onUpdateStatusBar(Editor* editor)
if (sprite->totalFrames() > 1) {
sprintf(
buf+std::strlen(buf), " :frame: %d :clock: %d",
- editor->frame()+1,
+ editor->frame()+editor->docPref().timeline.firstFrame(),
sprite->frameDuration(editor->frame()));
}
diff --git a/src/app/ui/editor/state_with_wheel_behavior.cpp b/src/app/ui/editor/state_with_wheel_behavior.cpp
index 5fee94fb8..dff24b784 100644
--- a/src/app/ui/editor/state_with_wheel_behavior.cpp
+++ b/src/app/ui/editor/state_with_wheel_behavior.cpp
@@ -113,15 +113,18 @@ bool StateWithWheelBehavior::onMouseWheel(Editor* editor, MouseMessage* msg)
}
break;
- case WHEEL_FRAME:
- {
- Command* command = CommandsModule::instance()->getCommandByName
- ((dz < 0.0) ? CommandId::GotoNextFrame:
- CommandId::GotoPreviousFrame);
- if (command)
- UIContext::instance()->executeCommand(command);
- }
+ case WHEEL_FRAME: {
+ Command* command = nullptr;
+
+ if (dz < 0.0)
+ command = CommandsModule::instance()->getCommandByName(CommandId::GotoNextFrame);
+ else if (dz > 0.0)
+ command = CommandsModule::instance()->getCommandByName(CommandId::GotoPreviousFrame);
+
+ if (command)
+ UIContext::instance()->executeCommand(command);
break;
+ }
case WHEEL_ZOOM: {
render::Zoom zoom = editor->zoom();
diff --git a/src/app/ui/file_list.cpp b/src/app/ui/file_list.cpp
index f45c94c37..089d6c40c 100644
--- a/src/app/ui/file_list.cpp
+++ b/src/app/ui/file_list.cpp
@@ -93,8 +93,6 @@ void FileList::setCurrentFolder(IFileItem* folder)
invalidate();
View::getView(this)->updateView();
-
- requestFocus();
}
void FileList::goUp()
@@ -279,7 +277,12 @@ bool FileList::onProcessMessage(Message* msg)
View* view = View::getView(this);
if (view) {
gfx::Point scroll = view->viewScroll();
- scroll += static_cast(msg)->wheelDelta() * 3*(textHeight()+4*guiscale());
+
+ if (static_cast(msg)->preciseWheel())
+ scroll += static_cast(msg)->wheelDelta();
+ else
+ scroll += static_cast(msg)->wheelDelta() * 3*(textHeight()+4*guiscale());
+
view->setViewScroll(scroll);
}
break;
diff --git a/src/app/ui/file_selector.cpp b/src/app/ui/file_selector.cpp
index 2c2dcd57b..86505604e 100644
--- a/src/app/ui/file_selector.cpp
+++ b/src/app/ui/file_selector.cpp
@@ -196,8 +196,8 @@ protected:
int unicode = keyMsg->unicodeChar();
bool up = (msg->cmdPressed() && scancode == kKeyUp);
bool enter = (msg->cmdPressed() && scancode == kKeyDown);
- bool back = (msg->cmdPressed() && msg->shiftPressed() && unicode == '[');
- bool forward = (msg->cmdPressed() && msg->shiftPressed() && unicode == ']');
+ bool back = (msg->cmdPressed() && (unicode == '[' || scancode == kKeyOpenbrace));
+ bool forward = (msg->cmdPressed() && (unicode == ']' || scancode == kKeyClosebrace));
#else
bool up = (msg->altPressed() && scancode == kKeyUp);
bool enter = (msg->altPressed() && scancode == kKeyDown);
@@ -851,12 +851,12 @@ void FileSelector::onLocationCloseListBox()
}
}
- if (fileItem != NULL) {
+ if (fileItem) {
m_fileList->setCurrentFolder(fileItem);
// Refocus the 'fileview' (the focus in that widget is more
// useful for the user)
- manager()->setFocus(m_fileList);
+ m_fileList->requestFocus();
}
}
diff --git a/src/app/ui/hex_color_entry.cpp b/src/app/ui/hex_color_entry.cpp
index 66a6b6ea6..fe7edb40b 100644
--- a/src/app/ui/hex_color_entry.cpp
+++ b/src/app/ui/hex_color_entry.cpp
@@ -14,21 +14,41 @@
#include "app/ui/hex_color_entry.h"
#include "base/hex.h"
#include "gfx/border.h"
+#include "ui/message.h"
#include "ui/theme.h"
namespace app {
using namespace ui;
+HexColorEntry::CustomEntry::CustomEntry()
+ : Entry(16, "")
+{
+}
+
+bool HexColorEntry::CustomEntry::onProcessMessage(ui::Message* msg)
+{
+ switch (msg->type()) {
+ case kMouseDownMessage:
+ setFocusStop(true);
+ requestFocus();
+ break;
+ case kFocusLeaveMessage:
+ setFocusStop(false);
+ break;
+ }
+ return Entry::onProcessMessage(msg);
+}
+
HexColorEntry::HexColorEntry()
: Box(HORIZONTAL)
, m_label("#")
- , m_entry(16, "")
{
addChild(&m_label);
addChild(&m_entry);
m_entry.Change.connect(&HexColorEntry::onEntryChange, this);
+ m_entry.setFocusStop(false);
initTheme();
diff --git a/src/app/ui/hex_color_entry.h b/src/app/ui/hex_color_entry.h
index 3eecfea25..30f221607 100644
--- a/src/app/ui/hex_color_entry.h
+++ b/src/app/ui/hex_color_entry.h
@@ -30,8 +30,15 @@ namespace app {
void onEntryChange();
private:
+ class CustomEntry : public ui::Entry {
+ public:
+ CustomEntry();
+ private:
+ bool onProcessMessage(ui::Message* msg) override;
+ };
+
ui::Label m_label;
- ui::Entry m_entry;
+ CustomEntry m_entry;
};
} // namespace app
diff --git a/src/app/ui/main_window.cpp b/src/app/ui/main_window.cpp
index 5efcc84cd..3a0271699 100644
--- a/src/app/ui/main_window.cpp
+++ b/src/app/ui/main_window.cpp
@@ -19,7 +19,6 @@
#include "app/pref/preferences.h"
#include "app/ui/color_bar.h"
#include "app/ui/context_bar.h"
-#include "app/ui/devconsole_view.h"
#include "app/ui/document_view.h"
#include "app/ui/editor/editor.h"
#include "app/ui/editor/editor_view.h"
@@ -43,6 +42,10 @@
#include "ui/system.h"
#include "ui/view.h"
+#ifdef ENABLE_SCRIPTING
+ #include "app/ui/devconsole_view.h"
+#endif
+
namespace app {
using namespace ui;
@@ -90,8 +93,10 @@ public:
MainWindow::MainWindow()
: m_mode(NormalMode)
, m_homeView(nullptr)
- , m_devConsoleView(nullptr)
, m_scalePanic(nullptr)
+#ifdef ENABLE_SCRIPTING
+ , m_devConsoleView(nullptr)
+#endif
{
// Load all menus by first time.
AppMenus::instance()->reload();
@@ -154,11 +159,14 @@ MainWindow::~MainWindow()
{
delete m_scalePanic;
+#ifdef ENABLE_SCRIPTING
if (m_devConsoleView) {
if (m_devConsoleView->parent())
m_workspace->removeView(m_devConsoleView);
delete m_devConsoleView;
}
+#endif
+
if (m_homeView) {
if (m_homeView->parent())
m_workspace->removeView(m_homeView);
@@ -241,6 +249,7 @@ bool MainWindow::isHomeSelected()
void MainWindow::showDevConsole()
{
+#ifdef ENABLE_SCRIPTING
if (!m_devConsoleView)
m_devConsoleView = new DevConsoleView;
@@ -248,6 +257,7 @@ void MainWindow::showDevConsole()
m_workspace->addView(m_devConsoleView);
m_tabsBar->selectTab(m_devConsoleView);
}
+#endif
}
void MainWindow::setMode(Mode mode)
diff --git a/src/app/ui/main_window.h b/src/app/ui/main_window.h
index 5a7f8e51c..d7ea5bee7 100644
--- a/src/app/ui/main_window.h
+++ b/src/app/ui/main_window.h
@@ -114,9 +114,11 @@ namespace app {
Workspace* m_workspace;
PreviewEditorWindow* m_previewEditor;
HomeView* m_homeView;
- DevConsoleView* m_devConsoleView;
Notifications* m_notifications;
INotificationDelegate* m_scalePanic;
+#ifdef ENABLE_SCRIPTING
+ DevConsoleView* m_devConsoleView;
+#endif
};
}
diff --git a/src/app/ui/palette_popup.cpp b/src/app/ui/palette_popup.cpp
index 3dea6456e..50501617f 100644
--- a/src/app/ui/palette_popup.cpp
+++ b/src/app/ui/palette_popup.cpp
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2001-2015 David Capello
+// Copyright (C) 2001-2016 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
@@ -34,9 +34,11 @@ PalettePopup::PalettePopup()
{
setAutoRemap(false);
setBorder(gfx::Border(4*guiscale()));
+ setEnterBehavior(EnterBehavior::DoNothingOnEnter);
addChild(m_popup);
+ m_paletteListBox.DoubleClickItem.connect(base::Bind(&PalettePopup::onLoadPal, this));
m_popup->loadPal()->Click.connect(base::Bind(&PalettePopup::onLoadPal, this));
m_popup->openFolder()->Click.connect(base::Bind(&PalettePopup::onOpenFolder, this));
@@ -75,6 +77,8 @@ void PalettePopup::onLoadPal()
CommandsModule::instance()->getCommandByName(CommandId::SetPalette));
cmd->setPalette(palette);
UIContext::instance()->executeCommand(cmd);
+
+ m_paletteListBox.requestFocus();
}
void PalettePopup::onOpenFolder()
diff --git a/src/app/ui/palette_view.cpp b/src/app/ui/palette_view.cpp
index 957769e39..5fa18f290 100644
--- a/src/app/ui/palette_view.cpp
+++ b/src/app/ui/palette_view.cpp
@@ -186,15 +186,15 @@ app::Color PaletteView::getColorByPosition(const gfx::Point& pos)
int PaletteView::getBoxSize() const
{
- return m_boxsize / guiscale();
+ return int(m_boxsize) / guiscale();
}
-void PaletteView::setBoxSize(int boxsize)
+void PaletteView::setBoxSize(double boxsize)
{
- m_boxsize = MID(4, boxsize, 32)*guiscale();
+ m_boxsize = MID(4.0, boxsize, 32.0)*guiscale();
if (m_delegate)
- m_delegate->onPaletteViewChangeSize(m_boxsize / guiscale());
+ m_delegate->onPaletteViewChangeSize(int(m_boxsize) / guiscale());
View* view = View::getView(this);
if (view)
@@ -382,13 +382,19 @@ bool PaletteView::onProcessMessage(Message* msg)
gfx::Point delta = static_cast(msg)->wheelDelta();
- if (msg->onlyCtrlPressed()) {
+ if (msg->onlyCtrlPressed() ||
+ msg->onlyCmdPressed()) {
int z = delta.x - delta.y;
setBoxSize(m_boxsize + z);
}
else {
gfx::Point scroll = view->viewScroll();
- scroll += delta * 3 * m_boxsize;
+
+ if (static_cast(msg)->preciseWheel())
+ scroll += delta;
+ else
+ scroll += delta * 3 * m_boxsize;
+
view->setViewScroll(scroll);
}
break;
@@ -411,6 +417,11 @@ bool PaletteView::onProcessMessage(Message* msg)
return true;
}
+ case kTouchMagnifyMessage: {
+ setBoxSize(m_boxsize + m_boxsize * static_cast(msg)->magnification());
+ break;
+ }
+
}
return Widget::onProcessMessage(msg);
@@ -475,14 +486,14 @@ void PaletteView::onPaint(ui::PaintEvent& ev)
case FgBgColors:
if (fgIndex == i) {
gfx::Color neg = color_utils::blackandwhite_neg(gfxColor);
- for (int i=0; idrawHLine(neg, box.x, box.y+i, m_boxsize/2-i);
}
if (bgIndex == i) {
gfx::Color neg = color_utils::blackandwhite_neg(gfxColor);
- for (int i=0; idrawHLine(neg, box.x+box.w-(i+1), box.y+box.h-m_boxsize/4+i, i+1);
+ for (int i=0; idrawHLine(neg, box.x+box.w-(i+1), box.y+box.h-int(m_boxsize/4)+i, i+1);
}
if (transparentIndex == i)
@@ -582,7 +593,7 @@ void PaletteView::onResize(ui::ResizeEvent& ev)
if (view) {
int columns =
(view->viewportBounds().w-this->childSpacing()*2)
- / (m_boxsize+this->childSpacing());
+ / (int(m_boxsize)+this->childSpacing());
setColumns(MAX(1, columns));
}
m_isUpdatingColumns = false;
@@ -602,8 +613,8 @@ void PaletteView::onSizeHint(ui::SizeHintEvent& ev)
}
gfx::Size sz;
- sz.w = border().width() + cols*m_boxsize + (cols-1)*childSpacing();
- sz.h = border().height() + rows*m_boxsize + (rows-1)*childSpacing();
+ sz.w = border().width() + cols*int(m_boxsize) + (cols-1)*childSpacing();
+ sz.h = border().height() + rows*int(m_boxsize) + (rows-1)*childSpacing();
ev.setSizeHint(sz);
}
@@ -629,18 +640,18 @@ void PaletteView::update_scroll(int color)
d = div(currentPalette()->size(), m_columns);
cols = m_columns;
- y = (m_boxsize+childSpacing()) * (color / cols);
- x = (m_boxsize+childSpacing()) * (color % cols);
+ y = (int(m_boxsize)+childSpacing()) * (color / cols);
+ x = (int(m_boxsize)+childSpacing()) * (color % cols);
if (scroll.x > x)
scroll.x = x;
- else if (scroll.x+vp.w-m_boxsize-2 < x)
- scroll.x = x-vp.w+m_boxsize+2;
+ else if (scroll.x+vp.w-int(m_boxsize)-2 < x)
+ scroll.x = x-vp.w+int(m_boxsize)+2;
if (scroll.y > y)
scroll.y = y;
- else if (scroll.y+vp.h-m_boxsize-2 < y)
- scroll.y = y-vp.h+m_boxsize+2;
+ else if (scroll.y+vp.h-int(m_boxsize)-2 < y)
+ scroll.y = y-vp.h+int(m_boxsize)+2;
view->setViewScroll(scroll);
}
@@ -662,9 +673,9 @@ gfx::Rect PaletteView::getPaletteEntryBounds(int index) const
int row = index / cols;
return gfx::Rect(
- bounds.x + border().left() + col*(m_boxsize+childSpacing()),
- bounds.y + border().top() + row*(m_boxsize+childSpacing()),
- m_boxsize, m_boxsize);
+ bounds.x + border().left() + col*(int(m_boxsize)+childSpacing()),
+ bounds.y + border().top() + row*(int(m_boxsize)+childSpacing()),
+ int(m_boxsize), int(m_boxsize));
}
PaletteView::Hit PaletteView::hitTest(const gfx::Point& pos)
@@ -716,8 +727,8 @@ PaletteView::Hit PaletteView::hitTest(const gfx::Point& pos)
}
gfx::Rect box = getPaletteEntryBounds(0);
- box.w = (m_boxsize+childSpacing());
- box.h = (m_boxsize+childSpacing());
+ box.w = (int(m_boxsize)+childSpacing());
+ box.h = (int(m_boxsize)+childSpacing());
int colsLimit = m_columns;
if (m_state == State::DRAGGING_OUTLINE)
diff --git a/src/app/ui/palette_view.h b/src/app/ui/palette_view.h
index 99250a9a6..759f77c14 100644
--- a/src/app/ui/palette_view.h
+++ b/src/app/ui/palette_view.h
@@ -74,7 +74,7 @@ namespace app {
app::Color getColorByPosition(const gfx::Point& pos) override;
int getBoxSize() const;
- void setBoxSize(int boxsize);
+ void setBoxSize(double boxsize);
void clearSelection();
void cutToClipboard();
@@ -147,7 +147,7 @@ namespace app {
PaletteViewStyle m_style;
PaletteViewDelegate* m_delegate;
int m_columns;
- int m_boxsize;
+ double m_boxsize;
int m_currentEntry;
int m_rangeAnchor;
doc::PalettePicks m_selectedEntries;
diff --git a/src/app/ui/palettes_listbox.cpp b/src/app/ui/palettes_listbox.cpp
index 2f19fb636..6189e644e 100644
--- a/src/app/ui/palettes_listbox.cpp
+++ b/src/app/ui/palettes_listbox.cpp
@@ -10,14 +10,19 @@
#include "app/ui/palettes_listbox.h"
+#include "app/document.h"
#include "app/modules/palettes.h"
#include "app/res/palette_resource.h"
#include "app/res/palettes_loader_delegate.h"
+#include "app/ui/document_view.h"
+#include "app/ui/editor/editor.h"
#include "app/ui/icon_button.h"
#include "app/ui/skin/skin_theme.h"
+#include "app/ui_context.h"
#include "base/bind.h"
#include "base/launcher.h"
#include "doc/palette.h"
+#include "doc/sprite.h"
#include "she/surface.h"
#include "ui/graphics.h"
#include "ui/listitem.h"
@@ -63,7 +68,7 @@ class PalettesListItem : public ResourceListItem {
int j, i = m_comment.find("http");
if (i != std::string::npos) {
- for (j=i+4; j(resource)->palette();
+ auto tick = SkinTheme::instance()->parts.checkSelected()->bitmap(0);
+
+ // Draw tick (to say "this palette matches the active sprite
+ // palette").
+ auto view = UIContext::instance()->activeView();
+ if (view && view->document()) {
+ auto docPal = view->document()->sprite()->palette(view->editor()->frame());
+ if (docPal && *docPal == *palette)
+ g->drawRgbaSurface(tick, bounds.x, bounds.y+bounds.h/2-tick->height()/2);
+ }
+
+ bounds.x += tick->width();
+ bounds.w -= tick->width();
gfx::Rect box(
bounds.x, bounds.y+bounds.h-6*guiscale(),
diff --git a/src/app/ui/palettes_listbox.h b/src/app/ui/palettes_listbox.h
index 5352ee7fb..20b6c2c19 100644
--- a/src/app/ui/palettes_listbox.h
+++ b/src/app/ui/palettes_listbox.h
@@ -28,7 +28,7 @@ namespace app {
protected:
virtual ResourceListItem* onCreateResourceItem(Resource* resource) override;
virtual void onResourceChange(Resource* resource) override;
- virtual void onPaintResource(ui::Graphics* g, const gfx::Rect& bounds, Resource* resource) override;
+ virtual void onPaintResource(ui::Graphics* g, gfx::Rect& bounds, Resource* resource) override;
virtual void onResourceSizeHint(Resource* resource, gfx::Size& size) override;
ui::TooltipManager m_tooltips;
diff --git a/src/app/ui/popup_window_pin.cpp b/src/app/ui/popup_window_pin.cpp
index 2dd6192a2..35bf90b60 100644
--- a/src/app/ui/popup_window_pin.cpp
+++ b/src/app/ui/popup_window_pin.cpp
@@ -32,6 +32,7 @@ PopupWindowPin::PopupWindowPin(const std::string& text, ClickBehavior clickBehav
{
SkinTheme* theme = SkinTheme::instance();
+ m_pin.setFocusStop(false);
m_pin.Click.connect(&PopupWindowPin::onPinClick, this);
m_pin.setIconInterface(
new ButtonIconImpl(theme->parts.unpinned(),
diff --git a/src/app/ui/resources_listbox.cpp b/src/app/ui/resources_listbox.cpp
index 0908f52d8..b80105f5c 100644
--- a/src/app/ui/resources_listbox.cpp
+++ b/src/app/ui/resources_listbox.cpp
@@ -67,7 +67,7 @@ void ResourceListItem::onPaint(PaintEvent& ev)
g->drawString(text(), fgcolor, gfx::ColorNone,
gfx::Point(
- bounds.x + guiscale()*2,
+ bounds.x + 2*guiscale(),
bounds.y + bounds.h/2 - g->measureUIString(text()).h/2));
}
@@ -124,7 +124,7 @@ Resource* ResourcesListBox::selectedResource()
return NULL;
}
-void ResourcesListBox::paintResource(Graphics* g, const gfx::Rect& bounds, Resource* resource)
+void ResourcesListBox::paintResource(Graphics* g, gfx::Rect& bounds, Resource* resource)
{
onPaintResource(g, bounds, resource);
}
diff --git a/src/app/ui/resources_listbox.h b/src/app/ui/resources_listbox.h
index 1bd932f8d..3dac65a08 100644
--- a/src/app/ui/resources_listbox.h
+++ b/src/app/ui/resources_listbox.h
@@ -47,11 +47,11 @@ class ResourceListItem : public ui::ListItem {
// abstract
virtual void onResourceChange(Resource* resource) = 0;
- virtual void onPaintResource(ui::Graphics* g, const gfx::Rect& bounds, Resource* resource) = 0;
+ virtual void onPaintResource(ui::Graphics* g, gfx::Rect& bounds, Resource* resource) = 0;
virtual void onResourceSizeHint(Resource* resource, gfx::Size& size) = 0;
private:
- void paintResource(ui::Graphics* g, const gfx::Rect& bounds, Resource* resource);
+ void paintResource(ui::Graphics* g, gfx::Rect& bounds, Resource* resource);
gfx::Size resourceSizeHint(Resource* resource);
void onTick();
diff --git a/src/app/ui/select_accelerator.cpp b/src/app/ui/select_accelerator.cpp
index 9c20e8d4e..663f829c3 100644
--- a/src/app/ui/select_accelerator.cpp
+++ b/src/app/ui/select_accelerator.cpp
@@ -23,6 +23,7 @@ using namespace ui;
class SelectAccelerator::KeyField : public ui::Entry {
public:
KeyField(const Accelerator& accel) : ui::Entry(256, "") {
+ setTranslateDeadKeys(false);
setExpansive(true);
setFocusMagnet(true);
setAccel(accel);
@@ -38,9 +39,13 @@ public:
protected:
bool onProcessMessage(Message* msg) override {
switch (msg->type()) {
+
case kKeyDownMessage:
if (hasFocus() && !isReadOnly()) {
KeyMessage* keymsg = static_cast(msg);
+ if (!keymsg->scancode() && keymsg->unicodeChar() < 32)
+ break;
+
KeyModifiers modifiers = keymsg->modifiers();
if (keymsg->scancode() == kKeySpace)
diff --git a/src/app/ui/skin/skin_theme.cpp b/src/app/ui/skin/skin_theme.cpp
index b6d8cad04..b4d819d5e 100644
--- a/src/app/ui/skin/skin_theme.cpp
+++ b/src/app/ui/skin/skin_theme.cpp
@@ -895,9 +895,13 @@ void SkinTheme::paintCheckBox(PaintEvent& ev)
if (iconInterface)
paintIcon(widget, g, iconInterface, icon.x, icon.y);
- // draw focus
- if (look != WithoutBordersLook && widget->hasFocus())
+ // Draw focus
+ if (look != WithoutBordersLook &&
+ (widget->hasFocus() || (iconInterface &&
+ widget->text().empty() &&
+ widget->hasMouseOver()))) {
drawRect(g, bounds, parts.checkFocus().get(), gfx::ColorNone);
+ }
}
void SkinTheme::paintGrid(PaintEvent& ev)
diff --git a/src/app/ui/status_bar.cpp b/src/app/ui/status_bar.cpp
index 5f454a355..d64ac70c8 100644
--- a/src/app/ui/status_bar.cpp
+++ b/src/app/ui/status_bar.cpp
@@ -35,6 +35,7 @@
#include "app/ui_context.h"
#include "app/util/range_utils.h"
#include "base/bind.h"
+#include "base/string.h"
#include "doc/document_event.h"
#include "doc/image.h"
#include "doc/layer.h"
@@ -475,7 +476,7 @@ public:
case kMouseEnterMessage:
if (Preferences::instance().statusBar.focusFrameFieldOnMouseover()) {
requestFocus();
- selectText(0, -1);
+ selectAllText();
}
break;
@@ -488,13 +489,11 @@ public:
scancode == kKeyEnterPad)) {
Command* cmd = CommandsModule::instance()->getCommandByName(CommandId::GotoFrame);
Params params;
- int frame = textInt();
- if (frame > 0) {
- params.set("frame", text().c_str());
- UIContext::instance()->executeCommand(cmd, params);
- }
+ params.set("frame", text().c_str());
+ UIContext::instance()->executeCommand(cmd, params);
+
// Select the text again
- selectText(0, -1);
+ selectAllText();
releaseFocus();
return true; // Key used.
}
@@ -611,17 +610,15 @@ void StatusBar::showBackupIcon(BackupIcon icon)
m_indicators->showBackupIcon(icon);
}
-bool StatusBar::setStatusText(int msecs, const char *format, ...)
+bool StatusBar::setStatusText(int msecs, const char* format, ...)
{
if ((base::current_tick() > m_timeout) || (msecs > 0)) {
- char buf[256]; // TODO warning buffer overflow
- va_list ap;
-
+ std::va_list ap;
va_start(ap, format);
- vsprintf(buf, format, ap);
+ std::string msg = base::string_vprintf(format, ap);
va_end(ap);
- IndicatorsGeneration(m_indicators).add(buf);
+ IndicatorsGeneration(m_indicators).add(msg.c_str());
m_timeout = base::current_tick() + msecs;
return true;
}
@@ -629,21 +626,18 @@ bool StatusBar::setStatusText(int msecs, const char *format, ...)
return false;
}
-void StatusBar::showTip(int msecs, const char *format, ...)
+void StatusBar::showTip(int msecs, const char* format, ...)
{
- char buf[256]; // TODO warning buffer overflow
- va_list ap;
- int x, y;
-
+ std::va_list ap;
va_start(ap, format);
- vsprintf(buf, format, ap);
+ std::string msg = base::string_vprintf(format, ap);
va_end(ap);
if (m_tipwindow == NULL) {
- m_tipwindow = new CustomizedTipWindow(buf);
+ m_tipwindow = new CustomizedTipWindow(msg);
}
else {
- m_tipwindow->setText(buf);
+ m_tipwindow->setText(msg);
}
m_tipwindow->setInterval(msecs);
@@ -654,14 +648,14 @@ void StatusBar::showTip(int msecs, const char *format, ...)
m_tipwindow->openWindow();
m_tipwindow->remapWindow();
- x = bounds().x2() - m_tipwindow->bounds().w;
- y = bounds().y - m_tipwindow->bounds().h;
+ int x = bounds().x2() - m_tipwindow->bounds().w;
+ int y = bounds().y - m_tipwindow->bounds().h;
m_tipwindow->positionWindow(x, y);
m_tipwindow->startTimer();
// Set the text in status-bar (with inmediate timeout)
- IndicatorsGeneration(m_indicators).add(buf);
+ IndicatorsGeneration(m_indicators).add(msg.c_str());
m_timeout = base::current_tick();
}
@@ -744,13 +738,15 @@ void StatusBar::onActiveSiteChange(const doc::Site& site)
ASSERT(m_doc == site.document());
}
+ auto& docPref = Preferences::instance().document(
+ static_cast(m_doc));
+
m_docControls->setVisible(true);
- showSnapToGridWarning(
- Preferences::instance().document(
- static_cast(m_doc)).grid.snap());
+ showSnapToGridWarning(docPref.grid.snap());
// Current frame
- m_currentFrame->setTextf("%d", site.frame()+1);
+ m_currentFrame->setTextf(
+ "%d", site.frame()+docPref.timeline.firstFrame());
}
else {
ASSERT(m_doc == nullptr);
diff --git a/src/app/ui/status_bar.h b/src/app/ui/status_bar.h
index 2a7dbcd1e..dc510d105 100644
--- a/src/app/ui/status_bar.h
+++ b/src/app/ui/status_bar.h
@@ -57,8 +57,8 @@ namespace app {
void clearText();
- bool setStatusText(int msecs, const char *format, ...);
- void showTip(int msecs, const char *format, ...);
+ bool setStatusText(int msecs, const char* format, ...);
+ void showTip(int msecs, const char* format, ...);
void showColor(int msecs, const char* text, const Color& color);
void showTool(int msecs, tools::Tool* tool);
void showSnapToGridWarning(bool state);
diff --git a/src/app/ui/timeline.cpp b/src/app/ui/timeline.cpp
index ca9216c7b..de627923b 100644
--- a/src/app/ui/timeline.cpp
+++ b/src/app/ui/timeline.cpp
@@ -51,6 +51,7 @@
#include "gfx/rect.h"
#include "she/font.h"
#include "she/surface.h"
+#include "she/system.h"
#include "ui/scroll_helper.h"
#include "ui/ui.h"
@@ -250,6 +251,9 @@ void Timeline::updateUsingEditor(Editor* editor)
m_hot.part = PART_NOTHING;
m_clk.part = PART_NOTHING;
+ m_firstFrameConn = Preferences::instance().document(m_document)
+ .timeline.firstFrame.AfterChange.connect(base::Bind(&Timeline::invalidate, this));
+
setFocusStop(true);
regenerateLayers();
setViewScroll(viewScroll());
@@ -258,6 +262,8 @@ void Timeline::updateUsingEditor(Editor* editor)
void Timeline::detachDocument()
{
+ m_firstFrameConn.disconnect();
+
if (m_document) {
m_thumbnailsPrefConn.disconnect();
m_document->remove_observer(this);
@@ -425,7 +431,8 @@ bool Timeline::onProcessMessage(Message* msg)
if (!m_document)
break;
- if (mouseMsg->middle() || she::is_key_pressed(kKeySpace)) {
+ if (mouseMsg->middle() ||
+ she::instance()->isKeyPressed(kKeySpace)) {
captureMouse();
m_state = STATE_SCROLLING;
m_oldPos = static_cast(msg)->position();
@@ -1048,7 +1055,7 @@ bool Timeline::onProcessMessage(Message* msg)
m_scroll = false;
// We have to clear all the kKeySpace keys in buffer.
- she::clear_keyboard_buffer();
+ she::instance()->clearKeyboardBuffer();
used = true;
break;
}
@@ -1065,35 +1072,23 @@ bool Timeline::onProcessMessage(Message* msg)
case kMouseWheelMessage:
if (m_document) {
- int base_size = skinTheme()->dimensions.timelineBaseSize();
- int dz = static_cast(msg)->wheelDelta().y * base_size;
-
- if (msg->altPressed()) {
- if (dz != 0) {
- double next_zoom = m_zoom + (dz < 0 ? 1 : -1);
- setZoomAndUpdate(next_zoom);
- }
- }
- else {
- int dx;
- int dy;
-
- if (msg->ctrlPressed()) {
- dx = dz;
- dy = 0;
- }
- else {
- dx = static_cast(msg)->wheelDelta().x * base_size;
- dy = dz;
- }
+ gfx::Point delta = static_cast(msg)->wheelDelta();
+ if (!static_cast(msg)->preciseWheel()) {
+ delta.x *= frameBoxWidth();
+ delta.y *= layerBoxHeight();
if (msg->shiftPressed()) {
- dx *= frameBoxWidth() / base_size;
- dy *= layerBoxHeight() / base_size;
+ // On macOS shift already changes the wheel axis
+ if (std::fabs(delta.y) > delta.x)
+ std::swap(delta.x, delta.y);
}
- setViewScroll(viewScroll() + gfx::Point(dx, dy));
+ if (msg->altPressed()) {
+ delta.x *= 3;
+ delta.y *= 3;
+ }
}
+ setViewScroll(viewScroll() + delta);
}
break;
@@ -1589,8 +1584,11 @@ void Timeline::drawHeaderFrame(ui::Graphics* g, frame_t frame)
return;
// Draw the header for the layers.
- char buf[256];
- std::sprintf(buf, "%d", (frame+1)%100); // Draw only the first two digits.
+ char buf[4];
+ std::snprintf(
+ buf, sizeof(buf), "%d",
+ // Draw only the first two digits
+ (docPref().timeline.firstFrame()+frame) % 100);
she::Font* oldFont = g->font();
g->setFont(skinTheme()->getMiniFont());
@@ -2694,9 +2692,10 @@ void Timeline::updateStatusBar(ui::Message* msg)
case PART_HEADER_FRAME:
if (validFrame(m_hot.frame)) {
- sb->setStatusText(0,
+ sb->setStatusText(
+ 0,
":frame: %d :clock: %d",
- (int)m_hot.frame+1,
+ (int)m_hot.frame+docPref().timeline.firstFrame(),
m_sprite->frameDuration(m_hot.frame));
return;
}
diff --git a/src/app/ui/timeline.h b/src/app/ui/timeline.h
index 99d86a0ec..68e5e1fe7 100644
--- a/src/app/ui/timeline.h
+++ b/src/app/ui/timeline.h
@@ -339,6 +339,7 @@ namespace app {
// Configure timeline
ConfigureTimelinePopup* m_confPopup;
obs::scoped_connection m_ctxConn;
+ obs::connection m_firstFrameConn;
// Marching ants stuff to show the range in the clipboard.
// TODO merge this with the marching ants of the sprite editor (ui::Editor)
diff --git a/src/app/ui/toolbar.cpp b/src/app/ui/toolbar.cpp
index 1f18e566f..43ef8b3ec 100644
--- a/src/app/ui/toolbar.cpp
+++ b/src/app/ui/toolbar.cpp
@@ -727,6 +727,11 @@ Rect ToolBar::ToolStrip::getToolBounds(int index)
iconsize.w, bounds.h);
}
+void ToolBar::onActiveToolChange(tools::Tool* tool)
+{
+ invalidate();
+}
+
void ToolBar::onSelectedToolChange(tools::Tool* tool)
{
if (tool && m_selectedInGroup[tool->getGroup()] != tool)
diff --git a/src/app/ui/toolbar.h b/src/app/ui/toolbar.h
index 93cc4cf27..cd7eb843a 100644
--- a/src/app/ui/toolbar.h
+++ b/src/app/ui/toolbar.h
@@ -61,6 +61,7 @@ namespace app {
void onClosePopup();
// ActiveToolObserver impl
+ void onActiveToolChange(tools::Tool* tool) override;
void onSelectedToolChange(tools::Tool* tool) override;
// What tool is selected for each tool-group
diff --git a/src/app/widget_loader.cpp b/src/app/widget_loader.cpp
index 00ef4e1b3..0bc532b17 100644
--- a/src/app/widget_loader.cpp
+++ b/src/app/widget_loader.cpp
@@ -296,6 +296,10 @@ Widget* WidgetLoader::convertXmlElementToWidget(const TiXmlElement* elem, Widget
else if (elem_name == "listbox") {
if (!widget)
widget = new ListBox();
+
+ bool multiselect = bool_attr_is_true(elem, "multiselect");
+ if (multiselect)
+ static_cast(widget)->setMultiselect(multiselect);
}
else if (elem_name == "listitem") {
ListItem* listitem;
diff --git a/src/clip b/src/clip
index 3e9533e33..926e3cf0a 160000
--- a/src/clip
+++ b/src/clip
@@ -1 +1 @@
-Subproject commit 3e9533e332e6b1982476c255eedaae917e63dfec
+Subproject commit 926e3cf0ae484111d89cacd769d29c4d1e073dec
diff --git a/src/she/CMakeLists.txt b/src/she/CMakeLists.txt
index d1d92f084..3360e5c3d 100644
--- a/src/she/CMakeLists.txt
+++ b/src/she/CMakeLists.txt
@@ -2,7 +2,8 @@
# Copyright (C) 2012-2016 David Capello
set(SHE_SOURCES
- common/freetype_font.cpp)
+ common/freetype_font.cpp
+ system.cpp)
######################################################################
# Allegro 4 backend
diff --git a/src/she/alleg4/key_poller.cpp b/src/she/alleg4/key_poller.cpp
index 2a98b9464..ecc50d382 100644
--- a/src/she/alleg4/key_poller.cpp
+++ b/src/she/alleg4/key_poller.cpp
@@ -1,5 +1,5 @@
// SHE library
-// Copyright (C) 2012-2015 David Capello
+// Copyright (C) 2012-2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@@ -21,14 +21,12 @@ int key_repeated[KEY_MAX];
int she_keyboard_ucallback(int unicode_char, int* scancode)
{
int c = ((*scancode) & 0x7f);
- Event ev;
+ Event ev;
ev.setType(Event::KeyDown);
ev.setScancode(static_cast(c));
if (unicode_char > 0)
ev.setUnicodeChar(unicode_char);
- else
- ev.setUnicodeChar(::scancode_to_ascii(c));
ev.setRepeat(key_repeated[c]++);
queue_event(ev);
diff --git a/src/she/alleg4/she.cpp b/src/she/alleg4/she.cpp
index f0e828c4e..bd685f006 100644
--- a/src/she/alleg4/she.cpp
+++ b/src/she/alleg4/she.cpp
@@ -63,8 +63,6 @@
#include "she/alleg4/mouse_poller.h"
#endif
-static she::System* g_instance = nullptr;
-
namespace she {
class Alleg4EventQueue : public EventQueue {
@@ -129,15 +127,11 @@ public:
#ifdef USE_MOUSE_POLLER
mouse_poller_init();
#endif
-
- g_instance = this;
}
~Alleg4System() {
remove_timer();
allegro_exit();
-
- g_instance = nullptr;
}
void dispose() override {
@@ -206,21 +200,48 @@ public:
return sur;
}
+ bool isKeyPressed(KeyScancode scancode) override {
+#ifdef ALLEGRO_UNIX
+ if (scancode == kKeyLShift || scancode == kKeyRShift) {
+ return key_shifts & KB_SHIFT_FLAG;
+ }
+ else if (scancode == kKeyLControl || scancode == kKeyRControl) {
+ return key_shifts & KB_CTRL_FLAG;
+ }
+ else if (scancode == kKeyAlt) {
+ return key_shifts & KB_ALT_FLAG;
+ }
+#endif
+ return key[scancode] ? true: false;
+ }
+
+
+ int getUnicodeFromScancode(KeyScancode scancode) override {
+ if (isKeyPressed(scancode))
+ return scancode_to_ascii(scancode);
+ else
+ return false;
+ }
+
+ void clearKeyboardBuffer() override {
+ clear_keybuf();
+ }
+
+ void setTranslateDeadKeys(bool state) override {
+ // Do nothing
+ }
+
};
-System* create_system() {
+System* create_system_impl() {
return new Alleg4System();
}
-System* instance()
-{
- return g_instance;
-}
-
void error_message(const char* msg)
{
- if (g_instance && g_instance->logger())
- g_instance->logger()->logError(msg);
+ System* sys = instance();
+ if (sys && sys->logger())
+ sys->logger()->logError(msg);
#ifdef _WIN32
std::wstring wmsg = base::from_utf8(msg);
@@ -231,27 +252,6 @@ void error_message(const char* msg)
#endif
}
-bool is_key_pressed(KeyScancode scancode)
-{
-#ifdef ALLEGRO_UNIX
- if (scancode == kKeyLShift || scancode == kKeyRShift) {
- return key_shifts & KB_SHIFT_FLAG;
- }
- else if (scancode == kKeyLControl || scancode == kKeyRControl) {
- return key_shifts & KB_CTRL_FLAG;
- }
- else if (scancode == kKeyAlt) {
- return key_shifts & KB_ALT_FLAG;
- }
-#endif
- return key[scancode] ? true: false;
-}
-
-void clear_keyboard_buffer()
-{
- clear_keybuf();
-}
-
} // namespace she
// It must be defined by the user program code.
diff --git a/src/she/common/system.h b/src/she/common/system.h
index 54097eb20..ad9d9ba18 100644
--- a/src/she/common/system.h
+++ b/src/she/common/system.h
@@ -78,6 +78,25 @@ public:
return loadFreeTypeFont(filename, height);
}
+ KeyModifiers keyModifiers() override {
+ return
+ (KeyModifiers)
+ ((isKeyPressed(kKeyLShift) ||
+ isKeyPressed(kKeyRShift) ? kKeyShiftModifier: 0) |
+ (isKeyPressed(kKeyLControl) ||
+ isKeyPressed(kKeyRControl) ? kKeyCtrlModifier: 0) |
+ (isKeyPressed(kKeyAlt) ? kKeyAltModifier: 0) |
+ (isKeyPressed(kKeyAltGr) ? (kKeyCtrlModifier | kKeyAltModifier): 0) |
+ (isKeyPressed(kKeyCommand) ? kKeyCmdModifier: 0) |
+ (isKeyPressed(kKeySpace) ? kKeySpaceModifier: 0) |
+ (isKeyPressed(kKeyLWin) ||
+ isKeyPressed(kKeyRWin) ? kKeyWinModifier: 0));
+ }
+
+ void clearKeyboardBuffer() override {
+ // Do nothing
+ }
+
private:
NativeDialogs* m_nativeDialogs;
};
diff --git a/src/she/event.h b/src/she/event.h
index ed9415e1d..f1c34624f 100644
--- a/src/she/event.h
+++ b/src/she/event.h
@@ -56,6 +56,7 @@ namespace she {
m_scancode(kKeyNil),
m_modifiers(kKeyUninitializedModifier),
m_unicodeChar(0),
+ m_isDead(false),
m_repeat(0),
m_preciseWheel(false),
m_pointerType(PointerType::Unknown),
@@ -70,6 +71,7 @@ namespace she {
KeyScancode scancode() const { return m_scancode; }
KeyModifiers modifiers() const { return m_modifiers; }
int unicodeChar() const { return m_unicodeChar; }
+ bool isDeadKey() const { return m_isDead; }
int repeat() const { return m_repeat; }
gfx::Point position() const { return m_position; }
gfx::Point wheelDelta() const { return m_wheelDelta; }
@@ -92,6 +94,7 @@ namespace she {
void setScancode(KeyScancode scancode) { m_scancode = scancode; }
void setModifiers(KeyModifiers modifiers) { m_modifiers = modifiers; }
void setUnicodeChar(int unicodeChar) { m_unicodeChar = unicodeChar; }
+ void setDeadKey(bool state) { m_isDead = state; }
void setRepeat(int repeat) { m_repeat = repeat; }
void setPosition(const gfx::Point& pos) { m_position = pos; }
void setWheelDelta(const gfx::Point& delta) { m_wheelDelta = delta; }
@@ -108,6 +111,7 @@ namespace she {
KeyScancode m_scancode;
KeyModifiers m_modifiers;
int m_unicodeChar;
+ bool m_isDead;
int m_repeat; // repeat=0 means the first time the key is pressed
gfx::Point m_position;
gfx::Point m_wheelDelta;
diff --git a/src/she/keys.h b/src/she/keys.h
index 5f7248342..fc088beaf 100644
--- a/src/she/keys.h
+++ b/src/she/keys.h
@@ -1,5 +1,5 @@
// SHE library
-// Copyright (C) 2012-2013, 2015 David Capello
+// Copyright (C) 2012-2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@@ -125,30 +125,30 @@ namespace she {
kKeyCircumflex = 100,
kKeyColon2 = 101,
kKeyKanji = 102,
- kKeyEqualsPad = 103, // MacOS X
- kKeyBackquote = 104, // MacOS X
- kKeySemicolon = 105, // MacOS X
- kKeyCommand = 106, // MacOS X
- kKeyUnknown1 = 107,
- kKeyUnknown2 = 108,
- kKeyUnknown3 = 109,
- kKeyUnknown4 = 110,
- kKeyUnknown5 = 111,
- kKeyUnknown6 = 112,
- kKeyUnknown7 = 113,
- kKeyUnknown8 = 114,
+ kKeyEqualsPad = 103, // macOS
+ kKeyBackquote = 104, // macOS
+ kKeySemicolon = 105, // macOS
+ kKeyUnknown1 = 106,
+ kKeyUnknown2 = 107,
+ kKeyUnknown3 = 108,
+ kKeyUnknown4 = 109,
+ kKeyUnknown5 = 110,
+ kKeyUnknown6 = 111,
+ kKeyUnknown7 = 112,
+ kKeyUnknown8 = 113,
- kKeyFirstModifierScancode = 115,
+ kKeyFirstModifierScancode = 114,
- kKeyLShift = 115,
- kKeyRShift = 116,
- kKeyLControl = 117,
- kKeyRControl = 118,
- kKeyAlt = 119,
- kKeyAltGr = 120,
- kKeyLWin = 121,
- kKeyRWin = 122,
- kKeyMenu = 123,
+ kKeyLShift = 114,
+ kKeyRShift = 115,
+ kKeyLControl = 116,
+ kKeyRControl = 117,
+ kKeyAlt = 118,
+ kKeyAltGr = 119,
+ kKeyLWin = 120,
+ kKeyRWin = 121,
+ kKeyMenu = 122,
+ kKeyCommand = 123, // macOS - TODO This should be inside the modifiers range
kKeyScrLock = 124,
kKeyNumLock = 125,
kKeyCapsLock = 126,
@@ -156,11 +156,6 @@ namespace she {
kKeyScancodes = 127
};
- // Deprecated API, use modifiers in she::Event
- // TODO mark these functions as deprecated
- bool is_key_pressed(KeyScancode scancode);
- void clear_keyboard_buffer();
-
} // namespace she
#endif
diff --git a/src/she/osx/event_queue.mm b/src/she/osx/event_queue.mm
index c80ea18da..5759a76d4 100644
--- a/src/she/osx/event_queue.mm
+++ b/src/she/osx/event_queue.mm
@@ -32,12 +32,11 @@ retry:;
inMode:NSDefaultRunLoopMode
dequeue:YES];
if (event) {
- // Intercept Control+Tab and send it to the main NSView. Without
- // this, the NSApplication intercepts the key combination and
- // use it to go to the next key view.
- if (event.type == NSKeyDown &&
- event.modifierFlags & NSControlKeyMask &&
- event.keyCode == kVK_Tab) {
+ // Intercept , , and other keyboard
+ // combinations, and send them directly to the main
+ // NSView. Without this, the NSApplication intercepts the key
+ // combination and use it to go to the next key view.
+ if (event.type == NSKeyDown) {
[app.mainWindow.contentView keyDown:event];
}
else {
diff --git a/src/she/osx/system.h b/src/she/osx/system.h
new file mode 100644
index 000000000..5e7998f14
--- /dev/null
+++ b/src/she/osx/system.h
@@ -0,0 +1,31 @@
+// SHE library
+// Copyright (C) 2016 David Capello
+//
+// This file is released under the terms of the MIT license.
+// Read LICENSE.txt for more information.
+
+#ifndef SHE_OSX_SYSTEM_H
+#define SHE_OSX_SYSTEM_H
+#pragma once
+
+#include "she/common/system.h"
+
+namespace she {
+
+bool osx_is_key_pressed(KeyScancode scancode);
+int osx_get_unicode_from_scancode(KeyScancode scancode);
+
+class OSXSystem : public CommonSystem {
+public:
+ bool isKeyPressed(KeyScancode scancode) override {
+ return osx_is_key_pressed(scancode);
+ }
+
+ int getUnicodeFromScancode(KeyScancode scancode) override {
+ return osx_get_unicode_from_scancode(scancode);
+ }
+};
+
+} // namespace she
+
+#endif
diff --git a/src/she/osx/view.h b/src/she/osx/view.h
index 853a892cd..20ba5568d 100644
--- a/src/she/osx/view.h
+++ b/src/she/osx/view.h
@@ -53,6 +53,8 @@
- (void)updateCurrentCursor;
- (NSDragOperation)draggingEntered:(id)sender;
- (BOOL)performDragOperation:(id)sender;
+- (void)doCommandBySelector:(SEL)selector;
+- (void)setTranslateDeadKeys:(BOOL)state;
@end
#endif
diff --git a/src/she/osx/view.mm b/src/she/osx/view.mm
index 2c4e2b9d7..f6589b0f3 100644
--- a/src/she/osx/view.mm
+++ b/src/she/osx/view.mm
@@ -16,15 +16,22 @@
#include "she/keys.h"
#include "she/osx/generate_drop_files.h"
#include "she/osx/window.h"
+#include "she/system.h"
-using namespace she;
+#include // For VK codes
+
+namespace she {
+
+bool osx_is_key_pressed(KeyScancode scancode);
namespace {
-// Internal array of pressed keys used in is_key_pressed()
-bool pressed_keys[kKeyScancodes];
+// Internal array of pressed keys used in isKeyPressed()
+int g_pressedKeys[kKeyScancodes];
+bool g_translateDeadKeys = false;
+UInt32 g_lastDeadKeyState = 0;
-inline gfx::Point get_local_mouse_pos(NSView* view, NSEvent* event)
+gfx::Point get_local_mouse_pos(NSView* view, NSEvent* event)
{
NSPoint point = [view convertPoint:[event locationInWindow]
fromView:nil];
@@ -37,7 +44,7 @@ inline gfx::Point get_local_mouse_pos(NSView* view, NSEvent* event)
(view.bounds.size.height - point.y) / scale);
}
-inline Event::MouseButton get_mouse_buttons(NSEvent* event)
+Event::MouseButton get_mouse_buttons(NSEvent* event)
{
// Some Wacom drivers on OS X report right-clicks with
// buttonNumber=0, so we've to check the type event anyway.
@@ -61,7 +68,7 @@ inline Event::MouseButton get_mouse_buttons(NSEvent* event)
return Event::MouseButton::NoneButton;
}
-inline KeyModifiers get_modifiers_from_nsevent(NSEvent* event)
+KeyModifiers get_modifiers_from_nsevent(NSEvent* event)
{
int modifiers = kKeyNoneModifier;
NSEventModifierFlags nsFlags = event.modifierFlags;
@@ -69,37 +76,71 @@ inline KeyModifiers get_modifiers_from_nsevent(NSEvent* event)
if (nsFlags & NSControlKeyMask) modifiers |= kKeyCtrlModifier;
if (nsFlags & NSAlternateKeyMask) modifiers |= kKeyAltModifier;
if (nsFlags & NSCommandKeyMask) modifiers |= kKeyCmdModifier;
- if (she::is_key_pressed(kKeySpace)) modifiers |= kKeySpaceModifier;
+ if (osx_is_key_pressed(kKeySpace)) modifiers |= kKeySpaceModifier;
return (KeyModifiers)modifiers;
}
-inline int get_unicodechar_from_nsevent(NSEvent* event)
+// Based on code from:
+// http://stackoverflow.com/questions/22566665/how-to-capture-unicode-from-key-events-without-an-nstextview
+// http://stackoverflow.com/questions/12547007/convert-key-code-into-key-equivalent-string
+// http://stackoverflow.com/questions/8263618/convert-virtual-key-code-to-unicode-string
+//
+// It includes a "translateDeadKeys" flag to avoid processing dead
+// keys in case that we want to use key
+CFStringRef get_unicode_from_key_code(NSEvent* event,
+ const bool translateDeadKeys)
{
- int chr = 0;
- // TODO we should use "event.characters" for a new kind of event (Event::Char or something like that)
- NSString* chars = event.charactersIgnoringModifiers;
- if (chars && chars.length >= 1) {
- chr = [chars characterAtIndex:0];
- if (chr < 32)
- chr = 0;
- }
- return chr;
+ TISInputSourceRef inputSource = TISCopyCurrentKeyboardInputSource();
+ CFDataRef keyLayoutData = (CFDataRef)TISGetInputSourceProperty(inputSource, kTISPropertyUnicodeKeyLayoutData);
+ const UCKeyboardLayout* keyLayout = (const UCKeyboardLayout*)CFDataGetBytePtr(keyLayoutData);
+
+ UInt32 deadKeyState = (translateDeadKeys ? g_lastDeadKeyState: 0);
+ UniChar output[4];
+ UniCharCount length;
+
+ // Reference here:
+ // https://developer.apple.com/reference/coreservices/1390584-uckeytranslate?language=objc
+ UCKeyTranslate(
+ keyLayout,
+ event.keyCode,
+ kUCKeyActionDown,
+ ((event.modifierFlags >> 16) & 0xFF),
+ LMGetKbdType(),
+ (translateDeadKeys ? 0: kUCKeyTranslateNoDeadKeysMask),
+ &deadKeyState,
+ sizeof(output) / sizeof(output[0]),
+ &length,
+ output);
+
+ if (translateDeadKeys)
+ g_lastDeadKeyState = deadKeyState;
+
+ CFRelease(inputSource);
+ return CFStringCreateWithCharacters(kCFAllocatorDefault, output, length);
}
} // anonymous namespace
-namespace she {
-
-bool is_key_pressed(KeyScancode scancode)
+bool osx_is_key_pressed(KeyScancode scancode)
{
if (scancode >= 0 && scancode < kKeyScancodes)
- return pressed_keys[scancode];
+ return (g_pressedKeys[scancode] != 0);
else
return false;
}
+int osx_get_unicode_from_scancode(KeyScancode scancode)
+{
+ if (scancode >= 0 && scancode < kKeyScancodes)
+ return g_pressedKeys[scancode];
+ else
+ return 0;
+}
+
} // namespace she
+using namespace she;
+
@implementation OSXView
- (id)initWithFrame:(NSRect)frameRect
@@ -167,17 +208,47 @@ bool is_key_pressed(KeyScancode scancode)
[super keyDown:event];
KeyScancode scancode = cocoavk_to_scancode(event.keyCode);
- if (scancode >= 0 && scancode < kKeyScancodes)
- pressed_keys[scancode] = true;
-
Event ev;
ev.setType(Event::KeyDown);
ev.setScancode(scancode);
ev.setModifiers(get_modifiers_from_nsevent(event));
ev.setRepeat(event.ARepeat ? 1: 0);
- ev.setUnicodeChar(get_unicodechar_from_nsevent(event));
+ ev.setUnicodeChar(0);
- queue_event(ev);
+ bool sendMsg = true;
+
+ CFStringRef strRef = get_unicode_from_key_code(event, false);
+ if (strRef) {
+ int length = CFStringGetLength(strRef);
+ if (length == 1)
+ ev.setUnicodeChar(CFStringGetCharacterAtIndex(strRef, 0));
+ CFRelease(strRef);
+ }
+
+ if (scancode >= 0 && scancode < kKeyScancodes)
+ g_pressedKeys[scancode] = (ev.unicodeChar() ? ev.unicodeChar(): 1);
+
+ if (g_translateDeadKeys) {
+ strRef = get_unicode_from_key_code(event, true);
+ if (strRef) {
+ int length = CFStringGetLength(strRef);
+ if (length > 0) {
+ sendMsg = false;
+ for (int i=0; i= 0 && scancode < kKeyScancodes)
- pressed_keys[scancode] = false;
+ g_pressedKeys[scancode] = 0;
Event ev;
ev.setType(Event::KeyUp);
ev.setScancode(scancode);
ev.setModifiers(get_modifiers_from_nsevent(event));
ev.setRepeat(event.ARepeat ? 1: 0);
- ev.setUnicodeChar(get_unicodechar_from_nsevent(event));
+ ev.setUnicodeChar(0);
queue_event(ev);
}
@@ -230,7 +301,7 @@ bool is_key_pressed(KeyScancode scancode)
((newFlags & flags[i]) != 0 ? Event::KeyDown:
Event::KeyUp));
- pressed_keys[scancodes[i]] = ((newFlags & flags[i]) != 0);
+ g_pressedKeys[scancodes[i]] = ((newFlags & flags[i]) != 0);
ev.setScancode(scancodes[i]);
ev.setModifiers(modifiers);
@@ -517,4 +588,15 @@ bool is_key_pressed(KeyScancode scancode)
return NO;
}
+- (void)doCommandBySelector:(SEL)selector
+{
+ // Do nothing (avoid beep pressing Escape key)
+}
+
+- (void)setTranslateDeadKeys:(BOOL)state
+{
+ g_translateDeadKeys = (state ? true: false);
+ g_lastDeadKeyState = 0;
+}
+
@end
diff --git a/src/she/skia/she.cpp b/src/she/skia/she.cpp
index a6be689cb..37c5e91e0 100644
--- a/src/she/skia/she.cpp
+++ b/src/she/skia/she.cpp
@@ -24,15 +24,8 @@
namespace she {
-static System* g_instance;
-
-System* create_system() {
- return g_instance = new SkiaSystem();
-}
-
-System* instance()
-{
- return g_instance;
+System* create_system_impl() {
+ return new SkiaSystem();
}
void error_message(const char* msg)
@@ -41,19 +34,11 @@ void error_message(const char* msg)
// TODO
}
-void clear_keyboard_buffer()
-{
- // Do nothing
-}
-
} // namespace she
extern int app_main(int argc, char* argv[]);
#if _WIN32
-extern int __argc;
-extern wchar_t** __wargv;
-
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PWSTR lpCmdLine, int nCmdShow) {
int argc = __argc;
diff --git a/src/she/skia/skia_display.cpp b/src/she/skia/skia_display.cpp
index b0bf18df0..5032d4de9 100644
--- a/src/she/skia/skia_display.cpp
+++ b/src/she/skia/skia_display.cpp
@@ -184,6 +184,11 @@ void SkiaDisplay::setLayout(const std::string& layout)
m_window.setLayout(layout);
}
+void SkiaDisplay::setTranslateDeadKeys(bool state)
+{
+ m_window.setTranslateDeadKeys(state);
+}
+
DisplayHandle SkiaDisplay::nativeHandle()
{
return (DisplayHandle)m_window.handle();
diff --git a/src/she/skia/skia_display.h b/src/she/skia/skia_display.h
index bc06c431a..b9ba96127 100644
--- a/src/she/skia/skia_display.h
+++ b/src/she/skia/skia_display.h
@@ -60,6 +60,8 @@ public:
std::string getLayout() override;
void setLayout(const std::string& layout) override;
+ void setTranslateDeadKeys(bool state);
+
// Returns the HWND on Windows.
DisplayHandle nativeHandle() override;
diff --git a/src/she/skia/skia_system.h b/src/she/skia/skia_system.h
index adddf5abb..cb6e07fd5 100644
--- a/src/she/skia/skia_system.h
+++ b/src/she/skia/skia_system.h
@@ -21,7 +21,8 @@
#elif __APPLE__
#include "she/osx/app.h"
#include "she/osx/event_queue.h"
- #define SkiaSystemBase CommonSystem
+ #include "she/osx/system.h"
+ #define SkiaSystemBase OSXSystem
#else
#include "she/x11/event_queue.h"
#define SkiaSystemBase CommonSystem
@@ -135,6 +136,15 @@ public:
return loadSurface(filename);
}
+ void setTranslateDeadKeys(bool state) override {
+ if (m_defaultDisplay)
+ m_defaultDisplay->setTranslateDeadKeys(state);
+
+#ifdef _WIN32
+ g_queue.setTranslateDeadKeys(state);
+#endif
+ }
+
private:
SkiaDisplay* m_defaultDisplay;
bool m_gpuAcceleration;
diff --git a/src/she/skia/skia_window_osx.h b/src/she/skia/skia_window_osx.h
index e79e3a652..dc19263d9 100644
--- a/src/she/skia/skia_window_osx.h
+++ b/src/she/skia/skia_window_osx.h
@@ -1,5 +1,5 @@
// SHE library
-// Copyright (C) 2012-2015 David Capello
+// Copyright (C) 2012-2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@@ -46,6 +46,7 @@ public:
void updateWindow(const gfx::Rect& bounds);
std::string getLayout() { return ""; }
void setLayout(const std::string& layout) { }
+ void setTranslateDeadKeys(bool state);
void* handle();
private:
diff --git a/src/she/skia/skia_window_osx.mm b/src/she/skia/skia_window_osx.mm
index 85b6c9093..22f3ca786 100644
--- a/src/she/skia/skia_window_osx.mm
+++ b/src/she/skia/skia_window_osx.mm
@@ -15,6 +15,7 @@
#include "gfx/size.h"
#include "she/event.h"
#include "she/event_queue.h"
+#include "she/osx/view.h"
#include "she/osx/window.h"
#include "she/skia/skia_display.h"
#include "she/skia/skia_surface.h"
@@ -122,6 +123,11 @@ public:
[view displayIfNeeded];
}
+ void setTranslateDeadKeys(bool state) {
+ OSXView* view = (OSXView*)m_window.contentView;
+ [view setTranslateDeadKeys:(state ? YES: NO)];
+ }
+
void* handle() {
return (__bridge void*)m_window;
}
@@ -450,6 +456,12 @@ void SkiaWindow::updateWindow(const gfx::Rect& bounds)
m_impl->updateWindow(bounds);
}
+void SkiaWindow::setTranslateDeadKeys(bool state)
+{
+ if (m_impl)
+ m_impl->setTranslateDeadKeys(state);
+}
+
void* SkiaWindow::handle()
{
if (m_impl)
diff --git a/src/she/skia/skia_window_x11.h b/src/she/skia/skia_window_x11.h
index 18e34cd97..454b2f12c 100644
--- a/src/she/skia/skia_window_x11.h
+++ b/src/she/skia/skia_window_x11.h
@@ -44,6 +44,10 @@ public:
std::string getLayout() { return ""; }
void setLayout(const std::string& layout) { }
+ void setTranslateDeadKeys(bool state) {
+ // Do nothing
+ }
+
private:
void onExpose() override;
diff --git a/src/she/system.cpp b/src/she/system.cpp
new file mode 100644
index 000000000..8155fb949
--- /dev/null
+++ b/src/she/system.cpp
@@ -0,0 +1,31 @@
+// SHE library
+// Copyright (C) 2016 David Capello
+//
+// This file is released under the terms of the MIT license.
+// Read LICENSE.txt for more information.
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "base/debug.h"
+#include "she/system.h"
+
+namespace she {
+
+static System* g_system = nullptr;
+
+System* create_system_impl(); // Defined on each back-end
+
+System* create_system()
+{
+ ASSERT(!g_system);
+ return g_system = create_system_impl();
+}
+
+System* instance()
+{
+ return g_system;
+}
+
+} // namespace she
diff --git a/src/she/system.h b/src/she/system.h
index cce34ad29..3bef4e756 100644
--- a/src/she/system.h
+++ b/src/she/system.h
@@ -10,6 +10,7 @@
#include "gfx/fwd.h"
#include "she/capabilities.h"
+#include "she/keys.h"
#include
@@ -49,6 +50,26 @@ namespace she {
virtual Surface* loadRgbaSurface(const char* filename) = 0;
virtual Font* loadSpriteSheetFont(const char* filename, int scale = 1) = 0;
virtual Font* loadTrueTypeFont(const char* filename, int height) = 0;
+
+ // Returns true if the the given scancode key is pressed/actived.
+ virtual bool isKeyPressed(KeyScancode scancode) = 0;
+
+ // Returns the active pressed modifiers.
+ virtual KeyModifiers keyModifiers() = 0;
+
+ // Returns the latest unicode character that activated the given
+ // scancode.
+ virtual int getUnicodeFromScancode(KeyScancode scancode) = 0;
+
+ // Clears the keyboard buffer (used only in the Allegro port).
+ // TODO (deprecated)
+ virtual void clearKeyboardBuffer() = 0;
+
+ // Indicates if you want to use dead keys or not. By default it's
+ // false, which behaves as regular shortcuts. You should set this
+ // to true when you're inside a text field in your app.
+ virtual void setTranslateDeadKeys(bool state) = 0;
+
};
System* create_system();
diff --git a/src/she/win/event_queue.h b/src/she/win/event_queue.h
index 0466d0376..d9fd5978a 100644
--- a/src/she/win/event_queue.h
+++ b/src/she/win/event_queue.h
@@ -1,5 +1,5 @@
// SHE library
-// Copyright (C) 2012-2015 David Capello
+// Copyright (C) 2012-2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@@ -19,6 +19,9 @@ namespace she {
class WinEventQueue : public EventQueue {
public:
+ WinEventQueue() : m_translateDeadKeys(false) {
+ }
+
void getEvent(Event& ev, bool canWait) override {
MSG msg;
@@ -34,7 +37,18 @@ public:
}
if (res) {
- TranslateMessage(&msg);
+ // Avoid transforming WM_KEYDOWN/UP into WM_DEADCHAR/WM_CHAR
+ // messages. Dead keys are converted manually in the
+ // WM_KEYDOWN processing on our WinWindow class.
+ //
+ // From MSDN TranslateMessage() documentation:
+ // "WM_KEYDOWN and WM_KEYUP combinations produce a WM_CHAR
+ // or WM_DEADCHAR message."
+ // https://msdn.microsoft.com/en-us/library/windows/desktop/ms644955.aspx
+ if (msg.message != WM_KEYDOWN &&
+ msg.message != WM_KEYUP) {
+ TranslateMessage(&msg);
+ }
DispatchMessage(&msg);
}
else if (!canWait)
@@ -54,8 +68,13 @@ public:
m_events.push(ev);
}
+ void setTranslateDeadKeys(bool state) {
+ m_translateDeadKeys = state;
+ }
+
private:
std::queue m_events;
+ bool m_translateDeadKeys;
};
typedef WinEventQueue EventQueueImpl;
diff --git a/src/she/win/system.h b/src/she/win/system.h
index 48414127f..70e8a922c 100644
--- a/src/she/win/system.h
+++ b/src/she/win/system.h
@@ -13,6 +13,9 @@
namespace she {
+bool win_is_key_pressed(KeyScancode scancode);
+int win_get_unicode_from_scancode(KeyScancode scancode);
+
class WindowSystem : public CommonSystem {
public:
WindowSystem() { }
@@ -22,6 +25,14 @@ public:
return m_penApi;
}
+ bool isKeyPressed(KeyScancode scancode) override {
+ return win_is_key_pressed(scancode);
+ }
+
+ int getUnicodeFromScancode(KeyScancode scancode) override {
+ return win_get_unicode_from_scancode(scancode);
+ }
+
private:
PenAPI m_penApi;
};
diff --git a/src/she/win/vk.cpp b/src/she/win/vk.cpp
index abab81426..f5b6355a1 100644
--- a/src/she/win/vk.cpp
+++ b/src/she/win/vk.cpp
@@ -8,9 +8,7 @@
#include "config.h"
#endif
-#include "she/keys.h"
-
-#include
+#include "she/win/vk.h"
namespace she {
@@ -116,7 +114,7 @@ KeyScancode win32vk_to_scancode(int vk)
kKeyZ, // 0x5A - VK_Z
kKeyLWin, // 0x5B - VK_LWIN
kKeyRWin, // 0x5C - VK_RWIN
- kKeyNil, // 0x5D - VK_APPS
+ kKeyMenu, // 0x5D - VK_APPS
kKeyNil, // 0x5E - Reserved
kKeyNil, // 0x5F - VK_SLEEP
// 0x60
@@ -337,7 +335,7 @@ static int scancode_to_win32vk(KeyScancode scancode)
return keymap[scancode];
}
-bool is_key_pressed(KeyScancode scancode)
+bool win_is_key_pressed(KeyScancode scancode)
{
int vk = scancode_to_win32vk(scancode);
if (vk)
@@ -346,4 +344,18 @@ bool is_key_pressed(KeyScancode scancode)
return false;
}
+int win_get_unicode_from_scancode(KeyScancode scancode)
+{
+ int vk = scancode_to_win32vk(scancode);
+ if (vk && (GetAsyncKeyState(vk) & 0x8000 ? true: false)) {
+ VkToUnicode tu;
+ if (tu) {
+ tu.toUnicode(vk, 0);
+ if (tu.size() > 0)
+ return tu[0];
+ }
+ }
+ return 0;
+}
+
} // namespace she
diff --git a/src/she/win/vk.h b/src/she/win/vk.h
new file mode 100644
index 000000000..c29dcf7d3
--- /dev/null
+++ b/src/she/win/vk.h
@@ -0,0 +1,59 @@
+// SHE library
+// Copyright (C) 2012-2016 David Capello
+//
+// This file is released under the terms of the MIT license.
+// Read LICENSE.txt for more information.
+
+#ifndef SHE_WIN_VK_H_INCLUDED
+#define SHE_WIN_VK_H_INCLUDED
+#pragma once
+
+#include
+
+#include "she/keys.h"
+
+namespace she {
+
+ KeyScancode win32vk_to_scancode(int vk);
+ KeyModifiers get_modifiers_from_last_win32_message();
+
+ class VkToUnicode {
+ public:
+ VkToUnicode() : m_size(0) {
+ m_ok = (GetKeyboardState(&m_keystate[0]) ? true: false);
+ }
+
+ void toUnicode(int vk, int scancode) {
+ // ToUnicode returns several characters inside the buffer in
+ // case that a dead-key wasn't combined with the next pressed
+ // character.
+ m_size =
+ ToUnicode(vk, scancode, m_keystate, m_buffer,
+ sizeof(m_buffer)/sizeof(m_buffer[0]), 0);
+ }
+
+ operator bool() { return m_ok; }
+ int size() const { return ABS(m_size); }
+ LPCWSTR begin() const { return m_buffer; }
+ LPCWSTR end() const { return m_buffer+size(); }
+
+ int operator[](const int i) {
+ ASSERT(i >= 0 && i < size());
+ return m_buffer[i];
+ }
+
+ // ToUnicode() returns -1 if there is dead key waiting
+ bool isDeadKey() const {
+ return (m_size == -1);
+ }
+
+ private:
+ bool m_ok;
+ BYTE m_keystate[256];
+ WCHAR m_buffer[8];
+ int m_size;
+ };
+
+} // namespace she
+
+#endif
diff --git a/src/she/win/window.h b/src/she/win/window.h
index 754954643..07a28aa67 100644
--- a/src/she/win/window.h
+++ b/src/she/win/window.h
@@ -17,11 +17,12 @@
#include
#include "base/base.h"
+#include "base/debug.h"
#include "gfx/size.h"
#include "she/event.h"
-#include "she/keys.h"
#include "she/native_cursor.h"
#include "she/win/system.h"
+#include "she/win/vk.h"
#include "she/win/window_dde.h"
#ifndef WM_MOUSEHWHEEL
@@ -30,9 +31,6 @@
namespace she {
- KeyScancode win32vk_to_scancode(int vk);
- KeyModifiers get_modifiers_from_last_win32_message();
-
#define SHE_WND_CLASS_NAME L"Aseprite.Window"
template
@@ -42,6 +40,7 @@ namespace she {
: m_clientSize(1, 1)
, m_restoredSize(0, 0)
, m_isCreated(false)
+ , m_translateDeadKeys(false)
, m_hasMouse(false)
, m_captureMouse(false)
, m_hpenctx(nullptr)
@@ -321,6 +320,23 @@ namespace she {
}
}
+ void setTranslateDeadKeys(bool state) {
+ m_translateDeadKeys = state;
+
+ // Here we clear dead keys so we don't get those keys in the new
+ // "translate dead keys" state. E.g. If we focus a text entry
+ // field and the translation of dead keys is enabled, we don't
+ // want to get previous dead keys. The same in case we leave the
+ // text field with a pending dead key, that dead key must be
+ // discarded.
+ VkToUnicode tu;
+ if (tu) {
+ tu.toUnicode(VK_SPACE, 0);
+ if (tu.size() != 0)
+ tu.toUnicode(VK_SPACE, 0);
+ }
+ }
+
HWND handle() {
return m_hwnd;
}
@@ -531,8 +547,6 @@ namespace she {
(msg == WM_MOUSEWHEEL ? -z: 0));
ev.setWheelDelta(delta);
- //LOG("WHEEL: %d %d\n", delta.x, delta.y);
-
queueEvent(ev);
break;
}
@@ -578,8 +592,6 @@ namespace she {
(msg == WM_VSCROLL ? (z-50): 0));
ev.setWheelDelta(delta);
- //LOG("SCROLL: %d %d\n", delta.x, delta.y);
-
SetScrollPos(m_hwnd, bar, 50, FALSE);
queueEvent(ev);
@@ -590,34 +602,38 @@ namespace she {
case WM_KEYDOWN: {
int vk = wparam;
int scancode = (lparam >> 16) & 0xff;
- BYTE keystate[256];
- WCHAR buffer[8];
- int charsInBuffer = 0;
-
- if (GetKeyboardState(&keystate[0])) {
- // ToUnicode can return several characters inside the
- // buffer in case that a dead-key wasn't combined with the
- // next pressed character.
- charsInBuffer = ToUnicode(vk, scancode, keystate, buffer,
- sizeof(buffer)/sizeof(buffer[0]), 0);
- }
+ bool sendMsg = true;
Event ev;
ev.setType(Event::KeyDown);
ev.setModifiers(get_modifiers_from_last_win32_message());
ev.setScancode(win32vk_to_scancode(vk));
+ ev.setUnicodeChar(0);
ev.setRepeat(MAX(0, (lparam & 0xffff)-1));
- if (charsInBuffer < 1) {
- ev.setUnicodeChar(0);
- queueEvent(ev);
- }
- else {
- for (int i=0; i 0) {
+ sendMsg = false;
+ for (int chr : tu) {
+ ev.setUnicodeChar(chr);
+ queueEvent(ev);
+ }
+ }
}
}
+
+ if (sendMsg)
+ queueEvent(ev);
+
return 0;
}
@@ -856,6 +872,7 @@ namespace she {
gfx::Size m_restoredSize;
int m_scale;
bool m_isCreated;
+ bool m_translateDeadKeys;
bool m_hasMouse;
bool m_captureMouse;
bool m_customHcursor;
diff --git a/src/she/x11/keys.cpp b/src/she/x11/keys.cpp
index 303072fcc..717d79e7f 100644
--- a/src/she/x11/keys.cpp
+++ b/src/she/x11/keys.cpp
@@ -12,9 +12,14 @@
namespace she {
-bool is_key_pressed(KeyScancode scancode)
+bool x11_is_key_pressed(KeyScancode scancode)
{
return false; // TODO
}
+int x11_get_unicode_from_scancode(KeyScancode scancode)
+{
+ return 0; // TODO
+}
+
} // namespace she
diff --git a/src/she/x11/system.h b/src/she/x11/system.h
new file mode 100644
index 000000000..bd1b4566f
--- /dev/null
+++ b/src/she/x11/system.h
@@ -0,0 +1,31 @@
+// SHE library
+// Copyright (C) 2016 David Capello
+//
+// This file is released under the terms of the MIT license.
+// Read LICENSE.txt for more information.
+
+#ifndef SHE_X11_SYSTEM_H
+#define SHE_X11_SYSTEM_H
+#pragma once
+
+#include "she/common/system.h"
+
+namespace she {
+
+bool x11_is_key_pressed(KeyScancode scancode);
+int x11_get_unicode_from_scancode(KeyScancode scancode);
+
+class X11System : public CommonSystem {
+public:
+ bool isKeyPressed(KeyScancode scancode) override {
+ return x11_is_key_pressed(scancode);
+ }
+
+ int getUnicodeFromScancode(KeyScancode scancode) override {
+ return x11_get_unicode_from_scancode(scancode);
+ }
+};
+
+} // namespace she
+
+#endif
diff --git a/src/tests/test.h b/src/tests/test.h
index 210dc4ecc..9238ab4d1 100644
--- a/src/tests/test.h
+++ b/src/tests/test.h
@@ -1,5 +1,5 @@
// Aseprite
-// Copyright (C) 2001-2015 David Capello
+// Copyright (C) 2001-2016 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
diff --git a/src/ui/accelerator.cpp b/src/ui/accelerator.cpp
index fc4c6a47a..8865f9e93 100644
--- a/src/ui/accelerator.cpp
+++ b/src/ui/accelerator.cpp
@@ -15,6 +15,7 @@
#include "base/split_string.h"
#include "base/string.h"
#include "base/unique_ptr.h"
+#include "she/system.h"
#include
#include
@@ -23,9 +24,6 @@
#include
-// #define REPORT_KEYS
-#define PREPROCESS_KEYS
-
namespace ui {
#ifdef _WIN32
@@ -34,19 +32,117 @@ namespace ui {
const char* kWinKeyName = "Super";
#endif
-static KeyModifiers get_pressed_modifiers_from_she()
-{
- KeyModifiers mods = kKeyNoneModifier;
- if (she::is_key_pressed(kKeyLShift) ) mods = KeyModifiers(int(mods) | int(kKeyShiftModifier));
- if (she::is_key_pressed(kKeyRShift) ) mods = KeyModifiers(int(mods) | int(kKeyShiftModifier));
- if (she::is_key_pressed(kKeyLControl)) mods = KeyModifiers(int(mods) | int(kKeyCtrlModifier));
- if (she::is_key_pressed(kKeyRControl)) mods = KeyModifiers(int(mods) | int(kKeyCtrlModifier));
- if (she::is_key_pressed(kKeyAlt) ) mods = KeyModifiers(int(mods) | int(kKeyAltModifier));
- if (she::is_key_pressed(kKeyCommand) ) mods = KeyModifiers(int(mods) | int(kKeyCmdModifier));
- if (she::is_key_pressed(kKeyLWin) ) mods = KeyModifiers(int(mods) | int(kKeyWinModifier));
- if (she::is_key_pressed(kKeyRWin) ) mods = KeyModifiers(int(mods) | int(kKeyWinModifier));
- return mods;
-}
+namespace {
+
+const char* scancode_to_string[] = { // Same order that she::KeyScancode
+ nullptr,
+ "A",
+ "B",
+ "C",
+ "D",
+ "E",
+ "F",
+ "G",
+ "H",
+ "I",
+ "J",
+ "K",
+ "L",
+ "M",
+ "N",
+ "O",
+ "P",
+ "Q",
+ "R",
+ "S",
+ "T",
+ "U",
+ "V",
+ "W",
+ "X",
+ "Y",
+ "Z",
+ "0",
+ "1",
+ "2",
+ "3",
+ "4",
+ "5",
+ "6",
+ "7",
+ "8",
+ "9",
+ "0 Pad",
+ "1 Pad",
+ "2 Pad",
+ "3 Pad",
+ "4 Pad",
+ "5 Pad",
+ "6 Pad",
+ "7 Pad",
+ "8 Pad",
+ "9 Pad",
+ "F1",
+ "F2",
+ "F3",
+ "F4",
+ "F5",
+ "F6",
+ "F7",
+ "F8",
+ "F9",
+ "F10",
+ "F11",
+ "F12",
+ "Esc",
+ "~",
+ "-",
+ "=",
+ "Backspace",
+ "Tab",
+ "[",
+ "]",
+ "Enter",
+ ";",
+ "\'",
+ "\\",
+ "KEY_BACKSLASH2",
+ ",",
+ ".",
+ "/",
+ "Space",
+ "Ins",
+ "Del",
+ "Home",
+ "End",
+ "PgUp",
+ "PgDn",
+ "Left",
+ "Right",
+ "Up",
+ "Down",
+ "/ Pad",
+ "* Pad",
+ "- Pad",
+ "+ Pad",
+ "Del Pad",
+ "Enter Pad",
+ "PrtScr",
+ "Pause",
+ "KEY_ABNT_C1",
+ "Yen",
+ "Kana",
+ "KEY_CONVERT",
+ "KEY_NOCONVERT",
+ "KEY_AT",
+ "KEY_CIRCUMFLEX",
+ "KEY_COLON2",
+ "Kanji",
+};
+int scancode_to_string_size =
+ sizeof(scancode_to_string) / sizeof(scancode_to_string[0]);
+
+} // anonymous namespace
Accelerator::Accelerator()
: m_modifiers(kKeyNoneModifier)
@@ -111,36 +207,8 @@ Accelerator::Accelerator(const std::string& str)
ASSERT(false && "Something wrong converting utf-8 to wchar string");
continue;
}
-
- wchar_t wchr = wstr[0];
- wchr = tolower(wchr);
-
- if ((wchr >= 'a') && (wchr <= 'z')) {
- m_unicodeChar = wchr;
- m_scancode = (KeyScancode)((int)kKeyA + wchr - 'a');
- }
- else {
- m_unicodeChar = wchr;
-
- if ((wchr >= '0') && (wchr <= '9'))
- m_scancode = (KeyScancode)((int)kKey0 + wchr - '0');
- else {
- switch (wchr) {
- case '~': m_scancode = kKeyTilde; break;
- case '-': m_scancode = kKeyMinus; break;
- case '=': m_scancode = kKeyEquals; break;
- case '[': m_scancode = kKeyOpenbrace; break;
- case ']': m_scancode = kKeyClosebrace; break;
- case ';': m_scancode = kKeyColon; break;
- case '\'': m_scancode = kKeyQuote; break;
- case '\\': m_scancode = kKeyBackslash; break;
- case ',': m_scancode = kKeyComma; break;
- case '.': m_scancode = kKeyStop; break;
- case '/': m_scancode = kKeySlash; break;
- case '*': m_scancode = kKeyAsterisk; break;
- }
- }
- }
+ m_unicodeChar = std::tolower(wstr[0]);
+ m_scancode = kKeyNil;
}
// Other ones
else {
@@ -218,19 +286,8 @@ Accelerator::Accelerator(const std::string& str)
bool Accelerator::operator==(const Accelerator& other) const
{
- if (m_modifiers != other.m_modifiers)
- return false;
-
- if (m_scancode == other.m_scancode) {
- if (m_scancode != kKeyNil)
- return true;
- else if (m_unicodeChar != 0)
- return (std::tolower(m_unicodeChar) == std::tolower(other.m_unicodeChar));
- else // Only comparing modifiers, and they are equal
- return true;
- }
-
- return false;
+ // TODO improve this, avoid conversion to std::string
+ return toString() == other.toString();
}
bool Accelerator::isEmpty() const
@@ -243,114 +300,6 @@ bool Accelerator::isEmpty() const
std::string Accelerator::toString() const
{
- // Same order that she::KeyScancode
- static const char* table[] = {
- NULL,
- "A",
- "B",
- "C",
- "D",
- "E",
- "F",
- "G",
- "H",
- "I",
- "J",
- "K",
- "L",
- "M",
- "N",
- "O",
- "P",
- "Q",
- "R",
- "S",
- "T",
- "U",
- "V",
- "W",
- "X",
- "Y",
- "Z",
- "0",
- "1",
- "2",
- "3",
- "4",
- "5",
- "6",
- "7",
- "8",
- "9",
- "0 Pad",
- "1 Pad",
- "2 Pad",
- "3 Pad",
- "4 Pad",
- "5 Pad",
- "6 Pad",
- "7 Pad",
- "8 Pad",
- "9 Pad",
- "F1",
- "F2",
- "F3",
- "F4",
- "F5",
- "F6",
- "F7",
- "F8",
- "F9",
- "F10",
- "F11",
- "F12",
- "Esc",
- "~",
- "-",
- "=",
- "Backspace",
- "Tab",
- "[",
- "]",
- "Enter",
- ";",
- "\'",
- "\\",
- "KEY_BACKSLASH2",
- ",",
- ".",
- "/",
- "Space",
- "Ins",
- "Del",
- "Home",
- "End",
- "PgUp",
- "PgDn",
- "Left",
- "Right",
- "Up",
- "Down",
- "/ Pad",
- "* Pad",
- "- Pad",
- "+ Pad",
- "Del Pad",
- "Enter Pad",
- "PrtScr",
- "Pause",
- "KEY_ABNT_C1",
- "Yen",
- "Kana",
- "KEY_CONVERT",
- "KEY_NOCONVERT",
- "KEY_AT",
- "KEY_CIRCUMFLEX",
- "KEY_COLON2",
- "Kanji",
- };
- static std::size_t table_size = sizeof(table) / sizeof(table[0]);
-
std::string buf;
// Shifts
@@ -358,7 +307,11 @@ std::string Accelerator::toString() const
if (m_modifiers & kKeyCmdModifier) buf += "Cmd+";
if (m_modifiers & kKeyAltModifier) buf += "Alt+";
if (m_modifiers & kKeyShiftModifier) buf += "Shift+";
- if (m_modifiers & kKeySpaceModifier) buf += "Space+";
+ if ((m_modifiers & kKeySpaceModifier) &&
+ (m_scancode != kKeySpace) &&
+ (m_unicodeChar != ' ')) {
+ buf += "Space+";
+ }
if (m_modifiers & kKeyWinModifier) {
buf += kWinKeyName;
buf += "+";
@@ -367,11 +320,12 @@ std::string Accelerator::toString() const
// Key
if (m_unicodeChar) {
std::wstring wideUnicodeChar;
- wideUnicodeChar.push_back((wchar_t)toupper(m_unicodeChar));
+ wideUnicodeChar.push_back((wchar_t)std::toupper(m_unicodeChar));
buf += base::to_utf8(wideUnicodeChar);
}
- else if (m_scancode && m_scancode > 0 && m_scancode < (int)table_size)
- buf += table[m_scancode];
+ else if (m_scancode > 0 &&
+ m_scancode < scancode_to_string_size)
+ buf += scancode_to_string[m_scancode];
else if (!buf.empty() && buf[buf.size()-1] == '+')
buf.erase(buf.size()-1);
@@ -380,97 +334,59 @@ std::string Accelerator::toString() const
bool Accelerator::isPressed(KeyModifiers modifiers, KeyScancode scancode, int unicodeChar) const
{
- // Preprocess the character to be compared with the accelerator
-#ifdef PREPROCESS_KEYS
- // Directly scancode
- if ((scancode >= kKeyF1 && scancode <= kKeyF12) ||
- (scancode == kKeyEsc) ||
- (scancode == kKeyBackspace) ||
- (scancode == kKeyTab) ||
- (scancode == kKeyEnter) ||
- (scancode == kKeyBackslash) ||
- (scancode == kKeyBackslash2) ||
- (scancode >= kKeySpace && scancode <= kKeyDown) ||
- (scancode >= kKeyEnterPad && scancode <= kKeyNoconvert) ||
- (scancode == kKeyKanji)) {
- unicodeChar = 0;
- }
- // For Ctrl+number
- /* scancode unicodeChar
- Ctrl+0 27 0
- Ctrl+1 28 2
- Ctrl+2 29 0
- Ctrl+3 30 27
- Ctrl+4 31 28
- Ctrl+5 32 29
- Ctrl+6 33 30
- Ctrl+7 34 31
- Ctrl+8 35 127
- Ctrl+9 36 2
- */
- else if ((scancode >= kKey0 && scancode <= kKey9) &&
- (unicodeChar < 32 || unicodeChar == 127)) {
- unicodeChar = '0' + scancode - kKey0;
- scancode = kKeyNil;
- }
- // For Ctrl+letter
- else if (unicodeChar >= 1 && unicodeChar <= 'z'-'a'+1) {
- unicodeChar = 'a'+unicodeChar-1;
- scancode = kKeyNil;
- }
- // For any other legal Unicode code
- else if (unicodeChar >= ' ') {
- unicodeChar = std::tolower(unicodeChar);
-
- /* without shift (because characters like '*' can be trigger with
- "Shift+8", so we don't want "Shift+*") */
- if (!(unicodeChar >= 'a' && unicodeChar <= 'z'))
- modifiers = (KeyModifiers)((int)modifiers & ((int)~kKeyShiftModifier));
-
- scancode = kKeyNil;
- }
-#endif
-
-#ifdef REPORT_KEYS
- printf("%3d==%3d %3d==%3d %s==%s ",
- m_scancode, scancode,
- m_unicodeChar, unicodeChar,
- toString().c_str(),
- Accelerator(modifiers, scancode, unicodeChar).toString().c_str());
-#endif
-
- if ((m_modifiers == modifiers) &&
- ((m_scancode != kKeyNil && m_scancode == scancode) ||
- (m_unicodeChar && m_unicodeChar == unicodeChar) ||
- (m_scancode == kKeyNil && scancode == kKeyNil && !m_unicodeChar && !unicodeChar))) {
-#ifdef REPORT_KEYS
- printf("true\n");
- fflush(stdout);
-#endif
- return true;
- }
-
-#ifdef REPORT_KEYS
- printf("false\n");
- fflush(stdout);
-#endif
- return false;
+ return ((scancode && *this == Accelerator(modifiers, scancode, 0)) ||
+ (unicodeChar && *this == Accelerator(modifiers, kKeyNil, unicodeChar)));
}
bool Accelerator::isPressed() const
{
- KeyModifiers modifiers = get_pressed_modifiers_from_she();
+ she::System* sys = she::instance();
+ if (!sys)
+ return false;
- return ((m_scancode == 0 || she::is_key_pressed(m_scancode)) &&
- (m_modifiers == modifiers));
+ KeyModifiers pressedModifiers = sys->keyModifiers();
+
+ // Check if this shortcut is only
+ if (m_scancode == kKeyNil && m_unicodeChar == 0)
+ return (m_modifiers == pressedModifiers);
+
+ // Compare with all pressed scancodes
+ for (int s=int(kKeyNil); sisKeyPressed(KeyScancode(s)) &&
+ isPressed(pressedModifiers,
+ KeyScancode(s),
+ sys->getUnicodeFromScancode(KeyScancode(s))))
+ return true;
+ }
+
+ return false;
}
bool Accelerator::isLooselyPressed() const
{
- KeyModifiers modifiers = get_pressed_modifiers_from_she();
+ she::System* sys = she::instance();
+ if (!sys)
+ return false;
- return ((m_scancode == 0 || she::is_key_pressed(m_scancode)) &&
- (int(m_modifiers & modifiers) == m_modifiers));
+ KeyModifiers pressedModifiers = sys->keyModifiers();
+
+ if ((m_modifiers & pressedModifiers) != m_modifiers)
+ return false;
+
+ // Check if this shortcut is only
+ if (m_scancode == kKeyNil && m_unicodeChar == 0)
+ return true;
+
+ // Compare with all pressed scancodes
+ for (int s=int(kKeyNil); sisKeyPressed(KeyScancode(s)) &&
+ isPressed(m_modifiers, // Use same modifiers (we've already compared the modifiers)
+ KeyScancode(s),
+ sys->getUnicodeFromScancode(KeyScancode(s))))
+ return true;
+ }
+
+ return false;
}
//////////////////////////////////////////////////////////////////////
diff --git a/src/ui/accelerator.h b/src/ui/accelerator.h
index 3bfd58667..5afcf618a 100644
--- a/src/ui/accelerator.h
+++ b/src/ui/accelerator.h
@@ -17,6 +17,7 @@ namespace ui {
extern const char* kWinKeyName;
+ // TODO rename this class to Shortcut
class Accelerator {
public:
Accelerator();
@@ -29,12 +30,12 @@ namespace ui {
bool isPressed(KeyModifiers modifiers, KeyScancode scancode, int unicodeChar) const;
- // Returns true if the key is pressed and only its modifiers are
+ // Returns true if the key is pressed and ONLY its modifiers are
// pressed.
bool isPressed() const;
- // Returns true if the key is pressed and the accelerator
- // modifiers are pressed (other modifiers are allowed).
+ // Returns true if the key + its modifiers are pressed (other
+ // modifiers are allowed too).
bool isLooselyPressed() const;
bool operator==(const Accelerator& other) const;
@@ -52,6 +53,7 @@ namespace ui {
int m_unicodeChar;
};
+ // TODO rename this class to Shortcuts
class Accelerators {
public:
typedef std::vector List;
diff --git a/src/ui/alert.cpp b/src/ui/alert.cpp
index c6ed1c847..29380ee51 100644
--- a/src/ui/alert.cpp
+++ b/src/ui/alert.cpp
@@ -35,6 +35,7 @@
#include "ui/alert.h"
#include "base/bind.h"
+#include "base/string.h"
#include "ui/box.h"
#include "ui/button.h"
#include "ui/grid.h"
@@ -73,50 +74,43 @@ void Alert::setProgress(double progress)
AlertPtr Alert::create(const char* format, ...)
{
- char buf[4096]; // TODO warning buffer overflow
- va_list ap;
-
// Process arguments
+ std::va_list ap;
va_start(ap, format);
- vsprintf(buf, format, ap);
+ std::string msg = base::string_vprintf(format, ap);
va_end(ap);
// Create the alert window
- std::vector labels;
- std::vector buttons;
-
AlertPtr window(new Alert());
- window->processString(buf, labels, buttons);
-
+ window->processString(msg);
return window;
}
// static
int Alert::show(const char* format, ...)
{
- char buf[4096]; // TODO warning buffer overflow
- va_list ap;
-
// Process arguments
+ std::va_list ap;
va_start(ap, format);
- vsprintf(buf, format, ap);
+ std::string msg = base::string_vprintf(format, ap);
va_end(ap);
// Create the alert window
- std::vector labels;
- std::vector buttons;
-
AlertPtr window(new Alert());
- window->processString(buf, labels, buttons);
+ window->processString(msg);
+ return window->show();
+}
+int Alert::show()
+{
// Open it
- window->openWindowInForeground();
+ openWindowInForeground();
// Check the closer
int ret = 0;
- if (Widget* closer = window->closer()) {
- for (int i=0; i<(int)buttons.size(); ++i) {
- if (closer == buttons[i]) {
+ if (Widget* closer = this->closer()) {
+ for (int i=0; i<(int)m_buttons.size(); ++i) {
+ if (closer == m_buttons[i]) {
ret = i+1;
break;
}
@@ -127,19 +121,18 @@ int Alert::show(const char* format, ...)
return ret;
}
-void Alert::processString(char* buf, std::vector& labels, std::vector& buttons)
+void Alert::processString(std::string& buf)
{
bool title = true;
bool label = false;
bool separator = false;
bool button = false;
int align = 0;
- char *beg;
- int c, chr;
+ int c, beg;
// Process buffer
c = 0;
- beg = buf;
+ beg = 0;
for (; ; c++) {
if ((!buf[c]) ||
((buf[c] == buf[c+1]) &&
@@ -149,41 +142,38 @@ void Alert::processString(char* buf, std::vector& labels, std::vectorsetAlign(align);
- labels.push_back(label);
+ m_labels.push_back(label);
}
else if (separator) {
- labels.push_back(new Separator("", HORIZONTAL));
+ m_labels.push_back(new Separator("", HORIZONTAL));
}
else if (button) {
char buttonId[256];
- Button* button_widget = new Button(beg);
+ Button* button_widget = new Button(item);
button_widget->setMinSize(gfx::Size(60*guiscale(), 0));
- buttons.push_back(button_widget);
+ m_buttons.push_back(button_widget);
- sprintf(buttonId, "button-%lu", buttons.size());
+ sprintf(buttonId, "button-%lu", m_buttons.size());
button_widget->setId(buttonId);
button_widget->Click.connect(base::Bind(&Window::closeWindow, this, button_widget));
}
-
- buf[c] = chr;
}
- /* done */
+ // Done
if (!buf[c])
break;
- /* next widget */
+ // Next widget
else {
title = label = separator = button = false;
- beg = buf+c+2;
+ beg = c+2;
align = 0;
switch (buf[c]) {
@@ -230,15 +220,15 @@ void Alert::processString(char* buf, std::vector& labels, std::vectoraddChildInCell(box3, 1, 1, CENTER | BOTTOM | HORIZONTAL);
- for (std::vector::iterator it = labels.begin(); it != labels.end(); ++it)
+ for (auto it=m_labels.begin(); it!=m_labels.end(); ++it)
box2->addChild(*it);
- for (std::vector::iterator it = buttons.begin(); it != buttons.end(); ++it)
+ for (auto it=m_buttons.begin(); it!=m_buttons.end(); ++it)
box3->addChild(*it);
// Default button is the last one
- if (!buttons.empty())
- buttons[buttons.size()-1]->setFocusMagnet(true);
+ if (!m_buttons.empty())
+ m_buttons[m_buttons.size()-1]->setFocusMagnet(true);
}
} // namespace ui
diff --git a/src/ui/alert.h b/src/ui/alert.h
index c157aedac..37c30709f 100644
--- a/src/ui/alert.h
+++ b/src/ui/alert.h
@@ -1,5 +1,5 @@
// Aseprite UI Library
-// Copyright (C) 2001-2013, 2015 David Capello
+// Copyright (C) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@@ -11,6 +11,9 @@
#include "base/shared_ptr.h"
#include "ui/window.h"
+#include
+#include
+
namespace ui {
class Box;
@@ -26,14 +29,18 @@ namespace ui {
void addProgress();
void setProgress(double progress);
+ int show();
+
static AlertPtr create(const char* format, ...);
static int show(const char* format, ...);
private:
- void processString(char* buf, std::vector& labels, std::vector& buttons);
+ void processString(std::string& buf);
Slider* m_progress;
Box* m_progressPlaceholder;
+ std::vector m_labels;
+ std::vector m_buttons;
};
} // namespace ui
diff --git a/src/ui/button.cpp b/src/ui/button.cpp
index b837fcac6..6b430e3f0 100644
--- a/src/ui/button.cpp
+++ b/src/ui/button.cpp
@@ -100,9 +100,8 @@ bool ButtonBase::onProcessMessage(Message* msg)
// If the button is enabled.
if (isEnabled()) {
bool mnemonicPressed =
- (msg->altPressed() &&
- mnemonicChar() &&
- mnemonicChar() == tolower(keymsg->unicodeChar()));
+ ((msg->altPressed() || msg->cmdPressed()) &&
+ mnemonicCharPressed(keymsg));
// For kButtonWidget
if (m_behaviorType == kButtonWidget) {
diff --git a/src/ui/entry.cpp b/src/ui/entry.cpp
index 4c71b3c43..f8f2a138a 100644
--- a/src/ui/entry.cpp
+++ b/src/ui/entry.cpp
@@ -14,6 +14,7 @@
#include "base/string.h"
#include "clip/clip.h"
#include "she/font.h"
+#include "she/system.h"
#include "ui/manager.h"
#include "ui/menu.h"
#include "ui/message.h"
@@ -41,6 +42,7 @@ Entry::Entry(std::size_t maxsize, const char* format, ...)
, m_password(false)
, m_recent_focused(false)
, m_lock_selection(false)
+ , m_translate_dead_keys(true)
{
enableFlags(CTRL_RIGHT_CLICK);
@@ -165,6 +167,11 @@ void Entry::setSuffix(const std::string& suffix)
invalidate();
}
+void Entry::setTranslateDeadKeys(bool state)
+{
+ m_translate_dead_keys = state;
+}
+
void Entry::getEntryThemeInfo(int* scroll, int* caret, int* state,
int* selbeg, int* selend)
{
@@ -213,6 +220,10 @@ bool Entry::onProcessMessage(Message* msg)
selectAllText();
m_recent_focused = true;
}
+
+ // Start processing dead keys
+ if (m_translate_dead_keys)
+ she::instance()->setTranslateDeadKeys(true);
break;
case kFocusLeaveMessage:
@@ -224,6 +235,10 @@ bool Entry::onProcessMessage(Message* msg)
deselectText();
m_recent_focused = false;
+
+ // Stop processing dead keys
+ if (m_translate_dead_keys)
+ she::instance()->setTranslateDeadKeys(false);
break;
case kKeyDownMessage:
@@ -285,7 +300,7 @@ bool Entry::onProcessMessage(Message* msg)
break;
default:
- // Map common Windows shortcuts for Cut/Copy/Paste
+ // Map common macOS/Windows shortcuts for Cut/Copy/Paste/Select all
#if defined __APPLE__
if (msg->onlyCmdPressed())
#else
@@ -299,21 +314,31 @@ bool Entry::onProcessMessage(Message* msg)
case kKeyA: cmd = EntryCmd::SelectAll; break;
}
}
- else if (manager()->isFocusMovementKey(msg)) {
- break;
- }
- else if (keymsg->unicodeChar() >= 32) {
- // Ctrl and Alt must be unpressed to insert a character
- // in the text-field.
- if ((msg->modifiers() & (kKeyCtrlModifier | kKeyAltModifier)) == 0) {
- cmd = EntryCmd::InsertChar;
- }
- }
break;
}
- if (cmd == EntryCmd::NoOp)
- break;
+ if (cmd == EntryCmd::NoOp) {
+ if (keymsg->unicodeChar() >= 32) {
+ executeCmd(EntryCmd::InsertChar, keymsg->unicodeChar(),
+ (msg->shiftPressed()) ? true: false);
+
+ // Select dead-key
+ if (keymsg->isDeadKey()) {
+ if (base::from_utf8(text()).size() < m_maxsize)
+ selectText(m_caret-1, m_caret);
+ }
+ return true;
+ }
+ // Consume all key down of modifiers only, e.g. so the user
+ // can press first "Ctrl" key, and then "Ctrl+C"
+ // combination.
+ else if (keymsg->scancode() >= kKeyFirstModifierScancode) {
+ return true;
+ }
+ else {
+ break; // Propagate to manager
+ }
+ }
executeCmd(cmd, keymsg->unicodeChar(),
(msg->shiftPressed()) ? true: false);
diff --git a/src/ui/entry.h b/src/ui/entry.h
index eaad41c74..52c3adcf0 100644
--- a/src/ui/entry.h
+++ b/src/ui/entry.h
@@ -37,6 +37,8 @@ namespace ui {
void setSuffix(const std::string& suffix);
const std::string& getSuffix() { return m_suffix; }
+ void setTranslateDeadKeys(bool state);
+
// for themes
void getEntryThemeInfo(int* scroll, int* caret, int* state,
int* selbeg, int* selend);
@@ -95,6 +97,7 @@ namespace ui {
bool m_password;
bool m_recent_focused;
bool m_lock_selection;
+ bool m_translate_dead_keys;
std::string m_suffix;
};
diff --git a/src/ui/int_entry.cpp b/src/ui/int_entry.cpp
index 77ab25d2d..bb083acbb 100644
--- a/src/ui/int_entry.cpp
+++ b/src/ui/int_entry.cpp
@@ -118,7 +118,7 @@ bool IntEntry::onProcessMessage(Message* msg)
if (hasFocus() && !isReadOnly()) {
KeyMessage* keymsg = static_cast(msg);
int chr = keymsg->unicodeChar();
- if (chr < '0' || chr > '9') {
+ if (chr && (chr < '0' || chr > '9')) {
// By-pass Entry::onProcessMessage()
return Widget::onProcessMessage(msg);
}
diff --git a/src/ui/keys.h b/src/ui/keys.h
index 25a99547c..cc99559f5 100644
--- a/src/ui/keys.h
+++ b/src/ui/keys.h
@@ -1,5 +1,5 @@
// Aseprite UI Library
-// Copyright (C) 2001-2015 David Capello
+// Copyright (C) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@@ -130,7 +130,6 @@ namespace ui {
using she::kKeyEqualsPad;
using she::kKeyBackquote;
using she::kKeySemicolon;
- using she::kKeyCommand;
using she::kKeyUnknown1;
using she::kKeyUnknown2;
using she::kKeyUnknown3;
@@ -151,6 +150,7 @@ namespace ui {
using she::kKeyLWin;
using she::kKeyRWin;
using she::kKeyMenu;
+ using she::kKeyCommand;
using she::kKeyScrLock;
using she::kKeyNumLock;
using she::kKeyCapsLock;
diff --git a/src/ui/listbox.cpp b/src/ui/listbox.cpp
index 91a01a044..97a3c6a33 100644
--- a/src/ui/listbox.cpp
+++ b/src/ui/listbox.cpp
@@ -19,17 +19,27 @@
#include "ui/theme.h"
#include "ui/view.h"
+#include
+
namespace ui {
using namespace gfx;
ListBox::ListBox()
: Widget(kListBoxWidget)
+ , m_multiselect(false)
+ , m_firstSelectedIndex(-1)
+ , m_lastSelectedIndex(-1)
{
setFocusStop(true);
initTheme();
}
+void ListBox::setMultiselect(const bool multiselect)
+{
+ m_multiselect = multiselect;
+}
+
Widget* ListBox::getSelectedChild()
{
for (auto child : children())
@@ -53,39 +63,87 @@ int ListBox::getSelectedIndex()
return -1;
}
-void ListBox::selectChild(Widget* item)
+int ListBox::getChildIndex(Widget* item)
{
- for (auto child : children()) {
- if (child->isSelected()) {
- if (item && child == item)
- return;
+ const WidgetsList& children = this->children();
+ auto it = std::find(children.begin(), children.end(), item);
+ if (it != children.end())
+ return it - children.begin();
+ else
+ return -1;
+}
- child->setSelected(false);
+Widget* ListBox::getChildByIndex(int index)
+{
+ const WidgetsList& children = this->children();
+ if (index >= 0 && index < int(children.size()))
+ return children[index];
+ else
+ return nullptr;
+}
+
+void ListBox::selectChild(Widget* item, Message* msg)
+{
+ int itemIndex = getChildIndex(item);
+ m_lastSelectedIndex = itemIndex;
+
+ if (m_multiselect) {
+ // Save current state of all children when we start selecting
+ if (msg == nullptr ||
+ msg->type() == kMouseDownMessage ||
+ msg->type() == kKeyDownMessage) {
+ m_firstSelectedIndex = itemIndex;
+ m_states.resize(children().size());
+
+ int i = 0;
+ for (auto child : children()) {
+ bool state = child->isSelected();
+ if (msg && !msg->ctrlPressed() && !msg->cmdPressed())
+ state = false;
+ m_states[i] = state;
+ ++i;
+ }
}
}
- if (item) {
- item->setSelected(true);
- makeChildVisible(item);
+ int i = 0;
+ for (auto child : children()) {
+ bool newState;
+
+ if (m_multiselect) {
+ newState = m_states[i];
+
+ if (i >= MIN(itemIndex, m_firstSelectedIndex) &&
+ i <= MAX(itemIndex, m_firstSelectedIndex)) {
+ newState = !newState;
+ }
+ }
+ else {
+ newState = (child == item);
+ }
+
+ if (child->isSelected() != newState)
+ child->setSelected(newState);
+
+ ++i;
}
+ if (item)
+ makeChildVisible(item);
+
onChange();
}
-void ListBox::selectIndex(int index)
+void ListBox::selectIndex(int index, Message* msg)
{
- const WidgetsList& children = this->children();
- if (index < 0 || index >= (int)children.size())
- return;
-
- ListItem* child = static_cast(children[index]);
- ASSERT(child);
- selectChild(child);
+ Widget* child = getChildByIndex(index);
+ if (child)
+ selectChild(child, msg);
}
-std::size_t ListBox::getItemsCount() const
+int ListBox::getItemsCount() const
{
- return children().size();
+ return int(children().size());
}
void ListBox::makeChildVisible(Widget* child)
@@ -152,21 +210,20 @@ bool ListBox::onProcessMessage(Message* msg)
case kMouseMoveMessage:
if (hasCapture()) {
gfx::Point mousePos = static_cast(msg)->position();
- int select = getSelectedIndex();
View* view = View::getView(this);
bool pick_item = true;
- if (view) {
+ if (view && m_lastSelectedIndex >= 0) {
gfx::Rect vp = view->viewportBounds();
if (mousePos.y < vp.y) {
int num = MAX(1, (vp.y - mousePos.y) / 8);
- selectIndex(select-num);
+ selectIndex(MID(0, m_lastSelectedIndex-num, getItemsCount()-1), msg);
pick_item = false;
}
else if (mousePos.y >= vp.y + vp.h) {
int num = MAX(1, (mousePos.y - (vp.y+vp.h-1)) / 8);
- selectIndex(select+num);
+ selectIndex(MID(0, m_lastSelectedIndex+num, getItemsCount()-1), msg);
pick_item = false;
}
}
@@ -181,10 +238,11 @@ bool ListBox::onProcessMessage(Message* msg)
picked = pick(mousePos);
}
- /* if the picked widget is a child of the list, select it */
+ // If the picked widget is a child of the list, select it
if (picked && hasChild(picked)) {
- if (ListItem* pickedItem = dynamic_cast(picked))
- selectChild(pickedItem);
+ if (ListItem* pickedItem = dynamic_cast(picked)) {
+ selectChild(pickedItem, msg);
+ }
}
}
@@ -199,8 +257,14 @@ bool ListBox::onProcessMessage(Message* msg)
case kMouseWheelMessage: {
View* view = View::getView(this);
if (view) {
+ auto mouseMsg = static_cast(msg);
gfx::Point scroll = view->viewScroll();
- scroll += static_cast(msg)->wheelDelta() * textHeight()*3;
+
+ if (mouseMsg->preciseWheel())
+ scroll += mouseMsg->wheelDelta();
+ else
+ scroll += mouseMsg->wheelDelta() * textHeight()*3;
+
view->setViewScroll(scroll);
}
break;
@@ -264,7 +328,7 @@ bool ListBox::onProcessMessage(Message* msg)
return Widget::onProcessMessage(msg);
}
- selectIndex(MID(0, select, bottom));
+ selectIndex(MID(0, select, bottom), msg);
return true;
}
break;
diff --git a/src/ui/listbox.h b/src/ui/listbox.h
index e27c45071..0939c7dc6 100644
--- a/src/ui/listbox.h
+++ b/src/ui/listbox.h
@@ -11,6 +11,8 @@
#include "obs/signal.h"
#include "ui/widget.h"
+#include
+
namespace ui {
class ListItem;
@@ -19,13 +21,16 @@ namespace ui {
public:
ListBox();
+ bool isMultiselect() const { return m_multiselect; }
+ void setMultiselect(const bool multiselect);
+
Widget* getSelectedChild();
int getSelectedIndex();
- void selectChild(Widget* item);
- void selectIndex(int index);
+ void selectChild(Widget* item, Message* msg = nullptr);
+ void selectIndex(int index, Message* msg = nullptr);
- std::size_t getItemsCount() const;
+ int getItemsCount() const;
void makeChildVisible(Widget* item);
void centerScroll();
@@ -41,6 +46,24 @@ namespace ui {
virtual void onSizeHint(SizeHintEvent& ev) override;
virtual void onChange();
virtual void onDoubleClickItem();
+
+ int getChildIndex(Widget* item);
+ Widget* getChildByIndex(int index);
+
+ // True if this listbox accepts selecting multiple items at the
+ // same time.
+ bool m_multiselect;
+
+ // Range of items selected when we click down/up. Used to specify
+ // the range of selected items in a multiselect operation.
+ int m_firstSelectedIndex;
+ int m_lastSelectedIndex;
+
+ // Initial state (isSelected()) of each list item when the
+ // selection operation started. It's used to switch the state of
+ // items in case that the user is Ctrl+clicking items several
+ // items at the same time.
+ std::vector m_states;
};
} // namespace ui
diff --git a/src/ui/listitem.cpp b/src/ui/listitem.cpp
index 94037b18c..2f37f2f6f 100644
--- a/src/ui/listitem.cpp
+++ b/src/ui/listitem.cpp
@@ -1,5 +1,5 @@
// Aseprite UI Library
-// Copyright (C) 2001-2013, 2015 David Capello
+// Copyright (C) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@@ -29,6 +29,20 @@ ListItem::ListItem(const std::string& text)
initTheme();
}
+bool ListItem::onProcessMessage(Message* msg)
+{
+ switch (msg->type()) {
+ case kDoubleClickMessage:
+ // Propagate the message to the parent.
+ if (parent())
+ return parent()->sendMessage(msg);
+ else
+ break;
+ break;
+ }
+ return Widget::onProcessMessage(msg);
+}
+
void ListItem::onPaint(PaintEvent& ev)
{
theme()->paintListItem(ev);
diff --git a/src/ui/listitem.h b/src/ui/listitem.h
index deb84a956..ffdf3d968 100644
--- a/src/ui/listitem.h
+++ b/src/ui/listitem.h
@@ -1,5 +1,5 @@
// Aseprite UI Library
-// Copyright (C) 2001-2013, 2015 David Capello
+// Copyright (C) 2001-2016 David Capello
//
// This file is released under the terms of the MIT license.
// Read LICENSE.txt for more information.
@@ -23,6 +23,7 @@ namespace ui {
}
protected:
+ bool onProcessMessage(Message* msg) override;
void onPaint(PaintEvent& ev) override;
void onResize(ResizeEvent& ev) override;
void onSizeHint(SizeHintEvent& ev) override;
diff --git a/src/ui/manager.cpp b/src/ui/manager.cpp
index 697eed78b..060c9b368 100644
--- a/src/ui/manager.cpp
+++ b/src/ui/manager.cpp
@@ -47,8 +47,7 @@ namespace ui {
static const int NFILTERS = (int)(kFirstRegisteredMessage+1);
-struct Filter
-{
+struct Filter {
int message;
Widget* widget;
@@ -67,6 +66,7 @@ static WidgetsList new_windows; // Windows that we should show
static WidgetsList mouse_widgets_list; // List of widgets to send mouse events
static Messages msg_queue; // Messages queue
static Filters msg_filters[NFILTERS]; // Filters for every enqueued message
+static int filter_locks = 0;
static Widget* focus_widget; // The widget with the focus
static Widget* mouse_widget; // The widget with the mouse
@@ -75,7 +75,6 @@ static Widget* capture_widget; // The widget that captures the mouse
static bool first_time = true; // true when we don't enter in poll yet
/* keyboard focus movement stuff */
-static bool move_focus(Manager* manager, Message* msg);
static int count_widgets_accept_focus(Widget* widget);
static bool childs_accept_focus(Widget* widget, bool first);
static Widget* next_widget(Widget* widget);
@@ -84,6 +83,37 @@ static int cmp_right(Widget* widget, int x, int y);
static int cmp_up(Widget* widget, int x, int y);
static int cmp_down(Widget* widget, int x, int y);
+namespace {
+
+class LockFilters {
+public:
+ LockFilters() {
+ ++filter_locks;
+ }
+ ~LockFilters() {
+ ASSERT(filter_locks > 0);
+ --filter_locks;
+
+ if (filter_locks == 0) {
+ // Clear empty filters
+ for (Filters& msg_filter : msg_filters) {
+ for (auto it = msg_filter.begin(); it != msg_filter.end(); ) {
+ Filter* filter = *it;
+ if (filter->widget == nullptr) {
+ delete filter;
+ it = msg_filter.erase(it);
+ }
+ else {
+ ++it;
+ }
+ }
+ }
+ }
+ }
+};
+
+} // anonymous namespace
+
Manager::Manager()
: Widget(kManagerWidget)
, m_display(NULL)
@@ -128,15 +158,13 @@ Manager::~Manager()
Timer::checkNoTimers();
// Destroy filters
- for (int c=0; c(msg)->setDeadKey(true);
+
broadcastKeyMsg(msg);
enqueueMessage(msg);
break;
@@ -589,48 +621,10 @@ void Manager::addToGarbage(Widget* widget)
m_garbage.push_back(widget);
}
-/**
- * @param msg You can't use the this message after calling this
- * routine. The message will be automatically freed through
- * @ref jmessage_free
- */
void Manager::enqueueMessage(Message* msg)
{
- ASSERT(msg != NULL);
-
-#ifdef REPORT_EVENTS
- if (msg->type() == kKeyDownMessage ||
- msg->type() == kKeyUpMessage) {
- int mods = (int)static_cast(msg)->modifiers();
- TRACE("Key%s scancode=%d unicode=%d mods=%s%s%s\n",
- (msg->type() == kKeyDownMessage ? "Down": "Up"),
- static_cast(msg)->scancode(),
- static_cast(msg)->unicodeChar(),
- mods & kKeyShiftModifier ? " Shift": "",
- mods & kKeyCtrlModifier ? " Ctrl": "",
- mods & kKeyAltModifier ? " Alt": "");
- }
-#endif
-
- // Check if this message must be filtered by some widget before
- int c = msg->type();
- if (c >= kFirstRegisteredMessage)
- c = kFirstRegisteredMessage;
-
- if (!msg_filters[c].empty()) { // OK, so are filters to add...
- // Add all the filters in the destination list of the message
- for (Filters::reverse_iterator it=msg_filters[c].rbegin(),
- end=msg_filters[c].rend(); it != end; ++it) {
- Filter* filter = *it;
- if (msg->type() == filter->message)
- msg->prependRecipient(filter->widget);
- }
- }
-
- if (msg->hasRecipients())
- msg_queue.push_back(msg);
- else
- delete msg;
+ ASSERT(msg);
+ msg_queue.push_back(msg);
}
Window* Manager::getTopWindow()
@@ -928,6 +922,7 @@ void Manager::removeMessagesForTimer(Timer* timer)
void Manager::addMessageFilter(int message, Widget* widget)
{
+ LockFilters lock;
int c = message;
if (c >= kFirstRegisteredMessage)
c = kFirstRegisteredMessage;
@@ -937,40 +932,36 @@ void Manager::addMessageFilter(int message, Widget* widget)
void Manager::removeMessageFilter(int message, Widget* widget)
{
+ LockFilters lock;
int c = message;
if (c >= kFirstRegisteredMessage)
c = kFirstRegisteredMessage;
- for (Filters::iterator it=msg_filters[c].begin(); it != msg_filters[c].end(); ) {
- Filter* filter = *it;
- if (filter->widget == widget) {
- delete filter;
- it = msg_filters[c].erase(it);
- }
- else
- ++it;
+ Filters& msg_filter = msg_filters[c];
+ for (Filter* filter : msg_filter) {
+ if (filter->widget == widget)
+ filter->widget = nullptr;
}
}
void Manager::removeMessageFilterFor(Widget* widget)
{
- for (int c=0; cwidget == widget) {
- delete filter;
- it = msg_filters[c].erase(it);
- }
- else
- ++it;
+ LockFilters lock;
+ for (Filters& msg_filter : msg_filters) {
+ for (Filter* filter : msg_filter) {
+ if (filter->widget == widget)
+ filter->widget = nullptr;
}
}
}
-bool Manager::isFocusMovementKey(Message* msg)
+bool Manager::isFocusMovementMessage(Message* msg)
{
- switch (static_cast(msg)->scancode()) {
+ if (msg->type() != kKeyDownMessage &&
+ msg->type() != kKeyUpMessage)
+ return false;
+ switch (static_cast(msg)->scancode()) {
case kKeyTab:
case kKeyLeft:
case kKeyRight:
@@ -986,7 +977,7 @@ void Manager::dirtyRect(const gfx::Rect& bounds)
m_dirtyRegion.createUnion(m_dirtyRegion, gfx::Region(bounds));
}
-/* configures the window for begin the loop */
+// Configures the window for begin the loop
void Manager::_openWindow(Window* window)
{
// Free all widgets of special states.
@@ -1006,6 +997,12 @@ void Manager::_openWindow(Window* window)
// Update the new windows list to show.
new_windows.push_back(window);
+
+ // Update mouse widget (as it can be a widget below the
+ // recently opened window).
+ Widget* widget = pick(ui::get_mouse_position());
+ if (widget)
+ setMouse(widget);
}
void Manager::_closeWindow(Window* window, bool redraw_background)
@@ -1103,7 +1100,7 @@ bool Manager::onProcessMessage(Message* msg)
// Check the focus movement for foreground (non-desktop) windows.
if (win && win->isForeground()) {
if (msg->type() == kKeyDownMessage)
- move_focus(this, msg);
+ processFocusMovementMessage(msg);
return true;
}
else
@@ -1223,7 +1220,7 @@ void Manager::pumpQueue()
base::tick_t t = base::current_tick();
#endif
- Messages::iterator it = msg_queue.begin();
+ auto it = msg_queue.begin();
while (it != msg_queue.end()) {
#ifdef LIMIT_DISPATCH_TIME
if (base::current_tick()-t > 250)
@@ -1250,102 +1247,36 @@ void Manager::pumpQueue()
}
bool done = false;
- for (auto widget : msg->recipients()) {
- if (!widget)
- continue;
-#ifdef REPORT_EVENTS
- {
- static char *msg_name[] = {
- "kOpenMessage",
- "kCloseMessage",
- "kCloseDisplayMessage",
- "kResizeDisplayMessage",
- "kPaintMessage",
- "kTimerMessage",
- "kDropFilesMessage",
- "kWinMoveMessage",
+ // Send this message to filters
+ {
+ Filters& msg_filter = msg_filters[MIN(msg->type(), kFirstRegisteredMessage)];
+ if (!msg_filter.empty()) {
+ LockFilters lock;
+ for (Filter* filter : msg_filter) {
+ // The widget can be nullptr in case that the filter was
+ // "pre-removed" (it'll finally erased from the
+ // msg_filter list from ~LockFilters()).
+ if (filter->widget != nullptr &&
+ msg->type() == filter->message) {
+ msg->setFromFilter(true);
+ done = sendMessageToWidget(msg, filter->widget);
+ msg->setFromFilter(false);
- "kKeyDownMessage",
- "kKeyUpMessage",
- "kFocusEnterMessage",
- "kFocusLeaveMessage",
-
- "kMouseDownMessage",
- "kMouseUpMessage",
- "kDoubleClickMessage",
- "kMouseEnterMessage",
- "kMouseLeaveMessage",
- "kMouseMoveMessage",
- "kSetCursorMessage",
- "kMouseWheelMessage",
- "kTouchMagnifyMessage",
- };
- const char* string =
- (msg->type() >= kOpenMessage &&
- msg->type() <= kMouseWheelMessage) ? msg_name[msg->type()]:
- "Unknown";
-
- std::cout << "Event " << msg->type() << " (" << string << ") "
- << "for " << typeid(*widget).name();
- if (!widget->id().empty())
- std::cout << " (" << widget->id() << ")";
- std::cout << std::endl;
- }
-#endif
-
- // We need to configure the clip region for paint messages
- // before we call Widget::sendMessage().
- if (msg->type() == kPaintMessage) {
- if (widget->hasFlags(HIDDEN))
- continue;
-
- PaintMessage* paintMsg = static_cast(msg);
- she::Surface* surface = m_display->getSurface();
- gfx::Rect oldClip = surface->getClipBounds();
-
- if (surface->intersectClipRect(paintMsg->rect())) {
-#ifdef REPORT_EVENTS
- std::cout << " - clip("
- << paintMsg->rect().x << ", "
- << paintMsg->rect().y << ", "
- << paintMsg->rect().w << ", "
- << paintMsg->rect().h << ")"
- << std::endl;
-#endif
-
-#ifdef DEBUG_PAINT_EVENTS
- {
- she::SurfaceLock lock(surface);
- surface->fillRect(gfx::rgba(0, 0, 255), paintMsg->rect());
- }
-
- if (m_display)
- m_display->flip(gfx::Rect(0, 0, display_w(), display_h()));
-
- base::this_thread::sleep_for(0.002);
-#endif
-
- if (surface) {
- // Call the message handler
- done = widget->sendMessage(msg);
-
- // Restore clip region for paint messages.
- surface->setClipBounds(oldClip);
+ if (done)
+ break;
}
}
-
- // As this kPaintMessage's rectangle was updated, we can
- // remove it from "m_invalidRegion".
- m_invalidRegion -= gfx::Region(paintMsg->rect());
- }
- else {
- // Call the message handler
- done = widget->sendMessage(msg);
}
+ }
- if (done)
- break;
+ if (!done) {
+ // Then send the message to its recipients
+ for (Widget* widget : msg->recipients()) {
+ done = sendMessageToWidget(msg, widget);
+ if (done)
+ break;
+ }
}
// Remove the message from the msg_queue
@@ -1356,6 +1287,106 @@ void Manager::pumpQueue()
}
}
+bool Manager::sendMessageToWidget(Message* msg, Widget* widget)
+{
+ if (!widget)
+ return false;
+
+#ifdef REPORT_EVENTS
+ {
+ static const char* msg_name[] = {
+ "kOpenMessage",
+ "kCloseMessage",
+ "kCloseDisplayMessage",
+ "kResizeDisplayMessage",
+ "kPaintMessage",
+ "kTimerMessage",
+ "kDropFilesMessage",
+ "kWinMoveMessage",
+
+ "kKeyDownMessage",
+ "kKeyUpMessage",
+ "kFocusEnterMessage",
+ "kFocusLeaveMessage",
+
+ "kMouseDownMessage",
+ "kMouseUpMessage",
+ "kDoubleClickMessage",
+ "kMouseEnterMessage",
+ "kMouseLeaveMessage",
+ "kMouseMoveMessage",
+ "kSetCursorMessage",
+ "kMouseWheelMessage",
+ "kTouchMagnifyMessage",
+ };
+ const char* string =
+ (msg->type() >= kOpenMessage &&
+ msg->type() <= kMouseWheelMessage) ? msg_name[msg->type()]:
+ "Unknown";
+
+ std::cout << "Event " << msg->type() << " (" << string << ") "
+ << "for " << typeid(*widget).name();
+ if (!widget->id().empty())
+ std::cout << " (" << widget->id() << ")";
+ std::cout << std::endl;
+ }
+#endif
+
+ bool used = false;
+
+ // We need to configure the clip region for paint messages
+ // before we call Widget::sendMessage().
+ if (msg->type() == kPaintMessage) {
+ if (widget->hasFlags(HIDDEN))
+ return false;
+
+ PaintMessage* paintMsg = static_cast(msg);
+ she::Surface* surface = m_display->getSurface();
+ gfx::Rect oldClip = surface->getClipBounds();
+
+ if (surface->intersectClipRect(paintMsg->rect())) {
+#ifdef REPORT_EVENTS
+ std::cout << " - clip("
+ << paintMsg->rect().x << ", "
+ << paintMsg->rect().y << ", "
+ << paintMsg->rect().w << ", "
+ << paintMsg->rect().h << ")"
+ << std::endl;
+#endif
+
+#ifdef DEBUG_PAINT_EVENTS
+ {
+ she::SurfaceLock lock(surface);
+ surface->fillRect(gfx::rgba(0, 0, 255), paintMsg->rect());
+ }
+
+ if (m_display)
+ m_display->flip(gfx::Rect(0, 0, display_w(), display_h()));
+
+ base::this_thread::sleep_for(0.002);
+#endif
+
+ if (surface) {
+ // Call the message handler
+ used = widget->sendMessage(msg);
+
+ // Restore clip region for paint messages.
+ surface->setClipBounds(oldClip);
+ }
+ }
+
+ // As this kPaintMessage's rectangle was updated, we can
+ // remove it from "m_invalidRegion".
+ m_invalidRegion -= gfx::Region(paintMsg->rect());
+ }
+ else {
+ // Call the message handler
+ used = widget->sendMessage(msg);
+ }
+
+ return used;
+}
+
void Manager::invalidateDisplayRegion(const gfx::Region& region)
{
// TODO intersect with getDrawableRegion()???
@@ -1502,7 +1533,10 @@ void Manager::broadcastKeyMsg(Message* msg)
Focus Movement
***********************************************************************/
-static bool move_focus(Manager* manager, Message* msg)
+// TODO rewrite this function, it is based in an old code from the
+// Allegro library GUI code
+
+bool Manager::processFocusMovementMessage(Message* msg)
{
int (*cmp)(Widget*, int, int) = NULL;
Widget* focus = NULL;
@@ -1515,8 +1549,8 @@ static bool move_focus(Manager* manager, Message* msg)
if (focus_widget) {
window = focus_widget->window();
}
- else if (!manager->children().empty()) {
- window = manager->getTopWindow();
+ else if (!this->children().empty()) {
+ window = this->getTopWindow();
}
if (!window)
@@ -1581,7 +1615,7 @@ static bool move_focus(Manager* manager, Message* msg)
for (i=c; i (*cmp) (list[j], x, y)) {
+ if ((*cmp)(list[i], x, y) > (*cmp)(list[j], x, y)) {
Widget* tmp = list[i];
list[i] = list[j];
list[j] = tmp;
@@ -1590,7 +1624,7 @@ static bool move_focus(Manager* manager, Message* msg)
}
// Check if the new widget to put the focus is not in the wrong way.
- if ((*cmp) (list[c], x, y) < std::numeric_limits::max())
+ if ((*cmp)(list[c], x, y) < std::numeric_limits::max())
focus = list[c];
}
// If only there are one widget, put the focus in this
@@ -1602,7 +1636,7 @@ static bool move_focus(Manager* manager, Message* msg)
}
if ((focus) && (focus != focus_widget))
- Manager::getDefault()->setFocus(focus);
+ setFocus(focus);
}
return ret;
diff --git a/src/ui/manager.h b/src/ui/manager.h
index 520a3a8de..6193e696a 100644
--- a/src/ui/manager.h
+++ b/src/ui/manager.h
@@ -45,11 +45,15 @@ namespace ui {
// Refreshes the real display with the UI content.
void flipDisplay();
+ // Adds the given "msg" message to the queue of messages to be
+ // dispached. "msg" cannot be used after this function, it'll be
+ // automatically deleted.
+ void enqueueMessage(Message* msg);
+
// Returns true if there are messages in the queue to be
- // distpatched through jmanager_dispatch_messages().
+ // dispatched through dispatchMessages().
bool generateMessages();
void dispatchMessages();
- void enqueueMessage(Message* msg);
void addToGarbage(Widget* widget);
void collectGarbage();
@@ -83,7 +87,8 @@ namespace ui {
LayoutIO* getLayoutIO();
- bool isFocusMovementKey(Message* msg);
+ bool isFocusMovementMessage(Message* msg);
+ bool processFocusMovementMessage(Message* msg);
// Returns the invalid region in the screen to being updated with
// PaintMessages. This region is cleared when each widget receives
@@ -145,6 +150,8 @@ namespace ui {
void handleWindowZOrder();
void pumpQueue();
+ bool sendMessageToWidget(Message* msg, Widget* widget);
+
static void removeWidgetFromRecipients(Widget* widget, Message* msg);
static bool someParentIsFocusStop(Widget* widget);
static Widget* findMagneticWidget(Widget* widget);
diff --git a/src/ui/menu.cpp b/src/ui/menu.cpp
index 99a2e18e8..0cd4f3e81 100644
--- a/src/ui/menu.cpp
+++ b/src/ui/menu.cpp
@@ -118,7 +118,7 @@ protected:
static MenuBox* get_base_menubox(Widget* widget);
static MenuBaseData* get_base(Widget* widget);
-static MenuItem* check_for_letter(Menu* menu, int ascii);
+static MenuItem* check_for_letter(Menu* menu, const KeyMessage* keymsg);
static MenuItem* find_nextitem(Menu* menu, MenuItem* menuitem);
static MenuItem* find_previtem(Menu* menu, MenuItem* menuitem);
@@ -485,10 +485,8 @@ bool MenuBox::onProcessMessage(Message* msg)
if (((this->type() == kMenuBoxWidget) && (msg->modifiers() == kKeyNoneModifier || // <-- Inside menu-boxes we can use letters without Alt modifier pressed
msg->modifiers() == kKeyAltModifier)) ||
((this->type() == kMenuBarWidget) && (msg->modifiers() == kKeyAltModifier))) {
- // TODO use scancode instead of unicodeChar
- selected = check_for_letter(menu,
- static_cast(msg)->unicodeChar());
-
+ auto keymsg = static_cast(msg);
+ selected = check_for_letter(menu, keymsg);
if (selected) {
menu->highlightItem(selected, true, true, true);
return true;
@@ -1188,15 +1186,14 @@ void MenuItem::executeClick()
Manager::getDefault()->enqueueMessage(msg);
}
-static MenuItem* check_for_letter(Menu* menu, int ascii)
+static MenuItem* check_for_letter(Menu* menu, const KeyMessage* keymsg)
{
for (auto child : menu->children()) {
if (child->type() != kMenuItemWidget)
continue;
MenuItem* menuitem = static_cast