]> git.lyx.org Git - lyx.git/blob - src/frontends/qt/GuiSelectionManager.cpp
Fix glitch in revert_biblatex_chicago
[lyx.git] / src / frontends / qt / GuiSelectionManager.cpp
1 /**
2  * \file GuiSelectionManager.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Richard Kimberly Heck
7  * \author Et Alia
8  *
9  * Some of the material in this file previously appeared in
10  * GuiCitationDialog.cpp.
11  *
12  * Full author contact details are available in file CREDITS.
13  */
14
15 #include <config.h>
16
17 #include "GuiSelectionManager.h"
18 #include "qt_helpers.h"
19
20 #include "support/debug.h"
21
22 #include <QAbstractItemModel>
23 #include <QAbstractListModel>
24 #include <QItemSelection>
25 #include <QListView>
26 #include <QKeyEvent>
27 #include <QPushButton>
28
29 #ifdef KeyPress
30 #undef KeyPress
31 #endif
32
33 #ifdef ControlModifier
34 #undef ControlModifier
35 #endif
36
37 #ifdef FocusIn
38 #undef FocusIn
39 #endif
40
41
42 namespace lyx {
43 namespace frontend {
44
45 GuiSelectionManager::GuiSelectionManager(QObject * parent,
46                                          QAbstractItemView * avail,
47                                          QAbstractItemView * sel,
48                                          QPushButton * add,
49                                          QPushButton * del,
50                                          QPushButton * up,
51                                          QPushButton * down,
52                                          QAbstractItemModel * amod,
53                                          QAbstractItemModel * smod,
54                                          int const main_sel_col)
55 : QObject(parent), availableLV(avail), selectedLV(sel),
56   addPB(add), deletePB(del), upPB(up), downPB(down),
57   availableModel(amod), selectedModel(smod),
58   selectedHasFocus_(false), main_sel_col_(main_sel_col),
59   allow_multi_selection_(false)
60 {
61         selectedLV->setModel(smod);
62         availableLV->setModel(amod);
63         selectedLV->setSelectionBehavior(QAbstractItemView::SelectRows);
64         selectedLV->setSelectionMode(QAbstractItemView::SingleSelection);
65
66         connect(availableLV->selectionModel(),
67                 SIGNAL(currentChanged(QModelIndex, QModelIndex)),
68                 this, SLOT(availableChanged(QModelIndex, QModelIndex)));
69         connect(selectedLV->selectionModel(),
70                 SIGNAL(currentChanged(QModelIndex, QModelIndex)),
71                 this, SLOT(selectedChanged(QModelIndex, QModelIndex)));
72         connect(availableLV->selectionModel(),
73                 SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
74                 this, SLOT(availableChanged(QItemSelection, QItemSelection)));
75         connect(availableLV->selectionModel(),
76                 SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
77                 this, SLOT(updateButtons()));
78         connect(selectedLV->selectionModel(),
79                 SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
80                 this, SLOT(selectedChanged(QItemSelection, QItemSelection)));
81         connect(selectedLV->selectionModel(),
82                 SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
83                 this, SLOT(updateButtons()));
84         connect(selectedLV->itemDelegate(), SIGNAL(commitData(QWidget*)),
85                 this, SLOT(selectedEdited()));
86         connect(addPB, SIGNAL(clicked()),
87                 this, SLOT(addPB_clicked()));
88         connect(deletePB, SIGNAL(clicked()),
89                 this, SLOT(deletePB_clicked()));
90         connect(upPB, SIGNAL(clicked()),
91                 this, SLOT(upPB_clicked()));
92         connect(downPB, SIGNAL(clicked()),
93                 this, SLOT(downPB_clicked()));
94         connect(availableLV, SIGNAL(doubleClicked(QModelIndex)),
95                 this, SLOT(availableLV_doubleClicked(QModelIndex)));
96
97         availableLV->installEventFilter(this);
98         selectedLV->installEventFilter(this);
99 }
100
101
102 void GuiSelectionManager::update()
103 {
104         updateAddPB();
105         updateDelPB();
106         updateDownPB();
107         updateUpPB();
108 }
109
110
111 void GuiSelectionManager::updateButtons()
112 {
113         update();
114         updateHook();
115 }
116
117
118 QModelIndex GuiSelectionManager::getSelectedIndex(int const c) const
119 {
120         QModelIndexList avail = availableLV->selectionModel()->selectedIndexes();
121         QModelIndexList sel   = selectedLV->selectionModel()->selectedRows(c);
122         bool const have_avl = !avail.isEmpty();
123         bool const have_sel = !sel.isEmpty();
124
125         if (selectedFocused()) {
126                 if (have_sel)
127                         return sel.front();
128                 if (have_avl)
129                         return avail.first();
130         }
131         else { // available has focus
132                 if (have_avl)
133                         return avail.first();
134                 if (have_sel)
135                         return sel.front();
136         }
137         return QModelIndex();
138 }
139
140
141 void GuiSelectionManager::updateAddPB()
142 {
143         int const arows = availableModel->rowCount();
144         QModelIndexList const availSels =
145                 availableLV->selectionModel()->selectedIndexes();
146         addPB->setEnabled(arows > 0 &&
147                 !availSels.isEmpty() &&
148                 (allow_multi_selection_ || !isSelected(availSels.first())));
149 }
150
151
152 void GuiSelectionManager::updateDelPB()
153 {
154         int const srows = selectedModel->rowCount();
155         if (srows == 0) {
156                 deletePB->setEnabled(false);
157                 return;
158         }
159         QModelIndexList const selSels =
160                 selectedLV->selectionModel()->selectedIndexes();
161         int const sel_nr = selSels.empty() ? -1 : selSels.first().row();
162         deletePB->setEnabled(sel_nr >= 0);
163 }
164
165
166 void GuiSelectionManager::updateUpPB()
167 {
168         int const srows = selectedModel->rowCount();
169         if (srows == 0) {
170                 upPB->setEnabled(false);
171                 return;
172         }
173         QModelIndexList const selSels =
174                         selectedLV->selectionModel()->selectedIndexes();
175         int const sel_nr = selSels.empty() ? -1 : selSels.first().row();
176         upPB->setEnabled(sel_nr > 0);
177 }
178
179
180 void GuiSelectionManager::updateDownPB()
181 {
182         int const srows = selectedModel->rowCount();
183         if (srows == 0) {
184                 downPB->setEnabled(false);
185                 return;
186         }
187         QModelIndexList const selSels =
188                         selectedLV->selectionModel()->selectedIndexes();
189         int const sel_nr = selSels.empty() ? -1 : selSels.first().row();
190         downPB->setEnabled(sel_nr >= 0 && sel_nr < srows - 1);
191 }
192
193
194 bool GuiSelectionManager::isSelected(const QModelIndex & idx)
195 {
196         if (selectedModel->rowCount() == 0)
197                 return false;
198         QVariant const & str = availableModel->data(idx, Qt::DisplayRole);
199         QModelIndexList qmil =
200                         selectedModel->match(selectedModel->index(0, main_sel_col_),
201                                              Qt::DisplayRole, str, 1,
202                                              Qt::MatchFlags(Qt::MatchExactly | Qt::MatchWrap));
203         return !qmil.empty();
204 }
205
206
207 void GuiSelectionManager::availableChanged(QItemSelection const & qis, QItemSelection const &)
208 {
209         QModelIndexList il = qis.indexes();
210         if (il.empty())
211                 return;
212         availableChanged(il.front(), QModelIndex());
213 }
214
215
216 void GuiSelectionManager::availableChanged(const QModelIndex & idx, const QModelIndex &)
217 {
218         if (!idx.isValid())
219                 return;
220
221         selectedHasFocus_ = false;
222         updateHook();
223 }
224
225
226 void GuiSelectionManager::selectedChanged(QItemSelection const & qis, QItemSelection const &)
227 {
228         QModelIndexList il = qis.indexes();
229         if (il.empty())
230                 return;
231         selectedChanged(il.front(), QModelIndex());
232 }
233
234
235 void GuiSelectionManager::selectedChanged(const QModelIndex & idx, const QModelIndex &)
236 {
237         if (!idx.isValid())
238                 return;
239
240         selectedHasFocus_ = true;
241         updateHook();
242 }
243
244
245 void GuiSelectionManager::selectedEdited()
246 {
247         selectionChanged();
248 }
249
250
251 bool GuiSelectionManager::insertRowToSelected(int i,
252                 QMap<int, QVariant> const & itemData)
253 {
254         if (i <= -1)
255                 i = 0;
256         if (i > selectedModel->rowCount())
257                 i = selectedModel->rowCount();
258         if (!selectedModel->insertRow(i))
259                 return false;
260         return selectedModel->setItemData(selectedModel->index(i, main_sel_col_), itemData);
261 }
262
263
264 bool GuiSelectionManager::insertRowToSelected(int i, QMap<int, QMap<int, QVariant>> & qms)
265 {
266         if (i <= -1)
267                 i = 0;
268         if (i > selectedModel->rowCount())
269                 i = selectedModel->rowCount();
270         if (!selectedModel->insertRow(i))
271                 return false;
272         bool res = true;
273         QMap<int, QMap<int, QVariant>>::const_iterator it = qms.constBegin();
274         for (; it != qms.constEnd(); ++it)
275                 res &= selectedModel->setItemData(selectedModel->index(i, it.key()), it.value());
276         return res;
277 }
278
279
280 void GuiSelectionManager::addPB_clicked()
281 {
282         QModelIndexList selIdx =
283                 availableLV->selectionModel()->selectedIndexes();
284         if (selIdx.isEmpty())
285                 return;
286
287         QModelIndex const idxToAdd = selIdx.first();
288         // Add item after selected item
289         int const currentRow = selectedLV->currentIndex().row();
290         int const srows = currentRow == -1 ? selectedModel->rowCount() :
291                                              currentRow + 1;
292
293         QMap<int, QVariant> qm = availableModel->itemData(idxToAdd);
294         bool const isAdded = insertRowToSelected(srows, qm);
295
296         selectionChanged(); //signal
297
298         QModelIndex const idx = selectedLV->currentIndex();
299         if (idx.isValid())
300                 selectedLV->setCurrentIndex(idx);
301
302         // select and show last added item
303         if (isAdded) {
304                 QModelIndex idx = selectedModel->index(srows, 0);
305                 selectedLV->setCurrentIndex(idx);
306         }
307
308         updateHook();
309 }
310
311
312 void GuiSelectionManager::deletePB_clicked()
313 {
314         QModelIndexList selIdx =
315                 selectedLV->selectionModel()->selectedIndexes();
316         if (selIdx.isEmpty())
317                 return;
318         QModelIndex idx = selIdx.first();
319
320         int const row = idx.row();
321
322         selectedModel->removeRow(row);
323         selectionChanged(); //signal
324
325         int nrows = selectedLV->model()->rowCount();
326
327         // select following item if one follows,
328         // otherwise use previous one if we have one
329         if (row < nrows)
330                 selectedLV->setCurrentIndex(selectedLV->model()->index(row, 0));
331         else if (nrows > 0 && row > 0)
332                 selectedLV->setCurrentIndex(selectedLV->model()->index(row - 1, 0));
333         selectedHasFocus_ = (nrows > 0);
334         updateHook();
335 }
336
337
338 void GuiSelectionManager::upPB_clicked()
339 {
340         QModelIndexList selIdx =
341                 selectedLV->selectionModel()->selectedIndexes();
342         if (selIdx.isEmpty())
343                 return;
344         QModelIndex idx = selIdx.first();
345
346         int const pos = idx.row();
347         if (pos <= 0)
348                 return;
349
350         QMap<int, QMap<int, QVariant>> qms;
351         QList<QModelIndex>::const_iterator it = selIdx.constBegin();
352         for (; it != selIdx.constEnd(); ++it)
353                 qms[it->column()] = selectedModel->itemData(*it);
354
355         selectedModel->removeRow(pos);
356         insertRowToSelected(pos - 1, qms);
357
358         idx = selIdx.first();
359         selectedLV->setCurrentIndex(idx.sibling(idx.row() - 1, idx.column()));
360         selectedHasFocus_ = true;
361         updateHook();
362 }
363
364
365 void GuiSelectionManager::downPB_clicked()
366 {
367         QModelIndexList selIdx =
368                 selectedLV->selectionModel()->selectedIndexes();
369         if (selIdx.isEmpty())
370                 return;
371         QModelIndex idx = selIdx.first();
372
373         int const pos = idx.row();
374         if (pos >= selectedModel->rowCount() - 1)
375                 return;
376
377         QMap<int, QMap<int, QVariant>> qms;
378         QList<QModelIndex>::const_iterator it = selIdx.constBegin();
379         for (; it != selIdx.constEnd(); ++it)
380                 qms[it->column()] = selectedModel->itemData(*it);
381
382         selectedModel->removeRow(pos);
383         insertRowToSelected(pos + 1, qms);
384
385         idx = selIdx.first();
386         selectedLV->setCurrentIndex(idx.sibling(idx.row() + 1, idx.column()));
387         selectedHasFocus_ = true;
388         updateHook();
389 }
390
391
392 void GuiSelectionManager::availableLV_doubleClicked(const QModelIndex & idx)
393 {
394         if (isSelected(idx) || !addPB->isEnabled())
395                 return;
396
397         if (idx.isValid())
398                 selectedHasFocus_ = false;
399         addPB_clicked();
400         //updateHook() will be emitted there
401 }
402
403
404 bool GuiSelectionManager::eventFilter(QObject * obj, QEvent * event)
405 {
406         QEvent::Type etype = event->type();
407         if (obj == availableLV) {
408                 if (etype == QEvent::KeyPress) {
409                         QKeyEvent * keyEvent = static_cast<QKeyEvent *>(event);
410                         int const keyPressed = keyEvent->key();
411                         Qt::KeyboardModifiers const keyModifiers = keyEvent->modifiers();
412                         // Enter key without modifier will add current item.
413                         // Ctrl-Enter will add it and close the dialog.
414                         // This is designed to work both with the main enter key
415                         // and the one on the numeric keypad.
416                         if (keyPressed == Qt::Key_Enter || keyPressed == Qt::Key_Return) {
417                                 if (!keyModifiers ||
418                                     keyModifiers == Qt::ControlModifier ||
419                                     keyModifiers == Qt::KeypadModifier  ||
420                                     keyModifiers == (Qt::ControlModifier
421                                                      | Qt::KeypadModifier)) {
422                                         if (addPB->isEnabled()) {
423                                                 addPB_clicked();
424                                         }
425                                         if (keyModifiers)
426                                                 okHook(); //signal
427                                 }
428                                 event->accept();
429                                 return true;
430                         }
431                         else if (keyPressed == Qt::Key_Right) {
432                                 QModelIndex const idx = availableLV->currentIndex();
433                                 if (availableLV->model()->hasChildren(idx)) { // skip for headers
434                                         return false;
435                                 }
436                                 focusAndHighlight(selectedLV);
437                                 event->accept();
438                                 return true;
439                         }
440                 } else if (etype == QEvent::FocusIn) {
441                         if (selectedHasFocus_) {
442                                 selectedHasFocus_ = false;
443                                 updateHook();
444                         }
445                         return false;
446                 }
447         } else if (obj == selectedLV) {
448                 if (etype == QEvent::KeyPress) {
449                         QKeyEvent * keyEvent = static_cast<QKeyEvent *>(event);
450                         int const keyPressed = keyEvent->key();
451                         Qt::KeyboardModifiers const keyModifiers = keyEvent->modifiers();
452                         // Delete or backspace key will delete current item
453                         // ...with control modifier will clear the list
454                         if (keyPressed == Qt::Key_Delete || keyPressed == Qt::Key_Backspace) {
455                                 if (keyModifiers == Qt::NoModifier && deletePB->isEnabled()) {
456                                         deletePB_clicked();
457                                         updateHook();
458                                 } else if (keyModifiers == Qt::ControlModifier) {
459                                         selectedModel->removeRows(0, selectedModel->rowCount());
460                                         updateHook();
461                                 } else
462                                         return QObject::eventFilter(obj, event);
463                         }
464                         // Ctrl-Up activates upPB
465                         else if (keyPressed == Qt::Key_Up) {
466                                 if (keyModifiers == Qt::ControlModifier) {
467                                         if (upPB->isEnabled())
468                                                 upPB_clicked();
469                                         event->accept();
470                                         return true;
471                                 }
472                         }
473                         // Ctrl-Down activates downPB
474                         else if (keyPressed == Qt::Key_Down) {
475                                 if (keyModifiers == Qt::ControlModifier) {
476                                         if (downPB->isEnabled())
477                                                 downPB_clicked();
478                                         event->accept();
479                                         return true;
480                                 }
481                         }
482                         else if (keyPressed == Qt::Key_Left) {
483                                 focusAndHighlight(availableLV);
484                                 event->accept();
485                                 return true;
486                         }
487                 } else if (etype == QEvent::FocusIn) {
488                         if (!selectedHasFocus_) {
489                                 selectedHasFocus_ = true;
490                                 updateHook();
491                         }
492                         return false;
493                 }
494         }
495         return QObject::eventFilter(obj, event);
496 }
497
498 } // namespace frontend
499 } // namespace lyx
500
501 #include "moc_GuiSelectionManager.cpp"