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