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