]> git.lyx.org Git - lyx.git/blob - src/frontends/qt/GuiSelectionManager.cpp
Fix completion in math when inline completion was not yet shown
[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         int const srows = selectedModel->rowCount();
289
290         QMap<int, QVariant> qm = availableModel->itemData(idxToAdd);
291         insertRowToSelected(srows, qm);
292
293         selectionChanged(); //signal
294         
295         QModelIndex const idx = selectedLV->currentIndex();
296         if (idx.isValid())
297                 selectedLV->setCurrentIndex(idx);
298
299         updateHook();
300 }
301
302
303 void GuiSelectionManager::deletePB_clicked()
304 {
305         QModelIndexList selIdx =
306                 selectedLV->selectionModel()->selectedIndexes();
307         if (selIdx.isEmpty())
308                 return;
309         QModelIndex idx = selIdx.first();
310
311         int const row = idx.row();
312         int nrows = selectedLV->model()->rowCount();
313
314         selectedModel->removeRow(row);
315         selectionChanged(); //signal
316
317         // select previous item
318         if (nrows > 0)
319                 selectedLV->setCurrentIndex(selectedLV->model()->index(row - 1, 0));
320         else if (nrows == 0)
321                 selectedLV->setCurrentIndex(selectedLV->model()->index(0, 0));
322         selectedHasFocus_ = (nrows > 1);
323         updateHook();
324 }
325
326
327 void GuiSelectionManager::upPB_clicked()
328 {
329         QModelIndexList selIdx =
330                 selectedLV->selectionModel()->selectedIndexes();
331         if (selIdx.isEmpty())
332                 return;
333         QModelIndex idx = selIdx.first();
334
335         int const pos = idx.row();
336         if (pos <= 0)
337                 return;
338
339         QMap<int, QMap<int, QVariant>> qms;
340         QList<QModelIndex>::const_iterator it = selIdx.constBegin();
341         for (; it != selIdx.constEnd(); ++it)
342                 qms[it->column()] = selectedModel->itemData(*it);
343
344         selectedModel->removeRow(pos);
345         insertRowToSelected(pos - 1, qms);
346
347         idx = selIdx.first();
348         selectedLV->setCurrentIndex(idx.sibling(idx.row() - 1, idx.column()));
349         selectedHasFocus_ = true;
350         updateHook();
351 }
352
353
354 void GuiSelectionManager::downPB_clicked()
355 {
356         QModelIndexList selIdx =
357                 selectedLV->selectionModel()->selectedIndexes();
358         if (selIdx.isEmpty())
359                 return;
360         QModelIndex idx = selIdx.first();
361
362         int const pos = idx.row();
363         if (pos >= selectedModel->rowCount() - 1)
364                 return;
365
366         QMap<int, QMap<int, QVariant>> qms;
367         QList<QModelIndex>::const_iterator it = selIdx.constBegin();
368         for (; it != selIdx.constEnd(); ++it)
369                 qms[it->column()] = selectedModel->itemData(*it);
370
371         selectedModel->removeRow(pos);
372         insertRowToSelected(pos + 1, qms);
373
374         idx = selIdx.first();
375         selectedLV->setCurrentIndex(idx.sibling(idx.row() + 1, idx.column()));
376         selectedHasFocus_ = true;
377         updateHook();
378 }
379
380
381 void GuiSelectionManager::availableLV_doubleClicked(const QModelIndex & idx)
382 {
383         if (isSelected(idx) || !addPB->isEnabled())
384                 return;
385
386         if (idx.isValid())
387                 selectedHasFocus_ = false;
388         addPB_clicked();
389         //updateHook() will be emitted there
390 }
391
392
393 bool GuiSelectionManager::eventFilter(QObject * obj, QEvent * event)
394 {
395         QEvent::Type etype = event->type();
396         if (obj == availableLV) {
397                 if (etype == QEvent::KeyPress) {
398                         QKeyEvent * keyEvent = static_cast<QKeyEvent *>(event);
399                         int const keyPressed = keyEvent->key();
400                         Qt::KeyboardModifiers const keyModifiers = keyEvent->modifiers();
401                         // Enter key without modifier will add current item.
402                         // Ctrl-Enter will add it and close the dialog.
403                         // This is designed to work both with the main enter key
404                         // and the one on the numeric keypad.
405                         if (keyPressed == Qt::Key_Enter || keyPressed == Qt::Key_Return) {
406                                 if (!keyModifiers ||
407                                     keyModifiers == Qt::ControlModifier ||
408                                     keyModifiers == Qt::KeypadModifier  ||
409                                     keyModifiers == (Qt::ControlModifier
410                                                      | Qt::KeypadModifier)) {
411                                         if (addPB->isEnabled()) {
412                                                 addPB_clicked();
413                                         }
414                                         if (keyModifiers)
415                                                 okHook(); //signal
416                                 }
417                                 event->accept();
418                                 return true;
419                         }
420                         else if (keyPressed == Qt::Key_Right) {
421                                 QModelIndex const idx = availableLV->currentIndex();
422                                 if (availableLV->model()->hasChildren(idx)) { // skip for headers
423                                         return false;
424                                 }
425                                 focusAndHighlight(selectedLV);
426                                 event->accept();
427                                 return true;
428                         }
429                 } else if (etype == QEvent::FocusIn) {
430                         if (selectedHasFocus_) {
431                                 selectedHasFocus_ = false;
432                                 updateHook();
433                         }
434                         return false;
435                 }
436         } else if (obj == selectedLV) {
437                 if (etype == QEvent::KeyPress) {
438                         QKeyEvent * keyEvent = static_cast<QKeyEvent *>(event);
439                         int const keyPressed = keyEvent->key();
440                         Qt::KeyboardModifiers const keyModifiers = keyEvent->modifiers();
441                         // Delete or backspace key will delete current item
442                         // ...with control modifier will clear the list
443                         if (keyPressed == Qt::Key_Delete || keyPressed == Qt::Key_Backspace) {
444                                 if (keyModifiers == Qt::NoModifier && deletePB->isEnabled()) {
445                                         deletePB_clicked();
446                                         updateHook();
447                                 } else if (keyModifiers == Qt::ControlModifier) {
448                                         selectedModel->removeRows(0, selectedModel->rowCount());
449                                         updateHook();
450                                 } else
451                                         return QObject::eventFilter(obj, event);
452                         }
453                         // Ctrl-Up activates upPB
454                         else if (keyPressed == Qt::Key_Up) {
455                                 if (keyModifiers == Qt::ControlModifier) {
456                                         if (upPB->isEnabled())
457                                                 upPB_clicked();
458                                         event->accept();
459                                         return true;
460                                 }
461                         }
462                         // Ctrl-Down activates downPB
463                         else if (keyPressed == Qt::Key_Down) {
464                                 if (keyModifiers == Qt::ControlModifier) {
465                                         if (downPB->isEnabled())
466                                                 downPB_clicked();
467                                         event->accept();
468                                         return true;
469                                 }
470                         }
471                         else if (keyPressed == Qt::Key_Left) {
472                                 focusAndHighlight(availableLV);
473                                 event->accept();
474                                 return true;
475                         }
476                 } else if (etype == QEvent::FocusIn) {
477                         if (!selectedHasFocus_) {
478                                 selectedHasFocus_ = true;
479                                 updateHook();
480                         }
481                         return false;
482                 }
483         }
484         return QObject::eventFilter(obj, event);
485 }
486
487 } // namespace frontend
488 } // namespace lyx
489
490 #include "moc_GuiSelectionManager.cpp"