1/*
2 * Copyright 2008-2009 Haiku Inc. All rights reserved.
3 * Distributed under the terms of the MIT license.
4 *
5 * Authors:
6 *		Pieter Panman
7 */
8
9
10#include <Application.h>
11#include <Catalog.h>
12#include <LayoutBuilder.h>
13#include <MenuBar.h>
14#include <ScrollView.h>
15#include <String.h>
16
17#include <iostream>
18
19#include "DevicesView.h"
20
21#undef B_TRANSLATION_CONTEXT
22#define B_TRANSLATION_CONTEXT "DevicesView"
23
24DevicesView::DevicesView()
25	:
26	BView("DevicesView", B_WILL_DRAW | B_FRAME_EVENTS)
27{
28	CreateLayout();
29	RescanDevices();
30	RebuildDevicesOutline();
31}
32
33
34void
35DevicesView::CreateLayout()
36{
37	BMenuBar* menuBar = new BMenuBar("menu");
38	BMenu* menu = new BMenu(B_TRANSLATE("Devices"));
39	BMenuItem* item;
40	menu->AddItem(new BMenuItem(B_TRANSLATE("Refresh devices"),
41		new BMessage(kMsgRefresh), 'R'));
42	menu->AddSeparatorItem();
43	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Report compatibility"),
44		new BMessage(kMsgReportCompatibility)));
45	item->SetEnabled(false);
46	menu->AddItem(item = new BMenuItem(B_TRANSLATE("Generate system information"),
47		new BMessage(kMsgGenerateSysInfo)));
48	item->SetEnabled(false);
49	menu->AddSeparatorItem();
50	menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
51		new BMessage(B_QUIT_REQUESTED), 'Q'));
52	menu->SetTargetForItems(this);
53	item->SetTarget(be_app);
54	menuBar->AddItem(menu);
55
56	fDevicesOutline = new BOutlineListView("devices_list");
57	fDevicesOutline->SetTarget(this);
58	fDevicesOutline->SetSelectionMessage(new BMessage(kMsgSelectionChanged));
59
60	BScrollView *scrollView = new BScrollView("devicesScrollView",
61		fDevicesOutline, B_WILL_DRAW | B_FRAME_EVENTS, true, true);
62	// Horizontal scrollbar doesn't behave properly like the vertical
63	// scrollbar... If you make the view bigger (exposing a larger percentage
64	// of the view), it does not adjust the width of the scroll 'dragger'
65	// why? Bug? In scrollview or in outlinelistview?
66
67	BPopUpMenu* orderByPopupMenu = new BPopUpMenu("orderByMenu");
68	BMenuItem* byCategory = new BMenuItem(B_TRANSLATE("Category"),
69		new BMessage(kMsgOrderCategory));
70	BMenuItem* byConnection = new BMenuItem(B_TRANSLATE("Connection"),
71		new BMessage(kMsgOrderConnection));
72	byCategory->SetMarked(true);
73	fOrderBy = byCategory->IsMarked() ? ORDER_BY_CATEGORY : ORDER_BY_CONNECTION;
74	orderByPopupMenu->AddItem(byCategory);
75	orderByPopupMenu->AddItem(byConnection);
76	fOrderByMenu = new BMenuField(B_TRANSLATE("Order by:"), orderByPopupMenu);
77	fAttributesView = new PropertyList("attributesView");
78
79	BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
80		.Add(menuBar)
81		.AddSplit(B_HORIZONTAL)
82			.SetInsets(B_USE_WINDOW_SPACING)
83			.AddGroup(B_VERTICAL)
84				.Add(fOrderByMenu, 1)
85				.Add(scrollView, 2)
86				.End()
87			.Add(fAttributesView, 2);
88}
89
90
91void
92DevicesView::RescanDevices()
93{
94	// Empty the outline and delete the devices in the list, incl. categories
95	fDevicesOutline->MakeEmpty();
96	DeleteDevices();
97	DeleteCategoryMap();
98
99	// Fill the devices list
100	status_t error;
101	device_node_cookie rootCookie;
102	if ((error = init_dm_wrapper()) < 0) {
103		std::cerr << "Error initializing device manager: " << strerror(error)
104			<< std::endl;
105		return;
106	}
107
108	get_root(&rootCookie);
109	AddDeviceAndChildren(&rootCookie, NULL);
110
111	uninit_dm_wrapper();
112
113	CreateCategoryMap();
114}
115
116
117void
118DevicesView::DeleteDevices()
119{
120	while (fDevices.size() > 0) {
121		delete fDevices.back();
122		fDevices.pop_back();
123	}
124}
125
126
127void
128DevicesView::CreateCategoryMap()
129{
130	CategoryMapIterator iter;
131	for (unsigned int i = 0; i < fDevices.size(); i++) {
132		Category category = fDevices[i]->GetCategory();
133		if (category < 0 || category >= kCategoryStringLength) {
134			std::cerr << "CreateCategoryMap: device " << fDevices[i]->GetName()
135				<< " returned an unknown category index (" << category << "). "
136				<< "Skipping device." << std::endl;
137			continue;
138		}
139
140		const char* categoryName = kCategoryString[category];
141
142		iter = fCategoryMap.find(category);
143		if (iter == fCategoryMap.end()) {
144			// This category has not yet been added, add it.
145			fCategoryMap[category] = new Device(NULL, BUS_NONE, CAT_NONE, categoryName);
146		}
147	}
148}
149
150
151void
152DevicesView::DeleteCategoryMap()
153{
154	CategoryMapIterator iter;
155	for (iter = fCategoryMap.begin(); iter != fCategoryMap.end(); iter++) {
156		delete iter->second;
157	}
158	fCategoryMap.clear();
159}
160
161
162int
163DevicesView::SortItemsCompare(const BListItem *item1, const BListItem *item2)
164{
165	const BStringItem* stringItem1 = dynamic_cast<const BStringItem*>(item1);
166	const BStringItem* stringItem2 = dynamic_cast<const BStringItem*>(item2);
167	if (!(stringItem1 && stringItem2)) {
168		// is this check necessary?
169		std::cerr << "Could not cast BListItem to BStringItem, file a bug\n";
170		return 0;
171	}
172	return Compare(stringItem1->Text(), stringItem2->Text());
173}
174
175
176void
177DevicesView::RebuildDevicesOutline()
178{
179	// Rearranges existing Devices into the proper hierarchy
180	fDevicesOutline->MakeEmpty();
181
182	if (fOrderBy == ORDER_BY_CONNECTION) {
183		for (unsigned int i = 0; i < fDevices.size(); i++) {
184			if (fDevices[i]->GetPhysicalParent() == NULL) {
185				// process each parent device and its children
186				fDevicesOutline->AddItem(fDevices[i]);
187				AddChildrenToOutlineByConnection(fDevices[i]);
188			}
189		}
190	} else if (fOrderBy == ORDER_BY_CATEGORY) {
191		// Add all categories to the outline
192		CategoryMapIterator iter;
193		for (iter = fCategoryMap.begin(); iter != fCategoryMap.end(); iter++) {
194			fDevicesOutline->AddItem(iter->second);
195		}
196
197		// Add all devices under the categories
198		for (unsigned int i = 0; i < fDevices.size(); i++) {
199			Category category = fDevices[i]->GetCategory();
200
201			iter = fCategoryMap.find(category);
202			if (iter == fCategoryMap.end()) {
203				std::cerr
204					<< "Tried to add device without category, file a bug\n";
205				continue;
206			} else {
207				fDevicesOutline->AddUnder(fDevices[i], iter->second);
208			}
209		}
210		fDevicesOutline->SortItemsUnder(NULL, true, SortItemsCompare);
211	}
212	// TODO: Implement BY_BUS
213}
214
215
216void
217DevicesView::AddChildrenToOutlineByConnection(Device* parent)
218{
219	for (unsigned int i = 0; i < fDevices.size(); i++) {
220		if (fDevices[i]->GetPhysicalParent() == parent) {
221			fDevicesOutline->AddUnder(fDevices[i], parent);
222			AddChildrenToOutlineByConnection(fDevices[i]);
223		}
224	}
225}
226
227
228void
229DevicesView::AddDeviceAndChildren(device_node_cookie *node, Device* parent)
230{
231	Attributes attributes;
232	Device* newDevice = NULL;
233
234	// Copy all its attributes,
235	// necessary because we can only request them once from the device manager
236	char data[256];
237	struct device_attr_info attr;
238	attr.cookie = 0;
239	attr.node_cookie = *node;
240	attr.value.raw.data = data;
241	attr.value.raw.length = sizeof(data);
242
243	while (dm_get_next_attr(&attr) == B_OK) {
244		BString attrString;
245		switch (attr.type) {
246			case B_STRING_TYPE:
247				attrString << attr.value.string;
248				break;
249			case B_UINT8_TYPE:
250				attrString << attr.value.ui8;
251				break;
252			case B_UINT16_TYPE:
253				attrString << attr.value.ui16;
254				break;
255			case B_UINT32_TYPE:
256				attrString << attr.value.ui32;
257				break;
258			case B_UINT64_TYPE:
259				attrString << attr.value.ui64;
260				break;
261			default:
262				attrString << "Raw data";
263		}
264		attributes.push_back(Attribute(attr.name, attrString));
265	}
266
267	// Determine what type of device it is and create it
268	for (unsigned int i = 0; i < attributes.size(); i++) {
269		// Devices Root
270		if (attributes[i].fName == B_DEVICE_PRETTY_NAME
271			&& attributes[i].fValue == "Devices Root") {
272			newDevice = new Device(parent, BUS_NONE,
273				CAT_COMPUTER, B_TRANSLATE("Computer"));
274			break;
275		}
276
277		// ACPI Controller
278		if (attributes[i].fName == B_DEVICE_PRETTY_NAME
279			&& attributes[i].fValue == "ACPI") {
280			newDevice = new Device(parent, BUS_ACPI,
281				CAT_BUS, B_TRANSLATE("ACPI bus"));
282			break;
283		}
284
285		// PCI bus
286		if (attributes[i].fName == B_DEVICE_PRETTY_NAME
287			&& attributes[i].fValue == "PCI") {
288			newDevice = new Device(parent, BUS_PCI,
289				CAT_BUS, B_TRANSLATE("PCI bus"));
290			break;
291		}
292
293		// ISA bus
294		if (attributes[i].fName == B_DEVICE_BUS
295			&& attributes[i].fValue == "isa") {
296			newDevice = new Device(parent, BUS_ISA,
297				CAT_BUS, B_TRANSLATE("ISA bus"));
298			break;
299		}
300
301		// USB bus
302		if (attributes[i].fName == B_DEVICE_PRETTY_NAME
303			&& attributes[i].fValue == "USB") {
304			newDevice = new Device(parent, BUS_USB,
305				CAT_BUS, B_TRANSLATE("USB bus"));
306			break;
307		}
308
309		// PCI device
310		if (attributes[i].fName == B_DEVICE_BUS
311			&& attributes[i].fValue == "pci") {
312			newDevice = new DevicePCI(parent);
313			break;
314		}
315
316		// ACPI device
317		if (attributes[i].fName == B_DEVICE_BUS
318			&& attributes[i].fValue == "acpi") {
319			newDevice = new DeviceACPI(parent);
320			break;
321		}
322
323		// USB device
324		if (attributes[i].fName == B_DEVICE_BUS
325			&& attributes[i].fValue == "usb") {
326			newDevice = new DeviceUSB(parent);
327			break;
328		}
329
330		// ATA / SCSI / IDE controller
331		if (attributes[i].fName == "controller_name") {
332			newDevice = new Device(parent, BUS_PCI,
333				CAT_MASS, attributes[i].fValue);
334		}
335
336		// SCSI device node
337		if (attributes[i].fName == B_DEVICE_BUS
338			&& attributes[i].fValue == "scsi") {
339			newDevice = new DeviceSCSI(parent);
340			break;
341		}
342
343		// Last resort, lets look for a pretty name
344		if (attributes[i].fName == B_DEVICE_PRETTY_NAME) {
345			newDevice = new Device(parent, BUS_NONE,
346				CAT_NONE, attributes[i].fValue);
347			break;
348		}
349	}
350
351	// A completely unknown device
352	if (newDevice == NULL) {
353		newDevice = new Device(parent, BUS_NONE,
354			CAT_NONE, B_TRANSLATE("Unknown device"));
355	}
356
357	// Add its attributes to the device, initialize it and add to the list.
358	for (unsigned int i = 0; i < attributes.size(); i++) {
359		newDevice->SetAttribute(attributes[i].fName, attributes[i].fValue);
360	}
361	newDevice->InitFromAttributes();
362	fDevices.push_back(newDevice);
363
364	// Process children
365	status_t err;
366	device_node_cookie child = *node;
367
368	if (get_child(&child) != B_OK)
369		return;
370
371	do {
372		AddDeviceAndChildren(&child, newDevice);
373	} while ((err = get_next_child(&child)) == B_OK);
374}
375
376
377DevicesView::~DevicesView()
378{
379	DeleteDevices();
380}
381
382
383void
384DevicesView::MessageReceived(BMessage *msg)
385{
386	switch (msg->what) {
387		case kMsgSelectionChanged:
388		{
389			int32 selected = fDevicesOutline->CurrentSelection(0);
390			if (selected >= 0) {
391				Device* device = (Device*)fDevicesOutline->ItemAt(selected);
392				fAttributesView->AddAttributes(device->GetAllAttributes());
393				fAttributesView->Invalidate();
394			}
395			break;
396		}
397
398		case kMsgOrderCategory:
399		{
400			fOrderBy = ORDER_BY_CATEGORY;
401			RescanDevices();
402			RebuildDevicesOutline();
403			break;
404		}
405
406		case kMsgOrderConnection:
407		{
408			fOrderBy = ORDER_BY_CONNECTION;
409			RescanDevices();
410			RebuildDevicesOutline();
411			break;
412		}
413
414		case kMsgRefresh:
415		{
416			fAttributesView->RemoveAll();
417			RescanDevices();
418			RebuildDevicesOutline();
419			break;
420		}
421
422		case kMsgReportCompatibility:
423		{
424			// To be implemented...
425			break;
426		}
427
428		case kMsgGenerateSysInfo:
429		{
430			// To be implemented...
431			break;
432		}
433
434		default:
435			BView::MessageReceived(msg);
436			break;
437	}
438}
439