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

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

"""
Module implementing a task viewer and associated classes.

Tasks can be defined manually or automatically. Automatically
generated tasks are derived from a comment with a specially
introductory text. This text is configurable.
"""

import os
import sys
import time

from qt import *

from TaskPropertiesDialog import TaskPropertiesDialog
import PixmapCache
import Preferences

class Task(QListViewItem):
    """
    Class implementing the task data structure.
    """
    def __init__(self, parent, description, priority=1, filename="", lineno=0, 
                 completed=0, _time=0, isProjectTask=0, isBugfixTask=0, ppath=""):
        """
        Constructor
        
        @param parent parent widget of the task (QWidget)
        @param description descriptive text of the task (string or QString)
        @param priority priority of the task (0=high, 1=normal, 2=low)
        @param filename filename containing the task (string or QString)
        @param lineno line number containing the task (integer)
        @param completed flag indicating completion status (boolean)
        @param _time creation time of the task (float, if 0 use current time)
        @param isProjectTask flag indicating a task related to the current project (boolean)
        @param isBugfixTask flag indicating a bugfix task (boolean)
        @param ppath the project path (string or QString)
        """
        self.description = unicode(description)
        if priority in [0, 1, 2]:
            self.priority = priority 
        else:
            self.priority = 1
        self.filename = unicode(filename)
        self.lineno = lineno
        self.completed = completed
        self.created = _time and _time or time.time()
        self._isProjectTask = isProjectTask
        self.isBugfixTask = isBugfixTask
        self.ppath = unicode(ppath);
        
        if isProjectTask:
            self.filename = self.filename.replace(self.ppath+os.sep, "")
            
        QListViewItem.__init__(self, parent, "", "", self.description, self.filename,
                               self.lineno and "%6d" % self.lineno or "")
        
        if self.completed:
            self.setPixmap(0, PixmapCache.getPixmap("taskCompleted.png"))
        else:
            self.setPixmap(0, PixmapCache.getPixmap("empty.png"))
        
        if self.priority == 1:
            self.setPixmap(1, PixmapCache.getPixmap("empty.png"))
        elif self.priority == 0:
            self.setPixmap(1, PixmapCache.getPixmap("taskPrioHigh.png"))
        elif self.priority == 2:
            self.setPixmap(1, PixmapCache.getPixmap("taskPrioLow.png"))
        else:
            self.setPixmap(1, PixmapCache.getPixmap("empty.png"))
    
    def paintCell(self, p, cg, column, width, alignment):
        """
        Overwritten class to set a different text color, if bold is true.
        
        @param p the painter (QPainter)
        @param cg the color group (QColorGroup)
        @param column the column (int)
        @param width width of the cell (int)
        @param alignment alignment of the cell (int)
        """
        _cg = QColorGroup(cg)
        c = _cg.text()
        
        if self.isBugfixTask:
            _cg.setColor(QColorGroup.Text, Preferences.getTasks("TasksBugfixColour"))
        else:
            _cg.setColor(QColorGroup.Text, Preferences.getTasks("TasksColour"))
        if self._isProjectTask:
            _cg.setColor(QColorGroup.Base, Preferences.getTasks("TasksProjectBgColour"))
        else:
            _cg.setColor(QColorGroup.Base, Preferences.getTasks("TasksBgColour"))
        
        QListViewItem.paintCell(self, p, _cg, column, width, alignment)
        
        _cg.setColor(QColorGroup.Text, c)

    def setDescription(self, description):
        """
        Public slot to update the description.
        
        @param decsription descriptive text of the task (string or QString)
        """
        self.description = unicode(description)
        self.setText(2, self.description)
    
    def setPriority(self, priority):
        """
        Public slot to update the priority.
        
        @param priority priority of the task (0=high, 1=normal, 2=low)
        """
        if priority in [0, 1, 2]:
            self.priority = priority 
        else:
            self.priority = 1
        if self.priority == 1:
            self.setPixmap(1, PixmapCache.getPixmap("empty.png"))
        elif self.priority == 0:
            self.setPixmap(1, PixmapCache.getPixmap("taskPrioHigh.png"))
        elif self.priority == 2:
            self.setPixmap(1, PixmapCache.getPixmap("taskPrioLow.png"))
        else:
            self.setPixmap(1, PixmapCache.getPixmap("empty.png"))
    
    def setCompleted(self, completed):
        """
        Public slot to update the completed flag.
        
        @param completed flag indicating completion status (boolean)
        """
        self.completed = completed
        if self.completed:
            self.setPixmap(0, PixmapCache.getPixmap("taskCompleted.png"))
        else:
            self.setPixmap(0, PixmapCache.getPixmap("empty.png"))
    
    def isCompleted(self):
        """
        Public slot to return the completion status.
        
        @return flag indicating the completion status (boolean)
        """
        return self.completed
    
    def getFilename(self):
        """
        Public method to retrieve the tasks filename.
        
        @return filename (string)
        """
        if self._isProjectTask and self.filename:
            return os.path.join(self.ppath, self.filename)
        else:
            return self.filename
    
    def getLineno(self):
        """
        Public method to retrieve the tasks linenumber.
        
        @return linenumber (integer)
        """
        return self.lineno
    
    def setProjectTask(self, pt):
        """
        Public method to set the project relation flag.
        
        @param pt flag indicating a project task (boolean)
        """
        self._isProjectTask = pt
    
    def isProjectTask(self):
        """
        Public slot to return the project relation status.
        
        @return flag indicating the project relation status (boolean)
        """
        return self._isProjectTask
    
class TaskViewer(QListView):
    """
    Class implementing the task viewer.
    
    @signal displayFile(string, integer) emitted to go to a file task
    """
    def __init__(self,parent, project):
        """
        Constructor
        
        @param parent the parent (QWidget)
        @param project reference to the project object
        """
        QListView.__init__(self,parent)
        
        self.setAllColumnsShowFocus(1)
        
        self.addColumn(QIconSet(PixmapCache.getPixmap("taskCompleted.png")), "", 24)
        self.addColumn(QIconSet(PixmapCache.getPixmap("taskPriority.png")), "", 24)
        self.addColumn(self.trUtf8("Description"), 200)
        self.addColumn(self.trUtf8("Filename"), 100)
        self.addColumn(self.trUtf8("Line"))
        
        self.setColumnAlignment(0, Qt.AlignCenter)
        self.setColumnAlignment(1, Qt.AlignCenter)
        self.setColumnAlignment(4, Qt.AlignRight)
        self.setSorting(2)
        self.setShowToolTips(1)
        
        self.tasks = []
        self.copyTask = None
        self.projectOpen = 0
        self.project = project
        
        self.menu = QPopupMenu(self)
        self.menu.insertItem(self.trUtf8("&New Task..."), self.handleNewTask)
        self.menu.insertSeparator()
        self.gotoItem = self.menu.insertItem(self.trUtf8("&Go To"), self.handleGoTo)
        self.menu.insertSeparator()
        self.copyItem = self.menu.insertItem(self.trUtf8("&Copy"), self.handleCopy)
        self.pasteItem = self.menu.insertItem(self.trUtf8("&Paste"), self.handlePaste)
        self.deleteItem = self.menu.insertItem(self.trUtf8("&Delete"), self.handleDelete)
        self.menu.insertSeparator()
        self.markCompletedItem = self.menu.insertItem(self.trUtf8("&Mark Completed"),
                                                      self.handleCompleted)
        self.menu.insertItem(self.trUtf8("Delete Completed &Tasks"),
                             self.handleDeleteCompleted)
        self.menu.insertSeparator()
        self.menu.insertItem(self.trUtf8("P&roperties..."), self.handleProperties)
        
        self.backMenu = QPopupMenu(self)
        self.backMenu.insertItem(self.trUtf8("&New Task..."), self.handleNewTask)
        self.backMenu.insertSeparator()
        self.backPasteItem = self.backMenu.insertItem(self.trUtf8("&Paste"), self.handlePaste)
        self.backMenu.insertSeparator()
        self.backMenu.insertItem(self.trUtf8("Delete Completed &Tasks"),
                             self.handleDeleteCompleted)
        
        self.connect(self,SIGNAL('contextMenuRequested(QListViewItem *, const QPoint &, int)'),
                     self.handleContextMenu)
        
        self.setIcon(PixmapCache.getPixmap("eric.png"))
    
    def contentsMouseDoubleClickEvent(self, mouseEvent):
        """
        Protected method of QListView. 
        
        Reimplemented to disable expanding/collapsing
        of items when double-clicking. Instead the double-clicked entry is opened.
        
        @param mouseEvent the mouse event (QMouseEvent)
        """
        itm = self.currentItem()
        fn = itm.getFilename()
        if fn:
            self.emit(PYSIGNAL("displayFile"), (fn, itm.getLineno()))

    def handleContextMenu(self, itm, coord, col):
        """
        Private slot to show the context menu of the listview.
        
        @param itm the selected listview item (QListViewItem)
        @param coord the position of the mouse pointer (QPoint)
        @param col the column of the mouse pointer (int)
        """
        if itm is None:
            if self.copyTask:
                self.backMenu.setItemEnabled(self.backPasteItem, 1)
            else:
                self.backMenu.setItemEnabled(self.backPasteItem, 0)
            self.backMenu.popup(coord)
        else:
            if itm.getFilename():
                self.menu.setItemEnabled(self.gotoItem, 1)
                self.menu.setItemEnabled(self.deleteItem, 1)
                self.menu.setItemEnabled(self.markCompletedItem, 0)
                self.menu.setItemEnabled(self.copyItem, 0)
            else:
                self.menu.setItemEnabled(self.gotoItem, 0)
                self.menu.setItemEnabled(self.deleteItem, 1)
                self.menu.setItemEnabled(self.markCompletedItem, 1)
                self.menu.setItemEnabled(self.copyItem, 1)
            if self.copyTask:
                self.menu.setItemEnabled(self.pasteItem, 1)
            else:
                self.menu.setItemEnabled(self.pasteItem, 0)
            
            self.menu.popup(coord)
    
    def setProjectOpen(self, o=0):
        """
        Public slot to set the project status.
        
        @param o flag indicating the project status
        """
        self.projectOpen = o
    
    def addTask(self, description, priority=1, filename="", lineno=0, 
                completed=0, _time=0, isProjectTask=0, isBugfixTask=0):
        """
        Public slot to add a task.
        
        @param description descriptive text of the task (string or QString)
        @param priority priority of the task (0=high, 1=normal, 2=low)
        @param filename filename containing the task (string or QString)
        @param lineno line number containing the task (integer)
        @param completed flag indicating completion status (boolean)
        @param _time creation time of the task (float, if 0 use current time)
        @param isProjectTask flag indicating a task related to the current project (boolean)
        @param isBugfixTask flag indicating a bugfix task (boolean)
        """
        itm = Task(self, description, priority, filename, lineno, completed, 
                   _time, isProjectTask, isBugfixTask, self.project.ppath)
        self.tasks.append(itm)
    
    def addFileTask(self, description, filename, lineno, isBugfixTask=0):
        """
        Public slot to add a file related task.
        
        @param description descriptive text of the task (string or QString)
        @param filename filename containing the task (string or QString)
        @param lineno line number containing the task (integer)
        @param isBugfixTask flag indicating a bugfix task (boolean)
        """
        self.addTask(description, filename=filename, lineno=lineno,
                     isProjectTask = self.project.isProjectSource(filename),
                     isBugfixTask = isBugfixTask)
        
    def getProjectTasks(self):
        """
        Public method to retrieve all project related tasks.
        
        @return copy of tasks (list of Task)
        """
        tasks = [task for task in self.tasks if task.isProjectTask()]
        return tasks[:]
        
    def getGlobalTasks(self):
        """
        Public method to retrieve all non project related tasks.
        
        @return copy of tasks (list of Task)
        """
        tasks = [task for task in self.tasks if not task.isProjectTask()]
        return tasks[:]
        
    def clearTasks(self):
        """
        Public slot to clear all tasks from display.
        """
        self.tasks = []
        self.clear()
        
    def clearProjectTasks(self):
        """
        Public slot to clear project related tasks.
        """
        for task in self.tasks[:]:
            if task.isProjectTask():
                if self.copyTask == task:
                    self.copyTask = None
                self.takeItem(task)
                self.tasks.remove(task)
                del task
        
    def clearFileTasks(self, filename):
        """
        Public slot to clear all task related to a file.
        
        @param filename name of the file (string or QString)
        """
        filename = unicode(filename)
        for task in self.tasks[:]:
            if task.getFilename() == filename:
                if self.copyTask == task:
                    self.copyTask = None
                self.takeItem(task)
                self.tasks.remove(task)
                del task
        
    def handleProperties(self):
        """
        Private slot to handle the "Properties" context menu entry
        """
        task = self.currentItem()
        dlg = TaskPropertiesDialog(task, self, self.projectOpen)
        ro = task.getFilename() != ""
        if ro:
            dlg.setReadOnly()
        if dlg.exec_loop() == QDialog.Accepted and not ro:
            data = dlg.getData()
            task.setDescription(data[0])
            task.setPriority(data[1])
            task.setCompleted(data[2])
            task.setProjectTask(data[3])
    
    def handleNewTask(self):
        """
        Private slot to handle the "New Task" context menu entry.
        """
        dlg = TaskPropertiesDialog(None, self, self.projectOpen)
        if dlg.exec_loop() == QDialog.Accepted:
            data = dlg.getData()
            self.addTask(data[0], data[1], completed=data[2], isProjectTask=data[3])
    
    def handleCompleted(self):
        """
        Private slot to handle the "Mark Completed" context menu entry.
        """
        task = self.currentItem()
        task.setCompleted(1)
    
    def handleDeleteCompleted(self):
        """
        Private slot to handle the "Delete Completed Tasks" context menu entry.
        """
        for task in self.tasks[:]:
            if task.isCompleted():
                if self.copyTask == task:
                    self.copyTask = None
                self.takeItem(task)
                self.tasks.remove(task)
                del task
    
    def handleCopy(self):
        """
        Private slot to handle the "Copy" context menu entry.
        """
        task = self.currentItem()
        self.copyTask = task
    
    def handlePaste(self):
        """
        Private slot to handle the "Paste" context menu entry.
        """
        if self.copyTask:
            self.addTask(self.copyTask.description,
                         priority = self.copyTask.priority,
                         completed = self.copyTask.completed)
    
    def handleDelete(self):
        """
        Private slot to handle the "Delete Task" context menu entry.
        """
        task = self.currentItem()
        if self.copyTask == task:
            self.copyTask = None
        self.takeItem(task)
        self.tasks.remove(task)
        del task
    
    def handleGoTo(self):
        """
        Private slot to handle the "Go To" context menu entry.
        """
        task = self.currentItem()
        self.emit(PYSIGNAL('displayFile'), (task.getFilename(), task.getLineno())) 

    def handlePreferencesChanged(self):
        """
        Private slot to react to changes of the preferences.
        """
        self.triggerUpdate()
