-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTimerForm.cs
More file actions
340 lines (256 loc) · 12.3 KB
/
TimerForm.cs
File metadata and controls
340 lines (256 loc) · 12.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
// Timer form logic
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using Microsoft.Win32;
using System.Reflection;
using System.Media;
using System.Threading.Tasks;
using static System.Windows.Forms.VisualStyles.VisualStyleElement.Button;
using Microsoft.VisualBasic;
using System.Diagnostics;
namespace KidTimerLock
{
public partial class TimerForm : Form
{
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern bool LockWorkStation();
private System.Windows.Forms.Timer countdownTimer;
private int remainingSeconds; // track seconds before countdown expires
private readonly int[] warningMinutes = new[] { 10, 5, 1 }; // minute intervals to show flashing warning - hard-coded for now
private HashSet<int> warningsShown = new(); // keeps track of which warnings have been shown so far
private Timer hideLabelTimer; // timer for the "Autostart enabled" label show/hide timer
private string itchIoLink = "https://dobbobbs.itch.io/kid-timer-lock"; // hard coding the Itch.io link
private string paypalLink = "https://www.paypal.com/ncp/payment/BT6KTSATEZW4E"; // hard-coded PayPal link
public TimerForm()
{
InitializeComponent();
/* Create a timer to hide the autostart enabled confirmation */
hideLabelTimer = new Timer();
hideLabelTimer.Interval = 5000; // 5 seconds
hideLabelTimer.Tick += HideLabelTimer_Tick;
autostartCheckbox.Checked = IsAutoStartEnabled(); // toggle Autostart enable/disable
autostartCheckbox.CheckedChanged += (s, e) =>
{
SetAutoStart(autostartCheckbox.Checked);
};
this.FormClosing += TimerForm_FormClosing; // catch the X closing event to hide instead of close
buttonStartAccept(); // default Enter to Start button
this.CancelButton = buttonStop; // Stop the timer on escape
ConfigManager.Load(); // load settings from config file
// associate GUI controls with config settings
comboFlashCount.Tag = nameof(ConfigManager.Config.FlashCount);
comboFlashCount.SelectedItem = ConfigManager.Config.FlashCount;
comboFlashInterval.Tag = nameof(ConfigManager.Config.FlashInterval);
comboFlashInterval.SelectedItem = ConfigManager.Config.FlashInterval;
checkBoxAudibleBeep.Tag = nameof(ConfigManager.Config.AudibleBeep);
checkBoxAudibleBeep.Checked = ConfigManager.Config.AudibleBeep;
linkVersionLabel.Text = $"Version: {VersionInfo.CurrentVersion}"; // display current version from version.txt
}
private void buttonStart_Click(object sender, EventArgs e)
{
buttonStopAccept(); // Set enter to map to Stop button once started (esc works as well)
ConfigManager.Reload(); // get the updated settings
if (int.TryParse(textBoxMinutes.Text, out int minutes) && minutes > 0) // parse minutes into seconds and start a new timer
{
remainingSeconds = minutes * 60;
countdownTimer = new System.Windows.Forms.Timer();
countdownTimer.Interval = 1000; // 1 second
countdownTimer.Tick += CountdownTimer_Tick;
countdownTimer.Start();
buttonStart.Enabled = false;
buttonStop.Enabled = true;
textBoxMinutes.Enabled = false;
this.Hide(); // hide window once timer started
}
else
{
MessageBox.Show("Please enter a valid number of minutes.", "Invalid input", MessageBoxButtons.OK, MessageBoxIcon.Warning);
buttonStartAccept();
this.Show();
}
}
private void buttonStop_Click(object sender, EventArgs e)
{
countdownTimer.Stop();
buttonStartAccept(); // Enter now maps to Start button again
buttonStart.Enabled = true;
buttonStop.Enabled = false;
textBoxMinutes.Enabled = true;
labelTimeRemaining.Text = $"Timer stopped";
WarningOverlayForm.CloseAllOverlays(); // close any overlays instantiated in this countdown
warningsShown.Clear(); // clear flag for which warnings have been shown
FocusMinutesInput(); // give focus back to minutes input field
}
private void CountdownTimer_Tick(object sender, EventArgs e) // countdown timer logic
{
remainingSeconds--;
int remainingMinutes = (int)Math.Ceiling(remainingSeconds / 60.0);
if (warningMinutes.Contains(remainingMinutes) && !warningsShown.Contains(remainingMinutes)) // show the warning overlay if minutes are equal to one of the unused timer intervals
{
warningsShown.Add(remainingMinutes); // push the last interval used onto the list of used warning intervals
ShowWarningOverlay(remainingMinutes);
}
labelTimeRemaining.Text = $"Time remaining: {remainingSeconds / 60:D2}:{remainingSeconds % 60:D2}"; // update visible countdown timer
if (remainingSeconds <= 0) // logic for when timer finished
{
countdownTimer.Stop();
buttonStart.Enabled = true;
textBoxMinutes.Enabled = true;
WarningOverlayForm.CloseAllOverlays(); // close any overlays instantiated in this countdown
warningsShown.Clear(); // clear flags
if (ConfigManager.Config.AudibleBeep) Task.Run(() => Console.Beep(1000, 3000)); // beep on timer end/lock - also conditional on GUI setting
LockWorkStation();
}
}
private void ShowWarningOverlay(int minutesLeft) // new warning overlay
{
string message = $"⚠ {minutesLeft} minute{(minutesLeft == 1 ? "" : "s")} remaining! ⚠";
var warning = new WarningOverlayForm(message);
warning.Show();
}
protected override void OnDeactivate(EventArgs e) // hide window when it loses focus
{
base.OnDeactivate(e);
this.Hide(); // Hide when the window loses focus
}
private const string AppName = "KidTimerLock";
private bool IsAutoStartEnabled() // check if autostart already enabled in the registry for app - logic for checkbox value
{
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run", false))
{
return key?.GetValue(AppName) != null;
}
}
private void SetAutoStart(bool enable) // set autostart key in registry for app based on current running directory - maybe not ideal for a portable app but want to avoid having an installer etc.
{
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Run", true))
{
if (enable) // add registry key
{
string exePath = System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName;
key.SetValue(AppName, $"\"{exePath}\"");
// show the little confirmation - although technically does not actually check the registry change was successful, something to revisit in future
autoStartLabel.Text = "Autostart enabled!";
autoStartLabel.Visible = true;
hideLabelTimer.Stop(); // In case it was already running
hideLabelTimer.Start(); // Start fresh countdown
}
else //
{
key.DeleteValue(AppName, false); // remove registry key
// same re: confirmation
autoStartLabel.Text = "Autostart disabled!";
autoStartLabel.Visible = true;
hideLabelTimer.Stop(); // In case it was already running
hideLabelTimer.Start(); // Start fresh countdown
}
}
}
private void autoStartLabel_Click(object sender, EventArgs e)
{
}
private void HideLabelTimer_Tick(object sender, EventArgs e) // hide the "austostart enabled" notification label
{
autoStartLabel.Visible = false;
hideLabelTimer.Stop();
}
private void TimerForm_Load(object sender, EventArgs e) // reload the config when the Timer Form is instantiated
{
ConfigManager.Reload();
}
private void GenericControlChanged(object sender, EventArgs e) // generic handler to update config when a setting is changed, handling multiple property types
{
if (sender is Control control && control.Tag is string settingKey)
{
object? newValue = control switch // handling behaviour for different property types
{
ComboBox cb => cb.SelectedItem,
System.Windows.Forms.CheckBox chk => chk.Checked,
/* NumericUpDown nud => nud.Value, */
TextBox tb => tb.Text,
_ => null
};
if (newValue == null) return;
var config = ConfigManager.Config;
var prop = config.GetType().GetProperty(settingKey);
if (prop != null)
{
object converted = Convert.ChangeType(newValue, prop.PropertyType); // make sure properties are the right type
prop.SetValue(config, converted);
// Debug.WriteLine($"Saving config");
ConfigManager.Save();
ConfigManager.Reload();
}
}
}
private void flashCount_toolTip_Popup(object sender, PopupEventArgs e)
{
}
private void flashInterval_toolTip_Popup(object sender, PopupEventArgs e)
{
}
private void audibleBeeptoolTip_Popup(object sender, PopupEventArgs e)
{
}
public void FocusMinutesInput() // always set focus to the minutes input field, to avoid mouse clicks
{
if (textBoxMinutes.Enabled && textBoxMinutes.Visible)
{
textBoxMinutes.Focus();
textBoxMinutes.Select();
}
}
private void TimerForm_FormClosing(object sender, FormClosingEventArgs e)
{
// Hide form on X instead of disposing
e.Cancel = true;
this.Hide();
}
private void linkVersionLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) // handle clicks on version number - direct to ItchIo
{
try
{
System.Diagnostics.Process.Start(new ProcessStartInfo
{
FileName = itchIoLink,
UseShellExecute = true // Important for .NET Core and .NET 5+
});
}
catch (Exception ex)
{
MessageBox.Show($"Unable to open link: {ex.Message}");
}
}
private void paypalLinkLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) // handle clicks on version number - direct to ItchIo
{
try
{
System.Diagnostics.Process.Start(new ProcessStartInfo
{
FileName = paypalLink,
UseShellExecute = true // Important for .NET Core and .NET 5+
});
}
catch (Exception ex)
{
MessageBox.Show($"Unable to open link: {ex.Message}");
}
}
public void buttonStartAccept() // setter to map Start button to Accept
{
this.AcceptButton = buttonStart;
}
public void buttonStopAccept() // setter to map Stop button to Accept
{
this.AcceptButton = buttonStop;
}
private void label4_Click(object sender, EventArgs e)
{
}
private void label4_Click_1(object sender, EventArgs e)
{
}
}
}