DEADSOFTWARE

Added new optios(thanks GranMinigun!), added .dfz extension(now default) and fixed...
[d2df-editor.git] / src / editor / f_main.pas
index 18a5696d9383a46c13361eef99112e5ae16fee06..95d468df1917067c9c67c1b4bb77d8423009a1e8 100644 (file)
@@ -1,14 +1,14 @@
 unit f_main;
 
-{$MODE Delphi}
+{$INCLUDE ../shared/a_modes.inc}
 
 interface
 
 uses
-  LCLIntf, LCLType, LMessages, SysUtils, Variants, Classes, Graphics,
-  Controls, Forms, Dialogs, ImgList, StdCtrls, Buttons,
-  ComCtrls, ValEdit, Types, ToolWin, Menus, ExtCtrls,
-  CheckLst, Grids, OpenGLContext;
+  LCLIntf, LCLType, SysUtils, Variants, Classes, Graphics,
+  Controls, Forms, Dialogs, StdCtrls, Buttons,
+  ComCtrls, ValEdit, Types, Menus, ExtCtrls,
+  CheckLst, Grids, OpenGLContext, utils, UTF8Process;
 
 type
 
@@ -26,6 +26,7 @@ type
     miSaveMapAs: TMenuItem;
     miOpenWadMap: TMenuItem;
     miLine1: TMenuItem;
+    miReopenMap: TMenuItem;
     miSaveMiniMap: TMenuItem;
     miDeleteMap: TMenuItem;
     miPackMap: TMenuItem;
@@ -204,15 +205,21 @@ type
     procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
     procedure FormCreate(Sender: TObject);
     procedure FormDestroy(Sender: TObject);
+    procedure FormDropFiles(Sender: TObject; const FileNames: array of String);
     procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
     procedure FormResize(Sender: TObject);
     procedure lbTextureListClick(Sender: TObject);
+    procedure lbTextureListDrawItem(Control: TWinControl; Index: Integer;
+      ARect: TRect; State: TOwnerDrawState);
+    procedure miReopenMapClick(Sender: TObject);
     procedure RenderPanelMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
     procedure RenderPanelMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
     procedure RenderPanelMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
     procedure RenderPanelPaint(Sender: TObject);
     procedure RenderPanelResize(Sender: TObject);
+    procedure Splitter1Moved(Sender: TObject);
     procedure vleObjectPropertyEditButtonClick(Sender: TObject);
+    procedure vleObjectPropertyApply(Sender: TObject);
     procedure vleObjectPropertyGetPickList(Sender: TObject; const KeyName: String; Values: TStrings);
     procedure vleObjectPropertyKeyDown(Sender: TObject; var Key: Word;
                                        Shift: TShiftState);
@@ -258,8 +265,9 @@ type
     procedure OnIdle(Sender: TObject; var Done: Boolean);
   public
     procedure RefreshRecentMenu();
-    { procedure lbTextureListDrawItem(Control: TWinControl; Index: Integer;
-                                    Rect: TRect; State: TOwnerDrawState); }
+    procedure OpenMapFile(FileName: String);
+    function RenderMousePos(): TPoint;
+    procedure RecountSelectedObjects();
   end;
 
 const
@@ -291,6 +299,7 @@ var
   DrawPanelSize: Boolean;
   BackColor: TColor;
   PreviewColor: TColor;
+  UseCheckerboard: Boolean;
   Scale: Byte;
   RecentCount: Integer;
   RecentFiles: TStringList;
@@ -309,7 +318,7 @@ var
 
   LayerEnabled: Array [LAYER_BACK..LAYER_TRIGGERS] of Boolean =
     (True, True, True, True, True, True, True, True, True);
-  PreviewMode: Boolean = False;
+  PreviewMode: Byte = 0;
   gLanguage: String;
 
   FormCaption: String;
@@ -323,14 +332,14 @@ procedure ChangeShownProperty(Name: String; NewValue: String);
 implementation
 
 uses
-  f_options, e_graphics, e_log, GL, GLExt, Math,
+  f_options, e_graphics, e_log, GL, Math,
   f_mapoptions, g_basic, f_about, f_mapoptimization,
   f_mapcheck, f_addresource_texture, g_textures,
-  f_activationtype, f_keys, MAPWRITER, MAPSTRUCT,
-  MAPREADER, f_selectmap, f_savemap, WADEDITOR, WADSTRUCT, MAPDEF,
+  f_activationtype, f_keys,
+  MAPREADER, f_selectmap, f_savemap, WADEDITOR, MAPDEF,
   g_map, f_saveminimap, f_addresource, CONFIG, f_packmap,
   f_addresource_sound, f_maptest, f_choosetype,
-  g_language, f_selectlang, ClipBrd;
+  g_language, f_selectlang, ClipBrd, g_resources;
 
 const
   UNDO_DELETE_PANEL   = 1;
@@ -380,7 +389,7 @@ const
   SELECTFLAG_SHOTPANEL  = 7;
   SELECTFLAG_SELECTED   = 8;
 
-  RECENT_FILES_MENU_START = 11;
+  RECENT_FILES_MENU_START = 12;
 
   CLIPBOARD_SIG         = 'DF:ED';
 
@@ -730,6 +739,7 @@ var
   str: String;
 begin
   MainForm.vleObjectProperty.Strings.Clear();
+  MainForm.RecountSelectedObjects();
 
 // Отображаем свойства если выделен только один объект:
   if SelectedObjectCount() <> 1 then
@@ -951,7 +961,8 @@ begin
           case TriggerType of
             TRIGGER_EXIT:
               begin
-                with ItemProps[InsertRow(_lc[I_PROP_TR_NEXT_MAP], Data.MapName, True)] do
+                str := win2utf(Data.MapName);
+                with ItemProps[InsertRow(_lc[I_PROP_TR_NEXT_MAP], str, True)] do
                 begin
                   EditStyle := esEllipsis;
                   ReadOnly := True;
@@ -1105,7 +1116,8 @@ begin
 
             TRIGGER_SOUND:
               begin
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SOUND_NAME], Data.SoundName, True)] do
+                str := win2utf(Data.SoundName);
+                with ItemProps[InsertRow(_lc[I_PROP_TR_SOUND_NAME], str, True)] do
                 begin
                   EditStyle := esEllipsis;
                   ReadOnly := True;
@@ -1268,7 +1280,8 @@ begin
 
            TRIGGER_MUSIC:
              begin
-               with ItemProps[InsertRow(_lc[I_PROP_TR_MUSIC_NAME], Data.MusicName, True)] do
+               str := win2utf(Data.MusicName);
+               with ItemProps[InsertRow(_lc[I_PROP_TR_MUSIC_NAME], str, True)] do
                begin
                  EditStyle := esEllipsis;
                  ReadOnly := True;
@@ -1370,7 +1383,8 @@ begin
                   EditStyle := esPickList;
                   ReadOnly := True;
                 end;
-                with ItemProps[InsertRow(_lc[I_PROP_TR_MESSAGE_TEXT], Data.MessageText, True)] do
+                str := win2utf(Data.MessageText);
+                with ItemProps[InsertRow(_lc[I_PROP_TR_MESSAGE_TEXT], str, True)] do
                 begin
                   EditStyle := esSimple;
                   MaxLength := 100;
@@ -1467,7 +1481,7 @@ begin
                   3: str := _lc[I_PROP_TR_SHOT_AIM_3];
                   else str := _lc[I_PROP_TR_SHOT_AIM_0];
                 end;
-                with ItemProps[InsertRow(_lc[I_PROP_TR_SHOT_AIM], str, True)-1] do
+                with ItemProps[InsertRow(_lc[I_PROP_TR_SHOT_AIM], str, True)] do
                 begin
                   EditStyle := esPickList;
                   ReadOnly := True;
@@ -1761,6 +1775,7 @@ begin
   RemoveSelectFromObjects();
 
   MainForm.miUndo.Enabled := UndoBuffer <> nil;
+  MainForm.RecountSelectedObjects();
 end;
 
 procedure Undo_Add(ObjectType: Byte; ID: DWORD; Group: Boolean = False);
@@ -1795,6 +1810,7 @@ procedure FullClear();
 begin
   RemoveSelectFromObjects();
   ClearMap();
+  LoadSky(gMapInfo.SkyName);
   UndoBuffer := nil;
   slInvalidTextures.Clear();
   MapCheckForm.lbErrorList.Clear();
@@ -1886,11 +1902,19 @@ var
   Width, Height: Word;
   fn: String;
 begin
+  Data := nil;
+  FrameLen := 0;
+  Width := 0;
+  Height := 0;
+
   if aSection = '..' then
     SectionName := ''
   else
     SectionName := aSection;
 
+  if aWAD = '' then
+    aWAD := _lc[I_WAD_SPECIAL_MAP];
+
   if aWAD = _lc[I_WAD_SPECIAL_MAP] then
     begin // Файл карты
       g_ProcessResourceStr(OpenedMap, @fn, nil, nil);
@@ -1906,7 +1930,7 @@ begin
       end
     else
       begin // Внешний WAD
-        FileName := EditorDir+'wads\'+aWAD;
+        FileName := EditorDir+'wads/'+aWAD;
         ResourceName := aWAD+':'+SectionName+'\'+aTex;
       end;
 
@@ -1949,14 +1973,21 @@ begin
       begin // Аним. текстура
         GetFrame(FullResourceName, Data, FrameLen, Width, Height);
 
-        if g_CreateTextureMemorySize(Data, FrameLen, ResourceName, 0, 0, Width, Height, 1) then
-          a := MainForm.lbTextureList.Items.Add(ResourceName);
+        if not g_CreateTextureMemorySize(Data, FrameLen, ResourceName, 0, 0, Width, Height, 1) then
+          ok := False;
+        a := MainForm.lbTextureList.Items.Add(ResourceName);
       end
     else // Обычная текстура
       begin
-        if g_CreateTextureWAD(ResourceName, FullResourceName) then
-          a := MainForm.lbTextureList.Items.Add(ResourceName);
+        if not g_CreateTextureWAD(ResourceName, FullResourceName) then
+          ok := False;
+        a := MainForm.lbTextureList.Items.Add(ResourceName);
       end;
+    if (not ok) and (slInvalidTextures.IndexOf(ResourceName) = -1) then
+    begin
+      slInvalidTextures.Add(ResourceName);
+      ok := True;
+    end;
     if (a > -1) and (not silent) then
       SelectTexture(a);
   end;
@@ -1984,6 +2015,7 @@ var
   MapName: String;
   idx: Integer;
 begin
+  SelectMapForm.Caption := _lc[I_CAP_OPEN];
   SelectMapForm.GetMaps(FileName);
 
   if (FileName = OpenedWAD) and
@@ -2314,7 +2346,8 @@ begin
   Result := Res;
 end;
 
-procedure StringToCopyBuffer(Str: String; var CopyBuf: TCopyRecArray);
+procedure StringToCopyBuffer(Str: String; var CopyBuf: TCopyRecArray;
+  var pmin: TPoint);
 var
   i, j, t: Integer;
 
@@ -2396,6 +2429,8 @@ begin
             PanelType := StrToIntDef(GetNext(), PANEL_WALL);
             X := StrToIntDef(GetNext(), 0);
             Y := StrToIntDef(GetNext(), 0);
+            pmin.X := Min(X, pmin.X);
+            pmin.Y := Min(Y, pmin.Y);
             Width := StrToIntDef(GetNext(), 16);
             Height := StrToIntDef(GetNext(), 16);
             TextureName := GetNext();
@@ -2410,6 +2445,8 @@ begin
           ItemType := StrToIntDef(GetNext(), ITEM_MEDKIT_SMALL);
           X := StrToIntDef(GetNext(), 0);
           Y := StrToIntDef(GetNext(), 0);
+          pmin.X := Min(X, pmin.X);
+          pmin.Y := Min(Y, pmin.Y);
           OnlyDM := (GetNext() = '1');
           Fall := (GetNext() = '1');
         end;
@@ -2420,6 +2457,8 @@ begin
           MonsterType := StrToIntDef(GetNext(), MONSTER_DEMON);
           X := StrToIntDef(GetNext(), 0);
           Y := StrToIntDef(GetNext(), 0);
+          pmin.X := Min(X, pmin.X);
+          pmin.Y := Min(Y, pmin.Y);
 
           if GetNext() = '1' then
             Direction := D_LEFT
@@ -2433,6 +2472,8 @@ begin
           AreaType := StrToIntDef(GetNext(), AREA_PLAYERPOINT1);
           X := StrToIntDef(GetNext(), 0);
           Y := StrToIntDef(GetNext(), 0);
+          pmin.X := Min(X, pmin.X);
+          pmin.Y := Min(Y, pmin.Y);
           if GetNext() = '1' then
             Direction := D_LEFT
           else
@@ -2445,6 +2486,8 @@ begin
           TriggerType := StrToIntDef(GetNext(), TRIGGER_EXIT);
           X := StrToIntDef(GetNext(), 0);
           Y := StrToIntDef(GetNext(), 0);
+          pmin.X := Min(X, pmin.X);
+          pmin.Y := Min(Y, pmin.Y);
           Width := StrToIntDef(GetNext(), 16);
           Height := StrToIntDef(GetNext(), 16);
           ActivateType := StrToIntDef(GetNext(), 0);
@@ -2454,6 +2497,34 @@ begin
 
           for j := 0 to 127 do
             Data.Default[j] := StrToIntDef(GetNext(), 0);
+
+          case TriggerType of
+            TRIGGER_TELEPORT:
+              begin
+                pmin.X := Min(Data.TargetPoint.X, pmin.X);
+                pmin.Y := Min(Data.TargetPoint.Y, pmin.Y);
+              end;
+            TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF:
+              begin
+                pmin.X := Min(Data.tX, pmin.X);
+                pmin.Y := Min(Data.tY, pmin.Y);
+              end;
+            TRIGGER_SPAWNMONSTER:
+              begin
+                pmin.X := Min(Data.MonPos.X, pmin.X);
+                pmin.Y := Min(Data.MonPos.Y, pmin.Y);
+              end;
+            TRIGGER_SPAWNITEM:
+              begin
+                pmin.X := Min(Data.ItemPos.X, pmin.X);
+                pmin.Y := Min(Data.ItemPos.Y, pmin.Y);
+              end;
+            TRIGGER_SHOT:
+              begin
+                pmin.X := Min(Data.ShotPos.X, pmin.X);
+                pmin.Y := Min(Data.ShotPos.Y, pmin.Y);
+              end;
+          end;
         end;
     end;
   end;
@@ -2498,6 +2569,7 @@ procedure TMainForm.aRecentFileExecute(Sender: TObject);
 var
   n, pw: Integer;
   s, fn: String;
+  b: Boolean;
 begin
   s := LowerCase((Sender as TMenuItem).Caption);
   Delete(s, Pos('&', s), 1);
@@ -2509,17 +2581,31 @@ begin
 
   s := RecentFiles[n];
   pw := Pos('.wad:\', LowerCase(s));
+  b := False;
   
   if pw > 0 then
     begin // Map name included
       fn := Copy(s, 1, pw + 3);
       Delete(s, 1, pw + 5);
       if (FileExists(fn)) then
+      begin
         OpenMap(fn, s);
+        b := True;
+      end;
     end
   else // Only wad name
     if (FileExists(s)) then
+    begin
       OpenMap(s, '');
+      b := True;
+    end;
+
+  if (not b) and (MessageBox(0, PChar(_lc[I_MSG_DEL_RECENT_PROMT]),
+    PChar(_lc[I_MSG_DEL_RECENT]), MB_ICONQUESTION or MB_YESNO) = idYes) then
+  begin
+    RecentFiles.Delete(n);
+    RefreshRecentMenu();
+  end;
 end;
 
 procedure TMainForm.aEditorOptionsExecute(Sender: TObject);
@@ -2532,21 +2618,15 @@ var
   cwdt, chgt: Byte;
   spc: ShortInt;
   ID: DWORD;
-  wad: TWADEditor_1;
   cfgdata: Pointer;
   cfglen: Integer;
   config: TConfig;
 begin
-  cfglen := 0;
-
-  wad := TWADEditor_1.Create;
-  if wad.ReadFile(EditorDir+'data\Game.wad') then
-    wad.GetResource('FONTS', cfgres, cfgdata, cfglen);
-  wad.Free();
-
-  if cfglen <> 0 then
+  ID := 0;
+  g_ReadResource(EditorDir + 'data/game.wad', 'FONTS', cfgres, cfgdata, cfglen);
+  if cfgdata <> nil then
   begin
-    if not g_CreateTextureWAD('FONT_STD', EditorDir+'data\Game.wad:FONTS\'+texture) then
+    if not g_CreateTextureWAD('FONT_STD', EditorDir + 'data/game.wad:FONTS\' + texture) then
       e_WriteLog('ERROR ERROR ERROR', MSG_WARNING);
 
     config := TConfig.CreateMem(cfgdata, cfglen);
@@ -2555,14 +2635,15 @@ begin
     spc := Min(Max(config.ReadInt('FontMap', 'Kerning', 0), -128), 127);
 
     if g_GetTexture('FONT_STD', ID) then
-      e_TextureFontBuild(ID, FontID, cwdt, chgt, spc-2);
+      e_TextureFontBuild(ID, FontID, cwdt, chgt, spc - 2);
 
     config.Free();
+    FreeMem(cfgdata)
   end
   else
-    e_WriteLog('Could not load FONT_STD', MSG_WARNING);
-
-  if cfglen <> 0 then FreeMem(cfgdata);
+  begin
+    e_WriteLog('Could not load FONT_STD', MSG_WARNING)
+  end
 end;
 
 procedure TMainForm.FormCreate(Sender: TObject);
@@ -2595,8 +2676,16 @@ begin
   OpenedMap := '';
   OpenedWAD := '';
 
-  config := TConfig.CreateFile(EditorDir+'\Editor.cfg');
+  config := TConfig.CreateFile(EditorDir+'Editor.cfg');
 
+  if config.ReadInt('Editor', 'XPos', -1) = -1 then
+    Position := poDesktopCenter
+  else begin
+    Left := config.ReadInt('Editor', 'XPos', Left);
+    Top := config.ReadInt('Editor', 'YPos', Top);
+    Width := config.ReadInt('Editor', 'Width', Width);
+    Height := config.ReadInt('Editor', 'Height', Height);
+  end;
   if config.ReadBool('Editor', 'Maximize', False) then
     WindowState := wsMaximized;
   ShowMap := config.ReadBool('Editor', 'Minimap', False);
@@ -2614,6 +2703,7 @@ begin
   DrawPanelSize := config.ReadBool('Editor', 'DrawPanelSize', True);
   BackColor := config.ReadInt('Editor', 'BackColor', $7F6040);
   PreviewColor := config.ReadInt('Editor', 'PreviewColor', $00FF00);
+  UseCheckerboard := config.ReadBool('Editor', 'UseCheckerboard', True);
   gColorEdge := config.ReadInt('Editor', 'EdgeColor', COLOR_EDGE);
   gAlphaEdge := config.ReadInt('Editor', 'EdgeAlpha', ALPHA_EDGE);
   if gAlphaEdge = 255 then
@@ -2631,6 +2721,8 @@ begin
   gAlphaTriggerArea := config.ReadInt('Editor', 'TriggerAlpha', ALPHA_AREA);
   if gAlphaTriggerArea = 255 then
     gAlphaTriggerArea := ALPHA_AREA;
+  gAlphaMonsterRect := config.ReadInt('Editor', 'MonsterRectAlpha', 0);
+  gAlphaAreaRect := config.ReadInt('Editor', 'AreaRectAlpha', 0);
   if config.ReadInt('Editor', 'Scale', 0) = 1 then
     Scale := 2
   else
@@ -2645,6 +2737,9 @@ begin
   s := config.ReadStr('Editor', 'Language', '');
   gLanguage := s;
 
+  Compress := config.ReadBool('Editor', 'Compress', True);
+  Backup := config.ReadBool('Editor', 'Backup', True);
+
   RecentCount := config.ReadInt('Editor', 'RecentCount', 5);
   if RecentCount > 10 then
     RecentCount := 10;
@@ -2686,6 +2781,11 @@ var
   ObjCount: Word;
   aX, aY, aX2, aY2, XX, ScaleSz: Integer;
 begin
+  ID := 0;
+  PID := 0;
+  Width := 0;
+  Height := 0;
+
   e_BeginRender();
 
   e_Clear(GL_COLOR_BUFFER_BIT,
@@ -2730,7 +2830,7 @@ begin
   end;
 
 // Рисуем сетку:
-  if DotEnable and (not PreviewMode) then
+  if DotEnable and (PreviewMode = 0) then
   begin
     if DotSize = 2 then
       a := -1
@@ -2747,13 +2847,19 @@ begin
 
 // Превью текстуры:
   if (lbTextureList.ItemIndex <> -1) and (cbPreview.Checked) and
-     (not IsSpecialTextureSel()) and (not PreviewMode) then
+     (not IsSpecialTextureSel()) and (PreviewMode = 0) then
   begin
     if not g_GetTexture(SelectedTexture(), ID) then
       g_GetTexture('NOTEXTURE', ID);
     g_GetTextureSizeByID(ID, Width, Height);
-    if g_GetTexture('PREVIEW', PID) then
-       e_DrawFill(PID, RenderPanel.Width-Width, RenderPanel.Height-Height, Width div 16 + 1, Height div 16 + 1, 0, True, False);
+    if UseCheckerboard then
+    begin
+      if g_GetTexture('PREVIEW', PID) then
+        e_DrawFill(PID, RenderPanel.Width-Width, RenderPanel.Height-Height, Width div 16 + 1, Height div 16 + 1, 0, True, False);
+    end else
+      e_DrawFillQuad(RenderPanel.Width-Width-2, RenderPanel.Height-Height-2,
+                     RenderPanel.Width-1, RenderPanel.Height-1,
+                     GetRValue(PreviewColor), GetGValue(PreviewColor), GetBValue(PreviewColor), 0);
     e_Draw(ID, RenderPanel.Width-Width, RenderPanel.Height-Height, 0, True, False);
   end;
 
@@ -2842,8 +2948,9 @@ begin
       g_GetTexture('NOTEXTURE', ID);
     g_GetTextureSizeByID(ID, Width, Height);
     with DrawRect^ do
-      e_DrawFill(ID, Min(Left, Right), Min(Top, Bottom), Abs(Right-Left) div Width,
-                 Abs(Bottom-Top) div Height, 0, True, False);
+      if (Abs(Right-Left) >= Width) and (Abs(Bottom-Top) >= Height) then
+        e_DrawFill(ID, Min(Left, Right), Min(Top, Bottom), Abs(Right-Left) div Width,
+                   Abs(Bottom-Top) div Height, 64, True, False);
   end;
 
 // Прямоугольник выделения:
@@ -2852,7 +2959,8 @@ begin
       e_DrawQuad(Left, Top, Right-1, Bottom-1, 255, 255, 255);
 
 // Чертим мышью панель/триггер или меняем мышью их размер:
-  if (MouseAction in [MOUSEACTION_DRAWPANEL, MOUSEACTION_DRAWTRIGGER, MOUSEACTION_RESIZE]) and
+  if (((MouseAction in [MOUSEACTION_DRAWPANEL, MOUSEACTION_DRAWTRIGGER]) and
+     not(ssCtrl in GetKeyShiftState())) or (MouseAction = MOUSEACTION_RESIZE)) and
      (DrawPanelSize) then
   begin
     e_DrawFillQuad(MousePos.X, MousePos.Y, MousePos.X+88, MousePos.Y+33, 192, 192, 192, 127);
@@ -2862,7 +2970,7 @@ begin
       begin // Чертим новый
         PrintBlack(MousePos.X+2, MousePos.Y+2, Format(_glc[I_HINT_WIDTH],
                           [Abs(MousePos.X-MouseLDownPos.X)]), gEditorFont);
-        PrintBlack(MousePos.X+2, MousePos.Y+14, Format(_glc[I_HINT_HEIGHT],
+        PrintBlack(MousePos.X+2, MousePos.Y+16, Format(_glc[I_HINT_HEIGHT],
                           [Abs(MousePos.Y-MouseLDownPos.Y)]), gEditorFont);
       end
     else // Растягиваем существующий
@@ -2881,7 +2989,7 @@ begin
 
         PrintBlack(MousePos.X+2, MousePos.Y+2, Format(_glc[I_HINT_WIDTH], [Width]),
                           gEditorFont);
-        PrintBlack(MousePos.X+2, MousePos.Y+14, Format(_glc[I_HINT_HEIGHT], [Height]),
+        PrintBlack(MousePos.X+2, MousePos.Y+16, Format(_glc[I_HINT_HEIGHT], [Height]),
                           gEditorFont);
       end;
   end;
@@ -3501,10 +3609,24 @@ procedure TMainForm.RenderPanelMouseUp(Sender: TObject;
 var
   panel: TPanel;
   trigger: TTrigger;
-  i: Integer;
-  IDArray: DWArray;
   rRect: TRectWH;
   rSelectRect: Boolean;
+  wWidth, wHeight: Word;
+  TextureID: DWORD;
+
+  procedure SelectObjects(ObjectType: Byte);
+  var
+    i: Integer;
+    IDArray: DWArray;
+  begin
+    IDArray := ObjectInRect(rRect.X, rRect.Y,
+                 rRect.Width, rRect.Height,
+                 ObjectType, rSelectRect);
+
+    if IDArray <> nil then
+      for i := 0 to High(IDArray) do
+        SelectObject(ObjectType, IDArray[i], (ssCtrl in Shift) or rSelectRect);
+  end;
 begin
   if Button = mbLeft then
     MouseLDown := False;
@@ -3513,14 +3635,18 @@ begin
 
   DrawRect := nil;
   ResizeType := RESIZETYPE_NONE;
+  TextureID := 0;
 
   if Button = mbLeft then // Left Mouse Button
     begin
       if MouseAction <> MOUSEACTION_NONE then
       begin // Было действие мышью
-      // Мышь сдвинулась во время удержания клавиши:
-        if (MousePos.X <> MouseLDownPos.X) and
-           (MousePos.Y <> MouseLDownPos.Y) then
+      // Мышь сдвинулась во время удержания клавиши,
+      // либо активирован режим быстрого рисования:
+        if ((MousePos.X <> MouseLDownPos.X) and
+           (MousePos.Y <> MouseLDownPos.Y)) or
+           ((MouseAction in [MOUSEACTION_DRAWPANEL, MOUSEACTION_DRAWTRIGGER]) and
+           (ssCtrl in Shift)) then
           case MouseAction of
           // Рисовали панель:
             MOUSEACTION_DRAWPANEL:
@@ -3550,8 +3676,25 @@ begin
 
                     Panel.X := Min(MousePos.X-MapOffset.X, MouseLDownPos.X-MapOffset.X);
                     Panel.Y := Min(MousePos.Y-MapOffset.Y, MouseLDownPos.Y-MapOffset.Y);
-                    Panel.Width := Abs(MousePos.X-MouseLDownPos.X);
-                    Panel.Height := Abs(MousePos.Y-MouseLDownPos.Y);
+                    if ssCtrl in Shift then
+                      begin
+                        wWidth := DotStep;
+                        wHeight := DotStep;
+                        if (lbTextureList.ItemIndex <> -1) and
+                           (not IsSpecialTextureSel()) then
+                        begin
+                          if not g_GetTexture(SelectedTexture(), TextureID) then
+                            g_GetTexture('NOTEXTURE', TextureID);
+                          g_GetTextureSizeByID(TextureID, wWidth, wHeight);
+                        end;
+                        Panel.Width := wWidth;
+                        Panel.Height := wHeight;
+                      end
+                    else
+                      begin
+                        Panel.Width := Abs(MousePos.X-MouseLDownPos.X);
+                        Panel.Height := Abs(MousePos.Y-MouseLDownPos.Y);
+                      end;
 
                   // Лифты, блокМон или отсутствие текстуры - пустая текстура:
                     if (lbPanelType.ItemIndex in [9, 10, 11, 12, 13]) or
@@ -3593,8 +3736,18 @@ begin
               begin
                 trigger.X := Min(MousePos.X-MapOffset.X, MouseLDownPos.X-MapOffset.X);
                 trigger.Y := Min(MousePos.Y-MapOffset.Y, MouseLDownPos.Y-MapOffset.Y);
-                trigger.Width := Abs(MousePos.X-MouseLDownPos.X);
-                trigger.Height := Abs(MousePos.Y-MouseLDownPos.Y);
+                if ssCtrl in Shift then
+                  begin
+                    wWidth := DotStep;
+                    wHeight := DotStep;
+                    trigger.Width := wWidth;
+                    trigger.Height := wHeight;
+                  end
+                else
+                  begin
+                    trigger.Width := Abs(MousePos.X-MouseLDownPos.X);
+                    trigger.Height := Abs(MousePos.Y-MouseLDownPos.Y);
+                  end;
 
                 trigger.Enabled := True;
                 trigger.TriggerType := lbTriggersList.ItemIndex+1;
@@ -3839,14 +3992,16 @@ begin
         RemoveSelectFromObjects();
 
     // Выделяем всё в выбранном прямоугольнике:
-      IDArray := ObjectInRect(rRect.X, rRect.Y,
-                   rRect.Width, rRect.Height,
-                   pcObjects.ActivePageIndex+1, rSelectRect);
-
-      if IDArray <> nil then
-        for i := 0 to High(IDArray) do
-          SelectObject(pcObjects.ActivePageIndex+1, IDArray[i],
-            (ssCtrl in Shift) or rSelectRect);
+      if (ssCtrl in Shift) and (ssAlt in Shift) then
+        begin
+          SelectObjects(OBJECT_PANEL);
+          SelectObjects(OBJECT_ITEM);
+          SelectObjects(OBJECT_MONSTER);
+          SelectObjects(OBJECT_AREA);
+          SelectObjects(OBJECT_TRIGGER);
+        end
+      else
+        SelectObjects(pcObjects.ActivePageIndex+1);
 
       FillProperty();
     end;
@@ -3857,14 +4012,30 @@ begin
   Draw();
 end;
 
+function TMainForm.RenderMousePos(): Types.TPoint;
+begin
+  Result := RenderPanel.ScreenToClient(Mouse.CursorPos);
+end;
+
+procedure TMainForm.RecountSelectedObjects();
+begin
+  if SelectedObjectCount() = 0 then
+    StatusBar.Panels[0].Text := ''
+  else
+    StatusBar.Panels[0].Text := Format(_lc[I_CAP_STAT_SELECTED], [SelectedObjectCount()]);
+end;
+
 procedure TMainForm.RenderPanelMouseMove(Sender: TObject;
   Shift: TShiftState; X, Y: Integer);
 var
   sX, sY: Integer;
   dWidth, dHeight: Integer;
   _id: Integer;
+  TextureID: DWORD;
+  wWidth, wHeight: Word;
 begin
   _id := GetFirstSelected();
+  TextureID := 0;
 
 // Рисуем панель с текстурой, сетка - размеры текстуры:
   if (MouseAction = MOUSEACTION_DRAWPANEL) and
@@ -3962,11 +4133,10 @@ begin
               RESIZEDIR_LEFT: dWidth := -dWidth;
             end;
 
-            ResizeObject(SelectedObjects[GetFirstSelected].ObjectType,
-                         SelectedObjects[GetFirstSelected].ID,
-                         dWidth, dHeight, ResizeDirection);
-
-            LastMovePoint := MousePos;
+            if ResizeObject(SelectedObjects[GetFirstSelected].ObjectType,
+                           SelectedObjects[GetFirstSelected].ID,
+                           dWidth, dHeight, ResizeDirection) then
+              LastMovePoint := MousePos;
           end;
         end;
   end;
@@ -3981,10 +4151,29 @@ begin
       begin
         if DrawRect = nil then
           New(DrawRect);
-        DrawRect.Top := MouseLDownPos.y;
-        DrawRect.Left := MouseLDownPos.x;
-        DrawRect.Bottom := MousePos.y;
-        DrawRect.Right := MousePos.x;
+        if ssCtrl in Shift then
+          begin
+            wWidth := DotStep;
+            wHeight := DotStep;
+            if (lbTextureList.ItemIndex <> -1) and (not IsSpecialTextureSel()) and
+               (MouseAction = MOUSEACTION_DRAWPANEL) then
+            begin
+              if not g_GetTexture(SelectedTexture(), TextureID) then
+                g_GetTexture('NOTEXTURE', TextureID);
+              g_GetTextureSizeByID(TextureID, wWidth, wHeight);
+            end;
+            DrawRect.Top := MouseLDownPos.y;
+            DrawRect.Left := MouseLDownPos.x;
+            DrawRect.Bottom := DrawRect.Top + wHeight;
+            DrawRect.Right := DrawRect.Left + wWidth;
+          end
+        else
+          begin
+            DrawRect.Top := MouseLDownPos.y;
+            DrawRect.Left := MouseLDownPos.x;
+            DrawRect.Bottom := MousePos.y;
+            DrawRect.Right := MousePos.x;
+          end;
       end
     else // Двигаем карту:
       if MouseAction = MOUSEACTION_MOVEMAP then
@@ -4020,8 +4209,22 @@ var
   config: TConfig;
   i: Integer;
 begin
-  config := TConfig.CreateFile(EditorDir+'\Editor.cfg');
+  config := TConfig.CreateFile(EditorDir+'Editor.cfg');
 
+  if WindowState <> wsMaximized then
+  begin
+    config.WriteInt('Editor', 'XPos', Left);
+    config.WriteInt('Editor', 'YPos', Top);
+    config.WriteInt('Editor', 'Width', Width);
+    config.WriteInt('Editor', 'Height', Height);
+  end
+  else
+  begin
+    config.WriteInt('Editor', 'XPos', RestoredLeft);
+    config.WriteInt('Editor', 'YPos', RestoredTop);
+    config.WriteInt('Editor', 'Width', RestoredWidth);
+    config.WriteInt('Editor', 'Height', RestoredHeight);
+  end;
   config.WriteBool('Editor', 'Maximize', WindowState = wsMaximized);
   config.WriteBool('Editor', 'Minimap', ShowMap);
   config.WriteInt('Editor', 'PanelProps', PanelProps.ClientWidth);
@@ -4035,6 +4238,8 @@ begin
   config.WriteInt('Editor', 'EdgeAlpha', gAlphaEdge);
   config.WriteInt('Editor', 'LineAlpha', gAlphaTriggerLine);
   config.WriteInt('Editor', 'TriggerAlpha', gAlphaTriggerArea);
+  config.WriteInt('Editor', 'MonsterRectAlpha', gAlphaMonsterRect);
+  config.WriteInt('Editor', 'AreaRectAlpha', gAlphaAreaRect);
 
   for i := 0 to RecentCount-1 do
     if i < RecentFiles.Count then
@@ -4043,18 +4248,32 @@ begin
       config.WriteStr('RecentFiles', IntToStr(i+1), '');
   RecentFiles.Free();
 
-  config.SaveFile(EditorDir+'\Editor.cfg');
+  config.SaveFile(EditorDir+'Editor.cfg');
   config.Free();
 
   slInvalidTextures.Free;
 end;
 
+procedure TMainForm.FormDropFiles(Sender: TObject;
+  const FileNames: array of String);
+begin
+  if Length(FileNames) <> 1 then
+    Exit;
+
+  OpenMapFile(FileNames[0]);
+end;
+
 procedure TMainForm.RenderPanelResize(Sender: TObject);
 begin
   if MainForm.Visible then
     MainForm.Resize();
 end;
 
+procedure TMainForm.Splitter1Moved(Sender: TObject);
+begin
+  FormResize(Sender);
+end;
+
 procedure TMainForm.aMapOptionsExecute(Sender: TObject);
 var
   ResName: String;
@@ -4141,17 +4360,44 @@ begin
 
     if not (ssCtrl in Shift) then
     begin
+    // Быстрое превью карты:
+      if Key = Ord('E') then
+      begin
+        if PreviewMode = 0 then
+          PreviewMode := 2;
+      end;
+
     // Вертикальный скролл карты:
       with sbVertical do
       begin
         if Key = Ord('W') then
         begin
+          if (MouseLDown or MouseRDown) and (Position >= DotStep) then
+          begin
+            if DrawRect <> nil then
+            begin
+              Inc(MouseLDownPos.y, DotStep);
+              Inc(MouseRDownPos.y, DotStep);
+            end;
+            Inc(LastMovePoint.Y, DotStep);
+            RenderPanelMouseMove(Sender, Shift, RenderMousePos().X, RenderMousePos().Y);
+          end;
           Position := IfThen(Position > DotStep, Position-DotStep, 0);
           MapOffset.Y := -Round(Position/16) * 16;
         end;
 
         if Key = Ord('S') then
         begin
+          if (MouseLDown or MouseRDown) and (Position+DotStep <= Max) then
+          begin
+            if DrawRect <> nil then
+            begin
+              Dec(MouseLDownPos.y, DotStep);
+              Dec(MouseRDownPos.y, DotStep);
+            end;
+            Dec(LastMovePoint.Y, DotStep);
+            RenderPanelMouseMove(Sender, Shift, RenderMousePos().X, RenderMousePos().Y);
+          end;
           Position := IfThen(Position+DotStep < Max, Position+DotStep, Max);
           MapOffset.Y := -Round(Position/16) * 16;
         end;
@@ -4162,16 +4408,46 @@ begin
       begin
         if Key = Ord('A') then
         begin
+          if (MouseLDown or MouseRDown) and (Position >= DotStep) then
+          begin
+            if DrawRect <> nil then
+            begin
+              Inc(MouseLDownPos.x, DotStep);
+              Inc(MouseRDownPos.x, DotStep);
+            end;
+            Inc(LastMovePoint.X, DotStep);
+            RenderPanelMouseMove(Sender, Shift, RenderMousePos().X, RenderMousePos().Y);
+          end;
           Position := IfThen(Position > DotStep, Position-DotStep, 0);
           MapOffset.X := -Round(Position/16) * 16;
         end;
 
         if Key = Ord('D') then
         begin
+          if (MouseLDown or MouseRDown) and (Position+DotStep <= Max) then
+          begin
+            if DrawRect <> nil then
+            begin
+              Dec(MouseLDownPos.x, DotStep);
+              Dec(MouseRDownPos.x, DotStep);
+            end;
+            Dec(LastMovePoint.X, DotStep);
+            RenderPanelMouseMove(Sender, Shift, RenderMousePos().X, RenderMousePos().Y);
+          end;
           Position := IfThen(Position+DotStep < Max, Position+DotStep, Max);
           MapOffset.X := -Round(Position/16) * 16;
         end;
       end;
+    end
+    else // ssCtrl in Shift
+    begin
+      if ssShift in Shift then
+      begin
+      // Вставка по абсолютному смещению:
+        if Key = Ord('V') then
+          aPasteObjectExecute(Sender);
+      end;
+      RenderPanelMouseMove(Sender, Shift, RenderMousePos().X, RenderMousePos().Y);
     end;
   end;
 
@@ -4230,6 +4506,7 @@ begin
         DrawPressRect := False;
         Exit;
       end;
+      i := -1;
 
     // Выбор области воздействия, в зависимости от типа триггера
       vleObjectProperty.FindRow(_lc[I_PROP_TR_EX_AREA], i);
@@ -4270,6 +4547,7 @@ begin
       if i > 0 then
       begin
         g_ProcessResourceStr(OpenedMap, @FileName, nil, nil);
+        SelectMapForm.Caption := _lc[I_CAP_SELECT];
         SelectMapForm.GetMaps(FileName);
 
         if SelectMapForm.ShowModal() = mrOK then
@@ -4330,6 +4608,9 @@ var
   TextureID: DWORD;
   TextureWidth, TextureHeight: Word;
 begin
+  TextureID := 0;
+  TextureWidth := 0;
+  TextureHeight := 0;
   if (lbTextureList.ItemIndex <> -1) and
      (not IsSpecialTextureSel()) then
     begin
@@ -4352,6 +4633,42 @@ begin
     end;
 end;
 
+procedure TMainForm.lbTextureListDrawItem(Control: TWinControl; Index: Integer;
+  ARect: TRect; State: TOwnerDrawState);
+begin
+  with Control as TListBox do
+  begin
+    if LCLType.odSelected in State then
+    begin
+      Canvas.Brush.Color := clHighlight;
+      Canvas.Font.Color := clHighlightText;
+    end else
+      if (Items <> nil) and (Index >= 0) then
+        if slInvalidTextures.IndexOf(Items[Index]) > -1 then
+        begin
+          Canvas.Brush.Color := clRed;
+          Canvas.Font.Color := clWhite;
+        end;
+    Canvas.FillRect(ARect);
+    Canvas.TextRect(ARect, ARect.Left, ARect.Top, Items[Index]);
+  end;
+end;
+
+procedure TMainForm.miReopenMapClick(Sender: TObject);
+var
+  FileName, Resource: String;
+begin
+  if OpenedMap = '' then
+    Exit;
+
+  if MessageBox(0, PChar(_lc[I_MSG_REOPEN_MAP_PROMT]),
+  PChar(_lc[I_MENU_FILE_REOPEN]), MB_ICONQUESTION or MB_YESNO) <> idYes then
+    Exit;
+
+  g_ProcessResourceStr(OpenedMap, @FileName, nil, @Resource);
+  OpenMap(FileName, Resource);
+end;
+
 procedure TMainForm.vleObjectPropertyGetPickList(Sender: TObject;
   const KeyName: String; Values: TStrings);
 begin
@@ -4462,6 +4779,10 @@ var
   NoTextureID: DWORD;
   NW, NH: Word;
 begin
+  NoTextureID := 0;
+  NW := 0;
+  NH := 0;
+
   if SelectedObjectCount() <> 1 then
     Exit;
   if not SelectedObjects[GetFirstSelected()].Live then
@@ -4659,10 +4980,10 @@ begin
           case TriggerType of
             TRIGGER_EXIT:
               begin
-                s := vleObjectProperty.Values[_lc[I_PROP_TR_NEXT_MAP]];
+                s := utf2win(vleObjectProperty.Values[_lc[I_PROP_TR_NEXT_MAP]]);
                 FillByte(Data.MapName[0], 16, 0);
                 if s <> '' then
-                  Move(Data.MapName[0], s[1], Min(Length(s), 16));
+                  Move(s[1], Data.MapName[0], Min(Length(s), 16));
               end;
 
             TRIGGER_TEXTURE:
@@ -4698,10 +5019,10 @@ begin
 
             TRIGGER_SOUND:
               begin
-                s := vleObjectProperty.Values[_lc[I_PROP_TR_SOUND_NAME]];
+                s := utf2win(vleObjectProperty.Values[_lc[I_PROP_TR_SOUND_NAME]]);
                 FillByte(Data.SoundName[0], 64, 0);
                 if s <> '' then
-                  Move(Data.SoundName[0], s[1], Min(Length(s), 64));
+                  Move(s[1], Data.SoundName[0], Min(Length(s), 64));
 
                 Data.Volume := Min(StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_SOUND_VOLUME]], 0), 255);
                 Data.Pan := Min(StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_SOUND_PAN]], 0), 255);
@@ -4752,10 +5073,10 @@ begin
 
             TRIGGER_MUSIC:
               begin
-                s := vleObjectProperty.Values[_lc[I_PROP_TR_MUSIC_NAME]];
+                s := utf2win(vleObjectProperty.Values[_lc[I_PROP_TR_MUSIC_NAME]]);
                 FillByte(Data.MusicName[0], 64, 0);
                 if s <> '' then
-                  Move(Data.MusicName[0], s[1], Min(Length(s), 64));
+                  Move(s[1], Data.MusicName[0], Min(Length(s), 64));
 
                 if vleObjectProperty.Values[_lc[I_PROP_TR_MUSIC_ACT]] = _lc[I_PROP_TR_MUSIC_ON] then
                   Data.MusicAction := 1
@@ -4812,10 +5133,10 @@ begin
                 else if vleObjectProperty.Values[_lc[I_PROP_TR_MESSAGE_TO]] = _lc[I_PROP_TR_MESSAGE_TO_5] then
                   Data.MessageSendTo := 5;
 
-                s := vleObjectProperty.Values[_lc[I_PROP_TR_MESSAGE_TEXT]];
+                s := utf2win(vleObjectProperty.Values[_lc[I_PROP_TR_MESSAGE_TEXT]]);
                 FillByte(Data.MessageText[0], 100, 0);
                 if s <> '' then
-                  Move(Data.MessageText[0], s[1], Min(Length(s), 100));
+                  Move(s[1], Data.MessageText[0], Min(Length(s), 100));
 
                 Data.MessageTime := Min(Max(
                   StrToIntDef(vleObjectProperty.Values[_lc[I_PROP_TR_MESSAGE_TIME]], 0), 0), 65535);
@@ -5222,12 +5543,18 @@ procedure TMainForm.aPasteObjectExecute(Sender: TObject);
 var
   a, h: Integer;
   CopyBuffer: TCopyRecArray;
-  res: Boolean;
+  res, rel: Boolean;
   swad, ssec, sres: String;
+  NoTextureID: DWORD;
+  pmin: TPoint;
 begin
   CopyBuffer := nil;
+  NoTextureID := 0;
+  pmin.X := High(pmin.X);
+  pmin.Y := High(pmin.Y);
 
-  StringToCopyBuffer(ClipBoard.AsText, CopyBuffer);
+  StringToCopyBuffer(ClipBoard.AsText, CopyBuffer, pmin);
+  rel := not(ssShift in GetKeyShiftState());
 
   if CopyBuffer = nil then
     Exit;
@@ -5242,8 +5569,11 @@ begin
         OBJECT_PANEL:
           if Panel <> nil then
           begin
-            Panel^.X := Panel^.X + 16;
-            Panel^.Y := Panel^.Y + 16;
+            if rel then
+            begin
+              Panel^.X := Panel^.X - pmin.X - MapOffset.X + 32;
+              Panel^.Y := Panel^.Y - pmin.Y - MapOffset.Y + 32;
+            end;
 
             Panel^.TextureID := TEXTURE_SPECIAL_NONE;
             Panel^.TextureWidth := 1;
@@ -5275,7 +5605,11 @@ begin
                       g_GetTextureSizeByName(Panel^.TextureName,
                         Panel^.TextureWidth, Panel^.TextureHeight)
                     else
-                      Panel^.TextureName := '';
+                      if g_GetTexture('NOTEXTURE', NoTextureID) then
+                      begin
+                        Panel^.TextureID := TEXTURE_SPECIAL_NOTEXTURE;
+                        g_GetTextureSizeByID(NoTextureID, Panel^.TextureWidth, Panel^.TextureHeight);
+                      end;
                   end
                 else // Спец.текстура:
                   begin
@@ -5294,8 +5628,11 @@ begin
 
         OBJECT_ITEM:
           begin
-            Item.X := Item.X + 16;
-            Item.Y := Item.Y + 16;
+            if rel then
+            begin
+              Item.X := Item.X - pmin.X - MapOffset.X + 32;
+              Item.Y := Item.Y - pmin.Y - MapOffset.Y + 32;
+            end;
 
             ID := AddItem(Item);
             Undo_Add(OBJECT_ITEM, ID, a > 0);
@@ -5304,8 +5641,11 @@ begin
 
         OBJECT_MONSTER:
           begin
-            Monster.X := Monster.X + 16;
-            Monster.Y := Monster.Y + 16;
+            if rel then
+            begin
+              Monster.X := Monster.X - pmin.X - MapOffset.X + 32;
+              Monster.Y := Monster.Y - pmin.Y - MapOffset.Y + 32;
+            end;
 
             ID := AddMonster(Monster);
             Undo_Add(OBJECT_MONSTER, ID, a > 0);
@@ -5314,8 +5654,11 @@ begin
 
         OBJECT_AREA:
           begin
-            Area.X := Area.X + 16;
-            Area.Y := Area.Y + 16;
+            if rel then
+            begin
+              Area.X := Area.X - pmin.X - MapOffset.X + 32;
+              Area.Y := Area.Y - pmin.Y - MapOffset.Y + 32;
+            end;
 
             ID := AddArea(Area);
             Undo_Add(OBJECT_AREA, ID, a > 0);
@@ -5324,8 +5667,48 @@ begin
 
         OBJECT_TRIGGER:
           begin
-            Trigger.X := Trigger.X + 16;
-            Trigger.Y := Trigger.Y + 16;
+            if rel then
+              with Trigger do
+              begin
+                X := X - pmin.X - MapOffset.X + 32;
+                Y := Y - pmin.Y - MapOffset.Y + 32;
+
+                case TriggerType of
+                  TRIGGER_TELEPORT:
+                    begin
+                      Data.TargetPoint.X :=
+                      Data.TargetPoint.X - pmin.X - MapOffset.X + 32;
+                      Data.TargetPoint.Y :=
+                      Data.TargetPoint.Y - pmin.Y - MapOffset.Y + 32;
+                    end;
+                  TRIGGER_PRESS, TRIGGER_ON, TRIGGER_OFF, TRIGGER_ONOFF:
+                    begin
+                      Data.tX := Data.tX - pmin.X - MapOffset.X + 32;
+                      Data.tY := Data.tY - pmin.Y - MapOffset.Y + 32;
+                    end;
+                  TRIGGER_SPAWNMONSTER:
+                    begin
+                      Data.MonPos.X :=
+                      Data.MonPos.X - pmin.X - MapOffset.X + 32;
+                      Data.MonPos.Y :=
+                      Data.MonPos.Y - pmin.Y - MapOffset.Y + 32;
+                    end;
+                  TRIGGER_SPAWNITEM:
+                    begin
+                      Data.ItemPos.X :=
+                      Data.ItemPos.X - pmin.X - MapOffset.X + 32;
+                      Data.ItemPos.Y :=
+                      Data.ItemPos.Y - pmin.Y - MapOffset.Y + 32;
+                    end;
+                  TRIGGER_SHOT:
+                    begin
+                      Data.ShotPos.X :=
+                      Data.ShotPos.X - pmin.X - MapOffset.X + 32;
+                      Data.ShotPos.Y :=
+                      Data.ShotPos.Y - pmin.Y - MapOffset.Y + 32;
+                    end;
+                end;
+              end;
 
             ID := AddTrigger(Trigger);
             Undo_Add(OBJECT_TRIGGER, ID, a > 0);
@@ -5400,7 +5783,7 @@ begin
         begin
           b := lbTypeSelect.ItemIndex;
           Values[Key] := PANELNAMES[b];
-          bApplyProperty.Click();
+          vleObjectPropertyApply(Sender);
         end;
       end
     end
@@ -5430,12 +5813,13 @@ begin
   else if Key = _lc[I_PROP_TR_NEXT_MAP] then
     begin // Выбор следующей карты:
       g_ProcessResourceStr(OpenedMap, @FileName, nil, nil);
+      SelectMapForm.Caption := _lc[I_CAP_SELECT];
       SelectMapForm.GetMaps(FileName);
 
       if SelectMapForm.ShowModal() = mrOK then
       begin
         vleObjectProperty.Values[Key] := SelectMapForm.lbMapList.Items[SelectMapForm.lbMapList.ItemIndex];
-        bApplyProperty.Click();
+        vleObjectPropertyApply(Sender);
       end;
     end
   else if (Key = _lc[I_PROP_TR_SOUND_NAME]) or
@@ -5448,7 +5832,7 @@ begin
       if (AddSoundForm.ShowModal() = mrOk) then
       begin
         vleObjectProperty.Values[Key] := AddSoundForm.ResourceName;
-        bApplyProperty.Click();
+        vleObjectPropertyApply(Sender);
       end;
     end
   else if Key = _lc[I_PROP_TR_ACTIVATION] then
@@ -5478,7 +5862,7 @@ begin
           b := b or ACTIVATE_NOMONSTER;
 
         Values[Key] := ActivateToStr(b);
-        bApplyProperty.Click();
+        vleObjectPropertyApply(Sender);
       end;
     end
   else if Key = _lc[I_PROP_TR_KEYS] then
@@ -5505,7 +5889,7 @@ begin
           b := b or KEY_BLUETEAM;
 
         Values[Key] := KeyToStr(b);
-        bApplyProperty.Click();
+        vleObjectPropertyApply(Sender);
       end;
     end
   else if Key = _lc[I_PROP_TR_FX_TYPE] then
@@ -5523,7 +5907,7 @@ begin
       begin
         b := lbTypeSelect.ItemIndex;
         Values[Key] := EffectToStr(b);
-        bApplyProperty.Click();
+        vleObjectPropertyApply(Sender);
       end;
     end
   else if Key = _lc[I_PROP_TR_MONSTER_TYPE] then
@@ -5541,7 +5925,7 @@ begin
       begin
         b := lbTypeSelect.ItemIndex + MONSTER_DEMON;
         Values[Key] := MonsterToStr(b);
-        bApplyProperty.Click();
+        vleObjectPropertyApply(Sender);
       end;
     end
   else if Key = _lc[I_PROP_TR_ITEM_TYPE] then
@@ -5556,6 +5940,8 @@ begin
       lbTypeSelect.Items.Add(ItemToStr(ITEM_HELMET));
       lbTypeSelect.Items.Add(ItemToStr(ITEM_JETPACK));
       lbTypeSelect.Items.Add(ItemToStr(ITEM_INVIS));
+      lbTypeSelect.Items.Add(ItemToStr(ITEM_WEAPON_FLAMETHROWER));
+      lbTypeSelect.Items.Add(ItemToStr(ITEM_AMMO_FUELCAN));
 
       b := StrToItem(Values[Key]);
       if b >= ITEM_BOTTLE then
@@ -5568,7 +5954,7 @@ begin
         if b >= ITEM_WEAPON_KASTET then
           b := b + 2;
         Values[Key] := ItemToStr(b);
-        bApplyProperty.Click();
+        vleObjectPropertyApply(Sender);
       end;
     end
   else if Key = _lc[I_PROP_TR_SHOT_TYPE] then
@@ -5586,7 +5972,7 @@ begin
       begin
         b := lbTypeSelect.ItemIndex;
         Values[Key] := ShotToStr(b);
-        bApplyProperty.Click();
+        vleObjectPropertyApply(Sender);
       end;
     end
   else if Key = _lc[I_PROP_TR_EFFECT_TYPE] then
@@ -5609,7 +5995,7 @@ begin
           Values[Key] := _lc[I_PROP_TR_EFFECT_PARTICLE]
         else
           Values[Key] := _lc[I_PROP_TR_EFFECT_ANIMATION];
-        bApplyProperty.Click();
+        vleObjectPropertyApply(Sender);
       end;
     end
   else if Key = _lc[I_PROP_TR_EFFECT_SUBTYPE] then
@@ -5665,7 +6051,7 @@ begin
             Values[Key] := _lc[I_PROP_TR_EFFECT_BUBBLE];
         end;
 
-        bApplyProperty.Click();
+        vleObjectPropertyApply(Sender);
       end;
     end
   else if Key = _lc[I_PROP_TR_EFFECT_COLOR] then
@@ -5675,16 +6061,24 @@ begin
       if ColorDialog.Execute then
       begin
         Values[Key] := IntToStr(ColorDialog.Color);
-        bApplyProperty.Click();
+        vleObjectPropertyApply(Sender);
       end;
     end
   else if Key = _lc[I_PROP_PANEL_TEX] then
     begin // Смена текстуры:
       vleObjectProperty.Values[Key] := SelectedTexture();
-      bApplyProperty.Click();
+      vleObjectPropertyApply(Sender);
     end;
 end;
 
+procedure TMainForm.vleObjectPropertyApply(Sender: TObject);
+begin
+  // hack to prevent empty ID in list
+  RenderPanel.SetFocus();
+  bApplyProperty.Click();
+  vleObjectProperty.SetFocus();
+end;
+
 procedure TMainForm.aSaveMapExecute(Sender: TObject);
 var
   FileName, Section, Res: String;
@@ -5706,31 +6100,35 @@ begin
 
   if OpenDialog.Execute() then
   begin
-    if (Pos('.ini', LowerCase(ExtractFileName(OpenDialog.FileName))) > 0) then
-      begin // INI карты:
-        FullClear();
+    OpenMapFile(OpenDialog.FileName);
+    OpenDialog.InitialDir := ExtractFileDir(OpenDialog.FileName);
+  end;
+end;
 
-        pLoadProgress.Left := (RenderPanel.Width div 2)-(pLoadProgress.Width div 2);
-        pLoadProgress.Top := (RenderPanel.Height div 2)-(pLoadProgress.Height div 2);
-        pLoadProgress.Show();
+procedure TMainForm.OpenMapFile(FileName: String);
+begin
+  if (Pos('.ini', LowerCase(ExtractFileName(FileName))) > 0) then
+    begin // INI карты:
+      FullClear();
 
-        OpenedMap := '';
-        OpenedWAD := '';
+      pLoadProgress.Left := (RenderPanel.Width div 2)-(pLoadProgress.Width div 2);
+      pLoadProgress.Top := (RenderPanel.Height div 2)-(pLoadProgress.Height div 2);
+      pLoadProgress.Show();
 
-        LoadMapOld(OpenDialog.FileName);
+      OpenedMap := '';
+      OpenedWAD := '';
 
-        MainForm.Caption := Format('%s - %s', [FormCaption, ExtractFileName(OpenDialog.FileName)]);
+      LoadMapOld(FileName);
 
-        pLoadProgress.Hide();
-        MainForm.FormResize(Self);
-      end
-    else // Карты из WAD:
-      begin
-        OpenMap(OpenDialog.FileName, '');
-      end;
+      MainForm.Caption := Format('%s - %s', [FormCaption, ExtractFileName(FileName)]);
 
-    OpenDialog.InitialDir := ExtractFileDir(OpenDialog.FileName);
-  end;
+      pLoadProgress.Hide();
+      MainForm.FormResize(Self);
+    end
+  else // Карты из WAD:
+    begin
+      OpenMap(FileName, '');
+    end;
 end;
 
 procedure TMainForm.FormActivate(Sender: TObject);
@@ -5749,9 +6147,9 @@ begin
       else gLanguage := LANGUAGE_RUSSIAN;
     end;
 
-    config := TConfig.CreateFile(EditorDir+'\Editor.cfg');
+    config := TConfig.CreateFile(EditorDir+'Editor.cfg');
     config.WriteStr('Editor', 'Language', gLanguage);
-    config.SaveFile(EditorDir+'\Editor.cfg');
+    config.SaveFile(EditorDir+'Editor.cfg');
     config.Free();
   end;
 
@@ -5762,75 +6160,55 @@ end;
 
 procedure TMainForm.aDeleteMap(Sender: TObject);
 var
-  WAD: TWADEditor_1;
-  MapList: SArray;
-  MapName: Char16;
-  a: Integer;
-  str: String;
+  res: Integer;
+  FileName: String;
+  MapName: String;
 begin
   OpenDialog.Filter := _lc[I_FILE_FILTER_WAD];
 
   if not OpenDialog.Execute() then
     Exit;
 
-  WAD := TWADEditor_1.Create();
+  FileName := OpenDialog.FileName;
+  SelectMapForm.Caption := _lc[I_CAP_REMOVE];
+  SelectMapForm.lbMapList.Items.Clear();
+  SelectMapForm.GetMaps(FileName);
 
-  if not WAD.ReadFile(OpenDialog.FileName) then
-  begin
-    WAD.Free();
+  if SelectMapForm.ShowModal() <> mrOK then
     Exit;
-  end;
 
-  WAD.CreateImage();
-
-  MapList := WAD.GetResourcesList('');
-
-  SelectMapForm.lbMapList.Items.Clear();
-
-  if MapList <> nil then
-    for a := 0 to High(MapList) do
-      SelectMapForm.lbMapList.Items.Add(MapList[a]);
+  MapName := SelectMapForm.lbMapList.Items[SelectMapForm.lbMapList.ItemIndex];
+  if MessageBox(0, PChar(Format(_lc[I_MSG_DELETE_MAP_PROMT], [MapName, OpenDialog.FileName])), PChar(_lc[I_MSG_DELETE_MAP]), MB_ICONQUESTION or MB_YESNO or MB_DEFBUTTON2) <> mrYes then
+    Exit;
 
-  if (SelectMapForm.ShowModal() = mrOK) then
+  g_DeleteResource(FileName, '', MapName, res);
+  if res <> 0 then
   begin
-    str := SelectMapForm.lbMapList.Items[SelectMapForm.lbMapList.ItemIndex];
-    MapName := '';
-    Move(MapName[0], str[1], Min(16, Length(str)));
-
-    if MessageBox(0, PChar(Format(_lc[I_MSG_DELETE_MAP_PROMT],
-                           [MapName, OpenDialog.FileName])),
-                  PChar(_lc[I_MSG_DELETE_MAP]),
-                  MB_ICONQUESTION or MB_YESNO or
-                  MB_DEFBUTTON2) <> mrYes then
-      Exit;
-
-    WAD.RemoveResource('', MapName);
-    
-    MessageBox(0, PChar(Format(_lc[I_MSG_MAP_DELETED_PROMT],
-                               [MapName])),
-               PChar(_lc[I_MSG_MAP_DELETED]),
-               MB_ICONINFORMATION or MB_OK or
-               MB_DEFBUTTON1);
+    MessageBox(0, PChar('Cant delete map res=' + IntToStr(res)), PChar('Map not deleted!'), MB_ICONINFORMATION or MB_OK or MB_DEFBUTTON1);
+    Exit
+  end;
 
-    WAD.SaveTo(OpenDialog.FileName);
+  MessageBox(
+    0,
+    PChar(Format(_lc[I_MSG_MAP_DELETED_PROMT], [MapName])),
+    PChar(_lc[I_MSG_MAP_DELETED]),
+    MB_ICONINFORMATION or MB_OK or MB_DEFBUTTON1
+  );
 
   // Удалили текущую карту - сохранять по старому ее нельзя:
-    if OpenedMap = (OpenDialog.FileName+':\'+MapName) then
-    begin
-      OpenedMap := '';
-      OpenedWAD := '';
-      MainForm.Caption := FormCaption;
-    end;
-  end;
-
-  WAD.Free();
+  if OpenedMap = (FileName + ':\' + MapName) then
+  begin
+    OpenedMap := '';
+    OpenedWAD := '';
+    MainForm.Caption := FormCaption
+  end
 end;
 
 procedure TMainForm.vleObjectPropertyKeyDown(Sender: TObject;
             var Key: Word; Shift: TShiftState);
 begin
   if Key = VK_RETURN then
-    bApplyProperty.Click();
+    vleObjectPropertyApply(Sender);
 end;
 
 procedure MovePanel(var ID: DWORD; MoveType: Byte);
@@ -6038,6 +6416,8 @@ begin
           if gTriggers[a].TriggerType <> TRIGGER_NONE then
             SelectObject(OBJECT_TRIGGER, a, True);
   end;
+
+  RecountSelectedObjects();
 end;
 
 procedure TMainForm.tbGridOnClick(Sender: TObject);
@@ -6055,7 +6435,9 @@ begin
     e_InitGL();
     e_WriteLog('Loading data', MSG_NOTIFY);
     LoadStdFont('STDTXT', 'STDFONT', gEditorFont);
+    e_WriteLog('Loading more data', MSG_NOTIFY);
     LoadData();
+    e_WriteLog('Loading even more data', MSG_NOTIFY);
     gDataLoaded := True;
     MainForm.FormResize(nil);
   end;
@@ -6064,7 +6446,10 @@ end;
 
 procedure TMainForm.miMapPreviewClick(Sender: TObject);
 begin
-  if not PreviewMode then
+  if PreviewMode = 2 then
+    Exit;
+
+  if PreviewMode = 0 then
     begin
       Splitter2.Visible := False;
       Splitter1.Visible := False;
@@ -6087,8 +6472,10 @@ begin
       sbVertical.Visible := True;
     end;
 
-  PreviewMode := not PreviewMode;
-  (Sender as TMenuItem).Checked := PreviewMode;
+  PreviewMode := PreviewMode xor 1;
+  (Sender as TMenuItem).Checked := PreviewMode > 0;
+
+  FormResize(Self);
 end;
 
 procedure TMainForm.miLayer1Click(Sender: TObject);
@@ -6221,21 +6608,33 @@ end;
 
 procedure TMainForm.miTestMapClick(Sender: TObject);
 var
-  cmd, mapWAD, mapToRun: String;
+  cmd, mapWAD, mapToRun, tempWAD: String;
   opt: LongWord;
   time: Integer;
-  lpMsgBuf: PChar;
+  proc: TProcessUTF8;
+  res: Boolean;
 begin
+  mapToRun := '';
+  if OpenedMap <> '' then
+  begin
+    // Указываем текущую карту для теста:
+    g_ProcessResourceStr(OpenedMap, @mapWAD, nil, @mapToRun);
+    mapToRun := mapWAD + ':\' + mapToRun;
+    mapToRun := ExtractRelativePath(ExtractFilePath(TestD2dExe) + 'maps/', mapToRun);
+  end;
   // Сохраняем временную карту:
   time := 0;
   repeat
-    mapWAD := ExtractFilePath(TestD2dExe) + Format('maps\temp%.4d.wad', [time]);
+    mapWAD := ExtractFilePath(TestD2dExe) + Format('maps/temp%.4d.wad', [time]);
     Inc(time);
   until not FileExists(mapWAD);
-  mapToRun := mapWAD + ':\' + TEST_MAP_NAME;
-  SaveMap(mapToRun);
+  tempWAD := mapWAD + ':\' + TEST_MAP_NAME;
+  SaveMap(tempWAD);
 
-  mapToRun := ExtractRelativePath(ExtractFilePath(TestD2dExe) + 'maps\', mapToRun);
+  tempWAD := ExtractRelativePath(ExtractFilePath(TestD2dExe) + 'maps/', tempWAD);
+// Если карта не была открыта, указываем временную в качестве текущей:
+  if mapToRun = '' then
+    mapToRun := tempWAD;
 
 // Опции игры:
   opt := 32 + 64;
@@ -6251,7 +6650,8 @@ begin
     opt := opt + 16;
 
 // Составляем командную строку:
-  cmd := ' -map "' + mapToRun + '"';
+  cmd := '-map "' + mapToRun + '"';
+  cmd := cmd + ' -testmap "' + tempWAD + '"';
   cmd := cmd + ' -gm ' + TestGameMode;
   cmd := cmd + ' -limt ' + TestLimTime;
   cmd := cmd + ' -lims ' + TestLimScore;
@@ -6261,16 +6661,29 @@ begin
     cmd := cmd + ' --close';
 
   cmd := cmd + ' --debug';
-  cmd := cmd + ' --tempdelete';
 
 // Запускаем:
-  Application.Minimize();
-  if ExecuteProcess(TestD2dExe, cmd) < 0 then
+  proc := TProcessUTF8.Create(nil);
+  proc.Executable := TestD2dExe;
+  proc.Parameters.Add(cmd);
+  res := True;
+  try
+    proc.Execute();
+  except
+    res := False;
+  end;
+  if res then
+  begin
+    Application.Minimize();
+    proc.WaitOnExit();
+  end;
+  if (not res) or (proc.ExitCode < 0) then
   begin
     MessageBox(0, 'FIXME',
                PChar(_lc[I_MSG_EXEC_ERROR]),
                MB_OK or MB_ICONERROR);
   end;
+  proc.Free();
 
   SysUtils.DeleteFile(mapWAD);
   Application.Restore();
@@ -6326,6 +6739,8 @@ begin
     for a := 0 to High(gTriggers) do
       if gTriggers[a].TriggerType <> TRIGGER_NONE then
         SelectObject(OBJECT_TRIGGER, a, True);
+
+  RecountSelectedObjects();
 end;
 
 procedure TMainForm.Splitter1CanResize(Sender: TObject;
@@ -6363,28 +6778,13 @@ begin
        (Key = Ord('V')) then
       FillProperty();
   end;
-end;
-
-{
-procedure TMainForm.lbTextureListDrawItem(Control: TWinControl; Index: Integer;
-      Rect: TRect; State: LCLType.TOwnerDrawState);
-begin
-  with Control as TListBox do
+// Быстрое превью карты:
+  if Key = Ord('E') then
   begin
-    if LCLType.odSelected in State then
-    begin
-      Canvas.Brush.Color := clHighlight;
-      Canvas.Font.Color := clHighlightText;
-    end else
-      if (Items <> nil) and (Index >= 0) then
-        if slInvalidTextures.IndexOf(Items[Index]) > -1 then
-        begin
-          Canvas.Brush.Color := clRed;
-          Canvas.Font.Color := clWhite;
-        end;
-    Canvas.FillRect(Rect);
-    Canvas.TextRect(Rect, Rect.Left, Rect.Top, Items[Index]);
+    if PreviewMode = 2 then
+      PreviewMode := 0;
   end;
+  RenderPanelMouseMove(Sender, Shift, RenderMousePos().X, RenderMousePos().Y);
 end;
-}
+
 end.