void vogleditor_apiCallTimelineModel::AddApiCallsToTimeline(vogleditor_apiCallTreeItem *pParentCallTreeItem, vogleditor_timelineItem *pParentTimelineItem)
{
    vogleditor_timelineItem *pNewTimelineItem;

    int numChildren = pParentCallTreeItem->childCount();
    for (int c = 0; c < numChildren; c++)
    {
        vogleditor_apiCallTreeItem *pChildCallTreeItem = pParentCallTreeItem->child(c);

        float beginFloat = u64ToFloat(pChildCallTreeItem->startTime() - m_rawBaseTime);
        float endFloat = u64ToFloat(pChildCallTreeItem->endTime() - m_rawBaseTime);

        if (pChildCallTreeItem->isGroup())
        {
            // Create a group timelineItem with group color
            pNewTimelineItem = new vogleditor_timelineItem(beginFloat, endFloat, m_rootItem, pChildCallTreeItem->groupItem());
            QColor color;
            if (pChildCallTreeItem->isStateChangeGroup())
            {
                color = Qt::green;
            }
            else if (pChildCallTreeItem->isRenderGroup())
            {
                color = Qt::red;
            }
            pNewTimelineItem->setBrush(new QBrush(color, Qt::Dense5Pattern));
        }
        else // (API call)
        {
            // close a timeline parent group if the tree parent group has ended
            if (!pChildCallTreeItem->parent()->isGroup() && pParentTimelineItem->isGroupItem())
            {
                pParentTimelineItem = pParentTimelineItem->parent();
            }

            // Create new timeline apicall item
            pNewTimelineItem = new vogleditor_timelineItem(beginFloat, endFloat, pParentTimelineItem, pChildCallTreeItem->apiCallItem());

            // Add random color for debug marker group parent
            if (g_settings.group_debug_marker_in_use())
            {
                // (For now) only if State/Render groups are enabled so that
                // default timeline display isn't affected (debug marker groups
                // are checked on)
                // TODO: remove check for state/render status if/when consensus
                //       is the debug marker groups should be separately colored
                //       by default (when checked on)
                if (g_settings.group_state_render_stat())
                {
                    if (vogl_is_marker_push_entrypoint(pChildCallTreeItem->entrypoint_id()))
                    {
                        pNewTimelineItem->setBrush(new QBrush(QColor(randomRGB())));
                    }
                }
            }
        }
        AddApiCallsToTimeline(pChildCallTreeItem, pNewTimelineItem);
    }
}
//-----------------------------------------------------------------------------
void vktraceviewer_QTimelineView::calculateRectsIfNecessary()
{
    if (!m_hashIsDirty)
    {
        return;
    }

    if (model() == NULL)
    {
        return;
    }

    int itemHeight = m_threadHeight * 0.4;

    for (int threadIndex = 0; threadIndex < m_threadIdList.size(); threadIndex++)
    {
        int top = (m_threadHeight * threadIndex) + (m_threadHeight * 0.5) - itemHeight/2;
        this->m_threadArea[threadIndex] = QRect(0, top, viewport()->width(), itemHeight);
    }

    int numRows = model()->rowCount();
    for (int row = 0; row < numRows; row++)
    {
        QRectF rect;
        QModelIndex item = model()->index(row, vktraceviewer_QTraceFileModel::Column_EntrypointName);

        vktrace_trace_packet_header* pHeader = (vktrace_trace_packet_header*)item.internalPointer();

        // make sure item is valid size
        if (pHeader->entrypoint_end_time > pHeader->entrypoint_begin_time)
        {
            int threadIndex = m_threadIdList.indexOf(pHeader->thread_id);
            int topOffset = (m_threadHeight * threadIndex) + (m_threadHeight * 0.5);

            uint64_t duration = pHeader->entrypoint_end_time - pHeader->entrypoint_begin_time;

            float leftOffset = u64ToFloat(pHeader->entrypoint_begin_time - m_rawStartTime);
            float Width = u64ToFloat(duration);

            // create the rect that represents this item
            rect.setLeft(leftOffset);
            rect.setTop(topOffset - (itemHeight/2));
            rect.setWidth(Width);
            rect.setHeight(itemHeight);
        }

        m_rowToRectMap[row] = rect;
    }

    m_hashIsDirty = false;
    viewport()->update();
}
void vogleditor_apiCallTimelineModel::AddApiCallsToTimeline(vogleditor_apiCallTreeItem* pRoot, vogleditor_timelineItem* pDestRoot)
{
   int numChildren = pRoot->childCount();
   for (int c = 0; c < numChildren; c++)
   {
      vogleditor_apiCallTreeItem* pChild = pRoot->child(c);
      if (pChild->apiCallItem() != NULL)
      {
          float beginFloat = u64ToFloat(pChild->apiCallItem()->startTime() - m_rawBaseTime);
          float endFloat = u64ToFloat(pChild->apiCallItem()->endTime() - m_rawBaseTime);

          vogleditor_timelineItem* pNewItem = new vogleditor_timelineItem(beginFloat, endFloat, pDestRoot);
          pNewItem->setApiCallItem(pChild->apiCallItem());
          pDestRoot->appendChild(pNewItem);
          AddApiCallsToTimeline(pChild, pNewItem);
      }
   }
}
void vktraceviewer_QTimelineItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    vktrace_trace_packet_header* pHeader = (vktrace_trace_packet_header*)index.internalPointer();

    if (pHeader->entrypoint_end_time <= pHeader->entrypoint_begin_time)
    {
        return;
    }

    painter->save();
    {
        vktraceviewer_QTimelineView* pTimeline = (vktraceviewer_QTimelineView*)parent();
        if (pTimeline != NULL)
        {
            QRectF rect = option.rect;

            if (rect.width() == 0)
            {
                rect.setWidth(1);
            }

            float duration = u64ToFloat(pHeader->entrypoint_end_time - pHeader->entrypoint_begin_time);
            float durationRatio = duration / pTimeline->getMaxItemDuration();
            int intensity = std::min(255, (int)(durationRatio * 255.0f));
            QColor color(intensity, 255-intensity, 0);

            // add gradient to the items better distinguish between the end of one and beginning of the next
            QLinearGradient linearGrad(rect.center(), rect.bottomRight());
            linearGrad.setColorAt(0, color);
            linearGrad.setColorAt(1, color.darker(150));

            painter->setBrush(linearGrad);
            painter->setPen(Qt::NoPen);

            painter->drawRect(rect);

            if (rect.width() >= 2)
            {
                // draw shadow and highlight around the item
                painter->setPen(color.darker(175));
                painter->drawLine(rect.right()-1, rect.top(), rect.right()-1, rect.bottom()-1);
                painter->drawLine(rect.right()-1, rect.bottom()-1, rect.left(), rect.bottom()-1);

                painter->setPen(color.lighter());
                painter->drawLine(rect.left(), rect.bottom()-1, rect.left(), rect.top());
                painter->drawLine(rect.left(), rect.top(), rect.right()-1, rect.top());
            }
        }
    }

    painter->restore();
}
//-----------------------------------------------------------------------------
void vktraceviewer_QTimelineView::resizeEvent(QResizeEvent *event)
{
    m_hashIsDirty = true;
    deletePixmap();

    // The duration to viewport scale should allow us to map the entire timeline into the current window width.
    if (m_lineLength > 0)
    {
        // Calculate zoom ratio prior to the resize
        float ratio = m_zoomFactor / m_durationToViewportScale;

        // Adjust scale that fits the timeline duration to the viewport area
        int timelineViewportWidth = viewport()->width() - 2*m_margin - m_scrollBarWidth;
        m_durationToViewportScale = (float)timelineViewportWidth / u64ToFloat(m_lineLength);

        // Adjust the zoom factor based on the new duration to viewport scale and the previous ratio
        m_zoomFactor = m_durationToViewportScale * ratio;

        // Adjust horizontal scroll bar to maintain current view as best as possible
        float hRatio = (float)horizontalScrollBar()->value() / qMax(1.0f,(float)horizontalScrollBar()->maximum());
        updateGeometries();
        horizontalScrollBar()->setValue(hRatio * horizontalScrollBar()->maximum());
    }
}
//-----------------------------------------------------------------------------
float vktraceviewer_QTimelineView::scaleDurationHorizontally(uint64_t value) const
{
    float scaled = u64ToFloat(value) * m_zoomFactor;

    return scaled;
}
//-----------------------------------------------------------------------------
void vktraceviewer_QTimelineView::setModel(QAbstractItemModel* pModel)
{
    QAbstractItemView::setModel(pModel);
    m_hashIsDirty = true;
    setItemDelegate(&m_itemDelegate);

    m_threadIdList.clear();
    m_threadMask.clear();
    m_maxItemDuration = 0;
    m_rawStartTime = 0;
    m_rawEndTime = 0;

    deletePixmap();

    // Gather some stats from the model
    if (model() == NULL)
    {
        horizontalScrollBar()->setRange(0,0);
        verticalScrollBar()->setRange(0,0);
        return;
    }

    int numRows = model()->rowCount();
    for (int i = 0; i < numRows; i++)
    {
        // Count number of unique thread Ids
        QModelIndex item = model()->index(i, vktraceviewer_QTraceFileModel::Column_ThreadId);
        if (item.isValid())
        {
            uint32_t threadId = item.data().toUInt();
            if (!m_threadIdList.contains(threadId))
            {
                m_threadIdList.append(threadId);
                m_threadMask.insert(threadId, QVector<int>());
                m_threadArea.append(QRect());
            }
        }

        // Find duration of longest item
        item = model()->index(i, vktraceviewer_QTraceFileModel::Column_CpuDuration);
        if (item.isValid())
        {
            float duration = item.data().toFloat();
            if (m_maxItemDuration < duration)
            {
                m_maxItemDuration = duration;
            }
        }
    }

    // Get start time
    QModelIndex start = model()->index(0, vktraceviewer_QTraceFileModel::Column_BeginTime);
    if (start.isValid())
    {
        m_rawStartTime = start.data().toULongLong();
    }

    // Get end time
    QModelIndex end = model()->index(numRows - 1, vktraceviewer_QTraceFileModel::Column_EndTime);
    if (end.isValid())
    {
        m_rawEndTime = end.data().toULongLong();
    }

    // the duration to viewport scale should allow us to map the entire timeline into the current window width.
    m_lineLength = m_rawEndTime - m_rawStartTime;

    int initialTimelineWidth = viewport()->width() - 2*m_margin - m_scrollBarWidth;
    m_durationToViewportScale = (float)initialTimelineWidth / u64ToFloat(m_lineLength);

    m_zoomFactor = m_durationToViewportScale;

    verticalScrollBar()->setMaximum(1000);
    verticalScrollBar()->setValue(0);
    verticalScrollBar()->setPageStep(1);
    verticalScrollBar()->setSingleStep(1);
}
void vogleditor_apiCallTimelineModel::refresh()
{
   if (m_pRootApiCall == NULL)
   {
      return;
   }


   float timelineStart = 0;
   float timelineEnd = 1;
   m_rawBaseTime = timelineStart;

   int numChildren = m_pRootApiCall->childCount();
   if (numChildren > 0)
   {
       uint64_t firstStart = 0;
       uint64_t firstEnd = 0;
       m_pRootApiCall->child(0)->frameItem()->getStartEndTimes(firstStart, firstEnd);

       uint64_t lastStart = 0;
       uint64_t lastEnd = 0;
       vogleditor_apiCallTreeItem* pLastChild = m_pRootApiCall->child(numChildren-1);
       pLastChild->frameItem()->getStartEndTimes(lastStart, lastEnd);

       m_rawBaseTime = firstStart;
       timelineStart = u64ToFloat(firstStart - m_rawBaseTime);
       timelineEnd = u64ToFloat(lastEnd - m_rawBaseTime);
   }

   // see if we actually have to update some of this stuff
   bool skipCreation = false;
   if (m_rootItem != NULL)
   {
      if (m_rootItem->getDuration() == timelineEnd - timelineStart &&
          m_rootItem->childCount() == numChildren)
      {
         // no need to make a new root
         skipCreation = true;
      }
   }

   if (!skipCreation)
   {
      if (m_rootItem != NULL)
      {
         delete m_rootItem;
      }

      m_rootItem = new vogleditor_timelineItem(timelineStart, timelineEnd);

      // add markers for the start of each frame
      float frameStart = 0;
      for (int c = 0; c < numChildren; c++)
      {
         vogleditor_apiCallTreeItem* pFrameItem = m_pRootApiCall->child(c);
         if (pFrameItem->childCount() > 0)
         {
            frameStart = u64ToFloat(pFrameItem->child(0)->apiCallItem()->startTime() - m_rawBaseTime);
            vogleditor_timelineItem* pFrameTimelineItem = new vogleditor_timelineItem(frameStart, m_rootItem);
            pFrameTimelineItem->setFrameItem(pFrameItem->frameItem());
            m_rootItem->appendChild(pFrameTimelineItem);
         }
         else
         {
            // if we get here, we are at a frame that had no api calls
         }
      }

      // recursively add each children
      for (int frameIndex = 0; frameIndex < numChildren; frameIndex++)
      {
         vogleditor_apiCallTreeItem* pFrameChild = m_pRootApiCall->child(frameIndex);

         AddApiCallsToTimeline(pFrameChild, m_rootItem);
      }
   }
}