DEADSOFTWARE

gui: redesign menu
[d2df-editor.git] / src / editor / f_main.pas
index 4498ee9efd55146bcab5e8b00205ed86bb668d16..790a646a334b9ff69700d3237260483e6baf427b 100644 (file)
@@ -18,18 +18,27 @@ type
     lLoad: TLabel;
   // Главное меню:
     MainMenu: TMainMenu;
+  // Apple menu:
+    miApple: TMenuItem;
+    miAppleAbout: TMenuItem;
+    miAppleLine0: TMenuItem;
+    miApplePref: TMenuItem;
+    miAppleLine1: TMenuItem;
   // "Файл":
     miMenuFile: TMenuItem;
     miNewMap: TMenuItem;
     miOpenMap: TMenuItem;
     miSaveMap: TMenuItem;
     miSaveMapAs: TMenuItem;
+    miMacRecentSubMenu: TMenuItem;
+    miMacRecentClear: TMenuItem;
     miOpenWadMap: TMenuItem;
     miLine1: TMenuItem;
     miReopenMap: TMenuItem;
     miSaveMiniMap: TMenuItem;
     miDeleteMap: TMenuItem;
     miPackMap: TMenuItem;
+    miWinRecent: TMenuItem;
     miLine2: TMenuItem;
     miExit: TMenuItem;
   // "Правка":
@@ -44,11 +53,8 @@ type
     miLine5: TMenuItem;
     miToFore: TMenuItem;
     miToBack: TMenuItem;
-  // "Инструменты":
-    miMenuTools: TMenuItem;
-    miSnapToGrid: TMenuItem;
-    miMiniMap: TMenuItem;
-    miSwitchGrid: TMenuItem;
+  // View menu:
+    miMenuView: TMenuItem;
     miShowEdges: TMenuItem;
     miLayers: TMenuItem;
     miLayer1: TMenuItem;
@@ -60,24 +66,29 @@ type
     miLayer7: TMenuItem;
     miLayer8: TMenuItem;
     miLayer9: TMenuItem;
+    miViewLine1: TMenuItem;
+    miMiniMap: TMenuItem;
+    miViewLine2: TMenuItem;
+    miMapPreview: TMenuItem;
+    miSnapToGrid: TMenuItem;
+    miSwitchGrid: TMenuItem;
+    miLine6: TMenuItem;
+    miOptions: TMenuItem;
+    miMapOptions: TMenuItem;
   // "Сервис":
     miMenuService: TMenuItem;
     miCheckMap: TMenuItem;
     miOptimmization: TMenuItem;
-    miMapPreview: TMenuItem;
     miTestMap: TMenuItem;
-  // "Настройка":
-    miMenuSettings: TMenuItem;
-    miMapOptions: TMenuItem;
-    miLine6: TMenuItem;
-    miOptions: TMenuItem;
-    miLine7: TMenuItem;
-    miMapTestSettings: TMenuItem;
+  // Window menu:
+    miMenuWindow: TMenuItem;
+    miMacMinimize: TMenuItem;
+    miMacZoom: TMenuItem;
   // "Справка":
     miMenuHelp: TMenuItem;
     miAbout: TMenuItem;
   // Скрытый пункт меню для Ctrl+Tab:
-    miHidden1: TMenuItem;
+    miMenuHidden: TMenuItem;
     minexttab: TMenuItem;
 
   // Панель инструментов:
@@ -85,6 +96,10 @@ type
     pbLoad: TProgressBar;
     pLoadProgress: TPanel;
     RenderPanel: TOpenGLControl;
+    Separator1: TMenuItem;
+    miMacRecentEnd: TMenuItem;
+    miWinRecentStart: TMenuItem;
+    Separator2: TMenuItem;
     tbNewMap: TToolButton;
     tbOpenMap: TToolButton;
     tbSaveMap: TToolButton;
@@ -109,9 +124,6 @@ type
     miLayerP7: TMenuItem;
     miLayerP8: TMenuItem;
     miLayerP9: TMenuItem;
-  // Всплывающее меню для кнопки теста карты:
-    pmMapTest: TPopupMenu;
-    miMapTestPMSet: TMenuItem;
 
   // Панель карты:
     PanelMap: TPanel;
@@ -124,6 +136,7 @@ type
   // Панель применения свойств:
     PanelPropApply: TPanel;
     bApplyProperty: TButton;
+    MapTestTimer: TTimer;
   // Редактор свойств объектов:
     vleObjectProperty: TValueListEditor;
 
@@ -208,9 +221,13 @@ type
     procedure FormDropFiles(Sender: TObject; const FileNames: array of String);
     procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
     procedure FormResize(Sender: TObject);
+    procedure FormWindowStateChange(Sender: TObject);
+    procedure miMacRecentClearClick(Sender: TObject);
+    procedure miMacZoomClick(Sender: TObject);
     procedure lbTextureListClick(Sender: TObject);
     procedure lbTextureListDrawItem(Control: TWinControl; Index: Integer;
       ARect: TRect; State: TOwnerDrawState);
+    procedure miMacMinimizeClick(Sender: TObject);
     procedure miReopenMapClick(Sender: TObject);
     procedure RenderPanelMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
     procedure RenderPanelMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
@@ -218,6 +235,7 @@ type
     procedure RenderPanelPaint(Sender: TObject);
     procedure RenderPanelResize(Sender: TObject);
     procedure Splitter1Moved(Sender: TObject);
+    procedure MapTestCheck(Sender: TObject);
     procedure vleObjectPropertyEditButtonClick(Sender: TObject);
     procedure vleObjectPropertyApply(Sender: TObject);
     procedure vleObjectPropertyGetPickList(Sender: TObject; const KeyName: String; Values: TStrings);
@@ -243,8 +261,6 @@ type
     procedure miSaveMiniMapClick(Sender: TObject);
     procedure bClearTextureClick(Sender: TObject);
     procedure miPackMapClick(Sender: TObject);
-    procedure aRecentFileExecute(Sender: TObject);
-    procedure miMapTestSettingsClick(Sender: TObject);
     procedure miTestMapClick(Sender: TObject);
     procedure sbVerticalScroll(Sender: TObject; ScrollCode: TScrollCode;
       var ScrollPos: Integer);
@@ -263,6 +279,7 @@ type
   private
     procedure Draw();
     procedure OnIdle(Sender: TObject; var Done: Boolean);
+    procedure RefillRecentMenu (menu: TMenuItem; start: Integer; fmt: AnsiString);
   public
     procedure RefreshRecentMenu();
     procedure OpenMapFile(FileName: String);
@@ -286,7 +303,7 @@ const
 
 var
   MainForm: TMainForm;
-  EditorDir: String;
+  StartMap: String;
   OpenedMap: String;
   OpenedWAD: String;
 
@@ -337,11 +354,11 @@ uses
   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, wadreader,
+  f_activationtype, f_keys, wadreader, fileutil,
   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_resources;
+  f_addresource_sound, f_choosetype,
+  g_language, f_selectlang, ClipBrd, g_resources, g_options;
 
 const
   UNDO_DELETE_PANEL   = 1;
@@ -457,6 +474,8 @@ var
 
   UndoBuffer: Array of Array of TUndoRec = nil;
 
+  MapTestProcess: TProcessUTF8;
+  MapTestFile: String;
 
 {$R *.lfm}
 
@@ -1839,7 +1858,7 @@ end;
 
 procedure ErrorMessageBox(str: String);
 begin
-  MessageBox(0, PChar(str), PChar(_lc[I_MSG_ERROR]),
+  Application.MessageBox(PChar(str), PChar(_lc[I_MSG_ERROR]),
              MB_ICONINFORMATION or MB_OK or MB_DEFBUTTON1);
 end;
 
@@ -1930,7 +1949,6 @@ begin
   if aWAD = _lc[I_WAD_SPECIAL_MAP] then
     begin // Файл карты
       g_ProcessResourceStr(OpenedMap, @fn, nil, nil);
-    //FileName := EditorDir+'maps\'+ExtractFileName(fn);
       FileName := fn;
       ResourceName := ':'+SectionName+'\'+aTex;
     end
@@ -1942,7 +1960,7 @@ begin
       end
     else
       begin // Внешний WAD
-        FileName := EditorDir+'wads/'+aWAD;
+        FileName := WadsDir + DirectorySeparator + aWAD;
         ResourceName := aWAD+':'+SectionName+'\'+aTex;
       end;
 
@@ -2241,6 +2259,7 @@ procedure SwitchMap();
 begin
   ShowMap := not ShowMap;
   MainForm.tbShowMap.Down := ShowMap;
+  MainForm.miMiniMap.Checked := ShowMap;
 end;
 
 procedure ShowEdges();
@@ -2249,6 +2268,7 @@ begin
     drEdge[3] := 255
   else
     drEdge[3] := gAlphaEdge;
+  MainForm.miShowEdges.Checked := drEdge[3] <> 255;
 end;
 
 function SelectedTexture(): String;
@@ -2546,60 +2566,105 @@ end;
 //Закончились вспомогательные процедуры
 //----------------------------------------
 
-procedure TMainForm.RefreshRecentMenu();
-var
-  i: Integer;
-  MI: TMenuItem;
+type
+  TRecentHandler = class
+    private
+      FForm: TMainForm;
+      FPath: String;
+    public
+      constructor Create (form: TMainForm; path: String);
+      procedure Execute (Sender: TObject);
+  end;
+
+constructor TRecentHandler.Create (form: TMainForm; path: String);
 begin
-// Лишние запомненные карты:
-  while RecentFiles.Count > RecentCount do
-    RecentFiles.Delete(RecentFiles.Count-1);
+  Assert(form <> nil);
+  FForm := form;
+  FPath := path;
+end;
 
-// Лишние строки меню:
-  while MainMenu.Items[0].Count > RECENT_FILES_MENU_START do
-    MainMenu.Items[0].Delete(MainMenu.Items[0].Count-1);
+procedure TRecentHandler.Execute (Sender: TObject);
+  var fn: AnsiString;
+begin
+  fn := g_ExtractWadName(FPath);
+  if FileExists(fn) then
+    OpenMap(fn, g_ExtractFilePathName(FPath))
+  else
+    Application.MessageBox('', 'File not available anymore', MB_OK);
+//  if Application.MessageBox(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;
 
-// Отделение списка карт от строки "Выход":
-  if RecentFiles.Count > 0 then
+procedure TMainForm.RefillRecentMenu (menu: TMenuItem; start: Integer; fmt: AnsiString);
+  var i: Integer; MI: TMenuItem; cb: TMethod; h: TRecentHandler; s: AnsiString;
+begin
+  Assert(menu <> nil);
+  Assert(start >= 0);
+  Assert(start <= menu.Count);
+
+  // clear all recent entries from menu
+  i := start;
+  while i < menu.Count do
   begin
-    MI := TMenuItem.Create(MainMenu.Items[0]);
-    MI.Caption := '-';
-    MainMenu.Items[0].Add(MI);
+    MI := menu.Items[i];
+    cb := TMethod(MI.OnClick);
+    if cb.Code = @TRecentHandler.Execute then
+    begin
+      // this is recent menu entry
+      // remove it and free callback handler
+      h := TRecentHandler(cb.Data);
+      menu.Delete(i);
+      MI.Free();
+      h.Free();
+    end
+    else
+      Inc(i);
   end;
 
-// Добавление в меню списка запомненных карт:
-  for i := 0 to RecentFiles.Count-1 do
+  // fill with a new ones
+  for i := 0 to RecentFiles.Count - 1 do
   begin
-    MI := TMenuItem.Create(MainMenu.Items[0]);
-    MI.Caption := IntToStr(i+1) + '  ' + RecentFiles[i];
-    MI.OnClick := aRecentFileExecute;
-    MainMenu.Items[0].Add(MI);
+    s := RecentFiles[i];
+    h := TRecentHandler.Create(self, s);
+    MI := TMenuItem.Create(menu);
+    MI.Caption := Format(fmt, [i + 1, g_ExtractWadNameNoPath(s), g_ExtractFilePathName(s)]);
+    MI.OnClick := h.Execute;
+    menu.Insert(start + i, MI);
   end;
 end;
 
-procedure TMainForm.aRecentFileExecute(Sender: TObject);
-var
-  n: Integer;
-  fn, s: String;
-begin
-  s := LowerCase((Sender as TMenuItem).Caption);
-  Delete(s, Pos('&', s), 1);
-  s := Trim(Copy(s, 1, 2));
-  n := StrToIntDef(s, 0) - 1;
-  if (n >= 0) and (n <= RecentFiles.Count) then
+procedure TMainForm.RefreshRecentMenu();
+  var start: Integer;
+begin
+  while RecentFiles.Count > RecentCount do
+    RecentFiles.Delete(RecentFiles.Count - 1);
+
+  if miMacRecentSubMenu.Visible then
   begin
-    fn := g_ExtractWadName(RecentFiles[n]);
-    if FileExists(fn) then
-    begin
-      s := g_ExtractFilePathName(RecentFiles[n]);
-      OpenMap(fn, s)
-    end
-    else if 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
+    // Reconstruct OSX-like recent list
+    RefillRecentMenu(miMacRecentSubMenu, 0, '%1:s - %2:s');
+    miMacRecentEnd.Enabled := RecentFiles.Count <> 0;
+    miMacRecentEnd.Visible := RecentFiles.Count <> 0;
+  end;
+
+  if miWinRecentStart.Visible then
+  begin
+    // Reconstruct Windows-like recent list
+    start := miMenuFile.IndexOf(miWinRecent);
+    if start < 0 then start := miMenuFile.Count else start := start + 1;
+    RefillRecentMenu(miMenuFile, start, '%0:d %1:s:%2:s');
+    miWinRecent.Enabled := False;
+    miWinRecent.Visible := RecentFiles.Count = 0;
+  end;
+end;
+
+procedure TMainForm.miMacRecentClearClick(Sender: TObject);
+begin
+  RecentFiles.Clear();
+  RefreshRecentMenu();
 end;
 
 procedure TMainForm.aEditorOptionsExecute(Sender: TObject);
@@ -2617,10 +2682,10 @@ var
   config: TConfig;
 begin
   ID := 0;
-  g_ReadResource(EditorDir + 'data/game.wad', 'FONTS', cfgres, cfgdata, cfglen);
+  g_ReadResource(GameWad, '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', GameWad + ':FONTS\' + texture) then
       e_WriteLog('ERROR ERROR ERROR', MSG_WARNING);
 
     config := TConfig.CreateMem(cfgdata, cfglen);
@@ -2648,9 +2713,69 @@ var
 begin
   Randomize();
 
-  EditorDir := ExtractFilePath(Application.ExeName);
-
-  e_InitLog(EditorDir+'Editor.log', WM_NEWFILE);
+  {$IFDEF DARWIN}
+    miApple.Enabled := True;
+    miApple.Visible := True;
+    miMacRecentSubMenu.Enabled := True;
+    miMacRecentSubMenu.Visible := True;
+    miWinRecentStart.Enabled := False;
+    miWinRecentStart.Visible := False;
+    miWinRecent.Enabled := False;
+    miWinRecent.Visible := False;
+    miLine2.Enabled := False;
+    miLine2.Visible := False;
+    miExit.Enabled := False;
+    miExit.Visible := False;
+    miOptions.Enabled := False;
+    miOptions.Visible := False;
+    miMenuWindow.Enabled := True;
+    miMenuWindow.Visible := True;
+    miAbout.Enabled := False;
+    miAbout.Visible := False;
+  {$ELSE}
+    miApple.Enabled := False;
+    miApple.Visible := False;
+    miMacRecentSubMenu.Enabled := False;
+    miMacRecentSubMenu.Visible := False;
+    miWinRecentStart.Enabled := True;
+    miWinRecentStart.Visible := True;
+    miWinRecent.Enabled := True;
+    miWinRecent.Visible := True;
+    miLine2.Enabled := True;
+    miLine2.Visible := True;
+    miExit.Enabled := True;
+    miExit.Visible := True;
+    miOptions.Enabled := True;
+    miOptions.Visible := True;
+    miMenuWindow.Enabled := False;
+    miMenuWindow.Visible := False;
+    miAbout.Enabled := True;
+    miAbout.Visible := True;
+  {$ENDIF}
+
+  miNewMap.ShortCut := ShortCut(VK_N, [ssModifier]);
+  miOpenMap.ShortCut := ShortCut(VK_O, [ssModifier]);
+  miSaveMap.ShortCut := ShortCut(VK_S, [ssModifier]);
+  {$IFDEF DARWIN}
+    miSaveMapAs.ShortCut := ShortCut(VK_S, [ssModifier, ssShift]);
+    miReopenMap.ShortCut := ShortCut(VK_F5, [ssModifier]);
+  {$ENDIF}
+  miUndo.ShortCut := ShortCut(VK_Z, [ssModifier]);
+  miCopy.ShortCut := ShortCut(VK_C, [ssModifier]);
+  miCut.ShortCut := ShortCut(VK_X, [ssModifier]);
+  miPaste.ShortCut := ShortCut(VK_V, [ssModifier]);
+  miSelectAll.ShortCut := ShortCut(VK_A, [ssModifier]);
+  miToFore.ShortCut := ShortCut(VK_LCL_CLOSE_BRACKET, [ssModifier]);
+  miToBack.ShortCut := ShortCut(VK_LCL_OPEN_BRACKET, [ssModifier]);
+  {$IFDEF DARWIN}
+    miMapOptions.Shortcut := ShortCut(VK_P, [ssModifier, ssAlt]);
+    selectall1.Shortcut := ShortCut(VK_A, [ssModifier, ssAlt]);
+  {$ENDIF}
+
+  e_WriteLog('Doom 2D: Forever Editor version ' + EDITOR_VERSION, MSG_NOTIFY);
+  e_WriteLog('Build date: ' + EDITOR_BUILDDATE + ' ' + EDITOR_BUILDTIME, MSG_NOTIFY);
+  e_WriteLog('Build hash: ' + g_GetBuildHash(), MSG_NOTIFY);
+  e_WriteLog('Build by: ' + g_GetBuilderName(), MSG_NOTIFY);
 
   slInvalidTextures := TStringList.Create;
 
@@ -2670,7 +2795,7 @@ begin
   OpenedMap := '';
   OpenedWAD := '';
 
-  config := TConfig.CreateFile(EditorDir+'Editor.cfg');
+  config := TConfig.CreateFile(CfgFileName);
 
   if config.ReadInt('Editor', 'XPos', -1) = -1 then
     Position := poDesktopCenter
@@ -2725,8 +2850,8 @@ begin
     DotSize := 2
   else
     DotSize := 1;
-  OpenDialog.InitialDir := config.ReadStr('Editor', 'LastOpenDir', EditorDir);
-  SaveDialog.InitialDir := config.ReadStr('Editor', 'LastSaveDir', EditorDir);
+  OpenDialog.InitialDir := config.ReadStr('Editor', 'LastOpenDir', MapsDir);
+  SaveDialog.InitialDir := config.ReadStr('Editor', 'LastSaveDir', MapsDir);
 
   s := config.ReadStr('Editor', 'Language', '');
   gLanguage := s;
@@ -2734,6 +2859,24 @@ begin
   Compress := config.ReadBool('Editor', 'Compress', True);
   Backup := config.ReadBool('Editor', 'Backup', True);
 
+  TestGameMode := config.ReadStr('TestRun', 'GameMode', 'DM');
+  TestLimTime := config.ReadStr('TestRun', 'LimTime', '0');
+  TestLimScore := config.ReadStr('TestRun', 'LimScore', '0');
+  TestOptionsTwoPlayers := config.ReadBool('TestRun', 'TwoPlayers', False);
+  TestOptionsTeamDamage := config.ReadBool('TestRun', 'TeamDamage', False);
+  TestOptionsAllowExit := config.ReadBool('TestRun', 'AllowExit', True);
+  TestOptionsWeaponStay := config.ReadBool('TestRun', 'WeaponStay', False);
+  TestOptionsMonstersDM := config.ReadBool('TestRun', 'MonstersDM', False);
+  TestMapOnce := config.ReadBool('TestRun', 'MapOnce', False);
+  {$IF DEFINED(DARWIN)}
+    TestD2dExe := config.ReadStr('TestRun', 'ExeDrawin', GameExeFile);
+  {$ELSEIF DEFINED(WINDOWS)}
+    TestD2dExe := config.ReadStr('TestRun', 'ExeWindows', GameExeFile);
+  {$ELSE}
+    TestD2dExe := config.ReadStr('TestRun', 'ExeUnix', GameExeFile);
+  {$ENDIF}
+  TestD2DArgs := config.ReadStr('TestRun', 'Args', '');
+
   RecentCount := config.ReadInt('Editor', 'RecentCount', 5);
   if RecentCount > 10 then
     RecentCount := 10;
@@ -2743,7 +2886,11 @@ begin
   RecentFiles := TStringList.Create();
   for i := 0 to RecentCount-1 do
   begin
-    s := config.ReadStr('RecentFiles', IntToStr(i+1), '');
+    {$IFDEF WINDOWS}
+      s := config.ReadStr('RecentFilesWin', IntToStr(i), '');
+    {$ELSE}
+      s := config.ReadStr('RecentFilesUnix', IntToStr(i), '');
+    {$ENDIF}
     if s <> '' then
       RecentFiles.Add(s);
   end;
@@ -3106,6 +3253,24 @@ begin
   MapOffset.Y := -sbVertical.Position;
 end;
 
+procedure TMainForm.FormWindowStateChange(Sender: TObject);
+  {$IFDEF DARWIN}
+    var e: Boolean;
+  {$ENDIF}
+begin
+  {$IFDEF DARWIN}
+    // deactivate all menus when main window minimized
+    e := self.WindowState <> wsMinimized;
+    miMenuFile.Enabled := e;
+    miMenuEdit.Enabled := e;
+    miMenuView.Enabled := e;
+    miMenuService.Enabled := e;
+    miMenuWindow.Enabled := e;
+    miMenuHelp.Enabled := e;
+    miMenuHidden.Enabled := e;
+  {$ENDIF}
+end;
+
 procedure SelectNextObject(X, Y: Integer; ObjectType: Byte; ID: DWORD);
 var
   j, j_max: Integer;
@@ -4219,7 +4384,7 @@ end;
 
 procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
 begin
-  CanClose := MessageBox(0, PChar(_lc[I_MSG_EXIT_PROMT]),
+  CanClose := Application.MessageBox(PChar(_lc[I_MSG_EXIT_PROMT]),
                          PChar(_lc[I_MSG_EXIT]),
                          MB_ICONQUESTION or MB_YESNO or
                          MB_DEFBUTTON1) = idYes;
@@ -4233,9 +4398,10 @@ end;
 procedure TMainForm.FormDestroy(Sender: TObject);
 var
   config: TConfig;
+  s: AnsiString;
   i: Integer;
 begin
-  config := TConfig.CreateFile(EditorDir+'Editor.cfg');
+  config := TConfig.CreateFile(CfgFileName);
 
   if WindowState <> wsMaximized then
   begin
@@ -4267,14 +4433,18 @@ begin
   config.WriteInt('Editor', 'MonsterRectAlpha', gAlphaMonsterRect);
   config.WriteInt('Editor', 'AreaRectAlpha', gAlphaAreaRect);
 
-  for i := 0 to RecentCount-1 do
-    if i < RecentFiles.Count then
-      config.WriteStr('RecentFiles', IntToStr(i+1), RecentFiles[i])
-    else
-      config.WriteStr('RecentFiles', IntToStr(i+1), '');
+  for i := 0 to RecentCount - 1 do
+  begin
+    if i < RecentFiles.Count then s := RecentFiles[i] else s := '';
+    {$IFDEF WINDOWS}
+      config.WriteStr('RecentFilesWin', IntToStr(i), s);
+    {$ELSE}
+      config.WriteStr('RecentFilesUnix', IntToStr(i), s);
+    {$ENDIF}
+  end;
   RecentFiles.Free();
 
-  config.SaveFile(EditorDir+'Editor.cfg');
+  config.SaveFile(CfgFileName);
   config.Free();
 
   slInvalidTextures.Free;
@@ -4300,6 +4470,22 @@ begin
   FormResize(Sender);
 end;
 
+procedure TMainForm.MapTestCheck(Sender: TObject);
+begin
+  if MapTestProcess <> nil then
+  begin
+    if MapTestProcess.Running = false then
+    begin
+      if MapTestProcess.ExitCode <> 0 then
+        Application.MessageBox(PChar(_lc[I_MSG_EXEC_ERROR]), 'FIXME', MB_OK or MB_ICONERROR);
+      SysUtils.DeleteFile(MapTestFile);
+      MapTestFile := '';
+      FreeAndNil(MapTestProcess);
+      tbTestMap.Enabled := True;
+    end;
+  end;
+end;
+
 procedure TMainForm.aMapOptionsExecute(Sender: TObject);
 var
   ResName: String;
@@ -4714,6 +4900,21 @@ begin
   end;
 end;
 
+procedure TMainForm.miMacMinimizeClick(Sender: TObject);
+begin
+  self.WindowState := wsMinimized;
+  self.FormWindowStateChange(Sender);
+end;
+
+procedure TMainForm.miMacZoomClick(Sender: TObject);
+begin
+  if self.WindowState = wsMaximized then
+    self.WindowState := wsNormal
+  else
+    self.WindowState := wsMaximized;
+  self.FormWindowStateChange(Sender);
+end;
+
 procedure TMainForm.miReopenMapClick(Sender: TObject);
 var
   FileName, Resource: String;
@@ -4721,7 +4922,7 @@ begin
   if OpenedMap = '' then
     Exit;
 
-  if MessageBox(0, PChar(_lc[I_MSG_REOPEN_MAP_PROMT]),
+  if Application.MessageBox(PChar(_lc[I_MSG_REOPEN_MAP_PROMT]),
   PChar(_lc[I_MENU_FILE_REOPEN]), MB_ICONQUESTION or MB_YESNO) <> idYes then
     Exit;
 
@@ -5151,7 +5352,7 @@ begin
                 if vleObjectProperty.Values[_lc[I_PROP_TR_MUSIC_ACT]] = _lc[I_PROP_TR_MUSIC_ON] then
                   Data.MusicAction := 1
                 else
-                  Data.MusicAction := 2;
+                  Data.MusicAction := 0;
               end;
 
             TRIGGER_PUSH:
@@ -5351,7 +5552,7 @@ begin
   if i = -1 then
     Exit;
 
-  if MessageBox(0, PChar(Format(_lc[I_MSG_DEL_TEXTURE_PROMT],
+  if Application.MessageBox(PChar(Format(_lc[I_MSG_DEL_TEXTURE_PROMT],
                                 [SelectedTexture()])),
                 PChar(_lc[I_MSG_DEL_TEXTURE]),
                 MB_ICONQUESTION or MB_YESNO or
@@ -5377,10 +5578,7 @@ end;
 
 procedure TMainForm.aNewMapExecute(Sender: TObject);
 begin
-  if (MessageBox(0, PChar(_lc[I_MSG_CLEAR_MAP_PROMT]),
-                 PChar(_lc[I_MSG_CLEAR_MAP]),
-                 MB_ICONQUESTION or MB_YESNO or
-                 MB_DEFBUTTON1) = mrYes) then
+  if Application.MessageBox(PChar(_lc[I_MSG_CLEAR_MAP_PROMT]), PChar(_lc[I_MSG_CLEAR_MAP]), MB_ICONQUESTION or MB_YESNO or MB_DEFBUTTON1) = mrYes then
     FullClear();
 end;
 
@@ -6224,17 +6422,18 @@ begin
   MainForm.ActiveControl := RenderPanel;
 
 // Язык:
-  if gLanguage = '' then
+  if (gLanguage = '') and not (fsModal in SelectLanguageForm.FormState) then
   begin
     lang := SelectLanguageForm.ShowModal();
     case lang of
       1:   gLanguage := LANGUAGE_ENGLISH;
-      else gLanguage := LANGUAGE_RUSSIAN;
+      2:   gLanguage := LANGUAGE_RUSSIAN;
+      else gLanguage := LANGUAGE_ENGLISH;
     end;
 
-    config := TConfig.CreateFile(EditorDir+'Editor.cfg');
+    config := TConfig.CreateFile(CfgFileName);
     config.WriteStr('Editor', 'Language', gLanguage);
-    config.SaveFile(EditorDir+'Editor.cfg');
+    config.SaveFile(CfgFileName);
     config.Free();
   end;
 
@@ -6263,18 +6462,17 @@ begin
     Exit;
 
   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
+  if Application.MessageBox(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;
 
   g_DeleteResource(FileName, '', MapName, res);
   if res <> 0 then
   begin
-    MessageBox(0, PChar('Cant delete map res=' + IntToStr(res)), PChar('Map not deleted!'), MB_ICONINFORMATION or MB_OK or MB_DEFBUTTON1);
+    Application.MessageBox(PChar('Cant delete map res=' + IntToStr(res)), PChar('Map not deleted!'), MB_ICONINFORMATION or MB_OK or MB_DEFBUTTON1);
     Exit
   end;
 
-  MessageBox(
-    0,
+  Application.MessageBox(
     PChar(Format(_lc[I_MSG_MAP_DELETED_PROMT], [MapName])),
     PChar(_lc[I_MSG_MAP_DELETED]),
     MB_ICONINFORMATION or MB_OK or MB_DEFBUTTON1
@@ -6512,6 +6710,7 @@ begin
 end;
 
 procedure TMainForm.OnIdle(Sender: TObject; var Done: Boolean);
+  var f: AnsiString;
 begin
   // FIXME: this is a shitty hack
   if not gDataLoaded then
@@ -6527,6 +6726,12 @@ begin
     MainForm.FormResize(nil);
   end;
   Draw();
+  if StartMap <> '' then
+  begin
+    f := StartMap;
+    StartMap := '';
+    OpenMap(f, '');
+  end;
 end;
 
 procedure TMainForm.miMapPreviewClick(Sender: TObject);
@@ -6686,11 +6891,6 @@ begin
   PackMapForm.ShowModal();
 end;
 
-procedure TMainForm.miMapTestSettingsClick(Sender: TObject);
-begin
-  MapTestForm.ShowModal();
-end;
-
 type SSArray = array of String;
 
 function ParseString (Str: AnsiString): SSArray;
@@ -6728,22 +6928,36 @@ end;
 
 procedure TMainForm.miTestMapClick(Sender: TObject);
 var
-  mapWAD, tempWAD: String;
+  newWAD, oldWAD, tempMap, ext: String;
   args: SSArray;
   opt: LongWord;
   time, i: Integer;
   proc: TProcessUTF8;
   res: Boolean;
 begin
+  // Ignore while map testing in progress
+  if MapTestProcess <> nil then
+    Exit;
+
   // Сохраняем временную карту:
   time := 0;
   repeat
-    mapWAD := ExtractFilePath(TestD2dExe) + Format('maps/temp%.4d.wad', [time]);
+    newWAD := Format('%s/temp%.4d', [MapsDir, time]);
     Inc(time);
-  until not FileExists(mapWAD);
-  tempWAD := mapWAD + ':\' + TEST_MAP_NAME;
-  SaveMap(tempWAD);
-  tempWAD := ExtractRelativePath(ExtractFilePath(TestD2dExe) + 'maps/', tempWAD);
+  until not FileExists(newWAD);
+  if OpenedMap <> '' then
+  begin
+    oldWad := g_ExtractWadName(OpenedMap);
+    newWad := newWad + ExtractFileExt(oldWad);
+    if CopyFile(oldWad, newWad) = false then
+      e_WriteLog('MapTest: unable to copy [' + oldWad + '] to [' + newWad + ']', MSG_WARNING)
+  end
+  else
+  begin
+    newWad := newWad + '.wad'
+  end;
+  tempMap := newWAD + ':\' + TEST_MAP_NAME;
+  SaveMap(tempMap);
 
 // Опции игры:
   opt := 32 + 64;
@@ -6761,10 +6975,13 @@ begin
 // Запускаем:
   proc := TProcessUTF8.Create(nil);
   proc.Executable := TestD2dExe;
+  {$IFDEF DARWIN}
+    // TODO: get real executable name from Info.plist
+    if LowerCase(ExtractFileExt(TestD2dExe)) = '.app' then
+      proc.Executable := TestD2dExe + DirectorySeparator + 'Contents' + DirectorySeparator + 'MacOS' + DirectorySeparator + 'Doom2DF';
+  {$ENDIF}
   proc.Parameters.Add('-map');
-  proc.Parameters.Add(tempWAD);
-  proc.Parameters.Add('-testmap');
-  proc.Parameters.Add(tempWAD);
+  proc.Parameters.Add(tempMap);
   proc.Parameters.Add('-gm');
   proc.Parameters.Add(TestGameMode);
   proc.Parameters.Add('-limt');
@@ -6789,19 +7006,16 @@ begin
   end;
   if res then
   begin
-    Application.Minimize();
-    proc.WaitOnExit();
-  end;
-  if (not res) or (proc.ExitCode < 0) then
+    tbTestMap.Enabled := False;
+    MapTestFile := newWAD;
+    MapTestProcess := proc;
+  end
+  else
   begin
-    MessageBox(0, 'FIXME',
-               PChar(_lc[I_MSG_EXEC_ERROR]),
-               MB_OK or MB_ICONERROR);
+    Application.MessageBox(PChar(_lc[I_MSG_EXEC_ERROR]), 'FIXME', MB_OK or MB_ICONERROR);
+    SysUtils.DeleteFile(newWAD);
+    proc.Free();
   end;
-  proc.Free();
-
-  SysUtils.DeleteFile(mapWAD);
-  Application.Restore();
 end;
 
 procedure TMainForm.sbVerticalScroll(Sender: TObject;