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