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