xref: /haiku/src/apps/devices/DevicesView.cpp (revision ed24eb5ff12640d052171c6a7feba37fab8a75d1)
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 
24 DevicesView::DevicesView()
25 	:
26 	BView("DevicesView", B_WILL_DRAW | B_FRAME_EVENTS)
27 {
28 	CreateLayout();
29 	RescanDevices();
30 	RebuildDevicesOutline();
31 }
32 
33 
34 void
35 DevicesView::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 
78 	fTabView = new BTabView("fTabView", B_WIDTH_FROM_LABEL);
79 	fDeviceDetailsTab = new BTab();
80 	fAttributesView = new PropertyList("attributesView");
81 	fTabView->AddTab(fAttributesView, fDeviceDetailsTab);
82 	fDeviceDetailsTab->SetLabel(B_TRANSLATE("<No device selected>"));
83 
84 	BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
85 		.Add(menuBar)
86 		.AddSplit(B_HORIZONTAL)
87 			.SetInsets(B_USE_WINDOW_SPACING)
88 			.AddGroup(B_VERTICAL)
89 				.Add(fOrderByMenu, 1)
90 				.Add(scrollView, 2)
91 				.End()
92 			.Add(fTabView, 2);
93 }
94 
95 
96 void
97 DevicesView::RescanDevices()
98 {
99 	// Empty the outline and delete the devices in the list, incl. categories
100 	fDevicesOutline->MakeEmpty();
101 	DeleteDevices();
102 	DeleteCategoryMap();
103 
104 	// Fill the devices list
105 	status_t error;
106 	device_node_cookie rootCookie;
107 	if ((error = init_dm_wrapper()) < 0) {
108 		std::cerr << "Error initializing device manager: " << strerror(error)
109 			<< std::endl;
110 		return;
111 	}
112 
113 	get_root(&rootCookie);
114 	AddDeviceAndChildren(&rootCookie, NULL);
115 
116 	uninit_dm_wrapper();
117 
118 	CreateCategoryMap();
119 }
120 
121 
122 void
123 DevicesView::DeleteDevices()
124 {
125 	while (fDevices.size() > 0) {
126 		delete fDevices.back();
127 		fDevices.pop_back();
128 	}
129 }
130 
131 
132 void
133 DevicesView::CreateCategoryMap()
134 {
135 	CategoryMapIterator iter;
136 	for (unsigned int i = 0; i < fDevices.size(); i++) {
137 		Category category = fDevices[i]->GetCategory();
138 		if (category < 0 || category >= kCategoryStringLength) {
139 			std::cerr << "CreateCategoryMap: device " << fDevices[i]->GetName()
140 				<< " returned an unknown category index (" << category << "). "
141 				<< "Skipping device." << std::endl;
142 			continue;
143 		}
144 
145 		const char* categoryName = kCategoryString[category];
146 
147 		iter = fCategoryMap.find(category);
148 		if (iter == fCategoryMap.end()) {
149 			// This category has not yet been added, add it.
150 			fCategoryMap[category] = new Device(NULL, BUS_NONE, CAT_NONE, categoryName);
151 		}
152 	}
153 }
154 
155 
156 void
157 DevicesView::DeleteCategoryMap()
158 {
159 	CategoryMapIterator iter;
160 	for (iter = fCategoryMap.begin(); iter != fCategoryMap.end(); iter++) {
161 		delete iter->second;
162 	}
163 	fCategoryMap.clear();
164 }
165 
166 
167 int
168 DevicesView::SortItemsCompare(const BListItem *item1, const BListItem *item2)
169 {
170 	const BStringItem* stringItem1 = dynamic_cast<const BStringItem*>(item1);
171 	const BStringItem* stringItem2 = dynamic_cast<const BStringItem*>(item2);
172 	if (!(stringItem1 && stringItem2)) {
173 		// is this check necessary?
174 		std::cerr << "Could not cast BListItem to BStringItem, file a bug\n";
175 		return 0;
176 	}
177 	return Compare(stringItem1->Text(), stringItem2->Text());
178 }
179 
180 
181 void
182 DevicesView::RebuildDevicesOutline()
183 {
184 	// Rearranges existing Devices into the proper hierarchy
185 	fDevicesOutline->MakeEmpty();
186 
187 	if (fOrderBy == ORDER_BY_CONNECTION) {
188 		for (unsigned int i = 0; i < fDevices.size(); i++) {
189 			if (fDevices[i]->GetPhysicalParent() == NULL) {
190 				// process each parent device and its children
191 				fDevicesOutline->AddItem(fDevices[i]);
192 				AddChildrenToOutlineByConnection(fDevices[i]);
193 			}
194 		}
195 	} else if (fOrderBy == ORDER_BY_CATEGORY) {
196 		// Add all categories to the outline
197 		CategoryMapIterator iter;
198 		for (iter = fCategoryMap.begin(); iter != fCategoryMap.end(); iter++) {
199 			fDevicesOutline->AddItem(iter->second);
200 		}
201 
202 		// Add all devices under the categories
203 		for (unsigned int i = 0; i < fDevices.size(); i++) {
204 			Category category = fDevices[i]->GetCategory();
205 
206 			iter = fCategoryMap.find(category);
207 			if (iter == fCategoryMap.end()) {
208 				std::cerr
209 					<< "Tried to add device without category, file a bug\n";
210 				continue;
211 			} else {
212 				fDevicesOutline->AddUnder(fDevices[i], iter->second);
213 			}
214 		}
215 		fDevicesOutline->SortItemsUnder(NULL, true, SortItemsCompare);
216 	}
217 	// TODO: Implement BY_BUS
218 }
219 
220 
221 void
222 DevicesView::AddChildrenToOutlineByConnection(Device* parent)
223 {
224 	for (unsigned int i = 0; i < fDevices.size(); i++) {
225 		if (fDevices[i]->GetPhysicalParent() == parent) {
226 			fDevicesOutline->AddUnder(fDevices[i], parent);
227 			AddChildrenToOutlineByConnection(fDevices[i]);
228 		}
229 	}
230 }
231 
232 
233 void
234 DevicesView::AddDeviceAndChildren(device_node_cookie *node, Device* parent)
235 {
236 	Attributes attributes;
237 	Device* newDevice = NULL;
238 
239 	// Copy all its attributes,
240 	// necessary because we can only request them once from the device manager
241 	char data[256];
242 	struct device_attr_info attr;
243 	attr.cookie = 0;
244 	attr.node_cookie = *node;
245 	attr.value.raw.data = data;
246 	attr.value.raw.length = sizeof(data);
247 
248 	while (dm_get_next_attr(&attr) == B_OK) {
249 		BString attrString;
250 		switch (attr.type) {
251 			case B_STRING_TYPE:
252 				attrString << attr.value.string;
253 				break;
254 			case B_UINT8_TYPE:
255 				attrString << attr.value.ui8;
256 				break;
257 			case B_UINT16_TYPE:
258 				attrString << attr.value.ui16;
259 				break;
260 			case B_UINT32_TYPE:
261 				attrString << attr.value.ui32;
262 				break;
263 			case B_UINT64_TYPE:
264 				attrString << attr.value.ui64;
265 				break;
266 			default:
267 				attrString << "Raw data";
268 		}
269 		attributes.push_back(Attribute(attr.name, attrString));
270 	}
271 
272 	// Determine what type of device it is and create it
273 	for (unsigned int i = 0; i < attributes.size(); i++) {
274 		// Devices Root
275 		if (attributes[i].fName == B_DEVICE_PRETTY_NAME
276 			&& attributes[i].fValue == "Devices Root") {
277 			newDevice = new Device(parent, BUS_NONE,
278 				CAT_COMPUTER, B_TRANSLATE("Computer"));
279 			break;
280 		}
281 
282 		// ACPI Controller
283 		if (attributes[i].fName == B_DEVICE_PRETTY_NAME
284 			&& attributes[i].fValue == "ACPI") {
285 			newDevice = new Device(parent, BUS_ACPI,
286 				CAT_BUS, B_TRANSLATE("ACPI bus"));
287 			break;
288 		}
289 
290 		// PCI bus
291 		if (attributes[i].fName == B_DEVICE_PRETTY_NAME
292 			&& attributes[i].fValue == "PCI") {
293 			newDevice = new Device(parent, BUS_PCI,
294 				CAT_BUS, B_TRANSLATE("PCI bus"));
295 			break;
296 		}
297 
298 		// ISA bus
299 		if (attributes[i].fName == B_DEVICE_BUS
300 			&& attributes[i].fValue == "isa") {
301 			newDevice = new Device(parent, BUS_ISA,
302 				CAT_BUS, B_TRANSLATE("ISA bus"));
303 			break;
304 		}
305 
306 		// USB bus
307 		if (attributes[i].fName == B_DEVICE_PRETTY_NAME
308 			&& attributes[i].fValue == "USB") {
309 			newDevice = new Device(parent, BUS_USB,
310 				CAT_BUS, B_TRANSLATE("USB bus"));
311 			break;
312 		}
313 
314 		// PCI device
315 		if (attributes[i].fName == B_DEVICE_BUS
316 			&& attributes[i].fValue == "pci") {
317 			newDevice = new DevicePCI(parent);
318 			break;
319 		}
320 
321 		// ACPI device
322 		if (attributes[i].fName == B_DEVICE_BUS
323 			&& attributes[i].fValue == "acpi") {
324 			newDevice = new DeviceACPI(parent);
325 			break;
326 		}
327 
328 		// USB device
329 		if (attributes[i].fName == B_DEVICE_BUS
330 			&& attributes[i].fValue == "usb") {
331 			newDevice = new DeviceUSB(parent);
332 			break;
333 		}
334 
335 		// ATA / SCSI / IDE controller
336 		if (attributes[i].fName == "controller_name") {
337 			newDevice = new Device(parent, BUS_PCI,
338 				CAT_MASS, attributes[i].fValue);
339 		}
340 
341 		// SCSI device node
342 		if (attributes[i].fName == B_DEVICE_BUS
343 			&& attributes[i].fValue == "scsi") {
344 			newDevice = new DeviceSCSI(parent);
345 			break;
346 		}
347 
348 		// Last resort, lets look for a pretty name
349 		if (attributes[i].fName == B_DEVICE_PRETTY_NAME) {
350 			newDevice = new Device(parent, BUS_NONE,
351 				CAT_NONE, attributes[i].fValue);
352 			break;
353 		}
354 	}
355 
356 	// A completely unknown device
357 	if (newDevice == NULL) {
358 		newDevice = new Device(parent, BUS_NONE,
359 			CAT_NONE, B_TRANSLATE("Unknown device"));
360 	}
361 
362 	// Add its attributes to the device, initialize it and add to the list.
363 	for (unsigned int i = 0; i < attributes.size(); i++) {
364 		newDevice->SetAttribute(attributes[i].fName, attributes[i].fValue);
365 	}
366 	newDevice->InitFromAttributes();
367 	fDevices.push_back(newDevice);
368 
369 	// Process children
370 	status_t err;
371 	device_node_cookie child = *node;
372 
373 	if (get_child(&child) != B_OK)
374 		return;
375 
376 	do {
377 		AddDeviceAndChildren(&child, newDevice);
378 	} while ((err = get_next_child(&child)) == B_OK);
379 }
380 
381 
382 DevicesView::~DevicesView()
383 {
384 	DeleteDevices();
385 }
386 
387 
388 void
389 DevicesView::MessageReceived(BMessage *msg)
390 {
391 	switch (msg->what) {
392 		case kMsgSelectionChanged:
393 		{
394 			int32 selected = fDevicesOutline->CurrentSelection(0);
395 			if (selected >= 0) {
396 				Device* device = (Device*)fDevicesOutline->ItemAt(selected);
397 				fAttributesView->AddAttributes(device->GetAllAttributes());
398 				fDeviceDetailsTab->SetLabel(device->GetName());
399 				// hmm the label doesn't automatically refresh
400 				fTabView->Invalidate();
401 			}
402 			break;
403 		}
404 
405 		case kMsgOrderCategory:
406 		{
407 			fOrderBy = ORDER_BY_CATEGORY;
408 			RescanDevices();
409 			RebuildDevicesOutline();
410 			break;
411 		}
412 
413 		case kMsgOrderConnection:
414 		{
415 			fOrderBy = ORDER_BY_CONNECTION;
416 			RescanDevices();
417 			RebuildDevicesOutline();
418 			break;
419 		}
420 
421 		case kMsgRefresh:
422 		{
423 			fAttributesView->RemoveAll();
424 			fDeviceDetailsTab->SetLabel(B_TRANSLATE("<No device selected>"));
425 			RescanDevices();
426 			RebuildDevicesOutline();
427 			break;
428 		}
429 
430 		case kMsgReportCompatibility:
431 		{
432 			// To be implemented...
433 			break;
434 		}
435 
436 		case kMsgGenerateSysInfo:
437 		{
438 			// To be implemented...
439 			break;
440 		}
441 
442 		default:
443 			BView::MessageReceived(msg);
444 			break;
445 	}
446 }
447