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