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:
2014-12-19 03:44:18 -05:00
parent 8e26881331
commit 728c294d0e
10 changed files with 257 additions and 131 deletions

View File

@@ -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();

View File

@@ -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;

View File

@@ -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;
}; };

View File

@@ -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);

View File

@@ -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;

View 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">

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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();
} }