/*
 * Copyright 2004-2008, Haiku.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Jérôme Duval
 *		Axel Doerfler, axeld@pinc-software.de
 */
 
//!	Keyboard input server addon
 
#include "TeamMonitorWindow.h"
 
#include <stdio.h>
 
#include <Application.h>
#include <CardLayout.h>
#include <Catalog.h>
#include <GroupLayoutBuilder.h>
#include <IconView.h>
#include <LayoutBuilder.h>
#include <LocaleRoster.h>
#include <Message.h>
#include <MessageRunner.h>
#include <Roster.h>
#include <ScrollView.h>
#include <Screen.h>
#include <SpaceLayoutItem.h>
#include <String.h>
#include <StringView.h>
#include <TextView.h>
 
#include <syscalls.h>
#include <tracker_private.h>
 
#include "KeyboardInputDevice.h"
#include "TeamListItem.h"
 
 
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Team monitor"
 
 
TeamMonitorWindow* gTeamMonitorWindow = NULL;
 
 
struct TeamQuitter {
	team_id team;
	thread_id thread;
	BLooper* window;
};
 
 
status_t
QuitTeamThreadFunction(void* data)
{
	TeamQuitter* teamQuitter = reinterpret_cast<TeamQuitter*>(data);
	if (teamQuitter == NULL)
		return B_ERROR;
 
	status_t status;
	BMessenger messenger(NULL, teamQuitter->team, &status);
	if (status != B_OK)
		return status;
 
	BMessage message(B_QUIT_REQUESTED);
	BMessage reply;
 
	messenger.SendMessage(&message, &reply, 3000000, 3000000);
 
	bool result;
	if (reply.what != B_REPLY
		|| reply.FindBool("result", &result) != B_OK
		|| result == false) {
		message.what = kMsgQuitFailed;
		message.AddPointer("TeamQuitter", teamQuitter);
		message.AddInt32("error", reply.what);
		if (teamQuitter->window != NULL)
			teamQuitter->window->PostMessage(&message);
		return reply.what;
	}
 
	return B_OK;
}
 
 
filter_result
FilterLocaleChanged(BMessage* message, BHandler** target,
	BMessageFilter *filter)
{
	if (message->what == B_LOCALE_CHANGED && gTeamMonitorWindow != NULL)
		gTeamMonitorWindow->LocaleChanged();
 
	return B_DISPATCH_MESSAGE;
}
 
 
filter_result
FilterKeyDown(BMessage* message, BHandler** target,
	BMessageFilter *filter)
{
	if (message->what == B_KEY_DOWN && gTeamMonitorWindow != NULL) {
		if (gTeamMonitorWindow->HandleKeyDown(message))
			return B_SKIP_MESSAGE;
	}
 
	return B_DISPATCH_MESSAGE;
}
 
 
class TeamDescriptionView : public BView {
public:
							TeamDescriptionView();
	virtual					~TeamDescriptionView();
 
	virtual void			MessageReceived(BMessage* message);
 
			void			CtrlAltDelPressed(bool keyDown);
 
			void			SetItem(TeamListItem* item);
			TeamListItem*	Item() { return fItem; }
 
private:
			TeamListItem*	fItem;
			int32			fSeconds;
			BMessageRunner*	fRebootRunner;
			IconView*		fIconView;
	const	char*			fInfoString;
			BCardLayout*	fLayout;
			BTextView*		fInfoTextView;
 
			BStringView*	fTeamName;
			BStringView*	fSysComponent;
			BStringView*	fQuitOverdue;
};
 
 
static const uint32 kMsgUpdate = 'TMup';
static const uint32 kMsgLaunchTerminal = 'TMlt';
const uint32 TM_CANCEL = 'TMca';
const uint32 TM_FORCE_REBOOT = 'TMfr';
const uint32 TM_KILL_APPLICATION = 'TMka';
const uint32 TM_QUIT_APPLICATION = 'TMqa';
const uint32 TM_RESTART_DESKTOP = 'TMrd';
const uint32 TM_SELECTED_TEAM = 'TMst';
 
static const uint32 kMsgRebootTick = 'TMrt';
 
 
TeamMonitorWindow::TeamMonitorWindow()
	:
	BWindow(BRect(0, 0, 350, 100), B_TRANSLATE("Team monitor"),
		B_TITLED_WINDOW_LOOK, B_MODAL_ALL_WINDOW_FEEL,
		B_NOT_MINIMIZABLE | B_NOT_ZOOMABLE | B_ASYNCHRONOUS_CONTROLS
			| B_CLOSE_ON_ESCAPE | B_AUTO_UPDATE_SIZE_LIMITS,
		B_ALL_WORKSPACES),
	fQuitting(false),
	fUpdateRunner(NULL)
{
	BGroupLayout* layout = new BGroupLayout(B_VERTICAL);
	float inset = 10;
	layout->SetInsets(inset, inset, inset, inset);
	layout->SetSpacing(inset);
	SetLayout(layout);
 
	layout->View()->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
 
	fListView = new BListView("teams");
	fListView->SetSelectionMessage(new BMessage(TM_SELECTED_TEAM));
 
	BScrollView* scrollView = new BScrollView("scroll_teams", fListView,
		0, B_SUPPORTS_LAYOUT, false, true, B_FANCY_BORDER);
	scrollView->SetExplicitMinSize(BSize(B_SIZE_UNSET, 150));
 
	fKillButton = new BButton("kill", B_TRANSLATE("Kill application"),
		new BMessage(TM_KILL_APPLICATION));
	fKillButton->SetEnabled(false);
 
	fQuitButton = new BButton("quit", B_TRANSLATE("Quit application"),
		new BMessage(TM_QUIT_APPLICATION));
	fQuitButton->SetEnabled(false);
 
	fDescriptionView = new TeamDescriptionView;
 
	BButton* forceReboot = new BButton("force", B_TRANSLATE("Force reboot"),
		new BMessage(TM_FORCE_REBOOT));
 
	fRestartButton = new BButton("restart", B_TRANSLATE("Restart the desktop"),
		new BMessage(TM_RESTART_DESKTOP));
 
	fCancelButton = new BButton("cancel", B_TRANSLATE("Cancel"),
		new BMessage(TM_CANCEL));
	SetDefaultButton(fCancelButton);
 
	BGroupLayoutBuilder(layout)
		.Add(scrollView)
		.AddGroup(B_HORIZONTAL)
			.SetInsets(0, 0, 0, 0)
			.Add(fKillButton)
			.Add(fQuitButton)
			.AddGlue()
			.End()
		.Add(fDescriptionView)
		.AddGroup(B_HORIZONTAL)
			.SetInsets(0, 0, 0, 0)
			.Add(forceReboot)
			.AddGlue()
			.Add(fRestartButton)
			.AddGlue(inset)
			.Add(fCancelButton);
 
	CenterOnScreen();
 
	fRestartButton->Hide();
 
	AddShortcut('T', B_COMMAND_KEY | B_OPTION_KEY,
		new BMessage(kMsgLaunchTerminal));
	AddShortcut('W', B_COMMAND_KEY, new BMessage(B_QUIT_REQUESTED));
 
	gLocalizedNamePreferred
		= BLocaleRoster::Default()->IsFilesystemTranslationPreferred();
 
	gTeamMonitorWindow = this;
 
	this->AddCommonFilter(new BMessageFilter(B_ANY_DELIVERY,
		B_ANY_SOURCE, B_KEY_DOWN, FilterKeyDown));
 
	if (be_app->Lock()) {
		be_app->AddCommonFilter(new BMessageFilter(B_ANY_DELIVERY,
			B_ANY_SOURCE, B_LOCALE_CHANGED, FilterLocaleChanged));
		be_app->Unlock();
	}
}
 
 
TeamMonitorWindow::~TeamMonitorWindow()
{
	while (fTeamQuitterList.ItemAt(0) != NULL) {
		TeamQuitter* teamQuitter = reinterpret_cast<TeamQuitter*>
			(fTeamQuitterList.RemoveItem((int32) 0));
		if (teamQuitter != NULL) {
			status_t status;
			wait_for_thread(teamQuitter->thread, &status);
			delete teamQuitter;
		}
	}
}
 
 
void
TeamMonitorWindow::MessageReceived(BMessage* msg)
{
	switch (msg->what) {
		case SYSTEM_SHUTTING_DOWN:
			fQuitting = true;
			break;
 
		case kMsgUpdate:
			_UpdateList();
			break;
 
		case kMsgCtrlAltDelPressed:
			bool keyDown;
			if (msg->FindBool("key down", &keyDown) != B_OK)
				break;
 
			fDescriptionView->CtrlAltDelPressed(keyDown);
			break;
 
		case kMsgDeselectAll:
			fListView->DeselectAll();
			break;
 
		case kMsgLaunchTerminal:
			be_roster->Launch("application/x-vnd.Haiku-Terminal");
			break;
 
		case TM_FORCE_REBOOT:
			_kern_shutdown(true);
			break;
 
		case TM_KILL_APPLICATION:
		{
			TeamListItem* item = dynamic_cast<TeamListItem*>(fListView->ItemAt(
				fListView->CurrentSelection()));
			if (item != NULL) {
				kill_team(item->GetInfo()->team);
				_UpdateList();
			}
			break;
		}
		case TM_QUIT_APPLICATION:
		{
			TeamListItem* item = dynamic_cast<TeamListItem*>(fListView->ItemAt(
				fListView->CurrentSelection()));
			if (item != NULL) {
				QuitTeam(item);
			}
			break;
		}
		case kMsgQuitFailed:
			MarkUnquittableTeam(msg);
			break;
 
		case TM_RESTART_DESKTOP:
		{
			if (!be_roster->IsRunning(kTrackerSignature))
				be_roster->Launch(kTrackerSignature);
			if (!be_roster->IsRunning(kDeskbarSignature))
				be_roster->Launch(kDeskbarSignature);
			fRestartButton->Hide();
			SetDefaultButton(fCancelButton);
			break;
		}
		case TM_SELECTED_TEAM:
		{
			fKillButton->SetEnabled(fListView->CurrentSelection() >= 0);
			TeamListItem* item = dynamic_cast<TeamListItem*>(fListView->ItemAt(
				fListView->CurrentSelection()));
			fDescriptionView->SetItem(item);
			fQuitButton->SetEnabled(item != NULL && item->IsApplication());
			break;
		}
		case TM_CANCEL:
			PostMessage(B_QUIT_REQUESTED);
			break;
 
		default:
			BWindow::MessageReceived(msg);
			break;
	}
}
 
 
void
TeamMonitorWindow::Show()
{
	fListView->MakeFocus();
	BWindow::Show();
}
 
 
bool
TeamMonitorWindow::QuitRequested()
{
	Disable();
	return fQuitting;
}
 
 
void
TeamMonitorWindow::Enable()
{
	if (Lock()) {
		if (IsHidden()) {
			BMessage message(kMsgUpdate);
			fUpdateRunner = new BMessageRunner(this, &message, 1000000LL);
 
			_UpdateList();
			Show();
		}
		Unlock();
	}
 
	// Not sure why this is needed, but without it the layout isn't correct
	// when the window is first shown and the buttons at the bottom aren't
	// visible.
	InvalidateLayout();
}
 
 
void
TeamMonitorWindow::Disable()
{
	delete fUpdateRunner;
	fUpdateRunner = NULL;
	Hide();
	fListView->DeselectAll();
	for (int32 i = 0; i < fListView->CountItems(); i++) {
		TeamListItem* item = dynamic_cast<TeamListItem*>(fListView->ItemAt(i));
		if (item != NULL)
			item->SetRefusingToQuit(false);
	}
}
 
 
void
TeamMonitorWindow::LocaleChanged()
{
	BLocaleRoster::Default()->Refresh();
	gLocalizedNamePreferred
		= BLocaleRoster::Default()->IsFilesystemTranslationPreferred();
 
	for (int32 i = 0; i < fListView->CountItems(); i++) {
		TeamListItem* item
			= dynamic_cast<TeamListItem*>(fListView->ItemAt(i));
		if (item != NULL)
			item->CacheLocalizedName();
	}
}
 
 
void
TeamMonitorWindow::QuitTeam(TeamListItem* item)
{
	if (item == NULL)
		return;
 
	TeamQuitter* teamQuitter = new TeamQuitter;
	teamQuitter->team = item->GetInfo()->team;
	teamQuitter->window = this;
	teamQuitter->thread = spawn_thread(QuitTeamThreadFunction,
		"team quitter", B_DISPLAY_PRIORITY, teamQuitter);
 
	if (teamQuitter->thread < 0) {
		delete teamQuitter;
		return;
	}
 
	fTeamQuitterList.AddItem(teamQuitter);
 
	if (resume_thread(teamQuitter->thread) != B_OK) {
		fTeamQuitterList.RemoveItem(teamQuitter);
		delete teamQuitter;
	}
}
 
 
void
TeamMonitorWindow::MarkUnquittableTeam(BMessage* message)
{
	if (message == NULL)
		return;
 
	int32 reply;
	if (message->FindInt32("error", &reply) != B_OK)
		return;
 
	TeamQuitter* teamQuitter;
	if (message->FindPointer("TeamQuitter",
		reinterpret_cast<void**>(&teamQuitter)) != B_OK)
		return;
 
	for (int32 i = 0; i < fListView->CountItems(); i++) {
		TeamListItem* item
			= dynamic_cast<TeamListItem*>(fListView->ItemAt(i));
		if (item != NULL && item->GetInfo()->team == teamQuitter->team) {
			item->SetRefusingToQuit(true);
			fListView->Select(i);
			fListView->InvalidateItem(i);
			fDescriptionView->SetItem(item);
			break;
		}
	}
 
	fTeamQuitterList.RemoveItem(teamQuitter);
	delete teamQuitter;
}
 
 
bool
TeamMonitorWindow::HandleKeyDown(BMessage* msg)
{
	uint32 rawChar = msg->FindInt32("raw_char");
	uint32 modifier = msg->FindInt32("modifiers");
 
	// Ignore the system modifier namespace
	if ((modifier & (B_CONTROL_KEY | B_COMMAND_KEY))
			== (B_CONTROL_KEY | B_COMMAND_KEY))
		return false;
 
	bool quit = false;
	bool kill = false;
	switch (rawChar) {
		case B_DELETE:
			if (modifier & B_SHIFT_KEY)
				kill = true;
			else
				quit = true;
			break;
		case 'q':
		case 'Q':
			quit = true;
			break;
		case 'k':
		case 'K':
			kill = true;
			break;
	}
 
	if (quit) {
		PostMessage(TM_QUIT_APPLICATION);
		return true;
	} else if (kill) {
		PostMessage(TM_KILL_APPLICATION);
		return true;
	}
 
	return false;
}
 
 
void
TeamMonitorWindow::_UpdateList()
{
	bool changed = false;
 
	for (int32 i = 0; i < fListView->CountItems(); i++) {
		TeamListItem* item = dynamic_cast<TeamListItem*>(fListView->ItemAt(i));
		if (item != NULL)
			item->SetFound(false);
	}
 
	int32 cookie = 0;
	team_info info;
	while (get_next_team_info(&cookie, &info) == B_OK) {
		if (info.team <=16)
			continue;
 
		bool found = false;
		for (int32 i = 0; i < fListView->CountItems(); i++) {
			TeamListItem* item
				= dynamic_cast<TeamListItem*>(fListView->ItemAt(i));
			if (item != NULL && item->GetInfo()->team == info.team) {
				item->SetFound(true);
				found = true;
			}
		}
 
		if (!found) {
			TeamListItem* item = new TeamListItem(info);
 
			fListView->AddItem(item,
				item->IsSystemServer() ? fListView->CountItems() : 0);
			item->SetFound(true);
			changed = true;
		}
	}
 
	for (int32 i = fListView->CountItems() - 1; i >= 0; i--) {
		TeamListItem* item = dynamic_cast<TeamListItem*>(fListView->ItemAt(i));
		if (item != NULL && !item->Found()) {
			if (item == fDescriptionView->Item()) {
				fDescriptionView->SetItem(NULL);
				fKillButton->SetEnabled(false);
				fQuitButton->SetEnabled(false);
			}
 
			delete fListView->RemoveItem(i);
			changed = true;
		}
	}
 
	if (changed)
		fListView->Invalidate();
 
	bool desktopRunning = be_roster->IsRunning(kTrackerSignature)
		&& be_roster->IsRunning(kDeskbarSignature);
	if (!desktopRunning && fRestartButton->IsHidden()) {
		fRestartButton->Show();
		SetDefaultButton(fRestartButton);
		fRestartButton->Parent()->Layout(true);
	}
 
	fRestartButton->SetEnabled(!desktopRunning);
}
 
 
//	#pragma mark -
 
 
TeamDescriptionView::TeamDescriptionView()
	:
	BView("description view", B_WILL_DRAW),
	fItem(NULL),
	fSeconds(4),
	fRebootRunner(NULL)
{
	fInfoString = B_TRANSLATE(
		"Select an application from the list above and click one of "
		"the buttons 'Kill application' and 'Quit application' "
		"in order to close it.\n\n"
		"Hold CONTROL+ALT+DELETE for %ld seconds to reboot.");
 
	fTeamName = new BStringView("team name", "team name");
	fTeamName->SetTruncation(B_TRUNCATE_BEGINNING);
	fTeamName->SetExplicitSize(BSize(StringWidth("x") * 60, B_SIZE_UNSET));
	fSysComponent = new BStringView("system component", B_TRANSLATE(
		"(This team is a system component)"));
	fQuitOverdue = new BStringView("quit overdue", B_TRANSLATE(
		"If the application will not quit you may have to kill it."));
	fQuitOverdue->SetFont(be_bold_font);
 
	fInfoTextView = new BTextView("info text");
	fInfoTextView->SetLowUIColor(B_PANEL_BACKGROUND_COLOR);
	fInfoTextView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
	BGroupView* infoGroup = new BGroupView(B_VERTICAL);
	BGroupLayoutBuilder(infoGroup)
		.Add(fInfoTextView)
		.AddGlue();
 
	fIconView = new IconView();
	fIconView->SetExplicitAlignment(
		BAlignment(B_ALIGN_HORIZONTAL_UNSET, B_ALIGN_VERTICAL_CENTER));
 
	BView* teamPropertiesView = new BView("team properties", B_WILL_DRAW);
	teamPropertiesView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
	BGridLayout* layout = new BGridLayout();
	teamPropertiesView->SetLayout(layout);
 
 
	BLayoutBuilder::Grid<>(layout)
		.SetInsets(0)
		.SetSpacing(B_USE_ITEM_SPACING, 0)
		.AddGlue(0, 0)
		.Add(fIconView, 0, 1, 1, 3)
		.AddGlue(1, 0)
		.Add(fTeamName, 1, 2)
		.Add(fSysComponent, 1, 3)
		.Add(fQuitOverdue, 1, 4)
		.AddGlue(0, 5)
		.AddGlue(2, 1);
 
	fLayout = new BCardLayout();
	SetLayout(fLayout);
	fLayout->AddView(infoGroup);
	fLayout->AddView(teamPropertiesView);
 
	infoGroup->SetExplicitMinSize(BSize(StringWidth("x") * 70, B_SIZE_UNSET));
	fInfoTextView->SetExplicitSize(BSize(B_SIZE_UNSET, be_plain_font->Size() * 7.2));
	fInfoTextView->MakeEditable(false);
	fInfoTextView->MakeSelectable(false);
 
	SetItem(NULL);
}
 
 
TeamDescriptionView::~TeamDescriptionView()
{
	delete fRebootRunner;
}
 
 
void
TeamDescriptionView::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case kMsgRebootTick:
			fSeconds--;
			if (fSeconds == 0)
				Window()->PostMessage(TM_FORCE_REBOOT);
			else
				SetItem(fItem);
			break;
 
		default:
			BView::MessageReceived(message);
	}
}
 
 
void
TeamDescriptionView::CtrlAltDelPressed(bool keyDown)
{
	if (!(keyDown ^ (fRebootRunner != NULL)))
		return;
 
	delete fRebootRunner;
	fRebootRunner = NULL;
	fSeconds = 4;
 
	if (keyDown) {
		Window()->PostMessage(kMsgDeselectAll);
		BMessage tick(kMsgRebootTick);
		fRebootRunner = new BMessageRunner(this, &tick, 1000000LL);
	}
 
	SetItem(NULL);
}
 
 
void
TeamDescriptionView::SetItem(TeamListItem* item)
{
	fItem = item;
 
	if (item == NULL) {
		int32 styleStart = 0;
		int32 styleEnd = 0;
		BString text;
 
		text.SetToFormat(fInfoString, fSeconds);
		fInfoTextView->SetText(text);
		if (fRebootRunner != NULL && fSeconds < 4) {
			styleStart = text.FindLast('\n');
			styleEnd = text.Length();
		}
 
		if (styleStart != styleEnd && fInfoTextView != NULL) {
			BFont font;
			fInfoTextView->GetFont(&font);
			font.SetFace(B_BOLD_FACE);
			fInfoTextView->SetStylable(true);
			fInfoTextView->SetFontAndColor(styleStart, styleEnd, &font);
		}
	} else {
		fTeamName->SetText(item->Path()->Path());
 
		if (item->IsSystemServer()) {
			if (fSysComponent->IsHidden(fSysComponent))
				fSysComponent->Show();
		} else {
			if (!fSysComponent->IsHidden(fSysComponent))
				fSysComponent->Hide();
		}
 
		if (item->IsRefusingToQuit()) {
			if (fQuitOverdue->IsHidden(fQuitOverdue))
				fQuitOverdue->Show();
		} else {
			if (!fQuitOverdue->IsHidden(fQuitOverdue))
				fQuitOverdue->Hide();
		}
 
		fIconView->SetIcon(item->Path()->Path());
	}
 
	if (fLayout == NULL)
		return;
 
	if (item == NULL)
		fLayout->SetVisibleItem((int32)0);
	else
		fLayout->SetVisibleItem((int32)1);
 
	Invalidate();
}
 
 

V595 The 'fInfoTextView' pointer was utilized before it was verified against nullptr. Check lines: 693, 699.