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
32 enum {
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
BBitmapStringField(BBitmap * bitmap,const char * string)43 BBitmapStringField::BBitmapStringField(BBitmap* bitmap, const char* string)
44 :
45 Inherited(string),
46 fBitmap(bitmap)
47 {
48 }
49
50
~BBitmapStringField()51 BBitmapStringField::~BBitmapStringField()
52 {
53 delete fBitmap;
54 }
55
56
57 void
SetBitmap(BBitmap * bitmap)58 BBitmapStringField::SetBitmap(BBitmap* bitmap)
59 {
60 delete fBitmap;
61 fBitmap = bitmap;
62 // TODO: cause a redraw?
63 }
64
65
66 // #pragma mark - TeamsColumn
67
68
69 float TeamsColumn::sTextMargin = 0.0;
70
71
TeamsColumn(const char * title,float width,float minWidth,float maxWidth,uint32 truncateMode,alignment align)72 TeamsColumn::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
82 void
DrawField(BField * field,BRect rect,BView * parent)83 TeamsColumn::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
148 float
GetPreferredWidth(BField * _field,BView * parent) const149 TeamsColumn::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
176 bool
AcceptsField(const BField * field) const177 TeamsColumn::AcceptsField(const BField* field) const
178 {
179 return dynamic_cast<const BStringField*>(field) != NULL;
180 }
181
182
183 void
InitTextMargin(BView * parent)184 TeamsColumn::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
195 enum {
196 kNameColumn,
197 kIDColumn
198 };
199
200
TeamRow(TeamInfo * info)201 TeamRow::TeamRow(TeamInfo* info)
202 : BRow(ceilf(be_control_look->DefaultLabelSpacing() * 3.3f))
203 {
204 _SetTo(info);
205 }
206
207
208 bool
NeedsUpdate(TeamInfo * info)209 TeamRow::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
223 status_t
_SetTo(TeamInfo * info)224 TeamRow::_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
TeamsListView(const char * name)277 TeamsListView::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
~TeamsListView()293 TeamsListView::~TeamsListView()
294 {
295 if (fHost != NULL)
296 fHost->ReleaseReference();
297 }
298
299
300 void
AttachedToWindow()301 TeamsListView::AttachedToWindow()
302 {
303 Inherited::AttachedToWindow();
304 TeamsColumn::InitTextMargin(ScrollView());
305 }
306
307
308 void
DetachedFromWindow()309 TeamsListView::DetachedFromWindow()
310 {
311 Inherited::DetachedFromWindow();
312 _SetInterface(NULL);
313 }
314
315
316 void
MessageReceived(BMessage * message)317 TeamsListView::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
388 TeamRow*
FindTeamRow(team_id teamId)389 TeamsListView::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
404 void
TeamAdded(TeamInfo * info)405 TeamsListView::TeamAdded(TeamInfo* info)
406 {
407 BMessage message(MSG_TEAM_ADDED);
408 message.AddInt32("team", info->TeamID());
409 BMessenger(this).SendMessage(&message);
410 }
411
412
413 void
TeamRemoved(team_id team)414 TeamsListView::TeamRemoved(team_id team)
415 {
416 BMessage message(MSG_TEAM_REMOVED);
417 message.AddInt32("team", team);
418 BMessenger(this).SendMessage(&message);
419 }
420
421
422 void
TeamRenamed(TeamInfo * info)423 TeamsListView::TeamRenamed(TeamInfo* info)
424 {
425 BMessage message(MSG_TEAM_RENAMED);
426 message.AddInt32("team", info->TeamID());
427 BMessenger(this).SendMessage(&message);
428 }
429
430
431 void
SelectedInterfaceChanged(TargetHostInterface * interface)432 TeamsListView::SelectedInterfaceChanged(TargetHostInterface* interface)
433 {
434 BMessage message(MSG_SELECTED_INTERFACE_CHANGED);
435 message.AddPointer("interface", interface);
436 BMessenger(this).SendMessage(&message);
437 }
438
439
440 void
_InitList()441 TeamsListView::_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
451 void
_SetInterface(TargetHostInterface * interface)452 TeamsListView::_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