diff --git a/docs/fileformats.md b/docs/fileformats.md
index 7660f84..90e09a7 100644
--- a/docs/fileformats.md
+++ b/docs/fileformats.md
@@ -2,13 +2,55 @@
Vocup uses different proprietary formats:
-| Extension | Name | Usage |
-|-----------|---------------------------------|------------------|
-| .vhf | **V**okabel**h**eft **f**ile | Vocabulary data |
-| .vhr | **V**okabel**h**eft **r**esults | Practice results |
-| .vdp | | Backup file |
+| Extension | Name | Usage | Remarks |
+|-----------|---------------------------------|------------------|---------|
+| .vhf | **V**okabel**h**eft **f**ile | Vocabulary data | |
+| .vhr | **V**okabel**h**eft **r**esults | Practice results | Not used for vhf2 files anymore |
+| .vdp | | Backup file | Backup creation removed in Vocup 1.8.0
Backup restore removed in Vocup 1.9.0 |
-## .vhf
+## .vhf v2
+
+Since Vocup v2, _Vokabelheft files_ are ZIP archives and not encrypted anymore.
+Vocup uses the ZIP header to determine whether a .vhf file is vhf1 or vhf2.
+
+The header file makes it easier to identify a ZIP archive as a Vocup file.
+Future file formats may maintain compatibility with Vocup v2 by keeping a `book.2.json` file but also adding a `book.3.json` with breaking changes.
+
+### Example
+
+`VOCUP VOCABULARY BOOK`
+```json
+{
+ "fileVersion": "2.0",
+}
+```
+
+`book.2.json`
+```json
+{
+ "motherTongue": "Deutsch",
+ "foreignLanguage": "Englisch",
+ "practiceMode": "AskForForeignLanguage",
+ "words": [
+ {
+ "motherTongue": "Diskriminierung",
+ "foreignLang": "discrimination",
+ "foreignLangSynonym": null,
+ "practiceStateNumber": 0,
+ "practiceDate": ""
+ },
+ {
+ "motherTongue": "eingehend untersuchen (AE/BE)",
+ "foreignLang": "to scrutinize",
+ "foreignLangSynonym": "to scrutinise",
+ "practiceStateNumber": 1,
+ "practiceDate": "2018-04-06T23:04:21"
+ }
+ ]
+}
+```
+
+## .vhf v1
*Vokabelheft files* contain the base64 encoded ciphertext of the DES encrypted inner file.
The encryption is done with a hard-coded key and offers no security but complicates reverse engineering of the file format.
@@ -35,14 +77,14 @@ Diskriminierung#discrimination#
eingehend untersuchen (AE/BE)#to scrutinize#to scrutinise
Entfremdung, Distanzierung#alienation#
entstellen#to warp#
-enttuscht#disappointed#crestfallen
-entwhnen#to wean off#
+enttäuscht#disappointed#crestfallen
+entwähnen#to wean off#
Freiheit#freedom#liberty
-Gefngnisstrafe#imprisonment#jail sentence
-gehuft, gestapelt#piled#
+Gefängnisstrafe#imprisonment#jail sentence
+gehäuft, gestapelt#piled#
Gewaltenteilung#system of checks and balances#
gipfeln (in)#to culminate (in)#
-Grndungsvter#Founding Fathers#
+Gründungsväter#Founding Fathers#
halbieren#to halve#
Haltung, Einstellung#attitude#
Handgelenk#wrist#
@@ -52,6 +94,8 @@ Haufen, Stapel#pile#
## .vhr
+> ⚠️ This file format is deprecated. Since vhf2 results are saved in the vocabulary book.
+
Like *Vokabelheft files* *Vokabelheft result* files are DES encrypted and saved as a base64 string.
The inner file contains 2 lines of metadata.
@@ -91,7 +135,9 @@ D:\Schule\Englisch\Vocabulary\Year 11.vhf
## .vdp
-This format is used for Vocup Backups. It is basically a zip file using `Deflate` compression:
+> ⚠️ This file format is deprecated. Since Vocup 1.8.0 it is not possible to create new backups anymore. Since Vocup 1.9.0 is also not possible to restore backups anymore.
+
+This format was used for Vocup Backups. It is basically a zip file using `Deflate` compression:
```
*.vdp/
chars/
@@ -131,4 +177,4 @@ Like the other logfiles it included one line per item:
#### vhr
The `vhr` folder works pretty much like the `chars` folder.
All `.vhr` files are stored in this folder with their original name.
-In the `vhr.log` file all these files are simply listed.
\ No newline at end of file
+In the `vhr.log` file all these files are simply listed.
diff --git a/src/Vocup/Forms/MergeFiles.cs b/src/Vocup/Forms/MergeFiles.cs
index 7d21bf1..429c4c1 100644
--- a/src/Vocup/Forms/MergeFiles.cs
+++ b/src/Vocup/Forms/MergeFiles.cs
@@ -37,7 +37,7 @@ private void BtnAdd_Click(object sender, EventArgs e)
{
Title = Words.AddVocabularyBooks,
InitialDirectory = Program.Settings.VhfPath,
- Filter = Words.VocupVocabularyBookFile + " (*.vhf)|*.vhf",
+ Filter = Words.FileFormatVhf + " (*.vhf)|*.vhf",
Multiselect = true
};
@@ -45,10 +45,10 @@ private void BtnAdd_Click(object sender, EventArgs e)
{
foreach (string file in addFile.FileNames)
{
- VocabularyBook book = new VocabularyBook();
- if (!VocabularyFile.ReadVhfFile(file, book))
+ VocabularyBook book = new();
+ if (!BookFileFormat.TryDetectAndRead(file, book, Program.Settings.VhrPath))
continue;
- VocabularyFile.ReadVhrFile(book);
+
VocabularyBook conflict = books.Where(x => x.FilePath.Equals(book.FilePath, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
if (conflict != null)
{
@@ -124,6 +124,7 @@ private void TextBox_Enter(object sender, EventArgs e)
private void BtnSave_Click(object sender, EventArgs e)
{
+ BookFileFormat format;
string path;
using (SaveFileDialog save = new SaveFileDialog
@@ -131,11 +132,12 @@ private void BtnSave_Click(object sender, EventArgs e)
Title = Words.SaveVocabularyBook,
FileName = TbMotherTongue.Text + " - " + TbForeignLang.Text,
InitialDirectory = Program.Settings.VhfPath,
- Filter = Words.VocupVocabularyBookFile + " (*.vhf)|*.vhf"
+ Filter = $"{Words.FileFormatVhf2} (*.vhf)|*.vhf|{Words.FileFormatVhf1} (*.vhf)|*.vhf"
})
{
if (save.ShowDialog() == DialogResult.OK)
{
+ format = save.FilterIndex == 2 ? BookFileFormat.Vhf1 : BookFileFormat.Vhf2;
path = save.FileName;
}
else
@@ -164,10 +166,8 @@ private void BtnSave_Click(object sender, EventArgs e)
result.GenerateVhrCode();
- if (!VocabularyFile.WriteVhfFile(path, result) ||
- !VocabularyFile.WriteVhrFile(result))
+ if (!format.TryWrite(path, result, Program.Settings.VhrPath))
{
- MessageBox.Show(Messages.VocupFileWriteError, Messages.VocupFileWriteErrorT, MessageBoxButtons.OK, MessageBoxIcon.Error);
DialogResult = DialogResult.Abort;
}
else
diff --git a/src/Vocup/IO/BookFileFormat.cs b/src/Vocup/IO/BookFileFormat.cs
new file mode 100644
index 0000000..f9e521f
--- /dev/null
+++ b/src/Vocup/IO/BookFileFormat.cs
@@ -0,0 +1,115 @@
+using System;
+using System.IO;
+using System.Windows;
+using Vocup.Models;
+using Vocup.Properties;
+
+namespace Vocup.IO;
+
+public abstract class BookFileFormat
+{
+ public static Vhf1Format Vhf1 { get; } = Vhf1Format.Instance;
+ public static Vhf2Format Vhf2 { get; } = Vhf2Format.Instance;
+
+ public static bool TryDetectAndRead(string path, VocabularyBook book, string vhrPath)
+ {
+ try
+ {
+ if (!DetectAndRead(path, book, vhrPath))
+ MessageBox.Show(Messages.VhfCompatMode, Messages.VhfCompatModeT, MessageBoxButton.OK, MessageBoxImage.Information);
+ return true;
+ }
+ catch (VhfFormatException ex)
+ {
+ (string message, string title) = ex.ErrorCode switch
+ {
+ VhfError.InvalidVersion => (Messages.VhfInvalidVersion, Messages.VhfCorruptFileT),
+ VhfError.InvalidVhrCode => (Messages.VhfInvalidVhrCode, Messages.VhfCorruptFileT),
+ VhfError.InvalidLanguages => (Messages.VhfInvalidLanguages, Messages.VhfCorruptFileT),
+ VhfError.InvalidRow => (Messages.VhfInvalidRow, Messages.VhfCorruptFileT),
+ VhfError.UpdateRequired => (Messages.VhfMustUpdate, Messages.VhfMustUpdateT),
+ _ => (Messages.VhfCorruptFile, Messages.VhfCorruptFileT),
+ };
+ MessageBox.Show(message, title, MessageBoxButton.OK, MessageBoxImage.Error);
+ return false;
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(string.Format(Messages.VocupFileReadError, ex.Message), Messages.VocupFileReadErrorT, MessageBoxButton.OK, MessageBoxImage.Error);
+ return false;
+ }
+ }
+
+ public static bool DetectAndRead(string path, VocabularyBook book, string vhrPath)
+ {
+ //await default(HopToThreadPoolAwaitable); // Perform IO operations on a separate thread
+
+ using FileStream stream = new(path, FileMode.Open, FileAccess.Read, FileShare.Read);
+
+ if (StartsWithZipHeader(stream))
+ {
+ return Vhf2Format.Instance.Read(stream, book);
+ }
+ else
+ {
+ Vhf1Format.Instance.Read(stream, book, vhrPath);
+ return true;
+ }
+ }
+
+ private static bool StartsWithZipHeader(Stream stream)
+ {
+ if (!stream.CanRead || !stream.CanSeek)
+ throw new ArgumentException("Stream must be readable and seekable.", nameof(stream));
+
+ Span buffer = stackalloc byte[4];
+ bool zipHeader = stream.Read(buffer) == 4
+ && buffer[0] == 0x50
+ && buffer[1] == 0x4B
+ && buffer[2] == 0x03
+ && buffer[3] == 0x04;
+
+ stream.Seek(0, SeekOrigin.Begin);
+ return zipHeader;
+ }
+
+ public bool TryWrite(string path, VocabularyBook book, string vhrPath, bool includeResults = true)
+ {
+ try
+ {
+ //await default(HopToThreadPoolAwaitable); // Perform IO operations on a separate thread
+
+ using FileStream stream = new(path, FileMode.Create, FileAccess.Write, FileShare.None);
+ Write(stream, book, vhrPath, includeResults);
+ return true;
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(string.Format(Messages.VocupFileWriteError, ex.Message), Messages.VocupFileWriteErrorT, MessageBoxButton.OK, MessageBoxImage.Error);
+ return false;
+ }
+ }
+
+ public abstract void Write(FileStream stream, VocabularyBook book, string vhrPath, bool includeResults);
+
+ protected static bool TryDeleteVhrFile(string? vhrCode, string vhrPath)
+ {
+ try
+ {
+ if (vhrCode != null)
+ {
+ string path = Path.Combine(vhrPath, vhrCode + ".vhr");
+
+ if (File.Exists(path))
+ {
+ File.Delete(path);
+ return true;
+ }
+ }
+ }
+ // Cleaning up practice results is just nice to have.
+ catch { }
+
+ return false;
+ }
+}
diff --git a/src/Vocup/IO/CsvFile.cs b/src/Vocup/IO/CsvFile.cs
index 1837df8..c9ade78 100644
--- a/src/Vocup/IO/CsvFile.cs
+++ b/src/Vocup/IO/CsvFile.cs
@@ -10,11 +10,11 @@
using Vocup.Models;
using Vocup.Properties;
-namespace Vocup.IO.Internal;
+namespace Vocup.IO;
-internal class CsvFile
+public static class CsvFile
{
- public bool Import(string path, VocabularyBook book, bool importSettings)
+ public static bool Import(string path, VocabularyBook book, bool importSettings)
{
try
{
@@ -105,7 +105,7 @@ public bool Import(string path, VocabularyBook book, bool importSettings)
return false;
}
- public bool Export(string path, VocabularyBook book)
+ public static bool Export(string path, VocabularyBook book)
{
try
{
diff --git a/src/Vocup/IO/Vhf1Format.cs b/src/Vocup/IO/Vhf1Format.cs
new file mode 100644
index 0000000..6af8293
--- /dev/null
+++ b/src/Vocup/IO/Vhf1Format.cs
@@ -0,0 +1,266 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Security.Cryptography;
+using System.Text;
+using Vocup.Models;
+
+namespace Vocup.IO;
+
+public class Vhf1Format : BookFileFormat
+{
+ public static Vhf1Format Instance { get; } = new();
+
+ private Vhf1Format() { }
+
+ public void Read(FileStream stream, VocabularyBook book, string vhrPath)
+ {
+ string decrypted = ReadAndDecrypt(stream);
+ using StringReader reader = new(decrypted);
+
+ string? version = reader.ReadLine();
+ string? vhrCode = reader.ReadLine();
+ string? motherTongue = reader.ReadLine();
+ string? foreignLanguage = reader.ReadLine();
+
+ if (string.IsNullOrWhiteSpace(version) || version != "1.0")
+ {
+ throw new VhfFormatException(VhfError.InvalidVersion);
+ }
+
+ if (vhrCode == null)
+ {
+ throw new VhfFormatException(VhfError.InvalidVhrCode);
+ }
+
+ if (string.IsNullOrWhiteSpace(motherTongue) ||
+ string.IsNullOrWhiteSpace(foreignLanguage) ||
+ motherTongue == foreignLanguage)
+ {
+ throw new VhfFormatException(VhfError.InvalidLanguages);
+ }
+
+ book.MotherTongue = motherTongue;
+ book.ForeignLang = foreignLanguage;
+
+ while (true)
+ {
+ string? line = reader.ReadLine();
+ if (line == null) break;
+ string[] columns = line.Split('#');
+ if (columns.Length != 3)
+ {
+ throw new VhfFormatException(VhfError.InvalidRow);
+ }
+ book.Words.Add(new(columns[0], columns[1])
+ {
+ ForeignLangSynonym = columns[2],
+ Owner = book
+ });
+ }
+
+ book.FilePath = stream.Name;
+
+ if (!string.IsNullOrEmpty(vhrCode))
+ {
+ // Read results from .vhr file
+ ReadResults(book, stream.Name, vhrCode, vhrPath);
+ }
+ }
+
+ public override void Write(FileStream stream, VocabularyBook book, string vhrPath, bool includeResults)
+ {
+ StringBuilder content = new();
+ content.AppendLine("1.0");
+ content.AppendLine(book.VhrCode);
+ content.AppendLine(book.MotherTongue);
+ content.AppendLine(book.ForeignLang);
+
+ foreach (VocabularyWord word in book.Words)
+ {
+ content.Append(word.MotherTongue);
+ content.Append('#');
+ content.Append(word.ForeignLang);
+ content.Append('#');
+ content.AppendLine(word.ForeignLangSynonym);
+ }
+
+ EncryptAndWrite(stream, content.ToString());
+
+ if (includeResults && !string.IsNullOrEmpty(book.VhrCode))
+ {
+ // Write results to .vhr file
+ WriteResults(book, stream.Name, book.VhrCode, vhrPath);
+ }
+
+ book.FilePath = stream.Name;
+ }
+
+ public string GenerateVhrCode()
+ {
+ const string chars = "0123456789abcdefghijklmnopqrstuvwxyz";
+
+ Span code = stackalloc char[24];
+ Random random = Random.Shared;
+
+ for (int i = 0; i < code.Length; i++)
+ {
+ int charIdx = random.Next(chars.Length);
+ {
+ code[i] = chars[charIdx];
+ }
+ }
+
+ return new string(code);
+ }
+
+ private void ReadResults(VocabularyBook book, string fileName, string vhrCode, string vhrPath)
+ {
+ try
+ {
+ using FileStream file = new(Path.Combine(vhrPath, vhrCode + ".vhr"), FileMode.Open, FileAccess.Read, FileShare.Read);
+ string decrypted = ReadAndDecrypt(file);
+ using StringReader reader = new(decrypted);
+
+ string? path = reader.ReadLine();
+ string? mode = reader.ReadLine();
+
+ if (string.IsNullOrWhiteSpace(path) ||
+ string.IsNullOrWhiteSpace(mode) || !int.TryParse(mode, out int imode) || !((PracticeMode)imode).IsValid())
+ {
+ // Delete .vhr files with corrupt header
+ TryDeleteVhrFile(vhrCode, vhrPath);
+ return;
+ }
+
+ var results = new List<(int stateNumber, DateTime date)>();
+
+ while (true)
+ {
+ string? line = reader.ReadLine();
+ if (line == null) break;
+ string[] columns = line.Split('#');
+ if (columns.Length != 2 || !int.TryParse(columns[0], out int state) || state < 0)
+ {
+ // Delete .vhr files with corrupt entries
+ TryDeleteVhrFile(vhrCode, vhrPath);
+ return;
+ }
+ DateTime time = default;
+ if (!string.IsNullOrWhiteSpace(columns[1])
+ && !DateTime.TryParseExact(columns[1], "dd.MM.yyyy HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.None, out time))
+ {
+ // Delete .vhr files with corrupt entries
+ TryDeleteVhrFile(vhrCode, vhrPath);
+ return;
+ }
+ results.Add((state, time));
+ }
+
+ if (book.Words.Count != results.Count)
+ {
+ // Delete .vhr files that are not in sync anymore.
+ // This can only happen when using a cloud storage for .vhf files but not .vhr files.
+ // When using save as or by copying a file manually, a new .vhr file will be created.
+ TryDeleteVhrFile(vhrCode, vhrPath);
+ return;
+ }
+
+ FileInfo vhfInfo = new(fileName);
+ FileInfo pathInfo = new(path);
+
+ if (!vhfInfo.FullName.Equals(pathInfo.FullName, StringComparison.OrdinalIgnoreCase) && pathInfo.Exists)
+ {
+ vhrCode = GenerateVhrCode(); // Save new results file if the old one is in use by another file
+ }
+
+ book.PracticeMode = (PracticeMode)imode;
+
+ for (int i = 0; i < book.Words.Count; i++)
+ {
+ VocabularyWord word = book.Words[i];
+ (word.PracticeStateNumber, word.PracticeDate) = results[i];
+ }
+
+ book.VhrCode = vhrCode;
+ }
+ catch (Exception ex) when (ex is IOException || ex is VhfFormatException)
+ {
+ // Delete corrupt .vhr files
+ TryDeleteVhrFile(vhrCode, vhrPath);
+ }
+ }
+
+ private void WriteResults(VocabularyBook book, string fileName, string vhrCode, string vhrPath)
+ {
+ string results;
+
+ using (StringWriter writer = new())
+ {
+ PracticeMode mode = book.PracticeMode switch
+ {
+ PracticeMode.AskForBothMixed => PracticeMode.AskForForeignLang,
+ _ => book.PracticeMode
+ };
+
+ writer.WriteLine(fileName);
+ writer.Write((int)mode);
+
+ foreach (VocabularyWord word in book.Words)
+ {
+ writer.WriteLine();
+
+ writer.Write(word.PracticeStateNumber);
+ writer.Write('#');
+ if (word.PracticeDate != default)
+ writer.Write(word.PracticeDate.ToString("dd.MM.yyyy HH:mm"));
+ }
+
+ results = writer.ToString();
+ }
+
+ string absoluteFileName = Path.Combine(vhrPath, vhrCode + ".vhr");
+ using FileStream file = new(absoluteFileName, FileMode.Create, FileAccess.Write, FileShare.None);
+ EncryptAndWrite(file, results);
+ }
+
+ private static string ReadAndDecrypt(Stream stream)
+ {
+ try
+ {
+ byte[] ciphertext;
+ using (StreamReader reader = new(stream, Encoding.UTF8))
+ ciphertext = Convert.FromBase64String(reader.ReadToEnd());
+
+ using MemoryStream plainstream = new();
+ using DES csp = DES.Create();
+ using ICryptoTransform transform = csp.CreateDecryptor(
+ [1, 8, 3, 4, 3, 2, 7, 1],
+ [3, 1, 3, 4, 6, 3, 4, 8]);
+ using CryptoStream cipherstream = new(plainstream, transform, CryptoStreamMode.Write);
+ cipherstream.Write(ciphertext, 0, ciphertext.Length);
+ cipherstream.FlushFinalBlock();
+ return Encoding.UTF8.GetString(plainstream.ToArray());
+ }
+ catch (Exception ex) when (ex is FormatException || ex is CryptographicException)
+ {
+ throw new VhfFormatException(VhfError.InvalidCiphertext, ex);
+ }
+ }
+
+ private static void EncryptAndWrite(Stream stream, string content)
+ {
+ byte[] buffer = Encoding.UTF8.GetBytes(content);
+ using StreamWriter writer = new(stream, Encoding.UTF8);
+ using MemoryStream cipherstream = new();
+ using DES csp = DES.Create();
+ using ICryptoTransform transform = csp.CreateEncryptor(
+ [1, 8, 3, 4, 3, 2, 7, 1],
+ [3, 1, 3, 4, 6, 3, 4, 8]);
+ using CryptoStream plainstream = new(cipherstream, transform, CryptoStreamMode.Write);
+ plainstream.Write(buffer, 0, buffer.Length);
+ plainstream.FlushFinalBlock();
+ writer.Write(Convert.ToBase64String(cipherstream.ToArray()));
+ }
+}
diff --git a/src/Vocup/IO/Vhf2Format.cs b/src/Vocup/IO/Vhf2Format.cs
new file mode 100644
index 0000000..667d1c3
--- /dev/null
+++ b/src/Vocup/IO/Vhf2Format.cs
@@ -0,0 +1,153 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Vocup.Models;
+
+namespace Vocup.IO;
+
+public class Vhf2Format : BookFileFormat
+{
+ private const string headerFileName = "VOCUP VOCABULARY BOOK";
+ private const string bookFileName = "book.2.json";
+ private readonly JsonSerializerOptions options;
+ private readonly Version maxSupportedFileVersion;
+
+ public static Vhf2Format Instance { get; } = new();
+
+ private Vhf2Format()
+ {
+ options = new JsonSerializerOptions
+ {
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ WriteIndented = true
+ };
+ options.Converters.Add(new PracticeModeConverter());
+ options.Converters.Add(new PracticeResultConverter());
+
+ maxSupportedFileVersion = new Version(2, 0);
+ }
+
+ public bool Read(FileStream stream, VocabularyBook book)
+ {
+ try
+ {
+ using ZipArchive archive = new(stream, ZipArchiveMode.Read, leaveOpen: true);
+
+ ZipArchiveEntry headerFile = archive.GetEntry(headerFileName)
+ ?? throw new VhfFormatException(VhfError.InvalidHeader);
+
+ Metadata metadata;
+
+ using (Stream headerStream = headerFile.Open())
+ {
+ metadata = JsonSerializer.Deserialize(headerStream, options)
+ ?? throw new VhfFormatException(VhfError.InvalidHeader);
+ }
+
+ ZipArchiveEntry? bookFile = archive.GetEntry(bookFileName)
+ ?? throw new VhfFormatException(VhfError.UpdateRequired);
+
+ JsonBook jsonBook;
+
+ using (Stream bookStream = bookFile.Open())
+ {
+ jsonBook = JsonSerializer.Deserialize(bookStream, options)
+ ?? throw new VhfFormatException(VhfError.InvalidJsonBook);
+ }
+
+ book.MotherTongue = jsonBook.MotherTongue;
+ book.ForeignLang = jsonBook.ForeignLanguage;
+ book.PracticeMode = jsonBook.PracticeMode;
+ book.FilePath = stream.Name;
+
+ foreach (JsonWord jsonWord in jsonBook.Words)
+ {
+ VocabularyWord word = new(jsonWord.MotherTongue, jsonWord.ForeignLang)
+ {
+ ForeignLangSynonym = jsonWord.ForeignLangSynonym,
+ PracticeStateNumber = jsonWord.PracticeStateNumber,
+ PracticeDate = jsonWord.PracticeDate
+ };
+ book.Words.Add(word);
+ }
+
+ return metadata.FileVersion <= maxSupportedFileVersion;
+ }
+ catch (InvalidDataException ex)
+ {
+ throw new VhfFormatException(VhfError.CorruptedArchive, ex);
+ }
+ catch (JsonException ex)
+ {
+ throw new VhfFormatException(VhfError.InvalidJsonBook, ex);
+ }
+ }
+
+ public override void Write(FileStream stream, VocabularyBook book, string vhrPath, bool includeResults)
+ {
+ using (ZipArchive archive = new(stream, ZipArchiveMode.Create, leaveOpen: true))
+ {
+ ZipArchiveEntry headerFile = archive.CreateEntry(headerFileName);
+ using (Stream headerStream = headerFile.Open())
+ {
+ JsonSerializer.Serialize(headerStream, new Metadata(maxSupportedFileVersion), options);
+ }
+
+ JsonBook jsonBook = new(book.MotherTongue, book.ForeignLang, book.PracticeMode, []);
+
+ foreach (VocabularyWord word in book.Words)
+ {
+ JsonWord jsonWord = includeResults
+ ? new(word.MotherTongue, word.ForeignLang, word.ForeignLangSynonym, word.PracticeStateNumber, word.PracticeDate)
+ : new(word.MotherTongue, word.ForeignLang, word.ForeignLangSynonym, 0, DateTime.MinValue);
+ jsonBook.Words.Add(jsonWord);
+ }
+
+ ZipArchiveEntry bookFile = archive.CreateEntry(bookFileName);
+ using Stream bookStream = bookFile.Open();
+ JsonSerializer.Serialize(bookStream, jsonBook, options);
+ }
+
+ // Delete vhr file when migrating from vhf1 to vhf2
+ TryDeleteVhrFile(book.VhrCode, vhrPath);
+
+ book.FilePath = stream.Name;
+ }
+
+ private record Metadata(Version FileVersion);
+
+ private record JsonBook(string MotherTongue, string ForeignLanguage, PracticeMode PracticeMode, List Words);
+
+ private record JsonWord(string MotherTongue, string ForeignLang, string? ForeignLangSynonym, int PracticeStateNumber, DateTime PracticeDate);
+
+ private class PracticeModeConverter : JsonConverter
+ {
+ public override PracticeMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ string value = reader.GetString() ?? throw new InvalidDataException("A PracticeMode cannot be null");
+ return (PracticeMode)Enum.Parse(typeToConvert, value);
+ }
+
+ public override void Write(Utf8JsonWriter writer, PracticeMode value, JsonSerializerOptions options)
+ {
+ writer.WriteStringValue(value.ToString());
+ }
+ }
+
+ private class PracticeResultConverter : JsonConverter
+ {
+ public override PracticeResult Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ string value = reader.GetString() ?? throw new InvalidDataException("A PracticeResult cannot be null");
+ return (PracticeResult)Enum.Parse(typeToConvert, value);
+ }
+
+ public override void Write(Utf8JsonWriter writer, PracticeResult value, JsonSerializerOptions options)
+ {
+ writer.WriteStringValue(value.ToString());
+ }
+ }
+}
diff --git a/src/Vocup/IO/VhfError.cs b/src/Vocup/IO/VhfError.cs
new file mode 100644
index 0000000..c013444
--- /dev/null
+++ b/src/Vocup/IO/VhfError.cs
@@ -0,0 +1,14 @@
+namespace Vocup.IO;
+
+public enum VhfError
+{
+ InvalidCiphertext,
+ InvalidVersion,
+ InvalidVhrCode,
+ InvalidLanguages,
+ InvalidRow,
+ UpdateRequired,
+ CorruptedArchive,
+ InvalidHeader,
+ InvalidJsonBook
+}
diff --git a/src/Vocup/IO/VhfFile.cs b/src/Vocup/IO/VhfFile.cs
deleted file mode 100644
index 0721f86..0000000
--- a/src/Vocup/IO/VhfFile.cs
+++ /dev/null
@@ -1,129 +0,0 @@
-using System;
-using System.IO;
-using System.Windows.Forms;
-using Vocup.Models;
-using Vocup.Properties;
-
-namespace Vocup.IO.Internal;
-
-internal class VhfFile : VocupFile
-{
- public bool Read(string path, VocabularyBook book)
- {
- string plaintext;
- try
- {
- plaintext = ReadFile(path);
- }
- catch (NotSupportedException)
- {
- MessageBox.Show(Messages.VhfMustUpdate, Messages.VhfMustUpdateT, MessageBoxButtons.OK, MessageBoxIcon.Error);
- return false;
- }
- catch (FormatException)
- {
- MessageBox.Show(Messages.VhfCorruptFile, Messages.VhfCorruptFileT, MessageBoxButtons.OK, MessageBoxIcon.Error);
- return false;
- }
- catch (System.Security.Cryptography.CryptographicException)
- {
- MessageBox.Show(Messages.VhfCorruptFile, Messages.VhfCorruptFileT, MessageBoxButtons.OK, MessageBoxIcon.Error);
- return false;
- }
-
- using (StringReader reader = new StringReader(plaintext))
- {
- string? version = reader.ReadLine();
- string? vhrCode = reader.ReadLine();
- string? motherTongue = reader.ReadLine();
- string? foreignLang = reader.ReadLine();
-
- if (string.IsNullOrWhiteSpace(version) || !Version.TryParse(version, out Version? versionObj))
- {
- MessageBox.Show(Messages.VhfInvalidVersion, Messages.VhfCorruptFileT, MessageBoxButtons.OK, MessageBoxIcon.Error);
- return false;
- }
- else if (versionObj.CompareTo(Util.AppInfo.FileVersion) == 1)
- {
- MessageBox.Show(Messages.VhfMustUpdate, Messages.VhfMustUpdateT, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
- return false;
- }
-
- if (vhrCode == null)
- {
- MessageBox.Show(Messages.VhfInvalidVhrCode, Messages.VhfCorruptFileT, MessageBoxButtons.OK, MessageBoxIcon.Error);
- return false;
- }
-
- book.VhrCode = vhrCode;
- book.FilePath = path;
-
- if (string.IsNullOrWhiteSpace(motherTongue) ||
- string.IsNullOrWhiteSpace(foreignLang) ||
- motherTongue == foreignLang)
- {
- MessageBox.Show(Messages.VhfInvalidLanguages, Messages.VhfCorruptFileT, MessageBoxButtons.OK, MessageBoxIcon.Error);
- return false;
- }
-
- book.MotherTongue = motherTongue;
- book.ForeignLang = foreignLang;
-
- while (true)
- {
- string? line = reader.ReadLine();
- if (line == null) break;
- string[] columns = line.Split('#');
- if (columns.Length != 3)
- {
- MessageBox.Show(Messages.VhfInvalidRow, Messages.VhfCorruptFileT, MessageBoxButtons.OK, MessageBoxIcon.Error);
- return false;
- }
- VocabularyWord word = new VocabularyWord(columns[0], columns[1])
- {
- Owner = book,
- ForeignLangSynonym = columns[2]
- };
- book.Words.Add(word);
- }
- }
-
- return true;
- }
-
- public bool Write(string path, VocabularyBook book)
- {
- string raw;
-
- using (StringWriter writer = new StringWriter())
- {
- writer.WriteLine("2.0");
- writer.WriteLine(book.VhrCode);
- writer.WriteLine(book.MotherTongue);
- writer.WriteLine(book.ForeignLang);
-
- foreach (VocabularyWord word in book.Words)
- {
- writer.Write(word.MotherTongue);
- writer.Write('#');
- writer.Write(word.ForeignLang);
- writer.Write('#');
- writer.WriteLine(word.ForeignLangSynonym ?? "");
- }
-
- raw = writer.ToString();
- }
-
- try
- {
- WriteFile(path, raw);
- }
- catch (Exception ex)
- {
- MessageBox.Show(string.Format(Messages.VocupFileWriteErrorEx, ex), Messages.VocupFileWriteErrorT, MessageBoxButtons.OK, MessageBoxIcon.Error);
- return false;
- }
-
- return true;
- }
-}
diff --git a/src/Vocup/IO/VhfFormatException.cs b/src/Vocup/IO/VhfFormatException.cs
new file mode 100644
index 0000000..23fa430
--- /dev/null
+++ b/src/Vocup/IO/VhfFormatException.cs
@@ -0,0 +1,18 @@
+using System;
+
+namespace Vocup.IO;
+
+public class VhfFormatException : FormatException
+{
+ public VhfFormatException(VhfError errorCode)
+ {
+ ErrorCode = errorCode;
+ }
+
+ public VhfFormatException(VhfError errorCode, Exception innerException) : base(null, innerException)
+ {
+ ErrorCode = errorCode;
+ }
+
+ public VhfError ErrorCode { get; }
+}
diff --git a/src/Vocup/IO/VhrFile.cs b/src/Vocup/IO/VhrFile.cs
deleted file mode 100644
index df25f5f..0000000
--- a/src/Vocup/IO/VhrFile.cs
+++ /dev/null
@@ -1,158 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.IO;
-using System.Windows.Forms;
-using Vocup.Models;
-using Vocup.Properties;
-
-namespace Vocup.IO.Internal;
-
-internal class VhrFile : VocupFile
-{
- public bool Read(VocabularyBook book)
- {
- if (string.IsNullOrWhiteSpace(book.VhrCode))
- return false;
- FileInfo vhrInfo = new FileInfo(Path.Combine(Program.Settings.VhrPath, book.VhrCode + ".vhr"));
- if (!vhrInfo.Exists)
- return false;
-
- string plaintext;
- try
- {
- plaintext = ReadFile(vhrInfo.FullName);
- }
- catch (FormatException)
- {
- DeleteInvalidFile(vhrInfo);
- return false;
- }
- catch (System.Security.Cryptography.CryptographicException)
- {
- DeleteInvalidFile(vhrInfo);
- return false;
- }
-
- using (StringReader reader = new StringReader(plaintext))
- {
- string? path = reader.ReadLine();
- string? mode = reader.ReadLine();
-
- if (string.IsNullOrWhiteSpace(path) ||
- string.IsNullOrWhiteSpace(mode) || !int.TryParse(mode, out int imode) || !((PracticeMode)imode).IsValid())
- {
- DeleteInvalidFile(vhrInfo);
- return false;
- }
-
- List<(int stateNumber, DateTime date)> results = new List<(int stateNumber, DateTime date)>();
-
- while (true)
- {
- string? line = reader.ReadLine();
- if (line == null) break;
- string[] columns = line.Split('#');
- if (columns.Length != 2 || !int.TryParse(columns[0], out int state) || !PracticeStateHelper.Parse(state).IsValid())
- {
- DeleteInvalidFile(vhrInfo);
- return false;
- }
- DateTime time = DateTime.MinValue;
- if (!string.IsNullOrWhiteSpace(columns[1]) && !DateTime.TryParseExact(columns[1], "dd.MM.yyyy HH:mm", CultureInfo.InvariantCulture, DateTimeStyles.None, out time))
- {
- DeleteInvalidFile(vhrInfo);
- return false;
- }
- results.Add((state, time));
- }
-
- bool countMatch = book.Words.Count == results.Count;
-
- FileInfo vhfInfo = new FileInfo(book.FilePath!);
- FileInfo pathInfo = new FileInfo(path);
-
- if (vhfInfo.FullName.Equals(pathInfo.FullName, StringComparison.OrdinalIgnoreCase))
- {
- if (!countMatch)
- {
- MessageBox.Show(Messages.VhrInvalidRowCount, Messages.VhrCorruptFileT, MessageBoxButtons.OK, MessageBoxIcon.Warning);
- try { vhrInfo.Delete(); } catch { }
- return false;
- }
- }
- else
- {
- if (!countMatch)
- {
- MessageBox.Show(Messages.VhrInvalidRowCountAndOtherFile, Messages.VhrCorruptFileT, MessageBoxButtons.OK, MessageBoxIcon.Warning);
- return false;
- }
-
- if (pathInfo.Exists)
- book.GenerateVhrCode(); // Save new results file if the old one is in use by another file
-
- book.UnsavedChanges = true;
- }
-
- book.PracticeMode = (PracticeMode)imode;
-
- for (int i = 0; i < book.Words.Count; i++)
- {
- VocabularyWord word = book.Words[i];
- (word.PracticeStateNumber, word.PracticeDate) = results[i];
- }
- }
-
- return true;
- }
-
- public bool Write(VocabularyBook book)
- {
- string raw;
-
- using (StringWriter writer = new StringWriter())
- {
- writer.WriteLine(book.FilePath);
- writer.Write((int)book.PracticeMode);
-
- foreach (VocabularyWord word in book.Words)
- {
- writer.WriteLine();
-
- writer.Write(word.PracticeStateNumber);
- writer.Write('#');
- if (word.PracticeDate != DateTime.MinValue)
- writer.Write(word.PracticeDate.ToString("dd.MM.yyyy HH:mm"));
- }
-
- raw = writer.ToString();
- }
-
- try
- {
- WriteFile(Path.Combine(Program.Settings.VhrPath, book.VhrCode + ".vhr"), raw);
- }
- catch (Exception ex)
- {
- MessageBox.Show(string.Format(Messages.VocupFileWriteErrorEx, ex), Messages.VocupFileWriteErrorT, MessageBoxButtons.OK, MessageBoxIcon.Error);
- return false;
- }
-
- return true;
- }
-
- ///
- /// Shows a message box and deletes an invalid result file.
- ///
- ///
- private void DeleteInvalidFile(FileInfo info)
- {
- MessageBox.Show(Messages.VhrCorruptFile, Messages.VhrCorruptFileT, MessageBoxButtons.OK, MessageBoxIcon.Error);
- try
- {
- info.Delete();
- }
- catch { }
- }
-}
diff --git a/src/Vocup/IO/VocabularyFile.cs b/src/Vocup/IO/VocabularyFile.cs
deleted file mode 100644
index bcddb61..0000000
--- a/src/Vocup/IO/VocabularyFile.cs
+++ /dev/null
@@ -1,71 +0,0 @@
-using System;
-using Vocup.IO.Internal;
-using Vocup.Models;
-
-namespace Vocup.IO;
-
-///
-/// Provides static methods for reading und writing vocabulary files.
-///
-public static class VocabularyFile
-{
- private static readonly VhfFile vhfFile = new VhfFile();
- private static readonly VhrFile vhrFile = new VhrFile();
- private static readonly CsvFile csvFile = new CsvFile();
-
- public static bool ReadVhfFile(string path, VocabularyBook book)
- {
- if (path == null)
- throw new ArgumentNullException(nameof(path));
- if (book == null)
- throw new ArgumentNullException(nameof(book));
-
- return vhfFile.Read(path, book);
- }
-
- public static bool WriteVhfFile(string path, VocabularyBook book)
- {
- if (path == null)
- throw new ArgumentNullException(nameof(path));
- if (book == null)
- throw new ArgumentNullException(nameof(book));
-
- return vhfFile.Write(path, book);
- }
-
- public static bool ReadVhrFile(VocabularyBook book)
- {
- if (book == null)
- throw new ArgumentNullException(nameof(book));
-
- return vhrFile.Read(book);
- }
-
- public static bool WriteVhrFile(VocabularyBook book)
- {
- if (book == null)
- throw new ArgumentNullException(nameof(book));
-
- return vhrFile.Write(book);
- }
-
- public static bool ImportCsvFile(string path, VocabularyBook book, bool importSettings)
- {
- if (path == null)
- throw new ArgumentNullException(nameof(path));
- if (book == null)
- throw new ArgumentNullException(nameof(book));
-
- return csvFile.Import(path, book, importSettings);
- }
-
- public static bool ExportCsvFile(string path, VocabularyBook book)
- {
- if (path == null)
- throw new ArgumentNullException(nameof(path));
- if (book == null)
- throw new ArgumentNullException(nameof(book));
-
- return csvFile.Export(path, book);
- }
-}
diff --git a/src/Vocup/IO/VocupFile.cs b/src/Vocup/IO/VocupFile.cs
deleted file mode 100644
index b9f915c..0000000
--- a/src/Vocup/IO/VocupFile.cs
+++ /dev/null
@@ -1,79 +0,0 @@
-using System;
-using System.IO;
-using System.Security.Cryptography;
-using System.Text;
-
-namespace Vocup.IO.Internal;
-
-///
-/// Represents an encrypted, Vocup specific file.
-///
-internal abstract class VocupFile
-{
- private static readonly DES csp;
-
- static VocupFile()
- {
- csp = DES.Create();
- csp.Key = new byte[8] { 1, 8, 3, 4, 3, 2, 7, 1 };
- csp.IV = new byte[8] { 3, 1, 3, 4, 6, 3, 4, 8 };
- }
-
- ///
- /// Decrypts Vocup specific file using DES and a hard-coded key.
- ///
- ///
- /// The UTF8 encoded plaintext.
- ///
- ///
- ///
- ///
- ///
- ///
- ///
- protected string ReadFile(string path)
- {
- byte[] ciphertext;
- using (FileStream file = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read))
- {
- if (file.ReadByte() == 0x50 && file.ReadByte() == 0x4B && file.ReadByte() == 0x03 && file.ReadByte() == 0x04)
- throw new NotSupportedException($"The file {path} cannot be opened by Vocup. A later version might be required.");
-
- file.Position = 0;
-
- using (StreamReader reader = new StreamReader(file, Encoding.UTF8))
- ciphertext = Convert.FromBase64String(reader.ReadToEnd());
- }
-
- using MemoryStream plainstream = new MemoryStream();
- using ICryptoTransform transform = csp.CreateDecryptor();
- using CryptoStream cipherstream = new CryptoStream(plainstream, transform, CryptoStreamMode.Write);
-
- cipherstream.Write(ciphertext, 0, ciphertext.Length);
- cipherstream.FlushFinalBlock();
- return Encoding.UTF8.GetString(plainstream.ToArray());
- }
-
- ///
- /// Encrypts a Vocup specific file using UTF-8 and DES with a hard-coded key.
- ///
- /// The path where to save the encrypted file.
- /// The context text that will be encoded with UTF-8 before encryption.
- ///
- ///
- ///
- ///
- protected void WriteFile(string path, string content)
- {
- byte[] buffer = Encoding.UTF8.GetBytes(content);
-
- using StreamWriter writer = new StreamWriter(path, false, Encoding.UTF8);
- using MemoryStream cipherstream = new MemoryStream();
- using ICryptoTransform transform = csp.CreateEncryptor();
- using CryptoStream plainstream = new CryptoStream(cipherstream, transform, CryptoStreamMode.Write);
-
- plainstream.Write(buffer, 0, buffer.Length);
- plainstream.FlushFinalBlock();
- writer.Write(Convert.ToBase64String(cipherstream.ToArray()));
- }
-}
diff --git a/src/Vocup/MainForm.Designer.cs b/src/Vocup/MainForm.Designer.cs
index 82c466e..14e842c 100644
--- a/src/Vocup/MainForm.Designer.cs
+++ b/src/Vocup/MainForm.Designer.cs
@@ -37,61 +37,62 @@ private void InitializeComponent()
System.Windows.Forms.ToolStripSeparator toolStripMenuItem6;
System.Windows.Forms.ToolStripSeparator toolStripMenuItem8;
System.Windows.Forms.ToolStripSeparator infoToolStripMenuItem;
- this.MenuStrip = new Vocup.Controls.ResponsiveMenuStrip();
- this.TsmiRootFile = new System.Windows.Forms.ToolStripMenuItem();
- this.TsmiCreateBook = new System.Windows.Forms.ToolStripMenuItem();
- this.TsmiOpenBook = new System.Windows.Forms.ToolStripMenuItem();
- this.TsmiCloseBook = new System.Windows.Forms.ToolStripMenuItem();
- this.TsmiSave = new System.Windows.Forms.ToolStripMenuItem();
- this.TsmiSaveAs = new System.Windows.Forms.ToolStripMenuItem();
- this.TsmiImport = new System.Windows.Forms.ToolStripMenuItem();
- this.TsmiExport = new System.Windows.Forms.ToolStripMenuItem();
- this.TsmiOpenInExplorer = new System.Windows.Forms.ToolStripMenuItem();
- this.toolStripMenuItem7 = new System.Windows.Forms.ToolStripSeparator();
- this.TsmiPrint = new System.Windows.Forms.ToolStripMenuItem();
- this.toolStripMenuItem3 = new System.Windows.Forms.ToolStripSeparator();
- this.TsmiExitApplication = new System.Windows.Forms.ToolStripMenuItem();
- this.TsmiRootEdit = new System.Windows.Forms.ToolStripMenuItem();
- this.TsmiAddWord = new System.Windows.Forms.ToolStripMenuItem();
- this.TsmiEditWord = new System.Windows.Forms.ToolStripMenuItem();
- this.TsmiDeleteWord = new System.Windows.Forms.ToolStripMenuItem();
- this.TsmiRootTools = new System.Windows.Forms.ToolStripMenuItem();
- this.TsmiPractice = new System.Windows.Forms.ToolStripMenuItem();
- this.TsmiBookOptions = new System.Windows.Forms.ToolStripMenuItem();
- this.TsmiMerge = new System.Windows.Forms.ToolStripMenuItem();
- this.TsmiSpecialChar = new System.Windows.Forms.ToolStripMenuItem();
- this.TsmiSettings = new System.Windows.Forms.ToolStripMenuItem();
- this.TsmiRootHelp = new System.Windows.Forms.ToolStripMenuItem();
- this.TsmiHelp = new System.Windows.Forms.ToolStripMenuItem();
- this.TsmiEvaluationInfo = new System.Windows.Forms.ToolStripMenuItem();
- this.TsmiUpdate = new System.Windows.Forms.ToolStripMenuItem();
- this.TsmiAbout = new System.Windows.Forms.ToolStripMenuItem();
- this.TbSearchWord = new System.Windows.Forms.TextBox();
- this.GroupBook = new System.Windows.Forms.GroupBox();
- this.BtnBookSettings = new Vocup.Controls.ResponsiveButton();
- this.BtnPractice = new Vocup.Controls.ResponsiveButton();
- this.GroupSearch = new System.Windows.Forms.GroupBox();
- this.BtnSearchWord = new Vocup.Controls.ResponsiveButton();
- this.GroupWord = new System.Windows.Forms.GroupBox();
- this.BtnAddWord = new Vocup.Controls.ResponsiveButton();
- this.BtnDeleteWord = new Vocup.Controls.ResponsiveButton();
- this.BtnEditWord = new Vocup.Controls.ResponsiveButton();
- this.StatusStrip = new System.Windows.Forms.StatusStrip();
- this.StatusLbOldVersion = new System.Windows.Forms.ToolStripStatusLabel();
- this.SideBar = new System.Windows.Forms.Panel();
- this.GroupStatistics = new Vocup.Controls.StatisticsPanel();
- this.SplitContainer = new Vocup.Controls.ResponsiveSplitContainer();
- this.FileTreeView = new Vocup.Controls.FileTreeView();
- this.LbEmptyForm = new System.Windows.Forms.Label();
- this.ToolStrip = new Vocup.Controls.ResponsiveToolStrip();
- this.TsbCreateBook = new System.Windows.Forms.ToolStripButton();
- this.TsbOpenBook = new System.Windows.Forms.ToolStripButton();
- this.TsbSave = new System.Windows.Forms.ToolStripButton();
- this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
- this.TsbPrint = new System.Windows.Forms.ToolStripButton();
- this.TsbEvaluationInfo = new System.Windows.Forms.ToolStripButton();
- this.toolStripLabel = new System.Windows.Forms.ToolStripLabel();
- this.TableLayout = new System.Windows.Forms.TableLayoutPanel();
+ MenuStrip = new Controls.ResponsiveMenuStrip();
+ TsmiRootFile = new System.Windows.Forms.ToolStripMenuItem();
+ TsmiCreateBook = new System.Windows.Forms.ToolStripMenuItem();
+ TsmiOpenBook = new System.Windows.Forms.ToolStripMenuItem();
+ TsmiCloseBook = new System.Windows.Forms.ToolStripMenuItem();
+ TsmiSave = new System.Windows.Forms.ToolStripMenuItem();
+ TsmiSaveAs = new System.Windows.Forms.ToolStripMenuItem();
+ TsmiShare = new System.Windows.Forms.ToolStripMenuItem();
+ TsmiImport = new System.Windows.Forms.ToolStripMenuItem();
+ TsmiExport = new System.Windows.Forms.ToolStripMenuItem();
+ TsmiOpenInExplorer = new System.Windows.Forms.ToolStripMenuItem();
+ toolStripMenuItem7 = new System.Windows.Forms.ToolStripSeparator();
+ TsmiPrint = new System.Windows.Forms.ToolStripMenuItem();
+ toolStripMenuItem3 = new System.Windows.Forms.ToolStripSeparator();
+ TsmiExitApplication = new System.Windows.Forms.ToolStripMenuItem();
+ TsmiRootEdit = new System.Windows.Forms.ToolStripMenuItem();
+ TsmiAddWord = new System.Windows.Forms.ToolStripMenuItem();
+ TsmiEditWord = new System.Windows.Forms.ToolStripMenuItem();
+ TsmiDeleteWord = new System.Windows.Forms.ToolStripMenuItem();
+ TsmiRootTools = new System.Windows.Forms.ToolStripMenuItem();
+ TsmiPractice = new System.Windows.Forms.ToolStripMenuItem();
+ TsmiBookOptions = new System.Windows.Forms.ToolStripMenuItem();
+ TsmiMerge = new System.Windows.Forms.ToolStripMenuItem();
+ TsmiSpecialChar = new System.Windows.Forms.ToolStripMenuItem();
+ TsmiSettings = new System.Windows.Forms.ToolStripMenuItem();
+ TsmiRootHelp = new System.Windows.Forms.ToolStripMenuItem();
+ TsmiHelp = new System.Windows.Forms.ToolStripMenuItem();
+ TsmiEvaluationInfo = new System.Windows.Forms.ToolStripMenuItem();
+ TsmiUpdate = new System.Windows.Forms.ToolStripMenuItem();
+ TsmiAbout = new System.Windows.Forms.ToolStripMenuItem();
+ TbSearchWord = new System.Windows.Forms.TextBox();
+ GroupBook = new System.Windows.Forms.GroupBox();
+ BtnBookSettings = new Controls.ResponsiveButton();
+ BtnPractice = new Controls.ResponsiveButton();
+ GroupSearch = new System.Windows.Forms.GroupBox();
+ BtnSearchWord = new Controls.ResponsiveButton();
+ GroupWord = new System.Windows.Forms.GroupBox();
+ BtnAddWord = new Controls.ResponsiveButton();
+ BtnDeleteWord = new Controls.ResponsiveButton();
+ BtnEditWord = new Controls.ResponsiveButton();
+ StatusStrip = new System.Windows.Forms.StatusStrip();
+ StatusLbOldVersion = new System.Windows.Forms.ToolStripStatusLabel();
+ SideBar = new System.Windows.Forms.Panel();
+ GroupStatistics = new Controls.StatisticsPanel();
+ SplitContainer = new Controls.ResponsiveSplitContainer();
+ FileTreeView = new Controls.FileTreeView();
+ LbEmptyForm = new System.Windows.Forms.Label();
+ ToolStrip = new Controls.ResponsiveToolStrip();
+ TsbCreateBook = new System.Windows.Forms.ToolStripButton();
+ TsbOpenBook = new System.Windows.Forms.ToolStripButton();
+ TsbSave = new System.Windows.Forms.ToolStripButton();
+ toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
+ TsbPrint = new System.Windows.Forms.ToolStripButton();
+ TsbEvaluationInfo = new System.Windows.Forms.ToolStripButton();
+ toolStripLabel = new System.Windows.Forms.ToolStripLabel();
+ TableLayout = new System.Windows.Forms.TableLayoutPanel();
toolStripMenuItem11 = new System.Windows.Forms.ToolStripSeparator();
toolStripMenuItem10 = new System.Windows.Forms.ToolStripSeparator();
toolStripMenuItem9 = new System.Windows.Forms.ToolStripSeparator();
@@ -100,19 +101,19 @@ private void InitializeComponent()
toolStripMenuItem6 = new System.Windows.Forms.ToolStripSeparator();
toolStripMenuItem8 = new System.Windows.Forms.ToolStripSeparator();
infoToolStripMenuItem = new System.Windows.Forms.ToolStripSeparator();
- this.MenuStrip.SuspendLayout();
- this.GroupBook.SuspendLayout();
- this.GroupSearch.SuspendLayout();
- this.GroupWord.SuspendLayout();
- this.StatusStrip.SuspendLayout();
- this.SideBar.SuspendLayout();
- ((System.ComponentModel.ISupportInitialize)(this.SplitContainer)).BeginInit();
- this.SplitContainer.Panel1.SuspendLayout();
- this.SplitContainer.Panel2.SuspendLayout();
- this.SplitContainer.SuspendLayout();
- this.ToolStrip.SuspendLayout();
- this.TableLayout.SuspendLayout();
- this.SuspendLayout();
+ MenuStrip.SuspendLayout();
+ GroupBook.SuspendLayout();
+ GroupSearch.SuspendLayout();
+ GroupWord.SuspendLayout();
+ StatusStrip.SuspendLayout();
+ SideBar.SuspendLayout();
+ ((System.ComponentModel.ISupportInitialize)SplitContainer).BeginInit();
+ SplitContainer.Panel1.SuspendLayout();
+ SplitContainer.Panel2.SuspendLayout();
+ SplitContainer.SuspendLayout();
+ ToolStrip.SuspendLayout();
+ TableLayout.SuspendLayout();
+ SuspendLayout();
//
// toolStripMenuItem11
//
@@ -156,490 +157,452 @@ private void InitializeComponent()
//
// MenuStrip
//
- this.MenuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.TsmiRootFile,
- this.TsmiRootEdit,
- this.TsmiRootTools,
- this.TsmiRootHelp});
- resources.ApplyResources(this.MenuStrip, "MenuStrip");
- this.MenuStrip.Name = "MenuStrip";
+ MenuStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { TsmiRootFile, TsmiRootEdit, TsmiRootTools, TsmiRootHelp });
+ resources.ApplyResources(MenuStrip, "MenuStrip");
+ MenuStrip.Name = "MenuStrip";
//
// TsmiRootFile
//
- this.TsmiRootFile.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.TsmiCreateBook,
- this.TsmiOpenBook,
- this.TsmiCloseBook,
- toolStripMenuItem11,
- this.TsmiSave,
- this.TsmiSaveAs,
- toolStripMenuItem10,
- this.TsmiImport,
- this.TsmiExport,
- toolStripMenuItem9,
- this.TsmiOpenInExplorer,
- this.toolStripMenuItem7,
- this.TsmiPrint,
- this.toolStripMenuItem3,
- this.TsmiExitApplication});
- this.TsmiRootFile.Name = "TsmiRootFile";
- resources.ApplyResources(this.TsmiRootFile, "TsmiRootFile");
+ TsmiRootFile.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { TsmiCreateBook, TsmiOpenBook, TsmiCloseBook, toolStripMenuItem11, TsmiSave, TsmiSaveAs, TsmiShare, toolStripMenuItem10, TsmiImport, TsmiExport, toolStripMenuItem9, TsmiOpenInExplorer, toolStripMenuItem7, TsmiPrint, toolStripMenuItem3, TsmiExitApplication });
+ TsmiRootFile.Name = "TsmiRootFile";
+ resources.ApplyResources(TsmiRootFile, "TsmiRootFile");
//
// TsmiCreateBook
//
- this.TsmiCreateBook.Image = global::Vocup.Properties.Icons.File;
- this.TsmiCreateBook.Name = "TsmiCreateBook";
- resources.ApplyResources(this.TsmiCreateBook, "TsmiCreateBook");
- this.TsmiCreateBook.Click += new System.EventHandler(this.TsmiCreateBook_Click);
+ TsmiCreateBook.Image = Properties.Icons.File;
+ TsmiCreateBook.Name = "TsmiCreateBook";
+ resources.ApplyResources(TsmiCreateBook, "TsmiCreateBook");
+ TsmiCreateBook.Click += TsmiCreateBook_Click;
//
// TsmiOpenBook
//
- this.TsmiOpenBook.Image = global::Vocup.Properties.Icons.Open;
- this.TsmiOpenBook.Name = "TsmiOpenBook";
- resources.ApplyResources(this.TsmiOpenBook, "TsmiOpenBook");
- this.TsmiOpenBook.Click += new System.EventHandler(this.TsmiOpenBook_Click);
+ TsmiOpenBook.Image = Properties.Icons.Open;
+ TsmiOpenBook.Name = "TsmiOpenBook";
+ resources.ApplyResources(TsmiOpenBook, "TsmiOpenBook");
+ TsmiOpenBook.Click += TsmiOpenBook_Click;
//
// TsmiCloseBook
//
- resources.ApplyResources(this.TsmiCloseBook, "TsmiCloseBook");
- this.TsmiCloseBook.Image = global::Vocup.Properties.Icons.Cancel;
- this.TsmiCloseBook.Name = "TsmiCloseBook";
- this.TsmiCloseBook.Click += new System.EventHandler(this.TsmiCloseBook_Click);
+ resources.ApplyResources(TsmiCloseBook, "TsmiCloseBook");
+ TsmiCloseBook.Image = Properties.Icons.Cancel;
+ TsmiCloseBook.Name = "TsmiCloseBook";
+ TsmiCloseBook.Click += TsmiCloseBook_Click;
//
// TsmiSave
//
- resources.ApplyResources(this.TsmiSave, "TsmiSave");
- this.TsmiSave.Image = global::Vocup.Properties.Icons.Save;
- this.TsmiSave.Name = "TsmiSave";
- this.TsmiSave.Click += new System.EventHandler(this.TsmiSave_Click);
+ resources.ApplyResources(TsmiSave, "TsmiSave");
+ TsmiSave.Image = Properties.Icons.Save;
+ TsmiSave.Name = "TsmiSave";
+ TsmiSave.Click += TsmiSave_Click;
//
// TsmiSaveAs
//
- resources.ApplyResources(this.TsmiSaveAs, "TsmiSaveAs");
- this.TsmiSaveAs.Image = global::Vocup.Properties.Icons.SaveAll;
- this.TsmiSaveAs.Name = "TsmiSaveAs";
- this.TsmiSaveAs.Click += new System.EventHandler(this.TsmiSaveAs_Click);
+ resources.ApplyResources(TsmiSaveAs, "TsmiSaveAs");
+ TsmiSaveAs.Image = Properties.Icons.SaveAll;
+ TsmiSaveAs.Name = "TsmiSaveAs";
+ TsmiSaveAs.Click += TsmiSaveAs_Click;
+ //
+ // TsmiShare
+ //
+ resources.ApplyResources(TsmiShare, "TsmiShare");
+ TsmiShare.Image = Properties.Icons.Share;
+ TsmiShare.Name = "TsmiShare";
+ TsmiShare.Click += TsmiShare_Click;
//
// TsmiImport
//
- this.TsmiImport.Image = global::Vocup.Properties.Icons.Import;
- this.TsmiImport.Name = "TsmiImport";
- resources.ApplyResources(this.TsmiImport, "TsmiImport");
- this.TsmiImport.Click += new System.EventHandler(this.TsmiImport_Click);
+ TsmiImport.Image = Properties.Icons.Import;
+ TsmiImport.Name = "TsmiImport";
+ resources.ApplyResources(TsmiImport, "TsmiImport");
+ TsmiImport.Click += TsmiImport_Click;
//
// TsmiExport
//
- resources.ApplyResources(this.TsmiExport, "TsmiExport");
- this.TsmiExport.Image = global::Vocup.Properties.Icons.Export;
- this.TsmiExport.Name = "TsmiExport";
- this.TsmiExport.Click += new System.EventHandler(this.TsmiExport_Click);
+ resources.ApplyResources(TsmiExport, "TsmiExport");
+ TsmiExport.Image = Properties.Icons.Export;
+ TsmiExport.Name = "TsmiExport";
+ TsmiExport.Click += TsmiExport_Click;
//
// TsmiOpenInExplorer
//
- resources.ApplyResources(this.TsmiOpenInExplorer, "TsmiOpenInExplorer");
- this.TsmiOpenInExplorer.Image = global::Vocup.Properties.Icons.Open;
- this.TsmiOpenInExplorer.Name = "TsmiOpenInExplorer";
- this.TsmiOpenInExplorer.Click += new System.EventHandler(this.TsmiOpenInExplorer_Click);
+ resources.ApplyResources(TsmiOpenInExplorer, "TsmiOpenInExplorer");
+ TsmiOpenInExplorer.Image = Properties.Icons.Open;
+ TsmiOpenInExplorer.Name = "TsmiOpenInExplorer";
+ TsmiOpenInExplorer.Click += TsmiOpenInExplorer_Click;
//
// toolStripMenuItem7
//
- this.toolStripMenuItem7.Name = "toolStripMenuItem7";
- resources.ApplyResources(this.toolStripMenuItem7, "toolStripMenuItem7");
+ toolStripMenuItem7.Name = "toolStripMenuItem7";
+ resources.ApplyResources(toolStripMenuItem7, "toolStripMenuItem7");
//
// TsmiPrint
//
- resources.ApplyResources(this.TsmiPrint, "TsmiPrint");
- this.TsmiPrint.Image = global::Vocup.Properties.Icons.Print;
- this.TsmiPrint.Name = "TsmiPrint";
- this.TsmiPrint.Click += new System.EventHandler(this.TsmiPrint_Click);
+ resources.ApplyResources(TsmiPrint, "TsmiPrint");
+ TsmiPrint.Image = Properties.Icons.Print;
+ TsmiPrint.Name = "TsmiPrint";
+ TsmiPrint.Click += TsmiPrint_Click;
//
// toolStripMenuItem3
//
- this.toolStripMenuItem3.Name = "toolStripMenuItem3";
- resources.ApplyResources(this.toolStripMenuItem3, "toolStripMenuItem3");
+ toolStripMenuItem3.Name = "toolStripMenuItem3";
+ resources.ApplyResources(toolStripMenuItem3, "toolStripMenuItem3");
//
// TsmiExitApplication
//
- this.TsmiExitApplication.Image = global::Vocup.Properties.Icons.DoorOpened;
- this.TsmiExitApplication.Name = "TsmiExitApplication";
- resources.ApplyResources(this.TsmiExitApplication, "TsmiExitApplication");
- this.TsmiExitApplication.Click += new System.EventHandler(this.TsmiExitApplication_Click);
+ TsmiExitApplication.Image = Properties.Icons.DoorOpened;
+ TsmiExitApplication.Name = "TsmiExitApplication";
+ resources.ApplyResources(TsmiExitApplication, "TsmiExitApplication");
+ TsmiExitApplication.Click += TsmiExitApplication_Click;
//
// TsmiRootEdit
//
- this.TsmiRootEdit.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.TsmiAddWord,
- this.TsmiEditWord,
- this.TsmiDeleteWord});
- this.TsmiRootEdit.Name = "TsmiRootEdit";
- resources.ApplyResources(this.TsmiRootEdit, "TsmiRootEdit");
+ TsmiRootEdit.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { TsmiAddWord, TsmiEditWord, TsmiDeleteWord });
+ TsmiRootEdit.Name = "TsmiRootEdit";
+ resources.ApplyResources(TsmiRootEdit, "TsmiRootEdit");
//
// TsmiAddWord
//
- resources.ApplyResources(this.TsmiAddWord, "TsmiAddWord");
- this.TsmiAddWord.Image = global::Vocup.Properties.Icons.Plus;
- this.TsmiAddWord.Name = "TsmiAddWord";
- this.TsmiAddWord.Click += new System.EventHandler(this.TsmiAddWord_Click);
+ resources.ApplyResources(TsmiAddWord, "TsmiAddWord");
+ TsmiAddWord.Image = Properties.Icons.Plus;
+ TsmiAddWord.Name = "TsmiAddWord";
+ TsmiAddWord.Click += TsmiAddWord_Click;
//
// TsmiEditWord
//
- resources.ApplyResources(this.TsmiEditWord, "TsmiEditWord");
- this.TsmiEditWord.Image = global::Vocup.Properties.Icons.Edit;
- this.TsmiEditWord.Name = "TsmiEditWord";
- this.TsmiEditWord.Click += new System.EventHandler(this.TsmiEditWord_Click);
+ resources.ApplyResources(TsmiEditWord, "TsmiEditWord");
+ TsmiEditWord.Image = Properties.Icons.Edit;
+ TsmiEditWord.Name = "TsmiEditWord";
+ TsmiEditWord.Click += TsmiEditWord_Click;
//
// TsmiDeleteWord
//
- resources.ApplyResources(this.TsmiDeleteWord, "TsmiDeleteWord");
- this.TsmiDeleteWord.Image = global::Vocup.Properties.Icons.Delete;
- this.TsmiDeleteWord.Name = "TsmiDeleteWord";
- this.TsmiDeleteWord.Click += new System.EventHandler(this.TsmiDeleteWord_Click);
+ resources.ApplyResources(TsmiDeleteWord, "TsmiDeleteWord");
+ TsmiDeleteWord.Image = Properties.Icons.Delete;
+ TsmiDeleteWord.Name = "TsmiDeleteWord";
+ TsmiDeleteWord.Click += TsmiDeleteWord_Click;
//
// TsmiRootTools
//
- this.TsmiRootTools.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.TsmiPractice,
- this.TsmiBookOptions,
- toolStripMenuItem4,
- this.TsmiMerge,
- toolStripMenuItem5,
- this.TsmiSpecialChar,
- toolStripMenuItem6,
- this.TsmiSettings});
- this.TsmiRootTools.Name = "TsmiRootTools";
- resources.ApplyResources(this.TsmiRootTools, "TsmiRootTools");
+ TsmiRootTools.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { TsmiPractice, TsmiBookOptions, toolStripMenuItem4, TsmiMerge, toolStripMenuItem5, TsmiSpecialChar, toolStripMenuItem6, TsmiSettings });
+ TsmiRootTools.Name = "TsmiRootTools";
+ resources.ApplyResources(TsmiRootTools, "TsmiRootTools");
//
// TsmiPractice
//
- resources.ApplyResources(this.TsmiPractice, "TsmiPractice");
- this.TsmiPractice.Image = global::Vocup.Properties.Icons.LightningBolt;
- this.TsmiPractice.Name = "TsmiPractice";
- this.TsmiPractice.Click += new System.EventHandler(this.TsmiPractice_Click);
+ resources.ApplyResources(TsmiPractice, "TsmiPractice");
+ TsmiPractice.Image = Properties.Icons.LightningBolt;
+ TsmiPractice.Name = "TsmiPractice";
+ TsmiPractice.Click += TsmiPractice_Click;
//
// TsmiBookOptions
//
- resources.ApplyResources(this.TsmiBookOptions, "TsmiBookOptions");
- this.TsmiBookOptions.Image = global::Vocup.Properties.Icons.FileSettings;
- this.TsmiBookOptions.Name = "TsmiBookOptions";
- this.TsmiBookOptions.Click += new System.EventHandler(this.TsmiBookOptions_Click);
+ resources.ApplyResources(TsmiBookOptions, "TsmiBookOptions");
+ TsmiBookOptions.Image = Properties.Icons.FileSettings;
+ TsmiBookOptions.Name = "TsmiBookOptions";
+ TsmiBookOptions.Click += TsmiBookOptions_Click;
//
// TsmiMerge
//
- this.TsmiMerge.Image = global::Vocup.Properties.Icons.MergeFiles;
- this.TsmiMerge.Name = "TsmiMerge";
- resources.ApplyResources(this.TsmiMerge, "TsmiMerge");
- this.TsmiMerge.Click += new System.EventHandler(this.TsmiMerge_Click);
+ TsmiMerge.Image = Properties.Icons.MergeFiles;
+ TsmiMerge.Name = "TsmiMerge";
+ resources.ApplyResources(TsmiMerge, "TsmiMerge");
+ TsmiMerge.Click += TsmiMerge_Click;
//
// TsmiSpecialChar
//
- this.TsmiSpecialChar.Image = global::Vocup.Properties.Icons.Alphabet;
- this.TsmiSpecialChar.Name = "TsmiSpecialChar";
- resources.ApplyResources(this.TsmiSpecialChar, "TsmiSpecialChar");
- this.TsmiSpecialChar.Click += new System.EventHandler(this.TsmiSpecialChar_Click);
+ TsmiSpecialChar.Image = Properties.Icons.Alphabet;
+ TsmiSpecialChar.Name = "TsmiSpecialChar";
+ resources.ApplyResources(TsmiSpecialChar, "TsmiSpecialChar");
+ TsmiSpecialChar.Click += TsmiSpecialChar_Click;
//
// TsmiSettings
//
- this.TsmiSettings.Image = global::Vocup.Properties.Icons.Settings;
- this.TsmiSettings.Name = "TsmiSettings";
- resources.ApplyResources(this.TsmiSettings, "TsmiSettings");
- this.TsmiSettings.Click += new System.EventHandler(this.TsmiSettings_Click);
+ TsmiSettings.Image = Properties.Icons.Settings;
+ TsmiSettings.Name = "TsmiSettings";
+ resources.ApplyResources(TsmiSettings, "TsmiSettings");
+ TsmiSettings.Click += TsmiSettings_Click;
//
// TsmiRootHelp
//
- this.TsmiRootHelp.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.TsmiHelp,
- this.TsmiEvaluationInfo,
- toolStripMenuItem8,
- this.TsmiUpdate,
- infoToolStripMenuItem,
- this.TsmiAbout});
- this.TsmiRootHelp.Name = "TsmiRootHelp";
- resources.ApplyResources(this.TsmiRootHelp, "TsmiRootHelp");
+ TsmiRootHelp.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { TsmiHelp, TsmiEvaluationInfo, toolStripMenuItem8, TsmiUpdate, infoToolStripMenuItem, TsmiAbout });
+ TsmiRootHelp.Name = "TsmiRootHelp";
+ resources.ApplyResources(TsmiRootHelp, "TsmiRootHelp");
//
// TsmiHelp
//
- this.TsmiHelp.Image = global::Vocup.Properties.Icons.Help;
- this.TsmiHelp.Name = "TsmiHelp";
- resources.ApplyResources(this.TsmiHelp, "TsmiHelp");
- this.TsmiHelp.Click += new System.EventHandler(this.TsmiHelp_Click);
+ TsmiHelp.Image = Properties.Icons.Help;
+ TsmiHelp.Name = "TsmiHelp";
+ resources.ApplyResources(TsmiHelp, "TsmiHelp");
+ TsmiHelp.Click += TsmiHelp_Click;
//
// TsmiEvaluationInfo
//
- this.TsmiEvaluationInfo.Image = global::Vocup.Properties.Icons.Info;
- this.TsmiEvaluationInfo.Name = "TsmiEvaluationInfo";
- resources.ApplyResources(this.TsmiEvaluationInfo, "TsmiEvaluationInfo");
- this.TsmiEvaluationInfo.Click += new System.EventHandler(this.TsmiEvaluationInfo_Click);
+ TsmiEvaluationInfo.Image = Properties.Icons.Info;
+ TsmiEvaluationInfo.Name = "TsmiEvaluationInfo";
+ resources.ApplyResources(TsmiEvaluationInfo, "TsmiEvaluationInfo");
+ TsmiEvaluationInfo.Click += TsmiEvaluationInfo_Click;
//
// TsmiUpdate
//
- this.TsmiUpdate.Image = global::Vocup.Properties.Icons.Update;
- this.TsmiUpdate.Name = "TsmiUpdate";
- resources.ApplyResources(this.TsmiUpdate, "TsmiUpdate");
- this.TsmiUpdate.Click += new System.EventHandler(this.TsmiUpdate_Click);
+ TsmiUpdate.Image = Properties.Icons.Update;
+ TsmiUpdate.Name = "TsmiUpdate";
+ resources.ApplyResources(TsmiUpdate, "TsmiUpdate");
+ TsmiUpdate.Click += TsmiUpdate_Click;
//
// TsmiAbout
//
- this.TsmiAbout.Image = global::Vocup.Properties.Icons.Info;
- this.TsmiAbout.Name = "TsmiAbout";
- resources.ApplyResources(this.TsmiAbout, "TsmiAbout");
- this.TsmiAbout.Click += new System.EventHandler(this.TsmiAbout_Click);
+ TsmiAbout.Image = Properties.Icons.Info;
+ TsmiAbout.Name = "TsmiAbout";
+ resources.ApplyResources(TsmiAbout, "TsmiAbout");
+ TsmiAbout.Click += TsmiAbout_Click;
//
// TbSearchWord
//
- resources.ApplyResources(this.TbSearchWord, "TbSearchWord");
- this.TbSearchWord.Name = "TbSearchWord";
- this.TbSearchWord.TextChanged += new System.EventHandler(this.TbSearchWord_TextChanged);
+ resources.ApplyResources(TbSearchWord, "TbSearchWord");
+ TbSearchWord.Name = "TbSearchWord";
+ TbSearchWord.TextChanged += TbSearchWord_TextChanged;
//
// GroupBook
//
- this.GroupBook.Controls.Add(this.BtnBookSettings);
- this.GroupBook.Controls.Add(this.BtnPractice);
- resources.ApplyResources(this.GroupBook, "GroupBook");
- this.GroupBook.Name = "GroupBook";
- this.GroupBook.TabStop = false;
+ GroupBook.Controls.Add(BtnBookSettings);
+ GroupBook.Controls.Add(BtnPractice);
+ resources.ApplyResources(GroupBook, "GroupBook");
+ GroupBook.Name = "GroupBook";
+ GroupBook.TabStop = false;
//
// BtnBookSettings
//
- this.BtnBookSettings.BaseImage = global::Vocup.Properties.Icons.FileSettings;
- resources.ApplyResources(this.BtnBookSettings, "BtnBookSettings");
- this.BtnBookSettings.Name = "BtnBookSettings";
- this.BtnBookSettings.UseVisualStyleBackColor = true;
- this.BtnBookSettings.Click += new System.EventHandler(this.BtnBookSettings_Click);
+ BtnBookSettings.BaseImage = Properties.Icons.FileSettings;
+ resources.ApplyResources(BtnBookSettings, "BtnBookSettings");
+ BtnBookSettings.Name = "BtnBookSettings";
+ BtnBookSettings.UseVisualStyleBackColor = true;
+ BtnBookSettings.Click += BtnBookSettings_Click;
//
// BtnPractice
//
- this.BtnPractice.BaseImage = global::Vocup.Properties.Icons.LightningBolt;
- resources.ApplyResources(this.BtnPractice, "BtnPractice");
- this.BtnPractice.Name = "BtnPractice";
- this.BtnPractice.UseVisualStyleBackColor = true;
- this.BtnPractice.Click += new System.EventHandler(this.BtnPractice_Click);
+ BtnPractice.BaseImage = Properties.Icons.LightningBolt;
+ resources.ApplyResources(BtnPractice, "BtnPractice");
+ BtnPractice.Name = "BtnPractice";
+ BtnPractice.UseVisualStyleBackColor = true;
+ BtnPractice.Click += BtnPractice_Click;
//
// GroupSearch
//
- this.GroupSearch.Controls.Add(this.TbSearchWord);
- this.GroupSearch.Controls.Add(this.BtnSearchWord);
- resources.ApplyResources(this.GroupSearch, "GroupSearch");
- this.GroupSearch.Name = "GroupSearch";
- this.GroupSearch.TabStop = false;
+ GroupSearch.Controls.Add(TbSearchWord);
+ GroupSearch.Controls.Add(BtnSearchWord);
+ resources.ApplyResources(GroupSearch, "GroupSearch");
+ GroupSearch.Name = "GroupSearch";
+ GroupSearch.TabStop = false;
//
// BtnSearchWord
//
- this.BtnSearchWord.BaseImage = global::Vocup.Properties.Icons.Search;
- resources.ApplyResources(this.BtnSearchWord, "BtnSearchWord");
- this.BtnSearchWord.Name = "BtnSearchWord";
- this.BtnSearchWord.UseVisualStyleBackColor = true;
- this.BtnSearchWord.Click += new System.EventHandler(this.BtnSearchWord_Click);
+ BtnSearchWord.BaseImage = Properties.Icons.Search;
+ resources.ApplyResources(BtnSearchWord, "BtnSearchWord");
+ BtnSearchWord.Name = "BtnSearchWord";
+ BtnSearchWord.UseVisualStyleBackColor = true;
+ BtnSearchWord.Click += BtnSearchWord_Click;
//
// GroupWord
//
- this.GroupWord.Controls.Add(this.BtnAddWord);
- this.GroupWord.Controls.Add(this.BtnDeleteWord);
- this.GroupWord.Controls.Add(this.BtnEditWord);
- resources.ApplyResources(this.GroupWord, "GroupWord");
- this.GroupWord.Name = "GroupWord";
- this.GroupWord.TabStop = false;
+ GroupWord.Controls.Add(BtnAddWord);
+ GroupWord.Controls.Add(BtnDeleteWord);
+ GroupWord.Controls.Add(BtnEditWord);
+ resources.ApplyResources(GroupWord, "GroupWord");
+ GroupWord.Name = "GroupWord";
+ GroupWord.TabStop = false;
//
// BtnAddWord
//
- this.BtnAddWord.BaseImage = global::Vocup.Properties.Icons.Plus;
- resources.ApplyResources(this.BtnAddWord, "BtnAddWord");
- this.BtnAddWord.Name = "BtnAddWord";
- this.BtnAddWord.UseVisualStyleBackColor = true;
- this.BtnAddWord.Click += new System.EventHandler(this.BtnAddWord_Click);
+ BtnAddWord.BaseImage = Properties.Icons.Plus;
+ resources.ApplyResources(BtnAddWord, "BtnAddWord");
+ BtnAddWord.Name = "BtnAddWord";
+ BtnAddWord.UseVisualStyleBackColor = true;
+ BtnAddWord.Click += BtnAddWord_Click;
//
// BtnDeleteWord
//
- this.BtnDeleteWord.BaseImage = global::Vocup.Properties.Icons.Delete;
- resources.ApplyResources(this.BtnDeleteWord, "BtnDeleteWord");
- this.BtnDeleteWord.Name = "BtnDeleteWord";
- this.BtnDeleteWord.UseVisualStyleBackColor = true;
- this.BtnDeleteWord.Click += new System.EventHandler(this.BtnDeleteWord_Click);
+ BtnDeleteWord.BaseImage = Properties.Icons.Delete;
+ resources.ApplyResources(BtnDeleteWord, "BtnDeleteWord");
+ BtnDeleteWord.Name = "BtnDeleteWord";
+ BtnDeleteWord.UseVisualStyleBackColor = true;
+ BtnDeleteWord.Click += BtnDeleteWord_Click;
//
// BtnEditWord
//
- this.BtnEditWord.BaseImage = global::Vocup.Properties.Icons.Edit;
- resources.ApplyResources(this.BtnEditWord, "BtnEditWord");
- this.BtnEditWord.Name = "BtnEditWord";
- this.BtnEditWord.UseVisualStyleBackColor = true;
- this.BtnEditWord.Click += new System.EventHandler(this.BtnEditWord_Click);
+ BtnEditWord.BaseImage = Properties.Icons.Edit;
+ resources.ApplyResources(BtnEditWord, "BtnEditWord");
+ BtnEditWord.Name = "BtnEditWord";
+ BtnEditWord.UseVisualStyleBackColor = true;
+ BtnEditWord.Click += BtnEditWord_Click;
//
// StatusStrip
//
- this.StatusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.StatusLbOldVersion});
- resources.ApplyResources(this.StatusStrip, "StatusStrip");
- this.StatusStrip.Name = "StatusStrip";
+ StatusStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { StatusLbOldVersion });
+ resources.ApplyResources(StatusStrip, "StatusStrip");
+ StatusStrip.Name = "StatusStrip";
//
// StatusLbOldVersion
//
- this.StatusLbOldVersion.Image = global::Vocup.Properties.Icons.Warning;
- this.StatusLbOldVersion.Name = "StatusLbOldVersion";
- this.StatusLbOldVersion.Padding = new System.Windows.Forms.Padding(10, 0, 10, 0);
- resources.ApplyResources(this.StatusLbOldVersion, "StatusLbOldVersion");
- this.StatusLbOldVersion.Click += new System.EventHandler(this.StatusLbOldVersion_Click);
+ StatusLbOldVersion.Image = Properties.Icons.Warning;
+ StatusLbOldVersion.Name = "StatusLbOldVersion";
+ StatusLbOldVersion.Padding = new System.Windows.Forms.Padding(10, 0, 10, 0);
+ resources.ApplyResources(StatusLbOldVersion, "StatusLbOldVersion");
+ StatusLbOldVersion.Click += StatusLbOldVersion_Click;
//
// SideBar
//
- this.SideBar.Controls.Add(this.GroupStatistics);
- this.SideBar.Controls.Add(this.GroupBook);
- this.SideBar.Controls.Add(this.GroupWord);
- this.SideBar.Controls.Add(this.GroupSearch);
- resources.ApplyResources(this.SideBar, "SideBar");
- this.SideBar.Name = "SideBar";
+ SideBar.Controls.Add(GroupStatistics);
+ SideBar.Controls.Add(GroupBook);
+ SideBar.Controls.Add(GroupWord);
+ SideBar.Controls.Add(GroupSearch);
+ resources.ApplyResources(SideBar, "SideBar");
+ SideBar.Name = "SideBar";
//
// GroupStatistics
//
- this.GroupStatistics.CorrectlyPracticed = 0;
- this.GroupStatistics.FullyPracticed = 0;
- resources.ApplyResources(this.GroupStatistics, "GroupStatistics");
- this.GroupStatistics.Name = "GroupStatistics";
- this.GroupStatistics.Unpracticed = 0;
- this.GroupStatistics.WronglyPracticed = 0;
+ GroupStatistics.CorrectlyPracticed = 0;
+ resources.ApplyResources(GroupStatistics, "GroupStatistics");
+ GroupStatistics.FullyPracticed = 0;
+ GroupStatistics.Name = "GroupStatistics";
+ GroupStatistics.Unpracticed = 0;
+ GroupStatistics.WronglyPracticed = 0;
//
// SplitContainer
//
- resources.ApplyResources(this.SplitContainer, "SplitContainer");
- this.SplitContainer.FixedPanel = System.Windows.Forms.FixedPanel.Panel1;
- this.SplitContainer.Name = "SplitContainer";
+ resources.ApplyResources(SplitContainer, "SplitContainer");
+ SplitContainer.FixedPanel = System.Windows.Forms.FixedPanel.Panel1;
+ SplitContainer.Name = "SplitContainer";
//
// SplitContainer.Panel1
//
- this.SplitContainer.Panel1.Controls.Add(this.FileTreeView);
+ SplitContainer.Panel1.Controls.Add(FileTreeView);
//
// SplitContainer.Panel2
//
- this.SplitContainer.Panel2.Controls.Add(this.LbEmptyForm);
- this.SplitContainer.SplitterBaseDistance = 150;
+ SplitContainer.Panel2.Controls.Add(LbEmptyForm);
+ SplitContainer.SplitterBaseDistance = 150;
//
// FileTreeView
//
- this.FileTreeView.BrowseButtonVisible = true;
- resources.ApplyResources(this.FileTreeView, "FileTreeView");
- this.FileTreeView.FileFilter = "*.vhf";
- this.FileTreeView.Name = "FileTreeView";
- this.FileTreeView.SelectedPath = null;
- this.FileTreeView.FileSelected += new System.EventHandler(this.FileTreeView_FileSelected);
- this.FileTreeView.BrowseClick += new System.EventHandler(this.FileTreeView_BrowseClick);
+ FileTreeView.BrowseButtonVisible = true;
+ resources.ApplyResources(FileTreeView, "FileTreeView");
+ FileTreeView.FileFilter = "*.vhf";
+ FileTreeView.Name = "FileTreeView";
+ FileTreeView.SelectedPath = null;
+ FileTreeView.FileSelected += FileTreeView_FileSelected;
+ FileTreeView.BrowseClick += FileTreeView_BrowseClick;
//
// LbEmptyForm
//
- this.LbEmptyForm.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
- resources.ApplyResources(this.LbEmptyForm, "LbEmptyForm");
- this.LbEmptyForm.Name = "LbEmptyForm";
+ LbEmptyForm.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
+ resources.ApplyResources(LbEmptyForm, "LbEmptyForm");
+ LbEmptyForm.Name = "LbEmptyForm";
//
// ToolStrip
//
- this.ToolStrip.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden;
- this.ToolStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
- this.TsbCreateBook,
- this.TsbOpenBook,
- this.TsbSave,
- this.toolStripSeparator1,
- this.TsbPrint,
- this.TsbEvaluationInfo,
- this.toolStripLabel});
- resources.ApplyResources(this.ToolStrip, "ToolStrip");
- this.ToolStrip.Name = "ToolStrip";
+ ToolStrip.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden;
+ ToolStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { TsbCreateBook, TsbOpenBook, TsbSave, toolStripSeparator1, TsbPrint, TsbEvaluationInfo, toolStripLabel });
+ resources.ApplyResources(ToolStrip, "ToolStrip");
+ ToolStrip.Name = "ToolStrip";
//
// TsbCreateBook
//
- this.TsbCreateBook.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
- this.TsbCreateBook.Image = global::Vocup.Properties.Icons.File;
- resources.ApplyResources(this.TsbCreateBook, "TsbCreateBook");
- this.TsbCreateBook.Margin = new System.Windows.Forms.Padding(2, 1, 1, 1);
- this.TsbCreateBook.Name = "TsbCreateBook";
- this.TsbCreateBook.Click += new System.EventHandler(this.TsbCreateBook_Click);
+ TsbCreateBook.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
+ TsbCreateBook.Image = Properties.Icons.File;
+ resources.ApplyResources(TsbCreateBook, "TsbCreateBook");
+ TsbCreateBook.Margin = new System.Windows.Forms.Padding(2, 1, 1, 1);
+ TsbCreateBook.Name = "TsbCreateBook";
+ TsbCreateBook.Click += TsbCreateBook_Click;
//
// TsbOpenBook
//
- this.TsbOpenBook.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
- this.TsbOpenBook.Image = global::Vocup.Properties.Icons.Open;
- resources.ApplyResources(this.TsbOpenBook, "TsbOpenBook");
- this.TsbOpenBook.Margin = new System.Windows.Forms.Padding(1);
- this.TsbOpenBook.Name = "TsbOpenBook";
- this.TsbOpenBook.Click += new System.EventHandler(this.TsbOpenBook_Click);
+ TsbOpenBook.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
+ TsbOpenBook.Image = Properties.Icons.Open;
+ resources.ApplyResources(TsbOpenBook, "TsbOpenBook");
+ TsbOpenBook.Margin = new System.Windows.Forms.Padding(1);
+ TsbOpenBook.Name = "TsbOpenBook";
+ TsbOpenBook.Click += TsbOpenBook_Click;
//
// TsbSave
//
- this.TsbSave.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
- resources.ApplyResources(this.TsbSave, "TsbSave");
- this.TsbSave.Image = global::Vocup.Properties.Icons.Save;
- this.TsbSave.Margin = new System.Windows.Forms.Padding(1);
- this.TsbSave.Name = "TsbSave";
- this.TsbSave.Click += new System.EventHandler(this.TsbSave_Click);
+ TsbSave.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
+ resources.ApplyResources(TsbSave, "TsbSave");
+ TsbSave.Image = Properties.Icons.Save;
+ TsbSave.Margin = new System.Windows.Forms.Padding(1);
+ TsbSave.Name = "TsbSave";
+ TsbSave.Click += TsbSave_Click;
//
// toolStripSeparator1
//
- this.toolStripSeparator1.Name = "toolStripSeparator1";
- resources.ApplyResources(this.toolStripSeparator1, "toolStripSeparator1");
+ toolStripSeparator1.Name = "toolStripSeparator1";
+ resources.ApplyResources(toolStripSeparator1, "toolStripSeparator1");
//
// TsbPrint
//
- this.TsbPrint.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
- resources.ApplyResources(this.TsbPrint, "TsbPrint");
- this.TsbPrint.Image = global::Vocup.Properties.Icons.Print;
- this.TsbPrint.Margin = new System.Windows.Forms.Padding(1);
- this.TsbPrint.Name = "TsbPrint";
- this.TsbPrint.Click += new System.EventHandler(this.TsbPrint_Click);
+ TsbPrint.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
+ resources.ApplyResources(TsbPrint, "TsbPrint");
+ TsbPrint.Image = Properties.Icons.Print;
+ TsbPrint.Margin = new System.Windows.Forms.Padding(1);
+ TsbPrint.Name = "TsbPrint";
+ TsbPrint.Click += TsbPrint_Click;
//
// TsbEvaluationInfo
//
- this.TsbEvaluationInfo.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right;
- this.TsbEvaluationInfo.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
- this.TsbEvaluationInfo.Image = global::Vocup.Properties.Icons.Info;
- resources.ApplyResources(this.TsbEvaluationInfo, "TsbEvaluationInfo");
- this.TsbEvaluationInfo.Margin = new System.Windows.Forms.Padding(1, 1, 2, 1);
- this.TsbEvaluationInfo.Name = "TsbEvaluationInfo";
- this.TsbEvaluationInfo.Click += new System.EventHandler(this.TsbtnEvaluationInfo_Click);
+ TsbEvaluationInfo.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right;
+ TsbEvaluationInfo.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image;
+ TsbEvaluationInfo.Image = Properties.Icons.Info;
+ resources.ApplyResources(TsbEvaluationInfo, "TsbEvaluationInfo");
+ TsbEvaluationInfo.Margin = new System.Windows.Forms.Padding(1, 1, 2, 1);
+ TsbEvaluationInfo.Name = "TsbEvaluationInfo";
+ TsbEvaluationInfo.Click += TsbtnEvaluationInfo_Click;
//
// toolStripLabel
//
- this.toolStripLabel.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right;
- this.toolStripLabel.Margin = new System.Windows.Forms.Padding(1);
- this.toolStripLabel.Name = "toolStripLabel";
- resources.ApplyResources(this.toolStripLabel, "toolStripLabel");
+ toolStripLabel.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right;
+ toolStripLabel.Margin = new System.Windows.Forms.Padding(1);
+ toolStripLabel.Name = "toolStripLabel";
+ resources.ApplyResources(toolStripLabel, "toolStripLabel");
//
// TableLayout
//
- resources.ApplyResources(this.TableLayout, "TableLayout");
- this.TableLayout.Controls.Add(this.SideBar, 1, 0);
- this.TableLayout.Controls.Add(this.SplitContainer, 0, 0);
- this.TableLayout.Name = "TableLayout";
+ resources.ApplyResources(TableLayout, "TableLayout");
+ TableLayout.Controls.Add(SideBar, 1, 0);
+ TableLayout.Controls.Add(SplitContainer, 0, 0);
+ TableLayout.Name = "TableLayout";
//
// MainForm
//
- this.AcceptButton = this.BtnAddWord;
+ AcceptButton = BtnAddWord;
resources.ApplyResources(this, "$this");
- this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
- this.Controls.Add(this.TableLayout);
- this.Controls.Add(this.StatusStrip);
- this.Controls.Add(this.ToolStrip);
- this.Controls.Add(this.MenuStrip);
- this.Icon = global::Vocup.Properties.Icons.Icon;
- this.MainMenuStrip = this.MenuStrip;
- this.Name = "MainForm";
- this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Form_FormClosing);
- this.Load += new System.EventHandler(this.Form_Load);
- this.Shown += new System.EventHandler(this.Form_Shown);
- this.MenuStrip.ResumeLayout(false);
- this.MenuStrip.PerformLayout();
- this.GroupBook.ResumeLayout(false);
- this.GroupSearch.ResumeLayout(false);
- this.GroupSearch.PerformLayout();
- this.GroupWord.ResumeLayout(false);
- this.StatusStrip.ResumeLayout(false);
- this.StatusStrip.PerformLayout();
- this.SideBar.ResumeLayout(false);
- this.SplitContainer.Panel1.ResumeLayout(false);
- this.SplitContainer.Panel2.ResumeLayout(false);
- ((System.ComponentModel.ISupportInitialize)(this.SplitContainer)).EndInit();
- this.SplitContainer.ResumeLayout(false);
- this.ToolStrip.ResumeLayout(false);
- this.ToolStrip.PerformLayout();
- this.TableLayout.ResumeLayout(false);
- this.ResumeLayout(false);
- this.PerformLayout();
-
+ AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ Controls.Add(TableLayout);
+ Controls.Add(StatusStrip);
+ Controls.Add(ToolStrip);
+ Controls.Add(MenuStrip);
+ Icon = Properties.Icons.Icon;
+ MainMenuStrip = MenuStrip;
+ Name = "MainForm";
+ FormClosing += Form_FormClosing;
+ Load += Form_Load;
+ Shown += Form_Shown;
+ MenuStrip.ResumeLayout(false);
+ MenuStrip.PerformLayout();
+ GroupBook.ResumeLayout(false);
+ GroupSearch.ResumeLayout(false);
+ GroupSearch.PerformLayout();
+ GroupWord.ResumeLayout(false);
+ StatusStrip.ResumeLayout(false);
+ StatusStrip.PerformLayout();
+ SideBar.ResumeLayout(false);
+ SplitContainer.Panel1.ResumeLayout(false);
+ SplitContainer.Panel2.ResumeLayout(false);
+ ((System.ComponentModel.ISupportInitialize)SplitContainer).EndInit();
+ SplitContainer.ResumeLayout(false);
+ ToolStrip.ResumeLayout(false);
+ ToolStrip.PerformLayout();
+ TableLayout.ResumeLayout(false);
+ ResumeLayout(false);
+ PerformLayout();
}
#endregion
@@ -699,6 +662,7 @@ private void InitializeComponent()
private Controls.FileTreeView FileTreeView;
private System.Windows.Forms.Label LbEmptyForm;
private System.Windows.Forms.ToolStripStatusLabel StatusLbOldVersion;
+ private System.Windows.Forms.ToolStripMenuItem TsmiShare;
}
}
diff --git a/src/Vocup/MainForm.cs b/src/Vocup/MainForm.cs
index e74ca7f..1b90c9c 100644
--- a/src/Vocup/MainForm.cs
+++ b/src/Vocup/MainForm.cs
@@ -52,7 +52,7 @@ public void VocabularyBookLoaded(bool value)
TsmiBookOptions.Enabled = value;
TsmiCloseBook.Enabled = value;
TsmiSaveAs.Enabled = value;
- StatisticsPanel.Enabled = value;
+ StatisticsPanel.Enabled = value;
}
public void VocabularyBookHasContent(bool value)
{
@@ -63,6 +63,7 @@ public void VocabularyBookHasContent(bool value)
TsbPrint.Enabled = value;
TsmiExport.Enabled = value;
+ TsmiShare.Enabled = value;
}
public void VocabularyBookPracticable(bool value)
{
@@ -356,6 +357,38 @@ private async void TsmiUpdate_Click(object sender, EventArgs e)
private void TsmiSaveAs_Click(object sender, EventArgs e) => SaveFile(true);
private void TsbSave_Click(object sender, EventArgs e) => SaveFile(false);
+ private void TsmiShare_Click(object sender, EventArgs e)
+ {
+ using SaveFileDialog save = new()
+ {
+ Title = Words.ShareVocabularyBook,
+ FileName = CurrentBook.MotherTongue + " - " + CurrentBook.ForeignLang,
+ InitialDirectory = Program.Settings.VhfPath,
+ Filter = $"{Words.FileFormatVhf2} (*.vhf)|*.vhf|{Words.FileFormatVhf1} (*.vhf)|*.vhf"
+ };
+ if (save.ShowDialog() == DialogResult.OK)
+ {
+ BookFileFormat format = save.FilterIndex == 2 ? BookFileFormat.Vhf1 : BookFileFormat.Vhf2;
+
+ if (format.TryWrite(save.FileName, CurrentBook, Program.Settings.VhrPath, includeResults: false))
+ {
+ try
+ {
+ string explorer = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "explorer.exe");
+ Process.Start(explorer, $"/select,\"{save.FileName}\"");
+ }
+ catch (Exception ex)
+ {
+ MessageBox.Show(string.Format(Messages.OpenInExplorerError, ex), Messages.OpenInExplorerErrorT, MessageBoxButtons.OK, MessageBoxIcon.Error);
+ }
+ }
+ }
+ else
+ {
+ return;
+ }
+ }
+
private void BtnPractice_Click(object sender, EventArgs e) => PracticeWords();
private void TsmiPractice_Click(object sender, EventArgs e) => PracticeWords();
@@ -430,17 +463,15 @@ private void TsmiExport_Click(object sender, EventArgs e)
}
}
- using (SaveFileDialog saveDialog = new SaveFileDialog
+ using SaveFileDialog saveDialog = new SaveFileDialog
{
Title = Words.Export,
Filter = "CSV (*.csv)|*.csv",
FileName = CurrentBook.Name
- })
+ };
+ if (saveDialog.ShowDialog() == DialogResult.OK)
{
- if (saveDialog.ShowDialog() == DialogResult.OK)
- {
- VocabularyFile.ExportCsvFile(saveDialog.FileName, CurrentBook);
- }
+ CsvFile.Export(saveDialog.FileName, CurrentBook);
}
}
@@ -496,9 +527,9 @@ private async void BtnSearchWord_Click(object sender, EventArgs e)
{
int index = CurrentBook.Words.NextIndexOf(word =>
{
- return word.MotherTongue.ToUpper().Contains(search_text)
- || word.ForeignLang.ToUpper().Contains(search_text)
- || (word.ForeignLangSynonym?.ToUpper().Contains(search_text) ?? false);
+ return word.MotherTongue.Contains(search_text, StringComparison.OrdinalIgnoreCase)
+ || word.ForeignLang.Contains(search_text, StringComparison.OrdinalIgnoreCase)
+ || (word.ForeignLangSynonym?.Contains(search_text, StringComparison.OrdinalIgnoreCase) ?? false);
}, lastSearchResult);
if (index != -1)
@@ -540,11 +571,10 @@ private void TbSearchWord_TextChanged(object sender, EventArgs e)
#region Utility methods
public void ReadFile(string path)
{
- VocabularyBook book = new VocabularyBook();
+ VocabularyBook book = new();
- if (VocabularyFile.ReadVhfFile(path, book))
+ if (BookFileFormat.TryDetectAndRead(path, book, Program.Settings.VhrPath))
{
- VocabularyFile.ReadVhrFile(book);
book.Notify();
LoadBook(book);
}
@@ -570,35 +600,32 @@ private bool EnsureSaved()
private bool SaveFile(bool saveAsNewFile)
{
- //Datei-Speichern-unter Dialogfeld öffnen
- if (string.IsNullOrWhiteSpace(CurrentBook.FilePath) ||
- string.IsNullOrWhiteSpace(CurrentBook.VhrCode) ||
- saveAsNewFile)
+ BookFileFormat format = BookFileFormat.Vhf2;
+
+ if (string.IsNullOrWhiteSpace(CurrentBook.FilePath) || saveAsNewFile)
{
- using (SaveFileDialog save = new SaveFileDialog
+ using SaveFileDialog save = new()
{
Title = Words.SaveVocabularyBook,
FileName = CurrentBook.MotherTongue + " - " + CurrentBook.ForeignLang,
InitialDirectory = Program.Settings.VhfPath,
- Filter = Words.VocupVocabularyBookFile + " (*.vhf)|*.vhf"
- })
+ Filter = $"{Words.FileFormatVhf2} (*.vhf)|*.vhf|{Words.FileFormatVhf1} (*.vhf)|*.vhf"
+ };
+ if (save.ShowDialog() == DialogResult.OK)
{
- if (save.ShowDialog() == DialogResult.OK)
- {
- CurrentBook.FilePath = save.FileName;
- CurrentBook.GenerateVhrCode();
- }
- else
- {
- return false;
- }
+ CurrentBook.FilePath = save.FileName;
+ CurrentBook.GenerateVhrCode();
+ format = save.FilterIndex == 2 ? BookFileFormat.Vhf1 : BookFileFormat.Vhf2;
+ }
+ else
+ {
+ return false;
}
}
Cursor.Current = Cursors.WaitCursor;
- if (VocabularyFile.WriteVhfFile(CurrentBook.FilePath, CurrentBook) &&
- VocabularyFile.WriteVhrFile(CurrentBook))
+ if (format.TryWrite(CurrentBook.FilePath, CurrentBook, Program.Settings.VhrPath))
{
CurrentBook.UnsavedChanges = false;
@@ -616,79 +643,73 @@ private bool SaveFile(bool saveAsNewFile)
private void OpenFile()
{
- using (OpenFileDialog open = new OpenFileDialog
+ using OpenFileDialog open = new()
{
Title = Words.OpenVocabularyBook,
InitialDirectory = Program.Settings.VhfPath,
- Filter = Words.VocupVocabularyBookFile + " (*.vhf)|*.vhf"
- })
+ Filter = Words.FileFormatVhf + " (*.vhf)|*.vhf"
+ };
+ if (open.ShowDialog() == DialogResult.OK)
{
- if (open.ShowDialog() == DialogResult.OK)
+ if (CurrentBook != null)
{
- if (CurrentBook != null)
- {
- if (UnsavedChanges && !EnsureSaved())
- return;
+ if (UnsavedChanges && !EnsureSaved())
+ return;
- UnloadBook(false);
- }
-
- ReadFile(open.FileName);
+ UnloadBook(false);
}
+
+ ReadFile(open.FileName);
}
}
private void CreateBook()
{
- using (VocabularyBookSettings dialog = new VocabularyBookSettings(out VocabularyBook book))
+ using VocabularyBookSettings dialog = new(out VocabularyBook book);
+ if (dialog.ShowDialog() == DialogResult.OK)
{
- if (dialog.ShowDialog() == DialogResult.OK)
+ if (CurrentBook != null)
{
- if (CurrentBook != null)
- {
- if (UnsavedChanges && !EnsureSaved())
- return;
+ if (UnsavedChanges && !EnsureSaved())
+ return;
- UnloadBook(false);
- }
+ UnloadBook(false);
+ }
- Program.TrackingService.Action("/book/new", "Book/Create");
+ Program.TrackingService.Action("/book/new", "Book/Create");
- // VocabularyBookSettings enables notification on creation
- LoadBook(book);
+ // VocabularyBookSettings enables notification on creation
+ LoadBook(book);
- BtnAddWord.Focus();
- }
+ BtnAddWord.Focus();
}
}
private void ImportCsv()
{
- using (OpenFileDialog openDialog = new OpenFileDialog
+ using OpenFileDialog openDialog = new()
{
Title = Words.Import,
Filter = $"CSV (*.csv)|*.csv"
- })
+ };
+ if (openDialog.ShowDialog() == DialogResult.OK)
{
- if (openDialog.ShowDialog() == DialogResult.OK)
+ if (CurrentBook != null)
{
- if (CurrentBook != null)
- {
- Program.TrackingService.Action("/book", "Book/Import");
+ Program.TrackingService.Action("/book", "Book/Import");
- VocabularyFile.ImportCsvFile(openDialog.FileName, CurrentBook, false);
- }
- else
- {
- Program.TrackingService.Action("/book/new", "Book/Import");
+ CsvFile.Import(openDialog.FileName, CurrentBook, false);
+ }
+ else
+ {
+ Program.TrackingService.Action("/book/new", "Book/Import");
- VocabularyBook book = new VocabularyBook();
- if (VocabularyFile.ImportCsvFile(openDialog.FileName, book, true))
- {
- book.Notify();
- book.UnsavedChanges = true;
- LoadBook(book);
- }
+ VocabularyBook book = new VocabularyBook();
+ if (CsvFile.Import(openDialog.FileName, book, true))
+ {
+ book.Notify();
+ book.UnsavedChanges = true;
+ LoadBook(book);
}
}
}
diff --git a/src/Vocup/MainForm.de.resx b/src/Vocup/MainForm.de.resx
index 2ad5c4b..83ac89e 100644
--- a/src/Vocup/MainForm.de.resx
+++ b/src/Vocup/MainForm.de.resx
@@ -250,4 +250,7 @@
Vocup
+
+ Teilen ohne Übungsergebnisse
+
\ No newline at end of file
diff --git a/src/Vocup/MainForm.nl.resx b/src/Vocup/MainForm.nl.resx
index 3839b1e..34a355e 100644
--- a/src/Vocup/MainForm.nl.resx
+++ b/src/Vocup/MainForm.nl.resx
@@ -250,4 +250,7 @@ Open een woordenboek of maak een nieuwe aan.
Vocup
+
+ Delen zonder oefenresultaten
+
\ No newline at end of file
diff --git a/src/Vocup/MainForm.resx b/src/Vocup/MainForm.resx
index 699d384..8066350 100644
--- a/src/Vocup/MainForm.resx
+++ b/src/Vocup/MainForm.resx
@@ -1,4 +1,64 @@
+
+
@@ -62,37 +122,37 @@
- 273, 6
+ 275, 6
False
- 273, 6
+ 275, 6
False
- 273, 6
+ 275, 6
False
- 277, 6
+ 330, 6
False
- 277, 6
+ 330, 6
False
- 277, 6
+ 330, 6
False
@@ -111,19 +171,19 @@
- Control+N
+ Ctrl+N
- 276, 22
+ 278, 22
Create new vocabulary book...
- Control+O
+ Ctrl+O
- 276, 22
+ 278, 22
Open vocabulary book...
@@ -133,10 +193,10 @@
False
- Control+X
+ Ctrl+X
- 276, 22
+ 278, 22
Close vocabulary book
@@ -145,10 +205,10 @@
False
- Control+S
+ Ctrl+S
- 276, 22
+ 278, 22
Save
@@ -160,16 +220,25 @@
Alt+S
- 276, 22
+ 278, 22
Save As
+
+ False
+
+
+ 278, 22
+
+
+ Share without practice results
+
- Control+I
+ Ctrl+I
- 276, 22
+ 278, 22
Import
@@ -178,10 +247,10 @@
False
- Control+E
+ Ctrl+E
- 276, 22
+ 278, 22
Export
@@ -190,37 +259,37 @@
False
- Control+M
+ Ctrl+M
- 276, 22
+ 278, 22
Open in Explorer
- 273, 6
+ 275, 6
False
- Control+P
+ Ctrl+P
- 276, 22
+ 278, 22
Print
- 273, 6
+ 275, 6
Alt+F4
- 276, 22
+ 278, 22
Exit
@@ -231,6 +300,48 @@
File
+
+ 39, 20
+
+
+ Edit
+
+
+ Ctrl+Shift+O
+
+
+ 46, 20
+
+
+ Tools
+
+
+ 44, 20
+
+
+ Help
+
+
+ 0, 0
+
+
+ 784, 24
+
+
+ 0
+
+
+ MenuStrip
+
+
+ Vocup.Controls.ResponsiveMenuStrip, Vocup, Culture=neutral, PublicKeyToken=null
+
+
+ $this
+
+
+ 3
+
False
@@ -247,7 +358,7 @@
False
- Control+R
+ Ctrl+R
170, 22
@@ -259,7 +370,7 @@
False
- Delete
+ Del
170, 22
@@ -267,20 +378,14 @@
Delete word
-
- 39, 20
-
-
- Edit
-
False
- Control+U
+ Ctrl+U
- 280, 22
+ 333, 22
Practice words
@@ -289,19 +394,19 @@
False
- Control+Shift+O
+ Ctrl+Shift+O
- 280, 22
+ 333, 22
Vocabulary book options
- Control+Z
+ Ctrl+Z
- 280, 22
+ 333, 22
Merge vocabulary books
@@ -310,29 +415,20 @@
Alt+C
- 280, 22
+ 333, 22
Manage special chars
- Control+Alt+O
+ Ctrl+Alt+O
- 280, 22
+ 333, 22
Settings
-
- Control+Shift+O
-
-
- 46, 20
-
-
- Tools
-
F1
@@ -369,33 +465,6 @@
About Vocup
-
- 44, 20
-
-
- Help
-
-
- 0, 0
-
-
- 784, 24
-
-
- 0
-
-
- MenuStrip
-
-
- Vocup.Controls.ResponsiveMenuStrip, Vocup, Culture=neutral, PublicKeyToken=null
-
-
- $this
-
-
- 3
-
10, 43
@@ -417,10 +486,61 @@
0
+
+ BtnBookSettings
+
+
+ Vocup.Controls.ResponsiveButton, Vocup, Culture=neutral, PublicKeyToken=null
+
+
+ GroupBook
+
+
+ 0
+
+
+ BtnPractice
+
+
+ Vocup.Controls.ResponsiveButton, Vocup, Culture=neutral, PublicKeyToken=null
+
+
+ GroupBook
+
+
+ 1
+
+
+ False
+
+
+ 2, 0
+
+
+ 120, 88
+
+
+ 0
+
+
+ Vocabulary book:
+
+
+ GroupBook
+
+
+ System.Windows.Forms.GroupBox, System.Windows.Forms, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ SideBar
+
+
+ 1
+
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
- vgAADr4B6kKxwAAAAi5JREFUOE+Nkt1PUmEcx7noX+m6izavvOjG8qJLvWiNzRO1tRVrAkWDjDkgZHPY
+ vAAADrwBlbxySQAAAi5JREFUOE+Nkt1PUmEcx7noX+m6izavvOjG8qJLvWiNzRO1tRVrAkWDjDkgZHPY
kpfly0kXkZ3yJdwMGaE4g2ODASfsQLDhMJsVRDPTWOG353gexyqpPtv34nfO8/mec57zKA7Q97PnDI5x
9nLPAKs2NSLNBsc99qp96AZdejgai5ubjOXxXNxAQGhEmj1TEVjuPsEVs9tFl/+JusfBhsQPKNaB7NdG
ij8A1+QCwtEEuCCP80bHIFV+RXpd6YmSlK40kt0GBnzzEMQ8JCYCUah7nbdNJtMRqso0K8iR+c5EEHb3
@@ -465,7 +585,7 @@
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
- vgAADr4B6kKxwAAAAfdJREFUOE+V0V1IU2Ecx/FDQTeFN915X1A35oUo3XSh1lCnq700LyIsKfAmQgRB
+ vAAADrwBlbxySQAAAfdJREFUOE+V0V1IU2Ecx/FDQTeFN915X1A35oUo3XSh1lCnq700LyIsKfAmQgRB
bDGtidl2toU1s+Vbu5i9UO34sjW3DnOyI+pKIqfuwuHUEJ3Hl7YF9ushni6izlofODf/832eh/McJhOv
qawhaCm2e9plP58xc5U91K3p5x+qTtJEmlt/6tiM4zq+xXkkIyySUSN2p27BZ6laGTLLcmgmbeRuEbsd
GQLwFdh7BqSfI+a5ieE7JRqaSHvZmHt0vKs6gbRIFnuAnQGkoizeGeVhnY45QDNpI/qC+vWpPnL6DrD5
@@ -506,32 +626,44 @@
1
-
+
+ BtnSearchWord
+
+
+ Vocup.Controls.ResponsiveButton, Vocup, Culture=neutral, PublicKeyToken=null
+
+
+ GroupSearch
+
+
+ 1
+
+
False
-
- 2, 0
+
+ 2, 234
-
- 120, 88
+
+ 120, 100
-
- 0
+
+ 2
-
- Vocabulary book:
+
+ Search for vocabulary words:
-
- GroupBook
+
+ GroupSearch
-
+
System.Windows.Forms.GroupBox, System.Windows.Forms, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
+
SideBar
-
- 1
+
+ 3
False
@@ -539,7 +671,7 @@
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
- vgAADr4B6kKxwAAAAhJJREFUOE9jwAdCQ0PZsqr6/PMaJnXn1k+cB8QNGZV9tlBp/CCrusemrGvOpenr
+ vAAADrwBlbxySQAAAhJJREFUOE9jwAdCQ0PZsqr6/PMaJnXn1k+cB8QNGZV9tlBp/CCrusemrGvOpenr
DvzfdObu/52Xn/5fd/zm/95FW/4XtU7bUlDVJwlVigkyq3usaict/rb72ov/1z///3/jKwTfBOIrH/7/
X3/i9v+S9hnXcysnikK1IEB8fT1HUevUG3uuvfx/+/v//5fe/f9/8S0CXwYacAsovvrI9f+5dRPmQ7Uh
QFpld9CM9QfBNqNrhuErH///P//qz/+6SUt+QbUhQE5174TNZx6AnYtNMwxf//L//5xNR/9DtSFATm3/
@@ -581,37 +713,73 @@
1
-
- False
+
+ BtnAddWord
-
- 2, 234
+
+ Vocup.Controls.ResponsiveButton, Vocup, Culture=neutral, PublicKeyToken=null
-
- 120, 100
+
+ GroupWord
-
+
+ 0
+
+
+ BtnDeleteWord
+
+
+ Vocup.Controls.ResponsiveButton, Vocup, Culture=neutral, PublicKeyToken=null
+
+
+ GroupWord
+
+
+ 1
+
+
+ BtnEditWord
+
+
+ Vocup.Controls.ResponsiveButton, Vocup, Culture=neutral, PublicKeyToken=null
+
+
+ GroupWord
+
+
2
-
- Search for vocabulary words:
+
+ False
-
- GroupSearch
+
+ 2, 103
-
+
+ 120, 115
+
+
+ 1
+
+
+ Word:
+
+
+ GroupWord
+
+
System.Windows.Forms.GroupBox, System.Windows.Forms, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
+
SideBar
-
- 3
+
+ 2
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
- vgAADr4B6kKxwAAAAqFJREFUOE99Uu1LU3EUviDUx4roU/QPVFCfCoJeCAJrNHV6jZaxosZevFuKL2Pl
+ vAAADrwBlbxySQAAAqFJREFUOE99Uu1LU3EUviDUx4roU/QPVFCfCoJeCAJrNHV6jZaxosZevFuKL2Pl
siKRcvfeubvmZnupZQlatnSGuuZ23XyLfak+hIahTR0U9BcYp9/53WsqVAcO9+Hec55znvNc5m+hFa0n
dI9sTpLdlRLn13qtdo3HfFD9/O/QkcaLoaZcY/wBeN/HIPKxH0If+sA9FQFr7921C4G63jK3ZZ9avjXK
PVyNsce19vTTK0gVMiCvZCFXnIRsMUfx2FIKfPkeqAk3L1fw3CG1TYmyTssx43PXr4H5Yciu5iC5NE4z
@@ -661,7 +829,7 @@
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
- vgAADr4B6kKxwAAAAa5JREFUOE9jGFygnoGB7bC1dRSUiwFWamkJAeXDoFxUsF5fX+Cav/+qPxkZ/894
+ vAAADrwBlbxySQAAAa5JREFUOE9jGFygnoGB7bC1dRSUiwFWamkJAeXDoFxUsF5fX+Cav/+qPxkZ/894
ePRCheGgmIGB+2ZAwP6faWn/z3l5VZwxNmaFSkHAATu7qP/p6f//V1T8/5uV9f+Uq2sPVAqs+Zqf357/
hYVg+UdhYf93GBtrQKUhYCbQxNPu7t1/MjP//y8thRnS3sPDI3LJx2cvWDNQ/GVs7L9jjo44vclwAmjz
X6gh71NS/l/283v8NzcXzH8VF/d/r41NJFQpbnDSxaXtU0rKr78gW4H4b0HB/9dxce/2WVmFQ5UQBCw3
@@ -707,7 +875,7 @@
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
- vgAADr4B6kKxwAAAAW5JREFUOE9joDu4ZGHi8jAyctYhJ6cIqBDxIK+hO3lrY9f//9Om/v9cXv7/gJmZ
+ vAAADrwBlbxySQAAAW5JREFUOE9joDu4ZGHi8jAyctYhJ6cIqBDxIK+hO3lrY9f//9Om/v9cXv7/gJmZ
LVSKMChv70/ac+bW/wM3X/3fP23e//+tLf+P29qGQ6Xxg9bK0Jhl24/9f/7t//8nX/7/P3Dt+f/5GYUH
n4uLc0OV4AZrOs3Dn12q+3vq1Jr/O08/+H//PdAHyzac5K9oF4QqwQ3W9AA1X6j/9//P2v//vy75f/bs
mv8d05cdUzJW4ocqwQ3gmn8BNX9e+f//9yX/L2yN322lzsALVYIbYGj+tuT/mfUxu30YGLigSnADbDaf
@@ -746,45 +914,9 @@
2
-
- False
-
-
- 2, 103
-
-
- 120, 115
-
-
- 1
-
-
- Word:
-
-
- GroupWord
-
-
- System.Windows.Forms.GroupBox, System.Windows.Forms, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- SideBar
-
-
- 2
-
0, 28
-
- 230, 17
-
-
- An incompatible version was found
-
-
- False
-
0, 539
@@ -806,25 +938,18 @@
1
-
- 2, 349
-
-
-
- 4, 3, 4, 3
+
+ 230, 17
-
- 120, 134
+
+ An incompatible version was found
-
- 3
+
+ False
GroupStatistics
-
- False
-
Vocup.Controls.StatisticsPanel, Vocup, Culture=neutral, PublicKeyToken=null
@@ -858,6 +983,34 @@
0
+
+ False
+
+
+ 2, 349
+
+
+
+ 4, 3, 4, 3
+
+
+ 120, 134
+
+
+ 3
+
+
+ GroupStatistics
+
+
+ Vocup.Controls.StatisticsPanel, Vocup, Culture=neutral, PublicKeyToken=null
+
+
+ SideBar
+
+
+ 0
+
Fill
@@ -970,6 +1123,27 @@ Just open a vocabulary book or create a new one.
0, 24
+
+ 0, 24
+
+
+ 784, 25
+
+
+ 1
+
+
+ ToolStrip
+
+
+ Vocup.Controls.ResponsiveToolStrip, Vocup, Culture=neutral, PublicKeyToken=null
+
+
+ $this
+
+
+ 2
+
Magenta
@@ -1030,27 +1204,6 @@ Just open a vocabulary book or create a new one.
Information about evaluation:
-
- 0, 24
-
-
- 784, 25
-
-
- 1
-
-
- ToolStrip
-
-
- Vocup.Controls.ResponsiveToolStrip, Vocup, Culture=neutral, PublicKeyToken=null
-
-
- $this
-
-
- 2
-
2
@@ -1192,6 +1345,12 @@ Just open a vocabulary book or create a new one.
System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+ TsmiShare
+
+
+ System.Windows.Forms.ToolStripMenuItem, System.Windows.Forms, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
TsmiImport
diff --git a/src/Vocup/Models/VocabularyBookController.cs b/src/Vocup/Models/VocabularyBookController.cs
index a734298..c20c4be 100644
--- a/src/Vocup/Models/VocabularyBookController.cs
+++ b/src/Vocup/Models/VocabularyBookController.cs
@@ -68,8 +68,7 @@ private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
if (VocabularyBook.UnsavedChanges && Program.Settings.AutoSave && !string.IsNullOrWhiteSpace(VocabularyBook.FilePath))
{
- if (VocabularyFile.WriteVhfFile(VocabularyBook.FilePath, VocabularyBook) &&
- VocabularyFile.WriteVhrFile(VocabularyBook))
+ if (BookFileFormat.Vhf2.TryWrite(VocabularyBook.FilePath, VocabularyBook, Program.Settings.VhrPath))
{
VocabularyBook.UnsavedChanges = false;
}
diff --git a/src/Vocup/Program.cs b/src/Vocup/Program.cs
index b052adc..fff3db2 100644
--- a/src/Vocup/Program.cs
+++ b/src/Vocup/Program.cs
@@ -32,12 +32,6 @@ private static void Main(string[] args)
// Prevents the installer from executing while the program is running
mutex = new Mutex(initiallyOwned: true, AppInfo.ProductName, out bool createdNew);
- // ApplicationConfiguration.Initialize() does not handle PerMonitorV2 correctly.
- Application.EnableVisualStyles();
- Application.SetCompatibleTextRenderingDefault(false);
- Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
- Application.SetDefaultFont(new Font("Microsoft Sans Serif", 8.25f));
-
if (!createdNew)
{
// Another instance of Vocup is already running so we change the focus
@@ -45,7 +39,13 @@ private static void Main(string[] args)
return;
}
- SplashScreen splash = new SplashScreen();
+ // ApplicationConfiguration.Initialize() does not handle PerMonitorV2 correctly.
+ Application.EnableVisualStyles();
+ Application.SetCompatibleTextRenderingDefault(false);
+ Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
+ Application.SetDefaultFont(new Font("Microsoft Sans Serif", 8.25f));
+
+ SplashScreen splash = new();
splash.Show();
Application.DoEvents();
diff --git a/src/Vocup/Properties/Icons.Designer.cs b/src/Vocup/Properties/Icons.Designer.cs
index dba1aeb..3c8b949 100644
--- a/src/Vocup/Properties/Icons.Designer.cs
+++ b/src/Vocup/Properties/Icons.Designer.cs
@@ -370,6 +370,16 @@ internal static System.Drawing.Bitmap Settings {
}
}
+ ///
+ /// Looks up a localized resource of type System.Drawing.Bitmap.
+ ///
+ internal static System.Drawing.Bitmap Share {
+ get {
+ object obj = ResourceManager.GetObject("Share", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
///
/// Looks up a localized resource of type System.Drawing.Bitmap.
///
diff --git a/src/Vocup/Properties/Icons.resx b/src/Vocup/Properties/Icons.resx
index 836a57b..0872305 100644
--- a/src/Vocup/Properties/Icons.resx
+++ b/src/Vocup/Properties/Icons.resx
@@ -380,4 +380,7 @@
..\icon.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+ ..\Resources\share.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
\ No newline at end of file
diff --git a/src/Vocup/Properties/Messages.Designer.cs b/src/Vocup/Properties/Messages.Designer.cs
index f63d528..792d0a7 100644
--- a/src/Vocup/Properties/Messages.Designer.cs
+++ b/src/Vocup/Properties/Messages.Designer.cs
@@ -101,7 +101,7 @@ internal static string CsvExportError {
}
///
- /// Looks up a localized string similar to Do you want to save your changes before exporting a CSV file? You can save your changes later on. Vocup always exports the current version including any unsafed changes..
+ /// Looks up a localized string similar to Do you want to save your changes before exporting a CSV file? You can save your changes later on. Vocup always exports the current version including any unsaved changes..
///
internal static string CsvExportSave {
get {
@@ -268,7 +268,7 @@ internal static string MutexLockedButNoOtherProcessT {
}
///
- /// Looks up a localized string similar to The current vocabulary book could not be opened in Explorer. Please be aware that this feature is not avalaible on some operatening systems like Linux or Windows 10 S.
+ /// Looks up a localized string similar to The current vocabulary book could not be opened in Explorer. Please be aware that this feature is not avalaible on some operating systems like Linux or Windows 10 S.
///Details: {0}.
///
internal static string OpenInExplorerError {
@@ -433,42 +433,21 @@ internal static string UnexpectedErrorT {
}
///
- /// Looks up a localized string similar to The Vocup backup file is corrupt and could not be opened:
- ///{0}.
- ///
- internal static string VdpCorruptFile {
- get {
- return ResourceManager.GetString("VdpCorruptFile", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Corrupt file.
- ///
- internal static string VdpCorruptFileT {
- get {
- return ResourceManager.GetString("VdpCorruptFileT", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Summary
- /// - {0} File(s) restored
- /// - {1} File(s) skipped
- /// - {2} File(s) failed.
+ /// Looks up a localized string similar to The vocabulary book file was saved by a later version of Vocup. It will be opened in compatibility mode. Not all information will be visible.
+ ///It is recommended that you update Vocup to the latest version..
///
- internal static string VdpRestored {
+ internal static string VhfCompatMode {
get {
- return ResourceManager.GetString("VdpRestored", resourceCulture);
+ return ResourceManager.GetString("VhfCompatMode", resourceCulture);
}
}
///
- /// Looks up a localized string similar to Recovery finished.
+ /// Looks up a localized string similar to Compatibility mode.
///
- internal static string VdpRestoredT {
+ internal static string VhfCompatModeT {
get {
- return ResourceManager.GetString("VdpRestoredT", resourceCulture);
+ return ResourceManager.GetString("VhfCompatModeT", resourceCulture);
}
}
@@ -557,7 +536,7 @@ internal static string VhfPathInvalid {
}
///
- /// Looks up a localized string similar to The folder for vocabulary books was not found at {0}. Vocup will replace your configuration with the default documents directory. You change that in the settings at any time..
+ /// Looks up a localized string similar to The folder for vocabulary books was not found at {0}. Vocup will replace your configuration with the default documents directory. You can change that in the settings at any time..
///
internal static string VhfPathNotFound {
get {
@@ -574,42 +553,6 @@ internal static string VhfPathNotFoundT {
}
}
- ///
- /// Looks up a localized string similar to The result file is corrupt and going to be deleted. Therefore you are going to loose your practice results..
- ///
- internal static string VhrCorruptFile {
- get {
- return ResourceManager.GetString("VhrCorruptFile", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to Corrupt result file.
- ///
- internal static string VhrCorruptFileT {
- get {
- return ResourceManager.GetString("VhrCorruptFileT", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to The referenced result file does not match the current vocabulary book. All results are going to be deleted..
- ///
- internal static string VhrInvalidRowCount {
- get {
- return ResourceManager.GetString("VhrInvalidRowCount", resourceCulture);
- }
- }
-
- ///
- /// Looks up a localized string similar to The referenced result file is in use by another vocabulary book. Results are not going to be loaded..
- ///
- internal static string VhrInvalidRowCountAndOtherFile {
- get {
- return ResourceManager.GetString("VhrInvalidRowCountAndOtherFile", resourceCulture);
- }
- }
-
///
/// Looks up a localized string similar to The selected path for is not suitable to save practice results. Do not use optical disk drives or system folders here..
///
@@ -620,7 +563,7 @@ internal static string VhrPathInvalid {
}
///
- /// Looks up a localized string similar to The folder for practice results was not found at {0}. Vocup will replace your configuration with the default application data directory. You change that in the settings at any time..
+ /// Looks up a localized string similar to The folder for practice results was not found at {0}. Vocup will replace your configuration with the default application data directory. You can change that in the settings at any time..
///
internal static string VhrPathNotFound {
get {
@@ -638,11 +581,21 @@ internal static string VhrPathNotFoundT {
}
///
- /// Looks up a localized string similar to A vocabulary book or result file could not be saved..
+ /// Looks up a localized string similar to A vocabulary book could not be opened:
+ ///{0}.
///
- internal static string VocupFileWriteError {
+ internal static string VocupFileReadError {
get {
- return ResourceManager.GetString("VocupFileWriteError", resourceCulture);
+ return ResourceManager.GetString("VocupFileReadError", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Error when opening a file.
+ ///
+ internal static string VocupFileReadErrorT {
+ get {
+ return ResourceManager.GetString("VocupFileReadErrorT", resourceCulture);
}
}
@@ -650,9 +603,9 @@ internal static string VocupFileWriteError {
/// Looks up a localized string similar to A vocabulary book or result file could not be saved:
///{0}.
///
- internal static string VocupFileWriteErrorEx {
+ internal static string VocupFileWriteError {
get {
- return ResourceManager.GetString("VocupFileWriteErrorEx", resourceCulture);
+ return ResourceManager.GetString("VocupFileWriteError", resourceCulture);
}
}
diff --git a/src/Vocup/Properties/Messages.de.resx b/src/Vocup/Properties/Messages.de.resx
index e9824d4..55e9915 100644
--- a/src/Vocup/Properties/Messages.de.resx
+++ b/src/Vocup/Properties/Messages.de.resx
@@ -259,23 +259,12 @@ Details: {0}
Unerwarteter Fehler
-
- Die Vocup-Datensicherung ist fehlerhaft und konnte ich geöffnet werden:
-{0}
- {0}: ErrorDetails
-
-
- Fehlerhafte Datei
+
+ Die Vokabeheft-Datei wurde mit einer neueren Version von Vocup gespeichert. Sie wird im Kompatibilitätsmodus geöffnet. Es werden nicht alle Informationen sichtbar sein.
+Es wird empfohlen, dass Sie Vocup auf die neueste Version aktualisieren.
-
- Zusammenfassung
- - {0} Datei(en) wiederhergestellt
- - {1} Datei(en) übersprungen
- - {2} Datei(en) fehlgeschlagen
- {0}: Restored, {1}: Skipped, {2}: Failed
-
-
- Wiederherstellung abgeschlossen
+
+ Kompatibilitätsmodus
Die Vokabelheftdatei ist fehlerhaft und konnte nicht geöffnet werden.
@@ -314,18 +303,6 @@ Stellen Sie sicher, dass die Datei nicht leer ist.
Ordner für Vokabelhefte nicht gefunden
-
- Die Ergbnisdatei ist fehlerhaft und wird gelöscht. Die Übungsergebnisse gehen deshalb verloren.
-
-
- Fehlerhafte Ergebnis-Datei
-
-
- Die referenzierte Ergebnisdatei passt nicht zu diesem Vokabelheft. Die Ergebnisse werden gelöscht.
-
-
- Die referenzierte Ergebnisdatei wird von einem anderen Vokabelheft verwendet und passt nicht zu diesem. Daher werden keine Ergebnisse geladen.
-
Der ausgewählte Pfad ist nicht zum Speichern von Übungsergebnissen geeignet. Verwenden Sie bitte keine optischen Laufwerke oder Systemordner an dieser Stelle.
@@ -335,10 +312,15 @@ Stellen Sie sicher, dass die Datei nicht leer ist.
Ordner für Übungsergebnisse nicht gefunden
-
- Eine Vokabelheft- oder Ergebnisdatei konnte nicht gespeichert werden.
+
+ Ein Vokabelheft konnte nicht geöffnet werden:
+{0}
+ {0}: ErrorDetails
+
+
+ Fehler beim Öffnen einer Datei
-
+
Eine Vokabelheft- oder Ergebnisdatei konnte nicht gespeichert werden:
{0}
{0}: ErrorDetails
diff --git a/src/Vocup/Properties/Messages.nl.resx b/src/Vocup/Properties/Messages.nl.resx
index 5020409..cc17745 100644
--- a/src/Vocup/Properties/Messages.nl.resx
+++ b/src/Vocup/Properties/Messages.nl.resx
@@ -259,23 +259,12 @@ Details: {0}
Onbekende fout
-
- Het Vocup back-up bestand is beschadigd en kon niet worden geopend:
-{0}
- {0}: ErrorDetails
-
-
- Beschadigd bestand
+
+ Het woordenboekbestand is opgeslagen in een nieuwere versie van Vocup. Het zal geopend worden in compatibiliteitsmodus. Niet alle informatie zal zichtbaar zijn.
+Het is aanbevolen om Vocup te updaten naar de laatste versie.
-
- Samenvatting
- - {0} bestand(en) hersteld
- - {1} bestand(en) overgeslagen
- - {2} bestand(en) mislukt
- {0}: Restored, {1}: Skipped, {2}: Failed
-
-
- Herstellen gereed
+
+ Compatibiliteitsmodus
Het woordenboek is beschadigd en kon niet worden geopend.
@@ -314,18 +303,6 @@ Controleer of het bestand niet leeg is.
Map voor woordenboeken niet gevonden
-
- Het resultatenbestand is beschadigd en zal worden verwijderd. Hierdoor zal je je oefenresultaten kwijt raken.
-
-
- Beschadigd resultatenbestand
-
-
- Het resultatenbestand match niet met het woordenboek. Alle resultaten zullen worden verwijderd.
-
-
- Het resultatenbestand is in gebruik door een ander woordenboek. Resultaten kunnen niet worden ingeladen.
-
Het gekozen pad is niet geschikt om oefenresultaten op te slaan. Gebruik hier geen optische schijven of systeemmappen.
@@ -335,10 +312,15 @@ Controleer of het bestand niet leeg is.
Map voor oefenresultaten niet gevonden
-
- Een woordenboek of resultatenbestand kon niet worden opgeslagen.
+
+ Een woordenboek kon niet worden geopend:
+{0}
+ {0}: ErrorDetails
+
+
+ Fout bij openen bestand
-
+
Een woordenboek of resultatenbestand kon niet worden opgeslagen:
{0}
{0}: ErrorDetails
diff --git a/src/Vocup/Properties/Messages.resx b/src/Vocup/Properties/Messages.resx
index 4cf6762..2d1785e 100644
--- a/src/Vocup/Properties/Messages.resx
+++ b/src/Vocup/Properties/Messages.resx
@@ -259,23 +259,12 @@ Details: {0}
Unexpected error
-
- The Vocup backup file is corrupt and could not be opened:
-{0}
- {0}: ErrorDetails
-
-
- Corrupt file
-
-
- Summary
- - {0} File(s) restored
- - {1} File(s) skipped
- - {2} File(s) failed
- {0}: Restored, {1}: Skipped, {2}: Failed
+
+ The vocabulary book file was saved by a later version of Vocup. It will be opened in compatibility mode. Not all information will be visible.
+It is recommended that you update Vocup to the latest version.
-
- Recovery finished
+
+ Compatibility mode
The vocabulary book file is corrupt and could not be opened.
@@ -314,18 +303,6 @@ Please ensure that the file is not empty.
Folder for vocabulary books not found
-
- The result file is corrupt and going to be deleted. Therefore you are going to loose your practice results.
-
-
- Corrupt result file
-
-
- The referenced result file does not match the current vocabulary book. All results are going to be deleted.
-
-
- The referenced result file is in use by another vocabulary book. Results are not going to be loaded.
-
The selected path for is not suitable to save practice results. Do not use optical disk drives or system folders here.
@@ -335,10 +312,15 @@ Please ensure that the file is not empty.
Folder for practice results not found
-
- A vocabulary book or result file could not be saved.
+
+ A vocabulary book could not be opened:
+{0}
+ {0}: ErrorDetails
+
+
+ Error when opening a file
-
+
A vocabulary book or result file could not be saved:
{0}
{0}: ErrorDetails
diff --git a/src/Vocup/Properties/Words.Designer.cs b/src/Vocup/Properties/Words.Designer.cs
index d389db3..5e5213b 100644
--- a/src/Vocup/Properties/Words.Designer.cs
+++ b/src/Vocup/Properties/Words.Designer.cs
@@ -168,6 +168,33 @@ internal static string Export {
}
}
+ ///
+ /// Looks up a localized string similar to Vocup vocabulary book.
+ ///
+ internal static string FileFormatVhf {
+ get {
+ return ResourceManager.GetString("FileFormatVhf", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Vocup vocabulary book v1.
+ ///
+ internal static string FileFormatVhf1 {
+ get {
+ return ResourceManager.GetString("FileFormatVhf1", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Vocup vocabulary book v2.
+ ///
+ internal static string FileFormatVhf2 {
+ get {
+ return ResourceManager.GetString("FileFormatVhf2", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Finish.
///
@@ -249,6 +276,15 @@ internal static string SaveVocabularyBook {
}
}
+ ///
+ /// Looks up a localized string similar to Share vocabulary book without practice results.
+ ///
+ internal static string ShareVocabularyBook {
+ get {
+ return ResourceManager.GetString("ShareVocabularyBook", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Site.
///
@@ -294,15 +330,6 @@ internal static string VocupBackupFile {
}
}
- ///
- /// Looks up a localized string similar to Vocup vocabulary book file.
- ///
- internal static string VocupVocabularyBookFile {
- get {
- return ResourceManager.GetString("VocupVocabularyBookFile", resourceCulture);
- }
- }
-
///
/// Looks up a localized string similar to Wrong.
///
diff --git a/src/Vocup/Properties/Words.de.resx b/src/Vocup/Properties/Words.de.resx
index 6f3f1c0..1c672fe 100644
--- a/src/Vocup/Properties/Words.de.resx
+++ b/src/Vocup/Properties/Words.de.resx
@@ -155,6 +155,15 @@
Exportieren
+
+ Vocup Vokabelheft
+
+
+ Vocup Vokabelheft v1
+
+
+ Vocup Vokabelheft v2
+
Fertig
@@ -183,6 +192,9 @@
Vokabelheft speichern
+
+ Vokabelheft ohne Übungsergebnisse teilen
+
Seite
@@ -199,9 +211,6 @@
Vocup Backup-Datei
-
- Vocup Vokabelheft-Datei
-
Falsch
diff --git a/src/Vocup/Properties/Words.nl.resx b/src/Vocup/Properties/Words.nl.resx
index 313a71e..e63c4cf 100644
--- a/src/Vocup/Properties/Words.nl.resx
+++ b/src/Vocup/Properties/Words.nl.resx
@@ -155,6 +155,15 @@
Exporteren
+
+ Vocup woordenboek
+
+
+ Vocup woordenboek v1
+
+
+ Vocup woordenboek v2
+
Klaar
@@ -183,6 +192,9 @@
Woordenboek opslaan
+
+ Woordenboek delen zonder oefenresultaten
+
Site
@@ -199,9 +211,6 @@
Vocup back-up bestand
-
- Vocup woordenboek
-
Fout
diff --git a/src/Vocup/Properties/Words.resx b/src/Vocup/Properties/Words.resx
index b78bdc5..51eb79c 100644
--- a/src/Vocup/Properties/Words.resx
+++ b/src/Vocup/Properties/Words.resx
@@ -155,6 +155,15 @@
Export
+
+ Vocup vocabulary book
+
+
+ Vocup vocabulary book v1
+
+
+ Vocup vocabulary book v2
+
Finish
@@ -183,6 +192,9 @@
Save vocabulary book
+
+ Share vocabulary book without practice results
+
Site
@@ -199,9 +211,6 @@
Vocup backup file
-
- Vocup vocabulary book file
-
Wrong
diff --git a/src/Vocup/Resources/share.png b/src/Vocup/Resources/share.png
new file mode 100644
index 0000000..a316a90
Binary files /dev/null and b/src/Vocup/Resources/share.png differ
diff --git a/src/Vocup/Vocup.csproj b/src/Vocup/Vocup.csproj
index 53b0909..e34b7a4 100644
--- a/src/Vocup/Vocup.csproj
+++ b/src/Vocup/Vocup.csproj
@@ -13,6 +13,7 @@
icon.ico
Microsoft Sans Serif, 8.25pt
PerMonitorV2
+ true
Vocabulary training application
VectorData
Copyright © 2011 Florian Amstutz, © 2018-2023 Daniel Lerch.
diff --git a/tests/Vocup.UnitTests/BookFileFormatTests.cs b/tests/Vocup.UnitTests/BookFileFormatTests.cs
new file mode 100644
index 0000000..f32cd5a
--- /dev/null
+++ b/tests/Vocup.UnitTests/BookFileFormatTests.cs
@@ -0,0 +1,269 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+using Vocup.IO;
+using Vocup.Models;
+using Xunit;
+
+namespace Vocup.UnitTests;
+
+public class BookFileFormatTests : IAsyncDisposable
+{
+ private readonly IAsyncDisposable disposable;
+
+ public BookFileFormatTests()
+ {
+ disposable = Program.InitializeServices();
+ }
+
+ public ValueTask DisposeAsync()
+ {
+ return disposable.DisposeAsync();
+ }
+
+ [Fact]
+ public void TestRead_Empty()
+ {
+ string path = Path.Join("Resources", "Vhf_Empty.vhf");
+ VocabularyBook book = new();
+ var ex = Assert.Throws(() => BookFileFormat.DetectAndRead(path, book, "Resources"));
+ Assert.Equal(VhfError.InvalidVersion, ex.ErrorCode);
+ }
+
+ [Fact]
+ public void TestReadVhf1()
+ {
+ string vhrPath = "Resources";
+ string path = Path.Join("Resources", "Year 11 (vhf1).vhf");
+ VocabularyBook book = new();
+ Assert.True(BookFileFormat.DetectAndRead(path, book, vhrPath));
+
+ Assert.Equal("Deutsch", book.MotherTongue);
+ Assert.Equal("Englisch", book.ForeignLang);
+ Assert.Equal(113, book.Words.Count);
+ Assert.EndsWith(path, book.FilePath);
+ Assert.Equal("2jgh9u3tuPCfYLxhhCJXGyPN", book.VhrCode);
+ Assert.Equal(PracticeMode.AskForForeignLang, book.PracticeMode);
+ }
+
+ [Fact]
+ public void TestReadVhf1_InvalidBase64()
+ {
+ string path = Path.Join("Resources", "Vhf1_InvalidBase64.vhf");
+ VocabularyBook book = new();
+ var ex = Assert.Throws(() => BookFileFormat.DetectAndRead(path, book, "Resources"));
+ Assert.Equal(VhfError.InvalidCiphertext, ex.ErrorCode);
+ }
+
+ [Fact]
+ public void TestReadVhf1_RandomBase64()
+ {
+ string path = Path.Join("Resources", "Vhf1_RandomBase64.vhf");
+ VocabularyBook book = new();
+ var ex = Assert.Throws(() => BookFileFormat.DetectAndRead(path, book, "Resources"));
+ Assert.Equal(VhfError.InvalidCiphertext, ex.ErrorCode);
+ }
+
+ [Fact]
+ public void TestWriteReadVhf1()
+ {
+ string tempPath = Path.GetTempPath();
+ string path = Path.Combine(tempPath, $"Vocup_{nameof(TestWriteReadVhf1)}.vhf");
+ string vhrCode = "o5xqm7rdg6y9fecs9ykuuckv";
+ VocabularyBook original = GenerateSampleBook();
+ original.VhrCode = vhrCode;
+
+ using (FileStream stream = new(path, FileMode.Create, FileAccess.Write, FileShare.None))
+ BookFileFormat.Vhf1.Write(stream, original, tempPath, includeResults: true);
+
+ Assert.False(string.IsNullOrEmpty(original.FilePath));
+
+ VocabularyBook actual = new();
+ Assert.True(BookFileFormat.DetectAndRead(path, actual, tempPath));
+
+ Assert.Equal(original.MotherTongue, actual.MotherTongue);
+ Assert.Equal(original.ForeignLang, actual.ForeignLang);
+ Assert.Equal(original.Words.Count, actual.Words.Count);
+ Assert.Equal(original.Words[1].PracticeStateNumber, actual.Words[1].PracticeStateNumber);
+ Assert.Equal(original.Words[1].PracticeDate, actual.Words[1].PracticeDate);
+ Assert.Equal(original.FilePath, actual.FilePath);
+ Assert.Equal(original.VhrCode, actual.VhrCode);
+ Assert.Equal(original.PracticeMode, actual.PracticeMode);
+
+ File.Delete(path);
+ File.Delete(Path.Combine(tempPath, vhrCode + ".vhr"));
+ }
+
+ [Fact]
+ public void TestWriteReadVhf1_PracticeModeMixed()
+ {
+ string tempPath = Path.GetTempPath();
+ string path = Path.Combine(tempPath, $"Vocup_{nameof(TestWriteReadVhf1_PracticeModeMixed)}.vhf");
+ string vhrCode = "ina5ucmjup2sbcioxdsrvqsu";
+ VocabularyBook original = GenerateSampleBook();
+ original.PracticeMode = PracticeMode.AskForBothMixed;
+ original.VhrCode = vhrCode;
+
+ using (FileStream stream = new(path, FileMode.Create, FileAccess.Write, FileShare.None))
+ BookFileFormat.Vhf1.Write(stream, original, tempPath, includeResults: true);
+
+ Assert.False(string.IsNullOrEmpty(original.FilePath));
+
+ VocabularyBook actual = new();
+ Assert.True(BookFileFormat.DetectAndRead(path, actual, tempPath));
+
+ Assert.Equal(original.MotherTongue, actual.MotherTongue);
+ Assert.Equal(original.ForeignLang, actual.ForeignLang);
+ Assert.Equal(original.Words.Count, actual.Words.Count);
+ Assert.Equal(original.Words[1].PracticeStateNumber, actual.Words[1].PracticeStateNumber);
+ Assert.Equal(original.Words[1].PracticeDate, actual.Words[1].PracticeDate);
+ Assert.Equal(original.FilePath, actual.FilePath);
+ Assert.Equal(original.VhrCode, actual.VhrCode);
+ Assert.Equal(PracticeMode.AskForForeignLang, actual.PracticeMode);
+
+ File.Delete(path);
+ File.Delete(Path.Combine(tempPath, vhrCode + ".vhr"));
+ }
+
+ [Fact]
+ public void TestWriteReadVhf1_WithoutResults()
+ {
+ string tempPath = Path.GetTempPath();
+ string path = Path.Combine(tempPath, $"Vocup_{nameof(TestWriteReadVhf1_WithoutResults)}.vhf");
+ string vhrCode = "wetwjlvwhspsre4slcb01mwk";
+ VocabularyBook original = GenerateSampleBook();
+ original.VhrCode = vhrCode;
+
+ using (FileStream stream = new(path, FileMode.Create, FileAccess.Write, FileShare.None))
+ BookFileFormat.Vhf1.Write(stream, original, tempPath, includeResults: false);
+
+ Assert.False(string.IsNullOrEmpty(original.FilePath));
+
+ VocabularyBook actual = new();
+ Assert.True(BookFileFormat.DetectAndRead(path, actual, tempPath));
+
+ Assert.Equal(original.MotherTongue, actual.MotherTongue);
+ Assert.Equal(original.ForeignLang, actual.ForeignLang);
+ Assert.Equal(original.Words.Count, actual.Words.Count);
+ Assert.Equal(default, actual.Words[1].PracticeStateNumber);
+ Assert.Equal(default, actual.Words[1].PracticeDate);
+ Assert.Equal(original.FilePath, actual.FilePath);
+ Assert.Null(actual.VhrCode);
+ Assert.Equal(PracticeMode.AskForForeignLang, actual.PracticeMode);
+ Assert.False(File.Exists(Path.Combine(tempPath, vhrCode + ".vhr")));
+
+ File.Delete(path);
+ }
+
+ [Fact]
+ public void TestReadVhf2()
+ {
+ string path = Path.Join("Resources", "Year 11 (vhf2).vhf");
+ VocabularyBook book = new();
+ Assert.True(BookFileFormat.DetectAndRead(path, book, "Resources"));
+
+ Assert.Equal("Deutsch", book.MotherTongue);
+ Assert.Equal("Englisch", book.ForeignLang);
+ Assert.Equal(113, book.Words.Count);
+ Assert.EndsWith(path, book.FilePath);
+ Assert.Null(book.VhrCode); // VhrCode is not used in vhf2 format
+ Assert.Equal(PracticeMode.AskForForeignLang, book.PracticeMode);
+ }
+
+ [Fact]
+ public void TestReadVhf2_CompatMode()
+ {
+ string path = Path.Join("Resources", "Vhf2_CompatMode.vhf");
+ VocabularyBook book = new();
+ Assert.False(BookFileFormat.DetectAndRead(path, book, "Resources"));
+
+ Assert.Equal("Deutsch", book.MotherTongue);
+ Assert.Equal("Englisch", book.ForeignLang);
+ Assert.Empty(book.Words);
+ Assert.EndsWith(path, book.FilePath);
+ Assert.Null(book.VhrCode); // VhrCode is not used in vhf2 format
+ Assert.Equal(PracticeMode.AskForForeignLang, book.PracticeMode);
+ }
+
+ [Fact]
+ public void TestReadVhf2_UpdateRequired()
+ {
+ string path = Path.Join("Resources", "Vhf2_UpdateRequired.vhf");
+ VocabularyBook book = new();
+ var ex = Assert.Throws(() => BookFileFormat.DetectAndRead(path, book, "Resources"));
+ Assert.Equal(VhfError.UpdateRequired, ex.ErrorCode);
+ }
+
+ [Fact]
+ public void TestWriteReadVhf2()
+ {
+ string tempPath = Path.GetTempPath();
+ string path = Path.Combine(tempPath, $"Vocup_{nameof(TestWriteReadVhf2)}.vhf");
+ VocabularyBook original = GenerateSampleBook();
+
+ using (FileStream stream = new(path, FileMode.Create, FileAccess.Write, FileShare.None))
+ BookFileFormat.Vhf2.Write(stream, original, vhrPath: null!, includeResults: true);
+
+ Assert.False(string.IsNullOrEmpty(original.FilePath));
+
+ VocabularyBook actual = new();
+ Assert.True(BookFileFormat.DetectAndRead(path, actual, tempPath));
+
+ Assert.Equal(original.MotherTongue, actual.MotherTongue);
+ Assert.Equal(original.ForeignLang, actual.ForeignLang);
+ Assert.Equal(original.Words.Count, actual.Words.Count);
+ Assert.Equal(original.Words[1].PracticeStateNumber, actual.Words[1].PracticeStateNumber);
+ Assert.Equal(original.Words[1].PracticeDate, actual.Words[1].PracticeDate);
+ Assert.Equal(original.FilePath, actual.FilePath);
+ Assert.Null(actual.VhrCode); // VhrCode is not used in vhf2 format
+ Assert.Equal(original.PracticeMode, actual.PracticeMode);
+
+ File.Delete(path);
+ }
+
+ [Fact]
+ public void TestWriteReadVhf2_WithoutResults()
+ {
+ string tempPath = Path.GetTempPath();
+ string path = Path.Combine(tempPath, $"Vocup_{nameof(TestWriteReadVhf2_WithoutResults)}.vhf");
+ VocabularyBook original = GenerateSampleBook();
+
+ using (FileStream stream = new(path, FileMode.Create, FileAccess.Write, FileShare.None))
+ BookFileFormat.Vhf2.Write(stream, original, vhrPath: null!, includeResults: false);
+
+ Assert.False(string.IsNullOrEmpty(original.FilePath));
+
+ VocabularyBook actual = new();
+ Assert.True(BookFileFormat.DetectAndRead(path, actual, tempPath));
+
+ Assert.Equal(original.MotherTongue, actual.MotherTongue);
+ Assert.Equal(original.ForeignLang, actual.ForeignLang);
+ Assert.Equal(original.Words.Count, actual.Words.Count);
+ Assert.Equal(default, actual.Words[1].PracticeStateNumber);
+ Assert.Equal(default, actual.Words[1].PracticeDate);
+ Assert.Equal(original.FilePath, actual.FilePath);
+ Assert.Null(actual.VhrCode); // VhrCode is not used in vhf2 format
+ Assert.Equal(original.PracticeMode, actual.PracticeMode);
+
+ File.Delete(path);
+ }
+
+ private static VocabularyBook GenerateSampleBook()
+ {
+ VocabularyBook book = new()
+ {
+ MotherTongue = "Deutsch",
+ ForeignLang = "Englisch",
+ PracticeMode = PracticeMode.AskForForeignLang
+ };
+ book.Words.Add(new("Katze", "cat"));
+ book.Words.Add(new("Farbe", "colour")
+ {
+ ForeignLangSynonym = "color",
+ PracticeStateNumber = 2,
+ // Vocup v1 file formats do not support seconds or timezones of practices
+ PracticeDate = new DateTime(2020, 8, 19, 16, 17, 0)
+ });
+ return book;
+ }
+}
diff --git a/tests/Vocup.UnitTests/Resources/2jgh9u3tuPCfYLxhhCJXGyPN.vhr b/tests/Vocup.UnitTests/Resources/2jgh9u3tuPCfYLxhhCJXGyPN.vhr
new file mode 100644
index 0000000..2c80c53
--- /dev/null
+++ b/tests/Vocup.UnitTests/Resources/2jgh9u3tuPCfYLxhhCJXGyPN.vhr
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/tests/Vocup.UnitTests/Resources/Vhf1_InvalidBase64.vhf b/tests/Vocup.UnitTests/Resources/Vhf1_InvalidBase64.vhf
new file mode 100644
index 0000000..bbefee5
--- /dev/null
+++ b/tests/Vocup.UnitTests/Resources/Vhf1_InvalidBase64.vhf
@@ -0,0 +1 @@
++QvLzy9W35yGYIuJScW=vigzdW8r\Y-8Gu47YU6z5A5X5QZtqHH3DN1yXMLew6pgaqURdgomqfE/PdX/MPcL8cigPuCwe7gVWPzk2TZi3mkqwXj2K0ITFZ4lcofMYcpJO6tmbKjUtnM0FsMDQDTwe1cJLvjZXvZe0F+DrPOMXRc=
\ No newline at end of file
diff --git a/tests/Vocup.UnitTests/Resources/Vhf1_RandomBase64.vhf b/tests/Vocup.UnitTests/Resources/Vhf1_RandomBase64.vhf
new file mode 100644
index 0000000..a30999f
--- /dev/null
+++ b/tests/Vocup.UnitTests/Resources/Vhf1_RandomBase64.vhf
@@ -0,0 +1 @@
++QvLzy9W35yGYIuJScWvvigzdW8roYU8Gu47YU6z5A5X5QZtqHH3DN1yXMLew6pgaqURdgomqfE/PdX/MPcL8cigPuCwe7gVWPzk2TZi3mkqwXj2K0ITFZ4lcofMYcpJO6tmbKjUtnM0FsMDQDTwe1cJLvjZXvZe0F+DrPOMXRc=
\ No newline at end of file
diff --git a/tests/Vocup.UnitTests/Resources/Vhf2_CompatMode.vhf b/tests/Vocup.UnitTests/Resources/Vhf2_CompatMode.vhf
new file mode 100644
index 0000000..bff7116
Binary files /dev/null and b/tests/Vocup.UnitTests/Resources/Vhf2_CompatMode.vhf differ
diff --git a/tests/Vocup.UnitTests/Resources/Vhf2_UpdateRequired.vhf b/tests/Vocup.UnitTests/Resources/Vhf2_UpdateRequired.vhf
new file mode 100644
index 0000000..726e8d4
Binary files /dev/null and b/tests/Vocup.UnitTests/Resources/Vhf2_UpdateRequired.vhf differ
diff --git a/tests/Vocup.UnitTests/Resources/Vhf_Empty.vhf b/tests/Vocup.UnitTests/Resources/Vhf_Empty.vhf
new file mode 100644
index 0000000..e69de29
diff --git a/tests/Vocup.UnitTests/Resources/Year 11 (vhf1).vhf b/tests/Vocup.UnitTests/Resources/Year 11 (vhf1).vhf
new file mode 100644
index 0000000..fa7e3c2
--- /dev/null
+++ b/tests/Vocup.UnitTests/Resources/Year 11 (vhf1).vhf
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/tests/Vocup.UnitTests/Resources/Year 11 (vhf2).vhf b/tests/Vocup.UnitTests/Resources/Year 11 (vhf2).vhf
new file mode 100644
index 0000000..426f743
Binary files /dev/null and b/tests/Vocup.UnitTests/Resources/Year 11 (vhf2).vhf differ
diff --git a/tests/Vocup.UnitTests/Vocup.UnitTests.csproj b/tests/Vocup.UnitTests/Vocup.UnitTests.csproj
index d1ad5c3..ad72b1b 100644
--- a/tests/Vocup.UnitTests/Vocup.UnitTests.csproj
+++ b/tests/Vocup.UnitTests/Vocup.UnitTests.csproj
@@ -19,4 +19,30 @@
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
+ PreserveNewest
+
+
\ No newline at end of file