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