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