]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiSelectionManager.cpp
ed2c640e4ffed720456784e50ffadd15e36eec0c
[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                                          QAbstractListModel * 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()->selectedIndexes();
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         selectedModel->removeRow(idx.row());
310         selectionChanged(); //signal
311
312         int nrows = selectedLV->model()->rowCount();
313         if (idx.row() == nrows) //was last item on list
314                 idx = idx.sibling(idx.row() - 1, idx.column());
315
316         if (nrows > 1)
317                 selectedLV->setCurrentIndex(idx);
318         else if (nrows == 1)
319                 selectedLV->setCurrentIndex(selectedLV->model()->index(0, 0));
320         selectedHasFocus_ = (nrows > 0);
321         updateHook();
322 }
323
324
325 void GuiSelectionManager::upPB_clicked()
326 {
327         QModelIndexList selIdx =
328                 selectedLV->selectionModel()->selectedIndexes();
329         if (selIdx.isEmpty())
330                 return;
331         QModelIndex idx = selIdx.first();
332
333         int const pos = idx.row();
334         if (pos <= 0)
335                 return;
336
337         QMap<int, QMap<int, QVariant>> qms;
338         QList<QModelIndex>::const_iterator it = selIdx.constBegin();
339         for (; it != selIdx.constEnd(); ++it)
340                 qms[it->column()] = selectedModel->itemData(*it);
341
342         selectedModel->removeRow(pos);
343         insertRowToSelected(pos - 1, qms);
344
345         selectionChanged(); //signal
346
347         selectedLV->setCurrentIndex(idx.sibling(idx.row() - 1, idx.column()));
348         selectedHasFocus_ = true;
349         updateHook();
350 }
351
352
353 void GuiSelectionManager::downPB_clicked()
354 {
355         QModelIndexList selIdx =
356                 selectedLV->selectionModel()->selectedIndexes();
357         if (selIdx.isEmpty())
358                 return;
359         QModelIndex idx = selIdx.first();
360
361         int const pos = idx.row();
362         if (pos >= selectedModel->rowCount() - 1)
363                 return;
364
365         QMap<int, QMap<int, QVariant>> qms;
366         QList<QModelIndex>::const_iterator it = selIdx.constBegin();
367         for (; it != selIdx.constEnd(); ++it)
368                 qms[it->column()] = selectedModel->itemData(*it);
369
370         selectedModel->removeRow(pos);
371         insertRowToSelected(pos + 1, qms);
372
373         selectionChanged(); //signal
374
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                                 focusAndHighlight(selectedLV);
422                                 event->accept();
423                                 return true;
424                         }
425                 } else if (etype == QEvent::FocusIn) {
426                         if (selectedHasFocus_) {
427                                 selectedHasFocus_ = false;
428                                 updateHook();
429                         }
430                         return false;
431                 }
432         } else if (obj == selectedLV) {
433                 if (etype == QEvent::KeyPress) {
434                         QKeyEvent * keyEvent = static_cast<QKeyEvent *>(event);
435                         int const keyPressed = keyEvent->key();
436                         Qt::KeyboardModifiers const keyModifiers = keyEvent->modifiers();
437                         // Delete or backspace key will delete current item
438                         // ...with control modifier will clear the list
439                         if (keyPressed == Qt::Key_Delete || keyPressed == Qt::Key_Backspace) {
440                                 if (keyModifiers == Qt::NoModifier && deletePB->isEnabled()) {
441                                         deletePB_clicked();
442                                         updateHook();
443                                 } else if (keyModifiers == Qt::ControlModifier) {
444                                         selectedModel->removeRows(0, selectedModel->rowCount());
445                                         updateHook();
446                                 } else
447                                         return QObject::eventFilter(obj, event);
448                         }
449                         // Ctrl-Up activates upPB
450                         else if (keyPressed == Qt::Key_Up) {
451                                 if (keyModifiers == Qt::ControlModifier) {
452                                         if (upPB->isEnabled())
453                                                 upPB_clicked();
454                                         event->accept();
455                                         return true;
456                                 }
457                         }
458                         // Ctrl-Down activates downPB
459                         else if (keyPressed == Qt::Key_Down) {
460                                 if (keyModifiers == Qt::ControlModifier) {
461                                         if (downPB->isEnabled())
462                                                 downPB_clicked();
463                                         event->accept();
464                                         return true;
465                                 }
466                         }
467                         else if (keyPressed == Qt::Key_Left) {
468                                 focusAndHighlight(availableLV);
469                                 event->accept();
470                                 return true;
471                         }
472                 } else if (etype == QEvent::FocusIn) {
473                         if (!selectedHasFocus_) {
474                                 selectedHasFocus_ = true;
475                                 updateHook();
476                         }
477                         return false;
478                 }
479         }
480         return QObject::eventFilter(obj, event);
481 }
482
483 } // namespace frontend
484 } // namespace lyx
485
486 #include "moc_GuiSelectionManager.cpp"