From 5380877c4a23fca2fc76883c4b5024d84fe59ccf Mon Sep 17 00:00:00 2001 From: David Capello Date: Sun, 10 Apr 2011 20:15:17 -0300 Subject: [PATCH] Refactor Editor class to use a state design pattern. + Added EditorState and derived classes (StandByState, DrawingState, etc.) + Added StatusBarListener. --- src/CMakeLists.txt | 5 + src/commands/cmd_goto_frame.cpp | 8 +- src/modules/editors.cpp | 9 +- src/tools/tool_loop.h | 2 +- src/tools/tool_loop_manager.cpp | 1 - src/widgets/editor/cursor.cpp | 9 +- src/widgets/editor/drawing_state.cpp | 165 +++ src/widgets/editor/drawing_state.h | 55 + src/widgets/editor/editor.cpp | 1261 ++------------------ src/widgets/editor/editor.h | 78 +- src/widgets/editor/editor_listener.h | 7 + src/widgets/editor/editor_listeners.cpp | 5 + src/widgets/editor/editor_listeners.h | 1 + src/widgets/editor/editor_state.h | 81 ++ src/widgets/editor/keys.cpp | 2 +- src/widgets/editor/moving_pixels_state.cpp | 274 +++++ src/widgets/editor/moving_pixels_state.h | 60 + src/widgets/editor/pixels_movement.cpp | 2 + src/widgets/editor/scrolling_state.cpp | 100 ++ src/widgets/editor/scrolling_state.h | 40 + src/widgets/editor/standby_state.cpp | 383 ++++++ src/widgets/editor/standby_state.h | 44 + src/widgets/editor/tool_loop_impl.cpp | 455 +++++++ src/widgets/editor/tool_loop_impl.h | 32 + src/widgets/statebar.cpp | 14 +- src/widgets/statebar.h | 16 + 26 files changed, 1900 insertions(+), 1209 deletions(-) create mode 100644 src/widgets/editor/drawing_state.cpp create mode 100644 src/widgets/editor/drawing_state.h create mode 100644 src/widgets/editor/editor_state.h create mode 100644 src/widgets/editor/moving_pixels_state.cpp create mode 100644 src/widgets/editor/moving_pixels_state.h create mode 100644 src/widgets/editor/scrolling_state.cpp create mode 100644 src/widgets/editor/scrolling_state.h create mode 100644 src/widgets/editor/standby_state.cpp create mode 100644 src/widgets/editor/standby_state.h create mode 100644 src/widgets/editor/tool_loop_impl.cpp create mode 100644 src/widgets/editor/tool_loop_impl.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 03f39ae29..03a9a7af5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -281,11 +281,16 @@ add_library(aseprite-library widgets/color_sliders.cpp widgets/editor/click.cpp widgets/editor/cursor.cpp + widgets/editor/drawing_state.cpp widgets/editor/editor.cpp widgets/editor/editor_listeners.cpp widgets/editor/editor_view.cpp widgets/editor/keys.cpp + widgets/editor/moving_pixels_state.cpp widgets/editor/pixels_movement.cpp + widgets/editor/scrolling_state.cpp + widgets/editor/standby_state.cpp + widgets/editor/tool_loop_impl.cpp widgets/fileview.cpp widgets/groupbut.cpp widgets/hex_color_entry.cpp diff --git a/src/commands/cmd_goto_frame.cpp b/src/commands/cmd_goto_frame.cpp index 0ba17e0e0..32889d787 100644 --- a/src/commands/cmd_goto_frame.cpp +++ b/src/commands/cmd_goto_frame.cpp @@ -59,7 +59,7 @@ void GotoFirstFrameCommand::onExecute(Context* context) sprite->setCurrentFrame(0); update_screen_for_document(document); - current_editor->editor_update_statusbar_for_standby(); + current_editor->updateStatusBar(); } ////////////////////////////////////////////////////////////////////// @@ -101,7 +101,7 @@ void GotoPreviousFrameCommand::onExecute(Context* context) sprite->setCurrentFrame(sprite->getTotalFrames()-1); update_screen_for_document(document); - current_editor->editor_update_statusbar_for_standby(); + current_editor->updateStatusBar(); } ////////////////////////////////////////////////////////////////////// @@ -143,7 +143,7 @@ void GotoNextFrameCommand::onExecute(Context* context) sprite->setCurrentFrame(0); update_screen_for_document(document); - current_editor->editor_update_statusbar_for_standby(); + current_editor->updateStatusBar(); } ////////////////////////////////////////////////////////////////////// @@ -180,7 +180,7 @@ void GotoLastFrameCommand::onExecute(Context* context) sprite->setCurrentFrame(sprite->getTotalFrames()-1); update_screen_for_document(document); - current_editor->editor_update_statusbar_for_standby(); + current_editor->updateStatusBar(); } ////////////////////////////////////////////////////////////////////// diff --git a/src/modules/editors.cpp b/src/modules/editors.cpp index 04fde9406..7b044739e 100644 --- a/src/modules/editors.cpp +++ b/src/modules/editors.cpp @@ -117,9 +117,12 @@ public: } void documentChanged(Editor* editor) OVERRIDE { - if (editor == current_editor) { + if (editor == current_editor) update_mini_editor_frame(editor); - } + } + + void stateChanged(Editor* editor) OVERRIDE { + // Do nothing } }; @@ -133,6 +136,7 @@ void exit_module_editors() { if (mini_editor_frame) { save_window_pos(mini_editor_frame, "MiniEditor"); + delete mini_editor_frame; mini_editor_frame = NULL; } @@ -590,6 +594,7 @@ static void update_mini_editor_frame(Editor* editor) if (mini_editor->getDocument() != document) { mini_editor->setDocument(document); mini_editor->setZoom(0); + mini_editor->setState(new EditorState); } mini_editor->centerInSpritePoint(pt.x, pt.y); diff --git a/src/tools/tool_loop.h b/src/tools/tool_loop.h index 99277d1dd..5ff477819 100644 --- a/src/tools/tool_loop.h +++ b/src/tools/tool_loop.h @@ -41,7 +41,7 @@ class PointShape; class Tool; // Interface to communicate the sprite editor with the tool when the user -// start using a tool to paint, select, pick color, etc. +// starts using a tool to paint, select, pick color, etc. // // All this information should be provided by the editor and consumed // by the tool (+controller+intertwiner+pointshape+ink). diff --git a/src/tools/tool_loop_manager.cpp b/src/tools/tool_loop_manager.cpp index 9222276e6..c70100be7 100644 --- a/src/tools/tool_loop_manager.cpp +++ b/src/tools/tool_loop_manager.cpp @@ -39,7 +39,6 @@ ToolLoopManager::ToolLoopManager(ToolLoop* toolLoop) ToolLoopManager::~ToolLoopManager() { - delete m_toolLoop; } bool ToolLoopManager::isCanceled() const diff --git a/src/widgets/editor/cursor.cpp b/src/widgets/editor/cursor.cpp index 6caed8b90..7b3931a8e 100644 --- a/src/widgets/editor/cursor.cpp +++ b/src/widgets/editor/cursor.cpp @@ -279,8 +279,7 @@ void Editor::editor_draw_cursor(int x, int y, bool refresh) generate_cursor_boundaries(); // draw pixel/pen preview - if (cursor_type & CURSOR_PENCIL && - m_state != EDITOR_STATE_DRAWING) { + if (cursor_type & CURSOR_PENCIL && m_state->requirePenPreview()) { IToolSettings* tool_settings = UIContext::instance() ->getSettings() ->getToolSettings(current_tool); @@ -364,8 +363,7 @@ void Editor::editor_move_cursor(int x, int y, bool refresh) ji_screen->clip = TRUE; release_bitmap(ji_screen); - if (cursor_type & CURSOR_PENCIL && - m_state != EDITOR_STATE_DRAWING) { + if (cursor_type & CURSOR_PENCIL && m_state->requirePenPreview()) { Pen* pen = editor_get_current_pen(); editors_draw_sprite(m_sprite, std::min(new_x, old_x)-pen->get_size()/2, @@ -420,8 +418,7 @@ void Editor::editor_clean_cursor(bool refresh) } // clean pixel/pen preview - if (cursor_type & CURSOR_PENCIL && - m_state != EDITOR_STATE_DRAWING) { + if (cursor_type & CURSOR_PENCIL && m_state->requirePenPreview()) { Pen* pen = editor_get_current_pen(); m_document->prepareExtraCel(x-pen->get_size()/2, diff --git a/src/widgets/editor/drawing_state.cpp b/src/widgets/editor/drawing_state.cpp new file mode 100644 index 000000000..7285e793a --- /dev/null +++ b/src/widgets/editor/drawing_state.cpp @@ -0,0 +1,165 @@ +/* ASE - Allegro Sprite Editor + * Copyright (C) 2001-2011 David Capello + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "config.h" + +#include "widgets/editor/drawing_state.h" + +#include "gui/message.h" +#include "gui/system.h" +#include "tools/ink.h" +#include "tools/tool.h" +#include "tools/tool_loop.h" +#include "tools/tool_loop_manager.h" +#include "widgets/editor/editor.h" +#include "widgets/editor/standby_state.h" + +#include + +static tools::ToolLoopManager::Pointer pointer_from_msg(Message* msg) +{ + tools::ToolLoopManager::Pointer::Button button = + (msg->mouse.right ? tools::ToolLoopManager::Pointer::Right: + (msg->mouse.middle ? tools::ToolLoopManager::Pointer::Middle: + tools::ToolLoopManager::Pointer::Left)); + + return tools::ToolLoopManager::Pointer(msg->mouse.x, msg->mouse.y, button); +} + +DrawingState::DrawingState(tools::ToolLoop* toolLoop, Editor* editor, Message* msg) + : m_toolLoop(toolLoop) + , m_toolLoopManager(new tools::ToolLoopManager(toolLoop)) +{ + // Hide the cursor (mainly to clean the pen preview) + editor->hideDrawingCursor(); + + m_toolLoopManager->prepareLoop(pointer_from_msg(msg)); + m_toolLoopManager->pressButton(pointer_from_msg(msg)); + + // Show drawing cursor again (without pen preview in this case) + editor->showDrawingCursor(); + + editor->captureMouse(); +} + +DrawingState::~DrawingState() +{ + delete m_toolLoopManager; + delete m_toolLoop; + m_toolLoopManager = NULL; + m_toolLoop = NULL; +} + +bool DrawingState::onMouseDown(Editor* editor, Message* msg) +{ + // Drawing loop + ASSERT(m_toolLoopManager != NULL); + + // Notify the mouse button down to the tool loop manager. + m_toolLoopManager->pressButton(pointer_from_msg(msg)); + + // Cancel drawing loop + if (m_toolLoopManager->isCanceled()) { + m_toolLoopManager->releaseLoop(pointer_from_msg(msg)); + + delete m_toolLoopManager; + m_toolLoopManager = NULL; + + // Change to standby state + editor->setState(new StandbyState()); + editor->releaseMouse(); + } + + return true; +} + +bool DrawingState::onMouseUp(Editor* editor, Message* msg) +{ + ASSERT(m_toolLoopManager != NULL); + + // Notify the release of the mouse button to the tool loop manager. + if (m_toolLoopManager->releaseButton(pointer_from_msg(msg))) + return true; + + m_toolLoopManager->releaseLoop(pointer_from_msg(msg)); + + // Back to standby state. + editor->setState(new StandbyState); + editor->releaseMouse(); + return true; +} + +bool DrawingState::onMouseMove(Editor* editor, Message* msg) +{ + ASSERT(m_toolLoopManager != NULL); + + acquire_bitmap(ji_screen); + + // Hide the drawing cursor + editor->hideDrawingCursor(); + + // Infinite scroll + editor->controlInfiniteScroll(msg); + + // Hide the cursor again + editor->hideDrawingCursor(); + + // notify mouse movement to the tool + ASSERT(m_toolLoopManager != NULL); + m_toolLoopManager->movement(pointer_from_msg(msg)); + + // draw the cursor again + editor->showDrawingCursor(); + + release_bitmap(ji_screen); + return true; +} + +bool DrawingState::onSetCursor(Editor* editor) +{ + if (m_toolLoop->getInk()->isEyedropper()) { + editor->hideDrawingCursor(); + jmouse_set_cursor(JI_CURSOR_EYEDROPPER); + } + else { + jmouse_set_cursor(JI_CURSOR_NULL); + editor->showDrawingCursor(); + } + return true; +} + +bool DrawingState::onKeyDown(Editor* editor, Message* msg) +{ + if (editor->processKeysToSetZoom(msg->key.scancode)) + return true; + + // When we are drawing, we "eat" all pressed keys. + return true; +} + +bool DrawingState::onKeyUp(Editor* editor, Message* msg) +{ + return true; +} + +bool DrawingState::onUpdateStatusBar(Editor* editor) +{ + // The status bar is updated by ToolLoopImpl::updateStatusBar() + // method called by the ToolLoopManager. + return false; +} diff --git a/src/widgets/editor/drawing_state.h b/src/widgets/editor/drawing_state.h new file mode 100644 index 000000000..6f73d8c99 --- /dev/null +++ b/src/widgets/editor/drawing_state.h @@ -0,0 +1,55 @@ +/* ASE - Allegro Sprite Editor + * Copyright (C) 2001-2011 David Capello + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef WIDGETS_EDITOR_DRAWING_STATE_H_INCLUDED +#define WIDGETS_EDITOR_DRAWING_STATE_H_INCLUDED + +#include "base/compiler_specific.h" +#include "widgets/editor/standby_state.h" + +namespace tools { + class ToolLoop; + class ToolLoopManager; +} + +class DrawingState : public StandbyState +{ +public: + DrawingState(tools::ToolLoop* loop, Editor* editor, Message* msg); + virtual ~DrawingState(); + virtual bool onMouseDown(Editor* editor, Message* msg) OVERRIDE; + virtual bool onMouseUp(Editor* editor, Message* msg) OVERRIDE; + virtual bool onMouseMove(Editor* editor, Message* msg) OVERRIDE; + virtual bool onSetCursor(Editor* editor) OVERRIDE; + virtual bool onKeyDown(Editor* editor, Message* msg) OVERRIDE; + virtual bool onKeyUp(Editor* editor, Message* msg) OVERRIDE; + virtual bool onUpdateStatusBar(Editor* editor) OVERRIDE; + + // Drawing state doesn't require the pen-preview because we are + // already drawing (viewing the real trace). + virtual bool requirePenPreview() OVERRIDE { return false; } + +private: + // The tool-loop. + tools::ToolLoop* m_toolLoop; + + // Tool-loop manager + tools::ToolLoopManager* m_toolLoopManager; +}; + +#endif // WIDGETS_EDITOR_DRAWING_STATE_H_INCLUDED diff --git a/src/widgets/editor/editor.cpp b/src/widgets/editor/editor.cpp index 505c8bcd8..302a12e80 100644 --- a/src/widgets/editor/editor.cpp +++ b/src/widgets/editor/editor.cpp @@ -37,53 +37,23 @@ #include "raster/raster.h" #include "settings/settings.h" #include "skin/skin_theme.h" -#include "tools/ink.h" -#include "tools/tool.h" -#include "tools/tool_loop.h" -#include "tools/tool_loop_manager.h" #include "ui_context.h" -#include "undo/undo_history.h" -#include "undoers/add_cel.h" -#include "undoers/add_image.h" -#include "undoers/close_group.h" -#include "undoers/dirty_area.h" -#include "undoers/open_group.h" -#include "undoers/replace_image.h" -#include "undoers/set_cel_position.h" #include "util/boundary.h" #include "util/misc.h" #include "util/render.h" #include "widgets/color_bar.h" -#include "widgets/editor/pixels_movement.h" +#include "widgets/editor/standby_state.h" #include "widgets/statebar.h" #include #include -#define has_shifts(msg,shift) \ - (((msg)->any.shifts & (shift)) == (shift)) - -#define has_only_shifts(msg,shift) \ - (((msg)->any.shifts & (KB_SHIFT_FLAG | \ - KB_ALT_FLAG | \ - KB_CTRL_FLAG)) == (shift)) - using namespace gfx; -static tools::ToolLoopManager::Pointer pointer_from_msg(Message* msg) -{ - tools::ToolLoopManager::Pointer::Button button = - (msg->mouse.right ? tools::ToolLoopManager::Pointer::Right: - (msg->mouse.middle ? tools::ToolLoopManager::Pointer::Middle: - tools::ToolLoopManager::Pointer::Left)); - - return tools::ToolLoopManager::Pointer(msg->mouse.x, msg->mouse.y, button); -} - Editor::Editor() : Widget(editor_type()) + , m_state(new StandbyState()) { - m_state = EDITOR_STATE_STANDBY; m_document = NULL; m_sprite = NULL; m_zoom = 0; @@ -94,9 +64,6 @@ Editor::Editor() m_cursor_editor_x = 0; m_cursor_editor_y = 0; - m_cursor_candraw = false; - m_insideSelection = false; - m_quicktool = NULL; m_offset_x = 0; @@ -104,10 +71,6 @@ Editor::Editor() m_mask_timer_id = jmanager_add_timer(this, 100); m_offset_count = 0; - m_refresh_region = NULL; - m_toolLoopManager = NULL; - m_pixelsMovement = NULL; - jwidget_focusrest(this, true); m_currentToolChangeSlot = @@ -119,9 +82,6 @@ Editor::~Editor() jmanager_remove_timer(m_mask_timer_id); remove_editor(this); - // Destroy tool-loop manager if it is created - delete m_toolLoopManager; - // Remove this editor as listener of CurrentToolChange signal. App::instance()->CurrentToolChange.disconnect(m_currentToolChangeSlot); delete m_currentToolChangeSlot; @@ -135,17 +95,50 @@ int editor_type() return type; } +void Editor::setState(EditorState* newState) +{ + try { + hideDrawingCursor(); + + // Fire before change state event, set the state, and fire after + // change state event. + m_state->onBeforeChangeState(this); + } + catch (...) { + delete newState; // An exception was thrown before we use the newState. + throw; + } + + // Delete old state (it cannot thrown exceptions) + delete m_state; + + // Change to the new state. + m_state = newState; + m_state->onAfterChangeState(this); + + // Redraw all the editors with the same document of this editor + update_screen_for_document(m_document); + + // Clear keyboard buffer just in case (to avoid sending keys to the + // new state). + clear_keybuf(); + + // Notify listeners + m_listeners.notifyStateChanged(this); + + // Setup the new mouse cursor + editor_setcursor(); + + updateStatusBar(); +} + void Editor::setDocument(Document* document) { if (this->hasMouse()) jmanager_free_mouse(); // TODO Why is this here? Review this code - // Do we need to drop files? - if (m_pixelsMovement) - dropPixels(); - - ASSERT(m_pixelsMovement == NULL && "You cannot change the current sprite while you are moving pixels"); - ASSERT(m_state == EDITOR_STATE_STANDBY && "You can change the current sprite only in stand-by state"); + // Reset current state. + setState(new StandbyState); if (m_cursor_thick) editor_clean_cursor(); @@ -580,20 +573,6 @@ void Editor::flashCurrentLayer() } } -void Editor::setMaskColorForPixelsMovement(const Color& color) -{ - ASSERT(m_sprite != NULL); - ASSERT(m_pixelsMovement != NULL); - - int imgtype = m_sprite->getImgType(); - m_pixelsMovement->setMaskColor(color_utils::color_for_image(color, imgtype)); -} - -/** - Control scroll when cursor goes out of the editor - - @param msg A mouse message received in Editor::onProcessMessage() -*/ void Editor::controlInfiniteScroll(Message* msg) { View* view = View::getView(this); @@ -626,27 +605,6 @@ void Editor::controlInfiniteScroll(Message* msg) } } -void Editor::dropPixels() -{ - ASSERT(m_pixelsMovement != NULL); - - if (m_pixelsMovement->isDragging()) - m_pixelsMovement->dropImageTemporarily(); - - // Drop pixels if the user press a button outside the selection - m_pixelsMovement->dropImage(); - delete m_pixelsMovement; - m_pixelsMovement = NULL; - - m_document->destroyExtraCel(); - - m_state = EDITOR_STATE_STANDBY; - releaseMouse(); - - app_get_statusbar()->hideMovePixelsOptions(); - editor_update_statusbar_for_standby(); -} - tools::Tool* Editor::getCurrentEditorTool() { if (m_quicktool) @@ -681,7 +639,7 @@ void Editor::showDrawingCursor() { ASSERT(m_sprite != NULL); - if (!m_cursor_thick && m_cursor_candraw) { + if (!m_cursor_thick && canDraw()) { jmouse_hide(); editor_draw_cursor(jmouse_x(0), jmouse_y(0)); jmouse_show(); @@ -697,6 +655,25 @@ void Editor::hideDrawingCursor() } } +void Editor::moveDrawingCursor() +{ + // Draw cursor + if (m_cursor_thick) { + int x, y; + + x = jmouse_x(0); + y = jmouse_y(0); + + // Redraw it only when the mouse change to other pixel (not + // when the mouse moves only). + if ((m_cursor_screen_x != x) || (m_cursor_screen_y != y)) { + jmouse_hide(); + editor_move_cursor(x, y); + jmouse_show(); + } + } +} + void Editor::addListener(EditorListener* listener) { m_listeners.addListener(listener); @@ -744,54 +721,10 @@ void Editor::centerInSpritePoint(int x, int y) m_listeners.notifyScrollChanged(this); } -void Editor::editor_update_statusbar_for_standby() +void Editor::updateStatusBar() { - tools::Tool* current_tool = getCurrentEditorTool(); - int x, y; - screenToEditor(jmouse_x(0), jmouse_y(0), &x, &y); - - if (!m_sprite) { - app_get_statusbar()->clearText(); - } - // For eye-dropper - else if (current_tool->getInk(0)->isEyedropper()) { - int imgtype = m_sprite->getImgType(); - uint32_t pixel = m_sprite->getPixel(x, y); - Color color = Color::fromImage(imgtype, pixel); - - int alpha = 255; - switch (imgtype) { - case IMAGE_RGB: alpha = _rgba_geta(pixel); break; - case IMAGE_GRAYSCALE: alpha = _graya_geta(pixel); break; - } - - char buf[256]; - usprintf(buf, "- Pos %d %d", x, y); - - app_get_statusbar()->showColor(0, buf, color, alpha); - } - // For other tools - else { - Mask* mask = m_document->getMask(); - - app_get_statusbar()->setStatusText - (0, "Pos %d %d, Size %d %d, Frame %d", - x, y, - ((mask && mask->bitmap)? mask->w: m_sprite->getWidth()), - ((mask && mask->bitmap)? mask->h: m_sprite->getHeight()), - m_sprite->getCurrentFrame()+1); - } -} - -// Update status bar for when the user is dragging pixels -void Editor::editor_update_statusbar_for_pixel_movement() -{ - ASSERT(m_pixelsMovement != NULL); - - Rect bounds = m_pixelsMovement->getImageBounds(); - app_get_statusbar()->setStatusText - (100, "Pos %d %d, Size %d %d", - bounds.x, bounds.y, bounds.w, bounds.h); + // Setup status bar using the current editor's state + m_state->onUpdateStatusBar(this); } void Editor::editor_update_quicktool() @@ -803,36 +736,20 @@ void Editor::editor_update_quicktool() // If the tool has changed, we must to update the status bar because // the new tool can display something different in the status bar (e.g. Eyedropper) if (old_quicktool != m_quicktool) - editor_update_statusbar_for_standby(); + updateStatusBar(); } ////////////////////////////////////////////////////////////////////// // Message handler for the editor -enum WHEEL_ACTION { WHEEL_NONE, - WHEEL_ZOOM, - WHEEL_VSCROLL, - WHEEL_HSCROLL, - WHEEL_FG, - WHEEL_BG, - WHEEL_FRAME }; - bool Editor::onProcessMessage(Message* msg) { - ASSERT((m_state == EDITOR_STATE_DRAWING && m_toolLoopManager != NULL) || - (m_state != EDITOR_STATE_DRAWING && m_toolLoopManager == NULL)); - switch (msg->type) { case JM_REQSIZE: editor_request_size(&msg->reqsize.w, &msg->reqsize.h); return true; - case JM_CLOSE: - // if (m_refresh_region) - // jregion_free(m_refresh_region); - break; - case JM_DRAW: { SkinTheme* theme = static_cast(this->getTheme()); @@ -914,10 +831,6 @@ bool Editor::onProcessMessage(Message* msg) break; case JM_MOUSEENTER: - // When the mouse enter to the editor, we can calculate the - // 'cursor_candraw' field to avoid a heavy if-condition in the - // 'editor_setcursor' routine - editor_update_candraw(); editor_update_quicktool(); break; @@ -927,357 +840,48 @@ bool Editor::onProcessMessage(Message* msg) break; case JM_BUTTONPRESSED: - if (!m_sprite) - break; - - // Drawing loop - if (m_state == EDITOR_STATE_DRAWING) { - ASSERT(m_toolLoopManager != NULL); - - m_toolLoopManager->pressButton(pointer_from_msg(msg)); - - // Cancel drawing loop - if (m_toolLoopManager->isCanceled()) { - m_toolLoopManager->releaseLoop(pointer_from_msg(msg)); - - delete m_toolLoopManager; - m_toolLoopManager = NULL; - m_state = EDITOR_STATE_STANDBY; - - // Redraw all the editors with this sprite - update_screen_for_document(m_document); - - clear_keybuf(); - - editor_setcursor(msg->mouse.x, msg->mouse.y); - - releaseMouse(); - return true; - } - } - - if (!hasCapture()) { - UIContext* context = UIContext::instance(); - tools::Tool* current_tool = getCurrentEditorTool(); - - set_current_editor(this); - context->setActiveDocument(m_document); - - // Start scroll loop - if (msg->mouse.middle || - current_tool->getInk(msg->mouse.right ? 1: 0)->isScrollMovement()) { - m_state = EDITOR_STATE_SCROLLING; - - editor_setcursor(msg->mouse.x, msg->mouse.y); - captureMouse(); - return true; - } - - if (m_pixelsMovement) { - // Start "moving pixels" loop - if (m_insideSelection) { - // Re-catch the image - int x, y; - screenToEditor(msg->mouse.x, msg->mouse.y, &x, &y); - m_pixelsMovement->catchImageAgain(x, y); - - captureMouse(); - return true; - } - // End "moving pixels" loop - else { - // Drop pixels (e.g. to start drawing) - dropPixels(); - } - } - - // Move frames position - if (current_tool->getInk(msg->mouse.right ? 1: 0)->isCelMovement()) { - if ((m_sprite->getCurrentLayer()) && - (m_sprite->getCurrentLayer()->getType() == GFXOBJ_LAYER_IMAGE)) { - // TODO you can move the `Background' with tiled mode - if (m_sprite->getCurrentLayer()->is_background()) { - Alert::show(PACKAGE - "<getCurrentLayer()->is_moveable()) { - Alert::show(PACKAGE "<getInk(0)->isSelection() && - msg->mouse.left) { - int x, y, opacity; - Image* image = m_sprite->getCurrentImage(&x, &y, &opacity); - if (image) { - if (!m_sprite->getCurrentLayer()->is_writable()) { - Alert::show(PACKAGE "<getMask()->x; - y = m_document->getMask()->y; - m_pixelsMovement = new PixelsMovement(m_document, m_sprite, tmpImage, x, y, opacity); - delete tmpImage; - - // If the CTRL key is pressed start dragging a copy of the selection - if (key[KEY_LCONTROL] || key[KEY_RCONTROL]) // TODO configurable - m_pixelsMovement->copyMask(); - else - m_pixelsMovement->cutMask(); - - screenToEditor(msg->mouse.x, msg->mouse.y, &x, &y); - m_pixelsMovement->catchImage(x, y); - - // Setup mask color - setMaskColorForPixelsMovement(app_get_statusbar()->getTransparentColor()); - - // Update status bar - editor_update_statusbar_for_pixel_movement(); - app_get_statusbar()->showMovePixelsOptions(); - } - captureMouse(); - } - // Call the eyedropper command - else if (current_tool->getInk(msg->mouse.right ? 1: 0)->isEyedropper()) { - Command* eyedropper_cmd = - CommandsModule::instance()->getCommandByName(CommandId::Eyedropper); - - Params params; - params.set("target", msg->mouse.right ? "background": "foreground"); - - UIContext::instance()->executeCommand(eyedropper_cmd, ¶ms); - return true; - } - // Start the Tool-Loop - else if (m_sprite->getCurrentLayer()) { - ASSERT(m_toolLoopManager == NULL); - - tools::ToolLoop* toolLoop = createToolLoopImpl(UIContext::instance(), msg); - if (!toolLoop) - return true; // Return without capturing mouse - - m_toolLoopManager = new tools::ToolLoopManager(toolLoop); - if (!m_toolLoopManager) - return true; // Return without capturing mouse - - // Clean the cursor (mainly to clean the pen preview) - int thick = m_cursor_thick; - if (thick) - editor_clean_cursor(); - - m_state = EDITOR_STATE_DRAWING; - - m_toolLoopManager->prepareLoop(pointer_from_msg(msg)); - m_toolLoopManager->pressButton(pointer_from_msg(msg)); - - // Redraw it (without pen preview) - if (thick) - editor_draw_cursor(msg->mouse.x, msg->mouse.y); - - captureMouse(); - } - } - return true; + if (m_sprite) + return m_state->onMouseDown(this, msg); + break; case JM_MOTION: - if (!m_sprite) - break; - - // Move the scroll - if (m_state == EDITOR_STATE_SCROLLING) { - View* view = View::getView(this); - Rect vp = view->getViewportBounds(); - Point scroll = view->getViewScroll(); - - setEditorScroll(scroll.x+jmouse_x(1)-jmouse_x(0), - scroll.y+jmouse_y(1)-jmouse_y(0), true); - - jmouse_control_infinite_scroll(vp); - - { - int x, y; - screenToEditor(jmouse_x(0), jmouse_y(0), &x, &y); - app_get_statusbar()->setStatusText - (0, "Pos %3d %3d (Size %3d %3d)", x, y, - m_sprite->getWidth(), m_sprite->getHeight()); - } - } - // Moving pixels - else if (m_pixelsMovement) { - // If there is a button pressed - if (m_pixelsMovement->isDragging()) { - // Infinite scroll - controlInfiniteScroll(msg); - - // Get the position of the mouse in the sprite - int x, y; - screenToEditor(msg->mouse.x, msg->mouse.y, &x, &y); - - // Drag the image to that position - Rect bounds = m_pixelsMovement->moveImage(x, y); - - // If "bounds" is empty is because the cel was not moved - if (!bounds.isEmpty()) { - // Redraw the extra cel in the new position - jmouse_hide(); - editors_draw_sprite_tiled(m_sprite, - bounds.x, bounds.y, - bounds.x+bounds.w-1, - bounds.y+bounds.h-1); - jmouse_show(); - } - } - else { - // Draw cursor - if (m_cursor_thick) { - int x, y; - - x = msg->mouse.x; - y = msg->mouse.y; - - // Redraw it only when the mouse change to other pixel (not - // when the mouse moves only). - if ((m_cursor_screen_x != x) || (m_cursor_screen_y != y)) { - jmouse_hide(); - editor_move_cursor(x, y); - jmouse_show(); - } - } - } - - editor_update_statusbar_for_pixel_movement(); - } - // In tool-loop - else if (m_state == EDITOR_STATE_DRAWING) { - acquire_bitmap(ji_screen); - - ASSERT(m_toolLoopManager != NULL); - - // Clean the area occupied by the cursor in the screen - if (m_cursor_thick) - editor_clean_cursor(); - - ASSERT(m_toolLoopManager != NULL); - - // Infinite scroll - controlInfiniteScroll(msg); - - // Clean the area occupied by the cursor in the screen - int thick = m_cursor_thick; - if (thick) - editor_clean_cursor(); - - // notify mouse movement to the tool - ASSERT(m_toolLoopManager != NULL); - m_toolLoopManager->movement(pointer_from_msg(msg)); - - // draw the cursor again - if (thick) - editor_draw_cursor(msg->mouse.x, msg->mouse.y); - - release_bitmap(ji_screen); - } - else if (m_state == EDITOR_STATE_STANDBY) { - // Draw cursor - if (m_cursor_thick) { - int x, y; - - x = msg->mouse.x; - y = msg->mouse.y; - - // Redraw it only when the mouse change to other pixel (not - // when the mouse moves only). - if ((m_cursor_screen_x != x) || (m_cursor_screen_y != y)) { - jmouse_hide(); - editor_move_cursor(x, y); - jmouse_show(); - } - } - - editor_update_statusbar_for_standby(); - } - return true; + if (m_sprite) + return m_state->onMouseMove(this, msg); + break; case JM_BUTTONRELEASED: - if (!m_sprite) - break; - - // Drawing - if (m_state == EDITOR_STATE_DRAWING) { - ASSERT(m_toolLoopManager != NULL); - - if (m_toolLoopManager->releaseButton(pointer_from_msg(msg))) + if (m_sprite) { + if (m_state->onMouseUp(this, msg)) return true; - - m_toolLoopManager->releaseLoop(pointer_from_msg(msg)); - - delete m_toolLoopManager; - m_toolLoopManager = NULL; - m_state = EDITOR_STATE_STANDBY; - - // redraw all the editors with this sprite - update_screen_for_document(m_document); - - clear_keybuf(); } - else if (m_state != EDITOR_STATE_STANDBY) { - ASSERT(m_toolLoopManager == NULL); - m_state = EDITOR_STATE_STANDBY; - } - // Moving pixels - else if (m_pixelsMovement) { - // Drop the image temporarily in this location (where the user releases the mouse) - m_pixelsMovement->dropImageTemporarily(); - } - - editor_setcursor(msg->mouse.x, msg->mouse.y); - editor_update_statusbar_for_standby(); - releaseMouse(); - return true; + break; case JM_KEYPRESSED: - if (m_state == EDITOR_STATE_STANDBY || - m_state == EDITOR_STATE_DRAWING) { - if (editor_keys_toset_zoom(msg->key.scancode)) - return true; - } + if (m_sprite) { + bool used = m_state->onKeyDown(this, msg); - if (this->hasMouse()) { - editor_update_quicktool(); - - if (msg->key.scancode == KEY_LCONTROL || // TODO configurable - msg->key.scancode == KEY_RCONTROL) { - // If the user press the CTRL key when he is dragging pixels (but not pressing the mouse buttons)... - if (!jmouse_b(0) && m_pixelsMovement) { - // Drop pixels (sure the user will press the mouse button to start dragging a copy) - dropPixels(); - } + if (hasMouse()) { + editor_update_quicktool(); + editor_setcursor(); } - editor_setcursor(jmouse_x(0), jmouse_y(0)); + if (used) + return true; } - - // When we are drawing, we "eat" all pressed keys - if (m_state == EDITOR_STATE_DRAWING) - return true; - break; case JM_KEYRELEASED: - editor_update_quicktool(); - editor_setcursor(jmouse_x(0), jmouse_y(0)); + if (m_sprite) { + bool used = m_state->onKeyUp(this, msg); + + if (hasMouse()) { + editor_update_quicktool(); + editor_setcursor(); + } + + if (used) + return true; + } break; case JM_FOCUSLEAVE: @@ -1287,131 +891,15 @@ bool Editor::onProcessMessage(Message* msg) break; case JM_WHEEL: - if (m_state == EDITOR_STATE_STANDBY || - m_state == EDITOR_STATE_DRAWING) { - // There are and sprite in the editor and the mouse is inside - if (m_sprite && this->hasMouse()) { - int dz = jmouse_z(1) - jmouse_z(0); - WHEEL_ACTION wheelAction = WHEEL_NONE; - bool scrollBigSteps = false; - - // Without modifiers - if (!(msg->any.shifts & (KB_SHIFT_FLAG | KB_ALT_FLAG | KB_CTRL_FLAG))) { - wheelAction = WHEEL_ZOOM; - } - else { -#if 1 // TODO make it configurable - if (has_shifts(msg, KB_ALT_FLAG)) { - if (has_shifts(msg, KB_SHIFT_FLAG)) - wheelAction = WHEEL_BG; - else - wheelAction = WHEEL_FG; - } - else if (has_shifts(msg, KB_CTRL_FLAG)) { - wheelAction = WHEEL_FRAME; - } -#else - if (has_shifts(msg, KB_CTRL_FLAG)) - wheelAction = WHEEL_HSCROLL; - else - wheelAction = WHEEL_VSCROLL; - - if (has_shifts(msg, KB_SHIFT_FLAG)) - scrollBigSteps = true; -#endif - } - - switch (wheelAction) { - - case WHEEL_NONE: - // Do nothing - break; - - case WHEEL_FG: - if (m_state == EDITOR_STATE_STANDBY) { - int newIndex = 0; - if (app_get_colorbar()->getFgColor().getType() == Color::IndexType) { - newIndex = app_get_colorbar()->getFgColor().getIndex() + dz; - newIndex = MID(0, newIndex, 255); - } - app_get_colorbar()->setFgColor(Color::fromIndex(newIndex)); - } - break; - - case WHEEL_BG: - if (m_state == EDITOR_STATE_STANDBY) { - int newIndex = 0; - if (app_get_colorbar()->getBgColor().getType() == Color::IndexType) { - newIndex = app_get_colorbar()->getBgColor().getIndex() + dz; - newIndex = MID(0, newIndex, 255); - } - app_get_colorbar()->setBgColor(Color::fromIndex(newIndex)); - } - break; - - case WHEEL_FRAME: - if (m_state == EDITOR_STATE_STANDBY) { - Command* command = CommandsModule::instance()->getCommandByName - ((dz < 0) ? CommandId::GotoNextFrame: - CommandId::GotoPreviousFrame); - if (command) - UIContext::instance()->executeCommand(command, NULL); - } - break; - - case WHEEL_ZOOM: { - int zoom = MID(MIN_ZOOM, m_zoom-dz, MAX_ZOOM); - if (m_zoom != zoom) - setZoomAndCenterInMouse(zoom, msg->mouse.x, msg->mouse.y); - break; - } - - case WHEEL_HSCROLL: - case WHEEL_VSCROLL: { - View* view = View::getView(this); - Rect vp = view->getViewportBounds(); - Point scroll; - int dx = 0; - int dy = 0; - int thick = m_cursor_thick; - - if (wheelAction == WHEEL_HSCROLL) { - dx = dz * vp.w; - } - else { - dy = dz * vp.h; - } - - if (scrollBigSteps) { - dx /= 2; - dy /= 2; - } - else { - dx /= 10; - dy /= 10; - } - - scroll = view->getViewScroll(); - - jmouse_hide(); - if (thick) - editor_clean_cursor(); - setEditorScroll(scroll.x+dx, scroll.y+dy, true); - if (thick) - editor_draw_cursor(jmouse_x(0), jmouse_y(0)); - jmouse_show(); - break; - } - - } - } + if (m_sprite && hasMouse()) { + if (m_state->onMouseWheel(this, msg)) + return true; } break; case JM_SETCURSOR: - editor_setcursor(msg->mouse.x, msg->mouse.y); + editor_setcursor(); return true; - } return Widget::onProcessMessage(msg); @@ -1420,16 +908,7 @@ bool Editor::onProcessMessage(Message* msg) // When the current tool is changed void Editor::onCurrentToolChange() { - tools::Tool* current_tool = getCurrentEditorTool(); - - // If the user changed the tool when he/she is moving pixels, - // we have to drop the pixels only if the new tool is not selection... - if (m_pixelsMovement && - (!current_tool->getInk(0)->isSelection() || - !current_tool->getInk(1)->isSelection())) { - // We have to drop pixels - dropPixels(); - } + m_state->onCurrentToolChange(this); } /** @@ -1453,133 +932,21 @@ void Editor::editor_request_size(int *w, int *h) } } -void Editor::editor_setcursor(int x, int y) +void Editor::editor_setcursor() { - tools::Tool* current_tool = getCurrentEditorTool(); - - switch (m_state) { - - case EDITOR_STATE_SCROLLING: - hideDrawingCursor(); - jmouse_set_cursor(JI_CURSOR_SCROLL); - break; - - case EDITOR_STATE_DRAWING: - if (current_tool->getInk(0)->isEyedropper()) { - hideDrawingCursor(); - jmouse_set_cursor(JI_CURSOR_EYEDROPPER); - return; - } - else { - jmouse_set_cursor(JI_CURSOR_NULL); - showDrawingCursor(); - } - break; - - case EDITOR_STATE_STANDBY: - if (m_sprite) { - tools::Tool* current_tool = getCurrentEditorTool(); - - editor_update_candraw(); // TODO remove this - - // Pixels movement - if (m_pixelsMovement) { - int x, y; - screenToEditor(jmouse_x(0), jmouse_y(0), &x, &y); - - // Move selection - if (m_pixelsMovement->isDragging() || - m_document->isMaskVisible() && - m_document->getMask()->contains_point(x, y)) { - hideDrawingCursor(); - jmouse_set_cursor(JI_CURSOR_MOVE); - - if (!m_insideSelection) - m_insideSelection = true; - return; - } - - if (m_insideSelection) - m_insideSelection = false; - - // Draw - if (m_cursor_candraw) { - jmouse_set_cursor(JI_CURSOR_NULL); - showDrawingCursor(); - } - // Forbidden - else { - hideDrawingCursor(); - jmouse_set_cursor(JI_CURSOR_FORBIDDEN); - } - } - else { - if (current_tool) { - // If the current tool change selection (e.g. rectangular marquee, etc.) - if (current_tool->getInk(0)->isSelection()) { - int x, y; - screenToEditor(jmouse_x(0), jmouse_y(0), &x, &y); - - // Move pixels - if (m_document->isMaskVisible() && - m_document->getMask()->contains_point(x, y)) { - hideDrawingCursor(); - if (key[KEY_LCONTROL] || - key[KEY_RCONTROL]) // TODO configurable keys - jmouse_set_cursor(JI_CURSOR_NORMAL_ADD); - else - jmouse_set_cursor(JI_CURSOR_MOVE); - - if (!m_insideSelection) - m_insideSelection = true; - return; - } - } - else if (current_tool->getInk(0)->isEyedropper()) { - hideDrawingCursor(); - jmouse_set_cursor(JI_CURSOR_EYEDROPPER); - return; - } - else if (current_tool->getInk(0)->isScrollMovement()) { - hideDrawingCursor(); - jmouse_set_cursor(JI_CURSOR_SCROLL); - return; - } - else if (current_tool->getInk(0)->isCelMovement()) { - hideDrawingCursor(); - jmouse_set_cursor(JI_CURSOR_MOVE); - return; - } - } - - if (m_insideSelection) - m_insideSelection = false; - - // Draw - if (m_cursor_candraw) { - jmouse_set_cursor(JI_CURSOR_NULL); - showDrawingCursor(); - } - // Forbidden - else { - hideDrawingCursor(); - jmouse_set_cursor(JI_CURSOR_FORBIDDEN); - } - } - } - else { - hideDrawingCursor(); - jmouse_set_cursor(JI_CURSOR_NORMAL); - } - break; + bool used = false; + if (m_sprite) + used = m_state->onSetCursor(this); + if (!used) { + hideDrawingCursor(); + jmouse_set_cursor(JI_CURSOR_NORMAL); } } -/* TODO this routine should be called in a change of context */ -void Editor::editor_update_candraw() +bool Editor::canDraw() { - m_cursor_candraw = + return (m_sprite != NULL && m_sprite->getCurrentLayer() != NULL && m_sprite->getCurrentLayer()->is_image() && @@ -1589,6 +956,16 @@ void Editor::editor_update_candraw() ); } +bool Editor::isInsideSelection() +{ + int x, y; + screenToEditor(jmouse_x(0), jmouse_y(0), &x, &y); + return + m_document != NULL && + m_document->isMaskVisible() && + m_document->getMask()->contains_point(x, y); +} + void Editor::setZoomAndCenterInMouse(int zoom, int mouse_x, int mouse_y) { View* view = View::getView(this); @@ -1630,409 +1007,3 @@ void Editor::setZoomAndCenterInMouse(int zoom, int mouse_x, int mouse_y) } showDrawingCursor(); } - -////////////////////////////////////////////////////////////////////// -// ToolLoop implementation - -class ToolLoopImpl : public tools::ToolLoop -{ - Editor* m_editor; - Context* m_context; - tools::Tool* m_tool; - Pen* m_pen; - Document* m_document; - Sprite* m_sprite; - Layer* m_layer; - Cel* m_cel; - Image* m_cel_image; - bool m_cel_created; - int m_old_cel_x; - int m_old_cel_y; - bool m_filled; - bool m_previewFilled; - int m_sprayWidth; - int m_spraySpeed; - TiledMode m_tiled_mode; - Image* m_src_image; - Image* m_dst_image; - bool m_useMask; - Mask* m_mask; - Point m_maskOrigin; - int m_opacity; - int m_tolerance; - Point m_offset; - Point m_speed; - bool m_canceled; - int m_button; - int m_primary_color; - int m_secondary_color; - -public: - ToolLoopImpl(Editor* editor, - Context* context, - tools::Tool* tool, - Document* document, - Sprite* sprite, - Layer* layer, - int button, const Color& primary_color, const Color& secondary_color) - : m_editor(editor) - , m_context(context) - , m_tool(tool) - , m_document(document) - , m_sprite(sprite) - , m_layer(layer) - , m_cel(NULL) - , m_cel_image(NULL) - , m_cel_created(false) - , m_canceled(false) - , m_button(button) - , m_primary_color(color_utils::color_for_layer(primary_color, layer)) - , m_secondary_color(color_utils::color_for_layer(secondary_color, layer)) - { - // Settings - ISettings* settings = m_context->getSettings(); - - m_tiled_mode = settings->getTiledMode(); - - switch (tool->getFill(m_button)) { - case tools::FillNone: - m_filled = false; - break; - case tools::FillAlways: - m_filled = true; - break; - case tools::FillOptional: - m_filled = settings->getToolSettings(m_tool)->getFilled(); - break; - } - m_previewFilled = settings->getToolSettings(m_tool)->getPreviewFilled(); - - m_sprayWidth = settings->getToolSettings(m_tool)->getSprayWidth(); - m_spraySpeed = settings->getToolSettings(m_tool)->getSpraySpeed(); - - // Create the pen - IPenSettings* pen_settings = settings->getToolSettings(m_tool)->getPen(); - ASSERT(pen_settings != NULL); - - m_pen = new Pen(pen_settings->getType(), - pen_settings->getSize(), - pen_settings->getAngle()); - - // Get cel and image where we can draw - - if (m_layer->is_image()) { - m_cel = static_cast(sprite->getCurrentLayer())->getCel(sprite->getCurrentFrame()); - if (m_cel) - m_cel_image = sprite->getStock()->getImage(m_cel->getImage()); - } - - if (m_cel == NULL) { - // create the image - m_cel_image = image_new(sprite->getImgType(), sprite->getWidth(), sprite->getHeight()); - image_clear(m_cel_image, - m_cel_image->mask_color); - - // create the cel - m_cel = new Cel(sprite->getCurrentFrame(), 0); - static_cast(sprite->getCurrentLayer())->addCel(m_cel); - - m_cel_created = true; - } - - m_old_cel_x = m_cel->getX(); - m_old_cel_y = m_cel->getY(); - - // region to draw - int x1, y1, x2, y2; - - // non-tiled - if (m_tiled_mode == TILED_NONE) { - x1 = MIN(m_cel->getX(), 0); - y1 = MIN(m_cel->getY(), 0); - x2 = MAX(m_cel->getX()+m_cel_image->w, m_sprite->getWidth()); - y2 = MAX(m_cel->getY()+m_cel_image->h, m_sprite->getHeight()); - } - else { // tiled - x1 = 0; - y1 = 0; - x2 = m_sprite->getWidth(); - y2 = m_sprite->getHeight(); - } - - // create two copies of the image region which we'll modify with the tool - m_src_image = image_crop(m_cel_image, - x1-m_cel->getX(), - y1-m_cel->getY(), x2-x1, y2-y1, - m_cel_image->mask_color); - m_dst_image = image_new_copy(m_src_image); - - m_useMask = m_document->isMaskVisible(); - - // Selection ink - if (getInk()->isSelection() && !m_document->isMaskVisible()) { - Mask emptyMask; - m_document->setMask(&emptyMask); - } - - m_mask = m_document->getMask(); - m_maskOrigin = (!m_mask->is_empty() ? Point(m_mask->x-x1, m_mask->y-y1): - Point(0, 0)); - - m_opacity = settings->getToolSettings(m_tool)->getOpacity(); - m_tolerance = settings->getToolSettings(m_tool)->getTolerance(); - m_speed.x = 0; - m_speed.y = 0; - - // we have to modify the cel position because it's used in the - // `render_sprite' routine to draw the `dst_image' - m_cel->setPosition(x1, y1); - m_offset.x = -x1; - m_offset.y = -y1; - - // Set undo label for any kind of undo used in the whole loop - if (m_document->getUndoHistory()->isEnabled()) { - m_document->getUndoHistory()->setLabel(m_tool->getText().c_str()); - - if (getInk()->isSelection() || - getInk()->isEyedropper() || - getInk()->isScrollMovement()) { - m_document->getUndoHistory()->setModification(undo::DoesntModifyDocument); - } - else - m_document->getUndoHistory()->setModification(undo::ModifyDocument); - } - } - - ~ToolLoopImpl() - { - if (!m_canceled) { - undo::UndoHistory* undo = m_document->getUndoHistory(); - - // Paint ink - if (getInk()->isPaint()) { - // If the size of each image is the same, we can create an - // undo with only the differences between both images. - if (m_cel->getX() == m_old_cel_x && - m_cel->getY() == m_old_cel_y && - m_cel_image->w == m_dst_image->w && - m_cel_image->h == m_dst_image->h) { - // Was the 'cel_image' created in the start of the tool-loop?. - if (m_cel_created) { - // Then we can keep the 'cel_image'... - - // We copy the 'destination' image to the 'cel_image'. - image_copy(m_cel_image, m_dst_image, 0, 0); - - // Add the 'cel_image' in the images' stock of the sprite. - m_cel->setImage(m_sprite->getStock()->addImage(m_cel_image)); - - // Is the undo enabled?. - if (undo->isEnabled()) { - // We can temporary remove the cel. - static_cast(m_sprite->getCurrentLayer())->removeCel(m_cel); - - // We create the undo information (for the new cel_image - // in the stock and the new cel in the layer)... - undo->pushUndoer(new undoers::OpenGroup()); - undo->pushUndoer(new undoers::AddImage(undo->getObjects(), - m_sprite->getStock(), m_cel->getImage())); - undo->pushUndoer(new undoers::AddCel(undo->getObjects(), - m_sprite->getCurrentLayer(), m_cel)); - undo->pushUndoer(new undoers::CloseGroup()); - - // And finally we add the cel again in the layer. - static_cast(m_sprite->getCurrentLayer())->addCel(m_cel); - } - } - else { - // Undo the dirty region. - if (undo->isEnabled()) { - Dirty* dirty = new Dirty(m_cel_image, m_dst_image); - // TODO error handling - - dirty->saveImagePixels(m_cel_image); - if (dirty != NULL) - undo->pushUndoer(new undoers::DirtyArea(undo->getObjects(), - m_cel_image, dirty)); - - delete dirty; - } - - // Copy the 'dst_image' to the cel_image. - image_copy(m_cel_image, m_dst_image, 0, 0); - } - } - // If the size of both images are different, we have to - // replace the entire image. - else { - if (undo->isEnabled()) { - undo->pushUndoer(new undoers::OpenGroup()); - - if (m_cel->getX() != m_old_cel_x || - m_cel->getY() != m_old_cel_y) { - int x = m_cel->getX(); - int y = m_cel->getY(); - m_cel->setPosition(m_old_cel_x, m_old_cel_y); - - undo->pushUndoer(new undoers::SetCelPosition(undo->getObjects(), m_cel)); - - m_cel->setPosition(x, y); - } - - undo->pushUndoer(new undoers::ReplaceImage(undo->getObjects(), - m_sprite->getStock(), m_cel->getImage())); - undo->pushUndoer(new undoers::CloseGroup()); - } - - // Replace the image in the stock. - m_sprite->getStock()->replaceImage(m_cel->getImage(), m_dst_image); - - // Destroy the old cel image. - image_free(m_cel_image); - - // Now the `dst_image' is used, so we haven't to destroy it. - m_dst_image = NULL; - } - } - - // Selection ink - if (getInk()->isSelection()) - m_document->generateMaskBoundaries(); - } - - // If the trace was not canceled or it is not a 'paint' ink... - if (m_canceled || !getInk()->isPaint()) { - // Here we destroy the temporary 'cel' created and restore all as it was before - - m_cel->setPosition(m_old_cel_x, m_old_cel_y); - - if (m_cel_created) { - static_cast(m_layer)->removeCel(m_cel); - delete m_cel; - delete m_cel_image; - } - } - - delete m_src_image; - delete m_dst_image; - delete m_pen; - } - - // IToolLoop interface - Context* getContext() { return m_context; } - tools::Tool* getTool() { return m_tool; } - Pen* getPen() { return m_pen; } - Document* getDocument() { return m_document; } - Sprite* getSprite() { return m_sprite; } - Layer* getLayer() { return m_layer; } - Image* getSrcImage() { return m_src_image; } - Image* getDstImage() { return m_dst_image; } - bool useMask() { return m_useMask; } - Mask* getMask() { return m_mask; } - Point getMaskOrigin() { return m_maskOrigin; } - int getMouseButton() { return m_button; } - int getPrimaryColor() { return m_primary_color; } - void setPrimaryColor(int color) { m_primary_color = color; } - int getSecondaryColor() { return m_secondary_color; } - void setSecondaryColor(int color) { m_secondary_color = color; } - int getOpacity() { return m_opacity; } - int getTolerance() { return m_tolerance; } - TiledMode getTiledMode() { return m_tiled_mode; } - bool getFilled() { return m_filled; } - bool getPreviewFilled() { return m_previewFilled; } - int getSprayWidth() { return m_sprayWidth; } - int getSpraySpeed() { return m_spraySpeed; } - Point getOffset() { return m_offset; } - void setSpeed(const Point& speed) { m_speed = speed; } - Point getSpeed() { return m_speed; } - tools::Ink* getInk() { return m_tool->getInk(m_button); } - tools::Controller* getController() { return m_tool->getController(m_button); } - tools::PointShape* getPointShape() { return m_tool->getPointShape(m_button); } - tools::Intertwine* getIntertwine() { return m_tool->getIntertwine(m_button); } - tools::TracePolicy getTracePolicy() { return m_tool->getTracePolicy(m_button); } - - void cancel() { m_canceled = true; } - bool isCanceled() { return m_canceled; } - - Point screenToSprite(const Point& screenPoint) - { - Point spritePoint; - m_editor->screenToEditor(screenPoint.x, screenPoint.y, - &spritePoint.x, &spritePoint.y); - return spritePoint; - } - - void updateArea(const Rect& dirty_area) - { - int x1 = dirty_area.x-m_offset.x; - int y1 = dirty_area.y-m_offset.y; - int x2 = dirty_area.x-m_offset.x+dirty_area.w-1; - int y2 = dirty_area.y-m_offset.y+dirty_area.h-1; - - acquire_bitmap(ji_screen); - editors_draw_sprite_tiled(m_sprite, x1, y1, x2, y2); - release_bitmap(ji_screen); - } - - void updateStatusBar(const char* text) - { - app_get_statusbar()->setStatusText(0, text); - } -}; - -tools::ToolLoop* Editor::createToolLoopImpl(Context* context, Message* msg) -{ - tools::Tool* current_tool = context->getSettings()->getCurrentTool(); - if (!current_tool) - return NULL; - - Sprite* sprite = getSprite(); - Layer* layer = sprite->getCurrentLayer(); - - if (!layer) { - Alert::show(PACKAGE "<is_readable()) { - Alert::show(PACKAGE - "<is_writable()) { - Alert::show(PACKAGE - "<getFgColor(); - Color bg = colorbar->getBgColor(); - - if (!fg.isValid() || !bg.isValid()) { - Alert::show(PACKAGE - "<mouse.left ? 0: 1, - msg->mouse.left ? fg: bg, - msg->mouse.left ? bg: fg); - - return tool_loop; -} diff --git a/src/widgets/editor/editor.h b/src/widgets/editor/editor.h index c3fe867e5..2a205419d 100644 --- a/src/widgets/editor/editor.h +++ b/src/widgets/editor/editor.h @@ -26,6 +26,7 @@ #include "gui/base.h" #include "gui/widget.h" #include "widgets/editor/editor_listeners.h" +#include "widgets/editor/editor_state.h" #define MIN_ZOOM 0 #define MAX_ZOOM 5 @@ -37,8 +38,6 @@ class View; namespace tools { class Tool; - class ToolLoop; - class ToolLoopManager; } class Editor : public Widget @@ -49,6 +48,11 @@ public: Editor(); ~Editor(); + EditorState* getState() const { return m_state; } + + // Changes the state of the editor. Deletes the old state. + void setState(EditorState* state); + Document* getDocument() { return m_document; } void setDocument(Document* document); @@ -81,13 +85,13 @@ public: void drawMaskSafe(); void flashCurrentLayer(); - void setMaskColorForPixelsMovement(const Color& color); void screenToEditor(int xin, int yin, int *xout, int *yout); void editorToScreen(int xin, int yin, int *xout, int *yout); void showDrawingCursor(); void hideDrawingCursor(); + void moveDrawingCursor(); void addListener(EditorListener* listener); void removeListener(EditorListener* listener); @@ -97,8 +101,23 @@ public: // Changes the scroll to see the given point as the center of the editor. void centerInSpritePoint(int x, int y); + + void updateStatusBar(); - void editor_update_statusbar_for_standby(); + // Control scroll when cursor goes out of the editor. + void controlInfiniteScroll(Message* msg); + + tools::Tool* getCurrentEditorTool(); + + // Returns true if we are able to draw in the current doc/sprite/layer/cel. + bool canDraw(); + + // Returns true if the cursor is inside the active mask/selection. + bool isInsideSelection(); + + void setZoomAndCenterInMouse(int zoom, int mouse_x, int mouse_y); + + bool processKeysToSetZoom(int scancode); // in cursor.c @@ -111,19 +130,12 @@ public: static void editor_cursor_exit(); private: - - void editor_update_statusbar_for_pixel_movement(); void editor_update_quicktool(); - void editor_draw_cursor(int x, int y, bool refresh = true); void editor_move_cursor(int x, int y, bool refresh = true); void editor_clean_cursor(bool refresh = true); bool editor_cursor_is_subpixel(); - // keys.c - - bool editor_keys_toset_zoom(int scancode); - public: // click.c @@ -147,45 +159,27 @@ protected: private: void drawGrid(const gfx::Rect& gridBounds, const Color& color); - void controlInfiniteScroll(Message* msg); - void dropPixels(); - - tools::Tool* getCurrentEditorTool(); - void editor_request_size(int *w, int *h); - void editor_setcursor(int x, int y); - void editor_update_candraw(); - void setZoomAndCenterInMouse(int zoom, int mouse_x, int mouse_y); - - tools::ToolLoop* createToolLoopImpl(Context* context, Message* msg); + void editor_setcursor(); void for_each_pixel_of_pen(int screen_x, int screen_y, int sprite_x, int sprite_y, int color, void (*pixel)(BITMAP *bmp, int x, int y, int color)); - // editor states - enum State { - EDITOR_STATE_STANDBY, - EDITOR_STATE_SCROLLING, - EDITOR_STATE_DRAWING, - }; - - // Main properties - State m_state; // Editor main state + // Current editor state (it can be shared between several editors to + // the same document). This member cannot be NULL. + EditorState* m_state; + Document* m_document; // Active document in the editor Sprite* m_sprite; // Active sprite in the editor int m_zoom; // Zoom in the editor // Drawing cursor int m_cursor_thick; - int m_cursor_screen_x; /* position in the screen (view) */ + int m_cursor_screen_x; // Position in the screen (view) int m_cursor_screen_y; - int m_cursor_editor_x; /* position in the editor (model) */ + int m_cursor_editor_x; // Position in the editor (model) int m_cursor_editor_y; - bool m_cursor_candraw : 1; - - // True if the cursor is inside the mask/selection - bool m_insideSelection : 1; // Current selected quicktool (this genererally should be NULL if // the user is not pressing any keyboard key). @@ -195,20 +189,10 @@ private: int m_offset_x; int m_offset_y; - /* marching ants stuff */ + // Marching ants stuff int m_mask_timer_id; int m_offset_count; - /* region that must be updated */ - JRegion m_refresh_region; - - // Tool-loop manager - tools::ToolLoopManager* m_toolLoopManager; - - // Helper member to move selection. If this member is NULL it means the - // user is not moving pixels. - PixelsMovement* m_pixelsMovement; - // This slot is used to disconnect the Editor from CurrentToolChange // signal (because the editor can be destroyed and the application // still continue running and generating CurrentToolChange diff --git a/src/widgets/editor/editor_listener.h b/src/widgets/editor/editor_listener.h index 811f56463..f5a74c2fa 100644 --- a/src/widgets/editor/editor_listener.h +++ b/src/widgets/editor/editor_listener.h @@ -26,7 +26,14 @@ class EditorListener public: virtual ~EditorListener() { } virtual void dispose() = 0; + + // Called when the editor's state changes. + virtual void stateChanged(Editor* editor) = 0; + + // Called when the scroll or zoom of the editor changes. virtual void scrollChanged(Editor* editor) = 0; + + // Called when the document shown in the editor changes. virtual void documentChanged(Editor* editor) = 0; }; diff --git a/src/widgets/editor/editor_listeners.cpp b/src/widgets/editor/editor_listeners.cpp index 5b578a595..bcc2d6427 100644 --- a/src/widgets/editor/editor_listeners.cpp +++ b/src/widgets/editor/editor_listeners.cpp @@ -37,6 +37,11 @@ void EditorListeners::removeListener(EditorListener* listener) m_listeners.removeListener(listener); } +void EditorListeners::notifyStateChanged(Editor* editor) +{ + m_listeners.notify(&EditorListener::stateChanged, editor); +} + void EditorListeners::notifyScrollChanged(Editor* editor) { m_listeners.notify(&EditorListener::scrollChanged, editor); diff --git a/src/widgets/editor/editor_listeners.h b/src/widgets/editor/editor_listeners.h index cc241ad17..6f859ca29 100644 --- a/src/widgets/editor/editor_listeners.h +++ b/src/widgets/editor/editor_listeners.h @@ -32,6 +32,7 @@ public: void addListener(EditorListener* listener); void removeListener(EditorListener* listener); + void notifyStateChanged(Editor* editor); void notifyScrollChanged(Editor* editor); void notifyDocumentChanged(Editor* editor); diff --git a/src/widgets/editor/editor_state.h b/src/widgets/editor/editor_state.h new file mode 100644 index 000000000..c6036bccf --- /dev/null +++ b/src/widgets/editor/editor_state.h @@ -0,0 +1,81 @@ +/* ASE - Allegro Sprite Editor + * Copyright (C) 2001-2011 David Capello + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef WIDGETS_EDITOR_EDITOR_STATE_H_INCLUDED +#define WIDGETS_EDITOR_EDITOR_STATE_H_INCLUDED + +class Editor; +union Message; + +// Represents one state of the sprite's editor (Editor class). This +// is a base class, a dummy state that ignores all events from the +// Editor. Subclasses overrides these methods to customize the +// behavior of the Editor to do different tasks (e.g. scrolling, +// drawing in the active sprite, etc.). +class EditorState +{ +public: + virtual ~EditorState() { } + + // Called just before this state is deleted and replaced by a new + // state in the Editor::setState() method. + virtual void onBeforeChangeState(Editor* editor) { } + + // Called when this instance is set as the new Editor's state when + // Editor::setState() method is used. + virtual void onAfterChangeState(Editor* editor) { } + + // Called when the current tool in the tool bar changes. It is + // useful for states which depends on the selected current tool (as + // MovingPixelsState which drops the pixels in case the user selects + // other drawing tool). + virtual void onCurrentToolChange(Editor* editor) { } + + // Called when the user presses a mouse button over the editor. + virtual bool onMouseDown(Editor* editor, Message* msg) { return false; } + + // Called when the user releases a mouse button. + virtual bool onMouseUp(Editor* editor, Message* msg) { return false; } + + // Called when the user moves the mouse over the editor. + virtual bool onMouseMove(Editor* editor, Message* msg) { return false; } + + // Called when the user moves the mouse wheel over the editor. + virtual bool onMouseWheel(Editor* editor, Message* msg) { return false; } + + // Called each time the mouse changes its position so we can set an + // appropiated cursor depending on the new coordinates of the mouse + // pointer. + virtual bool onSetCursor(Editor* editor) { return false; } + + // Called when a key is pressed over the current editor. + virtual bool onKeyDown(Editor* editor, Message* msg) { return false; } + + // Called when a key is released. + virtual bool onKeyUp(Editor* editor, Message* msg) { return false; } + + // Called when a key is released. + virtual bool onUpdateStatusBar(Editor* editor) { return false; } + + // Returns true if the this state requires the pen-preview as + // drawing cursor. + virtual bool requirePenPreview() { return false; } + +}; + +#endif // WIDGETS_EDITOR_EDITOR_STATE_H_INCLUDED diff --git a/src/widgets/editor/keys.cpp b/src/widgets/editor/keys.cpp index 453e45635..3862b877f 100644 --- a/src/widgets/editor/keys.cpp +++ b/src/widgets/editor/keys.cpp @@ -35,7 +35,7 @@ #include "widgets/color_bar.h" #include "widgets/editor/editor.h" -bool Editor::editor_keys_toset_zoom(int scancode) +bool Editor::processKeysToSetZoom(int scancode) { if ((m_sprite) && (this->hasMouse()) && diff --git a/src/widgets/editor/moving_pixels_state.cpp b/src/widgets/editor/moving_pixels_state.cpp new file mode 100644 index 000000000..394e6b9aa --- /dev/null +++ b/src/widgets/editor/moving_pixels_state.cpp @@ -0,0 +1,274 @@ +/* ASE - Allegro Sprite Editor + * Copyright (C) 2001-2011 David Capello + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "config.h" + +#include "widgets/editor/moving_pixels_state.h" + +#include "app.h" +#include "app/color_utils.h" +#include "gfx/rect.h" +#include "gui/message.h" +#include "gui/system.h" +#include "gui/view.h" +#include "modules/editors.h" +#include "raster/mask.h" +#include "raster/sprite.h" +#include "tools/ink.h" +#include "tools/tool.h" +#include "util/misc.h" +#include "widgets/editor/editor.h" +#include "widgets/editor/pixels_movement.h" +#include "widgets/editor/standby_state.h" +#include "widgets/statebar.h" + +#include + +MovingPixelsState::MovingPixelsState(Editor* editor, Message* msg, Image* imge, int x, int y, int opacity) +{ + // Copy the mask to the extra cel image + Document* document = editor->getDocument(); + Sprite* sprite = editor->getSprite(); + Image* tmpImage = NewImageFromMask(document); + x = document->getMask()->x; + y = document->getMask()->y; + m_pixelsMovement = new PixelsMovement(document, sprite, tmpImage, x, y, opacity); + delete tmpImage; + + // If the CTRL key is pressed start dragging a copy of the selection + if (key[KEY_LCONTROL] || key[KEY_RCONTROL]) // TODO configurable + m_pixelsMovement->copyMask(); + else + m_pixelsMovement->cutMask(); + + editor->screenToEditor(msg->mouse.x, msg->mouse.y, &x, &y); + m_pixelsMovement->catchImage(x, y); + + // Setup mask color + setTransparentColor(app_get_statusbar()->getTransparentColor()); + + app_get_statusbar()->addListener(this); + app_get_statusbar()->showMovePixelsOptions(); + + editor->captureMouse(); +} + +MovingPixelsState::~MovingPixelsState() +{ + app_get_statusbar()->removeListener(this); + + delete m_pixelsMovement; +} + +void MovingPixelsState::onBeforeChangeState(Editor* editor) +{ + ASSERT(m_pixelsMovement != NULL); + + // If we are changing to another state, we've to drop the image. + if (m_pixelsMovement->isDragging()) + m_pixelsMovement->dropImageTemporarily(); + + // Drop pixels if the user press a button outside the selection + m_pixelsMovement->dropImage(); + delete m_pixelsMovement; + m_pixelsMovement = NULL; + + editor->releaseMouse(); + + app_get_statusbar()->hideMovePixelsOptions(); +} + +void MovingPixelsState::onCurrentToolChange(Editor* editor) +{ + ASSERT(m_pixelsMovement != NULL); + + tools::Tool* current_tool = editor->getCurrentEditorTool(); + + // If the user changed the tool when he/she is moving pixels, + // we have to drop the pixels only if the new tool is not selection... + if (m_pixelsMovement && + (!current_tool->getInk(0)->isSelection() || + !current_tool->getInk(1)->isSelection())) { + // We have to drop pixels + dropPixels(editor); + } +} + +bool MovingPixelsState::onMouseDown(Editor* editor, Message* msg) +{ + ASSERT(m_pixelsMovement != NULL); + + // Start "moving pixels" loop + if (editor->isInsideSelection() && (msg->mouse.left || + msg->mouse.right)) { + // Re-catch the image + int x, y; + editor->screenToEditor(msg->mouse.x, msg->mouse.y, &x, &y); + m_pixelsMovement->catchImageAgain(x, y); + + editor->captureMouse(); + return true; + } + // End "moving pixels" loop + else { + // Drop pixels (e.g. to start drawing) + dropPixels(editor); + } + + // Use StandbyState implementation + return StandbyState::onMouseDown(editor, msg); +} + +bool MovingPixelsState::onMouseUp(Editor* editor, Message* msg) +{ + ASSERT(m_pixelsMovement != NULL); + + // Drop the image temporarily in this location (where the user releases the mouse) + m_pixelsMovement->dropImageTemporarily(); + + editor->releaseMouse(); + return true; +} + +bool MovingPixelsState::onMouseMove(Editor* editor, Message* msg) +{ + ASSERT(m_pixelsMovement != NULL); + + // If there is a button pressed + if (m_pixelsMovement->isDragging()) { + // Infinite scroll + editor->controlInfiniteScroll(msg); + + // Get the position of the mouse in the sprite + int x, y; + editor->screenToEditor(msg->mouse.x, msg->mouse.y, &x, &y); + + // Drag the image to that position + gfx::Rect bounds = m_pixelsMovement->moveImage(x, y); + + // If "bounds" is empty is because the cel was not moved + if (!bounds.isEmpty()) { + // Redraw the extra cel in the new position + jmouse_hide(); + editors_draw_sprite_tiled(editor->getSprite(), + bounds.x, bounds.y, + bounds.x+bounds.w-1, + bounds.y+bounds.h-1); + jmouse_show(); + } + editor->updateStatusBar(); + return true; + } + + // Use StandbyState implementation + return StandbyState::onMouseMove(editor, msg); +} + +bool MovingPixelsState::onMouseWheel(Editor* editor, Message* msg) +{ + ASSERT(m_pixelsMovement != NULL); + + // Use StandbyState implementation + return StandbyState::onMouseWheel(editor, msg); +} + +bool MovingPixelsState::onSetCursor(Editor* editor) +{ + ASSERT(m_pixelsMovement != NULL); + + // Move selection + if (m_pixelsMovement->isDragging() || editor->isInsideSelection()) { + editor->hideDrawingCursor(); + jmouse_set_cursor(JI_CURSOR_MOVE); + return true; + } + + // Use StandbyState implementation + return StandbyState::onSetCursor(editor); +} + +bool MovingPixelsState::onKeyDown(Editor* editor, Message* msg) +{ + ASSERT(m_pixelsMovement != NULL); + + if (msg->key.scancode == KEY_LCONTROL || // TODO configurable + msg->key.scancode == KEY_RCONTROL) { + // If the user press the CTRL key when he is dragging pixels (but + // not pressing the mouse buttons). + if (!jmouse_b(0) && m_pixelsMovement) { + // Drop pixels (sure the user will press the mouse button to + // start dragging a copy). + dropPixels(editor); + } + } + + // Use StandbyState implementation + return StandbyState::onKeyDown(editor, msg); +} + +bool MovingPixelsState::onKeyUp(Editor* editor, Message* msg) +{ + ASSERT(m_pixelsMovement != NULL); + + // Use StandbyState implementation + return StandbyState::onKeyUp(editor, msg); +} + +bool MovingPixelsState::onUpdateStatusBar(Editor* editor) +{ + ASSERT(m_pixelsMovement != NULL); + + gfx::Rect bounds = m_pixelsMovement->getImageBounds(); + + app_get_statusbar()->setStatusText + (100, "Pos %d %d, Size %d %d", + bounds.x, bounds.y, bounds.w, bounds.h); + + return true; +} + +void MovingPixelsState::dispose() +{ + // Never called as MovingPixelsState is removed automatically as + // StatusBar's listener. +} + +void MovingPixelsState::onChangeTransparentColor(const Color& color) +{ + setTransparentColor(color); +} + +void MovingPixelsState::setTransparentColor(const Color& color) +{ + ASSERT(current_editor != NULL); + ASSERT(m_pixelsMovement != NULL); + + Sprite* sprite = current_editor->getSprite(); + ASSERT(sprite != NULL); + + int imgtype = sprite->getImgType(); + m_pixelsMovement->setMaskColor(color_utils::color_for_image(color, imgtype)); +} + +void MovingPixelsState::dropPixels(Editor* editor) +{ + // Just change to standby state (we'll receive an + // onBeforeChangeState event). + editor->setState(new StandbyState); +} + diff --git a/src/widgets/editor/moving_pixels_state.h b/src/widgets/editor/moving_pixels_state.h new file mode 100644 index 000000000..5e514b7da --- /dev/null +++ b/src/widgets/editor/moving_pixels_state.h @@ -0,0 +1,60 @@ +/* ASE - Allegro Sprite Editor + * Copyright (C) 2001-2011 David Capello + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef WIDGETS_EDITOR_MOVING_PIXELS_STATE_H_INCLUDED +#define WIDGETS_EDITOR_MOVING_PIXELS_STATE_H_INCLUDED + +#include "base/compiler_specific.h" +#include "widgets/editor/standby_state.h" +#include "widgets/statebar.h" + +class Editor; +class Image; +class PixelsMovement; + +class MovingPixelsState : public StandbyState, StatusBarListener +{ +public: + MovingPixelsState(Editor* editor, Message* msg, Image* imge, int x, int y, int opacity); + virtual ~MovingPixelsState(); + + virtual void onBeforeChangeState(Editor* editor) OVERRIDE; + virtual void onCurrentToolChange(Editor* editor) OVERRIDE; + virtual bool onMouseDown(Editor* editor, Message* msg) OVERRIDE; + virtual bool onMouseUp(Editor* editor, Message* msg) OVERRIDE; + virtual bool onMouseMove(Editor* editor, Message* msg) OVERRIDE; + virtual bool onMouseWheel(Editor* editor, Message* msg) OVERRIDE; + virtual bool onSetCursor(Editor* editor) OVERRIDE; + virtual bool onKeyDown(Editor* editor, Message* msg) OVERRIDE; + virtual bool onKeyUp(Editor* editor, Message* msg) OVERRIDE; + virtual bool onUpdateStatusBar(Editor* editor) OVERRIDE; + +protected: + // StatusBarListener interface + virtual void dispose() OVERRIDE; + virtual void onChangeTransparentColor(const Color& color) OVERRIDE; + +private: + void setTransparentColor(const Color& color); + void dropPixels(Editor* editor); + + // Helper member to move selection. + PixelsMovement* m_pixelsMovement; +}; + +#endif // WIDGETS_EDITOR_MOVING_PIXELS_STATE_H_INCLUDED diff --git a/src/widgets/editor/pixels_movement.cpp b/src/widgets/editor/pixels_movement.cpp index 59d1ed0f0..d939b325f 100644 --- a/src/widgets/editor/pixels_movement.cpp +++ b/src/widgets/editor/pixels_movement.cpp @@ -179,6 +179,8 @@ public: DocumentWriter documentWriter(m_documentReader); m_undoTransaction.pasteImage(image, cel->getX(), cel->getY(), cel->getOpacity()); m_undoTransaction.commit(); + + documentWriter->destroyExtraCel(); } } diff --git a/src/widgets/editor/scrolling_state.cpp b/src/widgets/editor/scrolling_state.cpp new file mode 100644 index 000000000..11dae1aa4 --- /dev/null +++ b/src/widgets/editor/scrolling_state.cpp @@ -0,0 +1,100 @@ +/* ASE - Allegro Sprite Editor + * Copyright (C) 2001-2011 David Capello + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "config.h" + +#include "widgets/editor/scrolling_state.h" + +#include "app.h" +#include "gfx/rect.h" +#include "gui/message.h" +#include "gui/system.h" +#include "gui/view.h" +#include "modules/editors.h" +#include "raster/sprite.h" +#include "widgets/editor/editor.h" +#include "widgets/editor/standby_state.h" +#include "widgets/statebar.h" + +ScrollingState::ScrollingState() +{ +} + +ScrollingState::~ScrollingState() +{ +} + +bool ScrollingState::onMouseDown(Editor* editor, Message* msg) +{ + return true; +} + +bool ScrollingState::onMouseUp(Editor* editor, Message* msg) +{ + editor->setState(new StandbyState); + editor->releaseMouse(); + return true; +} + +bool ScrollingState::onMouseMove(Editor* editor, Message* msg) +{ + View* view = View::getView(editor); + gfx::Rect vp = view->getViewportBounds(); + gfx::Point scroll = view->getViewScroll(); + + editor->setEditorScroll(scroll.x+jmouse_x(1)-jmouse_x(0), + scroll.y+jmouse_y(1)-jmouse_y(0), true); + + jmouse_control_infinite_scroll(vp); + + int x, y; + editor->screenToEditor(jmouse_x(0), jmouse_y(0), &x, &y); + app_get_statusbar()->setStatusText + (0, "Pos %3d %3d (Size %3d %3d)", x, y, + editor->getSprite()->getWidth(), + editor->getSprite()->getHeight()); + + return true; +} + +bool ScrollingState::onMouseWheel(Editor* editor, Message* msg) +{ + return false; +} + +bool ScrollingState::onSetCursor(Editor* editor) +{ + editor->hideDrawingCursor(); + jmouse_set_cursor(JI_CURSOR_SCROLL); + return true; +} + +bool ScrollingState::onKeyDown(Editor* editor, Message* msg) +{ + return false; +} + +bool ScrollingState::onKeyUp(Editor* editor, Message* msg) +{ + return false; +} + +bool ScrollingState::onUpdateStatusBar(Editor* editor) +{ + return false; +} diff --git a/src/widgets/editor/scrolling_state.h b/src/widgets/editor/scrolling_state.h new file mode 100644 index 000000000..b123ffafa --- /dev/null +++ b/src/widgets/editor/scrolling_state.h @@ -0,0 +1,40 @@ +/* ASE - Allegro Sprite Editor + * Copyright (C) 2001-2011 David Capello + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef WIDGETS_EDITOR_SCROLLING_STATE_H_INCLUDED +#define WIDGETS_EDITOR_SCROLLING_STATE_H_INCLUDED + +#include "base/compiler_specific.h" +#include "widgets/editor/editor_state.h" + +class ScrollingState : public EditorState +{ +public: + ScrollingState(); + virtual ~ScrollingState(); + virtual bool onMouseDown(Editor* editor, Message* msg) OVERRIDE; + virtual bool onMouseUp(Editor* editor, Message* msg) OVERRIDE; + virtual bool onMouseMove(Editor* editor, Message* msg) OVERRIDE; + virtual bool onMouseWheel(Editor* editor, Message* msg) OVERRIDE; + virtual bool onSetCursor(Editor* editor) OVERRIDE; + virtual bool onKeyDown(Editor* editor, Message* msg) OVERRIDE; + virtual bool onKeyUp(Editor* editor, Message* msg) OVERRIDE; + virtual bool onUpdateStatusBar(Editor* editor) OVERRIDE; +}; + +#endif // WIDGETS_EDITOR_SCROLLING_STATE_H_INCLUDED diff --git a/src/widgets/editor/standby_state.cpp b/src/widgets/editor/standby_state.cpp new file mode 100644 index 000000000..81e7f204e --- /dev/null +++ b/src/widgets/editor/standby_state.cpp @@ -0,0 +1,383 @@ +/* ASE - Allegro Sprite Editor + * Copyright (C) 2001-2011 David Capello + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "config.h" + +#include "widgets/editor/standby_state.h" + +#include "app.h" +#include "commands/commands.h" +#include "commands/params.h" +#include "core/cfg.h" +#include "gfx/rect.h" +#include "gui/alert.h" +#include "gui/message.h" +#include "gui/system.h" +#include "gui/view.h" +#include "modules/editors.h" +#include "raster/layer.h" +#include "raster/mask.h" +#include "raster/sprite.h" +#include "tools/ink.h" +#include "tools/tool.h" +#include "ui_context.h" +#include "util/misc.h" +#include "widgets/color_bar.h" +#include "widgets/editor/drawing_state.h" +#include "widgets/editor/editor.h" +#include "widgets/editor/moving_pixels_state.h" +#include "widgets/editor/scrolling_state.h" +#include "widgets/editor/tool_loop_impl.h" +#include "widgets/statebar.h" + +#include + +enum WHEEL_ACTION { WHEEL_NONE, + WHEEL_ZOOM, + WHEEL_VSCROLL, + WHEEL_HSCROLL, + WHEEL_FG, + WHEEL_BG, + WHEEL_FRAME }; + +static inline bool has_shifts(Message* msg, int shift) +{ + return ((msg->any.shifts & shift) == shift); +} + +StandbyState::StandbyState() +{ +} + +StandbyState::~StandbyState() +{ +} + +bool StandbyState::onMouseDown(Editor* editor, Message* msg) +{ + if (editor->hasCapture()) + return true; + + UIContext* context = UIContext::instance(); + tools::Tool* current_tool = editor->getCurrentEditorTool(); + Sprite* sprite = editor->getSprite(); + + // Each time an editor is clicked the current editor and the active + // document are set. + set_current_editor(editor); + context->setActiveDocument(editor->getDocument()); + + // Start scroll loop + if (msg->mouse.middle || + current_tool->getInk(msg->mouse.right ? 1: 0)->isScrollMovement()) { + editor->setState(new ScrollingState()); + editor->captureMouse(); + return true; + } + + // Move frames position + if (current_tool->getInk(msg->mouse.right ? 1: 0)->isCelMovement()) { + if ((sprite->getCurrentLayer()) && + (sprite->getCurrentLayer()->getType() == GFXOBJ_LAYER_IMAGE)) { + // TODO you can move the `Background' with tiled mode + if (sprite->getCurrentLayer()->is_background()) { + Alert::show(PACKAGE + "<getCurrentLayer()->is_moveable()) { + Alert::show(PACKAGE "<isInsideSelection() && + current_tool->getInk(0)->isSelection() && + msg->mouse.left) { + int x, y, opacity; + Image* image = sprite->getCurrentImage(&x, &y, &opacity); + if (image) { + if (!sprite->getCurrentLayer()->is_writable()) { + Alert::show(PACKAGE "<setState(new MovingPixelsState(editor, msg, image, x, y, opacity)); + } + } + // Call the eyedropper command + else if (current_tool->getInk(msg->mouse.right ? 1: 0)->isEyedropper()) { + Command* eyedropper_cmd = + CommandsModule::instance()->getCommandByName(CommandId::Eyedropper); + + Params params; + params.set("target", msg->mouse.right ? "background": "foreground"); + + UIContext::instance()->executeCommand(eyedropper_cmd, ¶ms); + return true; + } + // Start the Tool-Loop + else if (sprite->getCurrentLayer()) { + tools::ToolLoop* toolLoop = create_tool_loop(editor, context, msg); + if (toolLoop) + editor->setState(new DrawingState(toolLoop, editor, msg)); + } + + return true; +} + +bool StandbyState::onMouseUp(Editor* editor, Message* msg) +{ + editor->releaseMouse(); + return true; +} + +bool StandbyState::onMouseMove(Editor* editor, Message* msg) +{ + editor->moveDrawingCursor(); + editor->updateStatusBar(); + return true; +} + +bool StandbyState::onMouseWheel(Editor* editor, Message* msg) +{ + int dz = jmouse_z(1) - jmouse_z(0); + WHEEL_ACTION wheelAction = WHEEL_NONE; + bool scrollBigSteps = false; + + // Without modifiers + if (!(msg->any.shifts & (KB_SHIFT_FLAG | KB_ALT_FLAG | KB_CTRL_FLAG))) { + wheelAction = WHEEL_ZOOM; + } + else { +#if 1 // TODO make it configurable + if (has_shifts(msg, KB_ALT_FLAG)) { + if (has_shifts(msg, KB_SHIFT_FLAG)) + wheelAction = WHEEL_BG; + else + wheelAction = WHEEL_FG; + } + else if (has_shifts(msg, KB_CTRL_FLAG)) { + wheelAction = WHEEL_FRAME; + } +#else + if (has_shifts(msg, KB_CTRL_FLAG)) + wheelAction = WHEEL_HSCROLL; + else + wheelAction = WHEEL_VSCROLL; + + if (has_shifts(msg, KB_SHIFT_FLAG)) + scrollBigSteps = true; +#endif + } + + switch (wheelAction) { + + case WHEEL_NONE: + // Do nothing + break; + + case WHEEL_FG: + // if (m_state == EDITOR_STATE_STANDBY) + { + int newIndex = 0; + if (app_get_colorbar()->getFgColor().getType() == Color::IndexType) { + newIndex = app_get_colorbar()->getFgColor().getIndex() + dz; + newIndex = MID(0, newIndex, 255); + } + app_get_colorbar()->setFgColor(Color::fromIndex(newIndex)); + } + break; + + case WHEEL_BG: + // if (m_state == EDITOR_STATE_STANDBY) + { + int newIndex = 0; + if (app_get_colorbar()->getBgColor().getType() == Color::IndexType) { + newIndex = app_get_colorbar()->getBgColor().getIndex() + dz; + newIndex = MID(0, newIndex, 255); + } + app_get_colorbar()->setBgColor(Color::fromIndex(newIndex)); + } + break; + + case WHEEL_FRAME: + // if (m_state == EDITOR_STATE_STANDBY) + { + Command* command = CommandsModule::instance()->getCommandByName + ((dz < 0) ? CommandId::GotoNextFrame: + CommandId::GotoPreviousFrame); + if (command) + UIContext::instance()->executeCommand(command, NULL); + } + break; + + case WHEEL_ZOOM: { + int zoom = MID(MIN_ZOOM, editor->getZoom()-dz, MAX_ZOOM); + if (editor->getZoom() != zoom) + editor->setZoomAndCenterInMouse(zoom, msg->mouse.x, msg->mouse.y); + break; + } + + case WHEEL_HSCROLL: + case WHEEL_VSCROLL: { + View* view = View::getView(editor); + gfx::Rect vp = view->getViewportBounds(); + int dx = 0; + int dy = 0; + + if (wheelAction == WHEEL_HSCROLL) { + dx = dz * vp.w; + } + else { + dy = dz * vp.h; + } + + if (scrollBigSteps) { + dx /= 2; + dy /= 2; + } + else { + dx /= 10; + dy /= 10; + } + + gfx::Point scroll = view->getViewScroll(); + + editor->hideDrawingCursor(); + editor->setEditorScroll(scroll.x+dx, scroll.y+dy, true); + editor->showDrawingCursor(); + break; + } + + } + + return true; +} + +bool StandbyState::onSetCursor(Editor* editor) +{ + tools::Tool* current_tool = editor->getCurrentEditorTool(); + + if (current_tool) { + // If the current tool change selection (e.g. rectangular marquee, etc.) + if (current_tool->getInk(0)->isSelection()) { + // Move pixels + if (editor->isInsideSelection()) { + editor->hideDrawingCursor(); + + if (key[KEY_LCONTROL] || + key[KEY_RCONTROL]) // TODO configurable keys + jmouse_set_cursor(JI_CURSOR_NORMAL_ADD); + else + jmouse_set_cursor(JI_CURSOR_MOVE); + + return true; + } + } + else if (current_tool->getInk(0)->isEyedropper()) { + editor->hideDrawingCursor(); + jmouse_set_cursor(JI_CURSOR_EYEDROPPER); + return true; + } + else if (current_tool->getInk(0)->isScrollMovement()) { + editor->hideDrawingCursor(); + jmouse_set_cursor(JI_CURSOR_SCROLL); + return true; + } + else if (current_tool->getInk(0)->isCelMovement()) { + editor->hideDrawingCursor(); + jmouse_set_cursor(JI_CURSOR_MOVE); + return true; + } + } + + // Draw + if (editor->canDraw()) { + jmouse_set_cursor(JI_CURSOR_NULL); + editor->showDrawingCursor(); + } + // Forbidden + else { + editor->hideDrawingCursor(); + jmouse_set_cursor(JI_CURSOR_FORBIDDEN); + } + + return true; +} + +bool StandbyState::onKeyDown(Editor* editor, Message* msg) +{ + return editor->processKeysToSetZoom(msg->key.scancode); +} + +bool StandbyState::onKeyUp(Editor* editor, Message* msg) +{ + return false; +} + +bool StandbyState::onUpdateStatusBar(Editor* editor) +{ + tools::Tool* current_tool = editor->getCurrentEditorTool(); + Sprite* sprite = editor->getSprite(); + int x, y; + + editor->screenToEditor(jmouse_x(0), jmouse_y(0), &x, &y); + + if (!sprite) { + app_get_statusbar()->clearText(); + } + // For eye-dropper + else if (current_tool->getInk(0)->isEyedropper()) { + int imgtype = sprite->getImgType(); + uint32_t pixel = sprite->getPixel(x, y); + Color color = Color::fromImage(imgtype, pixel); + + int alpha = 255; + switch (imgtype) { + case IMAGE_RGB: alpha = _rgba_geta(pixel); break; + case IMAGE_GRAYSCALE: alpha = _graya_geta(pixel); break; + } + + char buf[256]; + usprintf(buf, "- Pos %d %d", x, y); + + app_get_statusbar()->showColor(0, buf, color, alpha); + } + else { + Mask* mask = editor->getDocument()->getMask(); + + app_get_statusbar()->setStatusText + (0, "Pos %d %d, Size %d %d, Frame %d", + x, y, + ((mask && mask->bitmap)? mask->w: sprite->getWidth()), + ((mask && mask->bitmap)? mask->h: sprite->getHeight()), + sprite->getCurrentFrame()+1); + } + + return true; +} diff --git a/src/widgets/editor/standby_state.h b/src/widgets/editor/standby_state.h new file mode 100644 index 000000000..c21ae16aa --- /dev/null +++ b/src/widgets/editor/standby_state.h @@ -0,0 +1,44 @@ +/* ASE - Allegro Sprite Editor + * Copyright (C) 2001-2011 David Capello + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef WIDGETS_EDITOR_STANDBY_STATE_H_INCLUDED +#define WIDGETS_EDITOR_STANDBY_STATE_H_INCLUDED + +#include "base/compiler_specific.h" +#include "widgets/editor/editor_state.h" + +class StandbyState : public EditorState +{ +public: + StandbyState(); + virtual ~StandbyState(); + virtual bool onMouseDown(Editor* editor, Message* msg) OVERRIDE; + virtual bool onMouseUp(Editor* editor, Message* msg) OVERRIDE; + virtual bool onMouseMove(Editor* editor, Message* msg) OVERRIDE; + virtual bool onMouseWheel(Editor* editor, Message* msg) OVERRIDE; + virtual bool onSetCursor(Editor* editor) OVERRIDE; + virtual bool onKeyDown(Editor* editor, Message* msg) OVERRIDE; + virtual bool onKeyUp(Editor* editor, Message* msg) OVERRIDE; + virtual bool onUpdateStatusBar(Editor* editor) OVERRIDE; + + // Returns true as the standby state is the only one which shows the + // pen-preview. + virtual bool requirePenPreview() OVERRIDE { return true; } +}; + +#endif // WIDGETS_EDITOR_STANDBY_STATE_H_INCLUDED diff --git a/src/widgets/editor/tool_loop_impl.cpp b/src/widgets/editor/tool_loop_impl.cpp new file mode 100644 index 000000000..75b8dfab9 --- /dev/null +++ b/src/widgets/editor/tool_loop_impl.cpp @@ -0,0 +1,455 @@ +/* ASE - Allegro Sprite Editor + * Copyright (C) 2001-2011 David Capello + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "config.h" + +#include "widgets/editor/tool_loop_impl.h" + +#include "app.h" +#include "app/color.h" +#include "app/color_utils.h" +#include "context.h" +#include "gui/gui.h" +#include "modules/editors.h" +#include "raster/cel.h" +#include "raster/dirty.h" +#include "raster/image.h" +#include "raster/layer.h" +#include "raster/mask.h" +#include "raster/pen.h" +#include "raster/sprite.h" +#include "raster/stock.h" +#include "settings/settings.h" +#include "tools/ink.h" +#include "tools/tool.h" +#include "tools/tool_loop.h" +#include "undo/undo_history.h" +#include "undoers/add_cel.h" +#include "undoers/add_image.h" +#include "undoers/close_group.h" +#include "undoers/dirty_area.h" +#include "undoers/open_group.h" +#include "undoers/replace_image.h" +#include "undoers/set_cel_position.h" +#include "widgets/editor/editor.h" +#include "widgets/color_bar.h" +#include "widgets/statebar.h" + +#include + +class ToolLoopImpl : public tools::ToolLoop +{ + Editor* m_editor; + Context* m_context; + tools::Tool* m_tool; + Pen* m_pen; + Document* m_document; + Sprite* m_sprite; + Layer* m_layer; + Cel* m_cel; + Image* m_cel_image; + bool m_cel_created; + int m_old_cel_x; + int m_old_cel_y; + bool m_filled; + bool m_previewFilled; + int m_sprayWidth; + int m_spraySpeed; + TiledMode m_tiled_mode; + Image* m_src_image; + Image* m_dst_image; + bool m_useMask; + Mask* m_mask; + gfx::Point m_maskOrigin; + int m_opacity; + int m_tolerance; + gfx::Point m_offset; + gfx::Point m_speed; + bool m_canceled; + int m_button; + int m_primary_color; + int m_secondary_color; + +public: + ToolLoopImpl(Editor* editor, + Context* context, + tools::Tool* tool, + Document* document, + Sprite* sprite, + Layer* layer, + int button, const Color& primary_color, const Color& secondary_color) + : m_editor(editor) + , m_context(context) + , m_tool(tool) + , m_document(document) + , m_sprite(sprite) + , m_layer(layer) + , m_cel(NULL) + , m_cel_image(NULL) + , m_cel_created(false) + , m_canceled(false) + , m_button(button) + , m_primary_color(color_utils::color_for_layer(primary_color, layer)) + , m_secondary_color(color_utils::color_for_layer(secondary_color, layer)) + { + // Settings + ISettings* settings = m_context->getSettings(); + + m_tiled_mode = settings->getTiledMode(); + + switch (tool->getFill(m_button)) { + case tools::FillNone: + m_filled = false; + break; + case tools::FillAlways: + m_filled = true; + break; + case tools::FillOptional: + m_filled = settings->getToolSettings(m_tool)->getFilled(); + break; + } + m_previewFilled = settings->getToolSettings(m_tool)->getPreviewFilled(); + + m_sprayWidth = settings->getToolSettings(m_tool)->getSprayWidth(); + m_spraySpeed = settings->getToolSettings(m_tool)->getSpraySpeed(); + + // Create the pen + IPenSettings* pen_settings = settings->getToolSettings(m_tool)->getPen(); + ASSERT(pen_settings != NULL); + + m_pen = new Pen(pen_settings->getType(), + pen_settings->getSize(), + pen_settings->getAngle()); + + // Get cel and image where we can draw + + if (m_layer->is_image()) { + m_cel = static_cast(sprite->getCurrentLayer())->getCel(sprite->getCurrentFrame()); + if (m_cel) + m_cel_image = sprite->getStock()->getImage(m_cel->getImage()); + } + + if (m_cel == NULL) { + // create the image + m_cel_image = image_new(sprite->getImgType(), sprite->getWidth(), sprite->getHeight()); + image_clear(m_cel_image, + m_cel_image->mask_color); + + // create the cel + m_cel = new Cel(sprite->getCurrentFrame(), 0); + static_cast(sprite->getCurrentLayer())->addCel(m_cel); + + m_cel_created = true; + } + + m_old_cel_x = m_cel->getX(); + m_old_cel_y = m_cel->getY(); + + // region to draw + int x1, y1, x2, y2; + + // non-tiled + if (m_tiled_mode == TILED_NONE) { + x1 = MIN(m_cel->getX(), 0); + y1 = MIN(m_cel->getY(), 0); + x2 = MAX(m_cel->getX()+m_cel_image->w, m_sprite->getWidth()); + y2 = MAX(m_cel->getY()+m_cel_image->h, m_sprite->getHeight()); + } + else { // tiled + x1 = 0; + y1 = 0; + x2 = m_sprite->getWidth(); + y2 = m_sprite->getHeight(); + } + + // create two copies of the image region which we'll modify with the tool + m_src_image = image_crop(m_cel_image, + x1-m_cel->getX(), + y1-m_cel->getY(), x2-x1, y2-y1, + m_cel_image->mask_color); + m_dst_image = image_new_copy(m_src_image); + + m_useMask = m_document->isMaskVisible(); + + // Selection ink + if (getInk()->isSelection() && !m_document->isMaskVisible()) { + Mask emptyMask; + m_document->setMask(&emptyMask); + } + + m_mask = m_document->getMask(); + m_maskOrigin = (!m_mask->is_empty() ? gfx::Point(m_mask->x-x1, m_mask->y-y1): + gfx::Point(0, 0)); + + m_opacity = settings->getToolSettings(m_tool)->getOpacity(); + m_tolerance = settings->getToolSettings(m_tool)->getTolerance(); + m_speed.x = 0; + m_speed.y = 0; + + // we have to modify the cel position because it's used in the + // `render_sprite' routine to draw the `dst_image' + m_cel->setPosition(x1, y1); + m_offset.x = -x1; + m_offset.y = -y1; + + // Set undo label for any kind of undo used in the whole loop + if (m_document->getUndoHistory()->isEnabled()) { + m_document->getUndoHistory()->setLabel(m_tool->getText().c_str()); + + if (getInk()->isSelection() || + getInk()->isEyedropper() || + getInk()->isScrollMovement()) { + m_document->getUndoHistory()->setModification(undo::DoesntModifyDocument); + } + else + m_document->getUndoHistory()->setModification(undo::ModifyDocument); + } + } + + ~ToolLoopImpl() + { + if (!m_canceled) { + undo::UndoHistory* undo = m_document->getUndoHistory(); + + // Paint ink + if (getInk()->isPaint()) { + // If the size of each image is the same, we can create an + // undo with only the differences between both images. + if (m_cel->getX() == m_old_cel_x && + m_cel->getY() == m_old_cel_y && + m_cel_image->w == m_dst_image->w && + m_cel_image->h == m_dst_image->h) { + // Was the 'cel_image' created in the start of the tool-loop?. + if (m_cel_created) { + // Then we can keep the 'cel_image'... + + // We copy the 'destination' image to the 'cel_image'. + image_copy(m_cel_image, m_dst_image, 0, 0); + + // Add the 'cel_image' in the images' stock of the sprite. + m_cel->setImage(m_sprite->getStock()->addImage(m_cel_image)); + + // Is the undo enabled?. + if (undo->isEnabled()) { + // We can temporary remove the cel. + static_cast(m_sprite->getCurrentLayer())->removeCel(m_cel); + + // We create the undo information (for the new cel_image + // in the stock and the new cel in the layer)... + undo->pushUndoer(new undoers::OpenGroup()); + undo->pushUndoer(new undoers::AddImage(undo->getObjects(), + m_sprite->getStock(), m_cel->getImage())); + undo->pushUndoer(new undoers::AddCel(undo->getObjects(), + m_sprite->getCurrentLayer(), m_cel)); + undo->pushUndoer(new undoers::CloseGroup()); + + // And finally we add the cel again in the layer. + static_cast(m_sprite->getCurrentLayer())->addCel(m_cel); + } + } + else { + // Undo the dirty region. + if (undo->isEnabled()) { + Dirty* dirty = new Dirty(m_cel_image, m_dst_image); + // TODO error handling + + dirty->saveImagePixels(m_cel_image); + if (dirty != NULL) + undo->pushUndoer(new undoers::DirtyArea(undo->getObjects(), + m_cel_image, dirty)); + + delete dirty; + } + + // Copy the 'dst_image' to the cel_image. + image_copy(m_cel_image, m_dst_image, 0, 0); + } + } + // If the size of both images are different, we have to + // replace the entire image. + else { + if (undo->isEnabled()) { + undo->pushUndoer(new undoers::OpenGroup()); + + if (m_cel->getX() != m_old_cel_x || + m_cel->getY() != m_old_cel_y) { + int x = m_cel->getX(); + int y = m_cel->getY(); + m_cel->setPosition(m_old_cel_x, m_old_cel_y); + + undo->pushUndoer(new undoers::SetCelPosition(undo->getObjects(), m_cel)); + + m_cel->setPosition(x, y); + } + + undo->pushUndoer(new undoers::ReplaceImage(undo->getObjects(), + m_sprite->getStock(), m_cel->getImage())); + undo->pushUndoer(new undoers::CloseGroup()); + } + + // Replace the image in the stock. + m_sprite->getStock()->replaceImage(m_cel->getImage(), m_dst_image); + + // Destroy the old cel image. + image_free(m_cel_image); + + // Now the `dst_image' is used, so we haven't to destroy it. + m_dst_image = NULL; + } + } + + // Selection ink + if (getInk()->isSelection()) + m_document->generateMaskBoundaries(); + } + + // If the trace was not canceled or it is not a 'paint' ink... + if (m_canceled || !getInk()->isPaint()) { + // Here we destroy the temporary 'cel' created and restore all as it was before + + m_cel->setPosition(m_old_cel_x, m_old_cel_y); + + if (m_cel_created) { + static_cast(m_layer)->removeCel(m_cel); + delete m_cel; + delete m_cel_image; + } + } + + delete m_src_image; + delete m_dst_image; + delete m_pen; + } + + // IToolLoop interface + Context* getContext() { return m_context; } + tools::Tool* getTool() { return m_tool; } + Pen* getPen() { return m_pen; } + Document* getDocument() { return m_document; } + Sprite* getSprite() { return m_sprite; } + Layer* getLayer() { return m_layer; } + Image* getSrcImage() { return m_src_image; } + Image* getDstImage() { return m_dst_image; } + bool useMask() { return m_useMask; } + Mask* getMask() { return m_mask; } + gfx::Point getMaskOrigin() { return m_maskOrigin; } + int getMouseButton() { return m_button; } + int getPrimaryColor() { return m_primary_color; } + void setPrimaryColor(int color) { m_primary_color = color; } + int getSecondaryColor() { return m_secondary_color; } + void setSecondaryColor(int color) { m_secondary_color = color; } + int getOpacity() { return m_opacity; } + int getTolerance() { return m_tolerance; } + TiledMode getTiledMode() { return m_tiled_mode; } + bool getFilled() { return m_filled; } + bool getPreviewFilled() { return m_previewFilled; } + int getSprayWidth() { return m_sprayWidth; } + int getSpraySpeed() { return m_spraySpeed; } + gfx::Point getOffset() { return m_offset; } + void setSpeed(const gfx::Point& speed) { m_speed = speed; } + gfx::Point getSpeed() { return m_speed; } + tools::Ink* getInk() { return m_tool->getInk(m_button); } + tools::Controller* getController() { return m_tool->getController(m_button); } + tools::PointShape* getPointShape() { return m_tool->getPointShape(m_button); } + tools::Intertwine* getIntertwine() { return m_tool->getIntertwine(m_button); } + tools::TracePolicy getTracePolicy() { return m_tool->getTracePolicy(m_button); } + + void cancel() { m_canceled = true; } + bool isCanceled() { return m_canceled; } + + gfx::Point screenToSprite(const gfx::Point& screenPoint) + { + gfx::Point spritePoint; + m_editor->screenToEditor(screenPoint.x, screenPoint.y, + &spritePoint.x, &spritePoint.y); + return spritePoint; + } + + void updateArea(const gfx::Rect& dirty_area) + { + int x1 = dirty_area.x-m_offset.x; + int y1 = dirty_area.y-m_offset.y; + int x2 = dirty_area.x-m_offset.x+dirty_area.w-1; + int y2 = dirty_area.y-m_offset.y+dirty_area.h-1; + + acquire_bitmap(ji_screen); + editors_draw_sprite_tiled(m_sprite, x1, y1, x2, y2); + release_bitmap(ji_screen); + } + + void updateStatusBar(const char* text) + { + app_get_statusbar()->setStatusText(0, text); + } +}; + +tools::ToolLoop* create_tool_loop(Editor* editor, Context* context, Message* msg) +{ + tools::Tool* current_tool = context->getSettings()->getCurrentTool(); + if (!current_tool) + return NULL; + + Sprite* sprite = editor->getSprite(); + Layer* layer = sprite->getCurrentLayer(); + + if (!layer) { + Alert::show(PACKAGE "<is_readable()) { + Alert::show(PACKAGE + "<is_writable()) { + Alert::show(PACKAGE + "<getFgColor(); + Color bg = colorbar->getBgColor(); + + if (!fg.isValid() || !bg.isValid()) { + Alert::show(PACKAGE + "<getDocument(), + sprite, layer, + msg->mouse.left ? 0: 1, + msg->mouse.left ? fg: bg, + msg->mouse.left ? bg: fg); +} diff --git a/src/widgets/editor/tool_loop_impl.h b/src/widgets/editor/tool_loop_impl.h new file mode 100644 index 000000000..c585a69f6 --- /dev/null +++ b/src/widgets/editor/tool_loop_impl.h @@ -0,0 +1,32 @@ +/* ASE - Allegro Sprite Editor + * Copyright (C) 2001-2011 David Capello + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef WIDGETS_EDITOR_TOOL_LOOP_IMPL_H_INCLUDED +#define WIDGETS_EDITOR_TOOL_LOOP_IMPL_H_INCLUDED + +namespace tools { + class ToolLoop; +} + +class Context; +class Editor; +union Message; + +tools::ToolLoop* create_tool_loop(Editor* editor, Context* context, Message* msg); + +#endif diff --git a/src/widgets/statebar.cpp b/src/widgets/statebar.cpp index 60ee1fe68..d608fbd81 100644 --- a/src/widgets/statebar.cpp +++ b/src/widgets/statebar.cpp @@ -164,6 +164,16 @@ StatusBar::~StatusBar() delete m_tipwindow; // widget } +void StatusBar::addListener(StatusBarListener* listener) +{ + m_listeners.addListener(listener); +} + +void StatusBar::removeListener(StatusBarListener* listener) +{ + m_listeners.removeListener(listener); +} + void StatusBar::onCurrentToolChange() { if (isVisible()) { @@ -177,8 +187,8 @@ void StatusBar::onCurrentToolChange() void StatusBar::onTransparentColorChange() { - if (current_editor) - current_editor->setMaskColorForPixelsMovement(getTransparentColor()); + m_listeners.notify(&StatusBarListener::onChangeTransparentColor, + getTransparentColor()); } void StatusBar::clearText() diff --git a/src/widgets/statebar.h b/src/widgets/statebar.h index 0098740a3..ea792d5ec 100644 --- a/src/widgets/statebar.h +++ b/src/widgets/statebar.h @@ -25,6 +25,7 @@ #include "base/compiler_specific.h" #include "gui/base.h" #include "gui/widget.h" +#include "listeners.h" class Box; class Button; @@ -51,12 +52,25 @@ private: float m_pos; }; +class StatusBarListener +{ +public: + virtual ~StatusBarListener() { } + virtual void dispose() = 0; + virtual void onChangeTransparentColor(const Color& color) = 0; +}; + +typedef Listeners StatusBarListeners; + class StatusBar : public Widget { public: StatusBar(); ~StatusBar(); + void addListener(StatusBarListener* listener); + void removeListener(StatusBarListener* listener); + void clearText(); bool setStatusText(int msecs, const char *format, ...); @@ -116,6 +130,8 @@ private: Frame* m_tipwindow; int m_hot_layer; + + StatusBarListeners m_listeners; }; #endif