]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiSelectionManager.cpp
Use <cstdint> instead of <boost/cstdint.hpp>
[lyx.git] / src / frontends / qt4 / 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 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 {
60         selectedLV->setModel(smod);
61         availableLV->setModel(amod);
62         selectedLV->setSelectionBehavior(QAbstractItemView::SelectRows);
63         selectedLV->setSelectionMode(QAbstractItemView::SingleSelection);
64
65         connect(availableLV->selectionModel(),
66                 SIGNAL(currentChanged(QModelIndex, QModelIndex)),
67                 this, SLOT(availableChanged(QModelIndex, QModelIndex)));
68         connect(selectedLV->selectionModel(),
69                 SIGNAL(currentChanged(QModelIndex, QModelIndex)),
70                 this, SLOT(selectedChanged(QModelIndex, QModelIndex)));
71         connect(availableLV->selectionModel(),
72                 SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
73                 this, SLOT(availableChanged(QItemSelection, QItemSelection)));
74         connect(availableLV->selectionModel(),
75                 SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
76                 this, SLOT(updateButtons()));
77         connect(selectedLV->selectionModel(),
78                 SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
79                 this, SLOT(selectedChanged(QItemSelection, QItemSelection)));
80         connect(selectedLV->selectionModel(),
81                 SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
82                 this, SLOT(updateButtons()));
83         connect(selectedLV->itemDelegate(), SIGNAL(commitData(QWidget*)),
84                 this, SLOT(selectedEdited()));
85         connect(addPB, SIGNAL(clicked()),
86                 this, SLOT(addPB_clicked()));
87         connect(deletePB, SIGNAL(clicked()),
88                 this, SLOT(deletePB_clicked()));
89         connect(upPB, SIGNAL(clicked()),
90                 this, SLOT(upPB_clicked()));
91         connect(downPB, SIGNAL(clicked()),
92                 this, SLOT(downPB_clicked()));
93         connect(availableLV, SIGNAL(doubleClicked(QModelIndex)),
94                 this, SLOT(availableLV_doubleClicked(QModelIndex)));
95
96         availableLV->installEventFilter(this);
97         selectedLV->installEventFilter(this);
98 }
99
100
101 void GuiSelectionManager::update()
102 {
103         updateAddPB();
104         updateDelPB();
105         updateDownPB();
106         updateUpPB();
107 }
108
109
110 void GuiSelectionManager::updateButtons()
111 {
112         update();
113         updateHook();
114 }
115
116
117 QModelIndex GuiSelectionManager::getSelectedIndex(int const c) const
118 {
119         QModelIndexList avail = availableLV->selectionModel()->selectedRows(c);
120         QModelIndexList sel   = selectedLV->selectionModel()->selectedRows(c);
121         bool const have_avl = !avail.isEmpty();
122         bool const have_sel = !sel.isEmpty();
123
124         if (selectedFocused()) {
125                 if (have_sel)
126                         return sel.front();
127                 if (have_avl)
128                         return avail.front();
129         }
130         else { // available has focus
131                 if (have_avl)
132                         return avail.front();
133                 if (have_sel)
134                         return sel.front();
135         }
136         return QModelIndex();
137 }
138
139
140 void GuiSelectionManager::updateAddPB()
141 {
142         int const arows = availableModel->rowCount();
143         QModelIndexList const availSels =
144                 availableLV->selectionModel()->selectedIndexes();
145         addPB->setEnabled(arows > 0 &&
146                 !availSels.isEmpty() &&
147                 !isSelected(availSels.first()));
148 }
149
150
151 void GuiSelectionManager::updateDelPB()
152 {
153         int const srows = selectedModel->rowCount();
154         if (srows == 0) {
155                 deletePB->setEnabled(false);
156                 return;
157         }
158         QModelIndexList const selSels =
159                 selectedLV->selectionModel()->selectedIndexes();
160         int const sel_nr = selSels.empty() ? -1 : selSels.first().row();
161         deletePB->setEnabled(sel_nr >= 0);
162 }
163
164
165 void GuiSelectionManager::updateUpPB()
166 {
167         int const srows = selectedModel->rowCount();
168         if (srows == 0) {
169                 upPB->setEnabled(false);
170                 return;
171         }
172         QModelIndexList const selSels =
173                         selectedLV->selectionModel()->selectedIndexes();
174         int const sel_nr = selSels.empty() ? -1 : selSels.first().row();
175         upPB->setEnabled(sel_nr > 0);
176 }
177
178
179 void GuiSelectionManager::updateDownPB()
180 {
181         int const srows = selectedModel->rowCount();
182         if (srows == 0) {
183                 downPB->setEnabled(false);
184                 return;
185         }
186         QModelIndexList const selSels =
187                         selectedLV->selectionModel()->selectedIndexes();
188         int const sel_nr = selSels.empty() ? -1 : selSels.first().row();
189         downPB->setEnabled(sel_nr >= 0 && sel_nr < srows - 1);
190 }
191
192
193 bool GuiSelectionManager::isSelected(const QModelIndex & idx)
194 {
195         if (selectedModel->rowCount() == 0)
196                 return false;
197         QVariant const & str = availableModel->data(idx, Qt::DisplayRole);
198         QModelIndexList qmil =
199                         selectedModel->match(selectedModel->index(0, main_sel_col_),
200                                              Qt::DisplayRole, str, 1,
201                                              Qt::MatchFlags(Qt::MatchExactly | Qt::MatchWrap));
202         return !qmil.empty();
203 }
204
205
206 void GuiSelectionManager::availableChanged(QItemSelection const & qis, QItemSelection const &)
207 {
208         QModelIndexList il = qis.indexes();
209         if (il.empty())
210                 return;
211         availableChanged(il.front(), QModelIndex());
212 }
213
214
215 void GuiSelectionManager::availableChanged(const QModelIndex & idx, const QModelIndex &)
216 {
217         if (!idx.isValid())
218                 return;
219
220         selectedHasFocus_ = false;
221         updateHook();
222 }
223
224
225 void GuiSelectionManager::selectedChanged(QItemSelection const & qis, QItemSelection const &)
226 {
227         QModelIndexList il = qis.indexes();
228         if (il.empty())
229                 return;
230         selectedChanged(il.front(), QModelIndex());
231 }
232
233
234 void GuiSelectionManager::selectedChanged(const QModelIndex & idx, const QModelIndex &)
235 {
236         if (!idx.isValid())
237                 return;
238
239         selectedHasFocus_ = true;
240         updateHook();
241 }
242
243
244 void GuiSelectionManager::selectedEdited()
245 {
246         selectionChanged();
247 }
248
249
250 bool GuiSelectionManager::insertRowToSelected(int i,
251                 QMap<int, QVariant> const & itemData)
252 {
253         if (i <= -1)
254                 i = 0;
255         if (i > selectedModel->rowCount())
256                 i = selectedModel->rowCount();
257         if (!selectedModel->insertRow(i))
258                 return false;
259         return selectedModel->setItemData(selectedModel->index(i, main_sel_col_), itemData);
260 }
261
262
263 bool GuiSelectionManager::insertRowToSelected(int i, QMap<int, QMap<int, QVariant>> & qms)
264 {
265         if (i <= -1)
266                 i = 0;
267         if (i > selectedModel->rowCount())
268                 i = selectedModel->rowCount();
269         if (!selectedModel->insertRow(i))
270                 return false;
271         bool res = true;
272         QMap<int, QMap<int, QVariant>>::const_iterator it = qms.constBegin();
273         for (; it != qms.constEnd(); ++it)
274                 res &= selectedModel->setItemData(selectedModel->index(i, it.key()), it.value());
275         return res;
276 }
277
278
279 void GuiSelectionManager::addPB_clicked()
280 {
281         QModelIndexList selIdx =
282                 availableLV->selectionModel()->selectedIndexes();
283         if (selIdx.isEmpty())
284                 return;
285
286         QModelIndex const idxToAdd = selIdx.first();
287         QModelIndex const idx = selectedLV->currentIndex();
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         if (idx.isValid())
296                 selectedLV->setCurrentIndex(idx);
297
298         updateHook();
299 }
300
301
302 void GuiSelectionManager::deletePB_clicked()
303 {
304         QModelIndexList selIdx =
305                 selectedLV->selectionModel()->selectedIndexes();
306         if (selIdx.isEmpty())
307                 return;
308         QModelIndex idx = selIdx.first();
309
310         int const row = idx.row();
311         int nrows = selectedLV->model()->rowCount();
312
313         selectedModel->removeRow(row);
314         selectionChanged(); //signal
315
316         // select previous item
317         if (nrows > 0)
318                 selectedLV->setCurrentIndex(selectedLV->model()->index(row - 1, 0));
319         else if (nrows == 0)
320                 selectedLV->setCurrentIndex(selectedLV->model()->index(0, 0));
321         selectedHasFocus_ = (nrows > 1);
322         updateHook();
323 }
324
325
326 void GuiSelectionManager::upPB_clicked()
327 {
328         QModelIndexList selIdx =
329                 selectedLV->selectionModel()->selectedIndexes();
330         if (selIdx.isEmpty())
331                 return;
332         QModelIndex idx = selIdx.first();
333
334         int const pos = idx.row();
335         if (pos <= 0)
336                 return;
337
338         QMap<int, QMap<int, QVariant>> qms;
339         QList<QModelIndex>::const_iterator it = selIdx.constBegin();
340         for (; it != selIdx.constEnd(); ++it)
341                 qms[it->column()] = selectedModel->itemData(*it);
342
343         selectedModel->removeRow(pos);
344         insertRowToSelected(pos - 1, qms);
345
346         selectionChanged(); //signal
347
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         selectionChanged(); //signal
375
376         selectedLV->setCurrentIndex(idx.sibling(idx.row() + 1, idx.column()));
377         selectedHasFocus_ = true;
378         updateHook();
379 }
380
381
382 void GuiSelectionManager::availableLV_doubleClicked(const QModelIndex & idx)
383 {
384         if (isSelected(idx) || !addPB->isEnabled())
385                 return;
386
387         if (idx.isValid())
388                 selectedHasFocus_ = false;
389         addPB_clicked();
390         //updateHook() will be emitted there
391 }
392
393
394 bool GuiSelectionManager::eventFilter(QObject * obj, QEvent * event)
395 {
396         QEvent::Type etype = event->type();
397         if (obj == availableLV) {
398                 if (etype == QEvent::KeyPress) {
399                         QKeyEvent * keyEvent = static_cast<QKeyEvent *>(event);
400                         int const keyPressed = keyEvent->key();
401                         Qt::KeyboardModifiers const keyModifiers = keyEvent->modifiers();
402                         // Enter key without modifier will add current item.
403                         // Ctrl-Enter will add it and close the dialog.
404                         // This is designed to work both with the main enter key
405                         // and the one on the numeric keypad.
406                         if (keyPressed == Qt::Key_Enter || keyPressed == Qt::Key_Return) {
407                                 if (!keyModifiers ||
408                                     keyModifiers == Qt::ControlModifier ||
409                                     keyModifiers == Qt::KeypadModifier  ||
410                                     keyModifiers == (Qt::ControlModifier
411                                                      | Qt::KeypadModifier)) {
412                                         if (addPB->isEnabled()) {
413                                                 addPB_clicked();
414                                         }
415                                         if (keyModifiers)
416                                                 okHook(); //signal
417                                 }
418                                 event->accept();
419                                 return true;
420                         }
421                         else if (keyPressed == Qt::Key_Right) {
422                                 focusAndHighlight(selectedLV);
423                                 event->accept();
424                                 return true;
425                         }
426                 } else if (etype == QEvent::FocusIn) {
427                         if (selectedHasFocus_) {
428                                 selectedHasFocus_ = false;
429                                 updateHook();
430                         }
431                         return false;
432                 }
433         } else if (obj == selectedLV) {
434                 if (etype == QEvent::KeyPress) {
435                         QKeyEvent * keyEvent = static_cast<QKeyEvent *>(event);
436                         int const keyPressed = keyEvent->key();
437                         Qt::KeyboardModifiers const keyModifiers = keyEvent->modifiers();
438                         // Delete or backspace key will delete current item
439                         // ...with control modifier will clear the list
440                         if (keyPressed == Qt::Key_Delete || keyPressed == Qt::Key_Backspace) {
441                                 if (keyModifiers == Qt::NoModifier && deletePB->isEnabled()) {
442                                         deletePB_clicked();
443                                         updateHook();
444                                 } else if (keyModifiers == Qt::ControlModifier) {
445                                         selectedModel->removeRows(0, selectedModel->rowCount());
446                                         updateHook();
447                                 } else
448                                         return QObject::eventFilter(obj, event);
449                         }
450                         // Ctrl-Up activates upPB
451                         else if (keyPressed == Qt::Key_Up) {
452                                 if (keyModifiers == Qt::ControlModifier) {
453                                         if (upPB->isEnabled())
454                                                 upPB_clicked();
455                                         event->accept();
456                                         return true;
457                                 }
458                         }
459                         // Ctrl-Down activates downPB
460                         else if (keyPressed == Qt::Key_Down) {
461                                 if (keyModifiers == Qt::ControlModifier) {
462                                         if (downPB->isEnabled())
463                                                 downPB_clicked();
464                                         event->accept();
465                                         return true;
466                                 }
467                         }
468                         else if (keyPressed == Qt::Key_Left) {
469                                 focusAndHighlight(availableLV);
470                                 event->accept();
471                                 return true;
472                         }
473                 } else if (etype == QEvent::FocusIn) {
474                         if (!selectedHasFocus_) {
475                                 selectedHasFocus_ = true;
476                                 updateHook();
477                         }
478                         return false;
479                 }
480         }
481         return QObject::eventFilter(obj, event);
482 }
483
484 } // namespace frontend
485 } // namespace lyx
486
487 #include "moc_GuiSelectionManager.cpp"