ResidualVM logo ResidualVM website - Forums - Contact us BuildBot - Doxygen - Wiki curved edge

remap-dialog.cpp

Go to the documentation of this file.
00001 /* ScummVM - Graphic Adventure Engine
00002  *
00003  * ScummVM is the legal property of its developers, whose names
00004  * are too numerous to list here. Please refer to the COPYRIGHT
00005  * file distributed with this source distribution.
00006  *
00007  * This program is free software; you can redistribute it and/or
00008  * modify it under the terms of the GNU General Public License
00009  * as published by the Free Software Foundation; either version 2
00010  * of the License, or (at your option) any later version.
00011  *
00012  * This program is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  * GNU General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU General Public License
00018  * along with this program; if not, write to the Free Software
00019  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00020  *
00021  */
00022 
00023 #include "backends/keymapper/remap-dialog.h"
00024 
00025 #ifdef ENABLE_KEYMAPPER
00026 
00027 #include "common/system.h"
00028 #include "gui/gui-manager.h"
00029 #include "gui/widgets/popup.h"
00030 #include "gui/widgets/scrollbar.h"
00031 #include "gui/ThemeEval.h"
00032 #include "common/translation.h"
00033 
00034 namespace Common {
00035 
00036 enum {
00037     kRemapCmd = 'REMP',
00038     kClearCmd = 'CLER',
00039     kCloseCmd = 'CLOS'
00040 };
00041 
00042 RemapDialog::RemapDialog()
00043     : Dialog("KeyMapper"), _keymapTable(0), _topAction(0), _remapTimeout(0), _topKeymapIsGui(false) {
00044 
00045     _keymapper = g_system->getEventManager()->getKeymapper();
00046     assert(_keymapper);
00047 
00048     _kmPopUpDesc = new GUI::StaticTextWidget(this, "KeyMapper.PopupDesc", _("Keymap:"));
00049     _kmPopUp = new GUI::PopUpWidget(this, "KeyMapper.Popup");
00050 
00051     _scrollBar = new GUI::ScrollBarWidget(this, 0, 0, 0, 0);
00052 
00053     new GUI::ButtonWidget(this, "KeyMapper.Close", _("Close"), 0, kCloseCmd);
00054 }
00055 
00056 RemapDialog::~RemapDialog() {
00057     free(_keymapTable);
00058 }
00059 
00060 void RemapDialog::open() {
00061     const Stack<Keymapper::MapRecord> &activeKeymaps = _keymapper->getActiveStack();
00062 
00063     if (activeKeymaps.size() > 0) {
00064         if (activeKeymaps.top().keymap->getName() == Common::kGuiKeymapName)
00065             _topKeymapIsGui = true;
00066         // Add the entry for the "effective" special view. See RemapDialog::loadKeymap()
00067         _kmPopUp->appendEntry(activeKeymaps.top().keymap->getName() + _(" (Effective)"));
00068     }
00069 
00070     Keymapper::Domain *_globalKeymaps = &_keymapper->getGlobalDomain();
00071     Keymapper::Domain *_gameKeymaps = 0;
00072 
00073     int keymapCount = 0;
00074 
00075     if (_globalKeymaps->empty())
00076         _globalKeymaps = 0;
00077     else
00078         keymapCount += _globalKeymaps->size();
00079 
00080     if (ConfMan.getActiveDomain() != 0) {
00081         _gameKeymaps = &_keymapper->getGameDomain();
00082 
00083         if (_gameKeymaps->empty())
00084             _gameKeymaps = 0;
00085         else
00086             keymapCount += _gameKeymaps->size();
00087     }
00088 
00089     if (activeKeymaps.size() > 1) {
00090         keymapCount += activeKeymaps.size() - 1;
00091     }
00092 
00093     debug(3, "RemapDialog::open keymaps: %d", keymapCount);
00094 
00095     _keymapTable = (Keymap **)malloc(sizeof(Keymap *) * keymapCount);
00096 
00097     Keymapper::Domain::iterator it;
00098     uint32 idx = 0;
00099 
00100     if (activeKeymaps.size() > 1) {
00101         int topIndex = activeKeymaps.size() - 1;
00102         bool active = activeKeymaps[topIndex].transparent;
00103         for (int i = topIndex - 1; i >= 0; --i) {
00104             Keymapper::MapRecord mr = activeKeymaps[i];
00105             // Add an entry for each keymap in the stack after the top keymap. Mark it Active if it is
00106             // reachable or Blocked if an opaque keymap is on top of it thus blocking access to it.
00107             _kmPopUp->appendEntry(mr.keymap->getName() + (active ? _(" (Active)") : _(" (Blocked)")), idx);
00108             _keymapTable[idx++] = mr.keymap;
00109             active &= mr.transparent;
00110         }
00111     }
00112 
00113     _kmPopUp->appendEntry("");
00114 
00115     // Now add entries for all known keymaps. Note that there will be duplicates with the stack entries.
00116 
00117     if (_globalKeymaps) {
00118         for (it = _globalKeymaps->begin(); it != _globalKeymaps->end(); ++it) {
00119             // "global" means its keybindings apply to all games; saved in a global conf domain
00120             _kmPopUp->appendEntry(it->_value->getName() + _(" (Global)"), idx);
00121             _keymapTable[idx++] = it->_value;
00122         }
00123     }
00124 
00125     if (_gameKeymaps) {
00126         for (it = _gameKeymaps->begin(); it != _gameKeymaps->end(); ++it) {
00127             // "game" means its keybindings are saved per-target
00128             _kmPopUp->appendEntry(it->_value->getName() + _(" (Game)"), idx);
00129             _keymapTable[idx++] = it->_value;
00130         }
00131     }
00132 
00133     _changes = false;
00134 
00135     Dialog::open();
00136 
00137     _kmPopUp->setSelected(0);
00138     loadKeymap();
00139 }
00140 
00141 void RemapDialog::close() {
00142     _kmPopUp->clearEntries();
00143 
00144     free(_keymapTable);
00145     _keymapTable = 0;
00146 
00147     if (_changes)
00148         ConfMan.flushToDisk();
00149 
00150     Dialog::close();
00151 }
00152 
00153 void RemapDialog::reflowLayout() {
00154     Dialog::reflowLayout();
00155 
00156     int buttonHeight = g_gui.xmlEval()->getVar("Globals.Button.Height", 0);
00157     int scrollbarWidth = g_gui.xmlEval()->getVar("Globals.Scrollbar.Width", 0);
00158 
00159     int16 areaX, areaY;
00160     uint16 areaW, areaH;
00161     g_gui.xmlEval()->getWidgetData((const String&)String("KeyMapper.KeymapArea"), areaX, areaY, areaW, areaH);
00162 
00163     int spacing = g_gui.xmlEval()->getVar("Globals.KeyMapper.Spacing");
00164     int keyButtonWidth = g_gui.xmlEval()->getVar("Globals.KeyMapper.ButtonWidth");
00165     int clearButtonWidth = g_gui.xmlEval()->getVar("Globals.Line.Height");
00166     int clearButtonHeight = g_gui.xmlEval()->getVar("Globals.Line.Height");
00167 
00168     int colWidth = areaW - scrollbarWidth;
00169     int labelWidth =  colWidth - (keyButtonWidth + spacing + clearButtonWidth + spacing);
00170 
00171     _rowCount = (areaH + spacing) / (buttonHeight + spacing);
00172     debug(7, "rowCount = %d" , _rowCount);
00173     if (colWidth <= 0  || _rowCount <= 0)
00174         error("Remap dialog too small to display any keymaps");
00175 
00176     _scrollBar->resize(areaX + areaW - scrollbarWidth, areaY, scrollbarWidth, areaH);
00177     _scrollBar->_entriesPerPage = _rowCount;
00178     _scrollBar->_numEntries = 1;
00179     _scrollBar->recalc();
00180 
00181     uint textYOff = (buttonHeight - kLineHeight) / 2;
00182     uint clearButtonYOff = (buttonHeight - clearButtonHeight) / 2;
00183     uint oldSize = _keymapWidgets.size();
00184     uint newSize = _rowCount;
00185 
00186     _keymapWidgets.reserve(newSize);
00187 
00188     for (uint i = 0; i < newSize; i++) {
00189         ActionWidgets widg;
00190 
00191         if (i >= _keymapWidgets.size()) {
00192             widg.actionText =
00193                 new GUI::StaticTextWidget(this, 0, 0, 0, 0, "", Graphics::kTextAlignLeft);
00194             widg.keyButton =
00195                 new GUI::ButtonWidget(this, 0, 0, 0, 0, "", 0, kRemapCmd + i);
00196             widg.clearButton = addClearButton(this, "", kClearCmd + i, 0, 0, clearButtonWidth, clearButtonHeight);
00197             _keymapWidgets.push_back(widg);
00198         } else {
00199             widg = _keymapWidgets[i];
00200         }
00201 
00202         uint x = areaX;
00203         uint y = areaY + (i) * (buttonHeight + spacing);
00204 
00205         widg.keyButton->resize(x, y, keyButtonWidth, buttonHeight);
00206         widg.clearButton->resize(x + keyButtonWidth + spacing, y + clearButtonYOff, clearButtonWidth, clearButtonHeight);
00207         widg.actionText->resize(x + keyButtonWidth + spacing + clearButtonWidth + spacing, y + textYOff, labelWidth, kLineHeight);
00208 
00209     }
00210     while (oldSize > newSize) {
00211         ActionWidgets widg = _keymapWidgets.remove_at(--oldSize);
00212 
00213         removeWidget(widg.actionText);
00214         delete widg.actionText;
00215 
00216         removeWidget(widg.keyButton);
00217         delete widg.keyButton;
00218 
00219         removeWidget(widg.clearButton);
00220         delete widg.clearButton;
00221     }
00222 }
00223 
00224 void RemapDialog::handleCommand(GUI::CommandSender *sender, uint32 cmd, uint32 data) {
00225     debug(3, "RemapDialog::handleCommand %u %u", cmd, data);
00226 
00227     if (cmd >= kRemapCmd && cmd < kRemapCmd + _keymapWidgets.size()) {
00228         startRemapping(cmd - kRemapCmd);
00229     } else if (cmd >= kClearCmd && cmd < kClearCmd + _keymapWidgets.size()) {
00230         clearMapping(cmd - kClearCmd);
00231     } else if (cmd == GUI::kPopUpItemSelectedCmd) {
00232         loadKeymap();
00233     } else if (cmd == GUI::kSetPositionCmd) {
00234         refreshKeymap();
00235     } else if (cmd == kCloseCmd) {
00236         close();
00237     } else {
00238         GUI::Dialog::handleCommand(sender, cmd, data);
00239     }
00240 }
00241 
00242 void RemapDialog::clearMapping(uint i) {
00243     if (_topAction + i >= _currentActions.size())
00244         return;
00245 
00246     debug(3, "clear the mapping %u", i);
00247     Action *activeRemapAction = _currentActions[_topAction + i].action;
00248     activeRemapAction->mapInput(0);
00249     activeRemapAction->getParent()->saveMappings();
00250     _changes = true;
00251 
00252     // force refresh
00253     stopRemapping(true);
00254     refreshKeymap();
00255 }
00256 
00257 void RemapDialog::startRemapping(uint i) {
00258     if (_topAction + i >= _currentActions.size())
00259         return;
00260 
00261     if (_keymapper->isRemapping()) {
00262         // Handle a second click on the button as a stop to remapping
00263         stopRemapping(true);
00264         return;
00265     }
00266 
00267     _remapTimeout = g_system->getMillis() + kRemapTimeoutDelay;
00268     Action *activeRemapAction = _currentActions[_topAction + i].action;
00269     _keymapWidgets[i].keyButton->setLabel("...");
00270     _keymapWidgets[i].keyButton->markAsDirty();
00271     _keymapper->startRemappingMode(activeRemapAction);
00272 
00273 }
00274 
00275 void RemapDialog::stopRemapping(bool force) {
00276     _topAction = -1;
00277 
00278     refreshKeymap();
00279 
00280     if (force)
00281         _keymapper->stopRemappingMode();
00282 }
00283 
00284 void RemapDialog::handleKeyDown(Common::KeyState state) {
00285     if (_keymapper->isRemapping())
00286         return;
00287 
00288     GUI::Dialog::handleKeyDown(state);
00289 }
00290 
00291 void RemapDialog::handleKeyUp(Common::KeyState state) {
00292     if (_keymapper->isRemapping())
00293         return;
00294 
00295     GUI::Dialog::handleKeyUp(state);
00296 }
00297 
00298 void RemapDialog::handleOtherEvent(Event ev) {
00299     if (ev.type == EVENT_GUI_REMAP_COMPLETE_ACTION) {
00300         // _keymapper is telling us that something changed
00301         _changes = true;
00302         stopRemapping();
00303     } else {
00304         GUI::Dialog::handleOtherEvent(ev);
00305     }
00306 }
00307 
00308 void RemapDialog::handleMouseDown(int x, int y, int button, int clickCount) {
00309     if (_keymapper->isRemapping())
00310         stopRemapping();
00311     else
00312         Dialog::handleMouseDown(x, y, button, clickCount);
00313 }
00314 
00315 void RemapDialog::handleTickle() {
00316     if (_keymapper->isRemapping() && g_system->getMillis() > _remapTimeout)
00317         stopRemapping(true);
00318     Dialog::handleTickle();
00319 }
00320 
00321 void RemapDialog::loadKeymap() {
00322     _currentActions.clear();
00323     const Stack<Keymapper::MapRecord> &activeKeymaps = _keymapper->getActiveStack();
00324 
00325     debug(3, "RemapDialog::loadKeymap active keymaps: %u", activeKeymaps.size());
00326 
00327     if (!activeKeymaps.empty() && _kmPopUp->getSelected() == 0) {
00328         // This is the "effective" view which shows all effective actions:
00329         // - all of the topmost keymap action
00330         // - all mapped actions that are reachable
00331 
00332         List<const HardwareInput *> freeInputs(_keymapper->getHardwareInputs());
00333 
00334         int topIndex = activeKeymaps.size() - 1;
00335 
00336         // This is a WORKAROUND for changing the popup list selected item and changing it back
00337         // to the top entry. Upon changing it back, the top keymap is always "gui".
00338         if (!_topKeymapIsGui && activeKeymaps[topIndex].keymap->getName() == kGuiKeymapName)
00339             --topIndex;
00340 
00341         // add most active keymap's keys
00342         Keymapper::MapRecord top = activeKeymaps[topIndex];
00343         List<Action *>::iterator actIt;
00344         debug(3, "RemapDialog::loadKeymap top keymap: %s", top.keymap->getName().c_str());
00345         for (actIt = top.keymap->getActions().begin(); actIt != top.keymap->getActions().end(); ++actIt) {
00346             Action *act = *actIt;
00347             ActionInfo info = {act, false, act->description};
00348 
00349             _currentActions.push_back(info);
00350 
00351             if (act->getMappedInput())
00352                 freeInputs.remove(act->getMappedInput());
00353         }
00354 
00355         // loop through remaining finding mappings for unmapped keys
00356         if (top.transparent && topIndex >= 0) {
00357             for (int i = topIndex - 1; i >= 0; --i) {
00358                 Keymapper::MapRecord mr = activeKeymaps[i];
00359                 debug(3, "RemapDialog::loadKeymap keymap: %s", mr.keymap->getName().c_str());
00360                 List<const HardwareInput *>::iterator inputIt = freeInputs.begin();
00361                 const HardwareInput *input = *inputIt;
00362                 while (inputIt != freeInputs.end()) {
00363 
00364                     Action *act = 0;
00365                     if (input->type == kHardwareInputTypeKeyboard)
00366                         act = mr.keymap->getMappedAction(input->key);
00367                     else if (input->type == kHardwareInputTypeGeneric)
00368                         act = mr.keymap->getMappedAction(input->inputCode);
00369 
00370                     if (act) {
00371                         ActionInfo info = {act, true, act->description + " (" + mr.keymap->getName() + ")"};
00372                         _currentActions.push_back(info);
00373                         freeInputs.erase(inputIt);
00374                     } else {
00375                         ++inputIt;
00376                     }
00377                 }
00378 
00379                 if (mr.transparent == false || freeInputs.empty())
00380                     break;
00381             }
00382         }
00383 
00384     } else if (_kmPopUp->getSelected() != -1) {
00385         // This is the regular view of a keymap that isn't the topmost one.
00386         // It shows all of that keymap's actions
00387 
00388         Keymap *km = _keymapTable[_kmPopUp->getSelectedTag()];
00389 
00390         List<Action *>::iterator it;
00391 
00392         for (it = km->getActions().begin(); it != km->getActions().end(); ++it) {
00393             ActionInfo info = {*it, false, (*it)->description};
00394 
00395             _currentActions.push_back(info);
00396         }
00397     }
00398 
00399     // refresh scroll bar
00400     _scrollBar->_currentPos = 0;
00401     _scrollBar->_numEntries = _currentActions.size();
00402     _scrollBar->recalc();
00403 
00404     // force refresh
00405     _topAction = -1;
00406     refreshKeymap();
00407 }
00408 
00409 void RemapDialog::refreshKeymap() {
00410     int newTopAction = _scrollBar->_currentPos;
00411 
00412     if (newTopAction == _topAction)
00413         return;
00414 
00415     _topAction = newTopAction;
00416 
00417     //_container->markAsDirty();
00418     _scrollBar->markAsDirty();
00419 
00420     uint actionI = _topAction;
00421 
00422     for (uint widgetI = 0; widgetI < _keymapWidgets.size(); widgetI++) {
00423         ActionWidgets& widg = _keymapWidgets[widgetI];
00424 
00425         if (actionI < _currentActions.size()) {
00426             debug(8, "RemapDialog::refreshKeymap actionI=%u", actionI);
00427             ActionInfo&    info = _currentActions[actionI];
00428 
00429             widg.actionText->setLabel(info.description);
00430             widg.actionText->setEnabled(!info.inherited);
00431 
00432             const HardwareInput *mappedInput = info.action->getMappedInput();
00433 
00434             if (mappedInput)
00435                 widg.keyButton->setLabel(mappedInput->description);
00436             else
00437                 widg.keyButton->setLabel("-");
00438 
00439             widg.actionText->setVisible(true);
00440             widg.keyButton->setVisible(true);
00441             widg.clearButton->setVisible(true);
00442 
00443             actionI++;
00444         } else {
00445             widg.actionText->setVisible(false);
00446             widg.keyButton->setVisible(false);
00447             widg.clearButton->setVisible(false);
00448         }
00449         //widg.actionText->markAsDirty();
00450         //widg.keyButton->markAsDirty();
00451     }
00452     // need to redraw entire Dialog so that invisible
00453     // widgets disappear
00454     g_gui.scheduleTopDialogRedraw();
00455 }
00456 
00457 
00458 } // End of namespace Common
00459 
00460 #endif // #ifdef ENABLE_KEYMAPPER


Generated on Sat May 18 2019 05:01:17 for ResidualVM by doxygen 1.7.1
curved edge   curved edge