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