Abdelrazak Younes wrote:
Richard Heck wrote:
This addresses some bugs involving the mouse in the citation dialog. One was reported as 4121; the other is one I've seen, where clicking in the left pane causes a multi-element selection. This is because the selection model is being reset too often.
Could you elaborate a bit more on why this was reset at the first place?
I have no idea. I think it was just a mistake.
Does your patch modify the dialog behaviour?
Not significantly. But I think it was a bug that clicking in the selected pane, for example, constantly reset what was selected in the available pane, so in that sense, yes, it does, but only for the much better.

I've actually done a more extensive re-write of this as part of my branch that I'll post as a separate matter. It involves abstracting the whole available-selected business into a QSelectionManager, which can then be used elsewhere, too. I'm sure that'll need some cleaning.

Actually, I'll just attach those files, in case you have comments. It does succeed in replicating the behavior of the original dialog when matched to appropriate changes in QCitationDialog.{h,cpp}.

Richard

--
==================================================================
Richard G Heck, Jr
Professor of Philosophy
Brown University
http://frege.brown.edu/heck/
==================================================================
Get my public key from http://sks.keyserver.penguin.de
Hash: 0x1DE91F1E66FFBDEC
Learn how to sign your email using Thunderbird and GnuPG at:
http://dudu.dyn.2-h.org/nist/gpg-enigmail-howto

/**
 * \file QSelectionManager.cpp
 * This file is part of LyX, the document processor.
 * Licence details can be found in the file COPYING.
 *
 * \author Richard Heck
 * \author Et Alia
 *
 * Some of the material in this file previously appeared in 
 * QCitationDialog.cpp.
 *
 * Full author contact details are available in file CREDITS.
 */

#include "QSelectionManager.h"


namespace lyx {
namespace frontend {

QSelectionManager::QSelectionManager(
		QListView * avail, 
		QListView * sel,
		QPushButton * add, 
		QPushButton * del, 
		QPushButton * up, 
		QPushButton * down,
		QStringListModel * amod,
		QStringListModel * smod)
{
	availableLV = avail;
	selectedLV = sel;
	addPB = add;
	deletePB = del;
	upPB = up;
	downPB = down;
	availableModel = amod;
	selectedModel = smod;
	
	selectedLV->setModel(smod);
	availableLV->setModel(amod);

	connect(availableLV->selectionModel(),
					SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)),
								 this, SLOT(availableChanged(const QModelIndex &, const QModelIndex &)));
	connect(selectedLV->selectionModel(),
					SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)),
								 this, SLOT(selectedChanged(const QModelIndex &, const QModelIndex &)));
	connect(addPB, SIGNAL(clicked()), 
					this, SLOT(addPB_clicked()));
	connect(deletePB, SIGNAL(clicked()), 
					this, SLOT(deletePB_clicked()));
	connect(upPB, SIGNAL(clicked()), 
					this, SLOT(upPB_clicked()));
	connect(downPB, SIGNAL(clicked()), 
					this, SLOT(downPB_clicked()));
	connect(availableLV, SIGNAL(clicked(const QModelIndex &)), 
					this, SLOT(availableLV_clicked(const QModelIndex &)));
	connect(availableLV, SIGNAL(doubleClicked(const QModelIndex &)), 
					this, SLOT(availableLV_doubleClicked(const QModelIndex &)));
	connect(selectedLV, SIGNAL(clicked(const QModelIndex &)), 
					this, SLOT(selectedLV_clicked(const QModelIndex &)));

	availableLV->installEventFilter(this);
	selectedLV->installEventFilter(this);
}


void QSelectionManager::update() {
	int const arows = availableLV->model()->rowCount();
	QModelIndexList const availSels = 
			availableLV->selectionModel()->selectedIndexes();
	addPB->setEnabled(arows > 0 &&
			!availSels.isEmpty() &&
			!isSelected(availSels.first()));

	int const srows = selectedLV->model()->rowCount();
	QModelIndexList const selSels = 
			selectedLV->selectionModel()->selectedIndexes();
	int const sel_nr = 	selSels.empty() ? -1 : selSels.first().row();
	deletePB->setEnabled(sel_nr >= 0);
	upPB->setEnabled(sel_nr > 0);
	downPB->setEnabled(sel_nr >= 0 && sel_nr < srows - 1);
}


bool QSelectionManager::isSelected(const QModelIndex & idx)
{
	QString const str = idx.data().toString();
	return selectedModel->stringList().contains(str);
}


void QSelectionManager::availableChanged(const QModelIndex & idx, const QModelIndex &)
{
	if (!idx.isValid())
		return;

	selectedHasFocus_ = false;
	updateHook();
}


void QSelectionManager::selectedChanged(const QModelIndex & idx, const QModelIndex &)
{
	if (!idx.isValid())
		return;

	selectedHasFocus_ = true;
	updateHook();
}


namespace {
//helper function for next two
	QModelIndex getSelectedIndex(QListView * lv) {
	//Encourage compiler to use NRVO
		QModelIndex retval = QModelIndex();
		QModelIndexList selIdx = 
				lv->selectionModel()->selectedIndexes();
		if (!selIdx.empty())
			retval = selIdx.first();
		return retval;
	}
}//anonymous namespace


void QSelectionManager::addPB_clicked()
{
	QModelIndex const idxToAdd = getSelectedIndex(availableLV);
	if (!idxToAdd.isValid())
		return;
	QModelIndex idx = selectedLV->currentIndex();
	
	QStringList keys = selectedModel->stringList();
	keys.append(idxToAdd.data().toString());
	selectedModel->setStringList(keys);
	selectionChanged(); //signal
	
	if (idx.isValid())
		selectedLV->setCurrentIndex(idx);
	updateHook();
}


void QSelectionManager::deletePB_clicked()
{
	QModelIndex idx = getSelectedIndex(selectedLV);
	if (!idx.isValid())
		return;

	QStringList keys = selectedModel->stringList();
	keys.removeAt(idx.row());
	selectedModel->setStringList(keys);
	selectionChanged(); //signal

	int nrows = selectedLV->model()->rowCount();
	if (idx.row() == nrows) //was last item on list
		idx = idx.sibling(idx.row() - 1, idx.column());

	if (nrows > 1)
		selectedLV->setCurrentIndex(idx);
	else if (nrows == 1)
		selectedLV->setCurrentIndex(selectedLV->model()->index(0,0));
	selectedHasFocus_ = (nrows > 0);
	updateHook();
}


void QSelectionManager::upPB_clicked()
{
	QModelIndex idx = selectedLV->currentIndex();
	
	int const pos = idx.row();
	QStringList keys = selectedModel->stringList();
	keys.swap(pos, pos - 1);
	selectedModel->setStringList(keys);
	selectionChanged(); //signal
	
	selectedLV->setCurrentIndex(idx.sibling(idx.row() - 1, idx.column()));
	selectedHasFocus_ = true;
	updateHook();
}


void QSelectionManager::downPB_clicked()
{
	QModelIndex idx = selectedLV->currentIndex();
	
	int const pos = idx.row();
	QStringList keys = selectedModel->stringList();
	keys.swap(pos, pos + 1);
	selectedModel->setStringList(keys);
	selectionChanged(); //signal
	
	selectedLV->setCurrentIndex(idx.sibling(idx.row() + 1, idx.column()));
	selectedHasFocus_ = true;
	updateHook();
}


//FIXME These slots do not really do what they need to do, since focus
//can enter the QListView in other ways. But there are no signals sent
//in that case. We need to reimplement focusInEvent() to capture those,
//which means subclassing QListView. (rgh)
void QSelectionManager::availableLV_clicked(const QModelIndex &)
{
	selectedHasFocus_ = false;
	updateHook();
}


void QSelectionManager::availableLV_doubleClicked(const QModelIndex & idx)
{
	if (isSelected(idx))
		return;

	if (idx.isValid())
		selectedHasFocus_ = false;
	addPB_clicked();
	//updateHook() will be emitted there
}


void QSelectionManager::selectedLV_clicked(const QModelIndex &)
{
	selectedHasFocus_ = true;
	updateHook();
}


bool QSelectionManager::eventFilter(QObject * obj, QEvent * event) 
{
	if (obj == availableLV) {
		if (event->type() != QEvent::KeyPress)
			return QObject::eventFilter(obj, event);
		QKeyEvent * keyEvent = static_cast<QKeyEvent *>(event);
		int const keyPressed = keyEvent->key();
		Qt::KeyboardModifiers const keyModifiers = keyEvent->modifiers();
		//Enter key without modifier will add current item.
		//Ctrl-Enter will add it and close the dialog.
		//This is designed to work both with the main enter key
		//and the one on the numeric keypad.
		if ((keyPressed == Qt::Key_Enter || keyPressed == Qt::Key_Return) &&
				//We want one or both of Control and Keypad, and nothing else
				//(KeypadModifier is what you get if you use the Enter key on the
				//numeric keypad.)
					 (!keyModifiers || 
					 (keyModifiers == Qt::ControlModifier) ||
					 (keyModifiers == Qt::KeypadModifier)  ||
					 (keyModifiers == (Qt::ControlModifier | Qt::KeypadModifier))
					 )
			 ) {
			if (addPB->isEnabled()) {
				addPB_clicked();
				okHook(); //signal
			}
			event->accept();
			return true;
			 } 
	} else if (obj == selectedLV) {
		//Delete or backspace key will delete current item
		//...with control modifier will clear the list
		if (event->type() != QEvent::KeyPress)
			return QObject::eventFilter(obj, event);
		QKeyEvent * keyEvent = static_cast<QKeyEvent *>(event);
		int const keyPressed = keyEvent->key();
		Qt::KeyboardModifiers const keyModifiers = keyEvent->modifiers();
		if (keyPressed == Qt::Key_Delete || keyPressed == Qt::Key_Backspace) {
			if (keyModifiers == Qt::NoModifier && deletePB->isEnabled())
				deletePB_clicked();
			else if (keyModifiers == Qt::ControlModifier) {
				QStringList list = selectedModel->stringList();
				list.clear();
				selectedModel->setStringList(list);
				updateHook();
			} else
				//ignore it otherwise
				return QObject::eventFilter(obj, event);
			event->accept();
			return true;
		}
	}
	return QObject::eventFilter(obj, event);
}

}//namespace frontend
}//namespace lyx

#include "QSelectionManager_moc.cpp"
/**
 * \file QSelectionManager.h
 * This file is part of LyX, the document processor.
 * Licence details can be found in the file COPYING.
 *
 * \author Richard Heck
 * \author Et Alia
 *
 * Full author contact details are available in file CREDITS.
 */

#ifndef QSELECTIONMANAGER_H
#define QSELECTIONMANAGER_H

#include "Dialog.h"
#include <QObject>
#include <QKeyEvent>
#include <QStringList>
#include <QStringListModel>
#include <QListView>
#include <QPushButton>

namespace lyx {
namespace frontend {

/** Class to manage a collection of widgets that allows selection
 *  of items from a list of available items. Adapted from code originally
 *  written for QCitationDialog.
 *  Note that this is a not a QWidget, though it could be converted to
 *  one. Rather, the managed widgets---see constructor for descripton 
 *  of them---should be created independently, and then passed to the
 *  constructor.
 */
class QSelectionManager: public QObject {
	Q_OBJECT

	public:
		///
		QSelectionManager(
			QListView * availableLV, 
			QListView * selectedLV,
			QPushButton * addPB, 
			QPushButton * delPB, 
			QPushButton * upPB, 
			QPushButton * downPB,
			QStringListModel * availableModel,
			QStringListModel * selectedModel);
		/// Sets the state of the various push buttons, depending upon the
		/// state of the widgets. (E.g., "delete" is enabled only if the
		/// selection is non-empty.)
		virtual void update();
		
		/// Not strictly a matter of focus, which may be elsewhere, but
		/// whether selectedLV is `more focused' than availableLV. Intended
		/// to be used, for example, in displaying information about a
		/// highlighted item: should it be the highlighted available item
		/// or the highlighted selected item that is displayed?
		bool selectedFocused() { return selectedHasFocus_; };

	Q_SIGNALS:
		///Emitted when the list of selected items has changed. 
		void selectionChanged();
		///Emitted when something has changed that might lead the containing 
		///dialog to want to update---the focused subwidget or selected item.
		///(Specifically, it is emitted by *PB_clicked() and *LV_clicked.)
		///NOTE: No automatic update of the button state is done here. If you
		///just want to do that, connect updateHook() to update(). Much of the
		///time, though, you will want to do a bit more processing first, so
		///you can connect to some other function that itself calls update().
		void updateHook();
		///Emitted on double click in the availableLV pane. Intended to be
		///connected to an "OK" event in the containing widget.
		void okHook();

	
	protected:
		///Given a QModelIndex from availableLV, determines whether it has
		///been selected (i.e., is also in selectedLV).
		bool isSelected(const QModelIndex & idx);

	protected Q_SLOTS:
		///
		void availableChanged(const QModelIndex & idx, const QModelIndex &);
		///
		void selectedChanged(const QModelIndex & idx, const QModelIndex &);
		///
		void addPB_clicked();
		///
		void deletePB_clicked();
		///
		void upPB_clicked();
		///
		void downPB_clicked();
		///
		void availableLV_clicked(const QModelIndex &);
		///
		void availableLV_doubleClicked(const QModelIndex &);
		///
		void selectedLV_clicked(const QModelIndex &);
		///
		bool eventFilter(QObject *, QEvent *);

	private:
		QListView * availableLV;
		QListView * selectedLV;
		QPushButton * addPB;
		QPushButton * deletePB; 
		QPushButton * upPB;
		QPushButton * downPB;
		QStringListModel * availableModel;
		QStringListModel * selectedModel;
		Dialog::View * dialog;
		
		bool selectedHasFocus_;
};
}//namespace frontend
}//namespace lyx
#endif

Reply via email to