⛏️ index : haiku.git

/*
 * Copyright 2006-2010, Axel DΓΆrfler, axeld@pinc-software.de.
 * Distributed under the terms of the MIT License.
 */


#include "ExtensionWindow.h"
#include "FileTypes.h"
#include "FileTypesWindow.h"

#include <Button.h>
#include <Catalog.h>
#include <ControlLook.h>
#include <LayoutBuilder.h>
#include <Locale.h>
#include <MenuField.h>
#include <MenuItem.h>
#include <Mime.h>
#include <PopUpMenu.h>
#include <String.h>
#include <TextControl.h>

#include <strings.h>


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Extension Window"


const uint32 kMsgExtensionUpdated = 'exup';
const uint32 kMsgAccept = 'acpt';


static int
compare_extensions(const void* _a, const void* _b)
{
	const char* a = *(const char **)_a;
	const char* b = *(const char **)_b;

	int compare = strcasecmp(a, b);
	if (compare != 0)
		return compare;

	// sort lower case characters first
	return -strcmp(a, b);
}


//! newExtensionsList contains all the entries (char*) which are to be added.
status_t
merge_extensions(BMimeType& type, const BList& newExtensionsList,
	const char* removeExtension)
{
	BMessage extensions;
	status_t status = type.GetFileExtensions(&extensions);
	if (status < B_OK)
		return status;

	// replace the entry, and remove any equivalent entries
	BList mergedList;
	mergedList.AddList(&newExtensionsList);
	int32 originalCount = mergedList.CountItems();

	const char* extension;
	for (int32 i = 0; extensions.FindString("extensions", i,
			&extension) == B_OK; i++) {

		for (int32 j = originalCount; j-- > 0;) {
			if (!strcmp((const char*)mergedList.ItemAt(j), extension)) {
				// Do not add this old item again, since it's already
				// there.
				mergedList.RemoveItem(j);
				originalCount--;
			}
		}

		// The item will be added behind "originalCount", so we cannot
		// remove it accidentally in the next iterations, it's is added
		// for good.
		if (removeExtension == NULL || strcmp(removeExtension, extension))
			mergedList.AddItem((void *)extension);
	}

	mergedList.SortItems(compare_extensions);

	// Copy them to a new message (their memory is still part of the
	// original BMessage)
	BMessage newExtensions;
	for (int32 i = 0; i < mergedList.CountItems(); i++) {
		newExtensions.AddString("extensions",
			(const char*)mergedList.ItemAt(i));
	}

	return type.SetFileExtensions(&newExtensions);
}


status_t
replace_extension(BMimeType& type, const char* newExtension,
	const char* oldExtension)
{
	BList list;
	list.AddItem((void *)newExtension);

	return merge_extensions(type, list, oldExtension);
}


//	#pragma mark -


ExtensionWindow::ExtensionWindow(FileTypesWindow* target, BMimeType& type,
		const char* extension)
	: BWindow(BRect(100, 100, 350, 200), B_TRANSLATE("Extension"),
		B_MODAL_WINDOW_LOOK, B_MODAL_SUBSET_WINDOW_FEEL,
		B_NOT_ZOOMABLE | B_NOT_RESIZABLE
			| B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
	fTarget(target),
	fMimeType(type.Type()),
	fExtension(extension)
{
	fExtensionControl = new BTextControl(B_TRANSLATE("Extension:"),
		extension, NULL);
	fExtensionControl->SetModificationMessage(
		new BMessage(kMsgExtensionUpdated));
	fExtensionControl->SetAlignment(B_ALIGN_LEFT, B_ALIGN_LEFT);

	// filter out invalid characters that can't be part of an extension
	BTextView* textView = fExtensionControl->TextView();
	const char* disallowedCharacters = "/:";
	for (int32 i = 0; disallowedCharacters[i]; i++) {
		textView->DisallowChar(disallowedCharacters[i]);
	}

	fAcceptButton = new BButton(extension
		? B_TRANSLATE("Done") : B_TRANSLATE("Add"),
		new BMessage(kMsgAccept));
	fAcceptButton->SetEnabled(false);

	BButton* cancelButton = new BButton(B_TRANSLATE("Cancel"),
		new BMessage(B_QUIT_REQUESTED));

	float padding = be_control_look->DefaultItemSpacing();
	BLayoutBuilder::Grid<>(this, padding, padding)
		.SetInsets(padding, padding, padding, padding)
		.AddTextControl(fExtensionControl, 0, 0, B_ALIGN_HORIZONTAL_UNSET, 1, 2)
		.Add(BSpaceLayoutItem::CreateGlue(), 0, 1)
		.Add(cancelButton, 1, 1)
		.Add(fAcceptButton, 2, 1);

	// omit the leading dot
	if (fExtension.ByteAt(0) == '.')
		fExtension.Remove(0, 1);

	fAcceptButton->MakeDefault(true);
	fExtensionControl->MakeFocus(true);

	target->PlaceSubWindow(this);
	AddToSubset(target);
}


ExtensionWindow::~ExtensionWindow()
{
}


void
ExtensionWindow::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case kMsgExtensionUpdated:
		{
			bool enabled = fExtensionControl->Text() != NULL
				&& fExtensionControl->Text()[0] != '\0';
			if (enabled) {
				// There is some text, but we only accept it, if it
				// changed the previous extension
				enabled = strcmp(fExtensionControl->Text(), fExtension.String());
			}

			if (fAcceptButton->IsEnabled() != enabled)
				fAcceptButton->SetEnabled(enabled);
			break;
		}

		case kMsgAccept:
		{
			const char* newExtension = fExtensionControl->Text();
			// omit the leading dot
			if (newExtension[0] == '.')
				newExtension++;

			status_t status = replace_extension(fMimeType, newExtension,
				fExtension.String());
			if (status != B_OK)
				error_alert(B_TRANSLATE("Could not change file extensions"),
					status);

			PostMessage(B_QUIT_REQUESTED);
			break;
		}

		default:
			BWindow::MessageReceived(message);
			break;
	}
}