]> git.lyx.org Git - lyx.git/blobdiff - src/frontends/qt4/GuiSelectionManager.cpp
Allow Ctrl+Enter in GuiCitation even if the citation is already there
[lyx.git] / src / frontends / qt4 / GuiSelectionManager.cpp
index d4c1fe233e9ff85e3501f0b231291f323768abde..eeba9372f0ef73d4205863e7ed0399884722fed1 100644 (file)
  */
 
 #include <config.h>
+
 #include "GuiSelectionManager.h"
+#include "qt_helpers.h"
+
+#include "support/debug.h"
+
+#include <QAbstractItemModel>
+#include <QAbstractListModel>
+#include <QItemSelection>
+#include <QListView>
+#include <QKeyEvent>
+#include <QPushButton>
+
+#ifdef KeyPress
+#undef KeyPress
+#endif
+
+#ifdef ControlModifier
+#undef ControlModifier
+#endif
+
+#ifdef FocusIn
+#undef FocusIn
+#endif
 
 
 namespace lyx {
 namespace frontend {
 
-GuiSelectionManager::GuiSelectionManager(
-               QListView * avail, 
-               QListView * sel,
-               QPushButton * add, 
-               QPushButton * del, 
-               QPushButton * up, 
-               QPushButton * down,
-               QStringListModel * amod,
-               QStringListModel * smod)
+GuiSelectionManager::GuiSelectionManager(QObject * parent,
+                                         QAbstractItemView * avail,
+                                         QAbstractItemView * sel,
+                                         QPushButton * add,
+                                         QPushButton * del,
+                                         QPushButton * up,
+                                         QPushButton * down,
+                                         QAbstractListModel * amod,
+                                         QAbstractItemModel * smod,
+                                         int const main_sel_col)
+: QObject(parent), availableLV(avail), selectedLV(sel),
+  addPB(add), deletePB(del), upPB(up), downPB(down),
+  availableModel(amod), selectedModel(smod),
+  selectedHasFocus_(false), main_sel_col_(main_sel_col)
 {
-       availableLV = avail;
-       selectedLV = sel;
-       addPB = add;
-       deletePB = del;
-       upPB = up;
-       downPB = down;
-       availableModel = amod;
-       selectedModel = smod;
-       
        selectedLV->setModel(smod);
        availableLV->setModel(amod);
+       selectedLV->setSelectionBehavior(QAbstractItemView::SelectRows);
+       selectedLV->setSelectionMode(QAbstractItemView::SingleSelection);
 
        connect(availableLV->selectionModel(),
-                                       SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)),
-                                                                this, SLOT(availableChanged(const QModelIndex &, const QModelIndex &)));
+               SIGNAL(currentChanged(QModelIndex, QModelIndex)),
+               this, SLOT(availableChanged(QModelIndex, QModelIndex)));
+       connect(selectedLV->selectionModel(),
+               SIGNAL(currentChanged(QModelIndex, QModelIndex)),
+               this, SLOT(selectedChanged(QModelIndex, QModelIndex)));
+       connect(availableLV->selectionModel(),
+               SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
+               this, SLOT(availableChanged(QItemSelection, QItemSelection)));
        connect(selectedLV->selectionModel(),
-                                       SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)),
-                                                                this, SLOT(selectedChanged(const QModelIndex &, const QModelIndex &)));
-       connect(addPB, SIGNAL(clicked()), 
-                                       this, SLOT(addPB_clicked()));
-       connect(deletePB, SIGNAL(clicked()), 
-                                       this, SLOT(deletePB_clicked()));
-       connect(upPB, SIGNAL(clicked()), 
-                                       this, SLOT(upPB_clicked()));
-       connect(downPB, SIGNAL(clicked()), 
-                                       this, SLOT(downPB_clicked()));
-       connect(availableLV, SIGNAL(clicked(const QModelIndex &)), 
-                                       this, SLOT(availableLV_clicked(const QModelIndex &)));
-       connect(availableLV, SIGNAL(doubleClicked(const QModelIndex &)), 
-                                       this, SLOT(availableLV_doubleClicked(const QModelIndex &)));
-       connect(selectedLV, SIGNAL(clicked(const QModelIndex &)), 
-                                       this, SLOT(selectedLV_clicked(const QModelIndex &)));
+               SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
+               this, SLOT(selectedChanged(QItemSelection, QItemSelection)));
+       connect(selectedLV->itemDelegate(), SIGNAL(commitData(QWidget*)),
+               this, SLOT(selectedEdited()));
+       connect(addPB, SIGNAL(clicked()),
+               this, SLOT(addPB_clicked()));
+       connect(deletePB, SIGNAL(clicked()),
+               this, SLOT(deletePB_clicked()));
+       connect(upPB, SIGNAL(clicked()),
+               this, SLOT(upPB_clicked()));
+       connect(downPB, SIGNAL(clicked()),
+               this, SLOT(downPB_clicked()));
+       connect(availableLV, SIGNAL(doubleClicked(QModelIndex)),
+               this, SLOT(availableLV_doubleClicked(QModelIndex)));
 
        availableLV->installEventFilter(this);
        selectedLV->installEventFilter(this);
@@ -69,27 +94,108 @@ GuiSelectionManager::GuiSelectionManager(
 
 void GuiSelectionManager::update()
 {
-       int const arows = availableLV->model()->rowCount();
+       updateAddPB();
+       updateDelPB();
+       updateDownPB();
+       updateUpPB();
+}
+
+
+QModelIndex GuiSelectionManager::getSelectedIndex(int const c) const
+{
+       QModelIndexList avail = availableLV->selectionModel()->selectedIndexes();
+       QModelIndexList sel   = selectedLV->selectionModel()->selectedRows(c);
+       bool const have_avl = !avail.isEmpty();
+       bool const have_sel = !sel.isEmpty();
+
+       if (selectedFocused()) { 
+               if (have_sel)
+                       return sel.front();
+               if (have_avl)
+                       return avail.front();
+       } 
+       else { // available has focus
+               if (have_avl)
+                       return avail.front();
+               if (have_sel)
+                       return sel.front();
+       }
+       return QModelIndex();
+}
+
+
+void GuiSelectionManager::updateAddPB()
+{
+       int const arows = availableModel->rowCount();
        QModelIndexList const availSels = 
-                       availableLV->selectionModel()->selectedIndexes();
+               availableLV->selectionModel()->selectedIndexes();
        addPB->setEnabled(arows > 0 &&
-                       !availSels.isEmpty() &&
-                       !isSelected(availSels.first()));
+               !availSels.isEmpty() &&
+               !isSelected(availSels.first()));
+}
 
-       int const srows = selectedLV->model()->rowCount();
+
+void GuiSelectionManager::updateDelPB()
+{
+       int const srows = selectedModel->rowCount();
+       if (srows == 0) {
+               deletePB->setEnabled(false);
+               return;
+       }
        QModelIndexList const selSels = 
-                       selectedLV->selectionModel()->selectedIndexes();
-       int const sel_nr =      selSels.empty() ? -1 : selSels.first().row();
+               selectedLV->selectionModel()->selectedIndexes();
+       int const sel_nr = selSels.empty() ? -1 : selSels.first().row();
        deletePB->setEnabled(sel_nr >= 0);
+}
+
+
+void GuiSelectionManager::updateUpPB()
+{
+       int const srows = selectedModel->rowCount();
+       if (srows == 0) {
+               upPB->setEnabled(false);
+               return;
+       }
+       QModelIndexList const selSels = 
+                       selectedLV->selectionModel()->selectedIndexes();
+       int const sel_nr = selSels.empty() ? -1 : selSels.first().row();
        upPB->setEnabled(sel_nr > 0);
+}
+
+
+void GuiSelectionManager::updateDownPB()
+{
+       int const srows = selectedModel->rowCount();
+       if (srows == 0) {
+               downPB->setEnabled(false);
+               return;
+       }
+       QModelIndexList const selSels = 
+                       selectedLV->selectionModel()->selectedIndexes();
+       int const sel_nr = selSels.empty() ? -1 : selSels.first().row();
        downPB->setEnabled(sel_nr >= 0 && sel_nr < srows - 1);
 }
 
 
 bool GuiSelectionManager::isSelected(const QModelIndex & idx)
 {
-       QString const str = idx.data().toString();
-       return selectedModel->stringList().contains(str);
+       if (selectedModel->rowCount() == 0)
+               return false;
+       QVariant const & str = availableModel->data(idx, Qt::DisplayRole);
+       QModelIndexList qmil = 
+                       selectedModel->match(selectedModel->index(0, main_sel_col_),
+                                            Qt::DisplayRole, str, 1,
+                                            Qt::MatchFlags(Qt::MatchExactly | Qt::MatchWrap));
+       return !qmil.empty();
+}
+
+
+void GuiSelectionManager::availableChanged(QItemSelection const & qis, QItemSelection const &)
+{
+       QModelIndexList il = qis.indexes();
+       if (il.empty()) 
+               return;
+       availableChanged(il.front(), QModelIndex());
 }
 
 
@@ -103,6 +209,15 @@ void GuiSelectionManager::availableChanged(const QModelIndex & idx, const QModel
 }
 
 
+void GuiSelectionManager::selectedChanged(QItemSelection const & qis, QItemSelection const &)
+{
+       QModelIndexList il = qis.indexes();
+       if (il.empty()) 
+               return;
+       selectedChanged(il.front(), QModelIndex());
+}
+
+
 void GuiSelectionManager::selectedChanged(const QModelIndex & idx, const QModelIndex &)
 {
        if (!idx.isValid())
@@ -113,44 +228,72 @@ void GuiSelectionManager::selectedChanged(const QModelIndex & idx, const QModelI
 }
 
 
-static QModelIndex getSelectedIndex(QListView * lv)
+void GuiSelectionManager::selectedEdited()
+{
+       selectionChanged();
+}
+
+
+bool GuiSelectionManager::insertRowToSelected(int i, 
+               QMap<int, QVariant> const & itemData)
 {
-       QModelIndex retval = QModelIndex();
-       QModelIndexList selIdx = 
-                       lv->selectionModel()->selectedIndexes();
-       if (!selIdx.empty())
-               retval = selIdx.first();
-       return retval;
+       if (i <= -1)
+               i = 0;
+       if (i > selectedModel->rowCount())
+               i = selectedModel->rowCount();
+       if (!selectedModel->insertRow(i))
+               return false;
+       return selectedModel->setItemData(selectedModel->index(i, main_sel_col_), itemData);
+}
+
+
+bool GuiSelectionManager::insertRowToSelected(int i, QMap<int, QMap<int, QVariant>> & qms)
+{
+       if (i <= -1)
+               i = 0;
+       if (i > selectedModel->rowCount())
+               i = selectedModel->rowCount();
+       if (!selectedModel->insertRow(i))
+               return false;
+       bool res = true;
+       QMap<int, QMap<int, QVariant>>::const_iterator it = qms.constBegin();
+       for (; it != qms.constEnd(); ++it)
+               res &= selectedModel->setItemData(selectedModel->index(i, it.key()), it.value());
+       return res;
 }
 
 
 void GuiSelectionManager::addPB_clicked()
 {
-       QModelIndex const idxToAdd = getSelectedIndex(availableLV);
-       if (!idxToAdd.isValid())
+       QModelIndexList selIdx =
+               availableLV->selectionModel()->selectedIndexes();
+       if (selIdx.isEmpty())
                return;
-       QModelIndex idx = selectedLV->currentIndex();
-       
-       QStringList keys = selectedModel->stringList();
-       keys.append(idxToAdd.data().toString());
-       selectedModel->setStringList(keys);
+
+       QModelIndex const idxToAdd = selIdx.first();
+       QModelIndex const idx = selectedLV->currentIndex();
+       int const srows = selectedModel->rowCount();
+
+       QMap<int, QVariant> qm = availableModel->itemData(idxToAdd);
+       insertRowToSelected(srows, qm);
+
        selectionChanged(); //signal
-       
+
        if (idx.isValid())
                selectedLV->setCurrentIndex(idx);
+
        updateHook();
 }
 
 
 void GuiSelectionManager::deletePB_clicked()
 {
-       QModelIndex idx = getSelectedIndex(selectedLV);
-       if (!idx.isValid())
+       QModelIndexList selIdx =
+               selectedLV->selectionModel()->selectedIndexes();
+       if (selIdx.isEmpty())
                return;
-
-       QStringList keys = selectedModel->stringList();
-       keys.removeAt(idx.row());
-       selectedModel->setStringList(keys);
+       QModelIndex idx = selIdx.first();
+       selectedModel->removeRow(idx.row());
        selectionChanged(); //signal
 
        int nrows = selectedLV->model()->rowCount();
@@ -160,7 +303,7 @@ void GuiSelectionManager::deletePB_clicked()
        if (nrows > 1)
                selectedLV->setCurrentIndex(idx);
        else if (nrows == 1)
-               selectedLV->setCurrentIndex(selectedLV->model()->index(0,0));
+               selectedLV->setCurrentIndex(selectedLV->model()->index(0, 0));
        selectedHasFocus_ = (nrows > 0);
        updateHook();
 }
@@ -168,14 +311,26 @@ void GuiSelectionManager::deletePB_clicked()
 
 void GuiSelectionManager::upPB_clicked()
 {
-       QModelIndex idx = selectedLV->currentIndex();
-       
+       QModelIndexList selIdx =
+               selectedLV->selectionModel()->selectedIndexes();
+       if (selIdx.isEmpty())
+               return;
+       QModelIndex idx = selIdx.first();
+
        int const pos = idx.row();
-       QStringList keys = selectedModel->stringList();
-       keys.swap(pos, pos - 1);
-       selectedModel->setStringList(keys);
+       if (pos <= 0)
+               return;
+
+       QMap<int, QMap<int, QVariant>> qms;
+       QList<QModelIndex>::const_iterator it = selIdx.constBegin();
+       for (; it != selIdx.constEnd(); ++it)
+               qms[it->column()] = selectedModel->itemData(*it);
+
+       selectedModel->removeRow(pos);
+       insertRowToSelected(pos - 1, qms);
+
        selectionChanged(); //signal
-       
+
        selectedLV->setCurrentIndex(idx.sibling(idx.row() - 1, idx.column()));
        selectedHasFocus_ = true;
        updateHook();
@@ -184,34 +339,35 @@ void GuiSelectionManager::upPB_clicked()
 
 void GuiSelectionManager::downPB_clicked()
 {
-       QModelIndex idx = selectedLV->currentIndex();
-       
+       QModelIndexList selIdx =
+               selectedLV->selectionModel()->selectedIndexes();
+       if (selIdx.isEmpty())
+               return;
+       QModelIndex idx = selIdx.first();
+
        int const pos = idx.row();
-       QStringList keys = selectedModel->stringList();
-       keys.swap(pos, pos + 1);
-       selectedModel->setStringList(keys);
+       if (pos >= selectedModel->rowCount() - 1)
+               return;
+
+       QMap<int, QMap<int, QVariant>> qms;
+       QList<QModelIndex>::const_iterator it = selIdx.constBegin();
+       for (; it != selIdx.constEnd(); ++it)
+               qms[it->column()] = selectedModel->itemData(*it);
+
+       selectedModel->removeRow(pos);
+       insertRowToSelected(pos + 1, qms);
+
        selectionChanged(); //signal
-       
+
        selectedLV->setCurrentIndex(idx.sibling(idx.row() + 1, idx.column()));
        selectedHasFocus_ = true;
        updateHook();
 }
 
 
-//FIXME These slots do not really do what they need to do, since focus
-//can enter the QListView in other ways. But there are no signals sent
-//in that case. We need to reimplement focusInEvent() to capture those,
-//which means subclassing QListView. (rgh)
-void GuiSelectionManager::availableLV_clicked(const QModelIndex &)
-{
-       selectedHasFocus_ = false;
-       updateHook();
-}
-
-
 void GuiSelectionManager::availableLV_doubleClicked(const QModelIndex & idx)
 {
-       if (isSelected(idx))
+       if (isSelected(idx) || !addPB->isEnabled())
                return;
 
        if (idx.isValid())
@@ -221,69 +377,97 @@ void GuiSelectionManager::availableLV_doubleClicked(const QModelIndex & idx)
 }
 
 
-void GuiSelectionManager::selectedLV_clicked(const QModelIndex &)
-{
-       selectedHasFocus_ = true;
-       updateHook();
-}
-
-
 bool GuiSelectionManager::eventFilter(QObject * obj, QEvent * event) 
 {
+       QEvent::Type etype = event->type();
        if (obj == availableLV) {
-               if (event->type() != QEvent::KeyPress)
-                       return QObject::eventFilter(obj, event);
-               QKeyEvent * keyEvent = static_cast<QKeyEvent *>(event);
-               int const keyPressed = keyEvent->key();
-               Qt::KeyboardModifiers const keyModifiers = keyEvent->modifiers();
-               //Enter key without modifier will add current item.
-               //Ctrl-Enter will add it and close the dialog.
-               //This is designed to work both with the main enter key
-               //and the one on the numeric keypad.
-               if ((keyPressed == Qt::Key_Enter || keyPressed == Qt::Key_Return) &&
-                               //We want one or both of Control and Keypad, and nothing else
-                               //(KeypadModifier is what you get if you use the Enter key on the
-                               //numeric keypad.)
-                                        (!keyModifiers || 
-                                        (keyModifiers == Qt::ControlModifier) ||
-                                        (keyModifiers == Qt::KeypadModifier)  ||
-                                        (keyModifiers == (Qt::ControlModifier | Qt::KeypadModifier))
-                                        )
-                        ) {
-                       if (addPB->isEnabled()) {
-                               addPB_clicked();
-                               okHook(); //signal
+               if (etype == QEvent::KeyPress) {
+                       QKeyEvent * keyEvent = static_cast<QKeyEvent *>(event);
+                       int const keyPressed = keyEvent->key();
+                       Qt::KeyboardModifiers const keyModifiers = keyEvent->modifiers();
+                       // Enter key without modifier will add current item.
+                       // Ctrl-Enter will add it and close the dialog.
+                       // This is designed to work both with the main enter key
+                       // and the one on the numeric keypad.
+                       if (keyPressed == Qt::Key_Enter || keyPressed == Qt::Key_Return) {
+                               if (!keyModifiers ||
+                                   keyModifiers == Qt::ControlModifier ||
+                                   keyModifiers == Qt::KeypadModifier  ||
+                                   keyModifiers == (Qt::ControlModifier
+                                                    | Qt::KeypadModifier)) {
+                                       if (addPB->isEnabled()) {
+                                               addPB_clicked();
+                                       }
+                                       if (keyModifiers)
+                                               okHook(); //signal
+                               }
+                               event->accept();
+                               return true;
                        }
-                       event->accept();
-                       return true;
-                        } 
-       } else if (obj == selectedLV) {
-               //Delete or backspace key will delete current item
-               //...with control modifier will clear the list
-               if (event->type() != QEvent::KeyPress)
-                       return QObject::eventFilter(obj, event);
-               QKeyEvent * keyEvent = static_cast<QKeyEvent *>(event);
-               int const keyPressed = keyEvent->key();
-               Qt::KeyboardModifiers const keyModifiers = keyEvent->modifiers();
-               if (keyPressed == Qt::Key_Delete || keyPressed == Qt::Key_Backspace) {
-                       if (keyModifiers == Qt::NoModifier && deletePB->isEnabled())
-                               deletePB_clicked();
-                       else if (keyModifiers == Qt::ControlModifier) {
-                               QStringList list = selectedModel->stringList();
-                               list.clear();
-                               selectedModel->setStringList(list);
+                       else if (keyPressed == Qt::Key_Right) {
+                               focusAndHighlight(selectedLV);
+                               event->accept();
+                               return true;
+                       }
+               } else if (etype == QEvent::FocusIn) {
+                       if (selectedHasFocus_) {
+                               selectedHasFocus_ = false;
                                updateHook();
-                       } else
-                               //ignore it otherwise
-                               return QObject::eventFilter(obj, event);
+                       }
+                       return false;
+               }
+       } else if (obj == selectedLV) {
+               if (etype == QEvent::KeyPress) {
+                       QKeyEvent * keyEvent = static_cast<QKeyEvent *>(event);
+                       int const keyPressed = keyEvent->key();
+                       Qt::KeyboardModifiers const keyModifiers = keyEvent->modifiers();
+                       // Delete or backspace key will delete current item
+                       // ...with control modifier will clear the list
+                       if (keyPressed == Qt::Key_Delete || keyPressed == Qt::Key_Backspace) {
+                               if (keyModifiers == Qt::NoModifier && deletePB->isEnabled()) {
+                                       deletePB_clicked();
+                                       updateHook();
+                               } else if (keyModifiers == Qt::ControlModifier) {
+                                       selectedModel->removeRows(0, selectedModel->rowCount());
+                                       updateHook();
+                               } else
+                                       return QObject::eventFilter(obj, event);
+                       }
+                       // Ctrl-Up activates upPB
+                       else if (keyPressed == Qt::Key_Up) {
+                               if (keyModifiers == Qt::ControlModifier) {
+                                       if (upPB->isEnabled())
+                                               upPB_clicked();
+                                       event->accept();
+                                       return true;
+                               }
+                       }
+                       // Ctrl-Down activates downPB
+                       else if (keyPressed == Qt::Key_Down) {
+                               if (keyModifiers == Qt::ControlModifier) {
+                                       if (downPB->isEnabled())
+                                               downPB_clicked();
+                                       event->accept();
+                                       return true;
+                               }
+                       }
+                       else if (keyPressed == Qt::Key_Left) {
+                               focusAndHighlight(availableLV);
                                event->accept();
                                return true;
+                       }
+               } else if (etype == QEvent::FocusIn) {
+                       if (!selectedHasFocus_) {
+                               selectedHasFocus_ = true;
+                               updateHook();
+                       }
+                       return false;
                }
        }
        return QObject::eventFilter(obj, event);
 }
 
-}//namespace frontend
-}//namespace lyx
+} // namespace frontend
+} // namespace lyx
 
-#include "GuiSelectionManager_moc.cpp"
+#include "moc_GuiSelectionManager.cpp"