diff --git a/CHANGELOG b/CHANGELOG
index 9a7b7e80..79791722 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,13 @@
+1.6.0-bacon4
+ QWERTY keyboard name entry for new projects
+ * Press A on the project name field to enter QWERTY keyboard mode
+ * Navigate the on-screen keyboard with D-PAD/arrows
+ * Press A to input the selected character
+ * Press B to backspace
+ * Press L/R to move the text cursor left/right
+ * Press START or select OK to exit keyboard mode
+ * All keyboard navigation logic extracted to reusable KeyboardLayout.h utility
+
1.6.0-bacon2
Interpolate values in tables and phrases
R + B on single-column selection will fill values from lowest to highest
diff --git a/docs/wiki/What-is-LittlePiggyTracker.md b/docs/wiki/What-is-LittlePiggyTracker.md
index 52c6fd3c..83b4f86b 100644
--- a/docs/wiki/What-is-LittlePiggyTracker.md
+++ b/docs/wiki/What-is-LittlePiggyTracker.md
@@ -51,8 +51,24 @@ After that you can copy additional wavs to the lgptRoot/lgptProject/samples dire
## New project
-When creating a new project, use the Random button to generate a random name. Generate a new name with Random or edit it manually selecting characters with A and pressing up/down
-Attempting to create a project with the same name in the same location produces a notification that this operation is denied
+When creating a new project, you have several options for naming:
+
+**Random Name Generation:**
+- Select the "Random" button and press A to generate a random name
+
+**QWERTY Keyboard Entry:**
+- Move to the name field and press A to enter QWERTY keyboard mode
+- An on-screen keyboard will appear with these controls:
+ - **D-PAD/Arrows:** Navigate the keyboard
+ - **A:** Input the selected character
+ - **B:** Backspace (delete character)
+ - **L/R:** Move the text cursor left/right within your project name
+ - **START or OK key:** Exit keyboard mode and return to the dialog
+- The keyboard includes:
+ - Numbers (0-9)
+ - Uppercase and lowercase letters (A-Z, a-z)
+ - Special characters (@ | - _ < > ? ,)
+ - Space bar, backspace, and OK (done) buttons on the bottom row
## Multiple Projects
diff --git a/docs/wiki/tips_and_tricks.md b/docs/wiki/tips_and_tricks.md
index 470e2a87..15d84b16 100644
--- a/docs/wiki/tips_and_tricks.md
+++ b/docs/wiki/tips_and_tricks.md
@@ -1,3 +1,20 @@
+# Project Naming with QWERTY Keyboard
+
+Since version 1.6.0, you can use an on-screen QWERTY keyboard when creating new projects:
+
+**Quick Tips:**
+- Press A on the project name to enter keyboard mode
+- The keyboard cursor will jump to the character under your text cursor unless it's a space
+- Use L/R shoulder buttons to move through your project name
+- Erase with B and exit with Start
+
+**Keyboard Layout:**
+The on-screen keyboard is organized like this:
+- Numbers
+- Uppercase (A-Q + extra characters)
+- Lowercase (a-q + extra characters)
+- Special [Space] [Erase] [Done]
+
# Delays and Echoes
## Simulating LSDj's D command
diff --git a/projects/lgpt.vcxproj b/projects/lgpt.vcxproj
index e6f588df..24ee5329 100644
--- a/projects/lgpt.vcxproj
+++ b/projects/lgpt.vcxproj
@@ -440,6 +440,7 @@
+
diff --git a/projects/lgptest.dev b/projects/lgptest.dev
index e877c121..e3e0ef5b 100644
--- a/projects/lgptest.dev
+++ b/projects/lgptest.dev
@@ -2937,7 +2937,6 @@ Priority=1000
OverrideBuildCmd=0
BuildCmd=
-
[Unit288]
FileName=..\sources\Application\utils\RandomNames.h
CompileCpp=1
@@ -2948,3 +2947,13 @@ Priority=1000
OverrideBuildCmd=0
BuildCmd=
+[Unit289]
+FileName=..\sources\Application\utils\KeyboardLayout.h
+CompileCpp=1
+Folder=Application/Utils
+Compile=1
+Link=1
+Priority=1000
+OverrideBuildCmd=0
+BuildCmd=
+
diff --git a/sources/Application/Model/Project.h b/sources/Application/Model/Project.h
index 5b8f5c1a..0004b196 100644
--- a/sources/Application/Model/Project.h
+++ b/sources/Application/Model/Project.h
@@ -20,7 +20,7 @@
#define PROJECT_NUMBER "1"
#define PROJECT_RELEASE "6"
-#define BUILD_COUNT "0-bacon2"
+#define BUILD_COUNT "0-bacon4"
#define MAX_TAP 3
@@ -56,14 +56,14 @@ class Project: public Persistent,public VariableContainer,I_Observer {
void LoadFirstGen(const char *root);
protected:
- void buildMidiDeviceList() ;
-private:
- InstrumentBank *instrumentBank_ ;
- char **midiDeviceList_ ;
- int midiDeviceListSize_ ;
- int tempoNudge_ ;
- unsigned long lastTap_[MAX_TAP] ;
- unsigned int tempoTapCount_;
-} ;
+ void buildMidiDeviceList();
+private:
+ InstrumentBank *instrumentBank_;
+ char **midiDeviceList_;
+ int midiDeviceListSize_;
+ int tempoNudge_;
+ unsigned long lastTap_[MAX_TAP];
+ unsigned int tempoTapCount_;
+};
#endif
diff --git a/sources/Application/Utils/KeyboardLayout.h b/sources/Application/Utils/KeyboardLayout.h
new file mode 100644
index 00000000..c6ae7704
--- /dev/null
+++ b/sources/Application/Utils/KeyboardLayout.h
@@ -0,0 +1,102 @@
+#ifndef _KEYBOARD_LAYOUT_H_
+#define _KEYBOARD_LAYOUT_H_
+
+#include
+
+// Keyboard layout configuration
+#define SPACE_ROW 7
+#define KEYBOARD_ROWS (SPACE_ROW + 1)
+
+#define SPACE_START 0
+#define SPACE_END 3
+#define BACK_START 4
+#define BACK_END 6
+#define DONE_START 8
+#define DONE_END 10
+
+static const char* keyboardLayout[] = {
+ "1234567890",
+ "QWERTYUIOP",
+ "ASDFGHJKL@",
+ "ZXCVBNM,?>",
+ "qwertyuiop",
+ "asdfghjkl|",
+ "zxcvbnm-_<",
+ "[_] <- OK"
+};
+
+// Helper functions for special row section detection
+inline bool isInSpaceSection(int col) { return col < SPACE_END; }
+inline bool isInBackSection(int col) { return col >= BACK_START && col < BACK_END; }
+inline bool isInDoneSection(int col) { return col >= DONE_START; }
+
+// Get the character at a specific keyboard position
+inline char getKeyAtPosition(int row, int col) {
+ if (row < 0 || row >= KEYBOARD_ROWS) return '\0';
+ const char* rowStr = keyboardLayout[row];
+
+ // Handle special row with SPC, BACK, and DONE
+ if (row == SPACE_ROW) {
+ if (col >= 0 && col < SPACE_END)
+ return ' '; // [_] (space)
+ if (col >= BACK_START && col < BACK_END) return '\b'; // <- (backspace)
+ if (col >= DONE_START && col < DONE_END) return '\r'; // OK (carriage return)
+ return '\0';
+ }
+
+ int len = strlen(rowStr);
+ if (col < 0 || col >= len) return '\0';
+ return rowStr[col];
+}
+
+// Find a character's position in the keyboard layout
+inline void findCharacterInKeyboard(char ch, int &outRow, int &outCol) {
+ if (ch == ' ') return; // Skip space character
+
+ // Search for character in keyboard layout (excluding special row)
+ for (int row = 0; row < SPACE_ROW; row++) {
+ const char* rowStr = keyboardLayout[row];
+ int len = strlen(rowStr);
+ for (int col = 0; col < len; col++) {
+ if (rowStr[col] == ch) {
+ outRow = row;
+ outCol = col;
+ return;
+ }
+ }
+ }
+ // Character not found, don't change position
+}
+
+// Clamp keyboard column to valid range for current row
+inline void clampKeyboardColumn(int row, int& col) {
+ if (row == SPACE_ROW) {
+ if (col < SPACE_END) col = SPACE_START;
+ else if (col <= BACK_END) col = BACK_START;
+ else col = DONE_START;
+ } else {
+ int maxCol = strlen(keyboardLayout[row]) - 1;
+ if (col > maxCol) col = 0;
+ }
+}
+
+// Cycle keyboard column left (-1) or right (+1) within current row
+inline void cycleKeyboardColumn(int row, int direction, int& col) {
+ if (row == SPACE_ROW) {
+ if (direction < 0) { // LEFT
+ if (isInSpaceSection(col))
+ col = DONE_START;
+ else if (isInBackSection(col)) col = SPACE_START;
+ else col = BACK_START;
+ } else { // RIGHT
+ if (isInBackSection(col)) col = DONE_START;
+ else if (isInDoneSection(col)) col = SPACE_START;
+ else col = BACK_START;
+ }
+ } else {
+ int maxCol = strlen(keyboardLayout[row]) - 1;
+ col = (col + direction + maxCol + 1) % (maxCol + 1);
+ }
+}
+
+#endif
diff --git a/sources/Application/Views/ModalDialogs/NewProjectDialog.cpp b/sources/Application/Views/ModalDialogs/NewProjectDialog.cpp
index 132a63ca..1f4d7649 100644
--- a/sources/Application/Views/ModalDialogs/NewProjectDialog.cpp
+++ b/sources/Application/Views/ModalDialogs/NewProjectDialog.cpp
@@ -1,5 +1,6 @@
#include "NewProjectDialog.h"
+#include "Application/Utils/KeyboardLayout.h"
#include "Application/Utils/RandomNames.h"
static char *buttonText[BUTTONS_LENGTH] = {(char *)"Random", (char *)"Ok",
@@ -12,39 +13,88 @@ NewProjectDialog::NewProjectDialog(View &view, Path currentPath)
NewProjectDialog::~NewProjectDialog() {}
+// Move text cursor left (-1) or right (+1) and update keyboard position
+void NewProjectDialog::moveCursor(int direction) {
+ int newPos = currentChar_ + direction;
+ if (newPos >= 0 && newPos < MAX_NAME_LENGTH) {
+ currentChar_ = newPos;
+ findCharacterInKeyboard(name_[currentChar_], keyboardRow_,
+ keyboardCol_);
+ }
+}
+
void NewProjectDialog::DrawView() {
- SetWindow(DIALOG_WIDTH,5) ;
+ SetWindow(DIALOG_WIDTH, keyboardMode_ ? 15 : 5);
- GUITextProperties props ;
+ GUITextProperties props;
- SetColor(CD_NORMAL) ;
+ SetColor(CD_NORMAL);
// Draw string
int x = (DIALOG_WIDTH - MAX_NAME_LENGTH) / 3;
char buffer[2];
- buffer[1]=0 ;
- for (int i=0;i 0) {
+ currentChar_--;
+ name_[currentChar_] = ' ';
+ }
+ } else if (ch == '\r') {
+ // END key: exit keyboard mode (same as START)
+ keyboardMode_ = false;
+ isDirty_ = true;
+ return;
+ } else if (ch != '\0') {
+ name_[currentChar_] = ch;
+ lastChar_ = ch;
+ if (currentChar_ < MAX_NAME_LENGTH - 1) {
+ currentChar_++;
+ findCharacterInKeyboard(name_[currentChar_], keyboardRow_,
+ keyboardCol_);
+ }
+ }
+ isDirty_ = true;
+ return;
+ } else if (mask == EPBM_B) {
+ // Backspace: delete character and move cursor left
+ if (currentChar_ > 0) {
+ currentChar_--;
+ name_[currentChar_] = ' ';
+ isDirty_ = true;
+ }
+ return;
+ } else if (mask == EPBM_L) {
+ // Move cursor left
+ moveCursor(-1);
+ isDirty_ = true;
+ return;
+ } else if (mask == EPBM_R) {
+ // Move cursor right
+ moveCursor(1);
+ isDirty_ = true;
+ return;
+ } else if (mask == EPBM_UP) {
+ keyboardRow_ = (keyboardRow_ - 1 + KEYBOARD_ROWS) % KEYBOARD_ROWS;
+ clampKeyboardColumn(keyboardRow_, keyboardCol_);
+ isDirty_ = true;
+ return;
+ } else if (mask == EPBM_DOWN) {
+ keyboardRow_ = (keyboardRow_ + 1) % KEYBOARD_ROWS;
+ clampKeyboardColumn(keyboardRow_, keyboardCol_);
+ isDirty_ = true;
+ return;
+ } else if (mask == EPBM_LEFT) {
+ cycleKeyboardColumn(keyboardRow_, -1, keyboardCol_);
+ isDirty_ = true;
+ return;
+ } else if (mask == EPBM_RIGHT) {
+ cycleKeyboardColumn(keyboardRow_, 1, keyboardCol_);
+ isDirty_ = true;
+ return;
+ } else if (mask == EPBM_START) {
+ keyboardMode_ = false;
+ isDirty_ = true;
+ return;
+ }
+ return;
+ } else if (mask & EPBM_A) {
+ if (mask == EPBM_A) {
+ std::string randomName = "";
+ switch (selected_) {
+ case 0:
+ // Toggle keyboard mode
+ keyboardMode_ = !keyboardMode_;
+ // When entering keyboard mode, jump to current character
+ if (keyboardMode_) {
+ findCharacterInKeyboard(name_[currentChar_], keyboardRow_,
+ keyboardCol_);
+ }
+ isDirty_ = true;
+ break;
+ case 1:
+ do {
+ randomName = getRandomName();
+ std::fill(name_ + randomName.length(),
+ name_ + sizeof(name_) / sizeof(name_[0]), ' ');
+ strncpy(name_, randomName.c_str(), randomName.length());
+ lastChar_ = currentChar_ = randomName.length() - 1;
+ } while (currentPath_.Descend(GetName()).Exists());
+ isDirty_ = true;
+ break;
+ case 2:
+ if (currentPath_.Descend(GetName()).Exists()) {
+ std::string res("Name " + std::string(name_) + " busy");
+ View::SetNotification(res.c_str(), -6);
+ } else {
+ EndModal(1);
+ }
+ break;
+ case 3:
+ EndModal(0);
+ break;
+ }
+ }
+ if (mask & EPBM_UP) {
+ name_[currentChar_]+=1;
lastChar_=name_[currentChar_] ;
isDirty_=true ;
- }
- if (mask&EPBM_DOWN) {
+ }
+ if (mask&EPBM_DOWN) {
name_[currentChar_]-=1;
lastChar_=name_[currentChar_] ;
isDirty_=true ;
- }
- } else {
-
- // R Modifier
-
- if (mask & EPBM_R) {
- } else {
- // No modifier
- if (mask == EPBM_UP) {
- selected_ = (selected_ == 0) ? 1 : 0;
- isDirty_ = true;
- }
- if (mask == EPBM_DOWN) {
- selected_ = (selected_ == 0) ? 1 : 0;
- isDirty_ = true;
- }
-
- if (mask == EPBM_LEFT) {
- switch (selected_) {
- case 0:
- if (currentChar_ > 0)
- currentChar_--;
- break;
- case 1:
- case 2:
- case 3:
- if (selected_ > 0)
- selected_--;
- break;
- }
- isDirty_ = true;
- }
- if (mask == EPBM_RIGHT) {
- switch (selected_) {
- case 0:
- if (currentChar_ < MAX_NAME_LENGTH - 1)
- currentChar_++;
- else
- selected_++;
- break;
- case 1:
- case 2:
- case 3:
- if (selected_ < BUTTONS_LENGTH)
- selected_++;
- break;
- }
- isDirty_ = true;
- }
- }
- }
+ }
+ } else {
+
+ // R Modifier
+
+ if (mask & EPBM_R) {
+ } else {
+ // No modifier
+ if (mask == EPBM_UP) {
+ selected_ = (selected_ == 0) ? 1 : 0;
+ isDirty_ = true;
+ }
+ if (mask == EPBM_DOWN) {
+ selected_ = (selected_ == 0) ? 1 : 0;
+ isDirty_ = true;
+ }
+
+ if (mask == EPBM_LEFT) {
+ switch (selected_) {
+ case 0:
+ if (currentChar_ > 0)
+ currentChar_--;
+ break;
+ case 1:
+ case 2:
+ case 3:
+ if (selected_ > 0)
+ selected_--;
+ break;
+ }
+ isDirty_ = true;
+ }
+ if (mask == EPBM_RIGHT) {
+ switch (selected_) {
+ case 0:
+ if (currentChar_ < MAX_NAME_LENGTH - 1)
+ currentChar_++;
+ else
+ selected_++;
+ break;
+ case 1:
+ case 2:
+ case 3:
+ if (selected_ < BUTTONS_LENGTH)
+ selected_++;
+ break;
+ }
+ isDirty_ = true;
+ }
+ }
}
};
-
std::string NewProjectDialog::GetName() {
for (int i = MAX_NAME_LENGTH; i >= 0; i--) {
if (name_[i]==' ') {
diff --git a/sources/Application/Views/ModalDialogs/NewProjectDialog.h b/sources/Application/Views/ModalDialogs/NewProjectDialog.h
index 457f8692..fa159cac 100644
--- a/sources/Application/Views/ModalDialogs/NewProjectDialog.h
+++ b/sources/Application/Views/ModalDialogs/NewProjectDialog.h
@@ -1,6 +1,7 @@
#ifndef _NEW_PROJECT_DIALOG_H_
#define _NEW_PROJECT_DIALOG_H_
+#include "Application/Utils/KeyboardLayout.h"
#include "Application/Views/BaseClasses/ModalView.h"
#include
@@ -25,6 +26,9 @@ class NewProjectDialog:public ModalView {
int lastChar_;
char name_[MAX_NAME_LENGTH + 1];
int currentChar_;
-} ;
-
+ bool keyboardMode_;
+ int keyboardRow_;
+ int keyboardCol_ ;
+ void moveCursor(int direction);
+};
#endif