Skip to content

Commit b4ccf9f

Browse files
authored
Merge pull request #210 from ashtf8/OS_dev
International Characters Support
2 parents 2187b17 + 58f2711 commit b4ccf9f

6 files changed

Lines changed: 533 additions & 610 deletions

File tree

Code/PocketMage_V3/lib/PocketMage/src/pocketmage_kb.cpp

Lines changed: 147 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,16 @@ PocketmageKB& KB() { return pm_kb; }
727727

728728
// ===================== public functions =====================
729729
char PocketmageKB::updateKeypress() {
730+
// --- 1. UTF-8 Multi-Byte Buffer ---
731+
// Because special characters are multi-byte UTF-8, we use a static buffer
732+
// to return them one byte at a time to the main OS loop.
733+
static char utf8Buffer[8] = {0};
734+
if (utf8Buffer[0] != '\0') {
735+
char c = utf8Buffer[0];
736+
memmove(utf8Buffer, utf8Buffer + 1, 7); // Shift buffer left
737+
return c;
738+
}
739+
730740
// Check for USB char
731741
char USB_CHAR = pop_USB_char();
732742
if (USB_CHAR != '\0') {
@@ -745,14 +755,148 @@ char PocketmageKB::updateKeypress() {
745755
int intstat = keypad_.readRegister(TCA8418_REG_INT_STAT);
746756
if ((intstat & 0x01) == 0) TCA8418_event_ = false;
747757

748-
if (k & 0x80) { //Key pressed, not released
758+
if (k != 0) {
759+
bool isPress = (k & 0x80) != 0;
749760
k &= 0x7F;
750761
k--;
751-
//return currentKB[k/10][k%10];
752-
if ((k/10) < 4) {
762+
763+
if (isPress && (k / 10) < 4) {
753764
//Key was pressed, reset timeout counter
754765
CLOCK().setPrevTimeMillis(millis());
755766

767+
// --- 2. HOLD-TAB SPECIAL CHARACTER UI ---
768+
if (k == 20) { // TAB KEY
769+
unsigned long pressTime = millis();
770+
bool charSelected = false;
771+
int cycleIndex = 0;
772+
char activeBaseChar = 0;
773+
const char** activeCycle = nullptr;
774+
int activeCycleLen = 0;
775+
bool uiDrawn = false;
776+
777+
for (;;) {
778+
// Draw the "Holding" hint after 100ms
779+
if (!uiDrawn && (millis() - pressTime > 100)) {
780+
u8g2.setDrawColor(0);
781+
u8g2.setFont(u8g2_font_5x7_tf);
782+
u8g2.drawBox(u8g2.getDisplayWidth()-u8g2.getStrWidth("XXXX/XX/XXXX"), u8g2.getDisplayHeight()- 8, u8g2.getStrWidth("XXXX/XX/XXXX"), 8);
783+
u8g2.setDrawColor(1);
784+
u8g2.drawStr(u8g2.getDisplayWidth()-u8g2.getStrWidth("INTL"), u8g2.getDisplayHeight(), "INTL");
785+
u8g2.sendBuffer();
786+
uiDrawn = true;
787+
}
788+
789+
int next_k = keypad_.getEvent();
790+
if (next_k != 0) {
791+
bool nextPress = (next_k & 0x80) != 0;
792+
next_k &= 0x7F;
793+
next_k--;
794+
795+
// If TAB is RELEASED
796+
if (!nextPress && next_k == 20) {
797+
if (!charSelected) {
798+
if (uiDrawn) {
799+
u8g2.clearBuffer();
800+
u8g2.sendBuffer();
801+
}
802+
// Return normal TAB depending on SHIFT state
803+
return (kbState_ == 1 || kbState_ == 3) ? 14 : 9;
804+
} else {
805+
// Push the selected UTF-8 string into the buffer
806+
String sel = activeCycle[cycleIndex];
807+
strncpy(utf8Buffer, sel.c_str(), 7);
808+
809+
char c = utf8Buffer[0];
810+
memmove(utf8Buffer, utf8Buffer + 1, 7);
811+
812+
u8g2.clearBuffer();
813+
u8g2.sendBuffer();
814+
return c; // Return first byte instantly
815+
}
816+
}
817+
// If another key is PRESSED while holding TAB
818+
else if (nextPress && (next_k / 10) < 4) {
819+
char baseC = 0;
820+
switch (kbState_) {
821+
case 0: baseC = keysArray[next_k/10][next_k%10]; break;
822+
case 1: baseC = keysArraySHFT[next_k/10][next_k%10]; break;
823+
case 2: baseC = keysArrayFN[next_k/10][next_k%10]; break;
824+
case 3: baseC = keysArrayFN_SHFT[next_k/10][next_k%10]; break;
825+
}
826+
827+
// Special character arrays (Native UTF-8 Strings)
828+
static const char* cyc_a[] = {"a", "á", "à", "â", "ä", "ã", "å", "æ"};
829+
static const char* cyc_A[] = {"A", "Á", "À", "Â", "Ä", "Ã", "Å", "Æ"};
830+
static const char* cyc_e[] = {"e", "é", "è", "ê", "ë"};
831+
static const char* cyc_E[] = {"E", "É", "È", "Ê", "Ë"};
832+
static const char* cyc_i[] = {"i", "í", "ì", "î", "ï"};
833+
static const char* cyc_I[] = {"I", "Í", "Ì", "Î", "Ï"};
834+
static const char* cyc_o[] = {"o", "ó", "ò", "ô", "ö", "õ", "ø"};
835+
static const char* cyc_O[] = {"O", "Ó", "Ò", "Ô", "Ö", "Õ", "Ø"};
836+
static const char* cyc_u[] = {"u", "ú", "ù", "û", "ü"};
837+
static const char* cyc_U[] = {"U", "Ú", "Ù", "Û", "Ü"};
838+
static const char* cyc_n[] = {"n", "ñ"};
839+
static const char* cyc_N[] = {"N", "Ñ"};
840+
static const char* cyc_c[] = {"c", "ç"};
841+
static const char* cyc_C[] = {"C", "Ç"};
842+
843+
bool matched = true;
844+
if (baseC == 'a') { activeCycle = cyc_a; activeCycleLen = 8; }
845+
else if (baseC == 'A') { activeCycle = cyc_A; activeCycleLen = 8; }
846+
else if (baseC == 'e') { activeCycle = cyc_e; activeCycleLen = 5; }
847+
else if (baseC == 'E') { activeCycle = cyc_E; activeCycleLen = 5; }
848+
else if (baseC == 'i') { activeCycle = cyc_i; activeCycleLen = 5; }
849+
else if (baseC == 'I') { activeCycle = cyc_I; activeCycleLen = 5; }
850+
else if (baseC == 'o') { activeCycle = cyc_o; activeCycleLen = 7; }
851+
else if (baseC == 'O') { activeCycle = cyc_O; activeCycleLen = 7; }
852+
else if (baseC == 'u') { activeCycle = cyc_u; activeCycleLen = 5; }
853+
else if (baseC == 'U') { activeCycle = cyc_U; activeCycleLen = 5; }
854+
else if (baseC == 'n') { activeCycle = cyc_n; activeCycleLen = 2; }
855+
else if (baseC == 'N') { activeCycle = cyc_N; activeCycleLen = 2; }
856+
else if (baseC == 'c') { activeCycle = cyc_c; activeCycleLen = 2; }
857+
else if (baseC == 'C') { activeCycle = cyc_C; activeCycleLen = 2; }
858+
else matched = false;
859+
860+
if (matched) {
861+
if (baseC == activeBaseChar) {
862+
cycleIndex = (cycleIndex + 1) % activeCycleLen;
863+
} else {
864+
activeBaseChar = baseC;
865+
cycleIndex = 1 % activeCycleLen;
866+
}
867+
charSelected = true;
868+
869+
// Render Character Selector
870+
int cx = (u8g2.getDisplayWidth()-22*activeCycleLen)/2.0;
871+
u8g2.setDrawColor(0);
872+
u8g2.drawRBox(cx, 2, activeCycleLen*22, 28, 4);
873+
u8g2.setDrawColor(1);
874+
u8g2.drawRFrame(cx, 2, activeCycleLen*22, 28, 4);
875+
u8g2.setFont(u8g2_font_luBS18_tf);
876+
877+
for (int i=0; i<activeCycleLen; i++) {
878+
if (i == cycleIndex) {
879+
u8g2.setDrawColor(1);
880+
u8g2.drawRBox(cx, 2, 22, 28, 4);
881+
u8g2.setDrawColor(0);
882+
} else {
883+
u8g2.setDrawColor(1);
884+
}
885+
u8g2.drawUTF8(cx + ((22-u8g2.getUTF8Width(activeCycle[i]))/2.0), 25, activeCycle[i]);
886+
u8g2.setDrawColor(0);
887+
cx += 22;
888+
}
889+
u8g2.sendBuffer();
890+
u8g2.setDrawColor(1);
891+
uiDrawn = true;
892+
}
893+
}
894+
}
895+
delay(10); // Don't choke the RTOS
896+
}
897+
}
898+
// --- END SPECIAL CHARACTER LOGIC ---
899+
756900
//Return Key
757901
switch (kbState_) {
758902
case 0:
@@ -771,7 +915,6 @@ char PocketmageKB::updateKeypress() {
771915
}
772916

773917
return 0;
774-
775918
}
776919

777920
void PocketmageKB::checkUSBKB() {

Code/PocketMage_V3/lib/PocketMage/src/pocketmage_oled.cpp

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -37,52 +37,47 @@ void PocketmageOled::oledWord(String word, bool allowLarge, bool showInfo, Strin
3737
if (showInfo && bottomText == "") infoBar();
3838
else if (bottomText != "") {
3939
u8g2_.setFont(u8g2_font_5x7_tf);
40-
u8g2_.drawStr((dw - u8g2_.getStrWidth(bottomText.c_str())) / 2, dh, bottomText.c_str());
40+
u8g2_.drawUTF8((dw - u8g2_.getUTF8Width(bottomText.c_str())) / 2, dh, bottomText.c_str());
4141
}
4242

43+
// Changed all _tr fonts to _tf to include the extended UTF-8 character set
4344
if (allowLarge) {
44-
/*u8g2_.setFont(u8g2_font_ncenB24_tr);
45-
if (u8g2_.getStrWidth(word.c_str()) < dw) {
46-
u8g2_.drawStr((dw - u8g2_.getStrWidth(word.c_str()))/2,16+12,word.c_str());
47-
u8g2_.sendBuffer();
48-
return;
49-
}*/
50-
u8g2_.setFont(u8g2_font_ncenB18_tr);
51-
if (u8g2_.getStrWidth(word.c_str()) < dw) {
52-
u8g2_.drawStr((dw - u8g2_.getStrWidth(word.c_str()))/2, 16+5, word.c_str());
45+
u8g2_.setFont(u8g2_font_ncenB18_tf);
46+
if (u8g2_.getUTF8Width(word.c_str()) < dw) {
47+
u8g2_.drawUTF8((dw - u8g2_.getUTF8Width(word.c_str()))/2, 16+5, word.c_str());
5348
u8g2_.sendBuffer();
5449
return;
5550
}
5651
}
5752

58-
u8g2_.setFont(u8g2_font_ncenB14_tr);
59-
if (u8g2_.getStrWidth(word.c_str()) < dw) {
60-
u8g2_.drawStr((dw - u8g2_.getStrWidth(word.c_str()))/2, 16+3, word.c_str());
53+
u8g2_.setFont(u8g2_font_ncenB14_tf);
54+
if (u8g2_.getUTF8Width(word.c_str()) < dw) {
55+
u8g2_.drawUTF8((dw - u8g2_.getUTF8Width(word.c_str()))/2, 16+3, word.c_str());
6156
u8g2_.sendBuffer();
6257
return;
6358
}
6459

65-
u8g2_.setFont(u8g2_font_ncenB12_tr);
66-
if (u8g2_.getStrWidth(word.c_str()) < dw) {
67-
u8g2_.drawStr((dw - u8g2_.getStrWidth(word.c_str()))/2, 16+2, word.c_str());
60+
u8g2_.setFont(u8g2_font_ncenB12_tf);
61+
if (u8g2_.getUTF8Width(word.c_str()) < dw) {
62+
u8g2_.drawUTF8((dw - u8g2_.getUTF8Width(word.c_str()))/2, 16+2, word.c_str());
6863
u8g2_.sendBuffer();
6964
return;
7065
}
7166

72-
u8g2_.setFont(u8g2_font_ncenB10_tr);
73-
if (u8g2_.getStrWidth(word.c_str()) < dw) {
74-
u8g2_.drawStr((dw - u8g2_.getStrWidth(word.c_str()))/2, 16+1, word.c_str());
67+
u8g2_.setFont(u8g2_font_ncenB10_tf);
68+
if (u8g2_.getUTF8Width(word.c_str()) < dw) {
69+
u8g2_.drawUTF8((dw - u8g2_.getUTF8Width(word.c_str()))/2, 16+1, word.c_str());
7570
u8g2_.sendBuffer();
7671
return;
7772
}
7873

79-
u8g2_.setFont(u8g2_font_ncenB08_tr);
80-
if (u8g2_.getStrWidth(word.c_str()) < dw) {
81-
u8g2_.drawStr((dw - u8g2_.getStrWidth(word.c_str()))/2, 16, word.c_str());
74+
u8g2_.setFont(u8g2_font_ncenB08_tf);
75+
if (u8g2_.getUTF8Width(word.c_str()) < dw) {
76+
u8g2_.drawUTF8((dw - u8g2_.getUTF8Width(word.c_str()))/2, 16, word.c_str());
8277
u8g2_.sendBuffer();
8378
return;
8479
} else {
85-
u8g2_.drawStr(dw - u8g2_.getStrWidth(word.c_str()), 16, word.c_str());
80+
u8g2_.drawUTF8(dw - u8g2_.getUTF8Width(word.c_str()), 16, word.c_str());
8681
u8g2_.sendBuffer();
8782
return;
8883
}
@@ -93,14 +88,13 @@ void PocketmageOled::oledLine(String line, int input_pos, bool doProgressBar, St
9388
const uint16_t dw = u8g2_.getDisplayWidth();
9489
const uint16_t dh = u8g2_.getDisplayHeight();
9590

96-
uint8_t mcl = EINK().maxCharsPerLine();
97-
uint8_t maxLength = mcl;
9891
String left = "";
9992
u8g2_.clearBuffer();
10093

10194
//PROGRESS BAR
10295
if (doProgressBar && line.length() > 0) {
103-
const uint16_t charWidth = strWidth(line);
96+
// Fixed string measurement
97+
const uint16_t charWidth = u8g2_.getUTF8Width(line.c_str());
10498

10599
// Restored global display reference
106100
const uint8_t progress = map(charWidth, 0, display.width() - 5, 0, dw);
@@ -128,9 +122,9 @@ void PocketmageOled::oledLine(String line, int input_pos, bool doProgressBar, St
128122
// Display bottomMsg
129123
else {
130124
u8g2_.setFont(u8g2_font_5x7_tf);
131-
u8g2_.drawStr(0, dh, bottomMsg.c_str());
125+
u8g2_.drawUTF8(0, dh, bottomMsg.c_str());
132126

133-
// Draw FN/Shift indicator
127+
// Draw FN/Shift indicator (Standard ASCII is fine here)
134128
int state = KB().getKeyboardState();
135129
switch (state) {
136130
case 1: //SHIFT
@@ -147,47 +141,51 @@ void PocketmageOled::oledLine(String line, int input_pos, bool doProgressBar, St
147141
}
148142
}
149143

150-
// DRAW LINE TEXT (unchanged)
151-
u8g2_.setFont(u8g2_font_ncenB18_tr);
152-
if (u8g2_.getStrWidth(line.c_str()) < (dw - 5)) {
144+
// DRAW LINE TEXT (Upgraded to full UTF-8 Font and Draw routines)
145+
u8g2_.setFont(u8g2_font_ncenB18_tf);
146+
int lineWidth = u8g2_.getUTF8Width(line.c_str());
147+
148+
if (lineWidth < (dw - 5)) {
153149
if (line.length() > 0) {
154150
if (input_pos == 0) {
155-
u8g2_.drawStr(0, 20, line.c_str());
151+
u8g2_.drawUTF8(0, 20, line.c_str());
156152
u8g2_.drawVLine(0, 1, 22);
157153
} else if (input_pos == line.length()) {
158-
u8g2_.drawStr(0, 20, line.c_str());
159-
u8g2_.drawVLine(u8g2_.getStrWidth(line.c_str()) + 2, 1, 22);
154+
u8g2_.drawUTF8(0, 20, line.c_str());
155+
u8g2_.drawVLine(lineWidth + 2, 1, 22);
160156
} else {
161157
left = line.substring(0, input_pos);
162-
u8g2_.drawStr(0, 20, line.c_str());
163-
u8g2_.drawVLine(u8g2_.getStrWidth(left.c_str()), 1, 22);
158+
u8g2_.drawUTF8(0, 20, line.c_str());
159+
u8g2_.drawVLine(u8g2_.getUTF8Width(left.c_str()), 1, 22);
164160
}
165161
} else {
166-
u8g2_.drawStr(0, 20, line.c_str());
162+
u8g2_.drawUTF8(0, 20, line.c_str());
163+
u8g2_.drawVLine(0, 1, 22); // Still draw the cursor when string is empty
167164
}
168165
} else {
169166
if (input_pos == 0) {
170-
u8g2_.drawStr(0, 20, line.c_str());
167+
u8g2_.drawUTF8(0, 20, line.c_str());
171168
u8g2_.drawVLine(0, 1, 22);
172169
} else if (input_pos == line.length()) {
173170
//show end of line, input scrolls left
174-
u8g2_.drawStr(dw - 8 - u8g2_.getStrWidth(line.c_str()), 20, line.c_str());
171+
u8g2_.drawUTF8(dw - 8 - lineWidth, 20, line.c_str());
175172
u8g2_.drawVLine(dw - 6, 1, 22);
176173
} else {
177-
//calc cursor pos
174+
//calc cursor pos using perfect UTF-8 string split math
178175
left = line.substring(0, input_pos);
179-
int cursor_offset = u8g2_.getStrWidth(left.c_str());
176+
int cursor_offset = u8g2_.getUTF8Width(left.c_str());
180177
int line_start = 0;
178+
181179
if (cursor_offset > (dw - 8) / 2) {
182180
//shift left
183181
line_start += ((dw - 8) / 2) - cursor_offset;
184-
if (line_start + u8g2_.getStrWidth(line.c_str()) < dw - 8) {
182+
if (line_start + lineWidth < dw - 8) {
185183
//shift back right
186-
line_start += dw - 8 - (line_start + u8g2_.getStrWidth(line.c_str()));
184+
line_start += dw - 8 - (line_start + lineWidth);
187185
}
188186
cursor_offset += line_start;
189187
}
190-
u8g2_.drawStr(line_start, 20, line.c_str());
188+
u8g2_.drawUTF8(line_start, 20, line.c_str());
191189
u8g2_.drawVLine(cursor_offset, 1, 22);
192190
}
193191
}

0 commit comments

Comments
 (0)