]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiSelectionManager.cpp
Fix 18 memory leaks
[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 <QAbstractItemModel>
22 #include <QAbstractListModel>
23 #include <QItemSelection>
24 #include <QListView>
25 #include <QKeyEvent>
26 #include <QPushButton>
27
28 #ifdef KeyPress
29 #undef KeyPress
30 #endif
31
32 #ifdef ControlModifier
33 #undef ControlModifier
34 #endif
35
36 #ifdef FocusIn
37 #undef FocusIn
38 #endif
39
40
41 namespace lyx {
42 namespace frontend {
43
44 GuiSelectionManager::GuiSelectionManager(QObject * parent,
45                                          QAbstractItemView * avail,
46                                          QAbstractItemView * sel,
47                                          QPushButton * add,
48                                          QPushButton * del,
49                                          QPushButton * up,
50                                          QPushButton * down,
51                                          QAbstractListModel * amod,
52                                          QAbstractItemModel * smod,
53                                          int const main_sel_col)
54 : QObject(parent), availableLV(avail), selectedLV(sel),
55   addPB(add), deletePB(del), upPB(up), downPB(down),
56   availableModel(amod), selectedModel(smod),
57   selectedHasFocus_(false), main_sel_col_(main_sel_col)
58 {
59         selectedLV->setModel(smod);
60         availableLV->setModel(amod);
61         selectedLV->setSelectionBehavior(QAbstractItemView::SelectRows);
62         selectedLV->setSelectionMode(QAbstractItemView::SingleSelection);
63
64         connect(availableLV->selectionModel(),
65                 SIGNAL(currentChanged(QModelIndex, QModelIndex)),
66                 this, SLOT(availableChanged(QModelIndex, QModelIndex)));
67         connect(selectedLV->selectionModel(),
68                 SIGNAL(currentChanged(QModelIndex, QModelIndex)),
69                 this, SLOT(selectedChanged(QModelIndex, QModelIndex)));
70         connect(availableLV->selectionModel(),
71                 SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
72                 this, SLOT(availableChanged(QItemSelection, QItemSelection)));
73         connect(selectedLV->selectionModel(),
74                 SIGNAL(selectionChanged(QItemSelection, QItemSelection)),
75                 this, SLOT(selectedChanged(QItemSelection, QItemSelection)));
76         connect(selectedLV->itemDelegate(), SIGNAL(commitData(QWidget*)),
77                 this, SLOT(selectedEdited()));
78         connect(addPB, SIGNAL(clicked()),
79                 this, SLOT(addPB_clicked()));
80         connect(deletePB, SIGNAL(clicked()),
81                 this, SLOT(deletePB_clicked()));
82         connect(upPB, SIGNAL(clicked()),
83                 this, SLOT(upPB_clicked()));
84         connect(downPB, SIGNAL(clicked()),
85                 this, SLOT(downPB_clicked()));
86         connect(availableLV, SIGNAL(doubleClicked(QModelIndex)),
87                 this, SLOT(availableLV_doubleClicked(QModelIndex)));
88
89         availableLV->installEventFilter(this);
90         selectedLV->installEventFilter(this);
91 }
92
93
94 void GuiSelectionManager::update()
95 {
96         updateAddPB();
97         updateDelPB();
98         updateDownPB();
99         updateUpPB();
100 }
101
102
103 QModelIndex GuiSelectionManager::getSelectedIndex(int const c) const
104 {
105         QModelIndexList avail = availableLV->selectionModel()->selectedIndexes();
106         QModelIndexList sel   = selectedLV->selectionModel()->selectedRows(c);
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, main_sel_col_),
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 void GuiSelectionManager::selectedEdited()
231 {
232         selectionChanged();
233 }
234
235
236 bool GuiSelectionManager::insertRowToSelected(int i, 
237                 QMap<int, QVariant> const & itemData)
238 {
239         if (i <= -1)
240                 i = 0;
241         if (i > selectedModel->rowCount())
242                 i = selectedModel->rowCount();
243         if (!selectedModel->insertRow(i))
244                 return false;
245         return selectedModel->setItemData(selectedModel->index(i, main_sel_col_), itemData);
246 }
247
248
249 bool GuiSelectionManager::insertRowToSelected(int i, QMap<int, QMap<int, QVariant>> & qms)
250 {
251         if (i <= -1)
252                 i = 0;
253         if (i > selectedModel->rowCount())
254                 i = selectedModel->rowCount();
255         if (!selectedModel->insertRow(i))
256                 return false;
257         bool res = true;
258         QMap<int, QMap<int, QVariant>>::const_iterator it = qms.constBegin();
259         for (; it != qms.constEnd(); ++it)
260                 res &= selectedModel->setItemData(selectedModel->index(i, it.key()), it.value());
261         return res;
262 }
263
264
265 void GuiSelectionManager::addPB_clicked()
266 {
267         QModelIndexList selIdx =
268                 availableLV->selectionModel()->selectedIndexes();
269         if (selIdx.isEmpty())
270                 return;
271
272         QModelIndex const idxToAdd = selIdx.first();
273         QModelIndex const idx = selectedLV->currentIndex();
274         int const srows = selectedModel->rowCount();
275
276         QMap<int, QVariant> qm = availableModel->itemData(idxToAdd);
277         insertRowToSelected(srows, qm);
278
279         selectionChanged(); //signal
280
281         if (idx.isValid())
282                 selectedLV->setCurrentIndex(idx);
283
284         updateHook();
285 }
286
287
288 void GuiSelectionManager::deletePB_clicked()
289 {
290         QModelIndexList selIdx =
291                 selectedLV->selectionModel()->selectedIndexes();
292         if (selIdx.isEmpty())
293                 return;
294         QModelIndex idx = selIdx.first();
295         selectedModel->removeRow(idx.row());
296         selectionChanged(); //signal
297
298         int nrows = selectedLV->model()->rowCount();
299         if (idx.row() == nrows) //was last item on list
300                 idx = idx.sibling(idx.row() - 1, idx.column());
301
302         if (nrows > 1)
303                 selectedLV->setCurrentIndex(idx);
304         else if (nrows == 1)
305                 selectedLV->setCurrentIndex(selectedLV->model()->index(0, 0));
306         selectedHasFocus_ = (nrows > 0);
307         updateHook();
308 }
309
310
311 void GuiSelectionManager::upPB_clicked()
312 {
313         QModelIndexList selIdx =
314                 selectedLV->selectionModel()->selectedIndexes();
315         if (selIdx.isEmpty())
316                 return;
317         QModelIndex idx = selIdx.first();
318
319         int const pos = idx.row();
320         if (pos <= 0)
321                 return;
322
323         QMap<int, QMap<int, QVariant>> qms;
324         QList<QModelIndex>::const_iterator it = selIdx.constBegin();
325         for (; it != selIdx.constEnd(); ++it)
326                 qms[it->column()] = selectedModel->itemData(*it);
327
328         selectedModel->removeRow(pos);
329         insertRowToSelected(pos - 1, qms);
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::downPB_clicked()
340 {
341         QModelIndexList selIdx =
342                 selectedLV->selectionModel()->selectedIndexes();
343         if (selIdx.isEmpty())
344                 return;
345         QModelIndex idx = selIdx.first();
346
347         int const pos = idx.row();
348         if (pos >= selectedModel->rowCount() - 1)
349                 return;
350
351         QMap<int, QMap<int, QVariant>> qms;
352         QList<QModelIndex>::const_iterator it = selIdx.constBegin();
353         for (; it != selIdx.constEnd(); ++it)
354                 qms[it->column()] = selectedModel->itemData(*it);
355
356         selectedModel->removeRow(pos);
357         insertRowToSelected(pos + 1, qms);
358
359         selectionChanged(); //signal
360
361         selectedLV->setCurrentIndex(idx.sibling(idx.row() + 1, idx.column()));
362         selectedHasFocus_ = true;
363         updateHook();
364 }
365
366
367 void GuiSelectionManager::availableLV_doubleClicked(const QModelIndex & idx)
368 {
369         if (isSelected(idx) || !addPB->isEnabled())
370                 return;
371
372         if (idx.isValid())
373                 selectedHasFocus_ = false;
374         addPB_clicked();
375         //updateHook() will be emitted there
376 }
377
378
379 bool GuiSelectionManager::eventFilter(QObject * obj, QEvent * event) 
380 {
381         QEvent::Type etype = event->type();
382         if (obj == availableLV) {
383                 if (etype == QEvent::KeyPress) {
384                         QKeyEvent * keyEvent = static_cast<QKeyEvent *>(event);
385                         int const keyPressed = keyEvent->key();
386                         Qt::KeyboardModifiers const keyModifiers = keyEvent->modifiers();
387                         // Enter key without modifier will add current item.
388                         // Ctrl-Enter will add it and close the dialog.
389                         // This is designed to work both with the main enter key
390                         // and the one on the numeric keypad.
391                         if (keyPressed == Qt::Key_Enter || keyPressed == Qt::Key_Return) {
392                                 if (addPB->isEnabled()) {
393                                         if (!keyModifiers) {
394                                                 addPB_clicked();
395                                         } else if (keyModifiers == Qt::ControlModifier || 
396                                                   keyModifiers == Qt::KeypadModifier  ||
397                                                   keyModifiers == (Qt::ControlModifier | Qt::KeypadModifier)) {
398                                                 addPB_clicked();
399                                                 okHook(); //signal
400                                         }
401                                 }
402                                 event->accept();
403                                 return true;
404                         }
405                 } else if (etype == QEvent::FocusIn) {
406                         if (selectedHasFocus_) {
407                                 selectedHasFocus_ = false;
408                                 updateHook();
409                         }
410                         event->accept();
411                         return true;
412                 } 
413         } else if (obj == selectedLV) {
414                 if (etype == QEvent::KeyPress) {
415                         QKeyEvent * keyEvent = static_cast<QKeyEvent *>(event);
416                         int const keyPressed = keyEvent->key();
417                         Qt::KeyboardModifiers const keyModifiers = keyEvent->modifiers();
418                         // Delete or backspace key will delete current item
419                         // ...with control modifier will clear the list
420                         if (keyPressed == Qt::Key_Delete || keyPressed == Qt::Key_Backspace) {
421                                 if (keyModifiers == Qt::NoModifier && deletePB->isEnabled()) {
422                                         deletePB_clicked();
423                                         updateHook();
424                                 } else if (keyModifiers == Qt::ControlModifier) {
425                                         selectedModel->removeRows(0, selectedModel->rowCount());
426                                         updateHook();
427                                 } else
428                                         return QObject::eventFilter(obj, event);
429                         } 
430                         // Ctrl-Up activates upPB
431                         else if (keyPressed == Qt::Key_Up) {
432                                 if (keyModifiers == Qt::ControlModifier) {
433                                         if (upPB->isEnabled())
434                                                 upPB_clicked();
435                                         event->accept();
436                                         return true;
437                                 }
438                         } 
439                         // Ctrl-Down activates downPB
440                         else if (keyPressed == Qt::Key_Down) {
441                                 if (keyModifiers == Qt::ControlModifier) {
442                                         if (downPB->isEnabled())
443                                                 downPB_clicked();
444                                         event->accept();
445                                         return true;
446                                 }
447                         }
448                 } else if (etype == QEvent::FocusIn) {
449                         if (!selectedHasFocus_) {
450                                 selectedHasFocus_ = true;
451                                 updateHook();
452                         }
453                         event->accept();
454                         return true;
455                 }
456         }
457         return QObject::eventFilter(obj, event);
458 }
459
460 } // namespace frontend
461 } // namespace lyx
462
463 #include "moc_GuiSelectionManager.cpp"