TODO update, fix tux doesn't stop at igloo anymore
[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.Entry DataEntry;
28     [Glade.Widget]
29     private Gtk.Entry AnimFpsEntry;
30     [Glade.Widget]                 
31     private Gtk.Entry IDEntry;
32     [Glade.Widget]
33     private Gnome.AppBar AppBar;
34     [Glade.Widget]
35     private Gtk.VBox MainLayout;
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     private string tilesetdir;
44     private string tilesetfile;
45     private TileSet tileset;
46     private TileGroup selectedgroup;
47
48     private Tile[] Tiles;
49     private bool[] SelectionArray;
50     private ArrayList Selection = new ArrayList();
51     private int TilesX;
52     private int TilesY;
53     private bool toggling;
54     private bool selecting;
55
56     private string currentimage;
57     private Gdk.Pixbuf pixbuf;
58     
59     public static int Main(string[] args) {
60         Program kit = new Program("tiler", "0.0.1", Modules.UI, args);
61
62         Application app = new Application();
63
64         /* that's no proper commandlineparsing, but who'll notice... */
65         if(args.Length == 1)
66             app.LoadTileSet(args[0]);
67
68         kit.Run();
69         return 0;
70     }
71
72     public Application() {
73         Glade.XML gxml = new Glade.XML(null, "tiler.glade", null, null);
74         gxml.Autoconnect(this);
75
76         if(MainWindow == null || DrawingArea == null || AppBar == null)
77             throw new Exception("soem widgets not found");
78
79         DrawingArea.AddEvents((int) Gdk.EventMask.ButtonPressMask);
80         DrawingArea.AddEvents((int) Gdk.EventMask.ButtonReleaseMask);
81         DrawingArea.AddEvents((int) Gdk.EventMask.ButtonMotionMask);
82
83         // libglade missed interactivity property :-/
84         MainLayout.Remove(AppBar);
85         AppBar = new AppBar(true, true, PreferencesType.Always);
86         AppBar.UserResponse += new EventHandler(OnAppBarUserResponse);
87         MainLayout.PackStart(AppBar, false, false, 0);
88         AppBar.Show();
89
90         TileGroupComboBox.Entry.Activated 
91             += new EventHandler (OnTileGroupComboBoxEntryActivated);
92         
93         MainWindow.Show();
94     }
95
96     private void OnOpen(object o, EventArgs e) {
97         FileSelection selection = new FileSelection("Select TileSet");
98         selection.OkButton.Clicked += new EventHandler(OnSelectTileSetOk);
99         selection.CancelButton.Clicked += new EventHandler(OnSelectImageCancel);
100         selection.Show();
101     }
102
103     private void OnSelectTileSetOk(object o, EventArgs e) {
104         FileSelection selection = ((FileSelection.FSButton) o).FileSelection;
105         string file = selection.Filename;
106         selection.Destroy();
107
108         LoadTileSet(file);
109     }
110
111     private void LoadTileSet(string file) {
112         try {
113             tileset = new TileSet();
114             tileset.Parse(file);
115             tilesetfile = file;
116             tilesetdir = new FileInfo(file).Directory.ToString();
117         } catch(Exception exception) {
118             ShowException(exception);
119         }
120
121         Selection.Clear();
122         SelectionChanged();
123         FillTileGroupComboBox();
124         FillTileList();
125     }
126
127     private void OnImportImage(object o, EventArgs e) {
128         FileSelection selection = new FileSelection("Select ImageFile");
129         selection.OkButton.Clicked += new EventHandler(OnSelectImageOk);
130         selection.CancelButton.Clicked += new EventHandler(OnSelectImageCancel);
131         selection.Show();                           
132     }
133
134     private void OnSelectImageCancel(object o, EventArgs args) {
135         FileSelection selection = ((FileSelection.FSButton) o).FileSelection;
136         selection.Destroy();
137     }
138     
139     private void OnSelectImageOk(object o, EventArgs args) {
140         FileSelection selection = ((FileSelection.FSButton) o).FileSelection;
141         string file = selection.Filename;
142         selection.Destroy();
143
144         ChangeImage(new FileInfo(file).Name);
145         
146         int startid = tileset.Tiles.Count;
147         for(int y = 0; y < TilesY; ++y) {
148             for(int x = 0; x < TilesX; ++x) {
149                 int i = y*TilesX+x;
150                 Tile tile = new Tile();                                   
151                 tile.ID = startid + i;
152                 ImageRegion region = new ImageRegion();
153                 region.ImageFile = currentimage;
154                 region.Region = new System.Drawing.Rectangle(x*32, y*32, 32, 32);
155                 tile.Images.Add(region);
156                 if(Tiles[i] != null) {
157                     Console.WriteLine(
158                             "Warning Tile in this region already existed...");
159                 }
160                 Tiles[i] = tile;
161                 tileset.Tiles.Add(tile);
162             }
163         }
164
165         FillTileList();
166     }
167
168     private void ChangeImage(string file) {
169         if(file == "") {
170             currentimage = "";
171             pixbuf = null;
172             return;
173         }
174         try {
175             pixbuf = new Pixbuf(tilesetdir + "/" + file);
176             if(pixbuf.Width % 32 != 0 || pixbuf.Height % 32 != 0)
177                 throw new Exception(
178                         "Image Width or Height is not a multiple of 32");
179         } catch(Exception e) {
180             ShowException(e);
181             return;
182         }
183         currentimage = new FileInfo(file).Name;
184         TilesX = pixbuf.Width / 32;
185         TilesY = pixbuf.Height / 32;
186         SelectionArray = new bool[TilesX * TilesY];
187         Tiles = new Tile[TilesX * TilesY];
188         
189         // search tileset for tiles with matching image
190         foreach(Tile tile in tileset.Tiles) {
191             if(tile == null)
192                 continue;
193             if(tile.Images.Count == 0)
194                 continue;
195             ImageRegion region = (ImageRegion) tile.Images[0];
196             if(region.ImageFile == currentimage) {
197                 int px = region.Region.X / 32;
198                 int py = region.Region.Y / 32;
199                 int i = py*TilesX+px;
200                 if(i < 0 || i >= Tiles.Length) {
201                     Console.WriteLine("Invalid Imageregion at tile " +
202                             tile.ID);
203                     continue;
204                 }
205                 if(Tiles[i] != null) {
206                     Console.WriteLine("Multiple tiles for region " +
207                             px*32 + " , " + py*32);
208                     continue;
209                 }
210                 Tiles[i] = tile;
211             }
212         } 
213
214         /*   DrawingArea.Allocation 
215             = new Gdk.Rectangle(0, 0, pixbuf.Width, pixbuf.Height);*/
216         DrawingArea.WidthRequest = pixbuf.Width;
217         DrawingArea.HeightRequest = pixbuf.Height;
218         DrawingArea.QueueResize();
219     }
220
221     private void OnSave(object o, EventArgs e) {
222         tileset.Write(tilesetfile);
223     }
224
225     private void OnQuit(object o, EventArgs e) {
226         Gtk.Application.Quit();
227     }
228
229     private void OnAbout(object o, EventArgs e) {
230     }
231
232     private void OnRemapTiles(object o, EventArgs e) {
233         AppBar.SetPrompt("Start-ID:", true);
234     }
235
236     private void OnCreateTileGroup(object o, EventArgs e) {
237     }
238
239     private void OnRenameTileGroup(object o, EventArgs e) {
240     }
241
242     private void OnAppBarUserResponse(object o, EventArgs e) {
243         try {
244             if(AppBar.Response == null || AppBar.Response == "" 
245                     || Tiles == null)
246                 return;
247         
248             // remap tiles
249             int id;
250             try {
251                 id = Int32.Parse(AppBar.Response);
252             } catch(Exception exception) {
253                 ShowException(exception);
254                 return;
255             }
256             foreach(Tile tile in Selection) {
257                 if(tile.ID == -1)
258                     continue;
259                 
260                 int oldid = tile.ID;
261                 tile.ID = id++;
262                 // remap in all tilegroups...
263                 foreach(TileGroup tilegroup in tileset.TileGroups) {
264                     int idx = tilegroup.Tiles.IndexOf(oldid);
265                     if(idx >= 0) {
266                         tilegroup.Tiles[idx] = tile.ID;
267                     }
268                 }
269             }
270             FillTileList();
271             SelectionChanged();
272         } finally {
273             AppBar.ClearPrompt();
274         }
275     }
276
277     private void OnDrawingAreaExpose(object o, ExposeEventArgs e) {
278         if(pixbuf == null)
279             return;
280
281         Drawable drawable = e.Event.Window;
282         Gdk.GC gc = new Gdk.GC(drawable);
283         drawable.DrawPixbuf(gc, pixbuf, 0, 0, 0, 0,
284                 pixbuf.Width, pixbuf.Height, RgbDither.None, 0, 0);
285
286         gc.RgbFgColor = new Color(0xff, 0, 0);
287         foreach(Tile tile in Selection) {
288             System.Drawing.Rectangle rect 
289                 = ((ImageRegion) tile.Images[0]).Region;
290             drawable.DrawRectangle(gc, false, rect.X, rect.Y, rect.Width,
291                     rect.Height);
292         }
293
294         e.RetVal = false;
295     }
296
297     private void OnDrawingAreaButtonPress(object o, ButtonPressEventArgs e) {
298         if(SelectionArray == null)
299             return;
300
301         selecting = true;
302         
303         for(int i = 0; i < SelectionArray.Length; ++i)
304             SelectionArray[i] = false;
305         select((int) e.Event.X, (int) e.Event.Y);
306     }
307
308     private void select(int x, int y) {
309         int tile = y/32 * TilesX + x/32;
310         if(tile < 0 || tile >= SelectionArray.Length)
311             return;
312
313         SelectionArray[tile] = true;
314         SelectionArrayChanged();
315     }
316
317     private void OnDrawingAreaMotionNotify(object i, MotionNotifyEventArgs e) {
318         if(!selecting)
319             return;
320         select((int) e.Event.X, (int) e.Event.Y);
321     }
322
323     private void OnDrawingAreaButtonRelease(object o, ButtonPressEventArgs e) {
324         selecting = false;
325     }
326
327     private void OnCheckButtonToggled(object sender, EventArgs e) {
328         if(toggling)
329             return;
330         foreach(Tile tile in Selection) {
331             if(sender == SolidCheckButton)
332                 tile.Solid = SolidCheckButton.Active;
333             if(sender == UniSolidCheckButton)
334                 tile.UniSolid = UniSolidCheckButton.Active;
335             if(sender == IceCheckButton)
336                 tile.Ice = IceCheckButton.Active;
337             if(sender == WaterCheckButton)
338                 tile.Water = WaterCheckButton.Active;
339             if(sender == SlopeCheckButton)
340                 tile.Slope = SlopeCheckButton.Active;
341             if(sender == DontUseCheckButton)
342                 tile.ID = DontUseCheckButton.Active ? -1 : 0;
343         }
344     }
345
346     private void OnEntryChanged(object sender, EventArgs e) {
347         if(toggling)
348             return;
349         foreach(Tile tile in Selection) {
350             try {
351                 if(sender == IDEntry)
352                     tile.ID = Int32.Parse(IDEntry.Text);
353                 if(sender == DataEntry)
354                     tile.Data = Int32.Parse(DataEntry.Text);
355                 if(sender == AnimFpsEntry)
356                     tile.AnimFps = Single.Parse(AnimFpsEntry.Text);
357             } catch(Exception exception) {
358                 // ignore parse errors for now...
359             }
360         }
361     }
362
363     private void SelectionArrayChanged() {
364         Selection.Clear();
365         for(int i = 0; i < SelectionArray.Length; ++i) {
366             if(!SelectionArray[i])
367                 continue;
368             
369             if(Tiles[i] == null) {
370                 Console.WriteLine("Tile doesn't exist yet");
371                 // TODO ask user to create new tile...
372                 continue;
373             }
374             Selection.Add(Tiles[i]);
375         }
376
377         SelectionChanged();
378     }
379
380     private void SelectionChanged() {
381         bool first = true;
382         toggling = true;
383         string nextimage = "";
384         foreach(Tile tile in Selection) {
385             if(first) {
386                 SolidCheckButton.Active = tile.Solid;
387                 UniSolidCheckButton.Active = tile.UniSolid;
388                 IceCheckButton.Active = tile.Ice;
389                 WaterCheckButton.Active = tile.Water;
390                 SlopeCheckButton.Active = tile.Slope;
391                 DontUseCheckButton.Active = tile.ID == -1;
392                 DataEntry.Text = tile.Data.ToString();
393                 AnimFpsEntry.Text = tile.AnimFps.ToString();
394                 IDEntry.Text = tile.ID.ToString();
395                 IDEntry.Editable = true;
396                 first = false;
397
398                 if(tile.Images.Count > 0) {
399                     nextimage = ((ImageRegion) tile.Images[0]).ImageFile;
400                 }
401             } else {
402                 IDEntry.Text += "," + tile.ID.ToString();
403                 IDEntry.Editable = false;
404                 if(tile.Images.Count > 0 
405                         && ((ImageRegion) tile.Images[0]).ImageFile != nextimage) {
406                     nextimage = "";
407                     pixbuf = null;       
408                 }
409             }
410         }
411         if(nextimage != currentimage)
412             ChangeImage(nextimage);
413         toggling = false;
414         DrawingArea.QueueDraw();
415     }
416
417     private void FillTileList() {
418         TileList.HeadersVisible = true;
419         if(TileList.Columns.Length == 0)
420             TileList.AppendColumn("Tile", new CellRendererText(), "text", 0);
421
422         ListStore store = new ListStore(typeof(string));
423
424         if(selectedgroup == null) {
425             foreach(Tile tile in tileset.Tiles) {
426                 if(tile == null)
427                     continue;
428                 store.AppendValues(new object[] { tile.ID.ToString() });
429             }
430         } else {
431             foreach(int id in selectedgroup.Tiles) {
432                 Tile tile = (Tile) tileset.Tiles[id];
433                 if(tile == null) {
434                     Console.WriteLine("tilegroup contains deleted tile");
435                     continue;
436                 }
437                 store.AppendValues(new object[] { id.ToString() });
438             }
439         }
440         
441         TileList.Model = store;
442         TileList.Selection.Mode = SelectionMode.Multiple;
443     }
444
445     private void FillTileGroupComboBox() {
446         string[] groups = new string[tileset.TileGroups.Count+1];
447         groups[0] = "All";
448
449         //Submenu submenu = new Submenu();
450         for(int i = 0; i < tileset.TileGroups.Count; ++i) {
451             String tilegroup = ((TileGroup) tileset.TileGroups[i]).Name;
452             groups[i+1] = tilegroup;
453             //submenu.Add(new MenuItem(tilegroup));
454         }
455         TileGroupComboBox.PopdownStrings = groups;
456         TileGroupComboBox.Entry.Editable = false;
457
458         //AddTileGroupMenu.Submenu = submenu;
459     }
460
461     private void OnTileGroupComboBoxEntryActivated(object o, EventArgs args) {
462         if(TileGroupComboBox.Entry.Text == "All") {
463             selectedgroup = null;
464         } else {
465             foreach(TileGroup tilegroup in tileset.TileGroups) {
466                 if(tilegroup.Name == TileGroupComboBox.Entry.Text) {
467                     selectedgroup = tilegroup;
468                     break;
469                 }
470             }
471         }
472         FillTileList();
473     }
474
475     private void OnTileListCursorChanged(object sender, EventArgs e) {
476         TreeModel model;
477         TreePath[] selectpaths =
478             TileList.Selection.GetSelectedRows(out model); 
479
480         Selection.Clear();
481         foreach(TreePath path in selectpaths) {
482             TreeIter iter;
483             model.GetIter(out iter, path);
484             int id = Int32.Parse(model.GetValue(iter, 0).ToString());
485             Selection.Add(tileset.Tiles[id]);
486         }
487         SelectionChanged();
488     }
489
490     private void ShowException(Exception e) {
491         MessageDialog dialog = new MessageDialog(MainWindow,
492                 DialogFlags.Modal | DialogFlags.DestroyWithParent,
493                 MessageType.Error, ButtonsType.Ok,
494                 e.Message);
495         dialog.Run();
496         dialog.Destroy();
497     }
498 }