root/src/apps/debuganalyzer/gui/main_window/SchedulingPage.cpp
/*
 * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */


#include "main_window/SchedulingPage.h"

#include <stdio.h>

#include <algorithm>
#include <new>

#include <LayoutBuilder.h>
#include <LayoutUtils.h>
#include <MenuItem.h>
#include <PopUpMenu.h>
#include <ScrollView.h>
#include <SplitView.h>
#include <ToolTip.h>

#include <DebugEventStream.h>

#include <thread_defs.h>

#include "Array.h"

#include "chart/NanotimeChartAxisLegendSource.h"
#include "chart/LegendChartAxis.h"
#include "chart/StringChartLegend.h"
#include "HeaderView.h"
#include "Model.h"
#include "util/TimeUtils.h"


static const float kThreadNameMargin = 3.0f;
static const float kViewSeparationMargin = 1.0f;


enum {
        MSG_SCHEDULING_FILTER_HIDE_SELECTED             = 'schs',
        MSG_SCHEDULING_FILTER_HIDE_UNSELECTED   = 'schu',
        MSG_SCHEDULING_FILTER_SHOW_ALL                  = 'scsa'
};


enum {
        IO_SCHEDULING_STATE_IDLE,
        IO_SCHEDULING_STATE_PENDING_REQUEST,
        IO_SCHEDULING_STATE_PENDING_OPERATION
};


struct MainWindow::SchedulingPage::SchedulingEvent {
        nanotime_t                                              time;
        Model::ThreadWaitObjectGroup*   waitObject;
        ThreadState                                             state;

        SchedulingEvent(nanotime_t time, ThreadState state,
                Model::ThreadWaitObjectGroup* waitObject)
                :
                time(time),
                waitObject(waitObject),
                state(state)
        {
        }
};


struct MainWindow::SchedulingPage::IOSchedulingEvent {
        nanotime_t      time;
        uint32          state;

        IOSchedulingEvent(nanotime_t time, uint32 state)
                :
                time(time),
                state(state)
        {
        }
};


class MainWindow::SchedulingPage::SchedulingData {
public:
        SchedulingData()
                :
                fModel(NULL),
                fDataArrays(NULL),
                fIODataArrays(NULL),
                fRecordingEnabled(false)
        {
        }

        status_t InitCheck() const
        {
                return fDataArrays != NULL && fIODataArrays != NULL
                        ? B_OK : B_NO_MEMORY;
        }

        void SetModel(Model* model)
        {
                delete[] fDataArrays;
                delete[] fIODataArrays;

                fModel = model;
                fDataArrays = NULL;
                fIODataArrays = NULL;

                if (fModel != NULL) {
                        int32 threadCount = fModel->CountThreads();
                        fDataArrays = new(std::nothrow) DataArray[threadCount];
                        fIODataArrays = new(std::nothrow) IODataArray[threadCount];
                }
        }

        void SetRecordingEnabled(bool enabled)
        {
                fRecordingEnabled = enabled;
        }

        void Clear()
        {
                int32 count = fModel->CountThreads();

                if (fDataArrays != NULL) {
                        for (int32 i = 0; i < count; i++)
                                fDataArrays[i].Clear();
                }

                if (fIODataArrays != NULL) {
                        for (int32 i = 0; i < count; i++)
                                fIODataArrays[i].Clear();
                }
        }

        const Array<SchedulingEvent>& EventsForThread(int32 index)
        {
                return fDataArrays[index];
        }

        const Array<IOSchedulingEvent>& IOEventsForThread(int32 index)
        {
                return fIODataArrays[index];
        }

        void AddState(Model::Thread* thread, nanotime_t time, ThreadState state,
                Model::ThreadWaitObjectGroup* waitObject)
        {
                DataArray& array = fDataArrays[thread->Index()];
                if (!array.IsEmpty()) {
                        SchedulingEvent& lastEvent = array[array.Size() - 1];
                        if (fRecordingEnabled) {
                                if (lastEvent.state == state
                                        && lastEvent.waitObject == waitObject) {
                                        return;
                                }
                        } else {
                                // recording not enabled yet -- overwrite the last event
                                lastEvent = SchedulingEvent(time, state, waitObject);
                                return;
                        }
                }

                SchedulingEvent event(time, state, waitObject);
                array.Add(event);
        }

        void AddIOState(Model::Thread* thread, nanotime_t time, uint32 state)
        {
                IODataArray& array = fIODataArrays[thread->Index()];
                array.Add(IOSchedulingEvent(time, state));
        }

        void AddRun(Model::Thread* thread, nanotime_t time)
        {
                AddState(thread, time, RUNNING, NULL);
        }

        void AddLatency(Model::Thread* thread, nanotime_t time)
        {
                AddState(thread, time, READY, NULL);
        }

        void AddPreemption(Model::Thread* thread, nanotime_t time)
        {
                AddState(thread, time, PREEMPTED, NULL);
        }

        void AddWait(Model::Thread* thread, nanotime_t time,
                Model::ThreadWaitObjectGroup* waitObject)
        {
                AddState(thread, time, WAITING, waitObject);
        }

        void AddUnspecifiedWait(Model::Thread* thread, nanotime_t time)
        {
                AddState(thread, time, WAITING, NULL);
        }

private:
        typedef Array<SchedulingEvent> DataArray;
        typedef Array<IOSchedulingEvent> IODataArray;

private:
        Model*                  fModel;
        DataArray*              fDataArrays;
        IODataArray*    fIODataArrays;
        bool                    fRecordingEnabled;
};


struct MainWindow::SchedulingPage::TimeRange : BReferenceable {
        nanotime_t      startTime;
        nanotime_t      endTime;

        TimeRange(nanotime_t startTime, nanotime_t endTime)
                :
                startTime(startTime),
                endTime(endTime)
        {
        }
};


class MainWindow::SchedulingPage::TimelineHeaderRenderer
        : public HeaderRenderer {
public:
        TimelineHeaderRenderer()
                :
                fAxis(new NanotimeChartAxisLegendSource, new StringChartLegendRenderer)
        {
                fAxis.SetLocation(CHART_AXIS_TOP);
        }

        virtual float HeaderHeight(BView* view, const Header* header)
        {
                _SetupAxis(view, header);
                return fAxis.PreferredSize(view, view->Frame().Size()).height;
        }

        virtual float PreferredHeaderWidth(BView* view, const Header* header)
        {
                _SetupAxis(view, header);
                return fAxis.PreferredSize(view, view->Frame().Size()).width;
        }

        virtual void DrawHeader(BView* view, BRect frame, BRect updateRect,
                const Header* header, uint32 flags)
        {
                _SetupAxis(view, header);
                fAxis.SetFrame(frame);
                DrawHeaderBackground(view, frame, updateRect, flags);
                fAxis.Render(view, updateRect);
        }

private:
        void _SetupAxis(BView* view, const Header* header)
        {
                BVariant value;
                if (header->GetValue(value)) {
                        TimeRange* timeRange = dynamic_cast<TimeRange*>(
                                value.ToReferenceable());
                        if (timeRange != NULL) {
                                ChartDataRange range = ChartDataRange(timeRange->startTime,
                                        timeRange->endTime);
                                if (range != fRange) {
                                        fAxis.SetRange(range);
                                        fRange = range;
                                }
                        }
                }
        }

private:
        LegendChartAxis         fAxis;
        ChartDataRange          fRange;
};


class MainWindow::SchedulingPage::BaseView : public BView {
public:
        BaseView(const char* name, FontInfo& fontInfo,
                ListSelectionModel* filterModel, uint32 flags = 0)
                :
                BView(name, flags),
                fModel(NULL),
                fFilterModel(filterModel),
                fFontInfo(fontInfo)
        {
        }

        virtual void SetModel(Model* model)
        {
                fModel = model;

                InvalidateLayout();
        }

protected:
        int32 CountLines() const
        {
                return fFilterModel->CountSelectedItems();
        }

        float TotalHeight() const
        {
                return fFontInfo.lineHeight * CountLines();
        }

        int32 LineAt(BPoint point) const
        {
                int32 line = (int32)point.y / (int32)fFontInfo.lineHeight;
                return line < CountLines() ? line : -1;
        }

        void GetLineRange(BRect rect, int32& minLine, int32& maxLine) const
        {
                int32 lineHeight = (int32)fFontInfo.lineHeight;
                minLine = (int32)rect.top / lineHeight;
                maxLine = ((int32)ceilf(rect.bottom) + lineHeight - 1) / lineHeight;
                minLine = std::max(minLine, (int32)0);
                maxLine = std::min(maxLine, CountLines() - 1);
        }

        BRect LineRect(uint32 line) const
        {
                float y = (float)line * fFontInfo.lineHeight;
                return BRect(0, y, Bounds().right, y + fFontInfo.lineHeight - 1);
        }

protected:
        Model*                          fModel;
        ListSelectionModel*     fFilterModel;
        FontInfo&                       fFontInfo;
};


class MainWindow::SchedulingPage::LineBaseView : public BaseView,
        protected ListSelectionModel::Listener {
public:
        LineBaseView(const char* name, FontInfo& fontInfo,
                ListSelectionModel* filterModel, ListSelectionModel* selectionModel)
                :
                BaseView(name, fontInfo, filterModel, B_WILL_DRAW),
                fSelectionModel(selectionModel),
                fTextColor(ui_color(B_DOCUMENT_TEXT_COLOR)),
                fSelectedTextColor(fTextColor),
                fBackgroundColor(ui_color(B_DOCUMENT_BACKGROUND_COLOR)),
                fSelectedBackgroundColor(tint_color(fBackgroundColor, B_DARKEN_2_TINT))
        {
                fFilterModel->AddListener(this);
                fSelectionModel->AddListener(this);
        }

        void RemoveListeners()
        {
                fSelectionModel->RemoveListener(this);
                fFilterModel->RemoveListener(this);
        }

        virtual void MessageReceived(BMessage* message)
        {
                switch (message->what) {
                        case MSG_SCHEDULING_FILTER_HIDE_SELECTED:
                        {
                                int32 filteredCount = fFilterModel->CountSelectedItems();
                                int32 selectedCount = fSelectionModel->CountSelectedItems();
                                if (selectedCount == 0 || selectedCount == filteredCount)
                                        break;

                                // Set the filter model to the unselected items.
                                ListSelectionModel tempModel;
                                for (int32 i = 0; i < filteredCount; i++) {
                                        if (!fSelectionModel->IsItemSelected(i)) {
                                                tempModel.SelectItem(fFilterModel->SelectedItemAt(i),
                                                        true);
                                        }
                                }

                                fSelectionModel->Clear();
                                *fFilterModel = tempModel;
                                Invalidate();
                                break;
                        }

                        case MSG_SCHEDULING_FILTER_HIDE_UNSELECTED:
                        {
                                int32 filteredCount = fFilterModel->CountSelectedItems();
                                int32 selectedCount = fSelectionModel->CountSelectedItems();
                                if (selectedCount == 0 || selectedCount == filteredCount)
                                        break;

                                // Set the filter model to the selected items.
                                // There might already be a filter applied, so we have to
                                // build the new filter model manually.
                                ListSelectionModel tempModel;
                                for (int32 i = 0; i < selectedCount; i++) {
                                        int32 index = fFilterModel->SelectedItemAt(
                                                fSelectionModel->SelectedItemAt(i));
                                        if (index >= 0)
                                                tempModel.SelectItem(index, true);
                                }

                                fSelectionModel->Clear();
                                *fFilterModel = tempModel;
                                Invalidate();
                                break;
                        }

                        case MSG_SCHEDULING_FILTER_SHOW_ALL:
                        {
                                int32 threadCount = fModel->CountThreads();
                                if (fFilterModel->CountSelectedItems() == threadCount)
                                        break;

                                // unset the filter
                                ListSelectionModel tempModel = *fFilterModel;
                                fSelectionModel->Clear();
                                fFilterModel->SelectItems(0, threadCount, false);
                                *fSelectionModel = tempModel;
                                Invalidate();
                                break;
                        }

                        default:
                                BaseView::MessageReceived(message);
                                break;
                }
        }

        virtual void MouseDown(BPoint where)
        {
                // get buttons and modifiers
                BMessage* message = Looper()->CurrentMessage();
                if (message == NULL)
                        return;

                int32 buttons;
                if (message->FindInt32("buttons", &buttons) != B_OK)
                        return;

                int32 modifiers;
                if (message->FindInt32("modifiers", &modifiers) != B_OK)
                        modifiers = 0;

                // update selection
                int32 line = LineAt(where);
                if (line >= 0) {
                        if ((modifiers & B_SHIFT_KEY) != 0) {
                                int32 selectedLines = fSelectionModel->CountSelectedItems();
                                if (selectedLines > 0) {
                                        int32 firstLine = fSelectionModel->SelectedItemAt(0);
                                        int32 lastLine = fSelectionModel->SelectedItemAt(
                                                selectedLines - 1);
                                        firstLine = std::min(firstLine, line);
                                        lastLine = std::max(lastLine, line);
                                        fSelectionModel->SelectItems(firstLine,
                                                lastLine - firstLine + 1, false);
                                } else
                                        fSelectionModel->SelectItem(line, true);
                        } else {
                                if (fSelectionModel->IsItemSelected(line)) {
                                        if ((modifiers & B_COMMAND_KEY) != 0)
                                                fSelectionModel->DeselectItem(line);
                                } else {
                                        fSelectionModel->SelectItem(line,
                                                (modifiers & B_COMMAND_KEY) != 0);
                                }
                        }
                } else
                        fSelectionModel->Clear();

                // on right mouse button open context menu
                if ((buttons & B_SECONDARY_MOUSE_BUTTON) != 0 && fModel != NULL) {
                        BPopUpMenu* contextMenu = new BPopUpMenu("scheduling context menu",
                                false, false);

                        int32 filteredCount = fFilterModel->CountSelectedItems();
                        int32 selectedCount = fSelectionModel->CountSelectedItems();

                        BMenuItem* item = new BMenuItem("Hide selected threads",
                                new BMessage(MSG_SCHEDULING_FILTER_HIDE_SELECTED));
                        contextMenu->AddItem(item);
                        item->SetTarget(this);
                        if (selectedCount == 0 || selectedCount == filteredCount)
                                item->SetEnabled(false);

                        item = new BMenuItem("Hide unselected threads",
                                new BMessage(MSG_SCHEDULING_FILTER_HIDE_UNSELECTED));
                        contextMenu->AddItem(item);
                        item->SetTarget(this);
                        if (selectedCount == 0 || selectedCount == filteredCount)
                                item->SetEnabled(false);

                        item = new BMenuItem("Show all threads",
                                new BMessage(MSG_SCHEDULING_FILTER_SHOW_ALL));
                        contextMenu->AddItem(item);
                        item->SetTarget(this);
                        if (filteredCount == fModel->CountThreads())
                                item->SetEnabled(false);

                        BPoint screenWhere = ConvertToScreen(where);
                        BRect mouseRect(screenWhere, screenWhere);
                        mouseRect.InsetBy(-4.0, -4.0);
                        contextMenu->Go(screenWhere, true, false, mouseRect, true);
                }
        }

        virtual void ItemsSelected(ListSelectionModel* model, int32 index,
                int32 count)
        {
                if (model == fFilterModel)
                        Invalidate();
                else
                        InvalidateLines(index, count);
        }

        virtual void ItemsDeselected(ListSelectionModel* model, int32 index,
                int32 count)
        {
                if (model == fFilterModel)
                        Invalidate();
                else
                        InvalidateLines(index, count);
        }

        void InvalidateLines(int32 index, int32 count)
        {
                float top = (float)index * fFontInfo.lineHeight;
                float bottom = (float)(index + count) * fFontInfo.lineHeight - 1;
                BRect bounds(Bounds());
                Invalidate(BRect(bounds.left, top, bounds.right, bottom));
        }

protected:
        ListSelectionModel*             fSelectionModel;
        rgb_color                               fTextColor;
        rgb_color                               fSelectedTextColor;
        rgb_color                               fBackgroundColor;
        rgb_color                               fSelectedBackgroundColor;
};


class MainWindow::SchedulingPage::ThreadsView : public LineBaseView {
public:
        ThreadsView(FontInfo& fontInfo, ListSelectionModel* filterModel,
                ListSelectionModel* selectionModel)
                :
                LineBaseView("threads", fontInfo, filterModel, selectionModel)
        {
                SetViewColor(B_TRANSPARENT_32_BIT);
        }

        ~ThreadsView()
        {
        }

        virtual void Draw(BRect updateRect)
        {
                if (fModel == NULL)
                        return;

                // get the lines intersecting with the update rect
                int32 minLine, maxLine;
                GetLineRange(updateRect, minLine, maxLine);

                for (int32 i = minLine; i <= maxLine; i++) {
                        // draw the line background
                        BRect lineRect = LineRect(i);
                        if (fSelectionModel->IsItemSelected(i)) {
                                SetLowColor(fSelectedBackgroundColor);
                                SetHighColor(fTextColor);
                        } else {
                                SetLowColor(fBackgroundColor);
                                SetHighColor(fSelectedTextColor);
                        }
                        FillRect(lineRect, B_SOLID_LOW);

                        Model::Thread* thread = fModel->ThreadAt(
                                fFilterModel->SelectedItemAt(i));
                        if (thread == NULL)
                                continue;

                        // compose name string
                        BString name = thread->Name();
                        name << " (" << thread->ID() << ")";

                        // draw the string
                        float y = lineRect.bottom - fFontInfo.fontHeight.descent + 1;
                        DrawString(name, BPoint(kThreadNameMargin, y));
                }

                BRect bounds(Bounds());
                BRect lowerRect(0, (float)CountLines() * fFontInfo.lineHeight,
                        bounds.Width(), bounds.Height());
                if (lowerRect.Intersects(updateRect)) {
                        SetHighColor(fBackgroundColor);
                        FillRect(lowerRect);
                }
        }

        virtual BSize MinSize()
        {
                return BSize(100, TotalHeight());
        }

        virtual BSize PreferredSize()
        {
                return BSize(250, TotalHeight());
        }

        virtual BSize MaxSize()
        {
                return BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED);
        }
};


class MainWindow::SchedulingPage::SchedulingView : public LineBaseView {
public:
        struct Listener {
                virtual ~Listener()
                {
                }

                virtual void DataWidthChanged() = 0;
                virtual void DataRangeChanged() = 0;
        };

private:
        enum {
                COLOR_RUNNING = 0,
                COLOR_PREEMPTED,
                COLOR_READY,
                COLOR_IO_REQUEST,
                COLOR_IO_OPERATION,
                ACTIVITY_COLOR_COUNT
        };

public:
        SchedulingView(FontInfo& fontInfo, ListSelectionModel* filterModel,
                ListSelectionModel* selectionModel)
                :
                LineBaseView("scheduling", fontInfo, filterModel, selectionModel),
                fStartTime(0),
                fEndTime(0),
                fNSecsPerPixel(1000000),
                fLastMousePos(-1, -1),
                fListener(NULL)
        {
                fActivityColors[COLOR_RUNNING].set_to(0, 255, 0);
                fActivityColors[COLOR_PREEMPTED].set_to(255, 127, 0);
                fActivityColors[COLOR_READY].set_to(255, 0, 0);
                fActivityColors[COLOR_IO_REQUEST].set_to(127, 127, 255);
                fActivityColors[COLOR_IO_OPERATION].set_to(0, 0, 200);

                fActivitySelectedColors[COLOR_RUNNING] = tint_color(
                        fActivityColors[COLOR_RUNNING], B_DARKEN_2_TINT);
                fActivitySelectedColors[COLOR_PREEMPTED] = tint_color(
                        fActivityColors[COLOR_PREEMPTED], B_DARKEN_2_TINT);
                fActivitySelectedColors[COLOR_READY] = tint_color(
                        fActivityColors[COLOR_READY], B_DARKEN_2_TINT);
                fActivitySelectedColors[COLOR_IO_REQUEST] = tint_color(
                        fActivityColors[COLOR_IO_REQUEST], B_DARKEN_2_TINT);
                fActivitySelectedColors[COLOR_IO_OPERATION] = tint_color(
                        fActivityColors[COLOR_IO_OPERATION], B_DARKEN_2_TINT);
        }

        ~SchedulingView()
        {
        }

        virtual void SetModel(Model* model)
        {
                LineBaseView::SetModel(model);
                fSchedulingData.SetModel(model);
                fStartTime = 0;
                fEndTime = 0;

                if (fListener != NULL) {
                        fListener->DataWidthChanged();
                        fListener->DataRangeChanged();
                }

        }

        void SetListener(Listener* listener)
        {
                fListener = listener;
        }

        void UpdateScrollBar()
        {
                float width = Frame().Width();
                float dataWidth = std::max(width, MinSize().width);

                if (BScrollBar* scrollBar = ScrollBar(B_HORIZONTAL)) {
                        float range = dataWidth - width;
                        if (range > 0) {
                                scrollBar->SetRange(0, range);
                                scrollBar->SetProportion((width + 1) / (dataWidth + 1));
                                scrollBar->SetSteps(fFontInfo.lineHeight, width + 1);
                        } else {
                                scrollBar->SetRange(0, 0);
                                scrollBar->SetProportion(1);
                        }
                }
        }

        void GetDataRange(nanotime_t& _startTime, nanotime_t& _endTime)
        {
                _GetEventTimeRange(_startTime, _endTime);
        }

        virtual BSize MinSize()
        {
                nanotime_t timeSpan = fModel != NULL ? fModel->LastEventTime() : 0;
                float width = std::max(float(timeSpan / fNSecsPerPixel), 100.0f);
                return BSize(width, TotalHeight());
        }

        virtual BSize MaxSize()
        {
                return BSize(MinSize().width, B_SIZE_UNLIMITED);
        }

        virtual void ScrollTo(BPoint where)
        {
                LineBaseView::ScrollTo(where);
                fStartTime = 0;
                fEndTime = 0;

                if (fListener != NULL)
                        fListener->DataRangeChanged();
        }

        virtual void MessageReceived(BMessage* message)
        {
                switch (message->what) {
                        case B_MOUSE_WHEEL_CHANGED:
                        {
                                // We're only interested in Shift + vertical wheel.
                                float deltaY;
                                if ((modifiers() & B_SHIFT_KEY) == 0
                                        || message->FindFloat("be:wheel_delta_y", &deltaY)
                                                != B_OK) {
                                        break;
                                }

                                _Zoom(fLastMousePos.x, deltaY);

                                return;
                        }
                }

                LineBaseView::MessageReceived(message);
        }

        virtual void MouseMoved(BPoint where, uint32 code,
                const BMessage* dragMessage)
        {
                fLastMousePos = where - LeftTop();

//              if (fDraggingStartPos.x < 0)
//                      return;
//
//              ScrollBar(B_HORIZONTAL)->SetValue(fDraggingStartScrollValue
//                      + fDraggingStartPos.x - where.x);
        }

        virtual bool GetToolTipAt(BPoint point, BToolTip** _tip)
        {
                Model::Thread* thread;
                nanotime_t time;
                _GetThreadAndTimeAt(point, thread, time);
                if (thread == NULL)
                        return false;

                // get the thread's state
                ThreadState threadState = UNKNOWN;
                nanotime_t threadStateTime = time;
                nanotime_t threadStateEndTime = time;
                Model::ThreadWaitObjectGroup* threadWaitObject = NULL;
                {
                        const Array<SchedulingEvent>& events
                                = fSchedulingData.EventsForThread(thread->Index());
                        int32 eventCount = events.Size();

                        int32 lower = 0;
                        int32 upper = eventCount - 1;
                        while (lower < upper) {
                                int32 mid = (lower + upper + 1) / 2;
                                const SchedulingEvent& event = events[mid];
                                if (event.time > time)
                                        upper = mid - 1;
                                else
                                        lower = mid;
                        }

                        if (lower >= 0 && lower < eventCount) {
                                threadState = events[lower].state;
                                threadStateTime = events[lower].time;
                                threadWaitObject = events[lower].waitObject;
                                if (lower + 1 < eventCount)
                                        threadStateEndTime = events[lower + 1].time;
                                else
                                        threadStateEndTime = fModel->LastEventTime();
                        }
                }

                // get the thread's I/O state
                BObjectList<Model::IORequest> ioRequests;
                _GetIORequests(thread, time, ioRequests);

                // find out which threads are running
                system_profiler_event_header** events = fModel->Events();
                size_t eventIndex = fModel->ClosestEventIndex(time + 1);
                        // find the first event after the one we're interested in; we'll
                        // skip it in the loop

                int32 cpuCount = fModel->CountCPUs();
                int32 missingThreads = cpuCount;
                Model::Thread* runningThreads[cpuCount];
                memset(runningThreads, 0, sizeof(Model::Thread*) * cpuCount);

                while (missingThreads > 0 && eventIndex > 0) {
                        eventIndex--;
                        system_profiler_event_header* header = events[eventIndex];
                        if (header->event != B_SYSTEM_PROFILER_THREAD_SCHEDULED
                                || runningThreads[header->cpu] != NULL) {
                                continue;
                        }

                        system_profiler_thread_scheduled* event
                                = (system_profiler_thread_scheduled*)(header + 1);
                        if (Model::Thread* thread = fModel->ThreadByID(event->thread)) {
                                runningThreads[header->cpu] = thread;
                                missingThreads--;
                        }
                }

                // create the tool tip
                BString text;
                text << "Time: " << format_nanotime(time) << "\n";
                text << "Thread: " << thread->Name() << " (" << thread->ID() << ")\n";
                text << "State: " << thread_state_name(threadState);
                if (threadWaitObject != NULL) {
                        char objectName[32];
                        snprintf(objectName, sizeof(objectName), "%#lx",
                                threadWaitObject->Object());
                        text << " at " << wait_object_type_name(threadWaitObject->Type())
                                << " \"" << threadWaitObject->Name() << "\" "
                                << objectName << "\n";
                } else
                        text << "\n";
                text << "For: " << format_nanotime(time - threadStateTime) << " of "
                        << format_nanotime(threadStateEndTime - threadStateTime);

                if (!ioRequests.IsEmpty()) {
                        int32 scheduler = 0;
                        for (int32 i = 0; Model::IORequest* request = ioRequests.ItemAt(i);
                                        i++) {
                                if (i == 0 || scheduler != request->Scheduler()) {
                                        scheduler = request->Scheduler();
                                        text << "\nI/O channel " << scheduler << ":";
                                }
                                text << "\n  " << (request->IsWrite() ? "write" : "read")
                                        << " request: " << request->BytesTransferred() << "/"
                                        << request->Length() << " ";
                                if (request->IsFinished()) {
                                        text << (request->Status() == B_OK ? "ok" : "failed");
                                        text << ", "
                                                << format_nanotime(
                                                        request->FinishedTime() - request->ScheduledTime());
                                } else
                                        text << "unfinished";

                                for (size_t k = 0; k < request->operationCount; k++) {
                                        Model::IOOperation& operation = request->operations[k];
                                        if (operation.startedEvent->time > fModel->BaseTime() + time
                                                || (operation.IsFinished()
                                                        && operation.FinishedTime()
                                                                <= fModel->BaseTime() + time)) {
                                                continue;
                                        }

                                        text << "\n    " << (operation.IsWrite() ? "write" : "read")
                                                << ": " << operation.BytesTransferred() << "/"
                                                << operation.Length() << " ";
                                        if (operation.IsFinished()) {
                                                text << (request->Status() == B_OK ? "ok" : "failed");
                                                text << ", "
                                                        << format_nanotime(operation.FinishedTime()
                                                                - operation.StartedTime());
                                        } else
                                                text << "unfinished";
                                }
                        }
                }

                text << "\n\n";
                text << "Running threads:";
                for (int32 i = 0; i < cpuCount; i++) {
                        text << "\n  " << i << ": ";
                        if (Model::Thread* thread = runningThreads[i])
                                text << thread->Name() << " (" << thread->ID() << ")";
                        else
                                text << "?";
                }

                *_tip = new(std::nothrow) BTextToolTip(text);
                return *_tip != NULL;
        }

        virtual void Draw(BRect updateRect)
        {
                if (fModel == NULL || fSchedulingData.InitCheck() != B_OK)
                        return;

                _UpdateData();

                // draw the events

                // get the lines intersecting with the update rect
                int32 minLine, maxLine;
                GetLineRange(updateRect, minLine, maxLine);

                for (int32 i = minLine; i <= maxLine; i++) {
                        // draw the background
                        const rgb_color* activityColors;
                        if (fSelectionModel->IsItemSelected(i)) {
                                activityColors = fActivitySelectedColors;
                                SetLowColor(fSelectedBackgroundColor);
                        } else {
                                activityColors = fActivityColors;
                                SetLowColor(fBackgroundColor);
                        }
                        BRect lineRect = LineRect(i);
                        FillRect(lineRect, B_SOLID_LOW);

                        Model::Thread* thread = fModel->ThreadAt(
                                fFilterModel->SelectedItemAt(i));
                        if (thread == NULL)
                                continue;

                        BRect schedulingLineRect = lineRect;
                        schedulingLineRect.bottom -= lineRect.IntegerHeight() / 2;
                        BRect ioLineRect = lineRect;
                        ioLineRect.top = schedulingLineRect.bottom + 1;

                        const Array<SchedulingEvent>& events
                                = fSchedulingData.EventsForThread(thread->Index());

                        int32 eventCount = events.Size();
//printf("drawing events for thread %ld: %ld events\n", thread->Index(), eventCount);
                        for (int32 k = 0; k < eventCount; k++) {
                                const SchedulingEvent& event = events[k];
                                nanotime_t startTime = std::max(event.time, fStartTime);
                                nanotime_t endTime = k + 1 < eventCount
                                        ? std::min(events[k + 1].time, fEndTime) : fEndTime;

                                switch (event.state) {
                                        case RUNNING:
                                        case STILL_RUNNING:
                                                SetHighColor(activityColors[COLOR_RUNNING]);
                                                break;
                                        case PREEMPTED:
                                                SetHighColor(activityColors[COLOR_PREEMPTED]);
                                                break;
                                        case READY:
                                                SetHighColor(activityColors[COLOR_READY]);
                                                break;
                                        case WAITING:
                                        case UNKNOWN:
                                        default:
                                                continue;
                                }

                                BRect rect = schedulingLineRect;
                                rect.left = startTime / fNSecsPerPixel;
                                rect.right = endTime / fNSecsPerPixel - 1;
                                FillRect(rect);
                        }

                        const Array<IOSchedulingEvent>& ioEvents
                                = fSchedulingData.IOEventsForThread(thread->Index());
                        eventCount = ioEvents.Size();

                        for (int32 k = 0; k < eventCount; k++) {
                                const IOSchedulingEvent& event = ioEvents[k];
                                nanotime_t startTime = std::max(event.time, fStartTime);
                                nanotime_t endTime = k + 1 < eventCount
                                        ? std::min(ioEvents[k + 1].time, fEndTime) : fEndTime;

                                switch (event.state) {
                                        case IO_SCHEDULING_STATE_PENDING_REQUEST:
                                                SetHighColor(activityColors[COLOR_IO_REQUEST]);
                                                break;
                                        case IO_SCHEDULING_STATE_PENDING_OPERATION:
                                                SetHighColor(activityColors[COLOR_IO_OPERATION]);
                                                break;
                                        case IO_SCHEDULING_STATE_IDLE:
                                        default:
                                                continue;
                                }

                                BRect rect = ioLineRect;
                                rect.left = startTime / fNSecsPerPixel;
                                rect.right = endTime / fNSecsPerPixel - 1;
                                FillRect(rect);
                        }
                }
        }

private:
        // shorthands for the longish structure names
        typedef system_profiler_thread_enqueued_in_run_queue
                thread_enqueued_in_run_queue;
        typedef system_profiler_thread_removed_from_run_queue
                thread_removed_from_run_queue;

private:
        void _UpdateData()
        {
                // get the interesting event time range
                nanotime_t startTime;
                nanotime_t endTime;
                _GetEventTimeRange(startTime, endTime);

                if (startTime == fStartTime && endTime == fEndTime)
                        return;
                fStartTime = startTime;
                fEndTime = endTime;

                fSchedulingData.Clear();

//printf("MainWindow::SchedulingPage::SchedulingView::_UpdateData()\n");
//printf("  time range: %lld - %lld\n", startTime, endTime);

                // get a scheduling state close to our start time
                const Model::CompactSchedulingState* compactState
                        = fModel->ClosestSchedulingState(startTime);
//printf("  compactState: %p\n", compactState);
                fState.Clear();
                status_t error = fState.Init(compactState);
                if (error != B_OK)
                        return;

                // init the event stream
                BDebugEventInputStream input;
                error = input.SetTo((uint8*)fModel->EventData(),
                        fModel->EventDataSize(), false);
                if (error == B_OK && compactState != NULL)
                        error = input.Seek(compactState->EventOffset());
//printf("  event offset: %lld, input init error: %s\n", compactState != NULL ? compactState->EventOffset() : 0, strerror(error));
                if (error != B_OK)
                        return;

                fSchedulingData.SetRecordingEnabled(
                        fState.LastEventTime() >= startTime);

                // add the initial thread states to the scheduling data
                if (compactState != NULL) {
                        int32 threadStateCount = compactState->CountThreadsStates();
                        for (int32 i = 0; i < threadStateCount; i++) {
                                const Model::CompactThreadSchedulingState* threadState
                                        = compactState->ThreadStateAt(i);
                                switch (threadState->state) {
                                        case RUNNING:
                                        case STILL_RUNNING:
                                                fSchedulingData.AddRun(threadState->thread,
                                                        threadState->lastTime);
                                                break;
                                        case PREEMPTED:
                                                fSchedulingData.AddPreemption(threadState->thread,
                                                        threadState->lastTime);
                                                break;
                                        case READY:
                                                fSchedulingData.AddLatency(threadState->thread,
                                                        threadState->lastTime);
                                                break;
                                        case WAITING:
                                        {
                                                Model::ThreadWaitObjectGroup* group = NULL;
                                                if (threadState->waitObject != NULL) {
                                                        group = fModel->ThreadWaitObjectGroupFor(
                                                                threadState->ID(),
                                                                threadState->waitObject->Type(),
                                                                threadState->waitObject->Object());
                                                }
                                                fSchedulingData.AddWait(threadState->thread,
                                                        threadState->lastTime, group);
                                                break;
                                        }
                                        case UNKNOWN:
                                        default:
                                                break;
                                }
                        }
                }

                // process the events
                while (true) {
                        // get next event
                        uint32 event;
                        uint32 cpu;
                        const void* buffer;
                        off_t offset;
                        ssize_t bufferSize = input.ReadNextEvent(&event, &cpu, &buffer,
                                &offset);
                        if (bufferSize < 0)
{
printf("failed to read event!\n");
                                return;
}
                        if (buffer == NULL)
                                break;

                        // process the event
                        error = _ProcessEvent(event, cpu, buffer, bufferSize);
                        if (error != B_OK)
                                return;

                        if (fState.LastEventTime() >= startTime)
                                fSchedulingData.SetRecordingEnabled(true);
                        if (fState.LastEventTime() >= endTime)
                                break;
                }

                // process each thread's I/O events
                nanotime_t lastEventTime = fModel->BaseTime() - fModel->LastEventTime();
                int32 threadCount = fModel->CountThreads();
                for (int32 i = 0; i < threadCount; i++) {
                        Model::Thread* thread = fModel->ThreadAt(i);
                        Model::IORequest** requests = thread->IORequests();
                        size_t requestCount = thread->CountIORequests();
                        if (requestCount == 0)
                                continue;

                        // first request
                        nanotime_t clusterStart = requests[0]->scheduledEvent->time;
                        nanotime_t clusterEnd = requests[0]->finishedEvent != NULL
                                ? requests[0]->finishedEvent->time : lastEventTime;
                        BObjectList<Model::IOOperation> operations;

                        // add first request operations
                        for (size_t l = 0; l < requests[0]->operationCount; l++)
                                operations.AddItem(&requests[0]->operations[l]);

                        for (size_t k = 1; k < requestCount; k++) {
                                nanotime_t requestStart = requests[k]->scheduledEvent->time;
                                nanotime_t requestEnd = requests[k]->finishedEvent != NULL
                                        ? requests[k]->finishedEvent->time : lastEventTime;

                                if (requestStart >= endTime)
                                        break;

                                if (requestStart > clusterEnd) {
                                        if (clusterEnd > startTime) {
                                                _AddThreadIOData(thread, clusterStart, clusterEnd,
                                                        operations);
                                        }
                                        operations.MakeEmpty();
                                        clusterStart = requestStart;
                                        clusterEnd = requestEnd;
                                } else
                                        clusterEnd = std::max(clusterEnd, requestEnd);

                                // add request operations
                                for (size_t l = 0; l < requests[k]->operationCount; l++)
                                        operations.AddItem(&requests[k]->operations[l]);
                        }

                        if (clusterEnd > startTime)
                                _AddThreadIOData(thread, clusterStart, clusterEnd, operations);
                }
        }

        void _AddThreadIOData(Model::Thread* thread, nanotime_t startTime,
                nanotime_t endTime, BObjectList<Model::IOOperation>& operations)
        {
//printf("  IORequest cluster: %lld - %lld\n", startTime, endTime);
                // start in pending request state
                fSchedulingData.AddIOState(thread, startTime - fModel->BaseTime(),
                        IO_SCHEDULING_STATE_PENDING_REQUEST);

                int32 operationCount = operations.CountItems();
                if (operationCount == 0)
                        return;

                // sort the operations
                if (operationCount > 1)
                        operations.SortItems(Model::IOOperation::CompareByTime);

                nanotime_t lastEventTime = fModel->BaseTime() + fModel->LastEventTime();

                // process the operations
                Model::IOOperation* operation = operations.ItemAt(0);
                nanotime_t clusterStart = operation->startedEvent->time;
                nanotime_t clusterEnd = operation->finishedEvent != NULL
                        ? operation->finishedEvent->time : lastEventTime;

                for (int32 i = 1; i < operationCount; i++) {
                        operation = operations.ItemAt(i);
                        nanotime_t operationStart = operation->startedEvent->time;
                        nanotime_t operationEnd = operation->finishedEvent != NULL
                                ? operation->finishedEvent->time : lastEventTime;

                        if (operationStart > clusterEnd) {
                                fSchedulingData.AddIOState(thread,
                                        clusterStart - fModel->BaseTime(),
                                        IO_SCHEDULING_STATE_PENDING_OPERATION);
                                fSchedulingData.AddIOState(thread,
                                        clusterEnd - fModel->BaseTime(),
                                        IO_SCHEDULING_STATE_PENDING_REQUEST);

                                clusterStart = operationStart;
                                clusterEnd = operationEnd;
                        } else
                                clusterEnd = std::max(clusterEnd, operationEnd);
                }

                // add the last cluster
                fSchedulingData.AddIOState(thread, clusterStart - fModel->BaseTime(),
                        IO_SCHEDULING_STATE_PENDING_OPERATION);
                fSchedulingData.AddIOState(thread, clusterEnd - fModel->BaseTime(),
                        IO_SCHEDULING_STATE_PENDING_REQUEST);

                // after the end of the request cluster, state is idle again
                fSchedulingData.AddIOState(thread, endTime - fModel->BaseTime(),
                        IO_SCHEDULING_STATE_IDLE);
        }

        void _GetEventTimeRange(nanotime_t& _startTime, nanotime_t& _endTime)
        {
                if (fModel != NULL) {
                        float scrollOffset = _ScrollOffset();
                        _startTime = (nanotime_t)scrollOffset * fNSecsPerPixel;
                        _endTime = (nanotime_t)(scrollOffset + Bounds().Width() + 1)
                                * fNSecsPerPixel;
                } else {
                        _startTime = 0;
                        _endTime = 1;
                }
        }

        void _GetThreadAndTimeAt(BPoint point, Model::Thread*& _thread,
                nanotime_t& _time)
        {
                _thread = fModel->ThreadAt(
                        fFilterModel->SelectedItemAt(LineAt(point)));
                _time = (nanotime_t)point.x * fNSecsPerPixel;
        }

        void _GetIORequests(Model::Thread* thread, nanotime_t time,
                BObjectList<Model::IORequest>& ioRequests)
        {
                // find the time in the event data
                const Array<IOSchedulingEvent>& events
                        = fSchedulingData.IOEventsForThread(thread->Index());
                int32 eventCount = events.Size();

                int32 lower = 0;
                int32 upper = eventCount - 1;
                while (lower < upper) {
                        int32 mid = (lower + upper + 1) / 2;
                        const IOSchedulingEvent& event = events[mid];
                        if (event.time > time)
                                upper = mid - 1;
                        else
                                lower = mid;
                }

                if (lower < 0 || lower >= eventCount)
                        return;

                if (events[lower].state == IO_SCHEDULING_STATE_IDLE)
                        return;

                // find the beginning and end of the I/O request cluster
                while (lower > 0) {
                        if (events[lower - 1].state == IO_SCHEDULING_STATE_IDLE)
                                break;
                        lower--;
                }

                while (upper + 1 < eventCount) {
                        upper++;
                        if (events[upper].state == IO_SCHEDULING_STATE_IDLE)
                                break;
                }

                nanotime_t startTime = events[lower].time;
                nanotime_t endTime = events[upper].state == IO_SCHEDULING_STATE_IDLE
                        ? events[upper].time : fEndTime;

                // convert to absolute time -- as used by the I/O requests
                startTime += fModel->BaseTime();
                endTime += fModel->BaseTime();
                time += fModel->BaseTime();

                // collect the requests in the range
                Model::IORequest** requests = thread->IORequests();
                size_t requestCount = thread->CountIORequests();
                size_t index = thread->ClosestRequestStartIndex(startTime);
                for (; index < requestCount; index++) {
                        Model::IORequest* request = requests[index];

                        if (request->ScheduledTime() >= endTime)
                                break;

                        if (request->ScheduledTime() > time
                                || (request->finishedEvent != NULL
                                        && request->finishedEvent->time <= time)) {
                                continue;
                        }

                        ioRequests.AddItem(request);
                }

                if (ioRequests.CountItems() > 1)
                        ioRequests.SortItems(Model::IORequest::CompareSchedulerTime);
        }

        float _ScrollOffset() const
        {
                if (BScrollBar* scrollBar = ScrollBar(B_HORIZONTAL))
                        return scrollBar->Value();
                return 0;
        }

        void _Zoom(float x, float steps)
        {
                if (steps == 0 || fModel == NULL)
                        return;

                // compute the domain point where to zoom in
                float scrollOffset = _ScrollOffset();
                double timeForX = (scrollOffset + x) * fNSecsPerPixel;

                uint32 factor = 4;
                if (steps < 0) {
                        steps = -steps;
                        factor = 1;
                }

                uint32 oldNsecPerPixel = fNSecsPerPixel;
                for (; steps > 0; steps--)
                        fNSecsPerPixel = fNSecsPerPixel * factor / 2;

                if (fNSecsPerPixel < 1)
                        fNSecsPerPixel = 1;
                else if (fNSecsPerPixel > 1000000)
                        fNSecsPerPixel = 1000000;

                if (fNSecsPerPixel == oldNsecPerPixel)
                        return;

                Invalidate();

                UpdateScrollBar();
                if (BScrollBar* scrollBar = ScrollBar(B_HORIZONTAL))
                        scrollBar->SetValue(timeForX / fNSecsPerPixel - x);

                if (fListener != NULL) {
                        fListener->DataWidthChanged();
                        fListener->DataRangeChanged();
                }
        }

        inline void _UpdateLastEventTime(nanotime_t time)
        {
                fState.SetLastEventTime(time - fModel->BaseTime());
        }

        status_t _ProcessEvent(uint32 event, uint32 cpu, const void* buffer,
                size_t size)
        {
                switch (event) {
                        case B_SYSTEM_PROFILER_THREAD_ADDED:
                                _HandleThreadAdded((system_profiler_thread_added*)buffer);
                                break;

                        case B_SYSTEM_PROFILER_THREAD_REMOVED:
                                _HandleThreadRemoved((system_profiler_thread_removed*)buffer);
                                break;

                        case B_SYSTEM_PROFILER_THREAD_SCHEDULED:
                                _HandleThreadScheduled(
                                        (system_profiler_thread_scheduled*)buffer);
                                break;

                        case B_SYSTEM_PROFILER_THREAD_ENQUEUED_IN_RUN_QUEUE:
                                _HandleThreadEnqueuedInRunQueue(
                                        (thread_enqueued_in_run_queue*)buffer);
                                break;

                        case B_SYSTEM_PROFILER_THREAD_REMOVED_FROM_RUN_QUEUE:
                                _HandleThreadRemovedFromRunQueue(
                                        (thread_removed_from_run_queue*)buffer);
                                break;

                        default:
                                break;
                }

                return B_OK;
        }

        void _HandleThreadAdded(system_profiler_thread_added* event)
        {
//printf("  thread added: %ld\n", event->thread);
                // do we know the thread already?
                Model::ThreadSchedulingState* info = fState.LookupThread(event->thread);
                if (info != NULL) {
                        // TODO: ?
                        return;
                }

                Model::Thread* thread = fModel->ThreadByID(event->thread);
                if (thread == NULL)
                        return;

                // create and add a ThreadSchedulingState
                info = new(std::nothrow) Model::ThreadSchedulingState(thread);
                if (info == NULL)
                        return;

                fState.InsertThread(info);
        }

        void _HandleThreadRemoved(system_profiler_thread_removed* event)
        {
//printf("  thread removed: %ld\n", event->thread);
//              Model::ThreadSchedulingState* thread = fState.LookupThread(
//                      event->thread);
//              if (thread != NULL) {
//                      fState.RemoveThread(thread);
//                      delete thread;
//// TODO: The thread will be unscheduled in a moment and cause a warning! So
//// maybe keep it around in a separate hash table a bit longer?
//              }
        }

        void _HandleThreadScheduled(system_profiler_thread_scheduled* event)
        {
                _UpdateLastEventTime(event->time);

                Model::ThreadSchedulingState* thread = fState.LookupThread(
                        event->thread);
                if (thread == NULL) {
                        printf("Schedule event for unknown thread: %" B_PRId32 "\n",
                                event->thread);
                        return;
                }

                thread->lastTime = fState.LastEventTime();
                thread->state = RUNNING;
                fSchedulingData.AddRun(thread->thread, fState.LastEventTime());

                // unscheduled thread

                if (event->thread == event->previous_thread)
                        return;

                thread = fState.LookupThread(event->previous_thread);
                if (thread == NULL) {
                        printf("Schedule event for unknown previous thread: %" B_PRId32
                                "\n", event->previous_thread);
                        return;
                }

                if (thread->state == STILL_RUNNING) {
                        // thread preempted
                        fSchedulingData.AddPreemption(thread->thread,
                                fState.LastEventTime());

                        thread->lastTime = fState.LastEventTime();
                        thread->state = PREEMPTED;
                } else if (thread->state == RUNNING) {
                        // thread starts waiting (it hadn't been added to the run
                        // queue before being unscheduled)
                        if (event->previous_thread_state == B_THREAD_WAITING) {
                                addr_t waitObject = event->previous_thread_wait_object;
                                switch (event->previous_thread_wait_object_type) {
                                        case THREAD_BLOCK_TYPE_SNOOZE:
                                        case THREAD_BLOCK_TYPE_SIGNAL:
                                                waitObject = 0;
                                                break;
                                        case THREAD_BLOCK_TYPE_SEMAPHORE:
                                        case THREAD_BLOCK_TYPE_CONDITION_VARIABLE:
                                        case THREAD_BLOCK_TYPE_MUTEX:
                                        case THREAD_BLOCK_TYPE_RW_LOCK:
                                        case THREAD_BLOCK_TYPE_OTHER:
                                        case THREAD_BLOCK_TYPE_OTHER_OBJECT:
                                        default:
                                                break;
                                }

                                fSchedulingData.AddWait(thread->thread, fState.LastEventTime(),
                                        fModel->ThreadWaitObjectGroupFor(thread->ID(),
                                                event->previous_thread_wait_object_type, waitObject));
                        } else {
                                fSchedulingData.AddUnspecifiedWait(thread->thread,
                                        fState.LastEventTime());
                        }

                        thread->lastTime = fState.LastEventTime();
                        thread->state = WAITING;
                } else if (thread->state == UNKNOWN) {
                        uint32 threadState = event->previous_thread_state;
                        if (threadState == B_THREAD_WAITING
                                || threadState == B_THREAD_SUSPENDED) {
                                fSchedulingData.AddWait(thread->thread, fState.LastEventTime(),
                                        NULL);
                                thread->lastTime = fState.LastEventTime();
                                thread->state = WAITING;
                        } else if (threadState == B_THREAD_READY) {
                                thread->lastTime = fState.LastEventTime();
                                thread->state = PREEMPTED;
                                fSchedulingData.AddPreemption(thread->thread,
                                        fState.LastEventTime());
                        }
                }
        }

        void _HandleThreadEnqueuedInRunQueue(thread_enqueued_in_run_queue* event)
        {
                _UpdateLastEventTime(event->time);

                Model::ThreadSchedulingState* thread = fState.LookupThread(
                        event->thread);
                if (thread == NULL) {
                        printf("Enqueued in run queue event for unknown thread: %" B_PRId32
                                "\n", event->thread);
                        return;
                }

                if (thread->state == RUNNING || thread->state == STILL_RUNNING) {
                        // Thread was running and is reentered into the run queue. This
                        // is done by the scheduler, if the thread remains ready.
                        thread->state = STILL_RUNNING;
                } else {
                        // Thread was waiting and is ready now.
                        nanotime_t diffTime = fState.LastEventTime() - thread->lastTime;
                        if (thread->waitObject != NULL) {
                                thread->waitObject->AddWait(diffTime);
                                thread->waitObject = NULL;
                        }

                        fSchedulingData.AddLatency(thread->thread, fState.LastEventTime());
                        thread->lastTime = fState.LastEventTime();
                        thread->state = READY;
                }
        }

        void _HandleThreadRemovedFromRunQueue(thread_removed_from_run_queue* event)
        {
                _UpdateLastEventTime(event->time);

                Model::ThreadSchedulingState* thread = fState.LookupThread(
                        event->thread);
                if (thread == NULL) {
                        printf("Removed from run queue event for unknown thread: %" B_PRId32
                                "\n", event->thread);
                        return;
                }

                // This really only happens when the thread priority is changed
                // while the thread is ready.
                fSchedulingData.AddUnspecifiedWait(thread->thread,
                        fState.LastEventTime());

                thread->lastTime = fState.LastEventTime();
                thread->state = WAITING;
        }

private:
        Model::SchedulingState  fState;
        SchedulingData                  fSchedulingData;
        nanotime_t                              fStartTime;
        nanotime_t                              fEndTime;
        uint32                                  fNSecsPerPixel;
        BPoint                                  fLastMousePos;
        Listener*                               fListener;

        rgb_color                               fActivityColors[ACTIVITY_COLOR_COUNT];
        rgb_color                               fActivitySelectedColors[ACTIVITY_COLOR_COUNT];
};


class MainWindow::SchedulingPage::ViewPort : public BaseView,
        private HeaderListener, private SchedulingView::Listener {
public:
        ViewPort(HeaderView* headerView, ThreadsView* threadsView,
                SchedulingView* schedulingView, FontInfo& fontInfo,
                ListSelectionModel* filterModel)
                :
                BaseView("viewport", fontInfo, filterModel),
                fHeaderView(headerView),
                fThreadsView(threadsView),
                fSchedulingView(schedulingView)
        {
                AddChild(threadsView);
                AddChild(schedulingView);

                fSchedulingView->SetListener(this);

                HeaderModel* headerModel = fHeaderView->Model();

                Header* header = new Header((int32)fThreadsView->PreferredSize().width,
                        (int32)fThreadsView->MinSize().width,
                        (int32)fThreadsView->MaxSize().width,
                        (int32)fThreadsView->PreferredSize().width, 0);
                header->SetValue("Thread");
                headerModel->AddHeader(header);
                header->AddListener(this);

                header = new Header(100, 100, 10000, 200, 1);
                        // TODO: Set header width correctly!
                header->SetValue("Activity");
                header->SetHeaderRenderer(new TimelineHeaderRenderer);
                headerModel->AddHeader(header);
//              header->AddListener(this);
        }

        ~ViewPort()
        {
        }

        void RemoveListeners()
        {
                fHeaderView->Model()->HeaderAt(0)->RemoveListener(this);
        }

        void UpdateScrollBars()
        {
                float height = Frame().Height();
                float dataHeight = std::max(height, fSchedulingView->MinSize().height);

                fSchedulingView->UpdateScrollBar();

                if (BScrollBar* scrollBar = ScrollBar(B_VERTICAL)) {
                        float range = dataHeight - height;
                        if (range > 0) {
                                scrollBar->SetRange(0, range);
                                scrollBar->SetProportion(
                                        (height + 1) / (dataHeight + 1));
                                scrollBar->SetSteps(fFontInfo.lineHeight, height + 1);
                        } else {
                                scrollBar->SetRange(0, 0);
                                scrollBar->SetProportion(1);
                        }
                }
        }

        virtual BSize MinSize()
        {
                return BSize(10, 10);
        }

        virtual BSize MaxSize()
        {
                return BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED);
        }

        BSize PreferredSize()
        {
                BSize threadsViewSize(fThreadsView->PreferredSize());
                BSize schedulingViewSize(fSchedulingView->PreferredSize());
                return BSize(BLayoutUtils::AddDistances(
                                threadsViewSize.width + kViewSeparationMargin,
                                schedulingViewSize.width),
                        std::max(threadsViewSize.height, schedulingViewSize.height));
        }

        void DoLayout()
        {
                float width = Bounds().Width();
                float height = fThreadsView->MinSize().Height();

                float threadsViewWidth = 0;
                if (fHeaderView->Model()->HeaderAt(0) != NULL)
                        threadsViewWidth = fHeaderView->HeaderFrame(0).Width();
                threadsViewWidth = std::max(threadsViewWidth,
                        fThreadsView->MinSize().width);
                threadsViewWidth = std::min(threadsViewWidth,
                        fThreadsView->MaxSize().width);

                float schedulingViewLeft = threadsViewWidth + 1 + kViewSeparationMargin;
                float schedulingViewWidth = width - schedulingViewLeft;

                fThreadsView->MoveTo(0, 0);
                fThreadsView->ResizeTo(threadsViewWidth, height);

                fSchedulingView->MoveTo(schedulingViewLeft, 0);
                fSchedulingView->ResizeTo(schedulingViewWidth, height);

                if (Header* header = fHeaderView->Model()->HeaderAt(1)) {
                        float headerWidth = schedulingViewWidth + 1 + kViewSeparationMargin;
                        header->SetMinWidth(headerWidth);
                        header->SetMaxWidth(headerWidth);
                        header->SetPreferredWidth(headerWidth);
                        header->SetWidth(headerWidth);
                }

                UpdateScrollBars();
        }

private:
        virtual void HeaderWidthChanged(Header* header)
        {
                if (header->ModelIndex() == 0)
                        InvalidateLayout();
        }

        virtual void DataWidthChanged()
        {
        }

        virtual void DataRangeChanged()
        {
                Header* header = fHeaderView->Model()->HeaderAt(1);
                if (header == NULL)
                        return;

                nanotime_t startTime;
                nanotime_t endTime;
                fSchedulingView->GetDataRange(startTime, endTime);
                TimeRange* range = new(std::nothrow) TimeRange(startTime, endTime);
                if (range != NULL) {
                        header->SetValue(BVariant(range, 'time'));
                        range->ReleaseReference();
                }
        }

private:
        HeaderView*             fHeaderView;
        ThreadsView*    fThreadsView;
        SchedulingView* fSchedulingView;
};


MainWindow::SchedulingPage::SchedulingPage(MainWindow* parent)
        :
        BGroupView(B_VERTICAL),
        fParent(parent),
        fModel(NULL),
        fScrollView(NULL),
        fViewPort(NULL),
        fThreadsView(NULL),
        fSchedulingView(NULL),
        fFilterModel(),
        fSelectionModel()
{
        SetName("Scheduling");

        be_plain_font->GetHeight(&fFontInfo.fontHeight);
        fFontInfo.lineHeight = ceilf(fFontInfo.fontHeight.ascent)
                + ceilf(fFontInfo.fontHeight.descent);

        HeaderView* headerView = new HeaderView;
        BView* scrollChild = BLayoutBuilder::Group<>(B_VERTICAL)
                .Add(headerView)
                .Add(fViewPort = new ViewPort(headerView,
                        fThreadsView = new ThreadsView(fFontInfo, &fFilterModel,
                                &fSelectionModel),
                        fSchedulingView = new SchedulingView(fFontInfo, &fFilterModel,
                                &fSelectionModel),
                        fFontInfo, &fFilterModel)).View();
        ;
        scrollChild->SetFlags(scrollChild->Flags() | B_SCROLL_VIEW_AWARE);

        AddChild(fScrollView = new BScrollView("scroll", scrollChild, 0, true,
                true));

        fScrollView->ScrollBar(B_HORIZONTAL)->SetTarget(fSchedulingView);
        fScrollView->ScrollBar(B_VERTICAL)->SetTarget(fViewPort);
        fViewPort->UpdateScrollBars();
}


MainWindow::SchedulingPage::~SchedulingPage()
{
        fViewPort->RemoveListeners();
        fThreadsView->RemoveListeners();
        fSchedulingView->RemoveListeners();
}


void
MainWindow::SchedulingPage::SetModel(Model* model)
{
        if (model == fModel)
                return;

        fSelectionModel.Clear();
        fFilterModel.Clear();

        if (fModel != NULL) {
        }

        fModel = model;

        if (fModel != NULL) {
                fFilterModel.SelectItems(0, fModel->CountThreads(), false);
        }

        fViewPort->SetModel(fModel);
        fThreadsView->SetModel(fModel);
        fSchedulingView->SetModel(fModel);
}