From 0755a1c9dcbcdafacc637339c36239547b19a7a2 Mon Sep 17 00:00:00 2001 From: Bo Peng Date: Sat, 20 Oct 2007 20:50:56 +0000 Subject: [PATCH] PrefShortcuts: Roughly finish the shortcut configuration panel git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@21090 a592a061-630c-0410-9148-cb99ea01b6c8 --- src/LyX.cpp | 2 + src/frontends/qt4/GuiPrefs.cpp | 345 ++++++++++++++++++------ src/frontends/qt4/GuiPrefs.h | 33 ++- src/frontends/qt4/ui/PrefShortcutsUi.ui | 7 - src/frontends/qt4/ui/ShortcutUi.ui | 6 +- 5 files changed, 293 insertions(+), 100 deletions(-) diff --git a/src/LyX.cpp b/src/LyX.cpp index 81094b8ca6..85abc0f1d7 100644 --- a/src/LyX.cpp +++ b/src/LyX.cpp @@ -965,6 +965,8 @@ bool LyX::init() pimpl_->toplevel_keymap_.reset(new KeyMap); defaultKeyBindings(pimpl_->toplevel_keymap_.get()); pimpl_->toplevel_keymap_->read(lyxrc.bind_file); + // load user bind file user.bind + pimpl_->toplevel_keymap_->read("user"); pimpl_->lyxfunc_.initKeySequences(pimpl_->toplevel_keymap_.get()); diff --git a/src/frontends/qt4/GuiPrefs.cpp b/src/frontends/qt4/GuiPrefs.cpp index 265dca2afe..e679612d36 100644 --- a/src/frontends/qt4/GuiPrefs.cpp +++ b/src/frontends/qt4/GuiPrefs.cpp @@ -31,8 +31,12 @@ #include "paper.h" #include "Session.h" +#include "support/FileName.h" +#include "support/filetools.h" #include "support/lstrings.h" +#include "support/lyxlib.h" #include "support/os.h" +#include "support/Package.h" #include "support/FileFilterList.h" #include "frontend_helpers.h" @@ -75,7 +79,12 @@ using support::os::external_path; using support::os::external_path_list; using support::os::internal_path; using support::os::internal_path_list; +using support::FileName; using support::FileFilterList; +using support::addPath; +using support::addName; +using support::mkdir; +using support::package; ///////////////////////////////////////////////////////////////////// @@ -1700,123 +1709,222 @@ PrefShortcuts::PrefShortcuts(GuiPreferences * form, QWidget * parent) this, SLOT(select_bind())); connect(bindFileED, SIGNAL(textChanged(const QString &)), this, SIGNAL(changed())); + connect(removePB, SIGNAL(clicked()), + this, SIGNAL(changed())); shortcut_ = new GuiShortcutDialog(this); shortcut_bc_.setPolicy(ButtonPolicy::OkCancelPolicy); shortcut_bc_.setOK(shortcut_->okPB); shortcut_bc_.setCancel(shortcut_->cancelPB); - connect(shortcut_->okPB, SIGNAL(clicked()), + connect(shortcut_->okPB, SIGNAL(clicked()), shortcut_, SLOT(accept())); + connect(shortcut_->okPB, SIGNAL(clicked()), + this, SIGNAL(changed())); connect(shortcut_->cancelPB, SIGNAL(clicked()), shortcut_, SLOT(reject())); connect(shortcut_->okPB, SIGNAL(clicked()), - shortcut_, SLOT(setShortcut())); + this, SLOT(shortcut_okPB_pressed())); } void PrefShortcuts::apply(LyXRC & rc) const { rc.bind_file = internal_path(fromqstr(bindFileED->text())); + // write user_bind and user_unbind to .lyx/bind/user.bind + string bind_dir = addPath(package().user_support().absFilename(), "bind"); + if (!FileName(bind_dir).exists() && mkdir(FileName(bind_dir), 0777)) { + lyxerr << "LyX could not create the user bind directory '" + << bind_dir << "'. All user-defined key bindings will be lost." << endl; + return; + } + if (!FileName(bind_dir).isDirWritable()) { + lyxerr << "LyX could not write to the user bind directory '" + << bind_dir << "'. All user-defined key bindings will be lost." << endl; + return; + } + FileName user_bind_file = FileName(addName(bind_dir, "user.bind")); + user_bind_.write(user_bind_file.toFilesystemEncoding(), false, false); + user_unbind_.write(user_bind_file.toFilesystemEncoding(), true, true); + // immediately apply the keybindings. Why this is not done before? + // The good thing is that the menus are updated automatically. + theTopLevelKeymap().clear(); + theTopLevelKeymap().read(rc.bind_file); + theTopLevelKeymap().read("user"); } void PrefShortcuts::update(LyXRC const & rc) { bindFileED->setText(toqstr(external_path(rc.bind_file))); + // + system_bind_.clear(); + user_bind_.clear(); + user_unbind_.clear(); + system_bind_.read(rc.bind_file); + // \unbind in user.bind is added to user_unbind_ + user_bind_.read("user", &user_unbind_); + updateShortcutsTW(); +} + + +void PrefShortcuts::updateShortcutsTW() +{ shortcutsTW->clear(); - QTreeWidgetItem * editItem = new QTreeWidgetItem(shortcutsTW); - editItem->setText(0, toqstr("Cursor, Mouse and Editing functions")); - editItem->setFlags(editItem->flags() & ~Qt::ItemIsSelectable); + editItem_ = new QTreeWidgetItem(shortcutsTW); + editItem_->setText(0, toqstr("Cursor, Mouse and Editing functions")); + editItem_->setFlags(editItem_->flags() & ~Qt::ItemIsSelectable); - // first insert a few categories - QTreeWidgetItem * mathItem = new QTreeWidgetItem(shortcutsTW); - mathItem->setText(0, toqstr("Mathematical Symbols")); - mathItem->setFlags(mathItem->flags() & ~Qt::ItemIsSelectable); + mathItem_ = new QTreeWidgetItem(shortcutsTW); + mathItem_->setText(0, toqstr("Mathematical Symbols")); + mathItem_->setFlags(mathItem_->flags() & ~Qt::ItemIsSelectable); - QTreeWidgetItem * bufferItem = new QTreeWidgetItem(shortcutsTW); - bufferItem->setText(0, toqstr("Buffer and Window")); - bufferItem->setFlags(bufferItem->flags() & ~Qt::ItemIsSelectable); + bufferItem_ = new QTreeWidgetItem(shortcutsTW); + bufferItem_->setText(0, toqstr("Buffer and Window")); + bufferItem_->setFlags(bufferItem_->flags() & ~Qt::ItemIsSelectable); - QTreeWidgetItem * layoutItem = new QTreeWidgetItem(shortcutsTW); - layoutItem->setText(0, toqstr("Font, Layouts and Textclasses")); - layoutItem->setFlags(layoutItem->flags() & ~Qt::ItemIsSelectable); + layoutItem_ = new QTreeWidgetItem(shortcutsTW); + layoutItem_->setText(0, toqstr("Font, Layouts and Textclasses")); + layoutItem_->setFlags(layoutItem_->flags() & ~Qt::ItemIsSelectable); - QTreeWidgetItem * systemItem = new QTreeWidgetItem(shortcutsTW); - systemItem->setText(0, toqstr("System and Miscellaneous")); - systemItem->setFlags(systemItem->flags() & ~Qt::ItemIsSelectable); + systemItem_ = new QTreeWidgetItem(shortcutsTW); + systemItem_->setText(0, toqstr("System and Miscellaneous")); + systemItem_->setFlags(systemItem_->flags() & ~Qt::ItemIsSelectable); // listBindings(unbound=true) lists all bound and unbound lfuns - KeyMap::BindingList const bindinglist = theTopLevelKeymap().listBindings(true); + // Items in this list is tagged by its source. + KeyMap::BindingList bindinglist = system_bind_.listBindings(true, + static_cast(System)); + KeyMap::BindingList user_bindinglist = user_bind_.listBindings(false, + static_cast(UserBind)); + KeyMap::BindingList user_unbindinglist = user_unbind_.listBindings(false, + static_cast(UserUnbind)); + bindinglist.insert(bindinglist.end(), user_bindinglist.begin(), + user_bindinglist.end()); + bindinglist.insert(bindinglist.end(), user_unbindinglist.begin(), + user_unbindinglist.end()); KeyMap::BindingList::const_iterator it = bindinglist.begin(); KeyMap::BindingList::const_iterator it_end = bindinglist.end(); - for (; it != it_end; ++it) { - kb_action action = it->get<0>().action; - string const action_name = lyxaction.getActionName(action); - QString const lfun = toqstr(from_utf8(action_name) - + " " + it->get<0>().argument()); - QString const shortcut = toqstr(it->get<1>().print(KeySequence::Portable)); - - QTreeWidgetItem * newItem = NULL; - // if an item with the same lfun already exists, insert as a - // child item to that item. - // WARNING: this can be slow. - QList const items = shortcutsTW->findItems(lfun, - Qt::MatchFlags(Qt::MatchExactly | Qt::MatchRecursive), 0); - if (!items.empty()) - newItem = new QTreeWidgetItem(items[0]); - else { - switch(lyxaction.getActionType(action)) { - case LyXAction::Hidden: - continue; - case LyXAction::Edit: - newItem = new QTreeWidgetItem(editItem); - break; - case LyXAction::Math: - newItem = new QTreeWidgetItem(mathItem); - break; - case LyXAction::Buffer: - newItem = new QTreeWidgetItem(bufferItem); - break; - case LyXAction::Layout: - newItem = new QTreeWidgetItem(layoutItem); - break; - case LyXAction::System: - newItem = new QTreeWidgetItem(systemItem); - break; - default: - // this should not happen - newItem = new QTreeWidgetItem(shortcutsTW); - } - } + for (; it != it_end; ++it) + insertShortcutItem(it->get<0>(), it->get<1>(), + static_cast(it->get<2>())); - newItem->setText(0, lfun); - newItem->setText(1, shortcut); - - //newItem->setFlags(newItem->flags() | Qt::ItemIsEditable - // | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable); - } shortcutsTW->sortItems(0, Qt::AscendingOrder); QList items = shortcutsTW->selectedItems(); - modifyPB->setEnabled(!items.isEmpty()); removePB->setEnabled(!items.isEmpty() && !items[0]->text(1).isEmpty()); searchPB->setEnabled(!searchLE->text().isEmpty()); } +void PrefShortcuts::setItemType(QTreeWidgetItem * item, item_type tag) +{ + item->setData(0, Qt::UserRole, QVariant(tag)); + + switch (tag) { + case System: +#if QT_VERSION >= 0x040200 + item->setForeground(0, QBrush("black")); + item->setForeground(1, QBrush("black")); +#else + item->setTextColor(QColor("black")); +#endif + break; + case UserBind: +#if QT_VERSION >= 0x040200 + item->setForeground(0, QBrush("green")); + item->setForeground(1, QBrush("green")); +#else + item->setTextColor(QColor("green")); +#endif + break; + case UserUnbind: +#if QT_VERSION >= 0x040200 + item->setForeground(0, QBrush("red")); + item->setForeground(1, QBrush("red")); +#else + item->setTextColor(QColor("red")); +#endif + } +} + + +QTreeWidgetItem * PrefShortcuts::insertShortcutItem(FuncRequest const & lfun, + KeySequence const & seq, item_type tag) +{ + kb_action action = lfun.action; + string const action_name = lyxaction.getActionName(action); + QString const lfun_name = toqstr(from_utf8(action_name) + + " " + lfun.argument()); + // use BindFile format instead of a more verbose form Portable + // if the Shortcut dialog can hide all the bind file stuff, + // Portable format can be used. + QString const shortcut = toqstr(seq.print(KeySequence::BindFile)); + + QTreeWidgetItem * newItem = NULL; + // for unbind items, try to find an existing item in the system bind list + if (tag == UserUnbind) { + QList const items = shortcutsTW->findItems(lfun_name, + Qt::MatchFlags(Qt::MatchExactly | Qt::MatchRecursive), 0); + if (!items.empty()) + newItem = items[0]; + } + if (!newItem) { + switch(lyxaction.getActionType(action)) { + case LyXAction::Hidden: + return NULL; + case LyXAction::Edit: + newItem = new QTreeWidgetItem(editItem_); + break; + case LyXAction::Math: + newItem = new QTreeWidgetItem(mathItem_); + break; + case LyXAction::Buffer: + newItem = new QTreeWidgetItem(bufferItem_); + break; + case LyXAction::Layout: + newItem = new QTreeWidgetItem(layoutItem_); + break; + case LyXAction::System: + newItem = new QTreeWidgetItem(systemItem_); + break; + default: + // this should not happen + newItem = new QTreeWidgetItem(shortcutsTW); + } + } + + newItem->setText(0, lfun_name); + newItem->setText(1, shortcut); + setItemType(newItem, tag); + return newItem; +} + + void PrefShortcuts::on_shortcutsTW_itemSelectionChanged() { QList items = shortcutsTW->selectedItems(); - modifyPB->setEnabled(!items.isEmpty()); removePB->setEnabled(!items.isEmpty() && !items[0]->text(1).isEmpty()); + if (items.isEmpty()) + return; + + item_type tag = static_cast(items[0]->data(0, Qt::UserRole).toInt()); + if (tag == UserUnbind) + removePB->setText(toqstr("Restore")); + else + removePB->setText(toqstr("Remove")); } void PrefShortcuts::on_shortcutsTW_itemDoubleClicked() { - on_modifyPB_pressed(); + QTreeWidgetItem * item = shortcutsTW->currentItem(); + if (item->flags() & Qt::ItemIsSelectable) { + shortcut_->lfunLE->setText(item->text(0)); + shortcut_->shortcutLE->setText(item->text(1)); + shortcut_->exec(); + } } @@ -1825,43 +1933,68 @@ void PrefShortcuts::select_bind() docstring const name = from_utf8(internal_path(fromqstr(bindFileED->text()))); docstring file = form_->browsebind(name); - if (!file.empty()) + if (!file.empty()) { bindFileED->setText(toqstr(file)); + system_bind_ = KeyMap(); + system_bind_.read(to_utf8(file)); + updateShortcutsTW(); + } } void PrefShortcuts::on_newPB_pressed() { - shortcut_->lfunED->clear(); - shortcut_->shortcutED->clear(); - shortcut_->setWindowTitle(toqstr("Create new shortcut")); - shortcut_->exec(); -} - - -void PrefShortcuts::on_modifyPB_pressed() -{ - QTreeWidgetItem * item = shortcutsTW->currentItem(); - shortcut_->lfunED->setText(item->text(0)); - shortcut_->shortcutED->setText(item->text(1)); - shortcut_->setWindowTitle(toqstr("Modify shortcut")); + shortcut_->lfunLE->clear(); + shortcut_->shortcutLE->clear(); shortcut_->exec(); } void PrefShortcuts::on_removePB_pressed() { + // it seems that only one item can be selected, but I am + // removing all selected items anyway. QList items = shortcutsTW->selectedItems(); - for (int i = 0; i < items.size(); ++i) - items[i]->setText(1, QString()); + for (int i = 0; i < items.size(); ++i) { + string shortcut = fromqstr(items[i]->text(1)); + string lfun = fromqstr(items[i]->text(0)); + FuncRequest func = lyxaction.lookupFunc(lfun); + item_type tag = static_cast(items[i]->data(0, Qt::UserRole).toInt()); + + switch (tag) { + case System: { + // for system bind, we do not touch the item + // but add an user unbind item + user_unbind_.bind(shortcut, func); + setItemType(items[i], UserUnbind); + break; + } + case UserBind: { + // for user_bind, we remove this bind + QTreeWidgetItem * parent = items[i]->parent(); + parent->takeChild(parent->indexOfChild(items[i])); + user_bind_.unbind(shortcut, func); + break; + } + case UserUnbind: { + // for user_unbind, we remove the unbind, and the item + // become System again. + user_unbind_.unbind(shortcut, func); + setItemType(items[i], System); + break; + } + } + } } void PrefShortcuts::on_searchPB_pressed() { - QList matched = shortcutsTW->findItems( - searchLE->text(), - Qt::MatchFlags(Qt::MatchContains | Qt::MatchRecursive)); + // search both columns + QList matched = shortcutsTW->findItems(searchLE->text(), + Qt::MatchFlags(Qt::MatchContains | Qt::MatchRecursive), 0); + matched += shortcutsTW->findItems(searchLE->text(), + Qt::MatchFlags(Qt::MatchContains | Qt::MatchRecursive), 1); // hide everyone (to avoid searching in matched QList repeatedly QTreeWidgetItemIterator it(shortcutsTW, QTreeWidgetItemIterator::Selectable); @@ -1870,7 +2003,7 @@ void PrefShortcuts::on_searchPB_pressed() // show matched items for (int i = 0; i < matched.size(); ++i) { shortcutsTW->setItemHidden(matched[i], false); - shortcutsTW->setItemExpanded(matched[i], true); + shortcutsTW->setItemExpanded(matched[i]->parent(), true); } } @@ -1887,8 +2020,44 @@ void PrefShortcuts::on_searchLE_textChanged() } -void PrefShortcuts::setShortcut() +void PrefShortcuts::shortcut_okPB_pressed() { + string shortcut = fromqstr(shortcut_->shortcutLE->text()); + string lfun = fromqstr(shortcut_->lfunLE->text()); + FuncRequest func = lyxaction.lookupFunc(lfun); + + if (func.action == LFUN_UNKNOWN_ACTION) { + Alert::error(_("Failed to create shortcut"), + _("Unknown or invalid lyx function")); + return; + } + + KeySequence k(0, 0); + string::size_type const res = k.parse(shortcut); + if (res != string::npos) { + Alert::error(_("Failed to create shortcut"), + _("Invalid key sequence")); + return; + } + + // if both lfun and shortcut is valid + if (user_bind_.hasBinding(k, func) || system_bind_.hasBinding(k, func)) { + Alert::error(_("Failed to create shortcut"), + _("Shortcut is alreay defined")); + return; + } + + QTreeWidgetItem * item = insertShortcutItem(func, k, UserBind); + if (item) { + user_bind_.bind(shortcut, func); + shortcutsTW->sortItems(0, Qt::AscendingOrder); + shortcutsTW->setItemExpanded(item->parent(), true); + shortcutsTW->scrollToItem(item); + } else { + Alert::error(_("Failed to create shortcut"), + _("Can not insert shortcut to the list")); + return; + } } diff --git a/src/frontends/qt4/GuiPrefs.h b/src/frontends/qt4/GuiPrefs.h index eeeb7f7ad8..b057c26353 100644 --- a/src/frontends/qt4/GuiPrefs.h +++ b/src/frontends/qt4/GuiPrefs.h @@ -17,6 +17,8 @@ #include "Color.h" #include "Converter.h" #include "Format.h" +#include "KeyMap.h" +#include "lfuns.h" #include "LyXRC.h" #include "Mover.h" @@ -359,28 +361,55 @@ public: class PrefShortcuts : public PrefModule, public Ui::PrefShortcuts { Q_OBJECT +private: + enum item_type { + System, //< loaded from a bind file + UserBind, //< \bind loaded from user.bind + UserUnbind //< \unbind loaded from user.bind + }; public: PrefShortcuts(GuiPreferences * form, QWidget * parent = 0); void apply(LyXRC & rc) const; void update(LyXRC const & rc); + void updateShortcutsTW(); + /// + void setItemType(QTreeWidgetItem * item, item_type tag); + QTreeWidgetItem * insertShortcutItem(FuncRequest const & lfun, + KeySequence const & shortcut, item_type tag); public Q_SLOTS: void select_bind(); void on_newPB_pressed(); - void on_modifyPB_pressed(); void on_removePB_pressed(); void on_searchPB_pressed(); void on_searchLE_textChanged(); /// void on_shortcutsTW_itemSelectionChanged(); - void setShortcut(); + void shortcut_okPB_pressed(); void on_shortcutsTW_itemDoubleClicked(); + private: /// GuiShortcutDialog * shortcut_; /// ButtonController shortcut_bc_; + /// category items + QTreeWidgetItem * editItem_; + QTreeWidgetItem * mathItem_; + QTreeWidgetItem * bufferItem_; + QTreeWidgetItem * layoutItem_; + QTreeWidgetItem * systemItem_; + // system_bind_ holds bindings from rc.bind_file + // user_bind_ holds \bind bindings from user.bind + // user_unbind_ holds \unbind bindings from user.bind + // When an item is inserted, it is added to user_bind_ + // When an item from system_bind_ is deleted, it is added to user_unbind_ + // When an item in user_bind_ or user_unbind_ is deleted, it is + // deleted (unbind) + KeyMap system_bind_; + KeyMap user_bind_; + KeyMap user_unbind_; }; diff --git a/src/frontends/qt4/ui/PrefShortcutsUi.ui b/src/frontends/qt4/ui/PrefShortcutsUi.ui index f5b9373989..49111e6e61 100644 --- a/src/frontends/qt4/ui/PrefShortcutsUi.ui +++ b/src/frontends/qt4/ui/PrefShortcutsUi.ui @@ -122,13 +122,6 @@ - - - - Modify - - - diff --git a/src/frontends/qt4/ui/ShortcutUi.ui b/src/frontends/qt4/ui/ShortcutUi.ui index 066c96ecb8..0f36f31105 100644 --- a/src/frontends/qt4/ui/ShortcutUi.ui +++ b/src/frontends/qt4/ui/ShortcutUi.ui @@ -94,7 +94,7 @@ - + 7 @@ -109,7 +109,7 @@ - + 7 @@ -126,7 +126,7 @@ - lfunED + lfunLE okPB cancelPB -- 2.39.2