1 /*
2  * Copyright (C) 2014-2018 Christopho, Solarus - http://www.solarus-games.org
3  *
4  * Solarus Quest Editor is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * Solarus Quest Editor is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
17 #ifndef SOLARUSEDITOR_SPRITE_MODEL_H
18 #define SOLARUSEDITOR_SPRITE_MODEL_H
19 
20 #include "natural_comparator.h"
21 
22 #include <solarus/graphics/SpriteData.h>
23 #include <QAbstractItemModel>
24 #include <QImage>
25 #include <QItemSelectionModel>
26 #include <QPixmap>
27 #include <map>
28 #include <memory>
29 
30 namespace SolarusEditor {
31 
32 class Quest;
33 
34 /**
35  * @brief Model that wraps a sprite.
36  *
37  * It makes the link between the editor and the sprite data of the
38  * Solarus library.
39  * Signals are sent when something changes in the wrapped sprite.
40  * This model can be used as a model for a tree view of animations
41  * and directions.
42  * It also stores the selection information.
43  */
44 class SpriteModel : public QAbstractItemModel {
45   Q_OBJECT
46 
47 public:
48 
49   /**
50    * @brief Index identifying an animation and direction of a sprite.
51    */
52   struct Index {
53 
54   public:
55 
56     /**
57      * @brief Creates a sprite direction index.
58      * @param animation_name Animation name of this index.
59      * @param direction_nb Direction number of this index.
60      */
61     Index(const QString& animation_name = "", int direction_nb = -1) :
animation_nameIndex62       animation_name(animation_name),
63       direction_nb(direction_nb) {
64     }
65 
66     /**
67      * @brief Returns whether this index is valid.
68      * @return \c true if this index is valid.
69      */
is_validIndex70     bool is_valid() const {
71       return !animation_name.isEmpty();
72     }
73 
74     /**
75      * @brief Returns whether this index represents an animation.
76      * @return \c true if this index represents an animation.
77      */
is_animation_indexIndex78     bool is_animation_index() const {
79       return !animation_name.isEmpty() && direction_nb < 0;
80     }
81 
82     /**
83      * @brief Returns whether this index represents a direction.
84      * @return \c true if this index represents a direction.
85      */
is_direction_indexIndex86     bool is_direction_index() const {
87       return !animation_name.isEmpty() && direction_nb >= 0;
88     }
89 
90     QString animation_name; /**< Animation name of this index. */
91     int direction_nb;       /**< Direction number of this index. */
92   };
93 
94   // Creation.
95   SpriteModel(const Quest& quest, const QString& sprite_id, QObject* parent = nullptr);
96 
97   const Quest& get_quest() const;
98   QString get_sprite_id() const;
99   QString get_default_animation_name() const;
100   void set_default_animation_name(const QString& default_animation_name);
101   bool exists(const Index& index) const;
102   QModelIndex get_model_index(const Index& index) const;
103   QString get_tileset_id() const;
104   void set_tileset_id(const QString& tileset_id);
105 
106   // QAbstractItemModel interface.
107   int columnCount(const QModelIndex& parent = QModelIndex()) const override;
108   int rowCount(const QModelIndex& parent = QModelIndex()) const override;
109   QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override;
110   QModelIndex parent(const QModelIndex& model_index) const override;
111   bool hasChildren(const QModelIndex& parent = QModelIndex()) const override;
112   QVariant data(const QModelIndex& model_index, int role) const override;
113 
114   // Animation.
115   int get_animation_nb(const Index& index) const;
116   Index get_animation_index(int animation_nb) const;
117 
118   bool animation_exists(const Index& index) const;
119   void create_animation(const QString& animation_name);
120   void insert_animation(
121       const Index& index, const Solarus::SpriteAnimationData& data);
122   void delete_animation(const Index& index);
123   void set_animation_name(const Index& index, const QString& new_name);
124 
125   QStringList get_animation_names() const;
126   Solarus::SpriteAnimationData get_animation_data(const Index& index) const;
127   QString get_animation_source_image(const Index& index) const;
128   bool is_animation_image_tileset(const Index& index) const;
129   void set_animation_source_image(const Index& index, const QString &src_image);
130   uint32_t get_animation_frame_delay(const Index& index) const;
131   void set_animation_frame_delay(const Index& index, uint32_t frame_delay);
132   int get_animation_loop_on_frame(const Index& index) const;
133   void set_animation_loop_on_frame(const Index& index, int loop_on_frame);
134   int get_animation_num_directions(const Index& index) const;
135   int get_animation_max_num_frames(const Index& index) const;
136 
137   // Direction.
138   bool direction_exists(const Index& index) const;
139   int add_direction(
140     const Index& index, const QRect &frame,
141     int num_frames = 1, int num_columns = 1);
142   int insert_direction(
143       const Index& index, const Solarus::SpriteAnimationDirectionData &data);
144   void delete_direction(const Index& index);
145   void move_direction(const Index& index, int new_direction_nb);
146 
147   Solarus::SpriteAnimationDirectionData
148     get_direction_data(const Index& index) const;
149   QRect get_direction_first_frame_rect(const Index& index) const;
150   QRect get_direction_all_frames_rect(const Index& index) const;
151   QList<QRect> get_direction_frames(const Index& index) const;
152   QPoint get_direction_position(const Index& index) const;
153   void set_direction_position(const Index& index, const QPoint& position);
154   QSize get_direction_size(const Index& index) const;
155   void set_direction_size(const Index& index, const QSize& size);
156   QPoint get_direction_origin(const Index& index) const;
157   void set_direction_origin(const Index& index, const QPoint& origin);
158   static QPoint get_direction_default_origin(const QSize& frame_size);
159   bool is_direction_multi_frame(const Index& index) const;
160   int get_direction_num_frames(const Index& index) const;
161   void set_direction_num_frames(const Index& index, int num_frames);
162   int get_direction_num_columns(const Index& index) const;
163   void set_direction_num_columns(const Index& index, int num_columns);
164 
165   // Images.
166   QImage get_animation_image(const Index& index) const;
167   QList<QPixmap> get_direction_all_frames(const Index& index) const;
168   QPixmap get_direction_first_frame(const Index& index) const;
169   QPixmap get_direction_frame(const Index& index, int frame) const;
170   QPixmap get_direction_icon(const Index& index) const;
171   QPixmap get_icon() const;
172 
173   // Selection.
174   QItemSelectionModel& get_selection_model();
175   bool is_selection_empty() const;
176   Index get_selected_index() const;
177   void set_selected_index(const Index& index);
178   void set_selected_animation(const Index& index);
179   void clear_selection();
180 
181 signals:
182 
183   void default_animation_changed(
184       const QString& old_default_animation, const QString& new_default_animation);
185   void animation_created(const Index& index);
186   void animation_deleted(const Index& index);
187   void animation_name_changed(const Index& old_index, const Index& new_index);
188   void animation_image_changed(const Index& index, const QString& src_image);
189   void animation_frame_delay_changed(const Index& index, uint32_t frame_delay);
190   void animation_loop_on_frame_changed(const Index& index, int loop_on_frame);
191 
192   void direction_added(const Index& index);
193   void direction_deleted(const Index& index);
194   void direction_moved(const Index& index, int new_direction_nb);
195   void direction_position_changed(const Index& index, const QPoint& position);
196   void direction_size_changed(const Index& index, const QSize& size);
197   void direction_origin_changed(const Index& index, const QPoint& origin);
198   void direction_num_frames_changed(const Index& index, int num_frames);
199   void direction_num_columns_changed(const Index& index, int num_columns);
200 
201 public slots:
202 
203   void save() const;
204 
205 private:
206 
207   /**
208    * @brief Data of a specific sprite animation direction.
209    */
210   struct DirectionModel {
211 
212   public:
213 
214     /**
215      * @brief Creates a sprite animation direction model.
216      */
DirectionModelDirectionModel217     DirectionModel(const QString& animation_name, int direction_nb) :
218       index(new Index(animation_name, direction_nb)) {
219     }
220 
221     /**
222      * @brief Clears the image cache of this direction.
223      */
set_image_dirtyDirectionModel224     void set_image_dirty() const {
225       frames.clear();
226       icon = QPixmap();
227     }
228 
229     std::shared_ptr<Index> index;   /**< Index of the direction. */
230     mutable QList<QPixmap> frames;  /**< All frames of the direction. */
231     mutable QPixmap icon;           /**< 32x32 icon of the direction. */
232   };
233 
234   /**
235    * @brief Data of a specific sprite animation.
236    */
237   struct AnimationModel {
238 
239   public:
240 
241     /**
242      * @brief Creates a sprite animation model.
243      * @param name Name of the animation to represent.
244      */
AnimationModelAnimationModel245     AnimationModel(const QString& animation_name) :
246       index(new Index(animation_name)) {
247     }
248 
249     /**
250      * @brief Returns the name of this animation.
251      * @return The animation name.
252      */
get_animation_nameAnimationModel253     QString get_animation_name() const {
254       return index->animation_name;
255     }
256 
257     /**
258      * @brief Changes the name of this animation.
259      * @param animation_name The animation name.
260      */
set_animation_nameAnimationModel261     void set_animation_name(const QString& animation_name) {
262       index->animation_name = animation_name;
263       for (auto& direction: directions) {
264         direction.index->animation_name = animation_name;
265       }
266     }
267 
268     /**
269      * @brief Clears the image cache of this animation.
270      */
set_image_dirtyAnimationModel271     void set_image_dirty() const {
272       image = QImage();
273       for (const auto& direction: directions) {
274         direction.set_image_dirty();
275       }
276     }
277 
278     std::shared_ptr<Index> index;     /**< Index of the animation. */
279     QList<DirectionModel> directions; /**< Directions of the animation. */
280     mutable QImage image;             /**< Image of the animation. */
281   };
282 
283   void build_index_map();
284 
285   void set_animation_image_dirty(const Index& index);
286   void set_direction_image_dirty(const Index& index);
287 
288   const Solarus::SpriteAnimationData& get_animation(const Index& index) const;
289   Solarus::SpriteAnimationData& get_animation(const Index& index);
290 
291   const Solarus::SpriteAnimationDirectionData& get_direction(const Index& index) const;
292   Solarus::SpriteAnimationDirectionData& get_direction(const Index& index);
293 
294   const Quest& quest;             /**< The quest the sprite belongs to. */
295   const QString sprite_id;        /**< Id of the sprite. */
296   Solarus::SpriteData sprite;     /**< Sprite data wrapped by this model. */
297   QString tileset_id;             /**< Tileset id used for animations images. */
298 
299   std::map<QString, int, NaturalComparator>
300       names_to_indexes;           /**< Index in the list of each animation.
301                                    * The order is determined here. */
302 
303   QList<AnimationModel>
304       animations;                 /**< All animations. */
305 
306   QItemSelectionModel
307       selection_model;            /**< Animations currently selected. */
308 };
309 
310 }
311 
312 #endif
313