root/src/apps/devices/DevicesView.cpp
/*
 * Copyright 2008-2009 Haiku Inc. All rights reserved.
 * Distributed under the terms of the MIT license.
 *
 * Authors:
 *              Pieter Panman
 */


#include <Application.h>
#include <Catalog.h>
#include <LayoutBuilder.h>
#include <MenuBar.h>
#include <ScrollView.h>
#include <String.h>

#include <iostream>

#include "DevicesView.h"

#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "DevicesView"

DevicesView::DevicesView()
        :
        BView("DevicesView", B_WILL_DRAW | B_FRAME_EVENTS)
{
        CreateLayout();
        RescanDevices();
        RebuildDevicesOutline();
}


void
DevicesView::CreateLayout()
{
        BMenuBar* menuBar = new BMenuBar("menu");
        BMenu* menu = new BMenu(B_TRANSLATE("Devices"));
        BMenuItem* item;
        menu->AddItem(new BMenuItem(B_TRANSLATE("Refresh devices"),
                new BMessage(kMsgRefresh), 'R'));
        menu->AddSeparatorItem();
        menu->AddItem(item = new BMenuItem(B_TRANSLATE("Report compatibility"),
                new BMessage(kMsgReportCompatibility)));
        item->SetEnabled(false);
        menu->AddItem(item = new BMenuItem(B_TRANSLATE("Generate system information"),
                new BMessage(kMsgGenerateSysInfo)));
        item->SetEnabled(false);
        menu->AddSeparatorItem();
        menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
                new BMessage(B_QUIT_REQUESTED), 'Q'));
        menu->SetTargetForItems(this);
        item->SetTarget(be_app);
        menuBar->AddItem(menu);

        fDevicesOutline = new BOutlineListView("devices_list");
        fDevicesOutline->SetTarget(this);
        fDevicesOutline->SetSelectionMessage(new BMessage(kMsgSelectionChanged));

        BScrollView *scrollView = new BScrollView("devicesScrollView",
                fDevicesOutline, B_WILL_DRAW | B_FRAME_EVENTS, true, true);
        // Horizontal scrollbar doesn't behave properly like the vertical
        // scrollbar... If you make the view bigger (exposing a larger percentage
        // of the view), it does not adjust the width of the scroll 'dragger'
        // why? Bug? In scrollview or in outlinelistview?

        BPopUpMenu* orderByPopupMenu = new BPopUpMenu("orderByMenu");
        BMenuItem* byBus = new BMenuItem(B_TRANSLATE("Bus"),
                new BMessage(kMsgOrderBus));
        BMenuItem* byCategory = new BMenuItem(B_TRANSLATE("Category"),
                new BMessage(kMsgOrderCategory));
        BMenuItem* byConnection = new BMenuItem(B_TRANSLATE("Connection"),
                new BMessage(kMsgOrderConnection));
        byCategory->SetMarked(true);
        fOrderBy = byCategory->IsMarked() ? ORDER_BY_CATEGORY : ORDER_BY_CONNECTION;
        orderByPopupMenu->AddItem(byBus);
        orderByPopupMenu->AddItem(byCategory);
        orderByPopupMenu->AddItem(byConnection);
        fOrderByMenu = new BMenuField(B_TRANSLATE("Order by:"), orderByPopupMenu);
        fAttributesView = new PropertyList("attributesView");

        BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
                .Add(menuBar)
                .AddSplit(B_HORIZONTAL)
                        .SetInsets(B_USE_WINDOW_SPACING)
                        .AddGroup(B_VERTICAL)
                                .Add(fOrderByMenu, 1)
                                .Add(scrollView, 2)
                                .End()
                        .Add(fAttributesView, 2);
}


void
DevicesView::RescanDevices()
{
        // Empty the outline and delete the devices in the list, incl. categories
        fDevicesOutline->MakeEmpty();
        DeleteDevices();
        DeleteCategoryMap();

        // Fill the devices list
        status_t error;
        device_node_cookie rootCookie;
        if ((error = init_dm_wrapper()) < 0) {
                std::cerr << "Error initializing device manager: " << strerror(error)
                        << std::endl;
                return;
        }

        get_root(&rootCookie);
        AddDeviceAndChildren(&rootCookie, NULL);

        uninit_dm_wrapper();

        CreateCategoryMap();
}


void
DevicesView::DeleteDevices()
{
        while (fDevices.size() > 0) {
                delete fDevices.back();
                fDevices.pop_back();
        }
}


void
DevicesView::CreateCategoryMap()
{
        CategoryMapIterator iter;
        for (unsigned int i = 0; i < fDevices.size(); i++) {
                Category category = fDevices[i]->GetCategory();
                if (category < 0 || category >= kCategoryStringLength) {
                        std::cerr << "CreateCategoryMap: device " << fDevices[i]->GetName()
                                << " returned an unknown category index (" << category << "). "
                                << "Skipping device." << std::endl;
                        continue;
                }

                const char* categoryName = kCategoryString[category];

                iter = fCategoryMap.find(category);
                if (iter == fCategoryMap.end()) {
                        // This category has not yet been added, add it.
                        fCategoryMap[category] = new Device(NULL, BUS_NONE, CAT_NONE, categoryName);
                }
        }
}


void
DevicesView::DeleteCategoryMap()
{
        CategoryMapIterator iter;
        for (iter = fCategoryMap.begin(); iter != fCategoryMap.end(); iter++) {
                delete iter->second;
        }
        fCategoryMap.clear();
}


int
DevicesView::SortItemsCompare(const BListItem *item1, const BListItem *item2)
{
        const BStringItem* stringItem1 = dynamic_cast<const BStringItem*>(item1);
        const BStringItem* stringItem2 = dynamic_cast<const BStringItem*>(item2);
        if (!(stringItem1 && stringItem2)) {
                // is this check necessary?
                std::cerr << "Could not cast BListItem to BStringItem, file a bug\n";
                return 0;
        }
        return Compare(stringItem1->Text(), stringItem2->Text());
}


void
DevicesView::RebuildDevicesOutline()
{
        // Rearranges existing Devices into the proper hierarchy
        fDevicesOutline->MakeEmpty();

        if (fOrderBy == ORDER_BY_BUS) {
                // add all bus controllers to the outline
                for (unsigned int i = 0; i < fDevices.size(); i++)
                        if (fDevices[i]->GetCategory() == CAT_BUS)
                                fDevicesOutline->AddItem(fDevices[i]);

                // attach devices to their bus
                for (unsigned int i = 0; i < fDevices.size(); i++) {
                        if (fDevices[i]->GetCategory() != CAT_BUS) {
                                Device* busParent = fDevices[i]->GetPhysicalParent();

                                while (busParent != NULL && busParent->GetCategory() != CAT_BUS) {
                                        busParent = busParent->GetPhysicalParent();
                                }

                                if (busParent != NULL)
                                        fDevicesOutline->AddUnder(fDevices[i], busParent);
                                else
                                        fDevicesOutline->AddItem(fDevices[i]);
                        }
                }
                fDevicesOutline->SortItemsUnder(NULL, true, SortItemsCompare);
        } else if (fOrderBy == ORDER_BY_CATEGORY) {
                // Add all categories to the outline
                CategoryMapIterator iter;
                for (iter = fCategoryMap.begin(); iter != fCategoryMap.end(); iter++) {
                        fDevicesOutline->AddItem(iter->second);
                }

                // Add all devices under the categories
                for (unsigned int i = 0; i < fDevices.size(); i++) {
                        Category category = fDevices[i]->GetCategory();

                        iter = fCategoryMap.find(category);
                        if (iter == fCategoryMap.end()) {
                                std::cerr
                                        << "Tried to add device without category, file a bug\n";
                                continue;
                        } else {
                                fDevicesOutline->AddUnder(fDevices[i], iter->second);
                        }
                }
                fDevicesOutline->SortItemsUnder(NULL, true, SortItemsCompare);
        } else if (fOrderBy == ORDER_BY_CONNECTION) {
                for (unsigned int i = 0; i < fDevices.size(); i++) {
                        if (fDevices[i]->GetPhysicalParent() == NULL) {
                                // process each parent device and its children
                                fDevicesOutline->AddItem(fDevices[i]);
                                AddChildrenToOutlineByConnection(fDevices[i]);
                        }
                }
        }
}


void
DevicesView::AddChildrenToOutlineByConnection(Device* parent)
{
        for (unsigned int i = 0; i < fDevices.size(); i++) {
                if (fDevices[i]->GetPhysicalParent() == parent) {
                        fDevicesOutline->AddUnder(fDevices[i], parent);
                        AddChildrenToOutlineByConnection(fDevices[i]);
                }
        }
}


void
DevicesView::AddDeviceAndChildren(device_node_cookie *node, Device* parent)
{
        Attributes attributes;
        Device* newDevice = NULL;

        // Copy all its attributes,
        // necessary because we can only request them once from the device manager
        char data[256];
        struct device_attr_info attr;
        attr.cookie = 0;
        attr.node_cookie = *node;
        attr.value.raw.data = data;
        attr.value.raw.length = sizeof(data);

        while (dm_get_next_attr(&attr) == B_OK) {
                BString attrString;
                switch (attr.type) {
                        case B_STRING_TYPE:
                                attrString << attr.value.string;
                                break;
                        case B_UINT8_TYPE:
                                attrString << attr.value.ui8;
                                break;
                        case B_UINT16_TYPE:
                                attrString << attr.value.ui16;
                                break;
                        case B_UINT32_TYPE:
                                attrString << attr.value.ui32;
                                break;
                        case B_UINT64_TYPE:
                                attrString << attr.value.ui64;
                                break;
                        default:
                                attrString << "Raw data";
                }
                attributes.push_back(Attribute(attr.name, attrString));
        }

        // Determine what type of device it is and create it
        for (unsigned int i = 0; i < attributes.size(); i++) {
                // Devices Root
                if (attributes[i].fName == B_DEVICE_PRETTY_NAME
                        && attributes[i].fValue == "Devices Root") {
                        newDevice = new Device(parent, BUS_NONE,
                                CAT_COMPUTER, B_TRANSLATE("Computer"));
                        break;
                }

                // ACPI Controller
                if (attributes[i].fName == B_DEVICE_PRETTY_NAME
                        && attributes[i].fValue == "ACPI") {
                        newDevice = new Device(parent, BUS_ACPI,
                                CAT_BUS, B_TRANSLATE("ACPI bus"));
                        break;
                }

                // PCI bus
                if (attributes[i].fName == B_DEVICE_PRETTY_NAME
                        && attributes[i].fValue == "PCI") {
                        newDevice = new Device(parent, BUS_PCI,
                                CAT_BUS, B_TRANSLATE("PCI bus"));
                        break;
                }

                // ISA bus
                if (attributes[i].fName == B_DEVICE_BUS
                        && attributes[i].fValue == "isa") {
                        newDevice = new Device(parent, BUS_ISA,
                                CAT_BUS, B_TRANSLATE("ISA bus"));
                        break;
                }

                // USB bus
                if (attributes[i].fName == B_DEVICE_PRETTY_NAME
                        && attributes[i].fValue == "USB") {
                        newDevice = new Device(parent, BUS_USB,
                                CAT_BUS, B_TRANSLATE("USB bus"));
                        break;
                }

                // PCI device
                if (attributes[i].fName == B_DEVICE_BUS
                        && attributes[i].fValue == "pci") {
                        newDevice = new DevicePCI(parent);
                        break;
                }

                // ACPI device
                if (attributes[i].fName == B_DEVICE_BUS
                        && attributes[i].fValue == "acpi") {
                        newDevice = new DeviceACPI(parent);
                        break;
                }

                // USB device
                if (attributes[i].fName == B_DEVICE_BUS
                        && attributes[i].fValue == "usb") {
                        newDevice = new DeviceUSB(parent);
                        break;
                }

                // ATA / SCSI / IDE controller
                if (attributes[i].fName == "controller_name") {
                        newDevice = new Device(parent, BUS_PCI,
                                CAT_MASS, attributes[i].fValue);
                }

                // SCSI device node
                if (attributes[i].fName == B_DEVICE_BUS
                        && attributes[i].fValue == "scsi") {
                        newDevice = new DeviceSCSI(parent);
                        break;
                }

                // Last resort, lets look for a pretty name
                if (attributes[i].fName == B_DEVICE_PRETTY_NAME) {
                        newDevice = new Device(parent, BUS_NONE,
                                CAT_NONE, attributes[i].fValue);
                        break;
                }
        }

        // A completely unknown device
        if (newDevice == NULL) {
                newDevice = new Device(parent, BUS_NONE,
                        CAT_NONE, B_TRANSLATE("Unknown device"));
        }

        // Add its attributes to the device, initialize it and add to the list.
        for (unsigned int i = 0; i < attributes.size(); i++) {
                newDevice->SetAttribute(attributes[i].fName, attributes[i].fValue);
        }
        newDevice->InitFromAttributes();
        fDevices.push_back(newDevice);

        // Process children
        status_t err;
        device_node_cookie child = *node;

        if (get_child(&child) != B_OK)
                return;

        do {
                AddDeviceAndChildren(&child, newDevice);
        } while ((err = get_next_child(&child)) == B_OK);
}


DevicesView::~DevicesView()
{
        DeleteDevices();
}


void
DevicesView::MessageReceived(BMessage *msg)
{
        switch (msg->what) {
                case kMsgSelectionChanged:
                {
                        int32 selected = fDevicesOutline->CurrentSelection(0);
                        if (selected >= 0) {
                                Device* device = (Device*)fDevicesOutline->ItemAt(selected);
                                fAttributesView->AddAttributes(device->GetAllAttributes());
                                fAttributesView->Invalidate();
                        }
                        break;
                }

                case kMsgOrderBus:
                {
                        fOrderBy = ORDER_BY_BUS;
                        RescanDevices();
                        RebuildDevicesOutline();
                        break;
                }

                case kMsgOrderCategory:
                {
                        fOrderBy = ORDER_BY_CATEGORY;
                        RescanDevices();
                        RebuildDevicesOutline();
                        break;
                }

                case kMsgOrderConnection:
                {
                        fOrderBy = ORDER_BY_CONNECTION;
                        RescanDevices();
                        RebuildDevicesOutline();
                        break;
                }

                case kMsgRefresh:
                {
                        fAttributesView->RemoveAll();
                        RescanDevices();
                        RebuildDevicesOutline();
                        break;
                }

                case kMsgReportCompatibility:
                {
                        // To be implemented...
                        break;
                }

                case kMsgGenerateSysInfo:
                {
                        // To be implemented...
                        break;
                }

                default:
                        BView::MessageReceived(msg);
                        break;
        }
}