Expand on text field keyboard shortcuts
- Clipboard support - Text will now wrap when a word is longer than the width of the destination rect (doesn't just apply to text fields, but is most relevant there) - Edit menu stub added in scenario editor code - Rich text keys (eg cut, copy, paste, select all) are no longer processed by the dialog itself; only the text field processes them; this just means that if they were set as button equivalents they would no longer work (you'd have to set the raw equivalent instead, eg ctrl+C instead of copy). - Text fields now rely on SFML's TextEntered event for actual input - the practical result of this is that your keyboard layout is honoured (though non-ASCII characters just display as boxes). - In a similar vein, shift is not auto-applied to the input keys, so you'd have to set shift+2 instead of @ as the key equivalent (this actually fixes some stuff, such as in the spellcasting dialog, since I was already setting shift+2 instead of @).
This commit is contained in:
@@ -32,6 +32,7 @@ extern sf::Texture bg_gworld;
|
|||||||
extern bool play_sounds;
|
extern bool play_sounds;
|
||||||
const short cDialog::BG_DARK = 5, cDialog::BG_LIGHT = 16;
|
const short cDialog::BG_DARK = 5, cDialog::BG_LIGHT = 16;
|
||||||
short cDialog::defaultBackground = cDialog::BG_DARK;
|
short cDialog::defaultBackground = cDialog::BG_DARK;
|
||||||
|
cDialog* cDialog::topWindow = nullptr;
|
||||||
|
|
||||||
static std::string generateRandomString(){
|
static std::string generateRandomString(){
|
||||||
// Not bothering to seed, because it doesn't actually matter if it's truly random.
|
// Not bothering to seed, because it doesn't actually matter if it's truly random.
|
||||||
@@ -942,12 +943,21 @@ bool cDialog::remove(std::string key){
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool cDialog::sendInput(cKey key) {
|
||||||
|
if(topWindow == nullptr) return false;
|
||||||
|
std::string field = topWindow->currentFocus;
|
||||||
|
if(field.empty()) return true;
|
||||||
|
dynamic_cast<cTextField*>(topWindow->controls[field])->handleInput(key);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void cDialog::run(){
|
void cDialog::run(){
|
||||||
|
cDialog* formerTop = topWindow;
|
||||||
cursor_type former_curs = current_cursor;
|
cursor_type former_curs = current_cursor;
|
||||||
set_cursor(sword_curs);
|
set_cursor(sword_curs);
|
||||||
using kb = sf::Keyboard;
|
using kb = sf::Keyboard;
|
||||||
kb::Key k;
|
kb::Key k;
|
||||||
cKey key;
|
cKey key, pendingKey;
|
||||||
sf::Event currentEvent;
|
sf::Event currentEvent;
|
||||||
std::string itemHit = "";
|
std::string itemHit = "";
|
||||||
dialogNotToast = true;
|
dialogNotToast = true;
|
||||||
@@ -1011,6 +1021,10 @@ void cDialog::run(){
|
|||||||
key.spec = true;
|
key.spec = true;
|
||||||
key.k = key_tab;
|
key.k = key_tab;
|
||||||
break;
|
break;
|
||||||
|
case kb::Insert:
|
||||||
|
key.spec = true;
|
||||||
|
key.k = key_insert;
|
||||||
|
break;
|
||||||
case kb::F1:
|
case kb::F1:
|
||||||
key.spec = true;
|
key.spec = true;
|
||||||
key.k = key_help;
|
key.k = key_help;
|
||||||
@@ -1031,7 +1045,6 @@ void cDialog::run(){
|
|||||||
key.spec = true;
|
key.spec = true;
|
||||||
key.k = key_pgdn;
|
key.k = key_pgdn;
|
||||||
break;
|
break;
|
||||||
// TODO: Add cases for key_tab and key_help and others
|
|
||||||
case kb::LShift:
|
case kb::LShift:
|
||||||
case kb::RShift:
|
case kb::RShift:
|
||||||
case kb::LAlt:
|
case kb::LAlt:
|
||||||
@@ -1042,54 +1055,40 @@ void cDialog::run(){
|
|||||||
case kb::RSystem:
|
case kb::RSystem:
|
||||||
continue;
|
continue;
|
||||||
default:
|
default:
|
||||||
// TODO: Should probably only support system or control depending on OS
|
|
||||||
key.spec = false;
|
key.spec = false;
|
||||||
if(currentEvent.key.system || currentEvent.key.control) {
|
key.c = keyToChar(k, false);
|
||||||
if(k == kb::C) {
|
|
||||||
key.spec = true;
|
|
||||||
key.k = key_copy;
|
|
||||||
} else if(k == kb::X) {
|
|
||||||
key.spec = true;
|
|
||||||
key.k = key_cut;
|
|
||||||
} else if(k == kb::V) {
|
|
||||||
key.spec = true;
|
|
||||||
key.k = key_paste;
|
|
||||||
} else if(k == kb::A) {
|
|
||||||
key.spec = true;
|
|
||||||
key.k = key_selectall;
|
|
||||||
}
|
|
||||||
if(key.spec) break;
|
|
||||||
}
|
|
||||||
key.c = keyToChar(k, currentEvent.key.shift);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
key.mod = mod_none;
|
key.mod = mod_none;
|
||||||
if(currentEvent.key.control || currentEvent.key.system) {
|
if(currentEvent.key.*systemKey)
|
||||||
if(key.spec){
|
key.mod += mod_ctrl;
|
||||||
if(key.k == key_left) key.k = key_home;
|
|
||||||
else if(key.k == key_right) key.k = key_end;
|
|
||||||
else if(key.k == key_up) key.k = key_pgup;
|
|
||||||
else if(key.k == key_down) key.k = key_pgdn;
|
|
||||||
else if(key.k == key_copy || key.k == key_cut);
|
|
||||||
else if(key.k == key_paste || key.k == key_selectall);
|
|
||||||
else key.mod += mod_ctrl;
|
|
||||||
}else key.mod += mod_ctrl;
|
|
||||||
}
|
|
||||||
if(currentEvent.key.shift) key.mod += mod_shift;
|
if(currentEvent.key.shift) key.mod += mod_shift;
|
||||||
if(currentEvent.key.alt) key.mod += mod_alt;
|
if(currentEvent.key.alt) key.mod += mod_alt;
|
||||||
itemHit = process_keystroke(key); // TODO: This should be a separate check from the fields thing?
|
itemHit = process_keystroke(key);
|
||||||
if(itemHit.empty()) break;
|
if(!itemHit.empty())
|
||||||
where = controls[itemHit]->getBounds().centre();
|
where = controls[itemHit]->getBounds().centre();
|
||||||
if(controls[itemHit]->getType() == CTRL_FIELD){
|
// Now check for focused fields.
|
||||||
if(key.spec && key.k == key_tab){
|
if(currentFocus.empty()) break;
|
||||||
// Could use key.mod, but this is slightly easier.
|
// If it's a tab, handle tab order
|
||||||
if(currentEvent.key.shift)
|
if(key.spec && key.k == key_tab){
|
||||||
handleTabOrder(itemHit, tabOrder.rbegin(), tabOrder.rend());
|
// Could use key.mod, but this is slightly easier.
|
||||||
else handleTabOrder(itemHit, tabOrder.begin(), tabOrder.end());
|
if(currentEvent.key.shift)
|
||||||
} else if(!key.spec || key.k != key_enter || mod_contains(key.mod, mod_alt)) {
|
handleTabOrder(currentFocus, tabOrder.rbegin(), tabOrder.rend());
|
||||||
dynamic_cast<cTextField*>(controls[itemHit])->handleInput(key);
|
else handleTabOrder(currentFocus, tabOrder.begin(), tabOrder.end());
|
||||||
itemHit = "";
|
} else {
|
||||||
}
|
// If it's a character key, and the system key (control/command) is not pressed,
|
||||||
|
// we have an upcoming TextEntered event which contains more information.
|
||||||
|
// Otherwise, handle it right away. But never handle enter or escape.
|
||||||
|
if((key.spec && key.k != key_enter && key.k != key_esc) || mod_contains(key.mod, mod_ctrl))
|
||||||
|
dynamic_cast<cTextField*>(controls[currentFocus])->handleInput(key);
|
||||||
|
pendingKey = key;
|
||||||
|
if(key.k != key_enter && key.k != key_esc) itemHit = "";
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case sf::Event::TextEntered:
|
||||||
|
if(!pendingKey.spec && !currentFocus.empty()) {
|
||||||
|
pendingKey.c = currentEvent.text.unicode;
|
||||||
|
dynamic_cast<cTextField*>(controls[currentFocus])->handleInput(pendingKey);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case sf::Event::MouseButtonPressed:
|
case sf::Event::MouseButtonPressed:
|
||||||
@@ -1124,9 +1123,11 @@ void cDialog::run(){
|
|||||||
itemHit.clear();
|
itemHit.clear();
|
||||||
}
|
}
|
||||||
win.setVisible(false);
|
win.setVisible(false);
|
||||||
|
// TODO: The introduction of the static topWindow means I may be able to use this instead of parent->win; do I still need parent?
|
||||||
sf::RenderWindow* parentWin = &(parent ? parent->win : mainPtr);
|
sf::RenderWindow* parentWin = &(parent ? parent->win : mainPtr);
|
||||||
while(parentWin->pollEvent(currentEvent));
|
while(parentWin->pollEvent(currentEvent));
|
||||||
set_cursor(former_curs);
|
set_cursor(former_curs);
|
||||||
|
topWindow = formerTop;
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Iter> void cDialog::handleTabOrder(string& itemHit, Iter begin, Iter end) {
|
template<typename Iter> void cDialog::handleTabOrder(string& itemHit, Iter begin, Iter end) {
|
||||||
@@ -1144,8 +1145,6 @@ template<typename Iter> void cDialog::handleTabOrder(string& itemHit, Iter begin
|
|||||||
if(iter->second->getType() == CTRL_FIELD){
|
if(iter->second->getType() == CTRL_FIELD){
|
||||||
if(iter->second->triggerFocusHandler(*this,iter->first,false)){
|
if(iter->second->triggerFocusHandler(*this,iter->first,false)){
|
||||||
currentFocus = iter->first;
|
currentFocus = iter->first;
|
||||||
} else {
|
|
||||||
itemHit = "";
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1261,10 +1260,6 @@ bool cDialog::addLabelFor(std::string key, std::string label, eLabelPos where, s
|
|||||||
std::string cDialog::process_keystroke(cKey keyHit){
|
std::string cDialog::process_keystroke(cKey keyHit){
|
||||||
ctrlIter iter = controls.begin();
|
ctrlIter iter = controls.begin();
|
||||||
while(iter != controls.end()){
|
while(iter != controls.end()){
|
||||||
if(iter->second->getType() == CTRL_FIELD && iter->second->isVisible() && dynamic_cast<cTextField*>(iter->second)->hasFocus()) {
|
|
||||||
if(!keyHit.spec || (keyHit.k != key_esc && keyHit.k != key_help))
|
|
||||||
return iter->first;
|
|
||||||
}
|
|
||||||
if(iter->second->isVisible() && iter->second->isClickable() && iter->second->getAttachedKey() == keyHit){
|
if(iter->second->isVisible() && iter->second->isClickable() && iter->second->getAttachedKey() == keyHit){
|
||||||
iter->second->setActive(true);
|
iter->second->setActive(true);
|
||||||
draw();
|
draw();
|
||||||
|
@@ -47,6 +47,7 @@ class cDialog {
|
|||||||
void loadFromFile(std::string path);
|
void loadFromFile(std::string path);
|
||||||
template<typename Iter> void handleTabOrder(std::string& itemHit, Iter begin, Iter end);
|
template<typename Iter> void handleTabOrder(std::string& itemHit, Iter begin, Iter end);
|
||||||
std::vector<std::pair<std::string,cTextField*>> tabOrder;
|
std::vector<std::pair<std::string,cTextField*>> tabOrder;
|
||||||
|
static cDialog* topWindow; // Tracks the frontmost dialog.
|
||||||
public:
|
public:
|
||||||
/// Performs essential startup initialization. Generally should not be called directly.
|
/// Performs essential startup initialization. Generally should not be called directly.
|
||||||
static void init();
|
static void init();
|
||||||
@@ -171,6 +172,10 @@ public:
|
|||||||
/// Get the bounding rect of the dialog.
|
/// Get the bounding rect of the dialog.
|
||||||
/// @return The dialog's bounding rect.
|
/// @return The dialog's bounding rect.
|
||||||
rectangle getBounds() {return winRect;}
|
rectangle getBounds() {return winRect;}
|
||||||
|
/// Send keyboard input to the frontmost dialog.
|
||||||
|
/// Currently, only text edit fields will respond to this.
|
||||||
|
/// @return true if there was a dialog opened to send to.
|
||||||
|
static bool sendInput(cKey key);
|
||||||
/// Sets whether to animate graphics in dialogs.
|
/// Sets whether to animate graphics in dialogs.
|
||||||
static bool doAnimations;
|
static bool doAnimations;
|
||||||
cDialog& operator=(cDialog& other) = delete;
|
cDialog& operator=(cDialog& other) = delete;
|
||||||
|
@@ -38,11 +38,9 @@ enum eKeyMod {
|
|||||||
enum eSpecKey {
|
enum eSpecKey {
|
||||||
key_left, key_right, key_up, key_down,
|
key_left, key_right, key_up, key_down,
|
||||||
key_esc, key_enter, key_tab, key_help, // key_help should bind to the help key on Mac and the F1 key on Windows
|
key_esc, key_enter, key_tab, key_help, // key_help should bind to the help key on Mac and the F1 key on Windows
|
||||||
key_bsp, key_del, key_home, key_end, key_pgup, key_pgdn, // TODO: Implement these
|
key_bsp, key_del, key_home, key_end, key_pgup, key_pgdn, key_top, key_bottom, key_insert,
|
||||||
key_copy, key_cut, key_paste, key_selectall
|
key_copy, key_cut, key_paste, key_selectall, key_undo, key_redo,
|
||||||
// TODO: On Mac, command-left should trigger key_home; command-right should trigger key_end;
|
key_word_left, key_word_right, key_word_bsp, key_word_del,
|
||||||
// command-up should trigger key_pgup; and command-down should trigger key_pgdn.
|
|
||||||
// This is in addition to the home, end, pgup, pgdn keys triggering these.
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Represents a keypress.
|
/// Represents a keypress.
|
||||||
@@ -51,7 +49,7 @@ struct cKey {
|
|||||||
bool spec;
|
bool spec;
|
||||||
union {
|
union {
|
||||||
/// The character that has been typed.
|
/// The character that has been typed.
|
||||||
unsigned char c;
|
wchar_t c;
|
||||||
/// The special key that was pressed.
|
/// The special key that was pressed.
|
||||||
eSpecKey k;
|
eSpecKey k;
|
||||||
};
|
};
|
||||||
|
@@ -13,6 +13,7 @@
|
|||||||
#include "dialog.h"
|
#include "dialog.h"
|
||||||
#include "dlogutil.h"
|
#include "dlogutil.h"
|
||||||
#include "graphtool.h"
|
#include "graphtool.h"
|
||||||
|
#include "winutil.h"
|
||||||
|
|
||||||
void cTextField::attachClickHandler(click_callback_t) throw(xHandlerNotSupported){
|
void cTextField::attachClickHandler(click_callback_t) throw(xHandlerNotSupported){
|
||||||
throw xHandlerNotSupported(false);
|
throw xHandlerNotSupported(false);
|
||||||
@@ -185,10 +186,97 @@ void cTextField::draw(){
|
|||||||
undo_clip(*inWindow);
|
undo_clip(*inWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static cKey divineFunction(cKey key) {
|
||||||
|
/* Summary of platform-dependent navigation/edit keys:
|
||||||
|
Function | Mac | Windows
|
||||||
|
-----------+-------------------+-------------
|
||||||
|
Home | Home, Cmd-Left | Home
|
||||||
|
End | End, Cmd-Right | End
|
||||||
|
PgUp | PgUp, Alt-Up | PgUp
|
||||||
|
PgDn | PgDn, Alt-Down | PgDn
|
||||||
|
To Top | Cmd-Up, Cmd-Home| Ctrl-Up, Ctrl-Home
|
||||||
|
To Bottom | Cmd-Down, Cmd-End | Ctrl-Down, Ctrl-End
|
||||||
|
Word Left | Alt-Left | Ctrl-Left
|
||||||
|
Word Right | Alt-Right | Ctrl-Right
|
||||||
|
Word Del | Alt-Delete | Ctrl-Delete
|
||||||
|
Word Back | Alt-Backspace | Ctrl-Backspace
|
||||||
|
-----------+-------------------+----------------------
|
||||||
|
Cut | Cmd-X, Shift-Del | Ctrl-X, Shift-Del
|
||||||
|
Copy | Cmd-C, Cmd-Ins | Ctrl-C, Ctrl-Ins
|
||||||
|
Paste | Cmd-V, Shift-Ins | Ctrl-V, Shift-Ins
|
||||||
|
Select All | Cmd-A | Ctrl-A
|
||||||
|
Undo | Cmd-Z | Ctrl-Z
|
||||||
|
Redo | Cmd-Y, Shift-Cmd-Z| Ctrl-Y, Shift-Ctrl-Z
|
||||||
|
This is done to more closely emulate native Mac behaviour.
|
||||||
|
The Insert and Shift-Delete combos are included to more closely emulate
|
||||||
|
native Windows behaviour.
|
||||||
|
*/
|
||||||
|
if(!key.spec) {
|
||||||
|
if(mod_contains(key.mod, mod_ctrl)) {
|
||||||
|
if(key.c == 'c') {
|
||||||
|
key.spec = true;
|
||||||
|
key.k = key_copy;
|
||||||
|
} else if(key.c == 'x') {
|
||||||
|
key.spec = true;
|
||||||
|
key.k = key_cut;
|
||||||
|
} else if(key.c == 'v') {
|
||||||
|
key.spec = true;
|
||||||
|
key.k = key_paste;
|
||||||
|
} else if(key.c == 'a') {
|
||||||
|
key.spec = true;
|
||||||
|
key.k = key_selectall;
|
||||||
|
} else if(key.c == 'z') {
|
||||||
|
key.spec = true;
|
||||||
|
if(mod_contains(key.mod, mod_shift)) {
|
||||||
|
key.k = key_redo;
|
||||||
|
key.mod -= mod_shift;
|
||||||
|
} else key.k = key_undo;
|
||||||
|
} else if(key.c == 'y') {
|
||||||
|
key.spec = true;
|
||||||
|
key.k = key_redo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(key.spec) key.mod -= mod_ctrl;
|
||||||
|
} else {
|
||||||
|
eSpecKey former = key.k;
|
||||||
|
if(mod_contains(key.mod, mod_ctrl)) {
|
||||||
|
#ifdef __APPLE__
|
||||||
|
if(key.k == key_left) key.k = key_home;
|
||||||
|
else if(key.k == key_right) key.k = key_end;
|
||||||
|
#else
|
||||||
|
if(key.k == key_left) key.k = key_word_left;
|
||||||
|
else if(key.k == key_right) key.k = key_word_right;
|
||||||
|
#endif
|
||||||
|
else if(key.k == key_up) key.k = key_top;
|
||||||
|
else if(key.k == key_down) key.k = key_bottom;
|
||||||
|
else if(key.k == key_home) key.k = key_top;
|
||||||
|
else if(key.k == key_end) key.k = key_bottom;
|
||||||
|
else if(key.k == key_insert) key.k = key_copy;
|
||||||
|
#ifndef __APPLE__
|
||||||
|
else if(key.k == key_del) key.k = key_word_del;
|
||||||
|
else if(key.k == key_bsp) key.k = key_word_bsp;
|
||||||
|
#else
|
||||||
|
} else if(mod_contains(key.mod, mod_alt)) {
|
||||||
|
if(key.k == key_up) key.k = key_pgup;
|
||||||
|
else if(key.k == key_down) key.k = key_pgdn;
|
||||||
|
else if(key.k == key_left) key.k = key_word_left;
|
||||||
|
else if(key.k == key_right) key.k = key_word_right;
|
||||||
|
else if(key.k == key_del) key.k = key_word_del;
|
||||||
|
else if(key.k == key_bsp) key.k = key_word_bsp;
|
||||||
|
#endif
|
||||||
|
} else if(mod_contains(key.mod, mod_shift)) {
|
||||||
|
if(key.k == key_insert) key.k = key_paste;
|
||||||
|
else if(key.k == key_del) key.k = key_cut;
|
||||||
|
}
|
||||||
|
if(key.k != former) key.mod -= mod_ctrl;
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
void cTextField::handleInput(cKey key) {
|
void cTextField::handleInput(cKey key) {
|
||||||
bool select = mod_contains(key.mod, mod_shift);
|
bool select = mod_contains(key.mod, mod_shift);
|
||||||
bool word = mod_contains(key.mod, mod_alt) || mod_contains(key.mod, mod_ctrl);
|
|
||||||
bool haveSelection = insertionPoint != selectionPoint;
|
bool haveSelection = insertionPoint != selectionPoint;
|
||||||
|
key = divineFunction(key);
|
||||||
size_t new_ip;
|
size_t new_ip;
|
||||||
std::string contents = getText();
|
std::string contents = getText();
|
||||||
if(!key.spec) {
|
if(!key.spec) {
|
||||||
@@ -199,18 +287,18 @@ void cTextField::handleInput(cKey key) {
|
|||||||
handleInput(deleteKey);
|
handleInput(deleteKey);
|
||||||
contents = getText();
|
contents = getText();
|
||||||
}
|
}
|
||||||
contents.insert(contents.begin() + insertionPoint, key.c);
|
contents.insert(contents.begin() + insertionPoint, char(key.c));
|
||||||
selectionPoint = ++insertionPoint;
|
selectionPoint = ++insertionPoint;
|
||||||
} else switch(key.k) {
|
} else switch(key.k) {
|
||||||
// TODO: Implement all the other special keys
|
// TODO: Implement all the other special keys
|
||||||
case key_left:
|
case key_left: case key_word_left:
|
||||||
if(haveSelection && !select) {
|
if(haveSelection && !select) {
|
||||||
selectionPoint = insertionPoint = std::min(selectionPoint,insertionPoint);
|
selectionPoint = insertionPoint = std::min(selectionPoint,insertionPoint);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
new_ip = select ? selectionPoint : insertionPoint;
|
new_ip = select ? selectionPoint : insertionPoint;
|
||||||
if(new_ip == 0) break;
|
if(new_ip == 0) break;
|
||||||
if(word) {
|
if(key.k == key_word_left) {
|
||||||
new_ip--;
|
new_ip--;
|
||||||
while(new_ip > 0 && contents[new_ip - 1] != ' ')
|
while(new_ip > 0 && contents[new_ip - 1] != ' ')
|
||||||
new_ip--;
|
new_ip--;
|
||||||
@@ -218,14 +306,14 @@ void cTextField::handleInput(cKey key) {
|
|||||||
(select ? selectionPoint : insertionPoint) = new_ip;
|
(select ? selectionPoint : insertionPoint) = new_ip;
|
||||||
if(!select) selectionPoint = insertionPoint;
|
if(!select) selectionPoint = insertionPoint;
|
||||||
break;
|
break;
|
||||||
case key_right:
|
case key_right: case key_word_right:
|
||||||
if(haveSelection && !select) {
|
if(haveSelection && !select) {
|
||||||
selectionPoint = insertionPoint = std::max(selectionPoint,insertionPoint);
|
selectionPoint = insertionPoint = std::max(selectionPoint,insertionPoint);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
new_ip = select ? selectionPoint : insertionPoint;
|
new_ip = select ? selectionPoint : insertionPoint;
|
||||||
if(new_ip == contents.length()) break;
|
if(new_ip == contents.length()) break;
|
||||||
if(word) {
|
if(key.k == key_word_right) {
|
||||||
new_ip++;
|
new_ip++;
|
||||||
while(new_ip < contents.length() && contents[new_ip + 1] != ' ')
|
while(new_ip < contents.length() && contents[new_ip + 1] != ' ')
|
||||||
new_ip++;
|
new_ip++;
|
||||||
@@ -236,18 +324,15 @@ void cTextField::handleInput(cKey key) {
|
|||||||
case key_up:
|
case key_up:
|
||||||
case key_down:
|
case key_down:
|
||||||
break;
|
break;
|
||||||
case key_enter:
|
case key_bsp: case key_word_bsp:
|
||||||
key.spec = false;
|
|
||||||
key.c = '\n';
|
|
||||||
handleInput(key);
|
|
||||||
break;
|
|
||||||
case key_bsp:
|
|
||||||
if(haveSelection) {
|
if(haveSelection) {
|
||||||
|
if(key.k == key_word_bsp)
|
||||||
|
handleInput({true, key_word_right, mod_shift});
|
||||||
auto begin = contents.begin() + std::min(selectionPoint, insertionPoint);
|
auto begin = contents.begin() + std::min(selectionPoint, insertionPoint);
|
||||||
auto end = contents.begin() + std::max(selectionPoint, insertionPoint);
|
auto end = contents.begin() + std::max(selectionPoint, insertionPoint);
|
||||||
auto result = contents.erase(begin, end);
|
auto result = contents.erase(begin, end);
|
||||||
selectionPoint = insertionPoint = result - contents.begin();
|
selectionPoint = insertionPoint = result - contents.begin();
|
||||||
} else if(word) {
|
} else if(key.k == key_word_bsp) {
|
||||||
cKey selectKey = key;
|
cKey selectKey = key;
|
||||||
selectKey.k = key_left;
|
selectKey.k = key_left;
|
||||||
handleInput(selectKey);
|
handleInput(selectKey);
|
||||||
@@ -259,13 +344,15 @@ void cTextField::handleInput(cKey key) {
|
|||||||
selectionPoint = --insertionPoint;
|
selectionPoint = --insertionPoint;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case key_del:
|
case key_del: case key_word_del:
|
||||||
if(haveSelection) {
|
if(haveSelection) {
|
||||||
|
if(key.k == key_word_del)
|
||||||
|
handleInput({true, key_word_left, mod_shift});
|
||||||
auto begin = contents.begin() + std::min(selectionPoint, insertionPoint);
|
auto begin = contents.begin() + std::min(selectionPoint, insertionPoint);
|
||||||
auto end = contents.begin() + std::max(selectionPoint, insertionPoint);
|
auto end = contents.begin() + std::max(selectionPoint, insertionPoint);
|
||||||
auto result = contents.erase(begin, end);
|
auto result = contents.erase(begin, end);
|
||||||
selectionPoint = insertionPoint = result - contents.begin();
|
selectionPoint = insertionPoint = result - contents.begin();
|
||||||
} else if(word) {
|
} else if(key.k == key_word_del) {
|
||||||
cKey selectKey = key;
|
cKey selectKey = key;
|
||||||
selectKey.k = key_left;
|
selectKey.k = key_left;
|
||||||
handleInput(selectKey);
|
handleInput(selectKey);
|
||||||
@@ -276,21 +363,52 @@ void cTextField::handleInput(cKey key) {
|
|||||||
contents.erase(insertionPoint,1);
|
contents.erase(insertionPoint,1);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case key_top:
|
||||||
|
if(select) selectionPoint = 0;
|
||||||
|
else insertionPoint = 0;
|
||||||
|
break;
|
||||||
|
case key_bottom:
|
||||||
|
if(select) selectionPoint = contents.length();
|
||||||
|
else insertionPoint = contents.length();
|
||||||
|
break;
|
||||||
case key_end:
|
case key_end:
|
||||||
case key_home:
|
case key_home:
|
||||||
case key_pgup:
|
case key_pgup:
|
||||||
case key_pgdn:
|
case key_pgdn:
|
||||||
|
break;
|
||||||
case key_copy:
|
case key_copy:
|
||||||
case key_cut:
|
case key_cut:
|
||||||
|
set_clipboard(contents.substr(std::min(insertionPoint,selectionPoint), abs(insertionPoint - selectionPoint)));
|
||||||
|
if(key.k == key_cut) {
|
||||||
|
cKey deleteKey = key;
|
||||||
|
deleteKey.k = key_bsp;
|
||||||
|
handleInput(deleteKey);
|
||||||
|
contents = getText();
|
||||||
|
}
|
||||||
|
break;
|
||||||
case key_paste:
|
case key_paste:
|
||||||
|
if(!get_clipboard().empty()) {
|
||||||
|
cKey deleteKey = {true, key_bsp, mod_none};
|
||||||
|
handleInput(deleteKey);
|
||||||
|
contents = getText();
|
||||||
|
std::string toInsert = get_clipboard();
|
||||||
|
contents.insert(insertionPoint, toInsert);
|
||||||
|
insertionPoint += toInsert.length();
|
||||||
|
selectionPoint = insertionPoint;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case key_undo:
|
||||||
|
case key_redo:
|
||||||
break;
|
break;
|
||||||
case key_selectall:
|
case key_selectall:
|
||||||
selectionPoint = 0;
|
selectionPoint = 0;
|
||||||
insertionPoint = contents.length();
|
insertionPoint = contents.length();
|
||||||
break;
|
break;
|
||||||
|
// These keys have no function in this context.
|
||||||
case key_esc:
|
case key_esc:
|
||||||
case key_tab:
|
case key_tab:
|
||||||
case key_help:
|
case key_help:
|
||||||
|
case key_insert:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
setText(contents);
|
setText(contents);
|
||||||
|
@@ -58,6 +58,7 @@ void Handle_Update();
|
|||||||
void handle_menu_choice(long choice);
|
void handle_menu_choice(long choice);
|
||||||
void handle_apple_menu(int item_hit);
|
void handle_apple_menu(int item_hit);
|
||||||
void handle_file_menu(int item_hit);
|
void handle_file_menu(int item_hit);
|
||||||
|
void handle_edit_menu(int item_hit);
|
||||||
void handle_scenario_menu(int item_hit);
|
void handle_scenario_menu(int item_hit);
|
||||||
void handle_town_menu(int item_hit);
|
void handle_town_menu(int item_hit);
|
||||||
void handle_outdoor_menu(int item_hit);
|
void handle_outdoor_menu(int item_hit);
|
||||||
@@ -269,6 +270,25 @@ void handle_file_menu(int item_hit) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void handle_edit_menu(int item_hit) {
|
||||||
|
// Currently, this merely passes appropriate input to the frontmost dialog.
|
||||||
|
// TODO: Handle edit menu operations when no dialog is onscreen.
|
||||||
|
cKey key = {true};
|
||||||
|
switch(item_hit) {
|
||||||
|
case 1: key.k = key_undo; break;
|
||||||
|
case 2: key.k = key_redo; break;
|
||||||
|
case 4: key.k = key_cut; break;
|
||||||
|
case 5: key.k = key_copy; break;
|
||||||
|
case 6: key.k = key_paste; break;
|
||||||
|
case 7: key.k = key_del; break;
|
||||||
|
case 8: key.k = key_selectall; break;
|
||||||
|
}
|
||||||
|
if(!cDialog::sendInput(key)) {
|
||||||
|
// Handle non-dialog edit operations here.
|
||||||
|
switch(key.k) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void handle_scenario_menu(int item_hit) {
|
void handle_scenario_menu(int item_hit) {
|
||||||
short i;
|
short i;
|
||||||
fs::path file;
|
fs::path file;
|
||||||
|
@@ -318,7 +318,6 @@
|
|||||||
<reference key="NSMixedImage" ref="909111550"/>
|
<reference key="NSMixedImage" ref="909111550"/>
|
||||||
</object>
|
</object>
|
||||||
</array>
|
</array>
|
||||||
<bool key="NSNoAutoenable">YES</bool>
|
|
||||||
</object>
|
</object>
|
||||||
</object>
|
</object>
|
||||||
<object class="NSMenuItem" id="741259600">
|
<object class="NSMenuItem" id="741259600">
|
||||||
@@ -1069,62 +1068,6 @@
|
|||||||
</object>
|
</object>
|
||||||
<int key="connectionID">370</int>
|
<int key="connectionID">370</int>
|
||||||
</object>
|
</object>
|
||||||
<object class="IBConnectionRecord">
|
|
||||||
<object class="IBActionConnection" key="connection">
|
|
||||||
<string key="label">cut:</string>
|
|
||||||
<reference key="source" ref="1014"/>
|
|
||||||
<reference key="destination" ref="1002243476"/>
|
|
||||||
</object>
|
|
||||||
<int key="connectionID">738</int>
|
|
||||||
</object>
|
|
||||||
<object class="IBConnectionRecord">
|
|
||||||
<object class="IBActionConnection" key="connection">
|
|
||||||
<string key="label">paste:</string>
|
|
||||||
<reference key="source" ref="1014"/>
|
|
||||||
<reference key="destination" ref="582640805"/>
|
|
||||||
</object>
|
|
||||||
<int key="connectionID">739</int>
|
|
||||||
</object>
|
|
||||||
<object class="IBConnectionRecord">
|
|
||||||
<object class="IBActionConnection" key="connection">
|
|
||||||
<string key="label">redo:</string>
|
|
||||||
<reference key="source" ref="1014"/>
|
|
||||||
<reference key="destination" ref="45032972"/>
|
|
||||||
</object>
|
|
||||||
<int key="connectionID">742</int>
|
|
||||||
</object>
|
|
||||||
<object class="IBConnectionRecord">
|
|
||||||
<object class="IBActionConnection" key="connection">
|
|
||||||
<string key="label">undo:</string>
|
|
||||||
<reference key="source" ref="1014"/>
|
|
||||||
<reference key="destination" ref="155194070"/>
|
|
||||||
</object>
|
|
||||||
<int key="connectionID">746</int>
|
|
||||||
</object>
|
|
||||||
<object class="IBConnectionRecord">
|
|
||||||
<object class="IBActionConnection" key="connection">
|
|
||||||
<string key="label">copy:</string>
|
|
||||||
<reference key="source" ref="1014"/>
|
|
||||||
<reference key="destination" ref="619324772"/>
|
|
||||||
</object>
|
|
||||||
<int key="connectionID">752</int>
|
|
||||||
</object>
|
|
||||||
<object class="IBConnectionRecord">
|
|
||||||
<object class="IBActionConnection" key="connection">
|
|
||||||
<string key="label">delete:</string>
|
|
||||||
<reference key="source" ref="1014"/>
|
|
||||||
<reference key="destination" ref="483607956"/>
|
|
||||||
</object>
|
|
||||||
<int key="connectionID">753</int>
|
|
||||||
</object>
|
|
||||||
<object class="IBConnectionRecord">
|
|
||||||
<object class="IBActionConnection" key="connection">
|
|
||||||
<string key="label">selectAll:</string>
|
|
||||||
<reference key="source" ref="1014"/>
|
|
||||||
<reference key="destination" ref="280953604"/>
|
|
||||||
</object>
|
|
||||||
<int key="connectionID">755</int>
|
|
||||||
</object>
|
|
||||||
</array>
|
</array>
|
||||||
<object class="IBMutableOrderedSet" key="objectRecords">
|
<object class="IBMutableOrderedSet" key="objectRecords">
|
||||||
<array key="orderedObjects">
|
<array key="orderedObjects">
|
||||||
|
@@ -169,6 +169,7 @@ void update_item_menu() {
|
|||||||
|
|
||||||
void handle_apple_menu(int item_hit);
|
void handle_apple_menu(int item_hit);
|
||||||
void handle_file_menu(int item_hit);
|
void handle_file_menu(int item_hit);
|
||||||
|
void handle_edit_menu(int item_hit);
|
||||||
void handle_scenario_menu(int item_hit);
|
void handle_scenario_menu(int item_hit);
|
||||||
void handle_town_menu(int item_hit);
|
void handle_town_menu(int item_hit);
|
||||||
void handle_outdoor_menu(int item_hit);
|
void handle_outdoor_menu(int item_hit);
|
||||||
@@ -186,8 +187,11 @@ void handle_monst_menu(int item_hit);
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement edit menu (much work to be done here!)
|
// TODO: Implement edit menu (much work to be done here!)
|
||||||
|
// TODO: Fix edit menu being disabled while a modal dialog is onscreen.
|
||||||
|
// This means setting autoenable to false for the Edit menu and finding a
|
||||||
|
// way to make the menuitems work instead of just doing nothing.
|
||||||
-(void) editMenu:(id) sender {
|
-(void) editMenu:(id) sender {
|
||||||
(void) sender; // Suppress "unused parameter" warning
|
handle_edit_menu([[sender representedObject] intValue]);
|
||||||
}
|
}
|
||||||
|
|
||||||
-(void) scenMenu:(id) sender {
|
-(void) scenMenu:(id) sender {
|
||||||
|
@@ -325,12 +325,14 @@ void win_draw_string(sf::RenderTarget& dest_window,rectangle dest_rect,std::stri
|
|||||||
if(mode == eTextMode::WRAP) {
|
if(mode == eTextMode::WRAP) {
|
||||||
moveTo = location(dest_rect.left + 1 + adjust_x, dest_rect.top + 1 + adjust_y + 9);
|
moveTo = location(dest_rect.left + 1 + adjust_x, dest_rect.top + 1 + adjust_y + 9);
|
||||||
for(i = 0; text_len(i) != text_len(i + 1) && i < str_len;i++) {
|
for(i = 0; text_len(i) != text_len(i + 1) && i < str_len;i++) {
|
||||||
|
int iLen = text_len(i), lineLen = text_len(last_line_break);
|
||||||
if(((text_len(i) - text_len(last_line_break) > (dest_rect.width() - 6))
|
if(((text_len(i) - text_len(last_line_break) > (dest_rect.width() - 6))
|
||||||
&& (last_word_break > last_line_break)) || (str[i] == '|')) {
|
&& (last_word_break >= last_line_break)) || (str[i] == '|')) {
|
||||||
if(str[i] == '|') {
|
if(str[i] == '|') {
|
||||||
if(!options.showBreaks) str[i] = ' ';
|
if(!options.showBreaks) str[i] = ' ';
|
||||||
last_word_break = i + 1;
|
last_word_break = i + 1;
|
||||||
}
|
} else if(last_line_break == last_word_break)
|
||||||
|
last_word_break = i;
|
||||||
push_snippets(last_line_break, last_word_break, options, iHilite, str, moveTo);
|
push_snippets(last_line_break, last_word_break, options, iHilite, str, moveTo);
|
||||||
moveTo.y += line_height;
|
moveTo.y += line_height;
|
||||||
last_line_break = last_word_break;
|
last_line_break = last_word_break;
|
||||||
|
@@ -26,6 +26,12 @@ fs::path nav_put_party(fs::path def = "");
|
|||||||
fs::path nav_get_scenario();
|
fs::path nav_get_scenario();
|
||||||
fs::path nav_put_scenario(fs::path def = "");
|
fs::path nav_put_scenario(fs::path def = "");
|
||||||
|
|
||||||
|
// Deal with text snippets in the clipboard.
|
||||||
|
// If the clipboard contains something other than text,
|
||||||
|
// get_clipboard should return an empty string.
|
||||||
|
void set_clipboard(std::string text);
|
||||||
|
std::string get_clipboard();
|
||||||
|
|
||||||
void beep();
|
void beep();
|
||||||
|
|
||||||
class ModalSession {
|
class ModalSession {
|
||||||
|
@@ -67,6 +67,22 @@ char keyToChar(sf::Keyboard::Key key, bool isShift) {
|
|||||||
case kb::Return: return '\n';
|
case kb::Return: return '\n';
|
||||||
case kb::BackSpace: return '\b';
|
case kb::BackSpace: return '\b';
|
||||||
case kb::Delete: return '\x7f';
|
case kb::Delete: return '\x7f';
|
||||||
|
case kb::Numpad0: return '0';
|
||||||
|
case kb::Numpad1: return '1';
|
||||||
|
case kb::Numpad2: return '2';
|
||||||
|
case kb::Numpad3: return '3';
|
||||||
|
case kb::Numpad4: return '4';
|
||||||
|
case kb::Numpad5: return '5';
|
||||||
|
case kb::Numpad6: return '6';
|
||||||
|
case kb::Numpad7: return '7';
|
||||||
|
case kb::Numpad8: return '8';
|
||||||
|
case kb::Numpad9: return '9';
|
||||||
|
// TODO: Should have Equal here, but SFML doesn't distinguish between normal and keybad equal :/
|
||||||
|
// Ditto for the decimal point.
|
||||||
|
case kb::Divide: return '/';
|
||||||
|
case kb::Multiply: return '*';
|
||||||
|
case kb::Subtract: return '-';
|
||||||
|
case kb::Add: return '+';
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
@@ -106,6 +122,25 @@ void ModalSession::pumpEvents() {
|
|||||||
[[NSApplication sharedApplication] runModalSession: nsHandle];
|
[[NSApplication sharedApplication] runModalSession: nsHandle];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_clipboard(std::string text) {
|
||||||
|
NSString* str = [[NSString stringWithUTF8String: text.c_str()] stringByReplacingOccurrencesOfString: @"|" withString: @"\n"];
|
||||||
|
NSArray* contents = [NSArray arrayWithObject: str];
|
||||||
|
NSPasteboard* pb = [NSPasteboard generalPasteboard];
|
||||||
|
[pb clearContents];
|
||||||
|
[pb writeObjects: contents];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_clipboard() {
|
||||||
|
NSPasteboard* pb = [NSPasteboard generalPasteboard];
|
||||||
|
NSDictionary* options = [NSDictionary dictionary];
|
||||||
|
NSArray* types = [NSArray arrayWithObject: [NSString class]];
|
||||||
|
if(![pb canReadObjectForClasses: types options: options])
|
||||||
|
return "";
|
||||||
|
NSArray* contents = [pb readObjectsForClasses: types options: options];
|
||||||
|
NSString* str = [contents objectAtIndex: 0];
|
||||||
|
return [[str stringByReplacingOccurrencesOfString: @"\n" withString: @"|"] cStringUsingEncoding: NSUTF8StringEncoding];
|
||||||
|
}
|
||||||
|
|
||||||
void beep() {
|
void beep() {
|
||||||
NSBeep();
|
NSBeep();
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user