#include "Attributes.h"
#include "AutoLock.h"
#include "Commands.h"
#include "FSUtils.h"
#include "IconMenuItem.h"
#include "OpenWithWindow.h"
#include "MimeTypes.h"
#include "StopWatch.h"
#include "Tracker.h"
#include <map>
#include <Alert.h>
#include <Button.h>
#include <Catalog.h>
#include <ControlLook.h>
#include <Collator.h>
#include <GroupView.h>
#include <GridView.h>
#include <Locale.h>
#include <Mime.h>
#include <NodeInfo.h>
#include <NodeMonitor.h>
#include <Path.h>
#include <Roster.h>
#include <SpaceLayoutItem.h>
#include <Volume.h>
#include <VolumeRoster.h>
#include <stdlib.h>
#include <stdio.h>
#include <strings.h>
const char* kDefaultOpenWithTemplate = "OpenWithSettings";
const float kMaxMenuWidthFactor = 33.0f;
const int32 kDocumentKnobWidth = 16;
const int32 kOpenAndMakeDefault = 'OpDf';
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "OpenWithWindow"
OpenWithContainerWindow::OpenWithContainerWindow(BMessage* entriesToOpen,
LockingList<BWindow>* windowList)
:
BContainerWindow(windowList, 0),
fEntriesToOpen(entriesToOpen)
{
AutoLock<BWindow> lock(this);
BRect windowRect(85, 50, 718, 296);
MoveTo(windowRect.LeftTop());
ResizeTo(windowRect.Width(), windowRect.Height());
fButtonContainer = new BGroupView(B_HORIZONTAL, B_USE_ITEM_SPACING);
fButtonContainer->GroupLayout()->SetInsets(0, B_USE_ITEM_INSETS,
B_USE_ITEM_INSETS, 0);
fLaunchButton = new BButton("ok", B_TRANSLATE("Open"),
new BMessage(kDefaultButton));
fLaunchButton->MakeDefault(true);
fLaunchAndMakeDefaultButton = new BButton("make default",
B_TRANSLATE("Open and make preferred"),
new BMessage(kOpenAndMakeDefault));
fLaunchAndMakeDefaultButton->SetEnabled(false);
fCancelButton = new BButton("cancel", B_TRANSLATE("Cancel"),
new BMessage(kCancelButton));
fPoseView = NewPoseView(NULL, kListMode);
fBorderedView->GroupLayout()->AddView(fPoseView);
fPoseView->SetFlags(fPoseView->Flags() | B_NAVIGABLE);
fPoseView->SetPoseEditing(false);
if (CountRefs(fEntriesToOpen) == 1) {
entry_ref ref;
fEntriesToOpen->FindRef("refs", &ref);
BString buffer(B_TRANSLATE("Open %name with:"));
buffer.ReplaceFirst("%name", ref.name);
SetTitle(buffer.String());
} else {
SetTitle(B_TRANSLATE("Open selection with:"));
}
AddCommonFilter(new BMessageFilter(B_KEY_DOWN,
&OpenWithContainerWindow::KeyDownFilter));
}
OpenWithContainerWindow::~OpenWithContainerWindow()
{
delete fEntriesToOpen;
}
BPoseView*
OpenWithContainerWindow::NewPoseView(Model*, uint32)
{
return new OpenWithPoseView;
}
OpenWithPoseView*
OpenWithContainerWindow::PoseView() const
{
ASSERT(dynamic_cast<OpenWithPoseView*>(fPoseView) != NULL);
return static_cast<OpenWithPoseView*>(fPoseView);
}
const BMessage*
OpenWithContainerWindow::EntryList() const
{
return fEntriesToOpen;
}
void
OpenWithContainerWindow::OpenWithSelection()
{
int32 count = PoseView()->CountSelected();
ASSERT(count == 1);
if (count == 0)
return;
PoseView()->OpenSelection(PoseView()->SelectionList()->FirstItem(), 0);
}
static const entry_ref*
AddOneUniqueDocumentType(const entry_ref* ref, void* castToList)
{
BStringList* list = (BStringList*)castToList;
BEntry entry(ref, true);
char type[B_MIME_TYPE_LENGTH];
BFile file(&entry, O_RDONLY);
if (file.InitCheck() != B_OK)
return 0;
BNodeInfo info(&file);
if (info.GetType(type) != B_OK)
return 0;
if (list->HasString(type, true))
return 0;
list->Add(type);
return 0;
}
static bool
SetDefaultAppForOneType(const BString& element, void* castToEntryRef)
{
const entry_ref* appRef = (const entry_ref*)castToEntryRef;
BMimeType mime(element.String());
if (!mime.IsInstalled())
return 0;
BFile appFile(appRef, O_RDONLY);
if (appFile.InitCheck() != B_OK)
return 0;
char appSignature[B_MIME_TYPE_LENGTH];
if (GetAppSignatureFromAttr(&appFile, appSignature) != B_OK)
return 0;
if (mime.SetPreferredApp(appSignature) != B_OK)
return 0;
mime.SetTo(appSignature);
#if xDEBUG
status_t result =
#endif
mime.SetAppHint(appRef);
#if xDEBUG
BEntry debugEntry(appRef);
BPath debugPath;
debugEntry.GetPath(&debugPath);
PRINT(("setting %s, sig %s as default app for %s, result %s\n",
debugPath.Path(), appSignature, element->String(), strerror(result)));
#endif
return 0;
}
void
OpenWithContainerWindow::MakeDefaultAndOpen()
{
int32 count = PoseView()->CountSelected();
ASSERT(count == 1);
if (count == 0)
return;
BPose* selectedAppPose = PoseView()->SelectionList()->FirstItem();
ASSERT(selectedAppPose != NULL);
if (selectedAppPose == NULL)
return;
BStringList openedFileTypes(10);
EachEntryRef(EntryList(), AddOneUniqueDocumentType, &openedFileTypes, 100);
openedFileTypes.DoForEach(SetDefaultAppForOneType,
(void*)selectedAppPose->TargetModel()->EntryRef());
OpenWithSelection();
}
void
OpenWithContainerWindow::MessageReceived(BMessage* message)
{
switch (message->what) {
case kDefaultButton:
OpenWithSelection();
PostMessage(B_QUIT_REQUESTED);
return;
case kOpenAndMakeDefault:
MakeDefaultAndOpen();
PostMessage(B_QUIT_REQUESTED);
return;
case kCancelButton:
PostMessage(B_QUIT_REQUESTED);
return;
case B_OBSERVER_NOTICE_CHANGE:
return;
case kResizeToFit:
ResizeToFit();
break;
}
_inherited::MessageReceived(message);
}
filter_result
OpenWithContainerWindow::KeyDownFilter(BMessage* message, BHandler**,
BMessageFilter* filter)
{
uchar key;
if (message->FindInt8("byte", (int8*)&key) != B_OK)
return B_DISPATCH_MESSAGE;
int32 modifiers = message->GetInt32("modifiers", 0);
modifiers &= B_COMMAND_KEY | B_OPTION_KEY | B_SHIFT_KEY | B_CONTROL_KEY | B_MENU_KEY;
if (modifiers == 0 && key == B_ESCAPE) {
filter->Looper()->PostMessage(kCancelButton);
return B_SKIP_MESSAGE;
}
return B_DISPATCH_MESSAGE;
}
bool
OpenWithContainerWindow::ShouldAddMenus() const
{
return false;
}
void
OpenWithContainerWindow::ShowContextMenu(BPoint, const entry_ref*)
{
}
void
OpenWithContainerWindow::AddShortcuts()
{
AddShortcut('I', B_COMMAND_KEY, new BMessage(kGetInfo), PoseView());
AddShortcut('Y', B_COMMAND_KEY, new BMessage(kResizeToFit), PoseView());
}
void
OpenWithContainerWindow::NewAttributesMenu(BMenu* menu)
{
_inherited::NewAttributesMenu(menu);
BMessage* message = new BMessage(kAttributeItem);
message->AddString("attr_name", kAttrOpenWithRelation);
message->AddInt32("attr_type", B_STRING_TYPE);
message->AddInt32("attr_hash",
(int32)AttrHashString(kAttrOpenWithRelation, B_STRING_TYPE));
message->AddFloat("attr_width", 180);
message->AddInt32("attr_align", B_ALIGN_LEFT);
message->AddBool("attr_editable", false);
message->AddBool("attr_statfield", false);
BMenuItem* item = new BMenuItem(B_TRANSLATE("Relation"), message);
menu->AddItem(item);
message = new BMessage(kAttributeItem);
message->AddString("attr_name", kAttrAppVersion);
message->AddInt32("attr_type", B_STRING_TYPE);
message->AddInt32("attr_hash",
(int32)AttrHashString(kAttrAppVersion, B_STRING_TYPE));
message->AddFloat("attr_width", 70);
message->AddInt32("attr_align", B_ALIGN_LEFT);
message->AddBool("attr_editable", false);
message->AddBool("attr_statfield", false);
item = new BMenuItem(B_TRANSLATE("Version"), message);
menu->AddItem(item);
}
void
OpenWithContainerWindow::SaveState(bool)
{
BNode defaultingNode;
if (DefaultStateSourceNode(kDefaultOpenWithTemplate, &defaultingNode,
true, false)) {
AttributeStreamFileNode streamNodeDestination(&defaultingNode);
SaveWindowState(&streamNodeDestination);
fPoseView->SaveState(&streamNodeDestination);
}
}
void
OpenWithContainerWindow::SaveState(BMessage &message) const
{
_inherited::SaveState(message);
}
void
OpenWithContainerWindow::Init(const BMessage* message)
{
_inherited::Init(message);
}
void
OpenWithContainerWindow::InitLayout()
{
_inherited::InitLayout();
fMenuContainer->RemoveSelf();
fRootLayout->SetInsets(B_USE_ITEM_INSETS);
fPoseContainer->GridLayout()->SetInsets(0);
fVScrollBarContainer->GroupLayout()->SetInsets(-1, 0, 0, 0);
fRootLayout->AddView(fButtonContainer);
fButtonContainer->GroupLayout()->AddItem(BSpaceLayoutItem::CreateGlue());
fButtonContainer->GroupLayout()->AddView(fCancelButton);
fButtonContainer->GroupLayout()->AddView(fLaunchAndMakeDefaultButton);
fButtonContainer->GroupLayout()->AddView(fLaunchButton);
}
void
OpenWithContainerWindow::RestoreState()
{
BNode defaultingNode;
if (DefaultStateSourceNode(kDefaultOpenWithTemplate, &defaultingNode,
false)) {
AttributeStreamFileNode streamNodeSource(&defaultingNode);
RestoreWindowState(&streamNodeSource);
fPoseView->Init(&streamNodeSource);
} else {
RestoreWindowState(NULL);
fPoseView->Init(NULL);
}
InitLayout();
}
void
OpenWithContainerWindow::RestoreState(const BMessage &message)
{
_inherited::RestoreState(message);
}
void
OpenWithContainerWindow::RestoreWindowState(AttributeStreamNode* node)
{
if (node == NULL)
return;
const char* rectAttributeName = kAttrWindowFrame;
BRect frame(Frame());
if (node->Read(rectAttributeName, 0, B_RECT_TYPE, sizeof(BRect), &frame)
== sizeof(BRect)) {
MoveTo(frame.LeftTop());
ResizeTo(frame.Width(), frame.Height());
}
}
void
OpenWithContainerWindow::RestoreWindowState(const BMessage &message)
{
_inherited::RestoreWindowState(message);
}
bool
OpenWithContainerWindow::NeedsDefaultStateSetup()
{
return true;
}
void
OpenWithContainerWindow::SetupDefaultState()
{
}
bool
OpenWithContainerWindow::IsShowing(const node_ref*) const
{
return false;
}
bool
OpenWithContainerWindow::IsShowing(const entry_ref*) const
{
return false;
}
void
OpenWithContainerWindow::SetCanSetAppAsDefault(bool on)
{
fLaunchAndMakeDefaultButton->SetEnabled(on);
}
void
OpenWithContainerWindow::SetCanOpen(bool on)
{
fLaunchButton->SetEnabled(on);
}
OpenWithPoseView::OpenWithPoseView()
:
BPoseView(new Model(), kListMode),
fHaveCommonPreferredApp(false),
fIterator(NULL),
fRefFilter(NULL)
{
fSavePoseLocations = false;
fMultipleSelection = false;
fDragEnabled = false;
}
OpenWithPoseView::~OpenWithPoseView()
{
delete fRefFilter;
delete fIterator;
}
OpenWithContainerWindow*
OpenWithPoseView::ContainerWindow() const
{
OpenWithContainerWindow* window = dynamic_cast<OpenWithContainerWindow*>(Window());
ASSERT(window != NULL);
return window;
}
void
OpenWithPoseView::AdoptSystemColors()
{
SetViewUIColor(B_TOOL_TIP_BACKGROUND_COLOR);
SetLowUIColor(B_TOOL_TIP_BACKGROUND_COLOR);
SetHighUIColor(B_TOOL_TIP_TEXT_COLOR);
}
bool
OpenWithPoseView::HasSystemColors() const
{
float tint = B_NO_TINT;
return ViewUIColor(&tint) == B_TOOL_TIP_BACKGROUND_COLOR && tint == B_NO_TINT
&& LowUIColor(&tint) == B_TOOL_TIP_BACKGROUND_COLOR && tint == B_NO_TINT
&& HighUIColor(&tint) == B_TOOL_TIP_TEXT_COLOR && tint == B_NO_TINT;
}
bool
OpenWithPoseView::CanHandleDragSelection(const Model*, const BMessage*, bool)
{
return false;
}
static void
AddSupportingAppForTypeToQuery(SearchForSignatureEntryList* queryIterator,
const char* type)
{
BMimeType mime(type);
if (!mime.IsInstalled())
return;
BMessage message;
mime.GetSupportingApps(&message);
const char* signature;
for (int32 index = 0; message.FindString("applications", index,
&signature) == B_OK; index++) {
queryIterator->PushUniqueSignature(signature);
}
}
static const entry_ref*
AddOneRefSignatures(const entry_ref* ref, void* castToIterator)
{
SearchForSignatureEntryList* queryIterator =
(SearchForSignatureEntryList*)castToIterator;
Model model(ref, true, true);
if (model.InitCheck() != B_OK)
return NULL;
model.SniffMimeIfNeeded();
BString mimeType(model.MimeType());
if (!mimeType.Length() || mimeType.ICompare(B_FILE_MIMETYPE) == 0)
model.Mimeset(true);
entry_ref preferredRef;
if (model.PreferredAppSignature()[0]) {
if (be_roster->FindApp(model.PreferredAppSignature(), &preferredRef)
== B_OK) {
queryIterator->PushUniqueSignature(model.PreferredAppSignature());
queryIterator->TrySettingPreferredAppForFile(&preferredRef);
}
}
mimeType = model.MimeType();
mimeType.ToLower();
if (mimeType.Length() && mimeType.ICompare(B_FILE_MIMETYPE) != 0)
queryIterator->NonGenericFileFound();
AddSupportingAppForTypeToQuery(queryIterator, mimeType.String());
if (be_roster->FindApp(mimeType.String(), &preferredRef) == B_OK)
queryIterator->TrySettingPreferredApp(&preferredRef);
return NULL;
}
EntryListBase*
OpenWithPoseView::InitDirentIterator(const entry_ref*)
{
OpenWithContainerWindow* window = ContainerWindow();
const BMessage* entryList = window->EntryList();
fIterator = new SearchForSignatureEntryList(true);
EachEntryRef(entryList, AddOneRefSignatures, fIterator, 100);
AddSupportingAppForTypeToQuery(fIterator, B_FILE_MIMETYPE);
fHaveCommonPreferredApp = fIterator->GetPreferredApp(&fPreferredRef);
if (fIterator->Rewind() != B_OK) {
delete fIterator;
fIterator = NULL;
HideBarberPole();
return NULL;
}
fRefFilter = new OpenWithRefFilter(fIterator, entryList,
fHaveCommonPreferredApp ? &fPreferredRef : 0);
SetRefFilter(fRefFilter);
return fIterator;
}
void
OpenWithPoseView::ReturnDirentIterator(EntryListBase* iterator)
{
}
uint32
OpenWithPoseView::WatchNewNodeMask()
{
return B_WATCH_STAT | B_WATCH_INTERIM_STAT | B_WATCH_ATTR;
}
void
OpenWithPoseView::OpenSelection(BPose* pose, int32*)
{
OpenWithContainerWindow* window = ContainerWindow();
int32 count = CountSelected();
if (count == 0)
return;
if (pose == NULL)
pose = SelectionList()->FirstItem();
ASSERT(pose != NULL);
BEntry entry(pose->TargetModel()->EntryRef());
if (entry.InitCheck() != B_OK) {
BString errorString(
B_TRANSLATE("Could not find application \"%appname\""));
errorString.ReplaceFirst("%appname", pose->TargetModel()->Name());
BAlert* alert = new BAlert("", errorString.String(), B_TRANSLATE("OK"),
0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go();
return;
}
if (OpenWithRelation(pose->TargetModel()) == kNoRelation) {
if (!fIterator->GenericFilesOnly()) {
BString warning(B_TRANSLATE(
"The application \"%appname\" does not support the type of "
"document you are about to open.\nAre you sure you want to "
"proceed?\n\nIf you know that the application supports the "
"document type, you should contact the publisher of the "
"application and ask them to update their application to list "
"the type of your document as supported."));
warning.ReplaceFirst("%appname", pose->TargetModel()->Name());
BAlert* alert = new BAlert("", warning.String(),
B_TRANSLATE("Cancel"), B_TRANSLATE("Open"), 0, B_WIDTH_AS_USUAL,
B_WARNING_ALERT);
alert->SetShortcut(0, B_ESCAPE);
if (alert->Go() == 0)
return;
}
}
BMessage message(*window->EntryList());
message.RemoveName("launchUsingSelector");
message.AddRef("handler", pose->TargetModel()->EntryRef());
ASSERT(fSelectionHandler != NULL);
if (fSelectionHandler != NULL)
fSelectionHandler->PostMessage(&message);
window->PostMessage(B_QUIT_REQUESTED);
}
void
OpenWithPoseView::Pulse()
{
OpenWithContainerWindow* window = ContainerWindow();
if (!CountSelected()) {
window->SetCanSetAppAsDefault(false);
window->SetCanOpen(false);
_inherited::Pulse();
return;
}
Model* firstSelected = SelectionList()->FirstItem()->TargetModel();
if (OpenWithRelation(firstSelected) == kNoRelation) {
window->SetCanSetAppAsDefault(false);
window->SetCanOpen(true);
_inherited::Pulse();
return;
}
window->SetCanOpen(true);
if (!fHaveCommonPreferredApp) {
window->SetCanSetAppAsDefault(true);
_inherited::Pulse();
return;
}
ASSERT(CountSelected() == 1);
window->SetCanSetAppAsDefault((*SelectionList()->FirstItem()->
TargetModel()->EntryRef()) != fPreferredRef);
_inherited::Pulse();
}
void
OpenWithPoseView::SetupDefaultColumnsIfNeeded()
{
if (CountColumns() != 0)
return;
BColumn* nameColumn = new BColumn(B_TRANSLATE("Name"), 125,
B_ALIGN_LEFT, kAttrStatName, B_STRING_TYPE, true, true);
AddColumn(nameColumn);
BColumn* relationColumn = new BColumn(B_TRANSLATE("Relation"), 100,
B_ALIGN_LEFT, kAttrOpenWithRelation, B_STRING_TYPE, false, false);
AddColumn(relationColumn);
AddColumn(new BColumn(B_TRANSLATE("Location"), 225,
B_ALIGN_LEFT, kAttrPath, B_STRING_TYPE, true, false));
AddColumn(new BColumn(B_TRANSLATE("Version"), 70,
B_ALIGN_LEFT, kAttrAppVersion, B_STRING_TYPE, false, false));
SetPrimarySort(relationColumn->AttrHash());
SetSecondarySort(nameColumn->AttrHash());
}
bool
OpenWithPoseView::AddPosesThreadValid(const entry_ref*) const
{
return true;
}
void
OpenWithPoseView::CreatePoses(Model** models, PoseInfo* poseInfoArray,
int32 count, BPose** resultingPoses, bool insertionSort,
int32* lastPoseIndexPtr, BRect* boundsPtr, bool forceDraw)
{
_inherited::CreatePoses(models, poseInfoArray, count, resultingPoses,
insertionSort, lastPoseIndexPtr, boundsPtr, forceDraw);
if (resultingPoses != NULL) {
for (int32 index = 0; index < count; index++) {
if (resultingPoses[index] && fHaveCommonPreferredApp
&& *(models[index]->EntryRef()) == fPreferredRef) {
SelectPose(resultingPoses[index],
IndexOfPose(resultingPoses[index]));
}
}
}
}
void
OpenWithPoseView::KeyDown(const char* bytes, int32 count)
{
if (bytes[0] == B_TAB) {
BView::KeyDown(bytes, count);
} else
_inherited::KeyDown(bytes, count);
}
void
OpenWithPoseView::SaveState(AttributeStreamNode* node)
{
_inherited::SaveState(node);
}
void
OpenWithPoseView::RestoreState(AttributeStreamNode* node)
{
_inherited::RestoreState(node);
fViewState->SetViewMode(kListMode);
}
void
OpenWithPoseView::SaveState(BMessage &message) const
{
_inherited::SaveState(message);
}
void
OpenWithPoseView::RestoreState(const BMessage &message)
{
_inherited::RestoreState(message);
fViewState->SetViewMode(kListMode);
}
void
OpenWithPoseView::SavePoseLocations(BRect*)
{
}
void
OpenWithPoseView::MoveSelectionToTrash(bool)
{
}
void
OpenWithPoseView::MoveSelectionTo(BPoint, BPoint, BContainerWindow*)
{
}
void
OpenWithPoseView::MoveSelectionInto(Model*, BContainerWindow*, bool, bool)
{
}
bool
OpenWithPoseView::Represents(const node_ref*) const
{
return false;
}
bool
OpenWithPoseView::Represents(const entry_ref*) const
{
return false;
}
bool
OpenWithPoseView::HandleMessageDropped(BMessage* DEBUG_ONLY(message))
{
#if DEBUG
const rgb_color* color;
ssize_t size;
if (message->FindData("RGBColor", 'RGBC', (const void**)&color, &size)
== B_OK) {
SetViewColor(*color);
SetLowColor(*color);
Invalidate();
return true;
}
#endif
return false;
}
int32
OpenWithPoseView::OpenWithRelation(const Model* model) const
{
OpenWithContainerWindow* window = ContainerWindow();
return SearchForSignatureEntryList::Relation(window->EntryList(),
model, fHaveCommonPreferredApp ? &fPreferredRef : 0, 0);
}
void
OpenWithPoseView::OpenWithRelationDescription(const Model* model,
BString* description) const
{
OpenWithContainerWindow* window = ContainerWindow();
SearchForSignatureEntryList::RelationDescription(window->EntryList(),
model, description, fHaveCommonPreferredApp ? &fPreferredRef : 0, 0);
}
OpenWithRefFilter::OpenWithRefFilter(SearchForSignatureEntryList* iterator,
const BMessage *entryList, entry_ref* preferredRef)
:
fIterator(iterator),
fEntryList(entryList),
fPreferredRef(preferredRef)
{
}
bool
OpenWithRefFilter::Filter(const entry_ref* ref, BNode* node, stat_beos* st,
const char* filetype)
{
Model *model = new Model(ref, true, true);
bool canOpen = fIterator->CanOpenWithFilter(model, fEntryList,
fPreferredRef);
delete model;
return canOpen;
}
RelationCachingModelProxy::RelationCachingModelProxy(Model* model)
:
fModel(model),
fRelation(kUnknownRelation)
{
}
RelationCachingModelProxy::~RelationCachingModelProxy()
{
delete fModel;
}
int32
RelationCachingModelProxy::Relation(SearchForSignatureEntryList* iterator,
BMessage* entries) const
{
if (fRelation == kUnknownRelation)
fRelation = iterator->Relation(entries, fModel);
return fRelation;
}
OpenWithMenu::OpenWithMenu(const char* label, const BMessage* entriesToOpen,
BWindow* parentWindow, BHandler* target)
:
BSlowMenu(label),
fEntriesToOpen(*entriesToOpen),
target(target),
fIterator(NULL),
fSupportingAppList(NULL),
fParentWindow(parentWindow)
{
InitIconPreloader();
SetTriggersEnabled(false);
}
OpenWithMenu::OpenWithMenu(const char* label, const BMessage* entriesToOpen,
BWindow* parentWindow, const BMessenger &messenger)
:
BSlowMenu(label),
fEntriesToOpen(*entriesToOpen),
target(NULL),
fMessenger(messenger),
fIterator(NULL),
fSupportingAppList(NULL),
fParentWindow(parentWindow)
{
InitIconPreloader();
SetTriggersEnabled(false);
}
namespace BPrivate {
int
SortByRelation(const RelationCachingModelProxy* proxy1,
const RelationCachingModelProxy* proxy2, void* castToMenu)
{
OpenWithMenu* menu = (OpenWithMenu*)castToMenu;
int32 relation1 = proxy1->Relation(menu->fIterator, &menu->fEntriesToOpen);
int32 relation2 = proxy2->Relation(menu->fIterator, &menu->fEntriesToOpen);
if (relation1 < relation2)
return 1;
else if (relation1 > relation2)
return -1;
return 0;
}
int
SortByName(const RelationCachingModelProxy* proxy1,
const RelationCachingModelProxy* proxy2, void* castToMenu)
{
BCollator collator;
BLocale::Default()->GetCollator(&collator);
int nameDiff = collator.Compare(proxy1->fModel->Name(),
proxy2->fModel->Name());
if (nameDiff < 0)
return -1;
else if (nameDiff > 0)
return 1;
BVolume volume1(proxy1->fModel->NodeRef()->device);
BVolume volume2(proxy2->fModel->NodeRef()->device);
char volumeName1[B_FILE_NAME_LENGTH];
char volumeName2[B_FILE_NAME_LENGTH];
if (volume1.InitCheck() == B_OK && volume2.InitCheck() == B_OK
&& volume1.GetName(volumeName1) == B_OK
&& volume2.GetName(volumeName2) == B_OK) {
int volumeNameDiff = collator.Compare(volumeName1, volumeName2);
if (volumeNameDiff < 0)
return -1;
else if (volumeNameDiff > 0)
return 1;
}
return 0;
}
int
SortByRelationAndName(const RelationCachingModelProxy* proxy1,
const RelationCachingModelProxy* proxy2, void* castToMenu)
{
int relationDiff = SortByRelation(proxy1, proxy2, castToMenu);
if (relationDiff != 0)
return relationDiff;
int nameDiff = SortByName(proxy1, proxy2, castToMenu);
if (nameDiff != 0)
return nameDiff;
return 0;
}
}
bool
OpenWithMenu::StartBuildingItemList()
{
fIterator = new SearchForSignatureEntryList(false);
EachEntryRef(&fEntriesToOpen, AddOneRefSignatures, fIterator, 100);
AddSupportingAppForTypeToQuery(fIterator, B_FILE_MIMETYPE);
fHaveCommonPreferredApp = fIterator->GetPreferredApp(&fPreferredRef);
status_t error = fIterator->Rewind();
if (error != B_OK) {
PRINT(("failed to initialize iterator %s\n", strerror(error)));
return false;
}
fSupportingAppList = new BObjectList<RelationCachingModelProxy, true>(20);
return true;
}
bool
OpenWithMenu::AddNextItem()
{
BEntry entry;
if (fIterator->GetNextEntry(&entry) != B_OK)
return false;
Model* model = new Model(&entry, true);
if (model->InitCheck() != B_OK
|| !fIterator->CanOpenWithFilter(model, &fEntriesToOpen,
(fHaveCommonPreferredApp ? &fPreferredRef : 0))) {
delete model;
} else
fSupportingAppList->AddItem(new RelationCachingModelProxy(model));
return true;
}
void
OpenWithMenu::DoneBuildingItemList()
{
fSupportingAppList->SortItems(SortByName, this);
int32 count = fSupportingAppList->CountItems();
bool nameRepeats[count];
bool volumeRepeats[count];
memset(nameRepeats, 0, sizeof(bool) * count);
memset(volumeRepeats, 0, sizeof(bool) * count);
std::map<RelationCachingModelProxy*, BString> labels;
BCollator collator;
BLocale::Default()->GetCollator(&collator);
for (int32 index = 0; index < count - 1; index++) {
Model* model = fSupportingAppList->ItemAt(index)->fModel;
Model* next = fSupportingAppList->ItemAt(index + 1)->fModel;
if (collator.Compare(model->Name(), next->Name()) == 0) {
nameRepeats[index] = nameRepeats[index + 1] = true;
BVolume volume(model->NodeRef()->device);
BVolume nextVol(next->NodeRef()->device);
char volumeName[B_FILE_NAME_LENGTH];
char nextVolName[B_FILE_NAME_LENGTH];
if (volume.InitCheck() == B_OK && nextVol.InitCheck() == B_OK
&& volume.GetName(volumeName) == B_OK
&& nextVol.GetName(nextVolName) == B_OK
&& collator.Compare(volumeName, nextVolName) == 0) {
volumeRepeats[index] = volumeRepeats[index + 1] = true;
}
}
}
BFont font;
GetFont(&font);
for (int32 index = 0; index < count; index++) {
RelationCachingModelProxy* proxy
= fSupportingAppList->ItemAt(index);
Model* model = proxy->fModel;
BString label;
if (!nameRepeats[index]) {
label = model->Name();
} else {
if (!volumeRepeats[index]) {
BVolume volume(model->NodeRef()->device);
if (volume.InitCheck() == B_OK) {
char volumeName[B_FILE_NAME_LENGTH];
if (volume.GetName(volumeName) == B_OK)
label << "[" << volumeName << "] ";
}
label << model->Name();
} else {
BPath path;
BEntry entry(model->EntryRef());
if (entry.GetPath(&path) != B_OK) {
PRINT(("stale entry ref %s\n", model->Name()));
continue;
}
label = path.Path();
}
font.TruncateString(&label, B_TRUNCATE_MIDDLE,
kMaxMenuWidthFactor * be_control_look->DefaultLabelSpacing());
}
#if DEBUG
BString relationDescription;
fIterator->RelationDescription(&fEntriesToOpen, model,
&relationDescription);
label << " (" << relationDescription << ")";
#endif
labels[proxy] = label;
}
fSupportingAppList->SortItems(SortByRelationAndName, this);
int32 lastRelation = -1;
for (int32 index = 0; index < count; index++) {
RelationCachingModelProxy* proxy = fSupportingAppList->ItemAt(index);
Model* model = proxy->fModel;
int32 relation = proxy->Relation(fIterator, &fEntriesToOpen);
if (lastRelation != -1 && relation != lastRelation)
AddSeparatorItem();
lastRelation = relation;
BMessage* message = new BMessage(fEntriesToOpen);
message->AddRef("handler", model->EntryRef());
BContainerWindow* window
= dynamic_cast<BContainerWindow*>(fParentWindow);
if (window != NULL) {
message->AddData("nodeRefsToClose", B_RAW_TYPE,
window->TargetModel()->NodeRef(), sizeof(node_ref));
}
ModelMenuItem* item = new ModelMenuItem(model,
labels.find(proxy)->second.String(), message);
AddItem(item);
if (fHaveCommonPreferredApp && *(model->EntryRef()) == fPreferredRef) {
item->SetMarked(true);
}
}
if (target != NULL)
SetTargetForItems(target);
else
SetTargetForItems(fMessenger);
if (CountItems() == 0) {
BMenuItem* item = new BMenuItem(B_TRANSLATE("no supporting apps"), 0);
item->SetEnabled(false);
AddItem(item);
}
}
void
OpenWithMenu::ClearMenuBuildingState()
{
delete fIterator;
fIterator = NULL;
delete fSupportingAppList;
fSupportingAppList = NULL;
}
SearchForSignatureEntryList::SearchForSignatureEntryList(bool canAddAllApps)
:
fIteratorList(NULL),
fSignatures(20),
fPreferredAppCount(0),
fPreferredAppForFileCount(0),
fGenericFilesOnly(true),
fCanAddAllApps(canAddAllApps),
fFoundOneNonSuperHandler(false)
{
}
SearchForSignatureEntryList::~SearchForSignatureEntryList()
{
delete fIteratorList;
}
void
SearchForSignatureEntryList::PushUniqueSignature(const char* str)
{
if (fSignatures.HasString(str, true))
return;
fSignatures.Add(str);
}
status_t
SearchForSignatureEntryList::GetNextEntry(BEntry* entry, bool)
{
return fIteratorList->GetNextEntry(entry);
}
status_t
SearchForSignatureEntryList::GetNextRef(entry_ref* ref)
{
return fIteratorList->GetNextRef(ref);
}
int32
SearchForSignatureEntryList::GetNextDirents(struct dirent* buffer,
size_t length, int32 count)
{
return fIteratorList->GetNextDirents(buffer, length, count);
}
struct AddOneTermParams {
BString* result;
bool first;
};
static bool
AddOnePredicateTerm(const BString& item, void* castToParams)
{
AddOneTermParams* params = (AddOneTermParams*)castToParams;
if (!params->first)
(*params->result) << " || ";
(*params->result) << kAttrAppSignature << " = " << item.String();
params->first = false;
return false;
}
status_t
SearchForSignatureEntryList::Rewind()
{
if (fIteratorList)
return fIteratorList->Rewind();
if (!fSignatures.CountStrings())
return ENOENT;
fIteratorList = new CachedEntryIteratorList(false);
BString predicateString;
AddOneTermParams params;
params.result = &predicateString;
params.first = true;
fSignatures.DoForEach(AddOnePredicateTerm, ¶ms);
ASSERT(predicateString.Length());
fIteratorList->AddItem(new TWalkerWrapper(
new BTrackerPrivate::TQueryWalker(predicateString.String())));
fIteratorList->AddItem(new ConditionalAllAppsIterator(this));
return fIteratorList->Rewind();
}
int32
SearchForSignatureEntryList::CountEntries()
{
return 0;
}
bool
SearchForSignatureEntryList::GetPreferredApp(entry_ref* ref) const
{
if (fPreferredAppCount == 1)
*ref = fPreferredRef;
return fPreferredAppCount == 1;
}
void
SearchForSignatureEntryList::TrySettingPreferredApp(const entry_ref* ref)
{
if (!fPreferredAppCount) {
fPreferredRef = *ref;
fPreferredAppCount++;
} else if (fPreferredRef != *ref) {
fPreferredAppCount++;
}
}
void
SearchForSignatureEntryList::TrySettingPreferredAppForFile(const entry_ref* ref)
{
if (!fPreferredAppForFileCount) {
fPreferredRefForFile = *ref;
fPreferredAppForFileCount++;
} else if (fPreferredRefForFile != *ref) {
fPreferredAppForFileCount++;
}
}
void
SearchForSignatureEntryList::NonGenericFileFound()
{
fGenericFilesOnly = false;
}
bool
SearchForSignatureEntryList::GenericFilesOnly() const
{
return fGenericFilesOnly;
}
bool
SearchForSignatureEntryList::ShowAllApplications() const
{
return fCanAddAllApps && !fFoundOneNonSuperHandler;
}
int32
SearchForSignatureEntryList::Relation(const Model* nodeModel,
const Model* applicationModel)
{
int32 supportsMimeType = applicationModel->SupportsMimeType(
nodeModel->MimeType(), 0, true);
switch (supportsMimeType) {
case kDoesNotSupportType:
return kNoRelation;
case kSuperhandlerModel:
return kSuperhandler;
case kModelSupportsSupertype:
return kSupportsSupertype;
case kModelSupportsType:
return kSupportsType;
}
TRESPASS();
return kNoRelation;
}
int32
SearchForSignatureEntryList::Relation(const BMessage* entriesToOpen,
const Model* model) const
{
return Relation(entriesToOpen, model,
fPreferredAppCount == 1 ? &fPreferredRef : 0,
fPreferredAppForFileCount == 1 ? &fPreferredRefForFile : 0);
}
void
SearchForSignatureEntryList::RelationDescription(const BMessage* entriesToOpen,
const Model* model, BString* description) const
{
RelationDescription(entriesToOpen, model, description,
fPreferredAppCount == 1 ? &fPreferredRef : 0,
fPreferredAppForFileCount == 1 ? &fPreferredRefForFile : 0);
}
int32
SearchForSignatureEntryList::Relation(const BMessage* entriesToOpen,
const Model* applicationModel, const entry_ref* preferredApp,
const entry_ref* preferredAppForFile)
{
for (int32 index = 0; ; index++) {
entry_ref ref;
if (entriesToOpen->FindRef("refs", index, &ref) != B_OK)
break;
Model model(&ref, true, true);
if (model.InitCheck())
continue;
model.SniffMimeIfNeeded();
int32 result = Relation(&model, applicationModel);
if (result != kNoRelation) {
if (preferredAppForFile
&& *applicationModel->EntryRef() == *preferredAppForFile) {
return kPreferredForFile;
}
if (result == kSupportsType && preferredApp
&& *applicationModel->EntryRef() == *preferredApp) {
return kPreferredForType;
}
return result;
}
}
return kNoRelation;
}
void
SearchForSignatureEntryList::RelationDescription(const BMessage* entriesToOpen,
const Model* applicationModel, BString* description,
const entry_ref* preferredApp, const entry_ref* preferredAppForFile)
{
for (int32 index = 0; ;index++) {
entry_ref ref;
if (entriesToOpen->FindRef("refs", index, &ref) != B_OK)
break;
if (preferredAppForFile && ref == *preferredAppForFile) {
description->SetTo(B_TRANSLATE("Preferred for file"));
return;
}
Model model(&ref, true, true);
if (model.InitCheck())
continue;
model.SniffMimeIfNeeded();
BMimeType mimeType;
int32 result = Relation(&model, applicationModel);
switch (result) {
case kDoesNotSupportType:
continue;
case kSuperhandler:
description->SetTo(B_TRANSLATE("Handles any file"));
return;
case kSupportsSupertype:
{
mimeType.SetTo(model.MimeType());
char* type = (char*)mimeType.Type();
char* tmp = strchr(type, '/');
if (tmp != NULL)
*tmp = '\0';
description->SetTo(B_TRANSLATE("Handles any %type"));
description->ReplaceFirst("%type", type);
return;
}
case kSupportsType:
{
mimeType.SetTo(model.MimeType());
if (preferredApp != NULL
&& *applicationModel->EntryRef() == *preferredApp) {
description->SetTo(B_TRANSLATE("Preferred for %type"));
} else
description->SetTo(B_TRANSLATE("Handles %type"));
char shortDescription[256];
if (mimeType.GetShortDescription(shortDescription) == B_OK)
description->ReplaceFirst("%type", shortDescription);
else
description->ReplaceFirst("%type", mimeType.Type());
return;
}
}
}
description->SetTo(B_TRANSLATE("Does not handle file"));
}
bool
SearchForSignatureEntryList::CanOpenWithFilter(const Model* appModel,
const BMessage* entriesToOpen, const entry_ref* preferredApp)
{
ThrowOnAssert(appModel != NULL);
if (!appModel->IsExecutable() || !appModel->Node()) {
#if xDEBUG
BPath path;
BEntry entry(appModel->EntryRef());
entry.GetPath(&path);
PRINT(("filtering out %s- not executable \n", path.Path()));
#endif
return false;
}
if (strcasecmp(appModel->MimeType(), B_APP_MIME_TYPE) != 0) {
return false;
}
BFile* file = dynamic_cast<BFile*>(appModel->Node());
ASSERT(file != NULL);
char signature[B_MIME_TYPE_LENGTH];
if (GetAppSignatureFromAttr(file, signature) == B_OK
&& strcasecmp(signature, kTrackerSignature) == 0) {
app_info trackerInfo;
if (*appModel->EntryRef() != trackerInfo.ref) {
#if xDEBUG
BPath path1;
BPath path2;
BEntry entry(appModel->EntryRef());
entry.GetPath(&path1);
BEntry entry2(&trackerInfo.ref);
entry2.GetPath(&path2);
PRINT(("filtering out %s, sig %s, active Tracker at %s, "
"result %s, refName %s\n",
path1.Path(), signature, path2.Path(),
strerror(be_roster->GetActiveAppInfo(&trackerInfo)),
trackerInfo.ref.name));
#endif
return false;
}
}
if (FSInTrashDir(appModel->EntryRef()))
return false;
if (ShowAllApplications()) {
uint32 flags;
BAppFileInfo appFileInfo(dynamic_cast<BFile*>(appModel->Node()));
if (appFileInfo.GetAppFlags(&flags) != B_OK)
return false;
if ((flags & B_BACKGROUND_APP) || (flags & B_ARGV_ONLY))
return false;
if (!signature[0])
return false;
}
int32 relation = Relation(entriesToOpen, appModel, preferredApp, 0);
if (relation == kNoRelation && !ShowAllApplications()) {
#if xDEBUG
BPath path;
BEntry entry(appModel->EntryRef());
entry.GetPath(&path);
PRINT(("filtering out %s, does not handle any of opened files\n",
path.Path()));
#endif
return false;
}
if (relation != kNoRelation && relation != kSuperhandler
&& !fGenericFilesOnly) {
fFoundOneNonSuperHandler = true;
}
return true;
}
ConditionalAllAppsIterator::ConditionalAllAppsIterator(
SearchForSignatureEntryList* parent)
:
fParent(parent),
fWalker(NULL)
{
}
void
ConditionalAllAppsIterator::Instantiate()
{
if (fWalker != NULL)
return;
BString lookForAppsPredicate;
lookForAppsPredicate << "(" << kAttrAppSignature << " = \"*\" ) && ( "
<< kAttrMIMEType << " = " << B_APP_MIME_TYPE << " ) ";
fWalker
= new BTrackerPrivate::TQueryWalker(lookForAppsPredicate.String());
}
ConditionalAllAppsIterator::~ConditionalAllAppsIterator()
{
delete fWalker;
}
status_t
ConditionalAllAppsIterator::GetNextEntry(BEntry* entry, bool traverse)
{
if (!Iterate())
return B_ENTRY_NOT_FOUND;
Instantiate();
return fWalker->GetNextEntry(entry, traverse);
}
status_t
ConditionalAllAppsIterator::GetNextRef(entry_ref* ref)
{
if (!Iterate())
return B_ENTRY_NOT_FOUND;
Instantiate();
return fWalker->GetNextRef(ref);
}
int32
ConditionalAllAppsIterator::GetNextDirents(struct dirent* buffer,
size_t length, int32 count)
{
if (!Iterate())
return 0;
Instantiate();
return fWalker->GetNextDirents(buffer, length, count);
}
status_t
ConditionalAllAppsIterator::Rewind()
{
if (!Iterate())
return B_OK;
Instantiate();
return fWalker->Rewind();
}
int32
ConditionalAllAppsIterator::CountEntries()
{
if (!Iterate())
return 0;
Instantiate();
return fWalker->CountEntries();
}
bool
ConditionalAllAppsIterator::Iterate() const
{
return fParent->ShowAllApplications();
}