/***************************************************************************
 *
 * Copyright (C) 2005 Elad Lahav (elad_lahav@users.sourceforge.net)
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 ***************************************************************************/

#include <qapplication.h>
#include <qclipboard.h>
#include <klocale.h>
#include "queryview.h"
#include "queryresultsmenu.h"
#include "queryviewdlg.h"
#include "cscopefrontend.h"
#include "searchresultsdlg.h"

/**
 * Class constructor.
 * @param	pParent	The parent widget
 * @param	szName	The name of the widget
 */
QueryView::QueryView(QWidget* pParent, const char* szName) :
	QListView(pParent, szName),
	m_pLastItem(NULL)
{
	// Create the popup-menu
	m_pQueryMenu = new QueryResultsMenu(this);

	// Initialise the list's columns
	setAllColumnsShowFocus(true);
	addColumn(i18n("Function"));
	addColumn(i18n("File"));
	addColumn(i18n("Line"));
	addColumn(i18n("Text"));
	setColumnAlignment(2, Qt::AlignRight);

	setShowSortIndicator(true);
	
	// A record is selected if it is either double-clicked, or the ENTER
	// key is pressed while the record is highlighted
	connect(this, SIGNAL(doubleClicked(QListViewItem*)), this, 
		SLOT(slotRecordSelected(QListViewItem*)));
	connect(this, SIGNAL(returnPressed(QListViewItem*)), this, 
		SLOT(slotRecordSelected(QListViewItem*)));
		
	// Show the popup-menu when requested
	connect(this, 
		SIGNAL(contextMenuRequested(QListViewItem*, const QPoint&, int)),
		m_pQueryMenu, SLOT(slotShow(QListViewItem*, const QPoint&, int)));
		
	// Handle popup-menu commands
	connect(m_pQueryMenu, SIGNAL(viewSource(QListViewItem*)), this,
		SLOT(slotRecordSelected(QListViewItem*)));
	connect(m_pQueryMenu, SIGNAL(findDef(const QString&)), this,
		SLOT(slotFindDef(const QString&)));
	connect(m_pQueryMenu, SIGNAL(copy(QListViewItem*, int)), this,
		SLOT(slotCopy(QListViewItem*, int)));
	connect(m_pQueryMenu, SIGNAL(filter(int)), this, SLOT(slotFilter(int)));
	connect(m_pQueryMenu, SIGNAL(showAll()), this, 
		SLOT(slotShowAll()));
	connect(m_pQueryMenu, SIGNAL(remove(QListViewItem*)), this,
		SLOT(slotRemoveItem(QListViewItem*)));
}

/**
 * Class destructor.
 */
QueryView::~QueryView()
{
}

/**
 * Creates a new list item showing a query result record.
 * @param	sFunc	The name of the function
 * @param	sFile	The file path
 * @param	sLine	The line number in the above file
 * @param	sText	The line's text
 * @param	pParent	The parent item (ignored)
 */
void QueryView::addRecord(const QString& sFunc, const QString& sFile,
	const QString& sLine, const QString& sText, QListViewItem* /* pParent */)
{
	QListViewItem* pItem;
	
	pItem = new QueryViewItem(this, m_pLastItem, 2);
	pItem->setText(0, sFunc);
	pItem->setText(1, sFile);
	pItem->setText(2, sLine);
	pItem->setText(3, sText);
	
	m_pLastItem = pItem;
}

/**
 * Selects an item.
 * When an item is selected, it is highlighted and made visible. By
 * definition, the lineRequested() signal is also emitted.
 * This method is used for selecting records programmatically (@see
 * selectNext() for example). It has nothing to do with user selection.
 * @param	pItem	The list item to select
 */
void QueryView::select(QListViewItem* pItem)
{
	ensureItemVisible(pItem);
	setSelected(pItem, true);
	slotRecordSelected(pItem);
}

/**
 * Selects the next record in the list (if one exists).
 * The function selects the next item as follows:
 * - The first item in the list, if there is no current item
 * - The current item, if it is not selected
 * - The item immediately below the current item, otherwise
 */
void QueryView::selectNext()
{
	QListViewItem* pItem;
	
	// Do nothing if the list is empty
	if (firstChild() == NULL)
		return;
		
	// Find the next record
	pItem = currentItem();
	if (pItem == NULL) {
		pItem = firstChild();
	}
	else if (pItem->isSelected()) {
		pItem = pItem->itemBelow();
		if (pItem == NULL)
			return;
	}
	
	// Select the new item
	select(pItem);
}

/**
 * Selects the previous record in the list (if one exists).
 * The function selects the previous item as follows:
 * - The first item in the list, if there is no current item
 * - The current item, if it is not selected
 * - The item immediately above the current item, otherwise
 */
void QueryView::selectPrev()
{
	QListViewItem* pItem;
	
	// Do nothing if the list is empty
	if (firstChild() == NULL)
		return;
	
	// Find the item immediately above this one
	pItem = currentItem();
	if (pItem == NULL) {
		pItem = firstChild();
	}
	else if (pItem->isSelected()) {
		pItem = pItem->itemAbove();
		if (pItem == NULL)
			return;
	}
		
	// Select the new item
	select(pItem);
}

/**
 * Informs the view that query progress information has been received.
 * The view emits the needToShow() signal telling its parent that the widget
 * should become visible (if not already so).
 */
void QueryView::queryProgress()
{
	if (!isVisible())
		emit needToShow();
}

/**
 * Called when a query using this view terminates.
 * @param	nRecords	Number of records generated by the query
 */
void QueryView::queryFinished(uint nRecords, QListViewItem*)
{
	// Auto-select a single record (no need to emit the show() signal in
	// that case)
	if (nRecords == 1) {
		emit select(firstChild());
		return;
	}
	
	// Report a query that has returned an empty record set
	if (nRecords == 0)
		addRecord(i18n("No results"), "", "", "", NULL);
	
	// Data is available, instruct the owner object to show the view
	emit needToShow();
}

/**
 * Creates an iterator and initialises it to point to the first _visible_
 * item in the list.
 * @return	A new iterator initialised to the beginning of the list
 */
QueryView::Iterator QueryView::getIterator()
{
	QListViewItem* pItem;
	
	for (pItem = firstChild(); pItem != NULL && !pItem->isVisible(); 
		pItem = pItem->nextSibling());
		
	return Iterator(pItem);
}

/**
 * Handles double-click events over the view.
 * NOTE: We override this method since the QListView implementation opens
 * expandable items. This is undesired for tree-like query views (such as the
 * call tree.
 * @param	pEvent	Event description object
 */
void QueryView::contentsMouseDoubleClickEvent(QMouseEvent* pEvent)
{
	QListViewItem* pItem;
	
	// Handle only left button double-clicks
	if (pEvent == NULL || pEvent->button() != LeftButton)
		return;
		
	// Find the clicked item
	pItem = itemAt(contentsToViewport(pEvent->pos()));
	if (pItem == NULL)
		return;
		
	// Emit the doubleClicked() signal
	emit doubleClicked(pItem);
}

/**
 * Emits the lineRequested() signal when a list item is selected.
 * This slot is connected to the doubleClicked() and returnPressed()
 * signals of the list view.
 * @param	pItem	The selected item
 */
void QueryView::slotRecordSelected(QListViewItem* pItem)
{
	QString sFileName, sLine;

	// Get the file name and line number
	sFileName = pItem->text(1);
	sLine = pItem->text(2);

	// Do not process the "No results" item
	if (!sLine.isEmpty())
		emit lineRequested(sFileName, sLine.toUInt());
}

/**
 * Looks up the definition of a given function.
 * Results are displayed in a popup window.
 * This slot is connected to the findDef() signal emitted by the results menu.
 * @param	sFunc	The function to look for
 */
void QueryView::slotFindDef(const QString& sFunc)
{
	QueryViewDlg* pDlg;
	
	// Create a query view dialogue
	pDlg = new QueryViewDlg(QueryViewDlg::DestroyOnSelect, this);
	
	// Display a line when it is selected in the dialogue
	connect(pDlg, SIGNAL(lineRequested(const QString&, uint)), this,
		SIGNAL(lineRequested(const QString&, uint)));
		
	// Start the query
	pDlg->query(CscopeFrontend::Definition, sFunc);
}

/**
 * Copies the text of the requested field (item+column) to the clipboard.
 * This slot is connected to the copy() signal of the QueryResultsMenu object.
 * @param	pItem	The item from which to copy
 * @param	nCol	The index of the item field to copy
 */
void QueryView::slotCopy(QListViewItem* pItem, int nCol)
{
	QApplication::clipboard()->setText(pItem->text(nCol),
		QClipboard::Clipboard);
}

/**
 * Hides all items in the page that do not meet the given search criteria.
 * This slot is connected to the search() signal of the QueryResultsMenu
 * object.
 * The search is incremental: only visible items are checked, so that a new
 * search goes over the results of the previous one.
 * @param	nCol	The list column to search in
 */
void QueryView::slotFilter(int nCol)
{
	SearchResultsDlg dlg(this);
	QRegExp re;
	QListViewItem* pItem;
	bool bNegate;
	
	// Prepare the dialogue
	dlg.setColumn(nCol);
	
	// Show the dialogue
	if (dlg.exec() != QDialog::Accepted)
		return;
		
	// Get the selected regular expression
	dlg.getPattern(re);
	bNegate = dlg.isNegated();
	
	// Disable visual updates while search is in progress
	setUpdatesEnabled(false);
	
	// Iterate over all items in the list
	pItem = firstChild();
	while (pItem != NULL) {
		// Filter visible items only
		if (pItem->isVisible() &&
			(re.search(pItem->text(nCol)) == -1) != bNegate) {
			pItem->setVisible(false);
		}
		
		pItem = pItem->nextSibling();
	}
	
	// Redraw the list
	setUpdatesEnabled(true);
	triggerUpdate();
}

/**
 * Makes all list items visible.
 * This slot is connected to the showAll() signal of the QueryResultsMenu
 * object.
 */
void QueryView::slotShowAll()
{
	QListViewItem* pItem;
	
	// Iterate over all items in the list
	pItem = firstChild();
	while (pItem != NULL) {
		pItem->setVisible(true);
		pItem = pItem->nextSibling();
	}
}

/**
 * Deletes the item on which a popup-menu has been invoked.
 * This slot is connected to the remove() signal of the QueryResultsMenu
 * object.
 * @param	pItem	The item to remove
 */
void QueryView::slotRemoveItem(QListViewItem* pItem)
{
	delete pItem;
}

/**
 * Moves the iterator to the next _visible_ item in the list.
 */
void QueryView::Iterator::next()
{
	if (m_pItem == NULL)
		return;
		
	do {
		m_pItem = m_pItem->nextSibling();
	} while (m_pItem != NULL && !m_pItem->isVisible());
}

/**
 * @return	The function associated with the list item the iterator points to
 */
QString QueryView::Iterator::getFunc()
{
	if (m_pItem == NULL)
		return "";
		
	return m_pItem->text(0);
}

/**
 * @return	The file associated with the list item the iterator points to
 */
QString QueryView::Iterator::getFile()
{
	if (m_pItem == NULL)
		return "";
		
	return m_pItem->text(1);
}

/**
 * @return	The line number associated with the list item the iterator points
 *			to
 */
QString QueryView::Iterator::getLine()
{
	if (m_pItem == NULL)
		return "";
		
	return m_pItem->text(2);
}

/**
 * @return	The text associated with the list item the iterator points to
 */
QString QueryView::Iterator::getText()
{
	if (m_pItem == NULL)
		return "";
		
	return m_pItem->text(3);
}

#include "queryview.moc"
