diff --git a/GUI/FileErrorDialog.xaml b/GUI/FileErrorDialog.xaml
new file mode 100644
index 00000000..58830ee1
--- /dev/null
+++ b/GUI/FileErrorDialog.xaml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/GUI/FileErrorDialog.xaml.cs b/GUI/FileErrorDialog.xaml.cs
new file mode 100644
index 00000000..420e9129
--- /dev/null
+++ b/GUI/FileErrorDialog.xaml.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+
+namespace GARbro.GUI
+{
+ ///
+ /// Interaction logic for FileErrorDialog.xaml
+ ///
+ public partial class FileErrorDialog : Rnd.Windows.ModalWindow
+ {
+ public FileErrorDialog (string title, string error_text)
+ {
+ InitializeComponent();
+ this.DataContext = new ViewModel { Title = title, Text = error_text };
+ }
+
+ new public FileErrorDialogResult ShowDialog ()
+ {
+ bool dialog_result = base.ShowDialog() ?? false;
+ return new FileErrorDialogResult
+ {
+ Continue = dialog_result,
+ IgnoreErrors = IgnoreErrors.IsChecked ?? false
+ };
+ }
+
+ private void ContinueButton_Click (object sender, RoutedEventArgs e)
+ {
+ this.DialogResult = true;
+ }
+
+ private void AbortButton_Click (object sender, RoutedEventArgs e)
+ {
+ this.DialogResult = false;
+ }
+
+ private class ViewModel
+ {
+ public string Title { get; set; }
+ public string Text { get; set; }
+ public ICommand CopyCommand { get; private set; }
+
+ public ViewModel ()
+ {
+ CopyCommand = new ActionCommand (CopyText);
+ }
+
+ private void CopyText ()
+ {
+ try
+ {
+ Clipboard.SetText (Text);
+ }
+ catch (Exception X)
+ {
+ System.Diagnostics.Trace.WriteLine (X.Message, "Clipboard error");
+ }
+ }
+ }
+
+ private class ActionCommand : ICommand
+ {
+ readonly Action m_action;
+
+ public ActionCommand (Action action)
+ {
+ m_action = action;
+ }
+
+ public void Execute (object parameter)
+ {
+ m_action();
+ }
+
+ public bool CanExecute (object parameter)
+ {
+ return true;
+ }
+
+ #pragma warning disable 67
+ public event EventHandler CanExecuteChanged;
+ }
+ }
+
+ public struct FileErrorDialogResult
+ {
+ public bool Continue;
+ public bool IgnoreErrors;
+ }
+}
diff --git a/GUI/FileExistsDialog.xaml b/GUI/FileExistsDialog.xaml
new file mode 100644
index 00000000..d9ea71d6
--- /dev/null
+++ b/GUI/FileExistsDialog.xaml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/GUI/FileExistsDialog.xaml.cs b/GUI/FileExistsDialog.xaml.cs
new file mode 100644
index 00000000..b308f1df
--- /dev/null
+++ b/GUI/FileExistsDialog.xaml.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+
+namespace GARbro.GUI
+{
+ ///
+ /// Interaction logic for FileExistsDialog.xaml
+ ///
+ public partial class FileExistsDialog : Rnd.Windows.ModalWindow
+ {
+ public FileExistsDialog (string title, string text)
+ {
+ InitializeComponent ();
+ this.Title = title;
+ this.Notice.Text = text;
+ }
+
+ new public FileExistsDialogResult ShowDialog ()
+ {
+ bool dialog_result = base.ShowDialog() ?? false;
+ if (!dialog_result)
+ FileAction = ExistingFileAction.Abort;
+ return new FileExistsDialogResult
+ {
+ Action = FileAction,
+ ApplyToAll = ApplyToAll.IsChecked ?? false
+ };
+ }
+
+ public ExistingFileAction FileAction { get; set; }
+
+ private void SkipButton_Click (object sender, RoutedEventArgs e)
+ {
+ this.DialogResult = true;
+ this.FileAction = ExistingFileAction.Skip;
+ }
+
+ private void OverwriteButton_Click (object sender, RoutedEventArgs e)
+ {
+ this.DialogResult = true;
+ this.FileAction = ExistingFileAction.Overwrite;
+ }
+
+ private void RenameButton_Click (object sender, RoutedEventArgs e)
+ {
+ this.DialogResult = true;
+ this.FileAction = ExistingFileAction.Rename;
+ }
+
+ private void AbortButton_Click (object sender, RoutedEventArgs e)
+ {
+ this.DialogResult = false;
+ this.FileAction = ExistingFileAction.Abort;
+ }
+ }
+
+ public enum ExistingFileAction
+ {
+ Skip,
+ Overwrite,
+ Rename,
+ Abort
+ }
+
+ public struct FileExistsDialogResult
+ {
+ public ExistingFileAction Action;
+ public bool ApplyToAll;
+ }
+}
diff --git a/GUI/GARbro.GUI.csproj b/GUI/GARbro.GUI.csproj
index 7efbf283..aff20539 100644
--- a/GUI/GARbro.GUI.csproj
+++ b/GUI/GARbro.GUI.csproj
@@ -95,9 +95,6 @@
..\packages\NAudio.1.8.0\lib\net35\NAudio.dll
True
-
- ..\packages\Ookii.Dialogs.1.0\lib\net35\Ookii.Dialogs.Wpf.dll
-
@@ -147,14 +144,25 @@
ExtractFile.xaml
+
+ FileErrorDialog.xaml
+
+
+ FileExistsDialog.xaml
+
+
+
+
+ Component
+
@@ -195,6 +203,14 @@
Designer
MSBuild:Compile
+
+ Designer
+ MSBuild:Compile
+
+
+ Designer
+ MSBuild:Compile
+
MSBuild:Compile
Designer
@@ -284,6 +300,12 @@
+
+
+
+
+
+
perl "$(SolutionDir)inc-revision.pl" "$(ProjectPath)" $(ConfigurationName) "$(SolutionDir)\"
diff --git a/GUI/GarConvert.cs b/GUI/GarConvert.cs
index 739bf79d..1b5bb240 100644
--- a/GUI/GarConvert.cs
+++ b/GUI/GarConvert.cs
@@ -34,7 +34,6 @@ using System.Diagnostics;
using GameRes;
using GARbro.GUI.Strings;
using GARbro.GUI.Properties;
-using Ookii.Dialogs.Wpf;
using System.Runtime.InteropServices;
namespace GARbro.GUI
@@ -107,21 +106,17 @@ namespace GARbro.GUI
}
}
- internal class GarConvertMedia
+ internal class GarConvertMedia : GarOperation
{
- private MainWindow m_main;
- private ProgressDialog m_progress_dialog;
private IEnumerable m_source;
private ImageFormat m_image_format;
- private Exception m_pending_error;
private List> m_failed = new List>();
public bool IgnoreErrors { get; set; }
public IEnumerable> FailedFiles { get { return m_failed; } }
- public GarConvertMedia (MainWindow parent)
+ public GarConvertMedia (MainWindow parent) : base (parent, guiStrings.TextMediaConvertError)
{
- m_main = parent;
}
public void Convert (IEnumerable images, ImageFormat format)
@@ -144,42 +139,47 @@ namespace GARbro.GUI
void ConvertWorker (object sender, DoWorkEventArgs e)
{
m_pending_error = null;
- try
+ int total = m_source.Count();
+ int i = 0;
+ foreach (var entry in m_source)
{
- int total = m_source.Count();
- int i = 0;
- foreach (var entry in m_source)
+ if (m_progress_dialog.CancellationPending)
{
- if (m_progress_dialog.CancellationPending)
- throw new OperationCanceledException();
- var filename = entry.Name;
- int progress = i++*100/total;
- m_progress_dialog.ReportProgress (progress, string.Format (guiStrings.MsgConvertingFile,
- Path.GetFileName (filename)), null);
- try
- {
- if ("image" == entry.Type)
- ConvertImage (filename);
- else if ("audio" == entry.Type)
- ConvertAudio (filename);
- }
- catch (NotImplementedException X)
- {
- // target format creation not implemented
- m_pending_error = X;
- break;
- }
- catch (Exception X)
- {
- if (!IgnoreErrors)
- throw;
- m_failed.Add (Tuple.Create (Path.GetFileName (filename), X.Message));
- }
+ m_pending_error = new OperationCanceledException();
+ break;
+ }
+ var filename = entry.Name;
+ int progress = i++*100/total;
+ m_progress_dialog.ReportProgress (progress, string.Format (guiStrings.MsgConvertingFile,
+ Path.GetFileName (filename)), null);
+ try
+ {
+ if ("image" == entry.Type)
+ ConvertImage (filename);
+ else if ("audio" == entry.Type)
+ ConvertAudio (filename);
+ }
+ catch (SkipExistingFileException)
+ {
+ continue;
+ }
+ catch (OperationCanceledException X)
+ {
+ m_pending_error = X;
+ break;
+ }
+ catch (Exception X)
+ {
+ if (!IgnoreErrors)
+ {
+ var error_text = string.Format (guiStrings.TextErrorConverting, entry.Name, X.Message);
+ var result = ShowErrorDialog (error_text);
+ if (!result.Continue)
+ break;
+ IgnoreErrors = result.IgnoreErrors;
+ }
+ m_failed.Add (Tuple.Create (Path.GetFileName (filename), X.Message));
}
- }
- catch (Exception X)
- {
- m_pending_error = X;
}
}
@@ -232,41 +232,20 @@ namespace GARbro.GUI
return;
file.Position = 0;
var image = src_format.Item1.Read (file, src_format.Item2);
+ var output = CreateNewFile (target_name);
try
{
- using (var output = CreateNewFile (target_name))
- m_image_format.Write (output, image);
+ m_image_format.Write (output, image);
}
catch // delete destination file on conversion failure
{
+ // FIXME if user chooses to overwrite file, and conversion results in error,
+ // then original file will be lost.
+ output.Dispose();
File.Delete (target_name);
throw;
}
- }
- }
-
- ///
- /// Creates new file with specified filename, or, if it's already exists, tries to open
- /// files named "FILENAME.1.EXT", "FILENAME.2.EXT" and so on.
- /// Throws exception after 100th failed attempt.
- ///
-
- public static Stream CreateNewFile (string filename)
- {
- string name = filename;
- var ext = new Lazy (() => Path.GetExtension (filename));
- for (int attempt = 1; ; ++attempt)
- {
- try
- {
- return File.Open (name, FileMode.CreateNew);
- }
- catch (IOException) // file already exists
- {
- if (100 == attempt) // limit number of attempts
- throw;
- }
- name = Path.ChangeExtension (filename, attempt.ToString()+ext.Value);
+ output.Dispose();
}
}
diff --git a/GUI/GarCreate.cs b/GUI/GarCreate.cs
index 7388895a..93ff242e 100644
--- a/GUI/GarCreate.cs
+++ b/GUI/GarCreate.cs
@@ -34,7 +34,6 @@ using System.Windows.Input;
using GameRes;
using GARbro.GUI.Strings;
using GARbro.GUI.Properties;
-using Ookii.Dialogs.Wpf;
namespace GARbro.GUI
{
@@ -57,21 +56,17 @@ namespace GARbro.GUI
}
}
- internal class GarCreate
+ internal class GarCreate : GarOperation
{
- private MainWindow m_main;
private string m_arc_name;
private IList m_file_list;
- private ProgressDialog m_progress_dialog;
private ArchiveFormat m_format;
private ResourceOptions m_options;
- private Exception m_pending_error;
delegate void AddFilesEnumerator (IList list, string path, DirectoryInfo path_info);
- public GarCreate (MainWindow parent)
+ public GarCreate (MainWindow parent) : base (parent, guiStrings.TextCreateArchiveError)
{
- m_main = parent;
m_arc_name = Settings.Default.appLastCreatedArchive;
}
diff --git a/GUI/GarExtract.cs b/GUI/GarExtract.cs
index d7a36840..0da4bcdd 100644
--- a/GUI/GarExtract.cs
+++ b/GUI/GarExtract.cs
@@ -2,7 +2,7 @@
//! \date Fri Jul 25 05:52:27 2014
//! \brief Extract archive frontend.
//
-// Copyright (C) 2014-2015 by morkt
+// Copyright (C) 2014-2017 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
@@ -28,13 +28,12 @@ using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel;
-using System.Diagnostics;
+using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Input;
+using System.Windows.Interop;
using System.Windows.Media.Imaging;
-using Ookii.Dialogs.Wpf;
using GameRes;
-using GameRes.Strings;
using GARbro.GUI.Strings;
using GARbro.GUI.Properties;
@@ -103,9 +102,8 @@ namespace GARbro.GUI
}
}
- sealed internal class GarExtract : IDisposable
+ sealed internal class GarExtract : GarOperation, IDisposable
{
- private MainWindow m_main;
private string m_arc_name;
private ArchiveFileSystem m_fs;
private readonly bool m_should_ascend;
@@ -116,13 +114,12 @@ namespace GARbro.GUI
private bool m_convert_audio;
private ImageFormat m_image_format;
private int m_extract_count;
+ private int m_skip_count;
private bool m_extract_in_progress = false;
- private ProgressDialog m_progress_dialog;
- private Exception m_pending_error;
public bool IsActive { get { return m_extract_in_progress; } }
- public GarExtract (MainWindow parent, string source)
+ public GarExtract (MainWindow parent, string source) : base (parent, guiStrings.TextExtractionError)
{
m_arc_name = Path.GetFileName (source);
try
@@ -135,15 +132,13 @@ namespace GARbro.GUI
throw new OperationCanceledException (string.Format ("{1}: {0}", X.Message, m_arc_name));
}
m_fs = VFS.Top as ArchiveFileSystem;
- m_main = parent;
}
- public GarExtract (MainWindow parent, string source, ArchiveFileSystem fs)
+ public GarExtract (MainWindow parent, string source, ArchiveFileSystem fs) : base (parent, guiStrings.TextExtractionError)
{
if (null == fs)
throw new UnknownFormatException();
m_fs = fs;
- m_main = parent;
m_arc_name = Path.GetFileName (source);
m_should_ascend = false;
}
@@ -245,6 +240,7 @@ namespace GARbro.GUI
private void ExtractFilesFromArchive (string text, IEnumerable file_list)
{
+ file_list = file_list.Where (e => e.Offset >= 0);
if (file_list.Skip (1).Any() // file_list.Count() > 1
&& (m_skip_images || m_skip_script || m_skip_audio))
file_list = file_list.Where (f => !(m_skip_images && f.Type == "image") &&
@@ -269,8 +265,6 @@ namespace GARbro.GUI
m_progress_dialog.ProgressBarStyle = ProgressBarStyle.MarqueeProgressBar;
}
m_convert_audio = !m_skip_audio && Settings.Default.appConvertAudio;
- m_extract_count = 0;
- m_pending_error = null;
m_progress_dialog.DoWork += (s, e) => ExtractWorker (file_list);
m_progress_dialog.RunWorkerCompleted += OnExtractComplete;
m_progress_dialog.ShowDialog (m_main);
@@ -279,61 +273,82 @@ namespace GARbro.GUI
void ExtractWorker (IEnumerable file_list)
{
- try
+ m_extract_count = 0;
+ m_skip_count = 0;
+ var arc = m_fs.Source;
+ int total = file_list.Count();
+ int progress_count = 0;
+ bool ignore_errors = false;
+ foreach (var entry in file_list)
{
- var arc = m_fs.Source;
- int total = file_list.Count();
- foreach (var entry in file_list)
+ if (m_progress_dialog.CancellationPending)
+ break;
+ if (total > 1)
+ m_progress_dialog.ReportProgress (progress_count++*100/total, null, entry.Name);
+ try
{
- if (m_progress_dialog.CancellationPending)
- break;
- if (total > 1)
- m_progress_dialog.ReportProgress (m_extract_count*100/total, null, entry.Name);
if (null != m_image_format && entry.Type == "image")
ExtractImage (arc, entry, m_image_format);
else if (m_convert_audio && entry.Type == "audio")
ExtractAudio (arc, entry);
else
- arc.Extract (entry);
+ ExtractEntryAsIs (arc, entry);
++m_extract_count;
}
+ catch (SkipExistingFileException)
+ {
+ ++m_skip_count;
+ continue;
+ }
+ catch (OperationCanceledException)
+ {
+ break;
+ }
+ catch (Exception X)
+ {
+ if (!ignore_errors)
+ {
+ var error_text = string.Format (guiStrings.TextErrorExtracting, entry.Name, X.Message);
+ var result = ShowErrorDialog (error_text);
+ if (!result.Continue)
+ break;
+ ignore_errors = result.IgnoreErrors;
+ }
+ ++m_skip_count;
+ }
}
- catch (Exception X)
- {
- m_pending_error = X;
- }
+ }
+
+ void ExtractEntryAsIs (ArcFile arc, Entry entry)
+ {
+ using (var input = arc.OpenEntry (entry))
+ using (var output = CreateNewFile (entry.Name, true))
+ input.CopyTo (output);
}
void ExtractImage (ArcFile arc, Entry entry, ImageFormat target_format)
{
- try
+ using (var decoder = arc.OpenImage (entry))
{
- using (var decoder = arc.OpenImage (entry))
+ var src_format = decoder.SourceFormat; // could be null
+ string target_ext = target_format.Extensions.FirstOrDefault() ?? "";
+ string outname = Path.ChangeExtension (entry.Name, target_ext);
+ if (src_format == target_format)
{
- var src_format = decoder.SourceFormat; // could be null
- string target_ext = target_format.Extensions.FirstOrDefault() ?? "";
- string outname = FindUniqueFileName (entry.Name, target_ext);
- if (src_format == target_format)
- {
- // source format is the same as a target, copy file as is
- using (var output = ArchiveFormat.CreateFile (outname))
- decoder.Source.CopyTo (output);
- return;
- }
- ImageData image = decoder.Image;
- if (m_adjust_image_offset)
- {
- image = AdjustImageOffset (image);
- }
- using (var outfile = ArchiveFormat.CreateFile (outname))
- {
- target_format.Write (outfile, image);
- }
+ // source format is the same as a target, copy file as is
+ using (var output = CreateNewFile (outname, true))
+ decoder.Source.CopyTo (output);
+ return;
+ }
+ ImageData image = decoder.Image;
+ if (m_adjust_image_offset)
+ {
+ image = AdjustImageOffset (image);
+ }
+ using (var outfile = CreateNewFile (outname, true))
+ {
+ target_format.Write (outfile, image);
}
- }
- catch
- {
- throw new InvalidFormatException (string.Format ("{1}: {0}", guiStrings.MsgUnableInterpretImage, entry.Name));
}
}
@@ -366,24 +381,24 @@ namespace GARbro.GUI
return new ImageData (bitmap);
}
- static void ExtractAudio (ArcFile arc, Entry entry)
+ void ExtractAudio (ArcFile arc, Entry entry)
{
using (var file = arc.OpenBinaryEntry (entry))
using (var sound = AudioFormat.Read (file))
{
if (null == sound)
- throw new InvalidFormatException (string.Format ("{1}: {0}", guiStrings.MsgUnableInterpretAudio, entry.Name));
+ throw new InvalidFormatException (guiStrings.MsgUnableInterpretAudio);
ConvertAudio (entry.Name, sound);
}
}
- public static void ConvertAudio (string entry_name, SoundInput input)
+ public void ConvertAudio (string filename, SoundInput input)
{
string source_format = input.SourceFormat;
if (GarConvertMedia.CommonAudioFormats.Contains (source_format))
{
- string output_name = FindUniqueFileName (entry_name, source_format);
- using (var output = ArchiveFormat.CreateFile (output_name))
+ var output_name = Path.ChangeExtension (filename, source_format);
+ using (var output = CreateNewFile (output_name, true))
{
input.Source.Position = 0;
input.Source.CopyTo (output);
@@ -391,25 +406,12 @@ namespace GARbro.GUI
}
else
{
- string output_name = FindUniqueFileName (entry_name, "wav");
- using (var output = ArchiveFormat.CreateFile (output_name))
+ var output_name = Path.ChangeExtension (filename, "wav");
+ using (var output = CreateNewFile (output_name, true))
AudioFormat.Wav.Write (input, output);
}
}
- public static string FindUniqueFileName (string source_filename, string target_ext)
- {
- string ext = target_ext;
- for (int attempt = 1; attempt < 100; ++attempt)
- {
- string filename = Path.ChangeExtension (source_filename, ext);
- if (!File.Exists (filename))
- return filename;
- ext = string.Format ("{0}.{1}", attempt, target_ext);
- }
- throw new IOException ("File aready exists");
- }
-
void OnExtractComplete (object sender, RunWorkerCompletedEventArgs e)
{
m_extract_in_progress = false;
@@ -420,17 +422,6 @@ namespace GARbro.GUI
m_main.Dispatcher.Invoke (m_main.RefreshView);
}
m_main.SetStatusText (Localization.Format ("MsgExtractedFiles", m_extract_count));
- if (null != m_pending_error)
- {
- if (m_pending_error is OperationCanceledException)
- m_main.SetStatusText (m_pending_error.Message);
- else
- {
- string message = string.Format (guiStrings.TextErrorExtracting,
- m_progress_dialog.Description, m_pending_error.Message);
- m_main.PopupError (message, guiStrings.MsgErrorExtracting);
- }
- }
this.Dispose();
}
diff --git a/GUI/GarOperation.cs b/GUI/GarOperation.cs
new file mode 100644
index 00000000..c37d6d9d
--- /dev/null
+++ b/GUI/GarOperation.cs
@@ -0,0 +1,122 @@
+//! \file GarOperation.cs
+//! \date Fri Feb 03 19:06:52 2017
+//! \brief Base class for GARbro file operation.
+//
+// Copyright (C) 2017 by morkt
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+
+using System;
+using System.IO;
+using GARbro.GUI.Strings;
+
+namespace GARbro.GUI
+{
+ internal class GarOperation
+ {
+ internal MainWindow m_main;
+ internal ProgressDialog m_progress_dialog;
+ internal Exception m_pending_error;
+ internal FileExistsDialogResult m_duplicate_action;
+ internal string m_title;
+
+ const int MaxRenameAttempts = 100;
+
+ protected GarOperation (MainWindow parent, string dialog_title)
+ {
+ m_main = parent;
+ m_title = dialog_title;
+ }
+
+ ///
+ /// Create file . Also create path to file if is true.
+ /// If file aready exists, popup dialog asking for necessary action.
+ /// WARNING: path to file should be relative, ArchiveFormat.CreatePath strips drive/root specification.
+ ///
+ protected Stream CreateNewFile (string filename, bool create_path = false)
+ {
+ if (create_path)
+ filename = GameRes.ArchiveFormat.CreatePath (filename);
+ FileMode open_mode = FileMode.CreateNew;
+ if (m_duplicate_action.ApplyToAll &&
+ m_duplicate_action.Action == ExistingFileAction.Overwrite)
+ open_mode = FileMode.Create;
+ try
+ {
+ return File.Open (filename, open_mode);
+ }
+ catch (IOException) // file already exists?
+ {
+ if (!File.Exists (filename) || FileMode.Create == open_mode) // some unforseen I/O error, give up
+ throw;
+ }
+ if (!m_duplicate_action.ApplyToAll)
+ {
+ var msg_text = string.Format (guiStrings.TextFileAlreadyExists, Path.GetFileName (filename));
+ m_duplicate_action = m_main.Dispatcher.Invoke (() => m_main.ShowFileExistsDialog (m_title, msg_text, m_progress_dialog.GetWindowHandle()));
+ }
+ switch (m_duplicate_action.Action)
+ {
+ default:
+ case ExistingFileAction.Abort:
+ throw new OperationCanceledException();
+ case ExistingFileAction.Skip:
+ throw new SkipExistingFileException();
+ case ExistingFileAction.Rename:
+ return CreateRenamedFile (filename);
+ case ExistingFileAction.Overwrite:
+ return File.Open (filename, FileMode.Create);
+ }
+ }
+
+ ///
+ /// Creates new file with specified filename, or, if it's already exists, tries to open
+ /// files named "FILENAME.1.EXT", "FILENAME.2.EXT" and so on.
+ /// Throws exception after 100th failed attempt.
+ ///
+
+ public static Stream CreateRenamedFile (string filename)
+ {
+ var ext = Path.GetExtension (filename);
+ for (int attempt = 1; ; ++attempt)
+ {
+ var name = Path.ChangeExtension (filename, attempt.ToString()+ext);
+ try
+ {
+ return File.Open (name, FileMode.CreateNew);
+ }
+ catch (IOException) // file already exists
+ {
+ if (MaxRenameAttempts == attempt) // limit number of attempts
+ throw;
+ }
+ }
+ }
+
+ protected FileErrorDialogResult ShowErrorDialog (string error_text)
+ {
+ return m_main.Dispatcher.Invoke (() => m_main.ShowErrorDialog (m_title, error_text, m_progress_dialog.GetWindowHandle()));
+ }
+ }
+
+ internal class SkipExistingFileException : ApplicationException
+ {
+ }
+}
diff --git a/GUI/Images/64x64/actions.png b/GUI/Images/64x64/actions.png
new file mode 100644
index 00000000..4b26a139
Binary files /dev/null and b/GUI/Images/64x64/actions.png differ
diff --git a/GUI/Images/64x64/alert.png b/GUI/Images/64x64/alert.png
new file mode 100644
index 00000000..1a0ce795
Binary files /dev/null and b/GUI/Images/64x64/alert.png differ
diff --git a/GUI/MainWindow.xaml.cs b/GUI/MainWindow.xaml.cs
index 6adab05c..3d45960e 100644
--- a/GUI/MainWindow.xaml.cs
+++ b/GUI/MainWindow.xaml.cs
@@ -1,6 +1,6 @@
// Game Resource Browser
//
-// Copyright (C) 2014-2015 by morkt
+// Copyright (C) 2014-2017 by morkt
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
@@ -35,6 +35,7 @@ using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
+using System.Windows.Interop;
using System.Windows.Threading;
using Microsoft.VisualBasic.FileIO;
using GARbro.GUI.Properties;
@@ -195,6 +196,40 @@ namespace GARbro.GUI
Dispatcher.Invoke (() => MessageBox.Show (this, message, title, MessageBoxButton.OK, MessageBoxImage.Error));
}
+ internal FileErrorDialogResult ShowErrorDialog (string error_title, string error_text, IntPtr parent_hwnd)
+ {
+ var dialog = new FileErrorDialog (error_title, error_text);
+ SetModalWindowParent (dialog, parent_hwnd);
+ return dialog.ShowDialog();
+ }
+
+ internal FileExistsDialogResult ShowFileExistsDialog (string title, string text, IntPtr parent_hwnd)
+ {
+ var dialog = new FileExistsDialog (title, text);
+ SetModalWindowParent (dialog, parent_hwnd);
+ return dialog.ShowDialog();
+ }
+
+ private void SetModalWindowParent (Window dialog, IntPtr parent_hwnd)
+ {
+ if (parent_hwnd != IntPtr.Zero)
+ {
+ var native_dialog = new WindowInteropHelper (dialog);
+ native_dialog.Owner = parent_hwnd;
+ NativeMethods.EnableWindow (parent_hwnd, false);
+ EventHandler on_closed = null;
+ on_closed = (s, e) => {
+ NativeMethods.EnableWindow (parent_hwnd, true);
+ dialog.Closed -= on_closed;
+ };
+ dialog.Closed += on_closed;
+ }
+ else
+ {
+ dialog.Owner = this;
+ }
+ }
+
const int MaxRecentFiles = 9;
LinkedList m_recent_files;
diff --git a/GUI/ProgressDialog/IProgressDialog.cs b/GUI/ProgressDialog/IProgressDialog.cs
new file mode 100644
index 00000000..16308e9a
--- /dev/null
+++ b/GUI/ProgressDialog/IProgressDialog.cs
@@ -0,0 +1,116 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Runtime.InteropServices;
+
+namespace GARbro.GUI.Interop
+{
+ [ComImport, Guid ("F8383852-FCD3-11d1-A6B9-006097DF5BD4")]
+ internal class ProgressDialogRCW
+ {
+ }
+
+ [ComImport, Guid ("EBBC7C04-315E-11d2-B62F-006097DF5BD4"), CoClass (typeof(ProgressDialogRCW))]
+ internal interface ProgressDialog : IProgressDialog
+ {
+ }
+
+ [Flags]
+ internal enum ProgressDialogFlags : uint
+ {
+ Normal = 0x00000000,
+ Modal = 0x00000001,
+ AutoTime = 0x00000002,
+ NoTime = 0x00000004,
+ NoMinimize = 0x00000008,
+ NoProgressBar = 0x00000010,
+ MarqueeProgress = 0x00000020,
+ NoCancel = 0x00000040
+ }
+
+ [Flags]
+ internal enum ProgressTimerAction : uint
+ {
+ Reset = 0x00000001,
+ Pause = 0x00000002,
+ Resume = 0x00000003
+ }
+
+ [ComImport, Guid ("EBBC7C04-315E-11d2-B62F-006097DF5BD4"), InterfaceType (ComInterfaceType.InterfaceIsIUnknown)]
+ internal interface IProgressDialog
+ {
+
+ [PreserveSig]
+ void StartProgressDialog(
+ IntPtr hwndParent,
+ [MarshalAs(UnmanagedType.IUnknown)]
+ object punkEnableModless,
+ ProgressDialogFlags dwFlags,
+ IntPtr pvResevered
+ );
+
+ [PreserveSig]
+ void StopProgressDialog();
+
+ [PreserveSig]
+ void SetTitle(
+ [MarshalAs(UnmanagedType.LPWStr)]
+ string pwzTitle
+ );
+
+ [PreserveSig]
+ void SetAnimation(
+ IntPtr hInstAnimation,
+ ushort idAnimation
+ );
+
+ [PreserveSig]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ bool HasUserCancelled();
+
+ [PreserveSig]
+ void SetProgress(
+ uint dwCompleted,
+ uint dwTotal
+ );
+ [PreserveSig]
+ void SetProgress64(
+ ulong ullCompleted,
+ ulong ullTotal
+ );
+
+ [PreserveSig]
+ void SetLine(
+ uint dwLineNum,
+ [MarshalAs(UnmanagedType.LPWStr)]
+ string pwzString,
+ [MarshalAs(UnmanagedType.VariantBool)]
+ bool fCompactPath,
+ IntPtr pvResevered
+ );
+
+ [PreserveSig]
+ void SetCancelMsg(
+ [MarshalAs(UnmanagedType.LPWStr)]
+ string pwzCancelMsg,
+ object pvResevered
+ );
+
+ [PreserveSig]
+ void Timer(
+ ProgressTimerAction dwTimerAction,
+ object pvResevered
+ );
+ }
+
+ [ComImport, Guid ("00000114-0000-0000-C000-000000000046"), InterfaceType (ComInterfaceType.InterfaceIsIUnknown)]
+ internal interface IOleWindow
+ {
+ [PreserveSig]
+ void GetWindow (out IntPtr phwnd);
+
+ [PreserveSig]
+ void ContextSensitiveHelp ([MarshalAs(UnmanagedType.Bool)] bool fEnterMode);
+ }
+}
diff --git a/GUI/ProgressDialog/ProgressDialog.cs b/GUI/ProgressDialog/ProgressDialog.cs
new file mode 100644
index 00000000..fa1f47a4
--- /dev/null
+++ b/GUI/ProgressDialog/ProgressDialog.cs
@@ -0,0 +1,713 @@
+// Modified ProgressDialog from Ookii.Dialogs
+//
+// Copyright © Sven Groot (Ookii.org) 2009
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1) Redistributions of source code must retain the above copyright notice,
+// this list of conditions and the following disclaimer.
+// 2) Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+// 3) Neither the name of the ORGANIZATION nor the names of its contributors
+// may be used to endorse or promote products derived from this software
+// without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+// THE POSSIBILITY OF SUCH DAMAGE.
+//
+
+using System;
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+using System.Windows;
+using System.Windows.Interop;
+
+namespace GARbro.GUI
+{
+ public class ProgressDialog : Component
+ {
+ private class ProgressChangedData
+ {
+ public string Text { get; set; }
+ public string Description { get; set; }
+ public object UserState { get; set; }
+ }
+
+ private string _windowTitle;
+ private string _text;
+ private string _description;
+ private Interop.IProgressDialog _dialog;
+ private string _cancellationText;
+ private bool _useCompactPathsForText;
+ private bool _useCompactPathsForDescription;
+ private bool _cancellationPending;
+ private BackgroundWorker _backgroundWorker;
+
+ ///
+ /// Event raised when the dialog is displayed.
+ ///
+ ///
+ /// Use this event to perform the operation that the dialog is showing the progress for.
+ /// This event will be raised on a different thread than the UI thread.
+ ///
+ public event DoWorkEventHandler DoWork;
+
+ ///
+ /// Event raised when the operation completes.
+ ///
+ public event RunWorkerCompletedEventHandler RunWorkerCompleted;
+
+ ///
+ /// Event raised when is called.
+ ///
+ public event ProgressChangedEventHandler ProgressChanged;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ProgressDialog ()
+ {
+ InitializeComponent();
+
+ ProgressBarStyle = ProgressBarStyle.ProgressBar;
+ ShowCancelButton = true;
+ MinimizeBox = true;
+ }
+
+ ///
+ /// Gets or sets the text in the progress dialog's title bar.
+ ///
+ ///
+ /// The text in the progress dialog's title bar. The default value is an empty string.
+ ///
+ ///
+ ///
+ /// This property must be set before or is called. Changing property has
+ /// no effect while the dialog is being displayed.
+ ///
+ ///
+ [Localizable(true), Category("Appearance"), Description("The text in the progress dialog's title bar."), DefaultValue("")]
+ public string WindowTitle
+ {
+ get { return _windowTitle ?? string.Empty; }
+ set { _windowTitle = value; }
+ }
+
+ ///
+ /// Gets or sets a short description of the operation being carried out.
+ ///
+ ///
+ /// A short description of the operation being carried. The default value is an empty string.
+ ///
+ ///
+ ///
+ /// This is the primary message to the user.
+ ///
+ ///
+ /// This property can be changed while the dialog is running, but may only be changed from the thread which
+ /// created the progress dialog. The recommended method to change this value while the dialog is running
+ /// is to use the method.
+ ///
+ ///
+ [Localizable(true), Category("Appearance"), Description("A short description of the operation being carried out.")]
+ public string Text
+ {
+ get { return _text ?? string.Empty; }
+ set
+ {
+ _text = value;
+ if (_dialog != null)
+ _dialog.SetLine (1, Text, UseCompactPathsForText, IntPtr.Zero);
+ }
+ }
+
+ ///
+ /// Gets or sets a value that indicates whether path strings in the property should be compacted if
+ /// they are too large to fit on one line.
+ ///
+ ///
+ /// to compact path strings if they are too large to fit on one line; otherwise,
+ /// . The default value is .
+ ///
+ ///
+ ///
+ /// This property requires Windows Vista or later. On older versions of Windows, it has no effect.
+ ///
+ ///
+ /// This property can be changed while the dialog is running, but may only be changed from the thread which
+ /// created the progress dialog.
+ ///
+ ///
+ [Category("Behavior"), Description("Indicates whether path strings in the Text property should be compacted if they are too large to fit on one line."), DefaultValue(false)]
+ public bool UseCompactPathsForText
+ {
+ get { return _useCompactPathsForText; }
+ set
+ {
+ _useCompactPathsForText = value;
+ if (_dialog != null)
+ _dialog.SetLine (1, Text, _useCompactPathsForText, IntPtr.Zero);
+ }
+ }
+
+ ///
+ /// Gets or sets additional details about the operation being carried out.
+ ///
+ ///
+ /// Additional details about the operation being carried out. The default value is an empty string.
+ ///
+ ///
+ /// This text is used to provide additional details beyond the property.
+ ///
+ ///
+ ///
+ /// This property can be changed while the dialog is running, but may only be changed from the thread which
+ /// created the progress dialog. The recommended method to change this value while the dialog is running
+ /// is to use the method.
+ ///
+ ///
+ [Localizable(true), Category("Appearance"), Description("Additional details about the operation being carried out."), DefaultValue("")]
+ public string Description
+ {
+ get { return _description ?? string.Empty; }
+ set
+ {
+ _description = value;
+ if (_dialog != null)
+ _dialog.SetLine (2, Description, UseCompactPathsForDescription, IntPtr.Zero);
+ }
+ }
+
+ ///
+ /// Gets or sets a value that indicates whether path strings in the property should be compacted if
+ /// they are too large to fit on one line.
+ ///
+ ///
+ /// to compact path strings if they are too large to fit on one line; otherwise,
+ /// . The default value is .
+ ///
+ ///
+ ///
+ /// This property requires Windows Vista or later. On older versions of Windows, it has no effect.
+ ///
+ ///
+ /// This property can be changed while the dialog is running, but may only be changed from the thread which
+ /// created the progress dialog.
+ ///
+ ///
+ [Category("Behavior"), Description("Indicates whether path strings in the Description property should be compacted if they are too large to fit on one line."), DefaultValue(false)]
+ public bool UseCompactPathsForDescription
+ {
+ get { return _useCompactPathsForDescription; }
+ set
+ {
+ _useCompactPathsForDescription = value;
+ if( _dialog != null )
+ _dialog.SetLine(2, Description, UseCompactPathsForDescription, IntPtr.Zero);
+ }
+ }
+
+ ///
+ /// Gets or sets the text that will be shown after the Cancel button is pressed.
+ ///
+ ///
+ /// The text that will be shown after the Cancel button is pressed.
+ ///
+ ///
+ ///
+ /// This property must be set before or is called. Changing property has
+ /// no effect while the dialog is being displayed.
+ ///
+ ///
+ [Localizable(true), Category("Appearance"), Description("The text that will be shown after the Cancel button is pressed."), DefaultValue("")]
+ public string CancellationText
+ {
+ get { return _cancellationText ?? string.Empty; }
+ set { _cancellationText = value; }
+ }
+
+ ///
+ /// Gets or sets a value that indicates whether an estimate of the remaining time will be shown.
+ ///
+ ///
+ /// if an estimate of remaining time will be shown; otherwise, . The
+ /// default value is .
+ ///
+ ///
+ ///
+ /// This property must be set before or is called. Changing property has
+ /// no effect while the dialog is being displayed.
+ ///
+ ///
+ [Category("Appearance"), Description("Indicates whether an estimate of the remaining time will be shown."), DefaultValue(false)]
+ public bool ShowTimeRemaining { get; set; }
+
+ ///
+ /// Gets or sets a value that indicates whether the dialog has a cancel button.
+ ///
+ ///
+ /// if the dialog has a cancel button; otherwise, . The default
+ /// value is .
+ ///
+ ///
+ ///
+ /// This property requires Windows Vista or later; on older versions of Windows, the cancel button will always
+ /// be displayed.
+ ///
+ ///
+ /// The event handler for the event must periodically check the value of the
+ /// property to see if the operation has been cancelled if this
+ /// property is .
+ ///
+ ///
+ /// Setting this property to is not recommended unless absolutely necessary.
+ ///
+ ///
+ [Category("Appearance"), Description("Indicates whether the dialog has a cancel button. Do not set to false unless absolutely necessary."), DefaultValue(true)]
+ public bool ShowCancelButton { get; set; }
+
+ ///
+ /// Gets or sets a value that indicates whether the progress dialog has a minimize button.
+ ///
+ ///
+ /// if the dialog has a minimize button; otherwise, . The default
+ /// value is .
+ ///
+ ///
+ ///
+ /// This property has no effect on modal dialogs (which do not have a minimize button). It only applies
+ /// to modeless dialogs shown by using the method.
+ ///
+ ///
+ /// This property must be set before is called. Changing property has
+ /// no effect while the dialog is being displayed.
+ ///
+ ///
+ [Category("Window Style"), Description("Indicates whether the progress dialog has a minimize button."), DefaultValue(true)]
+ public bool MinimizeBox { get; set; }
+
+ ///
+ /// Gets a value indicating whether the user has requested cancellation of the operation.
+ ///
+ ///
+ /// if the user has cancelled the progress dialog; otherwise, . The default is .
+ ///
+ ///
+ /// The event handler for the event must periodically check this property and abort the operation
+ /// if it returns .
+ ///
+ [Browsable(false)]
+ public bool CancellationPending
+ {
+ get
+ {
+ _backgroundWorker.ReportProgress (-1); // Call with an out-of-range percentage will update the value of
+ // _cancellationPending but do nothing else.
+ return _cancellationPending;
+ }
+ }
+
+ ///
+ /// Gets or sets a value that indicates whether a regular or marquee style progress bar should be used.
+ ///
+ ///
+ /// One of the values of .
+ /// The default value is .
+ ///
+ ///
+ ///
+ /// Operating systems older than Windows Vista do not support marquee progress bars on the progress dialog. On those operating systems, the
+ /// progress bar will be hidden completely if this property is .
+ ///
+ ///
+ /// When this property is set to , use the method to set
+ /// the value of the progress bar. When this property is set to
+ /// you can still use the method to update the text of the dialog,
+ /// but the percentage will be ignored.
+ ///
+ ///
+ /// This property must be set before or is called. Changing property has
+ /// no effect while the dialog is being displayed.
+ ///
+ ///
+ [Category("Appearance"), Description("Indicates the style of the progress bar."), DefaultValue(ProgressBarStyle.ProgressBar)]
+ public ProgressBarStyle ProgressBarStyle { get; set; }
+
+
+ ///
+ /// Gets a value that indicates whether the is running an asynchronous operation.
+ ///
+ ///
+ /// if the is running an asynchronous operation;
+ /// otherwise, .
+ ///
+ [Browsable(false)]
+ public bool IsBusy
+ {
+ get { return _backgroundWorker.IsBusy; }
+ }
+
+ ///
+ /// Displays the progress dialog as a modeless dialog.
+ ///
+ ///
+ ///
+ /// This function will not block the parent window and will return immediately.
+ ///
+ ///
+ /// Although this function returns immediately, you cannot use the UI thread to do any processing. The dialog
+ /// will not function correctly unless the UI thread continues to handle window messages, so that thread may
+ /// not be blocked by some other activity. All processing related to the progress dialog must be done in
+ /// the event handler.
+ ///
+ ///
+ public void Show ()
+ {
+ Show (null);
+ }
+
+ ///
+ /// Displays the progress dialog as a modeless dialog.
+ ///
+ /// A parameter for use by the background operation to be executed in the event handler.
+ ///
+ ///
+ /// This function will not block the parent window and return immediately.
+ ///
+ ///
+ /// Although this function returns immediately, you cannot use the UI thread to do any processing. The dialog
+ /// will not function correctly unless the UI thread continues to handle window messages, so that thread may
+ /// not be blocked by some other activity. All processing related to the progress dialog must be done in
+ /// the event handler.
+ ///
+ ///
+ public void Show (object argument)
+ {
+ RunProgressDialog (IntPtr.Zero, argument);
+ }
+
+ ///
+ /// Displays the progress dialog as a modal dialog.
+ ///
+ ///
+ ///
+ /// The ShowDialog function for most .Net dialogs will not return until the dialog is closed. However,
+ /// the function for the class will return immediately.
+ /// The parent window will be disabled as with all modal dialogs.
+ ///
+ ///
+ /// Although this function returns immediately, you cannot use the UI thread to do any processing. The dialog
+ /// will not function correctly unless the UI thread continues to handle window messages, so that thread may
+ /// not be blocked by some other activity. All processing related to the progress dialog must be done in
+ /// the event handler.
+ ///
+ ///
+ /// The progress dialog's window will appear in the taskbar. This behaviour is also contrary to most .Net dialogs,
+ /// but is part of the underlying native progress dialog API so cannot be avoided.
+ ///
+ ///
+ /// When possible, it is recommended that you use a modeless dialog using the function.
+ ///
+ ///
+ public void ShowDialog()
+ {
+ ShowDialog (null, null);
+ }
+
+ ///
+ /// Displays the progress dialog as a modal dialog.
+ ///
+ /// The window that owns the dialog.
+ ///
+ ///
+ /// The ShowDialog function for most .Net dialogs will not return until the dialog is closed. However,
+ /// the function for the class will return immediately.
+ /// The parent window will be disabled as with all modal dialogs.
+ ///
+ ///
+ /// Although this function returns immediately, you cannot use the UI thread to do any processing. The dialog
+ /// will not function correctly unless the UI thread continues to handle window messages, so that thread may
+ /// not be blocked by some other activity. All processing related to the progress dialog must be done in
+ /// the event handler.
+ ///
+ ///
+ /// The progress dialog's window will appear in the taskbar. This behaviour is also contrary to most .Net dialogs,
+ /// but is part of the underlying native progress dialog API so cannot be avoided.
+ ///
+ ///
+ /// When possible, it is recommended that you use a modeless dialog using the function.
+ ///
+ ///
+ public void ShowDialog (Window owner)
+ {
+ ShowDialog (owner, null);
+ }
+
+ ///
+ /// Displays the progress dialog as a modal dialog.
+ ///
+ /// The window that owns the dialog.
+ /// A parameter for use by the background operation to be executed in the event handler.
+ ///
+ ///
+ /// The ShowDialog function for most .Net dialogs will not return until the dialog is closed. However,
+ /// the function for the class will return immediately.
+ /// The parent window will be disabled as with all modal dialogs.
+ ///
+ ///
+ /// Although this function returns immediately, you cannot use the UI thread to do any processing. The dialog
+ /// will not function correctly unless the UI thread continues to handle window messages, so that thread may
+ /// not be blocked by some other activity. All processing related to the progress dialog must be done in
+ /// the event handler.
+ ///
+ ///
+ /// The progress dialog's window will appear in the taskbar. This behaviour is also contrary to most .Net dialogs,
+ /// but is part of the underlying native progress dialog API so cannot be avoided.
+ ///
+ ///
+ /// When possible, it is recommended that you use a modeless dialog using the function.
+ ///
+ ///
+ public void ShowDialog (Window owner, object argument)
+ {
+ RunProgressDialog (owner == null ? NativeMethods.GetActiveWindow() : new WindowInteropHelper(owner).Handle, argument);
+ }
+
+ const int SW_HIDE = 0;
+ const int SW_SHOW = 5;
+
+ public void Hide ()
+ {
+ if (null == _dialog)
+ return;
+ var hwnd = GetWindowHandle();
+ if (hwnd != IntPtr.Zero)
+ NativeMethods.ShowWindow (hwnd, SW_HIDE);
+ }
+
+ public void Restore ()
+ {
+ if (null == _dialog)
+ return;
+ var hwnd = GetWindowHandle();
+ if (hwnd != IntPtr.Zero)
+ NativeMethods.ShowWindow (hwnd, SW_SHOW);
+ }
+
+ ///
+ /// Get win32 handle of the progress dialog window.
+ ///
+ public IntPtr GetWindowHandle ()
+ {
+ var ole = _dialog as Interop.IOleWindow;
+ if (null == ole)
+ return IntPtr.Zero;
+ IntPtr hwnd;
+ ole.GetWindow (out hwnd);
+ return hwnd;
+ }
+
+ ///
+ /// Updates the dialog's progress bar.
+ ///
+ /// The percentage, from 0 to 100, of the operation that is complete.
+ ///
+ ///
+ /// Call this method from the event handler if you want to report progress.
+ ///
+ ///
+ /// This method has no effect is is
+ /// or .
+ ///
+ ///
+ /// is out of range.
+ /// The progress dialog is not currently being displayed.
+ public void ReportProgress (int percentProgress)
+ {
+ ReportProgress (percentProgress, null, null, null);
+ }
+
+ ///
+ /// Updates the dialog's progress bar.
+ ///
+ /// The percentage, from 0 to 100, of the operation that is complete.
+ /// The new value of the progress dialog's primary text message, or to leave the value unchanged.
+ /// The new value of the progress dialog's additional description message, or to leave the value unchanged.
+ /// Call this method from the event handler if you want to report progress.
+ /// is out of range.
+ /// The progress dialog is not currently being displayed.
+ public void ReportProgress (int percentProgress, string text, string description)
+ {
+ ReportProgress (percentProgress, text, description, null);
+ }
+
+ ///
+ /// Updates the dialog's progress bar.
+ ///
+ /// The percentage, from 0 to 100, of the operation that is complete.
+ /// The new value of the progress dialog's primary text message, or to leave the value unchanged.
+ /// The new value of the progress dialog's additional description message, or to leave the value unchanged.
+ /// A state object that will be passed to the event handler.
+ /// Call this method from the event handler if you want to report progress.
+ /// is out of range.
+ /// The progress dialog is not currently being displayed.
+ public void ReportProgress (int percentProgress, string text, string description, object userState)
+ {
+ if (percentProgress < 0 || percentProgress > 100)
+ throw new ArgumentOutOfRangeException ("percentProgress");
+ if (_dialog == null)
+ throw new InvalidOperationException ("The progress dialog is not shown.");
+ _backgroundWorker.ReportProgress (percentProgress, new ProgressChangedData { Text = text, Description = description, UserState = userState });
+ }
+
+ ///
+ /// Raises the event.
+ ///
+ /// The containing data for the event.
+ protected virtual void OnDoWork (DoWorkEventArgs e)
+ {
+ var handler = DoWork;
+ if (handler != null)
+ handler (this, e);
+ }
+
+ ///
+ /// Raises the event.
+ ///
+ /// The containing data for the event.
+ protected virtual void OnRunWorkerCompleted (RunWorkerCompletedEventArgs e)
+ {
+ var handler = RunWorkerCompleted;
+ if (handler != null)
+ handler (this, e);
+ }
+
+ ///
+ /// Raises the event.
+ ///
+ /// The containing data for the event.
+ protected virtual void OnProgressChanged (ProgressChangedEventArgs e)
+ {
+ var handler = ProgressChanged;
+ if (handler != null)
+ handler (this, e);
+ }
+
+ private void RunProgressDialog (IntPtr owner, object argument)
+ {
+ if (_backgroundWorker.IsBusy)
+ throw new InvalidOperationException ("The progress dialog is already running.");
+
+ _cancellationPending = false;
+ _dialog = new Interop.ProgressDialog();
+ _dialog.SetTitle (WindowTitle);
+
+ if (CancellationText.Length > 0)
+ _dialog.SetCancelMsg (CancellationText, null);
+ _dialog.SetLine (1, Text, UseCompactPathsForText, IntPtr.Zero);
+ _dialog.SetLine (2, Description, UseCompactPathsForDescription, IntPtr.Zero);
+
+ var flags = Interop.ProgressDialogFlags.Normal;
+ if (owner != IntPtr.Zero)
+ flags |= Interop.ProgressDialogFlags.Modal;
+ switch (ProgressBarStyle)
+ {
+ case ProgressBarStyle.None:
+ flags |= Interop.ProgressDialogFlags.NoProgressBar;
+ break;
+ case ProgressBarStyle.MarqueeProgressBar:
+ if (NativeMethods.IsWindowsVistaOrLater)
+ flags |= Interop.ProgressDialogFlags.MarqueeProgress;
+ else
+ flags |= Interop.ProgressDialogFlags.NoProgressBar; // Older than Vista doesn't support marquee.
+ break;
+ }
+ if( ShowTimeRemaining )
+ flags |= Interop.ProgressDialogFlags.AutoTime;
+ if( !ShowCancelButton )
+ flags |= Interop.ProgressDialogFlags.NoCancel;
+ if( !MinimizeBox )
+ flags |= Interop.ProgressDialogFlags.NoMinimize;
+
+ _dialog.StartProgressDialog (owner, null, flags, IntPtr.Zero);
+ _backgroundWorker.RunWorkerAsync (argument);
+ }
+
+ private void InitializeComponent ()
+ {
+ _backgroundWorker = new BackgroundWorker();
+ _backgroundWorker.WorkerReportsProgress = true;
+ _backgroundWorker.WorkerSupportsCancellation = true;
+ _backgroundWorker.DoWork += new DoWorkEventHandler (_backgroundWorker_DoWork);
+ _backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler (_backgroundWorker_RunWorkerCompleted);
+ _backgroundWorker.ProgressChanged += new ProgressChangedEventHandler (_backgroundWorker_ProgressChanged);
+
+ }
+
+ private void _backgroundWorker_DoWork (object sender, DoWorkEventArgs e)
+ {
+ OnDoWork (e);
+ }
+
+ private void _backgroundWorker_RunWorkerCompleted (object sender, RunWorkerCompletedEventArgs e)
+ {
+ _dialog.StopProgressDialog();
+ Marshal.ReleaseComObject (_dialog);
+ _dialog = null;
+
+ OnRunWorkerCompleted (new RunWorkerCompletedEventArgs((!e.Cancelled && e.Error == null) ? e.Result : null, e.Error, e.Cancelled));
+ }
+
+ private void _backgroundWorker_ProgressChanged (object sender, ProgressChangedEventArgs e)
+ {
+ _cancellationPending = _dialog.HasUserCancelled();
+ // ReportProgress doesn't allow values outside this range. However, CancellationPending will call
+ // BackgroundWorker.ReportProgress directly with a value that is outside this range to update the value of the property.
+ if (e.ProgressPercentage >= 0 && e.ProgressPercentage <= 100)
+ {
+ _dialog.SetProgress ((uint)e.ProgressPercentage, 100);
+ var data = e.UserState as ProgressChangedData;
+ if (data != null)
+ {
+ if (data.Text != null)
+ Text = data.Text;
+ if (data.Description != null)
+ Description = data.Description;
+ OnProgressChanged (new ProgressChangedEventArgs (e.ProgressPercentage, data.UserState));
+ }
+ }
+ }
+ }
+
+ ///
+ /// Indicates the type of progress on a task dialog.
+ ///
+ public enum ProgressBarStyle
+ {
+ ///
+ /// No progress bar is displayed on the dialog.
+ ///
+ None,
+ ///
+ /// A regular progress bar is displayed on the dialog.
+ ///
+ ProgressBar,
+ ///
+ /// A marquee progress bar is displayed on the dialog. Use this value for operations
+ /// that cannot report concrete progress information.
+ ///
+ MarqueeProgressBar
+ }
+}
diff --git a/GUI/Strings/guiStrings.Designer.cs b/GUI/Strings/guiStrings.Designer.cs
index 9bcca2f4..6610230a 100644
--- a/GUI/Strings/guiStrings.Designer.cs
+++ b/GUI/Strings/guiStrings.Designer.cs
@@ -60,6 +60,15 @@ namespace GARbro.GUI.Strings {
}
}
+ ///
+ /// Looks up a localized string similar to _Abort.
+ ///
+ public static string ButtonAbort {
+ get {
+ return ResourceManager.GetString("ButtonAbort", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Cancel.
///
@@ -69,6 +78,15 @@ namespace GARbro.GUI.Strings {
}
}
+ ///
+ /// Looks up a localized string similar to _Continue.
+ ///
+ public static string ButtonContinue {
+ get {
+ return ResourceManager.GetString("ButtonContinue", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Convert.
///
@@ -96,6 +114,33 @@ namespace GARbro.GUI.Strings {
}
}
+ ///
+ /// Looks up a localized string similar to _Overwrite.
+ ///
+ public static string ButtonOverwrite {
+ get {
+ return ResourceManager.GetString("ButtonOverwrite", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to _Rename.
+ ///
+ public static string ButtonRename {
+ get {
+ return ResourceManager.GetString("ButtonRename", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to _Skip.
+ ///
+ public static string ButtonSkip {
+ get {
+ return ResourceManager.GetString("ButtonSkip", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to _Close.
///
@@ -294,6 +339,15 @@ namespace GARbro.GUI.Strings {
}
}
+ ///
+ /// Looks up a localized string similar to A_pply to all duplicate files.
+ ///
+ public static string LabelApplyToAll {
+ get {
+ return ResourceManager.GetString("LabelApplyToAll", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Archive format.
///
@@ -348,6 +402,15 @@ namespace GARbro.GUI.Strings {
}
}
+ ///
+ /// Looks up a localized string similar to What should be done?.
+ ///
+ public static string LabelDuplicateFileQuestion {
+ get {
+ return ResourceManager.GetString("LabelDuplicateFileQuestion", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Encoding.
///
@@ -384,6 +447,15 @@ namespace GARbro.GUI.Strings {
}
}
+ ///
+ /// Looks up a localized string similar to _Ignore further errors.
+ ///
+ public static string LabelIgnoreErrors {
+ get {
+ return ResourceManager.GetString("LabelIgnoreErrors", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Skip incovertible files..
///
@@ -936,6 +1008,17 @@ namespace GARbro.GUI.Strings {
}
}
+ ///
+ /// Looks up a localized string similar to Error occured while converting file
+ ///{0}
+ ///{1}.
+ ///
+ public static string TextErrorConverting {
+ get {
+ return ResourceManager.GetString("TextErrorConverting", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Error occured while extracting file
///{0}
@@ -965,6 +1048,15 @@ namespace GARbro.GUI.Strings {
}
}
+ ///
+ /// Looks up a localized string similar to File extraction error.
+ ///
+ public static string TextExtractionError {
+ get {
+ return ResourceManager.GetString("TextExtractionError", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Extract text.
///
@@ -983,6 +1075,15 @@ namespace GARbro.GUI.Strings {
}
}
+ ///
+ /// Looks up a localized string similar to File {0} already exists in the destination folder..
+ ///
+ public static string TextFileAlreadyExists {
+ get {
+ return ResourceManager.GetString("TextFileAlreadyExists", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Multimedia conversion error.
///
diff --git a/GUI/Strings/guiStrings.ko-KR.resx b/GUI/Strings/guiStrings.ko-KR.resx
index f4b22c62..f66c85d4 100644
--- a/GUI/Strings/guiStrings.ko-KR.resx
+++ b/GUI/Strings/guiStrings.ko-KR.resx
@@ -458,4 +458,46 @@
마스크로 파일 선택...
-
+
+ 중단
+
+
+ 계속
+
+
+ 추가 오류 무시
+
+
+ 파일 추출 오류
+
+
+ Overwrite
+ translation pending
+
+
+ Rename
+ translation pending
+
+
+ Skip
+ translation pending
+
+
+ Apply to all duplicate files
+ translation pending
+
+
+ What should be done?
+ translation pending
+
+
+ Error occured while converting file
+{0}
+{1}
+ translation pending
+
+
+ File {0} already exists in the destination folder.
+ translation pending
+
+
\ No newline at end of file
diff --git a/GUI/Strings/guiStrings.resx b/GUI/Strings/guiStrings.resx
index d2ad5db1..8c89f810 100644
--- a/GUI/Strings/guiStrings.resx
+++ b/GUI/Strings/guiStrings.resx
@@ -462,4 +462,39 @@ Overwrite?
Select files by mask...
+
+ _Abort
+
+
+ _Continue
+
+
+ _Ignore further errors
+
+
+ File extraction error
+
+
+ _Overwrite
+
+
+ _Rename
+
+
+ _Skip
+
+
+ A_pply to all duplicate files
+
+
+ What should be done?
+
+
+ Error occured while converting file
+{0}
+{1}
+
+
+ File {0} already exists in the destination folder.
+
\ No newline at end of file
diff --git a/GUI/Strings/guiStrings.ru-RU.resx b/GUI/Strings/guiStrings.ru-RU.resx
index 8650f6c1..cb36b5fe 100644
--- a/GUI/Strings/guiStrings.ru-RU.resx
+++ b/GUI/Strings/guiStrings.ru-RU.resx
@@ -460,7 +460,7 @@
Выбрать файлы
- Произошёл сбой во время извлечения файла
+ Не удалось извлечь файл
{0}
{1}
@@ -483,4 +483,39 @@
Выбрать файлы по маске...
+
+ Прервать
+
+
+ Продолжить
+
+
+ Игнорировать дальнейшие ошибки
+
+
+ Ошибка извлечения файла
+
+
+ Заменить
+
+
+ Переименовать
+
+
+ Пропустить
+
+
+ Применить ко всем совпадающим файлам
+
+
+ Что делать?
+
+
+ Не удадось конвертировать файл
+{0}
+{1}
+
+
+ Файл с именем {0} уже существует.
+
\ No newline at end of file
diff --git a/GUI/Strings/guiStrings.zh-Hans.resx b/GUI/Strings/guiStrings.zh-Hans.resx
index f175744f..a602e8fc 100644
--- a/GUI/Strings/guiStrings.zh-Hans.resx
+++ b/GUI/Strings/guiStrings.zh-Hans.resx
@@ -460,4 +460,49 @@
Select files by mask...
translation pending
+
+ Abort
+ translation pending
+
+
+ Continue
+ translation pending
+
+
+ Ignore further errors
+ translation pending
+
+
+ File extraction error
+ translation pending
+
+
+ Overwrite
+ translation pending
+
+
+ Rename
+ translation pending
+
+
+ Skip
+ translation pending
+
+
+ Apply to all duplicate files
+ translation pending
+
+
+ What should be done?
+ translation pending
+
+
+ Error occured while converting file
+{0}
+{1}
+ translation pending
+
+
+ 文件{0}已经存在。
+
\ No newline at end of file
diff --git a/GUI/Utility.cs b/GUI/Utility.cs
index f289b480..d0ca2a61 100644
--- a/GUI/Utility.cs
+++ b/GUI/Utility.cs
@@ -35,6 +35,14 @@ namespace GARbro.GUI
{
internal class NativeMethods
{
+ public static bool IsWindowsVistaOrLater
+ {
+ get
+ {
+ return Environment.OSVersion.Platform == PlatformID.Win32NT && Environment.OSVersion.Version >= new Version (6, 0, 6000);
+ }
+ }
+
[DllImport ("shlwapi.dll", CharSet = CharSet.Unicode)]
internal static extern int StrCmpLogicalW (string psz1, string psz2);
@@ -46,6 +54,15 @@ namespace GARbro.GUI
[DllImport ("user32.dll")]
internal static extern int ReleaseDC (IntPtr hWnd, IntPtr hDc);
+
+ [DllImport ("user32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
+ internal static extern IntPtr GetActiveWindow();
+
+ [DllImport ("user32.dll")][return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool ShowWindow (IntPtr hWnd, int nCmdShow);
+
+ [DllImport ("user32.dll")][return: MarshalAs(UnmanagedType.Bool)]
+ internal static extern bool EnableWindow (IntPtr hWnd, bool bEnable);
}
public static class Desktop