Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,15 @@ versioned baseline.
GUI runner's are INI files that set any of its colors by role, and **Save Theme Template...**
saves Carbon's as one to start from; the VCL GUI runner's are VCL style files (`.vsf`). The
choice is saved in the layout file, and a theme file's problems are reported in the log.
- Four more built-in dark themes in the Win32 GUI runner, tints of Carbon: Ember (warm brown),
Forest (green), Midnight (blue) and Plum (purple), listed in **View > Theme > Dark Theme** above
the theme files. The colors that show how tests did (the tree's status dots, failed and skipped
text, a failed run's progress bar) stay Carbon's.
- Four of RAD Studio's dark VCL styles as built-in dark themes in both GUI runners, beside Carbon:
Charcoal Dark Slate, Cobalt XEMedia, Onyx Blue and Tablet Dark. The VCL GUI runner's project
links them in, and a VCL GUI project of your own lists the ones it links; the Win32 GUI runner
paints their colors. The Win32 GUI runner's theme files can also set `ButtonText`,
`ButtonHotText` and `SelectionText`, new roles for a button's text and the tree's selected text.
- An ASWUnitTests icon, `resource\ASWUnitTests.ico`, which this repository's console and GUI
runners now have on Windows, with the PNGs it's made from and `resource\make_icon.ps1`, which
draws them. `ASWUNITTESTS_WIN32_GUI_SOURCES` includes `resource\ASWUnitTests_Icon.rc`, so a Win32
Expand Down
20 changes: 13 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -393,13 +393,17 @@ the Win32 GUI runner's dark look, the title bar is dark unless Windows shows the
title bars, which it then keeps while the window is active, and the Export JUnit Report dialog,
Windows' own, stays light.

**View > Theme > Dark Theme** chooses the dark look's colors: Carbon, built in, or a theme file of
**View > Theme > Dark Theme** chooses the dark look's colors: a built-in theme or a theme file of
your own, used whenever the look is dark, with Dark or with Use Windows Setting (high contrast keeps
its own colors). Theme files go in `%APPDATA%\ASWUnitTests\Themes`, which **Open Themes Folder**
opens, and are shared by every test app; the menu lists each one by its file name. The choice is
saved in the layout file with the theme, and a problem with a theme file, such as a misspelled role,
a color that isn't one or a missing file, is reported in the Log tab, with Carbon's colors used in
its place.
its own colors). Both runners have five of RAD Studio's dark VCL styles built in: Carbon, Charcoal
Dark Slate, Cobalt XEMedia, Onyx Blue and Tablet Dark, which the Win32 GUI runner paints in their
colors. The Win32 GUI runner also has four tints of Carbon: Ember (warm brown), Forest (green),
Midnight (blue) and Plum (purple), which keep Carbon's colors for how tests did. Theme files go in
`%APPDATA%\ASWUnitTests\Themes`, which **Open Themes Folder** opens, and are shared by every test
app; the menu lists each one by its file name, below the built-in themes, and leaves out a file
named like one of them. The choice is saved in the layout file with the theme, and a problem with a
theme file, such as a misspelled role, a color that isn't one or a missing file, is reported in the
Log tab, with Carbon's colors used in its place.

The Win32 GUI runner's theme files are INI files, `<Name>.ini`, and **Save Theme Template...** saves
Carbon's colors as one to start from, every role listed under a comment on what its group colors:
Expand Down Expand Up @@ -496,7 +500,9 @@ files in `gui\src` (shared by the GUI runners) and `vcl\gui\src`, including
a `WinMain()` modeled on `vcl\gui\rad370\ASWUnitTests_VCL_GUI.cpp`, which parses the command line,
applies the saved theme and starts the main form. For the dark theme, also add RAD Studio's Carbon
style to the application (Project > Options > Application > Appearance, which this repository's
project has as `Custom_Styles`); without it, Dark shows the light look. For the ASWUnitTests icon,
project has as `Custom_Styles`); without it, Dark shows the light look. The other built-in dark
themes (Charcoal Dark Slate, Cobalt XEMedia, Onyx Blue and Tablet Dark) are listed only if their
styles are added too. For the ASWUnitTests icon,
set the application's icon to `resource\ASWUnitTests.ico` there too, as this repository's project
does.

Expand Down
17 changes: 13 additions & 4 deletions gui/src/ASWUnitTests_GUI_Theme.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ bool GUIThemeIsDark(EGUITheme theme, bool windowsAppsUseDark, bool highContrast)
return windowsAppsUseDark && !highContrast;
}
//---------------------------------------------------------------------------
std::vector<std::string> GUIThemeNames(std::vector<std::string> const& fileNames, std::string const& extension)
std::vector<std::string> GUIThemeNames(std::vector<std::string> const& fileNames, std::string const& extension,
std::vector<std::string> const& builtInNames)
{
std::vector<std::string> names;

Expand All @@ -154,13 +155,16 @@ std::vector<std::string> GUIThemeNames(std::vector<std::string> const& fileNames
}

std::string const name = fileName.substr(0, fileName.size() - extension.size());
bool const listed = std::any_of(names.begin(), names.end(), [&name](std::string const& other)
auto const sameName = [&name](std::string const& other)
{
return SameText(other, name);
});
};

if (!listed && !SameText(name, GUIDefaultDarkThemeName()))
if (std::none_of(names.begin(), names.end(), sameName) &&
std::none_of(builtInNames.begin(), builtInNames.end(), sameName))
{
names.push_back(name);
}
}

std::stable_sort(names.begin(), names.end(), &LessText);
Expand All @@ -172,6 +176,11 @@ std::string GUIThemeProblemLine(std::string const& themeName, std::string const&
return "Dark theme \"" + themeName + "\": " + problem;
}
//---------------------------------------------------------------------------
std::vector<std::string> GUIVCLStyleDarkThemeNames()
{
return { "Charcoal Dark Slate", "Cobalt XEMedia", "Onyx Blue", "Tablet Dark" };
}
//---------------------------------------------------------------------------
std::string LoadGUIDarkTheme(TGUIINIDocument const& ini)
{
std::optional<std::string> const name = ini.ReadString(ViewSection, DarkThemeKey);
Expand Down
14 changes: 10 additions & 4 deletions gui/src/ASWUnitTests_GUI_Theme.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,20 +92,26 @@ struct TGUIThemeFile

// 'rgb', 0xRRGGBB, as a theme file writes a color: "#RRGGBB", in uppercase.
std::string FormatGUIThemeColor(std::uint32_t rgb);
// The built-in dark theme's name, "Carbon": the default for LoadGUIDarkTheme(), and a name GUIThemeNames()
// leaves out, so a theme file can't take it.
// The name of the built-in dark theme both runners have, "Carbon": the default for LoadGUIDarkTheme(), and one
// of the built-in names GUIThemeNames() leaves out, so a theme file can't take it.
std::string GUIDefaultDarkThemeName();
// Whether 'theme' is dark: Light and Dark as chosen, and Windows as 'windowsAppsUseDark' (Windows' app mode
// setting) says, except that it's light while 'highContrast' is on, so the window shows high contrast's own
// colors.
bool GUIThemeIsDark(EGUITheme theme, bool windowsAppsUseDark, bool highContrast);
// The names of the themes among 'fileNames', the files in the Themes folder: each one that ends in
// 'extension' (e.g. ".ini"), ignoring (ASCII) case, without it, sorted ignoring case, and each name once.
// Leaves out GUIDefaultDarkThemeName(), ignoring case, and a file named just 'extension'.
std::vector<std::string> GUIThemeNames(std::vector<std::string> const& fileNames, std::string const& extension);
// Leaves out 'builtInNames', the runner's built-in dark themes (GUIDefaultDarkThemeName() among them), ignoring
// case, which a file can't replace, and a file named just 'extension'.
std::vector<std::string> GUIThemeNames(std::vector<std::string> const& fileNames, std::string const& extension,
std::vector<std::string> const& builtInNames);
// A line for the log about one of the dark theme 'themeName''s problems (e.g. one of TGUIThemeFile's), as both
// GUI runners word it: Dark theme "<name>": <problem>
std::string GUIThemeProblemLine(std::string const& themeName, std::string const& problem);
// The dark themes both runners have built in besides Carbon, sorted: RAD Studio's dark VCL styles, by their
// style names, which the VCL GUI runner links in and the Win32 GUI runner has the colors of
// (BuiltInDarkThemes()).
std::vector<std::string> GUIVCLStyleDarkThemeNames();
// Reads the dark theme SaveGUIDarkTheme() wrote to 'ini', its [View] section's DarkTheme key: the name of a
// theme file, or GUIDefaultDarkThemeName() when it's missing or empty, or is that name in another case.
std::string LoadGUIDarkTheme(TGUIINIDocument const& ini);
Expand Down
58 changes: 58 additions & 0 deletions gui/src/ASWUnitTests_GUI_View.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ limitations under the License.
// Module header
#include "ASWUnitTests_GUI_View.h"
//---------------------------------------------------------------------------
#include <cstddef>
#include <iomanip>
#include <sstream>
//---------------------------------------------------------------------------
Expand Down Expand Up @@ -176,6 +177,63 @@ std::string GUIMenuItemText(std::string const& text)
return caption;
}
//---------------------------------------------------------------------------
std::vector<std::string> GUIMenuItemTextsWithAccelerators(std::vector<std::string> const& texts,
std::string const& takenLetters)
{
auto const lower = [](char c)
{
return c >= 'A' && c <= 'Z' ? static_cast<char>(c - 'A' + 'a') : c;
};
auto const isAccelerator = [](char c)
{
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
};

std::string taken;

for (char const c : takenLetters)
taken += lower(c);

std::vector<std::string> captions;

for (std::string const& text : texts)
{
std::size_t chosen = std::string::npos;

// A letter that starts a word first, then any.
for (int pass = 0; pass < 2 && chosen == std::string::npos; ++pass)
{
for (std::size_t i = 0; i < text.size() && chosen == std::string::npos; ++i)
{
bool const startsWord = i == 0 || text[i - 1] == ' ';

if (isAccelerator(text[i]) && (pass == 1 || startsWord) && taken.find(lower(text[i])) ==
std::string::npos)
{
chosen = i;
}
}
}

if (chosen != std::string::npos)
taken += lower(text[chosen]);

std::string caption;

for (std::size_t i = 0; i < text.size(); ++i)
{
if (i == chosen)
caption += '&';

caption += GUIMenuItemText(text.substr(i, 1));
}

captions.push_back(caption);
}

return captions;
}
//---------------------------------------------------------------------------
std::vector<std::string> GUIStatusPanels(TGUITestList const& testList, size_t runFinishedCount, size_t runTotalCount,
std::optional<double> elapsedSeconds, std::string const& shuffleText, std::string const& message)
{
Expand Down
5 changes: 5 additions & 0 deletions gui/src/ASWUnitTests_GUI_View.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ std::string GUIFirstLine(std::string const& text);
// 'text', e.g. a theme file's name, as a menu item's caption: each '&' doubled, so it shows rather than making
// the next letter the item's accelerator.
std::string GUIMenuItemText(std::string const& text);
// 'texts', a menu's items in order, as captions (see GUIMenuItemText()), each with an accelerator of its own: the
// first ASCII letter or digit, ignoring case, that's neither in 'takenLetters' (the menu's other items') nor an
// earlier text's, preferring one that starts a word. A text with none left gets none.
std::vector<std::string> GUIMenuItemTextsWithAccelerators(std::vector<std::string> const& texts,
std::string const& takenLetters);
// The status bar's panels' text, in order: the checked and shown tests, the run's progress, the passed, failed
// and skipped counts, the run's elapsed time (empty if no run has started), the shuffle setting ('shuffleText',
// from TGUIShuffle::StatusText()), and 'message'.
Expand Down
27 changes: 22 additions & 5 deletions gui/tests/Test_ASWUnitTests_GUI_Theme.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ TTest_ASWUnitTests_GUI_Theme::TTest_ASWUnitTests_GUI_Theme()
"GUIThemeIsDark_FollowsTheChoiceOrWindows");
RegisterTest(&TTest_ASWUnitTests_GUI_Theme::Test_GUIThemeNames_ListsTheFilesOfOneKindSorted,
"GUIThemeNames_ListsTheFilesOfOneKindSorted");
RegisterTest(&TTest_ASWUnitTests_GUI_Theme::Test_GUIVCLStyleDarkThemeNames_AreSortedWithoutCarbon,
"GUIVCLStyleDarkThemeNames_AreSortedWithoutCarbon");
RegisterTest(&TTest_ASWUnitTests_GUI_Theme::Test_LoadGUIDarkTheme_DefaultsToCarbon,
"LoadGUIDarkTheme_DefaultsToCarbon");
RegisterTest(&TTest_ASWUnitTests_GUI_Theme::Test_LoadGUITheme_DefaultsToWindows, "LoadGUITheme_DefaultsToWindows");
Expand Down Expand Up @@ -177,18 +179,33 @@ void TTest_ASWUnitTests_GUI_Theme::Test_GUIThemeNames_ListsTheFilesOfOneKindSort
// Arrange: the Themes folder's files, in the order a listing might give them.
std::vector<std::string> const files = {
"ocean.ini", "Zest.ini", "Notes.txt", "Carbon.ini", "OCEAN.INI", ".ini", "apple.Ini", "carbon.INI",
"Ocean.ini.bak", "Forest.vsf", "ini", "Two Words.ini",
"Ocean.ini.bak", "Forest.vsf", "ini", "Two Words.ini", "plum.ini",
};
std::vector<std::string> const carbon = { "Carbon" };
std::vector<std::string> const builtIns = { "Carbon", "Plum", "Zest" };

// Act and Assert
CheckEquals(std::string("apple\nocean\nTwo Words\nZest\n"), JoinLines(GUIThemeNames(files, ".ini")), __func__,
__LINE__, "the .ini files' names, sorted ignoring case, a name once, without Carbon or a bare extension");
CheckEquals(std::string("Forest\n"), JoinLines(GUIThemeNames(files, ".vsf")), __func__, __LINE__,
CheckEquals(std::string("apple\nocean\nplum\nTwo Words\nZest\n"), JoinLines(GUIThemeNames(files, ".ini", carbon)),
__func__, __LINE__, "the .ini files' names, sorted ignoring case, a name once, without Carbon or a bare "
"extension");
CheckEquals(std::string("apple\nocean\nTwo Words\n"), JoinLines(GUIThemeNames(files, ".ini", builtIns)), __func__,
__LINE__, "without any built-in theme's name, ignoring case");
CheckEquals(std::string("Forest\n"), JoinLines(GUIThemeNames(files, ".vsf", carbon)), __func__, __LINE__,
"the VCL GUI runner's .vsf files");
CheckEmpty(JoinLines(GUIThemeNames(std::vector<std::string>(), ".ini")), __func__, __LINE__,
CheckEmpty(JoinLines(GUIThemeNames(std::vector<std::string>(), ".ini", carbon)), __func__, __LINE__,
"an empty or missing folder");
}
//---------------------------------------------------------------------------
void TTest_ASWUnitTests_GUI_Theme::Test_GUIVCLStyleDarkThemeNames_AreSortedWithoutCarbon()
{
// Act
std::vector<std::string> const names = GUIVCLStyleDarkThemeNames();

// Assert: RAD Studio's style names, which the VCL GUI runner's project links in.
CheckEquals(std::string("Charcoal Dark Slate\nCobalt XEMedia\nOnyx Blue\nTablet Dark\n"), JoinLines(names), __func__,
__LINE__, "the picked styles, sorted, Carbon not among them");
}
//---------------------------------------------------------------------------
void TTest_ASWUnitTests_GUI_Theme::Test_LoadGUIDarkTheme_DefaultsToCarbon()
{
// Act and Assert
Expand Down
1 change: 1 addition & 0 deletions gui/tests/Test_ASWUnitTests_GUI_Theme.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class TTest_ASWUnitTests_GUI_Theme : public TTestGroupBase
void Test_FormatGUIThemeColor_RoundTripsThroughParse();
void Test_GUIThemeIsDark_FollowsTheChoiceOrWindows();
void Test_GUIThemeNames_ListsTheFilesOfOneKindSorted();
void Test_GUIVCLStyleDarkThemeNames_AreSortedWithoutCarbon();
void Test_LoadGUIDarkTheme_DefaultsToCarbon();
void Test_LoadGUITheme_DefaultsToWindows();
void Test_LoadGUITheme_ReadsEachThemeIgnoringCase();
Expand Down
36 changes: 36 additions & 0 deletions gui/tests/Test_ASWUnitTests_GUI_View.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ TTest_ASWUnitTests_GUI_View::TTest_ASWUnitTests_GUI_View()
"GUIFirstLine_StopsAtTheFirstLineBreak");
RegisterTest(&TTest_ASWUnitTests_GUI_View::Test_GUIMenuItemText_DoublesEachAmpersand,
"GUIMenuItemText_DoublesEachAmpersand");
RegisterTest(&TTest_ASWUnitTests_GUI_View::Test_GUIMenuItemTextsWithAccelerators_GiveEachALetterOfItsOwn,
"GUIMenuItemTextsWithAccelerators_GiveEachALetterOfItsOwn");
RegisterTest(&TTest_ASWUnitTests_GUI_View::Test_GUIStatusPanels_ShowCountsAndMessage,
"GUIStatusPanels_ShowCountsAndMessage");
RegisterTest(&TTest_ASWUnitTests_GUI_View::Test_GUIStatusPanelWidths_OnePerPanel, "GUIStatusPanelWidths_OnePerPanel");
Expand Down Expand Up @@ -228,6 +230,40 @@ void TTest_ASWUnitTests_GUI_View::Test_GUIMenuItemText_DoublesEachAmpersand()
CheckEmpty(GUIMenuItemText(std::string()), __func__, __LINE__, "empty");
}
//---------------------------------------------------------------------------
void TTest_ASWUnitTests_GUI_View::Test_GUIMenuItemTextsWithAccelerators_GiveEachALetterOfItsOwn()
{
// Arrange
auto const join = [](std::vector<std::string> const& captions)
{
std::string text;

for (std::string const& caption : captions)
text += caption + "\n";

return text;
};

std::vector<std::string> const themes = {
"Carbon", "Charcoal Dark Slate", "Cobalt XEMedia", "Ember", "Forest", "Midnight", "Onyx Blue", "Plum",
"Tablet Dark"
};

// Act and Assert
CheckEquals(std::string("&Carbon\nCharcoal &Dark Slate\nCobalt &XEMedia\n&Ember\n&Forest\n&Midnight\nOnyx &Blue\n"
"&Plum\n&Tablet Dark\n"), join(GUIMenuItemTextsWithAccelerators(themes, "so")), __func__, __LINE__,
"a word's first letter not taken before, by the menu's other items or an earlier text");
CheckEquals(std::string("S&ave\n"), join(GUIMenuItemTextsWithAccelerators({ "Save" }, "S")), __func__, __LINE__,
"else any letter, ignoring case");
CheckEquals(std::string("&A\nA&&&B\n"), join(GUIMenuItemTextsWithAccelerators({ "A", "A&B" }, "")), __func__,
__LINE__, "an '&' doubled, not an accelerator");
CheckEquals(std::string("\xC3\x89&cume\n"), join(GUIMenuItemTextsWithAccelerators({ "\xC3\x89" "cume" }, "")),
__func__, __LINE__, "an ASCII letter only");
CheckEquals(std::string("&x\nx\n"), join(GUIMenuItemTextsWithAccelerators({ "x", "x" }, "")), __func__, __LINE__,
"none when none is left");
CheckEmpty(join(GUIMenuItemTextsWithAccelerators(std::vector<std::string>(), "so")), __func__, __LINE__,
"no texts, no captions");
}
//---------------------------------------------------------------------------
void TTest_ASWUnitTests_GUI_View::Test_GUIStatusPanels_ShowCountsAndMessage()
{
// Arrange: three tests, one hidden by the filter box, one unchecked, one passed and one failed.
Expand Down
1 change: 1 addition & 0 deletions gui/tests/Test_ASWUnitTests_GUI_View.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class TTest_ASWUnitTests_GUI_View : public TTestGroupBase
void Test_GUIFilterBoxFilter_MatchesAnywhereIgnoringCase();
void Test_GUIFirstLine_StopsAtTheFirstLineBreak();
void Test_GUIMenuItemText_DoublesEachAmpersand();
void Test_GUIMenuItemTextsWithAccelerators_GiveEachALetterOfItsOwn();
void Test_GUIStatusPanels_ShowCountsAndMessage();
void Test_GUIStatusPanelWidths_OnePerPanel();
void Test_GUIUnhandledExceptionRecord_FailsTheRunningTest();
Expand Down
Loading
Loading