A small annoyance for me is the lack of unique shortcut letters in menus: some are duplicates, some are absent. It can be tedious to search for a unique letter for each item, especially when menus are long and many items have letters in common. So I wrote a small console-based utility to do this (below) for all to use as they see fit, including the Code::Blocks developpers of course. ;-)
Enter titles one at a time, either from the console or a redirected data file. The program also provides useful information like a table of letters used, available but unused shortcut candidates and unused letters to help vary the actual titles in order to achieve uniqueness. For example, a little bit of fiddling assisted by this utility yielded this reworked Code::Blocks "File" menu:
n: (N)ew file
e: N(e)w project
o: (O)pen
f: Recent (f)iles
r: (R)ecent projects
s: (S)ave file
a: S(a)ve project
v: Sa(v)e workspace
y: Save ever(y)thing
i: Save f(i)le as
j: Save pro(j)ect as
w: Save (w)orkspace as
c: (C)lose file
l: C(l)ose project
k: Close wor(k)space
p: (P)rint
x: E(x)port
u: R(u)n script
t: Proper(t)ies
q: (Q)uit
Changed: from "save all files" to "save everything" because:
- not enough unique shortcut letters,
- projects and workspaces are not edited like "files",
- it's even better to save all files, projects and workspace in one go.
Deleted: "close all", which is redundant with close file, close project and close workspace. Alternatively, use "h: close everyt(h)ing" to match "save everything".
Well, hopefully someone will find this useful. I think this is the right place to post it since the intent was to help developpers polish up the Code::Blocks menus.
//------------------------------------------------------------------------------
// AlphaKey
//
// This program takes as input a list of "titles" that may be used in menus
// or dialog boxes and attempts to identify a unique alphabetic key from 'a'
// to 'z' which can be used to uniquely identify each title. The purpose is
// to select the best letter as a shortcut to the title in question (which
// would be underlined in a Windows application).
//
// Obviously, there is an input limit of 26 titles. The program will fail to
// find a unique key for each title if there are less unique letters used in
// the list than there are titles. Even if there are enough unique letters,
// the search may still fail if they are not distributed so that each title
// indeed has at least one letter that can be uniquely its own. For example
// this list: "aa", "bb", "ab" and "xy" holds 4 unique letters in 4 titles
// yet does not have unique keys for each of the first 3 items.
//
// The search is exhaustive, all possible combinations are examined, which
// can take a while for larger title sets. The search is fast if a solution
// exists and the titles are already listed in the "most desirable" order,
// that is items that have a key early on in the title appear first. If the
// first title's only possible key is the last letter of the word, then all
// unsuccessful possibilities must first be ruled out starting at the first
// letter. Avoiding this by re-ordering the input prevents such worst-case
// scenarios.
//
// Despite an occasional worst-case run, an exhaustive search is good to
// provide the "best order" for keys since the first successful letter
// appears early in early titles for a fairly natural key selection.
//
// Tips & Tricks
//
// Reserve letter(s) that must be assigned to a specific titles by entering
// these single letters first followed by a '|' character. All letters after
// this will be ignored in the search. For efficiency, enter these first in
// the input list. Example: enter "s|Save" and "a|Save As" to reserve these
// letter keys for these titles. A few favorite letters can also be specified
// with "saf|Save file" and "av|Save As" for example.
//
// If a search takes too long, look for the presence of a unique letter in all
// titles (print the table to see). Reserve the unique letter for this title
// (as above), then repeat the search.
//
// Moving titles around a little can speed up the search process and/or
// produce alternate selections. Vary the input order.
//
//------------------------------------------------------------------------------
#include <cctype>
#include <cstdio>
#include <cstring>
//------------------------------------------------------------------------------
// Work data.
char titles[27][100]; // List of titles.
int titleCount; // Input count.
char streamlined[26][27]; // Unique lowercase letters in each title.
char keys[26]; // Resulting letter key for each title.
bool used[26]; // Work flags for letters already used.
//------------------------------------------------------------------------------
/* Read a line "cleanly" and return its length. */
int strget(char *destination, int bufferSize, FILE *inStream) {
char *str = destination;
int c;
// Skip leading whitespace.
do {
c = getc(inStream);
} while (c != EOF && c != '\n' && isspace(c));
// Read until the end or until the buffer is full.
while (c != EOF && c != '\n' && bufferSize > 1) {
*str++ = (char) c; bufferSize--;
c = getc(inStream);
}
// Terminate and trim trailing whitespace.
do {
*str-- = '\0';
} while (str >= destination && isspace(*str));
// Clear out the input buffer.
while (c != EOF && c != '\n') {
c = getc(inStream);
}
// Return the input length.
return str - destination + 1;
}
// Read a list of titles, one title per line. Return true if a list of up
// to 26 titles was provided. If the list is empty or exceeds 26, issue an
// error message and return false.
bool inputTitles() {
titleCount = 0;
while (strget(titles[titleCount], 100, stdin)) {
if (++titleCount > 26) {
fprintf(stderr, "Error: More than 26 titles.\n");
return false;
}
}
if (titleCount == 0) {
fprintf(stderr, "Error: No data.\n");
return false;
}
return true;
}
// For efficient processing, streamline a title by copying into 'dst' only
// unique lowercase letters from 'title'. Preserve ordering of used letters.
void streamline(char* dst, const char* title) {
char const * DST = dst;
while (*title && (*title != '|')) { // Ignore what follows '|'
if (isalpha(*title)) {
char letter = tolower(*title);
const char* cp = DST; // Is this letter already in dst?
while (cp < dst && letter != *cp) {
++cp;
}
if (cp == dst) { // No prior duplicate in 'dst':
*dst = letter; // store this unique letter
++dst; // and advance position.
}
}
++title; // Next title character.
}
*dst = '\0'; // End the letter array as a string.
}
// Set streamlined versions of all titles.
void streamlineAll() {
for (int i = 0; i < titleCount; ++i) {
streamline(streamlined[i], titles[i]);
}
}
// Clear all 'used' flags.
void clearUsed() {
for (int i = 0; i < 26; ++i) {
used[i] = false;
}
}
// Mark all letters used in the (streamlined) title at index 'i'.
void markUsedIn(int i) {
for (int j = 0; streamlined[i][j]; ++j) {
used[streamlined[i][j] - 'a'] = true;
}
}
// Mark all letters used in all titles.
void markUsedInAll() {
clearUsed();
for (int i = 0; i < titleCount; ++i) {
markUsedIn(i);
}
}
// Set all unique letters used and return their count.
int uniqueLetters() {
int count = 0;
markUsedInAll();
for (int i = 0; i < 26; ++i) {
if (used[i]) ++count;
}
return count;
}
//------------------------------------------------------------------------------
// Main recursive search function.
bool findUniqueAlpha(int titleIndex = 0) {
// If the title index has passed the end then the search was successful.
if (titleIndex == titleCount) {
return true;
}
// Try using each letter in turn as a key for this title.
for (char* letter = streamlined[titleIndex]; *letter; ++letter) {
// Progress report.
if (titleIndex < 4) {
printf("Searching...");
// Show nesting level visually using spaces.
for (int i = 0; i < titleIndex; ++i) {
printf(" ");
}
printf("%c\n", *letter);
}
// Try this letter as key if it is not already in use.
int index = *letter - 'a';
if (!used[index]) {
used[index] = true;
if (findUniqueAlpha(titleIndex + 1)) { // Recurse with next title.
keys[titleIndex] = *letter; // Success, store the key.
return true; // Leave the flag set.
}
used[index] = false; // Clear flag, continue.
}
}
// No solution found for this title given all letters already used as key.
return false;
}
//------------------------------------------------------------------------------
// Reporting functions
// Show letters either used or not used.
void showUsed(bool usage, const char* header = 0) {
if (header) {
printf(header);
}
for (int i = 0; i < 26; ++i) {
if (used[i] == usage) {
printf(" %c", char('a' + i));
}
}
}
// Show a table of letters used by each title.
void showTable() {
for (int i = 0; i < titleCount; ++i) {
// Mark each letter used by this title alone.
clearUsed();
markUsedIn(i);
// Show 26 markers, either a used letter or a delimiting character.
for (int j = 0; j < 26; ++j) {
printf("%c", used[j] ? char('a' + j) : '.');
}
// End the line with the corresponding title.
printf(" %s\n", titles[i]);
}
printf("\n");
}
// Show all keys and their corresponding titles.
void showKeys() {
for (int i = 0; i < titleCount; ++i) {
printf("%c: %s\n", keys[i], titles[i]);
}
}
// Show letters used in titles but not in keys.
void showAvailableKeys(const char* header = 0) {
// Check each used letter against the set of keys.
markUsedInAll();
int count = 0;
for (int i = 0; i < 26; ++i) {
if (used[i]) {
char letter = char('a' + i);
// Clear the flag is this letter is used in a key.
for (int j = 0; j < titleCount; ++j) {
if (keys[j] == letter) {
used[i] = false;
break;
}
}
// Is this letter still available for use as a key?
if (used[i]) {
++count;
}
}
}
// Report on available letters only if there are any.
if (count) {
showUsed(true, header);
}
}
int showHelp() {
printf("\n"
"AlphaKey requires input of one title per line, each one up to\n"
"99 characters in length. Input an empty line (press Enter alone)\n"
"to end data entry.\n"
"\n"
"Use \"AlphaKey -table\" to just see a table of letters used by\n"
"each title provided as input.\n"
"\n"
"Use character | to force the selection of any letter before it\n"
"as key for the title. Letters after | are ignored. Example: use\n"
"\"fn|Family Name\" to force selection of either f or n as key.\n");
return 0;
}
//------------------------------------------------------------------------------
int main(int argc, char* argv[]) {
// Check command line arguments.
bool wantTable = (argc == 2)
&& (*argv[1] == '-' || *argv[1] == '/')
&& (strcmp(argv[1] + 1, "table") == 0);
if (argc != 1 && !wantTable) {
return showHelp();
}
// Process the input, if any.
if (inputTitles()) {
// All accessory functions use array 'streamlined' instead of 'titles'.
streamlineAll();
if (wantTable) {
showTable();
}
else {
int uniqueCount = uniqueLetters();
if (uniqueCount < titleCount) {
// Error situation, not enough unique letters.
showTable();
printf("\nOnly %d unique letters in %d different titles.\n",
uniqueCount, titleCount);
showUsed(false, "\nUnused letters:");
}
else {
// Start searching (clear 'used' first).
clearUsed();
if (findUniqueAlpha()) {
printf("\n\n\n");
showTable();
showKeys();
showAvailableKeys("\nAvailable keys:");
if (titleCount < 26) {
markUsedInAll();
showUsed(false, "\nUnused letters:");
}
}
else {
showTable();
printf("\nNo solution found.\n");
}
}
printf("\n");
}
}
return 0;
}