1 module document;
2 
3 import pixelperfectengine.map.mapdata;
4 import pixelperfectengine.map.mapformat;
5 import editorevents;
6 import clipboard;
7 import contmenu;
8 import windows.rasterwindow;
9 import pixelperfectengine.concrete.eventchainsystem;
10 import pixelperfectengine.concrete.interfaces : MouseEventReceptor;
11 import pixelperfectengine.concrete.types;
12 import pixelperfectengine.graphics.common : Color, Coordinate;
13 import pixelperfectengine.graphics.bitmap;
14 import pixelperfectengine.graphics.layers;
15 import pixelperfectengine.system.input : MouseButton, ButtonState, MouseClickEvent, MouseMotionEvent, MouseWheelEvent, 
16 		MouseEventCommons, MouseButtonFlags;
17 import std.stdio;
18 
19 import app;
20 ///Individual document for parallel editing
21 public class MapDocument : MouseEventReceptor {
22 	/**
23 	 * Specifies the current edit mode
24 	 */
25 	public enum EditMode {
26 		selectDragScroll,
27 		tilePlacement,
28 		boxPlacement,
29 		spritePlacement,
30 	}
31 	UndoableStack		events;		///Per document event stack
32 	MapFormat			mainDoc;	///Used to reduce duplicate data as much as possible
33 	//ABitmap[] delegate() imageReturnFunc;
34 	Color[] delegate(MapDocument sender) paletteReturnFunc;	///Used for adding the palette for the document
35 	int					selectedLayer;	///Indicates the currently selected layer
36 	Box					mapSelection;	///Contains the selected map area parameters
37 	Box					areaSelection;	///Contains the selected layer area parameters in pixels
38 	RasterWindow		outputWindow;	///Window used to output the screen data
39 	EditMode			mode;			///Mose event mode selector
40 	LayerInfo[]			layerList;		///Local layerinfo for data lookup
41 	string				filename;		///Null if not yet saved, otherwise name of the target file
42 	protected int		prevMouseX;		///Previous mouse X position
43 	protected int		prevMouseY;		///Previous mouse Y position
44 
45 	public int			sXAmount;		///Continuous X scroll amount
46 	public int			sYAmount;		///Continuous Y scroll amount
47 	protected uint		flags;			///Various status flags combined into one.
48 	protected static enum	VOIDFILL_EN = 1<<0;	///If set, tilePlacement overrides only transparent (0xFFFF) tiles.
49 	protected static enum	LAYER_SCROLL = 1<<1;///If set, then layer is being scrolled.
50 	protected static enum	PLACEMENT = 1<<2;	///To solve some "debounce" issues around mouse click releases
51 	protected static enum	DISPL_SELECTION = 1<<3;	///If set, then selection is shown
52 	protected MappingElement	selectedMappingElement;	///Currently selected mapping element to write, including mirroring properties, palette selection, and priority attributes
53 	//public bool			voidfill;		///If true, tilePlacement overrides only transparent (0xFFFF) tiles.
54 	/**
55 	 * Loads the document from disk.
56 	 */
57 	public this(string filename) @trusted {
58 		mainDoc = new MapFormat(File(filename));
59 		events = new UndoableStack(20);
60 		mode = EditMode.selectDragScroll;
61 	}
62 	///New from scratch
63 	public this(string docName, int resX, int resY) @trusted {
64 		events = new UndoableStack(20);
65 		mainDoc = new MapFormat(docName, resX, resY);
66 		mode = EditMode.selectDragScroll;
67 	}
68 	///Returns the next available layer number.
69 	public int nextLayerNumber() @safe {
70 		int result = selectedLayer;
71 		do {
72 			if (mainDoc[result] is null)
73 				return result;
74 			result++;
75 		} while (true);
76 	}
77 	///Puts the loaded tiles onto a TileLayer
78 	public void addTileSet(int layer, ABitmap[ushort] tiles) @trusted {
79 		ITileLayer itl = cast(ITileLayer)mainDoc[layer];
80 		foreach (i ; tiles.byKey) {
81 			itl.addTile(tiles[i], i);
82 		}
83 	}
84 	///Ditto
85 	public void addTileSet(int layer, ABitmap[] tiles) @trusted {
86 		ITileLayer itl = cast(ITileLayer)mainDoc[layer];
87 		for (ushort i ; i < tiles.length ; i++) {
88 			itl.addTile(tiles[i], i);
89 		}
90 	}
91 	/**
92 	 * Clears selection area.
93 	 */
94 	public void clearSelection() {
95 		if (outputWindow) {
96 			outputWindow.disarmSelection();
97 			outputWindow.showSelection = false;
98 			outputWindow.updateRaster();
99 		}
100 	}
101 	/**
102 	 * Scrolls the selected layer by a given amount.
103 	 */
104 	public void scrollSelectedLayer (int x, int y) {
105 		if (selectedLayer in mainDoc.layeroutput) {
106 			mainDoc[selectedLayer].relScroll(x, y);
107 		}
108 		//outputWindow.updateRaster();
109 		updateSelection();
110 	}
111 	/**
112 	 * Sets the continuous scroll amounts.
113 	 */
114 	public void setContScroll (int x, int y) {
115 		sXAmount = x;
116 		sYAmount = y;
117 	}
118 	/**
119 	 * Moves the selected area by the given amounts.
120 	 */
121 	public void moveSelection(int x, int y) {
122 		if (selectedLayer in mainDoc.layeroutput) {
123 			if (getLayerInfo(selectedLayer).type == LayerType.Tile) {
124 				ITileLayer target = cast(ITileLayer)(mainDoc[selectedLayer]);
125 				const int tileWidth = target.getTileWidth, tileHeight = target.getTileHeight;
126 				areaSelection.relMove(x * tileWidth, y * tileHeight);
127 				mapSelection.relMove(x, y);
128 				updateSelection();
129 			}
130 		}
131 	}
132 	/**
133 	 * Updates the selection on the raster window.
134 	 */
135 	public void updateSelection() {
136 		if (selectedLayer in mainDoc.layeroutput) {
137 			const int sX = mainDoc[selectedLayer].getSX, sY = mainDoc[selectedLayer].getSY;
138 			Box onscreen = Box(sX, sY, sX + outputWindow.rasterX - 1, sY + outputWindow.rasterY - 1);
139 			if (onscreen.isBetween(areaSelection.cornerUL) || onscreen.isBetween(areaSelection.cornerUR) || 
140 					onscreen.isBetween(areaSelection.cornerLL) || onscreen.isBetween(areaSelection.cornerLR)) {
141 				outputWindow.selection = areaSelection;
142 				outputWindow.selection.relMove(sX * -1, sY * -1);
143 
144 				outputWindow.selection.left = outputWindow.selection.left < 0 ? 0 : outputWindow.selection.left;
145 				outputWindow.selection.left = outputWindow.selection.left >= outputWindow.rasterX ? outputWindow.rasterX - 1 : 
146 						outputWindow.selection.left;
147 				outputWindow.selection.right = outputWindow.selection.right < 0 ? 0 : outputWindow.selection.right;
148 				outputWindow.selection.right = outputWindow.selection.right >= outputWindow.rasterX ? outputWindow.rasterX - 1 : 
149 						outputWindow.selection.right;
150 				
151 				outputWindow.selection.top = outputWindow.selection.top < 0 ? 0 : outputWindow.selection.top;
152 				outputWindow.selection.top = outputWindow.selection.top >= outputWindow.rasterY ? outputWindow.rasterY - 1 : 
153 						outputWindow.selection.top;
154 				outputWindow.selection.bottom = outputWindow.selection.bottom < 0 ? 0 : outputWindow.selection.bottom;
155 				outputWindow.selection.bottom = outputWindow.selection.bottom >= outputWindow.rasterX ? outputWindow.rasterX - 1 : 
156 						outputWindow.selection.bottom;
157 			} else {
158 				outputWindow.selection = Box (0, 0, -1, -1);
159 			}
160 			outputWindow.updateRaster();
161 		}
162 	}
163 	/**
164 	 * Scrolls the selected layer by the amount set.
165 	 * Should be called for every frame.
166 	 */
167 	public void contScrollLayer () {
168 		if(sXAmount || sYAmount){
169 			scrollSelectedLayer (sXAmount, sYAmount);
170 		}
171 	}
172 	/**
173 	 * Updates the material list for the selected layer.
174 	 */
175 	public void updateMaterialList () {
176 		if (selectedLayer in mainDoc.layeroutput) {
177 			if (prg.materialList !is null) {
178 				TileInfo[] list = mainDoc.getTileInfo(selectedLayer);
179 				//writeln(list.length);
180 				prg.materialList.updateMaterialList(list);
181 			}
182 		}
183 	}
184 	/**
185 	 * Updates the layers for this document.
186 	 */
187 	public void updateLayerList () {
188 		layerList = mainDoc.getLayerInfo;
189 		if (prg.layerList !is null) {
190 			prg.layerList.updateLayerList(layerList);
191 			//prg.wh.layerlist
192 		}
193 	}
194 	public void onSelection () {
195 		updateLayerList;
196 		updateMaterialList;
197 	}
198 	public void tileMaterial_FlipHorizontal(bool pos) {
199 		selectedMappingElement.attributes.horizMirror = pos;
200 	}
201 	public void tileMaterial_FlipHorizontal() {
202 		selectedMappingElement.attributes.horizMirror = !selectedMappingElement.attributes.horizMirror;
203 	}
204 	public void tileMaterial_FlipVertical(bool pos) {
205 		selectedMappingElement.attributes.vertMirror = pos;
206 	}
207 	public void tileMaterial_FlipVertical() {
208 		selectedMappingElement.attributes.vertMirror = !selectedMappingElement.attributes.vertMirror;
209 	}
210 	public void tileMaterial_Select(wchar id) {
211 		selectedMappingElement.tileID = id;
212 		//mode = EditMode.tilePlacement;
213 	}
214 	public void tileMaterial_Up() {
215 		selectedMappingElement.tileID++;
216 	}
217 	public void tileMaterial_Down() {
218 		selectedMappingElement.tileID--;
219 	}
220 	public ushort tileMaterial_PaletteUp() {
221 		selectedMappingElement.paletteSel++;
222 		return selectedMappingElement.paletteSel;
223 	}
224 	public ushort tileMaterial_PaletteDown() {
225 		selectedMappingElement.paletteSel--;
226 		return selectedMappingElement.paletteSel;
227 	}
228 	public @property bool voidfill() @nogc @safe pure nothrow {
229 		return flags & VOIDFILL_EN;
230 	}
231 	public @property bool voidfill(bool val) @nogc @safe pure nothrow {
232 		if (val) flags |= VOIDFILL_EN;
233 		else flags &= ~VOIDFILL_EN;
234 		return flags & VOIDFILL_EN;
235 	}
236 	
237 	public void passMCE(MouseEventCommons mec, MouseClickEvent mce) {
238 		int x = mce.x, y = mce.y;
239 		final switch (mode) with (EditMode) {
240 			case tilePlacement:
241 				switch (mce.button) {
242 					case MouseButton.Left:			//Tile placement
243 						//Record the first cursor position upon mouse button press, then initialize either a single or zone write for the selected tile layer.
244 						if (selectedLayer !in mainDoc.layeroutput) return;	//Safety protection
245 						if (mce.state) {
246 							prevMouseX = x;
247 							prevMouseY = y;
248 							flags |= PLACEMENT;
249 						} else if (flags & PLACEMENT) {
250 							flags &= ~PLACEMENT;
251 							ITileLayer target = cast(ITileLayer)(mainDoc[selectedLayer]);
252 							const int tileWidth = target.getTileWidth, tileHeight = target.getTileHeight;
253 							const int hScroll = mainDoc[selectedLayer].getSX, vScroll = mainDoc[selectedLayer].getSX;
254 							x = (x + hScroll) / tileWidth;
255 							y = (y + vScroll) / tileHeight;
256 							prevMouseX = (prevMouseX + hScroll) / tileWidth;
257 							prevMouseY = (prevMouseY + vScroll) / tileHeight;
258 							Box c;
259 							
260 							if (x > prevMouseX){
261 								c.left = prevMouseX;
262 								c.right = x;
263 							} else {
264 								c.left = x;
265 								c.right = prevMouseX;
266 							}
267 
268 							if (y > prevMouseY){
269 								c.top = prevMouseY;
270 								c.bottom = y;
271 							} else {
272 								c.top = y;
273 								c.bottom = prevMouseY;
274 							}
275 
276 							if (voidfill) {
277 								if (c.width == 1 && c.height == 1) {
278 									if (target.readMapping(c.left, c.top).tileID == 0xFFFF)
279 										events.addToTop(new WriteToMapSingle(target, c.left, c.top, selectedMappingElement));
280 								} else {
281 									events.addToTop(new WriteToMapVoidFill(target, c, selectedMappingElement));
282 								}
283 							} else {
284 								if (c.width == 1 && c.height == 1) {
285 									events.addToTop(new WriteToMapSingle(target, c.left, c.top, selectedMappingElement));
286 								} else {
287 									events.addToTop(new WriteToMapOverwrite(target, c, selectedMappingElement));
288 								}
289 							}
290 						}
291 						outputWindow.updateRaster();
292 						break;
293 					case MouseButton.Mid:			//Tile deletion
294 						//Record the first cursor position upon mouse button press, then initialize either a single or zone delete for the selected tile layer.
295 						if (mce.state) {
296 							prevMouseX = x;
297 							prevMouseY = y;
298 							flags |= PLACEMENT;
299 						} else if (flags & PLACEMENT) {
300 							flags &= ~PLACEMENT;
301 							ITileLayer target = cast(ITileLayer)(mainDoc[selectedLayer]);
302 							x = (x + mainDoc[selectedLayer].getSX) / target.getTileWidth;
303 							y = (y + mainDoc[selectedLayer].getSY) / target.getTileHeight;
304 							prevMouseX = (prevMouseX + mainDoc[selectedLayer].getSX) / target.getTileWidth;
305 							prevMouseY = (prevMouseY + mainDoc[selectedLayer].getSY) / target.getTileHeight;
306 
307 							Box c;
308 
309 							if (x > prevMouseX){
310 								c.left = prevMouseX;
311 								c.right = x;
312 							} else {
313 								c.left = x;
314 								c.right = prevMouseX;
315 							}
316 
317 							if (y > prevMouseY){
318 								c.top = prevMouseY;
319 								c.bottom = y;
320 							} else {
321 								c.top = y;
322 								c.bottom = prevMouseY;
323 							}
324 
325 							if (c.width == 1 && c.height == 1) {
326 								events.addToTop(new WriteToMapSingle(target, c.left, c.top, MappingElement(0xFFFF)));
327 							} else {
328 								events.addToTop(new WriteToMapOverwrite(target, c, MappingElement(0xFFFF)));
329 							}
330 						}
331 						outputWindow.updateRaster();
332 						break;
333 					case MouseButton.Right:
334 						if (mce.state) prg.wh.addPopUpElement(createTilePlacementContextMenu(&onSelectContextMenuSelect));
335 						break;
336 					default:
337 						break;
338 				}
339 				break;
340 			case selectDragScroll:
341 				switch (mce.button) {
342 					case MouseButton.Left:
343 						
344 						//Initialize drag select
345 						if (mce.state) {
346 							prevMouseX = x;
347 							prevMouseY = y;
348 							outputWindow.armSelection;
349 							outputWindow.selection.left = x;
350 							outputWindow.selection.top = y;
351 							outputWindow.selection.right = x;
352 							outputWindow.selection.bottom = y;
353 						} else {
354 							outputWindow.disarmSelection;
355 							Box c = outputWindow.selection;
356 							if (getLayerInfo(selectedLayer).type != LayerType.init) {
357 								Layer l = mainDoc.layeroutput[selectedLayer];
358 								areaSelection = c;
359 								areaSelection.relMove(l.getSX, l.getSY);
360 								
361 								switch (getLayerInfo(selectedLayer).type) {
362 									case LayerType.Tile: 
363 										TileLayer tl = cast(TileLayer)l;
364 										const int tileWidth = tl.getTileWidth, tileHeight = tl.getTileHeight, 
365 												mapWidth = tl.getMX, mapHeight = tl.getMY;
366 										//calculate selected map area
367 										mapSelection.left = areaSelection.left / tileWidth;
368 										mapSelection.right = areaSelection.right / tileWidth; /+ + (areaSelection.right % tileWidth > 0 ? 1 : 0); +/
369 										mapSelection.top = areaSelection.top / tileHeight;
370 										mapSelection.bottom = areaSelection.bottom / tileHeight; /+ + (areaSelection.bottom % tileHeight > 0 ? 1 : 0); +/
371 										//Clamp map sizes between what map has
372 										import pixelperfectengine.system.etc : clamp;
373 										
374 										clamp(mapSelection.left, 0, mapWidth);
375 										clamp(mapSelection.right, 0, mapHeight);
376 										clamp(mapSelection.top, 0, mapWidth);
377 										clamp(mapSelection.bottom, 0, mapHeight);
378 										//adjust displayed selection to mapSelection
379 										areaSelection.left = mapSelection.left * tileWidth;
380 										areaSelection.right = (mapSelection.right + 1) * tileWidth;//areaSelection.right = mapSelection.right * tileWidth;
381 										areaSelection.top = mapSelection.top * tileHeight;
382 										areaSelection.bottom = (mapSelection.bottom + 1) * tileHeight;//areaSelection.bottom = mapSelection.bottom * tileHeight;								
383 										break;
384 									
385 									default:
386 										break;
387 								}
388 							}
389 							outputWindow.disarmSelection();
390 							outputWindow.showSelection = true;
391 							updateSelection();
392 						}
393 						break;
394 					case MouseButton.Mid:
395 						
396 						if (mce.state) {
397 							outputWindow.requestCursor(CursorType.Hand);
398 							prevMouseX = x;
399 							prevMouseY = y;
400 							outputWindow.moveEn = true;
401 						} else {
402 							outputWindow.requestCursor(CursorType.Arrow);
403 							outputWindow.moveEn = false;
404 						}
405 						//scrollSelectedLayer(prevMouseX - x, prevMouseY - y);
406 
407 						prevMouseX = x;
408 						prevMouseY = y;
409 						break;
410 					case MouseButton.Right:
411 						if (mce.state) prg.wh.addPopUpElement(createSelectContextMenu(&onSelectContextMenuSelect));
412 						break;
413 					default:
414 						break;
415 				}
416 				break;
417 			case boxPlacement:
418 				break;
419 			case spritePlacement:
420 				break;
421 		}
422 	}
423 	
424 	public void passMME(MouseEventCommons mec, MouseMotionEvent mme) {
425 		final switch (mode) with (EditMode) {
426 			case selectDragScroll:
427 				switch (mme.buttonState) {
428 					case MouseButtonFlags.Left:
429 						Box s;
430 						if (prevMouseX < mme.x) {
431 							s.left = prevMouseX;
432 							s.right = mme.x;
433 						} else {
434 							s.left = mme.x;
435 							s.right = prevMouseX;
436 						}
437 						if (prevMouseY < mme.y) {
438 							s.top = prevMouseY;
439 							s.bottom = mme.y;
440 						} else {
441 							s.top = mme.y;
442 							s.bottom = prevMouseY;
443 						}
444 						outputWindow.selection = s;
445 						outputWindow.updateRaster();
446 						break;
447 					case MouseButtonFlags.Mid:
448 						scrollSelectedLayer(prevMouseX - mme.x, prevMouseY - mme.y);
449 						prevMouseX = mme.x;
450 						prevMouseY = mme.y;
451 						outputWindow.updateRaster();
452 						break;
453 					default:
454 						break;
455 				}
456 				break;
457 			case tilePlacement:
458 				break;
459 			case boxPlacement:
460 				break;
461 			case spritePlacement:
462 				break;
463 		}
464 	}
465 	
466 	public void passMWE(MouseEventCommons mec, MouseWheelEvent mwe) {
467 		if (outputWindow.isSelectionArmed())
468 			scrollSelectedLayer(mwe.x, mwe.y);
469 	}
470 	
471 	public LayerInfo getLayerInfo(int pri) nothrow {
472 		foreach (key; layerList) {
473 			if (key.pri == pri)
474 				return key;
475 		}
476 		return LayerInfo.init;
477 	}
478 	/**
479 	 * Copies an area on a tilelayer to a MapClipboard item.
480 	 */
481 	protected MapClipboard.Item createMapClipboardItem() {
482 		MapClipboard.Item result;
483 		const LayerType lt = getLayerInfo(selectedLayer).type;
484 		if (lt == LayerType.Tile || lt == LayerType.TransformableTile) {
485 			result.map.length = mapSelection.area;
486 			result.width = mapSelection.width;
487 			result.height = mapSelection.height;
488 			ITileLayer tl = cast(ITileLayer)mainDoc.layeroutput[selectedLayer];
489 			for (int y ; y < mapSelection.height ; y++) {
490 				for (int x ; x < mapSelection.width; x++) {
491 					result.map[x + (mapSelection.width * y)] = tl.readMapping(mapSelection.left + x, mapSelection.top + y);
492 				}
493 			}
494 		}
495 		return result;
496 	}
497 	/**
498 	 * Creates a copy event if called.
499 	 * Uses the internal states of this document.
500 	 */
501 	public void copy() {
502 		switch (getLayerInfo(selectedLayer).type) {
503 			case LayerType.Tile, LayerType.TransformableTile:
504 				MapClipboard.Item area = createMapClipboardItem();
505 				prg.mapClipboard.addItem(area);
506 				break;
507 			default:
508 				break;
509 		}
510 		outputWindow.updateRaster();
511 	}
512 	/**
513 	 * Creates a cut event if called.
514 	 * Uses the internal states of this document.
515 	 */
516 	public void cut() {
517 		switch (getLayerInfo(selectedLayer).type) {
518 			case LayerType.Tile, LayerType.TransformableTile:
519 				MapClipboard.Item area = createMapClipboardItem();
520 				prg.mapClipboard.addItem(area);
521 				events.addToTop(new CutFromTileLayerEvent(cast(ITileLayer)mainDoc.layeroutput[selectedLayer], mapSelection));
522 				break;
523 			default:
524 				break;
525 		}
526 		outputWindow.updateRaster();
527 	}
528 	/**
529 	 * Deletes the selected area.
530 	 */
531 	public void deleteArea() {
532 		switch (getLayerInfo(selectedLayer).type) {
533 			case LayerType.Tile, LayerType.TransformableTile:
534 				events.addToTop(new CutFromTileLayerEvent(cast(ITileLayer)mainDoc.layeroutput[selectedLayer], mapSelection));
535 				break;
536 			default:
537 				break;
538 		}
539 		outputWindow.updateRaster();
540 	}
541 	/**
542 	 * Creates a paste event if called.
543 	 * Uses the internal states of this document.
544 	 */
545 	public void paste(size_t which = 0) {
546 		switch (getLayerInfo(selectedLayer).type) {
547 			case LayerType.Tile, LayerType.TransformableTile:
548 				events.addToTop(new PasteIntoTileLayerEvent(prg.mapClipboard.getItem(which), 
549 						cast(ITileLayer)(mainDoc.layeroutput[selectedLayer]), mapSelection.cornerUL, voidfill));
550 				break;
551 			default:
552 				break;
553 		}
554 		outputWindow.updateRaster();
555 	}
556 	/**
557 	 * Context menu for selection events go here
558 	 */
559 	protected void onSelectContextMenuSelect (Event ev) {
560 		MenuEvent me = cast(MenuEvent)ev;
561 		switch (me.itemSource) {
562 			case "copy":
563 				copy;
564 				break;
565 			case "cut":
566 				cut;
567 				break;
568 			case "paste":
569 				paste;
570 				break;
571 			case "flph":
572 				flipTilesHoriz();
573 				break;
574 			case "flpv":
575 				flipTilesVert();
576 				break;
577 			case "mirh":
578 				selMirrorHoriz();
579 				break;
580 			case "mirv":
581 				selMirrorVert();
582 				break;
583 			case "shp":
584 				break;
585 			case "hm":
586 				selectedMappingElement.attributes.horizMirror = !selectedMappingElement.attributes.horizMirror;
587 				break;
588 			case "vm":
589 				selectedMappingElement.attributes.vertMirror = !selectedMappingElement.attributes.vertMirror;
590 				break;
591 			case "p+":
592 				selectedMappingElement.paletteSel++;
593 				break;
594 			case "p-":
595 				selectedMappingElement.paletteSel--;
596 				break;
597 			default:
598 				break;
599 		}
600 	}
601 	/**
602 	 * Removes a tile from the material list.
603 	 */
604 	public void removeTile(int id) {
605 		if (mainDoc.layeroutput[selectedLayer])
606 			events.addToTop(new RemoveTile(id, this, selectedLayer));
607 	}
608 	/**
609 	 * Renames a tile on the material list.
610 	 */
611 	public void renameTile(int id, string name) {
612 		if (mainDoc.layeroutput[selectedLayer])
613 			events.addToTop(new RenameTile(id, this, selectedLayer, name));
614 	}
615 	/**
616 	 * Removes a layer.
617 	 */
618 	public void removeLayer() {
619 		if (mainDoc.layeroutput[selectedLayer])
620 			events.addToTop(new RemoveLayer(this, selectedLayer));
621 	}
622 	/**
623 	 * Renames a layer.
624 	 */
625 	public void renameLayer(string newName) {
626 		if (mainDoc.layeroutput[selectedLayer])
627 			events.addToTop(new RenameLayer(this, selectedLayer, newName));
628 	}
629 	/**
630 	 * Moves the priority of a layer.
631 	 */
632 	public void changeLayerPriority(int newPri) {
633 		if (mainDoc.layeroutput[newPri]) {
634 			prg.wh.message("Layer edit error!", "Layer priority is already in use or invalid!");
635 		} else if (mainDoc.layeroutput[selectedLayer]) {
636 			events.addToTop(new ChangeLayerPriority(this, selectedLayer, newPri));
637 		}
638 	}
639 	/**
640 	 * Mirrors selected items horizontally.
641 	 */
642 	public void selMirrorHoriz() {
643 		switch (getLayerInfo(selectedLayer).type) {
644 			case LayerType.Tile, LayerType.TransformableTile:
645 				events.addToTop(new MirrorSelHTL(cast(ITileLayer)(mainDoc.layeroutput[selectedLayer]), mapSelection));
646 				break;
647 			default:
648 				break;
649 		}
650 	}
651 	/**
652 	 * Mirrors selected items vertically.
653 	 */
654 	public void selMirrorVert() {
655 		switch (getLayerInfo(selectedLayer).type) {
656 			case LayerType.Tile, LayerType.TransformableTile:
657 				events.addToTop(new MirrorSelVTL(cast(ITileLayer)(mainDoc.layeroutput[selectedLayer]), mapSelection));
658 				outputWindow.updateRaster();
659 				break;
660 			default:
661 				break;
662 		}
663 	}
664 	/**
665 	 * Mirrors selected items horizontally and vertically.
666 	 */
667 	public void selMirrorBoth() {
668 		switch (getLayerInfo(selectedLayer).type) {
669 			case LayerType.Tile, LayerType.TransformableTile:
670 				events.addToTop(new MirrorSelBTL(cast(ITileLayer)(mainDoc.layeroutput[selectedLayer]), mapSelection));
671 				outputWindow.updateRaster();
672 				break;
673 			default:
674 				break;
675 		}
676 	}
677 	/**
678 	 * Flips selected tiles horizontally.
679 	 */
680 	public void flipTilesHoriz() {
681 		if (getLayerInfo(selectedLayer).type == LayerType.Tile || 
682 				getLayerInfo(selectedLayer).type == LayerType.TransformableTile) {
683 			events.addToTop(new FlipSelTilesH(cast(ITileLayer)(mainDoc.layeroutput[selectedLayer]), mapSelection));
684 			outputWindow.updateRaster();
685 		}
686 	}
687 	/**
688 	 * Flips selected tiles vertically.
689 	 */
690 	public void flipTilesVert() {
691 		if (getLayerInfo(selectedLayer).type == LayerType.Tile || 
692 				getLayerInfo(selectedLayer).type == LayerType.TransformableTile) {
693 			events.addToTop(new FlipSelTilesV(cast(ITileLayer)(mainDoc.layeroutput[selectedLayer]), mapSelection));
694 			outputWindow.updateRaster();
695 		}
696 	}
697 	/**
698 	 * Fills selected area with selected tile, using overwrite rules defined by the materiallist.
699 	 */
700 	public void fillSelectedArea() {
701 		if (getLayerInfo(selectedLayer).type == LayerType.Tile || 
702 				getLayerInfo(selectedLayer).type == LayerType.TransformableTile) {
703 			ITileLayer target = cast(ITileLayer)mainDoc.layeroutput[selectedLayer];
704 			if (voidfill) {
705 				events.addToTop(new WriteToMapVoidFill(target, mapSelection, selectedMappingElement));
706 			} else {
707 				events.addToTop(new WriteToMapOverwrite(target, mapSelection, selectedMappingElement));
708 			}
709 			outputWindow.updateRaster();
710 		}
711 	}
712 	/**
713 	 * Assigns an imported tilemap to the currently selected layer.
714 	 */
715 	public void assignImportedTilemap(MappingElement[] map, int w, int h) {
716 		if (getLayerInfo(selectedLayer).type == LayerType.Tile || 
717 				getLayerInfo(selectedLayer).type == LayerType.TransformableTile) {
718 			events.addToTop(new ImportLayerData(cast(ITileLayer)mainDoc.layeroutput[selectedLayer], mainDoc.layerData[selectedLayer], map, w, 
719 					h));
720 		}
721 	}
722 }