]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiSelectionManager.cpp
GuiCitation: Do not allow to add entries by pressing Enter when the Add button is...
[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
19 #include "support/debug.h"
20
21 #include <QAbstractListModel>
22 #include <QItemSelection>
23 #include <QListView>
24 #include <QKeyEvent>
25 #include <QPushButton>
26
27 #ifdef KeyPress
28 #undef KeyPress
29 #endif
30
31 #ifdef ControlModifier
32 #undef ControlModifier
33 #endif
34
35 #ifdef FocusIn
36 #undef FocusIn
37 #endif
38
39
40 namespace lyx {
41 namespace frontend {
42
43 GuiSelectionManager::GuiSelectionManager(
44         QAbstractItemView * avail,
45         QListView * sel,
46         QPushButton * add, 
47         QPushButton * del, 
48         QPushButton * up, 
49         QPushButton * down,
50         QAbstractListModel * amod,
51         QAbstractListModel * smod)
52   : availableLV(avail), selectedLV(sel), addPB(add), deletePB(del),
53                 upPB(up), downPB(down), availableModel(amod), selectedModel(smod),
54     selectedHasFocus_(false)
55 {
56         
57         selectedLV->setModel(smod);
58         availableLV->setModel(amod);
59         
60         connect(availableLV->selectionModel(),
61                 SIGNAL(currentChanged(QModelIndex, QModelIndex)),
62                 this, SLOT(availableChanged(QModelIndex, QModelIndex)));
63         connect(selectedLV->selectionModel(),
64                 SIGNAL(currentChanged(QModelIndex, QModelIndex)),
65                 this, SLOT(selectedChanged(QModelIndex, QModelIndex)));
66         connect(availableLV->selectionModel(),
67                 SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
68                 this, SLOT(availableChanged(QItemSelection, QItemSelection)));
69         connect(selectedLV->selectionModel(),
70                 SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
71                 this, SLOT(selectedChanged(QItemSelection, QItemSelection)));
72         connect(addPB, SIGNAL(clicked()), 
73                 this, SLOT(addPB_clicked()));
74         connect(deletePB, SIGNAL(clicked()), 
75                 this, SLOT(deletePB_clicked()));
76         connect(upPB, SIGNAL(clicked()), 
77                 this, SLOT(upPB_clicked()));
78         connect(downPB, SIGNAL(clicked()), 
79                 this, SLOT(downPB_clicked()));
80         connect(availableLV, SIGNAL(doubleClicked(QModelIndex)), 
81                 this, SLOT(availableLV_doubleClicked(QModelIndex)));
82         
83         availableLV->installEventFilter(this);
84         selectedLV->installEventFilter(this);
85 }
86
87
88 void GuiSelectionManager::update()
89 {
90         updateAddPB();
91         updateDelPB();
92         updateDownPB();
93         updateUpPB();
94 }
95
96
97 QModelIndex GuiSelectionManager::getSelectedIndex() const
98 {
99         QModelIndexList avail = availableLV->selectionModel()->selectedIndexes();
100         QModelIndexList sel   = selectedLV->selectionModel()->selectedIndexes();
101         bool const have_avl = !avail.isEmpty();
102         bool const have_sel = !sel.isEmpty();
103
104         if (selectedFocused()) { 
105                 if (have_sel)
106                         return sel.front();
107                 if (have_avl)
108                         return avail.front();
109         } 
110         else { // available has focus
111                 if (have_avl)
112                         return avail.front();
113                 if (have_sel)
114                         return sel.front();
115         }
116         return QModelIndex();
117 }
118
119
120 void GuiSelectionManager::updateAddPB()
121 {
122         int const arows = availableModel->rowCount();
123         QModelIndexList const availSels = 
124                 availableLV->selectionModel()->selectedIndexes();
125         addPB->setEnabled(arows > 0 &&
126                 !availSels.isEmpty() &&
127                 !isSelected(availSels.first()));
128 }
129
130
131 void GuiSelectionManager::updateDelPB()
132 {
133         int const srows = selectedModel->rowCount();
134         if (srows == 0) {
135                 deletePB->setEnabled(false);
136                 return;
137         }
138         QModelIndexList const selSels = 
139                 selectedLV->selectionModel()->selectedIndexes();
140         int const sel_nr =      selSels.empty() ? -1 : selSels.first().row();
141         deletePB->setEnabled(sel_nr >= 0);
142 }
143
144
145 void GuiSelectionManager::updateUpPB()
146 {
147         int const srows = selectedModel->rowCount();
148         if (srows == 0) {
149                 upPB->setEnabled(false);
150                 return;
151         }
152         QModelIndexList const selSels = 
153                         selectedLV->selectionModel()->selectedIndexes();
154         int const sel_nr =      selSels.empty() ? -1 : selSels.first().row();
155         upPB->setEnabled(sel_nr > 0);
156 }
157
158
159 void GuiSelectionManager::updateDownPB()
160 {
161         int const srows = selectedModel->rowCount();
162         if (srows == 0) {
163                 downPB->setEnabled(false);
164                 return;
165         }
166         QModelIndexList const selSels = 
167                         selectedLV->selectionModel()->selectedIndexes();
168         int const sel_nr =      selSels.empty() ? -1 : selSels.first().row();
169         downPB->setEnabled(sel_nr >= 0 && sel_nr < srows - 1);
170 }
171
172
173 bool GuiSelectionManager::isSelected(const QModelIndex & idx)
174 {
175         if (selectedModel->rowCount() == 0)
176                 return false;
177         QVariant const & str = availableModel->data(idx, Qt::DisplayRole);
178         QModelIndexList qmil = 
179                         selectedModel->match(selectedModel->index(0), 
180                                              Qt::DisplayRole, str, 1,
181                                              Qt::MatchFlags(Qt::MatchExactly | Qt::MatchWrap));
182         return !qmil.empty();
183 }
184
185
186 void GuiSelectionManager::availableChanged(QItemSelection const & qis, QItemSelection const &)
187 {
188         QModelIndexList il = qis.indexes();
189         if (il.empty()) 
190                 return;
191         availableChanged(il.front(), QModelIndex());
192 }
193
194
195 void GuiSelectionManager::availableChanged(const QModelIndex & idx, const QModelIndex &)
196 {
197         if (!idx.isValid())
198                 return;
199         
200         selectedHasFocus_ = false;
201         updateHook();
202 }
203
204
205 void GuiSelectionManager::selectedChanged(QItemSelection const & qis, QItemSelection const &)
206 {
207         QModelIndexList il = qis.indexes();
208         if (il.empty()) 
209                 return;
210         selectedChanged(il.front(), QModelIndex());
211 }
212
213
214 void GuiSelectionManager::selectedChanged(const QModelIndex & idx, const QModelIndex &)
215 {
216         if (!idx.isValid())
217                 return;
218         
219         selectedHasFocus_ = true;
220         updateHook();
221 }
222
223
224 bool GuiSelectionManager::insertRowToSelected(int i, 
225                 QMap<int, QVariant> const & itemData)
226 {
227         if (i <= -1)
228                 i = 0;
229         if (i > selectedModel->rowCount())
230                 i = selectedModel->rowCount();
231         if (!selectedModel->insertRow(i))
232                 return false;
233         return selectedModel->setItemData(selectedModel->index(i), itemData);
234 }
235
236
237 void GuiSelectionManager::addPB_clicked()
238 {
239         QModelIndexList selIdx =
240                 availableLV->selectionModel()->selectedIndexes();
241         if (selIdx.isEmpty())
242                 return;
243
244         QModelIndex const idxToAdd = selIdx.first();
245         QModelIndex const idx = selectedLV->currentIndex();
246         int const srows = selectedModel->rowCount();
247         
248         QMap<int, QVariant> qm = availableModel->itemData(idxToAdd);
249         insertRowToSelected(srows, qm);
250         
251         selectionChanged(); //signal
252
253         if (idx.isValid())
254                 selectedLV->setCurrentIndex(idx);
255         
256         updateHook();
257 }
258
259
260 void GuiSelectionManager::deletePB_clicked()
261 {
262         QModelIndexList selIdx =
263                 selectedLV->selectionModel()->selectedIndexes();
264         if (selIdx.isEmpty())
265                 return;
266         QModelIndex idx = selIdx.first();
267         selectedModel->removeRow(idx.row());
268         selectionChanged(); //signal
269         
270         int nrows = selectedLV->model()->rowCount();
271         if (idx.row() == nrows) //was last item on list
272                 idx = idx.sibling(idx.row() - 1, idx.column());
273         
274         if (nrows > 1)
275                 selectedLV->setCurrentIndex(idx);
276         else if (nrows == 1)
277                 selectedLV->setCurrentIndex(selectedLV->model()->index(0, 0));
278         selectedHasFocus_ = (nrows > 0);
279         updateHook();
280 }
281
282
283 void GuiSelectionManager::upPB_clicked()
284 {
285         QModelIndexList selIdx =
286                 selectedLV->selectionModel()->selectedIndexes();
287         if (selIdx.isEmpty())
288                 return;
289         QModelIndex idx = selIdx.first();
290
291         int const pos = idx.row();
292         if (pos <= 0)
293                 return;
294         
295         QMap<int, QVariant> qm = selectedModel->itemData(idx);
296
297         selectedModel->removeRow(pos);
298         insertRowToSelected(pos - 1, qm);
299
300         selectionChanged(); //signal
301
302         selectedLV->setCurrentIndex(idx.sibling(idx.row() - 1, idx.column()));
303         selectedHasFocus_ = true;
304         updateHook();
305 }
306
307
308 void GuiSelectionManager::downPB_clicked()
309 {
310         QModelIndexList selIdx =
311                 selectedLV->selectionModel()->selectedIndexes();
312         if (selIdx.isEmpty())
313                 return;
314         QModelIndex idx = selIdx.first();
315
316         int const pos = idx.row();
317         if (pos >= selectedModel->rowCount() - 1)
318                 return;
319
320         QMap<int, QVariant> qm = selectedModel->itemData(idx);
321
322         selectedModel->removeRow(pos);
323         insertRowToSelected(pos + 1, qm);
324
325         selectionChanged(); //signal
326         
327         selectedLV->setCurrentIndex(idx.sibling(idx.row() + 1, idx.column()));
328         selectedHasFocus_ = true;
329         updateHook();
330 }
331
332
333 void GuiSelectionManager::availableLV_doubleClicked(const QModelIndex & idx)
334 {
335         if (isSelected(idx) || !addPB->isEnabled())
336                 return;
337         
338         if (idx.isValid())
339                 selectedHasFocus_ = false;
340         addPB_clicked();
341         //updateHook() will be emitted there
342 }
343
344
345 bool GuiSelectionManager::eventFilter(QObject * obj, QEvent * event) 
346 {
347         QEvent::Type etype = event->type();
348         if (obj == availableLV) {
349                 if (etype == QEvent::KeyPress) {
350                         QKeyEvent * keyEvent = static_cast<QKeyEvent *>(event);
351                         int const keyPressed = keyEvent->key();
352                         Qt::KeyboardModifiers const keyModifiers = keyEvent->modifiers();
353                         // Enter key without modifier will add current item.
354                         // Ctrl-Enter will add it and close the dialog.
355                         // This is designed to work both with the main enter key
356                         // and the one on the numeric keypad.
357                         if (keyPressed == Qt::Key_Enter || keyPressed == Qt::Key_Return) {
358                                 if (addPB->isEnabled()) {
359                                         if (!keyModifiers) {
360                                                 addPB_clicked();
361                                         } else if (keyModifiers == Qt::ControlModifier || 
362                                                   keyModifiers == Qt::KeypadModifier  ||
363                                                   keyModifiers == (Qt::ControlModifier | Qt::KeypadModifier)) {
364                                                 addPB_clicked();
365                                                 okHook(); //signal
366                                         }
367                                 }
368                                 event->accept();
369                                 return true;
370                         }
371                 } else if (etype == QEvent::FocusIn) {
372                         if (selectedHasFocus_) {
373                                 selectedHasFocus_ = false;
374                                 updateHook();
375                         }
376                         event->accept();
377                         return true;
378                 } 
379         } else if (obj == selectedLV) {
380                 if (etype == QEvent::KeyPress) {
381                         QKeyEvent * keyEvent = static_cast<QKeyEvent *>(event);
382                         int const keyPressed = keyEvent->key();
383                         Qt::KeyboardModifiers const keyModifiers = keyEvent->modifiers();
384                         // Delete or backspace key will delete current item
385                         // ...with control modifier will clear the list
386                         if (keyPressed == Qt::Key_Delete || keyPressed == Qt::Key_Backspace) {
387                                 if (keyModifiers == Qt::NoModifier && deletePB->isEnabled()) {
388                                         deletePB_clicked();
389                                         updateHook();
390                                 } else if (keyModifiers == Qt::ControlModifier) {
391                                         selectedModel->removeRows(0, selectedModel->rowCount());
392                                         updateHook();
393                                 } else
394                                         return QObject::eventFilter(obj, event);
395                         } 
396                         // Ctrl-Up activates upPB
397                         else if (keyPressed == Qt::Key_Up) {
398                                 if (keyModifiers == Qt::ControlModifier) {
399                                         if (upPB->isEnabled())
400                                                 upPB_clicked();
401                                         event->accept();
402                                         return true;
403                                 }
404                         } 
405                         // Ctrl-Down activates downPB
406                         else if (keyPressed == Qt::Key_Down) {
407                                 if (keyModifiers == Qt::ControlModifier) {
408                                         if (downPB->isEnabled())
409                                                 downPB_clicked();
410                                         event->accept();
411                                         return true;
412                                 }
413                         }
414                 } else if (etype == QEvent::FocusIn) {
415                         if (!selectedHasFocus_) {
416                                 selectedHasFocus_ = true;
417                                 updateHook();
418                         }
419                         event->accept();
420                         return true;
421                 }
422         }
423         return QObject::eventFilter(obj, event);
424 }
425
426 } // namespace frontend
427 } // namespace lyx
428
429 #include "moc_GuiSelectionManager.cpp"