Added checkboxes for attributes defined in supertux/src/tile.hpp
[supertux.git] / tools / tilemanager / Application.cs
1 //  $Id$
2 using System;
3 using System.IO;
4 using System.Collections;
5 using Gtk;
6 using Gdk;
7 using Glade;
8
9 public class Application {
10     [Glade.Widget]
11     private Gtk.Window MainWindow;
12     [Glade.Widget]
13     private Gtk.CheckMenuItem useNewSyntax;
14     [Glade.Widget]
15     private Gtk.DrawingArea DrawingArea;
16     [Glade.Widget]
17         //Flags CheckButtons
18     private Gtk.CheckButton SolidCheckButton;
19     [Glade.Widget]
20     private Gtk.CheckButton UniSolidCheckButton;
21     [Glade.Widget]
22     private Gtk.CheckButton IceCheckButton;
23     [Glade.Widget]
24     private Gtk.CheckButton WaterCheckButton;
25     [Glade.Widget]
26     private Gtk.CheckButton HurtsCheckButton;
27     [Glade.Widget]
28     private Gtk.CheckButton FireCheckButton;
29     [Glade.Widget]
30     private Gtk.CheckButton BrickCheckButton;
31     [Glade.Widget]
32     private Gtk.CheckButton CoinCheckButton;
33     [Glade.Widget]
34     private Gtk.CheckButton FullBoxCheckButton;
35     [Glade.Widget]
36     private Gtk.CheckButton SlopeCheckButton;
37     [Glade.Widget]
38     private Gtk.CheckButton GoalCheckButton;
39     [Glade.Widget]
40     private Gtk.CheckButton DontUseCheckButton;
41     [Glade.Widget]
42     private Gtk.CheckButton HiddenCheckButton;
43     [Glade.Widget]
44
45     private Gtk.Entry DataEntry;
46     [Glade.Widget]
47     private Gtk.Entry AnimFpsEntry;
48     [Glade.Widget]
49     private Gtk.Entry IDEntry;
50     [Glade.Widget]
51     private Gtk.TreeView TileList;
52     [Glade.Widget]
53     private Gtk.Combo TileGroupComboBox;
54     [Glade.Widget]
55     private Gtk.MenuItem AddTileGroupMenu;
56
57     [Glade.Widget]
58     private Gtk.Dialog RemapDialog;
59     [Glade.Widget]
60     private Gtk.SpinButton RD_spinButton;
61
62     private string tilesetdir;
63     private string tilesetfile;
64     private TileSet tileset;
65     private TileGroup selectedgroup;
66
67     private Tile[] Tiles;
68     private bool[] SelectionArray;
69     private ArrayList Selection = new ArrayList();
70     private int TilesX;
71     private int TilesY;
72     private bool toggling;
73     private bool selecting;
74
75     private string currentimage;
76     private Gdk.Pixbuf pixbuf;
77
78     public static int Main(string[] args) {
79         Gtk.Application.Init();
80
81         Application app = new Application();
82
83         /* that's no proper commandlineparsing, but who'll notice... */
84         if(args.Length == 1)
85             app.LoadTileSet(args[0]);
86
87         Gtk.Application.Run();
88         return 0;
89     }
90
91     public Application() {
92         Glade.XML gxml = new Glade.XML(null, "tiler.glade", null, null);
93         gxml.Autoconnect(this);
94
95         if(MainWindow == null || DrawingArea == null || RemapDialog == null)
96             throw new Exception("some widgets not found");
97
98         DrawingArea.AddEvents((int) Gdk.EventMask.ButtonPressMask);
99         DrawingArea.AddEvents((int) Gdk.EventMask.ButtonReleaseMask);
100         DrawingArea.AddEvents((int) Gdk.EventMask.ButtonMotionMask);
101
102         MainWindow.Show();
103     }
104
105     protected void OnOpen(object o, EventArgs e) {
106         FileChooserDialog fileChooser = new FileChooserDialog("Select TileSet", MainWindow, FileChooserAction.Open, new object[] {});
107
108         fileChooser.AddButton(Gtk.Stock.Cancel, Gtk.ResponseType.Cancel);
109         fileChooser.AddButton(Gtk.Stock.Ok, Gtk.ResponseType.Ok);
110         fileChooser.DefaultResponse = Gtk.ResponseType.Ok;
111         Gtk.FileFilter filter;
112         filter = new Gtk.FileFilter();
113         filter.Name = "Supertux tilesets";
114         filter.AddPattern("*.strf");
115         filter.AddPattern("*.stgt");
116         fileChooser.AddFilter( filter );
117         filter = new Gtk.FileFilter();
118         filter.Name = "Supertux 0.1.x tilesets";
119         filter.AddPattern("*.stgt");
120         fileChooser.AddFilter( filter );
121         filter = new Gtk.FileFilter();
122         filter.Name = "Supertux 0.3.x tilesets";
123         filter.AddPattern("*.strf");
124         fileChooser.AddFilter( filter );
125         Gtk.FileFilter all = new Gtk.FileFilter();
126         all.Name = "All Files";
127         all.AddPattern("*");
128         fileChooser.AddFilter( all );
129         int result = fileChooser.Run();
130         fileChooser.Hide();
131
132         if(result != (int) ResponseType.Ok)
133                 return;
134
135         LoadTileSet(fileChooser.Filename);
136     }
137
138     private void LoadTileSet(string file) {
139         try {
140             tileset = new TileSet();
141             tileset.Parse(file);
142             tilesetfile = file;
143             tilesetdir = new FileInfo(file).Directory.ToString();
144         } catch(Exception exception) {
145             ShowException(exception);
146         }
147
148         Selection.Clear();
149         SelectionChanged();
150         FillTileGroupComboBox();
151         FillTileList();
152
153         useNewSyntax.Active = tileset.IsNew;
154         if (tileset.IsNew)
155                 Console.WriteLine("Warning: new syntax of 0.3.x files \"More tiles in one image\" isn't currently supported for WRITING");
156
157     }
158
159     protected void OnImportImage(object o, EventArgs e) {
160         FileChooserDialog fileChooser = new FileChooserDialog("Select ImageFile", MainWindow, FileChooserAction.Open, new object[] {});
161
162         fileChooser.AddButton(Gtk.Stock.Cancel, Gtk.ResponseType.Cancel);
163         fileChooser.AddButton(Gtk.Stock.Ok, Gtk.ResponseType.Ok);
164         fileChooser.DefaultResponse = Gtk.ResponseType.Ok;
165         Gtk.FileFilter all = new Gtk.FileFilter();
166         all.Name = "All Files";
167         all.AddPattern("*");
168         fileChooser.AddFilter( all );
169         int result = fileChooser.Run();
170         fileChooser.Hide();
171
172         if(result != (int) ResponseType.Ok)
173                 return;
174
175         string file = fileChooser.Filename;
176         string trim = tilesetdir + "/";
177
178         if (!file.StartsWith(trim)){
179                 Console.WriteLine(
180                         "Imported file must be located inside tileset directory");
181                 return;
182         }
183
184         ChangeImage(file.TrimStart(trim.ToCharArray()));
185
186         int startid = tileset.Tiles.Count;
187         for(int y = 0; y < TilesY; ++y) {
188             for(int x = 0; x < TilesX; ++x) {
189                 int i = y*TilesX+x;
190                 Tile tile = new Tile();
191                 tile.ID = startid + i;
192                 ImageRegion region = new ImageRegion();
193                 region.ImageFile = currentimage;
194                 region.Region = new System.Drawing.Rectangle(x*TileSet.TILE_WIDTH, y*TileSet.TILE_HEIGHT, TileSet.TILE_WIDTH, TileSet.TILE_HEIGHT);
195                 tile.Images.Add(region);
196                 if(Tiles[i] != null) {
197                     Console.WriteLine(
198                             "Warning Tile in this region already existed...");
199                 }
200                 Tiles[i] = tile;
201                 tileset.Tiles.Add(tile);
202             }
203         }
204
205         FillTileList();
206     }
207
208     private void ChangeImage(string file) {
209         if(file == "") {
210             currentimage = "";
211             pixbuf = null;
212             return;
213         }
214         try {
215             pixbuf = new Pixbuf(tilesetdir + "/" + file);
216             if(pixbuf.Width % TileSet.TILE_WIDTH != 0 || pixbuf.Height % TileSet.TILE_HEIGHT != 0)
217                 Console.WriteLine("Warning: Image Width or Height is not a multiple of 32");
218         } catch(Exception e) {
219             ShowException(e);
220             return;
221         }
222         currentimage = new FileInfo(file).Name;
223         TilesX = pixbuf.Width / TileSet.TILE_WIDTH;
224         TilesY = pixbuf.Height / TileSet.TILE_HEIGHT;
225         SelectionArray = new bool[TilesX * TilesY];
226         Tiles = new Tile[TilesX * TilesY];
227
228         // search tileset for tiles with matching image
229         foreach(Tile tile in tileset.Tiles) {
230             if(tile == null)
231                 continue;
232             if(tile.Images.Count == 0)
233                 continue;
234             ImageRegion region = (ImageRegion) tile.Images[0];
235             if(region.ImageFile == currentimage) {
236                 int px = region.Region.X / TileSet.TILE_WIDTH;
237                 int py = region.Region.Y / TileSet.TILE_HEIGHT;
238                 int i = py*TilesX+px;
239                 if(i < 0 || i >= Tiles.Length) {
240                     Console.WriteLine("Invalid Imageregion at tile " +
241                             tile.ID);
242                     continue;
243                 }
244                 if(Tiles[i] != null) {
245                     Console.WriteLine("Multiple tiles for region " +
246                             px*TileSet.TILE_WIDTH + " , " + py*TileSet.TILE_HEIGHT);
247                     continue;
248                 }
249                 Tiles[i] = tile;
250             }
251         }
252
253         /*   DrawingArea.Allocation
254             = new Gdk.Rectangle(0, 0, pixbuf.Width, pixbuf.Height);*/
255         DrawingArea.WidthRequest = pixbuf.Width;
256         DrawingArea.HeightRequest = pixbuf.Height;
257         DrawingArea.QueueResize();
258     }
259
260     protected void OnSave(object o, EventArgs e) {
261         if (tileset.IsNew && useNewSyntax.Active) {
262                 MessageDialog dialog = new MessageDialog(MainWindow, DialogFlags.Modal | DialogFlags.DestroyWithParent, MessageType.Error, ButtonsType.Ok,
263                                                                 "Sorry, the file you are editing is too new and 0.3.x syntax is not supported yet.");
264                 dialog.Run();
265                 dialog.Destroy();
266         } else {
267                 tileset.Write(tilesetfile);
268         }
269     }
270
271     protected void OnQuit(object o, EventArgs e) {
272         Gtk.Application.Quit();
273     }
274
275     protected void OnDeleteQuit(object o, DeleteEventArgs e) {
276         Gtk.Application.Quit();
277     }
278
279     protected void OnAbout(object o, EventArgs e) {
280 //              string[] authors = new string[]{
281 //                      "<autors?>",
282 //              };
283
284                 Gtk.AboutDialog dialog = new Gtk.AboutDialog();
285 //              dialog.Icon = <icon>;
286                 dialog.ProgramName = "SuperTux Tiler";
287                 dialog.Version = "0.0.3";
288                 dialog.Comments = "A tileset editor for SuperTux 0.1.x";
289 //              dialog.Authors = authors;
290                 dialog.Copyright = "Copyright (c) 2006 SuperTux Devel Team";
291                 dialog.License =
292                         "This program is free software; you can redistribute it and/or modify" + Environment.NewLine +
293                         "it under the terms of the GNU General Public License as published by" + Environment.NewLine +
294                         "the Free Software Foundation; either version 2 of the License, or" + Environment.NewLine +
295                         "(at your option) any later version." + Environment.NewLine +
296                         Environment.NewLine +
297                         "This program is distributed in the hope that it will be useful," + Environment.NewLine +
298                         "but WITHOUT ANY WARRANTY; without even the implied warranty of" + Environment.NewLine +
299                         "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the" + Environment.NewLine +
300                         "GNU General Public License for more details." + Environment.NewLine +
301                         Environment.NewLine +
302                         "You should have received a copy of the GNU General Public License" + Environment.NewLine +
303                         "along with this program; if not, write to the Free Software Foundation, Inc.," + Environment.NewLine +
304                         "59 Temple Place, Suite 330, Boston, MA 02111-1307 USA" + Environment.NewLine;
305                 dialog.Website = "http://supertux.lethargik.org/";
306                 dialog.WebsiteLabel = "SuperTux on the Web";
307                 dialog.Run();
308                 dialog.Destroy();
309     }
310
311     protected void OnRemapTiles(object o, EventArgs e) {
312         if(Tiles == null)
313                 return;
314         RemapDialog.Show();
315     }
316
317     protected void OnRemapDialogCancel(object o, EventArgs e) {
318         RemapDialog.Hide();
319     }
320
321     protected void OnRemapDialogApply(object o, EventArgs e) {
322         RemapDialog.Hide();
323         try {
324
325             // remap tiles
326             int id;
327             try {
328                 id = RD_spinButton.ValueAsInt;
329             } catch(Exception exception) {
330                 ShowException(exception);
331                 return;
332             }
333             RemapTiles(id);
334         } finally {
335             RD_spinButton.Value = 1;
336         }
337     }
338
339     protected void OnCreateTileGroup(object o, EventArgs e) {
340     }
341
342     protected void OnRenameTileGroup(object o, EventArgs e) {
343     }
344
345     protected void RemapTiles(int startID) {
346         if(Tiles == null)
347                 return;
348
349         // remap tiles
350         int id = startID;
351         foreach(Tile tile in Selection) {
352                 if(tile.ID == -1)
353                         continue;
354
355                 int oldid = tile.ID;
356                 tile.ID = id++;
357                 // remap in all tilegroups...
358                 foreach(TileGroup tilegroup in tileset.TileGroups) {
359                         int idx = tilegroup.Tiles.IndexOf(oldid);
360                         if(idx >= 0) {
361                                 tilegroup.Tiles[idx] = tile.ID;
362                         }
363                 }
364         }
365         FillTileList();
366         SelectionChanged();
367     }
368
369     protected void OnDrawingAreaExpose(object o, ExposeEventArgs e) {
370         if(pixbuf == null)
371             return;
372
373         Drawable drawable = e.Event.Window;
374         Gdk.GC gc = new Gdk.GC(drawable);
375         drawable.DrawPixbuf(gc, pixbuf, 0, 0, 0, 0,
376                 pixbuf.Width, pixbuf.Height, RgbDither.None, 0, 0);
377
378         gc.RgbFgColor = new Color(0xff, 0, 0);
379         foreach(Tile tile in Selection) {
380             System.Drawing.Rectangle rect
381                 = ((ImageRegion) tile.Images[0]).Region;
382             drawable.DrawRectangle(gc, false, rect.X, rect.Y, rect.Width,
383                     rect.Height);
384         }
385
386         e.RetVal = false;
387     }
388
389     protected void OnDrawingAreaButtonPress(object o, ButtonPressEventArgs e) {
390         if(SelectionArray == null)
391             return;
392
393         selecting = true;
394
395         for(int i = 0; i < SelectionArray.Length; ++i)
396             SelectionArray[i] = false;
397         select((int) e.Event.X, (int) e.Event.Y);
398     }
399
400     private void select(int x, int y) {
401         int tile = y/TileSet.TILE_HEIGHT * TilesX + x/TileSet.TILE_WIDTH;
402         if(tile < 0 || tile >= SelectionArray.Length)
403             return;
404
405         SelectionArray[tile] = true;
406         SelectionArrayChanged();
407     }
408
409     protected void OnDrawingAreaMotionNotify(object i, MotionNotifyEventArgs e) {
410         if(!selecting)
411             return;
412         select((int) e.Event.X, (int) e.Event.Y);
413     }
414
415     protected void OnDrawingAreaButtonRelease(object o, ButtonPressEventArgs e) {
416         selecting = false;
417     }
418
419     protected void OnCheckButtonToggled(object sender, EventArgs e) {
420         if(toggling)
421             return;
422         foreach(Tile tile in Selection) {
423             if(sender == SolidCheckButton)
424                 tile.SetAttribute(Attribute.SOLID, SolidCheckButton.Active);
425             if(sender == UniSolidCheckButton)
426                 tile.SetAttribute(Attribute.UNISOLID, UniSolidCheckButton.Active);
427             if(sender == IceCheckButton)
428                 tile.SetAttribute(Attribute.ICE, IceCheckButton.Active);
429             if(sender == WaterCheckButton)
430                 tile.SetAttribute(Attribute.WATER, WaterCheckButton.Active);
431             if(sender == HurtsCheckButton)
432                 tile.SetAttribute(Attribute.HURTS, HurtsCheckButton.Active);
433             if(sender == FireCheckButton)
434                 tile.SetAttribute(Attribute.FIRE, FireCheckButton.Active);
435             if(sender == BrickCheckButton)
436                 tile.SetAttribute(Attribute.BRICK, BrickCheckButton.Active);
437             if(sender == CoinCheckButton)
438                 tile.SetAttribute(Attribute.COIN, CoinCheckButton.Active);
439             if(sender == FullBoxCheckButton)
440                 tile.SetAttribute(Attribute.FULLBOX, FullBoxCheckButton.Active);
441             if(sender == SlopeCheckButton)
442                 tile.SetAttribute(Attribute.SLOPE, SlopeCheckButton.Active);
443             if(sender == GoalCheckButton)
444                 tile.SetAttribute(Attribute.GOAL, GoalCheckButton.Active);
445             if(sender == HiddenCheckButton)
446                 tile.Hidden = HiddenCheckButton.Active;
447             if(sender == DontUseCheckButton)
448                 tile.ID = DontUseCheckButton.Active ? -1 : 0;
449         }
450     }
451
452     protected void OnEntryChanged(object sender, EventArgs e) {
453         if(toggling)
454             return;
455         foreach(Tile tile in Selection) {
456             try {
457                 if(sender == IDEntry)
458                     tile.ID = Int32.Parse(IDEntry.Text);
459                 if(sender == DataEntry)
460                     tile.Data = Int32.Parse(DataEntry.Text);
461                 if(sender == AnimFpsEntry)
462                     tile.AnimFps = Single.Parse(AnimFpsEntry.Text);
463             } catch(Exception) {
464                 // ignore parse errors for now...
465             }
466         }
467     }
468
469     private void SelectionArrayChanged() {
470         Selection.Clear();
471         for(int i = 0; i < SelectionArray.Length; ++i) {
472             if(!SelectionArray[i])
473                 continue;
474
475             if(Tiles[i] == null) {
476                 Console.WriteLine("Tile doesn't exist yet");
477                 // TODO ask user to create new tile...
478                 continue;
479             }
480             Selection.Add(Tiles[i]);
481         }
482
483         SelectionChanged();
484     }
485
486     private void SelectionChanged() {
487         bool first = true;
488         toggling = true;
489         string nextimage = "";
490         foreach(Tile tile in Selection) {
491             if(first) {
492                 SolidCheckButton.Active = tile.HasAttribute(Attribute.SOLID);
493                 UniSolidCheckButton.Active = tile.HasAttribute(Attribute.UNISOLID);
494                 IceCheckButton.Active = tile.HasAttribute(Attribute.ICE);
495                 WaterCheckButton.Active = tile.HasAttribute(Attribute.WATER);
496                 HurtsCheckButton.Active = tile.HasAttribute(Attribute.HURTS);
497                 FireCheckButton.Active = tile.HasAttribute(Attribute.FIRE);
498                 BrickCheckButton.Active = tile.HasAttribute(Attribute.BRICK);
499                 CoinCheckButton.Active = tile.HasAttribute(Attribute.COIN);
500                 FullBoxCheckButton.Active = tile.HasAttribute(Attribute.FULLBOX);
501                 SlopeCheckButton.Active = tile.HasAttribute(Attribute.SLOPE);
502                 GoalCheckButton.Active = tile.HasAttribute(Attribute.GOAL);
503                 HiddenCheckButton.Active = tile.Hidden;
504                 DontUseCheckButton.Active = tile.ID == -1;
505                 DataEntry.Text = tile.Data.ToString();
506                 AnimFpsEntry.Text = tile.AnimFps.ToString();
507                 IDEntry.Text = tile.ID.ToString();
508                 IDEntry.IsEditable = true;
509                 first = false;
510
511                 if(tile.Images.Count > 0) {
512                     nextimage = ((ImageRegion) tile.Images[0]).ImageFile;
513                 }
514             } else {
515                 IDEntry.Text += "," + tile.ID.ToString();
516                 IDEntry.IsEditable = false;
517                 if(tile.Images.Count > 0
518                         && ((ImageRegion) tile.Images[0]).ImageFile != nextimage) {
519                     nextimage = "";
520                     pixbuf = null;
521                 }
522             }
523         }
524         if(nextimage != currentimage)
525             ChangeImage(nextimage);
526         toggling = false;
527         DrawingArea.QueueDraw();
528     }
529
530     private void FillTileList() {
531         TileList.HeadersVisible = true;
532         if(TileList.Columns.Length == 0)
533             TileList.AppendColumn("Tile", new CellRendererText(), "text", 0);
534
535         ListStore store = new ListStore(typeof(string));
536
537         if(selectedgroup == null) {
538             foreach(Tile tile in tileset.Tiles) {
539                 if(tile == null)
540                     continue;
541                 store.AppendValues(new object[] { tile.ID.ToString() });
542             }
543         } else {
544             foreach(int id in selectedgroup.Tiles) {
545                 Tile tile;
546                 if (id >= tileset.Tiles.Count){
547                         Console.WriteLine("Tile ID is above Tiles.Count: " + id.ToString());
548                         continue;
549                 } else {
550                         tile = (Tile) tileset.Tiles[id];
551                 }
552                 if(tile == null) {
553                     Console.WriteLine("tilegroup contains deleted tile");
554                     continue;
555                 }
556                 store.AppendValues(new object[] { id.ToString() });
557             }
558         }
559
560         TileList.Model = store;
561         TileList.Selection.Mode = SelectionMode.Multiple;
562     }
563
564     private void FillTileGroupComboBox() {
565         string[] groups = new string[tileset.TileGroups.Count+1];
566         groups[0] = "All";
567
568         //Submenu submenu = new Submenu();
569         for(int i = 0; i < tileset.TileGroups.Count; ++i) {
570             String tilegroup = ((TileGroup) tileset.TileGroups[i]).Name;
571             groups[i+1] = tilegroup;
572             //submenu.Add(new MenuItem(tilegroup));
573         }
574         TileGroupComboBox.PopdownStrings = groups;
575         TileGroupComboBox.Entry.IsEditable = false;
576
577         //AddTileGroupMenu.Submenu = submenu;
578     }
579
580     protected void OnTileGroupComboBoxEntryActivated(object o, EventArgs args) {
581         if(TileGroupComboBox.Entry.Text == "All") {
582             selectedgroup = null;
583         } else {
584             foreach(TileGroup tilegroup in tileset.TileGroups) {
585                 if(tilegroup.Name == TileGroupComboBox.Entry.Text) {
586                     selectedgroup = tilegroup;
587                     break;
588                 }
589             }
590         }
591         FillTileList();
592     }
593
594     protected void OnTileListCursorChanged(object sender, EventArgs e) {
595         TreeModel model;
596         TreePath[] selectpaths =
597             TileList.Selection.GetSelectedRows(out model);
598
599         Selection.Clear();
600         foreach(TreePath path in selectpaths) {
601             TreeIter iter;
602             model.GetIter(out iter, path);
603             int id = Int32.Parse(model.GetValue(iter, 0).ToString());
604             Selection.Add(tileset.Tiles[id]);
605         }
606         SelectionChanged();
607     }
608
609     private void ShowException(Exception e) {
610         MessageDialog dialog = new MessageDialog(MainWindow,
611                 DialogFlags.Modal | DialogFlags.DestroyWithParent,
612                 MessageType.Error, ButtonsType.Ok,
613                 e.Message);
614         dialog.Run();
615         dialog.Destroy();
616     }
617 }