diff --git a/README.md b/README.md
index 122171e..04fb36e 100644
--- a/README.md
+++ b/README.md
@@ -3,3 +3,5 @@
utility that lets you quickly force any program to be always on top
should work on any version of windows that supports .net framework 4.0
+
+[Click here to download!](https://github.com/flashwave/topmostfriend/releases/latest)
diff --git a/TopMostFriend/AboutWindow.cs b/TopMostFriend/AboutWindow.cs
index f50ab16..a14a4f7 100644
--- a/TopMostFriend/AboutWindow.cs
+++ b/TopMostFriend/AboutWindow.cs
@@ -15,7 +15,7 @@ namespace TopMostFriend {
}
public AboutWindow() {
- Text = @"About Top Most Friend";
+ Text = Locale.String(@"AboutTitle");
Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath);
BackgroundImage = Properties.Resources.about;
StartPosition = FormStartPosition.CenterScreen;
@@ -24,11 +24,12 @@ namespace TopMostFriend {
ClientSize = Properties.Resources.about.Size;
MaximizeBox = MinimizeBox = false;
MaximumSize = MinimumSize = Size;
+ TopMost = true;
int tabIndex = 0;
Button closeButton = new Button {
- Text = @"Close",
+ Text = Locale.String(@"AboutClose"),
Size = new Size(BUTTON_WIDTH, BUTTON_HEIGHT),
TabIndex = ++tabIndex,
};
@@ -37,7 +38,7 @@ namespace TopMostFriend {
Controls.Add(closeButton);
Button websiteButton = new Button {
- Text = @"Website",
+ Text = Locale.String(@"AboutWebsite"),
Size = new Size(BUTTON_WIDTH, BUTTON_HEIGHT),
TabIndex = ++tabIndex,
};
@@ -46,7 +47,7 @@ namespace TopMostFriend {
Controls.Add(websiteButton);
Button donateButton = new Button {
- Text = @"Donate",
+ Text = Locale.String(@"AboutDonate"),
Size = new Size(BUTTON_WIDTH, BUTTON_HEIGHT),
TabIndex = ++tabIndex,
};
diff --git a/TopMostFriend/ActionTimeout.cs b/TopMostFriend/ActionTimeout.cs
new file mode 100644
index 0000000..cf2b542
--- /dev/null
+++ b/TopMostFriend/ActionTimeout.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Threading;
+
+namespace TopMostFriend {
+ public class ActionTimeout {
+ private readonly Action Action;
+ private bool Continue = true;
+ private int Remaining = 0;
+ private const int STEP = 500;
+
+ public ActionTimeout(Action action, int timeout) {
+ Action = action ?? throw new ArgumentNullException(nameof(action));
+ if(timeout < 1)
+ throw new ArgumentException(@"Timeout must be a positive integer.", nameof(timeout));
+ Remaining = timeout;
+ new Thread(ThreadBody) { IsBackground = true }.Start();
+ }
+
+ private void ThreadBody() {
+ do {
+ Thread.Sleep(STEP);
+ Remaining -= STEP;
+
+ if(!Continue)
+ return;
+ } while(Remaining > 0);
+
+ Action.Invoke();
+ }
+
+ public void Cancel() {
+ Continue = false;
+ }
+ }
+}
diff --git a/TopMostFriend/BlacklistWindow.cs b/TopMostFriend/BlacklistWindow.cs
index 91dcc16..605900e 100644
--- a/TopMostFriend/BlacklistWindow.cs
+++ b/TopMostFriend/BlacklistWindow.cs
@@ -35,26 +35,27 @@ namespace TopMostFriend {
MinimizeBox = MaximizeBox = false;
MinimumSize = Size;
DialogResult = DialogResult.Cancel;
+ TopMost = true;
BlacklistView = new ListBox {
TabIndex = 101,
IntegralHeight = false,
Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right,
- Location = new Point(BUTTON_WIDTH + (SPACING * 2), SPACING),
+ Location = new Point(SPACING, SPACING),
ClientSize = new Size(ClientSize.Width - BUTTON_WIDTH - (int)(SPACING * 3.5), ClientSize.Height - (int)(SPACING * 2.5)),
};
BlacklistView.SelectedIndexChanged += BlacklistView_SelectedIndexChanged;
BlacklistView.MouseDoubleClick += BlacklistView_MouseDoubleClick;
AddButton = new Button {
- Anchor = AnchorStyles.Top | AnchorStyles.Left,
- Text = @"Add",
+ Anchor = AnchorStyles.Top | AnchorStyles.Right,
+ Text = Locale.String(@"BlacklistAdd"),
ClientSize = new Size(BUTTON_WIDTH, BUTTON_HEIGHT),
- Location = new Point(SPACING, SPACING),
+ Location = new Point(BlacklistView.Width + (SPACING * 2), SPACING),
TabIndex = 201,
};
EditButton = new Button {
- Text = @"Edit",
+ Text = Locale.String(@"BlacklistEdit"),
Location = new Point(AddButton.Location.X, AddButton.Location.Y + AddButton.Height + SPACING),
Enabled = false,
Anchor = AddButton.Anchor,
@@ -62,7 +63,7 @@ namespace TopMostFriend {
TabIndex = 202,
};
RemoveButton = new Button {
- Text = @"Remove",
+ Text = Locale.String(@"BlacklistRemove"),
Location = new Point(AddButton.Location.X, EditButton.Location.Y + AddButton.Height + SPACING),
Enabled = false,
Anchor = AddButton.Anchor,
@@ -75,16 +76,16 @@ namespace TopMostFriend {
RemoveButton.Click += RemoveButton_Click;
CancelButton = new Button {
- Anchor = AnchorStyles.Bottom | AnchorStyles.Left,
- Text = @"Cancel",
- Location = new Point(SPACING, ClientSize.Height - AddButton.ClientSize.Height - SPACING),
+ Anchor = AnchorStyles.Bottom | AnchorStyles.Right,
+ Text = Locale.String(@"BlacklistCancel"),
+ Location = new Point(AddButton.Location.X, ClientSize.Height - AddButton.ClientSize.Height - SPACING),
ClientSize = AddButton.ClientSize,
TabIndex = 10001,
};
Button acceptButton = new Button {
- Anchor = AnchorStyles.Bottom | AnchorStyles.Left,
- Text = @"Done",
- Location = new Point(SPACING, ClientSize.Height - ((AddButton.ClientSize.Height + SPACING) * 2)),
+ Anchor = AnchorStyles.Bottom | AnchorStyles.Right,
+ Text = Locale.String(@"BlacklistDone"),
+ Location = new Point(AddButton.Location.X, ClientSize.Height - ((AddButton.ClientSize.Height + SPACING) * 2)),
ClientSize = AddButton.ClientSize,
TabIndex = 10002,
};
@@ -106,16 +107,18 @@ namespace TopMostFriend {
public string Original { get; }
public string String { get => TextBox.Text; }
- private TextBox TextBox;
+ private readonly TextBox TextBox;
public BlacklistEditorWindow(string original = null) {
Original = original ?? string.Empty;
- Text = original == null ? @"Adding new entry..." : $@"Editing {original}...";
+ Text = Locale.String(original == null ? @"BlacklistEditorAdding" : @"BlacklistEditorEditing", original);
+ Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath);
StartPosition = FormStartPosition.CenterParent;
- FormBorderStyle = FormBorderStyle.FixedToolWindow;
+ FormBorderStyle = FormBorderStyle.FixedSingle;
ClientSize = new Size(500, 39);
MaximizeBox = MinimizeBox = false;
MaximumSize = MinimumSize = Size;
+ TopMost = true;
Button cancelButton = new Button {
Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Right,
@@ -123,7 +126,7 @@ namespace TopMostFriend {
Name = @"cancelButton",
Size = new Size(75, 23),
TabIndex = 102,
- Text = @"Cancel",
+ Text = Locale.String(@"BlacklistEditorCancel"),
};
cancelButton.Click += (s, e) => { DialogResult = DialogResult.Cancel; Close(); };
@@ -133,14 +136,14 @@ namespace TopMostFriend {
Name = @"saveButton",
Size = new Size(75, 23),
TabIndex = 101,
- Text = @"Save",
+ Text = Locale.String(@"BlacklistEditorSave"),
};
saveButton.Click += (s, e) => { DialogResult = DialogResult.OK; Close(); };
TextBox = new TextBox {
Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right,
Location = new Point(8, 9),
- Name = @"deviceSelection",
+ Name = @"deviceSelection", // ??????
Size = new Size(ClientSize.Width - 8 - cancelButton.Width - saveButton.Width - 19, 23),
TabIndex = 100,
Text = Original,
diff --git a/TopMostFriend/FirstRunWindow.cs b/TopMostFriend/FirstRunWindow.cs
new file mode 100644
index 0000000..1648281
--- /dev/null
+++ b/TopMostFriend/FirstRunWindow.cs
@@ -0,0 +1,357 @@
+using System;
+using System.Diagnostics;
+using System.Drawing;
+using System.Threading;
+using System.Windows.Forms;
+
+namespace TopMostFriend {
+ public class FirstRunWindow : Form {
+ public static void Display() {
+ using(FirstRunWindow firstRun = new FirstRunWindow())
+ firstRun.ShowDialog();
+ }
+
+ private bool CanClose = false;
+ private bool IsClosing = false;
+ private bool IsSizing = false;
+
+ private Button NextBtn { get; }
+ private Button PrevBtn { get; }
+
+ private Action NextAct = null;
+ private Action PrevAct = null;
+
+ private bool NextVisible { get => NextBtn.Visible; set => NextBtn.Visible = value; }
+ private bool PrevVisible { get => PrevBtn.Visible; set => PrevBtn.Visible = value; }
+
+ private Panel WorkArea { get; }
+
+ public FirstRunWindow() {
+ Text = Program.TITLE + @" v" + Application.ProductVersion.Substring(0, Application.ProductVersion.Length - 2);
+ Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath);
+ StartPosition = FormStartPosition.CenterScreen;
+ FormBorderStyle = FormBorderStyle.FixedSingle;
+ AutoScaleMode = AutoScaleMode.Dpi;
+ MaximizeBox = MinimizeBox = false;
+ TopMost = true;
+ ClientSize = new Size(410, 80);
+
+ Controls.Add(new PictureBox {
+ Image = Properties.Resources.firstrun,
+ Size = Properties.Resources.firstrun.Size,
+ Location = new Point(0, 0),
+ });
+
+ NextBtn = new Button {
+ Text = Locale.String(@"FirstRunNext"),
+ Anchor = AnchorStyles.Right | AnchorStyles.Bottom,
+ Visible = false,
+ Location = new Point(ClientSize.Width - 81, ClientSize.Height - 29),
+ };
+ NextBtn.Click += NextBtn_Click;
+ Controls.Add(NextBtn);
+
+ PrevBtn = new Button {
+ Text = Locale.String(@"FirstRunPrev"),
+ Anchor = AnchorStyles.Left | AnchorStyles.Bottom,
+ Visible = false,
+ Location = new Point(6, ClientSize.Height - 29),
+ };
+ PrevBtn.Click += PrevBtn_Click;
+ Controls.Add(PrevBtn);
+
+ WorkArea = new Panel {
+ Dock = DockStyle.Fill,
+ };
+ Controls.Add(WorkArea);
+ }
+
+ private void PrevBtn_Click(object sender, EventArgs e) {
+ if(!PrevVisible)
+ return;
+ WorkArea.Controls.Clear();
+ if(PrevAct == null)
+ Close();
+ else
+ Invoke(PrevAct);
+ }
+
+ private void NextBtn_Click(object sender, EventArgs e) {
+ if(!NextVisible)
+ return;
+ WorkArea.Controls.Clear();
+ if(NextAct == null)
+ Close();
+ else
+ Invoke(NextAct);
+ }
+
+ protected override void OnShown(EventArgs e) {
+ base.OnShown(e);
+ Update();
+ Thread.Sleep(500);
+ ShowPageIntro();
+ }
+
+ protected override void OnFormClosing(FormClosingEventArgs e) {
+ if(e.CloseReason == CloseReason.UserClosing && !CanClose) {
+ e.Cancel = true;
+
+ if(!IsClosing) {
+ IsClosing = true;
+
+ SetHeight(80, new Action(() => {
+ CanClose = true;
+ Thread.Sleep(100);
+ Close();
+ }));
+ }
+ return;
+ }
+
+ base.OnFormClosing(e);
+ }
+
+ public void SetHeight(int height, Action onFinish = null) {
+ if(height < 80)
+ throw new ArgumentException(@"target height must be more than or equal to 80.", nameof(height));
+
+ if(IsSizing)
+ return;
+ IsSizing = true;
+
+ const int timeout = 1000 / 60;
+ double time = 0;
+ double period = timeout / 400d;
+
+ int currentHeight = ClientSize.Height;
+ int currentY = Location.Y;
+ int diffHeight = height - currentHeight;
+ int diffY = diffHeight / 2;
+
+ Action setHeight = new Action(() => {
+ int newHeight = currentHeight + (int)Math.Ceiling(time * diffHeight);
+ int newY = currentY - (int)Math.Ceiling(time * diffY);
+ ClientSize = new Size(ClientSize.Width, newHeight);
+ Location = new Point(Location.X, newY);
+ });
+
+ new Thread(() => {
+ Stopwatch sw = new Stopwatch();
+
+ try {
+ do {
+ sw.Restart();
+
+ Invoke(setHeight);
+ time += period;
+
+ int delay = timeout - (int)sw.ElapsedMilliseconds;
+ if(delay > 1)
+ Thread.Sleep(delay);
+ } while(time < 1d + period);
+ } finally {
+ sw.Stop();
+ if(onFinish != null)
+ Invoke(onFinish);
+ IsSizing = false;
+ }
+ }) {
+ IsBackground = true,
+ Priority = ThreadPriority.AboveNormal,
+ }.Start();
+ }
+
+ public void ShowPageIntro() {
+ Text = Locale.String(@"FirstRunWelcomeTitle");
+ PrevVisible = false;
+ NextVisible = true;
+ NextAct = ShowPageHotKey;
+
+ SetHeight(190, new Action(() => {
+ WorkArea.Controls.Add(new Label {
+ Text = Locale.String(@"FirstRunWelcomeIntro"),
+ Location = new Point(10, 90),
+ Size = new Size(ClientSize.Width - 20, 200),
+ });
+ }));
+ }
+
+ public void ShowPageHotKey() {
+ Text = Locale.String(@"FirstRunHotKeyTitle");
+ PrevVisible = NextVisible = true;
+ PrevAct = ShowPageIntro;
+ NextAct = () => {
+ Program.SetForegroundHotKey(Settings.Get(Program.FOREGROUND_HOTKEY_SETTING, 0));
+ ShowPageElevation();
+ };
+
+ SetHeight(230, new Action(() => {
+ WorkArea.Controls.Add(new Label {
+ Text = Locale.String(@"FirstRunHotKeyExplain"),
+ Location = new Point(10, 90),
+ Size = new Size(ClientSize.Width - 20, 40),
+ });
+
+ SettingsWindow.CreateHotKeyInput(
+ WorkArea,
+ () => Settings.Get(Program.FOREGROUND_HOTKEY_SETTING, 0),
+ keyCode => Settings.Set(Program.FOREGROUND_HOTKEY_SETTING, keyCode),
+ 0,
+ 110
+ );
+
+ CheckBox flShowNotification = new CheckBox {
+ Text = Locale.String(@"FirstRunHotKeyNotify"),
+ Location = new Point(12, 170),
+ Checked = Settings.Get(Program.TOGGLE_BALLOON_SETTING, Program.ToggleBalloonDefault),
+ AutoSize = true,
+ TabIndex = 201,
+ };
+ flShowNotification.CheckedChanged += (s, e) => {
+ Settings.Set(Program.TOGGLE_BALLOON_SETTING, flShowNotification.Checked);
+ };
+ WorkArea.Controls.Add(flShowNotification);
+ }));
+ }
+
+ public void ShowPageElevation() {
+ Text = Locale.String(@"FirstRunAdminTitle");
+ PrevVisible = NextVisible = true;
+ PrevAct = ShowPageHotKey;
+ NextAct = ShowPageThanks;
+
+ SetHeight(280, () => {
+ WorkArea.Controls.Add(new Label {
+ Text = Locale.String(@"FirstRunAdminExplain"),
+ Location = new Point(10, 90),
+ Size = new Size(ClientSize.Width - 20, 40),
+ });
+
+ bool alwaysAdmin = Settings.Get(Program.ALWAYS_ADMIN_SETTING, false);
+ bool implicitAdmin = Settings.Get(Program.ALWAYS_RETRY_ELEVATED, false);
+
+ RadioButton rdAsk = new RadioButton {
+ Text = Locale.String(@"FirstRunAdminOptionAsk"),
+ Location = new Point(10, 140),
+ Size = new Size(ClientSize.Width - 20, 30),
+ Appearance = Appearance.Button,
+ Checked = !alwaysAdmin && !implicitAdmin,
+ };
+ rdAsk.CheckedChanged += (s, e) => {
+ if(rdAsk.Checked) {
+ Settings.Set(Program.ALWAYS_ADMIN_SETTING, false);
+ Settings.Set(Program.ALWAYS_RETRY_ELEVATED, false);
+ }
+ };
+ WorkArea.Controls.Add(rdAsk);
+
+ RadioButton rdImplicit = new RadioButton {
+ Text = Locale.String(@"FirstRunAdminOptionImplicit"),
+ Location = new Point(rdAsk.Location.X, rdAsk.Location.Y + 36),
+ Size = rdAsk.Size,
+ Appearance = rdAsk.Appearance,
+ Checked = implicitAdmin && !alwaysAdmin,
+ };
+ rdImplicit.CheckedChanged += (s, e) => {
+ if(rdImplicit.Checked) {
+ Settings.Set(Program.ALWAYS_ADMIN_SETTING, false);
+ Settings.Set(Program.ALWAYS_RETRY_ELEVATED, true);
+ }
+ };
+ WorkArea.Controls.Add(rdImplicit);
+
+ RadioButton rdAlways = new RadioButton {
+ Text = Locale.String(@"FirstRunAdminOptionAlways"),
+ Location = new Point(rdAsk.Location.X, rdImplicit.Location.Y + 36),
+ Size = rdAsk.Size,
+ Appearance = rdAsk.Appearance,
+ Checked = alwaysAdmin,
+ };
+ rdAlways.CheckedChanged += (s, e) => {
+ if(rdAlways.Checked) {
+ Settings.Set(Program.ALWAYS_ADMIN_SETTING, true);
+ Settings.Set(Program.ALWAYS_RETRY_ELEVATED, false);
+ }
+ };
+ WorkArea.Controls.Add(rdAlways);
+ });
+ }
+
+ public void ShowPageThanks() {
+ Text = Locale.String(@"FirstRunThanksTitle");
+ PrevVisible = NextVisible = true;
+ PrevAct = ShowPageElevation;
+ NextAct = CheckRestartNeeded;
+
+ SetHeight(270, () => {
+ Label thankYou = new Label {
+ Text = Locale.String(@"FirstRunThanksThank"),
+ Location = new Point(10, 90),
+ Size = new Size(ClientSize.Width - 20, 20),
+ };
+ WorkArea.Controls.Add(thankYou);
+
+ string updateLinkString = Locale.String(@"FirstRunThanksUpdate");
+
+ int websiteStart = updateLinkString.IndexOf(@"[WEB]");
+ updateLinkString = updateLinkString.Substring(0, websiteStart) + updateLinkString.Substring(websiteStart + 5);
+ int websiteEnd = updateLinkString.IndexOf(@"[/WEB]");
+ updateLinkString = updateLinkString.Substring(0, websiteEnd) + updateLinkString.Substring(websiteEnd + 6);
+
+ int changelogStart = updateLinkString.IndexOf(@"[CHANGELOG]");
+ updateLinkString = updateLinkString.Substring(0, changelogStart) + updateLinkString.Substring(changelogStart + 11);
+ int changelogEnd = updateLinkString.IndexOf(@"[/CHANGELOG]");
+ updateLinkString = updateLinkString.Substring(0, changelogEnd) + updateLinkString.Substring(changelogEnd + 12);
+
+ LinkLabel updateLink;
+ WorkArea.Controls.Add(updateLink = new LinkLabel {
+ Text = updateLinkString,
+ Location = new Point(10, 120),
+ Size = new Size(ClientSize.Width - 20, 34),
+ Font = thankYou.Font,
+ Links = {
+ new LinkLabel.Link(websiteStart, websiteEnd - websiteStart, @"https://flash.moe/topmostfriend"),
+ new LinkLabel.Link(changelogStart, changelogEnd - changelogStart, @"https://flash.moe/topmostfriend/changelog.php"),
+ },
+ });
+ updateLink.LinkClicked += (s, e) => {
+ Process.Start((string)e.Link.LinkData);
+ };
+
+ string settingsLinkString = Locale.String(@"FirstRunThanksSettings");
+
+ int settingsStart = settingsLinkString.IndexOf(@"[SETTINGS]");
+ settingsLinkString = settingsLinkString.Substring(0, settingsStart) + settingsLinkString.Substring(settingsStart + 10);
+ int settingsEnd = settingsLinkString.IndexOf(@"[/SETTINGS]");
+ settingsLinkString = settingsLinkString.Substring(0, settingsEnd) + settingsLinkString.Substring(settingsEnd + 11);
+
+ LinkLabel settingsLink;
+ WorkArea.Controls.Add(settingsLink = new LinkLabel {
+ Text = settingsLinkString,
+ Location = new Point(10, 160),
+ Size = new Size(ClientSize.Width - 20, 30),
+ Font = thankYou.Font,
+ Links = {
+ new LinkLabel.Link(settingsStart, settingsEnd - settingsStart),
+ },
+ });
+ settingsLink.LinkClicked += (s, e) => {
+ SettingsWindow.Display();
+ };
+
+ WorkArea.Controls.Add(new Label {
+ Text = Locale.String(@"FirstRunThanksAdmin"),
+ Location = new Point(10, 200),
+ Size = new Size(ClientSize.Width - 20, 80),
+ });
+ });
+ }
+
+ public void CheckRestartNeeded() {
+ if(Settings.Get(Program.ALWAYS_ADMIN_SETTING, false))
+ UAC.RestartElevated();
+ Close();
+ }
+ }
+}
diff --git a/TopMostFriend/Languages/Language.cs b/TopMostFriend/Languages/Language.cs
new file mode 100644
index 0000000..0c89bfd
--- /dev/null
+++ b/TopMostFriend/Languages/Language.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Linq;
+using System.Xml;
+using System.Xml.Serialization;
+
+namespace TopMostFriend.Languages {
+ [XmlRoot(@"Language")]
+ public class Language {
+ [XmlElement(@"Info")]
+ public LanguageInfo Info { get; set; }
+
+ [XmlArray(@"Strings")]
+ [XmlArrayItem(@"String", Type = typeof(LanguageString))]
+ public LanguageString[] Strings { get; set; }
+
+ public LanguageString GetString(string name) {
+ if(name == null)
+ throw new ArgumentNullException(nameof(name));
+ return Strings.FirstOrDefault(s => name.Equals(s.Name));
+ }
+ }
+}
diff --git a/TopMostFriend/Languages/LanguageInfo.cs b/TopMostFriend/Languages/LanguageInfo.cs
new file mode 100644
index 0000000..e1bc766
--- /dev/null
+++ b/TopMostFriend/Languages/LanguageInfo.cs
@@ -0,0 +1,21 @@
+using System.Xml.Serialization;
+
+namespace TopMostFriend.Languages {
+ public class LanguageInfo {
+ [XmlElement(@"Id")]
+ public string Id { get; set; }
+
+ [XmlElement(@"NameNative")]
+ public string NameNative { get; set; }
+
+ [XmlElement(@"NameEnglish")]
+ public string NameEnglish { get; set; }
+
+ [XmlElement(@"TargetVersion")]
+ public string TargetVersion { get; set; }
+
+ public override string ToString() {
+ return $@"{NameNative} / {NameEnglish} ({Id})";
+ }
+ }
+}
diff --git a/TopMostFriend/Languages/LanguageString.cs b/TopMostFriend/Languages/LanguageString.cs
new file mode 100644
index 0000000..8511cd9
--- /dev/null
+++ b/TopMostFriend/Languages/LanguageString.cs
@@ -0,0 +1,19 @@
+using System.Xml.Serialization;
+
+namespace TopMostFriend.Languages {
+ public class LanguageString {
+ [XmlAttribute(@"name")]
+ public string Name { get; set; }
+
+ [XmlText]
+ public string Value { get; set; }
+
+ public string Format(params object[] args) {
+ return string.Format(Value, args);
+ }
+
+ public override string ToString() {
+ return $@"{Name}: {Value}";
+ }
+ }
+}
diff --git a/TopMostFriend/Languages/en-GB.xml b/TopMostFriend/Languages/en-GB.xml
new file mode 100644
index 0000000..781735f
--- /dev/null
+++ b/TopMostFriend/Languages/en-GB.xml
@@ -0,0 +1,109 @@
+
+
+
+ en-GB
+ British English
+ British English
+ 1.6.0
+
+
+ An instance of {0} is already running.
+
+ &Refresh
+ &Settings
+ &About
+ &Quit
+
+ Now always on top
+ No longer always on top
+ Window has no title.
+
+ Wasn't able to change always on top status on this window.
+ Do you want to try again as an Administrator? A User Account Control dialog will pop up after selecting Yes.
+ {0} was still unable to change always on top status for the window. It might be protected by Windows.
+
+ About {0}
+ Close
+ Website
+ Donate
+
+ {0} Settings
+ Apply
+ Cancel
+ OK
+
+ Hot Keys
+ Reset
+ Toggle always on top status on active window
+
+ Options
+ Show notification when using toggle hot key
+ Show icon of window affected by hot key
+ Always retry changing always on top status as Administrator on first failure
+ SHIFT+CLICK items in the tray area list to add to the title blacklist
+ Revert status to before {0} altered them on exit
+ Show list of open windows in the tray area menu
+ Always run as Administrator
+
+ Language
+ You should restart {0} for the language change to fully take effect. A lot of text will still appear in the previously selected language.
+
+ Other
+ Manage Blacklist...
+ Title Blacklist
+ Start with Windows...
+
+Should {0} start with Windows?
+Clicking Yes will create a shortcut to the {0} in your Start-up folder, clicking No will delete the shortcut.
+Make sure you've placed your {0} executable in a permanent location before clicking Yes.
+
+ Reset All Settings...
+
+This will reset all {0} settings and restart the application.
+Are you absolutely sure?
+
+
+ Add
+ Edit
+ Remove
+ Cancel
+ Done
+
+ Adding new entry...
+ Editing {0}...
+ Cancel
+ Save
+
+ Next
+ Previous
+
+ Welcome to {0}!
+
+{0} is a utility that lets you manage the always-on-top state of windows from programs you have open.
+
+You will now be taken through a few steps to configure {0} to your liking!
+
+
+ Selecting a hot key
+
+Select a global hot key for toggling the always on top status of whatever window is currently in the foreground.
+If you don't wish to specify one just hit Reset followed by Next.
+
+ Show a notification when the hot key has been used
+
+ Administrator actions
+
+Sometimes {0} is not able to change the always on top state for certain windows without having administrative privileges.
+{0} provides multiple options for this situation:
+
+ Ask me what to do when it's required.
+ Ask for administrative privileges automatically when required.
+ Always run {0} as Administrator (not recommended).
+
+ Thank you!
+ Thank you for using {0}!
+ Be sure to check the {0} [WEB]website[/WEB] and [CHANGELOG]changelog[/CHANGELOG] for updates from time to time.
+ There are more options you may be interested in taking a look at in the [SETTINGS]Settings[/SETTINGS] window.
+ If you have chosen to always run {0} as Administrator, you will receive a User Account Control prompt after hitting next.
+
+
diff --git a/TopMostFriend/Languages/nl-NL.xml b/TopMostFriend/Languages/nl-NL.xml
new file mode 100644
index 0000000..c1fa96d
--- /dev/null
+++ b/TopMostFriend/Languages/nl-NL.xml
@@ -0,0 +1,109 @@
+
+
+
+ nl-NL
+ Nederlands
+ Dutch
+ 1.6.0
+
+
+ {0} is al gestart.
+
+ &Verversen
+ &Instellingen
+ &Over
+ &Sluiten
+
+ Nu altijd op voorgrond
+ Niet langer altijd op voorgrond
+ Venster heeft geen titel.
+
+ Het was niet mogelijk om de altijd op voorgrond status van dit venster te veranderen.
+ Wil je het opnieuw proberen als Administrator? Een Gebruikersaccountbeheer dialoog zal verschijnen als je op Ja klikt.
+ {0} kon nog steeds niet de altijd op voorgrond status van dit venster veranderen. Het wordt mogelijk beschermd door Windows.
+
+ Over {0}
+ Sluiten
+ Website
+ Doneer
+
+ {0} Instellingen
+ Toepassen
+ Annuleren
+ OK
+
+ Sneltoetsen
+ Herstel
+ Schakel altijd op voorgrond status op actief venster
+
+ Opties
+ Toon notificatie als de sneltoets gebruikt is
+ Toon icoon van venster dat beïnvloed was door de sneltoets
+ Probeer altijd opnieuw als Administrator bij de eerste mislukking van het veranderen van de status
+ SHIFT+CLICK items in de systeemvak lijst om ze toe te voegen aan de titel blacklist
+ Herstel status naar voordat {0} ze heeft aangepast tijdens het sluiten
+ Geef lijst met open venster weer in het systeemvak menu
+ Start altijd als Administrator
+
+ Taal (Language)
+ Je moet {0} opnieuw starten om de verandering van de taal volledig effect te laten nemen. Veel tekst zal nog steeds verschijnen in de voorheen geselecteerde taal.
+
+ Overig
+ Beheer Blacklist...
+ Titel Blacklist
+ Start met Windows...
+
+Moet {0} tegelijkertijd met Windows starten?
+Als je op Ja klikt zal een een snelkoppeling aangemaakt worden in de Opstarten map van het start menu, als je op Nee klikt wordt de snelkoppeling verwijderd.
+Zorg ervoor dat je het EXE bestand voor {0} in een permanente locatie hebt voordat je op Ja klikt.
+
+ Herstel alle instellingen...
+
+Dit zal alle instellingen verwijderen en het programma opnieuw opstarten.
+Weet je het absoluut zeker?
+
+
+ Toevoegen
+ Bewerken
+ Verwijderen
+ Annuleren
+ Klaar
+
+ Nieuw item toevoegen...
+ {0} bewerken...
+ Annuleren
+ Opslaan
+
+ Volgende
+ Vorige
+
+ Welkom bij {0}!
+
+{0} is een hulpprogramma dat jou controle geeft over de altijd-op-voorgrond status van programma's die je open hebt.
+
+Je zal nu door een aantal stappen genomen worden om {0} naar jouw wens te configureren!
+
+
+ Sneltoets selecteren
+
+Selecteer een globale sneltoets voor het wijzigen van de altijd op voorgrond status van het venster dat op dat moment in de voorgrond staat.
+Als je geen sneltoets toe wil wijzen, druk op Herstel en vervolgens op Volgende.
+
+ Toon een melding wanneer de sneltoets is gebruikt
+
+ Administrator acties
+
+Soms kan {0} niet de altijd op voorgrond status van bepaalde vensters aanpassen omdat daar administratieve rechten voor nodig zijn.
+{0} geeft je de volgende opties voor deze situatie:
+
+ Vraag mij wat te doen wanneer het nodig is.
+ Vraag mij automatisch om administratieve rechten wanneer het nodig is.
+ Start {0} altijd als Administrator (niet aanbevolen).
+
+ Bedankt!
+ Bedankt voor het gebruiken van {0}!
+ Controlleer de {0} [WEB]website[/WEB] en [CHANGELOG]changelog[/CHANGELOG] eens in de zoveel tijd om te kijken of er updates zijn.
+ Naast de opties die je al gekregen hebt, zijn er nog meer beschikbaar in het [SETTINGS]Instellingen[/SETTINGS] venster als je daarin geïnteresseerd bent.
+ Als je gekozen hebt om {0} altijd als Administrator te starten, zal je een Gebruikersaccountbeheer dialoog zien nadat je op volgende klikt.
+
+
diff --git a/TopMostFriend/Locale.cs b/TopMostFriend/Locale.cs
new file mode 100644
index 0000000..9c50742
--- /dev/null
+++ b/TopMostFriend/Locale.cs
@@ -0,0 +1,89 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Xml;
+using System.Xml.Serialization;
+using TopMostFriend.Languages;
+
+namespace TopMostFriend {
+ public static class Locale {
+ public const string DEFAULT = @"en-GB";
+ private static XmlSerializer Serializer { get; }
+ private static Dictionary Languages { get; }
+ private static Language ActiveLanguage { get; set; }
+
+ static Locale() {
+ Serializer = new XmlSerializer(typeof(Language));
+ Languages = new Dictionary();
+ Assembly currentAsm = Assembly.GetExecutingAssembly();
+ string[] resources = currentAsm.GetManifestResourceNames();
+
+ foreach(string resource in resources)
+ if(resource.StartsWith(@"TopMostFriend.Languages.") && resource.EndsWith(@".xml"))
+ using(Stream resourceStream = currentAsm.GetManifestResourceStream(resource))
+ LoadLanguage(resourceStream);
+ }
+
+ public static string LoadLanguage(Stream stream) {
+ Language lang = (Language)Serializer.Deserialize(stream);
+ foreach(LanguageString ls in lang.Strings)
+ ls.Value = ls.Value.Trim();
+
+ Languages.Add(lang.Info.Id, lang);
+ if(ActiveLanguage == null && DEFAULT.Equals(lang.Info.Id))
+ ActiveLanguage = lang;
+
+#if DEBUG
+ Debug.WriteLine(@" ==========");
+ Debug.WriteLine(lang.Info);
+ foreach(LanguageString str in lang.Strings)
+ Debug.WriteLine(str);
+ Debug.WriteLine(string.Empty);
+#endif
+
+ return lang.Info.Id;
+ }
+
+ public static LanguageInfo GetCurrentLanguage() {
+ return ActiveLanguage.Info;
+ }
+
+ public static LanguageInfo[] GetAvailableLanguages() {
+ return Languages.Values.Select(l => l.Info).ToArray();
+ }
+
+ public static string GetPreferredLanguage() {
+ return Settings.Has(Program.LANGUAGE)
+ ? Settings.Get(Program.LANGUAGE, DEFAULT)
+ : CultureInfo.InstalledUICulture.Name;
+ }
+
+ public static void SetLanguage(string langId) {
+ if(!Languages.ContainsKey(langId))
+ langId = DEFAULT;
+ ActiveLanguage = Languages[langId];
+ }
+
+ public static void SetLanguage(LanguageInfo langInfo) {
+ SetLanguage(langInfo.Id);
+ }
+
+ public static string String(string name, params object[] args) {
+ LanguageString str = ActiveLanguage.GetString(name);
+ if(str == null)
+ return name;
+
+ List