Skip to content

Commit

Permalink
## Hourglass Changes
Browse files Browse the repository at this point in the history
### Installer

- Adds the **Hourglass** executable path to the Windows App Paths, so the **Hourglass** command-line is available out of the box.

### UI

- Uses a Windows Task Dialog instead of a message box.

### Notification Area

- The double click shows/hides all the timer windows.
- All the timers are arranged by the time left in the notification area context menu.
- The nearest remaining running timers are shown in the notification area tooltip.
- The **Exit** menu asks to close all the running timer windows if the **Prompt on exit** option is on.
- The new **Command-line usage** menu.

### Timer Windows

- The `Esc` shortcut minimizes the timer window.
- The `F11` shortcut makes the timer window full screen and back.
- The `Ctrl+N` shortcut creates a new timer window.
- The mouse double-click on progress border makes the timer window full screen and back.
- The minimum timer window size is limited by the Windows.
- The timer tooltip is shown if the timer window size is too small.
- All the timer windows are arranged by the time left. The order of timer windows is new, expired, paused, running.
- When the timer window is minimized or closed the next visible non-minimized timer window is activated.
- The **Window title** submenu is available directly from the timer window context menu.
- The **Reset bounds** menu item sets the timer window default position and size.
- The **Restore**, **Minimize** and **Maximize** timer window commands are always present in the timer window context menu.
- All the timer window commands are available in the timer window context menu.
- Shortcuts are displayed in the timer window context menu.
- The progress bar changes direction to vertical when the height is more than the width and vice versa.
- The switching between light and dark themes is improved.
- The **Display time in the digital clock format** menu item toggles the displayed time digital clock time format. It can be found under the **Advanced options** submenu of the timer window context menu. Command-line option is  `--digital-clock-time` / `-c`

### Misc

- The **Hourglass** is built deterministically using the GitHub Actions.
  • Loading branch information
i2van authored and ivan-ivon committed Jan 15, 2024
1 parent 06a5f63 commit e16bb79
Show file tree
Hide file tree
Showing 30 changed files with 320 additions and 119 deletions.
2 changes: 1 addition & 1 deletion Hourglass.Bundle/Bundle.wxs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:bal="http://schemas.microsoft.com/wix/BalExtension">
<Bundle Name="Hourglass"
Version="1.15.14.0"
Version="1.15.15.0"
Manufacturer="2021 Chris Dziemborowicz, 2024 Ivan Ivon"
UpgradeCode="f1d002c9-cfc9-40fb-84af-96e7aec26e0b"
IconSourceFile="$(var.Hourglass.ProjectDir)Resources\AppIcon.ico">
Expand Down
2 changes: 1 addition & 1 deletion Hourglass.Setup/Product.wxs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:netfx="http://schemas.microsoft.com/wix/NetFxExtension">
<Product Id="*" Name="Hourglass" Language="1033" Version="1.15.14.0" Manufacturer="Chris Dziemborowicz, Ivan Ivon" UpgradeCode="172d3713-8820-4374-8195-3e2374e7724f">
<Product Id="*" Name="Hourglass" Language="1033" Version="1.15.15.0" Manufacturer="Chris Dziemborowicz, Ivan Ivon" UpgradeCode="172d3713-8820-4374-8195-3e2374e7724f">
<Package InstallerVersion="200" Compressed="yes" InstallScope="perMachine"/>

<Icon Id="AppIcon.exe" SourceFile="$(var.Hourglass.ProjectDir)Resources\AppIcon.ico"/>
Expand Down
4 changes: 2 additions & 2 deletions Hourglass.Test/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
[assembly: Guid("002a4be7-7323-4bf9-ab08-5fc8978d9eb0")]
[assembly: AssemblyVersion("1.15.14.0")]
[assembly: AssemblyFileVersion("1.15.14.0")]
[assembly: AssemblyVersion("1.15.15.0")]
[assembly: AssemblyFileVersion("1.15.15.0")]
3 changes: 3 additions & 0 deletions Hourglass/App.config
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@
<setting name="Prefer24HourTime" serializeAs="String">
<value>False</value>
</setting>
<setting name="DigitalClockTime" serializeAs="String">
<value>False</value>
</setting>
</Hourglass.Properties.Settings>
</userSettings>
</configuration>
35 changes: 33 additions & 2 deletions Hourglass/CommandLineArguments.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public static string Usage
{
string assemblyLocation = Assembly.GetEntryAssembly()!.CodeBase;
string assemblyFileName = Path.GetFileName(assemblyLocation);
return string.Format(Resources.Usage, assemblyFileName.ToLowerInvariant());
return Resources.Usage.Replace("hourglass.exe", assemblyFileName.ToLowerInvariant());
}
}

Expand Down Expand Up @@ -102,6 +102,11 @@ public static string Usage
/// </summary>
public bool ReverseProgressBar { get; private set; }

/// <summary>
/// Gets a value indicating whether to display time in the digital clock format.
/// </summary>
public bool DigitalClockTime { get; private set; }

/// <summary>
/// Gets a value indicating whether to show the time elapsed rather than the time left.
/// </summary>
Expand Down Expand Up @@ -228,6 +233,7 @@ public TimerOptions GetTimerOptions()
ShowProgressInTaskbar = ShowProgressInTaskbar,
DoNotKeepComputerAwake = DoNotKeepComputerAwake,
ReverseProgressBar = ReverseProgressBar,
DigitalClockTime = DigitalClockTime,
ShowTimeElapsed = ShowTimeElapsed,
LoopTimer = LoopTimer,
PopUpWhenExpired = PopUpWhenExpired,
Expand Down Expand Up @@ -277,6 +283,7 @@ private static CommandLineArguments GetArgumentsFromMostRecentOptions()
ShowProgressInTaskbar = options.ShowProgressInTaskbar,
DoNotKeepComputerAwake = options.DoNotKeepComputerAwake,
ReverseProgressBar = options.ReverseProgressBar,
DigitalClockTime = options.DigitalClockTime,
ShowTimeElapsed = options.ShowTimeElapsed,
ShowInNotificationArea = Settings.Default.ShowInNotificationArea,
LoopTimer = options.LoopTimer,
Expand Down Expand Up @@ -317,6 +324,7 @@ private static CommandLineArguments GetArgumentsFromFactoryDefaults()
ShowProgressInTaskbar = defaultOptions.ShowProgressInTaskbar,
DoNotKeepComputerAwake = defaultOptions.DoNotKeepComputerAwake,
ReverseProgressBar = defaultOptions.ReverseProgressBar,
DigitalClockTime = defaultOptions.DigitalClockTime,
ShowTimeElapsed = defaultOptions.ShowTimeElapsed,
ShowInNotificationArea = false,
LoopTimer = defaultOptions.LoopTimer,
Expand Down Expand Up @@ -455,6 +463,19 @@ private static CommandLineArguments GetCommandLineArguments(IEnumerable<string>
argumentsBasedOnFactoryDefaults.ReverseProgressBar = reverseProgressBar;
break;

case "--digital-clock-time":
case "-c":
ThrowIfDuplicateSwitch(specifiedSwitches, "--digital-clock-time");

bool digitalClockTime = GetBoolValue(
arg,
remainingArgs,
argumentsBasedOnMostRecentOptions.DigitalClockTime);

argumentsBasedOnMostRecentOptions.DigitalClockTime = digitalClockTime;
argumentsBasedOnFactoryDefaults.DigitalClockTime = digitalClockTime;
break;

case "--show-time-elapsed":
case "-u":
ThrowIfDuplicateSwitch(specifiedSwitches, "--show-time-elapsed");
Expand Down Expand Up @@ -1011,7 +1032,7 @@ private static Rect GetRectValue(string arg, Queue<string> remainingArgs, Rect l
arg,
value);

throw new ParseException(message);
throw new ParseException(message, ex);
}
}

Expand Down Expand Up @@ -1104,5 +1125,15 @@ public ParseException(string message)
: base(message)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="ParseException"/> class.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference (<see langword="Nothing" /> in Visual Basic) if no inner exception is specified.</param>
public ParseException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}
66 changes: 40 additions & 26 deletions Hourglass/Extensions/TimeSpanExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,48 +47,61 @@ public static TimeSpan RoundUp(this TimeSpan timeSpan)
/// </summary>
/// <param name="timeSpan">A <see cref="TimeSpan"/>.</param>
/// <param name="provider">An <see cref="IFormatProvider"/>.</param>
/// <param name="compact">Use compact time format.</param>
/// <returns>The natural string representation of the <see cref="TimeSpan"/>.</returns>
public static string ToNaturalString(this TimeSpan timeSpan, IFormatProvider provider)
public static string ToNaturalString(this TimeSpan timeSpan, IFormatProvider provider, bool compact)
{
List<string> parts = new();
return compact
#pragma warning disable S3358
? timeSpan.ToString(
timeSpan.Days != 0
? Resources.CompactTimeSpanWithDaysFormat
: Resources.CompactTimeSpanFormat)
#pragma warning restore S3358
: string.Join(
Resources.ResourceManager.GetString("TimeSpanExtensionsUnitSeparator", provider),
GetParts());

// Days
if (timeSpan.Days != 0)
IEnumerable<string> GetParts()
{
parts.Add(GetStringWithUnits(timeSpan.Days, "Day", provider));
}
bool hasValue = false;

// Hours
if (timeSpan.Hours != 0 || parts.Count != 0)
{
parts.Add(GetStringWithUnits(timeSpan.Hours, "Hour", provider));
}
// Days
if (timeSpan.Days != 0)
{
hasValue = true;
yield return GetStringWithUnits(timeSpan.Days, "Day", provider);
}

// Minutes
if (timeSpan.Minutes != 0 || parts.Count != 0)
{
parts.Add(GetStringWithUnits(timeSpan.Minutes, "Minute", provider));
}
// Hours
if (timeSpan.Hours != 0 || hasValue)
{
hasValue = true;
yield return GetStringWithUnits(timeSpan.Hours, "Hour", provider);
}

// Seconds
parts.Add(GetStringWithUnits(timeSpan.Seconds, "Second", provider));
// Minutes
if (timeSpan.Minutes != 0 || hasValue)
{
yield return GetStringWithUnits(timeSpan.Minutes, "Minute", provider);
}

// Join parts
return string.Join(
Resources.ResourceManager.GetString("TimeSpanExtensionsUnitSeparator", provider),
parts);
// Seconds
yield return GetStringWithUnits(timeSpan.Seconds, "Second", provider);
}
}

/// <summary>
/// Converts the value of a <see cref="Nullable{TimeSpan}"/> object to its equivalent natural string
/// representation.
/// </summary>
/// <param name="timeSpan">A <see cref="Nullable{TimeSpan}"/>.</param>
/// <param name="compact">Use compact time format.</param>
/// <returns>The natural string representation of the <see cref="TimeSpan"/> represented by <paramref
/// name="timeSpan"/>, or <see cref="string.Empty"/> if <paramref name="timeSpan"/> is <c>null</c>.</returns>
public static string ToNaturalString(this TimeSpan? timeSpan)
public static string ToNaturalString(this TimeSpan? timeSpan, bool compact)
{
return timeSpan.ToNaturalString(CultureInfo.CurrentCulture);
return timeSpan.ToNaturalString(CultureInfo.CurrentCulture, compact);
}

/// <summary>
Expand All @@ -97,11 +110,12 @@ public static string ToNaturalString(this TimeSpan? timeSpan)
/// </summary>
/// <param name="timeSpan">A <see cref="Nullable{TimeSpan}"/>.</param>
/// <param name="provider">An <see cref="IFormatProvider"/>.</param>
/// <param name="compact">Use compact time format.</param>
/// <returns>The natural string representation of the <see cref="TimeSpan"/> represented by <paramref
/// name="timeSpan"/>, or <see cref="string.Empty"/> if <paramref name="timeSpan"/> is <c>null</c>.</returns>
public static string ToNaturalString(this TimeSpan? timeSpan, IFormatProvider provider)
public static string ToNaturalString(this TimeSpan? timeSpan, IFormatProvider provider, bool compact)
{
return timeSpan.HasValue ? timeSpan.Value.ToNaturalString(provider) : string.Empty;
return timeSpan.HasValue ? timeSpan.Value.ToNaturalString(provider, compact) : string.Empty;
}

/// <summary>
Expand Down
5 changes: 5 additions & 0 deletions Hourglass/Extensions/TimerWindowExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ public static void BringNextToFrontAndActivate(this TimerWindow thisWindow)

TimerWindow GetNextWindow()
{
if (Application.Current is null)
{
return null;
}

var allWindows = Application.Current.Windows.OfType<TimerWindow>().Arrange().ToList();

return GetNextApplicableWindow(allWindows.SkipWhile(NotThisWindow).Skip(1)) ??
Expand Down
3 changes: 2 additions & 1 deletion Hourglass/Extensions/WindowExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,8 @@ private static Rect Offset(this Rect rect)
return rect;
}

const int OffsetAmount = 25;
const int OffsetAmount = 0;

Rect offsetRect = rect;

// Move the rect down and to the right
Expand Down
4 changes: 2 additions & 2 deletions Hourglass/Managers/SoundManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,14 @@ private SoundManager()
/// Gets a collection of the sounds stored in the assembly.
/// </summary>
#pragma warning disable S2365
public IList<Sound> BuiltInSounds => _sounds.Where(s => s.IsBuiltIn).ToList();
public IList<Sound> BuiltInSounds => _sounds.Where(static s => s.IsBuiltIn).ToList();
#pragma warning restore S2365

/// <summary>
/// Gets a collection of the sounds stored in the file system.
/// </summary>
#pragma warning disable S2365
public IList<Sound> UserProvidedSounds => _sounds.Where(s => !s.IsBuiltIn).ToList();
public IList<Sound> UserProvidedSounds => _sounds.Where(static s => !s.IsBuiltIn).ToList();
#pragma warning restore S2365

/// <summary>
Expand Down
7 changes: 5 additions & 2 deletions Hourglass/Managers/ThemeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ namespace Hourglass.Managers;
/// </summary>
public sealed class ThemeManager : Manager
{
private const string DefaultThemeIdentifier = "blue";
private const string DefaultDarkThemeIdentifier = $"{DefaultThemeIdentifier}-dark";

/// <summary>
/// Singleton instance of the <see cref="ThemeManager"/> class.
/// </summary>
Expand All @@ -38,12 +41,12 @@ private ThemeManager()
/// <summary>
/// Gets the default theme.
/// </summary>
public Theme DefaultTheme => GetThemeByIdentifier("blue");
public Theme DefaultTheme => GetThemeByIdentifier(DefaultThemeIdentifier);

/// <summary>
/// Gets the default dark theme.
/// </summary>
public Theme DefaultDarkTheme => GetThemeByIdentifier("blue-dark");
public Theme DefaultDarkTheme => GetThemeByIdentifier(DefaultDarkThemeIdentifier);

/// <summary>
/// Gets a collection of the themes stored in the assembly.
Expand Down
2 changes: 1 addition & 1 deletion Hourglass/Managers/TimerManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,6 @@ public void ClearResumableTimers()
/// <returns>A value indicating whether the timer is bound to any <see cref="TimerWindow"/>. </returns>
private static bool IsBoundToWindow(Timer timer)
{
return Application.Current?.Windows.OfType<TimerWindow>().Any(w => w.Timer == timer) == true;
return Application.Current?.Windows.OfType<TimerWindow>().Any(w => ReferenceEquals(w.Timer, timer)) == true;
}
}
2 changes: 1 addition & 1 deletion Hourglass/Properties/App.manifest
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<assemblyIdentity version="1.15.14.0" name="Hourglass"/>
<assemblyIdentity version="1.15.15.0" name="Hourglass"/>
<description>The simple countdown timer for Windows.</description>
<dependency>
<dependentAssembly>
Expand Down
4 changes: 2 additions & 2 deletions Hourglass/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
[assembly: AssemblyCopyright("Copyright © 2021 Chris Dziemborowicz, 2024 Ivan Ivon")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: AssemblyVersion("1.15.14.0")]
[assembly: AssemblyFileVersion("1.15.14.0")]
[assembly: AssemblyVersion("1.15.15.0")]
[assembly: AssemblyFileVersion("1.15.15.0")]
[assembly: NeutralResourcesLanguage("en-US")]
[assembly: Guid("83DBAA61-6193-4288-BBB7-BEAEC33FE321")]
[assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]
Expand Down
Loading

0 comments on commit e16bb79

Please sign in to comment.