ovcc-tile-set.vala 8.44 KB
Newer Older
Colomban Wendling's avatar
Colomban Wendling committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
/* 
 * 
 * Copyright (C) 2009-2011 Colomban Wendling <ban@herbesfolles.org>
 *                         Jonathan Michalon <studios.chalmion@no-log.org>
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 * 
 */

namespace OVCC
{
  public errordomain TileSetError
  {
    MISSING_FIRST,
    MISSING_TILE,
    FAILED
  }
  
30
  class TileSetEntry
Colomban Wendling's avatar
Colomban Wendling committed
31
  {
32 33
    public Tile tile;   /* the tile */
    public uint count;  /* number of occurrences of this tile */
Colomban Wendling's avatar
Colomban Wendling committed
34 35 36 37 38 39 40 41 42
    
    public TileSetEntry (Tile tile,
                         uint count)
    {
      this.tile = tile;
      this.count = count;
    }
  }
  
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102
  /* FIXME: update the doc */
  /**
   * This module manages Tile Sets. Tile Sets are list of tiles and the number of
   * them in the set.
   * Tile Sets works together with tiles definitions to provide a set of tiles.
   * 
   * Tile Sets are represented by #OVCCTileSet objects.
   * 
   * A Tile Set is created with ovcc_tileset_new() and freed using
   * ovcc_tileset_unref(). They can be loaded from XML definitions with
   * ovcc_tileset_load();
   * Adding a tile to a Tile Set is done using ovcc_tileset_add(),
   * removing one is done using ovcc_tileset_remove(), and removing all tiles from
   * a set is done using ovcc_tileset_clear().
   * 
   * =Simple example showing the load of a Tile Set from an URI=
   * 
   * {{{
   * #include <glib.h>
   * #include <gio/gio.h>
   * #include "tileset.h"
   * 
   * OVCCTileSet *
   * load_our_tileset (const gchar  *uri,
   *                   OVCCTilesDef *available_tiles)
   * {
   *   OVCCTileSet *set;
   *   GFile       *file;
   *   GError      *err = NULL;
   *   
   *   file = g_file_new_for_uri (uri);
   *   set = ovcc_tileset_new ();
   *   if (! ovcc_tileset_load (set, available_tiles, file, &err)) {
   *     g_critical ("Failed to load Tile Set: %s", err->message);
   *     g_error_free (err);
   *     // Return NULL if the tile set cannot be loaded
   *     ovcc_tileset_unref (set), set = NULL;
   *   }
   *   g_object_unref (file);
   *   
   *   return set;
   * }
   * }}}
   * 
   * =Example of a tileset XML file=
   * 
   * {{{
   * <?xml version="1.0" encoding="utf8"?>
   * <!DOCTYPE tileset SYSTEM "tileset.dtd">
   * <tileset>
   *   <!-- The first tile is a #1 -->
   *   <first id="t1" />
   *   
   *   <!-- Tile #1 appears 12 times in the set -->
   *   <tile id="t1" count="12" />
   *   <!-- Tile #2 appreas 2 times in the set -->
   *   <tile id="t2" count="2" />
   * </tileset>
   * }}}
   */
Colomban Wendling's avatar
Colomban Wendling committed
103 104
  public class TileSet : Object
  {
105 106
    private HashTable<uint,TileSetEntry>  _tiles = new HashTable<uint,TileSetEntry> (null, null);
    private List<TileSetEntry>            _tiles_list = new List<TileSetEntry> ();
Colomban Wendling's avatar
Colomban Wendling committed
107 108 109 110 111 112 113 114 115 116 117 118 119
    
    public  string  name { get; set; }
    public  Tile    first { get; set; }
    public  uint    size { get; private set; }
    
    public TileSet ()
    {
      
    }
    
    public void clear ()
    {
      this._tiles.remove_all ();
120
      this._tiles_list = new List<TileSetEntry> ();
Colomban Wendling's avatar
Colomban Wendling committed
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
      this.size = 0;
    }
    
    public bool has_tile (TileID id)
    {
      return this._tiles.lookup (id) != null;
    }
    
    public bool is_empty ()
    {
      return 0 == this.size;
    }
    
    public void add (Tile tile,
                     uint count)
    {
137 138 139
      var e = new TileSetEntry (tile, count);
      this._tiles.insert (tile.id, e);
      this._tiles_list.prepend (e);
Colomban Wendling's avatar
Colomban Wendling committed
140 141 142 143 144
      this.size ++;
    }
    
    public bool remove (TileID id)
    {
145 146 147 148
      var entry = this._tiles.lookup (id);
      if (entry != null) {
        this._tiles.remove (id);
        this._tiles_list.remove (entry);
Colomban Wendling's avatar
Colomban Wendling committed
149 150 151 152 153 154 155 156 157 158
        this.size --;
        return true;
      }
      return false;
    }
    
    public delegate bool  TileSetForeachFunc (TileSet ts, Tile t, uint count);
    
    public void @foreach (TileSetForeachFunc f)
    {
159 160 161
      foreach (var entry in this._tiles_list) {
        if (! f (this, entry.tile, entry.count)) {
          break;
Colomban Wendling's avatar
Colomban Wendling committed
162
        }
163
      }
Colomban Wendling's avatar
Colomban Wendling committed
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
    }
    
    /* XML loading */
    
    private bool xml_read_tile (Xml.Node *node,
                                TilesDef  tiles)
      throws Error
    {
      TileID  id = 0;
      uint    count = 0;
      
      /* read attributes */
      for (var attr = node->properties; attr != null; attr = attr->next) {
        if (Xml.ElementType.ATTRIBUTE_NODE == attr->type) {
          switch (attr->name) {
            case "id":
180
              id = TileID.from_string (attr->children->content.strip ());
Colomban Wendling's avatar
Colomban Wendling committed
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202
              break;
            
            case "count":
              count = Utils.string_to_uint (attr->children->content.strip ());
              if (count <= 0) {
                debug ("What a stupid tile count: %u (for tile id #%u)", count, id);
              }
              break;
          }
        }
      }
      
      /* if this tile is the first one, don't load the first again.
       * this supposes that a tile ID appears only once in a tileset. */
      if (count > 0 && this.first.id == id) {
        count --;
      }
      if (count > 0) {
        Tile tile;
        
        tile = tiles.get_tile (id);
        if (tile == null) {
203
          throw new TileSetError.MISSING_TILE ("Tile ID #%u not found in the tiles definitions", id);
Colomban Wendling's avatar
Colomban Wendling committed
204 205 206 207 208 209 210
        } else {
          this.add (tile, count);
        }
      }
      
      return true;
    }
211 212
    
    private bool xml_read_tiles (Xml.Node *node,
Colomban Wendling's avatar
Colomban Wendling committed
213 214 215 216 217 218
                                 TilesDef  tiles)
      throws Error
    {
      for (var attr = node->properties; attr != null; attr = attr->next) {
        if (Xml.ElementType.ATTRIBUTE_NODE == attr->type) {
          switch (attr->name) {
219 220 221 222 223 224 225 226
            case "first":
              var id = TileID.from_string (attr->children->content.strip ());
              var tile = tiles.get_tile (id);
              if (tile == null) {
                throw new TileSetError.MISSING_TILE ("First tile (ID #%u) not found in the tiles definitions", id);
              } else {
                this.first = tile;
              }
Colomban Wendling's avatar
Colomban Wendling committed
227 228 229 230 231
              break;
          }
        }
      }
      for (var child = node->children; child != null; child = child->next) {
232
        if (Xml.ElementType.ELEMENT_NODE == child->type) {
Colomban Wendling's avatar
Colomban Wendling committed
233 234
          switch (child->name) {
            case "tile":  xml_read_tile (child, tiles); break;
235
            case "name":  this.name = child->children->content.strip (); break;
Colomban Wendling's avatar
Colomban Wendling committed
236 237 238 239 240 241 242 243 244 245 246 247
          }
        }
      }
      
      return true;
    }

    private bool xml_read_tileset_doc (Xml.Node *node,
                                       TilesDef  tiles)
      throws Error
    {
      for (var child = node; child != null; child = child->next) {
248
        if (Xml.ElementType.ELEMENT_NODE == child->type) {
Colomban Wendling's avatar
Colomban Wendling committed
249 250 251 252 253 254 255 256 257 258 259 260 261
          switch (child->name) {
            case "tileset": xml_read_tiles (child, tiles); break;
          }
        }
      }
      
      if (this.first == null) {
        throw new TileSetError.MISSING_FIRST ("No first tile");
      }
      
      return true;
    }
    
262 263
    public bool load_from_string (TilesDef  tiles,
                                  string data)
Colomban Wendling's avatar
Colomban Wendling committed
264 265
      throws Error
    {
266 267
      File dtd = Utils.lookup_resource_path("tileset.dtd");
      var doc = Xml.Parser.read_memory (data, data.length, dtd.get_path(), null,
Colomban Wendling's avatar
Colomban Wendling committed
268 269 270
                                        Xml.ParserOption.DTDVALID |
                                        Xml.ParserOption.PEDANTIC);
      if (doc == null) {
271
        throw new MarkupError.INVALID_CONTENT ("""Data is not valid XML""");
Colomban Wendling's avatar
Colomban Wendling committed
272 273 274 275 276 277 278 279
      } else {
        if (! this.xml_read_tileset_doc (doc->get_root_element (), tiles)) {
          warning ("Document loading failed");
        }
      }
      
      return true;
    }
280 281 282 283 284 285 286 287 288 289 290 291

    public bool load (TilesDef  tiles,
                      string    filename)
      throws Error
    {
      uint8[] data;
      
      File file = Utils.lookup_resource_path(filename);
      debug ("""Loading tileset definitions from "%s"...""", file.get_path());
      file.load_contents (null, out data, null);
      return load_from_string (tiles, (string)data);
    }
Colomban Wendling's avatar
Colomban Wendling committed
292 293
  }
}