/* This file is part of KCachegrind.
   Copyright (c) 2011-2016 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>

   KCachegrind 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, version 2.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; see the file COPYING.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.
*/

/*
 * Source View
 */

#include "sourceview.h"

#include <QDebug>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QAction>
#include <QMenu>
#include <QScrollBar>
#include <QHeaderView>
#include <QKeyEvent>

#include "globalconfig.h"
#include "sourceitem.h"



//
// SourceView
//


SourceView::SourceView(TraceItemView* parentView,
                       QWidget* parent)
    : QTreeWidget(parent), TraceItemView(parentView)
{
    _inSelectionUpdate = false;

    _arrowLevels = 0;

    setColumnCount(5);
    setRootIsDecorated(false);
    setAllColumnsShowFocus(true);
    setUniformRowHeights(true);
    // collapsing call/jump lines by double-click is confusing
    setExpandsOnDoubleClick(false);

    QStringList headerLabels;
    headerLabels << tr( "#" )
                 << tr( "Cost" )
                 << tr( "Cost 2" )
                 << QString()
                 <<  tr( "Source");
    setHeaderLabels(headerLabels);

    // sorting will be enabled after refresh()
    sortByColumn(0, Qt::AscendingOrder);
    header()->setSortIndicatorShown(false);
    this->setItemDelegate(new SourceItemDelegate(this));
    this->setWhatsThis( whatsThis());

    connect( this,
             &QTreeWidget::currentItemChanged,
             this, &SourceView::selectedSlot);

    setContextMenuPolicy(Qt::CustomContextMenu);
    connect( this,
             &QWidget::customContextMenuRequested,
             this, &SourceView::context);

    connect(this,
            &QTreeWidget::itemDoubleClicked,
            this, &SourceView::activatedSlot);

    connect(header(), &QHeaderView::sectionClicked,
            this, &SourceView::headerClicked);
}

QString SourceView::whatsThis() const
{
    return tr( "<b>Annotated Source</b>"
               "<p>The annotated source list shows the "
               "source lines of the current selected function "
               "together with (self) cost spent while executing the "
               "code of this source line. If there was a call "
               "in a source line, lines with details on the "
               "call happening are inserted into the source: "
               "the cost spent inside of the call, the "
               "number of calls happening, and the call destination.</p>"
               "<p>Select a inserted call information line to "
               "make the destination function current.</p>");
}

void SourceView::context(const QPoint & p)
{
    int c = columnAt(p.x());
    QTreeWidgetItem* i = itemAt(p);
    QMenu popup;

    TraceLineCall* lc = i ? ((SourceItem*) i)->lineCall() : nullptr;
    TraceLineJump* lj = i ? ((SourceItem*) i)->lineJump() : nullptr;
    TraceFunction* f = lc ? lc->call()->called() : nullptr;
    TraceLine* line = lj ? lj->lineTo() : nullptr;

    QAction* activateFunctionAction = nullptr;
    QAction* activateLineAction = nullptr;
    if (f) {
        QString menuText = tr("Go to '%1'").arg(GlobalConfig::shortenSymbol(f->prettyName()));
        activateFunctionAction = popup.addAction(menuText);
        popup.addSeparator();
    }
    else if (line) {
        QString menuText = tr("Go to Line %1").arg(line->name());
        activateLineAction = popup.addAction(menuText);
        popup.addSeparator();
    }

    if ((c == 1) || (c == 2)) {
        addEventTypeMenu(&popup);
        popup.addSeparator();
    }
    addGoMenu(&popup);

    QAction* a = popup.exec(mapToGlobal(p + QPoint(0,header()->height())));
    if (a == activateFunctionAction)
        TraceItemView::activated(f);
    else if (a == activateLineAction)
        TraceItemView::activated(line);
}


void SourceView::selectedSlot(QTreeWidgetItem *i, QTreeWidgetItem *)
{
    if (!i) return;
    // programatically selected items are not signalled
    if (_inSelectionUpdate) return;

    TraceLineCall* lc = ((SourceItem*) i)->lineCall();
    TraceLineJump* lj = ((SourceItem*) i)->lineJump();

    if (!lc && !lj) {
        TraceLine* l = ((SourceItem*) i)->line();
        if (l) {
            _selectedItem = l;
            selected(l);
        }
        return;
    }

    TraceFunction* f = lc ? lc->call()->called() : nullptr;
    if (f) {
        _selectedItem = f;
        selected(f);
    }
    else {
        TraceLine* line = lj ? lj->lineTo() : nullptr;
        if (line) {
            _selectedItem = line;
            selected(line);
        }
    }
}

void SourceView::activatedSlot(QTreeWidgetItem* i, int)
{
    if (!i) return;

    TraceLineCall* lc = ((SourceItem*) i)->lineCall();
    TraceLineJump* lj = ((SourceItem*) i)->lineJump();

    if (!lc && !lj) {
        TraceLine* l = ((SourceItem*) i)->line();
        if (l) TraceItemView::activated(l);
        return;
    }

    TraceFunction* f = lc ? lc->call()->called() : nullptr;
    if (f) TraceItemView::activated(f);
    else {
        TraceLine* line = lj ? lj->lineTo() : nullptr;
        if (line) TraceItemView::activated(line);
    }
}

void SourceView::keyPressEvent(QKeyEvent* event)
{
    QTreeWidgetItem *item = currentItem();
    if (item && ((event->key() == Qt::Key_Return) ||
                 (event->key() == Qt::Key_Space)))
    {
        activatedSlot(item, 0);
    }
    QTreeView::keyPressEvent(event);
}

CostItem* SourceView::canShow(CostItem* i)
{
    ProfileContext::Type t = i ? i->type() : ProfileContext::InvalidType;

    switch(t) {
    case ProfileContext::Function:
    case ProfileContext::Instr:
    case ProfileContext::Line:
        return i;

    default:
        break;
    }

    return nullptr;
}

void SourceView::doUpdate(int changeType, bool)
{
    // Special case ?
    if (changeType == selectedItemChanged) {

        if (!_selectedItem) {
            clearSelection();
            return;
        }

        TraceLine* sLine = nullptr;
        if (_selectedItem->type() == ProfileContext::Line)
            sLine = (TraceLine*) _selectedItem;
        if (_selectedItem->type() == ProfileContext::Instr)
            sLine = ((TraceInstr*)_selectedItem)->line();
        if ((_selectedItem->type() != ProfileContext::Function) && (sLine == nullptr))
            return;

        QList<QTreeWidgetItem*> items = selectedItems();
        SourceItem* si = (items.count() > 0) ? (SourceItem*)items[0] : nullptr;
        if (si) {
            if (sLine && (si->line() == sLine)) return;
            if (si->lineCall() &&
                (si->lineCall()->call()->called() == _selectedItem)) return;
        }

        QTreeWidgetItem *item, *item2;
        for (int i=0; i<topLevelItemCount(); i++) {
            item = topLevelItem(i);
            si = (SourceItem*)item;
            if (sLine && (si->line() == sLine)) {
                scrollToItem(item);
                _inSelectionUpdate = true;
                setCurrentItem(item);
                _inSelectionUpdate = false;
                break;
            }
            bool foundCall = false;
            for (int j=0; j<item->childCount(); j++) {
                item2 = item->child(j);
                si = (SourceItem*)item2;
                if (!si->lineCall()) continue;
                if (si->lineCall()->call()->called() == _selectedItem) {
                    scrollToItem(item2);
                    _inSelectionUpdate = true;
                    setCurrentItem(item2);
                    _inSelectionUpdate = false;
                    foundCall = true;
                    break;
                }
            }
            if (foundCall) break;
        }
        return;
    }

    if (changeType == groupTypeChanged) {
        // update group colors for call lines
        QTreeWidgetItem *item, *item2;
        for (int i=0; i<topLevelItemCount(); i++) {
            item = topLevelItem(i);
            for (int j=0; i<item->childCount(); i++) {
                item2 = item->child(j);
                ((SourceItem*)item2)->updateGroup();
            }
        }
        return;
    }

    // On eventTypeChanged, we can not just change the costs shown in
    // already existing items, as costs of 0 should make the line to not
    // be shown at all. So we do a full refresh.

    refresh();
}

void SourceView::refresh()
{
    int originalPosition = verticalScrollBar()->value();
    clear();
    setColumnWidth(0, 20);
    setColumnWidth(1, 50);
    setColumnHidden(2, (_eventType2 == nullptr));
    setColumnWidth(2, 50);
    // arrows, defaults to invisible
    setColumnHidden(3, true);
    if (_eventType)
        headerItem()->setText(1, _eventType->name());
    if (_eventType2)
        headerItem()->setText(2, _eventType2->name());

    _arrowLevels = 0;
    if (!_data || !_activeItem) {
        return;
    }

    ProfileContext::Type t = _activeItem->type();
    TraceFunction* f = nullptr;
    if (t == ProfileContext::Function) f = (TraceFunction*) _activeItem;
    if (t == ProfileContext::Instr) {
        f = ((TraceInstr*)_activeItem)->function();
        if (!_selectedItem)
            _selectedItem = ((TraceInstr*)_activeItem)->line();
    }
    if (t == ProfileContext::Line) {
        f = ((TraceLine*)_activeItem)->functionSource()->function();
        if (!_selectedItem) _selectedItem = _activeItem;
    }

    if (!f) return;

    TraceFunctionSource* mainSF = f->sourceFile();

    // skip first source if there is no debug info and there are more sources
    // (this is for a bug in GCC 2.95.x giving unknown source for prologs)
    if (mainSF &&
        (mainSF->firstLineno() == 0) &&
        (mainSF->lastLineno() == 0) &&
        (f->sourceFiles().count()>1) ) {
        // skip
    }
    else
        fillSourceFile(mainSF, 0);

    int fileno = 0;
    foreach(TraceFunctionSource* sf, f->sourceFiles()) {
        fileno++;
        if (sf != mainSF)
            fillSourceFile(sf, fileno);
    }

    if (!_eventType2) {
#if QT_VERSION >= 0x050000
        header()->setSectionResizeMode(2, QHeaderView::Interactive);
#else
        header()->setResizeMode(2, QHeaderView::Interactive);
#endif
    }
    // reset to the original position - this is useful when the view
    // is refreshed just because we change between relative/absolute
    // FIXME: this overrides scrolling to selected item
    verticalScrollBar()->setValue(originalPosition);
}


/* Helper for fillSourceList:
 * search recursive for a file, starting from a base dir
 * If found, returns true and <dir> is set to the file path.
 */
static bool searchFileRecursive(QString& dir, const QString& name)
{
    // we leave this in...
    qDebug("Checking %s/%s", qPrintable(dir), qPrintable(name));

    if (QFile::exists(dir + '/' + name)) return true;

    // check in subdirectories
    QDir d(dir);
    d.setFilter( QDir::Dirs | QDir::NoSymLinks );
    d.setSorting( QDir::Unsorted );
    QStringList subdirs = d.entryList();
    QStringList::const_iterator it =subdirs.constBegin();
    for(; it != subdirs.constEnd(); ++it ) {
        if (*it == QLatin1String(".") || *it == QLatin1String("..") || *it == QLatin1String("CVS")) continue;

        dir = d.filePath(*it);
        if (searchFileRecursive(dir, name)) return true;
    }
    return false;
}

/* Search for a source file in different places.
 * If found, returns true and <dir> is set to the file path.
 */
bool SourceView::searchFile(QString& dir,
                            TraceFunctionSource* sf)
{
    QString name = sf->file()->shortName();

    if (QDir::isAbsolutePath(dir)) {
        if (QFile::exists(dir + '/' + name)) return true;
    }
    else {
        /* Directory is relative. Check
     * - relative to cwd
     * - relative to path of data file
     */
        QString base = QDir::currentPath() + '/' + dir;
        if (QFile::exists(base + '/' + name)) {
            dir = base;
            return true;
        }

        TracePart* firstPart = _data->parts().first();
        if (firstPart) {
            QFileInfo partFile(firstPart->name());
            if (QFileInfo(partFile.absolutePath(), name).exists()) {
                dir = partFile.absolutePath();
                return true;
            }
        }
    }

    QStringList list = GlobalConfig::sourceDirs(_data,
                                                sf->function()->object());
    QStringList::const_iterator it;
    for ( it = list.constBegin(); it != list.constEnd(); ++it ) {
        dir = *it;
        if (searchFileRecursive(dir, name)) return true;
    }

    return false;
}


void SourceView::updateJumpArray(uint lineno, SourceItem* si,
                                 bool ignoreFrom, bool ignoreTo)
{
    uint lowLineno, highLineno;
    int iEnd = -1, iStart = -1;

    if (0) qDebug("updateJumpArray(line %d, jump to %s)",
                  lineno,
                  si->lineJump()
                  ? qPrintable(si->lineJump()->lineTo()->name()) : "?" );

    while(_lowListIter != _lowList.end()) {
        TraceLineJump* lj= *_lowListIter;
        lowLineno = lj->lineFrom()->lineno();
        if (lj->lineTo()->lineno() < lowLineno)
            lowLineno = lj->lineTo()->lineno();

        if (lowLineno > lineno) break;

        if (ignoreFrom && (lowLineno < lj->lineTo()->lineno())) break;
        if (ignoreTo && (lowLineno < lj->lineFrom()->lineno())) break;

        if (si->lineJump() && (lj != si->lineJump())) break;

        int asize = (int)_jump.size();
#if 0
        for(iStart=0;iStart<asize;iStart++)
            if (_jump[iStart] &&
                (_jump[iStart]->lineTo() == lj->lineTo())) break;
#else
        iStart = asize;
#endif

        if (iStart == asize) {
            for(iStart=0; iStart<asize; ++iStart)
                if (_jump[iStart] == nullptr) break;

            if (iStart== asize) {
                asize++;
                _jump.resize(asize);
                if (asize > _arrowLevels) _arrowLevels = asize;
            }

            if (0) qDebug(" start %d (%s to %s)",
                          iStart,
                          qPrintable(lj->lineFrom()->name()),
                          qPrintable(lj->lineTo()->name()));

            _jump[iStart] = lj;
        }
        _lowListIter++;
    }

    si->setJumpArray(_jump);

    while(_highListIter != _highList.end()) {
        TraceLineJump* lj= *_highListIter;
        highLineno = lj->lineFrom()->lineno();
        if (lj->lineTo()->lineno() > highLineno) {
            highLineno = lj->lineTo()->lineno();
            if (ignoreTo) break;
        }
        else if (ignoreFrom) break;

        if (highLineno > lineno) break;

        for(iEnd=0; iEnd< (int)_jump.size(); ++iEnd)
            if (_jump[iEnd] == lj) break;
        if (iEnd == (int)_jump.size()) {
            qDebug("LineView: no jump start for end at %x ?", highLineno);
            iEnd = -1;
        }

        if (0 && (iEnd>=0))
            qDebug(" end %d (%s to %s)",
                   iEnd,
                   qPrintable(_jump[iEnd]->lineFrom()->name()),
                   qPrintable(_jump[iEnd]->lineTo()->name()));

        if (0 && lj) qDebug("next end: %s to %s",
                            qPrintable(lj->lineFrom()->name()),
                            qPrintable(lj->lineTo()->name()));

        _highListIter++;

        if (highLineno > lineno)
            break;
        else {
            if (iEnd>=0) _jump[iEnd] = nullptr;
            iEnd = -1;
        }
    }
    if (iEnd>=0) _jump[iEnd] = nullptr;
}


// compare functions for jump arrow drawing

void getJumpLines(const TraceLineJump* jump, uint& low, uint& high)
{
    low  = jump->lineFrom()->lineno();
    high = jump->lineTo()->lineno();

    if (low > high) {
        uint t = low;
        low = high;
        high = t;
    }
}

// sort jumps according to lower line number
bool lineJumpLowLessThan(const TraceLineJump* jump1,
                         const TraceLineJump* jump2)
{
    uint line1Low, line1High, line2Low, line2High;

    getJumpLines(jump1, line1Low, line1High);
    getJumpLines(jump2, line2Low, line2High);

    if (line1Low != line2Low) return (line1Low < line2Low);
    // jump ends come before jump starts
    bool low1IsEnd = (line1Low == jump1->lineTo()->lineno());
    bool low2IsEnd = (line2Low == jump2->lineTo()->lineno());
    if (low1IsEnd && !low2IsEnd) return true;
    if (low2IsEnd && !low1IsEnd) return false;
    // both the low line of jump 1 and 2 are end or start
    return (line1High < line2High);
}

// sort jumps according to higher line number
bool lineJumpHighLessThan(const TraceLineJump* jump1,
                          const TraceLineJump* jump2)
{
    uint line1Low, line1High, line2Low, line2High;

    getJumpLines(jump1, line1Low, line1High);
    getJumpLines(jump2, line2Low, line2High);

    if (line1High != line2High) return (line1High < line2High);
    // jump ends come before jump starts
    bool high1IsEnd = (line1High == jump1->lineTo()->lineno());
    bool high2IsEnd = (line2High == jump2->lineTo()->lineno());
    if (high1IsEnd && !high2IsEnd) return true;
    if (high2IsEnd && !high1IsEnd) return false;
    // both the high line of jump 1 and 2 are end or start
    return (line1Low < line2Low);
}

/* If sourceList is empty we set the source file name into the header,
 * else this code is of a inlined function, and we add "inlined from..."
 */
void SourceView::fillSourceFile(TraceFunctionSource* sf, int fileno)
{
    if (!sf) return;

    if (0) qDebug("Selected Item %s",
                  _selectedItem ? qPrintable(_selectedItem->name()) : "(none)");

    TraceLineMap::Iterator lineIt, lineItEnd;
    int nextCostLineno = 0, lastCostLineno = 0;

    bool validSourceFile = (!sf->file()->name().isEmpty());

    TraceLine* sLine = nullptr;
    if (_selectedItem) {
        if (_selectedItem->type() == ProfileContext::Line)
            sLine = (TraceLine*) _selectedItem;
        if (_selectedItem->type() == ProfileContext::Instr)
            sLine = ((TraceInstr*)_selectedItem)->line();
    }

    if (validSourceFile) {
        TraceLineMap* lineMap = sf->lineMap();
        if (lineMap) {
            lineIt    = lineMap->begin();
            lineItEnd = lineMap->end();
            // get first line with cost of selected type
            while(lineIt != lineItEnd) {
                if (&(*lineIt) == sLine) break;
                if ((*lineIt).hasCost(_eventType)) break;
                if (_eventType2 && (*lineIt).hasCost(_eventType2)) break;
                ++lineIt;
            }

            nextCostLineno     = (lineIt == lineItEnd) ? 0 : (*lineIt).lineno();
            if (nextCostLineno<0) {
                qDebug() << "SourceView::fillSourceFile: Negative line number "
                         << nextCostLineno;
                qDebug() << "  Function '" << sf->function()->name() << "'";
                qDebug() << "  File '" << sf->file()->name() << "'";
                nextCostLineno = 0;
            }

        }

        if (nextCostLineno == 0) {
            new SourceItem(this, this, fileno, 1, false,
                           tr("There is no cost of current selected type associated"));
            new SourceItem(this, this, fileno, 2, false,
                           tr("with any source line of this function in file"));
            new SourceItem(this, this, fileno, 3, false,
                           QStringLiteral("    '%1'").arg(sf->file()->prettyName()));
            new SourceItem(this, this, fileno, 4, false,
                           tr("Thus, no annotated source can be shown."));
            return;
        }
    }

    QString filename = sf->file()->shortName();
    QString dir = sf->file()->directory();
    if (!dir.isEmpty())
        filename = dir + '/' + filename;

    if (nextCostLineno>0) {
        // we have debug info... search for source file
        if (searchFile(dir, sf)) {
            filename = dir + '/' + sf->file()->shortName();
            // no need to search again
            sf->file()->setDirectory(dir);
        }
        else
            nextCostLineno = 0;
    }

    // do it here, because the source directory could have been set before
    if (topLevelItemCount()==0) {
        if (validSourceFile && (nextCostLineno != 0))
            new SourceItem(this, this, fileno, 0, true,
                           tr("--- From '%1' ---").arg(filename));
    }
    else {
        new SourceItem(this, this, fileno, 0, true,
                       validSourceFile ?
                           tr("--- Inlined from '%1' ---").arg(filename) :
                           tr("--- Inlined from unknown source ---"));
    }

    if (nextCostLineno == 0) {
        new SourceItem(this, this, fileno, 1, false,
                       tr("There is no source available for the following function:"));
        new SourceItem(this, this, fileno, 2, false,
                       QStringLiteral("    '%1'").arg(sf->function()->prettyName()));
        if (sf->file()->name().isEmpty()) {
            new SourceItem(this, this, fileno, 3, false,
                           tr("This is because no debug information is present."));
            new SourceItem(this, this, fileno, 4, false,
                           tr("Recompile source and redo the profile run."));
            if (sf->function()->object()) {
                new SourceItem(this, this, fileno, 5, false,
                               tr("The function is located in this ELF object:"));
                new SourceItem(this, this, fileno, 6, false,
                               QStringLiteral("    '%1'")
                               .arg(sf->function()->object()->prettyName()));
            }
        }
        else {
            new SourceItem(this, this, fileno, 3, false,
                           tr("This is because its source file cannot be found:"));
            new SourceItem(this, this, fileno, 4, false,
                           QStringLiteral("    '%1'").arg(sf->file()->name()));
            new SourceItem(this, this, fileno, 5, false,
                           tr("Add the folder of this file to the source folder list."));
            new SourceItem(this, this, fileno, 6, false,
                           tr("The list can be found in the configuration dialog."));
        }
        return;
    }

    // initialisation for arrow drawing
    // create sorted list of jumps (for jump arrows)
    TraceLineMap::Iterator it = lineIt, nextIt;
    _lowList.clear();
    _highList.clear();
    while(1) {

        nextIt = it;
        ++nextIt;
        while(nextIt != lineItEnd) {
            if (&(*nextIt) == sLine) break;
            if ((*nextIt).hasCost(_eventType)) break;
            if (_eventType2 && (*nextIt).hasCost(_eventType2)) break;
            ++nextIt;
        }

        TraceLineJumpList jlist = (*it).lineJumps();
        foreach(TraceLineJump* lj, jlist) {
            if (lj->executedCount()==0) continue;
            // skip jumps to next source line with cost
            //if (lj->lineTo() == &(*nextIt)) continue;

            _lowList.append(lj);
            _highList.append(lj);
        }
        it = nextIt;
        if (it == lineItEnd) break;
    }
    std::sort(_lowList.begin(), _lowList.end(), lineJumpLowLessThan);
    std::sort(_highList.begin(), _highList.end(), lineJumpHighLessThan);
    _lowListIter = _lowList.begin(); // iterators to list start
    _highListIter = _highList.begin();
    _jump.resize(0);

    char buf[160];
    bool inside = false, skipLineWritten = true;
    int readBytes;
    int fileLineno = 0;
    SubCost most = 0;

    QList<QTreeWidgetItem*> items;
    TraceLine* currLine;
    SourceItem *si, *si2, *item = nullptr, *first = nullptr, *selected = nullptr;
    QFile file(filename);
    bool fileEndReached = false;
    if (!file.open(QIODevice::ReadOnly)) return;
    while (1) {
        readBytes=file.readLine(buf, sizeof( buf ));
        if (readBytes<=0) {
            // for nice empty 4 lines after function with EOF
            buf[0] = 0;
            if (readBytes<0) fileEndReached = true;
        }

        if ((readBytes >0) && (buf[readBytes-1] != '\n')) {
            /* Something was read but not ending in newline. I.e.
         * - buffer was not big enough: discard rest of line, add "..."
         * - this is last line of file, not ending in newline
         * NB: checking for '\n' is enough for all systems.
         */
            int r;
            char buf2[32];
            bool somethingRead = false;
            while(1) {
                r = file.readLine(buf2, sizeof(buf2));
                if ((r<=0) || (buf2[r-1] == '\n')) break;
                somethingRead = true;
            }
            if (somethingRead) {
                // add dots as sign that we truncated the line
                Q_ASSERT(readBytes>3);
                buf[readBytes-1] = buf[readBytes-2] = buf[readBytes-3] = '.';
            }
        }
        else if ((readBytes>0) && (buf[readBytes-1] == '\n'))
            buf[readBytes-1] = 0;


        // keep fileLineno inside [lastCostLineno;nextCostLineno]
        fileLineno++;
        if (fileLineno == nextCostLineno) {
            currLine = &(*lineIt);

            // get next line with cost of selected type
            ++lineIt;
            while(lineIt != lineItEnd) {
                if (&(*lineIt) == sLine) break;
                if ((*lineIt).hasCost(_eventType)) break;
                if (_eventType2 && (*lineIt).hasCost(_eventType2)) break;
                ++lineIt;
            }

            lastCostLineno = nextCostLineno;
            nextCostLineno = (lineIt == lineItEnd) ? 0 : (*lineIt).lineno();
        }
        else
            currLine = nullptr;

        // update inside
        if (!inside) {
            if (currLine) inside = true;
        }
        else {
            if ( (fileLineno > lastCostLineno) &&
                 ((nextCostLineno == 0) ||
                  (fileLineno < nextCostLineno - GlobalConfig::noCostInside()) ))
                inside = false;
        }

        int context = GlobalConfig::context();

        if ( ((lastCostLineno==0) || (fileLineno > lastCostLineno + context)) &&
             ((nextCostLineno==0) || (fileLineno < nextCostLineno - context))) {
            if ((lineIt == lineItEnd) || fileEndReached) break;

            if (!skipLineWritten) {
                skipLineWritten = true;
                // a "skipping" line: print "..." instead of a line number
                strcpy(buf,"...");
            }
            else
                continue;
        }
        else
            skipLineWritten = false;

        QString s = QString(buf);
        if(s.size() > 0 && s.at(s.length()-1) == '\r')
            s = s.left(s.length()-1);
        si = new SourceItem(this, nullptr,
                            fileno, fileLineno, inside, s,
                            currLine);
        items.append(si);

        if (!currLine) continue;

        if (!selected && (currLine == sLine)) selected = si;
        if (!first) first = si;

        if (currLine->subCost(_eventType) > most) {
            item = si;
            most = currLine->subCost(_eventType);
        }

        si->setExpanded(true);
        foreach(TraceLineCall* lc,  currLine->lineCalls()) {
            if ((lc->subCost(_eventType)==0) &&
                (lc->subCost(_eventType2)==0)) continue;

            if (lc->subCost(_eventType) > most) {
                item = si;
                most = lc->subCost(_eventType);
            }

            si2 = new SourceItem(this, si, fileno, fileLineno, currLine, lc);

            if (!selected && (lc->call()->called() == _selectedItem))
                selected = si2;
        }

        foreach(TraceLineJump* lj, currLine->lineJumps()) {
            if (lj->executedCount()==0) continue;

            new SourceItem(this, si, fileno, fileLineno, currLine, lj);
        }
    }

    file.close();

    // Resize column 0 (line number) and 1/2 (cost) to contents
#if QT_VERSION >= 0x050000
    header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
    header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
    header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
#else
    header()->setResizeMode(0, QHeaderView::ResizeToContents);
    header()->setResizeMode(1, QHeaderView::ResizeToContents);
    header()->setResizeMode(2, QHeaderView::ResizeToContents);
#endif

    setSortingEnabled(false);
    addTopLevelItems(items);
    this->expandAll();
    setSortingEnabled(true);
    // always reset to line number sort
    sortByColumn(0, Qt::AscendingOrder);
    header()->setSortIndicatorShown(false);

    // Reallow interactive column size change after resizing to content
#if QT_VERSION >= 0x050000
    header()->setSectionResizeMode(0, QHeaderView::Interactive);
    header()->setSectionResizeMode(1, QHeaderView::Interactive);
    header()->setSectionResizeMode(2, QHeaderView::Interactive);
#else
    header()->setResizeMode(0, QHeaderView::Interactive);
    header()->setResizeMode(1, QHeaderView::Interactive);
    header()->setResizeMode(2, QHeaderView::Interactive);
#endif

    if (selected) item = selected;
    if (item) first = item;
    if (first) {
        scrollToItem(first);
        _inSelectionUpdate = true;
        setCurrentItem(first);
        _inSelectionUpdate = false;
    }

    // for arrows: go down the list according to list sorting
    QTreeWidgetItem *item1, *item2;
    for (int i=0; i<topLevelItemCount(); i++) {
        item1 = topLevelItem(i);
        si = (SourceItem*)item1;
        updateJumpArray(si->lineno(), si, true, false);

        for (int j=0; j<item1->childCount(); j++) {
            item2 = item1->child(j);
            si2 = (SourceItem*)item2;
            if (si2->lineJump())
                updateJumpArray(si->lineno(), si2, false, true);
            else
                si2->setJumpArray(_jump);
        }
    }

    if (arrowLevels()) {
        //fix this: setColumnWidth(3, 10 + 6*arrowLevels() + itemMargin() * 2);
        setColumnWidth(3, 10 + 6*arrowLevels() + 2);
        setColumnHidden(3, false);
    }
    else
        setColumnHidden(3, true);
}


void SourceView::headerClicked(int col)
{
    if (col == 0) {
        sortByColumn(col, Qt::AscendingOrder);
    }
    //All others but Source Text column Descending
    else if (col !=4) {
        sortByColumn(col, Qt::DescendingOrder);
    }
}

