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

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

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

import os
import re

from qt import *

from KdeQt import KQFileDialog

from FindFileForm import FindFileForm
import Utilities

class FindFileDialog(FindFileForm):
    """
    Class implementing a dialog to search for text in files.
    
    The occurences found are
    displayed in a QListView widget 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 pythonFile(string, int) emitted to open a Python 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)
        """
        FindFileForm.__init__(self, parent)
        
        self.searchHistory = QStringList()
        self.project = project
        
        self.findList.setColumnAlignment(1, Qt.AlignRight)
        self.findList.setAllColumnsShowFocus(1)
        self.findList.setColumnWidthMode(3,QListView.Manual)
        self.findList.setColumnWidth(3,0)
        self.findList.setColumnWidthMode(4,QListView.Manual)
        self.findList.setColumnWidth(4,0)
        
        # Python files
        self.filterSource = r'.*\.py$|.*\.pyx$|.*\.ptl$'
        # C/C++ files
        self.filterSource += r'|.*\.h$|.*\.c$|.*\.hpp$|.*\.hh$|.*\.cxx$|.*\.cpp$|.*\.cc$'
        # C# files
        self.filterSource += r'|.*\.cs$'
        # IDL files
        self.filterSource += r'|.*\.idl$'
        # Java files
        self.filterSource += r'|.*\.java$'
        # JavaScript files
        self.filterSource += r'|.*\.js$'
        
        # Qt Designer form files
        self.filterForms = r'.*\.ui$|.*\.ui\.h$'
        
        self.filterRe = None
        
        # lexers of the following languages are only supported for QScintilla > 1.0
        # HTML/XML files
        self.filterSource += r'|.*\.html$|.*\.htm$|.*\.asp$|.*\.shtml$|.*\.css$'
        # PHP files
        self.filterSource += r'|.*\.php$|.*\.php3$|.*\.php4$|.*\.php5$|.*\.phtml$'
        # XML files
        self.filterSource += r'|.*\.xml$|.*\.xsl$|.*\.xslt$|.*\.dtd$|.*\.docbook$'
        # SQL files
        self.filterSource += r'|.*\.sql$'
        
        # lexers of the following languages are only supported for QScintilla > 1.2
        # Perl files
        self.filterSource += r'|.*\.pl$|.*\.pm$|.*\.ph$'
        
        # lexers of the following languages are only supported for QScintilla > 1.3
        # Bash shell scripts
        self.filterSource += r'|.*\.sh$'
        
        # lexers of the following languages are only supported for QScintilla > 1.4
        # Ruby files
        self.filterSource += r'|.*\.rb$'
        
    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.isOpen():
            self.projectButton.setEnabled(1)
        else:
            self.projectButton.setEnabled(0)
            self.dirButton.setChecked(1)
            
        self.findtextCombo.setCurrentText(txt)
        self.findtextCombo.lineEdit().selectAll()
        self.findtextCombo.setFocus()
        
        FindFileForm.show(self)
        
    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(0)
        else:
            self.findButton.setEnabled(1)
        
    def findPressed(self):
        """
        Private slot to handle the find button being pressed.
        """
        if self.projectButton.isChecked():
            if self.allFilesButton.isChecked():
                files = self.project.pdata["SOURCES"] + \
                          self.project.pdata["FORMS"] + \
                          ['%s.h' % f for f in self.project.pdata["FORMS"]]
            elif self.sourcesButton.isChecked():
                files = self.project.pdata["SOURCES"]
            elif self.formsButton.isChecked():
                files = self.project.pdata["FORMS"] + \
                          ['%s.h' % f for f in self.project.pdata["FORMS"]]
            else:
                return  # should not happen
                
        elif self.dirButton.isChecked():
            if self.allFilesButton.isChecked():
                self.filterRe = re.compile('%s|%s' % (self.filterSource, self.filterForms))
            elif self.sourcesButton.isChecked():
                self.filterRe = re.compile(self.filterSource)
            elif self.formsButton.isChecked():
                self.filterRe = re.compile(self.filterForms)
            else:
                return  # should not happen
            files = self.getFileList(os.path.abspath(unicode(self.dirEdit.text())))
                
        else:
            return  # should not happen
            
        self.findList.clear()
        self.findProgress.setTotalSteps(len(files))
        qApp.processEvents()
        
        # retrieve the values
        reg = self.regexpCheckBox.isChecked()
        wo = self.wordCheckBox.isChecked()
        cs = self.caseCheckBox.isChecked()
        ct = self.findtextCombo.currentText()
        if reg:
            regexp = QRegExp(ct, cs)
        else:
            search = ct
        if wo:
            reg = 1
            txt = QString(ct)
            regexp = QRegExp(txt.prepend(r'\b').append(r'\b'), cs)
            
        # reset the findtextCombo
        self.searchHistory.remove(ct)
        self.searchHistory.prepend(ct)
        self.findtextCombo.clear()
        self.findtextCombo.insertStringList(self.searchHistory)
        
        # now go through all the files
        progress = 0
        for file in files:
            fn = os.path.join(self.project.ppath, 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:
                progress += 1
                self.findProgress.setProgress(progress)
                continue
            
            # now perform the search and display the lines found
            count = 0
            for line in lines:
                count += 1
                if reg:
                    start = QString(line).find(regexp)
                    contains = (start != -1)
                    if contains:
                        end = start + regexp.matchedLength()
                else:
                    start = QString(line).find(search, 0, cs)
                    contains = (start != -1)
                    if contains:
                        end = start + search.length()
                if contains:
                    QListViewItem(self.findList,
                        file, ' %5d ' % count, line, '%5d' % start, '%5d' % end)
                
            progress += 1
            self.findProgress.setProgress(progress)
        
    def handleDoubleClick(self, itm):
        """
        Private slot to handle the double click. 
        
        It emits the signal
        pythonFile or designerFile depending on the file extension.
        
        @param itm the double clicked listview item (QListViewItem)
        """
        file = itm.text(0)
        line = int(str(itm.text(1)))
        start = int(str(itm.text(3)))
        end = int(str(itm.text(4)))
        
        if file.endsWith('.ui'):
            self.emit(PYSIGNAL('designerFile'), 
                (os.path.join(self.project.ppath, unicode(file)),))
        elif file.endsWith('.ui.h'):
            fn = os.path.splitext(unicode(file))
            self.emit(PYSIGNAL('designerFile'), 
                (os.path.join(self.project.ppath, fn),))
        else:
            self.emit(PYSIGNAL('pythonFile'), 
                (os.path.join(self.project.ppath, unicode(file)), line, (start, end)))
        
    def handleDirSelection(self):
        """
        Private slot to display a directory selection dialog.
        """
        directory = KQFileDialog.getExistingDirectory(self.dirEdit.text(),
            self, None, self.trUtf8("Select directory"))
            
        if not directory.isNull():
            self.dirEdit.setText(QDir.convertSeparators(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 = []
        os.path.walk(path, self.walkFilter, files)
        return files
        
    def walkFilter(self, arg, dirname, names):
        """
        Private method used to walk through a directory tree building a list of files we are interested in.
        
        @param arg variable we add our interesting files to
        @param dirname name of directory (string)
        @param names filenames of files in directory dirname (list of strings)
        """
        files = [os.path.join(dirname, f) for f in names if re.match(self.filterRe, f)]
        arg += files

    def keyPressEvent(self, evt):
        """
        Protected slot to intercept the return key press.
        
        @param evt a key press event (QKeyEvent)
        """
        key = evt.key()
        if (key == Qt.Key_Enter or key == Qt.Key_Return) and \
            self.findButton.isEnabled():
            self.findButton.animateClick()
        else:
            evt.ignore()
