xref: /haiku/src/add-ons/input_server/devices/keyboard/TeamMonitorWindow.cpp (revision 4a55cc230cf7566cadcbb23b1928eefff8aea9a2)
1 /*
2  * Copyright 2004-2020, Haiku.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Jérôme Duval
7  *		Axel Doerfler, axeld@pinc-software.de
8  */
9 
10 //!	Keyboard input server addon
11 
12 #include "TeamMonitorWindow.h"
13 
14 #include <stdio.h>
15 
16 #include <Application.h>
17 #include <CardLayout.h>
18 #include <Catalog.h>
19 #include <GroupLayoutBuilder.h>
20 #include <IconView.h>
21 #include <LayoutBuilder.h>
22 #include <LocaleRoster.h>
23 #include <Message.h>
24 #include <MessageRunner.h>
25 #include <Roster.h>
26 #include <ScrollView.h>
27 #include <Screen.h>
28 #include <SpaceLayoutItem.h>
29 #include <String.h>
30 #include <StringFormat.h>
31 #include <StringView.h>
32 
33 #include <syscalls.h>
34 #include <tracker_private.h>
35 
36 #include "KeyboardInputDevice.h"
37 #include "TeamListItem.h"
38 
39 
40 #undef B_TRANSLATION_CONTEXT
41 #define B_TRANSLATION_CONTEXT "Team monitor"
42 
43 
44 TeamMonitorWindow* gTeamMonitorWindow = NULL;
45 
46 
47 struct TeamQuitter {
48 	team_id team;
49 	thread_id thread;
50 	BLooper* window;
51 };
52 
53 
54 status_t
55 QuitTeamThreadFunction(void* data)
56 {
57 	TeamQuitter* teamQuitter = reinterpret_cast<TeamQuitter*>(data);
58 	if (teamQuitter == NULL)
59 		return B_ERROR;
60 
61 	status_t status;
62 	BMessenger messenger(NULL, teamQuitter->team, &status);
63 	if (status != B_OK)
64 		return status;
65 
66 	BMessage message(B_QUIT_REQUESTED);
67 	BMessage reply;
68 
69 	messenger.SendMessage(&message, &reply, 3000000, 3000000);
70 
71 	bool result;
72 	if (reply.what != B_REPLY
73 		|| reply.FindBool("result", &result) != B_OK
74 		|| result == false) {
75 		message.what = kMsgQuitFailed;
76 		message.AddPointer("TeamQuitter", teamQuitter);
77 		message.AddInt32("error", reply.what);
78 		if (teamQuitter->window != NULL)
79 			teamQuitter->window->PostMessage(&message);
80 		return reply.what;
81 	}
82 
83 	return B_OK;
84 }
85 
86 
87 filter_result
88 FilterLocaleChanged(BMessage* message, BHandler** target,
89 	BMessageFilter *filter)
90 {
91 	if (message->what == B_LOCALE_CHANGED && gTeamMonitorWindow != NULL)
92 		gTeamMonitorWindow->LocaleChanged();
93 
94 	return B_DISPATCH_MESSAGE;
95 }
96 
97 
98 filter_result
99 FilterKeyDown(BMessage* message, BHandler** target,
100 	BMessageFilter *filter)
101 {
102 	if (message->what == B_KEY_DOWN && gTeamMonitorWindow != NULL) {
103 		if (gTeamMonitorWindow->HandleKeyDown(message))
104 			return B_SKIP_MESSAGE;
105 	}
106 
107 	return B_DISPATCH_MESSAGE;
108 }
109 
110 
111 class TeamDescriptionView : public BView {
112 public:
113 							TeamDescriptionView();
114 	virtual					~TeamDescriptionView();
115 
116 	virtual void			MessageReceived(BMessage* message);
117 
118 			void			CtrlAltDelPressed(bool keyDown);
119 
120 			void			SetItem(TeamListItem* item);
121 			TeamListItem*	Item() { return fItem; }
122 
123 private:
124 			TeamListItem*	fItem;
125 			int32			fSeconds;
126 			BMessageRunner*	fRebootRunner;
127 			IconView*		fIconView;
128 			BCardLayout*	fLayout;
129 			BStringView*	fInfoTextView;
130 
131 			BStringView*	fTeamName;
132 			BStringView*	fSysComponent;
133 			BStringView*	fQuitOverdue;
134 };
135 
136 
137 static const uint32 kMsgUpdate = 'TMup';
138 static const uint32 kMsgLaunchTerminal = 'TMlt';
139 const uint32 TM_CANCEL = 'TMca';
140 const uint32 TM_FORCE_REBOOT = 'TMfr';
141 const uint32 TM_KILL_APPLICATION = 'TMka';
142 const uint32 TM_QUIT_APPLICATION = 'TMqa';
143 const uint32 TM_RESTART_DESKTOP = 'TMrd';
144 const uint32 TM_SELECTED_TEAM = 'TMst';
145 
146 static const uint32 kMsgRebootTick = 'TMrt';
147 
148 
149 TeamMonitorWindow::TeamMonitorWindow()
150 	:
151 	BWindow(BRect(0, 0, 350, 100), B_TRANSLATE("Team monitor"),
152 		B_TITLED_WINDOW_LOOK, B_MODAL_ALL_WINDOW_FEEL,
153 		B_NOT_MINIMIZABLE | B_NOT_ZOOMABLE | B_ASYNCHRONOUS_CONTROLS
154 			| B_CLOSE_ON_ESCAPE | B_AUTO_UPDATE_SIZE_LIMITS,
155 		B_ALL_WORKSPACES),
156 	fQuitting(false),
157 	fUpdateRunner(NULL)
158 {
159 	BGroupLayout* layout = new BGroupLayout(B_VERTICAL);
160 	float inset = 10;
161 	layout->SetInsets(inset, inset, inset, inset);
162 	layout->SetSpacing(inset);
163 	SetLayout(layout);
164 
165 	layout->View()->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
166 
167 	fListView = new BListView("teams");
168 	fListView->SetSelectionMessage(new BMessage(TM_SELECTED_TEAM));
169 
170 	BScrollView* scrollView = new BScrollView("scroll_teams", fListView,
171 		0, B_SUPPORTS_LAYOUT, false, true, B_FANCY_BORDER);
172 	scrollView->SetExplicitMinSize(BSize(B_SIZE_UNSET, 150));
173 
174 	fKillButton = new BButton("kill", B_TRANSLATE("Kill application"),
175 		new BMessage(TM_KILL_APPLICATION));
176 	fKillButton->SetEnabled(false);
177 
178 	fQuitButton = new BButton("quit", B_TRANSLATE("Quit application"),
179 		new BMessage(TM_QUIT_APPLICATION));
180 	fQuitButton->SetEnabled(false);
181 
182 	fDescriptionView = new TeamDescriptionView;
183 
184 	BButton* forceReboot = new BButton("force", B_TRANSLATE("Force reboot"),
185 		new BMessage(TM_FORCE_REBOOT));
186 
187 	fRestartButton = new BButton("restart", B_TRANSLATE("Restart the desktop"),
188 		new BMessage(TM_RESTART_DESKTOP));
189 
190 	BButton* openTerminal = new BButton("terminal",
191 		B_TRANSLATE("Open Terminal"), new BMessage(kMsgLaunchTerminal));
192 
193 	fCancelButton = new BButton("cancel", B_TRANSLATE("Cancel"),
194 		new BMessage(TM_CANCEL));
195 	SetDefaultButton(fCancelButton);
196 
197 	BGroupLayoutBuilder(layout)
198 		.Add(scrollView, 10)
199 		.AddGroup(B_HORIZONTAL)
200 			.SetInsets(0, 0, 0, 0)
201 			.Add(fKillButton)
202 			.Add(fQuitButton)
203 			.AddGlue()
204 			.End()
205 		.Add(fDescriptionView)
206 		.AddGroup(B_HORIZONTAL)
207 			.SetInsets(0, 0, 0, 0)
208 			.Add(forceReboot)
209 			.AddGlue()
210 			.Add(fRestartButton)
211 			.AddGlue(inset)
212 			.Add(openTerminal)
213 			.Add(fCancelButton);
214 
215 	CenterOnScreen();
216 
217 	fRestartButton->Hide();
218 
219 	AddShortcut('T', B_COMMAND_KEY | B_OPTION_KEY,
220 		new BMessage(kMsgLaunchTerminal));
221 	AddShortcut('W', B_COMMAND_KEY, new BMessage(B_QUIT_REQUESTED));
222 
223 	gLocalizedNamePreferred
224 		= BLocaleRoster::Default()->IsFilesystemTranslationPreferred();
225 
226 	gTeamMonitorWindow = this;
227 
228 	this->AddCommonFilter(new BMessageFilter(B_ANY_DELIVERY,
229 		B_ANY_SOURCE, B_KEY_DOWN, FilterKeyDown));
230 
231 	if (be_app->Lock()) {
232 		be_app->AddCommonFilter(new BMessageFilter(B_ANY_DELIVERY,
233 			B_ANY_SOURCE, B_LOCALE_CHANGED, FilterLocaleChanged));
234 		be_app->Unlock();
235 	}
236 }
237 
238 
239 TeamMonitorWindow::~TeamMonitorWindow()
240 {
241 	while (fTeamQuitterList.ItemAt(0) != NULL) {
242 		TeamQuitter* teamQuitter = reinterpret_cast<TeamQuitter*>
243 			(fTeamQuitterList.RemoveItem((int32) 0));
244 		if (teamQuitter != NULL) {
245 			status_t status;
246 			wait_for_thread(teamQuitter->thread, &status);
247 			delete teamQuitter;
248 		}
249 	}
250 }
251 
252 
253 void
254 TeamMonitorWindow::MessageReceived(BMessage* msg)
255 {
256 	switch (msg->what) {
257 		case SYSTEM_SHUTTING_DOWN:
258 			fQuitting = true;
259 			break;
260 
261 		case kMsgUpdate:
262 			_UpdateList();
263 			break;
264 
265 		case kMsgCtrlAltDelPressed:
266 			bool keyDown;
267 			if (msg->FindBool("key down", &keyDown) != B_OK)
268 				break;
269 
270 			fDescriptionView->CtrlAltDelPressed(keyDown);
271 			break;
272 
273 		case kMsgDeselectAll:
274 			fListView->DeselectAll();
275 			break;
276 
277 		case kMsgLaunchTerminal:
278 			be_roster->Launch("application/x-vnd.Haiku-Terminal");
279 			PostMessage(B_QUIT_REQUESTED);
280 			break;
281 
282 		case TM_FORCE_REBOOT:
283 			_kern_shutdown(true);
284 			break;
285 
286 		case TM_KILL_APPLICATION:
287 		{
288 			TeamListItem* item = dynamic_cast<TeamListItem*>(fListView->ItemAt(
289 				fListView->CurrentSelection()));
290 			if (item != NULL) {
291 				kill_team(item->GetInfo()->team);
292 				_UpdateList();
293 			}
294 			break;
295 		}
296 		case TM_QUIT_APPLICATION:
297 		{
298 			TeamListItem* item = dynamic_cast<TeamListItem*>(fListView->ItemAt(
299 				fListView->CurrentSelection()));
300 			if (item != NULL) {
301 				QuitTeam(item);
302 			}
303 			break;
304 		}
305 		case kMsgQuitFailed:
306 			MarkUnquittableTeam(msg);
307 			break;
308 
309 		case TM_RESTART_DESKTOP:
310 		{
311 			if (!be_roster->IsRunning(kTrackerSignature))
312 				be_roster->Launch(kTrackerSignature);
313 			if (!be_roster->IsRunning(kDeskbarSignature))
314 				be_roster->Launch(kDeskbarSignature);
315 			fRestartButton->Hide();
316 			SetDefaultButton(fCancelButton);
317 			break;
318 		}
319 		case TM_SELECTED_TEAM:
320 		{
321 			fKillButton->SetEnabled(fListView->CurrentSelection() >= 0);
322 			TeamListItem* item = dynamic_cast<TeamListItem*>(fListView->ItemAt(
323 				fListView->CurrentSelection()));
324 			fDescriptionView->SetItem(item);
325 			fQuitButton->SetEnabled(item != NULL && item->IsApplication());
326 			break;
327 		}
328 		case TM_CANCEL:
329 			PostMessage(B_QUIT_REQUESTED);
330 			break;
331 
332 		default:
333 			BWindow::MessageReceived(msg);
334 			break;
335 	}
336 }
337 
338 
339 void
340 TeamMonitorWindow::Show()
341 {
342 	fListView->MakeFocus();
343 	BWindow::Show();
344 }
345 
346 
347 bool
348 TeamMonitorWindow::QuitRequested()
349 {
350 	Disable();
351 	return fQuitting;
352 }
353 
354 
355 void
356 TeamMonitorWindow::Enable()
357 {
358 	if (Lock()) {
359 		if (IsHidden()) {
360 			BMessage message(kMsgUpdate);
361 			fUpdateRunner = new BMessageRunner(this, &message, 1000000LL);
362 
363 			_UpdateList();
364 			Show();
365 		}
366 		Unlock();
367 	}
368 
369 	// Not sure why this is needed, but without it the layout isn't correct
370 	// when the window is first shown and the buttons at the bottom aren't
371 	// visible.
372 	InvalidateLayout();
373 }
374 
375 
376 void
377 TeamMonitorWindow::Disable()
378 {
379 	delete fUpdateRunner;
380 	fUpdateRunner = NULL;
381 	Hide();
382 	fListView->DeselectAll();
383 	for (int32 i = 0; i < fListView->CountItems(); i++) {
384 		TeamListItem* item = dynamic_cast<TeamListItem*>(fListView->ItemAt(i));
385 		if (item != NULL)
386 			item->SetRefusingToQuit(false);
387 	}
388 }
389 
390 
391 void
392 TeamMonitorWindow::LocaleChanged()
393 {
394 	BLocaleRoster::Default()->Refresh();
395 	gLocalizedNamePreferred
396 		= BLocaleRoster::Default()->IsFilesystemTranslationPreferred();
397 
398 	for (int32 i = 0; i < fListView->CountItems(); i++) {
399 		TeamListItem* item
400 			= dynamic_cast<TeamListItem*>(fListView->ItemAt(i));
401 		if (item != NULL)
402 			item->CacheLocalizedName();
403 	}
404 }
405 
406 
407 void
408 TeamMonitorWindow::QuitTeam(TeamListItem* item)
409 {
410 	if (item == NULL)
411 		return;
412 
413 	TeamQuitter* teamQuitter = new TeamQuitter;
414 	teamQuitter->team = item->GetInfo()->team;
415 	teamQuitter->window = this;
416 	teamQuitter->thread = spawn_thread(QuitTeamThreadFunction,
417 		"team quitter", B_DISPLAY_PRIORITY, teamQuitter);
418 
419 	if (teamQuitter->thread < 0) {
420 		delete teamQuitter;
421 		return;
422 	}
423 
424 	fTeamQuitterList.AddItem(teamQuitter);
425 
426 	if (resume_thread(teamQuitter->thread) != B_OK) {
427 		fTeamQuitterList.RemoveItem(teamQuitter);
428 		delete teamQuitter;
429 	}
430 }
431 
432 
433 void
434 TeamMonitorWindow::MarkUnquittableTeam(BMessage* message)
435 {
436 	if (message == NULL)
437 		return;
438 
439 	int32 reply;
440 	if (message->FindInt32("error", &reply) != B_OK)
441 		return;
442 
443 	TeamQuitter* teamQuitter;
444 	if (message->FindPointer("TeamQuitter",
445 		reinterpret_cast<void**>(&teamQuitter)) != B_OK)
446 		return;
447 
448 	for (int32 i = 0; i < fListView->CountItems(); i++) {
449 		TeamListItem* item
450 			= dynamic_cast<TeamListItem*>(fListView->ItemAt(i));
451 		if (item != NULL && item->GetInfo()->team == teamQuitter->team) {
452 			item->SetRefusingToQuit(true);
453 			fListView->Select(i);
454 			fListView->InvalidateItem(i);
455 			fDescriptionView->SetItem(item);
456 			break;
457 		}
458 	}
459 
460 	fTeamQuitterList.RemoveItem(teamQuitter);
461 	delete teamQuitter;
462 }
463 
464 
465 bool
466 TeamMonitorWindow::HandleKeyDown(BMessage* msg)
467 {
468 	uint32 rawChar = msg->FindInt32("raw_char");
469 	uint32 modifier = msg->FindInt32("modifiers");
470 
471 	// Ignore the system modifier namespace
472 	if ((modifier & (B_CONTROL_KEY | B_COMMAND_KEY))
473 			== (B_CONTROL_KEY | B_COMMAND_KEY))
474 		return false;
475 
476 	bool quit = false;
477 	bool kill = false;
478 	switch (rawChar) {
479 		case B_DELETE:
480 			if (modifier & B_SHIFT_KEY)
481 				kill = true;
482 			else
483 				quit = true;
484 			break;
485 		case 'q':
486 		case 'Q':
487 			quit = true;
488 			break;
489 		case 'k':
490 		case 'K':
491 			kill = true;
492 			break;
493 	}
494 
495 	if (quit) {
496 		PostMessage(TM_QUIT_APPLICATION);
497 		return true;
498 	} else if (kill) {
499 		PostMessage(TM_KILL_APPLICATION);
500 		return true;
501 	}
502 
503 	return false;
504 }
505 
506 
507 void
508 TeamMonitorWindow::_UpdateList()
509 {
510 	bool changed = false;
511 
512 	for (int32 i = 0; i < fListView->CountItems(); i++) {
513 		TeamListItem* item = dynamic_cast<TeamListItem*>(fListView->ItemAt(i));
514 		if (item != NULL)
515 			item->SetFound(false);
516 	}
517 
518 	int32 cookie = 0;
519 	team_info info;
520 	while (get_next_team_info(&cookie, &info) == B_OK) {
521 		if (info.team <=16)
522 			continue;
523 
524 		bool found = false;
525 		for (int32 i = 0; i < fListView->CountItems(); i++) {
526 			TeamListItem* item
527 				= dynamic_cast<TeamListItem*>(fListView->ItemAt(i));
528 			if (item != NULL && item->GetInfo()->team == info.team) {
529 				item->SetFound(true);
530 				found = true;
531 			}
532 		}
533 
534 		if (!found) {
535 			TeamListItem* item = new TeamListItem(info);
536 
537 			fListView->AddItem(item,
538 				item->IsSystemServer() ? fListView->CountItems() : 0);
539 			item->SetFound(true);
540 			changed = true;
541 		}
542 	}
543 
544 	for (int32 i = fListView->CountItems() - 1; i >= 0; i--) {
545 		TeamListItem* item = dynamic_cast<TeamListItem*>(fListView->ItemAt(i));
546 		if (item != NULL && !item->Found()) {
547 			if (item == fDescriptionView->Item()) {
548 				fDescriptionView->SetItem(NULL);
549 				fKillButton->SetEnabled(false);
550 				fQuitButton->SetEnabled(false);
551 			}
552 
553 			delete fListView->RemoveItem(i);
554 			changed = true;
555 		}
556 	}
557 
558 	if (changed)
559 		fListView->Invalidate();
560 
561 	bool desktopRunning = be_roster->IsRunning(kTrackerSignature)
562 		&& be_roster->IsRunning(kDeskbarSignature);
563 	if (!desktopRunning && fRestartButton->IsHidden()) {
564 		fRestartButton->Show();
565 		SetDefaultButton(fRestartButton);
566 		fRestartButton->Parent()->Layout(true);
567 	}
568 
569 	fRestartButton->SetEnabled(!desktopRunning);
570 }
571 
572 
573 //	#pragma mark -
574 
575 
576 TeamDescriptionView::TeamDescriptionView()
577 	:
578 	BView("description view", B_WILL_DRAW),
579 	fItem(NULL),
580 	fSeconds(4),
581 	fRebootRunner(NULL)
582 {
583 	fTeamName = new BStringView("team name", "team name");
584 	fTeamName->SetTruncation(B_TRUNCATE_BEGINNING);
585 	fTeamName->SetExplicitSize(BSize(StringWidth("x") * 60, B_SIZE_UNSET));
586 	fSysComponent = new BStringView("system component", B_TRANSLATE(
587 		"(This team is a system component)"));
588 	fQuitOverdue = new BStringView("quit overdue", B_TRANSLATE(
589 		"If the application will not quit you may have to kill it."));
590 	fQuitOverdue->SetFont(be_bold_font);
591 
592 	fInfoTextView = new BStringView("info text", "");
593 	fInfoTextView->SetLowUIColor(B_PANEL_BACKGROUND_COLOR);
594 	fInfoTextView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
595 
596 	fIconView = new IconView();
597 	fIconView->SetExplicitAlignment(
598 		BAlignment(B_ALIGN_HORIZONTAL_UNSET, B_ALIGN_VERTICAL_CENTER));
599 
600 	BView* teamPropertiesView = new BView("team properties", B_WILL_DRAW);
601 	teamPropertiesView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
602 	BGridLayout* layout = new BGridLayout();
603 	teamPropertiesView->SetLayout(layout);
604 
605 	BLayoutBuilder::Grid<>(layout)
606 		.SetInsets(0)
607 		.SetSpacing(B_USE_ITEM_SPACING, 0)
608 		.AddGlue(0, 0)
609 		.Add(fIconView, 0, 1, 1, 3)
610 		.AddGlue(1, 0)
611 		.Add(fTeamName, 1, 2)
612 		.Add(fSysComponent, 1, 3)
613 		.Add(fQuitOverdue, 1, 4)
614 		.AddGlue(0, 5)
615 		.AddGlue(2, 1);
616 
617 	fLayout = new BCardLayout();
618 	SetLayout(fLayout);
619 	fLayout->AddView(fInfoTextView);
620 	fLayout->AddView(teamPropertiesView);
621 
622 	SetItem(NULL);
623 }
624 
625 
626 TeamDescriptionView::~TeamDescriptionView()
627 {
628 	delete fRebootRunner;
629 }
630 
631 
632 void
633 TeamDescriptionView::MessageReceived(BMessage* message)
634 {
635 	switch (message->what) {
636 		case kMsgRebootTick:
637 			fSeconds--;
638 			if (fSeconds == 0)
639 				Window()->PostMessage(TM_FORCE_REBOOT);
640 			else
641 				SetItem(fItem);
642 			break;
643 
644 		default:
645 			BView::MessageReceived(message);
646 	}
647 }
648 
649 
650 void
651 TeamDescriptionView::CtrlAltDelPressed(bool keyDown)
652 {
653 	if (!(keyDown ^ (fRebootRunner != NULL)))
654 		return;
655 
656 	delete fRebootRunner;
657 	fRebootRunner = NULL;
658 	fSeconds = 4;
659 
660 	if (keyDown) {
661 		Window()->PostMessage(kMsgDeselectAll);
662 		BMessage tick(kMsgRebootTick);
663 		fRebootRunner = new BMessageRunner(this, &tick, 1000000LL);
664 	}
665 
666 	SetItem(NULL);
667 }
668 
669 
670 void
671 TeamDescriptionView::SetItem(TeamListItem* item)
672 {
673 	fItem = item;
674 
675 	if (item == NULL) {
676 		if (fInfoTextView != NULL) {
677 			BFont font;
678 			fInfoTextView->GetFont(&font);
679 			font.SetFace(B_REGULAR_FACE);
680 
681 			if (fRebootRunner != NULL && fSeconds < 4)
682 				font.SetFace(B_BOLD_FACE);
683 
684 			fInfoTextView->SetFont(&font);
685 
686 			static BStringFormat format(B_TRANSLATE("{0, plural,"
687 				"one{Hold CONTROL+ALT+DELETE for # second to reboot.}"
688 				"other{Hold CONTROL+ALT+DELETE for # seconds to reboot.}}"));
689 			BString text;
690 			format.Format(text, fSeconds);
691 			fInfoTextView->SetText(text);
692 		}
693 	} else {
694 		fTeamName->SetText(item->Path()->Path());
695 
696 		if (item->IsSystemServer()) {
697 			if (fSysComponent->IsHidden(fSysComponent))
698 				fSysComponent->Show();
699 		} else {
700 			if (!fSysComponent->IsHidden(fSysComponent))
701 				fSysComponent->Hide();
702 		}
703 
704 		if (item->IsRefusingToQuit()) {
705 			if (fQuitOverdue->IsHidden(fQuitOverdue))
706 				fQuitOverdue->Show();
707 		} else {
708 			if (!fQuitOverdue->IsHidden(fQuitOverdue))
709 				fQuitOverdue->Hide();
710 		}
711 
712 		fIconView->SetIcon(item->LargeIcon());
713 	}
714 
715 	if (fLayout == NULL)
716 		return;
717 
718 	if (item == NULL)
719 		fLayout->SetVisibleItem((int32)0);
720 	else
721 		fLayout->SetVisibleItem((int32)1);
722 
723 	Invalidate();
724 }
725