⛏️ index : haiku.git

/*
 * Copyright 2006, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
 * Distributed under the terms of the MIT License.
 */


#include "FileTypes.h"
#include "IconMenuItem.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 IconMenuItem*
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 IconMenuItem(name, message, signature);

	return new IconMenuItem(signature, message, signature);
}


//	#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(true);
	}
}


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;
}