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