/***************************************************************************
    begin                : Tue May 13 2003
    copyright            : (C) 2003 by John Birch
    email                : jbb@kdevelop.org
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "gdbbreakpointwidget.h"
#include "gdbtable.h"
#include "debuggertracingdialog.h"
#include "gdbcommand.h"
#include "gdbcontroller.h"

#include "breakpoint.h"
#include "domutil.h"

#include <kdebug.h>
#include <kiconloader.h>
#include <klocale.h>
#include <kpopupmenu.h>
#include <kurl.h>
#include <kmessagebox.h>

#include <qvbuttongroup.h>
#include <qfileinfo.h>
#include <qheader.h>
#include <qtable.h>
#include <qtoolbutton.h>
#include <qtooltip.h>
#include <qwhatsthis.h>
#include <qvbox.h>
#include <qlayout.h>
#include <qlabel.h>
#include <qpushbutton.h>
#include <qcheckbox.h>

#include <stdlib.h>
#include <ctype.h>

/***************************************************************************/
/***************************************************************************/
/***************************************************************************/

namespace GDBDebugger
{

enum Column {
    Control     = 0,
    Enable      = 1,
    Type        = 2,
    Status      = 3,
    Location    = 4,
    Condition   = 5,
    IgnoreCount = 6,
    Hits        = 7,
    Tracing     = 8
};


#define numCols 9

enum BW_ITEMS { BW_ITEM_Show, BW_ITEM_Edit, BW_ITEM_Disable, BW_ITEM_Delete,
                BW_ITEM_DisableAll, BW_ITEM_EnableAll, BW_ITEM_DeleteAll};

static int m_activeFlag = 0;

/***************************************************************************/
/***************************************************************************/
/***************************************************************************/

class BreakpointTableRow : public QTableItem
{
public:

    BreakpointTableRow(QTable* table, EditType editType, Breakpoint* bp);
    ~BreakpointTableRow();

    bool match (Breakpoint* bp) const;
    void reset ();
    void setRow();

    Breakpoint* breakpoint()        { return m_breakpoint; }

private:
    void appendEmptyRow();

private:
    Breakpoint* m_breakpoint;
};

/***************************************************************************/
/***************************************************************************/
/***************************************************************************/

BreakpointTableRow::BreakpointTableRow(QTable* parent, EditType editType,
                                       Breakpoint* bp) :
        QTableItem(parent, editType, ""),
        m_breakpoint(bp)
{
    appendEmptyRow();
    setRow();
}

/***************************************************************************/

BreakpointTableRow::~BreakpointTableRow()
{
    m_breakpoint->deleteLater();
}

/***************************************************************************/

bool BreakpointTableRow::match(Breakpoint* breakpoint) const
{
    return m_breakpoint->match(breakpoint);
}

/***************************************************************************/

void BreakpointTableRow::reset()
{
    m_breakpoint->reset();
    setRow();
}

/***************************************************************************/

void BreakpointTableRow::appendEmptyRow()
{
    int row = table()->numRows();
    table()->setNumRows(row+1);

    table()->setItem(row, Control, this);

    QCheckTableItem* cti = new QCheckTableItem( table(), "");
    table()->setItem(row, Enable, cti);

    ComplexEditCell* act = new ComplexEditCell(table());
    table()->setItem(row, Tracing, act);
    QObject::connect(act, SIGNAL(edit(QTableItem*)), 
                     table()->parent(), SLOT(editTracing(QTableItem*)));
}

/***************************************************************************/

void BreakpointTableRow::setRow()
{
    if ( m_breakpoint )
    {
        QTableItem *item =  table()->item ( row(), Enable );
        Q_ASSERT(item->rtti() == 2);
        ((QCheckTableItem*)item)->setChecked(m_breakpoint->isEnabled());

        QString status=m_breakpoint->statusDisplay(m_activeFlag);

        table()->setText(row(), Status, status);
        table()->setText(row(), Condition, m_breakpoint->conditional());
        table()->setText(row(), IgnoreCount, QString::number(m_breakpoint->ignoreCount() ));
        table()->setText(row(), Hits, QString::number(m_breakpoint->hits() ));

        QString displayType = m_breakpoint->displayType();
        table()->setText(row(), Location, m_breakpoint->location());

               
        QTableItem* ce = table()->item( row(), Tracing );
        ce->setText(breakpoint()->tracingEnabled() ? "Enabled" : "Disabled");
        // In case there's editor open in this cell, update it too.
        static_cast<ComplexEditCell*>(ce)->updateValue();


        if (m_breakpoint->isTemporary())
            displayType = i18n(" temporary");
        if (m_breakpoint->isHardwareBP())
            displayType += i18n(" hw");

        table()->setText(row(), Type, displayType);
        table()->adjustColumn(Type);
        table()->adjustColumn(Status);
        table()->adjustColumn(Location);
        table()->adjustColumn(Hits);
        table()->adjustColumn(IgnoreCount);
        table()->adjustColumn(Condition);
    }
}

/***************************************************************************/
/***************************************************************************/
/***************************************************************************/

GDBBreakpointWidget::GDBBreakpointWidget(GDBController* controller,
                                         QWidget *parent, const char *name) :
QHBox(parent, name),
controller_(controller)
{
    m_table = new GDBTable(0, numCols, this, name);
    m_table->setSelectionMode(QTable::SingleRow);
    m_table->setShowGrid (false);
    m_table->setLeftMargin(0);
    m_table->setFocusStyle(QTable::FollowStyle);

    m_table->hideColumn(Control);
    m_table->setColumnReadOnly(Type, true);
    m_table->setColumnReadOnly(Status, true);
    m_table->setColumnReadOnly(Hits, true);
    m_table->setColumnWidth( Enable, 20);

    QHeader *header = m_table->horizontalHeader();

    header->setLabel( Enable,       "" );
    header->setLabel( Type,         i18n("Type") );
    header->setLabel( Status,       i18n("Status") );
    header->setLabel( Location,     i18n("Location") );
    header->setLabel( Condition,    i18n("Condition") );
    header->setLabel( IgnoreCount,  i18n("Ignore Count") );
    header->setLabel( Hits,         i18n("Hits") );
    header->setLabel( Tracing,      i18n("Tracing") );

    QPopupMenu* newBreakpoint = new QPopupMenu(this);
    newBreakpoint->insertItem(i18n("Code breakpoint", "Code"), 
                              BP_TYPE_FilePos);
    newBreakpoint->insertItem(i18n("Data breakpoint", "Data write"), 
                              BP_TYPE_Watchpoint);
    newBreakpoint->insertItem(i18n("Data read breakpoint", "Data read"),
                              BP_TYPE_ReadWatchpoint);


    m_ctxMenu = new QPopupMenu( this );
    m_ctxMenu->insertItem( i18n("New breakpoint", "New"),
                                newBreakpoint);
    m_ctxMenu->insertItem( i18n( "Show text" ),    BW_ITEM_Show );
    int edit_id = 
        m_ctxMenu->insertItem( i18n( "Edit" ),    BW_ITEM_Edit );
    m_ctxMenu->setAccel(Qt::Key_Enter, edit_id);
    m_ctxMenu->insertItem( i18n( "Disable" ), BW_ITEM_Disable );
    int del_id = 
        m_ctxMenu->insertItem( SmallIcon("breakpoint_delete"),
                               i18n( "Delete" ),  BW_ITEM_Delete );
    m_ctxMenu->setAccel(Qt::Key_Delete, del_id);
    m_ctxMenu->insertSeparator();
    m_ctxMenu->insertItem( i18n( "Disable all"), BW_ITEM_DisableAll );
    m_ctxMenu->insertItem( i18n( "Enable all"), BW_ITEM_EnableAll );
    m_ctxMenu->insertItem( i18n( "Delete all"), BW_ITEM_DeleteAll );

    m_table->show();

    connect( newBreakpoint,       SIGNAL(activated(int)),
             this,          SLOT(slotAddBlankBreakpoint(int)) );

    connect( m_table,       SIGNAL(contextMenuRequested(int, int, const QPoint &)),
             this,          SLOT(slotContextMenuShow(int, int, const QPoint & )) );
    connect( m_ctxMenu,     SIGNAL(activated(int)),
             this,          SLOT(slotContextMenuSelect(int)) );

    connect( m_table,       SIGNAL(doubleClicked(int, int, int, const QPoint &)),
             this,          SLOT(slotRowDoubleClicked(int, int, int, const QPoint &)));

    connect( m_table,       SIGNAL(valueChanged(int, int)),
             this,          SLOT(slotNewValue(int, int)));

    connect( m_table,       SIGNAL(returnPressed()),
             this,          SLOT(slotEditBreakpoint()));
//    connect( m_table,       SIGNAL(f2Pressed()),
//             this,          SLOT(slotEditBreakpoint()));
    connect( m_table,       SIGNAL(deletePressed()),
             this,          SLOT(slotRemoveBreakpoint()));
    connect( m_table,       SIGNAL(insertPressed()),
             this,          SLOT(slotAddBlankBreakpoint()));

    // FIXME: maybe, all debugger components should derive from
    // a base class that does this connect.
    connect(controller, SIGNAL(event(GDBController::event_t)),
            this,       SLOT(slotEvent(GDBController::event_t)));

    connect(controller, 
            SIGNAL(watchpointHit(int, const QString&, const QString&)),
            this,
            SLOT(slotWatchpointHit(int, const QString&, const QString&)));
}

/***************************************************************************/

GDBBreakpointWidget::~GDBBreakpointWidget()
{
    delete m_table;
}

/***************************************************************************/

void GDBBreakpointWidget::reset()
{
    for ( int row = 0; row < m_table->numRows(); row++ )
    {
        BreakpointTableRow* btr = (BreakpointTableRow *) m_table->item(row, Control);
        if (btr)
        {
            btr->reset();
            sendToGdb(*(btr->breakpoint()));
        }
    }
}

/***************************************************************************/

// When a file is loaded then we need to tell the editor (display window)
// which lines contain a breakpoint.
void GDBBreakpointWidget::slotRefreshBP(const KURL &filename)
{
    for ( int row = 0; row < m_table->numRows(); row++ )
    {
        BreakpointTableRow* btr = (BreakpointTableRow *) m_table->item(row, Control);
        if (btr)
        {
            FilePosBreakpoint* bp = dynamic_cast<FilePosBreakpoint*>(btr->breakpoint());
            if (bp && bp->hasFileAndLine() 
                && (bp->fileName() == filename.path()))
                emit refreshBPState(*bp);
        }
    }
}

void GDBBreakpointWidget::slotBreakpointHit(int id)
{
    BreakpointTableRow* br = findId(id);

    // FIXME: should produce an message, this is most likely 
    // an error.
    if (!br)
        return;

    Breakpoint* b = br->breakpoint();

    if (b->tracingEnabled())
    {
        controller_->addCommand(
            new CliCommand(("printf " 
                            + b->traceRealFormatString()).latin1(),
                           this,
                           &GDBBreakpointWidget::handleTracingPrintf));
                           
        controller_->addCommand(new 
                            GDBCommand("-exec-continue"));
        
    }
    else
    {
        controller_->demandAttention();
    }
}

void GDBBreakpointWidget::slotWatchpointHit(int id,
                                            const QString& oldValue,
                                            const QString& newValue)
{
    BreakpointTableRow* br = findId(id);

    // FIXME: should produce an message, this is most likely 
    // an error.
    if (!br)
        return;

    Watchpoint* b = dynamic_cast<Watchpoint*>(br->breakpoint());


    KMessageBox::information(
        0, 
        i18n("<b>Data write breakpoint</b><br>"
             "Expression: %1<br>"
             "Address: 0x%2<br>"
             "Old value: %3<br>"
             "New value: %4")
        .arg(b->varName())
        .arg(b->address(), 0, 16)
        .arg(oldValue)
        .arg(newValue));    
}

/***************************************************************************/

BreakpointTableRow* GDBBreakpointWidget::find(Breakpoint *breakpoint)
{
    // NOTE:- The match doesn't have to be equal. Each type of bp
    // must decide on the match criteria.
    Q_ASSERT (breakpoint);

    for ( int row = 0; row < m_table->numRows(); row++ )
    {
        BreakpointTableRow* btr = (BreakpointTableRow *) m_table->item(row, Control);
        if (btr && btr->match(breakpoint))
            return btr;
    }

    return 0;
}

/***************************************************************************/

// The Id is supplied by the debugger
BreakpointTableRow* GDBBreakpointWidget::findId(int dbgId)
{
    for ( int row = 0; row < m_table->numRows(); row++ )
    {
        BreakpointTableRow* btr = (BreakpointTableRow *) m_table->item(row, Control);
        if (btr && btr->breakpoint()->dbgId() == dbgId)
            return btr;
    }

    return 0;
}

/***************************************************************************/

// The key is a unique number supplied by us
BreakpointTableRow* GDBBreakpointWidget::findKey(int BPKey)
{
    for ( int row = 0; row < m_table->numRows(); row++ )
    {
        BreakpointTableRow* btr = (BreakpointTableRow *) m_table->item(row, Control);
        if (btr && btr->breakpoint()->key() == BPKey)
            return btr;
    }

    return 0;
}

bool GDBBreakpointWidget::hasWatchpointForAddress(
    unsigned long long address) const
{
    for(int i = 0; i < m_table->numRows(); ++i)
    {
        BreakpointTableRow* br = (BreakpointTableRow*)
            m_table->item(i, Control);

        Watchpoint* w = dynamic_cast<Watchpoint*>(br->breakpoint());
        if (w && w->address() == address)
            return true;
    }
    return false;
}

/***************************************************************************/

BreakpointTableRow* GDBBreakpointWidget::addBreakpoint(Breakpoint *bp)
{
    BreakpointTableRow* btr =
        new BreakpointTableRow( m_table, QTableItem::WhenCurrent, bp );

    connect(bp, SIGNAL(modified(Breakpoint*)),
            this, SLOT(slotBreakpointModified(Breakpoint*)));
    
    sendToGdb(*bp);
                                              
    return btr;
}

/***************************************************************************/

void GDBBreakpointWidget::removeBreakpoint(BreakpointTableRow* btr)
{
    if (!btr)
        return;

    // Pending but the debugger hasn't started processing this bp so
    // we can just remove it.   
    Breakpoint* bp = btr->breakpoint();
    // No gdb breakpoint, and no breakpoint addition command in the
    // queue. Just remove.
    if (bp->dbgId() == -1 && !bp->isDbgProcessing())
    {
        bp->setActionDie();
        sendToGdb(*bp);
        m_table->removeRow(btr->row());
    }
    else
    {
        bp->setActionClear(true);
        sendToGdb(*bp);
        btr->setRow();
    }
}

/***************************************************************************/

void GDBBreakpointWidget::slotToggleBreakpoint(const QString &fileName, int lineNum)
{
    FilePosBreakpoint *fpBP = new FilePosBreakpoint(fileName, lineNum+1);

    BreakpointTableRow* btr = find(fpBP);
    if (btr)
    {
        removeBreakpoint(btr);
    }
    else
        addBreakpoint(fpBP);
}

/***************************************************************************/

void GDBBreakpointWidget::slotToggleBreakpointEnabled(const QString &fileName, int lineNum)
{
    FilePosBreakpoint *fpBP = new FilePosBreakpoint(fileName, lineNum+1);

    BreakpointTableRow* btr = find(fpBP);
    delete fpBP;
    if (btr)
    {
        Breakpoint* bp=btr->breakpoint();
        bp->setEnabled(!bp->isEnabled());
        sendToGdb(*bp);
    }
}

/***************************************************************************/

void GDBBreakpointWidget::slotToggleWatchpoint(const QString &varName)
{
    Watchpoint *watchpoint = new Watchpoint(varName, false, true);
    BreakpointTableRow* btr = find(watchpoint);
    if (btr)
    {
        removeBreakpoint(btr);
        delete watchpoint;
    }
    else
        addBreakpoint(watchpoint);
}

void GDBBreakpointWidget::handleBreakpointList(const GDBMI::ResultRecord& r)
{
    m_activeFlag++;

    const GDBMI::Value& blist = r["BreakpointTable"]["body"];

    for(unsigned i = 0, e = blist.size(); i != e; ++i)
    {
        const GDBMI::Value& b = blist[i];

        int id = b["number"].literal().toInt();
        BreakpointTableRow* btr = findId(id);
        if (btr)
        {
            Breakpoint *bp = btr->breakpoint();
            bp->setActive(m_activeFlag, id);
            bp->setHits(b["times"].toInt());
            if (b.hasField("ignore"))                
                bp->setIgnoreCount(b["ignore"].toInt());
            else
                bp->setIgnoreCount(0);
            if (b.hasField("cond"))
                bp->setConditional(b["cond"].literal());
            else
                bp->setConditional(QString::null);
            btr->setRow();
            emit publishBPState(*bp);
        }
        else
        {
            // It's a breakpoint added outside, most probably
            // via gdb console. Add it now.
            QString type = b["type"].literal();

            if (type == "breakpoint" || type == "hw breakpoint")
            {
                if (b.hasField("fullname") && b.hasField("line"))
                {
                    Breakpoint* bp = new FilePosBreakpoint(
                        b["fullname"].literal(), 
                        b["line"].literal().toInt());

                    bp->setActive(m_activeFlag, id);
                    bp->setActionAdd(false);
                    bp->setPending(false);

                    new BreakpointTableRow(m_table, 
                                           QTableItem::WhenCurrent, 
                                           bp);

                    emit publishBPState(*bp);
                }
            }
            
        }
    }

    // Remove any inactive breakpoints.
    for ( int row = m_table->numRows()-1; row >= 0 ; row-- )
    {
        BreakpointTableRow* btr = (BreakpointTableRow *) m_table->item(row, Control);
        if (btr)
        {
            Breakpoint* bp = btr->breakpoint();
            if (!(bp->isActive(m_activeFlag)))
            {
                // FIXME: need to review is this happens for 
                // as-yet unset breakpoint.
                bp->removedInGdb();
            }
        }
    }
}

void GDBBreakpointWidget::handleTracingPrintf(const QValueVector<QString>& s)
{
    // The first line of output is the command itself, which we don't need.
    for(unsigned i = 1; i < s.size(); ++i)
        emit tracingOutput(s[i].local8Bit());
}

/***************************************************************************/

void GDBBreakpointWidget::slotBreakpointSet(Breakpoint* bp)
{
    // FIXME: why 'key' is used here?
    BreakpointTableRow* btr = findKey(bp->key());
    if (!btr)
    {
        kdDebug(9012) << "Early return\n";
        return;
    }

    btr->setRow();
}

/***************************************************************************/

void GDBBreakpointWidget::slotAddBlankBreakpoint(int idx)
{
    BreakpointTableRow* btr = 0;
    switch (idx)
    {
      case BP_TYPE_FilePos:
          btr = addBreakpoint(new FilePosBreakpoint());
          break;

      case BP_TYPE_Watchpoint:
          btr = addBreakpoint(new Watchpoint(""));
          break;

      case BP_TYPE_ReadWatchpoint:
          btr = addBreakpoint(new ReadWatchpoint(""));
          break;

      default:
          break;
    }

    if (btr)
    {
        m_table->selectRow(btr->row());
        m_table->editCell(btr->row(), Location, false);
    }
}

/***************************************************************************/

void GDBBreakpointWidget::slotRemoveBreakpoint()
{
    int row = m_table->currentRow();
    if ( row != -1)
    {
        BreakpointTableRow* btr = (BreakpointTableRow *) m_table->item(row, Control);
        removeBreakpoint(btr);
    }
}

/***************************************************************************/

void GDBBreakpointWidget::slotRemoveAllBreakpoints()
{
    for ( int row = m_table->numRows()-1; row>=0; row-- )
    {
        BreakpointTableRow* btr = (BreakpointTableRow *) m_table->item(row, Control);
        removeBreakpoint(btr);
    }
}

/***************************************************************************/

void GDBBreakpointWidget::slotRowDoubleClicked(int row, int col, int btn, const QPoint &)
{
    if ( btn == Qt::LeftButton )
    {
//    kdDebug(9012) << "in slotRowSelected row=" << row << endl;
        BreakpointTableRow* btr = (BreakpointTableRow *) m_table->item(row, Control);
        if (btr)
        {
            FilePosBreakpoint* bp = dynamic_cast<FilePosBreakpoint*>(btr->breakpoint());
            if (bp && bp->hasFileAndLine())
                emit gotoSourcePosition(bp->fileName(), bp->lineNum()-1);

            // put the focus back on the clicked item if appropriate
            if (col == Location || col ==  Condition || col == IgnoreCount)
                m_table->editCell(row, col, false);
        }
    }
}

void GDBBreakpointWidget::slotContextMenuShow( int row, int /*col*/, const QPoint &mousePos )
{
    BreakpointTableRow *btr = (BreakpointTableRow *)m_table->item(row, Control );
    
    if (btr == NULL)
    {
        btr = (BreakpointTableRow *)m_table->item(m_table->currentRow(), 
                                                  Control );
    }

    if (btr != NULL)
    {
        m_ctxMenu->setItemEnabled( 
            BW_ITEM_Show, 
            btr->breakpoint()->hasFileAndLine());

        if (btr->breakpoint( )->isEnabled( ))
        {
            m_ctxMenu->changeItem( BW_ITEM_Disable, i18n("Disable") );
        }
        else
        {
            m_ctxMenu->changeItem( BW_ITEM_Disable, i18n("Enable") );
        }

        m_ctxMenu->setItemEnabled(BW_ITEM_Disable, true);
        m_ctxMenu->setItemEnabled(BW_ITEM_Delete, true);
        m_ctxMenu->setItemEnabled(BW_ITEM_Edit, true);
    }
    else
    {
        m_ctxMenu->setItemEnabled(BW_ITEM_Show, false);
        m_ctxMenu->setItemEnabled(BW_ITEM_Disable, false);
        m_ctxMenu->setItemEnabled(BW_ITEM_Delete, false);
        m_ctxMenu->setItemEnabled(BW_ITEM_Edit, false);
    }

    bool has_bps = (m_table->numRows() != 0);
    m_ctxMenu->setItemEnabled(BW_ITEM_DisableAll, has_bps);
    m_ctxMenu->setItemEnabled(BW_ITEM_EnableAll, has_bps);
    m_ctxMenu->setItemEnabled(BW_ITEM_Delete, has_bps);

    m_ctxMenu->popup( mousePos );
}

void GDBBreakpointWidget::slotContextMenuSelect( int item )
{
    int                  row, col;
    BreakpointTableRow  *btr;
    Breakpoint          *bp;
    FilePosBreakpoint   *fbp;
    
    row= m_table->currentRow( );
    if (row == -1)
        return;
    btr = (BreakpointTableRow *)m_table->item( row, Control );
    if (btr == NULL)
        return;
    bp = btr->breakpoint( );
    if (bp == NULL)
        return;
    fbp = dynamic_cast<FilePosBreakpoint*>(bp);

    switch( item )
    {
        case BW_ITEM_Show:
            if (fbp)
                emit gotoSourcePosition(fbp->fileName(), fbp->lineNum()-1);
            break;
        case BW_ITEM_Edit:
            col = m_table->currentColumn( );
            if (col == Location || col ==  Condition || col == IgnoreCount)
                m_table->editCell(row, col, false);
            break;
        case BW_ITEM_Disable:
            
            bp->setEnabled( !bp->isEnabled( ) );
            btr->setRow( );
            sendToGdb( *bp );
            break;
        case BW_ITEM_Delete:
            slotRemoveBreakpoint( );
            break;
        case BW_ITEM_DeleteAll:
            slotRemoveAllBreakpoints();
            break;
        case BW_ITEM_DisableAll:
        case BW_ITEM_EnableAll:
            for ( int row = 0; row < m_table->numRows(); row++ )
            {
                BreakpointTableRow* btr = (BreakpointTableRow *) 
                    m_table->item(row, Control);

                if (btr)
                {
                    btr->breakpoint()->setEnabled(item == BW_ITEM_EnableAll);
                    btr->setRow();
                    sendToGdb(*btr->breakpoint());
                }
            }
            break;
        default:
            // oops, check it out! this case is not in sync with the
            // m_ctxMenu.  Check the enum in the header file.
            return;
    }
}

/***************************************************************************/

void GDBBreakpointWidget::slotEditRow(int row, int col, const QPoint &)
{
//    kdDebug(9012) << "in slotEditRow row=" << row << endl;
    BreakpointTableRow* btr = (BreakpointTableRow *) m_table->item(row, Control);
    if (btr)
    {
        if (col == Location || col ==  Condition || col == IgnoreCount)
            m_table->editCell(row, col, false);
    }
}

/***************************************************************************/

void GDBBreakpointWidget::slotNewValue(int row, int col)
{
    BreakpointTableRow* btr = (BreakpointTableRow *) m_table->item(row, Control);

    QString new_value = m_table->text(row, col);

    if (btr)
    {
        Breakpoint* bp = btr->breakpoint();
        switch (col)
        {
        case Enable:
        {
            QCheckTableItem *item = 
                (QCheckTableItem*)m_table->item ( row, Enable );
            bp->setEnabled(item->isChecked());
        }
        break;

        case Location:
        {
            if (bp->location() != new_value)
            {
                // GDB does not allow to change location of
                // an existing breakpoint. So, need to remove old
                // breakpoint and add another.
                
                // Announce to editor that breakpoit at its
                // current location is dying.
                bp->setActionDie();
                emit publishBPState(*bp);

                // However, we don't want the line in breakpoint
                // widget to disappear and appear again.
                    
                // Emit delete command. This won't resync breakpoint
                // table (unlike clearBreakpoint), so we won't have
                // nasty effect where line in the table first disappears
                // and then appears again, and won't have internal issues
                // as well.
                if (!controller_->stateIsOn(s_dbgNotStarted))
                    controller_->addCommand(bp->dbgRemoveCommand().latin1());

                // Now add new breakpoint in gdb. It will correspond to
                // the same 'Breakpoint' and 'BreakpointRow' objects in
                // KDevelop is the previous, deleted, breakpoint.
                
                // Note: clears 'actionDie' implicitly.
                bp->setActionAdd(true);
                bp->setLocation(new_value);
            }
            break;
        }

        case Condition:
        {
            bp->setConditional(new_value);
            break;
        }

        case IgnoreCount:
        {
            bp->setIgnoreCount(new_value.toInt());
            break;
        }
        default:
            break;
        }

        bp->setActionModify(true);


        // This is not needed for most changes, since we've
        // just read a value from table cell to breakpoint, and 
        // setRow will write back the same value to the cell.
        // It's only really needed for tracing column changes,
        // where tracing config dialog directly changes breakpoint,
        // so we need to send those changes to the table.
        btr->setRow();


        sendToGdb(*bp);
    }
}

/***************************************************************************/

void GDBBreakpointWidget::slotEditBreakpoint(const QString &fileName, int lineNum)
{
    FilePosBreakpoint *fpBP = new FilePosBreakpoint(fileName, lineNum+1);

    BreakpointTableRow* btr = find(fpBP);
    delete fpBP;

    if (btr)
    {
        QTableSelection ts;
        ts.init(btr->row(), 0);
        ts.expandTo(btr->row(), numCols);
        m_table->addSelection(ts);
        m_table->editCell(btr->row(), Location, false);
    }

}

void GDBBreakpointWidget::sendToGdb(Breakpoint& BP)
{
    // Announce the change in state. We need to do this before
    // everything. For example, if debugger is not yet running, we'll
    // immediate exit after setting pending flag, but we still want changes
    // in "enabled" flag to be shown on the left border of the editor.
    emit publishBPState(BP);

    BP.sendToGdb(controller_);
}

void GDBBreakpointWidget::slotBreakpointModified(Breakpoint* b)
{
    emit publishBPState(*b);

    if (BreakpointTableRow* btr = find(b))
    {
        if (b->isActionDie())
        {
            // Breakpoint was deleted, kill the table row.
            m_table->removeRow(btr->row());
        }
        else
        {
            btr->setRow();
        }        
    }
}

void GDBBreakpointWidget::slotEvent(GDBController::event_t e)
{
    switch(e)
    {
    case GDBController::program_state_changed:
        {
            controller_->addCommand(
                new GDBCommand("-break-list",
                               this,
                               &GDBBreakpointWidget::handleBreakpointList));
            break;
        }

    case GDBController::shared_library_loaded:
    case GDBController::connected_to_program:
        {
            for ( int row = 0; row < m_table->numRows(); row++ )
            {
                BreakpointTableRow* btr = (BreakpointTableRow *) 
                    m_table->item(row, Control);
                
                if (btr)
                {
                    Breakpoint* bp = btr->breakpoint();
                    if ( (bp->dbgId() == -1 ||  bp->isPending())
                         && !bp->isDbgProcessing() 
                         && bp->isValid())
                    {
                        sendToGdb(*bp);
                    }
                }
            }
            break;
        }
    case GDBController::program_exited:
        {
            for(int row = 0; row < m_table->numRows(); ++row)
            {
                Breakpoint* b = static_cast<BreakpointTableRow*>(
                    m_table->item(row, Control))->breakpoint();

                b->applicationExited(controller_);
            }
        }

    default:        
        ;
    }
}


/***************************************************************************/

void GDBBreakpointWidget::slotEditBreakpoint()
{
    m_table->editCell(m_table->currentRow(), Location, false);
}


void GDBBreakpointWidget::editTracing(QTableItem* item)
{
    BreakpointTableRow* btr = (BreakpointTableRow *)
        m_table->item(item->row(), Control);

    DebuggerTracingDialog* d = new DebuggerTracingDialog(
        btr->breakpoint(), m_table, "");

    int r = d->exec();

    // Note: change cell text here and explicitly call slotNewValue here.
    // We want this signal to be emitted when we close the tracing dialog
    // and not when we select some other cell, as happens in Qt by default.
    if (r == QDialog::Accepted)
    {
        // The dialog has modified "btr->breakpoint()" already.
        // Calling 'slotNewValue' will flush the changes back
        // to the table.
        slotNewValue(item->row(), item->col());
    }

    delete d;
}


/***************************************************************************/

void GDBBreakpointWidget::savePartialProjectSession(QDomElement* el)
{
    QDomDocument domDoc = el->ownerDocument();
    if (domDoc.isNull())
        return;

    QDomElement breakpointListEl = domDoc.createElement("breakpointList");
    for ( int row = 0; row < m_table->numRows(); row++ )
    {
        BreakpointTableRow* btr =
            (BreakpointTableRow *) m_table->item(row, Control);
        Breakpoint* bp = btr->breakpoint();

        QDomElement breakpointEl =
            domDoc.createElement("breakpoint"+QString::number(row));

        breakpointEl.setAttribute("type", bp->type());
        breakpointEl.setAttribute("location", bp->location(false));
        breakpointEl.setAttribute("enabled", bp->isEnabled());
        breakpointEl.setAttribute("condition", bp->conditional());
        breakpointEl.setAttribute("tracingEnabled", 
                                  QString::number(bp->tracingEnabled()));
        breakpointEl.setAttribute("traceFormatStringEnabled", 
                                  QString::number(bp->traceFormatStringEnabled()));        
        breakpointEl.setAttribute("tracingFormatString",
                                  bp->traceFormatString());

        QDomElement tracedExpressions = 
            domDoc.createElement("tracedExpressions");

        QStringList::const_iterator i, e;
        for(i = bp->tracedExpressions().begin(),
                e = bp->tracedExpressions().end();
            i != e; ++i)
        {
            QDomElement expr = domDoc.createElement("expression");
            expr.setAttribute("value", *i);
            tracedExpressions.appendChild(expr);
        }
        
        breakpointEl.appendChild(tracedExpressions);

        breakpointListEl.appendChild(breakpointEl);
    }

    if (!breakpointListEl.isNull())
        el->appendChild(breakpointListEl);
}

/***************************************************************************/

void GDBBreakpointWidget::restorePartialProjectSession(const QDomElement* el)
{
    /** Eventually, would be best to make each breakpoint type handle loading/
        saving it's data. The only problem is that on load, we need to allocate
        with new different objects, depending on type, and that requires some 
        kind of global registry. Gotta find out if a solution for that exists in 
        KDE (Boost.Serialization is too much dependency, and rolling my own is
        boring).
    */
    QDomElement breakpointListEl = el->namedItem("breakpointList").toElement();
    if (!breakpointListEl.isNull())
    {
        QDomElement breakpointEl;
        for (breakpointEl = breakpointListEl.firstChild().toElement();
                !breakpointEl.isNull();
                breakpointEl = breakpointEl.nextSibling().toElement())
        {
            Breakpoint* bp=0;
            BP_TYPES type = (BP_TYPES) breakpointEl.attribute( "type", "0").toInt();
            switch (type)
            {
            case BP_TYPE_FilePos:
            {
                bp = new FilePosBreakpoint();
                break;
            }
            case BP_TYPE_Watchpoint:
            {
                bp = new Watchpoint("");
                break;
            }
            default:
                break;
            }

            // Common settings for any type of breakpoint
            if (bp)
            {
                bp->setLocation(breakpointEl.attribute( "location", ""));
                if (type == BP_TYPE_Watchpoint)
                {
                    bp->setEnabled(false);
                }
                else
                {
                    bp->setEnabled(
                        breakpointEl.attribute( "enabled", "1").toInt());
                }
                bp->setConditional(breakpointEl.attribute( "condition", ""));

                bp->setTracingEnabled(
                    breakpointEl.attribute("tracingEnabled", "0").toInt());
                bp->setTraceFormatString(
                    breakpointEl.attribute("tracingFormatString", ""));
                bp->setTraceFormatStringEnabled(
                    breakpointEl.attribute("traceFormatStringEnabled", "0")
                    .toInt());

                QDomNode tracedExpr = 
                    breakpointEl.namedItem("tracedExpressions");

                if (!tracedExpr.isNull())
                {
                    QStringList l;

                    for(QDomNode c = tracedExpr.firstChild(); !c.isNull(); 
                        c = c.nextSibling())
                    {
                        QDomElement el = c.toElement();
                        l.push_back(el.attribute("value", ""));
                    }
                    bp->setTracedExpressions(l);
                }

                // Now add the breakpoint. Don't try to check if 
                // breakpoint already exists.
                // It's easy to check that breakpoint on the same
                // line already exists, but it might have different condition,
                // and checking conditions for equality is too complex thing.
                // And anyway, it's will be suprising of realoading a project
                // changes the set of breakpoints.
                addBreakpoint(bp);
            }
        }
    }
}

/***************************************************************************/

void GDBBreakpointWidget::focusInEvent( QFocusEvent */* e*/ )
{
    // Without the following 'if', when we first open the breakpoints
    // widget, the background is all black. This happens only with
    //    m_table->setFocusStyle(QTable::FollowStyle);
    // in constructor, so I suspect Qt bug. But anyway, without
    // current cell keyboard actions like Enter for edit won't work,
    // so keyboard focus does not makes much sense.
    if (m_table->currentRow() == -1 ||
        m_table->currentColumn() == -1)
    {
        m_table->setCurrentCell(0, 0);
    }
    m_table->setFocus();
}

ComplexEditCell::
ComplexEditCell(QTable* table)
: QTableItem(table, QTableItem::WhenCurrent)
{
}


QWidget* ComplexEditCell::createEditor() const
{
    QHBox* box = new QHBox( table()->viewport() );
    box->setPaletteBackgroundColor(
               table()->palette().active().highlight());

    label_ = new QLabel(text(), box, "label");
    label_->setBackgroundMode(Qt::PaletteHighlight);
    // Sorry for hardcode, but '2' is already hardcoded in
    // Qt source, in QTableItem::paint. Since I don't want the 
    // text to jump 2 pixels to the right when editor is activated, 
    // need to set the same indent for label.
    label_->setIndent(2);
    QPalette p = label_->palette();

    p.setColor(QPalette::Active, QColorGroup::Foreground, 
               table()->palette().active().highlightedText());
    p.setColor(QPalette::Inactive, QColorGroup::Foreground, 
               table()->palette().active().highlightedText());

    label_->setPalette(p);
    
    QPushButton* b = new QPushButton("...", box);
    // This is exactly what is done in QDesigner source in the
    // similar context. Haven't had any success making the good look
    // with layout, I suppose that sizeHint for button is always larger
    // than 20.
    b->setFixedWidth( 20 );

    connect(b, SIGNAL(clicked()), this, SLOT(slotEdit()));

    return box;
}

void ComplexEditCell::updateValue()
{
    if (!label_.isNull())
    {
        label_->setText(table()->text(row(), col()));
    }
}

void ComplexEditCell::slotEdit()
{
    emit edit(this);
}

}


#include "gdbbreakpointwidget.moc"
