/*
 * Copyright 2006, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
 * Distributed under the terms of the MIT License.
 */
 
 
#include "FileTypes.h"
#include "PreferredAppMenu.h"
 
#include <Alert.h>
#include <AppFileInfo.h>
#include <Catalog.h>
#include <Locale.h>
#include <Menu.h>
#include <MenuItem.h>
#include <Mime.h>
#include <NodeInfo.h>
#include <String.h>
 
#include <stdio.h>
#include <strings.h>
 
 
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Preferred App Menu"
 
 
static int
compare_menu_items(const void* _a, const void* _b)
{
	BMenuItem* a = *(BMenuItem**)_a;
	BMenuItem* b = *(BMenuItem**)_b;
 
	return strcasecmp(a->Label(), b->Label());
}
 
 
static bool
is_application_in_message(BMessage& applications, const char* app)
{
	const char* signature;
	int32 i = 0;
	while (applications.FindString("applications", i++, &signature) == B_OK) {
		if (!strcasecmp(signature, app))
			return true;
	}
 
	return false;
}
 
 
static void
add_signature(BMenuItem* item, const char* signature)
{
	const char* subType = strchr(signature, '/');
	if (subType == NULL)
		return;
 
	char label[B_MIME_TYPE_LENGTH];
	snprintf(label, sizeof(label), "%s (%s)", item->Label(), subType + 1);
 
	item->SetLabel(label);
}
 
 
static BMenuItem*
create_application_item(const char* signature, uint32 what)
{
	char name[B_FILE_NAME_LENGTH];
 
	BMessage* message = new BMessage(what);
	message->AddString("signature", signature);
 
	BMimeType applicationType(signature);
	if (applicationType.GetShortDescription(name) == B_OK)
		return new BMenuItem(name, message);
 
	return new BMenuItem(signature, message);
}
 
 
//	#pragma mark - Public functions
 
 
void
update_preferred_app_menu(BMenu* menu, BMimeType* type, uint32 what,
	const char* preferredFrom)
{
	// clear menu (but leave the first entry, ie. "None")
 
	for (int32 i = menu->CountItems(); i-- > 1;) {
		delete menu->RemoveItem(i);
	}
 
	// fill it again
 
	menu->ItemAt(0)->SetMarked(true);
 
	BMessage applications;
	if (type == NULL || type->GetSupportingApps(&applications) != B_OK)
		return;
 
	char preferred[B_MIME_TYPE_LENGTH];
	if (type->GetPreferredApp(preferred) != B_OK)
		preferred[0] = '\0';
 
	int32 lastFullSupport;
	if (applications.FindInt32("be:sub", &lastFullSupport) != B_OK)
		lastFullSupport = -1;
 
	BList subList;
	BList superList;
 
	const char* signature;
	int32 i = 0;
	while (applications.FindString("applications", i, &signature) == B_OK) {
		BMenuItem* item = create_application_item(signature, what);
 
		if (i < lastFullSupport)
			subList.AddItem(item);
		else
			superList.AddItem(item);
 
		i++;
	}
 
	// sort lists
 
	subList.SortItems(compare_menu_items);
	superList.SortItems(compare_menu_items);
 
	// add lists to the menu
 
	if (subList.CountItems() != 0 || superList.CountItems() != 0)
		menu->AddSeparatorItem();
 
	for (int32 i = 0; i < subList.CountItems(); i++) {
		menu->AddItem((BMenuItem*)subList.ItemAt(i));
	}
 
	// Add type separator
	if (superList.CountItems() != 0 && subList.CountItems() != 0)
		menu->AddSeparatorItem();
 
	for (int32 i = 0; i < superList.CountItems(); i++) {
		menu->AddItem((BMenuItem*)superList.ItemAt(i));
	}
 
	// make items unique and select current choice
 
	bool lastItemSame = false;
	const char* lastSignature = NULL;
	BMenuItem* last = NULL;
	BMenuItem* select = NULL;
 
	for (int32 index = 0; index < menu->CountItems(); index++) {
		BMenuItem* item = menu->ItemAt(index);
		if (item == NULL)
			continue;
 
		if (item->Message() == NULL
			|| item->Message()->FindString("signature", &signature) != B_OK)
			continue;
 
		if ((preferredFrom == NULL && !strcasecmp(signature, preferred))
			|| (preferredFrom != NULL
				&& !strcasecmp(signature, preferredFrom))) {
			select = item;
		}
 
		if (last == NULL || strcmp(last->Label(), item->Label())) {
			if (lastItemSame)
				add_signature(last, lastSignature);
 
			lastItemSame = false;
			last = item;
			lastSignature = signature;
			continue;
		}
 
		lastItemSame = true;
		add_signature(last, lastSignature);
 
		last = item;
		lastSignature = signature;
	}
 
	if (lastItemSame)
		add_signature(last, lastSignature);
 
	if (select != NULL) {
		// We don't select the item earlier, so that the menu field can
		// pick up the signature as well as label.
		select->SetMarked(true);
	} else if ((preferredFrom == NULL && preferred[0])
		|| (preferredFrom != NULL && preferredFrom[0])) {
		// The preferred application is not an application that support
		// this file type!
		BMenuItem* item = create_application_item(preferredFrom
			? preferredFrom : preferred, what);
 
		menu->AddSeparatorItem();
		menu->AddItem(item);
		item->SetMarked(item);
	}
}
 
 
status_t
retrieve_preferred_app(BMessage* message, bool sameAs, const char* forType,
	BString& preferredApp)
{
	entry_ref ref;
	if (message == NULL || message->FindRef("refs", &ref) != B_OK)
		return B_BAD_VALUE;
 
	BFile file(&ref, B_READ_ONLY);
	status_t status = file.InitCheck();
 
	char preferred[B_MIME_TYPE_LENGTH];
 
	if (status == B_OK) {
		if (sameAs) {
			// get preferred app from file
			BNodeInfo nodeInfo(&file);
			status = nodeInfo.InitCheck();
			if (status == B_OK) {
				if (nodeInfo.GetPreferredApp(preferred) != B_OK)
					preferred[0] = '\0';
 
				if (!preferred[0]) {
					// get MIME type from file
					char type[B_MIME_TYPE_LENGTH];
					if (nodeInfo.GetType(type) == B_OK) {
						BMimeType mimeType(type);
						mimeType.GetPreferredApp(preferred);
					}
				}
			}
		} else {
			// get application signature
			BAppFileInfo appInfo(&file);
			status = appInfo.InitCheck();
 
			if (status == B_OK && appInfo.GetSignature(preferred) != B_OK)
				preferred[0] = '\0';
		}
	}
 
	if (status != B_OK) {
		error_alert(B_TRANSLATE("File could not be opened"),
			status, B_STOP_ALERT);
		return status;
	}
 
	if (!preferred[0]) {
		error_alert(sameAs
			? B_TRANSLATE("Could not retrieve preferred application of this "
				"file")
			: B_TRANSLATE("Could not retrieve application signature"));
		return B_ERROR;
	}
 
	// Check if the application chosen supports this type
 
	BMimeType mimeType(forType);
	bool found = false;
 
	BMessage applications;
	if (mimeType.GetSupportingApps(&applications) == B_OK
		&& is_application_in_message(applications, preferred))
		found = true;
 
	applications.MakeEmpty();
 
	if (!found && mimeType.GetWildcardApps(&applications) == B_OK
		&& is_application_in_message(applications, preferred))
		found = true;
 
	if (!found) {
		// warn user
		BMimeType appType(preferred);
		char description[B_MIME_TYPE_LENGTH];
		if (appType.GetShortDescription(description) != B_OK)
			description[0] = '\0';
 
		char warning[512];
		snprintf(warning, sizeof(warning), B_TRANSLATE("The application "
			"\"%s\" does not support this file type.\n"
			"Are you sure you want to set it anyway?"),
			description[0] ? description : preferred);
 
		BAlert* alert = new BAlert(B_TRANSLATE("FileTypes request"), warning,
			B_TRANSLATE("Set preferred application"), B_TRANSLATE("Cancel"),
			NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
		alert->SetShortcut(1, B_ESCAPE);
		if (alert->Go() == 1)
			return B_ERROR;
	}
 
	preferredApp = preferred;
	return B_OK;
}
 

V773 The function was exited without releasing the 'alert' pointer. A memory leak is possible.

V678 An object is used as an argument to its own method. Consider checking the first actual argument of the 'SetMarked' function.