1/*
2 * Copyright 2009-2010, Philippe Houdoin, phoudoin@haiku-os.org. All rights reserved.
3 * Copyright 2013-2018, Rene Gollent, rene@gollent.com.
4 * Distributed under the terms of the MIT License.
5 */
6
7#include "TeamsListView.h"
8
9#include <algorithm>
10#include <new>
11
12#include <stdio.h>
13#include <string.h>
14
15#include <AppMisc.h>
16#include <Bitmap.h>
17#include <ColumnTypes.h>
18#include <ControlLook.h>
19#include <FindDirectory.h>
20#include <MimeType.h>
21#include <MessageRunner.h>
22#include <NodeInfo.h>
23#include <Path.h>
24#include <Roster.h>
25#include <String.h>
26
27#include <AutoLocker.h>
28
29#include "TargetHostInterface.h"
30
31
32enum {
33	MSG_SELECTED_INTERFACE_CHANGED = 'seic',
34	MSG_TEAM_ADDED = 'tead',
35	MSG_TEAM_REMOVED = 'tere',
36	MSG_TEAM_RENAMED = 'tern'
37};
38
39
40// #pragma mark - BitmapStringField
41
42
43BBitmapStringField::BBitmapStringField(BBitmap* bitmap, const char* string)
44	:
45	Inherited(string),
46	fBitmap(bitmap)
47{
48}
49
50
51BBitmapStringField::~BBitmapStringField()
52{
53	delete fBitmap;
54}
55
56
57void
58BBitmapStringField::SetBitmap(BBitmap* bitmap)
59{
60	delete fBitmap;
61	fBitmap = bitmap;
62	// TODO: cause a redraw?
63}
64
65
66// #pragma mark - TeamsColumn
67
68
69float TeamsColumn::sTextMargin = 0.0;
70
71
72TeamsColumn::TeamsColumn(const char* title, float width, float minWidth,
73		float maxWidth, uint32 truncateMode, alignment align)
74	:
75	Inherited(title, width, minWidth, maxWidth, align),
76	fTruncateMode(truncateMode)
77{
78	SetWantsEvents(true);
79}
80
81
82void
83TeamsColumn::DrawField(BField* field, BRect rect, BView* parent)
84{
85	BBitmapStringField* bitmapField
86		= dynamic_cast<BBitmapStringField*>(field);
87	BStringField* stringField = dynamic_cast<BStringField*>(field);
88
89	if (bitmapField) {
90		const BBitmap* bitmap = bitmapField->Bitmap();
91
92		// figure out the placement
93		float x = 0.0;
94		BRect r = bitmap ? bitmap->Bounds() : BRect(0, 0, 15, 15);
95		float y = rect.top + ((rect.Height() - r.Height()) / 2);
96		float width = 0.0;
97
98		switch (Alignment()) {
99			default:
100			case B_ALIGN_LEFT:
101			case B_ALIGN_CENTER:
102				x = rect.left + sTextMargin;
103				width = rect.right - (x + r.Width()) - (2 * sTextMargin);
104				r.Set(x + r.Width(), rect.top, rect.right - width, rect.bottom);
105				break;
106
107			case B_ALIGN_RIGHT:
108				x = rect.right - sTextMargin - r.Width();
109				width = (x - rect.left - (2 * sTextMargin));
110				r.Set(rect.left, rect.top, rect.left + width, rect.bottom);
111				break;
112		}
113
114		if (width != bitmapField->Width()) {
115			BString truncatedString(bitmapField->String());
116			parent->TruncateString(&truncatedString, fTruncateMode, width + 2);
117			bitmapField->SetClippedString(truncatedString.String());
118			bitmapField->SetWidth(width);
119		}
120
121		// draw the bitmap
122		if (bitmap) {
123			parent->SetDrawingMode(B_OP_ALPHA);
124			parent->DrawBitmap(bitmap, BPoint(x, y));
125			parent->SetDrawingMode(B_OP_OVER);
126		}
127
128		// draw the string
129		DrawString(bitmapField->ClippedString(), parent, r);
130
131	} else if (stringField) {
132
133		float width = rect.Width() - (2 * sTextMargin);
134
135		if (width != stringField->Width()) {
136			BString truncatedString(stringField->String());
137
138			parent->TruncateString(&truncatedString, fTruncateMode, width + 2);
139			stringField->SetClippedString(truncatedString.String());
140			stringField->SetWidth(width);
141		}
142
143		DrawString(stringField->ClippedString(), parent, rect);
144	}
145}
146
147
148float
149TeamsColumn::GetPreferredWidth(BField *_field, BView* parent) const
150{
151	BBitmapStringField* bitmapField
152		= dynamic_cast<BBitmapStringField*>(_field);
153	BStringField* stringField = dynamic_cast<BStringField*>(_field);
154
155	float parentWidth = Inherited::GetPreferredWidth(_field, parent);
156	float width = 0.0;
157
158	if (bitmapField) {
159		const BBitmap* bitmap = bitmapField->Bitmap();
160		BFont font;
161		parent->GetFont(&font);
162		width = font.StringWidth(bitmapField->String()) + 3 * sTextMargin;
163		if (bitmap)
164			width += bitmap->Bounds().Width();
165		else
166			width += 16;
167	} else if (stringField) {
168		BFont font;
169		parent->GetFont(&font);
170		width = font.StringWidth(stringField->String()) + 2 * sTextMargin;
171	}
172	return max_c(width, parentWidth);
173}
174
175
176bool
177TeamsColumn::AcceptsField(const BField* field) const
178{
179	return dynamic_cast<const BStringField*>(field) != NULL;
180}
181
182
183void
184TeamsColumn::InitTextMargin(BView* parent)
185{
186	BFont font;
187	parent->GetFont(&font);
188	sTextMargin = ceilf(font.Size() * 0.8);
189}
190
191
192// #pragma mark - TeamRow
193
194
195enum {
196	kNameColumn,
197	kIDColumn
198};
199
200
201TeamRow::TeamRow(TeamInfo* info)
202	: BRow(ceilf(be_control_look->DefaultLabelSpacing() * 3.3f))
203{
204	_SetTo(info);
205}
206
207
208bool
209TeamRow::NeedsUpdate(TeamInfo* info)
210{
211	// Check if we need to rebuilt the row's fields because the team critical
212	// info (basically, app image running under that team ID) has changed
213
214	if (info->Arguments() != fTeamInfo.Arguments()) {
215		_SetTo(info);
216		return true;
217	}
218
219	return false;
220}
221
222
223status_t
224TeamRow::_SetTo(TeamInfo* info)
225{
226	fTeamInfo = *info;
227
228	app_info appInfo;
229	status_t status = be_roster->GetRunningAppInfo(fTeamInfo.TeamID(),
230		&appInfo);
231	if (status != B_OK) {
232		// Not an application known to be_roster
233
234		if (fTeamInfo.TeamID() == B_SYSTEM_TEAM) {
235			// Get icon and name from kernel image
236			system_info	systemInfo;
237			get_system_info(&systemInfo);
238
239			BPath kernelPath;
240			find_directory(B_BEOS_SYSTEM_DIRECTORY, &kernelPath);
241			kernelPath.Append(systemInfo.kernel_name);
242
243			get_ref_for_path(kernelPath.Path(), &appInfo.ref);
244
245		} else
246			BPrivate::get_app_ref(fTeamInfo.TeamID(), &appInfo.ref);
247	}
248
249	BBitmap* icon = new BBitmap(BRect(BPoint(0, 0),
250		be_control_look->ComposeIconSize(B_MINI_ICON)), B_RGBA32);
251
252	icon_size iconSize = (icon_size)(icon->Bounds().Width() + 1);
253	status = BNodeInfo::GetTrackerIcon(&appInfo.ref, icon, iconSize);
254	if (status != B_OK) {
255		BMimeType genericAppType(B_APP_MIME_TYPE);
256		status = genericAppType.GetIcon(icon, iconSize);
257	}
258
259	if (status != B_OK) {
260		delete icon;
261		icon = NULL;
262	}
263
264	BString tmp;
265	tmp << fTeamInfo.TeamID();
266
267	SetField(new BBitmapStringField(icon, fTeamInfo.Arguments()), kNameColumn);
268	SetField(new BStringField(tmp), kIDColumn);
269
270	return status;
271}
272
273
274//	#pragma mark - TeamsListView
275
276
277TeamsListView::TeamsListView(const char* name)
278	:
279	Inherited(name, B_NAVIGABLE, B_PLAIN_BORDER),
280	TargetHost::Listener(),
281	TeamsWindow::Listener(),
282	fInterface(NULL),
283	fHost(NULL)
284{
285	AddColumn(new TeamsColumn("Name", 400, 100, 600,
286		B_TRUNCATE_BEGINNING), kNameColumn);
287	AddColumn(new TeamsColumn("ID", 80, 40, 100,
288		B_TRUNCATE_MIDDLE, B_ALIGN_RIGHT), kIDColumn);
289	SetSortingEnabled(false);
290}
291
292
293TeamsListView::~TeamsListView()
294{
295	if (fHost != NULL)
296		fHost->ReleaseReference();
297}
298
299
300void
301TeamsListView::AttachedToWindow()
302{
303	Inherited::AttachedToWindow();
304	TeamsColumn::InitTextMargin(ScrollView());
305}
306
307
308void
309TeamsListView::DetachedFromWindow()
310{
311	Inherited::DetachedFromWindow();
312	_SetInterface(NULL);
313}
314
315
316void
317TeamsListView::MessageReceived(BMessage* message)
318{
319	switch (message->what) {
320		case MSG_SELECTED_INTERFACE_CHANGED:
321		{
322			TargetHostInterface* interface;
323			if (message->FindPointer("interface", reinterpret_cast<void**>(
324					&interface)) == B_OK) {
325				_SetInterface(interface);
326			}
327			break;
328		}
329
330		case MSG_TEAM_ADDED:
331		{
332			TeamInfo* info;
333			team_id team;
334			if (message->FindInt32("team", &team) != B_OK)
335				break;
336
337			TargetHost* host = fInterface->GetTargetHost();
338			AutoLocker<TargetHost> hostLocker(host);
339			info = host->TeamInfoByID(team);
340			if (info == NULL)
341				break;
342
343			TeamRow* row = new TeamRow(info);
344			AddRow(row);
345			break;
346		}
347
348		case MSG_TEAM_REMOVED:
349		{
350			team_id team;
351			if (message->FindInt32("team", &team) != B_OK)
352				break;
353
354			TeamRow* row = FindTeamRow(team);
355			if (row != NULL) {
356				RemoveRow(row);
357				delete row;
358			}
359			break;
360		}
361
362		case MSG_TEAM_RENAMED:
363		{
364			TeamInfo* info;
365			team_id team;
366			if (message->FindInt32("team", &team) != B_OK)
367				break;
368
369			TargetHost* host = fInterface->GetTargetHost();
370			AutoLocker<TargetHost> hostLocker(host);
371			info = host->TeamInfoByID(team);
372			if (info == NULL)
373				break;
374
375			TeamRow* row = FindTeamRow(info->TeamID());
376			if (row != NULL && row->NeedsUpdate(info))
377				UpdateRow(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::TeamAdded(TeamInfo* info)
406{
407	BMessage message(MSG_TEAM_ADDED);
408	message.AddInt32("team", info->TeamID());
409	BMessenger(this).SendMessage(&message);
410}
411
412
413void
414TeamsListView::TeamRemoved(team_id team)
415{
416	BMessage message(MSG_TEAM_REMOVED);
417	message.AddInt32("team", team);
418	BMessenger(this).SendMessage(&message);
419}
420
421
422void
423TeamsListView::TeamRenamed(TeamInfo* info)
424{
425	BMessage message(MSG_TEAM_RENAMED);
426	message.AddInt32("team", info->TeamID());
427	BMessenger(this).SendMessage(&message);
428}
429
430
431void
432TeamsListView::SelectedInterfaceChanged(TargetHostInterface* interface)
433{
434	BMessage message(MSG_SELECTED_INTERFACE_CHANGED);
435	message.AddPointer("interface", interface);
436	BMessenger(this).SendMessage(&message);
437}
438
439
440void
441TeamsListView::_InitList()
442{
443	AutoLocker<TargetHost> hostLocker(fHost);
444	for (int32 i = 0; TeamInfo* info = fHost->TeamInfoAt(i); i++) {
445		BRow* row = new TeamRow(info);
446		AddRow(row);
447	}
448}
449
450
451void
452TeamsListView::_SetInterface(TargetHostInterface* interface)
453{
454	if (interface == fInterface)
455		return;
456
457	if (fInterface != NULL) {
458		Clear();
459		fHost->RemoveListener(this);
460		fHost->ReleaseReference();
461		fHost = NULL;
462	}
463
464	fInterface = interface;
465	if (fInterface == NULL)
466		return;
467
468	fHost = fInterface->GetTargetHost();
469	fHost->AcquireReference();
470	fHost->AddListener(this);
471	_InitList();
472}
473