# -*- coding: utf-8 -*-

# Copyright (c) 2002 - 2008 Detlev Offenbach <detlev@die-offenbachs.de>
#

"""
Module implementing a dialog to search for text in files.
"""

import os
import re

import sys

from PyQt4.QtCore import *
from PyQt4.QtGui import *

from KdeQt import KQFileDialog

from Ui_FindFileDialog import Ui_FindFileDialog

import Utilities
import Preferences

class FindFileTreeWidgetItem(QTreeWidgetItem):
    """
    Class implementing a custom QTreeWidgetItem to customize sorting.
    """
    def __lt__(self, other):
        """
        Public method to check, if the item is less than the other one.
        
        @param other reference to item to compare against (ProfileTreeWidgetItem)
        @return true, if this item is less than other (boolean)
        """
        column = self.treeWidget().sortColumn()
        if column == 0 or column == 1:
            if self.text(0) == other.text(0):
                return int(str(self.text(1))) < int(str(other.text(1)))
            else:
                return self.text(0) < other.text(0)
        return self.text(column) < other.text(column)

class FindFileDialog(QDialog, Ui_FindFileDialog):
    """
    Class implementing a dialog to search for text in files.
    
    The occurrences found are displayed in a QTreeWidget showing the filename, the 
    linenumber and the found text. The file will be opened upon a double click onto 
    the respective entry of the list.
    
    @signal sourceFile(string, int, string, (int, int)) emitted to open a 
        source file at a line
    @signal designerFile(string) emitted to open a Qt-Designer file
    """
    def __init__(self, project, parent=None):
        """
        Constructor
        
        @param project reference to the project object
        @param parent parent widget of this dialog (QWidget)
        """
        QDialog.__init__(self, parent)
        self.setupUi(self)
        self.setWindowFlags(Qt.WindowFlags(Qt.Window))
        
        self.stopButton = \
            self.buttonBox.addButton(self.trUtf8("Stop"), QDialogButtonBox.ActionRole)
        self.stopButton.setEnabled(False)
        
        self.findButton = \
            self.buttonBox.addButton(self.trUtf8("Find"), QDialogButtonBox.ActionRole)
        self.findButton.setEnabled(False)
        self.findButton.setDefault(True)
        
        self.searchHistory = QStringList()
        self.project = project
        
        self.findList.header().setSortIndicator(0, Qt.AscendingOrder)
        
        # Qt Designer form files
        self.filterForms = r'.*\.ui$|.*\.ui\.h$'
        self.formsExt = ['*.ui', '*.ui.h']
        
        # Corba interface files
        self.filterInterfaces = r'.*\.idl$'
        self.interfacesExt = ['*.idl']
        
        # Qt resources files
        self.filterResources = r'.*\.qrc$'
        self.resourcesExt = ['*.qrc']
        
        self.filterRe = None
        
        self.__cancelSearch = False
        
    def __createItem(self, file, line, text, start, end):
        """
        Private method to create an entry in the file list.
        
        @param file filename of file (string or QString)
        @param line line number (integer)
        @param text text found (string or QString)
        @param start start position of match (integer)
        @param end end position of match (integer)
        """
        itm = FindFileTreeWidgetItem(self.findList, QStringList() \
            << file << ' %5d ' % line << text << "" << '%5d' % start << '%5d' % end)
        itm.setTextAlignment(1, Qt.AlignRight)
        
    def show(self, txt = ""):
        """
        Overwritten method to enable/disable the project button.
        
        @param txt text to be shown in the searchtext combo (string or QString)
        """
        if self.project and self.project.isOpen():
            self.projectButton.setEnabled(True)
        else:
            self.projectButton.setEnabled(False)
            self.dirButton.setChecked(True)
            
        self.findtextCombo.setEditText(txt)
        self.findtextCombo.lineEdit().selectAll()
        self.findtextCombo.setFocus()
        
        QWidget.show(self)
        
    def on_findtextCombo_editTextChanged(self, text):
        """
        Private slot to handle the editTextChanged signal of the find text combo.
        
        @param text (ignored)
        """
        self.__enableFindButton()
        
    def on_dirEdit_textChanged(self, text):
        """
        Private slot to handle the textChanged signal of the directory edit.
        
        @param text (ignored)
        """
        self.__enableFindButton()
        
    @pyqtSignature("")
    def on_projectButton_clicked(self):
        """
        Private slot to handle the selection of the project radio button.
        """
        self.__enableFindButton()
        
    @pyqtSignature("")
    def on_dirButton_clicked(self):
        """
        Private slot to handle the selection of the project radio button.
        """
        self.__enableFindButton()
        
    def __enableFindButton(self):
        """
        Private slot called to enable the find button.
        """
        if self.findtextCombo.currentText().isEmpty() or \
            (self.dirButton.isChecked() and \
                (self.dirEdit.text().isEmpty() or \
                 not os.path.exists(os.path.abspath(unicode(self.dirEdit.text()))))):
            self.findButton.setEnabled(False)
            self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
        else:
            self.findButton.setEnabled(True)
            self.findButton.setDefault(True)
        
    def on_buttonBox_clicked(self, button):
        """
        Private slot called by a button of the button box clicked.
        
        @param button button that was clicked (QAbstractButton)
        """
        if button == self.findButton:
            self.__doSearch()
        elif button == self.stopButton:
            self.__stopSearch()
        
    def __stopSearch(self):
        """
        Private slot to handle the stop button being pressed.
        """
        self.__cancelSearch = True
        
    def __doSearch(self):
        """
        Private slot to handle the find button being pressed.
        """
        self.__cancelSearch = False
        self.stopButton.setEnabled(True)
        self.stopButton.setDefault(True)
        self.findButton.setEnabled(False)
        
        if self.projectButton.isChecked():
            files = []
            if self.sourcesCheckBox.isChecked():
                files += self.project.pdata["SOURCES"]
            if self.formsCheckBox.isChecked():
                files += self.project.pdata["FORMS"] + \
                          ['%s.h' % f for f in self.project.pdata["FORMS"]]
            if self.interfacesCheckBox.isChecked():
                files += self.project.pdata["INTERFACES"]
            if self.resourcesCheckBox.isChecked():
                files += self.project.pdata["RESOURCES"]
        elif self.dirButton.isChecked():
            filters = []
            if self.sourcesCheckBox.isChecked():
                filters.extend(
                    ["^%s$" % assoc.replace(".", "\.").replace("*", ".*") \
                    for assoc in Preferences.getEditorLexerAssocs().keys() \
                    if assoc not in self.formsExt + self.interfacesExt])
            if self.formsCheckBox.isChecked():
                filters.append(self.filterForms)
            if self.interfacesCheckBox.isChecked():
                filters.append(self.filterInterfaces)
            if self.resourcesCheckBox.isChecked():
                filters.append(self.filterResources)
            filterString = "|".join(filters)
            self.filterRe = re.compile(filterString)
            files = self.__getFileList(os.path.abspath(unicode(self.dirEdit.text())))
        
        self.findList.clear()
        QApplication.processEvents()
        QApplication.processEvents()
        self.findProgress.setMaximum(len(files))
        
        # retrieve the values
        reg = self.regexpCheckBox.isChecked() 
        wo = self.wordCheckBox.isChecked()
        cs = self.caseCheckBox.isChecked()
        ct = unicode(self.findtextCombo.currentText())
        if reg:
            txt = ct
        else:
            txt = re.escape(ct)
        if wo:
            txt = "\\b%s\\b" % txt
        flags = re.UNICODE | re.LOCALE
        if not cs:
            flags |= re.IGNORECASE
        search = re.compile(txt, flags)
        
        # reset the findtextCombo
        self.searchHistory.removeAll(ct)
        self.searchHistory.prepend(ct)
        self.findtextCombo.clear()
        self.findtextCombo.addItems(self.searchHistory)
        
        # now go through all the files
        self.findList.setUpdatesEnabled(False)
        progress = 0
        for file in files:
            if self.__cancelSearch:
                break
            
            if self.projectButton.isChecked():
                fn = os.path.join(self.project.ppath, file)
            else:
                fn = file
            # read the file and split it into textlines
            try:
                f = open(fn, 'rb')
                text, encoding = Utilities.decode(f.read())
                lines = text.splitlines()
                f.close()
            except IOError:
                progress += 1
                self.findProgress.setValue(progress)
                continue
            
            # now perform the search and display the lines found
            count = 0
            for line in lines:
                if self.__cancelSearch:
                    break
                
                count += 1
                contains = search.search(line)
                if contains:
                    start = contains.start()
                    end = contains.end()
                    if len(line) > 1024:
                        line = "%s ..." % line[:1024]
                    self.__createItem(file, count, line, start, end)
                QApplication.processEvents()
            
            progress += 1
            self.findProgress.setValue(progress)
        self.findList.setUpdatesEnabled(True)
        
        self.stopButton.setEnabled(False)
        self.findButton.setEnabled(True)
        self.findButton.setDefault(True)
        self.findList.sortItems(self.findList.sortColumn(), 
                                self.findList.header().sortIndicatorOrder())
        self.findList.header().resizeSections(QHeaderView.ResizeToContents)
        self.findList.header().setStretchLastSection(True)
        
    def on_findList_itemDoubleClicked(self, itm, column):
        """
        Private slot to handle the double click on a file item. 
        
        It emits the signal
        sourceFile or designerFile depending on the file extension.
        
        @param itm the double clicked tree item (FindFileTreeWidgetItem)
        @param column column that was double clicked (integer) (ignored)
        """
        file = itm.text(0)
        line = int(str(itm.text(1)))
        start = int(str(itm.text(4)))
        end = int(str(itm.text(5)))
        
        if self.project:
            fn = os.path.join(self.project.ppath, unicode(file))
        else:
            fn = unicode(file)
        if fn.endswith('.ui'):
            self.emit(SIGNAL('designerFile'), fn)
        elif fn.endswith('.ui.h'):
            fn = os.path.splitext(unicode(file))[0]
            self.emit(SIGNAL('designerFile'), fn)
        else:
            self.emit(SIGNAL('sourceFile'), fn, line, "", (start, end))
        
    @pyqtSignature("")
    def on_dirSelectButton_clicked(self):
        """
        Private slot to display a directory selection dialog.
        """
        directory = KQFileDialog.getExistingDirectory(\
            self,
            self.trUtf8("Select directory"),
            self.dirEdit.text(),
            QFileDialog.Options(QFileDialog.ShowDirsOnly))
            
        if not directory.isEmpty():
            self.dirEdit.setText(Utilities.toNativeSeparators(directory))
        
    def __getFileList(self, path):
        """
        Private method to get a list of files to search.
        
        @param path the root directory to search in (string)
        @return list of files to be processed (list of strings)
        """
        path = os.path.abspath(path)
        files = []
        for dirname, _, names in os.walk(path):
            files.extend([os.path.join(dirname, f) \
                          for f in names \
                          if re.match(self.filterRe, f)]
            )
        return files
        
    def setSearchDirectory(self, searchDir):
        """
        Public slot to set the name of the directory to search in.
        
        @param searchDir name of the directory to search in (string or QString)
        """
        self.dirButton.setChecked(True)
        self.dirEdit.setText(Utilities.toNativeSeparators(searchDir))
