1/*
2 * Copyright 2009-2010, Philippe Houdoin, phoudoin@haiku-os.org. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7#include <new>
8
9#include <stdio.h>
10#include <string.h>
11
12#include <AppMisc.h>
13#include <Bitmap.h>
14#include <ColumnTypes.h>
15#include <FindDirectory.h>
16#include <MimeType.h>
17#include <MessageRunner.h>
18#include <NodeInfo.h>
19#include <Path.h>
20#include <Roster.h>
21#include <String.h>
22
23#include "TeamsListView.h"
24
25
26// #pragma mark - BitmapStringField
27
28
29BBitmapStringField::BBitmapStringField(BBitmap* bitmap, const char* string)
30	:
31	Inherited(string),
32	fBitmap(bitmap)
33{
34}
35
36
37BBitmapStringField::~BBitmapStringField()
38{
39	delete fBitmap;
40}
41
42
43void
44BBitmapStringField::SetBitmap(BBitmap* bitmap)
45{
46	delete fBitmap;
47	fBitmap = bitmap;
48	// TODO: cause a redraw?
49}
50
51
52// #pragma mark - TeamsColumn
53
54
55float TeamsColumn::sTextMargin = 0.0;
56
57
58TeamsColumn::TeamsColumn(const char* title, float width, float minWidth,
59		float maxWidth, uint32 truncateMode, alignment align)
60	:
61	Inherited(title, width, minWidth, maxWidth, align),
62	fTruncateMode(truncateMode)
63{
64	SetWantsEvents(true);
65}
66
67
68void
69TeamsColumn::DrawField(BField* field, BRect rect, BView* parent)
70{
71	BBitmapStringField* bitmapField
72		= dynamic_cast<BBitmapStringField*>(field);
73	BStringField* stringField = dynamic_cast<BStringField*>(field);
74
75	if (bitmapField) {
76		const BBitmap* bitmap = bitmapField->Bitmap();
77
78		// figure out the placement
79		float x = 0.0;
80		BRect r = bitmap ? bitmap->Bounds() : BRect(0, 0, 15, 15);
81		float y = rect.top + ((rect.Height() - r.Height()) / 2);
82		float width = 0.0;
83
84		switch (Alignment()) {
85			default:
86			case B_ALIGN_LEFT:
87			case B_ALIGN_CENTER:
88				x = rect.left + sTextMargin;
89				width = rect.right - (x + r.Width()) - (2 * sTextMargin);
90				r.Set(x + r.Width(), rect.top, rect.right - width, rect.bottom);
91				break;
92
93			case B_ALIGN_RIGHT:
94				x = rect.right - sTextMargin - r.Width();
95				width = (x - rect.left - (2 * sTextMargin));
96				r.Set(rect.left, rect.top, rect.left + width, rect.bottom);
97				break;
98		}
99
100		if (width != bitmapField->Width()) {
101			BString truncatedString(bitmapField->String());
102			parent->TruncateString(&truncatedString, fTruncateMode, width + 2);
103			bitmapField->SetClippedString(truncatedString.String());
104			bitmapField->SetWidth(width);
105		}
106
107		// draw the bitmap
108		if (bitmap) {
109			parent->SetDrawingMode(B_OP_ALPHA);
110			parent->DrawBitmap(bitmap, BPoint(x, y));
111			parent->SetDrawingMode(B_OP_OVER);
112		}
113
114		// draw the string
115		DrawString(bitmapField->ClippedString(), parent, r);
116
117	} else if (stringField) {
118
119		float width = rect.Width() - (2 * sTextMargin);
120
121		if (width != stringField->Width()) {
122			BString truncatedString(stringField->String());
123
124			parent->TruncateString(&truncatedString, fTruncateMode, width + 2);
125			stringField->SetClippedString(truncatedString.String());
126			stringField->SetWidth(width);
127		}
128
129		DrawString(stringField->ClippedString(), parent, rect);
130	}
131}
132
133
134float
135TeamsColumn::GetPreferredWidth(BField *_field, BView* parent) const
136{
137	BBitmapStringField* bitmapField
138		= dynamic_cast<BBitmapStringField*>(_field);
139	BStringField* stringField = dynamic_cast<BStringField*>(_field);
140
141	float parentWidth = Inherited::GetPreferredWidth(_field, parent);
142	float width = 0.0;
143
144	if (bitmapField) {
145		const BBitmap* bitmap = bitmapField->Bitmap();
146		BFont font;
147		parent->GetFont(&font);
148		width = font.StringWidth(bitmapField->String()) + 3 * sTextMargin;
149		if (bitmap)
150			width += bitmap->Bounds().Width();
151		else
152			width += 16;
153	} else if (stringField) {
154		BFont font;
155		parent->GetFont(&font);
156		width = font.StringWidth(stringField->String()) + 2 * sTextMargin;
157	}
158	return max_c(width, parentWidth);
159}
160
161
162bool
163TeamsColumn::AcceptsField(const BField* field) const
164{
165	return dynamic_cast<const BStringField*>(field) != NULL;
166}
167
168
169void
170TeamsColumn::InitTextMargin(BView* parent)
171{
172	BFont font;
173	parent->GetFont(&font);
174	sTextMargin = ceilf(font.Size() * 0.8);
175}
176
177
178// #pragma mark - TeamRow
179
180
181enum {
182	kNameColumn,
183	kIDColumn,
184	kThreadCountColumn,
185};
186
187
188TeamRow::TeamRow(team_info& info)
189	: BRow(20.0)
190{
191	_SetTo(info);
192}
193
194
195TeamRow::TeamRow(team_id team)
196	: BRow(20.0)
197{
198	team_info info;
199	get_team_info(team, &info);
200	_SetTo(info);
201}
202
203
204bool
205TeamRow::NeedsUpdate(team_info& info)
206{
207	// Check if we need to rebuilt the row's fields because the team critical
208	// info (basically, app image running under that team ID) has changed
209
210	if (info.argc != fTeamInfo.argc
211		|| strncmp(info.args, fTeamInfo.args, sizeof(fTeamInfo.args)) != 0) {
212		_SetTo(info);
213		return true;
214	}
215
216	return false;
217}
218
219
220status_t
221TeamRow::_SetTo(team_info& info)
222{
223	team_info teamInfo = fTeamInfo = info;
224
225	// strip any trailing space(s)...
226	for (int len = strlen(teamInfo.args) - 1;
227			len >= 0 && teamInfo.args[len] == ' '; len--) {
228		teamInfo.args[len] = 0;
229	}
230
231	app_info appInfo;
232	status_t status = be_roster->GetRunningAppInfo(teamInfo.team, &appInfo);
233	if (status != B_OK) {
234		// Not an application known to be_roster
235
236		if (teamInfo.team == B_SYSTEM_TEAM) {
237			// Get icon and name from kernel image
238			system_info	systemInfo;
239			get_system_info(&systemInfo);
240
241			BPath kernelPath;
242			find_directory(B_BEOS_SYSTEM_DIRECTORY, &kernelPath);
243			kernelPath.Append(systemInfo.kernel_name);
244
245			get_ref_for_path(kernelPath.Path(), &appInfo.ref);
246
247		} else
248			BPrivate::get_app_ref(teamInfo.team, &appInfo.ref);
249	}
250
251	BBitmap* icon = new BBitmap(BRect(0, 0, B_MINI_ICON - 1, B_MINI_ICON - 1), B_RGBA32);
252
253	status = BNodeInfo::GetTrackerIcon(&appInfo.ref, icon, B_MINI_ICON);
254	if (status != B_OK) {
255			BMimeType genericAppType(B_APP_MIME_TYPE);
256			status = genericAppType.GetIcon(icon, B_MINI_ICON);
257	}
258
259	if (status != B_OK) {
260		delete icon;
261		icon = NULL;
262	}
263
264	BString tmp;
265	tmp << teamInfo.team;
266
267	SetField(new BBitmapStringField(icon, teamInfo.args), kNameColumn);
268	SetField(new BStringField(tmp), kIDColumn);
269
270	tmp = "";
271	tmp << teamInfo.thread_count;
272
273	SetField(new BStringField(tmp), kThreadCountColumn);
274
275	return status;
276}
277
278
279//	#pragma mark - TeamsListView
280
281
282TeamsListView::TeamsListView(BRect frame, const char* name)
283	:
284	Inherited(frame, name, B_FOLLOW_ALL, 0, B_NO_BORDER, true),
285	fUpdateRunner(NULL)
286{
287	AddColumn(new TeamsColumn("Name", 400, 100, 600,
288		B_TRUNCATE_BEGINNING), kNameColumn);
289	AddColumn(new TeamsColumn("ID", 80, 40, 100,
290		B_TRUNCATE_MIDDLE, B_ALIGN_RIGHT), kIDColumn);
291
292/*
293	AddColumn(new TeamsColumn("Thread count", 100, 50, 500,
294		B_TRUNCATE_MIDDLE, B_ALIGN_RIGHT), kThreadCountColumn);
295*/
296	SetSortingEnabled(false);
297
298	team_info tmi;
299	get_team_info(B_CURRENT_TEAM, &tmi);
300	fThisTeam = tmi.team;
301/*
302#ifdef __HAIKU__
303	SetFlags(Flags() | B_SUBPIXEL_PRECISE);
304#endif
305*/
306}
307
308
309TeamsListView::~TeamsListView()
310{
311	delete fUpdateRunner;
312}
313
314
315void
316TeamsListView::AttachedToWindow()
317{
318	Inherited::AttachedToWindow();
319	TeamsColumn::InitTextMargin(ScrollView());
320
321	_InitList();
322
323	be_roster->StartWatching(this, B_REQUEST_LAUNCHED | B_REQUEST_QUIT);
324
325	BMessage msg(kMsgUpdateTeamsList);
326	fUpdateRunner = new BMessageRunner(this, &msg, 100000L);	// 10Hz
327}
328
329
330void
331TeamsListView::DetachedFromWindow()
332{
333	Inherited::DetachedFromWindow();
334
335	be_roster->StopWatching(this);
336
337	delete fUpdateRunner;
338	fUpdateRunner = NULL;
339
340	Clear(); // MakeEmpty();
341}
342
343
344void
345TeamsListView::MessageReceived(BMessage* message)
346{
347	switch (message->what) {
348		case kMsgUpdateTeamsList:
349			_UpdateList();
350			break;
351
352		case B_SOME_APP_LAUNCHED:
353		{
354			team_id	team;
355			if (message->FindInt32("be:team", &team) != B_OK)
356				break;
357
358			TeamRow* row = new(std::nothrow) TeamRow(team);
359			if (row != NULL) {
360				AddRow(row);
361				/*else
362					SortItems(&TeamListItem::Compare);
363				*/
364			}
365			break;
366		}
367
368		case B_SOME_APP_QUIT:
369		{
370			team_id	team;
371			if (message->FindInt32("be:team", &team) != B_OK)
372				break;
373
374			TeamRow* row = FindTeamRow(team);
375			if (row != NULL) {
376				RemoveRow(row);
377				delete row;
378			}
379			break;
380		}
381
382		default:
383			Inherited::MessageReceived(message);
384	}
385}
386
387
388TeamRow*
389TeamsListView::FindTeamRow(team_id teamId)
390{
391	for (int32 i = CountRows(); i-- > 0;) {
392		TeamRow* row = dynamic_cast<TeamRow*>(RowAt(i));
393		if (row == NULL)
394			continue;
395
396		if (row->TeamID() == teamId)
397			return row;
398	}
399
400	return NULL;
401}
402
403
404void
405TeamsListView::_InitList()
406{
407	int32 tmi_cookie = 0;
408	team_info tmi;
409
410	while (get_next_team_info(&tmi_cookie, &tmi) == B_OK) {
411		TeamRow* row = new(std::nothrow)  TeamRow(tmi);
412		if (row == NULL) {
413			// Memory issue. Bail out.
414			break;
415		}
416
417		if (tmi.team == B_SYSTEM_TEAM ||
418			tmi.team == fThisTeam) {
419			// We don't support debugging kernel and... ourself!
420			row->SetEnabled(false);
421		}
422
423		AddRow(row);
424	}
425}
426
427
428void
429TeamsListView::_UpdateList()
430{
431	int32 tmi_cookie = 0;
432	team_info tmi;
433	TeamRow* row;
434	int32 index = 0;
435
436	// NOTA: assuming get_next_team_info() returns teams ordered by team ID...
437	while (get_next_team_info(&tmi_cookie, &tmi) == B_OK) {
438
439		row = dynamic_cast<TeamRow*>(RowAt(index));
440		while (row && tmi.team > row->TeamID()) {
441				RemoveRow(row);
442				delete row;
443				row = dynamic_cast<TeamRow*>(RowAt(index));
444		}
445
446		if (row != NULL && tmi.team == row->TeamID()
447			&& row->NeedsUpdate(tmi)) {
448			// The team image app could have change due after an exec*() call,
449			UpdateRow(row);
450		} else if (row == NULL || tmi.team != row->TeamID()) {
451			// Team not found in previously known teams list: insert a new row
452			TeamRow* newRow = new(std::nothrow) TeamRow(tmi);
453			if (newRow != NULL) {
454				if (row == NULL) {
455					// No row found with bigger team id: append at list end
456					AddRow(newRow);
457				} else
458					AddRow(newRow, index);
459			}
460		}
461		index++;	// Move list sync head.
462	}
463
464	// Remove tail list rows, if we don't walk list thru the end
465	while ((row = dynamic_cast<TeamRow*>(RowAt(index))) != NULL) {
466		RemoveRow(row);
467		delete row;
468		row = dynamic_cast<TeamRow*>(RowAt(++index));
469	}
470}
471