2 Commits
Author SHA1 Message Date
PANDA Project 597e180141 Add MIT license 2026-08-10 08:21:08 +02:00
PANDA Project 4bbdd85ae1 Add configurable default shift and confirmation dialog 2026-08-07 15:07:52 +02:00
9 changed files with 345 additions and 11 deletions
+1
View File
@@ -7,6 +7,7 @@ PANDA-Uninstall.Payload.exe
panda-preview.png
panda-import-assistent.png
panda-setup.png
panda-settings.png
# Lokale Exporte und temporäre Tests
export.csv
+4 -4
View File
@@ -11,8 +11,8 @@ using Microsoft.Win32;
[assembly: AssemblyDescription("Installer für PANDA")]
[assembly: AssemblyProduct("PANDA")]
[assembly: AssemblyCompany("PANDA")]
[assembly: AssemblyVersion("1.3.0.0")]
[assembly: AssemblyFileVersion("1.3.0.0")]
[assembly: AssemblyVersion("1.4.0.0")]
[assembly: AssemblyFileVersion("1.4.0.0")]
namespace PandaSetup
{
@@ -143,7 +143,7 @@ namespace PandaSetup
});
heading.Controls.Add(new Label
{
Text = "Version 1.3.0 • Installation für den aktuellen Windows-Benutzer",
Text = "Version 1.4.0 • Installation für den aktuellen Windows-Benutzer",
ForeColor = Blue,
AutoSize = true,
Location = new Point(2, 73)
@@ -329,7 +329,7 @@ namespace PandaSetup
using (RegistryKey key = Registry.CurrentUser.CreateSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\PANDA"))
{
key.SetValue("DisplayName", "PANDA");
key.SetValue("DisplayVersion", "1.3.0");
key.SetValue("DisplayVersion", "1.4.0");
key.SetValue("DisplayIcon", applicationPath);
key.SetValue("Publisher", "PANDA");
key.SetValue("InstallLocation", target);
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 PANDA Project contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Binary file not shown.
BIN
View File
Binary file not shown.
+295 -5
View File
@@ -11,8 +11,8 @@ using System.Windows.Forms;
[assembly: AssemblyDescription("Pseudonymisierung alphanumerischer Nutzdaten durch Alphabetverschiebung")]
[assembly: AssemblyProduct("PANDA")]
[assembly: AssemblyCompany("PANDA")]
[assembly: AssemblyVersion("1.3.0.0")]
[assembly: AssemblyFileVersion("1.3.0.0")]
[assembly: AssemblyVersion("1.4.0.0")]
[assembly: AssemblyFileVersion("1.4.0.0")]
namespace Panda
{
@@ -57,6 +57,22 @@ namespace Panda
}
return;
}
if (args.Length == 2 && string.Equals(args[0], "--settings-screenshot", StringComparison.OrdinalIgnoreCase))
{
var previewSettings = new AppSettings { DefaultShift = 6, ConfirmBeforeShift = true };
using (var settingsForm = new SettingsForm(previewSettings))
{
settingsForm.Show();
Application.DoEvents();
using (var bitmap = new Bitmap(settingsForm.Width, settingsForm.Height))
{
settingsForm.DrawToBitmap(bitmap, new Rectangle(Point.Empty, settingsForm.Size));
bitmap.Save(args[1], System.Drawing.Imaging.ImageFormat.Png);
}
settingsForm.Close();
}
return;
}
Application.Run(new MainForm());
}
}
@@ -314,6 +330,227 @@ namespace Panda
}
}
internal sealed class AppSettings
{
public int DefaultShift = 1;
public bool ConfirmBeforeShift = true;
private static string SettingsPath
{
get
{
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "PANDA", "settings.ini");
}
}
public static AppSettings Load()
{
try
{
if (File.Exists(SettingsPath))
return Parse(File.ReadAllLines(SettingsPath));
}
catch
{
}
return new AppSettings();
}
public void Save()
{
string directory = Path.GetDirectoryName(SettingsPath);
if (!Directory.Exists(directory))
Directory.CreateDirectory(directory);
File.WriteAllLines(SettingsPath, Serialize(), new UTF8Encoding(false));
}
internal static AppSettings Parse(IEnumerable<string> lines)
{
var settings = new AppSettings();
foreach (string line in lines)
{
if (string.IsNullOrWhiteSpace(line) || line.TrimStart().StartsWith("#"))
continue;
int separator = line.IndexOf('=');
if (separator <= 0)
continue;
string key = line.Substring(0, separator).Trim();
string value = line.Substring(separator + 1).Trim();
if (string.Equals(key, "DefaultShift", StringComparison.OrdinalIgnoreCase))
{
int parsed;
if (int.TryParse(value, out parsed))
settings.DefaultShift = Math.Max(1, Math.Min(25, parsed));
}
else if (string.Equals(key, "ConfirmBeforeShift", StringComparison.OrdinalIgnoreCase))
{
bool parsed;
if (bool.TryParse(value, out parsed))
settings.ConfirmBeforeShift = parsed;
}
}
return settings;
}
internal string[] Serialize()
{
return new[]
{
"DefaultShift=" + DefaultShift,
"ConfirmBeforeShift=" + ConfirmBeforeShift
};
}
}
internal sealed class SettingsForm : Form
{
private readonly Color Navy = Color.FromArgb(24, 38, 58);
private readonly Color Blue = Color.FromArgb(41, 112, 255);
private readonly Color Background = Color.FromArgb(244, 247, 251);
private readonly Color Muted = Color.FromArgb(94, 108, 128);
private readonly NumericUpDown defaultShiftNumeric = new NumericUpDown();
private readonly CheckBox confirmationCheckBox = new CheckBox();
public int DefaultShift { get { return (int)defaultShiftNumeric.Value; } }
public bool ConfirmBeforeShift { get { return confirmationCheckBox.Checked; } }
public SettingsForm(AppSettings settings)
{
Text = "PANDA Einstellungen";
StartPosition = FormStartPosition.CenterParent;
FormBorderStyle = FormBorderStyle.FixedDialog;
MaximizeBox = false;
MinimizeBox = false;
ClientSize = new Size(560, 330);
BackColor = Background;
Font = new Font("Segoe UI", 9F);
Icon = System.Drawing.Icon.ExtractAssociatedIcon(Application.ExecutablePath) ?? SystemIcons.Application;
BuildLayout(settings);
}
private void BuildLayout(AppSettings settings)
{
var root = new TableLayoutPanel
{
Dock = DockStyle.Fill,
ColumnCount = 1,
RowCount = 3,
Padding = new Padding(24, 20, 24, 18),
BackColor = Background
};
root.RowStyles.Add(new RowStyle(SizeType.Absolute, 78));
root.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
root.RowStyles.Add(new RowStyle(SizeType.Absolute, 58));
Controls.Add(root);
var heading = new Panel { Dock = DockStyle.Fill };
heading.Controls.Add(new Label
{
Text = "Einstellungen",
Font = new Font("Segoe UI Semibold", 18F),
ForeColor = Navy,
AutoSize = true,
Location = new Point(0, 0)
});
heading.Controls.Add(new Label
{
Text = "Lege die Standardwerte für zukünftige Umwandlungen fest.",
ForeColor = Muted,
AutoSize = true,
Location = new Point(2, 40)
});
root.Controls.Add(heading, 0, 0);
var card = new TableLayoutPanel
{
Dock = DockStyle.Fill,
ColumnCount = 2,
RowCount = 4,
BackColor = Color.White,
Padding = new Padding(18, 14, 18, 14),
Margin = new Padding(0, 0, 0, 12)
};
card.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
card.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 90));
card.RowStyles.Add(new RowStyle(SizeType.Absolute, 28));
card.RowStyles.Add(new RowStyle(SizeType.Absolute, 38));
card.RowStyles.Add(new RowStyle(SizeType.Absolute, 16));
card.RowStyles.Add(new RowStyle(SizeType.Absolute, 42));
var shiftLabel = new Label
{
Text = "Standard-Zählwert",
Font = new Font("Segoe UI Semibold", 10F),
ForeColor = Navy,
AutoSize = true,
Anchor = AnchorStyles.Left
};
card.Controls.Add(shiftLabel, 0, 0);
card.SetColumnSpan(shiftLabel, 2);
card.Controls.Add(new Label
{
Text = "Dieser Wert wird beim Programmstart und nach dem Speichern voreingestellt.",
ForeColor = Muted,
AutoSize = true,
Anchor = AnchorStyles.Left
}, 0, 1);
defaultShiftNumeric.Minimum = 1;
defaultShiftNumeric.Maximum = 25;
defaultShiftNumeric.Value = Math.Max(1, Math.Min(25, settings.DefaultShift));
defaultShiftNumeric.TextAlign = HorizontalAlignment.Center;
defaultShiftNumeric.Dock = DockStyle.Fill;
defaultShiftNumeric.Margin = new Padding(8, 5, 0, 5);
card.Controls.Add(defaultShiftNumeric, 1, 1);
confirmationCheckBox.Text = "Vor jeder Umwandlung eine Bestätigung mit dem gewählten Zählwert anzeigen";
confirmationCheckBox.Checked = settings.ConfirmBeforeShift;
confirmationCheckBox.AutoSize = true;
confirmationCheckBox.ForeColor = Navy;
confirmationCheckBox.Anchor = AnchorStyles.Left;
card.SetColumnSpan(confirmationCheckBox, 2);
card.Controls.Add(confirmationCheckBox, 0, 3);
root.Controls.Add(card, 0, 1);
var footer = new TableLayoutPanel
{
Dock = DockStyle.Fill,
ColumnCount = 4,
RowCount = 1,
Padding = new Padding(0, 10, 0, 0)
};
footer.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
footer.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 130));
footer.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 10));
footer.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 130));
footer.RowStyles.Add(new RowStyle(SizeType.Absolute, 36));
var cancelButton = new Button
{
Text = "Abbrechen",
DialogResult = DialogResult.Cancel,
Dock = DockStyle.Fill,
FlatStyle = FlatStyle.Flat,
BackColor = Color.White,
ForeColor = Navy,
Margin = new Padding(0)
};
cancelButton.FlatAppearance.BorderColor = Color.FromArgb(206, 216, 230);
var saveButton = new Button
{
Text = "Speichern",
DialogResult = DialogResult.OK,
Dock = DockStyle.Fill,
FlatStyle = FlatStyle.Flat,
BackColor = Blue,
ForeColor = Color.White,
Margin = new Padding(0)
};
saveButton.FlatAppearance.BorderSize = 0;
footer.Controls.Add(cancelButton, 1, 0);
footer.Controls.Add(saveButton, 3, 0);
root.Controls.Add(footer, 0, 2);
AcceptButton = saveButton;
CancelButton = cancelButton;
}
}
internal sealed class ImportWizardForm : Form
{
private readonly string csvPath;
@@ -683,9 +920,11 @@ namespace Panda
private readonly Label fileLabel = new Label();
private readonly Button exportButton = new Button();
private readonly Button resetButton = new Button();
private readonly Button settingsButton = new Button();
private CsvDocument document;
private string importedPath;
private AppSettings appSettings = AppSettings.Load();
public MainForm()
{
@@ -742,7 +981,7 @@ namespace Panda
var toolbar = new TableLayoutPanel
{
Dock = DockStyle.Fill,
ColumnCount = 8,
ColumnCount = 9,
RowCount = 2,
Padding = new Padding(12, 10, 12, 8),
BackColor = Color.White,
@@ -755,6 +994,7 @@ namespace Panda
toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 135));
toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 108));
toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 122));
toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 116));
toolbar.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 100));
toolbar.RowStyles.Add(new RowStyle(SizeType.Absolute, 34));
toolbar.RowStyles.Add(new RowStyle(SizeType.Absolute, 28));
@@ -773,7 +1013,7 @@ namespace Panda
stepNumeric.Minimum = 1;
stepNumeric.Maximum = 25;
stepNumeric.Value = 1;
stepNumeric.Value = Math.Max(1, Math.Min(25, appSettings.DefaultShift));
stepNumeric.Dock = DockStyle.Fill;
stepNumeric.TextAlign = HorizontalAlignment.Center;
stepNumeric.Margin = new Padding(7, 3, 7, 3);
@@ -797,6 +1037,11 @@ namespace Panda
exportButton.Click += delegate { ExportCsv(); };
toolbar.Controls.Add(exportButton, 6, 0);
settingsButton.Text = "Einstellungen";
StyleSecondaryButton(settingsButton);
settingsButton.Click += delegate { OpenSettings(); };
toolbar.Controls.Add(settingsButton, 7, 0);
fileLabel.Text = "Noch keine CSV geladen";
fileLabel.ForeColor = Muted;
fileLabel.AutoEllipsis = true;
@@ -813,7 +1058,7 @@ namespace Panda
Dock = DockStyle.Fill,
TextAlign = ContentAlignment.MiddleRight
};
toolbar.SetColumnSpan(hint, 4);
toolbar.SetColumnSpan(hint, 5);
toolbar.Controls.Add(hint, 4, 1);
var grids = new TableLayoutPanel
@@ -894,6 +1139,29 @@ namespace Panda
button.FlatAppearance.BorderColor = Color.FromArgb(206, 216, 230);
}
private void OpenSettings()
{
using (var dialog = new SettingsForm(appSettings))
{
if (dialog.ShowDialog(this) != DialogResult.OK)
return;
appSettings.DefaultShift = dialog.DefaultShift;
appSettings.ConfirmBeforeShift = dialog.ConfirmBeforeShift;
try
{
appSettings.Save();
stepNumeric.Value = appSettings.DefaultShift;
statusLabel.Text = "Einstellungen gespeichert. Standard-Zählwert: " + appSettings.DefaultShift + ".";
statusLabel.ForeColor = Color.FromArgb(29, 132, 88);
}
catch (Exception exception)
{
MessageBox.Show(this, "Die Einstellungen konnten nicht gespeichert werden.\r\n\r\n" + exception.Message, "Speichern fehlgeschlagen", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
private void ConfigureGrid(DataGridView grid, bool selectable)
{
grid.Dock = DockStyle.Fill;
@@ -1104,6 +1372,18 @@ namespace Panda
return;
}
if (appSettings.ConfirmBeforeShift)
{
string message = BuildConfirmationMessage(amount, cells.Count, scopeComboBox.SelectedIndex == 2);
DialogResult confirmation = MessageBox.Show(this, message, "Umwandlung bestätigen", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2);
if (confirmation != DialogResult.Yes)
{
statusLabel.Text = "Umwandlung abgebrochen.";
statusLabel.ForeColor = Muted;
return;
}
}
int changed = 0;
foreach (var coordinate in cells)
{
@@ -1119,6 +1399,16 @@ namespace Panda
statusLabel.ForeColor = Color.FromArgb(29, 132, 88);
}
internal static string BuildConfirmationMessage(int amount, int cellCount, bool allValues)
{
string subject = allValues ? "Alle Werte" : "Die ausgewählten Werte";
string direction = amount > 0 ? "hochgezählt" : "runtergezählt";
return subject + " werden um " + Math.Abs(amount) + " " + direction + ".\r\n\r\n"
+ "Gewählter Zählwert: " + Math.Abs(amount) + "\r\n"
+ "Betroffene Zellen: " + cellCount + "\r\n\r\n"
+ "Möchtest du die Umwandlung durchführen?";
}
private List<Tuple<int, int>> GetTargetCells()
{
var result = new List<Tuple<int, int>>();
+10
View File
@@ -29,6 +29,12 @@ Alternativ `PANDA-Portable.exe` doppelt anklicken. Diese Einzeldatei benötigt k
6. Schrittweite festlegen und **Hochzählen (+)** oder **Runterzählen (-)** verwenden.
7. Das Ergebnis rechts kontrollieren und mit **CSV exportieren** speichern.
## Einstellungen
Über **Einstellungen** lässt sich der Standard-Zählwert von 1 bis 25 festlegen. Dieser Wert wird benutzerbezogen gespeichert und beim nächsten Programmstart wiederhergestellt.
Standardmäßig zeigt PANDA vor jeder Umwandlung eine Bestätigung mit Richtung, gewähltem Zählwert und Anzahl der betroffenen Zellen. Diese Abfrage kann im Einstellungsfenster deaktiviert werden.
Auswahlmarkierungen werden ausschließlich in der linken Originaltabelle dargestellt.
Die Buchstaben `A-Z` und `a-z` werden zyklisch verschoben. Beispiel: `Z + 1 = A` und `a - 1 = z`. Zahlen, Leerzeichen, Satzzeichen und Umlaute bleiben unverändert.
@@ -42,3 +48,7 @@ Die Buchstaben `A-Z` und `a-z` werden zyklisch verschoben. Beispiel: `Z + 1 = A`
- `Program.cs` Programmquellcode
- `Installer.cs` / `Uninstaller.cs` Setupquellcode
- `build.ps1` / `Tests.cs` Build und automatische Tests
## Lizenz
PANDA wird unter der [MIT-Lizenz](LICENSE) veröffentlicht. Nutzung, Veränderung und Weitergabe sind erlaubt, sofern der Lizenz- und Urheberhinweis erhalten bleibt.
+12
View File
@@ -32,6 +32,18 @@ namespace Panda
AssertEqual("C", filtered.Headers[0], "selected column order");
AssertEqual("a2", filtered.Rows[1][1], "selected column values");
var settings = AppSettings.Parse(new[] { "DefaultShift=6", "ConfirmBeforeShift=False" });
AssertEqual(6, settings.DefaultShift, "settings default shift");
AssertEqual(false, settings.ConfirmBeforeShift, "settings confirmation flag");
var boundedSettings = AppSettings.Parse(new[] { "DefaultShift=99" });
AssertEqual(25, boundedSettings.DefaultShift, "settings upper bound");
AssertEqual("DefaultShift=6", settings.Serialize()[0], "settings serialization");
string confirmationUp = MainForm.BuildConfirmationMessage(6, 12, false);
AssertEqual(true, confirmationUp.Contains("Die ausgewählten Werte werden um 6 hochgezählt."), "confirmation count up text");
AssertEqual(true, confirmationUp.Contains("Betroffene Zellen: 12"), "confirmation affected cells");
string confirmationDown = MainForm.BuildConfirmationMessage(-4, 20, true);
AssertEqual(true, confirmationDown.Contains("Alle Werte werden um 4 runtergezählt."), "confirmation count down text");
string tempPath = Path.Combine(Path.GetTempPath(), "csv-buchstaben-test-" + Guid.NewGuid().ToString("N") + ".csv");
try
{
+2 -2
View File
@@ -10,8 +10,8 @@ using Microsoft.Win32;
[assembly: AssemblyDescription("Deinstallationsprogramm für PANDA")]
[assembly: AssemblyProduct("PANDA")]
[assembly: AssemblyCompany("PANDA")]
[assembly: AssemblyVersion("1.3.0.0")]
[assembly: AssemblyFileVersion("1.3.0.0")]
[assembly: AssemblyVersion("1.4.0.0")]
[assembly: AssemblyFileVersion("1.4.0.0")]
namespace PandaUninstall
{