Browse Source

Merge pull request #134 from PixiEditor/master

0.1.4.0 Release
Krzysztof Krysiński 4 years ago
parent
commit
dba788efe6
100 changed files with 2039 additions and 2240 deletions
  1. 3 0
      .gitignore
  2. 353 55
      Installer/installer-setup-x64-light.iss
  3. 353 56
      Installer/installer-setup-x86-light.iss
  4. 0 0
      Installer/netcorecheck.exe
  5. 0 0
      Installer/netcorecheck_x64.exe
  6. 0 49
      Installer/scripts/isxdl/chinese.ini
  7. 0 53
      Installer/scripts/isxdl/czech.ini
  8. 0 49
      Installer/scripts/isxdl/dutch.ini
  9. 0 49
      Installer/scripts/isxdl/english.ini
  10. 0 45
      Installer/scripts/isxdl/french.ini
  11. 0 45
      Installer/scripts/isxdl/french2.ini
  12. 0 46
      Installer/scripts/isxdl/french3.ini
  13. 0 49
      Installer/scripts/isxdl/german.ini
  14. BIN
      Installer/scripts/isxdl/isxdl.dll
  15. 0 12
      Installer/scripts/isxdl/isxdl.iss
  16. 0 49
      Installer/scripts/isxdl/italian.ini
  17. 0 49
      Installer/scripts/isxdl/japanese.ini
  18. 0 49
      Installer/scripts/isxdl/korean.ini
  19. 0 47
      Installer/scripts/isxdl/norwegian.ini
  20. 0 45
      Installer/scripts/isxdl/polish.ini
  21. 0 45
      Installer/scripts/isxdl/portugues.ini
  22. 0 46
      Installer/scripts/isxdl/portuguese.ini
  23. 0 49
      Installer/scripts/isxdl/russian.ini
  24. 0 46
      Installer/scripts/isxdl/spanish.ini
  25. 0 48
      Installer/scripts/isxdl/swedish.ini
  26. 0 18
      Installer/scripts/lang/chinese.iss
  27. 0 18
      Installer/scripts/lang/dutch.iss
  28. 0 15
      Installer/scripts/lang/english.iss
  29. 0 18
      Installer/scripts/lang/french.iss
  30. 0 18
      Installer/scripts/lang/german.iss
  31. 0 18
      Installer/scripts/lang/italian.iss
  32. 0 18
      Installer/scripts/lang/japanese.iss
  33. 0 18
      Installer/scripts/lang/polish.iss
  34. 0 18
      Installer/scripts/lang/russian.iss
  35. 0 4
      Installer/scripts/products.iss
  36. 0 324
      Installer/scripts/products.pas
  37. 0 92
      Installer/scripts/products/dotnetfxversion.iss
  38. 0 27
      Installer/scripts/products/netcore31.iss
  39. 0 27
      Installer/scripts/products/netcore31desktop.iss
  40. 0 32
      Installer/scripts/products/netcorecheck.iss
  41. 0 60
      Installer/scripts/products/stringversion.iss
  42. 0 47
      Installer/scripts/products/winversion.iss
  43. 1 1
      LICENSE
  44. 2 2
      PixiEditor.UpdateInstaller/PixiEditor.UpdateInstaller.csproj
  45. 1 1
      PixiEditor.UpdateModule/PixiEditor.UpdateModule.csproj
  46. 2 0
      PixiEditor/App.xaml
  47. 35 0
      PixiEditor/Exceptions/CorruptedFileException.cs
  48. 25 0
      PixiEditor/Helpers/Behaviours/ClearFocusOnClickBehavior.cs
  49. 16 4
      PixiEditor/Helpers/Behaviours/TextBoxFocusBehavior.cs
  50. 20 0
      PixiEditor/Helpers/Converters/EqualityBoolToVisibilityConverter.cs
  51. 77 0
      PixiEditor/Helpers/Extensions/ParserHelpers.cs
  52. 0 0
      PixiEditor/Images/FloodFillImage.png
  53. 45 101
      PixiEditor/Models/Controllers/BitmapManager.cs
  54. 14 8
      PixiEditor/Models/Controllers/BitmapOperationsUtility.cs
  55. 25 1
      PixiEditor/Models/Controllers/ClipboardController.cs
  56. 25 19
      PixiEditor/Models/Controllers/UndoManager.cs
  57. 8 0
      PixiEditor/Models/DataHolders/BitmapPixelChanges.cs
  58. 254 0
      PixiEditor/Models/DataHolders/Document.cs
  59. 10 4
      PixiEditor/Models/Dialogs/NewFileDialog.cs
  60. 15 15
      PixiEditor/Models/IO/Exporter.cs
  61. 69 28
      PixiEditor/Models/IO/Importer.cs
  62. 12 10
      PixiEditor/Models/ImageManipulation/BitmapUtils.cs
  63. 1 0
      PixiEditor/Models/Layers/Layer.cs
  64. 3 3
      PixiEditor/Models/Position/CoordinatesCalculator.cs
  65. 6 2
      PixiEditor/Models/Tools/BitmapOperationTool.cs
  66. 0 2
      PixiEditor/Models/Tools/ShapeTool.cs
  67. 38 5
      PixiEditor/Models/Tools/Tool.cs
  68. 11 3
      PixiEditor/Models/Tools/ToolSettings/Settings/ColorSetting.cs
  69. 0 19
      PixiEditor/Models/Tools/ToolType.cs
  70. 23 8
      PixiEditor/Models/Tools/Tools/BrightnessTool.cs
  71. 17 1
      PixiEditor/Models/Tools/Tools/CircleTool.cs
  72. 1 2
      PixiEditor/Models/Tools/Tools/ColorPickerTool.cs
  73. 5 5
      PixiEditor/Models/Tools/Tools/EraserTool.cs
  74. 2 3
      PixiEditor/Models/Tools/Tools/FloodFill.cs
  75. 35 6
      PixiEditor/Models/Tools/Tools/LineTool.cs
  76. 24 9
      PixiEditor/Models/Tools/Tools/MoveTool.cs
  77. 2 3
      PixiEditor/Models/Tools/Tools/MoveViewportTool.cs
  78. 6 5
      PixiEditor/Models/Tools/Tools/PenTool.cs
  79. 18 2
      PixiEditor/Models/Tools/Tools/RectangleTool.cs
  80. 12 9
      PixiEditor/Models/Tools/Tools/SelectTool.cs
  81. 18 3
      PixiEditor/Models/Tools/Tools/ZoomTool.cs
  82. 90 0
      PixiEditor/Models/UserPreferences/PreferencesSettings.cs
  83. 8 7
      PixiEditor/PixiEditor.csproj
  84. 3 3
      PixiEditor/Properties/AssemblyInfo.cs
  85. 1 1
      PixiEditor/Properties/Settings.Designer.cs
  86. 2 5
      PixiEditor/Properties/Settings.settings
  87. 39 0
      PixiEditor/Styles/DarkCheckboxStyle.xaml
  88. 22 0
      PixiEditor/Styles/LabelStyles.xaml
  89. 31 0
      PixiEditor/Styles/ThemeStyle.xaml
  90. 0 64
      PixiEditor/ViewModels/FeedbackDialogViewModel.cs
  91. 43 32
      PixiEditor/ViewModels/ImportFilePopupViewModel.cs
  92. 5 3
      PixiEditor/ViewModels/NewFileMenuViewModel.cs
  93. 43 0
      PixiEditor/ViewModels/SettingsWindowViewModel.cs
  94. 6 6
      PixiEditor/ViewModels/SubViewModels/Main/ClipboardViewModel.cs
  95. 22 4
      PixiEditor/ViewModels/SubViewModels/Main/DocumentViewModel.cs
  96. 69 27
      PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs
  97. 11 30
      PixiEditor/ViewModels/SubViewModels/Main/IoViewModel.cs
  98. 6 5
      PixiEditor/ViewModels/SubViewModels/Main/LayersViewModel.cs
  99. 47 0
      PixiEditor/ViewModels/SubViewModels/Main/MiscViewModel.cs
  100. 4 16
      PixiEditor/ViewModels/SubViewModels/Main/SelectionViewModel.cs

+ 3 - 0
.gitignore

@@ -332,3 +332,6 @@ ASALocalRun/
 PixiEditor/Properties/
 
 .vscode/
+
+Builds/
+Installer/Assets

+ 353 - 55
Installer/installer-setup-x64-light.iss

@@ -1,6 +1,14 @@
-// contribute: https://github.com/domgho/InnoDependencyInstaller
-// official article: https://www.codeproject.com/Articles/20868/Inno-Setup-Dependency-Installer
+// contribute: https://github.com/DomGries/InnoDependencyInstaller
+// official article: https://codeproject.com/Articles/20868/Inno-Setup-Dependency-Installer
 
+// requires netcorecheck.exe and netcorecheck_x64.exe (see download link below)
+#define UseNetCoreCheck
+#ifdef UseNetCoreCheck
+  ;#define UseDotNet50
+  #define UseDotNet50Desktop
+#endif
+
+// custom setup info
 #define MyAppName "PixiEditor"
 #define MyAppVersion GetFileVersion("..\Builds\PixiEditor-x64-light\PixiEditor\PixiEditor.exe")     ;Not perfect solution, it's enviroment dependend
 #define MyAppPublisher "PixiEditor"
@@ -9,8 +17,6 @@
 #define TargetPlatform "x64-light"
 
 [Setup]
-; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
-; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
 AppId={{83DE4F2A-1F75-43AE-9546-3184F1C44517}
 AppName={#MyAppName}
 AppVersion={#MyAppVersion}
@@ -35,57 +41,333 @@ SolidCompression=yes
 WizardStyle=modern
 ChangesAssociations = yes
 
+MinVersion=6.0
 PrivilegesRequired=admin
-ArchitecturesAllowed=x86 x64 ia64
-ArchitecturesInstallIn64BitMode=x64 ia64
+ArchitecturesInstallIn64BitMode=x64
 
-// downloading and installing dependencies will only work if the memo/ready page is enabled (default and current behaviour)
+// dependency installation requires ready page and ready memo to be enabled (default behaviour)
 DisableReadyPage=no
 DisableReadyMemo=no
 
-// requires netcorecheck.exe and netcorecheck_x64.exe in src dir
-#define use_netcorecheck
-#define use_netcore31
-#define use_netcore31desktop
-// supported languages
-#include "scripts\lang\english.iss"
-#include "scripts\lang\german.iss"
-#include "scripts\lang\french.iss"
-#include "scripts\lang\italian.iss"
-#include "scripts\lang\dutch.iss"
-
-#ifdef UNICODE
-#include "scripts\lang\chinese.iss"
-#include "scripts\lang\polish.iss"
-#include "scripts\lang\russian.iss"
-#include "scripts\lang\japanese.iss"
-#endif
 
-// shared code for installing the products
-#include "scripts\products.iss"
+// shared code for installing the dependencies
+[Code]
+// types and variables
+type
+  TDependency = record
+    Filename: String;
+    Parameters: String;
+    Title: String;
+    URL: String;
+    Checksum: String;
+    ForceSuccess: Boolean;
+    InstallClean: Boolean;
+    RebootAfter: Boolean;
+  end;
 
-// helper functions
-#include "scripts\products\stringversion.iss"
-#include "scripts\products\winversion.iss"
-#include "scripts\products\dotnetfxversion.iss"
+  InstallResult = (InstallSuccessful, InstallRebootRequired, InstallError);
 
-#ifdef use_netcorecheck
-#include "scripts\products\netcorecheck.iss"
-#endif
-#ifdef use_netcore31
-#include "scripts\products\netcore31.iss"
-#endif
-#ifdef use_netcore31desktop
-#include "scripts\products\netcore31desktop.iss"
+var
+  MemoInstallInfo: String;
+  Dependencies: array of TDependency;
+  DelayedReboot, ForceX86: Boolean;
+  DownloadPage: TDownloadWizardPage;
+
+procedure AddDependency(const Filename, Parameters, Title, URL, Checksum: String; const ForceSuccess, InstallClean, RebootAfter: Boolean);
+var
+  Dependency: TDependency;
+  I: Integer;
+begin
+  MemoInstallInfo := MemoInstallInfo + #13#10 + '%1' + Title;
+
+  Dependency.Filename := Filename;
+  Dependency.Parameters := Parameters;
+  Dependency.Title := Title;
+
+  if FileExists(ExpandConstant('{tmp}{\}') + Filename) then begin
+    Dependency.URL := '';
+  end else begin
+    Dependency.URL := URL;
+  end;
+
+  Dependency.Checksum := Checksum;
+  Dependency.ForceSuccess := ForceSuccess;
+  Dependency.InstallClean := InstallClean;
+  Dependency.RebootAfter := RebootAfter;
+
+  I := GetArrayLength(Dependencies);
+  SetArrayLength(Dependencies, I + 1);
+  Dependencies[I] := Dependency;
+end;
+
+function IsPendingReboot: Boolean;
+var
+  Value: String;
+begin
+  Result := RegQueryMultiStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager', 'PendingFileRenameOperations', Value) or
+    (RegQueryMultiStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager', 'SetupExecute', Value) and (Value <> ''));
+end;
+
+function InstallProducts: InstallResult;
+var
+  ResultCode, I, ProductCount: Integer;
+begin
+  Result := InstallSuccessful;
+  ProductCount := GetArrayLength(Dependencies);
+  MemoInstallInfo := SetupMessage(msgReadyMemoTasks);
+
+  if ProductCount > 0 then begin
+    DownloadPage.Show;
+
+    for I := 0 to ProductCount - 1 do begin
+      if Dependencies[I].InstallClean and (DelayedReboot or IsPendingReboot) then begin
+        Result := InstallRebootRequired;
+        break;
+      end;
+
+      DownloadPage.SetText(Dependencies[I].Title, '');
+      DownloadPage.SetProgress(I + 1, ProductCount);
+
+      while True do begin
+        ResultCode := 0;
+        if ShellExec('', ExpandConstant('{tmp}{\}') + Dependencies[I].Filename, Dependencies[I].Parameters, '', SW_SHOWNORMAL, ewWaitUntilTerminated, ResultCode) then begin
+          if Dependencies[I].RebootAfter then begin
+            // delay reboot after install if we installed the last dependency anyways
+            if I = ProductCount - 1 then begin
+              DelayedReboot := True;
+            end else begin
+              Result := InstallRebootRequired;
+              MemoInstallInfo := Dependencies[I].Title;
+            end;
+            break;
+          end else if (ResultCode = 0) or Dependencies[I].ForceSuccess then begin
+            break;
+          end else if ResultCode = 3010 then begin
+            // Windows Installer ResultCode 3010: ERROR_SUCCESS_REBOOT_REQUIRED
+            DelayedReboot := True;
+            break;
+          end;
+        end;
+
+        case SuppressibleMsgBox(FmtMessage(SetupMessage(msgErrorFunctionFailed), [Dependencies[I].Title, IntToStr(ResultCode)]), mbError, MB_ABORTRETRYIGNORE, IDIGNORE) of
+          IDABORT: begin
+            Result := InstallError;
+            MemoInstallInfo := MemoInstallInfo + #13#10 + '      ' + Dependencies[I].Title;
+            break;
+          end;
+          IDIGNORE: begin
+            MemoInstallInfo := MemoInstallInfo + #13#10 + '      ' + Dependencies[I].Title;
+            break;
+          end;
+        end;
+      end;
+
+      if Result <> InstallSuccessful then begin
+        break;
+      end;
+    end;
+
+    DownloadPage.Hide;
+  end;
+end;
+
+// Inno Setup event functions
+procedure InitializeWizard;
+begin
+  DownloadPage := CreateDownloadPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), nil);
+end;
+
+function PrepareToInstall(var NeedsRestart: Boolean): String;
+var
+  I: Integer;
+begin
+  DelayedReboot := False;
+
+  case InstallProducts of
+    InstallError: begin
+      Result := MemoInstallInfo;
+    end;
+    InstallRebootRequired: begin
+      Result := MemoInstallInfo;
+      NeedsRestart := True;
+
+      // write into the registry that the installer needs to be executed again after restart
+      RegWriteStringValue(HKEY_CURRENT_USER, 'SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce', 'InstallBootstrap', ExpandConstant('{srcexe}'));
+    end;
+  end;
+end;
+
+function NeedRestart: Boolean;
+begin
+  Result := DelayedReboot;
+end;
+
+function UpdateReadyMemo(const Space, NewLine, MemoUserInfoInfo, MemoDirInfo, MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo: String): String;
+begin
+  Result := '';
+  if MemoUserInfoInfo <> '' then begin
+    Result := Result + MemoUserInfoInfo + Newline + NewLine;
+  end;
+  if MemoDirInfo <> '' then begin
+    Result := Result + MemoDirInfo + Newline + NewLine;
+  end;
+  if MemoTypeInfo <> '' then begin
+    Result := Result + MemoTypeInfo + Newline + NewLine;
+  end;
+  if MemoComponentsInfo <> '' then begin
+    Result := Result + MemoComponentsInfo + Newline + NewLine;
+  end;
+  if MemoGroupInfo <> '' then begin
+    Result := Result + MemoGroupInfo + Newline + NewLine;
+  end;
+  if MemoTasksInfo <> '' then begin
+    Result := Result + MemoTasksInfo;
+  end;
+
+  if MemoInstallInfo <> '' then begin
+    if MemoTasksInfo = '' then begin
+      Result := Result + SetupMessage(msgReadyMemoTasks);
+    end;
+    Result := Result + FmtMessage(MemoInstallInfo, [Space]);
+  end;
+end;
+
+function NextButtonClick(const CurPageID: Integer): Boolean;
+var
+  I, ProductCount: Integer;
+  Retry: Boolean;
+begin
+  Result := True;
+
+  if (CurPageID = wpReady) and (MemoInstallInfo <> '') then begin
+    DownloadPage.Show;
+
+    ProductCount := GetArrayLength(Dependencies);
+    for I := 0 to ProductCount - 1 do begin
+      if Dependencies[I].URL <> '' then begin
+        DownloadPage.Clear;
+        DownloadPage.Add(Dependencies[I].URL, Dependencies[I].Filename, Dependencies[I].Checksum);
+
+        Retry := True;
+        while Retry do begin
+          Retry := False;
+
+          try
+            DownloadPage.Download;
+          except
+            if GetExceptionMessage = SetupMessage(msgErrorDownloadAborted) then begin
+              Result := False;
+              I := ProductCount;
+            end else begin
+              case SuppressibleMsgBox(AddPeriod(GetExceptionMessage), mbError, MB_ABORTRETRYIGNORE, IDIGNORE) of
+                IDABORT: begin
+                  Result := False;
+                  I := ProductCount;
+                end;
+                IDRETRY: begin
+                  Retry := True;
+                end;
+              end;
+            end;
+          end;
+        end;
+      end;
+    end;
+
+    DownloadPage.Hide;
+  end;
+end;
+
+// architecture helper functions
+function IsX64: Boolean;
+begin
+  Result := not ForceX86 and Is64BitInstallMode;
+end;
+
+function GetString(const x86, x64: String): String;
+begin
+  if IsX64 then begin
+    Result := x64;
+  end else begin
+    Result := x86;
+  end;
+end;
+
+function GetArchitectureSuffix: String;
+begin
+  Result := GetString('', '_x64');
+end;
+
+function GetArchitectureTitle: String;
+begin
+  Result := GetString(' (x86)', ' (x64)');
+end;
+
+function CompareVersion(const Version1, Version2: String): Integer;
+var
+  Position, Number1, Number2: Integer;
+begin
+  Result := 0;
+  while (Version1 <> '') or (Version2 <> '') do begin
+    Position := Pos('.', Version1);
+    if Position > 0 then begin
+      Number1 := StrToIntDef(Copy(Version1, 1, Position - 1), 0);
+      Delete(Version1, 1, Position);
+    end else if Version1 <> '' then begin
+      Number1 := StrToIntDef(Version1, 0);
+      Version1 := '';
+    end else begin
+      Number1 := 0;
+    end;
+
+    Position := Pos('.', Version2);
+    if Position > 0 then begin
+      Number2 := StrToIntDef(Copy(Version2, 1, Position - 1), 0);
+      Delete(Version2, 1, Position);
+    end else if Version2 <> '' then begin
+      Number2 := StrToIntDef(Version2, 0);
+      Version2 := '';
+    end else begin
+      Number2 := 0;
+    end;
+
+    if Number1 < Number2 then begin
+      Result := -1;
+      break;
+    end else if Number1 > Number2 then begin
+      Result := 1;
+      break;
+    end;
+  end;
+end;
+
+#ifdef UseNetCoreCheck
+// https://github.com/dotnet/deployment-tools/tree/master/src/clickonce/native/projects/NetCoreCheck
+function IsNetCoreInstalled(const Version: String): Boolean;
+var
+  ResultCode: Integer;
+begin
+  if not FileExists(ExpandConstant('{tmp}{\}') + 'netcorecheck' + GetArchitectureSuffix + '.exe') then begin
+    ExtractTemporaryFile('netcorecheck' + GetArchitectureSuffix + '.exe');
+  end;
+  Result := ShellExec('', ExpandConstant('{tmp}{\}') + 'netcorecheck' + GetArchitectureSuffix + '.exe', Version, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0);
+end;
 #endif
 
-// content
-[Tasks]
-[Tasks]
-Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
-Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 6.1; Check: not IsAdminInstallMode
+// custom setup content
+[Languages]
+Name: en; MessagesFile: "compiler:Default.isl"
+Name: nl; MessagesFile: "compiler:Languages\Dutch.isl"
+Name: de; MessagesFile: "compiler:Languages\German.isl"
 
 [Files]
+#ifdef UseNetCoreCheck
+// download netcorecheck.exe: https://go.microsoft.com/fwlink/?linkid=2135256
+// download netcorecheck_x64.exe: https://go.microsoft.com/fwlink/?linkid=2135504
+Source: "netcorecheck.exe"; Flags: dontcopy noencryption
+Source: "netcorecheck_x64.exe"; Flags: dontcopy noencryption
+#endif
+
 Source: "..\Builds\PixiEditor-{#TargetPlatform}\PixiEditor\PixiEditor.exe"; DestDir: "{app}"; Flags: ignoreversion
 Source: "..\Builds\PixiEditor-{#TargetPlatform}\PixiEditor\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
 
@@ -94,6 +376,10 @@ Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
 Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
 Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: quicklaunchicon
 
+[Tasks]
+Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
+Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 6.1; Check: not IsAdminInstallMode
+
 [Run]
 Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
 
@@ -104,21 +390,33 @@ Root: HKCR; Subkey: "{#MyAppName}";                     ValueData: "Program {#My
 Root: HKCR; Subkey: "{#MyAppName}\DefaultIcon";             ValueData: "{app}\{#MyAppExeName},0";               ValueType: string;  ValueName: ""
 Root: HKCR; Subkey: "{#MyAppName}\shell\open\command";  ValueData: """{app}\{#MyAppExeName}"" ""%1""";  ValueType: string;  ValueName: ""
 
-[CustomMessages]
-DependenciesDir=MyProgramDependencies
-WindowsServicePack=Windows %1 Service Pack %2
-
 [Code]
-function InitializeSetup(): Boolean;
+function InitializeSetup: Boolean;
+var
+  Version: String;
 begin
-	// initialize windows version
-	initwinversion();
 
-#ifdef use_netcore31
-	netcore31();
+#ifdef UseDotNet50
+  // https://dotnet.microsoft.com/download/dotnet/5.0
+  if not IsNetCoreInstalled('Microsoft.NETCore.App 5.0.0') then begin
+    AddDependency('dotnet50' + GetArchitectureSuffix + '.exe',
+      '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+      '.NET Runtime 5.0' + GetArchitectureTitle,
+      GetString('https://download.visualstudio.microsoft.com/download/pr/a7e15da3-7a15-43c2-a481-cf50bf305214/c69b951e8b47101e90b1289c387bb01a/dotnet-runtime-5.0.0-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/36a9dc4e-1745-4f17-8a9c-f547a12e3764/ae25e38f20a4854d5e015a88659a22f9/dotnet-runtime-5.0.0-win-x64.exe'),
+      '', False, False, False);
+  end;
 #endif
-#ifdef use_netcore31desktop
-	netcore31desktop();
+
+#ifdef UseDotNet50Desktop
+  // https://dotnet.microsoft.com/download/dotnet/5.0
+  if not IsNetCoreInstalled('Microsoft.WindowsDesktop.App 5.0.0') then begin
+    AddDependency('dotnet50desktop' + GetArchitectureSuffix + '.exe',
+      '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+      '.NET Desktop Runtime 5.0' + GetArchitectureTitle,
+      GetString('https://download.visualstudio.microsoft.com/download/pr/b2780d75-e54a-448a-95fc-da9721b2b4c2/62310a9e9f0ba7b18741944cbae9f592/windowsdesktop-runtime-5.0.0-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/1b3a8899-127a-4465-a3c2-7ce5e4feb07b/1e153ad470768baa40ed3f57e6e7a9d8/windowsdesktop-runtime-5.0.0-win-x64.exe'),
+      '', False, False, False);
+  end;
 #endif
-	Result := true;
+
+  Result := True;
 end;

+ 353 - 56
Installer/installer-setup-x86-light.iss

@@ -1,16 +1,22 @@
-// contribute: https://github.com/domgho/InnoDependencyInstaller
-// official article: https://www.codeproject.com/Articles/20868/Inno-Setup-Dependency-Installer
+// contribute: https://github.com/DomGries/InnoDependencyInstaller
+// official article: https://codeproject.com/Articles/20868/Inno-Setup-Dependency-Installer
 
+// requires netcorecheck.exe and netcorecheck_x64.exe (see download link below)
+#define UseNetCoreCheck
+#ifdef UseNetCoreCheck
+  ;#define UseDotNet50
+  #define UseDotNet50Desktop
+#endif
+
+// custom setup info
 #define MyAppName "PixiEditor"
-#define MyAppVersion GetFileVersion("..\Builds\PixiEditor-x64-light\PixiEditor\PixiEditor.exe")     ;Not perfect solution, it's enviroment dependend
+#define MyAppVersion GetFileVersion("..\Builds\PixiEditor-x86-light\PixiEditor\PixiEditor.exe")     ;Not perfect solution, it's enviroment dependend
 #define MyAppPublisher "PixiEditor"
 #define MyAppURL "https://github.com/PixiEditor/PixiEditor"
 #define MyAppExeName "PixiEditor.exe"
 #define TargetPlatform "x86-light"
 
 [Setup]
-; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications.
-; (To generate a new GUID, click Tools | Generate GUID inside the IDE.)
 AppId={{83DE4F2A-1F75-43AE-9546-3184F1C44517}
 AppName={#MyAppName}
 AppVersion={#MyAppVersion}
@@ -35,57 +41,332 @@ SolidCompression=yes
 WizardStyle=modern
 ChangesAssociations = yes
 
+MinVersion=6.0
 PrivilegesRequired=admin
-ArchitecturesAllowed=x86
-ArchitecturesInstallIn64BitMode=x64 ia64
 
-// downloading and installing dependencies will only work if the memo/ready page is enabled (default and current behaviour)
+// dependency installation requires ready page and ready memo to be enabled (default behaviour)
 DisableReadyPage=no
 DisableReadyMemo=no
 
-// requires netcorecheck.exe and netcorecheck_x64.exe in src dir
-#define use_netcorecheck
-#define use_netcore31
-#define use_netcore31desktop
-// supported languages
-#include "scripts\lang\english.iss"
-#include "scripts\lang\german.iss"
-#include "scripts\lang\french.iss"
-#include "scripts\lang\italian.iss"
-#include "scripts\lang\dutch.iss"
-
-#ifdef UNICODE
-#include "scripts\lang\chinese.iss"
-#include "scripts\lang\polish.iss"
-#include "scripts\lang\russian.iss"
-#include "scripts\lang\japanese.iss"
-#endif
 
-// shared code for installing the products
-#include "scripts\products.iss"
+// shared code for installing the dependencies
+[Code]
+// types and variables
+type
+  TDependency = record
+    Filename: String;
+    Parameters: String;
+    Title: String;
+    URL: String;
+    Checksum: String;
+    ForceSuccess: Boolean;
+    InstallClean: Boolean;
+    RebootAfter: Boolean;
+  end;
 
-// helper functions
-#include "scripts\products\stringversion.iss"
-#include "scripts\products\winversion.iss"
-#include "scripts\products\dotnetfxversion.iss"
+  InstallResult = (InstallSuccessful, InstallRebootRequired, InstallError);
 
-#ifdef use_netcorecheck
-#include "scripts\products\netcorecheck.iss"
-#endif
-#ifdef use_netcore31
-#include "scripts\products\netcore31.iss"
-#endif
-#ifdef use_netcore31desktop
-#include "scripts\products\netcore31desktop.iss"
+var
+  MemoInstallInfo: String;
+  Dependencies: array of TDependency;
+  DelayedReboot, ForceX86: Boolean;
+  DownloadPage: TDownloadWizardPage;
+
+procedure AddDependency(const Filename, Parameters, Title, URL, Checksum: String; const ForceSuccess, InstallClean, RebootAfter: Boolean);
+var
+  Dependency: TDependency;
+  I: Integer;
+begin
+  MemoInstallInfo := MemoInstallInfo + #13#10 + '%1' + Title;
+
+  Dependency.Filename := Filename;
+  Dependency.Parameters := Parameters;
+  Dependency.Title := Title;
+
+  if FileExists(ExpandConstant('{tmp}{\}') + Filename) then begin
+    Dependency.URL := '';
+  end else begin
+    Dependency.URL := URL;
+  end;
+
+  Dependency.Checksum := Checksum;
+  Dependency.ForceSuccess := ForceSuccess;
+  Dependency.InstallClean := InstallClean;
+  Dependency.RebootAfter := RebootAfter;
+
+  I := GetArrayLength(Dependencies);
+  SetArrayLength(Dependencies, I + 1);
+  Dependencies[I] := Dependency;
+end;
+
+function IsPendingReboot: Boolean;
+var
+  Value: String;
+begin
+  Result := RegQueryMultiStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager', 'PendingFileRenameOperations', Value) or
+    (RegQueryMultiStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager', 'SetupExecute', Value) and (Value <> ''));
+end;
+
+function InstallProducts: InstallResult;
+var
+  ResultCode, I, ProductCount: Integer;
+begin
+  Result := InstallSuccessful;
+  ProductCount := GetArrayLength(Dependencies);
+  MemoInstallInfo := SetupMessage(msgReadyMemoTasks);
+
+  if ProductCount > 0 then begin
+    DownloadPage.Show;
+
+    for I := 0 to ProductCount - 1 do begin
+      if Dependencies[I].InstallClean and (DelayedReboot or IsPendingReboot) then begin
+        Result := InstallRebootRequired;
+        break;
+      end;
+
+      DownloadPage.SetText(Dependencies[I].Title, '');
+      DownloadPage.SetProgress(I + 1, ProductCount);
+
+      while True do begin
+        ResultCode := 0;
+        if ShellExec('', ExpandConstant('{tmp}{\}') + Dependencies[I].Filename, Dependencies[I].Parameters, '', SW_SHOWNORMAL, ewWaitUntilTerminated, ResultCode) then begin
+          if Dependencies[I].RebootAfter then begin
+            // delay reboot after install if we installed the last dependency anyways
+            if I = ProductCount - 1 then begin
+              DelayedReboot := True;
+            end else begin
+              Result := InstallRebootRequired;
+              MemoInstallInfo := Dependencies[I].Title;
+            end;
+            break;
+          end else if (ResultCode = 0) or Dependencies[I].ForceSuccess then begin
+            break;
+          end else if ResultCode = 3010 then begin
+            // Windows Installer ResultCode 3010: ERROR_SUCCESS_REBOOT_REQUIRED
+            DelayedReboot := True;
+            break;
+          end;
+        end;
+
+        case SuppressibleMsgBox(FmtMessage(SetupMessage(msgErrorFunctionFailed), [Dependencies[I].Title, IntToStr(ResultCode)]), mbError, MB_ABORTRETRYIGNORE, IDIGNORE) of
+          IDABORT: begin
+            Result := InstallError;
+            MemoInstallInfo := MemoInstallInfo + #13#10 + '      ' + Dependencies[I].Title;
+            break;
+          end;
+          IDIGNORE: begin
+            MemoInstallInfo := MemoInstallInfo + #13#10 + '      ' + Dependencies[I].Title;
+            break;
+          end;
+        end;
+      end;
+
+      if Result <> InstallSuccessful then begin
+        break;
+      end;
+    end;
+
+    DownloadPage.Hide;
+  end;
+end;
+
+// Inno Setup event functions
+procedure InitializeWizard;
+begin
+  DownloadPage := CreateDownloadPage(SetupMessage(msgWizardPreparing), SetupMessage(msgPreparingDesc), nil);
+end;
+
+function PrepareToInstall(var NeedsRestart: Boolean): String;
+var
+  I: Integer;
+begin
+  DelayedReboot := False;
+
+  case InstallProducts of
+    InstallError: begin
+      Result := MemoInstallInfo;
+    end;
+    InstallRebootRequired: begin
+      Result := MemoInstallInfo;
+      NeedsRestart := True;
+
+      // write into the registry that the installer needs to be executed again after restart
+      RegWriteStringValue(HKEY_CURRENT_USER, 'SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce', 'InstallBootstrap', ExpandConstant('{srcexe}'));
+    end;
+  end;
+end;
+
+function NeedRestart: Boolean;
+begin
+  Result := DelayedReboot;
+end;
+
+function UpdateReadyMemo(const Space, NewLine, MemoUserInfoInfo, MemoDirInfo, MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo: String): String;
+begin
+  Result := '';
+  if MemoUserInfoInfo <> '' then begin
+    Result := Result + MemoUserInfoInfo + Newline + NewLine;
+  end;
+  if MemoDirInfo <> '' then begin
+    Result := Result + MemoDirInfo + Newline + NewLine;
+  end;
+  if MemoTypeInfo <> '' then begin
+    Result := Result + MemoTypeInfo + Newline + NewLine;
+  end;
+  if MemoComponentsInfo <> '' then begin
+    Result := Result + MemoComponentsInfo + Newline + NewLine;
+  end;
+  if MemoGroupInfo <> '' then begin
+    Result := Result + MemoGroupInfo + Newline + NewLine;
+  end;
+  if MemoTasksInfo <> '' then begin
+    Result := Result + MemoTasksInfo;
+  end;
+
+  if MemoInstallInfo <> '' then begin
+    if MemoTasksInfo = '' then begin
+      Result := Result + SetupMessage(msgReadyMemoTasks);
+    end;
+    Result := Result + FmtMessage(MemoInstallInfo, [Space]);
+  end;
+end;
+
+function NextButtonClick(const CurPageID: Integer): Boolean;
+var
+  I, ProductCount: Integer;
+  Retry: Boolean;
+begin
+  Result := True;
+
+  if (CurPageID = wpReady) and (MemoInstallInfo <> '') then begin
+    DownloadPage.Show;
+
+    ProductCount := GetArrayLength(Dependencies);
+    for I := 0 to ProductCount - 1 do begin
+      if Dependencies[I].URL <> '' then begin
+        DownloadPage.Clear;
+        DownloadPage.Add(Dependencies[I].URL, Dependencies[I].Filename, Dependencies[I].Checksum);
+
+        Retry := True;
+        while Retry do begin
+          Retry := False;
+
+          try
+            DownloadPage.Download;
+          except
+            if GetExceptionMessage = SetupMessage(msgErrorDownloadAborted) then begin
+              Result := False;
+              I := ProductCount;
+            end else begin
+              case SuppressibleMsgBox(AddPeriod(GetExceptionMessage), mbError, MB_ABORTRETRYIGNORE, IDIGNORE) of
+                IDABORT: begin
+                  Result := False;
+                  I := ProductCount;
+                end;
+                IDRETRY: begin
+                  Retry := True;
+                end;
+              end;
+            end;
+          end;
+        end;
+      end;
+    end;
+
+    DownloadPage.Hide;
+  end;
+end;
+
+// architecture helper functions
+function IsX64: Boolean;
+begin
+  Result := not ForceX86 and Is64BitInstallMode;
+end;
+
+function GetString(const x86, x64: String): String;
+begin
+  if IsX64 then begin
+    Result := x64;
+  end else begin
+    Result := x86;
+  end;
+end;
+
+function GetArchitectureSuffix: String;
+begin
+  Result := GetString('', '_x64');
+end;
+
+function GetArchitectureTitle: String;
+begin
+  Result := GetString(' (x86)', ' (x64)');
+end;
+
+function CompareVersion(const Version1, Version2: String): Integer;
+var
+  Position, Number1, Number2: Integer;
+begin
+  Result := 0;
+  while (Version1 <> '') or (Version2 <> '') do begin
+    Position := Pos('.', Version1);
+    if Position > 0 then begin
+      Number1 := StrToIntDef(Copy(Version1, 1, Position - 1), 0);
+      Delete(Version1, 1, Position);
+    end else if Version1 <> '' then begin
+      Number1 := StrToIntDef(Version1, 0);
+      Version1 := '';
+    end else begin
+      Number1 := 0;
+    end;
+
+    Position := Pos('.', Version2);
+    if Position > 0 then begin
+      Number2 := StrToIntDef(Copy(Version2, 1, Position - 1), 0);
+      Delete(Version2, 1, Position);
+    end else if Version2 <> '' then begin
+      Number2 := StrToIntDef(Version2, 0);
+      Version2 := '';
+    end else begin
+      Number2 := 0;
+    end;
+
+    if Number1 < Number2 then begin
+      Result := -1;
+      break;
+    end else if Number1 > Number2 then begin
+      Result := 1;
+      break;
+    end;
+  end;
+end;
+
+#ifdef UseNetCoreCheck
+// https://github.com/dotnet/deployment-tools/tree/master/src/clickonce/native/projects/NetCoreCheck
+function IsNetCoreInstalled(const Version: String): Boolean;
+var
+  ResultCode: Integer;
+begin
+  if not FileExists(ExpandConstant('{tmp}{\}') + 'netcorecheck' + GetArchitectureSuffix + '.exe') then begin
+    ExtractTemporaryFile('netcorecheck' + GetArchitectureSuffix + '.exe');
+  end;
+  Result := ShellExec('', ExpandConstant('{tmp}{\}') + 'netcorecheck' + GetArchitectureSuffix + '.exe', Version, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) and (ResultCode = 0);
+end;
 #endif
 
-// content
-[Tasks]
-[Tasks]
-Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
-Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 6.1; Check: not IsAdminInstallMode
+// custom setup content
+[Languages]
+Name: en; MessagesFile: "compiler:Default.isl"
+Name: nl; MessagesFile: "compiler:Languages\Dutch.isl"
+Name: de; MessagesFile: "compiler:Languages\German.isl"
 
 [Files]
+#ifdef UseNetCoreCheck
+// download netcorecheck.exe: https://go.microsoft.com/fwlink/?linkid=2135256
+// download netcorecheck_x64.exe: https://go.microsoft.com/fwlink/?linkid=2135504
+Source: "netcorecheck.exe"; Flags: dontcopy noencryption
+Source: "netcorecheck_x64.exe"; Flags: dontcopy noencryption
+#endif
+
 Source: "..\Builds\PixiEditor-{#TargetPlatform}\PixiEditor\PixiEditor.exe"; DestDir: "{app}"; Flags: ignoreversion
 Source: "..\Builds\PixiEditor-{#TargetPlatform}\PixiEditor\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
 
@@ -94,6 +375,10 @@ Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
 Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon
 Name: "{userappdata}\Microsoft\Internet Explorer\Quick Launch\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: quicklaunchicon
 
+[Tasks]
+Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
+Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 6.1; Check: not IsAdminInstallMode
+
 [Run]
 Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent
 
@@ -104,21 +389,33 @@ Root: HKCR; Subkey: "{#MyAppName}";                     ValueData: "Program {#My
 Root: HKCR; Subkey: "{#MyAppName}\DefaultIcon";             ValueData: "{app}\{#MyAppExeName},0";               ValueType: string;  ValueName: ""
 Root: HKCR; Subkey: "{#MyAppName}\shell\open\command";  ValueData: """{app}\{#MyAppExeName}"" ""%1""";  ValueType: string;  ValueName: ""
 
-[CustomMessages]
-DependenciesDir=MyProgramDependencies
-WindowsServicePack=Windows %1 Service Pack %2
-
 [Code]
-function InitializeSetup(): Boolean;
+function InitializeSetup: Boolean;
+var
+  Version: String;
 begin
-	// initialize windows version
-	initwinversion();
 
-#ifdef use_netcore31
-	netcore31();
+#ifdef UseDotNet50
+  // https://dotnet.microsoft.com/download/dotnet/5.0
+  if not IsNetCoreInstalled('Microsoft.NETCore.App 5.0.0') then begin
+    AddDependency('dotnet50' + GetArchitectureSuffix + '.exe',
+      '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+      '.NET Runtime 5.0' + GetArchitectureTitle,
+      GetString('https://download.visualstudio.microsoft.com/download/pr/a7e15da3-7a15-43c2-a481-cf50bf305214/c69b951e8b47101e90b1289c387bb01a/dotnet-runtime-5.0.0-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/36a9dc4e-1745-4f17-8a9c-f547a12e3764/ae25e38f20a4854d5e015a88659a22f9/dotnet-runtime-5.0.0-win-x64.exe'),
+      '', False, False, False);
+  end;
 #endif
-#ifdef use_netcore31desktop
-	netcore31desktop();
+
+#ifdef UseDotNet50Desktop
+  // https://dotnet.microsoft.com/download/dotnet/5.0
+  if not IsNetCoreInstalled('Microsoft.WindowsDesktop.App 5.0.0') then begin
+    AddDependency('dotnet50desktop' + GetArchitectureSuffix + '.exe',
+      '/lcid ' + IntToStr(GetUILanguage) + ' /passive /norestart',
+      '.NET Desktop Runtime 5.0' + GetArchitectureTitle,
+      GetString('https://download.visualstudio.microsoft.com/download/pr/b2780d75-e54a-448a-95fc-da9721b2b4c2/62310a9e9f0ba7b18741944cbae9f592/windowsdesktop-runtime-5.0.0-win-x86.exe', 'https://download.visualstudio.microsoft.com/download/pr/1b3a8899-127a-4465-a3c2-7ce5e4feb07b/1e153ad470768baa40ed3f57e6e7a9d8/windowsdesktop-runtime-5.0.0-win-x64.exe'),
+      '', False, False, False);
+  end;
 #endif
-	Result := true;
+
+  Result := True;
 end;

+ 0 - 0
Installer/src/netcorecheck.exe → Installer/netcorecheck.exe


+ 0 - 0
Installer/src/netcorecheck_x64.exe → Installer/netcorecheck_x64.exe


+ 0 - 49
Installer/scripts/isxdl/chinese.ini

@@ -1,49 +0,0 @@
-[strings]
-; General
-100=文件下载
-101=要取消下载吗?
-102=%1 (%2 / %3)
-103=%1 KB
-104=%1 KB / %2 KB (%3%)
-
-; Status information
-110=获取文件信息
-111=正在重定向到 %1
-112=正在发送请求...
-113=正在响应 %1
-114=已连接到 %1
-115=接收...
-116=正在连接到 %1
-
-; Error messages
-120=网络连接错误\n\n%1
-121=访问出错 %1.\n\n服务器返回的状态码为 %2.
-122=URL 读取错误.\n\n%1
-123=文件写入错误 %1.\n\n%2
-124=文件打开错误 %1.\n\n%2
-125='%1' 是无效的 URL.
-126=打开出错 %1.\n\n%2
-127=发送请求出错.\n\n%1
-128=不支持的协议类型. 只支持 HTTP 和 FTP 类型.
-129=无法连接到 %1.\n\n%2
-130=查询状态码失败.\n\n%1
-131=文件请求错误.\n\n%1
-
-; Other
-144=关于...
-146=下载
-147=安装程序正在下载附加文件到计算机上.
-
-; labels
-160=文件:
-161=速度:
-162=状态:
-163=已用时间:
-164=剩余时间:
-165=当前文件:
-166=总进度:
-167=取消
-168=确定
-169=用户名和密码
-170=用户名:
-171=密码:

+ 0 - 53
Installer/scripts/isxdl/czech.ini

@@ -1,53 +0,0 @@
-[strings]
-
-; Translation (c) 2005 Martin Kozák ([email protected])
-
-; General
-100=Stažení souboru
-101=Přejete si přerušit stahování?
-102=%1 (%2 z %3)
-103=%1 KB
-104=%1 KB z %2 KB (%3%)
-
-; Status information
-110=Získávání informací o souboru...
-111=Přesměrování na %1
-112=Odesílání požadavku...
-113=Zpracování %1
-114=Spojení s %1 navázáno
-115=Přijímání...
-116=Připojování k %1
-
-; Error messages
-120=Chyba při připojování k síti Internet.\n\n%1
-121=Chyba při otevírání %1.\n\nServer nastavil návratový kód %2.
-122=Chyba při čtení URL.\n\n%1
-123=Chyba při zápisu do souboru %1.\n\n%2
-124=Chyba při otevírání souboru %1.\n\n%2
-125='%1' není platné URL.
-126=Chyba při otevírání %1.\n\n%2
-127=Chyba při zasílání požadavku.\n\n%1
-128=Nepodporovaný protokol. Podporovány jsou pouze protokoly HTTP a FTP.
-129=Pokus o připojení k %1 selhalo.\n\n%2
-130=Pokus o získání návratového kódu serveru selhal.\n\n%1
-131=Chyba při zadávání požadavku na soubor.\n\n%1
-
-; Other
-144=O knihovně...
-146=Stažení komponent
-147=Průvodce instalací stahuje přídavné komponenty do vašeho počítače.
-
-; labels
-160=Soubor:
-161=Přenosová rychlost:
-162=Stav:
-163=Uplynulý čas:
-164=Zbývající čas:
-165=Zpracovávaný soubor:
-166=Celkový průběh:
-167=Zrušit
-168=OK
-169=Uživatelské jméno a heslo
-170=Uživatelské jméno:
-171=Heslo:
-

+ 0 - 49
Installer/scripts/isxdl/dutch.ini

@@ -1,49 +0,0 @@
-[strings]
-; Algemeen
-100=Bestand downloaden
-101=Wilt u de download annuleren?
-102=1% (%2 van %3)
-103=%1 KB
-104=%1 KB van %2 KB (%3%)
-
-; Status informatie
-110=Bestandsinformatie ophalen ...
-111=omgeleid naar %1
-112=Verzoek verzenden ...
-113=oplossen %1
-114=Verbonden met 1%
-115=Het ontvangen ...
-116=Verbinden met %1
-
-; foutmeldingen
-120=Fout bij het verbinden met Internet. \n\n%1
-121=Fout bij het openen van %1.\n\nDe server terug statuscode %2.
-122=Fout bij het lezen URL.\n\n%1
-123=Fout bij het schrijven bestand %1.\n\n%2
-124=Fout bij openen bestand %1.\n\n%2
-125='%1' is een ongeldige URL.
-126=Fout bij openen %1.\n\n%2
-127=Fout bij het verzenden verzoek.\n\n%1
-128=Niet ondersteund protocol. Alleen HTTP en FTP-protocollen worden ondersteund.
-129=Kan geen verbinding maken %1.\n\n%2
-130=Kan de status code opvragen.\n\n%1
-131=Fout bij het aanvragen van het bestand.\n\n%1
-
-; anders
-144=Over ...
-146=Download
-147=Setup is nu het downloaden van extra bestanden naar uw computer.
-
-; etiket
-160=Bestand:
-161=Speed:
-162=Status:
-163=Verstreken tijd:
-164=Resterende tijd:
-165=Huidige File:
-166=Algemeen Voortgang:
-167=Annuleren
-168=OK
-169=gebruikersnaam en wachtwoord
-170=Gebruikersnaam:
-171=Wachtwoord:

+ 0 - 49
Installer/scripts/isxdl/english.ini

@@ -1,49 +0,0 @@
-[strings]
-; General
-100=File download
-101=Do you want to cancel the download?
-102=%1 (%2 of %3)
-103=%1 KB
-104=%1 KB of %2 KB (%3%)
-
-; Status information
-110=Getting file information...
-111=Redirecting to %1
-112=Sending request...
-113=Resolving %1
-114=Connected to %1
-115=Receiving...
-116=Connecting to %1
-
-; Error messages
-120=Error connecting to the internet.\n\n%1
-121=Error opening %1.\n\nThe server returned status code %2.
-122=Error reading URL.\n\n%1
-123=Error writing file %1.\n\n%2
-124=Error opening file %1.\n\n%2
-125='%1' is an invalid URL.
-126=Error opening %1.\n\n%2
-127=Error sending request.\n\n%1
-128=Unsupported protocol. Only HTTP and FTP protocols are supported.
-129=Failed to connect to %1.\n\n%2
-130=Failed to query status code.\n\n%1
-131=Error requesting file.\n\n%1
-
-; Other
-144=About...
-146=Download
-147=Setup is now downloading additional files to your computer.
-
-; labels
-160=File:
-161=Speed:
-162=Status:
-163=Elapsed Time:
-164=Remaining Time:
-165=Current File:
-166=Overall Progress:
-167=Cancel
-168=OK
-169=User Name and Password
-170=User Name:
-171=Password:

+ 0 - 45
Installer/scripts/isxdl/french.ini

@@ -1,45 +0,0 @@
-[strings]
-; General
-100=Téléchargement des fichiers
-101=Souhaitez-vous annuler le téléchargement ?
-102=%1 (%2 / %3)
-103=%1 Ko
-104=%1 Ko / %2 Ko (%3%)
-
-; Etat du téléchargement
-110=Accès au fichier...
-111=Redirection vers %1
-112=Envoi de la requête...
-113=Recherche %1
-114=Connecté à %1
-115=Réception...
-116=Connexion à %1
-
-; Messages d'erreur
-120=Impossible de se connecter à internet.\n\n%1
-121=Impossible d'ouvrir %1.\n\nLe serveur a renvoyé le code d'erreur %2.
-122=Impossible de lire l'adresse.\n\n%1
-123=Impossible de créer le fichier %1.\n\n%2
-124=Impossible d'ouvrir le fichier %1.\n\n%2
-125='%1' est une adresse incorrecte.
-126=Impossible d'ouvrir %1.\n\n%2
-127=Impossible d'accéder au serveur.\n\n%1
-128=Protocole non supporté. Seuls les protocoles HTTP et FTP sont pris en charge.
-129=Impossible de se connecter à %1.\n\n%2
-130=Impossible de récupérer le code d'état.\n\n%1
-131=Impossible de récupérer le fichier.\n\n%1
-
-; Autre
-144=A propos...
-146=Téléchargement
-147=Certains fichiers requis vont être téléchargés.
-
-; Labels
-160=Fichier :
-161=Vitesse :
-162=Etat :
-163=Temps écoulé :
-164=Temps restant :
-165=Fichier courant :
-166=Tous les fichiers :
-167=Annuler

+ 0 - 45
Installer/scripts/isxdl/french2.ini

@@ -1,45 +0,0 @@
-[strings]
-; General
-100=Téléchargement de fichier
-101=Voulez vous annuler le téléchargement ?
-102=%1 (%2 de %3)
-103=%1 Ko
-104=%1 Ko de %2 Ko (%3%)
-
-; Status information
-110=Réception des informations du fichier...
-111=Redirection vers %1
-112=envoie de la demande...
-113=Résolution %1
-114=Connecté a %1
-115=Réception...
-116=Connexion à %1
-
-; Error messages
-120=Erreur de connexion à Internet.\n\n%1
-121=Erreur d'ouverture%1.\n\nLe Serveur à répondu par le code d'état %2.
-122=Erreur de lecture de l'URL.\n\n%1
-123=Erreur d'écriture du fichier %1.\n\n%2
-124=Erreur d'ouverture du fichier %1.\n\n%2
-125='%1' est une URL invalide.
-126=Erreur d’ouverture %1.\n\n%2
-127=Erreur pendant l'envoi de la demande.\n\n%1
-128=Protocole non supporté. Seuls les protocoles HTTP et FTP sont acceptés.
-129=Echec de connexion à %1.\n\n%2
-130=Echec d'obtention du code d'état.\n\n%1
-131=Erreur lors de la demande du fichier.\n\n%1
-
-; Other
-144=A Propos...
-146=Téléchargement
-147=LiveUpdate télécharge maintenant des fichiers complémentaires sur votre ordinateur.
-
-; labels
-160=Fichier:
-161=Vitesse:
-162=Etat:
-163=Temps écoulé:
-164=Temps restant:
-165=Fichier en cours:
-166=Avancement global:
-167=Annuler

+ 0 - 46
Installer/scripts/isxdl/french3.ini

@@ -1,46 +0,0 @@
-; By Fabien ILLIDE ([email protected])
-[strings]
-; General
-100=Téléchargement de fichier
-101=Voulez-vous annuler le téléchargement ?
-102=%1 (%2 de %3)
-103=%1 Ko
-104=%1 Ko de %2 Ko (%3%)
-
-; Status information
-110=Obtention des informations du fichier...
-111=Redirection vers %1
-112=Envoi de la requête...
-113=Résolution de %1
-114=Connecté à %1
-115=Réception...
-116=Connexion à %1
-
-; Error messages
-120=Erreur de connexion à Internet.\n\n%1
-121=Erreur en ouvrant %1.\n\nLe serveur à retourné le code d'état %2.
-122=Erreur de lecture d'URL.\n\n%1
-123=Erreur d'écriture pour %1.\n\n%2
-124=Erreur en ouvrant le fichier %1.\n\n%2
-125='%1' est une URL invalide.
-126=Erreur en ouvrant %1.\n\n%2
-127=Erreur d'envoi de requête.\n\n%1
-128=Protocole non supporté. Seuls les protocoles HTTP et FTP sont supportés.
-129=Echec de connexion à %1.\n\n%2
-130=Echec de demande du code d'état.\n\n%1
-131=Erreur en demandant le fichier.\n\n%1
-
-; Other
-144=A propos...
-146=Télécharger
-147=L'installateur télécharge maintenant les fichiers additionnels sur votre ordinateur.
-
-; labels
-160=Fichier :
-161=Vitesse :
-162=Etat :
-163=Temps écoulé :
-164=Temps restant :
-165=Fichier en cours :
-166=Avancement global :
-167=Annuler

+ 0 - 49
Installer/scripts/isxdl/german.ini

@@ -1,49 +0,0 @@
-[strings]
-; General
-100=Datei herunterladen
-101=Möchten Sie das Herunterladen der Datei abbrechen?
-102=%1 (%2 von %3)
-103=%1 KB
-104=%1 KB von %2 KB (%3%)
-
-; Status information
-110=Dateiinformationen werden ermittelt...
-111=Weiterleitung zu %1
-112=Anforderung wird gesendet...
-113=Auflösen von %1
-114=Verbunden mit %1
-115=Empfange...
-116=Verbinden mit %1
-
-; Error messages
-120=Fehler beim Verbinden mit dem Internet.\n\n%1
-121=Fehler beim Öffnen von %1.\n\nDer Server meldet Statuscode %2.
-122=Fehler beim Lesen der URL.\n\n%1
-123=Fehler beim Schreiben der Datei %1.\n\n%2
-124=Fehler beim Öffnen der Datei %1.\n\n%2
-125='%1' ist eine ungültige URL.
-126=Fehler beim Öffnen von %1.\n\n%2
-127=Fehler beim Senden der Anforderung.\n\n%1
-128=Protokoll wird nicht unterstützt. Nur HTTP und FTP werden unterstützt.
-129=Verbindung zu %1 fehlgeschlagen.\n\n%2
-130=Fehler bei der Abfrage des Statuscodes.\n\n%1
-131=Fehler bei der Anforderung der Datei.\n\n%1
-
-; Other
-144=Über...
-146=Download
-147=Das Setup lädt nun zusätzliche Dateien auf Ihren Computer.
-
-; labels
-160=Datei:
-161=Geschwindigkeit:
-162=Status:
-163=Bisherige Zeit:
-164=Verbleibende Zeit:
-165=Aktuelle Datei:
-166=Gesamter Vorgang:
-167=Abbrechen
-168=OK
-169=Benutzername und Kennwort
-170=Benutzername:
-171=Kennwort:

BIN
Installer/scripts/isxdl/isxdl.dll


+ 0 - 12
Installer/scripts/isxdl/isxdl.iss

@@ -1,12 +0,0 @@
-[Files]
-Source: "scripts\isxdl\isxdl.dll"; Flags: dontcopy noencryption
-
-[Code]
-procedure isxdl_AddFile(URL, Filename: PAnsiChar);
-external 'isxdl_AddFile@files:isxdl.dll stdcall';
-
-function isxdl_DownloadFiles(hWnd: Integer): Integer;
-external 'isxdl_DownloadFiles@files:isxdl.dll stdcall';
-
-function isxdl_SetOption(Option, Value: PAnsiChar): Integer;
-external 'isxdl_SetOption@files:isxdl.dll stdcall';

+ 0 - 49
Installer/scripts/isxdl/italian.ini

@@ -1,49 +0,0 @@
-[strings]
-; Generale
-100=Download del file
-101=Vuoi annullare il download?
-102=%1 (%2 di %3)
-103=%1 KB
-104=%1 KB di %2 KB (%3%)
-
-; Informazioni di servizio
-110=Raccolta informazioni sul file...
-111=Reindirizzamento a %1
-112=Invio richiesta...
-113=Risoluzione %1
-114=Connesso al %1
-115=Ricezione...
-116=Collegamento a %1
-
-; Messaggi di errore
-120=Errore nel collegamento a Internet.\n\n%1
-121=Errore nell'apertura di %1.\n\nIl server ha restituito il codice %2.
-122=Errore nella lettura dell'URL.\n\n%1
-123=Errore nella scrittura del file %1.\n\n%2
-124=Errore nell'apertura del file %1.\n\n%2
-125='%1' è un URL non valido.
-126=Errore nell'apertura di %1.\n\n%2
-127=Errore durante l'invio della richiesta.\n\n%1
-128=Protocollo non supportato. Sono supportati solo i protocolli HTTP e FTP.
-129=Impossibile connettersi a %1.\n\n%2
-130=Impossibile risolvere il codice di servizio.\n\n%1
-131=Errore nella richiesta del file.\n\n%1
-
-; Altro
-144=Informazioni su...
-146=Download
-147=Il programma d'installazione sta scaricando sul computer i files aggiuntivi.
-
-; Etichette
-160=File:
-161=Velocità:
-162=Stato:
-163=Tempo trascorso:
-164=Tempo rimanente:
-165=File attuale:
-166=Avanzamento generale:
-167=Annulla
-168=OK
-169=Nome utente e password
-170=Nome utente:
-171=Password:

+ 0 - 49
Installer/scripts/isxdl/japanese.ini

@@ -1,49 +0,0 @@
-[strings]
-; General
-100=ファイルダウンロード
-101=ダウンロードをキャンセルしますか?
-102=%1 (%2 of %3)
-103=%1 KB
-104=%1 KB of %2 KB (%3%)
-
-; Status information
-110=ファイル情報を取得中...
-111=リダイレクト %1
-112=リクエスト送信...
-113=解決中 %1
-114=接続完了 %1
-115=受信中...
-116=接続中 %1
-
-; Error messages
-120=インターネット接続エラー.\n\n%1
-121=開始エラー %1.\n\nサーバーのステータスコード %2.
-122=URL読み取りエラー.\n\n%1
-123=ファイル書込みエラー %1.\n\n%2
-124=ファイルオープンエラー %1.\n\n%2
-125='%1' は不正なURLです.
-126=オープンエラー %1.\n\n%2
-127=リクエスト送信エラー.\n\n%1
-128=サポートされていないプロトコルです. HTTPとFTPプロトコルだけがサポートされています.
-129=接続に失敗しました %1.\n\n%2
-130=ステータスコードの問い合わせ失敗.\n\n%1
-131=ファイルリクエストエラー.\n\n%1
-
-; Other
-144=About...
-146=ダウンロード
-147=セットアップはコンピューターに追加のファイルをダウンロードしています.
-
-; labels
-160=ファイル:
-161=速度:
-162=状態:
-163=経過時間:
-164=残り時間:
-165=現在のファイル:
-166=全体の進捗状況:
-167=キャンセル
-168=OK
-169=ユーザーとパスワード
-170=ユーザー:
-171=パスワード:

+ 0 - 49
Installer/scripts/isxdl/korean.ini

@@ -1,49 +0,0 @@
-[strings]
-; General
-100=파일 다운로드
-101=다운로드를 취소하시겠습니까?
-102=%1 (%2 중 %3)
-103=%1 KB
-104=%1 KB 중 %2 KB (%3%)
-
-; Status information
-110=파일 정보를 받고 있습니다...
-111=Redirecting to %1
-112=요청을 보내고 있습니다...
-113= %1
-114=%1(으)로 연결되었습니다.
-115=받는중...
-116=%1(으)로 연결중입니다.
-
-; Error messages
-120=인터넷 연결중 문제가 발생했습니다.\n\n%1
-121=여는도중 애러 발생 %1.\n\n서버 상태 메시지 %2.
-122=URL을 읽는 도중 문제가 발생했습니다.\n\n%1
-123=파일을 쓰는 도중 문제가 발생했습니다 %1.\n\n%2
-124=파일을 여는 도중 문제가 발생했습니다 %1.\n\n%2
-125='%1' 은 잘못된 URL입니다.
-126=여는 도중 문제가 발생했습니다 %1.\n\n%2
-127=요청을 보내는 도중 문제가 발생했습니다\n\n%1
-128=지원하지 않는 프로토콜입니다. HTTP 와 FTP 만을 지원합니다.
-129=%1로 연결이 실패하였습니다.\n\n%2
-130=쿼리가 실패했습니다. 상태 코드는 다음과 같습니다.\n\n%1
-131=파일 요청중 문제가 발생했습니다.\n\n%1
-
-; Other
-144=About...
-146=다운로드
-147=이 설치 프로그램은 추가적으로 이 파일들을 다운로드 하게 됩니다.
-
-; labels
-160=파일:
-161=속도:
-162=상태:
-163=경과 시간:
-164=남은 시간:
-165=현재 파일:
-166=전체 진행도:
-167=취소
-168=확인
-169=사용자명과 패스워드
-170=사용자명:
-171=패스워드:

+ 0 - 47
Installer/scripts/isxdl/norwegian.ini

@@ -1,47 +0,0 @@
-[strings]
-; General
-100=Nedlasting
-101=Vil du avbryte nedlastinga?
-102=%1 (%2 av %3)
-103=%1 KB
-104=%1 KB av %2 KB (%3%)
-
-; Status information
-110=Henter filinformasjon...
-111=Omdirigerer til %1
-112=Sender forespørsel...
-113=Resolving %1
-114=Oppkoblet mot %1
-115=Mottar...
-116=Kobler til %1
-; Error messages
-120=Feil ved oppkobling til Internett.\n\n%1
-121=Feil ved åpning av %1.\n\nTjeneren returnerte statuskode %2.
-122=Feil ved lesing av URL.\n\n%1
-123=Feil ved skriving til '%1'.\n\n%2
-124=Feil ved åpning av '%1'.\n\n%2
-125='%1' er ikke en gyldig url.
-126=Feil ved åpning av %1.\n\n%2
-127=Feil ved sending av forespørsel.\n\n%1
-128=Ikke støttet protokoll. Bare HTTP og FTP protokoller er støttet.
-129=Klarte ikke å koble til %1.\n\n%2
-130=Klarte ikke å lese statuskode.\n\n%1
-131=Feil ved forespørsel av fil.\n\n%1
-; Other
-144=Om...
-146=Nedlasting
-147=Setup laster nå ned flere filer til din datamaskin.
-
-; labels
-160=Fil:
-161=Hastighet:
-162=Status:
-163=Brukt tid:
-164=Gjenstående tid:
-165=Gjeldende fil:
-166=Total fremgang:
-167=Avbryt
-168=OK
-169=Brukernavn og passord
-170=Brukernavn:
-171=Passord:

+ 0 - 45
Installer/scripts/isxdl/polish.ini

@@ -1,45 +0,0 @@
-[strings]
-; General
-100=Pobieranie pliku
-101=Czy chcesz przerwać pobieranie?
-102=%1 (%2 z %3)
-103=%1 KB
-104=%1 KB z %2 KB (%3%)
-
-; Status information
-110=Pobieranie informacji o pliku...
-111=Przekierowywanie do %1
-112=Wysyłanie żądania...
-113=Rozwiązywanie %1
-114=Połączony z %1
-115=Pobieranie...
-116=Podłączanie do %1
-
-; Error messages
-120=Nie można podłączyć się do Internetu.\n\n%1
-121=Błąd otwierania %1.\n\nSerwer zwrócił kod błędu %2.
-122=Błąd czytania URL.\n\n%1
-123=Błąd zapisu pliku %1.\n\n%2
-124=Błąd otwarcia pliku %1.\n\n%2
-125='%1' nie jest prawidłowym URL.
-126=Błąd otwarcia %1.\n\n%2
-127=Błąd wysłania żądania.\n\n%1
-128=Nieznany protokół. Tylko protokoły HTTP i FTP są obsługiwane.
-129=Nie udało się podłączenie do %1.\n\n%2
-130=Nie udało się zapytać o kod stanu.\n\n%1
-131=Błąd żądania pliku.\n\n%1
-
-; Other
-144=O pobieraniu...
-146=Pobieranie
-147=W tej chwili Instalator pobiera dodatkowe pliki do Twojego komputera.
-
-; labels
-160=Plik:
-161=Prędkość:
-162=Stan:
-163=Upłynęło:
-164=Pozostały czas:
-165=Aktualny plik:
-166=Całkowity postęp:
-167=Anuluj

+ 0 - 45
Installer/scripts/isxdl/portugues.ini

@@ -1,45 +0,0 @@
-[strings]
-; General
-100=Donwload de ficheiro
-101=Deseja cancelar o donwload?
-102=%1 (%2 de %3)
-103=%1 KB
-104=%1 KB de %2 KB (%3%)
-
-; Status information
-110=A receber informação do ficheiro...
-111=A redirecionar para %1
-112=A enviar pedido...
-113=A resolver %1
-114=Ligado a %1
-115=A receber...
-116=A ligar a %1
-
-; Error messages
-120=Erro na ligação à internet.\n\n%1
-121=Erro na abertura de %1.\n\nO servidor retornou o código de erro %2.
-122=Erro a ler o URL.\n\n%1
-123=Erro na escrita do ficheiro %1.\n\n%2
-124=Erro na abertura do ficheiro %1.\n\n%2
-125='%1' é um URL inválido.
-126=Erro na abertura de %1.\n\n%2
-127=Erro no envio do pedido.\n\n%1
-128=Protocolo não suportado.
-129=Falha na ligação a %1.\n\n%2
-130=Falha na tentativa de retirar o código de status.\n\n%1
-131=Erro no pedido do ficheiro.\n\n%1
-
-; Other
-144=Acerca...
-146=Download
-147=A instalação está a efectuar o download de ficheiros adicionais necessários.
-
-; labels
-160=Ficheiro:
-161=Velocidade:
-162=Estado:
-163=Tempo usado:
-164=Tempo em falta:
-165=Ficheiro:
-166=Progresso:
-167=Cancelar

+ 0 - 46
Installer/scripts/isxdl/portuguese.ini

@@ -1,46 +0,0 @@
-; By António Pinto (AP SoftWare)
-[strings]
-; Geral
-100=Download
-101=Deseja cancelar o download?
-102=%1 (%2 de %3)
-103=%1 KB
-104=%1 KB de %2 KB (%3%)
-
-; Status information
-110=A receber informação do ficheiro...
-111=Redireccionando a %1
-112=A enviar petição...
-113=Resolvendo %1
-114=Ligado a %1
-115=A receber...
-116=A efectuar ligação a %1
-
-; Error messages
-120=Erro a ligar à Internet.\n\n%1
-121=Erro ao abrir %1.\n\nO servidor devolveu o código %2.
-122=Erro ao ler URL.\n\n%1
-123=Erro ao escrever o ficheiro %1.\n\n%2
-124=Erro ao abrir o ficheiro %1.\n\n%2
-125='%1' é uma URL inválida.
-126=Erro ao abrir %1.\n\n%2
-127=Erro ao enviar a petição.\n\n%1
-128=Erro de protocolo. Só os protocolos HTTP ou FTP são aceites.
-129=Erro ao ligar a %1.\n\n%2
-130=Erro ao processar o código.\n\n%1
-131=Erro ao procurar o ficheiro.\n\n%1
-
-; Other
-144=Acerca...
-146=Descarregar
-147=O assistente está a descarregar ficheiros para o seu computador.
-
-; labels
-160=Ficheiro:
-161=Velocidade:
-162=Estado:
-163=Tempo transcorrido:
-164=Tempo estimado:
-165=Ficheiro actual:
-166=Progressão geral:
-167=Cancelar

+ 0 - 49
Installer/scripts/isxdl/russian.ini

@@ -1,49 +0,0 @@
-[strings]
-; General
-100=Загрузка файла
-101=Вы действительно хотите прервать загрузку?
-102=%1 (%2 из %3)
-103=%1 KB
-104=%1 KB из %2 KB (%3%)
-
-; Status information
-110=Получение информации о файле...
-111=Переход на %1
-112=Отправка запроса...
-113=Подключение к %1
-114=Подключен к %1
-115=Получение...
-116=Соединение с %1
-
-; Error messages
-120=Ошибка подключения к интернету.\n\n%1
-121=Ошибка открытия %1.\n\nСервер вернул код ошибки %2.
-122=Ошибка чтения адреса.\n\n%1
-123=Ошибка записи файла %1.\n\n%2
-124=Ошибка открытия файла %1.\n\n%2
-125='%1' неправильный интернет адрес.
-126=Ошибка открытия %1.\n\n%2
-127=Ошибка при отправке запроса.\n\n%1
-128=Протокол не поддерживается. Программой поддерживаются только протоколы HTTP и FTP.
-129=Невозможно подключиться к %1.\n\n%2
-130=Невозможно получить код статуса.\n\n%1
-131=Ошибка при запросе файла.\n\n%1
-
-; Other
-144=О программе...
-146=Загрузка
-147=Сейчас происходит загрузка дополнительных файлов на ваш компьютер.
-
-; labels
-160=Файл:
-161=Скорость:
-162=Статус:
-163=Прошло времени:
-164=Осталось времени:
-165=Текущий файл:
-166=Всего закачено:
-167=Отмена
-168=OK
-169=Имя пользователя и пароль
-170=Имя пользователя:
-171=Пароль:

+ 0 - 46
Installer/scripts/isxdl/spanish.ini

@@ -1,46 +0,0 @@
-; By Lobo Lunar
-[strings]
-; General
-100=Descarga
-101=¿Deseas cancelar la descarga?
-102=%1 (%2 de %3)
-103=%1 KB
-104=%1 KB de %2 KB (%3%)
-
-; Status information
-110=Recibiendo información del archivo...
-111=Redireccionando a %1
-112=Enviando petición...
-113=Resolviendo %1
-114=Conectando a %1
-115=Descargando...
-116=Conectando a %1
-
-; Error messages
-120=Error al conectar a Internet.\n\n%1
-121=Error al abrir %1.\n\nEl servidor regresó %2.
-122=Error al leer URL.\n\n%1
-123=Error al escribir el archivo %1.\n\n%2
-124=Error al abrir archivo %1.\n\n%2
-125='%1' es URL inválido.
-126=Error al abrir %1.\n\n%2
-127=Error al mandar petición.\n\n%1
-128=Error de protocolo. Sólo HTTP o FTP son aceptados.
-129=Error al conectar a %1.\n\n%2
-130=Error al procesar el código.\n\n%1
-131=Error al buscar archivo.\n\n%1
-
-; Other
-144=Acerca...
-146=Descargar
-147=El asistente está descargando archivos.
-
-; labels
-160=Archivo:
-161=Velocidad:
-162=Estado:
-163=Tiempo transcurrido:
-164=Tiempo estimado:
-165=Archivo actual:
-166=Progreso general:
-167=Cancelar

+ 0 - 48
Installer/scripts/isxdl/swedish.ini

@@ -1,48 +0,0 @@
-[strings]
-; General
-100=Fil Nedladdning
-101=Vill du avbruta nedladdningen?
-102=%1 (%2 av %3)
-103=%1 KB
-104=%1 KB av %2 KB (%3%)
-
-; Status information
-110=Hämtar fil information...
-111=Omdirigerar till %1
-112=Sänder förfrågan...
-113=Delar upp %1
-114=Ansluten %1
-115=Tar emot...
-116=Ansluter till %1
-
-; Error messages
-120=Fel vid anslutning till Internet.\n\n%1
-121=Fel vid öppning av %1.\n\nServern returnerade felkod %2.
-122=Fel vid läsninga av URL.\n\n%1
-123=Fel vid skrivning av fil %1.\n\n%2
-124=Fel vid öppning av fil %1.\n\n%2
-125='%1' är en felaktig URL.
-126=Kan inte öppna %1.\n\n%2
-127=Fel vid sändning av förfrågan.\n\n%1
-128=Stöd saknas för protokollet. Endast HTTP och FTP protokollen understöds.
-129=Misslyckades att ansluta till %1.\n\n%2
-130=Misslyckades att kontrollera status kod.\n\n%1
-131=Fel vid förfrågan efter fil.\n\n%1
-
-; Other
-144=Om...
-146=Ladd ner
-147=Setup laddar nu ner tilläggsfiler till din dator
-; labels
-160=Fil:
-161=Hastighet:
-162=Status:
-163=Förfluten Time:
-164=Återstående Time:
-165=Nuvarande Fil:
-166=Totalt Förlopp:
-167=Avbryt
-168=OK
-169=Användarnamn och Lösenord
-170=Användarnamn:
-171=Lösenord:

+ 0 - 18
Installer/scripts/lang/chinese.iss

@@ -1,18 +0,0 @@
-[Languages]
-Name: "chs"; MessagesFile: "compiler:Default.isl"
-
-[CustomMessages]
-// https://www.microsoft.com/globaldev/reference/lcid-all.mspx
-chs.lcid=2052
-chs.depdownload_memo_title=下载依赖组建
-chs.depinstall_memo_title=安装依赖组建
-chs.depinstall_title=安装依赖组建
-chs.depinstall_description=安装程序正在安装所需的依赖组建,请稍后。
-chs.depinstall_status=正在安装 %1...
-chs.depinstall_missing=必须安装 %1 之后才能继续本安装程序。请先安装 %1,然后在重新运行本安装程序。
-chs.depinstall_error=安装依赖组建时出错。请重新启动计算机并再次运行安装程序,或手动安装下列依赖组建:%n
-
-chs.isxdl_langfile=chinese.ini
-
-[Files]
-Source: "scripts\isxdl\chinese.ini"; Flags: dontcopy noencryption

+ 0 - 18
Installer/scripts/lang/dutch.iss

@@ -1,18 +0,0 @@
-[Languages]
-Name: "nl"; MessagesFile: "compiler:Languages\Dutch.isl"
-
-[CustomMessages]
-// https://www.microsoft.com/globaldev/reference/lcid-all.mspx
-nl.lcid=1043
-nl.depdownload_memo_title=Download afhankelijkheden
-nl.depinstall_memo_title=Installeer afhankelijkheden
-nl.depinstall_title=Installeer afhankelijkheden
-nl.depinstall_description=Een moment geduld aub Setup installeert afhankelijkheden op uw computer.
-nl.depinstall_status=Installeren %1...
-nl.depinstall_missing=%1 moet worden geïnstalleerd vóór de installatie kan worden voortgezet. Installeer %1 en voer Setup opnieuw uit.
-nl.depinstall_error=Er is een fout opgetreden tijdens het installeren van de afhankelijkheden. Gelieve de computer opnieuw op en voer de installatie opnieuw uit of de volgende afhankelijkheden handmatig installeren:%n
-
-nl.isxdl_langfile=dutch.ini
-
-[Files]
-Source: "scripts\isxdl\dutch.ini"; Flags: dontcopy noencryption

+ 0 - 15
Installer/scripts/lang/english.iss

@@ -1,15 +0,0 @@
-[Languages]
-Name: "en"; MessagesFile: "compiler:Default.isl"
-
-[CustomMessages]
-// https://www.microsoft.com/globaldev/reference/lcid-all.mspx
-en.lcid=1033
-en.depdownload_memo_title=Download dependencies
-en.depinstall_memo_title=Install dependencies
-en.depinstall_title=Installing dependencies
-en.depinstall_description=Please wait while Setup installs dependencies on your computer.
-en.depinstall_status=Installing %1...
-en.depinstall_missing=%1 must be installed before setup can continue. Please install %1 and run Setup again.
-en.depinstall_error=An error occured while installing the dependencies. Please restart the computer and run the setup again or install the following dependencies manually:%n
-
-en.isxdl_langfile=

+ 0 - 18
Installer/scripts/lang/french.iss

@@ -1,18 +0,0 @@
-[Languages]
-Name: "fr"; MessagesFile: "compiler:Languages\French.isl"
-
-[CustomMessages]
-// https://www.microsoft.com/globaldev/reference/lcid-all.mspx
-fr.lcid=1036
-fr.depdownload_memo_title=Télécharger les dépendances
-fr.depinstall_memo_title=Installez les dépendances
-fr.depinstall_title=Installation des dépendances
-fr.depinstall_description=Veuillez patienter pendant que les dépendances sont installées sur votre ordinateur.
-fr.depinstall_status=Installation de %1...
-fr.depinstall_missing=%1 doit être installé avant de pouvoir continuer. Veuillez installer %1 et exécutez à nouveau le programme d'installation.
-fr.depinstall_error=Une erreur est survenue lors de l'installation des dépendances. Veuillez redémarrer l'ordinateur, et exécutez à nouveau le programme d'installation, ou installez les dépendances suivantes manuellement :%n
-
-fr.isxdl_langfile=french3.ini
-
-[Files]
-Source: "scripts\isxdl\french3.ini"; Flags: dontcopy noencryption

+ 0 - 18
Installer/scripts/lang/german.iss

@@ -1,18 +0,0 @@
-[Languages]
-Name: "de"; MessagesFile: "compiler:Languages\German.isl"
-
-[CustomMessages]
-// https://www.microsoft.com/globaldev/reference/lcid-all.mspx
-de.lcid=1031
-de.depdownload_memo_title=Abhängigkeiten downloaden
-de.depinstall_memo_title=Abhängigkeiten installieren
-de.depinstall_title=Installiere Abhängigkeiten
-de.depinstall_description=Warten Sie bitte während Abhängigkeiten auf Ihrem Computer installiert wird.
-de.depinstall_status=Installiere %1...
-de.depinstall_missing=%1 muss installiert werden bevor das Setup fortfahren kann. Bitte installieren Sie %1 und starten Sie das Setup erneut.
-de.depinstall_error=Ein Fehler ist während der Installation der Abghängigkeiten aufgetreten. Bitte starten Sie den Computer neu und führen Sie das Setup erneut aus oder installieren Sie die folgenden Abhängigkeiten per Hand:%n
-
-de.isxdl_langfile=german.ini
-
-[Files]
-Source: "scripts\isxdl\german.ini"; Flags: dontcopy noencryption

+ 0 - 18
Installer/scripts/lang/italian.iss

@@ -1,18 +0,0 @@
-[Languages]
-Name: "it"; MessagesFile: "compiler:Languages\Italian.isl"
-
-[CustomMessages]
-// https://www.microsoft.com/globaldev/reference/lcid-all.mspx
-it.lcid=1040
-it.depdownload_memo_title=Dipendenze da scaricare
-it.depinstall_memo_title=Dipendenze da installare
-it.depinstall_title=Installazione delle dipendenze
-it.depinstall_description=Si prega di attendere mentre vengono installate le dipendenze necessarie sul computer.
-it.depinstall_status=Installazione %1...
-it.depinstall_missing=%1 deve essere installato per poter continuare. Si prega di installare %1 ed eseguire nuovamente il programma d'installazione.
-it.depinstall_error=Si è verificato un errore durante l'installazione delle dipendenze. Si prega di riavviare il computer ed eseguire nuovamente il programma d'installazione oppure di installare manualmente le seguenti applicazioni:%n
-
-it.isxdl_langfile=italian.ini
-
-[Files]
-Source: "scripts\isxdl\italian.ini"; Flags: dontcopy noencryption

+ 0 - 18
Installer/scripts/lang/japanese.iss

@@ -1,18 +0,0 @@
-[Languages]
-Name: "ja"; MessagesFile: "compiler:Languages\Japanese.isl"
-
-[CustomMessages]
-// https://www.microsoft.com/globaldev/reference/lcid-all.mspx
-ja.lcid=1041
-ja.depdownload_memo_title=ダウンロードする依存ファイル
-ja.depinstall_memo_title=インストールする依存ファイル
-ja.depinstall_title=依存ファイルのインストール
-ja.depinstall_description=セットアップが依存ファイルをインストールするまでお待ちください
-ja.depinstall_status=インストール中 %1...
-ja.depinstall_missing=セットアップを継続するために %1 をインストールする必要があります. %1 をインストールし、再度セットアップを実行してください.
-ja.depinstall_error=依存ファイルのインストール中にエラーが発生しました. コンピューターを再起動しセットアップを再度実行するか、依存ファイルを手動でインストールしてください:%n
-
-ja.isxdl_langfile=japanese.ini
-
-[Files]
-Source: "scripts\isxdl\japanese.ini"; Flags: dontcopy noencryption

+ 0 - 18
Installer/scripts/lang/polish.iss

@@ -1,18 +0,0 @@
-[Languages]
-Name: "pl"; MessagesFile: "compiler:Languages\Polish.isl"
-
-[CustomMessages]
-// https://www.microsoft.com/globaldev/reference/lcid-all.mspx
-pl.lcid=1045
-pl.depdownload_memo_title=Pobierz zależności
-pl.depinstall_memo_title=Zainstaluj zależności
-pl.depinstall_title=Instalowanie zależności
-pl.depinstall_description=Instalator instaluje zależności na komputerze, proszę czekać.
-pl.depinstall_status=Instalowanie %1....
-pl.depinstall_missing=%1 musi być zainstalowany przed instalacją, aby mogła ona być kontynuowana. Zainstaluj %1 i ponownie uruchom program instalacyjny.
-pl.depinstall_error=Wystąpił błąd podczas instalowania zależności. Uruchom ponownie komputer, a następnie ponownie uruchom program instalacyjny lub ręcznie zainstaluj następujące programy:%n
-
-pl.isxdl_langfile=polish.ini
-
-[Files]
-Source: "scripts\isxdl\polish.ini"; Flags: dontcopy noencryption

+ 0 - 18
Installer/scripts/lang/russian.iss

@@ -1,18 +0,0 @@
-[Languages]
-Name: "ru"; MessagesFile: "compiler:Languages\Russian.isl"
-
-[CustomMessages]
-// https://www.microsoft.com/globaldev/reference/lcid-all.mspx
-ru.lcid=1049
-ru.depdownload_memo_title=Загрузить зависимости
-ru.depinstall_memo_title=Установить зависимости
-ru.depinstall_title=Установка зависимостей
-ru.depinstall_description=Пожалуйста, подождите, пока зависимости будут установлены.
-ru.depinstall_status=Установка %1...
-ru.depinstall_missing=%1 должен быть установлен прежде чем установка может быть продолжена. Пожалуйста, установите %1 и повторите установку.
-ru.depinstall_error=В процессе установки зависимостей произошла ошибка. Пожалуйста, перезапустите компьютер и повторите установку, либо установите следующие зависимости вручную:%n
-
-ru.isxdl_langfile=russian.ini
-
-[Files]
-Source: "scripts\isxdl\russian.ini"; Flags: dontcopy noencryption

+ 0 - 4
Installer/scripts/products.iss

@@ -1,4 +0,0 @@
-#include "isxdl\isxdl.iss"
-
-[Code]
-#include "products.pas"

+ 0 - 324
Installer/scripts/products.pas

@@ -1,324 +0,0 @@
-{
-	--- TYPES AND VARIABLES ---
-}
-type
-	TProduct = record
-		File: String;
-		Title: String;
-		Parameters: String;
-		ForceSuccess: Boolean;
-		InstallClean: Boolean;
-		MustRebootAfter: Boolean;
-	end;
-
-	InstallResult = (InstallSuccessful, InstallRebootRequired, InstallError);
-
-var
-	installMemo, downloadMemo: String;
-	products: array of TProduct;
-	delayedReboot, isForcedX86: Boolean;
-	DependencyPage: TOutputProgressWizardPage;
-
-procedure AddProduct(filename, parameters, title, size, url: String; forceSuccess, installClean, mustRebootAfter: Boolean);
-{
-	Adds a product to the list of products to download.
-	Parameters:
-		filename: the file name under which to save the file
-		parameters: the parameters with which to run the file
-		title: the product title
-		size: the file size
-		url: the URL to download from
-		forceSuccess: whether to continue in case of setup failure
-		installClean: whether the product needs a reboot before installing
-		mustRebootAfter: whether the product needs a reboot after installing
-}
-var
-	path: String;
-	i: Integer;
-begin
-	path := ExpandConstant('{src}{\}') + CustomMessage('DependenciesDir') + '\' + filename;
-	if not FileExists(path) then begin
-		path := ExpandConstant('{tmp}{\}') + filename;
-
-		if not FileExists(path) then begin
-			isxdl_AddFile(url, path);
-
-			downloadMemo := downloadMemo + '%1' + title + ' (' + size + ')' + #13;
-		end else begin
-			installMemo := installMemo + '%1' + title + #13;
-		end;
-	end else begin
-		installMemo := installMemo + '%1' + title + #13;
-	end;
-
-	i := GetArrayLength(products);
-	SetArrayLength(products, i + 1);
-	products[i].File := path;
-	products[i].Title := title;
-	products[i].Parameters := parameters;
-	products[i].ForceSuccess := forceSuccess;
-	products[i].InstallClean := installClean;
-	products[i].MustRebootAfter := mustRebootAfter;
-end;
-
-function SmartExec(product: TProduct; var resultCode: Integer): Boolean;
-{
-	Executes a product and returns the exit code.
-	Parameters:
-		product: the product to install
-		resultCode: the exit code
-}
-begin
-	Result := ShellExec('', product.File, product.Parameters, '', SW_SHOWNORMAL, ewWaitUntilTerminated, resultCode);
-end;
-
-function PendingReboot: Boolean;
-{
-	Checks whether the machine has a pending reboot.
-}
-var
-	names: String;
-begin
-	if (RegQueryMultiStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager', 'PendingFileRenameOperations', names)) then begin
-		Result := true;
-	end else if ((RegQueryMultiStringValue(HKEY_LOCAL_MACHINE, 'SYSTEM\CurrentControlSet\Control\Session Manager', 'SetupExecute', names)) and (names <> ''))  then begin
-		Result := true;
-	end else begin
-		Result := false;
-	end;
-end;
-
-function InstallProducts: InstallResult;
-{
-	Installs the downloaded products
-}
-var
-	resultCode, i, productCount, finishCount: Integer;
-begin
-	Result := InstallSuccessful;
-	productCount := GetArrayLength(products);
-
-	if productCount > 0 then begin
-		DependencyPage := CreateOutputProgressPage(CustomMessage('depinstall_title'), CustomMessage('depinstall_description'));
-		DependencyPage.Show;
-
-		for i := 0 to productCount - 1 do begin
-			if (products[i].InstallClean and (delayedReboot or PendingReboot())) then begin
-				Result := InstallRebootRequired;
-				break;
-			end;
-
-			DependencyPage.SetText(FmtMessage(CustomMessage('depinstall_status'), [products[i].Title]), '');
-			DependencyPage.SetProgress(i, productCount);
-
-			while true do begin
-				// set 0 as used code for shown error if SmartExec fails
-				resultCode := 0;
-				if SmartExec(products[i], resultCode) then begin
-					// setup executed; resultCode contains the exit code
-					if (products[i].MustRebootAfter) then begin
-						// delay reboot after install if we installed the last dependency anyways
-						if (i = productCount - 1) then begin
-							delayedReboot := true;
-						end else begin
-							Result := InstallRebootRequired;
-						end;
-						break;
-					end else if (resultCode = 0) or (products[i].ForceSuccess) then begin
-						finishCount := finishCount + 1;
-						break;
-					end else if (resultCode = 3010) then begin
-						// Windows Installer resultCode 3010: ERROR_SUCCESS_REBOOT_REQUIRED
-						delayedReboot := true;
-						finishCount := finishCount + 1;
-						break;
-					end;
-				end;
-
-				case MsgBox(FmtMessage(SetupMessage(msgErrorFunctionFailed), [products[i].Title, IntToStr(resultCode)]), mbError, MB_ABORTRETRYIGNORE) of
-					IDABORT: begin
-						Result := InstallError;
-						break;
-					end;
-					IDIGNORE: begin
-						break;
-					end;
-				end;
-			end;
-
-			if Result <> InstallSuccessful then begin
-				break;
-			end;
-		end;
-
-		// only leave not installed products for error message
-		for i := 0 to productCount - finishCount - 1 do begin
-			products[i] := products[i+finishCount];
-		end;
-		SetArrayLength(products, productCount - finishCount);
-
-		DependencyPage.Hide;
-	end;
-end;
-
-{
-	--------------------
-	INNO EVENT FUNCTIONS
-	--------------------
-}
-
-function PrepareToInstall(var NeedsRestart: Boolean): String;
-{
-	Before the "preparing to install" page.
-	See: https://www.jrsoftware.org/ishelp/index.php?topic=scriptevents
-}
-var
-	i: Integer;
-	s: String;
-begin
-	delayedReboot := false;
-
-	case InstallProducts() of
-		InstallError: begin
-			s := CustomMessage('depinstall_error');
-
-			for i := 0 to GetArrayLength(products) - 1 do begin
-				s := s + #13 + '	' + products[i].Title;
-			end;
-
-			Result := s;
-			end;
-		InstallRebootRequired: begin
-			Result := products[0].Title;
-			NeedsRestart := true;
-
-			// write into the registry that the installer needs to be executed again after restart
-			RegWriteStringValue(HKEY_CURRENT_USER, 'SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce', 'InstallBootstrap', ExpandConstant('{srcexe}'));
-			end;
-	end;
-end;
-
-function NeedRestart: Boolean;
-{
-	Checks whether a restart is needed at the end of install
-	See: https://www.jrsoftware.org/ishelp/index.php?topic=scriptevents
-}
-begin
-	Result := delayedReboot;
-end;
-
-function UpdateReadyMemo(Space, NewLine, MemoUserInfoInfo, MemoDirInfo, MemoTypeInfo, MemoComponentsInfo, MemoGroupInfo, MemoTasksInfo: String): String;
-{
-	Just before the "ready" page.
-	See: https://www.jrsoftware.org/ishelp/index.php?topic=scriptevents
-}
-var
-	s: String;
-begin
-	if downloadMemo <> '' then
-		s := s + CustomMessage('depdownload_memo_title') + ':' + NewLine + FmtMessage(downloadMemo, [Space]) + NewLine;
-	if installMemo <> '' then
-		s := s + CustomMessage('depinstall_memo_title') + ':' + NewLine + FmtMessage(installMemo, [Space]) + NewLine;
-
-	if MemoDirInfo <> '' then
-		s := s + MemoDirInfo + NewLine + NewLine;
-	if MemoGroupInfo <> '' then
-		s := s + MemoGroupInfo + NewLine + NewLine;
-	if MemoTasksInfo <> '' then
-		s := s + MemoTasksInfo;
-
-	Result := s
-end;
-
-function NextButtonClick(CurPageID: Integer): Boolean;
-{
-	At each "next" button click
-	See: https://www.jrsoftware.org/ishelp/index.php?topic=scriptevents
-}
-begin
-	Result := true;
-
-	if CurPageID = wpReady then begin
-		if downloadMemo <> '' then begin
-			// change isxdl language only if it is not english because isxdl default language is already english
-			if (ActiveLanguage() <> 'en') then begin
-				ExtractTemporaryFile(CustomMessage('isxdl_langfile'));
-				isxdl_SetOption('language', ExpandConstant('{tmp}{\}') + CustomMessage('isxdl_langfile'));
-			end;
-
-			if isxdl_DownloadFiles(StrToInt(ExpandConstant('{wizardhwnd}'))) = 0 then
-				Result := false;
-		end;
-	end;
-end;
-
-{
-	-----------------------------
-	ARCHITECTURE HELPER FUNCTIONS
-	-----------------------------
-}
-
-function IsX86: Boolean;
-{
-	Gets whether the computer is x86 (32 bits).
-}
-begin
-	Result := isForcedX86 or (ProcessorArchitecture = paX86) or (ProcessorArchitecture = paUnknown);
-end;
-
-function IsX64: Boolean;
-{
-	Gets whether the computer is x64 (64 bits).
-}
-begin
-	Result := (not isForcedX86) and Is64BitInstallMode and (ProcessorArchitecture = paX64);
-end;
-
-function IsIA64: Boolean;
-{
-	Gets whether the computer is IA64 (Itanium 64 bits).
-}
-begin
-	Result := (not isForcedX86) and Is64BitInstallMode and (ProcessorArchitecture = paIA64);
-end;
-
-function GetString(x86, x64, ia64: String): String;
-{
-	Gets a string depending on the computer architecture.
-	Parameters:
-		x86: the string if the computer is x86
-		x64: the string if the computer is x64
-		ia64: the string if the computer is IA64
-}
-begin
-	if IsX64() and (x64 <> '') then begin
-		Result := x64;
-	end else if IsIA64() and (ia64 <> '') then begin
-		Result := ia64;
-	end else begin
-		Result := x86;
-	end;
-end;
-
-function GetArchitectureString(): String;
-{
-	Gets the "standard" architecture suffix string.
-	Returns either _x64, _ia64 or nothing.
-}
-begin
-	if IsX64() then begin
-		Result := '_x64';
-	end else if IsIA64() then begin
-		Result := '_ia64';
-	end else begin
-		Result := '';
-	end;
-end;
-
-procedure SetForceX86(value: Boolean);
-{
-	Forces the setup to use X86 products
-}
-begin
-	isForcedX86 := value;
-end;

+ 0 - 92
Installer/scripts/products/dotnetfxversion.iss

@@ -1,92 +0,0 @@
-[Code]
-type
-	NetFXType = (NetFx10, NetFx11, NetFx20, NetFx30, NetFx35, NetFx40Client, NetFx40Full, NetFx4x);
-
-const
-	netfx11plus_reg = 'Software\Microsoft\NET Framework Setup\NDP\';
-
-function dotnetfxinstalled(version: NetFXType; lcid: String): Boolean;
-var
-	regVersion: Cardinal;
-	regVersionString: String;
-begin
-	if (lcid <> '') then
-		lcid := '\' + lcid;
-
-	case version of
-		NetFx10:
-			Result := RegQueryStringValue(HKLM, 'Software\Microsoft\.NETFramework\Policy\v1.0\3705', 'Install', regVersionString) and (regVersionString <> '');
-		NetFx11:
-			Result := RegQueryDWordValue(HKLM, netfx11plus_reg + 'v1.1.4322' + lcid, 'Install', regVersion) and (regVersion <> 0);
-		NetFx20:
-			Result := RegQueryDWordValue(HKLM, netfx11plus_reg + 'v2.0.50727' + lcid, 'Install', regVersion) and (regVersion <> 0);
-		NetFx30:
-			Result := RegQueryDWordValue(HKLM, netfx11plus_reg + 'v3.0\Setup' + lcid, 'InstallSuccess', regVersion) and (regVersion <> 0);
-		NetFx35:
-			Result := RegQueryDWordValue(HKLM, netfx11plus_reg + 'v3.5' + lcid, 'Install', regVersion) and (regVersion <> 0);
-		NetFx40Client:
-			Result := RegQueryDWordValue(HKLM, netfx11plus_reg + 'v4\Client' + lcid, 'Install', regVersion) and (regVersion <> 0);
-		NetFx40Full:
-			Result := RegQueryDWordValue(HKLM, netfx11plus_reg + 'v4\Full' + lcid, 'Install', regVersion) and (regVersion <> 0);
-		NetFx4x:
-			Result := RegQueryDWordValue(HKLM, netfx11plus_reg + 'v4\Full' + lcid, 'Release', regVersion) and (regVersion >= 378389); // 4.5.0+
-	end;
-end;
-
-function dotnetfxspversion(version: NetFXType; lcid: String): Integer;
-var
-	regVersion: Cardinal;
-begin
-	if (lcid <> '') then
-		lcid := '\' + lcid;
-
-	case version of
-		NetFx10:
-			// not supported
-			regVersion := -1;
-		NetFx11:
-			if (not RegQueryDWordValue(HKLM, netfx11plus_reg + 'v1.1.4322' + lcid, 'SP', regVersion)) then
-				regVersion := -1;
-		NetFx20:
-			if (not RegQueryDWordValue(HKLM, netfx11plus_reg + 'v2.0.50727' + lcid, 'SP', regVersion)) then
-				regVersion := -1;
-		NetFx30:
-			if (not RegQueryDWordValue(HKLM, netfx11plus_reg + 'v3.0' + lcid, 'SP', regVersion)) then
-				regVersion := -1;
-		NetFx35:
-			if (not RegQueryDWordValue(HKLM, netfx11plus_reg + 'v3.5' + lcid, 'SP', regVersion)) then
-				regVersion := -1;
-		NetFx40Client:
-			if (not RegQueryDWordValue(HKLM, netfx11plus_reg + 'v4\Client' + lcid, 'Servicing', regVersion)) then
-				regVersion := -1;
-		NetFx40Full:
-			if (not RegQueryDWordValue(HKLM, netfx11plus_reg + 'v4\Full' + lcid, 'Servicing', regVersion)) then
-				regVersion := -1;
-		NetFx4x:
-			if (RegQueryDWordValue(HKLM, netfx11plus_reg + 'v4\Full' + lcid, 'Release', regVersion)) then begin
-				if (regVersion >= 528040) then
-					regVersion := 80 // 4.8.0+ 
-				else if (regVersion >= 461808) then
-					regVersion := 72 // 4.7.2+
-				else if (regVersion >= 461308) then
-					regVersion := 71 // 4.7.1+
-				else if (regVersion >= 460798) then
-					regVersion := 70 // 4.7.0+
-				else if (regVersion >= 394802) then
-					regVersion := 62 // 4.6.2+
-				else if (regVersion >= 394254) then
-					regVersion := 61 // 4.6.1+
-				else if (regVersion >= 393295) then
-					regVersion := 60 // 4.6.0+
-				else if (regVersion >= 379893) then
-					regVersion := 52 // 4.5.2+
-				else if (regVersion >= 378675) then
-					regVersion := 51 // 4.5.1+
-				else if (regVersion >= 378389) then
-					regVersion := 50 // 4.5.0+
-				else
-					regVersion := -1;
-			end;
-	end;
-	Result := regVersion;
-end;

+ 0 - 27
Installer/scripts/products/netcore31.iss

@@ -1,27 +0,0 @@
-// requires Windows 10 Version 1607+, Windows 7 SP1+, Windows 8.1, Windows Server 2012 R2
-// https://dotnet.microsoft.com/download/dotnet-core/3.1
-
-[CustomMessages]
-netcore31_title=.NET Core Runtime 3.1.6 (x86)
-netcore31_title_x64=.NET Core Runtime 3.1.6 (x64)
-
-netcore31_size=23 MB
-netcore31_size_x64=26 MB
-
-[Code]
-const
-	netcore31_url = 'http://go.microsoft.com/fwlink/?linkid=2137641';
-	netcore31_url_x64 = 'http://go.microsoft.com/fwlink/?linkid=2137640';
-
-procedure netcore31();
-begin
-	if (not IsIA64()) then begin
-		if not netcoreinstalled(Core, '3.1.6') then
-			AddProduct('netcore31' + GetArchitectureString() + '.exe',
-				'/lcid ' + CustomMessage('lcid') + ' /passive /norestart',
-				CustomMessage('netcore31_title' + GetArchitectureString()),
-				CustomMessage('netcore31_size' + GetArchitectureString()),
-				GetString(netcore31_url, netcore31_url_x64, ''),
-				false, false, false);
-	end;
-end;

+ 0 - 27
Installer/scripts/products/netcore31desktop.iss

@@ -1,27 +0,0 @@
-// requires Windows 10 Version 1607+, Windows 7 SP1+, Windows 8.1, Windows Server 2012 R2
-// https://dotnet.microsoft.com/download/dotnet-core/3.1
-
-[CustomMessages]
-netcore31desktop_title=.NET Desktop Runtime 3.1.6 (x86)
-netcore31desktop_title_x64=.NET Desktop Runtime 3.1.6 (x64)
-
-netcore31desktop_size=23 MB
-netcore31desktop_size_x64=26 MB
-
-[Code]
-const
-	netcore31desktop_url = 'http://go.microsoft.com/fwlink/?linkid=2137844';
-	netcore31desktop_url_x64 = 'http://go.microsoft.com/fwlink/?linkid=2137941';
-
-procedure netcore31desktop();
-begin
-	if (not IsIA64()) then begin
-		if not netcoreinstalled(Desktop, '3.1.6') then
-			AddProduct('netcore31desktop' + GetArchitectureString() + '.exe',
-				'/lcid ' + CustomMessage('lcid') + ' /passive /norestart',
-				CustomMessage('netcore31desktop_title' + GetArchitectureString()),
-				CustomMessage('netcore31desktop_size' + GetArchitectureString()),
-				GetString(netcore31desktop_url, netcore31desktop_url_x64, ''),
-				false, false, false);
-	end;
-end;

+ 0 - 32
Installer/scripts/products/netcorecheck.iss

@@ -1,32 +0,0 @@
-// NetCoreCheck tool is necessary for detecting if a specific version of .NET Core/.NET 5.0 is installed: https://github.com/dotnet/runtime/issues/36479
-// source code: https://github.com/dotnet/deployment-tools/tree/master/src/clickonce/native/projects/NetCoreCheck
-// download netcorecheck.exe: https://go.microsoft.com/fwlink/?linkid=2135256
-// download netcorecheck_x64.exe: https://go.microsoft.com/fwlink/?linkid=2135504
-
-[Files]
-Source: "src\netcorecheck.exe"; Flags: dontcopy noencryption
-Source: "src\netcorecheck_x64.exe"; Flags: dontcopy noencryption
-
-[Code]
-type
-	NetCoreRuntimeType = (Asp, Core, Desktop);
-
-function netcoreinstalled(runtime: NetCoreRuntimeType; version: String): Boolean;
-var
-	netcoreRuntime: String;
-	resultCode: Integer;
-begin
-	case runtime of
-		Asp:
-			netcoreRuntime := 'Microsoft.AspNetCore.App';
-		Core:
-			netcoreRuntime := 'Microsoft.NETCore.App';
-		Desktop:
-			netcoreRuntime := 'Microsoft.WindowsDesktop.App';
-	end;
-
-	if not FileExists(ExpandConstant('{tmp}{\}') + 'netcorecheck' + GetArchitectureString() + '.exe') then
-		ExtractTemporaryFile('netcorecheck' + GetArchitectureString() + '.exe');
-
-	Result := Exec(ExpandConstant('{tmp}{\}') + 'netcorecheck' + GetArchitectureString() + '.exe', netcoreRuntime + ' ' + version, '', SW_HIDE, ewWaitUntilTerminated, resultCode) and (resultCode = 0);
-end;

+ 0 - 60
Installer/scripts/products/stringversion.iss

@@ -1,60 +0,0 @@
-[Code]
-function stringtoversion(var temp: String): Integer;
-var
-	part: String;
-	pos1: Integer;
-
-begin
-	if (Length(temp) = 0) then begin
-		Result := -1;
-		Exit;
-	end;
-
-	pos1 := Pos('.', temp);
-	if (pos1 = 0) then begin
-		Result := StrToInt(temp);
-		temp := '';
-	end else begin
-		part := Copy(temp, 1, pos1 - 1);
-		temp := Copy(temp, pos1 + 1, Length(temp));
-		Result := StrToInt(part);
-	end;
-end;
-
-function compareinnerversion(var x, y: String): Integer;
-var
-	num1, num2: Integer;
-
-begin
-	num1 := stringtoversion(x);
-	num2 := stringtoversion(y);
-	if (num1 = -1) and (num2 = -1) then begin
-		Result := 0;
-		Exit;
-	end;
-
-	if (num1 < 0) then begin
-		num1 := 0;
-	end;
-	if (num2 < 0) then begin
-		num2 := 0;
-	end;
-
-	if (num1 < num2) then begin
-		Result := -1;
-	end else if (num1 > num2) then begin
-		Result := 1;
-	end else begin
-		Result := compareinnerversion(x, y);
-	end;
-end;
-
-function compareversion(versionA, versionB: String): Integer;
-var
-  temp1, temp2: String;
-
-begin
-	temp1 := versionA;
-	temp2 := versionB;
-	Result := compareinnerversion(temp1, temp2);
-end;

+ 0 - 47
Installer/scripts/products/winversion.iss

@@ -1,47 +0,0 @@
-[Code]
-var
-	WindowsVersion: TWindowsVersion;
-
-procedure initwinversion();
-begin
-	GetWindowsVersionEx(WindowsVersion);
-end;
-
-function exactwinversion(MajorVersion, MinorVersion: Integer): Boolean;
-begin
-	Result := (WindowsVersion.Major = MajorVersion) and (WindowsVersion.Minor = MinorVersion);
-end;
-
-function minwinversion(MajorVersion, MinorVersion: Integer): Boolean;
-begin
-	Result := (WindowsVersion.Major > MajorVersion) or ((WindowsVersion.Major = MajorVersion) and (WindowsVersion.Minor >= MinorVersion));
-end;
-
-function maxwinversion(MajorVersion, MinorVersion: Integer): Boolean;
-begin
-	Result := (WindowsVersion.Major < MajorVersion) or ((WindowsVersion.Major = MajorVersion) and (WindowsVersion.Minor <= MinorVersion));
-end;
-
-function exactwinspversion(MajorVersion, MinorVersion, SpVersion: Integer): Boolean;
-begin
-	if exactwinversion(MajorVersion, MinorVersion) then
-		Result := WindowsVersion.ServicePackMajor = SpVersion
-	else
-		Result := true;
-end;
-
-function minwinspversion(MajorVersion, MinorVersion, SpVersion: Integer): Boolean;
-begin
-	if exactwinversion(MajorVersion, MinorVersion) then
-		Result := WindowsVersion.ServicePackMajor >= SpVersion
-	else
-		Result := true;
-end;
-
-function maxwinspversion(MajorVersion, MinorVersion, SpVersion: Integer): Boolean;
-begin
-	if exactwinversion(MajorVersion, MinorVersion) then
-		Result := WindowsVersion.ServicePackMajor <= SpVersion
-	else
-		Result := true;
-end;

+ 1 - 1
LICENSE

@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2018-2020 flabbet
+Copyright (c) PixiEditor Organization
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal

+ 2 - 2
PixiEditor.UpdateInstaller/PixiEditor.UpdateInstaller.csproj

@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <OutputType>WinExe</OutputType>
-    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <TargetFramework>net5.0-windows</TargetFramework>
     <UseWPF>true</UseWPF>
     <ApplicationManifest>app.manifest</ApplicationManifest>
   </PropertyGroup>
@@ -12,7 +12,7 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="Tools.InnoSetup" Version="6.0.5" />
+    <PackageReference Include="Tools.InnoSetup" Version="6.1.2" />
   </ItemGroup>
 
   <ItemGroup>

+ 1 - 1
PixiEditor.UpdateModule/PixiEditor.UpdateModule.csproj

@@ -1,7 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
   </PropertyGroup>
 
 </Project>

+ 2 - 0
PixiEditor/App.xaml

@@ -14,6 +14,8 @@
                 <ResourceDictionary Source="Styles/DockingManagerStyle.xaml" />
                 <ResourceDictionary Source="Styles/DarkScrollBarStyle.xaml" />
                 <ResourceDictionary Source="Styles/ImageCheckBoxStyle.xaml" />
+                <ResourceDictionary Source="Styles/DarkCheckboxStyle.xaml" />
+                <ResourceDictionary Source="Styles/LabelStyles.xaml" />
             </ResourceDictionary.MergedDictionaries>
         </ResourceDictionary>
     </Application.Resources>

+ 35 - 0
PixiEditor/Exceptions/CorruptedFileException.cs

@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PixiEditor.Exceptions
+{
+
+    [Serializable]
+    public class CorruptedFileException : Exception
+    {
+        public CorruptedFileException()
+            : base("Selected file is invalid or corrupted.")
+        {
+        }
+
+        public CorruptedFileException(string message)
+            : base(message)
+        {
+        }
+
+        public CorruptedFileException(string message, Exception inner)
+            : base(message, inner)
+        {
+        }
+
+        protected CorruptedFileException(
+          System.Runtime.Serialization.SerializationInfo info,
+          System.Runtime.Serialization.StreamingContext context)
+            : base(info, context)
+        {
+        }
+    }
+}

+ 25 - 0
PixiEditor/Helpers/Behaviours/ClearFocusOnClickBehavior.cs

@@ -0,0 +1,25 @@
+using System.Windows;
+using System.Windows.Input;
+using System.Windows.Interactivity;
+
+namespace PixiEditor.Helpers.Behaviours
+{
+    public class ClearFocusOnClickBehavior : Behavior<FrameworkElement>
+    {
+        protected override void OnAttached()
+        {
+            AssociatedObject.MouseDown += AssociatedObject_MouseDown;
+            base.OnAttached();
+        }
+
+        private void AssociatedObject_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            AssociatedObject.Focus();
+        }
+
+        protected override void OnDetaching()
+        {
+            AssociatedObject.MouseDown -= AssociatedObject_MouseDown;
+        }
+    }
+}

+ 16 - 4
PixiEditor/Helpers/Behaviours/TextBoxFocusBehavior.cs

@@ -54,7 +54,19 @@ namespace PixiEditor.Helpers.Behaviours
             }
 
             ConvertValue();
-            AssociatedObject.MoveFocus(new TraversalRequest(FocusNavigationDirection.Down));
+            RemoveFocus();
+        }
+
+        private void RemoveFocus()
+        {
+            FrameworkElement parent = (FrameworkElement)AssociatedObject.Parent;
+            while (parent != null && parent is IInputElement element && !element.Focusable)
+            {
+                parent = (FrameworkElement)parent.Parent;
+            }
+
+            DependencyObject scope = FocusManager.GetFocusScope(AssociatedObject);
+            FocusManager.SetFocusedElement(scope, parent);
         }
 
         private void AssociatedObjectGotKeyboardFocus(
@@ -95,14 +107,14 @@ namespace PixiEditor.Helpers.Behaviours
         /// </summary>
         private void ConvertValue()
         {
-            if (valueConverted || FillSize == false)
+            if (valueConverted || FillSize == false || AssociatedObject.Text == oldText)
             {
                 return;
             }
 
-            if (int.TryParse(Regex.Replace(AssociatedObject.Text, "\\p{L}", string.Empty), out int result) && result > 0)
+            if (int.TryParse(Regex.Replace(AssociatedObject.Text, "\\p{L}", string.Empty).Trim(), out int result) && result > 0)
             {
-                AssociatedObject.Text = $"{AssociatedObject.Text} px";
+                AssociatedObject.Text = $"{result} px";
             }
 
             // If text in textbox isn't number, set it to old value

+ 20 - 0
PixiEditor/Helpers/Converters/EqualityBoolToVisibilityConverter.cs

@@ -0,0 +1,20 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace PixiEditor.Helpers.Converters
+{
+    public class EqualityBoolToVisibilityConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return value.Equals(parameter) ? Visibility.Visible : Visibility.Collapsed;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 77 - 0
PixiEditor/Helpers/Extensions/ParserHelpers.cs

@@ -0,0 +1,77 @@
+using System;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Windows;
+using System.Windows.Media;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.ImageManipulation;
+using PixiEditor.Models.Layers;
+
+namespace PixiEditor.Helpers.Extensions
+{
+    public static class ParserHelpers
+    {
+        public static Document ToDocument(this Parser.SerializableDocument serializableDocument)
+        {
+            Document document = new Document(serializableDocument.Width, serializableDocument.Height)
+            {
+                Layers = serializableDocument.ToLayers(),
+                Swatches = new ObservableCollection<Color>(serializableDocument.Swatches.Select(x =>
+                    Color.FromArgb(x.Item1, x.Item2, x.Item3, x.Item4)))
+            };
+
+            return document;
+        }
+
+        public static ObservableCollection<Layer> ToLayers(this Parser.SerializableDocument serializableDocument)
+        {
+            ObservableCollection<Layer> layers = new ObservableCollection<Layer>();
+            for (int i = 0; i < serializableDocument.Layers.Length; i++)
+            {
+                Parser.SerializableLayer serLayer = serializableDocument.Layers[i];
+                Layer layer =
+                    new Layer(serLayer.Name, BitmapUtils.BytesToWriteableBitmap(serLayer.Width, serLayer.Height, serLayer.BitmapBytes))
+                    {
+                        IsVisible = serLayer.IsVisible,
+                        Offset = new Thickness(serLayer.OffsetX, serLayer.OffsetY, 0, 0),
+                        Opacity = serLayer.Opacity
+                    };
+                layers.Add(layer);
+            }
+
+            return layers;
+        }
+
+        public static Parser.SerializableDocument ToSerializable(this Document document)
+        {
+            Parser.SerializableDocument serializable = new Parser.SerializableDocument
+            {
+                Width = document.Width,
+                Height = document.Height,
+                Layers = document.Layers.Select(x => x.ToSerializable()).ToArray(),
+                Swatches = document.Swatches.Select(x => new Tuple<byte, byte, byte, byte>(x.A, x.R, x.G, x.B)).ToArray()
+            };
+
+            return serializable;
+        }
+
+        public static Parser.SerializableLayer ToSerializable(this Layer layer)
+        {
+            Parser.SerializableLayer serializable = new Parser.SerializableLayer
+            {
+                Name = layer.Name,
+                Width = layer.Width,
+                Height = layer.Height,
+                BitmapBytes = layer.ConvertBitmapToBytes(),
+                IsVisible = layer.IsVisible,
+                OffsetX = (int)layer.Offset.Left,
+                OffsetY = (int)layer.Offset.Top,
+                Opacity = layer.Opacity,
+                MaxWidth = layer.MaxWidth,
+                MaxHeight = layer.MaxHeight
+            };
+
+            return serializable;
+        }
+    }
+}

+ 0 - 0
PixiEditor/Images/BucketImage.png → PixiEditor/Images/FloodFillImage.png


+ 45 - 101
PixiEditor/Models/Controllers/BitmapManager.cs

@@ -1,4 +1,6 @@
 using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Linq;
 using System.Windows;
 using System.Windows.Input;
@@ -6,7 +8,6 @@ using System.Windows.Media;
 using System.Windows.Media.Imaging;
 using PixiEditor.Helpers;
 using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Enums;
 using PixiEditor.Models.Events;
 using PixiEditor.Models.ImageManipulation;
 using PixiEditor.Models.Layers;
@@ -20,7 +21,6 @@ namespace PixiEditor.Models.Controllers
     public class BitmapManager : NotifyableObject
     {
         private Document activeDocument;
-        private Layer previewLayer;
         private Tool selectedTool;
 
         public BitmapManager()
@@ -33,9 +33,7 @@ namespace PixiEditor.Models.Controllers
             MouseController.OnMouseUp += MouseController_OnMouseUp;
             BitmapOperations = new BitmapOperationsUtility(this);
             ReadonlyToolUtility = new ReadonlyToolUtility();
-        }
-
-        public event EventHandler<LayersChangedEventArgs> LayersChanged;
+        }
 
         public event EventHandler<DocumentChangedEventArgs> DocumentChanged;
 
@@ -51,16 +49,6 @@ namespace PixiEditor.Models.Controllers
             }
         }
 
-        public Layer PreviewLayer
-        {
-            get => previewLayer;
-            set
-            {
-                previewLayer = value;
-                RaisePropertyChanged("PreviewLayer");
-            }
-        }
-
         public Layer ActiveLayer => ActiveDocument.ActiveLayer;
 
         public Color PrimaryColor { get; set; }
@@ -95,6 +83,8 @@ namespace PixiEditor.Models.Controllers
             }
         }
 
+        public ObservableCollection<Document> Documents { get; set; } = new ObservableCollection<Document>();
+
         /// <summary>
         ///     Returns if tool is BitmapOperationTool.
         /// </summary>
@@ -103,69 +93,17 @@ namespace PixiEditor.Models.Controllers
             return tool is BitmapOperationTool;
         }
 
-        public void SetActiveTool(Tool tool)
-        {
-            PreviewLayer = null;
-            SelectedTool?.Toolbar.SaveToolbarSettings();
-            SelectedTool = tool;
-            SelectedTool.Toolbar.LoadSharedSettings();
-        }
-
-        public void SetActiveLayer(int index)
-        {
-            if (ActiveDocument.ActiveLayerIndex <= ActiveDocument.Layers.Count - 1)
-            {
-                ActiveDocument.ActiveLayer.IsActive = false;
-            }
-
-            ActiveDocument.ActiveLayerIndex = index;
-            ActiveDocument.ActiveLayer.IsActive = true;
-            LayersChanged?.Invoke(this, new LayersChangedEventArgs(index, LayerAction.SetActive));
-        }
-
-        public void AddNewLayer(string name, WriteableBitmap bitmap, bool setAsActive = true)
-        {
-            AddNewLayer(name, bitmap.PixelWidth, bitmap.PixelHeight, setAsActive);
-            ActiveDocument.Layers.Last().LayerBitmap = bitmap;
-        }
-
-        public void AddNewLayer(string name, bool setAsActive = true)
-        {
-            AddNewLayer(name, 0, 0, setAsActive);
-        }
-
-        public void AddNewLayer(string name, int width, int height, bool setAsActive = true)
-        {
-            ActiveDocument.Layers.Add(new Layer(name, width, height)
-            {
-                MaxHeight = ActiveDocument.Height,
-                MaxWidth = ActiveDocument.Width
-            });
-            if (setAsActive)
-            {
-                SetActiveLayer(ActiveDocument.Layers.Count - 1);
-            }
-
-            LayersChanged?.Invoke(this, new LayersChangedEventArgs(0, LayerAction.Add));
-        }
-
-        public void RemoveLayer(int layerIndex)
-        {
-            if (ActiveDocument.Layers.Count == 0)
+        public void CloseDocument(Document document)
+        {
+            int nextIndex = 0;
+            if (document == ActiveDocument)
             {
-                return;
+                nextIndex = Documents.Count > 1 ? Documents.IndexOf(document) : -1;
+                nextIndex += nextIndex > 0 ? -1 : 0;
             }
 
-            bool wasActive = ActiveDocument.Layers[layerIndex].IsActive;
-            ActiveDocument.Layers.RemoveAt(layerIndex);
-            if (wasActive)
-            {
-                SetActiveLayer(0);
-            }
-            else if (ActiveDocument.ActiveLayerIndex > ActiveDocument.Layers.Count - 1)
-            {
-                SetActiveLayer(ActiveDocument.Layers.Count - 1);
-            }
+            Documents.Remove(document);
+            ActiveDocument = nextIndex >= 0 ? Documents[nextIndex] : null;
         }
 
         public void ExecuteTool(Coordinates newPosition, bool clickedOnCanvas)
@@ -183,18 +121,6 @@ namespace PixiEditor.Models.Controllers
             }
         }
 
-        public void GeneratePreviewLayer()
-        {
-            if (ActiveDocument != null)
-            {
-                PreviewLayer = new Layer("_previewLayer")
-                {
-                    MaxWidth = ActiveDocument.Width,
-                    MaxHeight = ActiveDocument.Height
-                };
-            }
-        }
-
         public WriteableBitmap GetCombinedLayersBitmap()
         {
             return BitmapUtils.CombineLayers(ActiveDocument.Layers.Where(x => x.IsVisible).ToArray(), ActiveDocument.Width, ActiveDocument.Height);
@@ -206,6 +132,17 @@ namespace PixiEditor.Models.Controllers
         public bool IsOperationTool()
         {
             return IsOperationTool(SelectedTool);
+        }
+
+        public void SetActiveTool(Tool tool)
+        {
+            if (ActiveDocument != null)
+            {
+                ActiveDocument.PreviewLayer = null;
+            }
+            SelectedTool?.Toolbar.SaveToolbarSettings();
+            SelectedTool = tool;
+            SelectedTool.Toolbar.LoadSharedSettings();
         }
 
         private void Controller_MousePositionChanged(object sender, MouseMovementEventArgs e)
@@ -239,7 +176,10 @@ namespace PixiEditor.Models.Controllers
         private void MouseController_StartedRecordingChanges(object sender, EventArgs e)
         {
             SelectedTool.OnRecordingLeftMouseDown(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
-            PreviewLayer = null;
+            if (ActiveDocument != null)
+            {
+                ActiveDocument.PreviewLayer = null;
+            }
         }
 
         private void MouseController_StoppedRecordingChanges(object sender, EventArgs e)
@@ -247,7 +187,7 @@ namespace PixiEditor.Models.Controllers
             SelectedTool.OnStoppedRecordingMouseUp(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
             if (IsOperationTool(SelectedTool) && ((BitmapOperationTool)SelectedTool).RequiresPreviewLayer)
             {
-                BitmapOperations.StopAction();
+                BitmapOperations.ApplyPreviewLayer();
             }
         }
 
@@ -258,35 +198,39 @@ namespace PixiEditor.Models.Controllers
                 return;
             }
 
-            Coordinates[] highlightArea = CoordinatesCalculator.RectangleToCoordinates(
+            IEnumerable<Coordinates> highlightArea = CoordinatesCalculator.RectangleToCoordinates(
                 CoordinatesCalculator.CalculateThicknessCenter(newPosition, ToolSize));
             if (CanChangeHighlightOffset(highlightArea))
             {
-                PreviewLayer.Offset = new Thickness(highlightArea[0].X, highlightArea[0].Y, 0, 0);
+                Coordinates start = highlightArea.First();
+                ActiveDocument.PreviewLayer.Offset = new Thickness(start.X, start.Y, 0, 0);
             }
             else if (!IsInsideBounds(highlightArea))
             {
-                PreviewLayer = null;
+                ActiveDocument.PreviewLayer = null;
             }
             else
             {
-                GeneratePreviewLayer();
-                PreviewLayer.SetPixels(
+                ActiveDocument.GeneratePreviewLayer();
+                ActiveDocument.PreviewLayer.SetPixels(
                     BitmapPixelChanges.FromSingleColoredArray(highlightArea, Color.FromArgb(77, 0, 0, 0)));
             }
         }
 
-        private bool CanChangeHighlightOffset(Coordinates[] highlightArea)
+        private bool CanChangeHighlightOffset(IEnumerable<Coordinates> highlightArea)
         {
-            return highlightArea.Length > 0 && PreviewLayer != null &&
-                   IsInsideBounds(highlightArea) && highlightArea.Length == PreviewLayer.Width * PreviewLayer.Height;
+            int count = highlightArea.Count();
+            return count > 0 && ActiveDocument.PreviewLayer != null &&
+                   IsInsideBounds(highlightArea) && count == ActiveDocument.PreviewLayer.Width * ActiveDocument.PreviewLayer.Height;
         }
 
-        private bool IsInsideBounds(Coordinates[] highlightArea)
+        private bool IsInsideBounds(IEnumerable<Coordinates> highlightArea)
         {
-            return highlightArea[0].X <= ActiveDocument.Width - 1 &&
-                    highlightArea[0].Y <= ActiveDocument.Height - 1 &&
-                   highlightArea[^1].X >= 0 && highlightArea[^1].Y >= 0;
+            Coordinates start = highlightArea.First();
+            Coordinates end = highlightArea.Last();
+            return start.X <= ActiveDocument.Width - 1 &&
+                    start.Y <= ActiveDocument.Height - 1 &&
+                   end.X >= 0 && end.Y >= 0;
         }
     }
 }

+ 14 - 8
PixiEditor/Models/Controllers/BitmapOperationsUtility.cs

@@ -29,19 +29,25 @@ namespace PixiEditor.Models.Controllers
 
         public void DeletePixels(Layer[] layers, Coordinates[] pixels)
         {
+            if (Manager.ActiveDocument == null)
+            {
+                return;
+            }
+
             BitmapPixelChanges changes = BitmapPixelChanges.FromSingleColoredArray(pixels, Color.FromArgb(0, 0, 0, 0));
             Dictionary<Layer, Color[]> oldValues = BitmapUtils.GetPixelsForSelection(layers, pixels);
             LayerChange[] old = new LayerChange[layers.Length];
             LayerChange[] newChange = new LayerChange[layers.Length];
             for (int i = 0; i < layers.Length; i++)
             {
+                int indexOfLayer = Manager.ActiveDocument.Layers.IndexOf(layers[i]);
                 old[i] = new LayerChange(
-                    BitmapPixelChanges.FromArrays(pixels, oldValues[layers[i]]), i);
-                newChange[i] = new LayerChange(changes, i);
+                    BitmapPixelChanges.FromArrays(pixels, oldValues[layers[i]]), indexOfLayer);
+                newChange[i] = new LayerChange(changes, indexOfLayer);
                 layers[i].SetPixels(changes);
             }
 
-            UndoManager.AddUndoChange(new Change("UndoChanges", old, newChange, "Deleted pixels"));
+            Manager.ActiveDocument.UndoManager.AddUndoChange(new Change("UndoChanges", old, newChange, "Deleted pixels"));
         }
 
         /// <summary>
@@ -52,7 +58,7 @@ namespace PixiEditor.Models.Controllers
         /// <param name="tool">Tool to execute.</param>
         public void ExecuteTool(Coordinates newPos, List<Coordinates> mouseMove, BitmapOperationTool tool)
         {
-            if (Manager.ActiveDocument != null && tool != null && tool.ToolType != ToolType.None)
+            if (Manager.ActiveDocument != null && tool != null)
             {
                 if (Manager.ActiveDocument.Layers.Count == 0 || mouseMove.Count == 0)
                 {
@@ -69,7 +75,7 @@ namespace PixiEditor.Models.Controllers
         /// <summary>
         ///     Applies pixels from preview layer to selected layer.
         /// </summary>
-        public void StopAction()
+        public void ApplyPreviewLayer()
         {
             if (lastModifiedLayers == null)
             {
@@ -86,7 +92,7 @@ namespace PixiEditor.Models.Controllers
                     lastModifiedLayers[i].PixelChanges,
                     oldValues,
                     lastModifiedLayers[i].LayerIndex));
-                Manager.PreviewLayer = null;
+                Manager.ActiveDocument.GeneratePreviewLayer();
             }
         }
 
@@ -178,13 +184,13 @@ namespace PixiEditor.Models.Controllers
             LayerChange[] modifiedLayers;
             if (mouseMove.Count > 0 && mouseMove[0] != lastMousePos)
             {
-                Manager.GeneratePreviewLayer();
+                Manager.ActiveDocument.GeneratePreviewLayer();
                 modifiedLayers = ((BitmapOperationTool)Manager.SelectedTool).Use(
                     Manager.ActiveDocument.ActiveLayer,
                     mouseMove.ToArray(),
                     Manager.PrimaryColor);
                 BitmapPixelChanges[] changes = modifiedLayers.Select(x => x.PixelChanges).ToArray();
-                Manager.PreviewLayer.SetPixels(BitmapPixelChanges.CombineOverride(changes));
+                Manager.ActiveDocument.PreviewLayer.SetPixels(BitmapPixelChanges.CombineOverride(changes));
                 lastModifiedLayers = modifiedLayers;
             }
         }

+ 25 - 1
PixiEditor/Models/Controllers/ClipboardController.cs

@@ -2,6 +2,7 @@
 using System.Linq;
 using System.Windows;
 using System.Windows.Media.Imaging;
+using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.ImageManipulation;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
@@ -44,9 +45,32 @@ namespace PixiEditor.Models.Controllers
             if (image != null)
             {
                 AddImageToLayers(image);
+                int latestLayerIndex = ViewModelMain.Current.BitmapManager.ActiveDocument.Layers.Count - 1;
+                ViewModelMain.Current.BitmapManager.ActiveDocument.UndoManager.AddUndoChange(
+                    new Change(RemoveLayerProcess, new object[] { latestLayerIndex }, AddLayerProcess, new object[] { image }));
             }
         }
 
+        private static void RemoveLayerProcess(object[] parameters)
+        {
+            if (parameters.Length == 0 || !(parameters[0] is int))
+            {
+                return;
+            }
+
+            ViewModelMain.Current.BitmapManager.ActiveDocument.RemoveLayer((int)parameters[0]);
+        }
+
+        private static void AddLayerProcess(object[] parameters)
+        {
+            if (parameters.Length == 0 || !(parameters[0] is WriteableBitmap))
+            {
+                return;
+            }
+
+            AddImageToLayers((WriteableBitmap)parameters[0]);
+        }
+
         /// <summary>
         ///     Gets image from clipboard, supported PNG, Dib and Bitmap.
         /// </summary>
@@ -101,7 +125,7 @@ namespace PixiEditor.Models.Controllers
 
         private static void AddImageToLayers(WriteableBitmap image)
         {
-            ViewModelMain.Current.BitmapManager.AddNewLayer("Image", image);
+            ViewModelMain.Current.BitmapManager.ActiveDocument.AddNewLayer("Image", image);
         }
     }
 }

+ 25 - 19
PixiEditor/Models/Controllers/UndoManager.cs

@@ -1,44 +1,50 @@
 using System.Collections.Generic;
 using System.Linq;
 using PixiEditor.Models.DataHolders;
+using PixiEditor.ViewModels;
 
 namespace PixiEditor.Models.Controllers
 {
-    public static class UndoManager
+    public class UndoManager
     {
-        private static bool lastChangeWasUndo;
+        private bool lastChangeWasUndo;
 
-        public static Stack<Change> UndoStack { get; set; } = new Stack<Change>();
+        public Stack<Change> UndoStack { get; set; } = new Stack<Change>();
 
-        public static Stack<Change> RedoStack { get; set; } = new Stack<Change>();
+        public Stack<Change> RedoStack { get; set; } = new Stack<Change>();
 
-        public static bool CanUndo => UndoStack.Count > 0;
+        public bool CanUndo => UndoStack.Count > 0;
 
-        public static bool CanRedo => RedoStack.Count > 0;
+        public bool CanRedo => RedoStack.Count > 0;
 
-        public static object MainRoot { get; set; }
+        public object MainRoot { get; set; }
 
-        /// <summary>
-        ///     Sets object(root) in which undo properties are stored.
-        /// </summary>
-        /// <param name="root">Parent object.</param>
-        public static void SetMainRoot(object root)
+        public UndoManager()
+        {
+            if (ViewModelMain.Current != null && ViewModelMain.Current.UndoSubViewModel != null)
+            {
+                MainRoot = ViewModelMain.Current.UndoSubViewModel;
+            }
+        }
+
+        public UndoManager(object mainRoot)
         {
-            MainRoot = root;
+            MainRoot = mainRoot;
         }
 
         /// <summary>
         ///     Adds property change to UndoStack.
         /// </summary>
-        public static void AddUndoChange(Change change)
+        public void AddUndoChange(Change change)
         {
-            // Clears RedoStack if las move wasn't redo or undo and if redo stack is greater than 0
+            lastChangeWasUndo = false;
+
+            // Clears RedoStack if last move wasn't redo or undo and if redo stack is greater than 0.
             if (lastChangeWasUndo == false && RedoStack.Count > 0)
             {
                 RedoStack.Clear();
             }
 
-            lastChangeWasUndo = false;
             change.Root ??= MainRoot;
             UndoStack.Push(change);
         }
@@ -46,7 +52,7 @@ namespace PixiEditor.Models.Controllers
         /// <summary>
         ///     Sets top property in UndoStack to Old Value.
         /// </summary>
-        public static void Undo()
+        public void Undo()
         {
             lastChangeWasUndo = true;
             Change change = UndoStack.Pop();
@@ -65,7 +71,7 @@ namespace PixiEditor.Models.Controllers
         /// <summary>
         ///     Sets top property from RedoStack to old value.
         /// </summary>
-        public static void Redo()
+        public void Redo()
         {
             lastChangeWasUndo = true;
             Change change = RedoStack.Pop();
@@ -81,7 +87,7 @@ namespace PixiEditor.Models.Controllers
             UndoStack.Push(change);
         }
 
-        private static void SetPropertyValue(object target, string propName, object value)
+        private void SetPropertyValue(object target, string propName, object value)
         {
             string[] bits = propName.Split('.');
             for (int i = 0; i < bits.Length - 1; i++)

+ 8 - 0
PixiEditor/Models/DataHolders/BitmapPixelChanges.cs

@@ -1,9 +1,12 @@
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Linq;
 using System.Windows.Media;
+using System.Windows.Media.Imaging;
 using PixiEditor.Exceptions;
 using PixiEditor.Helpers.Extensions;
+using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 
 namespace PixiEditor.Models.DataHolders
@@ -59,6 +62,11 @@ namespace PixiEditor.Models.DataHolders
             return output;
         }
 
+        public static BitmapPixelChanges CombineOverride(BitmapPixelChanges changes1, BitmapPixelChanges changes2)
+        {
+            return CombineOverride(new[] { changes1, changes2 });
+        }
+
         /// <summary>
         ///     Builds BitmapPixelChanges using 2 same-length enumerables of coordinates and colors.
         /// </summary>

+ 254 - 0
PixiEditor/Models/DataHolders/Document.cs

@@ -1,13 +1,18 @@
 using System;
+using System.Buffers;
 using System.Collections.ObjectModel;
+using System.IO;
 using System.Linq;
 using System.Windows;
 using System.Windows.Media;
+using System.Windows.Media.Imaging;
 using PixiEditor.Helpers;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Enums;
+using PixiEditor.Models.IO;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
+using PixiEditor.ViewModels;
 
 namespace PixiEditor.Models.DataHolders
 {
@@ -21,10 +26,66 @@ namespace PixiEditor.Models.DataHolders
         {
             Width = width;
             Height = height;
+            RequestCloseDocumentCommand = new RelayCommand(RequestCloseDocument);
+            SetAsActiveOnClickCommand = new RelayCommand(SetAsActiveOnClick);
+            UndoManager = new UndoManager();
+            XamlAccesibleViewModel = ViewModelMain.Current ?? null;
+            GeneratePreviewLayer();
+            DocumentSizeChanged?.Invoke(this, new DocumentSizeChangedEventArgs(0, 0, width, height));
         }
 
         public event EventHandler<DocumentSizeChangedEventArgs> DocumentSizeChanged;
 
+        public event EventHandler<LayersChangedEventArgs> LayersChanged;
+
+        public RelayCommand RequestCloseDocumentCommand { get; set; }
+
+        public RelayCommand SetAsActiveOnClickCommand { get; set; }
+
+        private ViewModelMain xamlAccesibleViewModel = null;
+
+        public ViewModelMain XamlAccesibleViewModel // Used to access ViewModelMain, without changing DataContext in XAML
+        {
+            get => xamlAccesibleViewModel;
+            set
+            {
+                xamlAccesibleViewModel = value;
+                RaisePropertyChanged(nameof(XamlAccesibleViewModel));
+            }
+        }
+
+        private string documentFilePath = string.Empty;
+
+        public string DocumentFilePath
+        {
+            get => documentFilePath;
+            set
+            {
+                documentFilePath = value;
+                RaisePropertyChanged(nameof(DocumentFilePath));
+                RaisePropertyChanged(nameof(Name));
+            }
+        }
+
+        private bool changesSaved = true;
+
+        public bool ChangesSaved
+        {
+            get => changesSaved;
+            set
+            {
+                changesSaved = value;
+                RaisePropertyChanged(nameof(ChangesSaved));
+                RaisePropertyChanged(nameof(Name)); // This updates name so it shows asterisk if unsaved
+            }
+        }
+
+        public string Name
+        {
+            get => (string.IsNullOrEmpty(DocumentFilePath) ? "Untitled" : Path.GetFileName(DocumentFilePath))
+                + (!ChangesSaved ? " *" : string.Empty);
+        }
+
         public int Width
         {
             get => width;
@@ -45,6 +106,92 @@ namespace PixiEditor.Models.DataHolders
             }
         }
 
+        private Selection selection = new Selection(Array.Empty<Coordinates>());
+
+        public Selection ActiveSelection
+        {
+            get => selection;
+            set
+            {
+                selection = value;
+                RaisePropertyChanged("ActiveSelection");
+            }
+        }
+
+        private Layer previewLayer;
+
+        public Layer PreviewLayer
+        {
+            get => previewLayer;
+            set
+            {
+                previewLayer = value;
+                RaisePropertyChanged("PreviewLayer");
+            }
+        }
+
+        private double mouseXonCanvas;
+
+        private double mouseYonCanvas;
+
+        public double MouseXOnCanvas // Mouse X coordinate relative to canvas
+        {
+            get => mouseXonCanvas;
+            set
+            {
+                mouseXonCanvas = value;
+                RaisePropertyChanged(nameof(MouseXOnCanvas));
+            }
+        }
+
+        public double MouseYOnCanvas // Mouse Y coordinate relative to canvas
+        {
+            get => mouseYonCanvas;
+            set
+            {
+                mouseYonCanvas = value;
+                RaisePropertyChanged(nameof(MouseYOnCanvas));
+            }
+        }
+
+        private double zoomPercentage = 100;
+
+        public double ZoomPercentage
+        {
+            get => zoomPercentage;
+            set
+            {
+                zoomPercentage = value;
+                RaisePropertyChanged(nameof(ZoomPercentage));
+            }
+        }
+
+        private Point viewPortPosition;
+
+        public Point ViewportPosition
+        {
+            get => viewPortPosition;
+            set
+            {
+                viewPortPosition = value;
+                RaisePropertyChanged(nameof(ViewportPosition));
+            }
+        }
+
+        private bool recenterZoombox = true;
+
+        public bool RecenterZoombox
+        {
+            get => recenterZoombox;
+            set
+            {
+                recenterZoombox = value;
+                RaisePropertyChanged(nameof(RecenterZoombox));
+            }
+        }
+
+        public UndoManager UndoManager { get; set; }
+
         public ObservableCollection<Layer> Layers { get; set; } = new ObservableCollection<Layer>();
 
         public Layer ActiveLayer => Layers.Count > 0 ? Layers[ActiveLayerIndex] : null;
@@ -60,6 +207,41 @@ namespace PixiEditor.Models.DataHolders
             }
         }
 
+        public void GeneratePreviewLayer()
+        {
+            PreviewLayer = new Layer("_previewLayer")
+            {
+                MaxWidth = Width,
+                MaxHeight = Height
+            };
+        }
+
+        public void CenterViewport()
+        {
+            RecenterZoombox = false; // It's a trick to trigger change in UserControl
+            RecenterZoombox = true;
+            ViewportPosition = default;
+            ZoomPercentage = default;
+        }
+
+        public void SaveWithDialog()
+        {
+            bool savedSuccessfully = Exporter.SaveAsEditableFileWithDialog(this, out string path);
+            DocumentFilePath = path;
+            ChangesSaved = savedSuccessfully;
+        }
+
+        public void Save()
+        {
+            Save(DocumentFilePath);
+        }
+
+        public void Save(string path)
+        {
+            DocumentFilePath = Exporter.SaveAsEditableFile(this, path);
+            ChangesSaved = true;
+        }
+
         public ObservableCollection<Color> Swatches { get; set; } = new ObservableCollection<Color>();
 
         /// <summary>
@@ -96,6 +278,63 @@ namespace PixiEditor.Models.DataHolders
             DocumentSizeChanged?.Invoke(this, new DocumentSizeChangedEventArgs(oldWidth, oldHeight, width, height));
         }
 
+        public void SetActiveLayer(int index)
+        {
+            if (ActiveLayerIndex <= Layers.Count - 1)
+            {
+                ActiveLayer.IsActive = false;
+            }
+
+            ActiveLayerIndex = index;
+            ActiveLayer.IsActive = true;
+            LayersChanged?.Invoke(this, new LayersChangedEventArgs(index, LayerAction.SetActive));
+        }
+
+        public void AddNewLayer(string name, WriteableBitmap bitmap, bool setAsActive = true)
+        {
+            AddNewLayer(name, bitmap.PixelWidth, bitmap.PixelHeight, setAsActive);
+            Layers.Last().LayerBitmap = bitmap;
+        }
+
+        public void AddNewLayer(string name, bool setAsActive = true)
+        {
+            AddNewLayer(name, 0, 0, setAsActive);
+        }
+
+        public void AddNewLayer(string name, int width, int height, bool setAsActive = true)
+        {
+            Layers.Add(new Layer(name, width, height)
+            {
+                MaxHeight = Height,
+                MaxWidth = Width
+            });
+            if (setAsActive)
+            {
+                SetActiveLayer(Layers.Count - 1);
+            }
+
+            LayersChanged?.Invoke(this, new LayersChangedEventArgs(0, LayerAction.Add));
+        }
+
+        public void RemoveLayer(int layerIndex)
+        {
+            if (Layers.Count == 0)
+            {
+                return;
+            }
+
+            bool wasActive = Layers[layerIndex].IsActive;
+            Layers.RemoveAt(layerIndex);
+            if (wasActive)
+            {
+                SetActiveLayer(0);
+            }
+            else if (ActiveLayerIndex > Layers.Count - 1)
+            {
+                SetActiveLayer(Layers.Count - 1);
+            }
+        }
+
         /// <summary>
         ///     Resizes all document layers using NearestNeighbor interpolation.
         /// </summary>
@@ -153,6 +392,9 @@ namespace PixiEditor.Models.DataHolders
                 "Clip canvas"));
         }
 
+        /// <summary>
+        /// Centers content inside document.
+        /// </summary>
         public void CenterContent()
         {
             DoubleCords points = GetEdgePoints();
@@ -183,6 +425,18 @@ namespace PixiEditor.Models.DataHolders
                     "Center content"));
         }
 
+        private void SetAsActiveOnClick(object obj)
+        {
+            XamlAccesibleViewModel.BitmapManager.MouseController.StopRecordingMouseMovementChanges();
+            XamlAccesibleViewModel.BitmapManager.MouseController.StartRecordingMouseMovementChanges(true);
+            XamlAccesibleViewModel.BitmapManager.ActiveDocument = this;
+        }
+
+        private void RequestCloseDocument(object parameter)
+        {
+            ViewModelMain.Current.DocumentSubViewModel.RequestCloseDocument(this);
+        }
+
         private int GetOffsetXForAnchor(int srcWidth, int destWidth, AnchorPoint anchor)
         {
             if (anchor.HasFlag(AnchorPoint.Center))

+ 10 - 4
PixiEditor/Models/Dialogs/NewFileDialog.cs

@@ -1,13 +1,15 @@
-using System.Windows;
+using System;
+using System.Windows;
+using PixiEditor.Models.UserPreferences;
 using PixiEditor.Views;
 
 namespace PixiEditor.Models.Dialogs
 {
     public class NewFileDialog : CustomDialog
     {
-        private int height;
+        private int height = (int)PreferencesSettings.GetPreference("DefaultNewFileHeight", 16L);
 
-        private int width;
+        private int width = (int)PreferencesSettings.GetPreference("DefaultNewFileWidth", 16L);
 
         public int Width
         {
@@ -37,7 +39,11 @@ namespace PixiEditor.Models.Dialogs
 
         public override bool ShowDialog()
         {
-            Window popup = new NewFilePopup();
+            Window popup = new NewFilePopup()
+            {
+                FileWidth = Width,
+                FileHeight = Height
+            };
             popup.ShowDialog();
             Height = (popup as NewFilePopup).FileHeight;
             Width = (popup as NewFilePopup).FileWidth;

+ 15 - 15
PixiEditor/Models/IO/Exporter.cs

@@ -3,23 +3,21 @@ using System.IO;
 using System.Windows;
 using System.Windows.Media.Imaging;
 using Microsoft.Win32;
+using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Dialogs;
+using PixiEditor.Parser;
 
 namespace PixiEditor.Models.IO
 {
     public class Exporter
     {
-        public static Size FileDimensions { get; set; }
-
-        public static string SaveDocumentPath { get; set; }
-
         /// <summary>
         ///     Saves document as .pixi file that contains all document data.
         /// </summary>
         /// <param name="document">Document to save.</param>
-        /// <param name="updateWorkspacePath">Should editor remember dialog path for further saves.</param>
-        public static bool SaveAsEditableFileWithDialog(Document document, bool updateWorkspacePath = false)
+        /// <param name="path">Path where file was saved.</param>
+        public static bool SaveAsEditableFileWithDialog(Document document, out string path)
         {
             SaveFileDialog dialog = new SaveFileDialog
             {
@@ -28,21 +26,24 @@ namespace PixiEditor.Models.IO
             };
             if ((bool)dialog.ShowDialog())
             {
-                SaveAsEditableFile(document, dialog.FileName, updateWorkspacePath);
+                path = SaveAsEditableFile(document, dialog.FileName);
                 return true;
             }
 
+            path = string.Empty;
             return false;
         }
 
-        public static void SaveAsEditableFile(Document document, string path, bool updateWorkspacePath = false)
+        /// <summary>
+        /// Saves editable file to chosen path and returns it.
+        /// </summary>
+        /// <param name="document">Document to be saved.</param>
+        /// <param name="path">Path where to save file.</param>
+        /// <returns>Path.</returns>
+        public static string SaveAsEditableFile(Document document, string path)
         {
-            BinarySerialization.WriteToBinaryFile(path, new SerializableDocument(document));
-
-            if (updateWorkspacePath)
-            {
-                SaveDocumentPath = path;
-            }
+            PixiParser.Serialize(document.ToSerializable(), path);
+            return path;
         }
 
         /// <summary>
@@ -64,7 +65,6 @@ namespace PixiEditor.Models.IO
                     return;
                 }
 
-                FileDimensions = new Size(info.FileWidth, info.FileHeight);
                 SaveAsPng(info.FilePath, info.FileWidth, info.FileHeight, bitmap);
             }
         }

+ 69 - 28
PixiEditor/Models/IO/Importer.cs

@@ -1,8 +1,13 @@
 using System;
+using System.IO;
+using System.Runtime.Serialization;
 using System.Windows.Media.Imaging;
+using PixiEditor.Exceptions;
 using PixiEditor.Helpers;
+using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.DataHolders;
-
+using PixiEditor.Parser;
+
 namespace PixiEditor.Models.IO
 {
     public class Importer : NotifyableObject
@@ -13,42 +18,78 @@ namespace PixiEditor.Models.IO
         /// <param name="path">Path of image.</param>
         /// <param name="width">New width of image.</param>
         /// <param name="height">New height of image.</param>
-        /// <returns></returns>
+        /// <returns>WriteableBitmap of improted image.</returns>
         public static WriteableBitmap ImportImage(string path, int width, int height)
-        {
-            WriteableBitmap wbmp = ImportImage(path);
+        {
+            WriteableBitmap wbmp = ImportImage(path);
             if (wbmp.PixelWidth != width || wbmp.PixelHeight != height)
             {
                 return wbmp.Resize(width, height, WriteableBitmapExtensions.Interpolation.NearestNeighbor);
             }
 
-            return wbmp;
-        }
-
+            return wbmp;
+        }
+
         /// <summary>
         ///     Imports image from path and resizes it to given dimensions.
         /// </summary>
         /// <param name="path">Path of image.</param>
-        public static WriteableBitmap ImportImage(string path)
-        {
-            Uri uri = new Uri(path);
-            BitmapImage bitmap = new BitmapImage();
-            bitmap.BeginInit();
-            bitmap.UriSource = uri;
-            bitmap.EndInit();
-
-            return BitmapFactory.ConvertToPbgra32Format(bitmap);
-        }
-
-        public static Document ImportDocument(string path)
-        {
-            return BinarySerialization.ReadFromBinaryFile<SerializableDocument>(path).ToDocument();
-        }
-
-        public static bool IsSupportedFile(string path)
-        {
-            path = path.ToLower();
-            return path.EndsWith(".pixi") || path.EndsWith(".png") || path.EndsWith(".jpg") || path.EndsWith(".jpeg");
-        }
+        public static WriteableBitmap ImportImage(string path)
+        {
+            try
+            {
+                Uri uri = new Uri(path);
+                BitmapImage bitmap = new BitmapImage();
+                bitmap.BeginInit();
+                bitmap.UriSource = uri;
+                bitmap.EndInit();
+
+                return BitmapFactory.ConvertToPbgra32Format(bitmap);
+            }
+            catch (NotSupportedException)
+            {
+                throw new CorruptedFileException();
+            }
+            catch (FileFormatException)
+            {
+                throw new CorruptedFileException();
+            }
+        }
+
+        public static Document ImportDocument(string path)
+        {
+            try
+            {
+                Document doc = PixiParser.Deserialize(path).ToDocument();
+                doc.DocumentFilePath = path;
+                return doc;
+            }
+            catch (InvalidFileException)
+            {
+                throw new CorruptedFileException();
+            }
+        }
+
+        public static Document ImportOldDocument(string path)
+        {
+            try
+            {
+                using FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read);
+
+                Document doc = PixiParser.DeserializeOld(stream).ToDocument();
+                doc.DocumentFilePath = path;
+                return doc;
+            }
+            catch (SerializationException)
+            {
+                throw new CorruptedFileException();
+            }
+        }
+
+        public static bool IsSupportedFile(string path)
+        {
+            path = path.ToLower();
+            return path.EndsWith(".pixi") || path.EndsWith(".png") || path.EndsWith(".jpg") || path.EndsWith(".jpeg");
+        }
     }
 }

+ 12 - 10
PixiEditor/Models/ImageManipulation/BitmapUtils.cs

@@ -18,7 +18,10 @@ namespace PixiEditor.Models.ImageManipulation
         public static WriteableBitmap BytesToWriteableBitmap(int currentBitmapWidth, int currentBitmapHeight, byte[] byteArray)
         {
             WriteableBitmap bitmap = BitmapFactory.New(currentBitmapWidth, currentBitmapHeight);
-            bitmap.FromByteArray(byteArray);
+            if (byteArray != null)
+            {
+                bitmap.FromByteArray(byteArray);
+            }
             return bitmap;
         }
 
@@ -37,29 +40,28 @@ namespace PixiEditor.Models.ImageManipulation
             {
                 for (int i = 0; i < layers.Length; i++)
                 {
+                    float layerOpacity = layers[i].Opacity;
                     for (int y = 0; y < finalBitmap.Height; y++)
                     {
                         for (int x = 0; x < finalBitmap.Width; x++)
                         {
                             Color color = layers[i].GetPixelWithOffset(x, y);
-                            float layerOpacity = layers[i].Opacity;
-                            if (i > 0 && ((color.A < 255 && color.A > 0) || (layerOpacity < 1f && layerOpacity > 0)))
+                            if (i > 0 && ((color.A < 255 && color.A > 0) || (layerOpacity < 1f && layerOpacity > 0 && color.A > 0)))
                             {
                                 var lastLayerPixel = finalBitmap.GetPixel(x, y);
-                                byte lastPixelA = (byte)(lastLayerPixel.A * layers[i - 1].Opacity);
                                 byte pixelA = (byte)(color.A * layerOpacity);
-                                byte r = (byte)((color.R * pixelA / 255) + (lastLayerPixel.R * lastPixelA * (255 - pixelA) / (255 * 255)));
-                                byte g = (byte)((color.G * pixelA / 255) + (lastLayerPixel.G * lastPixelA * (255 - pixelA) / (255 * 255)));
-                                byte b = (byte)((color.B * pixelA / 255) + (lastLayerPixel.B * lastPixelA * (255 - pixelA) / (255 * 255)));
-                                byte a = (byte)(pixelA + (lastPixelA * (255 - pixelA) / 255));
+                                byte r = (byte)((color.R * pixelA / 255) + (lastLayerPixel.R * lastLayerPixel.A * (255 - pixelA) / (255 * 255)));
+                                byte g = (byte)((color.G * pixelA / 255) + (lastLayerPixel.G * lastLayerPixel.A * (255 - pixelA) / (255 * 255)));
+                                byte b = (byte)((color.B * pixelA / 255) + (lastLayerPixel.B * lastLayerPixel.A * (255 - pixelA) / (255 * 255)));
+                                byte a = (byte)(pixelA + (lastLayerPixel.A * (255 - pixelA) / 255));
                                 color = Color.FromArgb(a, r, g, b);
                             }
                             else
                             {
-                                color = Color.FromArgb((byte)(color.A * layers[i].Opacity), color.R, color.G, color.B);
+                                color = Color.FromArgb(color.A, color.R, color.G, color.B);
                             }
 
-                            if (color.A != 0 || color.R != 0 || color.B != 0 || color.G != 0)
+                            if (color.A > 0)
                             {
                                 finalBitmap.SetPixel(x, y, color);
                             }

+ 1 - 0
PixiEditor/Models/Layers/Layer.cs

@@ -333,6 +333,7 @@ namespace PixiEditor.Models.Layers
         public void Clear()
         {
             LayerBitmap.Clear();
+            ClipCanvas();
         }
 
         /// <summary>

+ 3 - 3
PixiEditor/Models/Position/CoordinatesCalculator.cs

@@ -47,7 +47,7 @@ namespace PixiEditor.Models.Position
         /// <param name="y1">Top left y position.</param>
         /// <param name="x2">Bottom right x position.</param>
         /// <param name="y2">Bottom right Y position.</param>
-        public static Coordinates[] RectangleToCoordinates(int x1, int y1, int x2, int y2)
+        public static IEnumerable<Coordinates> RectangleToCoordinates(int x1, int y1, int x2, int y2)
         {
             x2++;
             y2++;
@@ -60,10 +60,10 @@ namespace PixiEditor.Models.Position
                 }
             }
 
-            return coordinates.ToArray();
+            return coordinates;
         }
 
-        public static Coordinates[] RectangleToCoordinates(DoubleCords coordinates)
+        public static IEnumerable<Coordinates> RectangleToCoordinates(DoubleCords coordinates)
         {
             return RectangleToCoordinates(coordinates.Coords1.X, coordinates.Coords1.Y, coordinates.Coords2.X, coordinates.Coords2.Y);
         }

+ 6 - 2
PixiEditor/Models/Tools/BitmapOperationTool.cs

@@ -10,17 +10,21 @@ namespace PixiEditor.Models.Tools
         public bool RequiresPreviewLayer { get; set; }
 
         public bool UseDefaultUndoMethod { get; set; } = true;
+
+        private readonly LayerChange[] onlyLayerArr = new LayerChange[] { new LayerChange(BitmapPixelChanges.Empty, 0) };
 
         public abstract LayerChange[] Use(Layer layer, Coordinates[] mouseMove, Color color);
 
         protected LayerChange[] Only(BitmapPixelChanges changes, Layer layer)
         {
-            return new[] { new LayerChange(changes, layer) };
+            onlyLayerArr[0] = new LayerChange(changes, layer);
+            return onlyLayerArr;
         }
 
         protected LayerChange[] Only(BitmapPixelChanges changes, int layerIndex)
         {
-            return new[] { new LayerChange(changes, layerIndex) };
+            onlyLayerArr[0] = new LayerChange(changes, layerIndex);
+            return onlyLayerArr;
         }
     }
 }

+ 0 - 2
PixiEditor/Models/Tools/ShapeTool.cs

@@ -18,8 +18,6 @@ namespace PixiEditor.Models.Tools
             Toolbar = new BasicShapeToolbar();
         }
 
-        public abstract override ToolType ToolType { get; }
-
         public abstract override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color);
 
         protected IEnumerable<Coordinates> GetThickShape(IEnumerable<Coordinates> shape, int thickness)

+ 38 - 5
PixiEditor/Models/Tools/Tool.cs

@@ -1,5 +1,7 @@
-using System.Windows.Input;
+using System;
+using System.Windows.Input;
 using PixiEditor.Helpers;
+using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Tools.ToolSettings;
 using PixiEditor.Models.Tools.ToolSettings.Toolbars;
 
@@ -7,15 +9,38 @@ namespace PixiEditor.Models.Tools
 {
     public abstract class Tool : NotifyableObject
     {
+        protected string name;
         private bool isActive;
+        private string actionDisplay = string.Empty;
+
+        public string ToolName
+        {
+            get
+            {
+                if (string.IsNullOrWhiteSpace(name))
+                {
+                    return GetType().Name.Replace("Tool", string.Empty);
+                }
 
-        public abstract ToolType ToolType { get; }
+                return name;
+            }
+        }
 
-        public string ImagePath => $"/Images/{ToolType}Image.png";
+        public string ImagePath => $"/Images/{ToolName}Image.png";
 
         public bool HideHighlight { get; set; } = false;
 
-        public string Tooltip { get; set; }
+        public string Tooltip { get; set; }
+
+        public string ActionDisplay
+        {
+            get => actionDisplay;
+            set
+            {
+                actionDisplay = value;
+                RaisePropertyChanged("ActionDisplay");
+            }
+        }
 
         public bool IsActive
         {
@@ -41,6 +66,14 @@ namespace PixiEditor.Models.Tools
         {
         }
 
+        public virtual void OnKeyDown(KeyEventArgs e)
+        {
+        }
+
+        public virtual void OnKeyUp(KeyEventArgs e)
+        {
+        }
+
         public virtual void OnRecordingLeftMouseDown(MouseEventArgs e)
         {
         }
@@ -53,7 +86,7 @@ namespace PixiEditor.Models.Tools
         {
         }
 
-        public virtual void AfterAddedUndo()
+        public virtual void AfterAddedUndo(UndoManager undoManager)
         {
         }
     }

+ 11 - 3
PixiEditor/Models/Tools/ToolSettings/Settings/ColorSetting.cs

@@ -1,4 +1,5 @@
-using System.Windows.Data;
+using System.Windows;
+using System.Windows.Data;
 using System.Windows.Media;
 using ColorPicker;
 
@@ -11,12 +12,19 @@ namespace PixiEditor.Models.Tools.ToolSettings.Settings
         {
             Label = label;
             SettingControl = GenerateColorPicker();
-            Value = Color.FromArgb(0, 0, 0, 0);
+            Value = Color.FromArgb(255, 255, 255, 255);
         }
 
         private PortableColorPicker GenerateColorPicker()
         {
-            PortableColorPicker picker = new PortableColorPicker();
+            var resourceDictionary = new ResourceDictionary();
+            resourceDictionary.Source = new System.Uri("pack://application:,,,/ColorPicker;component/Styles/DefaultColorPickerStyle.xaml",
+                System.UriKind.RelativeOrAbsolute);
+            PortableColorPicker picker = new PortableColorPicker
+            {
+                Style = (Style)resourceDictionary["DefaultColorPickerStyle"],
+                SecondaryColor = System.Windows.Media.Colors.Black
+            };
             Binding binding = new Binding("Value")
             {
                 Mode = BindingMode.TwoWay

+ 0 - 19
PixiEditor/Models/Tools/ToolType.cs

@@ -1,19 +0,0 @@
-namespace PixiEditor.Models.Tools
-{
-    public enum ToolType
-    {
-        None,
-        MoveViewport,
-        Move,
-        Pen,
-        Select,
-        Bucket,
-        Line,
-        Circle,
-        Rectangle,
-        Eraser,
-        Brightness,
-        ColorPicker,
-        Zoom
-    }
-}

+ 23 - 8
PixiEditor/Models/Tools/Tools/BrightnessTool.cs

@@ -21,12 +21,11 @@ namespace PixiEditor.Models.Tools.Tools
 
         public BrightnessTool()
         {
+            ActionDisplay = "Draw on pixel to make it brighter. Hold Ctrl to darken.";
             Tooltip = "Makes pixel brighter or darker pixel (U). Hold Ctrl to make pixel darker.";
             Toolbar = new BrightnessToolToolbar(CorrectionFactor);
         }
 
-        public override ToolType ToolType => ToolType.Brightness;
-
         public BrightnessMode Mode { get; set; } = BrightnessMode.Default;
 
         public override void OnRecordingLeftMouseDown(MouseEventArgs e)
@@ -34,6 +33,22 @@ namespace PixiEditor.Models.Tools.Tools
             pixelsVisited.Clear();
         }
 
+        public override void OnKeyDown(KeyEventArgs e)
+        {
+            if (e.Key == Key.LeftCtrl)
+            {
+                ActionDisplay = "Draw on pixel to make it darker. Release Ctrl to brighten.";
+            }
+        }
+
+        public override void OnKeyUp(KeyEventArgs e)
+        {
+            if (e.Key == Key.LeftCtrl)
+            {
+                ActionDisplay = "Draw on pixel to make it brighter. Hold Ctrl to darken.";
+            }
+        }
+
         public override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color)
         {
             int toolSize = Toolbar.GetSetting<SizeSetting>("ToolSize").Value;
@@ -57,31 +72,31 @@ namespace PixiEditor.Models.Tools.Tools
         public BitmapPixelChanges ChangeBrightness(Layer layer, Coordinates coordinates, int toolSize, float correctionFactor)
         {
             DoubleCords centeredCoords = CoordinatesCalculator.CalculateThicknessCenter(coordinates, toolSize);
-            Coordinates[] rectangleCoordinates = CoordinatesCalculator.RectangleToCoordinates(
+            IEnumerable<Coordinates> rectangleCoordinates = CoordinatesCalculator.RectangleToCoordinates(
                 centeredCoords.Coords1.X,
                 centeredCoords.Coords1.Y,
                 centeredCoords.Coords2.X,
                 centeredCoords.Coords2.Y);
             BitmapPixelChanges changes = new BitmapPixelChanges(new Dictionary<Coordinates, Color>());
 
-            for (int i = 0; i < rectangleCoordinates.Length; i++)
+            foreach (Coordinates coordinate in rectangleCoordinates)
             {
                 if (Mode == BrightnessMode.Default)
                 {
-                    if (pixelsVisited.Contains(rectangleCoordinates[i]))
+                    if (pixelsVisited.Contains(coordinate))
                     {
                         continue;
                     }
 
-                    pixelsVisited.Add(rectangleCoordinates[i]);
+                    pixelsVisited.Add(coordinate);
                 }
 
-                Color pixel = layer.GetPixelWithOffset(rectangleCoordinates[i].X, rectangleCoordinates[i].Y);
+                Color pixel = layer.GetPixelWithOffset(coordinate.X, coordinate.Y);
                 Color newColor = ExColor.ChangeColorBrightness(
                     Color.FromArgb(pixel.A, pixel.R, pixel.G, pixel.B),
                     correctionFactor);
                 changes.ChangedPixels.Add(
-                    new Coordinates(rectangleCoordinates[i].X, rectangleCoordinates[i].Y),
+                    new Coordinates(coordinate.X, coordinate.Y),
                     newColor);
             }
 

+ 17 - 1
PixiEditor/Models/Tools/Tools/CircleTool.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Windows.Input;
 using System.Windows.Media;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.DataHolders;
@@ -14,10 +15,25 @@ namespace PixiEditor.Models.Tools.Tools
     {
         public CircleTool()
         {
+            ActionDisplay = "Click and move mouse to draw a circle. Hold Shift to draw an even one.";
             Tooltip = "Draws circle on canvas (C). Hold Shift to draw even circle.";
         }
 
-        public override ToolType ToolType => ToolType.Circle;
+        public override void OnKeyDown(KeyEventArgs e)
+        {
+            if (e.Key == Key.LeftShift)
+            {
+                ActionDisplay = "Click and move mouse to draw an even circle.";
+            }
+        }
+
+        public override void OnKeyUp(KeyEventArgs e)
+        {
+            if (e.Key == Key.LeftShift)
+            {
+                ActionDisplay = "Click and move mouse to draw a circle. Hold Shift to draw an even one.";
+            }
+        }
 
         public override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color)
         {

+ 1 - 2
PixiEditor/Models/Tools/Tools/ColorPickerTool.cs

@@ -10,11 +10,10 @@ namespace PixiEditor.Models.Tools.Tools
         public ColorPickerTool()
         {
             HideHighlight = true;
+            ActionDisplay = "Press on pixel to make it the primary color.";
             Tooltip = "Swaps primary color with selected on canvas. (O)";
         }
 
-        public override ToolType ToolType => ToolType.ColorPicker;
-
         public override void Use(Coordinates[] coordinates)
         {
             ViewModelMain.Current.ColorsSubViewModel.PrimaryColor = GetColorUnderMouse();

+ 5 - 5
PixiEditor/Models/Tools/Tools/EraserTool.cs

@@ -9,14 +9,15 @@ namespace PixiEditor.Models.Tools.Tools
 {
     public class EraserTool : BitmapOperationTool
     {
+        private readonly PenTool pen = new PenTool();
+
         public EraserTool()
         {
-            Tooltip = "Erasers color from pixel (E)";
+            ActionDisplay = "Draw to remove color from a pixel.";
+            Tooltip = "Erasers color from pixel. (E)";
             Toolbar = new BasicToolbar();
         }
 
-        public override ToolType ToolType => ToolType.Eraser;
-
         public override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color)
         {
             return Erase(layer, coordinates, Toolbar.GetSetting<SizeSetting>("ToolSize").Value);
@@ -25,9 +26,8 @@ namespace PixiEditor.Models.Tools.Tools
         public LayerChange[] Erase(Layer layer, Coordinates[] coordinates, int toolSize)
         {
             Coordinates startingCords = coordinates.Length > 1 ? coordinates[1] : coordinates[0];
-            PenTool pen = new PenTool();
             BitmapPixelChanges pixels = pen.Draw(startingCords, coordinates[0], System.Windows.Media.Colors.Transparent, toolSize);
-            return new[] { new LayerChange(pixels, layer) };
+            return Only(pixels, layer);
         }
     }
 }

+ 2 - 3
PixiEditor/Models/Tools/Tools/FloodFill.cs

@@ -11,11 +11,10 @@ namespace PixiEditor.Models.Tools.Tools
     {
         public FloodFill()
         {
-            Tooltip = "Fills area with color (G)";
+            ActionDisplay = "Press on a area to fill it.";
+            Tooltip = "Fills area with color. (G)";
         }
 
-        public override ToolType ToolType => ToolType.Bucket;
-
         public override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color)
         {
             return Only(ForestFire(layer, coordinates[0], color), layer);

+ 35 - 6
PixiEditor/Models/Tools/Tools/LineTool.cs

@@ -1,5 +1,6 @@
 using System.Collections.Generic;
 using System.Linq;
+using System.Windows.Input;
 using System.Windows.Media;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Enums;
@@ -12,13 +13,31 @@ namespace PixiEditor.Models.Tools.Tools
 {
     public class LineTool : ShapeTool
     {
+        private readonly CircleTool circleTool;
+
         public LineTool()
         {
+            ActionDisplay = "Click and move to draw a line. Hold Shift to draw an even one.";
             Tooltip = "Draws line on canvas (L). Hold Shift to draw even line.";
             Toolbar = new BasicToolbar();
+            circleTool = new CircleTool();
         }
 
-        public override ToolType ToolType => ToolType.Line;
+        public override void OnKeyDown(KeyEventArgs e)
+        {
+            if (e.Key == Key.LeftShift)
+            {
+                ActionDisplay = "Click and move mouse to draw an even line.";
+            }
+        }
+
+        public override void OnKeyUp(KeyEventArgs e)
+        {
+            if (e.Key == Key.LeftShift)
+            {
+                ActionDisplay = "Click and move to draw a line. Hold Shift to draw an even one.";
+            }
+        }
 
         public override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color)
         {
@@ -34,7 +53,7 @@ namespace PixiEditor.Models.Tools.Tools
 
         public IEnumerable<Coordinates> CreateLine(Coordinates start, Coordinates end, int thickness)
         {
-            return CreateLine(new[] { end, start }, thickness, CapType.Square, CapType.Square);
+            return CreateLineFastest(start, end, thickness);
         }
 
         public IEnumerable<Coordinates> CreateLine(Coordinates start, Coordinates end, int thickness, CapType startCap, CapType endCap)
@@ -54,6 +73,17 @@ namespace PixiEditor.Models.Tools.Tools
             return GetLinePoints(startingCoordinates, latestCoordinates, thickness, startCap, endCap);
         }
 
+        private IEnumerable<Coordinates> CreateLineFastest(Coordinates start, Coordinates end, int thickness)
+        {
+            IEnumerable<Coordinates> line = BresenhamLine(start.X, start.Y, end.X, end.Y);
+            if (thickness == 1)
+            {
+                return line;
+            }
+
+            return GetThickShape(line, thickness);
+        }
+
         private IEnumerable<Coordinates> GetLinePoints(Coordinates start, Coordinates end, int thickness, CapType startCap, CapType endCap)
         {
             IEnumerable<Coordinates> startingCap = GetCapCoordinates(startCap, start, thickness);
@@ -69,7 +99,7 @@ namespace PixiEditor.Models.Tools.Tools
             output.AddRange(GetCapCoordinates(endCap, end, thickness));
             if (line.Count() > 2)
             {
-                output.AddRange(GetThickShape(line.Except(new[] { start, end }).ToArray(), thickness));
+                output.AddRange(GetThickShape(line.Except(new[] { start, end }), thickness));
             }
 
             return output.Distinct();
@@ -96,10 +126,9 @@ namespace PixiEditor.Models.Tools.Tools
         /// <param name="thickness">Thickness of cap.</param>
         private IEnumerable<Coordinates> GetRoundCap(Coordinates position, int thickness)
         {
-            CircleTool circle = new CircleTool();
-            Coordinates[] rectangleCords = CoordinatesCalculator.RectangleToCoordinates(
+            IEnumerable<Coordinates> rectangleCords = CoordinatesCalculator.RectangleToCoordinates(
                 CoordinatesCalculator.CalculateThicknessCenter(position, thickness));
-            return circle.CreateEllipse(rectangleCords[0], rectangleCords[^1], 1, true);
+            return circleTool.CreateEllipse(rectangleCords.First(), rectangleCords.Last(), 1, true);
         }
 
         private IEnumerable<Coordinates> BresenhamLine(int x1, int y1, int x2, int y2)

+ 24 - 9
PixiEditor/Models/Tools/Tools/MoveTool.cs

@@ -30,7 +30,8 @@ namespace PixiEditor.Models.Tools.Tools
 
         public MoveTool()
         {
-            Tooltip = "Moves selected pixels (V). Hold Ctrl to move all layers";
+            ActionDisplay = "Hold mouse to move selected pixels. Hold Ctrl to move all layers.";
+            Tooltip = "Moves selected pixels (V). Hold Ctrl to move all layers.";
             Cursor = Cursors.Arrow;
             HideHighlight = true;
             RequiresPreviewLayer = true;
@@ -39,9 +40,23 @@ namespace PixiEditor.Models.Tools.Tools
 
         public bool MoveAll { get; set; } = false;
 
-        public override ToolType ToolType => ToolType.Move;
+        public override void OnKeyDown(KeyEventArgs e)
+        {
+            if (e.Key == Key.LeftCtrl)
+            {
+                ActionDisplay = "Hold mouse to move all selected layers.";
+            }
+        }
+
+        public override void OnKeyUp(KeyEventArgs e)
+        {
+            if (e.Key == Key.LeftCtrl)
+            {
+                ActionDisplay = "Hold mouse to move selected pixels. Hold Ctrl to move all layers.";
+            }
+        }
 
-        public override void AfterAddedUndo()
+        public override void AfterAddedUndo(UndoManager undoManager)
         {
             if (currentSelection != null && currentSelection.Length != 0)
             {
@@ -49,7 +64,7 @@ namespace PixiEditor.Models.Tools.Tools
                 foreach (var item in startPixelColors)
                 {
                     BitmapPixelChanges beforeMovePixels = BitmapPixelChanges.FromArrays(startSelection, item.Value);
-                    Change changes = UndoManager.UndoStack.Peek();
+                    Change changes = undoManager.UndoStack.Peek();
                     int layerIndex = ViewModelMain.Current.BitmapManager.ActiveDocument.Layers.IndexOf(item.Key);
 
                     ((LayerChange[])changes.OldValue).First(x => x.LayerIndex == layerIndex).PixelChanges.ChangedPixels
@@ -69,7 +84,7 @@ namespace PixiEditor.Models.Tools.Tools
         {
             if (currentSelection != null && currentSelection.Length == 0)
             {
-                UndoManager.AddUndoChange(new Change(
+                ViewModelMain.Current.BitmapManager.ActiveDocument.UndoManager.AddUndoChange(new Change(
                     ApplyOffsets,
                     new object[] { startingOffsets },
                     ApplyOffsets,
@@ -88,10 +103,10 @@ namespace PixiEditor.Models.Tools.Tools
                 ResetSelectionValues(start);
 
                 // Move offset if no selection
-                if (ViewModelMain.Current.SelectionSubViewModel.ActiveSelection != null && 
-                    ViewModelMain.Current.SelectionSubViewModel.ActiveSelection.SelectedPoints.Count > 0)
+                Selection selection = ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection;
+                if (selection != null && selection.SelectedPoints.Count > 0)
                 {
-                    currentSelection = ViewModelMain.Current.SelectionSubViewModel.ActiveSelection.SelectedPoints.ToArray();
+                    currentSelection = selection.SelectedPoints.ToArray();
                 }
                 else
                 {
@@ -144,7 +159,7 @@ namespace PixiEditor.Models.Tools.Tools
             currentSelection = TranslateSelection(end, out Coordinates[] previousSelection);
             if (updateViewModelSelection)
             {
-                ViewModelMain.Current.SelectionSubViewModel.ActiveSelection.SetSelection(currentSelection, SelectionType.New);
+                ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection.SetSelection(currentSelection, SelectionType.New);
             }
 
             ClearSelectedPixels(layer, previousSelection);

+ 2 - 3
PixiEditor/Models/Tools/Tools/MoveViewportTool.cs

@@ -13,10 +13,9 @@ namespace PixiEditor.Models.Tools.Tools
         {
             HideHighlight = true;
             Cursor = Cursors.SizeAll;
+            ActionDisplay = "Click and move to pan viewport.";
             Tooltip = "Move viewport. (H)";
         }
-
-        public override ToolType ToolType => ToolType.MoveViewport;
 
         public override void OnMouseDown(MouseEventArgs e)
         {
@@ -31,7 +30,7 @@ namespace PixiEditor.Models.Tools.Tools
             if (e.LeftButton == MouseButtonState.Pressed || e.MiddleButton == MouseButtonState.Pressed)
             {
                 var point = MousePositionConverter.GetCursorPosition();
-                ViewModelMain.Current.ViewportSubViewModel.ViewportPosition = new System.Windows.Point(
+                ViewModelMain.Current.BitmapManager.ActiveDocument.ViewportPosition = new System.Windows.Point(
                     point.X - clickPoint.X,
                     point.Y - clickPoint.Y);
             }

+ 6 - 5
PixiEditor/Models/Tools/Tools/PenTool.cs

@@ -5,23 +5,25 @@ using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools.ToolSettings.Settings;
 using PixiEditor.Models.Tools.ToolSettings.Toolbars;
+using PixiEditor.ViewModels;
 
 namespace PixiEditor.Models.Tools.Tools
 {
     public class PenTool : BitmapOperationTool
     {
         private readonly SizeSetting toolSizeSetting;
+        private readonly LineTool lineTool;
 
         public PenTool()
         {
             Cursor = Cursors.Pen;
-            Tooltip = "Standard brush (B)";
+            ActionDisplay = "Click and move to draw.";
+            Tooltip = "Standard brush. (B)";
             Toolbar = new BasicToolbar();
             toolSizeSetting = Toolbar.GetSetting<SizeSetting>("ToolSize");
+            lineTool = new LineTool();
         }
 
-        public override ToolType ToolType => ToolType.Pen;
-
         public override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color)
         {
             Coordinates startingCords = coordinates.Length > 1 ? coordinates[1] : coordinates[0];
@@ -31,9 +33,8 @@ namespace PixiEditor.Models.Tools.Tools
 
         public BitmapPixelChanges Draw(Coordinates startingCoords, Coordinates latestCords, Color color, int toolSize)
         {
-            LineTool line = new LineTool();
             return BitmapPixelChanges.FromSingleColoredArray(
-                line.CreateLine(startingCoords, latestCords, toolSize), color);
+                lineTool.CreateLine(startingCoords, latestCords, toolSize), color);
         }
     }
 }

+ 18 - 2
PixiEditor/Models/Tools/Tools/RectangleTool.cs

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Windows.Input;
 using System.Windows.Media;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.DataHolders;
@@ -14,13 +15,28 @@ namespace PixiEditor.Models.Tools.Tools
     {
         public RectangleTool()
         {
+            ActionDisplay = "Click and move to draw a rectangle.  Hold Shift to draw square.";
             Tooltip = "Draws rectangle on canvas (R). Hold Shift to draw square.";
         }
 
-        public override ToolType ToolType => ToolType.Rectangle;
-
         public bool Filled { get; set; } = false;
 
+        public override void OnKeyDown(KeyEventArgs e)
+        {
+            if (e.Key == Key.LeftShift)
+            {
+                ActionDisplay = "Click and move to draw a square.";
+            }
+        }
+
+        public override void OnKeyUp(KeyEventArgs e)
+        {
+            if (e.Key == Key.LeftShift)
+            {
+                ActionDisplay = "Click and move to draw a rectangle.  Hold Shift to draw square.";
+            }
+        }
+
         public override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color)
         {
             int thickness = Toolbar.GetSetting<SizeSetting>("ToolSize").Value;

+ 12 - 9
PixiEditor/Models/Tools/Tools/SelectTool.cs

@@ -19,36 +19,39 @@ namespace PixiEditor.Models.Tools.Tools
 
         public SelectTool()
         {
+            ActionDisplay = "Click and move to select an area.";
             Tooltip = "Selects area. (M)";
             Toolbar = new SelectToolToolbar();
         }
 
         public SelectionType SelectionType { get; set; } = SelectionType.Add;
 
-        public override ToolType ToolType => ToolType.Select;
-
         public override void OnRecordingLeftMouseDown(MouseEventArgs e)
         {
             Enum.TryParse((Toolbar.GetSetting<DropdownSetting>("SelectMode")?.Value as ComboBoxItem)?.Content as string, out SelectionType selectionType);
             SelectionType = selectionType;
 
             oldSelection = null;
-            if (ViewModelMain.Current.SelectionSubViewModel.ActiveSelection != null &&
-                ViewModelMain.Current.SelectionSubViewModel.ActiveSelection.SelectedPoints != null)
+            Selection selection = ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection;
+            if (selection != null && selection.SelectedPoints != null)
             {
-                oldSelection = ViewModelMain.Current.SelectionSubViewModel.ActiveSelection;
+                oldSelection = selection;
             }
         }
 
         public override void OnStoppedRecordingMouseUp(MouseEventArgs e)
         {
-            if (ViewModelMain.Current.SelectionSubViewModel.ActiveSelection.SelectedPoints.Count() <= 1)
+            if (ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection.SelectedPoints.Count() <= 1)
             {
                 // If we have not selected multiple points, clear the selection
-                ViewModelMain.Current.SelectionSubViewModel.ActiveSelection.Clear();
+                ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection.Clear();
             }
 
-            UndoManager.AddUndoChange(new Change("ActiveSelection", oldSelection, ViewModelMain.Current.SelectionSubViewModel.ActiveSelection, "Select pixels"));
+            ViewModelMain.Current.BitmapManager.ActiveDocument.UndoManager.AddUndoChange(
+                new Change("ActiveSelection", 
+                oldSelection,
+                ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection,
+                "Select pixels", ViewModelMain.Current.SelectionSubViewModel));
         }
 
         public override void Use(Coordinates[] pixels)
@@ -85,7 +88,7 @@ namespace PixiEditor.Models.Tools.Tools
         private void Select(Coordinates[] pixels)
         {
             IEnumerable<Coordinates> selection = GetRectangleSelectionForPoints(pixels[^1], pixels[0]);
-            ViewModelMain.Current.SelectionSubViewModel.ActiveSelection.SetSelection(selection, SelectionType);
+            ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection.SetSelection(selection, SelectionType);
         }
     }
 }

+ 18 - 3
PixiEditor/Models/Tools/Tools/ZoomTool.cs

@@ -19,16 +19,31 @@ namespace PixiEditor.Models.Tools.Tools
         {
             HideHighlight = true;
             CanStartOutsideCanvas = true;
+            ActionDisplay = "Click and move to zoom. Click to zoom in, hold alt and click to zoom out.";
             Tooltip = "Zooms viewport (Z). Click to zoom in, hold alt and click to zoom out.";
             pixelsPerZoomMultiplier = workAreaWidth / ZoomSensitivityMultiplier;
         }
 
-        public override ToolType ToolType => ToolType.Zoom;
+        public override void OnKeyDown(KeyEventArgs e)
+        {
+            if (e.Key == Key.LeftAlt)
+            {
+                ActionDisplay = "Click and move to zoom. Click to zoom out, release alt and click to zoom in.";
+            }
+        }
+
+        public override void OnKeyUp(KeyEventArgs e)
+        {
+            if (e.Key == Key.LeftAlt)
+            {
+                ActionDisplay = "Click and move to zoom. Click to zoom in, hold alt and click to zoom out.";
+            }
+        }
 
         public override void OnRecordingLeftMouseDown(MouseEventArgs e)
         {
             startingX = MousePositionConverter.GetCursorPosition().X;
-            ViewModelMain.Current.ViewportSubViewModel.ZoomPercentage = 100; // This resest the value, so callback in MainDrawingPanel can fire again later
+            ViewModelMain.Current.BitmapManager.ActiveDocument.ZoomPercentage = 100; // This resest the value, so callback in MainDrawingPanel can fire again later
         }
 
         public override void OnMouseMove(MouseEventArgs e)
@@ -61,7 +76,7 @@ namespace PixiEditor.Models.Tools.Tools
 
         public void Zoom(double percentage)
         {
-            ViewModelMain.Current.ViewportSubViewModel.ZoomPercentage = percentage;
+            ViewModelMain.Current.BitmapManager.ActiveDocument.ZoomPercentage = percentage;
         }
 
         public override void Use(Coordinates[] pixels)

+ 90 - 0
PixiEditor/Models/UserPreferences/PreferencesSettings.cs

@@ -0,0 +1,90 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Newtonsoft.Json;
+
+namespace PixiEditor.Models.UserPreferences
+{
+    public static class PreferencesSettings
+    {
+        public static bool IsLoaded { get; private set; } = false;
+
+        public static string PathToUserPreferences { get; private set; } = Path.Join(
+            Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
+            "PixiEditor",
+            "user_preferences.json");
+
+        public static Dictionary<string, object> Preferences { get; set; } = new Dictionary<string, object>();
+
+        public static void Init()
+        {
+            Init(PathToUserPreferences);
+        }
+
+        public static void Init(string path)
+        {
+            PathToUserPreferences = path;
+            if (IsLoaded == false)
+            {
+                string dir = Path.GetDirectoryName(path);
+                if (!Directory.Exists(dir))
+                {
+                    Directory.CreateDirectory(dir);
+                }
+
+                if (!File.Exists(path))
+                {
+                    File.WriteAllText(path, "{\n}");
+                }
+                else
+                {
+                    string json = File.ReadAllText(path);
+                    Preferences = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
+                }
+
+                IsLoaded = true;
+            }
+        }
+
+        public static void UpdatePreference<T>(string name, T value)
+        {
+            if (IsLoaded == false)
+            {
+                Init();
+            }
+
+            Preferences[name] = value;
+
+            Save();
+        }
+
+        public static void Save()
+        {
+            if (IsLoaded == false)
+            {
+                Init();
+            }
+
+            File.WriteAllText(PathToUserPreferences, JsonConvert.SerializeObject(Preferences));
+        }
+
+#nullable enable
+
+        public static T? GetPreference<T>(string name)
+        {
+            return GetPreference(name, default(T));
+        }
+
+        public static T? GetPreference<T>(string name, T? fallbackValue)
+        {
+            if (IsLoaded == false)
+            {
+                Init();
+            }
+
+            return Preferences.ContainsKey(name)
+                ? (T)Preferences[name]
+                : fallbackValue;
+        }
+    }
+}

+ 8 - 7
PixiEditor/PixiEditor.csproj

@@ -2,18 +2,18 @@
 
   <PropertyGroup>
     <OutputType>WinExe</OutputType>
-    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <TargetFramework>net5.0-windows</TargetFramework>
     <UseWPF>true</UseWPF>
     <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
     <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
     <GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
     <AssemblyName>PixiEditor</AssemblyName>
     <RootNamespace>PixiEditor</RootNamespace>
-    <RepositoryUrl>https://github.com/flabbet/PixiEditor</RepositoryUrl>
+    <RepositoryUrl>https://github.com/PixiEditor/PixiEditor</RepositoryUrl>
     <PackageLicenseFile>LICENSE</PackageLicenseFile>
     <PackageIcon>icon.ico</PackageIcon>
     <ApplicationIcon>..\icon.ico</ApplicationIcon>
-    <Authors>Krzysztof Krysiński</Authors>
+    <Authors>Krzysztof Krysiński, Egor Mozgovoy, CPK</Authors>
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
@@ -45,21 +45,22 @@
     </None>
   </ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Dirkster.AvalonDock.Themes.VS2013" Version="4.40.0" />
+    <PackageReference Include="Dirkster.AvalonDock.Themes.VS2013" Version="4.50.0" />
     <PackageReference Include="Expression.Blend.Sdk">
       <Version>1.0.2</Version>
     </PackageReference>
     <PackageReference Include="Extended.Wpf.Toolkit" Version="3.8.2" />
     <PackageReference Include="MvvmLightLibs" Version="5.4.1.1" />
-    <PackageReference Include="PixiEditor.ColorPicker" Version="1.0.1" />
-    <PackageReference Include="System.Drawing.Common" Version="4.7.0" />
+    <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
+    <PackageReference Include="PixiEditor.ColorPicker" Version="2.0.0" />
+    <PackageReference Include="PixiEditor.Parser" Version="1.0.1.1" />
     <PackageReference Include="WriteableBitmapEx">
       <Version>1.6.7</Version>
     </PackageReference>
   </ItemGroup>
   <ItemGroup>
     <Resource Include="Images\AnchorDot.png" />
-    <Resource Include="Images\BucketImage.png" />
+    <Resource Include="Images\FloodFillImage.png" />
     <Resource Include="Images\CircleImage.png" />
     <Resource Include="Images\EraserImage.png" />
     <Resource Include="Images\BrightnessImage.png" />

+ 3 - 3
PixiEditor/Properties/AssemblyInfo.cs

@@ -10,7 +10,7 @@ using System.Windows;
 [assembly: AssemblyConfiguration("")]
 [assembly: AssemblyCompany("PixiEditor")]
 [assembly: AssemblyProduct("PixiEditor")]
-[assembly: AssemblyCopyright("Copyright Krzysztof Krysiński © 2018 - 2020")]
+[assembly: AssemblyCopyright("Copyright PixiEditor © 2018 - 2021")]
 [assembly: AssemblyTrademark("")]
 [assembly: AssemblyCulture("")]
 
@@ -50,5 +50,5 @@ using System.Windows;
 // You can specify all the values or you can default the Build and Revision Numbers
 // by using the '*' as shown below:
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("0.1.3.6")]
-[assembly: AssemblyFileVersion("0.1.3.6")]
+[assembly: AssemblyVersion("0.1.4.0")]
+[assembly: AssemblyFileVersion("0.1.4.0")]

+ 1 - 1
PixiEditor/Properties/Settings.Designer.cs

@@ -12,7 +12,7 @@ namespace PixiEditor.Properties {
     
     
     [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
-    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.7.0.0")]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.8.1.0")]
     internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
         
         private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));

+ 2 - 5
PixiEditor/Properties/Settings.settings

@@ -1,8 +1,5 @@
 <?xml version='1.0' encoding='utf-8'?>
-
-<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
-  <Profiles>
-    <Profile Name="(Default)" />
-  </Profiles>
+<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
+  <Profiles />
   <Settings />
 </SettingsFile>

+ 39 - 0
PixiEditor/Styles/DarkCheckboxStyle.xaml

@@ -0,0 +1,39 @@
+<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+                    xmlns:local="clr-namespace:PixiEditor.Styles">
+    <Style TargetType="CheckBox">
+        <Setter Property="SnapsToDevicePixels" Value="true"/>
+        <Setter Property="OverridesDefaultStyle" Value="true"/>
+        <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
+        <Setter Property="Foreground" Value="White"/>
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate TargetType="CheckBox">
+                    <BulletDecorator Background="Transparent">
+                        <BulletDecorator.Bullet>
+                            <Border x:Name="Border" Width="20" Height="20" CornerRadius="2" Background="#FF1B1B1B" BorderThickness="0">
+                                <Path Width="9" Height="9" x:Name="CheckMark" SnapsToDevicePixels="False" Stroke="#FF0077C9" StrokeThickness="2" Data="M 0 4 L 3 8 8 0" />
+                            </Border>
+                        </BulletDecorator.Bullet>
+                        <ContentPresenter Margin="4,0,0,0" VerticalAlignment="Center" HorizontalAlignment="Left" RecognizesAccessKey="True"/>
+                    </BulletDecorator>
+                    <ControlTemplate.Triggers>
+                        <Trigger Property="IsChecked" Value="false">
+                            <Setter TargetName="CheckMark" Property="Visibility" Value="Collapsed"/>
+                        </Trigger>
+                        <Trigger Property="IsChecked" Value="{x:Null}">
+                            <Setter TargetName="CheckMark" Property="Data" Value="M 0 8 L 8 0" />
+                        </Trigger>
+                        <Trigger Property="IsMouseOver" Value="true">
+                            <Setter TargetName="Border" Property="Background" Value="#FF131313" />
+                        </Trigger>
+                        <Trigger Property="IsEnabled" Value="false">
+                            <Setter TargetName="CheckMark" Property="Stroke" Value="#FF6C6C6C"/>
+                            <Setter Property="Foreground" Value="Gray"/>
+                        </Trigger>
+                    </ControlTemplate.Triggers>
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </Style>
+</ResourceDictionary>

+ 22 - 0
PixiEditor/Styles/LabelStyles.xaml

@@ -0,0 +1,22 @@
+<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+                    xmlns:local="clr-namespace:PixiEditor.Styles">
+
+    <Style TargetType="Label" x:Key="BaseLabel">
+        <Setter Property="Foreground" Value="White"/>
+    </Style>
+    
+    <Style x:Key="Header1" TargetType="Label" BasedOn="{StaticResource BaseLabel}">
+        <Setter Property="FontSize" Value="36"/>
+        <Setter Property="Margin" Value="20"/>
+    </Style>
+
+    <Style x:Key="Header2" TargetType="Label" BasedOn="{StaticResource BaseLabel}">
+        <Setter Property="FontSize" Value="20"/>
+        <Setter Property="Margin" Value="20"/>
+    </Style>
+
+    <Style x:Key="Paragraph" TargetType="Label" BasedOn="{StaticResource BaseLabel}">
+        <Setter Property="Margin" Value="0 10 0 10"/>
+    </Style>
+</ResourceDictionary>

+ 31 - 0
PixiEditor/Styles/ThemeStyle.xaml

@@ -69,6 +69,37 @@
         </Setter>
     </Style>
 
+    <Style TargetType="Button" x:Key="AccentDarkRoundButton" BasedOn="{StaticResource BaseDarkButton}">
+        <Setter Property="OverridesDefaultStyle" Value="True" />
+        <Setter Property="Background" Value="{StaticResource AccentColor}" />
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate TargetType="Button">
+                    <Border CornerRadius="4" Background="{TemplateBinding Background}">
+                        <ContentPresenter Content="{TemplateBinding Content}" HorizontalAlignment="Center"
+                                          VerticalAlignment="Center" />
+                    </Border>
+                    <ControlTemplate.Triggers>
+                        <Trigger Property="IsEnabled" Value="False">
+                            <Setter Property="Background" Value="Transparent" />
+                            <Setter Property="Foreground" Value="Gray" />
+                            <Setter Property="Cursor" Value="Arrow" />
+                        </Trigger>
+                        <Trigger Property="IsMouseOver" Value="True">
+                            <Setter Property="Background" Value="#FF515151" />
+                            <Setter Property="Foreground" Value="White" />
+                            <Setter Property="Cursor" Value="Hand" />
+                        </Trigger>
+                        <Trigger Property="IsPressed" Value="True">
+                            <Setter Property="Background" Value="#505050" />
+                            <Setter Property="Foreground" Value="White" />
+                        </Trigger>
+                    </ControlTemplate.Triggers>
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </Style>
+
 
     <Style TargetType="Button" x:Key="ImageButtonStyle">
         <Setter Property="OverridesDefaultStyle" Value="True" />

+ 0 - 64
PixiEditor/ViewModels/FeedbackDialogViewModel.cs

@@ -1,64 +0,0 @@
-using System.Windows;
-using PixiEditor.Helpers;
-
-namespace PixiEditor.ViewModels
-{
-    internal class FeedbackDialogViewModel : ViewModelBase
-    {
-        private string _emailBody;
-
-
-        private string _mailFrom;
-
-        public FeedbackDialogViewModel()
-        {
-            CloseButtonCommand = new RelayCommand(CloseWindow);
-            SendButtonCommand = new RelayCommand(Send, CanSend);
-        }
-
-        public RelayCommand CloseButtonCommand { get; set; }
-        public RelayCommand SendButtonCommand { get; set; }
-
-        public string MailFrom
-        {
-            get => _mailFrom;
-            set
-            {
-                if (_mailFrom != value)
-                {
-                    _mailFrom = value;
-                    RaisePropertyChanged("MailFrom");
-                }
-            }
-        }
-
-        public string EmailBody
-        {
-            get => _emailBody;
-            set
-            {
-                if (_emailBody != value)
-                {
-                    _emailBody = value;
-                    RaisePropertyChanged("EmailBody");
-                }
-            }
-        }
-
-        private void CloseWindow(object parameter)
-        {
-            ((Window) parameter).DialogResult = false;
-            CloseButton(parameter);
-        }
-
-        private void Send(object parameter)
-        {
-            CloseButton(parameter);
-        }
-
-        private bool CanSend(object property)
-        {
-            return !string.IsNullOrWhiteSpace(MailFrom);
-        }
-    }
-}

+ 43 - 32
PixiEditor/ViewModels/ImportFilePopupViewModel.cs

@@ -3,25 +3,22 @@ using System.IO;
 using System.Windows;
 using System.Windows.Media.Imaging;
 using Microsoft.Win32;
+using PixiEditor.Exceptions;
 using PixiEditor.Helpers;
 
 namespace PixiEditor.ViewModels
 {
     internal class ImportFilePopupViewModel : ViewModelBase
     {
-        private string _filePath;
+        private string filePath;
 
+        private int importHeight = 16;
 
-        private int _importHeight = 16;
+        private int importWidth = 16;
 
+        private string pathButtonBorder = "#f08080";
 
-        private int _importWidth = 16;
-
-
-        private string _pathButtonBorder = "#f08080";
-
-
-        private bool _pathIsCorrect;
+        private bool pathIsCorrect;
 
         public ImportFilePopupViewModel()
         {
@@ -32,18 +29,21 @@ namespace PixiEditor.ViewModels
         }
 
         public RelayCommand CloseButtonCommand { get; set; }
+
         public RelayCommand DragMoveCommand { get; set; }
+
         public RelayCommand ChoosePathCommand { get; set; }
+
         public RelayCommand OkCommand { get; set; }
 
         public string PathButtonBorder
         {
-            get => _pathButtonBorder;
+            get => pathButtonBorder;
             set
             {
-                if (_pathButtonBorder != value)
+                if (pathButtonBorder != value)
                 {
-                    _pathButtonBorder = value;
+                    pathButtonBorder = value;
                     RaisePropertyChanged("PathButtonBorder");
                 }
             }
@@ -51,12 +51,12 @@ namespace PixiEditor.ViewModels
 
         public bool PathIsCorrect
         {
-            get => _pathIsCorrect;
+            get => pathIsCorrect;
             set
             {
-                if (_pathIsCorrect != value)
+                if (pathIsCorrect != value)
                 {
-                    _pathIsCorrect = value;
+                    pathIsCorrect = value;
                     RaisePropertyChanged("PathIsCorrect");
                 }
             }
@@ -64,12 +64,12 @@ namespace PixiEditor.ViewModels
 
         public string FilePath
         {
-            get => _filePath;
+            get => filePath;
             set
             {
-                if (_filePath != value)
+                if (filePath != value)
                 {
-                    _filePath = value;
+                    filePath = value;
                     CheckForPath(value);
                     RaisePropertyChanged("FilePath");
                 }
@@ -78,12 +78,12 @@ namespace PixiEditor.ViewModels
 
         public int ImportWidth
         {
-            get => _importWidth;
+            get => importWidth;
             set
             {
-                if (_importWidth != value)
+                if (importWidth != value)
                 {
-                    _importWidth = value;
+                    importWidth = value;
                     RaisePropertyChanged("ImportWidth");
                 }
             }
@@ -91,21 +91,21 @@ namespace PixiEditor.ViewModels
 
         public int ImportHeight
         {
-            get => _importHeight;
+            get => importHeight;
             set
             {
-                if (_importHeight != value)
+                if (importHeight != value)
                 {
-                    _importHeight = value;
+                    importHeight = value;
                     RaisePropertyChanged("ImportHeight");
                 }
             }
         }
 
         /// <summary>
-        ///     Command that handles Path choosing to save file
+        ///     Command that handles Path choosing to save file.
         /// </summary>
-        /// <param name="parameter"></param>
+        /// <param name="parameter">Binding parameter.</param>
         private void ChoosePath(object parameter)
         {
             OpenFileDialog path = new OpenFileDialog
@@ -132,12 +132,23 @@ namespace PixiEditor.ViewModels
         {
             if (File.Exists(path) && (path.EndsWith(".png") || path.EndsWith(".jpeg") || path.EndsWith(".jpg")))
             {
-                PathButtonBorder = "#b8f080";
-                PathIsCorrect = true;
-                _filePath = path;
-                BitmapImage bitmap = new BitmapImage(new Uri(path));
-                ImportHeight = bitmap.PixelHeight;
-                ImportWidth = bitmap.PixelWidth;
+                try
+                {
+                    PathButtonBorder = "#b8f080";
+                    PathIsCorrect = true;
+                    filePath = path;
+                    BitmapImage bitmap = new BitmapImage(new Uri(path));
+                    ImportHeight = bitmap.PixelHeight;
+                    ImportWidth = bitmap.PixelWidth;
+                }
+                catch (NotSupportedException)
+                {
+                    throw new CorruptedFileException();
+                }
+                catch (FileFormatException)
+                {
+                    throw new CorruptedFileException();
+                }
             }
         }
 

+ 5 - 3
PixiEditor/ViewModels/NewFileMenuViewModel.cs

@@ -13,18 +13,20 @@ namespace PixiEditor.ViewModels
         }
 
         public RelayCommand OkCommand { get; set; }
+
         public RelayCommand CloseCommand { get; set; }
+
         public RelayCommand DragMoveCommand { get; set; }
 
         private void OkButton(object parameter)
         {
-            ((Window) parameter).DialogResult = true;
-            ((Window) parameter).Close();
+            ((Window)parameter).DialogResult = true;
+            ((Window)parameter).Close();
         }
 
         private void CloseWindow(object parameter)
         {
-            ((Window) parameter).DialogResult = false;
+            ((Window)parameter).DialogResult = false;
             CloseButton(parameter);
         }
 

+ 43 - 0
PixiEditor/ViewModels/SettingsWindowViewModel.cs

@@ -0,0 +1,43 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using PixiEditor.Helpers;
+using PixiEditor.ViewModels.SubViewModels.UserPreferences;
+
+namespace PixiEditor.ViewModels
+{
+    public class SettingsWindowViewModel : ViewModelBase
+    {
+        public RelayCommand SelectCategoryCommand { get; set; }
+
+        private string selectedCategory = "General";
+
+        public string SelectedCategory
+        {
+            get => selectedCategory;
+            set
+            {
+                selectedCategory = value;
+                RaisePropertyChanged(nameof(SelectedCategory));
+            }
+        }
+
+        public SettingsViewModel SettingsSubViewModel { get; set; }
+
+        public SettingsWindowViewModel()
+        {
+            SettingsSubViewModel = new SettingsViewModel(this);
+            SelectCategoryCommand = new RelayCommand(SelectCategory);
+        }
+
+        private void SelectCategory(object parameter)
+        {
+            if (parameter is not null && parameter is string value)
+            {
+                SelectedCategory = value;
+            }
+        }
+    }
+}

+ 6 - 6
PixiEditor/ViewModels/SubViewModels/Main/ClipboardViewModel.cs

@@ -6,6 +6,7 @@ using System.Windows.Media;
 using PixiEditor.Helpers;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Position;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
 {
@@ -37,10 +38,9 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         public void Cut(object parameter)
         {
             Copy(null);
-            Owner.BitmapManager.ActiveLayer.SetPixels(
-                BitmapPixelChanges.FromSingleColoredArray(
-                    Owner.SelectionSubViewModel.ActiveSelection.SelectedPoints.ToArray(),
-                    Colors.Transparent));
+            Owner.BitmapManager.BitmapOperations.DeletePixels(
+                new[] { Owner.BitmapManager.ActiveDocument.ActiveLayer },
+                Owner.BitmapManager.ActiveDocument.ActiveSelection.SelectedPoints.ToArray());
         }
 
         public void Paste(object parameter)
@@ -56,8 +56,8 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         private void Copy(object parameter)
         {
             ClipboardController.CopyToClipboard(
-                Owner.BitmapManager.ActiveDocument.Layers.ToArray(),
-                Owner.SelectionSubViewModel.ActiveSelection.SelectedPoints.ToArray(),
+                new[] { Owner.BitmapManager.ActiveDocument.ActiveLayer },
+                Owner.BitmapManager.ActiveDocument.ActiveSelection.SelectedPoints.ToArray(),
                 Owner.BitmapManager.ActiveDocument.Width,
                 Owner.BitmapManager.ActiveDocument.Height);
         }

+ 22 - 4
PixiEditor/ViewModels/SubViewModels/Main/DocumentViewModel.cs

@@ -1,6 +1,9 @@
-using System.Linq;
+using System;
+using System.Linq;
 using PixiEditor.Helpers;
+using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Dialogs;
+using PixiEditor.Models.Enums;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
 {
@@ -8,8 +11,6 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
     {
         public const string ConfirmationDialogMessage = "Document was modified. Do you want to save changes?";
 
-        public bool UnsavedDocumentModified { get; set; }
-
         public RelayCommand CenterContentCommand { get; set; }
 
         public RelayCommand ClipCanvasCommand { get; set; }
@@ -32,11 +33,28 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             Owner.BitmapManager.ActiveDocument?.ClipCanvas();
         }
 
+        public void RequestCloseDocument(Document document)
+        {
+            if (!document.ChangesSaved)
+            {
+                ConfirmationType result = ConfirmationDialog.Show(ConfirmationDialogMessage);
+                if (result == ConfirmationType.Yes)
+                {
+                    Owner.FileSubViewModel.SaveDocument(false);
+                }
+                else if (result == ConfirmationType.Canceled)
+                {
+                    return;
+                }
+            }
+            Owner.BitmapManager.CloseDocument(document);
+        }
+
         private void DeletePixels(object parameter)
         {
             Owner.BitmapManager.BitmapOperations.DeletePixels(
                 new[] { Owner.BitmapManager.ActiveLayer },
-                Owner.SelectionSubViewModel.ActiveSelection.SelectedPoints.ToArray());
+                Owner.BitmapManager.ActiveDocument.ActiveSelection.SelectedPoints.ToArray());
         }
 
         private void OpenResizePopup(object parameter)

+ 69 - 27
PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs

@@ -4,11 +4,15 @@ using System.Linq;
 using System.Windows;
 using System.Windows.Media.Imaging;
 using Microsoft.Win32;
+using PixiEditor.Exceptions;
 using PixiEditor.Helpers;
+using PixiEditor.Models.Controllers;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Dialogs;
 using PixiEditor.Models.Enums;
 using PixiEditor.Models.IO;
+using PixiEditor.Models.UserPreferences;
+using PixiEditor.Parser;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
 {
@@ -47,10 +51,11 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         public void NewDocument(int width, int height, bool addBaseLayer = true)
         {
-            Owner.BitmapManager.ActiveDocument = new Document(width, height);
+            Owner.BitmapManager.Documents.Add(new Document(width, height));
+            Owner.BitmapManager.ActiveDocument = Owner.BitmapManager.Documents[^1];
             if (addBaseLayer)
             {
-                Owner.BitmapManager.AddNewLayer("Base Layer");
+                Owner.BitmapManager.ActiveDocument.AddNewLayer("Base Layer");
             }
 
             Owner.ResetProgramStateValues();
@@ -72,7 +77,10 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             if (dialog.ShowDialog())
             {
                 NewDocument(dialog.FileWidth, dialog.FileHeight, false);
-                Owner.BitmapManager.AddNewLayer("Image", Importer.ImportImage(dialog.FilePath, dialog.FileWidth, dialog.FileHeight));
+                Owner.BitmapManager.ActiveDocument.DocumentFilePath = path;
+                Owner.BitmapManager.ActiveDocument.AddNewLayer(
+                    "Image",
+                    Importer.ImportImage(dialog.FilePath, dialog.FileWidth, dialog.FileHeight));
             }
         }
 
@@ -90,33 +98,47 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
             else
             {
-                OpenNewFilePopup(null);
+                if (PreferencesSettings.GetPreference("ShowNewFilePopupOnStartup", true))
+                {
+                    OpenNewFilePopup(null);
+                }
             }
         }
 
         private void Open(string path)
         {
-            if (Owner.DocumentSubViewModel.UnsavedDocumentModified)
+            try
             {
-                var result = ConfirmationDialog.Show(DocumentViewModel.ConfirmationDialogMessage);
-                if (result == ConfirmationType.Yes)
+                if (path.EndsWith(".pixi"))
                 {
-                    SaveDocument(null);
+                    OpenDocument(path);
                 }
-                else if (result == ConfirmationType.Canceled)
+                else
                 {
-                    return;
+                    OpenFile(path);
                 }
-            }
 
-            Owner.ResetProgramStateValues();
-            if (path.EndsWith(".pixi"))
+                Owner.ResetProgramStateValues();
+            }
+            catch (CorruptedFileException ex)
             {
-                OpenDocument(path);
+                MessageBox.Show(ex.Message, "Failed to open file.", MessageBoxButton.OK, MessageBoxImage.Error);
             }
-            else
+            catch (OldFileFormatException)
             {
-                OpenFile(path);
+                MessageBoxResult result = MessageBox.Show("This pixi file uses the old file format and is insecure.\nOnly continue if you trust the source of the file", "Old file format", MessageBoxButton.OKCancel);
+
+                if (result == MessageBoxResult.OK)
+                {
+                    try
+                    {
+                        OpenDocument(path, true);
+                    }
+                    catch (CorruptedFileException ex)
+                    {
+                        MessageBox.Show(ex.Message, "Failed to open file.", MessageBoxButton.OK, MessageBoxImage.Error);
+                    }
+                }
             }
         }
 
@@ -132,31 +154,51 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
                 if (Importer.IsSupportedFile(dialog.FileName))
                 {
                     Open(dialog.FileName);
-                }
 
-                Owner.ViewportSubViewModel.CenterViewport();
+                    if (Owner.BitmapManager.Documents.Count > 0)
+                    {
+                        Owner.BitmapManager.ActiveDocument = Owner.BitmapManager.Documents.Last();
+                    }
+                }
             }
         }
 
-        private void OpenDocument(string path)
+        private void OpenDocument(string path, bool openOld = false)
         {
-            Owner.BitmapManager.ActiveDocument = Importer.ImportDocument(path);
-            Exporter.SaveDocumentPath = path;
-            Owner.DocumentSubViewModel.UnsavedDocumentModified = false;
+            Document document;
+
+            if (openOld)
+            {
+                document = Importer.ImportOldDocument(path);
+            }
+            else
+            {
+                document = Importer.ImportDocument(path);
+            }
+
+            if (Owner.BitmapManager.Documents.Select(x => x.DocumentFilePath).All(y => y != path))
+            {
+                Owner.BitmapManager.Documents.Add(document);
+                Owner.BitmapManager.ActiveDocument = Owner.BitmapManager.Documents.Last();
+            }
+            else
+            {
+                Owner.BitmapManager.ActiveDocument = Owner.BitmapManager.Documents.First(y => y.DocumentFilePath == path);
+            }
         }
 
         private void SaveDocument(object parameter)
         {
             bool paramIsAsNew = parameter != null && parameter.ToString()?.ToLower() == "asnew";
-            if (paramIsAsNew || Exporter.SaveDocumentPath == null)
+            if (paramIsAsNew ||
+                string.IsNullOrEmpty(Owner.BitmapManager.ActiveDocument.DocumentFilePath) ||
+                !Owner.BitmapManager.ActiveDocument.DocumentFilePath.EndsWith(".pixi"))
             {
-                var saved = Exporter.SaveAsEditableFileWithDialog(Owner.BitmapManager.ActiveDocument, !paramIsAsNew);
-                Owner.DocumentSubViewModel.UnsavedDocumentModified = Owner.DocumentSubViewModel.UnsavedDocumentModified && !saved;
+                Owner.BitmapManager.ActiveDocument.SaveWithDialog();
             }
             else
             {
-                Exporter.SaveAsEditableFile(Owner.BitmapManager.ActiveDocument, Exporter.SaveDocumentPath);
-                Owner.DocumentSubViewModel.UnsavedDocumentModified = false;
+                Owner.BitmapManager.ActiveDocument.Save();
             }
         }
 

+ 11 - 30
PixiEditor/ViewModels/SubViewModels/Main/IoViewModel.cs

@@ -17,30 +17,6 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         public RelayCommand KeyUpCommand { get; set; }
 
-        private double mouseXonCanvas;
-
-        private double mouseYonCanvas;
-
-        public double MouseXOnCanvas // Mouse X coordinate relative to canvas
-        {
-            get => mouseXonCanvas;
-            set
-            {
-                mouseXonCanvas = value;
-                RaisePropertyChanged(nameof(MouseXOnCanvas));
-            }
-        }
-
-        public double MouseYOnCanvas // Mouse Y coordinate relative to canvas
-        {
-            get => mouseYonCanvas;
-            set
-            {
-                mouseYonCanvas = value;
-                RaisePropertyChanged(nameof(MouseYOnCanvas));
-            }
-        }
-
         private bool restoreToolOnKeyUp = false;
 
         public IoViewModel(ViewModelMain owner)
@@ -76,11 +52,12 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
 
             Owner.ShortcutController.KeyPressed(args.Key, Keyboard.Modifiers);
+            Owner.BitmapManager.SelectedTool.OnKeyDown(args);
         }
 
         private void MouseDown(object parameter)
         {
-            if (Owner.BitmapManager.ActiveDocument.Layers.Count == 0)
+            if (Owner.BitmapManager.ActiveDocument == null || Owner.BitmapManager.ActiveDocument.Layers.Count == 0)
             {
                 return;
             }
@@ -89,10 +66,10 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             {
                 if (!Owner.BitmapManager.MouseController.IsRecordingChanges)
                 {
-                    bool clickedOnCanvas = MouseXOnCanvas >= 0 &&
-                        MouseXOnCanvas <= Owner.BitmapManager.ActiveDocument.Width &&
-                        MouseYOnCanvas >= 0 &&
-                        MouseYOnCanvas <= Owner.BitmapManager.ActiveDocument.Height;
+                    bool clickedOnCanvas = Owner.BitmapManager.ActiveDocument.MouseXOnCanvas >= 0 &&
+                        Owner.BitmapManager.ActiveDocument.MouseXOnCanvas <= Owner.BitmapManager.ActiveDocument.Width &&
+                        Owner.BitmapManager.ActiveDocument.MouseYOnCanvas >= 0 &&
+                        Owner.BitmapManager.ActiveDocument.MouseYOnCanvas <= Owner.BitmapManager.ActiveDocument.Height;
                     Owner.BitmapManager.MouseController.StartRecordingMouseMovementChanges(clickedOnCanvas);
                     Owner.BitmapManager.MouseController.RecordMouseMovementChange(MousePositionConverter.CurrentCoordinates);
                 }
@@ -114,7 +91,9 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         /// <param name="parameter">CommandParameter.</param>
         private void MouseMove(object parameter)
         {
-            Coordinates cords = new Coordinates((int)MouseXOnCanvas, (int)MouseYOnCanvas);
+            Coordinates cords = new Coordinates(
+                (int)Owner.BitmapManager.ActiveDocument.MouseXOnCanvas,
+                (int)Owner.BitmapManager.ActiveDocument.MouseYOnCanvas);
             MousePositionConverter.CurrentCoordinates = cords;
 
             if (Owner.BitmapManager.MouseController.IsRecordingChanges && Mouse.LeftButton == MouseButtonState.Pressed)
@@ -135,6 +114,8 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
                 Owner.ToolsSubViewModel.SetActiveTool(Owner.ToolsSubViewModel.LastActionTool);
                 ShortcutController.BlockShortcutExecution = false;
             }
+
+            Owner.BitmapManager.SelectedTool.OnKeyUp(args);
         }
     }
 }

+ 6 - 5
PixiEditor/ViewModels/SubViewModels/Main/LayersViewModel.cs

@@ -29,7 +29,8 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         public void NewLayer(object parameter)
         {
-            Owner.BitmapManager.AddNewLayer($"New Layer {Owner.BitmapManager.ActiveDocument.Layers.Count}");
+            //TODO: Implement AddNewLayer to Document, not BitmapManager
+            Owner.BitmapManager.ActiveDocument.AddNewLayer($"New Layer {Owner.BitmapManager.ActiveDocument.Layers.Count}");
         }
 
         public bool CanCreateNewLayer(object parameter)
@@ -39,12 +40,12 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         public void SetActiveLayer(object parameter)
         {
-            Owner.BitmapManager.SetActiveLayer((int)parameter);
+            Owner.BitmapManager.ActiveDocument.SetActiveLayer((int)parameter);
         }
 
         public void DeleteLayer(object parameter)
         {
-            Owner.BitmapManager.RemoveLayer((int)parameter);
+            Owner.BitmapManager.ActiveDocument.RemoveLayer((int)parameter);
         }
 
         public bool CanDeleteLayer(object property)
@@ -63,7 +64,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             Owner.BitmapManager.ActiveDocument.Layers.Move(oldIndex, oldIndex + 1);
             if (Owner.BitmapManager.ActiveDocument.ActiveLayerIndex == oldIndex)
             {
-                Owner.BitmapManager.SetActiveLayer(oldIndex + 1);
+                Owner.BitmapManager.ActiveDocument.SetActiveLayer(oldIndex + 1);
             }
         }
 
@@ -73,7 +74,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             Owner.BitmapManager.ActiveDocument.Layers.Move(oldIndex, oldIndex - 1);
             if (Owner.BitmapManager.ActiveDocument.ActiveLayerIndex == oldIndex)
             {
-                Owner.BitmapManager.SetActiveLayer(oldIndex - 1);
+                Owner.BitmapManager.ActiveDocument.SetActiveLayer(oldIndex - 1);
             }
         }
 

+ 47 - 0
PixiEditor/ViewModels/SubViewModels/Main/MiscViewModel.cs

@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using PixiEditor.Helpers;
+using PixiEditor.Views.Dialogs;
+
+namespace PixiEditor.ViewModels.SubViewModels.Main
+{
+    public class MiscViewModel : SubViewModel<ViewModelMain>
+    {
+        public RelayCommand OpenHyperlinkCommand { get; set; }
+
+        public RelayCommand OpenSettingsWindowCommand { get; set; }
+
+        public MiscViewModel(ViewModelMain owner)
+            : base(owner)
+        {
+            OpenHyperlinkCommand = new RelayCommand(OpenHyperlink);
+            OpenSettingsWindowCommand = new RelayCommand(OpenSettingsWindow);
+        }
+
+        private void OpenSettingsWindow(object parameter)
+        {
+            SettingsWindow settings = new SettingsWindow();
+            settings.Show();
+        }
+
+        private void OpenHyperlink(object parameter)
+        {
+            if (parameter == null)
+            {
+                return;
+            }
+
+            var url = (string)parameter;
+            var processInfo = new ProcessStartInfo()
+            {
+                FileName = url,
+                UseShellExecute = true
+            };
+            Process.Start(processInfo);
+        }
+    }
+}

+ 4 - 16
PixiEditor/ViewModels/SubViewModels/Main/SelectionViewModel.cs

@@ -13,40 +13,28 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         public RelayCommand SelectAllCommand { get; set; }
 
-        private Selection selection;
-
-        public Selection ActiveSelection
-        {
-            get => selection;
-            set
-            {
-                selection = value;
-                RaisePropertyChanged("ActiveSelection");
-            }
-        }
-
         public SelectionViewModel(ViewModelMain owner)
             : base(owner)
         {
             DeselectCommand = new RelayCommand(Deselect, SelectionIsNotEmpty);
             SelectAllCommand = new RelayCommand(SelectAll, CanSelectAll);
-            ActiveSelection = new Selection(Array.Empty<Coordinates>());
         }
 
         public void SelectAll(object parameter)
         {
             SelectTool select = new SelectTool();
-            ActiveSelection.SetSelection(select.GetAllSelection(), SelectionType.New);
+            Owner.BitmapManager.ActiveDocument.ActiveSelection.SetSelection(select.GetAllSelection(), SelectionType.New);
         }
 
         public void Deselect(object parameter)
         {
-            ActiveSelection?.Clear();
+            Owner.BitmapManager.ActiveDocument.ActiveSelection?.Clear();
         }
 
         public bool SelectionIsNotEmpty(object property)
         {
-            return ActiveSelection?.SelectedPoints != null && ActiveSelection.SelectedPoints.Count > 0;
+            var selectedPoints = Owner.BitmapManager.ActiveDocument?.ActiveSelection.SelectedPoints;
+            return selectedPoints != null && selectedPoints.Count > 0;
         }
 
         private bool CanSelectAll(object property)

Some files were not shown because too many files changed in this diff