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

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

"""
Module implementing the Watchpoint viewer widget.
"""

from qt import QListView, QListViewItem, QPopupMenu, SIGNAL, QDialog, Qt, QString

from EditWatchpointDialog import EditWatchpointDialog
from KdeQt import KQMessageBox

class WatchPointViewer(QListView):
    """
    Class implementing the Watchpoint viewer widget.
    
    Watchpoints will be shown with all their details. They can be modified through
    the context menu of this widget.
    """
    def __init__(self, dbs, parent = None):
        """
        Constructor
        
        @param reference to the debug server object
        @param parent the parent (QWidget)
        """
        QListView.__init__(self,parent)
        
        self.dbs = dbs
        
        self.addColumn(self.trUtf8('Condition'))
        
        col = self.addColumn(self.trUtf8('Special'))
        
        col = self.addColumn(self.trUtf8('Temporary'))
        self.setColumnAlignment(col, Qt.AlignHCenter)
        
        col = self.addColumn(self.trUtf8('Enabled'))
        self.setColumnAlignment(col, Qt.AlignHCenter)
        
        col = self.addColumn(self.trUtf8('Ignore Count'))
        self.setColumnAlignment(col, Qt.AlignRight)
        
        self.setSorting(0)
        self.setSelectionMode(QListView.Extended)
        self.setAllColumnsShowFocus(1)
        
        self.setCaption(self.trUtf8("Watchpoints"))
        
        self.connect(self,SIGNAL('contextMenuRequested(QListViewItem *, const QPoint &, int)'),
                     self.handleContextMenu)
        self.connect(self,SIGNAL('doubleClicked(QListViewItem *, const QPoint &, int)'),
                     self.handleDoubleClicked)
        
        self.createPopupMenus()
        
    def createPopupMenus(self):
        """
        Private method to generate the popup menus.
        """
        self.menu = QPopupMenu()
        self.menu.insertItem(self.trUtf8("Add"), self.addWatch)
        self.menu.insertItem(self.trUtf8("Edit..."), self.editWatch)
        self.menu.insertSeparator()
        self.menu.insertItem(self.trUtf8("Enable"), self.enableWatch)
        self.menu.insertItem(self.trUtf8("Enable all"), self.enableAllWatches)
        self.menu.insertSeparator()
        self.menu.insertItem(self.trUtf8("Disable"), self.disableWatch)
        self.menu.insertItem(self.trUtf8("Disable all"), self.disableAllWatches)
        self.menu.insertSeparator()
        self.menu.insertItem(self.trUtf8("Delete"), self.deleteWatch)
        self.menu.insertItem(self.trUtf8("Delete all"), self.deleteAllWatches)

        self.backMenuIds = {}
        self.backMenu = QPopupMenu()
        self.backMenu.insertItem(self.trUtf8("Add"), self.addWatch)
        self.backMenuIds["EnableAll"] = \
            self.backMenu.insertItem(self.trUtf8("Enable all"), self.enableAllWatches)
        self.backMenuIds["DisableAll"] = \
            self.backMenu.insertItem(self.trUtf8("Disable all"), self.disableAllWatches)
        self.backMenuIds["DeleteAll"] = \
            self.backMenu.insertItem(self.trUtf8("Delete all"), self.deleteAllWatches)
        self.connect(self.backMenu, SIGNAL('aboutToShow()'), self.handleShowBackMenu)

        self.multiMenu = QPopupMenu()
        self.multiMenu.insertItem(self.trUtf8("Add"), self.addWatch)
        self.multiMenu.insertSeparator()
        self.multiMenu.insertItem(self.trUtf8("Enable selected"), self.enableSelectedWatches)
        self.multiMenu.insertItem(self.trUtf8("Enable all"), self.enableAllWatches)
        self.multiMenu.insertSeparator()
        self.multiMenu.insertItem(self.trUtf8("Disable selected"), self.disableSelectedWatches)
        self.multiMenu.insertItem(self.trUtf8("Disable all"), self.disableAllWatches)
        self.multiMenu.insertSeparator()
        self.multiMenu.insertItem(self.trUtf8("Delete selected"), self.deleteSelectedWatches)
        self.multiMenu.insertItem(self.trUtf8("Delete all"), self.deleteAllWatches)

    def findDuplicates(self, cond, special, showMessage=0):
        """
        Private method to check, if an entry already exists.
        
        @param cond condition to check (QString)
        @param special special condition to check (QString)
        @param showMessage flag indicating a message should be shown,
            if a duplicate entry is found (boolean)
        @return flag indicating a duplicate entry (boolean)
        """
        itm = self.firstChild()
        while itm is not None:
            if QString.compare(itm.text(0), cond) == 0:
                if special.isEmpty():
                    break
                else:
                    if QString.compare(itm.text(1), special) == 0:
                        break
            itm = itm.nextSibling()
        
        duplicate = itm is not None
        if showMessage and duplicate:
            if special.isEmpty():
                msg = self.trUtf8("""<p>A watchpoint with the condition '<b>%1</b>'"""
                                  """ already exists.</p>""").arg(cond)
            else:
                msg = self.trUtf8("""<p>A watchpoint with the condition '<b>%1</b>'"""
                                  """ for the variable <b>%2</b> already exists.</p>""")\
                                  .arg(special).arg(cond)
            KQMessageBox.warning(None,
                self.trUtf8("Watchpoint already exists"),
                msg,
                self.trUtf8("&OK"),
                QString.null,
                QString.null,
                0, -1)

        return duplicate
    
    def makeCondition(self, cond, special):
        """
        Private method to construct the condition string.
        
        @param cond condition (QString)
        @param special special condition (QString)
        @return condition string (QString)
        """
        if special.isEmpty():
            _cond = QString(cond)
        else:
            if special.compare(\
                self.trUtf8("created", "must be same as in EditWatchpointForm")) == 0:
                _cond = QString(cond).append(" ??created??")
            elif special.compare(\
                self.trUtf8("changed", "must be same as in EditWatchpointForm")) == 0:
                _cond = QString(cond).append(" ??changed??")
        return _cond
        
    def addWatch(self):
        """
        Private slot to handle the add watchpoint context menu entry.
        """
        dlg = EditWatchpointDialog((QString(""), 0, 1, 0, QString("")), self)
        if dlg.exec_loop() == QDialog.Accepted:
            cond, temp, enabled, ignorecount, special = dlg.getData()
            if not self.findDuplicates(cond, special, 1):
                QListViewItem(self, 
                              cond,
                              special, 
                              temp and self.trUtf8("Yes") or self.trUtf8("No"),
                              enabled and self.trUtf8("Yes") or self.trUtf8("No"),
                              "%d" % ignorecount)
                _cond = self.makeCondition(cond, special)
                self.dbs.remoteWatchpoint(_cond, 1, temp)
                if not enabled:
                    self.dbs.remoteWatchpointEnable(_cond, 0)
                if ignorecount:
                    self.dbs.remoteWatchpointIgnore(_cond, ignorecount)
    
    def editWatch(self):
        """
        Private slot to handle the edit watchpoint context menu entry.
        """
        itm = self.currentItem()
        if itm is not None:
            self.editWatchpoint(itm)
        
    def editWatchpoint(self, itm):
        """
        Private slot to edit a watchpoint.
        
        @param itm watchpoint to be edited (QListViewItem)
        """
        wpProperties = self.extractData(itm)
        dlg = EditWatchpointDialog(wpProperties, self)
        if dlg.exec_loop() == QDialog.Accepted:
            cond, temp, enabled, ignorecount, special = dlg.getData()
            
            itm.setText(0, cond)
            itm.setText(1, special)
            itm.setText(2, temp and self.trUtf8("Yes") or self.trUtf8("No"))
            itm.setText(3, enabled and self.trUtf8("Yes") or self.trUtf8("No"))
            itm.setText(4, "%d" % ignorecount)
            
            # step 1: delete the watchpoint
            _cond = self.makeCondition(wpProperties[0], wpProperties[4])
            self.dbs.remoteWatchpoint(_cond, 0)
            
            # step 2: add a new watchpoint with the new properties
            _cond = self.makeCondition(cond, special)
            self.dbs.remoteWatchpoint(_cond, 1, temp)
            if not enabled:
                self.dbs.remoteWatchpointEnable(_cond, 0)
            if ignorecount:
                self.dbs.remoteWatchpointIgnore(_cond, ignorecount)
    
    def deleteWatch(self):
        """
        Private slot to handle the delete watchpoint context menu entry.
        """
        itm = self.currentItem()
        cond, temp, enabled, ignorecount, special = self.extractData(itm)
        _cond = self.makeCondition(cond, special)
        self.dbs.remoteWatchpoint(_cond, 0)
        self.takeItem(itm)
        del itm
    
    def deleteAllWatches(self):
        """
        Private slot to handle the delete all watchpoints context menu entry.
        """
        itm = self.firstChild()
        while itm:
            cond, temp, enabled, ignorecount, special = self.extractData(itm)
            _cond = self.makeCondition(cond, special)
            self.dbs.remoteWatchpoint(_cond, 0)
            itm = itm.itemBelow()
        self.clear()
    
    def deleteSelectedWatches(self):
        """
        Private slot to handle the delete selected watchpoints context menu entry.
        """
        for itm in self.getSelectedItems():
            cond, temp, enabled, ignorecount, special = self.extractData(itm)
            _cond = self.makeCondition(cond, special)
            self.dbs.remoteWatchpoint(_cond, 0)
            self.takeItem(itm)
            del itm
    
    def enableWatch(self):
        """
        Private slot to handle the enable watchpoint context menu entry.
        """
        itm = self.currentItem()
        cond, temp, enabled, ignorecount, special = self.extractData(itm)
        _cond = self.makeCondition(cond, special)
        self.dbs.remoteWatchpointEnable(_cond, 1)
        itm.setText(3, self.trUtf8("Yes"))
    
    def enableAllWatches(self):
        """
        Private slot to handle the enable all watchpoints context menu entry.
        """
        itm = self.firstChild()
        while itm:
            cond, temp, enabled, ignorecount, special = self.extractData(itm)
            _cond = self.makeCondition(cond, special)
            self.dbs.remoteWatchpointEnable(_cond, 1)
            itm.setText(3, self.trUtf8("Yes"))
            itm = itm.itemBelow()
    
    def enableSelectedWatches(self):
        """
        Private slot to handle the enable selected watchpoints context menu entry.
        """
        for itm in self.getSelectedItems():
            cond, temp, enabled, ignorecount, special = self.extractData(itm)
            _cond = self.makeCondition(cond, special)
            self.dbs.remoteWatchpointEnable(_cond, 1)
            itm.setText(3, self.trUtf8("Yes"))
   
    def disableWatch(self):
        """
        Private slot to handle the disable watchpoint context menu entry.
        """
        itm = self.currentItem()
        cond, temp, enabled, ignorecount, special = self.extractData(itm)
        _cond = self.makeCondition(cond, special)
        self.dbs.remoteWatchpointEnable(_cond, 0)
        itm.setText(3, self.trUtf8("No"))

    def disableAllWatches(self):
        """
        Private slot to handle the disable all watchpoints context menu entry.
        """
        itm = self.firstChild()
        while itm:
            cond, temp, enabled, ignorecount, special = self.extractData(itm)
            _cond = self.makeCondition(cond, special)
            self.dbs.remoteWatchpointEnable(_cond, 0)
            itm.setText(3, self.trUtf8("No"))
            itm = itm.itemBelow()
    
    def disableSelectedWatches(self):
        """
        Private slot to handle the disable selected watchpoints context menu entry.
        """
        for itm in self.getSelectedItems():
            cond, temp, enabled, ignorecount, special = self.extractData(itm)
            _cond = self.makeCondition(cond, special)
            self.dbs.remoteWatchpointEnable(_cond, 0)
            itm.setText(3, self.trUtf8("No"))
    
    def clearWatchpoint(self, cond):
        """
        Public slot to clear a watchpoint.
        
        @param cond condition of the watchpoint (string or QString)
        """
        itm = self.findItem(cond, 0)
        if itm is not None:
            self.takeItem(itm)
            del itm
    
    def handleClientWatchConditionError(self, cond):
        """
        Public method to handle a condition error of a watchpoint.
        
        @param cond condition of the watchpoint (string or QString)
        """
        KQMessageBox.critical(None,
            self.trUtf8("Watchpoint Condition Error"),
            self.trUtf8("""<p>The watchpoint condition <b>%1</b>"""
                        """ contains a syntax error.</p>""")\
                        .arg(cond),
            self.trUtf8("&OK"),
            QString.null,
            QString.null,
            0, -1)
        itm = self.findItem(cond, 0)
        if itm is not None:
            self.editWatchpoint(itm)

    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)
        """
        cnt = self.getSelectedItemsCount()
        if cnt > 1:
            self.multiMenu.popup(coord)
        elif cnt == 1:
            self.menu.popup(coord)
        else:
            self.backMenu.popup(coord)
    
    def handleDoubleClicked(self,itm,coord,col):
        """
        Private slot to handle the double clicked signal.
        
        @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 not None:
            self.editWatchpoint(itm)
    
    def handleShowBackMenu(self):
        """
        Private slot to handle the aboutToShow signal of the background menu.
        """
        if self.childCount() == 0:
            self.backMenu.setItemEnabled(self.backMenuIds["EnableAll"], 0)
            self.backMenu.setItemEnabled(self.backMenuIds["DisableAll"], 0)
            self.backMenu.setItemEnabled(self.backMenuIds["DeleteAll"], 0)
        else:
            self.backMenu.setItemEnabled(self.backMenuIds["EnableAll"], 1)
            self.backMenu.setItemEnabled(self.backMenuIds["DisableAll"], 1)
            self.backMenu.setItemEnabled(self.backMenuIds["DeleteAll"], 1)
    
    def getSelectedItems(self):
        """
        Public method to get the selected items.
        
        @return list of selected items (list of QListViewItem)
        """
        selectedItems = []
        itm = self.firstChild()
        while itm:
            if itm.isSelected():
                selectedItems.append(itm)
            itm = itm.itemBelow()
        return selectedItems
    
    def getSelectedItemsCount(self):
        """
        Public method to get the count of items selcted.
        
        @return count of items selected (integer)
        """
        count = 0
        itm = self.firstChild()
        while itm:
            if itm.isSelected():
                count += 1
            itm = itm.itemBelow()
        return count

    def getWatchpoints(self):
        """
        Public mathod to retrieve all watchpoints
        
        @return list of tuples with condition, temp, enabled, ignoreCount
            (QString, boolean, boolean, integer)
        """
        wpList = []
        itm = self.firstChild()
        while itm:
            cond, temp, enabled, ignorecount, special = self.extractData(itm)
            _cond = self.makeCondition(cond, special)
            wpList.append((_cond, temp, enabled, ignorecount))
            itm = itm.itemBelow()
        return wpList
        
    def getWatchpointsProperties(self):
        """
        Public method to retrieve the properties of all watchpoints
        
        @return list of tuples with condition, temp, enabled, ignoreCount, special
            (QString, boolean, boolean, integer, QString)
        """
        wpList = []
        itm = self.firstChild()
        while itm:
            cond, temp, enabled, ignorecount, special = self.extractData(itm)
            wpList.append((cond, temp, enabled, ignorecount, special))
            itm = itm.itemBelow()
        return wpList
        
    def setWatchpoint(self, properties):
        """
        Public method to set a watchpoint with its properties.
        
        @param properties tuple of condition, temp, enabled, ignoreCount, special
            (QString or string, boolean, boolean, integer, QString or string)
        """
        cond, temp, enabled, ignorecount, special = properties
        cond = QString(cond)
        special = QString(special)
        if not self.findDuplicates(cond, special):
            QListViewItem(self, 
                          cond,
                          special, 
                          temp and self.trUtf8("Yes") or self.trUtf8("No"),
                          enabled and self.trUtf8("Yes") or self.trUtf8("No"),
                          "%d" % ignorecount)
            _cond = self.makeCondition(cond, special)
            self.dbs.remoteWatchpoint(_cond, 1, temp)
            if not enabled:
                self.dbs.remoteWatchpointEnable(_cond, 0)
            if ignorecount:
                self.dbs.remoteWatchpointIgnore(_cond, ignorecount)
        
    def extractData(self, itm):
        """
        Private method to extract the data of a watchpoint item.
        
        @param itm the watchpoint item (QListViewItem)
        @return tuple with condition, temp, enabled, ignoreCount
            (QString, boolean, boolean, integer)
        """
        temp = itm.text(2).compare(self.trUtf8("Yes")) == 0 and 1 or 0
        enabled = itm.text(3).compare(self.trUtf8("Yes")) == 0 and 1 or 0
        count, ok = itm.text(4).toUInt()
        return (itm.text(0), temp, enabled, count, itm.text(1))
